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 * @version 2021-08-03
080 */
081public abstract class ECDH1PUCryptoProvider extends BaseJWEProvider {
082        
083        
084        /**
085         * The supported JWE algorithms by the ECDH crypto provider class.
086         */
087        public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS;
088        
089        
090        /**
091         * The supported encryption methods by the ECDH crypto provider class.
092         */
093        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS;
094        
095        
096        static {
097                Set<JWEAlgorithm> algs = new LinkedHashSet<>();
098                algs.add(JWEAlgorithm.ECDH_1PU);
099                algs.add(JWEAlgorithm.ECDH_1PU_A128KW);
100                algs.add(JWEAlgorithm.ECDH_1PU_A192KW);
101                algs.add(JWEAlgorithm.ECDH_1PU_A256KW);
102                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
103        }
104        
105        
106        /**
107         * The elliptic curve.
108         */
109        private final Curve curve;
110        
111        
112        /**
113         * The Concatenation Key Derivation Function (KDF).
114         */
115        private final ConcatKDF concatKDF;
116        
117        
118        /**
119         * Creates a new Elliptic Curve Diffie-Hellman One-Pass Unified Model
120         * encryption / decryption provider.
121         *
122         * @param curve The elliptic curve. Must be supported and not
123         *              {@code null}.
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        /**
237         * Decrypts the encrypted JWE parts using the specified shared secret ("Z").
238         */
239        protected byte[] decryptWithZ(final JWEHeader header,
240                                      final SecretKey Z,
241                                      final Base64URL encryptedKey,
242                                      final Base64URL iv,
243                                      final Base64URL cipherText,
244                                      final Base64URL authTag)
245                throws JOSEException {
246                
247                final JWEAlgorithm alg = header.getAlgorithm();
248                final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg);
249                
250                // Derive shared key via concat KDF
251                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
252                
253                final SecretKey cek;
254                
255                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
256                        cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF());
257                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
258                        if (encryptedKey == null) {
259                                throw new JOSEException("Missing JWE encrypted key");
260                        }
261                        
262                        SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, authTag, getConcatKDF());
263                        cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
264                } else {
265                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
266                }
267                
268                return ContentCryptoProvider.decrypt(header, null, iv, cipherText, authTag, cek, getJCAContext());
269        }
270}