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 java.util.Collections;
022import java.util.LinkedHashSet;
023import java.util.Set;
024import javax.crypto.SecretKey;
025
026import com.nimbusds.jose.*;
027import com.nimbusds.jose.jwk.Curve;
028import com.nimbusds.jose.util.Base64URL;
029
030
031/**
032 * The base abstract class for Elliptic Curve Diffie-Hellman One-Pass Unified
033 * Model encrypters and decrypters of {@link com.nimbusds.jose.JWEObject JWE
034 * objects}.
035 *
036 * <p>Supports the following key management algorithms:
037 *
038 * <ul>
039 *     <li>{@link JWEAlgorithm#ECDH_1PU}
040 *     <li>{@link JWEAlgorithm#ECDH_1PU_A128KW}
041 *     <li>{@link JWEAlgorithm#ECDH_1PU_A192KW}
042 *     <li>{@link JWEAlgorithm#ECDH_1PU_A256KW}
043 * </ul>
044 *
045 * <p>Supports the following elliptic curves:
046 *
047 * <ul>
048 *     <li>{@link Curve#P_256}
049 *     <li>{@link Curve#P_384}
050 *     <li>{@link Curve#P_521}
051 *     <li>{@link Curve#X25519}
052 * </ul>
053 *
054 * <p>Supports the following content encryption algorithms for Direct key
055 * agreement mode:
056 *
057 * <ul>
058 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
059 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
060 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
061 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
062 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
063 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
065 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
066 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
067 * </ul>
068 *
069 * <p>Supports the following content encryption algorithms for Key wrapping
070 * mode:
071 *
072 * <ul>
073 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
074 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
075 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
076 * </ul>
077 *
078 * @author Alexander Martynov
079 * @author Egor Puzanov
080 * @version 2023-09-10
081 */
082public abstract class ECDH1PUCryptoProvider extends BaseJWEProvider {
083        
084        
085        /**
086         * The supported JWE algorithms by the ECDH crypto provider class.
087         */
088        public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS;
089        
090        
091        /**
092         * The supported encryption methods by the ECDH crypto provider class.
093         */
094        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS;
095        
096        
097        static {
098                Set<JWEAlgorithm> algs = new LinkedHashSet<>();
099                algs.add(JWEAlgorithm.ECDH_1PU);
100                algs.add(JWEAlgorithm.ECDH_1PU_A128KW);
101                algs.add(JWEAlgorithm.ECDH_1PU_A192KW);
102                algs.add(JWEAlgorithm.ECDH_1PU_A256KW);
103                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
104        }
105        
106        
107        /**
108         * The elliptic curve.
109         */
110        private final Curve curve;
111        
112        
113        /**
114         * The Concatenation Key Derivation Function (KDF).
115         */
116        private final ConcatKDF concatKDF;
117        
118        
119        /**
120         * Creates a new Elliptic Curve Diffie-Hellman One-Pass Unified Model
121         * encryption / decryption provider.
122         *
123         * @param curve The elliptic curve. Must be supported and not
124         *              {@code null}.
125         * @param cek   The content encryption key (CEK) to use. If specified
126         *              its algorithm must be "AES" or "ChaCha20" and its length
127         *              must match the expected for the JWE encryption method
128         *              ("enc"). If {@code null} a CEK will be generated for
129         *              each JWE.
130         *
131         * @throws JOSEException If the elliptic curve is not supported.
132         */
133        protected ECDH1PUCryptoProvider(final Curve curve, final SecretKey cek)
134                throws JOSEException {
135                
136                super(SUPPORTED_ALGORITHMS, ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS, cek);
137                
138                Curve definedCurve = curve != null ? curve : new Curve("unknown");
139                
140                if (!supportedEllipticCurves().contains(curve)) {
141                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEllipticCurve(
142                                definedCurve, supportedEllipticCurves()));
143                }
144                
145                this.curve = curve;
146                
147                concatKDF = new ConcatKDF("SHA-256");
148        }
149        
150        
151        /**
152         * Returns the Concatenation Key Derivation Function (KDF).
153         *
154         * @return The concat KDF.
155         */
156        protected ConcatKDF getConcatKDF() {
157                
158                return concatKDF;
159        }
160        
161        
162        /**
163         * Returns the names of the supported elliptic curves. These correspond
164         * to the {@code crv} JWK parameter.
165         *
166         * @return The supported elliptic curves.
167         */
168        public abstract Set<Curve> supportedEllipticCurves();
169        
170        
171        /**
172         * Returns the elliptic curve of the key (JWK designation).
173         *
174         * @return The elliptic curve.
175         */
176        public Curve getCurve() {
177                
178                return curve;
179        }
180        
181        
182        /**
183         * Encrypts the specified plaintext using the specified shared secret
184         * ("Z").
185         */
186        protected JWECryptoParts encryptWithZ(final JWEHeader header,
187                                              final SecretKey Z,
188                                              final byte[] clearText,
189                                              final byte[] aad)
190                throws JOSEException {
191                
192                final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(header);
193                final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg);
194                final EncryptionMethod enc = header.getEncryptionMethod();
195                
196                final SecretKey cek;
197                final Base64URL encryptedKey; // The CEK encrypted (second JWE part)
198                
199                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
200                        if (isCEKProvided()) {
201                                throw new JOSEException("The provided CEK is not supported");
202                        }
203                        // Derive shared key via concat KDF
204                        getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
205                        cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF());
206                        
207                        return ContentCryptoProvider.encrypt(header, clearText, aad, cek, null, getJCAContext());
208                }
209                
210                if (algMode.equals(ECDH.AlgorithmMode.KW)) {
211                        
212                        // Key wrapping mode supports only AES_CBC_HMAC_SHA2
213                        // See https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1
214                        if (!EncryptionMethod.Family.AES_CBC_HMAC_SHA.contains(enc)) {
215                                throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
216                                        header.getEncryptionMethod(),
217                                        EncryptionMethod.Family.AES_CBC_HMAC_SHA));
218                        }
219                        
220                        cek = getCEK(enc);
221                        
222                        JWECryptoParts encrypted = ContentCryptoProvider.encrypt(header, clearText, aad, cek, null, getJCAContext());
223                        
224                        SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, encrypted.getAuthenticationTag(), getConcatKDF());
225                        encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
226                        
227                        return new JWECryptoParts(
228                                header,
229                                encryptedKey,
230                                encrypted.getInitializationVector(),
231                                encrypted.getCipherText(),
232                                encrypted.getAuthenticationTag()
233                        );
234                }
235                
236                throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
237        }
238        
239        
240        /**
241         * Decrypts the encrypted JWE parts using the specified shared secret ("Z").
242         */
243        protected byte[] decryptWithZ(final JWEHeader header,
244                                      final byte[] aad,
245                                      final SecretKey Z,
246                                      final Base64URL encryptedKey,
247                                      final Base64URL iv,
248                                      final Base64URL cipherText,
249                                      final Base64URL authTag)
250                throws JOSEException {
251                
252                final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(header);
253                final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg);
254                
255                // Derive shared key via concat KDF
256                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
257                
258                final SecretKey cek;
259                
260                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
261                        cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF());
262                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
263                        if (encryptedKey == null) {
264                                throw new JOSEException("Missing JWE encrypted key");
265                        }
266                        
267                        SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, authTag, getConcatKDF());
268                        cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
269                } else {
270                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
271                }
272                
273                return ContentCryptoProvider.decrypt(header, aad, null, iv, cipherText, authTag, cek, getJCAContext());
274        }
275}