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