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 unsecured (plain / {@code alg=none}), JSON Web
014 * Signature (JWS) secured and JSON Web Encryption (JWE) secured objects.
015 *
016 * @author Vladimir Dzhuvinov
017 * @version 2015-06-10
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 specified.
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                parsedParts = null;
058        }
059
060
061        /**
062         * Creates a new JOSE object with the specified payload.
063         *
064         * @param payload The payload, {@code null} if not available (e.g for 
065         *                an encrypted JWE object).
066         */
067        protected JOSEObject(final Payload payload) {
068
069                this.payload = payload;
070        }
071
072
073        /**
074         * Returns the header of this JOSE object.
075         *
076         * @return The header.
077         */
078        public abstract Header getHeader();
079
080
081        /**
082         * Sets the payload of this JOSE object.
083         *
084         * @param payload The payload, {@code null} if not available (e.g. for 
085         *                an encrypted JWE object).
086         */
087        protected void setPayload(final Payload payload) {
088
089                this.payload = payload;
090        }
091
092
093        /**
094         * Returns the payload of this JOSE object.
095         *
096         * @return The payload, {@code null} if not available (for an encrypted
097         *         JWE object that hasn't been decrypted).
098         */
099        public Payload getPayload() {
100
101                return payload;
102        }
103
104
105        /**
106         * Sets the original parsed Base64URL parts used to create this JOSE 
107         * object.
108         *
109         * @param parts The original Base64URL parts used to creates this JOSE
110         *              object, {@code null} if the object was created from
111         *              scratch. The individual parts may be empty or 
112         *              {@code null} to indicate a missing part.
113         */
114        protected void setParsedParts(final Base64URL... parts) {
115
116                parsedParts = parts;
117        }
118
119
120        /**
121         * Returns the original parsed Base64URL parts used to create this JOSE
122         * object.
123         *
124         * @return The original Base64URL parts used to creates this JOSE 
125         *         object, {@code null} if the object was created from scratch. 
126         *         The individual parts may be empty or {@code null} to 
127         *         indicate a missing part.
128         */
129        public Base64URL[] getParsedParts() {
130
131                return parsedParts;
132        }
133
134
135        /**
136         * Returns the original parsed string used to create this JOSE object.
137         *
138         * @see #getParsedParts
139         * 
140         * @return The parsed string used to create this JOSE object, 
141         *         {@code null} if the object was creates from scratch.
142         */
143        public String getParsedString() {
144
145                if (parsedParts == null) {
146                        return null;
147                }
148
149                StringBuilder sb = new StringBuilder();
150
151                for (Base64URL part: parsedParts) {
152
153                        if (sb.length() > 0) {
154                                sb.append('.');
155                        }
156
157                        if (part != null) {
158                                sb.append(part.toString());
159                        }
160                }
161
162                return sb.toString();
163        }
164
165
166        /**
167         * Serialises this JOSE object to its compact format consisting of 
168         * Base64URL-encoded parts delimited by period ('.') characters.
169         *
170         * @return The serialised JOSE object.
171         *
172         * @throws IllegalStateException If the JOSE object is not in a state 
173         *                               that permits serialisation.
174         */
175        public abstract String serialize();
176
177
178        /**
179         * Splits a compact serialised JOSE object into its Base64URL-encoded
180         * parts.
181         *
182         * @param s The compact serialised JOSE object to split. Must not be
183         *          {@code null}.
184         *
185         * @return The JOSE Base64URL-encoded parts (three for unsecured 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 unsecured/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 unsecured/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 unsecured/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         *                        unsecured, 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 unsecured/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}