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