001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
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;
019
020
021import java.security.interfaces.ECPrivateKey;
022import java.security.interfaces.ECPublicKey;
023import java.util.Set;
024import javax.crypto.SecretKey;
025
026import com.nimbusds.jose.*;
027import com.nimbusds.jose.crypto.utils.ECChecks;
028import com.nimbusds.jose.jwk.Curve;
029import com.nimbusds.jose.jwk.ECKey;
030import com.nimbusds.jose.util.Base64URL;
031
032
033/**
034 * Elliptic Curve Diffie-Hellman decrypter of
035 * {@link com.nimbusds.jose.JWEObject JWE objects}. Expects a private EC key
036 * (with a P-256, P-384 or P-521 curve).
037 *
038 * <p>See RFC 7518
039 * <a href="https://tools.ietf.org/html/rfc7518#section-4.6">section 4.6</a>
040 * for more information.
041 *
042 * <p>This class is thread-safe.
043 *
044 * <p>Supports the following key management algorithms:
045 *
046 * <ul>
047 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
048 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
049 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
050 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
051 * </ul>
052 *
053 * <p>Supports the following elliptic curves:
054 *
055 * <ul>
056 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_256}
057 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_384}
058 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_521}
059 * </ul>
060 *
061 * <p>Supports the following content encryption algorithms:
062 *
063 * <ul>
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
065 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
066 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
067 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
068 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
069 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
070 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
071 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
072 * </ul>
073 *
074 * @author Vladimir Dzhuvinov
075 * @version 2017-04-13
076 */
077public class ECDHDecrypter extends ECDHCryptoProvider implements JWEDecrypter, CriticalHeaderParamsAware {
078
079
080        /**
081         * The private EC key.
082         */
083        private final ECPrivateKey privateKey;
084
085
086        /**
087         * The critical header policy.
088         */
089        private final CriticalHeaderParamsDeferral critPolicy = new CriticalHeaderParamsDeferral();
090
091
092        /**
093         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
094         *
095         * @param privateKey The private EC key. Must not be {@code null}.
096         *
097         * @throws JOSEException If the elliptic curve is not supported.
098         */
099        public ECDHDecrypter(final ECPrivateKey privateKey)
100                throws JOSEException {
101
102                this(privateKey, null);
103        }
104
105
106        /**
107         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
108         *
109         * @param ecJWK The EC JSON Web Key (JWK). Must contain a private
110         *              part. Must not be {@code null}.
111         *
112         * @throws JOSEException If the elliptic curve is not supported.
113         */
114        public ECDHDecrypter(final ECKey ecJWK)
115                throws JOSEException {
116
117                super(ecJWK.getCurve());
118
119                if (! ecJWK.isPrivate()) {
120                        throw new JOSEException("The EC JWK doesn't contain a private part");
121                }
122
123                this.privateKey = ecJWK.toECPrivateKey();
124        }
125
126
127        /**
128         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
129         *
130         * @param privateKey     The private EC key. Must not be {@code null}.
131         * @param defCritHeaders The names of the critical header parameters
132         *                       that are deferred to the application for
133         *                       processing, empty set or {@code null} if none.
134         *
135         * @throws JOSEException If the elliptic curve is not supported.
136         */
137        public ECDHDecrypter(final ECPrivateKey privateKey, final Set<String> defCritHeaders)
138                throws JOSEException {
139
140                super(Curve.forECParameterSpec(privateKey.getParams()));
141
142                critPolicy.setDeferredCriticalHeaderParams(defCritHeaders);
143
144                this.privateKey = privateKey;
145        }
146
147
148        /**
149         * Returns the private EC key.
150         *
151         * @return The private EC key.
152         */
153        public ECPrivateKey getPrivateKey() {
154
155                return privateKey;
156        }
157
158
159        @Override
160        public Set<String> getProcessedCriticalHeaderParams() {
161
162                return critPolicy.getProcessedCriticalHeaderParams();
163        }
164
165
166        @Override
167        public Set<String> getDeferredCriticalHeaderParams() {
168
169                return critPolicy.getProcessedCriticalHeaderParams();
170        }
171
172
173        @Override
174        public byte[] decrypt(final JWEHeader header,
175                              final Base64URL encryptedKey,
176                              final Base64URL iv,
177                              final Base64URL cipherText,
178                              final Base64URL authTag)
179                throws JOSEException {
180
181                final JWEAlgorithm alg = header.getAlgorithm();
182                final ECDH.AlgorithmMode algMode = ECDH.resolveAlgorithmMode(alg);
183
184                critPolicy.ensureHeaderPasses(header);
185
186                // Get ephemeral EC key
187                ECKey ephemeralKey = header.getEphemeralPublicKey();
188
189                if (ephemeralKey == null) {
190                        throw new JOSEException("Missing ephemeral public EC key \"epk\" JWE header parameter");
191                }
192
193                ECPublicKey ephemeralPublicKey = ephemeralKey.toECPublicKey();
194                
195                // Curve check
196                if (! ECChecks.isPointOnCurve(ephemeralPublicKey, getPrivateKey())) {
197                        throw new JOSEException("Invalid ephemeral public EC key: Point(s) not on the expected curve");
198                }
199
200                // Derive 'Z'
201                SecretKey Z = ECDH.deriveSharedSecret(
202                        ephemeralPublicKey,
203                        privateKey,
204                        getJCAContext().getKeyEncryptionProvider());
205
206                // Derive shared key via concat KDF
207                getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat
208                SecretKey sharedKey = ECDH.deriveSharedKey(header, Z, getConcatKDF());
209
210                final SecretKey cek;
211
212                if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) {
213                        cek = sharedKey;
214                } else if (algMode.equals(ECDH.AlgorithmMode.KW)) {
215                        if (encryptedKey == null) {
216                                throw new JOSEException("Missing JWE encrypted key");
217                        }
218                        cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
219                } else {
220                        throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode);
221                }
222
223                return ContentCryptoProvider.decrypt(header, encryptedKey, iv, cipherText, authTag, cek, getJCAContext());
224        }
225}