001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2026, 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 com.nimbusds.jose.*;
022import com.nimbusds.jose.crypto.impl.AAD;
023import com.nimbusds.jose.crypto.impl.CriticalHeaderParamsDeferral;
024import com.nimbusds.jose.crypto.impl.ECDH;
025import com.nimbusds.jose.crypto.impl.ECDHCryptoProvider;
026import com.nimbusds.jose.crypto.opts.OptionUtils;
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
032import javax.crypto.SecretKey;
033import java.security.PrivateKey;
034import java.security.interfaces.ECPrivateKey;
035import java.security.interfaces.ECPublicKey;
036import java.util.Collections;
037import java.util.LinkedHashSet;
038import java.util.Set;
039
040
041/**
042 * Elliptic Curve Diffie-Hellman decrypter of
043 * {@link com.nimbusds.jose.JWEObject JWE objects} for curves using EC JWK
044 * keys. Expects a private EC key (with a P-256, P-384 or P-521 curve).
045 *
046 * <p>See RFC 7518
047 * <a href="https://tools.ietf.org/html/rfc7518#section-4.6">section 4.6</a>
048 * for more information.
049 *
050 * <p>For Curve25519/X25519, see {@link X25519Decrypter} instead.
051 *
052 * <p>This class is thread-safe.
053 *
054 * <p>Supports the following key management algorithms:
055 *
056 * <ul>
057 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
058 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
059 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
060 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
061 * </ul>
062 *
063 * <p>Supports the following elliptic curves:
064 *
065 * <ul>
066 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_256}
067 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_384}
068 *     <li>{@link com.nimbusds.jose.jwk.Curve#P_521}
069 * </ul>
070 *
071 * <p>Supports the following content encryption algorithms:
072 *
073 * <ul>
074 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
075 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
076 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
077 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
078 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
079 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
080 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
081 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
082 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
083 * </ul>
084 *
085 * @author Vladimir Dzhuvinov
086 * @author Egor Puzanov
087 * @version 2026-09-16
088 */
089public class ECDHDecrypter extends ECDHCryptoProvider implements JWEDecrypter, CriticalHeaderParamsAware {
090
091
092        /**
093         * The supported EC JWK curves by the ECDH crypto provider class.
094         */
095        public static final Set<Curve> SUPPORTED_ELLIPTIC_CURVES;
096
097
098        static {
099                Set<Curve> curves = new LinkedHashSet<>();
100                curves.add(Curve.P_256);
101                curves.add(Curve.P_384);
102                curves.add(Curve.P_521);
103                SUPPORTED_ELLIPTIC_CURVES = Collections.unmodifiableSet(curves);
104        }
105
106
107        /**
108         * The private EC key.
109         */
110        private final PrivateKey privateKey;
111
112
113        /**
114         * The critical header policy.
115         */
116        private final CriticalHeaderParamsDeferral critPolicy = new CriticalHeaderParamsDeferral();
117
118
119        /**
120         * The configured options, empty set if none.
121         */
122        private final Set<JWEDecrypterOption> opts;
123
124
125        /**
126         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
127         *
128         * @param privateKey The private EC key. Must not be {@code null}.
129         *
130         * @throws JOSEException If the elliptic curve is not supported.
131         */
132        public ECDHDecrypter(final ECPrivateKey privateKey)
133                throws JOSEException {
134
135                this(privateKey, (Set<String>) null, Collections.<JWEDecrypterOption>emptySet());
136        }
137
138
139        /**
140         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
141         *
142         * @param ecJWK The EC JSON Web Key (JWK). Must contain a private
143         *              part. Must not be {@code null}.
144         *
145         * @throws JOSEException If the elliptic curve is not supported.
146         */
147        public ECDHDecrypter(final ECKey ecJWK)
148                throws JOSEException {
149
150                this(ecJWK, null, Collections.<JWEDecrypterOption>emptySet());
151        }
152
153
154        /**
155         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
156         *
157         * @param ecJWK The EC JSON Web Key (JWK). Must contain a private
158         *              part. Must not be {@code null}.
159         * @param opts  The decryption options, empty or {@code null} if none.
160         *
161         * @throws JOSEException If the elliptic curve is not supported.
162         */
163        public ECDHDecrypter(final ECKey ecJWK,
164                             final Set<JWEDecrypterOption> opts)
165                throws JOSEException {
166
167                this(ecJWK, null, opts);
168        }
169
170
171        /**
172         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
173         *
174         * @param ecJWK          The EC JSON Web Key (JWK). Must contain a
175         *                       private part. Must not be {@code null}.
176         * @param defCritHeaders The names of the critical header parameters
177         *                       that are deferred to the application for
178         *                       processing, empty set or {@code null} if none.
179         * @param opts           The decryption options, empty or {@code null}
180         *                       if none.
181         *
182         * @throws JOSEException If the elliptic curve is not supported.
183         */
184        public ECDHDecrypter(final ECKey ecJWK,
185                             final Set<String> defCritHeaders,
186                             final Set<JWEDecrypterOption> opts)
187                throws JOSEException {
188
189                super(ecJWK.getCurve(), null);
190
191                if (! ecJWK.isPrivate()) {
192                        throw new JOSEException("The EC JWK doesn't contain a private part");
193                }
194
195                this.privateKey = ecJWK.toECPrivateKey();
196                this.opts = opts != null ? opts : Collections.<JWEDecrypterOption>emptySet();
197                critPolicy.setDeferredCriticalHeaderParams(defCritHeaders);
198        }
199
200
201        /**
202         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
203         *
204         * @param privateKey     The private EC key. Must not be {@code null}.
205         * @param defCritHeaders The names of the critical header parameters
206         *                       that are deferred to the application for
207         *                       processing, empty set or {@code null} if none.
208         *
209         * @throws JOSEException If the elliptic curve is not supported.
210         */
211        public ECDHDecrypter(final ECPrivateKey privateKey, final Set<String> defCritHeaders)
212                throws JOSEException {
213
214                this(privateKey, defCritHeaders, Collections.<JWEDecrypterOption>emptySet());
215        }
216
217
218        /**
219         * Creates a new Elliptic Curve Diffie-Hellman decrypter.
220         *
221         * @param privateKey     The private EC key. Must not be {@code null}.
222         * @param defCritHeaders The names of the critical header parameters
223         *                       that are deferred to the application for
224         *                       processing, empty set or {@code null} if none.
225         * @param opts           The decryption options, empty or {@code null}
226         *                       if none.
227         *
228         * @throws JOSEException If the elliptic curve is not supported.
229         */
230        public ECDHDecrypter(final ECPrivateKey privateKey,
231                             final Set<String> defCritHeaders,
232                             final Set<JWEDecrypterOption> opts)
233                throws JOSEException {
234
235                this(privateKey, defCritHeaders, Curve.forECParameterSpec(privateKey.getParams()), opts);
236        }
237
238
239        /**
240         * Creates a new Elliptic Curve Diffie-Hellman decrypter. This
241         * constructor can also accept a private EC key located in a PKCS#11
242         * store that doesn't expose the private key parameters (such as a
243         * smart card or HSM).
244         *
245         * @param privateKey     The private EC key. Must not be {@code null}.
246         * @param defCritHeaders The names of the critical header parameters
247         *                       that are deferred to the application for
248         *                       processing, empty set or {@code null} if none.
249         * @param curve          The key curve. Must not be {@code null}.
250         *
251         * @throws JOSEException If the elliptic curve is not supported.
252         */
253        public ECDHDecrypter(final PrivateKey privateKey,
254                             final Set<String> defCritHeaders,
255                             final Curve curve)
256                throws JOSEException {
257
258                this(privateKey, defCritHeaders, curve, Collections.<JWEDecrypterOption>emptySet());
259        }
260
261
262        /**
263         * Creates a new Elliptic Curve Diffie-Hellman decrypter. This
264         * constructor can also accept a private EC key located in a PKCS#11
265         * store that doesn't expose the private key parameters (such as a
266         * smart card or HSM).
267         *
268         * @param privateKey     The private EC key. Must not be {@code null}.
269         * @param defCritHeaders The names of the critical header parameters
270         *                       that are deferred to the application for
271         *                       processing, empty set or {@code null} if none.
272         * @param curve          The key curve. Must not be {@code null}.
273         * @param opts           The decryption options, empty or {@code null}
274         *                       if none.
275         *
276         * @throws JOSEException If the elliptic curve is not supported.
277         */
278        public ECDHDecrypter(final PrivateKey privateKey,
279                             final Set<String> defCritHeaders,
280                             final Curve curve,
281                             final Set<JWEDecrypterOption> opts)
282                throws JOSEException {
283
284                super(curve, null);
285
286                critPolicy.setDeferredCriticalHeaderParams(defCritHeaders);
287
288                this.privateKey = privateKey;
289
290                this.opts = opts != null ? opts : Collections.<JWEDecrypterOption>emptySet();
291        }
292
293
294        /**
295         * Returns the private EC key.
296         *
297         * @return The private EC key. Casting to
298         *         {@link java.security.interfaces.ECPrivateKey} may not be
299         *         possible if the key is located in a PKCS#11 store that
300         *         doesn't expose the private key parameters.
301         */
302        public PrivateKey getPrivateKey() {
303
304                return privateKey;
305        }
306
307
308        @Override
309        public Set<Curve> supportedEllipticCurves() {
310
311                return SUPPORTED_ELLIPTIC_CURVES;
312        }
313
314
315        @Override
316        public Set<String> getProcessedCriticalHeaderParams() {
317
318                return critPolicy.getProcessedCriticalHeaderParams();
319        }
320
321
322        @Override
323        public Set<String> getDeferredCriticalHeaderParams() {
324
325                return critPolicy.getDeferredCriticalHeaderParams();
326        }
327
328
329        /**
330         * Decrypts the specified cipher text of a {@link JWEObject JWE
331         * Object}.
332         *
333         * @param header       The JSON Web Encryption (JWE) header. Must
334         *                     specify a supported JWE algorithm and method.
335         *                     Must not be {@code null}.
336         * @param encryptedKey The encrypted key, {@code null} if not required
337         *                     by the JWE algorithm.
338         * @param iv           The initialisation vector, {@code null} if not
339         *                     required by the JWE algorithm.
340         * @param cipherText   The cipher text to decrypt. Must not be
341         *                     {@code null}.
342         * @param authTag      The authentication tag, {@code null} if not
343         *                     required.
344         *
345         * @return The clear text.
346         *
347         * @throws JOSEException If the JWE algorithm or method is not
348         *                       supported, if a critical header parameter is
349         *                       not supported or marked for deferral to the
350         *                       application, or if decryption failed for some
351         *                       other reason.
352         */
353        @Deprecated
354        public byte[] decrypt(final JWEHeader header,
355                       final Base64URL encryptedKey,
356                       final Base64URL iv,
357                       final Base64URL cipherText,
358                       final Base64URL authTag)
359                throws JOSEException {
360
361                return decrypt(header, encryptedKey, iv, cipherText, authTag, AAD.compute(header));
362        }
363
364
365        @Override
366        public byte[] decrypt(final JWEHeader header,
367                              final Base64URL encryptedKey,
368                              final Base64URL iv,
369                              final Base64URL cipherText,
370                              final Base64URL authTag,
371                              final byte[] aad)
372                throws JOSEException {
373
374                critPolicy.ensureHeaderPasses(header);
375
376                // Get ephemeral EC key
377                ECKey ephemeralKey = (ECKey) header.getEphemeralPublicKey();
378
379                if (ephemeralKey == null) {
380                        throw new JOSEException("Missing ephemeral public EC key \"epk\" JWE header parameter");
381                }
382
383                ECPublicKey ephemeralPublicKey = ephemeralKey.toECPublicKey();
384                
385                // Curve check
386                if (getPrivateKey() instanceof ECPrivateKey) {
387                        ECPrivateKey ecPrivateKey = (ECPrivateKey)getPrivateKey();
388                        if (!ECChecks.isPointOnCurve(ephemeralPublicKey, ecPrivateKey)) {
389                                throw new JOSEException("Invalid ephemeral public EC key: Point(s) not on the expected curve");
390                        }
391                } else {
392                        if (!ECChecks.isPointOnCurve(ephemeralPublicKey, getCurve().toECParameterSpec())) {
393                                throw new JOSEException("Invalid ephemeral public EC key: Point(s) not on the expected curve");
394                        }
395                }
396
397                // Derive 'Z'
398                SecretKey Z = ECDH.deriveSharedSecret(
399                        ephemeralPublicKey,
400                        privateKey,
401                        getJCAContext().getKeyEncryptionProvider());
402
403                int maxDecompressedLength = OptionUtils.resolveMaxDecompressedPlainTextLength(opts);
404
405                return decryptWithZ(header, aad, Z, encryptedKey, iv, cipherText, authTag, maxDecompressedLength);
406        }
407}