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