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 (unsecured), JSON Web Signature (JWS)
014 * and JSON Web Encryption (JWE) objects.
015 *
016 * @author Vladimir Dzhuvinov
017 * @version $version$ (2014-11-18)
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         * Returns the header of this JOSE object.
076         *
077         * @return The header.
078         */
079        public abstract Header 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         * Returns 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         * Returns 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         * Returns 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                                sb.append(part.toString());
160                        }
161                }
162
163                return sb.toString();
164        }
165
166
167        /**
168         * Serialises this JOSE object to its compact format consisting of 
169         * Base64URL-encoded parts delimited by period ('.') characters.
170         *
171         * @return The serialised JOSE object.
172         *
173         * @throws IllegalStateException If the JOSE object is not in a state 
174         *                               that permits serialisation.
175         */
176        public abstract String serialize();
177
178
179        /**
180         * Splits a serialised JOSE object into its Base64URL-encoded parts.
181         *
182         * @param s The serialised JOSE object to split. Must not be 
183         *          {@code null}.
184         *
185         * @return The JOSE Base64URL-encoded parts (three for plaintext 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 plain/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 plain/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 plain/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         *                       plaintext, 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 plain/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
283
284        /**
285         * Parses a {@link PlainObject plain}, {@link JWSObject JWS} or
286         * {@link JWEObject JWE object} from the specified string in compact
287         * format.
288         *
289         * @param s       The string to parse. Must not be {@code null}.
290         * @param handler Handler for the parsed JOSE object. Must not be
291         *                {@code null}.
292         *
293         * @return The object returned by the handler, {@code null} if none is
294         *         returned.
295         *
296         * @throws ParseException If the string couldn't be parsed to a valid
297         *                        plain, signed or encrypted JWT.
298         */
299        public static <T> T parse(final String s, JOSEObjectHandler<T> handler)
300                throws ParseException {
301
302                JOSEObject joseObject = parse(s);
303
304                if (joseObject instanceof PlainObject) {
305                        return handler.onPlainObject((PlainObject)joseObject);
306                } else if (joseObject instanceof JWSObject) {
307                        return handler.onJWSObject((JWSObject)joseObject);
308                } else {
309                        return handler.onJWEObject((JWEObject)joseObject);
310                }
311        }
312}