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