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