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