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