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 net.jcip.annotations.Immutable;
026
027import net.minidev.json.JSONObject;
028
029import com.nimbusds.jose.jwk.JWK;
030import com.nimbusds.jose.util.Base64;
031import com.nimbusds.jose.util.Base64URL;
032import com.nimbusds.jose.util.JSONObjectUtils;
033import com.nimbusds.jose.util.X509CertChainUtils;
034
035
036/**
037 * JSON Web Signature (JWS) header. This class is immutable.
038 *
039 * <p>Supports all {@link #getRegisteredParameterNames registered header
040 * parameters} of the JWS specification:
041 *
042 * <ul>
043 *     <li>alg
044 *     <li>jku
045 *     <li>jwk
046 *     <li>x5u
047 *     <li>x5t
048 *     <li>x5t#S256
049 *     <li>x5c
050 *     <li>kid
051 *     <li>typ
052 *     <li>cty
053 *     <li>crit
054 * </ul>
055 *
056 * <p>The header may also include {@link #getCustomParams custom
057 * parameters}; these will be serialised and parsed along the registered ones.
058 *
059 * <p>Example header of a JSON Web Signature (JWS) object using the 
060 * {@link JWSAlgorithm#HS256 HMAC SHA-256 algorithm}:
061 *
062 * <pre>
063 * {
064 *   "alg" : "HS256"
065 * }
066 * </pre>
067 *
068 * @author Vladimir Dzhuvinov
069 * @version 2015-04-15
070 */
071@Immutable
072public final class JWSHeader extends CommonSEHeader {
073
074
075        private static final long serialVersionUID = 1L;
076
077
078        /**
079         * The registered parameter names.
080         */
081        private static final Set<String> REGISTERED_PARAMETER_NAMES;
082
083
084        /**
085         * Initialises the registered parameter name set.
086         */
087        static {
088                Set<String> p = new HashSet<>();
089
090                p.add("alg");
091                p.add("jku");
092                p.add("jwk");
093                p.add("x5u");
094                p.add("x5t");
095                p.add("x5t#S256");
096                p.add("x5c");
097                p.add("kid");
098                p.add("typ");
099                p.add("cty");
100                p.add("crit");
101
102                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
103        }
104
105
106        /**
107         * Builder for constructing JSON Web Signature (JWS) headers.
108         *
109         * <p>Example usage:
110         *
111         * <pre>
112         * JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256).
113         *                    contentType("text/plain").
114         *                    customParam("exp", new Date().getTime()).
115         *                    build();
116         * </pre>
117         */
118        public static class Builder {
119
120
121                /**
122                 * The JWS algorithm.
123                 */
124                private final JWSAlgorithm alg;
125
126
127                /**
128                 * The JOSE object type.
129                 */
130                private JOSEObjectType typ;
131
132
133                /**
134                 * The content type.
135                 */
136                private String cty;
137
138
139                /**
140                 * The critical headers.
141                 */
142                private Set<String> crit;
143
144
145                /**
146                 * JWK Set URL.
147                 */
148                private URI jku;
149
150
151                /**
152                 * JWK.
153                 */
154                private JWK jwk;
155
156
157                /**
158                 * X.509 certificate URL.
159                 */
160                private URI x5u;
161
162
163                /**
164                 * X.509 certificate SHA-1 thumbprint.
165                 */
166                private Base64URL x5t;
167
168
169                /**
170                 * X.509 certificate SHA-256 thumbprint.
171                 */
172                private Base64URL x5t256;
173
174
175                /**
176                 * The X.509 certificate chain corresponding to the key used to
177                 * sign the JWS object.
178                 */
179                private List<Base64> x5c;
180
181
182                /**
183                 * Key ID.
184                 */
185                private String kid;
186
187
188                /**
189                 * Custom header parameters.
190                 */
191                private Map<String,Object> customParams;
192
193
194                /**
195                 * The parsed Base64URL.
196                 */
197                private Base64URL parsedBase64URL;
198
199
200                /**
201                 * Creates a new JWS header builder.
202                 *
203                 * @param alg The JWS algorithm ({@code alg}) parameter. Must
204                 *            not be "none" or {@code null}.
205                 */
206                public Builder(final JWSAlgorithm alg) {
207
208                        if (alg.getName().equals(Algorithm.NONE.getName())) {
209                                throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\"");
210                        }
211
212                        this.alg = alg;
213                }
214
215
216                /**
217                 * Creates a new JWS header builder with the parameters from
218                 * the specified header.
219                 *
220                 * @param jwsHeader The JWS header to use. Must not not be
221                 *                  {@code null}.
222                 */
223                public Builder(final JWSHeader jwsHeader) {
224
225                        this(jwsHeader.getAlgorithm());
226
227                        typ = jwsHeader.getType();
228                        cty = jwsHeader.getContentType();
229                        crit = jwsHeader.getCriticalParams();
230
231                        jku = jwsHeader.getJWKURL();
232                        jwk = jwsHeader.getJWK();
233                        x5u = jwsHeader.getX509CertURL();
234                        x5t = jwsHeader.getX509CertThumbprint();
235                        x5t256 = jwsHeader.getX509CertSHA256Thumbprint();
236                        x5c = jwsHeader.getX509CertChain();
237                        kid = jwsHeader.getKeyID();
238                        customParams = jwsHeader.getCustomParams();
239                }
240
241
242                /**
243                 * Sets the type ({@code typ}) parameter.
244                 *
245                 * @param typ The type parameter, {@code null} if not
246                 *            specified.
247                 *
248                 * @return This builder.
249                 */
250                public Builder type(final JOSEObjectType typ) {
251
252                        this.typ = typ;
253                        return this;
254                }
255
256
257                /**
258                 * Sets the content type ({@code cty}) parameter.
259                 *
260                 * @param cty The content type parameter, {@code null} if not
261                 *            specified.
262                 *
263                 * @return This builder.
264                 */
265                public Builder contentType(final String cty) {
266
267                        this.cty = cty;
268                        return this;
269                }
270
271
272                /**
273                 * Sets the critical header parameters ({@code crit})
274                 * parameter.
275                 *
276                 * @param crit The names of the critical header parameters,
277                 *             empty set or {@code null} if none.
278                 *
279                 * @return This builder.
280                 */
281                public Builder criticalParams(final Set<String> crit) {
282
283                        this.crit = crit;
284                        return this;
285                }
286
287
288                /**
289                 * Sets the JSON Web Key (JWK) Set URL ({@code jku}) parameter.
290                 *
291                 * @param jku The JSON Web Key (JWK) Set URL parameter,
292                 *            {@code null} if not specified.
293                 *
294                 * @return This builder.
295                 */
296                public Builder jwkURL(final URI jku) {
297
298                        this.jku = jku;
299                        return this;
300                }
301
302
303                /**
304                 * Sets the JSON Web Key (JWK) ({@code jwk}) parameter.
305                 *
306                 * @param jwk The JSON Web Key (JWK) ({@code jwk}) parameter,
307                 *            {@code null} if not specified.
308                 *
309                 * @return This builder.
310                 */
311                public Builder jwk(final JWK jwk) {
312
313                        this.jwk = jwk;
314                        return this;
315                }
316
317
318                /**
319                 * Sets the X.509 certificate URL ({@code x5u}) parameter.
320                 *
321                 * @param x5u The X.509 certificate URL parameter, {@code null}
322                 *            if not specified.
323                 *
324                 * @return This builder.
325                 */
326                public Builder x509CertURL(final URI x5u) {
327
328                        this.x5u = x5u;
329                        return this;
330                }
331
332
333                /**
334                 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t})
335                 * parameter.
336                 *
337                 * @param x5t The X.509 certificate SHA-1 thumbprint parameter,
338                 *            {@code null} if not specified.
339                 *
340                 * @return This builder.
341                 */
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}