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