001 package com.nimbusds.jose; 002 003 004 import java.text.ParseException; 005 006 import net.minidev.json.JSONObject; 007 008 import com.nimbusds.jose.util.Base64URL; 009 import com.nimbusds.jose.util.JSONObjectUtils; 010 011 012 /** 013 * The base abstract class for plaintext, JSON Web Signature (JWS) - secured 014 * and JSON Web Encryption (JWE) - secured objects. 015 * 016 * @author Vladimir Dzhuvinov 017 * @version $version$ (2012-10-23) 018 */ 019 public abstract class JOSEObject { 020 021 022 /** 023 * The payload (message), {@code null} if not defined. 024 */ 025 private Payload payload; 026 027 028 /** 029 * The original parsed Base64URL parts, {@code null} if the JOSE object 030 * was created from scratch. The individual parts may be empty or 031 * {@code null} to indicate a missing part. 032 */ 033 private Base64URL[] parsedParts; 034 035 036 /** 037 * Creates a new JOSE object. The payload and the original parsed 038 * Base64URL parts are not not defined. 039 */ 040 protected JOSEObject() { 041 042 payload = null; 043 044 parsedParts = null; 045 } 046 047 048 /** 049 * Creates a new JOSE object with the specified payload. 050 * 051 * @param payload The payload, {@code null} if not available (e.g for 052 * an encrypted JWE object). 053 */ 054 protected JOSEObject(final Payload payload) { 055 056 this.payload = payload; 057 } 058 059 060 /** 061 * Gets the header of this JOSE object. 062 * 063 * @return The header. 064 */ 065 public abstract ReadOnlyHeader getHeader(); 066 067 068 /** 069 * Sets the payload of this JOSE object. 070 * 071 * @param payload The payload, {@code null} if not available (e.g. for 072 * an encrypted JWE object). 073 */ 074 protected void setPayload(final Payload payload) { 075 076 this.payload = payload; 077 } 078 079 080 /** 081 * Gets the payload of this JOSE object. 082 * 083 * @return The payload, {@code null} if not available (for an encrypted 084 * JWE object that hasn't been decrypted). 085 */ 086 public Payload getPayload() { 087 088 return payload; 089 } 090 091 092 /** 093 * Sets the original parsed Base64URL parts used to create this JOSE 094 * object. 095 * 096 * @param parts The original Base64URL parts used to creates this JOSE 097 * object, {@code null} if the object was created from 098 * scratch. The individual parts may be empty or 099 * {@code null} to indicate a missing part. 100 */ 101 protected void setParsedParts(final Base64URL... parts) { 102 103 parsedParts = parts; 104 } 105 106 107 /** 108 * Gets the original parsed Base64URL parts used to create this JOSE 109 * object. 110 * 111 * @return The original Base64URL parts used to creates this JOSE 112 * object, {@code null} if the object was created from scratch. 113 * The individual parts may be empty or {@code null} to 114 * indicate a missing part. 115 */ 116 public Base64URL[] getParsedParts() { 117 118 return parsedParts; 119 } 120 121 122 /** 123 * Gets the original parsed string used to create this JOSE object. 124 * 125 * @see #getParsedParts 126 * 127 * @return The parsed string used to create this JOSE object, 128 * {@code null} if the object was creates from scratch. 129 */ 130 public String getParsedString() { 131 132 if (parsedParts == null) 133 return null; 134 135 StringBuilder sb = new StringBuilder(); 136 137 for (Base64URL part: parsedParts) { 138 139 if (sb.length() > 0) 140 sb.append('.'); 141 142 if (part == null) 143 continue; 144 else 145 sb.append(part.toString()); 146 } 147 148 return sb.toString(); 149 } 150 151 152 /** 153 * Serialises this JOSE object to its compact format consisting of 154 * Base64URL-encoded parts delimited by period ('.') characters. 155 * 156 * @return The serialised JOSE object. 157 * 158 * @throws IllegalStateException If the JOSE object is not in a state 159 * that permits serialisation. 160 */ 161 public abstract String serialize(); 162 163 164 /** 165 * Splits a serialised JOSE object into its Base64URL-encoded parts. 166 * 167 * @param s The serialised JOSE object to split. Must not be 168 * {@code null}. 169 * 170 * @return The JOSE Base64URL-encoded parts (three for plaintext and 171 * JWS objects, five for JWE objects). 172 * 173 * @throws ParseException If the specified string couldn't be split 174 * into three or five Base64URL-encoded parts. 175 */ 176 public static Base64URL[] split(final String s) 177 throws ParseException { 178 179 // We must have 2 (JWS) or 4 dots (JWE) 180 181 // String.split() cannot handle empty parts 182 final int dot1 = s.indexOf("."); 183 184 if (dot1 == -1) 185 throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing part delimiters", 0); 186 187 final int dot2 = s.indexOf(".", dot1 + 1); 188 189 if (dot2 == -1) 190 throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing second delimiter", 0); 191 192 // Third dot for JWE only 193 final int dot3 = s.indexOf(".", dot2 + 1); 194 195 if (dot3 == -1) { 196 197 // Two dots only? -> We have a JWS 198 Base64URL[] parts = new Base64URL[3]; 199 parts[0] = new Base64URL(s.substring(0, dot1)); 200 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 201 parts[2] = new Base64URL(s.substring(dot2 + 1)); 202 return parts; 203 } 204 205 // Fourth final dot for JWE 206 final int dot4 = s.indexOf(".", dot3 + 1); 207 208 if (dot4 == -1) 209 throw new ParseException("Invalid serialized JWE object: Missing fourth delimiter", 0); 210 211 if (dot4 != -1 && s.indexOf(".", dot4 + 1) != -1) 212 throw new ParseException("Invalid serialized plain/JWS/JWE object: Too many part delimiters", 0); 213 214 // Four dots -> five parts 215 Base64URL[] parts = new Base64URL[5]; 216 parts[0] = new Base64URL(s.substring(0, dot1)); 217 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 218 parts[2] = new Base64URL(s.substring(dot2 + 1, dot3)); 219 parts[3] = new Base64URL(s.substring(dot3 + 1, dot4)); 220 parts[4] = new Base64URL(s.substring(dot4 + 1)); 221 return parts; 222 } 223 224 225 /** 226 * Parses a JOSE object from the specified string in compact format. 227 * 228 * @param s The string to parse. Must not be {@code null}. 229 * 230 * @return The corresponding {@link PlainObject}, {@link JWSObject} or 231 * {@link JWEObject} instance. 232 * 233 * @throws ParseException If the string couldn't be parsed to a valid 234 * plaintext, JWS or JWE object. 235 */ 236 public static JOSEObject parse(final String s) 237 throws ParseException { 238 239 Base64URL[] parts = split(s); 240 241 JSONObject jsonObject = null; 242 243 try { 244 jsonObject = JSONObjectUtils.parseJSONObject(parts[0].decodeToString()); 245 246 } catch (ParseException e) { 247 248 throw new ParseException("Invalid plain/JWS/JWE header: " + e.getMessage(), 0); 249 } 250 251 Algorithm alg = Header.parseAlgorithm(jsonObject); 252 253 if (alg.equals(Algorithm.NONE)) 254 return PlainObject.parse(s); 255 256 else if (alg instanceof JWSAlgorithm) 257 return JWSObject.parse(s); 258 259 else if (alg instanceof JWEAlgorithm) 260 return JWEObject.parse(s); 261 262 else 263 throw new AssertionError("Unexpected algorithm type: " + alg); 264 } 265 }