001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2021, 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; 019 020 021import com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.JWECryptoParts; 023import com.nimbusds.jose.JWEEncrypter; 024import com.nimbusds.jose.JWEHeader; 025import com.nimbusds.jose.crypto.impl.ECDH; 026import com.nimbusds.jose.crypto.impl.ECDH1PU; 027import com.nimbusds.jose.crypto.impl.ECDH1PUCryptoProvider; 028import com.nimbusds.jose.jwk.Curve; 029import com.nimbusds.jose.jwk.ECKey; 030import net.jcip.annotations.ThreadSafe; 031 032import javax.crypto.SecretKey; 033import java.security.*; 034import java.security.interfaces.ECPrivateKey; 035import java.security.interfaces.ECPublicKey; 036import java.security.spec.ECParameterSpec; 037import java.util.Collections; 038import java.util.LinkedHashSet; 039import java.util.Set; 040 041 042/** 043 * Elliptic Curve Diffie-Hellman encrypter of 044 * {@link com.nimbusds.jose.JWEObject JWE objects} for curves using an EC JWK. 045 * Expects a public EC key (with a P-256, P-384, or P-521 curve). 046 * 047 * <p>Public Key Authenticated Encryption for JOSE 048 * <a href="https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04">ECDH-1PU</a> 049 * for more information. 050 * 051 * <p>For Curve25519/X25519, see {@link ECDH1PUX25519Encrypter} instead. 052 * 053 * <p>This class is thread-safe. 054 * 055 * <p>Supports the following key management algorithms: 056 * 057 * <ul> 058 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_1PU} 059 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_1PU_A128KW} 060 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_1PU_A192KW} 061 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_1PU_A256KW} 062 * </ul> 063 * 064 * <p>Supports the following elliptic curves: 065 * 066 * <ul> 067 * <li>{@link Curve#P_256} 068 * <li>{@link Curve#P_384} 069 * <li>{@link Curve#P_521} 070 * </ul> 071 * 072 * <p>Supports the following content encryption algorithms for Direct key agreement mode: 073 * 074 * <ul> 075 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 076 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 077 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 078 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 079 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} 080 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 081 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} 082 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} 083 * <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P} 084 * </ul> 085 * 086 * <p>Supports the following content encryption algorithms for Key wrapping mode: 087 * 088 * <ul> 089 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 090 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 091 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 092 * </ul> 093 * 094 * @author Alexander Martynov 095 * @version 2021-08-03 096 */ 097@ThreadSafe 098public class ECDH1PUEncrypter extends ECDH1PUCryptoProvider implements JWEEncrypter { 099 100 101 /** 102 * The supported EC JWK curves by the ECDH crypto provider class. 103 */ 104 public static final Set<Curve> SUPPORTED_ELLIPTIC_CURVES; 105 106 107 static { 108 Set<Curve> curves = new LinkedHashSet<>(); 109 curves.add(Curve.P_256); 110 curves.add(Curve.P_384); 111 curves.add(Curve.P_521); 112 SUPPORTED_ELLIPTIC_CURVES = Collections.unmodifiableSet(curves); 113 } 114 115 116 /** 117 * The public EC key. 118 */ 119 private final ECPublicKey publicKey; 120 121 /** 122 * The private EC key; 123 */ 124 private final ECPrivateKey privateKey; 125 126 /** 127 * The externally supplied AES content encryption key (CEK) to use, 128 * {@code null} to generate a CEK for each JWE. 129 */ 130 private final SecretKey contentEncryptionKey; 131 132 /** 133 * Creates a new Elliptic Curve Diffie-Hellman encrypter. 134 * 135 * @param privateKey The private EC key. Must not be {@code null}. 136 * @param publicKey The public EC key. Must not be {@code null}. 137 * 138 * @throws JOSEException If the elliptic curve is not supported. 139 */ 140 public ECDH1PUEncrypter(final ECPrivateKey privateKey, final ECPublicKey publicKey) 141 throws JOSEException { 142 143 this(privateKey, publicKey, null); 144 } 145 146 147 /** 148 * Creates a new Elliptic Curve Diffie-Hellman encrypter with an 149 * optionally specified content encryption key (CEK). 150 * 151 * @param privateKey The private EC key. Must not be 152 * {@code null}. 153 * @param publicKey The public EC key. Must not be 154 * {@code null}. 155 * @param contentEncryptionKey The content encryption key (CEK) to use. 156 * If specified its algorithm must be "AES" 157 * and its length must match the expected 158 * for the JWE encryption method ("enc"). 159 * If {@code null} a CEK will be generated 160 * for each JWE. 161 * @throws JOSEException If the elliptic curve is not supported. 162 */ 163 public ECDH1PUEncrypter(final ECPrivateKey privateKey, 164 final ECPublicKey publicKey, 165 final SecretKey contentEncryptionKey) 166 throws JOSEException { 167 168 super(Curve.forECParameterSpec(publicKey.getParams())); 169 170 this.privateKey = privateKey; 171 this.publicKey = publicKey; 172 173 if (contentEncryptionKey != null && (contentEncryptionKey.getAlgorithm() == null || !contentEncryptionKey.getAlgorithm().equals("AES"))) 174 throw new IllegalArgumentException("The algorithm of the content encryption key (CEK) must be AES"); 175 176 this.contentEncryptionKey = contentEncryptionKey; 177 } 178 179 180 /** 181 * Returns the public EC key. 182 * 183 * @return The public EC key. 184 */ 185 public ECPublicKey getPublicKey() { 186 187 return publicKey; 188 } 189 190 191 /** 192 * Returns the private EC key. 193 * 194 * @return The private EC key. 195 */ 196 public ECPrivateKey getPrivateKey() { 197 198 return privateKey; 199 } 200 201 202 @Override 203 public Set<Curve> supportedEllipticCurves() { 204 205 return SUPPORTED_ELLIPTIC_CURVES; 206 } 207 208 209 @Override 210 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText) 211 throws JOSEException { 212 213 ECDH1PU.validateSameCurve(privateKey, publicKey); 214 215 // Generate ephemeral EC key pair on the same curve as the consumer's public key 216 KeyPair ephemeralKeyPair = generateEphemeralKeyPair(publicKey.getParams()); 217 ECPublicKey ephemeralPublicKey = (ECPublicKey)ephemeralKeyPair.getPublic(); 218 ECPrivateKey ephemeralPrivateKey = (ECPrivateKey)ephemeralKeyPair.getPrivate(); 219 220 // Add the ephemeral public EC key to the header 221 JWEHeader updatedHeader = new JWEHeader.Builder(header). 222 ephemeralPublicKey(new ECKey.Builder(getCurve(), ephemeralPublicKey).build()). 223 build(); 224 225 SecretKey Ze = ECDH.deriveSharedSecret( 226 publicKey, 227 ephemeralPrivateKey, 228 getJCAContext().getKeyEncryptionProvider()); 229 230 SecretKey Zs = ECDH.deriveSharedSecret( 231 publicKey, 232 privateKey, 233 getJCAContext().getKeyEncryptionProvider()); 234 235 SecretKey Z = ECDH1PU.deriveZ(Ze, Zs); 236 237 return encryptWithZ(updatedHeader, Z, clearText, contentEncryptionKey); 238 } 239 240 241 /** 242 * Generates a new ephemeral EC key pair with the specified curve. 243 * 244 * @param ecParameterSpec The EC key spec. Must not be {@code null}. 245 * 246 * @return The EC key pair. 247 * 248 * @throws JOSEException If the EC key pair couldn't be generated. 249 */ 250 private KeyPair generateEphemeralKeyPair(final ECParameterSpec ecParameterSpec) 251 throws JOSEException { 252 253 Provider keProvider = getJCAContext().getKeyEncryptionProvider(); 254 255 try { 256 KeyPairGenerator generator; 257 258 if (keProvider != null) { 259 generator = KeyPairGenerator.getInstance("EC", keProvider); 260 } else { 261 generator = KeyPairGenerator.getInstance("EC"); 262 } 263 264 generator.initialize(ecParameterSpec); 265 return generator.generateKeyPair(); 266 } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { 267 throw new JOSEException("Couldn't generate ephemeral EC key pair: " + e.getMessage(), e); 268 } 269 } 270}