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 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 136 StringBuilder sb = new StringBuilder(); 137 138 for (Base64URL part: parsedParts) { 139 140 if (sb.length() > 0) { 141 sb.append('.'); 142 } 143 144 if (part == null) { 145 continue; 146 } else { 147 sb.append(part.toString()); 148 } 149 } 150 151 return sb.toString(); 152 } 153 154 155 /** 156 * Serialises this JOSE object to its compact format consisting of 157 * Base64URL-encoded parts delimited by period ('.') characters. 158 * 159 * @return The serialised JOSE object. 160 * 161 * @throws IllegalStateException If the JOSE object is not in a state 162 * that permits serialisation. 163 */ 164 public abstract String serialize(); 165 166 167 /** 168 * Splits a serialised JOSE object into its Base64URL-encoded parts. 169 * 170 * @param s The serialised JOSE object to split. Must not be 171 * {@code null}. 172 * 173 * @return The JOSE Base64URL-encoded parts (three for plaintext and 174 * JWS objects, five for JWE objects). 175 * 176 * @throws ParseException If the specified string couldn't be split 177 * into three or five Base64URL-encoded parts. 178 */ 179 public static Base64URL[] split(final String s) 180 throws ParseException { 181 182 // We must have 2 (JWS) or 4 dots (JWE) 183 184 // String.split() cannot handle empty parts 185 final int dot1 = s.indexOf("."); 186 187 if (dot1 == -1) { 188 throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing part delimiters", 0); 189 } 190 191 final int dot2 = s.indexOf(".", dot1 + 1); 192 193 if (dot2 == -1) { 194 throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing second delimiter", 0); 195 } 196 197 // Third dot for JWE only 198 final int dot3 = s.indexOf(".", dot2 + 1); 199 200 if (dot3 == -1) { 201 202 // Two dots only? -> We have a JWS 203 Base64URL[] parts = new Base64URL[3]; 204 parts[0] = new Base64URL(s.substring(0, dot1)); 205 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 206 parts[2] = new Base64URL(s.substring(dot2 + 1)); 207 return parts; 208 } 209 210 // Fourth final dot for JWE 211 final int dot4 = s.indexOf(".", dot3 + 1); 212 213 if (dot4 == -1) { 214 throw new ParseException("Invalid serialized JWE object: Missing fourth delimiter", 0); 215 } 216 217 if (dot4 != -1 && s.indexOf(".", dot4 + 1) != -1) { 218 throw new ParseException("Invalid serialized plain/JWS/JWE object: Too many part delimiters", 0); 219 } 220 221 // Four dots -> five parts 222 Base64URL[] parts = new Base64URL[5]; 223 parts[0] = new Base64URL(s.substring(0, dot1)); 224 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 225 parts[2] = new Base64URL(s.substring(dot2 + 1, dot3)); 226 parts[3] = new Base64URL(s.substring(dot3 + 1, dot4)); 227 parts[4] = new Base64URL(s.substring(dot4 + 1)); 228 return parts; 229 } 230 231 232 /** 233 * Parses a JOSE object from the specified string in compact format. 234 * 235 * @param s The string to parse. Must not be {@code null}. 236 * 237 * @return The corresponding {@link PlainObject}, {@link JWSObject} or 238 * {@link JWEObject} instance. 239 * 240 * @throws ParseException If the string couldn't be parsed to a valid 241 * plaintext, JWS or JWE object. 242 */ 243 public static JOSEObject parse(final String s) 244 throws ParseException { 245 246 Base64URL[] parts = split(s); 247 248 JSONObject jsonObject = null; 249 250 try { 251 jsonObject = JSONObjectUtils.parseJSONObject(parts[0].decodeToString()); 252 253 } catch (ParseException e) { 254 255 throw new ParseException("Invalid plain/JWS/JWE header: " + e.getMessage(), 0); 256 } 257 258 Algorithm alg = Header.parseAlgorithm(jsonObject); 259 260 if (alg.equals(Algorithm.NONE)) { 261 return PlainObject.parse(s); 262 } else if (alg instanceof JWSAlgorithm) { 263 return JWSObject.parse(s); 264 } else if (alg instanceof JWEAlgorithm) { 265 return JWEObject.parse(s); 266 } else { 267 throw new AssertionError("Unexpected algorithm type: " + alg); 268 } 269 } 270 }