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.*;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.Set;
025import javax.crypto.SecretKey;
026
027import com.google.crypto.tink.subtle.X25519;
028import com.nimbusds.jose.*;
029import com.nimbusds.jose.crypto.impl.AAD;
030import com.nimbusds.jose.crypto.impl.ECDH;
031import com.nimbusds.jose.crypto.impl.ECDHCryptoProvider;
032import com.nimbusds.jose.jwk.Curve;
033import com.nimbusds.jose.jwk.OctetKeyPair;
034import com.nimbusds.jose.util.Base64URL;
035import net.jcip.annotations.ThreadSafe;
036
037
038/**
039 * Curve25519 Elliptic Curve Diffie-Hellman encrypter of
040 * {@link com.nimbusds.jose.JWEObject JWE objects}.
041 * Expects a public {@link OctetKeyPair} key with {@code "crv"} X25519.
042 *
043 * <p>See <a href="https://tools.ietf.org/html/rfc8037">RFC 8037</a>
044 * for more information.
045 *
046 * <p>See also {@link ECDHEncrypter} for ECDH on other curves.
047 *
048 * <p>This class is thread-safe.
049 *
050 * <p>Supports the following key management algorithms:
051 *
052 * <ul>
053 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES}
054 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW}
055 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW}
056 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW}
057 * </ul>
058 *
059 * <p>Supports the following elliptic curve:
060 *
061 * <ul>
062 *     <li>{@link com.nimbusds.jose.jwk.Curve#X25519} (Curve25519)
063 * </ul>
064 *
065 * <p>Supports the following content encryption algorithms:
066 *
067 * <ul>
068 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
069 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
070 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
071 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
072 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
073 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
074 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
075 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
076 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
077 * </ul>
078 *
079 * @author Tim McLean
080 * @author Egor Puzanov
081 * @version 2023-03-26
082 */
083@ThreadSafe
084public class X25519Encrypter extends ECDHCryptoProvider implements JWEEncrypter {
085
086
087        /**
088         * The public key.
089         */
090        private final OctetKeyPair publicKey;
091
092
093        /**
094         * Creates a new Curve25519 Elliptic Curve Diffie-Hellman encrypter.
095         *
096         * @param publicKey The public key. Must not be {@code null}.
097         *
098         * @throws JOSEException If the key subtype is not supported.
099         */
100        public X25519Encrypter(final OctetKeyPair publicKey)
101                throws JOSEException {
102
103                this(publicKey, null);
104        }
105
106
107        /**
108         * Creates a new Curve25519 Elliptic Curve Diffie-Hellman encrypter.
109         *
110         * @param publicKey            The public key. Must not be {@code null}.
111         * @param contentEncryptionKey The content encryption key (CEK) to use.
112         *                             If specified its algorithm must be "AES"
113         *                             or "ChaCha20" and its length must match
114         *                             the expected for the JWE encryption
115         *                             method ("enc"). If {@code null} a CEK
116         *                             will be generated for each JWE.
117         *
118         * @throws JOSEException If the key subtype is not supported.
119         */
120        public X25519Encrypter(final OctetKeyPair publicKey, final SecretKey contentEncryptionKey)
121                throws JOSEException {
122
123                super(publicKey.getCurve(), contentEncryptionKey);
124
125                if (! Curve.X25519.equals(publicKey.getCurve())) {
126                        throw new JOSEException("X25519Encrypter only supports OctetKeyPairs with crv=X25519");
127                }
128
129                if (publicKey.isPrivate()) {
130                        throw new JOSEException("X25519Encrypter requires a public key, use OctetKeyPair.toPublicJWK()");
131                }
132
133                this.publicKey = publicKey;
134        }
135
136
137        @Override
138        public Set<Curve> supportedEllipticCurves() {
139
140                return Collections.singleton(Curve.X25519);
141        }
142
143
144        /**
145         * Returns the public key.
146         *
147         * @return The public key.
148         */
149        public OctetKeyPair getPublicKey() {
150
151                return publicKey;
152        }
153
154
155        /**
156         * Encrypts the specified clear text of a {@link JWEObject JWE object}.
157         *
158         * @param header    The JSON Web Encryption (JWE) header. Must specify
159         *                  a supported JWE algorithm and method. Must not be
160         *                  {@code null}.
161         * @param clearText The clear text to encrypt. Must not be {@code null}.
162         *
163         * @return The resulting JWE crypto parts.
164         *
165         * @throws JOSEException If the JWE algorithm or method is not
166         *                       supported or if encryption failed for some
167         *                       other internal reason.
168         */
169        @Deprecated
170        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
171                throws JOSEException {
172
173                return encrypt(header, clearText, AAD.compute(header));
174        }
175
176
177        @Override
178        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText, final byte[] aad)
179                throws JOSEException {
180
181                // Generate ephemeral X25519 key pair
182                final byte[] ephemeralPrivateKeyBytes = X25519.generatePrivateKey();
183                final byte[] ephemeralPublicKeyBytes;
184                try {
185                        ephemeralPublicKeyBytes = X25519.publicFromPrivate(ephemeralPrivateKeyBytes);
186
187                } catch (InvalidKeyException e) {
188                        // Should never happen since we just generated this private key
189                        throw new JOSEException(e.getMessage(), e);
190                }
191
192                final OctetKeyPair ephemeralPrivateKey =
193                        new OctetKeyPair.Builder(getCurve(), Base64URL.encode(ephemeralPublicKeyBytes)).
194                        d(Base64URL.encode(ephemeralPrivateKeyBytes)).
195                        build();
196                final OctetKeyPair ephemeralPublicKey = ephemeralPrivateKey.toPublicJWK();
197
198                // Add the ephemeral public EC key to the header
199                JWEHeader updatedHeader = new JWEHeader.Builder(header).
200                        ephemeralPublicKey(ephemeralPublicKey).
201                        build();
202
203                // Derive 'Z'
204                SecretKey Z = ECDH.deriveSharedSecret(publicKey, ephemeralPrivateKey);
205
206                // for JWEObject we need update the AAD as well
207                final byte[] updatedAAD = Arrays.equals(AAD.compute(header), aad) ? AAD.compute(updatedHeader) : aad;
208
209                return encryptWithZ(updatedHeader, Z, clearText, updatedAAD);
210        }
211}