001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2019, 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 encrypters and
033 * decrypters of {@link com.nimbusds.jose.JWEObject JWE objects}.
034 *
035 * <p>Supports the following key management algorithms:
036 *
037 * <ul>
038 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
039 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
040 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
041 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
042 * </ul>
043 *
044 * <p>Supports the following elliptic curves:
045 *
046 * <ul>
047 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_256}
048 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_384}
049 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_521}
050 *     <li>{@link com.nimbusds.jose.jwk.Curve#X25519}
051 * </ul>
052 *
053 * <p>Supports the following content encryption algorithms:
054 *
055 * <ul>
056 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
057 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
058 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
059 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
060 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
061 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
062 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
063 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
065 * </ul>
066 *
067 * @author Tim McLean
068 * @author Vladimir Dzhuvinov
069 * @author Fernando González Callejas
070 * @author Egor Puzanov
071 * @version 2023-09-10
072 */
073public abstract class ECDHCryptoProvider extends BaseJWEProvider {
074
075
076        /**
077         * The supported JWE algorithms by the ECDH crypto provider class.
078         */
079        public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS;
080
081
082        /**
083         * The supported encryption methods by the ECDH crypto provider class.
084         */
085        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS;
086
087
088        static {
089                Set<JWEAlgorithm> algs = new LinkedHashSet<>();
090                algs.add(JWEAlgorithm.ECDH_ES);
091                algs.add(JWEAlgorithm.ECDH_ES_A128KW);
092                algs.add(JWEAlgorithm.ECDH_ES_A192KW);
093                algs.add(JWEAlgorithm.ECDH_ES_A256KW);
094                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
095        }
096
097
098        /**
099         * The elliptic curve.
100         */
101        private final Curve curve;
102
103
104        /**
105         * The Concatenation Key Derivation Function (KDF).
106         */
107        private final ConcatKDF concatKDF;
108
109
110        /**
111         * Creates a new Elliptic Curve Diffie-Hellman encryption /decryption
112         * provider.
113         *
114         * @param curve The elliptic curve. Must be supported and not
115         *              {@code null}.
116         * @param cek   The content encryption key (CEK) to use. If specified
117         *              its algorithm must be "AES" or "ChaCha20" and its length
118         *              must match the expected for the JWE encryption method
119         *              ("enc"). If {@code null} a CEK will be generated for
120         *              each JWE.
121         *
122         * @throws JOSEException If the elliptic curve is not supported.
123         */
124        protected ECDHCryptoProvider(final Curve curve, final SecretKey cek)
125                throws JOSEException {
126
127                super(SUPPORTED_ALGORITHMS, ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS, cek);
128
129                Curve definedCurve = curve != null ? curve : new Curve("unknown");
130
131                if (! supportedEllipticCurves().contains(curve)) {
132                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEllipticCurve(
133                                definedCurve, supportedEllipticCurves()));
134                }
135
136                this.curve = curve;
137
138                concatKDF = new ConcatKDF("SHA-256");
139        }
140
141
142        /**
143         * Returns the Concatenation Key Derivation Function (KDF).
144         *
145         * @return The concat KDF.
146         */
147        protected ConcatKDF getConcatKDF() {
148
149                return concatKDF;
150        }
151
152
153        /**
154         * Returns the names of the supported elliptic curves. These correspond
155         * to the {@code crv} EC JWK parameter.
156         *
157         * @return The supported elliptic curves.
158         */
159        public abstract Set<Curve> supportedEllipticCurves();
160
161
162        /**
163         * Returns the elliptic curve of the key (JWK designation).
164         *
165         * @return The elliptic curve.
166         */
167        public Curve getCurve() {
168
169                return curve;
170        }
171
172
173        /**
174         * Encrypts the specified plaintext using the specified shared secret
175         * ("Z").
176         */
177        protected JWECryptoParts encryptWithZ(final JWEHeader header, final SecretKey Z, final byte[] clearText, final byte[] aad)
178                throws JOSEException {
179
180                final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(header);
181                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
182                final EncryptionMethod enc = header.getEncryptionMethod();
183
184                // Derive shared key via concat KDF
185                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
186                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
187
188                final SecretKey cek;
189                final Base64URL encryptedKey; // The CEK encrypted (second JWE part)
190
191                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
192                        if (isCEKProvided()) {
193                                throw new JOSEException("The provided CEK is not supported");
194                        }
195                        cek = sharedKey;
196                        encryptedKey = null;
197                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
198                        cek = getCEK(enc);
199                        encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
200                } else {
201                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
202                }
203
204                return ContentCryptoProvider.encrypt(header, clearText, aad, cek, encryptedKey, getJCAContext());
205        }
206
207
208        /**
209         * Decrypts the encrypted JWE parts using the specified shared secret
210         * ("Z").
211         */
212        protected byte[] decryptWithZ(final JWEHeader header,
213                                      final byte[] aad,
214                                      final SecretKey Z,
215                                      final Base64URL encryptedKey,
216                                      final Base64URL iv,
217                                      final Base64URL cipherText,
218                                      final Base64URL authTag)
219                throws JOSEException {
220
221                final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(header);
222                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
223
224                // Derive shared key via concat KDF
225                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
226                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
227
228                final SecretKey cek;
229
230                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
231                        cek = sharedKey;
232                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
233                        if (encryptedKey == null) {
234                                throw new JOSEException("Missing JWE encrypted key");
235                        }
236                        cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
237                } else {
238                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
239                }
240
241                return ContentCryptoProvider.decrypt(header, aad, encryptedKey, iv, cipherText, authTag, cek, getJCAContext());
242        }
243}