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