001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2021, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose;
019
020import com.nimbusds.jose.util.Base64URL;
021import com.nimbusds.jose.util.JSONArrayUtils;
022import com.nimbusds.jose.util.JSONObjectUtils;
023import net.jcip.annotations.ThreadSafe;
024import sun.reflect.generics.reflectiveObjects.NotImplementedException;
025
026import java.text.ParseException;
027import java.util.List;
028import java.util.Map;
029
030/**
031 * JSON Web Encryption (JWE) secured object.
032 *
033 * Provides <a href="https://datatracker.ietf.org/doc/html/rfc7516#section-7.2">JWE JSON Serialization</a>
034 *
035 * This class is thread-safe.
036 *
037 * @author Alexander Martynov
038 * @version 2021-08-17
039 */
040@ThreadSafe
041public class JWEObjectJSON extends JOSEObject implements JSONSerializable {
042
043    private static final long serialVersionUID = 1L;
044
045    /**
046     * Enumeration of the states of a JSON Web Encryption (JWE) object.
047     */
048    public enum State {
049
050
051        /**
052         * The JWE object is created but not encrypted yet.
053         */
054        UNENCRYPTED,
055
056
057        /**
058         * The JWE object is encrypted.
059         */
060        ENCRYPTED,
061
062
063        /**
064         * The JWE object is decrypted.
065         */
066        DECRYPTED
067    }
068
069
070    /**
071     * The header.
072     */
073    private JWEHeader header;
074
075
076    /**
077     * The recipients, {@code null} if not computed or applicable.
078     */
079    private List<JWERecipient> recipients;
080
081
082    /**
083     * The initialisation vector, {@code null} if not generated or 
084     * applicable.
085     */
086    private Base64URL iv;
087
088
089    /**
090     * The cipher text, {@code null} if not computed.
091     */
092    private Base64URL cipherText;
093
094
095    /**
096     * The authentication tag, {@code null} if not computed or applicable.
097     */
098    private Base64URL authTag;
099
100
101    /**
102     * The JWE object state.
103     */
104    private State state;
105
106
107    /**
108     * Creates a new to-be-encrypted JSON Web Encryption (JWE) object with 
109     * the specified header and payload. The initial state will be 
110     * {@link JWEObjectJSON.State#UNENCRYPTED unencrypted}.
111     *
112     * @param header  The JWE header. Must not be {@code null}.
113     * @param payload The payload. Must not be {@code null}.
114     */
115    public JWEObjectJSON(final JWEHeader header, final Payload payload) {
116
117        this.header = header;
118
119        if (payload == null) {
120
121            throw new IllegalArgumentException("The payload must not be null");
122        }
123
124        setPayload(payload);
125
126        recipients = null;
127
128        cipherText = null;
129
130        state = JWEObjectJSON.State.UNENCRYPTED;
131    }
132
133    /**
134     * Creates a new encrypted JSON Web Encryption (JWE) object with the
135     * specified serialised parts. The state will be {@link JWEObject.State#ENCRYPTED
136     * encrypted}.
137     *
138     * @param header     The JWE Protected header. Must not be {@code null}.
139     * @param recipients The recipients array. Empty or {@code null} if none.
140     * @param iv         The initialisation vector. Empty or {@code null} if none.
141     * @param ciphertext The cipher text. Must not be {@code null}.
142     * @param tag        The authentication tag. Empty of {@code null} if none.
143     *
144     * @throws ParseException If parsing of the serialised parts failed.
145     */
146    public JWEObjectJSON(final Base64URL header,
147                         final List<JWERecipient> recipients,
148                         final Base64URL iv,
149                         final Base64URL ciphertext,
150                         final Base64URL tag)
151            throws ParseException {
152
153        if (header == null) {
154
155            throw new IllegalArgumentException("The header must not be null");
156        }
157
158        try {
159            this.header = JWEHeader.parse(header);
160
161        } catch (ParseException e) {
162
163            throw new ParseException("Invalid JWE header: " + e.getMessage(), 0);
164        }
165
166        if (recipients == null || recipients.isEmpty()) {
167
168            this.recipients = null;
169
170        } else {
171
172            this.recipients = recipients;
173        }
174
175        if (iv == null || iv.toString().isEmpty()) {
176
177            this.iv = null;
178
179        } else {
180
181            this.iv = iv;
182        }
183
184        if (ciphertext == null) {
185
186            throw new IllegalArgumentException("The ciphertext must not be null");
187        }
188
189        this.cipherText = ciphertext;
190
191        if (tag == null || tag.toString().isEmpty()) {
192
193            authTag = null;
194
195        } else {
196
197            authTag = tag;
198        }
199
200        state = JWEObjectJSON.State.ENCRYPTED; // but not decrypted yet!
201    }
202
203
204    @Override
205    public JWEHeader getHeader() {
206
207        return header;
208    }
209
210
211    /**
212     * Returns the recipients of this JWE object.
213     *
214     * @return The recipients, {@code null} if not
215     *         applicable or the JWE object has not been encrypted yet.
216     */
217    public List<JWERecipient> getRecipients() {
218
219        return recipients;
220    }
221
222
223    /**
224     * Returns the initialisation vector (IV) of this JWE object.
225     *
226     * @return The initialisation vector (IV), {@code null} if not 
227     *         applicable or the JWE object has not been encrypted yet.
228     */
229    public Base64URL getIV() {
230
231        return iv;
232    }
233
234
235    /**
236     * Returns the cipher text of this JWE object.
237     *
238     * @return The cipher text, {@code null} if the JWE object has not been
239     *         encrypted yet.
240     */
241    public Base64URL getCipherText() {
242
243        return cipherText;
244    }
245
246
247    /**
248     * Returns the authentication tag of this JWE object.
249     *
250     * @return The authentication tag, {@code null} if not applicable or
251     *         the JWE object has not been encrypted yet.
252     */
253    public Base64URL getAuthTag() {
254
255        return authTag;
256    }
257
258
259    /**
260     * Returns the state of this JWE object.
261     *
262     * @return The state.
263     */
264    public JWEObjectJSON.State getState() {
265
266        return state;
267    }
268
269
270    /**
271     * Ensures the current state is {@link JWEObjectJSON.State#UNENCRYPTED unencrypted}.
272     *
273     * @throws IllegalStateException If the current state is not 
274     *                               unencrypted.
275     */
276    private void ensureUnencryptedState() {
277
278        if (state != JWEObjectJSON.State.UNENCRYPTED) {
279
280            throw new IllegalStateException("The JWE object must be in an unencrypted state");
281        }
282    }
283
284
285    /**
286     * Ensures the current state is {@link JWEObjectJSON.State#ENCRYPTED encrypted}.
287     *
288     * @throws IllegalStateException If the current state is not encrypted.
289     */
290    private void ensureEncryptedState() {
291
292        if (state != JWEObjectJSON.State.ENCRYPTED) {
293
294            throw new IllegalStateException("The JWE object must be in an encrypted state");
295        }
296    }
297
298
299    /**
300     * Ensures the current state is {@link JWEObjectJSON.State#ENCRYPTED encrypted} or
301     * {@link JWEObjectJSON.State#DECRYPTED decrypted}.
302     *
303     * @throws IllegalStateException If the current state is not encrypted 
304     *                               or decrypted.
305     */
306    private void ensureEncryptedOrDecryptedState() {
307
308        if (state != JWEObjectJSON.State.ENCRYPTED && state != JWEObjectJSON.State.DECRYPTED) {
309
310            throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state");
311        }
312    }
313
314
315    /**
316     * Ensures the specified JWE encrypter supports the algorithms of this 
317     * JWE object.
318     *
319     * @throws JOSEException If the JWE algorithms are not supported.
320     */
321    private void ensureJWEEncrypterSupport(final JWEEncrypterMulti encrypter)
322            throws JOSEException {
323
324        if (! encrypter.supportedJWEAlgorithms().contains(getHeader().getAlgorithm())) {
325
326            throw new JOSEException("The " + getHeader().getAlgorithm() +
327                    " algorithm is not supported by the JWE encrypter: Supported algorithms: " + encrypter.supportedJWEAlgorithms());
328        }
329
330        if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
331
332            throw new JOSEException("The " + getHeader().getEncryptionMethod() +
333                    " encryption method or key size is not supported by the JWE encrypter: Supported methods: " + encrypter.supportedEncryptionMethods());
334        }
335    }
336
337
338    /**
339     * Encrypts this JWE object with the specified encrypter. The JWE 
340     * object must be in an {@link JWEObjectJSON.State#UNENCRYPTED unencrypted} state.
341     *
342     * @param encrypter The JWE encrypter. Must not be {@code null}.
343     *
344     * @throws IllegalStateException If the JWE object is not in an 
345     *                               {@link JWEObjectJSON.State#UNENCRYPTED unencrypted
346     *                               state}.
347     * @throws JOSEException         If the JWE object couldn't be 
348     *                               encrypted.
349     */
350    public synchronized void encrypt(final JWEEncrypterMulti encrypter)
351            throws JOSEException {
352
353        ensureUnencryptedState();
354
355        ensureJWEEncrypterSupport(encrypter);
356
357        JWECryptoParts parts;
358
359        try {
360            parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
361
362        } catch (JOSEException e) {
363
364            throw e;
365
366        } catch (Exception e) {
367
368            // Prevent throwing unchecked exceptions at this point,
369            // see issue #20
370            throw new JOSEException(e.getMessage(), e);
371        }
372
373        // Check if the header has been modified
374        if (parts.getHeader() != null) {
375            header = parts.getHeader();
376        }
377
378        recipients = parts.getRecipients();
379        iv = parts.getInitializationVector();
380        cipherText = parts.getCipherText();
381        authTag = parts.getAuthenticationTag();
382
383        state = JWEObjectJSON.State.ENCRYPTED;
384    }
385
386
387    /**
388     * Decrypts this JWE object with the specified decrypter. The JWE 
389     * object must be in a {@link JWEObjectJSON.State#ENCRYPTED encrypted} state.
390     *
391     * @param decrypter The JWE decrypter. Must not be {@code null}.
392     *
393     * @throws IllegalStateException If the JWE object is not in an 
394     *                               {@link JWEObjectJSON.State#ENCRYPTED encrypted
395     *                               state}.
396     * @throws JOSEException         If the JWE object couldn't be 
397     *                               decrypted.
398     */
399    public synchronized void decrypt(final JWEDecrypterMulti decrypter)
400            throws JOSEException {
401
402        ensureEncryptedState();
403
404        try {
405            setPayload(new Payload(decrypter.decrypt(getHeader(),
406                    getRecipients(),
407                    getIV(),
408                    getCipherText(),
409                    getAuthTag())));
410
411        } catch (JOSEException e) {
412
413            throw e;
414
415        } catch (Exception e) {
416
417            // Prevent throwing unchecked exceptions at this point,
418            // see issue #20
419            throw new JOSEException(e.getMessage(), e);
420        }
421
422        state = JWEObjectJSON.State.DECRYPTED;
423    }
424
425
426    /**
427     * Serialises this JWE object to General JSON format.
428     *
429     * @return The serialised JWE object.
430     *
431     * @throws IllegalStateException If the JWS object is not in a
432     *                               {@link JWSObject.State#SIGNED signed} or
433     *                               {@link JWSObject.State#VERIFIED verified} state.
434     */
435    @Override
436    public String serialize() {
437
438        ensureEncryptedOrDecryptedState();
439
440        return JSONObjectUtils.toJSONString(toGeneralJSONObject());
441    }
442
443
444    /**
445     * Parses a JWE object from the specified string in json form. The
446     * parsed JWE object will be given an {@link JWEObjectJSON.State#ENCRYPTED} state.
447     *
448     * NOTE: Supports only General Serialization Syntax
449     *
450     * @param s The string to parse. Must not be {@code null}.
451     *
452     * @return The JWE object.
453     *
454     * @throws ParseException If the string couldn't be parsed to a valid 
455     *                        JWE object.
456     */
457    public static JWEObjectJSON parse(final String s)
458            throws ParseException {
459        Map<String, Object> json = JSONObjectUtils.parse(s);
460
461        return parse(json);
462    }
463
464    /**
465     * Parses a JWE object from the map. The
466     * parsed JWE object will be given
467     * an {@link JWEObjectJSON.State#ENCRYPTED} state.
468     *
469     * NOTE: Supports only General Serialization Syntax
470     *
471     * @param jsonObject The json map. Must not be {@code null}.
472     *
473     * @return The JWE object.
474     *
475     * @throws ParseException If the string couldn't be parsed to a valid
476     *                        JWE object.
477     */
478    public static JWEObjectJSON parse(final Map<String, Object> jsonObject)
479            throws ParseException {
480
481        return new JWEObjectJSON(
482                JSONObjectUtils.getBase64URL(jsonObject, "protected"),
483                JWERecipient.parse(JSONObjectUtils.getJSONObjectArray(jsonObject, "recipients")),
484                JSONObjectUtils.getBase64URL(jsonObject, "iv"),
485                JSONObjectUtils.getBase64URL(jsonObject, "ciphertext"),
486                JSONObjectUtils.getBase64URL(jsonObject, "tag"));
487    }
488
489    @Override
490    public Map<String, Object> toGeneralJSONObject() {
491        ensureEncryptedOrDecryptedState();
492
493        List<Object> recipients = JSONArrayUtils.newJSONArray();
494
495        for (JWERecipient recipient : getRecipients()) {
496            recipients.add(recipient.toJSONObject());
497        }
498
499        Map<String, Object> json = JSONObjectUtils.newJSONObject();
500        json.put("iv", getIV().toString());
501        json.put("recipients", recipients);
502        json.put("tag", getAuthTag().toString());
503        json.put("protected", getHeader().toBase64URL().toString());
504        json.put("ciphertext", getCipherText().toString());
505        return json;
506    }
507
508    @Override
509    public Map<String, Object> toFlattenedJSONObject() {
510        // flattened JSON serialization is not implemented
511        throw new NotImplementedException();
512
513    }
514}