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