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.io.Serializable;
022import java.text.ParseException;
023import java.util.*;
024
025import net.minidev.json.JSONArray;
026import net.minidev.json.JSONObject;
027
028import com.nimbusds.jose.util.Base64URL;
029import com.nimbusds.jose.util.JSONObjectUtils;
030
031
032/**
033 * The base abstract class for unsecured ({@code alg=none}), JSON Web Signature
034 * (JWS) and JSON Web Encryption (JWE) headers.
035 *
036 * <p>The header may also include {@link #getCustomParams custom
037 * parameters}; these will be serialised and parsed along the registered ones.
038 *
039 * @author Vladimir Dzhuvinov
040 * @version 2019-10-04
041 */
042public abstract class Header implements Serializable {
043
044
045        private static final long serialVersionUID = 1L;
046
047
048        /**
049         * The algorithm ({@code alg}) parameter.
050         */
051        private final Algorithm alg;
052
053
054        /**
055         * The JOSE object type ({@code typ}) parameter.
056         */
057        private final JOSEObjectType typ;
058
059
060        /**
061         * The content type ({@code cty}) parameter.
062         */
063        private final String cty;
064
065
066        /**
067         * The critical headers ({@code crit}) parameter.
068         */
069        private final Set<String> crit;
070
071
072        /**
073         * Custom header parameters.
074         */
075        private final Map<String,Object> customParams;
076
077
078        /**
079         * Empty custom parameters constant.
080         */
081        private static final Map<String,Object> EMPTY_CUSTOM_PARAMS =
082                Collections.unmodifiableMap(new HashMap<String,Object>());
083
084
085        /**
086         * The original parsed Base64URL, {@code null} if the header was 
087         * created from scratch.
088         */
089        private final Base64URL parsedBase64URL;
090
091
092        /**
093         * Creates a new abstract header.
094         *
095         * @param alg             The algorithm ({@code alg}) parameter. Must
096         *                        not be {@code null}.
097         * @param typ             The type ({@code typ}) parameter,
098         *                        {@code null} if not specified.
099         * @param cty             The content type ({@code cty}) parameter,
100         *                        {@code null} if not specified.
101         * @param crit            The names of the critical header
102         *                        ({@code crit}) parameters, empty set or
103         *                        {@code null} if none.
104         * @param customParams    The custom parameters, empty map or
105         *                        {@code null} if none.
106         * @param parsedBase64URL The parsed Base64URL, {@code null} if the
107         *                        header is created from scratch.
108         */
109        protected Header(final Algorithm alg,
110                         final JOSEObjectType typ,
111                         final String cty, Set<String> crit,
112                         final Map<String,Object> customParams,
113                         final Base64URL parsedBase64URL) {
114
115                if (alg == null) {
116                        throw new IllegalArgumentException("The algorithm \"alg\" header parameter must not be null");
117                }
118
119                this.alg = alg;
120
121                this.typ = typ;
122                this.cty = cty;
123
124                if (crit != null) {
125                        // Copy and make unmodifiable
126                        this.crit = Collections.unmodifiableSet(new HashSet<>(crit));
127                } else {
128                        this.crit = null;
129                }
130
131                if (customParams != null) {
132                        // Copy and make unmodifiable
133                        this.customParams = Collections.unmodifiableMap(new HashMap<>(customParams));
134                } else {
135                        this.customParams = EMPTY_CUSTOM_PARAMS;
136                }
137
138                this.parsedBase64URL = parsedBase64URL;
139        }
140
141
142        /**
143         * Deep copy constructor.
144         *
145         * @param header The header to copy. Must not be {@code null}.
146         */
147        protected Header(final Header header) {
148
149                this(
150                        header.getAlgorithm(),
151                        header.getType(),
152                        header.getContentType(),
153                        header.getCriticalParams(),
154                        header.getCustomParams(),
155                        header.getParsedBase64URL());
156        }
157
158
159        /**
160         * Gets the algorithm ({@code alg}) parameter.
161         *
162         * @return The algorithm parameter.
163         */
164        public Algorithm getAlgorithm() {
165
166                return alg;
167        }
168
169
170        /**
171         * Gets the type ({@code typ}) parameter.
172         *
173         * @return The type parameter, {@code null} if not specified.
174         */
175        public JOSEObjectType getType() {
176
177                return typ;
178        }
179
180
181        /**
182         * Gets the content type ({@code cty}) parameter.
183         *
184         * @return The content type parameter, {@code null} if not specified.
185         */
186        public String getContentType() {
187
188                return cty;
189        }
190
191
192        /**
193         * Gets the critical header parameters ({@code crit}) parameter.
194         *
195         * @return The names of the critical header parameters, as a
196         *         unmodifiable set, {@code null} if not specified.
197         */
198        public Set<String> getCriticalParams() {
199
200                return crit;
201        }
202
203
204        /**
205         * Gets a custom (non-registered) parameter.
206         *
207         * @param name The name of the custom parameter. Must not be
208         *             {@code null}.
209         *
210         * @return The custom parameter, {@code null} if not specified.
211         */
212        public Object getCustomParam(final String name) {
213
214                return customParams.get(name);
215        }
216
217
218        /**
219         * Gets the custom (non-registered) parameters.
220         *
221         * @return The custom parameters, as a unmodifiable map, empty map if
222         *         none.
223         */
224        public Map<String,Object> getCustomParams() {
225
226                return customParams;
227        }
228
229
230        /**
231         * Gets the original Base64URL used to create this header.
232         *
233         * @return The parsed Base64URL, {@code null} if the header was created
234         *         from scratch.
235         */
236        public Base64URL getParsedBase64URL() {
237
238                return parsedBase64URL;
239        }
240
241
242        /**
243         * Gets the names of all included parameters (registered and custom) in
244         * the header instance.
245         *
246         * @return The included parameters.
247         */
248        public Set<String> getIncludedParams() {
249
250                Set<String> includedParameters =
251                        new HashSet<>(getCustomParams().keySet());
252
253                includedParameters.add("alg");
254
255                if (getType() != null) {
256                        includedParameters.add("typ");
257                }
258
259                if (getContentType() != null) {
260                        includedParameters.add("cty");
261                }
262
263                if (getCriticalParams() != null && ! getCriticalParams().isEmpty()) {
264                        includedParameters.add("crit");
265                }
266
267                return includedParameters;
268        }
269
270
271        /**
272         * Returns a JSON object representation of the header. All custom
273         * parameters are included if they serialise to a JSON entity and
274         * their names don't conflict with the registered ones.
275         *
276         * @return The JSON object representation of the header.
277         */
278        public JSONObject toJSONObject() {
279
280                // Include custom parameters, they will be overwritten if their
281                // names match specified registered ones
282                JSONObject o = new JSONObject(customParams);
283
284                // Alg is always defined
285                o.put("alg", alg.toString());
286
287                if (typ != null) {
288                        o.put("typ", typ.toString());
289                }
290
291                if (cty != null) {
292                        o.put("cty", cty);
293                }
294
295                if (crit != null && ! crit.isEmpty()) {
296                        JSONArray jsonArray = new JSONArray();
297                        for (String c: crit) {
298                                jsonArray.add(c);
299                        }
300                        o.put("crit", jsonArray);
301                }
302
303                return o;
304        }
305
306
307        /**
308         * Returns a JSON string representation of the header. All custom
309         * parameters will be included if they serialise to a JSON entity and
310         * their names don't conflict with the registered ones.
311         *
312         * @return The JSON string representation of the header.
313         */
314        public String toString() {
315
316                return toJSONObject().toString();
317        }
318
319
320        /**
321         * Returns a Base64URL representation of the header. If the header was
322         * parsed always returns the original Base64URL (required for JWS
323         * validation and authenticated JWE decryption).
324         *
325         * @return The original parsed Base64URL representation of the header,
326         *         or a new Base64URL representation if the header was created
327         *         from scratch.
328         */
329        public Base64URL toBase64URL() {
330
331                if (parsedBase64URL == null) {
332
333                        // Header was created from scratch, return new Base64URL
334                        return Base64URL.encode(toString());
335
336                } else {
337
338                        // Header was parsed, return original Base64URL
339                        return parsedBase64URL;
340                }
341        }
342
343
344        /**
345         * Parses an algorithm ({@code alg}) parameter from the specified 
346         * header JSON object. Intended for initial parsing of unsecured
347         * (plain), JWS and JWE headers.
348         *
349         * <p>The algorithm type (none, JWS or JWE) is determined by inspecting
350         * the algorithm name for "none" and the presence of an "enc"
351         * parameter.
352         *
353         * @param json The JSON object to parse. Must not be {@code null}.
354         *
355         * @return The algorithm, an instance of {@link Algorithm#NONE},
356         *         {@link JWSAlgorithm} or {@link JWEAlgorithm}. {@code null}
357         *         if not found.
358         *
359         * @throws ParseException If the {@code alg} parameter couldn't be 
360         *                        parsed.
361         */
362        public static Algorithm parseAlgorithm(final JSONObject json)
363                throws ParseException {
364
365                String algName = JSONObjectUtils.getString(json, "alg");
366                
367                if (algName == null) {
368                        throw new ParseException("Missing \"alg\" in header JSON object", 0);
369                }
370
371                // Infer algorithm type
372                if (algName.equals(Algorithm.NONE.getName())) {
373                        // Plain
374                        return Algorithm.NONE;
375                } else if (json.containsKey("enc")) {
376                        // JWE
377                        return JWEAlgorithm.parse(algName);
378                } else {
379                        // JWS
380                        return JWSAlgorithm.parse(algName);
381                }
382        }
383
384
385        /**
386         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
387         * from the specified JSON object.
388         *
389         * @param jsonObject      The JSON object to parse. Must not be
390         *                        {@code null}.
391         *
392         * @return The header.
393         *
394         * @throws ParseException If the specified JSON object doesn't
395         *                        represent a valid header.
396         */
397        public static Header parse(final JSONObject jsonObject)
398                throws ParseException {
399
400                return parse(jsonObject, null);
401        }
402
403
404        /**
405         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 
406         * from the specified JSON object.
407         *
408         * @param jsonObject      The JSON object to parse. Must not be
409         *                        {@code null}.
410         * @param parsedBase64URL The original parsed Base64URL, {@code null}
411         *                        if not applicable.
412         *
413         * @return The header.
414         *
415         * @throws ParseException If the specified JSON object doesn't 
416         *                        represent a valid header.
417         */
418        public static Header parse(final JSONObject jsonObject,
419                                   final Base64URL parsedBase64URL)
420                throws ParseException {
421
422                Algorithm alg = parseAlgorithm(jsonObject);
423
424                if (alg.equals(Algorithm.NONE)) {
425
426                        return PlainHeader.parse(jsonObject, parsedBase64URL);
427
428                } else if (alg instanceof JWSAlgorithm) {
429
430                        return JWSHeader.parse(jsonObject, parsedBase64URL);
431
432                } else if (alg instanceof JWEAlgorithm) {
433
434                        return JWEHeader.parse(jsonObject, parsedBase64URL);
435
436                } else {
437
438                        throw new AssertionError("Unexpected algorithm type: " + alg);
439                }
440        }
441
442
443        /**
444         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
445         * from the specified JSON object string.
446         *
447         * @param jsonString      The JSON object string to parse. Must not be
448         *                        {@code null}.
449         *
450         * @return The header.
451         *
452         * @throws ParseException If the specified JSON object string doesn't
453         *                        represent a valid header.
454         */
455        public static Header parse(final String jsonString)
456                throws ParseException {
457
458                return parse(jsonString, null);
459        }
460
461
462        /**
463         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
464         * from the specified JSON object string.
465         *
466         * @param jsonString      The JSON object string to parse. Must not be
467         *                        {@code null}.
468         * @param parsedBase64URL The original parsed Base64URL, {@code null}
469         *                        if not applicable.
470         *
471         * @return The header.
472         *
473         * @throws ParseException If the specified JSON object string doesn't
474         *                        represent a valid header.
475         */
476        public static Header parse(final String jsonString,
477                                   final Base64URL parsedBase64URL)
478                throws ParseException {
479
480                JSONObject jsonObject = JSONObjectUtils.parse(jsonString);
481
482                return parse(jsonObject, parsedBase64URL);
483        }
484
485
486        /**
487         * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader}
488         * from the specified Base64URL.
489         *
490         * @param base64URL The Base64URL to parse. Must not be {@code null}.
491         *
492         * @return The header.
493         *
494         * @throws ParseException If the specified Base64URL doesn't represent
495         *                        a valid header.
496         */
497        public static Header parse(final Base64URL base64URL)
498                throws ParseException {
499
500                return parse(base64URL.decodeToString(), base64URL);
501        }
502}