001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2019, Connect2id Ltd.
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;
019
020
021import java.security.InvalidAlgorithmParameterException;
022import java.security.KeyPair;
023import java.security.KeyPairGenerator;
024import java.security.NoSuchAlgorithmException;
025import java.security.Provider;
026import java.security.interfaces.ECPrivateKey;
027import java.security.interfaces.ECPublicKey;
028import java.security.spec.ECParameterSpec;
029import java.util.Arrays;
030import java.util.Collections;
031import java.util.LinkedHashSet;
032import java.util.Set;
033
034import javax.crypto.SecretKey;
035
036import com.nimbusds.jose.*;
037import com.nimbusds.jose.crypto.impl.AAD;
038import com.nimbusds.jose.crypto.impl.ECDH;
039import com.nimbusds.jose.crypto.impl.ECDHCryptoProvider;
040import com.nimbusds.jose.jwk.Curve;
041import com.nimbusds.jose.jwk.ECKey;
042
043import net.jcip.annotations.ThreadSafe;
044
045
046/**
047 * Elliptic Curve Diffie-Hellman encrypter of
048 * {@link com.nimbusds.jose.JWEObject JWE objects} for curves using EC JWK keys.
049 * Expects a public EC key (with a P-256, P-384 or P-521 curve).
050 *
051 * <p>See RFC 7518
052 * <a href="https://tools.ietf.org/html/rfc7518#section-4.6">section 4.6</a>
053 * for more information.
054 *
055 * <p>For Curve25519/X25519, see {@link X25519Encrypter} instead.
056 *
057 * <p>This class is thread-safe.
058 *
059 * <p>Supports the following key management algorithms:
060 *
061 * <ul>
062 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
063 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
064 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
065 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
066 * </ul>
067 *
068 * <p>Supports the following elliptic curves:
069 *
070 * <ul>
071 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_256}
072 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_384}
073 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_521}
074 * </ul>
075 *
076 * <p>Supports the following content encryption algorithms:
077 *
078 * <ul>
079 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
080 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
081 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
082 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
083 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
084 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
085 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
086 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
087 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
088 * </ul>
089 *
090 * @author Tim McLean
091 * @author Vladimir Dzhuvinov
092 * @author Fernando González Callejas
093 * @author Egor Puzanov
094 * @version 2023-05-17
095 */
096@ThreadSafe
097public class ECDHEncrypter extends ECDHCryptoProvider implements JWEEncrypter {
098
099
100        /**
101         * The supported EC JWK curves by the ECDH crypto provider class.
102         */
103        public static final Set<Curve> SUPPORTED_ELLIPTIC_CURVES;
104
105
106        static {
107                Set<Curve> curves = new LinkedHashSet<>();
108                curves.add(Curve.P_256);
109                curves.add(Curve.P_384);
110                curves.add(Curve.P_521);
111                SUPPORTED_ELLIPTIC_CURVES = Collections.unmodifiableSet(curves);
112        }
113
114
115        /**
116         * The public EC key.
117         */
118        private final ECPublicKey publicKey;
119
120        /**
121         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
122         *
123         * @param publicKey The public EC key. Must not be {@code null}.
124         *
125         * @throws JOSEException If the elliptic curve is not supported.
126         */
127        public ECDHEncrypter(final ECPublicKey publicKey)
128                throws JOSEException {
129
130                this(publicKey, null);
131        }
132
133
134        /**
135         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
136         *
137         * @param ecJWK The EC JSON Web Key (JWK). Must not be {@code null}.
138         *
139         * @throws JOSEException If the elliptic curve is not supported.
140         */
141        public ECDHEncrypter(final ECKey ecJWK) throws
142                JOSEException {
143
144                this(ecJWK.toECPublicKey(), null);
145        }
146        
147        /**
148         * Creates a new Elliptic Curve Diffie-Hellman encrypter with an
149         * optionally specified content encryption key (CEK).
150         *
151         * @param publicKey            The public EC key. Must not be
152         *                             {@code null}.
153         * @param contentEncryptionKey The content encryption key (CEK) to use.
154         *                             If specified its algorithm must be "AES"
155         *                             and its length must match the expected
156         *                             for the JWE encryption method ("enc").
157         *                             If {@code null} a CEK will be generated
158         *                             for each JWE.
159         * @throws JOSEException       If the elliptic curve is not supported.
160         */
161        public ECDHEncrypter(final ECPublicKey publicKey, final SecretKey contentEncryptionKey)
162                throws JOSEException {
163                
164                super(Curve.forECParameterSpec(publicKey.getParams()), contentEncryptionKey);
165                
166                this.publicKey = publicKey;
167        }
168
169
170        /**
171         * Returns the public EC key.
172         *
173         * @return The public EC key.
174         */
175        public ECPublicKey getPublicKey() {
176
177                return publicKey;
178        }
179
180
181        @Override
182        public Set<Curve> supportedEllipticCurves() {
183
184                return SUPPORTED_ELLIPTIC_CURVES;
185        }
186
187
188        /**
189         * Encrypts the specified clear text of a {@link JWEObject JWE object}.
190         *
191         * @param header    The JSON Web Encryption (JWE) header. Must specify
192         *                  a supported JWE algorithm and method. Must not be
193         *                  {@code null}.
194         * @param clearText The clear text to encrypt. Must not be {@code null}.
195         *
196         * @return The resulting JWE crypto parts.
197         *
198         * @throws JOSEException If the JWE algorithm or method is not
199         *                       supported or if encryption failed for some
200         *                       other internal reason.
201         */
202        @Deprecated
203        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
204                throws JOSEException {
205
206                return encrypt(header, clearText, AAD.compute(header));
207        }
208
209
210        @Override
211        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText, final byte[] aad)
212                throws JOSEException {
213
214                // Generate ephemeral EC key pair on the same curve as the consumer's public key
215                KeyPair ephemeralKeyPair = generateEphemeralKeyPair(publicKey.getParams());
216                ECPublicKey ephemeralPublicKey = (ECPublicKey)ephemeralKeyPair.getPublic();
217                ECPrivateKey ephemeralPrivateKey = (ECPrivateKey)ephemeralKeyPair.getPrivate();
218
219                // Add the ephemeral public EC key to the header
220                JWEHeader updatedHeader = new JWEHeader.Builder(header).
221                        ephemeralPublicKey(new ECKey.Builder(getCurve(), ephemeralPublicKey).build()).
222                        build();
223
224                // Derive 'Z'
225                SecretKey Z = ECDH.deriveSharedSecret(
226                        publicKey,
227                        ephemeralPrivateKey,
228                        getJCAContext().getKeyEncryptionProvider());
229
230                // for JWEObject we need update the AAD as well
231                final byte[] updatedAAD = Arrays.equals(AAD.compute(header), aad) ? AAD.compute(updatedHeader) : aad;
232
233                return encryptWithZ(updatedHeader, Z, clearText, updatedAAD);
234        }
235
236
237        /**
238         * Generates a new ephemeral EC key pair with the specified curve.
239         *
240         * @param ecParameterSpec The EC key spec. Must not be {@code null}.
241         *
242         * @return The EC key pair.
243         *
244         * @throws JOSEException If the EC key pair couldn't be generated.
245         */
246        private KeyPair generateEphemeralKeyPair(final ECParameterSpec ecParameterSpec)
247                throws JOSEException {
248
249                Provider keProvider = getJCAContext().getKeyEncryptionProvider();
250
251                try {
252                        KeyPairGenerator generator;
253
254                        if (keProvider != null) {
255                                generator = KeyPairGenerator.getInstance("EC", keProvider);
256                        } else {
257                                generator = KeyPairGenerator.getInstance("EC");
258                        }
259
260                        generator.initialize(ecParameterSpec);
261                        return generator.generateKeyPair();
262                } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
263                        throw new JOSEException("Couldn't generate ephemeral EC key pair: " + e.getMessage(), e);
264                }
265        }
266}