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