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