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.crypto.impl;
019
020
021import com.nimbusds.jose.*;
022import com.nimbusds.jose.jwk.Curve;
023import com.nimbusds.jose.util.Base64URL;
024import com.nimbusds.jose.util.Pair;
025
026import javax.crypto.SecretKey;
027import java.util.*;
028
029
030/**
031 * The base abstract class for Elliptic Curve Diffie-Hellman One-Pass Unified
032 * Model encrypters and decrypters of {@link com.nimbusds.jose.JWEObject JWE
033 * objects}.
034 *
035 * <p>Supports the following key management algorithms:
036 *
037 * <ul>
038 *     <li>{@link JWEAlgorithm#ECDH_1PU}
039 *     <li>{@link JWEAlgorithm#ECDH_1PU_A128KW}
040 *     <li>{@link JWEAlgorithm#ECDH_1PU_A192KW}
041 *     <li>{@link JWEAlgorithm#ECDH_1PU_A256KW}
042 * </ul>
043 *
044 * <p>Supports the following elliptic curves:
045 *
046 * <ul>
047 *     <li>{@link Curve#P_256}
048 *     <li>{@link Curve#P_384}
049 *     <li>{@link Curve#P_521}
050 *     <li>{@link Curve#X25519}
051 * </ul>
052 *
053 * <p>Supports the following content encryption algorithms for Direct key
054 * agreement mode:
055 *
056 * <ul>
057 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
058 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
059 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
060 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
061 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
062 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
063 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
065 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
066 * </ul>
067 *
068 * <p>Supports the following content encryption algorithms for Key wrapping
069 * mode:
070 *
071 * <ul>
072 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
073 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
074 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
075 * </ul>
076 *
077 * @author Alexander Martynov
078 * @version 2021-08-03
079 */
080public abstract class ECDH1PUCryptoProvider extends BaseJWEProvider {
081
082
083    /**
084     * The supported JWE algorithms by the ECDH crypto provider class.
085     */
086    public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS;
087
088
089    /**
090     * The supported encryption methods by the ECDH crypto provider class.
091     */
092    public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS;
093
094
095    static {
096        Set<JWEAlgorithm> algs = new LinkedHashSet<>();
097        algs.add(JWEAlgorithm.ECDH_1PU);
098        algs.add(JWEAlgorithm.ECDH_1PU_A128KW);
099        algs.add(JWEAlgorithm.ECDH_1PU_A192KW);
100        algs.add(JWEAlgorithm.ECDH_1PU_A256KW);
101        SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
102    }
103
104
105    /**
106     * The elliptic curve.
107     */
108    private final Curve curve;
109
110
111    /**
112     * The Concatenation Key Derivation Function (KDF).
113     */
114    private final ConcatKDF concatKDF;
115
116
117    /**
118     * Creates a new Elliptic Curve Diffie-Hellman One-Pass Unified Model
119     * encryption / decryption provider.
120     *
121     * @param curve The elliptic curve. Must be supported and not
122     *              {@code null}.
123     *
124     * @throws JOSEException If the elliptic curve is not supported.
125     */
126    protected ECDH1PUCryptoProvider(final Curve curve)
127        throws JOSEException {
128
129        super(SUPPORTED_ALGORITHMS, ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS);
130
131        Curve definedCurve = curve != null ? curve : new Curve("unknown");
132
133        if (!supportedEllipticCurves().contains(curve)) {
134            throw new JOSEException(AlgorithmSupportMessage.unsupportedEllipticCurve(
135                definedCurve, supportedEllipticCurves()));
136        }
137
138        this.curve = curve;
139
140        concatKDF = new ConcatKDF("SHA-256");
141    }
142
143
144    /**
145     * Returns the Concatenation Key Derivation Function (KDF).
146     *
147     * @return The concat KDF.
148     */
149    protected ConcatKDF getConcatKDF() {
150
151        return concatKDF;
152    }
153
154
155    /**
156     * Returns the names of the supported elliptic curves. These correspond
157     * to the {@code crv} JWK parameter.
158     *
159     * @return The supported elliptic curves.
160     */
161    public abstract Set<Curve> supportedEllipticCurves();
162
163
164    /**
165     * Returns the elliptic curve of the key (JWK designation).
166     *
167     * @return The elliptic curve.
168     */
169    public Curve getCurve() {
170
171        return curve;
172    }
173
174    
175    /**
176     * Encrypts the specified plaintext using the specified shared secret
177     * ("Z"), with an optionally externally supplied content encryption key
178     * (CEK) for {@link ECDH.AlgorithmMode#KW}.
179     */
180    protected JWECryptoParts encryptWithZ(final JWEHeader header,
181                                          final SecretKey Z,
182                                          final byte[] clearText,
183                                          final SecretKey contentEncryptionKey)
184        throws JOSEException {
185
186        final JWEAlgorithm alg = header.getAlgorithm();
187        final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg);
188        final EncryptionMethod enc = header.getEncryptionMethod();
189
190        final SecretKey cek;
191        final Base64URL encryptedKey; // The CEK encrypted (second JWE part)
192
193        if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
194
195            // Derive shared key via concat KDF
196            getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
197            cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF());
198
199            return ContentCryptoProvider.encrypt(header, clearText, cek, null, getJCAContext());
200        }
201
202        if (algMode.equals(ECDH.AlgorithmMode.KW)) {
203
204            // Key wrapping mode supports only AES_CBC_HMAC_SHA2
205            // See https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1
206            if (!EncryptionMethod.Family.AES_CBC_HMAC_SHA.contains(enc)) {
207                throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
208                        header.getEncryptionMethod(),
209                        EncryptionMethod.Family.AES_CBC_HMAC_SHA));
210            }
211
212            if (contentEncryptionKey != null) { // Use externally supplied CEK
213                cek = contentEncryptionKey;
214            } else { // Generate the CEK according to the enc method
215                cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom());
216            }
217
218            JWECryptoParts encrypted = ContentCryptoProvider.encrypt(header, clearText, cek, null, getJCAContext());
219
220            SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, encrypted.getAuthenticationTag(), getConcatKDF());
221            encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
222
223            return new JWECryptoParts(
224                    header,
225                    encryptedKey,
226                    encrypted.getInitializationVector(),
227                    encrypted.getCipherText(),
228                    encrypted.getAuthenticationTag()
229            );
230        }
231
232        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
233    }
234
235    /**
236     * Decrypts the encrypted JWE parts using the specified shared secret ("Z").
237     */
238    protected byte[] decryptWithZ(final JWEHeader header,
239                                  final SecretKey Z,
240                                  final Base64URL encryptedKey,
241                                  final Base64URL iv,
242                                  final Base64URL cipherText,
243                                  final Base64URL authTag)
244        throws JOSEException {
245
246        final JWEAlgorithm alg = header.getAlgorithm();
247        final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg);
248
249        // Derive shared key via concat KDF
250        getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
251
252        final SecretKey cek;
253
254        if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
255            cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF());
256        } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
257            if (encryptedKey == null) {
258                throw new JOSEException("Missing JWE encrypted key");
259            }
260
261            SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, authTag, getConcatKDF());
262            cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
263        } else {
264            throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
265        }
266
267        return ContentCryptoProvider.decrypt(header, null, iv, cipherText, authTag, cek, getJCAContext());
268    }
269
270    protected JWECryptoParts encryptMulti(final JWEHeader header,
271                                          final List<Pair<UnprotectedHeader, SecretKey>> sharedSecrets,
272                                          final byte[] clearText) throws JOSEException {
273
274        final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(header.getAlgorithm());
275        final SecretKey cek = ContentCryptoProvider.generateCEK(
276                header.getEncryptionMethod(),
277               getJCAContext().getSecureRandom()
278        );
279
280        List<JWERecipient> recipients = new ArrayList<>();
281        boolean encrypted = false;
282        JWECryptoParts parts = null;
283
284        for (Pair<UnprotectedHeader, SecretKey> rs : sharedSecrets) {
285            Base64URL encryptedKey = null;
286
287            if (!encrypted) {
288                parts = encryptWithZ(header, rs.getRight(), clearText, cek);
289                encryptedKey = parts.getEncryptedKey();
290                encrypted = true;
291            } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
292                SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, rs.getRight(), parts.getAuthenticationTag(), getConcatKDF());
293                encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
294            }
295
296            if (encryptedKey != null) {
297                recipients.add(new JWERecipient(rs.getLeft(), encryptedKey));
298            }
299        }
300
301        if (parts == null) {
302            throw new JOSEException("Content MUST be encrypted");
303        }
304
305        return new JWECryptoParts(
306                parts.getHeader(),
307                Collections.unmodifiableList(recipients),
308                parts.getInitializationVector(),
309                parts.getCipherText(),
310                parts.getAuthenticationTag()
311        );
312    }
313
314    protected byte[] decryptMulti(final JWEHeader header,
315                                  final List<Pair<UnprotectedHeader, SecretKey>> sharedSecrets,
316                                  final List<JWERecipient> recipients,
317                                  final Base64URL iv,
318                                  final Base64URL cipherText,
319                                  final Base64URL authTag) throws JOSEException {
320
321        byte[] result = null;
322
323        for (Pair<UnprotectedHeader, SecretKey> rs : sharedSecrets) {
324            String kid = rs.getLeft().getKeyID();
325            Base64URL encryptedKey = null;
326
327            if (recipients != null) {
328                for (JWERecipient recipient : recipients) {
329                    if (recipient.getHeader() == null)
330                        continue;
331
332                    if (kid.equals(recipient.getHeader().getKeyID())) {
333                        encryptedKey = recipient.getEncryptedKey();
334                        break;
335                    }
336                }
337            }
338
339            result = decryptWithZ(header, rs.getRight(), encryptedKey, iv, cipherText, authTag);
340        }
341
342        return result;
343    }
344}