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                        customParams = jwsHeader.getCustomParams();
231
232                        jku = jwsHeader.getJWKURL();
233                        jwk = jwsHeader.getJWK();
234                        x5u = jwsHeader.getX509CertURL();
235                        x5t = jwsHeader.getX509CertThumbprint();
236                        x5t256 = jwsHeader.getX509CertSHA256Thumbprint();
237                        x5c = jwsHeader.getX509CertChain();
238                        kid = jwsHeader.getKeyID();
239                        customParams = jwsHeader.getCustomParams();
240                }
241
242
243                /**
244                 * Sets the type ({@code typ}) parameter.
245                 *
246                 * @param typ The type parameter, {@code null} if not
247                 *            specified.
248                 *
249                 * @return This builder.
250                 */
251                public Builder type(final JOSEObjectType typ) {
252
253                        this.typ = typ;
254                        return this;
255                }
256
257
258                /**
259                 * Sets the content type ({@code cty}) parameter.
260                 *
261                 * @param cty The content type parameter, {@code null} if not
262                 *            specified.
263                 *
264                 * @return This builder.
265                 */
266                public Builder contentType(final String cty) {
267
268                        this.cty = cty;
269                        return this;
270                }
271
272
273                /**
274                 * Sets the critical header parameters ({@code crit})
275                 * parameter.
276                 *
277                 * @param crit The names of the critical header parameters,
278                 *             empty set or {@code null} if none.
279                 *
280                 * @return This builder.
281                 */
282                public Builder criticalParams(final Set<String> crit) {
283
284                        this.crit = crit;
285                        return this;
286                }
287
288
289                /**
290                 * Sets the JSON Web Key (JWK) Set URL ({@code jku}) parameter.
291                 *
292                 * @param jku The JSON Web Key (JWK) Set URL parameter,
293                 *            {@code null} if not specified.
294                 *
295                 * @return This builder.
296                 */
297                public Builder jwkURL(final URI jku) {
298
299                        this.jku = jku;
300                        return this;
301                }
302
303
304                /**
305                 * Sets the JSON Web Key (JWK) ({@code jwk}) parameter.
306                 *
307                 * @param jwk The JSON Web Key (JWK) ({@code jwk}) parameter,
308                 *            {@code null} if not specified.
309                 *
310                 * @return This builder.
311                 */
312                public Builder jwk(final JWK jwk) {
313
314                        this.jwk = jwk;
315                        return this;
316                }
317
318
319                /**
320                 * Sets the X.509 certificate URL ({@code x5u}) parameter.
321                 *
322                 * @param x5u The X.509 certificate URL parameter, {@code null}
323                 *            if not specified.
324                 *
325                 * @return This builder.
326                 */
327                public Builder x509CertURL(final URI x5u) {
328
329                        this.x5u = x5u;
330                        return this;
331                }
332
333
334                /**
335                 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t})
336                 * parameter.
337                 *
338                 * @param x5t The X.509 certificate SHA-1 thumbprint parameter,
339                 *            {@code null} if not specified.
340                 *
341                 * @return This builder.
342                 */
343                public Builder x509CertThumbprint(final Base64URL x5t) {
344
345                        this.x5t = x5t;
346                        return this;
347                }
348
349
350                /**
351                 * Sets the X.509 certificate SHA-256 thumbprint
352                 * ({@code x5t#S256}) parameter.
353                 *
354                 * @param x5t256 The X.509 certificate SHA-256 thumbprint
355                 *               parameter, {@code null} if not specified.
356                 *
357                 * @return This builder.
358                 */
359                public Builder x509CertSHA256Thumbprint(final Base64URL x5t256) {
360
361                        this.x5t256 = x5t256;
362                        return this;
363                }
364
365
366                /**
367                 * Sets the X.509 certificate chain parameter ({@code x5c})
368                 * corresponding to the key used to sign the JWS object.
369                 *
370                 * @param x5c The X.509 certificate chain parameter,
371                 *            {@code null} if not specified.
372                 *
373                 * @return This builder.
374                 */
375                public Builder x509CertChain(final List<Base64> x5c) {
376
377                        this.x5c = x5c;
378                        return this;
379                }
380
381
382                /**
383                 * Sets the key ID ({@code kid}) parameter.
384                 *
385                 * @param kid The key ID parameter, {@code null} if not
386                 *            specified.
387                 *
388                 * @return This builder.
389                 */
390                public Builder keyID(final String kid) {
391
392                        this.kid = kid;
393                        return this;
394                }
395
396
397                /**
398                 * Sets a custom (non-registered) parameter.
399                 *
400                 * @param name  The name of the custom parameter. Must not
401                 *              match a registered parameter name and must not
402                 *              be {@code null}.
403                 * @param value The value of the custom parameter, should map
404                 *              to a valid JSON entity, {@code null} if not
405                 *              specified.
406                 *
407                 * @return This builder.
408                 *
409                 * @throws IllegalArgumentException If the specified parameter
410                 *                                  name matches a registered
411                 *                                  parameter name.
412                 */
413                public Builder customParam(final String name, final Object value) {
414
415                        if (getRegisteredParameterNames().contains(name)) {
416                                throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name");
417                        }
418
419                        if (customParams == null) {
420                                customParams = new HashMap<>();
421                        }
422
423                        customParams.put(name, value);
424
425                        return this;
426                }
427
428
429                /**
430                 * Sets the custom (non-registered) parameters. The values must
431                 * be serialisable to a JSON entity, otherwise will be ignored.
432                 *
433                 * @param customParameters The custom parameters, empty map or
434                 *                         {@code null} if none.
435                 *
436                 * @return This builder.
437                 */
438                public Builder customParams(final Map<String, Object> customParameters) {
439
440                        this.customParams = customParameters;
441                        return this;
442                }
443
444
445                /**
446                 * Sets the parsed Base64URL.
447                 *
448                 * @param base64URL The parsed Base64URL, {@code null} if the
449                 *                  header is created from scratch.
450                 *
451                 * @return This builder.
452                 */
453                public Builder parsedBase64URL(final Base64URL base64URL) {
454
455                        this.parsedBase64URL = base64URL;
456                        return this;
457                }
458
459
460                /**
461                 * Builds a new JWS header.
462                 *
463                 * @return The JWS header.
464                 */
465                public JWSHeader build() {
466
467                        return new JWSHeader(
468                                alg, typ, cty, crit,
469                                jku, jwk, x5u, x5t, x5t256, x5c, kid,
470                                customParams, parsedBase64URL);
471                }
472        }
473
474
475        /**
476         * Creates a new minimal JSON Web Signature (JWS) header.
477         *
478         * <p>Note: Use {@link PlainHeader} to create a header with algorithm
479         * {@link Algorithm#NONE none}.
480         *
481         * @param alg The JWS algorithm ({@code alg}) parameter. Must not be
482         *            "none" or {@code null}.
483         */
484        public JWSHeader(final JWSAlgorithm alg) {
485
486                this(alg, null, null, null, null, null, null, null, null, null, null, null, null);
487        }
488
489
490        /**
491         * Creates a new JSON Web Signature (JWS) header.
492         *
493         * <p>Note: Use {@link PlainHeader} to create a header with algorithm
494         * {@link Algorithm#NONE none}.
495         *
496         * @param alg             The JWS algorithm ({@code alg}) parameter.
497         *                        Must not be "none" or {@code null}.
498         * @param typ             The type ({@code typ}) parameter,
499         *                        {@code null} if not specified.
500         * @param cty             The content type ({@code cty}) parameter,
501         *                        {@code null} if not specified.
502         * @param crit            The names of the critical header
503         *                        ({@code crit}) parameters, empty set or
504         *                        {@code null} if none.
505         * @param jku             The JSON Web Key (JWK) Set URL ({@code jku})
506         *                        parameter, {@code null} if not specified.
507         * @param jwk             The X.509 certificate URL ({@code jwk})
508         *                        parameter, {@code null} if not specified.
509         * @param x5u             The X.509 certificate URL parameter
510         *                        ({@code x5u}), {@code null} if not specified.
511         * @param x5t             The X.509 certificate SHA-1 thumbprint
512         *                        ({@code x5t}) parameter, {@code null} if not
513         *                        specified.
514         * @param x5t256          The X.509 certificate SHA-256 thumbprint
515         *                        ({@code x5t#S256}) parameter, {@code null} if
516         *                        not specified.
517         * @param x5c             The X.509 certificate chain ({@code x5c})
518         *                        parameter, {@code null} if not specified.
519         * @param kid             The key ID ({@code kid}) parameter,
520         *                        {@code null} if not specified.
521         * @param customParams    The custom parameters, empty map or
522         *                        {@code null} if none.
523         * @param parsedBase64URL The parsed Base64URL, {@code null} if the
524         *                        header is created from scratch.
525         */
526        public JWSHeader(final JWSAlgorithm alg,
527                         final JOSEObjectType typ,
528                         final String cty,
529                         final Set<String> crit,
530                         final URI jku,
531                         final JWK jwk,
532                         final URI x5u,
533                         final Base64URL x5t,
534                         final Base64URL x5t256,
535                         final List<Base64> x5c,
536                         final String kid,
537                         final Map<String,Object> customParams,
538                         final Base64URL parsedBase64URL) {
539
540                super(alg, typ, cty, crit, jku, jwk, x5u, x5t, x5t256, x5c, kid, customParams, parsedBase64URL);
541
542                if (alg.getName().equals(Algorithm.NONE.getName())) {
543                        throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\"");
544                }
545        }
546
547
548        /**
549         * Deep copy constructor.
550         *
551         * @param jwsHeader The JWS header to copy. Must not be {@code null}.
552         */
553        public JWSHeader(final JWSHeader jwsHeader) {
554
555                this(
556                        jwsHeader.getAlgorithm(),
557                        jwsHeader.getType(),
558                        jwsHeader.getContentType(),
559                        jwsHeader.getCriticalParams(),
560                        jwsHeader.getJWKURL(),
561                        jwsHeader.getJWK(),
562                        jwsHeader.getX509CertURL(),
563                        jwsHeader.getX509CertThumbprint(),
564                        jwsHeader.getX509CertSHA256Thumbprint(),
565                        jwsHeader.getX509CertChain(),
566                        jwsHeader.getKeyID(),
567                        jwsHeader.getCustomParams(),
568                        jwsHeader.getParsedBase64URL()
569                );
570        }
571
572
573        /**
574         * Gets the registered parameter names for JWS headers.
575         *
576         * @return The registered parameter names, as an unmodifiable set.
577         */
578        public static Set<String> getRegisteredParameterNames() {
579
580                return REGISTERED_PARAMETER_NAMES;
581        }
582
583
584        /**
585         * Gets the algorithm ({@code alg}) parameter.
586         *
587         * @return The algorithm parameter.
588         */
589        @Override
590        public JWSAlgorithm getAlgorithm() {
591
592                return (JWSAlgorithm)super.getAlgorithm();
593        }
594
595
596        /**
597         * Parses a JWS header from the specified JSON object.
598         *
599         * @param jsonObject The JSON object to parse. Must not be
600         *                   {@code null}.
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                throws ParseException {
609
610                return parse(jsonObject, null);
611        }
612
613
614        /**
615         * Parses a JWS header from the specified JSON object.
616         *
617         * @param jsonObject      The JSON object to parse. Must not be
618         *                        {@code null}.
619         * @param parsedBase64URL The original parsed Base64URL, {@code null}
620         *                        if not applicable.
621         *
622         * @return The JWS header.
623         *
624         * @throws ParseException If the specified JSON object doesn't 
625         *                        represent a valid JWS header.
626         */
627        public static JWSHeader parse(final JSONObject jsonObject,
628                                      final Base64URL parsedBase64URL)
629                throws ParseException {
630
631                // Get the "alg" parameter
632                Algorithm alg = Header.parseAlgorithm(jsonObject);
633
634                if (! (alg instanceof JWSAlgorithm)) {
635                        throw new ParseException("The algorithm \"alg\" header parameter must be for signatures", 0);
636                }
637
638                JWSHeader.Builder header = new Builder((JWSAlgorithm)alg).parsedBase64URL(parsedBase64URL);
639
640                // Parse optional + custom parameters
641                for (final String name: jsonObject.keySet()) {
642
643                        if("alg".equals(name)) {
644                                // skip
645                        } else if("typ".equals(name)) {
646                                header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name)));
647                        } else if("cty".equals(name)) {
648                                header = header.contentType(JSONObjectUtils.getString(jsonObject, name));
649                        } else if("crit".equals(name)) {
650                                header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name)));
651                        } else if("jku".equals(name)) {
652                                header = header.jwkURL(JSONObjectUtils.getURI(jsonObject, name));
653                        } else if("jwk".equals(name)) {
654                                header = header.jwk(JWK.parse(JSONObjectUtils.getJSONObject(jsonObject, name)));
655                        } else if("x5u".equals(name)) {
656                                header = header.x509CertURL(JSONObjectUtils.getURI(jsonObject, name));
657                        } else if("x5t".equals(name)) {
658                                header = header.x509CertThumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name)));
659                        } else if("x5t#S256".equals(name)) {
660                                header = header.x509CertSHA256Thumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name)));
661                        } else if("x5c".equals(name)) {
662                                header = header.x509CertChain(X509CertChainUtils.parseX509CertChain(JSONObjectUtils.getJSONArray(jsonObject, name)));
663                        } else if("kid".equals(name)) {
664                                header = header.keyID(JSONObjectUtils.getString(jsonObject, name));
665                        } else {
666                                header = header.customParam(name, jsonObject.get(name));
667                        }
668                }
669
670                return header.build();
671        }
672
673
674        /**
675         * Parses a JWS header from the specified JSON object string.
676         *
677         * @param jsonString The JSON string to parse. Must not be
678         *                   {@code null}.
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                throws ParseException {
687
688                return parse(jsonString, null);
689        }
690
691
692        /**
693         * Parses a JWS header from the specified JSON object string.
694         *
695         * @param jsonString      The JSON string to parse. Must not be
696         *                        {@code null}.
697         * @param parsedBase64URL The original parsed Base64URL, {@code null}
698         *                        if not applicable.
699         *
700         * @return The JWS header.
701         *
702         * @throws ParseException If the specified JSON object string doesn't 
703         *                        represent a valid JWS header.
704         */
705        public static JWSHeader parse(final String jsonString,
706                                      final Base64URL parsedBase64URL)
707                throws ParseException {
708
709                return parse(JSONObjectUtils.parse(jsonString), parsedBase64URL);
710        }
711
712
713        /**
714         * Parses a JWS header from the specified Base64URL.
715         *
716         * @param base64URL The Base64URL to parse. Must not be {@code null}.
717         *
718         * @return The JWS header.
719         *
720         * @throws ParseException If the specified Base64URL doesn't represent
721         *                        a valid JWS header.
722         */
723        public static JWSHeader parse(final Base64URL base64URL)
724                throws ParseException {
725
726                return parse(base64URL.decodeToString(), base64URL);
727        }
728}