001package com.nimbusds.jose;
002
003
004import java.text.ParseException;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.Set;
008
009import net.minidev.json.JSONObject;
010
011import com.nimbusds.jose.jwk.ECKey;
012import com.nimbusds.jose.jwk.JWK;
013import com.nimbusds.jose.util.Base64URL;
014import com.nimbusds.jose.util.JSONObjectUtils;
015
016
017/**
018 * JSON Web Encryption (JWE) header.
019 *
020 * <p>Supports all {@link #getReservedParameterNames reserved header parameters}
021 * of the JWE specification:
022 *
023 * <ul>
024 *     <li>alg
025 *     <li>enc
026 *     <li>epk
027 *     <li>zip
028 *     <li>jku
029 *     <li>jwk
030 *     <li>x5u
031 *     <li>x5t
032 *     <li>x5c
033 *     <li>kid
034 *     <li>typ
035 *     <li>cty
036 *     <li>apu
037 *     <li>apv
038 *     <li>epu
039 *     <li>epv
040 * </ul>
041 *
042 * <p>The header may also carry {@link #setCustomParameters custom parameters};
043 * these will be serialised and parsed along the reserved ones.
044 *
045 * <p>Example header:
046 *
047 * <pre>
048 * { 
049 *   "alg" : "RSA1_5",
050 *   "enc" : "A128CBC+HS256"
051 * }
052 * </pre>
053 *
054 * @author Vladimir Dzhuvinov
055 * @version $version$ (2013-01-08)
056 */
057public class JWEHeader extends CommonSEHeader implements ReadOnlyJWEHeader {
058
059
060        /**
061         * The reserved parameter names.
062         */
063        private static final Set<String> RESERVED_PARAMETER_NAMES;
064
065
066        /**
067         * Initialises the reserved parameter name set.
068         */
069        static {
070                Set<String> p = new HashSet<String>();
071
072                p.add("alg");
073                p.add("enc");
074                p.add("epk");
075                p.add("zip");
076                p.add("jku");
077                p.add("jwk");
078                p.add("x5u");
079                p.add("x5t");
080                p.add("x5c");
081                p.add("kid");
082                p.add("typ");
083                p.add("cty");
084                p.add("apu");
085                p.add("apv");
086                p.add("epu");
087                p.add("epv");
088
089                RESERVED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
090        }
091
092
093        /**
094         * The encryption method ({@code enc}) parameter.
095         */
096        private EncryptionMethod enc;
097
098
099        /**
100         * The ephemeral public key ({@code epk}) parameter.
101         */
102        private ECKey epk;
103
104
105        /**
106         * The compression algorithm ({@code zip}) parameter.
107         */
108        private CompressionAlgorithm zip;
109
110
111        /**
112         * The agreement PartyUInfo ({@code apu}) parameter.
113         */
114        private Base64URL apu;
115
116
117        /**
118         * The agreement PartyVInfo ({@code apv}) parameter.
119         */
120        private Base64URL apv;
121
122
123        /**
124         * The encryption PartyUInfo ({@code epu}) parameter.
125         */
126        private Base64URL epu;
127
128
129        /**
130         * The encryption PartyUInfo ({@code epv}) parameter.
131         */
132        private Base64URL epv;
133
134
135        /**
136         * Creates a new JSON Web Encryption (JWE) header.
137         *
138         * @param alg The JWE algorithm parameter. Must not be {@code null}.
139         * @param enc The encryption method parameter. Must not be {@code null}.
140         */
141        public JWEHeader(final JWEAlgorithm alg, final EncryptionMethod enc) {
142
143                super(alg);
144
145                if (enc == null) {
146                        throw new IllegalArgumentException("The encryption method \"enc\" parameter must not be null");
147                }
148
149                this.enc = enc;
150        }
151
152
153        /**
154         * Gets the reserved parameter names for JWE headers.
155         *
156         * @return The reserved parameter names, as an unmodifiable set.
157         */
158        public static Set<String> getReservedParameterNames() {
159
160                return RESERVED_PARAMETER_NAMES;
161        }
162
163
164        @Override
165        public JWEAlgorithm getAlgorithm() {
166
167                return (JWEAlgorithm)alg;
168        }
169
170
171        @Override
172        public EncryptionMethod getEncryptionMethod() {
173
174                return enc;
175        }
176
177
178        @Override
179        public ECKey getEphemeralPublicKey() {
180
181                return epk;
182        }
183
184
185        /**
186         * Sets the Ephemeral Public Key ({@code epk}) parameter.
187         *
188         * @param epk The Ephemeral Public Key parameter, {@code null} if not 
189         *            specified.
190         */
191        public void setEphemeralPublicKey(final ECKey epk) {
192
193                this.epk = epk;
194        }
195
196
197        @Override
198        public CompressionAlgorithm getCompressionAlgorithm() {
199
200                return zip;
201        }
202
203
204        /**
205         * Sets the compression algorithm ({@code zip}) parameter.
206         *
207         * @param zip The compression algorithm parameter, {@code null} if not 
208         *            specified.
209         */
210        public void setCompressionAlgorithm(final CompressionAlgorithm zip) {
211
212                this.zip = zip;
213        }
214
215
216        @Override
217        public Base64URL getAgreementPartyUInfo() {
218
219                return apu;
220        }
221
222
223        /**
224         * Sets the agreement PartyUInfo ({@code apu}) parameter.
225         *
226         * @param apu The agreement PartyUInfo parameter, {@code null} if not
227         *            specified.
228         */
229        public void setAgreementPartyUInfo(final Base64URL apu) {
230
231                this.apu = apu;
232        }
233
234
235        @Override
236        public Base64URL getAgreementPartyVInfo() {
237
238                return apv;
239        }
240
241
242        /**
243         * Sets the agreement PartyVInfo ({@code apv}) parameter.
244         *
245         * @param apv The agreement PartyVInfo parameter, {@code null} if not
246         *            specified.
247         */
248        public void setAgreementPartyVInfo(final Base64URL apv) {
249
250                this.apv = apv;
251        }
252
253
254        @Override
255        public Base64URL getEncryptionPartyUInfo() {
256
257                return epu;
258        }
259
260
261        /**
262         * Sets the encryption PartyUInfo ({@code epu}) parameter.
263         *
264         * @param epu The encryption PartyUInfo parameter, {@code null} if not
265         *            specified.
266         */
267        public void setEncryptionPartyUInfo(final Base64URL epu) {
268
269                this.epu = epu;
270        }
271
272
273        @Override
274        public Base64URL getEncryptionPartyVInfo() {
275
276                return epv;
277        }
278
279
280        /**
281         * Sets the encryption PartyVInfo ({@code epv}) parameter.
282         *
283         * @param epv The encryption PartyVInfo parameter, {@code null} if not
284         *            specified.
285         */
286        public void setEncryptionPartyVInfo(final Base64URL epv) {
287
288                this.epv = epv;
289        }
290
291
292        /**
293         * @throws IllegalArgumentException If the specified parameter name
294         *                                  matches a reserved parameter name.
295         */
296        @Override
297        public void setCustomParameter(final String name, final Object value) {
298
299                if (getReservedParameterNames().contains(name)) {
300                        throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a reserved name");
301                }
302
303                super.setCustomParameter(name, value);
304        }
305
306
307        @Override
308        public Set<String> getIncludedParameters() {
309
310                Set<String> includedParameters = 
311                                new HashSet<String>(getCustomParameters().keySet());
312
313                includedParameters.add("alg");
314                includedParameters.add("enc");
315
316                if (getEphemeralPublicKey() != null) {
317                        includedParameters.add("epk");
318                }
319
320                if (getCompressionAlgorithm() != null) {
321                        includedParameters.add("zip");
322                }
323
324                if (getType() != null) {
325                        includedParameters.add("typ");
326                }
327
328                if (getContentType() != null) {
329                        includedParameters.add("cty");
330                }
331
332                if (getJWKURL() != null) {
333                        includedParameters.add("jku");
334                }
335
336                if (getJWK() != null) {
337                        includedParameters.add("jwk");
338                }
339
340                if (getX509CertURL() != null) {
341                        includedParameters.add("x5u");
342                }
343
344                if (getX509CertThumbprint() != null) {
345                        includedParameters.add("x5t");
346                }
347
348                if (getX509CertChain() != null) {
349                        includedParameters.add("x5c");
350                }
351
352                if (getKeyID() != null) {
353                        includedParameters.add("kid");
354                }
355
356                if (getAgreementPartyUInfo() != null) {
357                        includedParameters.add("apu");
358                }
359
360                if (getAgreementPartyVInfo() != null) {
361                        includedParameters.add("apv");
362                }
363
364                if (getEncryptionPartyUInfo() != null) {
365                        includedParameters.add("epu");
366                }
367
368                if (getEncryptionPartyVInfo() != null) {
369                        includedParameters.add("epv");
370                }
371
372                return includedParameters;
373        }
374
375
376        @Override
377        public JSONObject toJSONObject() {
378
379                JSONObject o = super.toJSONObject();
380
381                if (enc != null) {
382                        o.put("enc", enc.toString());
383                }
384
385                if (epk != null) {
386                        o.put("epk", epk.toJSONObject());
387                }
388
389                if (zip != null) {
390                        o.put("zip", zip.toString());
391                }
392
393                if (apu != null) {
394                        o.put("apu", apu.toString());
395                }
396
397                if (apv != null) {
398                        o.put("apv", apv.toString());
399                }
400
401                if (epu != null) {
402                        o.put("epu", epu.toString());
403                }
404
405                if (epv != null) {
406                        o.put("epv", epv.toString());
407                }
408
409                return o;
410        }
411
412
413        /**
414         * Parses an encryption method ({@code enc}) parameter from the 
415         * specified JWE header JSON object.
416         *
417         * @param json The JSON object to parse. Must not be {@code null}.
418         *
419         * @return The encryption method.
420         *
421         * @throws ParseException If the {@code enc} parameter couldn't be 
422         *                        parsed.
423         */
424        private static EncryptionMethod parseEncryptionMethod(final JSONObject json)
425                        throws ParseException {
426
427                return EncryptionMethod.parse(JSONObjectUtils.getString(json, "enc"));
428        }
429
430
431        /**
432         * Parses a JWE header from the specified JSON object.
433         *
434         * @param json The JSON object to parse. Must not be {@code null}.
435         *
436         * @return The JWE header.
437         *
438         * @throws ParseException If the specified JSON object doesn't 
439         *                        represent a valid JWE header.
440         */
441        public static JWEHeader parse(final JSONObject json)
442                        throws ParseException {
443
444                // Get the "alg" parameter
445                Algorithm alg = Header.parseAlgorithm(json);
446
447                if (! (alg instanceof JWEAlgorithm)) {
448                        throw new ParseException("The algorithm \"alg\" header parameter must be for encryption", 0);
449                }
450
451                // Get the "enc" parameter
452                EncryptionMethod enc = parseEncryptionMethod(json);
453
454                // Create a minimal header
455                JWEHeader h = new JWEHeader((JWEAlgorithm)alg, enc);
456
457                // Parse optional + custom parameters
458                for(final String name: json.keySet()) {
459
460                        if (name.equals("alg")) {
461                                continue; // skip
462                        } else if (name.equals("enc")) {
463                                continue; // skip
464                        } else if (name.equals("epk")) {
465                                h.setEphemeralPublicKey(ECKey.parse(JSONObjectUtils.getJSONObject(json, name)));
466                        } else if (name.equals("zip")) {
467                                h.setCompressionAlgorithm(new CompressionAlgorithm(JSONObjectUtils.getString(json, name)));
468                        } else if (name.equals("typ")) {
469                                h.setType(new JOSEObjectType(JSONObjectUtils.getString(json, name)));
470                        } else if (name.equals("cty")) {
471                                h.setContentType(JSONObjectUtils.getString(json, name));
472                        } else if (name.equals("jku")) {
473                                h.setJWKURL(JSONObjectUtils.getURL(json, name));
474                        } else if (name.equals("jwk")) {
475                                h.setJWK(JWK.parse(JSONObjectUtils.getJSONObject(json, name)));
476                        } else if (name.equals("x5u")) {
477                                h.setX509CertURL(JSONObjectUtils.getURL(json, name));
478                        } else if (name.equals("x5t")) {
479                                h.setX509CertThumbprint(new Base64URL(JSONObjectUtils.getString(json, name)));
480                        } else if (name.equals("x5c")) {
481                                h.setX509CertChain(CommonSEHeader.parseX509CertChain(JSONObjectUtils.getJSONArray(json, name)));
482                        } else if (name.equals("kid")) {
483                                h.setKeyID(JSONObjectUtils.getString(json, name));
484                        } else if (name.equals("apu")) {
485                                h.setAgreementPartyUInfo(new Base64URL(JSONObjectUtils.getString(json, name)));
486                        } else if (name.equals("apv")) {
487                                h.setAgreementPartyVInfo(new Base64URL(JSONObjectUtils.getString(json, name)));
488                        } else if (name.equals("epu")) {
489                                h.setEncryptionPartyUInfo(new Base64URL(JSONObjectUtils.getString(json, name)));
490                        } else if (name.equals("epv")) {
491                                h.setEncryptionPartyVInfo(new Base64URL(JSONObjectUtils.getString(json, name)));
492                        } else {
493                                h.setCustomParameter(name, json.get(name));
494                        }
495
496                }
497
498                return h;
499        }
500
501
502        /**
503         * Parses a JWE header from the specified JSON string.
504         *
505         * @param s The JSON string to parse. Must not be {@code null}.
506         *
507         * @return The JWE header.
508         *
509         * @throws ParseException If the specified JSON object string doesn't 
510         *                        represent a valid JWE header.
511         */
512        public static JWEHeader parse(final String s)
513                        throws ParseException {
514
515                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(s);
516
517                return parse(jsonObject);
518        }
519
520
521        /**
522         * Parses a JWE header from the specified Base64URL.
523         *
524         * @param base64URL The Base64URL to parse. Must not be {@code null}.
525         *
526         * @return The JWE header.
527         *
528         * @throws ParseException If the specified Base64URL doesn't represent a 
529         *                        valid JWE header.
530         */
531        public static JWEHeader parse(final Base64URL base64URL)
532                        throws ParseException {
533
534                return parse(base64URL.decodeToString());
535        }
536}