001    package com.nimbusds.jose;
002    
003    
004    import java.nio.charset.Charset;
005    import java.text.ParseException;
006    
007    import net.jcip.annotations.ThreadSafe;
008    
009    import com.nimbusds.jose.util.Base64URL;
010    
011    
012    /**
013     * JSON Web Signature (JWS) object. This class is thread-safe.
014     *
015     * @author Vladimir Dzhuvinov
016     * @version $version$ (2013-07-15)
017     */
018    @ThreadSafe
019    public class JWSObject extends JOSEObject {
020    
021    
022            /**
023             * Enumeration of the states of a JSON Web Signature (JWS) object.
024             */
025            public static enum State {
026    
027    
028                    /**
029                     * The JWS object is created but not signed yet.
030                     */
031                    UNSIGNED,
032    
033    
034                    /**
035                     * The JWS object is signed but its signature is not verified.
036                     */
037                    SIGNED,
038    
039    
040                    /**
041                     * The JWS object is signed and its signature was successfully verified.
042                     */
043                    VERIFIED;
044            }
045    
046    
047            /**
048             * The header.
049             */
050            private final JWSHeader header;
051    
052    
053            /**
054             * The signing input for this JWS object.
055             *
056             * <p>Format:
057             *
058             * <pre>
059             * [header-base64url].[payload-base64url]
060             * </pre>
061             */
062            private byte[] signingInput;
063    
064    
065            /**
066             * The signature, {@code null} if not signed.
067             */
068            private Base64URL signature;
069    
070    
071            /**
072             * The JWS object state.
073             */
074            private State state;
075    
076    
077            /**
078             * Creates a new to-be-signed JSON Web Signature (JWS) object with the 
079             * specified header and payload. The initial state will be 
080             * {@link State#UNSIGNED unsigned}.
081             *
082             * @param header  The JWS header. Must not be {@code null}.
083             * @param payload The payload. Must not be {@code null}.
084             */
085            public JWSObject(final JWSHeader header, final Payload payload) {
086    
087                    if (header == null) {
088    
089                            throw new IllegalArgumentException("The JWS header must not be null");
090                    }
091    
092                    this.header = header;
093    
094                    if (payload == null) {
095    
096                            throw new IllegalArgumentException("The payload must not be null");
097                    }
098    
099                    setPayload(payload);
100    
101                    setSigningInput(header.toBase64URL(), payload.toBase64URL());
102    
103                    signature = null;
104    
105                    state = State.UNSIGNED;
106            }
107    
108    
109            /**
110             * Creates a new signed JSON Web Signature (JWS) object with the 
111             * specified serialised parts. The state will be 
112             * {@link State#SIGNED signed}.
113             *
114             * @param firstPart  The first part, corresponding to the JWS header. 
115             *                   Must not be {@code null}.
116             * @param secondPart The second part, corresponding to the payload. Must
117             *                   not be {@code null}.
118             * @param thirdPart  The third part, corresponding to the signature.
119             *                   Must not be {@code null}.
120             *
121             * @throws ParseException If parsing of the serialised parts failed.
122             */
123            public JWSObject(final Base64URL firstPart, final Base64URL secondPart, final Base64URL thirdPart)      
124                    throws ParseException {
125    
126                    if (firstPart == null) {
127    
128                            throw new IllegalArgumentException("The first part must not be null");
129                    }
130    
131                    try {
132                            this.header = JWSHeader.parse(firstPart);
133    
134                    } catch (ParseException e) {
135    
136                            throw new ParseException("Invalid JWS header: " + e.getMessage(), 0);
137                    }
138    
139                    if (secondPart == null) {
140    
141                            throw new IllegalArgumentException("The second part must not be null");
142                    }
143    
144                    setPayload(new Payload(secondPart));
145    
146                    setSigningInput(firstPart, secondPart);
147    
148                    if (thirdPart == null) {
149                            throw new IllegalArgumentException("The third part must not be null");
150                    }
151    
152                    signature = thirdPart;
153    
154                    state = State.SIGNED; // but signature not verified yet!
155    
156                    setParsedParts(firstPart, secondPart, thirdPart);
157            }
158    
159    
160            @Override
161            public ReadOnlyJWSHeader getHeader() {
162    
163                    return header;
164            }
165    
166    
167            /**
168             * Sets the signing input for this JWS object.
169             *
170             * <p>Format:
171             *
172             * <pre>
173             * [header-base64url].[payload-base64url]
174             * </pre>
175             *
176             * @param firstPart  The first part, corresponding to the JWS header.
177             *                   Must not be {@code null}.
178             * @param secondPart The second part, corresponding to the payload. 
179             *                   Must not be {@code null}.
180             */
181            private void setSigningInput(final Base64URL firstPart, final Base64URL secondPart) {
182    
183                    StringBuilder sb = new StringBuilder(firstPart.toString());
184                    sb.append('.');
185                    sb.append(secondPart.toString());
186    
187                    signingInput = sb.toString().getBytes(Charset.forName("UTF-8"));
188            }
189    
190    
191            /**
192             * @deprecated Use {@link #setSigningInput} instead.
193             */
194            private void setSignableContent(final Base64URL firstPart, final Base64URL secondPart) {
195    
196                    setSigningInput(firstPart, secondPart);
197            }
198    
199    
200            /**
201             * Gets the signing input for this JWS object.
202             *
203             * <p>Format:
204             *
205             * <pre>
206             * [header-base64url].[payload-base64url]
207             * </pre>
208             *
209             * @return The signing input, to be passed to a JWS signer or verifier.
210             */
211            public byte[] getSigningInput() {
212    
213                    return signingInput;
214            }
215    
216    
217            /**
218             * @deprecated Use {@link #getSigningInput} instead.
219             */
220            @Deprecated
221            public byte[] getSignableContent() {
222    
223                    return getSigningInput();
224            }
225    
226    
227            /**
228             * Gets the signature of this JWS object.
229             *
230             * @return The signature, {@code null} if the JWS object is not signed 
231             *         yet.
232             */
233            public Base64URL getSignature() {
234    
235                    return signature;
236            }
237    
238    
239            /**
240             * Gets the state of this JWS object.
241             *
242             * @return The state.
243             */
244            public State getState() {
245    
246                    return state;
247            }
248    
249    
250            /**
251             * Ensures the current state is {@link State#UNSIGNED unsigned}.
252             *
253             * @throws IllegalStateException If the current state is not unsigned.
254             */
255            private void ensureUnsignedState() {
256    
257                    if (state != State.UNSIGNED) {
258    
259                            throw new IllegalStateException("The JWS object must be in an unsigned state");
260                    }
261            }
262    
263    
264            /**
265             * Ensures the current state is {@link State#SIGNED signed} or
266             * {@link State#VERIFIED verified}.
267             *
268             * @throws IllegalStateException If the current state is not signed or
269             *                               verified.
270             */
271            private void ensureSignedOrVerifiedState() {
272    
273                    if (state != State.SIGNED && state != State.VERIFIED) {
274    
275                            throw new IllegalStateException("The JWS object must be in a signed or verified state");
276                    }
277            }
278    
279    
280            /**
281             * Ensures the specified JWS signer supports the algorithm of this JWS
282             * object.
283             *
284             * @throws JOSEException If the JWS algorithm is not supported.
285             */
286            private void ensureJWSSignerSupport(final JWSSigner signer)
287                    throws JOSEException {
288    
289                    if (! signer.supportedAlgorithms().contains(getHeader().getAlgorithm())) {
290    
291                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
292                                                    "\" algorithm is not supported by the JWS signer");
293                    }
294            }
295    
296    
297            /**
298             * Ensures the specified JWS verifier accepts the algorithm and the headers 
299             * of this JWS object.
300             *
301             * @throws JOSEException If the JWS algorithm or headers are not accepted.
302             */
303            private void ensureJWSVerifierAcceptance(final JWSVerifier verifier)
304                    throws JOSEException {
305    
306                    JWSHeaderFilter filter = verifier.getJWSHeaderFilter();
307    
308                    if (filter == null) {
309                            return;
310                    }
311    
312                    if (! filter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) {
313    
314                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
315                                            "\" algorithm is not accepted by the JWS verifier");
316                    }
317    
318    
319                    if (! filter.getAcceptedParameters().containsAll(getHeader().getIncludedParameters())) {
320    
321                            throw new JOSEException("One or more header parameters not accepted by the JWS verifier");
322                    }
323            }
324    
325    
326            /**
327             * Signs this JWS object with the specified signer. The JWS object must
328             * be in a {@link State#UNSIGNED unsigned} state.
329             *
330             * @param signer The JWS signer. Must not be {@code null}.
331             *
332             * @throws IllegalStateException If the JWS object is not in an 
333             *                               {@link State#UNSIGNED unsigned state}.
334             * @throws JOSEException         If the JWS object couldn't be signed.
335             */
336            public synchronized void sign(final JWSSigner signer)
337                    throws JOSEException {
338    
339                    ensureUnsignedState();
340    
341                    ensureJWSSignerSupport(signer);
342    
343                    try {
344                            signature = signer.sign(getHeader(), getSigningInput());
345    
346                    } catch (JOSEException e) {
347    
348                            throw e;
349                                    
350                    } catch (Exception e) {
351    
352                            // Prevent throwing unchecked exceptions at this point,
353                            // see issue #20
354                            throw new JOSEException(e.getMessage(), e);
355                    }
356                    
357    
358                    state = State.SIGNED;
359            }
360    
361    
362            /**
363             * Checks the signature of this JWS object with the specified verifier. 
364             * The JWS object must be in a {@link State#SIGNED signed} state.
365             *
366             * @param verifier The JWS verifier. Must not be {@code null}.
367             *
368             * @return {@code true} if the signature was successfully verified, 
369             *         else {@code false}.
370             *
371             * @throws IllegalStateException If the JWS object is not in a 
372             *                               {@link State#SIGNED signed} or
373             *                               {@link State#VERIFIED verified state}.
374             * @throws JOSEException         If the JWS object couldn't be verified.
375             */
376            public synchronized boolean verify(final JWSVerifier verifier)
377                    throws JOSEException {
378    
379                    ensureSignedOrVerifiedState();
380    
381                    ensureJWSVerifierAcceptance(verifier);
382    
383                    boolean verified = false;
384    
385                    try {
386                            verified = verifier.verify(getHeader(), getSigningInput(), getSignature());
387    
388                    } catch (JOSEException e) {
389    
390                            throw e;
391    
392                    } catch (Exception e) {
393    
394                            // Prevent throwing unchecked exceptions at this point,
395                            // see issue #20
396                            throw new JOSEException(e.getMessage(), e);
397                    }
398    
399                    if (verified) {
400    
401                            state = State.VERIFIED;
402                    }
403    
404                    return verified;
405            }
406    
407    
408            /**
409             * Serialises this JWS object to its compact format consisting of 
410             * Base64URL-encoded parts delimited by period ('.') characters. It 
411             * must be in a {@link State#SIGNED signed} or 
412             * {@link State#VERIFIED verified} state.
413             *
414             * <pre>
415             * [header-base64url].[payload-base64url].[signature-base64url]
416             * </pre>
417             *
418             * @return The serialised JWS object.
419             *
420             * @throws IllegalStateException If the JWS object is not in a 
421             *                               {@link State#SIGNED signed} or
422             *                               {@link State#VERIFIED verified} state.
423             */
424            @Override
425            public String serialize() {
426    
427                    ensureSignedOrVerifiedState();
428    
429                    StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
430                    sb.append('.');
431                    sb.append(getPayload().toBase64URL().toString());
432                    sb.append('.');
433                    sb.append(signature.toString());
434                    return sb.toString();
435            }
436    
437    
438            /**
439             * Parses a JWS object from the specified string in compact format. The
440             * parsed JWS object will be given a {@link State#SIGNED} state.
441             *
442             * @param s The string to parse. Must not be {@code null}.
443             *
444             * @return The JWS object.
445             *
446             * @throws ParseException If the string couldn't be parsed to a valid 
447             *                        JWS object.
448             */
449            public static JWSObject parse(final String s)
450                    throws ParseException {
451    
452                    Base64URL[] parts = JOSEObject.split(s);
453    
454                    if (parts.length != 3) {
455    
456                            throw new ParseException("Unexpected number of Base64URL parts, must be three", 0);
457                    }
458    
459                    return new JWSObject(parts[0], parts[1], parts[2]);
460            }
461    }