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