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 * </ul> 089 * 090 * @author Tim McLean 091 * @author Vladimir Dzhuvinov 092 * @author Fernando González Callejas 093 * @version 2019-01-24 094 */ 095@ThreadSafe 096public class ECDHEncrypter extends ECDHCryptoProvider implements JWEEncrypter { 097 098 099 /** 100 * The supported EC JWK curves by the ECDH crypto provider class. 101 */ 102 public static final Set<Curve> SUPPORTED_ELLIPTIC_CURVES; 103 104 105 static { 106 Set<Curve> curves = new LinkedHashSet<>(); 107 curves.add(Curve.P_256); 108 curves.add(Curve.P_384); 109 curves.add(Curve.P_521); 110 SUPPORTED_ELLIPTIC_CURVES = Collections.unmodifiableSet(curves); 111 } 112 113 114 /** 115 * The public EC key. 116 */ 117 private final ECPublicKey publicKey; 118 119 /** 120 * The externally supplied AES content encryption key (CEK) to use, 121 * {@code null} to generate a CEK for each JWE. 122 */ 123 private final SecretKey contentEncryptionKey; 124 125 /** 126 * Creates a new Elliptic Curve Diffie-Hellman encrypter. 127 * 128 * @param publicKey The public EC key. Must not be {@code null}. 129 * 130 * @throws JOSEException If the elliptic curve is not supported. 131 */ 132 public ECDHEncrypter(final ECPublicKey publicKey) 133 throws JOSEException { 134 135 this(publicKey, null); 136 } 137 138 139 /** 140 * Creates a new Elliptic Curve Diffie-Hellman encrypter. 141 * 142 * @param ecJWK The EC JSON Web Key (JWK). Must not be {@code null}. 143 * 144 * @throws JOSEException If the elliptic curve is not supported. 145 */ 146 public ECDHEncrypter(final ECKey ecJWK) throws 147 JOSEException { 148 149 super(ecJWK.getCurve()); 150 151 publicKey = ecJWK.toECPublicKey(); 152 contentEncryptionKey = null; 153 } 154 155 /** 156 * Creates a new Elliptic Curve Diffie-Hellman encrypter with an 157 * optionally specified content encryption key (CEK). 158 * 159 * @param publicKey The public EC key. Must not be 160 * {@code null}. 161 * @param contentEncryptionKey The content encryption key (CEK) to use. 162 * If specified its algorithm must be "AES" 163 * and its length must match the expected 164 * for the JWE encryption method ("enc"). 165 * If {@code null} a CEK will be generated 166 * for each JWE. 167 * @throws JOSEException If the elliptic curve is not supported. 168 */ 169 public ECDHEncrypter(final ECPublicKey publicKey, final SecretKey contentEncryptionKey) 170 throws JOSEException { 171 172 super(Curve.forECParameterSpec(publicKey.getParams())); 173 174 this.publicKey = publicKey; 175 176 if (contentEncryptionKey != null) { 177 if (contentEncryptionKey.getAlgorithm() == null || !contentEncryptionKey.getAlgorithm().equals("AES")) { 178 throw new IllegalArgumentException("The algorithm of the content encryption key (CEK) must be AES"); 179 } else { 180 this.contentEncryptionKey = contentEncryptionKey; 181 } 182 } else { 183 this.contentEncryptionKey = null; 184 } 185 } 186 187 188 /** 189 * Returns the public EC key. 190 * 191 * @return The public EC key. 192 */ 193 public ECPublicKey getPublicKey() { 194 195 return publicKey; 196 } 197 198 199 @Override 200 public Set<Curve> supportedEllipticCurves() { 201 202 return SUPPORTED_ELLIPTIC_CURVES; 203 } 204 205 206 @Override 207 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText) 208 throws JOSEException { 209 210 // Generate ephemeral EC key pair on the same curve as the consumer's public key 211 KeyPair ephemeralKeyPair = generateEphemeralKeyPair(publicKey.getParams()); 212 ECPublicKey ephemeralPublicKey = (ECPublicKey)ephemeralKeyPair.getPublic(); 213 ECPrivateKey ephemeralPrivateKey = (ECPrivateKey)ephemeralKeyPair.getPrivate(); 214 215 // Add the ephemeral public EC key to the header 216 JWEHeader updatedHeader = new JWEHeader.Builder(header). 217 ephemeralPublicKey(new ECKey.Builder(getCurve(), ephemeralPublicKey).build()). 218 build(); 219 220 // Derive 'Z' 221 SecretKey Z = ECDH.deriveSharedSecret( 222 publicKey, 223 ephemeralPrivateKey, 224 getJCAContext().getKeyEncryptionProvider()); 225 226 return encryptWithZ(updatedHeader, Z, clearText, contentEncryptionKey); 227 } 228 229 230 /** 231 * Generates a new ephemeral EC key pair with the specified curve. 232 * 233 * @param ecParameterSpec The EC key spec. Must not be {@code null}. 234 * 235 * @return The EC key pair. 236 * 237 * @throws JOSEException If the EC key pair couldn't be generated. 238 */ 239 private KeyPair generateEphemeralKeyPair(final ECParameterSpec ecParameterSpec) 240 throws JOSEException { 241 242 Provider keProvider = getJCAContext().getKeyEncryptionProvider(); 243 244 try { 245 KeyPairGenerator generator; 246 247 if (keProvider != null) { 248 generator = KeyPairGenerator.getInstance("EC", keProvider); 249 } else { 250 generator = KeyPairGenerator.getInstance("EC"); 251 } 252 253 generator.initialize(ecParameterSpec); 254 return generator.generateKeyPair(); 255 } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { 256 throw new JOSEException("Couldn't generate ephemeral EC key pair: " + e.getMessage(), e); 257 } 258 } 259}