001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    import java.util.Collections;
006    import java.util.HashSet;
007    import java.util.Set;
008    
009    import net.minidev.json.JSONObject;
010    
011    import com.nimbusds.jose.jwk.ECKey;
012    import com.nimbusds.jose.jwk.JWK;
013    import com.nimbusds.jose.util.Base64URL;
014    import 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-04-15)
056     */
057    public 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                    JWEHeader header = parse(base64URL.decodeToString());
535                    header.setParsedBase64URL(base64URL);
536                    return header;
537            }
538    }