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 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                    StringBuilder sb = new StringBuilder();
136    
137                    for (Base64URL part: parsedParts) {
138    
139                            if (sb.length() > 0)
140                                    sb.append('.');
141    
142                            if (part == null)
143                                    continue;
144                            else
145                                    sb.append(part.toString());
146                    }
147    
148                    return sb.toString();
149            }
150            
151            
152            /**
153             * Serialises this JOSE object to its compact format consisting of 
154             * Base64URL-encoded parts delimited by period ('.') characters.
155             *
156             * @return The serialised JOSE object.
157             *
158             * @throws IllegalStateException If the JOSE object is not in a state 
159             *                               that permits serialisation.
160             */
161            public abstract String serialize();
162            
163            
164            /**
165             * Splits a serialised JOSE object into its Base64URL-encoded parts.
166             *
167             * @param s The serialised JOSE object to split. Must not be 
168             *          {@code null}.
169             *
170             * @return The JOSE Base64URL-encoded parts (three for plaintext and 
171             *         JWS objects, five for JWE objects).
172             *
173             * @throws ParseException If the specified string couldn't be split 
174             *                        into three or five Base64URL-encoded parts.
175             */
176            public static Base64URL[] split(final String s)
177                    throws ParseException {
178                    
179                    // We must have 2 (JWS) or 4 dots (JWE)
180                    
181                    // String.split() cannot handle empty parts
182                    final int dot1 = s.indexOf(".");
183                    
184                    if (dot1 == -1)
185                            throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing part delimiters", 0);
186                            
187                    final int dot2 = s.indexOf(".", dot1 + 1);
188                    
189                    if (dot2 == -1)
190                            throw new ParseException("Invalid serialized plain/JWS/JWE object: Missing second delimiter", 0);
191                    
192                    // Third dot for JWE only
193                    final int dot3 = s.indexOf(".", dot2 + 1);
194                    
195                    if (dot3 == -1) {
196                    
197                            // Two dots only? -> We have a JWS
198                            Base64URL[] parts = new Base64URL[3];
199                            parts[0] = new Base64URL(s.substring(0, dot1));
200                            parts[1] = new Base64URL(s.substring(dot1 + 1, dot2));
201                            parts[2] = new Base64URL(s.substring(dot2 + 1));
202                            return parts;
203                    }
204                    
205                    // Fourth final dot for JWE
206                    final int dot4 = s.indexOf(".", dot3 + 1);
207                    
208                    if (dot4 == -1)
209                            throw new ParseException("Invalid serialized JWE object: Missing fourth delimiter", 0);
210                    
211                    if (dot4 != -1 && s.indexOf(".", dot4 + 1) != -1)
212                            throw new ParseException("Invalid serialized plain/JWS/JWE object: Too many part delimiters", 0);
213                    
214                    // Four dots -> five parts
215                    Base64URL[] parts = new Base64URL[5];
216                    parts[0] = new Base64URL(s.substring(0, dot1));
217                    parts[1] = new Base64URL(s.substring(dot1 + 1, dot2));
218                    parts[2] = new Base64URL(s.substring(dot2 + 1, dot3));
219                    parts[3] = new Base64URL(s.substring(dot3 + 1, dot4));
220                    parts[4] = new Base64URL(s.substring(dot4 + 1));
221                    return parts;
222            }
223    
224    
225            /**
226             * Parses a JOSE object from the specified string in compact format.
227             *
228             * @param s The string to parse. Must not be {@code null}.
229             *
230             * @return The corresponding {@link PlainObject}, {@link JWSObject} or
231             *         {@link JWEObject} instance.
232             *
233             * @throws ParseException If the string couldn't be parsed to a valid 
234             *                       plaintext, JWS or JWE object.
235             */
236            public static JOSEObject parse(final String s) 
237                    throws ParseException {
238                    
239                    Base64URL[] parts = split(s);
240                    
241                    JSONObject jsonObject = null;
242                    
243                    try {
244                            jsonObject = JSONObjectUtils.parseJSONObject(parts[0].decodeToString());
245                            
246                    } catch (ParseException e) {
247                    
248                            throw new ParseException("Invalid plain/JWS/JWE header: " + e.getMessage(), 0);
249                    }
250                    
251                    Algorithm alg = Header.parseAlgorithm(jsonObject);
252    
253                    if (alg.equals(Algorithm.NONE))
254                            return PlainObject.parse(s);
255                    
256                    else if (alg instanceof JWSAlgorithm)
257                            return JWSObject.parse(s);
258                            
259                    else if (alg instanceof JWEAlgorithm)
260                            return JWEObject.parse(s);
261                            
262                    else
263                            throw new AssertionError("Unexpected algorithm type: " + alg);
264            }
265    }