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