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