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