001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2019, Connect2id Ltd and contributors.
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.impl;
019
020
021import java.util.Collections;
022import java.util.LinkedHashSet;
023import java.util.Set;
024import javax.crypto.SecretKey;
025
026import com.nimbusds.jose.*;
027import com.nimbusds.jose.jwk.Curve;
028import com.nimbusds.jose.util.Base64URL;
029
030
031/**
032 * The base abstract class for Elliptic Curve Diffie-Hellman encrypters and
033 * decrypters of {@link com.nimbusds.jose.JWEObject JWE objects}.
034 *
035 * <p>Supports the following key management algorithms:
036 *
037 * <ul>
038 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
039 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
040 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
041 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
042 * </ul>
043 *
044 * <p>Supports the following elliptic curves:
045 *
046 * <ul>
047 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_256}
048 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_384}
049 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_521}
050 *     <li>{@link com.nimbusds.jose.jwk.Curve#X25519}
051 * </ul>
052 *
053 * <p>Supports the following content encryption algorithms:
054 *
055 * <ul>
056 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
057 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
058 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
059 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
060 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
061 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
062 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
063 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
065 * </ul>
066 *
067 * @author Tim McLean
068 * @author Vladimir Dzhuvinov
069 * @author Fernando González Callejas
070 * @version 2019-01-24
071 */
072public abstract class ECDHCryptoProvider extends BaseJWEProvider {
073
074
075        /**
076         * The supported JWE algorithms by the ECDH crypto provider class.
077         */
078        public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS;
079
080
081        /**
082         * The supported encryption methods by the ECDH crypto provider class.
083         */
084        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS;
085
086
087        static {
088                Set<JWEAlgorithm> algs = new LinkedHashSet<>();
089                algs.add(JWEAlgorithm.ECDH_ES);
090                algs.add(JWEAlgorithm.ECDH_ES_A128KW);
091                algs.add(JWEAlgorithm.ECDH_ES_A192KW);
092                algs.add(JWEAlgorithm.ECDH_ES_A256KW);
093                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
094        }
095
096
097        /**
098         * The elliptic curve.
099         */
100        private final Curve curve;
101
102
103        /**
104         * The Concatenation Key Derivation Function (KDF).
105         */
106        private final ConcatKDF concatKDF;
107
108
109        /**
110         * Creates a new Elliptic Curve Diffie-Hellman encryption /decryption
111         * provider.
112         *
113         * @param curve The elliptic curve. Must be supported and not
114         *              {@code null}.
115         *
116         * @throws JOSEException If the elliptic curve is not supported.
117         */
118        protected ECDHCryptoProvider(final Curve curve)
119                throws JOSEException {
120
121                super(SUPPORTED_ALGORITHMS, ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS);
122
123                Curve definedCurve = curve != null ? curve : new Curve("unknown");
124
125                if (! supportedEllipticCurves().contains(curve)) {
126                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEllipticCurve(
127                                definedCurve, supportedEllipticCurves()));
128                }
129
130                this.curve = curve;
131
132                concatKDF = new ConcatKDF("SHA-256");
133        }
134
135
136        /**
137         * Returns the Concatenation Key Derivation Function (KDF).
138         *
139         * @return The concat KDF.
140         */
141        protected ConcatKDF getConcatKDF() {
142
143                return concatKDF;
144        }
145
146
147        /**
148         * Returns the names of the supported elliptic curves. These correspond
149         * to the {@code crv} EC JWK parameter.
150         *
151         * @return The supported elliptic curves.
152         */
153        public abstract Set<Curve> supportedEllipticCurves();
154
155
156        /**
157         * Returns the elliptic curve of the key (JWK designation).
158         *
159         * @return The elliptic curve.
160         */
161        public Curve getCurve() {
162
163                return curve;
164        }
165
166        /**
167         * Encrypts the specified plaintext using the specified shared secret
168         * ("Z").
169         */
170        protected JWECryptoParts encryptWithZ(final JWEHeader header, final SecretKey Z, final byte[] clearText)
171                throws JOSEException {
172                
173                return this.encryptWithZ(header, Z, clearText, null);
174        }
175
176        /**
177         * Encrypts the specified plaintext using the specified shared secret
178         * ("Z") and, if provided, the content encryption key (CEK).
179         */
180        protected JWECryptoParts encryptWithZ(final JWEHeader header,
181                                              final SecretKey Z,
182                                              final byte[] clearText,
183                                              final SecretKey contentEncryptionKey)
184                throws JOSEException {
185
186                final JWEAlgorithm alg = header.getAlgorithm();
187                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
188                final EncryptionMethod enc = header.getEncryptionMethod();
189
190                // Derive shared key via concat KDF
191                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
192                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
193
194                final SecretKey cek;
195                final Base64URL encryptedKey; // The CEK encrypted (second JWE part)
196
197                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
198                        cek = sharedKey;
199                        encryptedKey = null;
200                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
201                        if(contentEncryptionKey != null) { // Use externally supplied CEK
202                                cek = contentEncryptionKey;
203                        } else { // Generate the CEK according to the enc method
204                                cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom());
205                        }
206                        encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider()));
207                } else {
208                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
209                }
210
211                return ContentCryptoProvider.encrypt(header, clearText, cek, encryptedKey, getJCAContext());
212        }
213
214
215        /**
216         * Decrypts the encrypted JWE parts using the specified shared secret
217         * ("Z").
218         */
219        protected byte[] decryptWithZ(final JWEHeader header,
220                                      final SecretKey Z,
221                                      final Base64URL encryptedKey,
222                                      final Base64URL iv,
223                                      final Base64URL cipherText,
224                                      final Base64URL authTag)
225                throws JOSEException {
226
227                final JWEAlgorithm alg = header.getAlgorithm();
228                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
229
230                // Derive shared key via concat KDF
231                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
232                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
233
234                final SecretKey cek;
235
236                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
237                        cek = sharedKey;
238                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
239                        if (encryptedKey == null) {
240                                throw new JOSEException("Missing JWE encrypted key");
241                        }
242                        cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
243                } else {
244                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
245                }
246
247                return ContentCryptoProvider.decrypt(header, encryptedKey, iv, cipherText, authTag, cek, getJCAContext());
248        }
249}