001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    
006    import net.minidev.json.JSONObject;
007    
008    import com.nimbusds.jose.util.Base64URL;
009    import 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     */
019    public 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 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    }