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