001package com.nimbusds.jose;
002
003
004import java.net.URI;
005import java.text.ParseException;
006import java.util.*;
007
008import net.jcip.annotations.Immutable;
009
010import net.minidev.json.JSONObject;
011
012import com.nimbusds.jose.jwk.JWK;
013import com.nimbusds.jose.util.Base64;
014import com.nimbusds.jose.util.Base64URL;
015import com.nimbusds.jose.util.JSONObjectUtils;
016import com.nimbusds.jose.util.X509CertChainUtils;
017
018
019/**
020 * JSON Web Signature (JWS) header. This class is immutable.
021 *
022 * <p>Supports all {@link #getRegisteredParameterNames registered header
023 * parameters} of the JWS specification:
024 *
025 * <ul>
026 *     <li>alg
027 *     <li>jku
028 *     <li>jwk
029 *     <li>x5u
030 *     <li>x5t
031 *     <li>x5t#S256
032 *     <li>x5c
033 *     <li>kid
034 *     <li>typ
035 *     <li>cty
036 *     <li>crit
037 * </ul>
038 *
039 * <p>The header may also include {@link #getCustomParams custom
040 * parameters}; these will be serialised and parsed along the registered ones.
041 *
042 * <p>Example header of a JSON Web Signature (JWS) object using the 
043 * {@link JWSAlgorithm#HS256 HMAC SHA-256 algorithm}:
044 *
045 * <pre>
046 * {
047 *   "alg" : "HS256"
048 * }
049 * </pre>
050 *
051 * @author Vladimir Dzhuvinov
052 * @version 2015-04-15
053 */
054@Immutable
055public final class JWSHeader extends CommonSEHeader {
056
057
058        private static final long serialVersionUID = 1L;
059
060
061        /**
062         * The registered parameter names.
063         */
064        private static final Set<String> REGISTERED_PARAMETER_NAMES;
065
066
067        /**
068         * Initialises the registered parameter name set.
069         */
070        static {
071                Set<String> p = new HashSet<>();
072
073                p.add("alg");
074                p.add("jku");
075                p.add("jwk");
076                p.add("x5u");
077                p.add("x5t");
078                p.add("x5t#S256");
079                p.add("x5c");
080                p.add("kid");
081                p.add("typ");
082                p.add("cty");
083                p.add("crit");
084
085                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
086        }
087
088
089        /**
090         * Builder for constructing JSON Web Signature (JWS) headers.
091         *
092         * <p>Example usage:
093         *
094         * <pre>
095         * JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256).
096         *                    contentType("text/plain").
097         *                    customParam("exp", new Date().getTime()).
098         *                    build();
099         * </pre>
100         */
101        public static class Builder {
102
103
104                /**
105                 * The JWS algorithm.
106                 */
107                private final JWSAlgorithm alg;
108
109
110                /**
111                 * The JOSE object type.
112                 */
113                private JOSEObjectType typ;
114
115
116                /**
117                 * The content type.
118                 */
119                private String cty;
120
121
122                /**
123                 * The critical headers.
124                 */
125                private Set<String> crit;
126
127
128                /**
129                 * JWK Set URL.
130                 */
131                private URI jku;
132
133
134                /**
135                 * JWK.
136                 */
137                private JWK jwk;
138
139
140                /**
141                 * X.509 certificate URL.
142                 */
143                private URI x5u;
144
145
146                /**
147                 * X.509 certificate SHA-1 thumbprint.
148                 */
149                private Base64URL x5t;
150
151
152                /**
153                 * X.509 certificate SHA-256 thumbprint.
154                 */
155                private Base64URL x5t256;
156
157
158                /**
159                 * The X.509 certificate chain corresponding to the key used to
160                 * sign the JWS object.
161                 */
162                private List<Base64> x5c;
163
164
165                /**
166                 * Key ID.
167                 */
168                private String kid;
169
170
171                /**
172                 * Custom header parameters.
173                 */
174                private Map<String,Object> customParams;
175
176
177                /**
178                 * The parsed Base64URL.
179                 */
180                private Base64URL parsedBase64URL;
181
182
183                /**
184                 * Creates a new JWS header builder.
185                 *
186                 * @param alg The JWS algorithm ({@code alg}) parameter. Must
187                 *            not be "none" or {@code null}.
188                 */
189                public Builder(final JWSAlgorithm alg) {
190
191                        if (alg.getName().equals(Algorithm.NONE.getName())) {
192                                throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\"");
193                        }
194
195                        this.alg = alg;
196                }
197
198
199                /**
200                 * Creates a new JWS header builder with the parameters from
201                 * the specified header.
202                 *
203                 * @param jwsHeader The JWS header to use. Must not not be
204                 *                  {@code null}.
205                 */
206                public Builder(final JWSHeader jwsHeader) {
207
208                        this(jwsHeader.getAlgorithm());
209
210                        typ = jwsHeader.getType();
211                        cty = jwsHeader.getContentType();
212                        crit = jwsHeader.getCriticalParams();
213                        customParams = jwsHeader.getCustomParams();
214
215                        jku = jwsHeader.getJWKURL();
216                        jwk = jwsHeader.getJWK();
217                        x5u = jwsHeader.getX509CertURL();
218                        x5t = jwsHeader.getX509CertThumbprint();
219                        x5t256 = jwsHeader.getX509CertSHA256Thumbprint();
220                        x5c = jwsHeader.getX509CertChain();
221                        kid = jwsHeader.getKeyID();
222                        customParams = jwsHeader.getCustomParams();
223                }
224
225
226                /**
227                 * Sets the type ({@code typ}) parameter.
228                 *
229                 * @param typ The type parameter, {@code null} if not
230                 *            specified.
231                 *
232                 * @return This builder.
233                 */
234                public Builder type(final JOSEObjectType typ) {
235
236                        this.typ = typ;
237                        return this;
238                }
239
240
241                /**
242                 * Sets the content type ({@code cty}) parameter.
243                 *
244                 * @param cty The content type parameter, {@code null} if not
245                 *            specified.
246                 *
247                 * @return This builder.
248                 */
249                public Builder contentType(final String cty) {
250
251                        this.cty = cty;
252                        return this;
253                }
254
255
256                /**
257                 * Sets the critical header parameters ({@code crit})
258                 * parameter.
259                 *
260                 * @param crit The names of the critical header parameters,
261                 *             empty set or {@code null} if none.
262                 *
263                 * @return This builder.
264                 */
265                public Builder criticalParams(final Set<String> crit) {
266
267                        this.crit = crit;
268                        return this;
269                }
270
271
272                /**
273                 * Sets the JSON Web Key (JWK) Set URL ({@code jku}) parameter.
274                 *
275                 * @param jku The JSON Web Key (JWK) Set URL parameter,
276                 *            {@code null} if not specified.
277                 *
278                 * @return This builder.
279                 */
280                public Builder jwkURL(final URI jku) {
281
282                        this.jku = jku;
283                        return this;
284                }
285
286
287                /**
288                 * Sets the JSON Web Key (JWK) ({@code jwk}) parameter.
289                 *
290                 * @param jwk The JSON Web Key (JWK) ({@code jwk}) parameter,
291                 *            {@code null} if not specified.
292                 *
293                 * @return This builder.
294                 */
295                public Builder jwk(final JWK jwk) {
296
297                        this.jwk = jwk;
298                        return this;
299                }
300
301
302                /**
303                 * Sets the X.509 certificate URL ({@code x5u}) parameter.
304                 *
305                 * @param x5u The X.509 certificate URL parameter, {@code null}
306                 *            if not specified.
307                 *
308                 * @return This builder.
309                 */
310                public Builder x509CertURL(final URI x5u) {
311
312                        this.x5u = x5u;
313                        return this;
314                }
315
316
317                /**
318                 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t})
319                 * parameter.
320                 *
321                 * @param x5t The X.509 certificate SHA-1 thumbprint parameter,
322                 *            {@code null} if not specified.
323                 *
324                 * @return This builder.
325                 */
326                public Builder x509CertThumbprint(final Base64URL x5t) {
327
328                        this.x5t = x5t;
329                        return this;
330                }
331
332
333                /**
334                 * Sets the X.509 certificate SHA-256 thumbprint
335                 * ({@code x5t#S256}) parameter.
336                 *
337                 * @param x5t256 The X.509 certificate SHA-256 thumbprint
338                 *               parameter, {@code null} if not specified.
339                 *
340                 * @return This builder.
341                 */
342                public Builder x509CertSHA256Thumbprint(final Base64URL x5t256) {
343
344                        this.x5t256 = x5t256;
345                        return this;
346                }
347
348
349                /**
350                 * Sets the X.509 certificate chain parameter ({@code x5c})
351                 * corresponding to the key used to sign the JWS object.
352                 *
353                 * @param x5c The X.509 certificate chain parameter,
354                 *            {@code null} if not specified.
355                 *
356                 * @return This builder.
357                 */
358                public Builder x509CertChain(final List<Base64> x5c) {
359
360                        this.x5c = x5c;
361                        return this;
362                }
363
364
365                /**
366                 * Sets the key ID ({@code kid}) parameter.
367                 *
368                 * @param kid The key ID parameter, {@code null} if not
369                 *            specified.
370                 *
371                 * @return This builder.
372                 */
373                public Builder keyID(final String kid) {
374
375                        this.kid = kid;
376                        return this;
377                }
378
379
380                /**
381                 * Sets a custom (non-registered) parameter.
382                 *
383                 * @param name  The name of the custom parameter. Must not
384                 *              match a registered parameter name and must not
385                 *              be {@code null}.
386                 * @param value The value of the custom parameter, should map
387                 *              to a valid JSON entity, {@code null} if not
388                 *              specified.
389                 *
390                 * @return This builder.
391                 *
392                 * @throws IllegalArgumentException If the specified parameter
393                 *                                  name matches a registered
394                 *                                  parameter name.
395                 */
396                public Builder customParam(final String name, final Object value) {
397
398                        if (getRegisteredParameterNames().contains(name)) {
399                                throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name");
400                        }
401
402                        if (customParams == null) {
403                                customParams = new HashMap<>();
404                        }
405
406                        customParams.put(name, value);
407
408                        return this;
409                }
410
411
412                /**
413                 * Sets the custom (non-registered) parameters. The values must
414                 * be serialisable to a JSON entity, otherwise will be ignored.
415                 *
416                 * @param customParameters The custom parameters, empty map or
417                 *                         {@code null} if none.
418                 *
419                 * @return This builder.
420                 */
421                public Builder customParams(final Map<String, Object> customParameters) {
422
423                        this.customParams = customParameters;
424                        return this;
425                }
426
427
428                /**
429                 * Sets the parsed Base64URL.
430                 *
431                 * @param base64URL The parsed Base64URL, {@code null} if the
432                 *                  header is created from scratch.
433                 *
434                 * @return This builder.
435                 */
436                public Builder parsedBase64URL(final Base64URL base64URL) {
437
438                        this.parsedBase64URL = base64URL;
439                        return this;
440                }
441
442
443                /**
444                 * Builds a new JWS header.
445                 *
446                 * @return The JWS header.
447                 */
448                public JWSHeader build() {
449
450                        return new JWSHeader(
451                                alg, typ, cty, crit,
452                                jku, jwk, x5u, x5t, x5t256, x5c, kid,
453                                customParams, parsedBase64URL);
454                }
455        }
456
457
458        /**
459         * Creates a new minimal JSON Web Signature (JWS) header.
460         *
461         * <p>Note: Use {@link PlainHeader} to create a header with algorithm
462         * {@link Algorithm#NONE none}.
463         *
464         * @param alg The JWS algorithm ({@code alg}) parameter. Must not be
465         *            "none" or {@code null}.
466         */
467        public JWSHeader(final JWSAlgorithm alg) {
468
469                this(alg, null, null, null, null, null, null, null, null, null, null, null, null);
470        }
471
472
473        /**
474         * Creates a new JSON Web Signature (JWS) header.
475         *
476         * <p>Note: Use {@link PlainHeader} to create a header with algorithm
477         * {@link Algorithm#NONE none}.
478         *
479         * @param alg             The JWS algorithm ({@code alg}) parameter.
480         *                        Must not be "none" or {@code null}.
481         * @param typ             The type ({@code typ}) parameter,
482         *                        {@code null} if not specified.
483         * @param cty             The content type ({@code cty}) parameter,
484         *                        {@code null} if not specified.
485         * @param crit            The names of the critical header
486         *                        ({@code crit}) parameters, empty set or
487         *                        {@code null} if none.
488         * @param jku             The JSON Web Key (JWK) Set URL ({@code jku})
489         *                        parameter, {@code null} if not specified.
490         * @param jwk             The X.509 certificate URL ({@code jwk})
491         *                        parameter, {@code null} if not specified.
492         * @param x5u             The X.509 certificate URL parameter
493         *                        ({@code x5u}), {@code null} if not specified.
494         * @param x5t             The X.509 certificate SHA-1 thumbprint
495         *                        ({@code x5t}) parameter, {@code null} if not
496         *                        specified.
497         * @param x5t256          The X.509 certificate SHA-256 thumbprint
498         *                        ({@code x5t#S256}) parameter, {@code null} if
499         *                        not specified.
500         * @param x5c             The X.509 certificate chain ({@code x5c})
501         *                        parameter, {@code null} if not specified.
502         * @param kid             The key ID ({@code kid}) parameter,
503         *                        {@code null} if not specified.
504         * @param customParams    The custom parameters, empty map or
505         *                        {@code null} if none.
506         * @param parsedBase64URL The parsed Base64URL, {@code null} if the
507         *                        header is created from scratch.
508         */
509        public JWSHeader(final JWSAlgorithm alg,
510                         final JOSEObjectType typ,
511                         final String cty,
512                         final Set<String> crit,
513                         final URI jku,
514                         final JWK jwk,
515                         final URI x5u,
516                         final Base64URL x5t,
517                         final Base64URL x5t256,
518                         final List<Base64> x5c,
519                         final String kid,
520                         final Map<String,Object> customParams,
521                         final Base64URL parsedBase64URL) {
522
523                super(alg, typ, cty, crit, jku, jwk, x5u, x5t, x5t256, x5c, kid, customParams, parsedBase64URL);
524
525                if (alg.getName().equals(Algorithm.NONE.getName())) {
526                        throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\"");
527                }
528        }
529
530
531        /**
532         * Deep copy constructor.
533         *
534         * @param jwsHeader The JWS header to copy. Must not be {@code null}.
535         */
536        public JWSHeader(final JWSHeader jwsHeader) {
537
538                this(
539                        jwsHeader.getAlgorithm(),
540                        jwsHeader.getType(),
541                        jwsHeader.getContentType(),
542                        jwsHeader.getCriticalParams(),
543                        jwsHeader.getJWKURL(),
544                        jwsHeader.getJWK(),
545                        jwsHeader.getX509CertURL(),
546                        jwsHeader.getX509CertThumbprint(),
547                        jwsHeader.getX509CertSHA256Thumbprint(),
548                        jwsHeader.getX509CertChain(),
549                        jwsHeader.getKeyID(),
550                        jwsHeader.getCustomParams(),
551                        jwsHeader.getParsedBase64URL()
552                );
553        }
554
555
556        /**
557         * Gets the registered parameter names for JWS headers.
558         *
559         * @return The registered parameter names, as an unmodifiable set.
560         */
561        public static Set<String> getRegisteredParameterNames() {
562
563                return REGISTERED_PARAMETER_NAMES;
564        }
565
566
567        /**
568         * Gets the algorithm ({@code alg}) parameter.
569         *
570         * @return The algorithm parameter.
571         */
572        @Override
573        public JWSAlgorithm getAlgorithm() {
574
575                return (JWSAlgorithm)super.getAlgorithm();
576        }
577
578
579        /**
580         * Parses a JWS header from the specified JSON object.
581         *
582         * @param jsonObject The JSON object to parse. Must not be
583         *                   {@code null}.
584         *
585         * @return The JWS header.
586         *
587         * @throws ParseException If the specified JSON object doesn't
588         *                        represent a valid JWS header.
589         */
590        public static JWSHeader parse(final JSONObject jsonObject)
591                throws ParseException {
592
593                return parse(jsonObject, null);
594        }
595
596
597        /**
598         * Parses a JWS header from the specified JSON object.
599         *
600         * @param jsonObject      The JSON object to parse. Must not be
601         *                        {@code null}.
602         * @param parsedBase64URL The original parsed Base64URL, {@code null}
603         *                        if not applicable.
604         *
605         * @return The JWS header.
606         *
607         * @throws ParseException If the specified JSON object doesn't 
608         *                        represent a valid JWS header.
609         */
610        public static JWSHeader parse(final JSONObject jsonObject,
611                                      final Base64URL parsedBase64URL)
612                throws ParseException {
613
614                // Get the "alg" parameter
615                Algorithm alg = Header.parseAlgorithm(jsonObject);
616
617                if (! (alg instanceof JWSAlgorithm)) {
618                        throw new ParseException("The algorithm \"alg\" header parameter must be for signatures", 0);
619                }
620
621                JWSHeader.Builder header = new Builder((JWSAlgorithm)alg).parsedBase64URL(parsedBase64URL);
622
623                // Parse optional + custom parameters
624                for (final String name: jsonObject.keySet()) {
625
626                        if("alg".equals(name)) {
627                                // skip
628                        } else if("typ".equals(name)) {
629                                header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name)));
630                        } else if("cty".equals(name)) {
631                                header = header.contentType(JSONObjectUtils.getString(jsonObject, name));
632                        } else if("crit".equals(name)) {
633                                header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name)));
634                        } else if("jku".equals(name)) {
635                                header = header.jwkURL(JSONObjectUtils.getURI(jsonObject, name));
636                        } else if("jwk".equals(name)) {
637                                header = header.jwk(JWK.parse(JSONObjectUtils.getJSONObject(jsonObject, name)));
638                        } else if("x5u".equals(name)) {
639                                header = header.x509CertURL(JSONObjectUtils.getURI(jsonObject, name));
640                        } else if("x5t".equals(name)) {
641                                header = header.x509CertThumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name)));
642                        } else if("x5t#S256".equals(name)) {
643                                header = header.x509CertSHA256Thumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name)));
644                        } else if("x5c".equals(name)) {
645                                header = header.x509CertChain(X509CertChainUtils.parseX509CertChain(JSONObjectUtils.getJSONArray(jsonObject, name)));
646                        } else if("kid".equals(name)) {
647                                header = header.keyID(JSONObjectUtils.getString(jsonObject, name));
648                        } else {
649                                header = header.customParam(name, jsonObject.get(name));
650                        }
651                }
652
653                return header.build();
654        }
655
656
657        /**
658         * Parses a JWS header from the specified JSON object string.
659         *
660         * @param jsonString The JSON string to parse. Must not be
661         *                   {@code null}.
662         *
663         * @return The JWS header.
664         *
665         * @throws ParseException If the specified JSON object string doesn't
666         *                        represent a valid JWS header.
667         */
668        public static JWSHeader parse(final String jsonString)
669                throws ParseException {
670
671                return parse(jsonString, null);
672        }
673
674
675        /**
676         * Parses a JWS header from the specified JSON object string.
677         *
678         * @param jsonString      The JSON string to parse. Must not be
679         *                        {@code null}.
680         * @param parsedBase64URL The original parsed Base64URL, {@code null}
681         *                        if not applicable.
682         *
683         * @return The JWS header.
684         *
685         * @throws ParseException If the specified JSON object string doesn't 
686         *                        represent a valid JWS header.
687         */
688        public static JWSHeader parse(final String jsonString,
689                                      final Base64URL parsedBase64URL)
690                throws ParseException {
691
692                return parse(JSONObjectUtils.parse(jsonString), parsedBase64URL);
693        }
694
695
696        /**
697         * Parses a JWS header from the specified Base64URL.
698         *
699         * @param base64URL The Base64URL to parse. Must not be {@code null}.
700         *
701         * @return The JWS header.
702         *
703         * @throws ParseException If the specified Base64URL doesn't represent
704         *                        a valid JWS header.
705         */
706        public static JWSHeader parse(final Base64URL base64URL)
707                throws ParseException {
708
709                return parse(base64URL.decodeToString(), base64URL);
710        }
711}