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