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