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 * </ul>
089 *
090 * @author Tim McLean
091 * @author Vladimir Dzhuvinov
092 * @author Fernando González Callejas
093 * @version 2019-01-24
094 */
095@ThreadSafe
096public class ECDHEncrypter extends ECDHCryptoProvider implements JWEEncrypter {
097
098
099        /**
100         * The supported EC JWK curves by the ECDH crypto provider class.
101         */
102        public static final Set<Curve> SUPPORTED_ELLIPTIC_CURVES;
103
104
105        static {
106                Set<Curve> curves = new LinkedHashSet<>();
107                curves.add(Curve.P_256);
108                curves.add(Curve.P_384);
109                curves.add(Curve.P_521);
110                SUPPORTED_ELLIPTIC_CURVES = Collections.unmodifiableSet(curves);
111        }
112
113
114        /**
115         * The public EC key.
116         */
117        private final ECPublicKey publicKey;
118
119        /**
120         * The externally supplied AES content encryption key (CEK) to use,
121         * {@code null} to generate a CEK for each JWE.
122         */
123        private final SecretKey contentEncryptionKey;
124
125        /**
126         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
127         *
128         * @param publicKey The public EC key. Must not be {@code null}.
129         *
130         * @throws JOSEException If the elliptic curve is not supported.
131         */
132        public ECDHEncrypter(final ECPublicKey publicKey)
133                throws JOSEException {
134
135                this(publicKey, null);
136        }
137
138
139        /**
140         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
141         *
142         * @param ecJWK The EC JSON Web Key (JWK). Must not be {@code null}.
143         *
144         * @throws JOSEException If the elliptic curve is not supported.
145         */
146        public ECDHEncrypter(final ECKey ecJWK) throws
147                JOSEException {
148
149                super(ecJWK.getCurve());
150
151                publicKey = ecJWK.toECPublicKey();
152                contentEncryptionKey = null;
153        }
154        
155        /**
156         * Creates a new Elliptic Curve Diffie-Hellman encrypter with an
157         * optionally specified content encryption key (CEK).
158         *
159         * @param publicKey            The public EC key. Must not be
160         *                             {@code null}.
161         * @param contentEncryptionKey The content encryption key (CEK) to use.
162         *                             If specified its algorithm must be "AES"
163         *                             and its length must match the expected
164         *                             for the JWE encryption method ("enc").
165         *                             If {@code null} a CEK will be generated
166         *                             for each JWE.
167         * @throws JOSEException       If the elliptic curve is not supported.
168         */
169        public ECDHEncrypter(final ECPublicKey publicKey, final SecretKey contentEncryptionKey)
170                throws JOSEException {
171                
172                super(Curve.forECParameterSpec(publicKey.getParams()));
173                
174                this.publicKey = publicKey;
175
176                if (contentEncryptionKey != null) {
177                        if (contentEncryptionKey.getAlgorithm() == null || !contentEncryptionKey.getAlgorithm().equals("AES")) {
178                                throw new IllegalArgumentException("The algorithm of the content encryption key (CEK) must be AES");
179                        } else {
180                                this.contentEncryptionKey = contentEncryptionKey;
181                        }
182                } else {
183                        this.contentEncryptionKey = null;
184                }
185        }
186
187
188        /**
189         * Returns the public EC key.
190         *
191         * @return The public EC key.
192         */
193        public ECPublicKey getPublicKey() {
194
195                return publicKey;
196        }
197
198
199        @Override
200        public Set<Curve> supportedEllipticCurves() {
201
202                return SUPPORTED_ELLIPTIC_CURVES;
203        }
204
205
206        @Override
207        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
208                throws JOSEException {
209
210                // Generate ephemeral EC key pair on the same curve as the consumer's public key
211                KeyPair ephemeralKeyPair = generateEphemeralKeyPair(publicKey.getParams());
212                ECPublicKey ephemeralPublicKey = (ECPublicKey)ephemeralKeyPair.getPublic();
213                ECPrivateKey ephemeralPrivateKey = (ECPrivateKey)ephemeralKeyPair.getPrivate();
214
215                // Add the ephemeral public EC key to the header
216                JWEHeader updatedHeader = new JWEHeader.Builder(header).
217                        ephemeralPublicKey(new ECKey.Builder(getCurve(), ephemeralPublicKey).build()).
218                        build();
219
220                // Derive 'Z'
221                SecretKey Z = ECDH.deriveSharedSecret(
222                        publicKey,
223                        ephemeralPrivateKey,
224                        getJCAContext().getKeyEncryptionProvider());
225
226                return encryptWithZ(updatedHeader, Z, clearText, contentEncryptionKey);
227        }
228
229
230        /**
231         * Generates a new ephemeral EC key pair with the specified curve.
232         *
233         * @param ecParameterSpec The EC key spec. Must not be {@code null}.
234         *
235         * @return The EC key pair.
236         *
237         * @throws JOSEException If the EC key pair couldn't be generated.
238         */
239        private KeyPair generateEphemeralKeyPair(final ECParameterSpec ecParameterSpec)
240                throws JOSEException {
241
242                Provider keProvider = getJCAContext().getKeyEncryptionProvider();
243
244                try {
245                        KeyPairGenerator generator;
246
247                        if (keProvider != null) {
248                                generator = KeyPairGenerator.getInstance("EC", keProvider);
249                        } else {
250                                generator = KeyPairGenerator.getInstance("EC");
251                        }
252
253                        generator.initialize(ecParameterSpec);
254                        return generator.generateKeyPair();
255                } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
256                        throw new JOSEException("Couldn't generate ephemeral EC key pair: " + e.getMessage(), e);
257                }
258        }
259}