001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    
006    import javax.mail.internet.ContentType;
007    import javax.mail.internet.ParameterList;
008    
009    import net.minidev.json.JSONObject;
010    
011    import com.nimbusds.jose.util.Base64URL;
012    import 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     */
022    public 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    }