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