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