001package com.nimbusds.jose.crypto;
002
003
004import java.security.*;
005import java.security.interfaces.ECPrivateKey;
006import java.security.interfaces.ECPublicKey;
007import java.security.spec.ECParameterSpec;
008
009import javax.crypto.SecretKey;
010
011import net.jcip.annotations.ThreadSafe;
012
013import com.nimbusds.jose.*;
014import com.nimbusds.jose.jwk.ECKey;
015import com.nimbusds.jose.util.Base64URL;
016
017
018/**
019 * Elliptic Curve Diffie-Hellman encrypter of
020 * {@link com.nimbusds.jose.JWEObject JWE objects}. Expects a public EC key
021 * (with a P-256, P-384 or P-521 curve).
022 *
023 * <p>See RFC 7518
024 * <a href="https://tools.ietf.org/html/rfc7518#section-4.6">section 4.6</a>
025 * for more information.
026 *
027 * <p>This class is thread-safe.
028 *
029 * <p>Supports the following key management algorithms:
030 *
031 * <ul>
032 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
033 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
034 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
035 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
036 * </ul>
037 *
038 * <p>Supports the following elliptic curves:
039 *
040 * <ul>
041 *     <li>{@link com.nimbusds.jose.jwk.ECKey.Curve#P_256}
042 *     <li>{@link com.nimbusds.jose.jwk.ECKey.Curve#P_384}
043 *     <li>{@link com.nimbusds.jose.jwk.ECKey.Curve#P_521}
044 * </ul>
045 *
046 * <p>Supports the following content encryption algorithms:
047 *
048 * <ul>
049 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
050 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
051 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
052 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
053 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
054 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
055 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
056 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
057 * </ul>
058 *
059 * @author Vladimir Dzhuvinov
060 * @version 2015-06-08
061 */
062@ThreadSafe
063public class ECDHEncrypter extends ECDHCryptoProvider implements JWEEncrypter {
064
065
066        /**
067         * The public EC key.
068         */
069        private final ECPublicKey publicKey;
070
071
072        /**
073         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
074         *
075         * @param publicKey The public EC key. Must not be {@code null}.
076         *
077         * @throws JOSEException If the elliptic curve is not supported.
078         */
079        public ECDHEncrypter(final ECPublicKey publicKey)
080                throws JOSEException {
081
082                super(ECKey.Curve.forECParameterSpec(publicKey.getParams()));
083
084                this.publicKey = publicKey;
085        }
086
087
088        /**
089         * Creates a new Elliptic Curve Diffie-Hellman encrypter.
090         *
091         * @param ecJWK The EC JSON Web Key (JWK). Must not be {@code null}.
092         *
093         * @throws JOSEException If the elliptic curve is not supported.
094         */
095        public ECDHEncrypter(final ECKey ecJWK)
096                throws JOSEException {
097
098                super(ecJWK.getCurve());
099
100                publicKey = ecJWK.toECPublicKey();
101        }
102
103
104        /**
105         * Returns the public EC key.
106         *
107         * @return The public EC key.
108         */
109        public ECPublicKey getPublicKey() {
110
111                return publicKey;
112        }
113
114
115        @Override
116        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
117                throws JOSEException {
118
119                final JWEAlgorithm alg = header.getAlgorithm();
120                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
121                final EncryptionMethod enc = header.getEncryptionMethod();
122
123                // Generate ephemeral EC key pair on the same curve as the consumer's public key
124                KeyPair ephemeralKeyPair = generateEphemeralKeyPair(publicKey.getParams());
125                ECPublicKey ephemeralPublicKey = (ECPublicKey)ephemeralKeyPair.getPublic();
126                ECPrivateKey ephemeralPrivateKey = (ECPrivateKey)ephemeralKeyPair.getPrivate();
127
128                // Derive 'Z'
129                SecretKey Z = ECDH.deriveSharedSecret(
130                        publicKey,
131                        ephemeralPrivateKey,
132                        getJCAContext().getKeyEncryptionProvider());
133
134                // Derive shared key via concat KDF
135                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
136                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
137
138                final SecretKey cek;
139                final Base64URL encryptedKey; // The CEK encrypted (second JWE part)
140
141                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
142                        cek = sharedKey;
143                        encryptedKey = null;
144                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
145                        cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom());
146                        encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
147                } else {
148                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
149                }
150
151                // Add the ephemeral public EC key to the header
152                JWEHeader updatedHeader = new JWEHeader.Builder(header).
153                        ephemeralPublicKey(new ECKey.Builder(getCurve(), ephemeralPublicKey).build()).
154                        build();
155
156                return ContentCryptoProvider.encrypt(updatedHeader, clearText, cek, encryptedKey, getJCAContext());
157        }
158
159
160        /**
161         * Generates a new ephemeral EC key pair with the specified curve.
162         *
163         * @param ecParameterSpec The EC key spec. Must not be {@code null}.
164         *
165         * @return The EC key pair.
166         *
167         * @throws JOSEException If the EC key pair couldn't be generated.
168         */
169        private KeyPair generateEphemeralKeyPair(final ECParameterSpec ecParameterSpec)
170                throws JOSEException {
171
172                Provider keProvider = getJCAContext().getKeyEncryptionProvider();
173
174                try {
175                        KeyPairGenerator generator;
176
177                        if (keProvider != null) {
178                                generator = KeyPairGenerator.getInstance("EC", keProvider);
179                        } else {
180                                generator = KeyPairGenerator.getInstance("EC");
181                        }
182
183                        generator.initialize(ecParameterSpec);
184                        return generator.generateKeyPair();
185                } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
186                        throw new JOSEException("Couldn't generate ephemeral EC key pair: " + e.getMessage(), e);
187                }
188        }
189}