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