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