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