001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, 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.nio.charset.StandardCharsets;
022import java.security.InvalidKeyException;
023import java.security.NoSuchAlgorithmException;
024import java.security.PrivateKey;
025import java.security.Provider;
026import java.security.interfaces.ECPublicKey;
027import javax.crypto.KeyAgreement;
028import javax.crypto.SecretKey;
029import javax.crypto.spec.SecretKeySpec;
030
031import com.google.crypto.tink.subtle.X25519;
032
033import com.nimbusds.jose.EncryptionMethod;
034import com.nimbusds.jose.JOSEException;
035import com.nimbusds.jose.JWEAlgorithm;
036import com.nimbusds.jose.JWEHeader;
037import com.nimbusds.jose.jwk.Curve;
038import com.nimbusds.jose.jwk.OctetKeyPair;
039
040
041/**
042 * Elliptic Curve Diffie-Hellman key agreement functions and utilities.
043 *
044 * @author Vladimir Dzhuvinov
045 * @version 2018-12-12
046 */
047public class ECDH {
048
049
050        /**
051         * Enumeration of the Elliptic Curve Diffie-Hellman Ephemeral Static
052         * algorithm modes.
053         */
054        public enum AlgorithmMode {
055
056                /**
057                 * Direct key agreement mode.
058                 */
059                DIRECT,
060
061
062                /**
063                 * Key wrapping mode.
064                 */
065                KW
066        }
067
068
069        /**
070         * Resolves the ECDH algorithm mode.
071         *
072         * @param alg The JWE algorithm. Must be supported and not
073         *            {@code null}.
074         *
075         * @return The algorithm mode.
076         *
077         * @throws JOSEException If the JWE algorithm is not supported.
078         */
079        public static AlgorithmMode resolveAlgorithmMode(final JWEAlgorithm alg)
080                throws JOSEException {
081
082                if (alg.equals(JWEAlgorithm.ECDH_ES)) {
083
084                        return AlgorithmMode.DIRECT;
085
086                } else if (alg.equals(JWEAlgorithm.ECDH_ES_A128KW) ||
087                        alg.equals(JWEAlgorithm.ECDH_ES_A192KW) ||
088                        alg.equals(JWEAlgorithm.ECDH_ES_A256KW)) {
089
090                        return AlgorithmMode.KW;
091                } else {
092
093                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(
094                                alg,
095                                ECDHCryptoProvider.SUPPORTED_ALGORITHMS));
096                }
097        }
098
099
100        /**
101         * Returns the bit length of the shared key (derived via concat KDF)
102         * for the specified JWE ECDH algorithm.
103         *
104         * @param alg The JWE ECDH algorithm. Must be supported and not
105         *            {@code null}.
106         * @param enc The encryption method. Must be supported} and not
107         *            {@code null}.
108         *
109         * @return The bit length of the shared key.
110         *
111         * @throws JOSEException If the JWE algorithm or encryption method is
112         *                       not supported.
113         */
114        public static int sharedKeyLength(final JWEAlgorithm alg, final EncryptionMethod enc)
115                throws JOSEException {
116
117                if (alg.equals(JWEAlgorithm.ECDH_ES)) {
118
119                        int length = enc.cekBitLength();
120
121                        if (length == 0) {
122                                throw new JOSEException("Unsupported JWE encryption method " + enc);
123                        }
124
125                        return length;
126
127                } else if (alg.equals(JWEAlgorithm.ECDH_ES_A128KW)) {
128                        return 128;
129                } else if (alg.equals(JWEAlgorithm.ECDH_ES_A192KW)) {
130                        return  192;
131                } else if (alg.equals(JWEAlgorithm.ECDH_ES_A256KW)) {
132                        return  256;
133                } else {
134                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(
135                                alg, ECDHCryptoProvider.SUPPORTED_ALGORITHMS));
136                }
137        }
138
139
140        /**
141         * Derives a shared secret (also called 'Z') from the specified ECDH
142         * key agreement.
143         *
144         * @param publicKey  The public EC key, i.e. the consumer's public EC
145         *                   key on encryption, or the ephemeral public EC key
146         *                   on decryption. Must not be {@code null}.
147         * @param privateKey The private EC Key, i.e. the ephemeral private EC
148         *                   key on encryption, or the consumer's private EC
149         *                   key on decryption. Must not be {@code null}.
150         * @param provider   The specific JCA provider for the ECDH key
151         *                   agreement, {@code null} to use the default one.
152         *
153         * @return The derived shared secret ('Z'), with algorithm "AES".
154         *
155         * @throws JOSEException If derivation of the shared secret failed.
156         */
157        public static SecretKey deriveSharedSecret(final ECPublicKey publicKey,
158                                                   final PrivateKey privateKey,
159                                                   final Provider provider)
160                throws JOSEException {
161
162                // Get an ECDH key agreement instance from the JCA provider
163                KeyAgreement keyAgreement;
164
165                try {
166                        if (provider != null) {
167                                keyAgreement = KeyAgreement.getInstance("ECDH", provider);
168                        } else {
169                                keyAgreement = KeyAgreement.getInstance("ECDH");
170                        }
171
172                } catch (NoSuchAlgorithmException e) {
173                        throw new JOSEException("Couldn't get an ECDH key agreement instance: " + e.getMessage(), e);
174                }
175
176                try {
177                        keyAgreement.init(privateKey);
178                        keyAgreement.doPhase(publicKey, true);
179
180                } catch (InvalidKeyException e) {
181                        throw new JOSEException("Invalid key for ECDH key agreement: " + e.getMessage(), e);
182                }
183
184                return new SecretKeySpec(keyAgreement.generateSecret(), "AES");
185        }
186
187
188        /**
189         * Derives a shared secret (also called 'Z') from the specified ECDH
190         * key agreement.
191         *
192         * @param publicKey  The public OKP key, i.e. the consumer's public EC
193         *                   key on encryption, or the ephemeral public EC key
194         *                   on decryption. Must not be {@code null}.
195         * @param privateKey The private OKP key, i.e. the ephemeral private EC
196         *                   key on encryption, or the consumer's private EC
197         *                   key on decryption. Must not be {@code null}.
198         *
199         * @return The derived shared secret ('Z'), with algorithm "AES".
200         *
201         * @throws JOSEException If derivation of the shared secret failed.
202         */
203        public static SecretKey deriveSharedSecret(final OctetKeyPair publicKey, final OctetKeyPair privateKey)
204                throws JOSEException {
205
206                if (publicKey.isPrivate()) {
207                        throw new JOSEException("Expected public key but received OKP with 'd' value");
208                }
209
210                if (! Curve.X25519.equals(publicKey.getCurve())) {
211                        throw new JOSEException("Expected public key OKP with crv=X25519");
212                }
213
214                if (! privateKey.isPrivate()) {
215                        throw new JOSEException("Expected private key but received OKP without 'd' value");
216                }
217
218                if (! Curve.X25519.equals(privateKey.getCurve())) {
219                        throw new JOSEException("Expected private key OKP with crv=X25519");
220                }
221
222                final byte[] privateKeyBytes = privateKey.getDecodedD();
223                final byte[] publicKeyBytes = publicKey.getDecodedX();
224
225                final byte[] sharedSecretBytes;
226                try {
227                        sharedSecretBytes = X25519.computeSharedSecret(privateKeyBytes, publicKeyBytes);
228                } catch (InvalidKeyException e) {
229                        throw new JOSEException(e.getMessage(), e);
230                }
231
232                return new SecretKeySpec(sharedSecretBytes, "AES");
233        }
234
235
236        /**
237         * Derives a shared key (via concat KDF).
238         *
239         * @param header    The JWE header. Its algorithm and encryption method
240         *                  must be supported. Must not be {@code null}.
241         * @param Z         The derived shared secret ('Z'). Must not be
242         *                  {@code null}.
243         * @param concatKDF The concat KDF. Must be initialised and not
244         *                  {@code null}.
245         *
246         * @return The derived shared key.
247         *
248         * @throws JOSEException If derivation of the shared key failed.
249         */
250        public static SecretKey deriveSharedKey(final JWEHeader header,
251                                                final SecretKey Z,
252                                                final ConcatKDF concatKDF)
253                throws JOSEException {
254
255                final int sharedKeyLength = sharedKeyLength(header.getAlgorithm(), header.getEncryptionMethod());
256
257                // Set the alg ID for the concat KDF
258                AlgorithmMode algMode = resolveAlgorithmMode(header.getAlgorithm());
259
260                final String algID;
261
262                if (algMode == AlgorithmMode.DIRECT) {
263                        // algID = enc
264                        algID = header.getEncryptionMethod().getName();
265                } else if (algMode == AlgorithmMode.KW) {
266                        // algID = alg
267                        algID = header.getAlgorithm().getName();
268                } else {
269                        throw new JOSEException("Unsupported JWE ECDH algorithm mode: " + algMode);
270                }
271
272                return concatKDF.deriveKey(
273                        Z,
274                        sharedKeyLength,
275                        ConcatKDF.encodeDataWithLength(algID.getBytes(StandardCharsets.US_ASCII)),
276                        ConcatKDF.encodeDataWithLength(header.getAgreementPartyUInfo()),
277                        ConcatKDF.encodeDataWithLength(header.getAgreementPartyVInfo()),
278                        ConcatKDF.encodeIntData(sharedKeyLength),
279                        ConcatKDF.encodeNoData());
280        }
281
282        
283        /**
284         * Prevents public instantiation.
285         */
286        private ECDH() {
287
288        }
289}