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