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.impl; 019 020 021import com.nimbusds.jose.*; 022import com.nimbusds.jose.jwk.Curve; 023import com.nimbusds.jose.util.Base64URL; 024import com.nimbusds.jose.util.Pair; 025 026import javax.crypto.SecretKey; 027import java.util.*; 028 029 030/** 031 * The base abstract class for Elliptic Curve Diffie-Hellman One-Pass Unified 032 * Model encrypters and decrypters of {@link com.nimbusds.jose.JWEObject JWE 033 * objects}. 034 * 035 * <p>Supports the following key management algorithms: 036 * 037 * <ul> 038 * <li>{@link JWEAlgorithm#ECDH_1PU} 039 * <li>{@link JWEAlgorithm#ECDH_1PU_A128KW} 040 * <li>{@link JWEAlgorithm#ECDH_1PU_A192KW} 041 * <li>{@link JWEAlgorithm#ECDH_1PU_A256KW} 042 * </ul> 043 * 044 * <p>Supports the following elliptic curves: 045 * 046 * <ul> 047 * <li>{@link Curve#P_256} 048 * <li>{@link Curve#P_384} 049 * <li>{@link Curve#P_521} 050 * <li>{@link Curve#X25519} 051 * </ul> 052 * 053 * <p>Supports the following content encryption algorithms for Direct key 054 * agreement mode: 055 * 056 * <ul> 057 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 058 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 059 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 060 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} 061 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} 062 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} 063 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} 064 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} 065 * <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P} 066 * </ul> 067 * 068 * <p>Supports the following content encryption algorithms for Key wrapping 069 * mode: 070 * 071 * <ul> 072 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} 073 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} 074 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} 075 * </ul> 076 * 077 * @author Alexander Martynov 078 * @version 2021-08-03 079 */ 080public abstract class ECDH1PUCryptoProvider extends BaseJWEProvider { 081 082 083 /** 084 * The supported JWE algorithms by the ECDH crypto provider class. 085 */ 086 public static final Set<JWEAlgorithm> SUPPORTED_ALGORITHMS; 087 088 089 /** 090 * The supported encryption methods by the ECDH crypto provider class. 091 */ 092 public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS = ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS; 093 094 095 static { 096 Set<JWEAlgorithm> algs = new LinkedHashSet<>(); 097 algs.add(JWEAlgorithm.ECDH_1PU); 098 algs.add(JWEAlgorithm.ECDH_1PU_A128KW); 099 algs.add(JWEAlgorithm.ECDH_1PU_A192KW); 100 algs.add(JWEAlgorithm.ECDH_1PU_A256KW); 101 SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs); 102 } 103 104 105 /** 106 * The elliptic curve. 107 */ 108 private final Curve curve; 109 110 111 /** 112 * The Concatenation Key Derivation Function (KDF). 113 */ 114 private final ConcatKDF concatKDF; 115 116 117 /** 118 * Creates a new Elliptic Curve Diffie-Hellman One-Pass Unified Model 119 * encryption / decryption provider. 120 * 121 * @param curve The elliptic curve. Must be supported and not 122 * {@code null}. 123 * 124 * @throws JOSEException If the elliptic curve is not supported. 125 */ 126 protected ECDH1PUCryptoProvider(final Curve curve) 127 throws JOSEException { 128 129 super(SUPPORTED_ALGORITHMS, ContentCryptoProvider.SUPPORTED_ENCRYPTION_METHODS); 130 131 Curve definedCurve = curve != null ? curve : new Curve("unknown"); 132 133 if (!supportedEllipticCurves().contains(curve)) { 134 throw new JOSEException(AlgorithmSupportMessage.unsupportedEllipticCurve( 135 definedCurve, supportedEllipticCurves())); 136 } 137 138 this.curve = curve; 139 140 concatKDF = new ConcatKDF("SHA-256"); 141 } 142 143 144 /** 145 * Returns the Concatenation Key Derivation Function (KDF). 146 * 147 * @return The concat KDF. 148 */ 149 protected ConcatKDF getConcatKDF() { 150 151 return concatKDF; 152 } 153 154 155 /** 156 * Returns the names of the supported elliptic curves. These correspond 157 * to the {@code crv} JWK parameter. 158 * 159 * @return The supported elliptic curves. 160 */ 161 public abstract Set<Curve> supportedEllipticCurves(); 162 163 164 /** 165 * Returns the elliptic curve of the key (JWK designation). 166 * 167 * @return The elliptic curve. 168 */ 169 public Curve getCurve() { 170 171 return curve; 172 } 173 174 175 /** 176 * Encrypts the specified plaintext using the specified shared secret 177 * ("Z"), with an optionally externally supplied content encryption key 178 * (CEK) for {@link ECDH.AlgorithmMode#KW}. 179 */ 180 protected JWECryptoParts encryptWithZ(final JWEHeader header, 181 final SecretKey Z, 182 final byte[] clearText, 183 final SecretKey contentEncryptionKey) 184 throws JOSEException { 185 186 final JWEAlgorithm alg = header.getAlgorithm(); 187 final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg); 188 final EncryptionMethod enc = header.getEncryptionMethod(); 189 190 final SecretKey cek; 191 final Base64URL encryptedKey; // The CEK encrypted (second JWE part) 192 193 if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) { 194 195 // Derive shared key via concat KDF 196 getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat 197 cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF()); 198 199 return ContentCryptoProvider.encrypt(header, clearText, cek, null, getJCAContext()); 200 } 201 202 if (algMode.equals(ECDH.AlgorithmMode.KW)) { 203 204 // Key wrapping mode supports only AES_CBC_HMAC_SHA2 205 // See https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1 206 if (!EncryptionMethod.Family.AES_CBC_HMAC_SHA.contains(enc)) { 207 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 208 header.getEncryptionMethod(), 209 EncryptionMethod.Family.AES_CBC_HMAC_SHA)); 210 } 211 212 if (contentEncryptionKey != null) { // Use externally supplied CEK 213 cek = contentEncryptionKey; 214 } else { // Generate the CEK according to the enc method 215 cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom()); 216 } 217 218 JWECryptoParts encrypted = ContentCryptoProvider.encrypt(header, clearText, cek, null, getJCAContext()); 219 220 SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, encrypted.getAuthenticationTag(), getConcatKDF()); 221 encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider())); 222 223 return new JWECryptoParts( 224 header, 225 encryptedKey, 226 encrypted.getInitializationVector(), 227 encrypted.getCipherText(), 228 encrypted.getAuthenticationTag() 229 ); 230 } 231 232 throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode); 233 } 234 235 /** 236 * Decrypts the encrypted JWE parts using the specified shared secret ("Z"). 237 */ 238 protected byte[] decryptWithZ(final JWEHeader header, 239 final SecretKey Z, 240 final Base64URL encryptedKey, 241 final Base64URL iv, 242 final Base64URL cipherText, 243 final Base64URL authTag) 244 throws JOSEException { 245 246 final JWEAlgorithm alg = header.getAlgorithm(); 247 final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(alg); 248 249 // Derive shared key via concat KDF 250 getConcatKDF().getJCAContext().setProvider(getJCAContext().getMACProvider()); // update before concat 251 252 final SecretKey cek; 253 254 if (algMode.equals(ECDH.AlgorithmMode.DIRECT)) { 255 cek = ECDH1PU.deriveSharedKey(header, Z, getConcatKDF()); 256 } else if (algMode.equals(ECDH.AlgorithmMode.KW)) { 257 if (encryptedKey == null) { 258 throw new JOSEException("Missing JWE encrypted key"); 259 } 260 261 SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, Z, authTag, getConcatKDF()); 262 cek = AESKW.unwrapCEK(sharedKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider()); 263 } else { 264 throw new JOSEException("Unexpected JWE ECDH algorithm mode: " + algMode); 265 } 266 267 return ContentCryptoProvider.decrypt(header, null, iv, cipherText, authTag, cek, getJCAContext()); 268 } 269 270 protected JWECryptoParts encryptMulti(final JWEHeader header, 271 final List<Pair<UnprotectedHeader, SecretKey>> sharedSecrets, 272 final byte[] clearText) throws JOSEException { 273 274 final ECDH.AlgorithmMode algMode = ECDH1PU.resolveAlgorithmMode(header.getAlgorithm()); 275 final SecretKey cek = ContentCryptoProvider.generateCEK( 276 header.getEncryptionMethod(), 277 getJCAContext().getSecureRandom() 278 ); 279 280 List<JWERecipient> recipients = new ArrayList<>(); 281 boolean encrypted = false; 282 JWECryptoParts parts = null; 283 284 for (Pair<UnprotectedHeader, SecretKey> rs : sharedSecrets) { 285 Base64URL encryptedKey = null; 286 287 if (!encrypted) { 288 parts = encryptWithZ(header, rs.getRight(), clearText, cek); 289 encryptedKey = parts.getEncryptedKey(); 290 encrypted = true; 291 } else if (algMode.equals(ECDH.AlgorithmMode.KW)) { 292 SecretKey sharedKey = ECDH1PU.deriveSharedKey(header, rs.getRight(), parts.getAuthenticationTag(), getConcatKDF()); 293 encryptedKey = Base64URL.encode(AESKW.wrapCEK(cek, sharedKey, getJCAContext().getKeyEncryptionProvider())); 294 } 295 296 if (encryptedKey != null) { 297 recipients.add(new JWERecipient(rs.getLeft(), encryptedKey)); 298 } 299 } 300 301 if (parts == null) { 302 throw new JOSEException("Content MUST be encrypted"); 303 } 304 305 return new JWECryptoParts( 306 parts.getHeader(), 307 Collections.unmodifiableList(recipients), 308 parts.getInitializationVector(), 309 parts.getCipherText(), 310 parts.getAuthenticationTag() 311 ); 312 } 313 314 protected byte[] decryptMulti(final JWEHeader header, 315 final List<Pair<UnprotectedHeader, SecretKey>> sharedSecrets, 316 final List<JWERecipient> recipients, 317 final Base64URL iv, 318 final Base64URL cipherText, 319 final Base64URL authTag) throws JOSEException { 320 321 byte[] result = null; 322 323 for (Pair<UnprotectedHeader, SecretKey> rs : sharedSecrets) { 324 String kid = rs.getLeft().getKeyID(); 325 Base64URL encryptedKey = null; 326 327 if (recipients != null) { 328 for (JWERecipient recipient : recipients) { 329 if (recipient.getHeader() == null) 330 continue; 331 332 if (kid.equals(recipient.getHeader().getKeyID())) { 333 encryptedKey = recipient.getEncryptedKey(); 334 break; 335 } 336 } 337 } 338 339 result = decryptWithZ(header, rs.getRight(), encryptedKey, iv, cipherText, authTag); 340 } 341 342 return result; 343 } 344}