package sun.security.ssl;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPublicKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLHandshakeException;
import sun.security.action.GetPropertyAction;
import sun.security.ssl.CipherSuite.HashAlg;
import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
import sun.security.ssl.SupportedGroupsExtension.NamedGroupType;
import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
import sun.security.ssl.X509Authentication.X509Possession;
import sun.security.util.KeyUtil;
final class DHKeyExchange {
static final SSLPossessionGenerator poGenerator =
new DHEPossessionGenerator(false);
static final SSLPossessionGenerator poExportableGenerator =
new DHEPossessionGenerator(true);
static final SSLKeyAgreementGenerator kaGenerator =
new DHEKAGenerator();
static final class DHECredentials implements SSLCredentials {
final DHPublicKey popPublicKey;
final NamedGroup namedGroup;
DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) {
this.popPublicKey = popPublicKey;
this.namedGroup = namedGroup;
}
static DHECredentials valueOf(NamedGroup ng,
byte[] encodedPublic) throws IOException, GeneralSecurityException {
if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) {
throw new RuntimeException(
"Credentials decoding: Not FFDHE named group");
}
if (encodedPublic == null || encodedPublic.length == 0) {
return null;
}
DHParameterSpec params = (DHParameterSpec)ng.getParameterSpec();
if (params == null) {
return null;
}
KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman");
DHPublicKeySpec spec = new DHPublicKeySpec(
new BigInteger(1, encodedPublic),
params.getP(), params.getG());
DHPublicKey publicKey =
(DHPublicKey)kf.generatePublic(spec);
return new DHECredentials(publicKey, ng);
}
}
static final class DHEPossession implements SSLPossession {
final PrivateKey privateKey;
final DHPublicKey publicKey;
final NamedGroup namedGroup;
DHEPossession(NamedGroup namedGroup, SecureRandom random) {
try {
KeyPairGenerator kpg =
JsseJce.getKeyPairGenerator("DiffieHellman");
DHParameterSpec params =
(DHParameterSpec)namedGroup.getParameterSpec();
kpg.initialize(params, random);
KeyPair kp = generateDHKeyPair(kpg);
if (kp == null) {
throw new RuntimeException("Could not generate DH keypair");
}
privateKey = kp.getPrivate();
publicKey = (DHPublicKey)kp.getPublic();
} catch (GeneralSecurityException gse) {
throw new RuntimeException(
"Could not generate DH keypair", gse);
}
this.namedGroup = namedGroup;
}
DHEPossession(int keyLength, SecureRandom random) {
DHParameterSpec params =
PredefinedDHParameterSpecs.definedParams.get(keyLength);
try {
KeyPairGenerator kpg =
JsseJce.getKeyPairGenerator("DiffieHellman");
if (params != null) {
kpg.initialize(params, random);
} else {
kpg.initialize(keyLength, random);
}
KeyPair kp = generateDHKeyPair(kpg);
if (kp == null) {
throw new RuntimeException(
"Could not generate DH keypair of " +
keyLength + " bits");
}
privateKey = kp.getPrivate();
publicKey = (DHPublicKey)kp.getPublic();
} catch (GeneralSecurityException gse) {
throw new RuntimeException(
"Could not generate DH keypair", gse);
}
this.namedGroup = NamedGroup.valueOf(publicKey.getParams());
}
DHEPossession(DHECredentials credentials, SecureRandom random) {
try {
KeyPairGenerator kpg =
JsseJce.getKeyPairGenerator("DiffieHellman");
kpg.initialize(credentials.popPublicKey.getParams(), random);
KeyPair kp = generateDHKeyPair(kpg);
if (kp == null) {
throw new RuntimeException("Could not generate DH keypair");
}
privateKey = kp.getPrivate();
publicKey = (DHPublicKey)kp.getPublic();
} catch (GeneralSecurityException gse) {
throw new RuntimeException(
"Could not generate DH keypair", gse);
}
this.namedGroup = credentials.namedGroup;
}
private KeyPair generateDHKeyPair(
KeyPairGenerator kpg) throws GeneralSecurityException {
boolean doExtraValiadtion =
(!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName()));
boolean isRecovering = false;
for (int i = 0; i <= 2; i++) {
KeyPair kp = kpg.generateKeyPair();
if (doExtraValiadtion) {
DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic());
try {
KeyUtil.validate(spec);
} catch (InvalidKeyException ivke) {
if (isRecovering) {
throw ivke;
}
isRecovering = true;
continue;
}
}
return kp;
}
return null;
}
private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) {
if (key instanceof DHPublicKey) {
DHPublicKey dhKey = (DHPublicKey)key;
DHParameterSpec params = dhKey.getParams();
return new DHPublicKeySpec(dhKey.getY(),
params.getP(), params.getG());
}
try {
KeyFactory factory = JsseJce.getKeyFactory("DiffieHellman");
return factory.getKeySpec(key, DHPublicKeySpec.class);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Unable to get DHPublicKeySpec", e);
}
}
@Override
public byte[] encode() {
byte[] encoded = Utilities.toByteArray(publicKey.getY());
int pSize = (KeyUtil.getKeySize(publicKey) + 7) >>> 3;
if (pSize > 0 && encoded.length < pSize) {
byte[] buffer = new byte[pSize];
System.arraycopy(encoded, 0,
buffer, pSize - encoded.length, encoded.length);
encoded = buffer;
}
return encoded;
}
}
private static final class
DHEPossessionGenerator implements SSLPossessionGenerator {
private static final boolean useSmartEphemeralDHKeys;
private static final boolean useLegacyEphemeralDHKeys;
private static final int customizedDHKeySize;
private final boolean exportable;
static {
String property = GetPropertyAction.privilegedGetProperty(
"jdk.tls.ephemeralDHKeySize");
if (property == null || property.isEmpty()) {
useLegacyEphemeralDHKeys = false;
useSmartEphemeralDHKeys = false;
customizedDHKeySize = -1;
} else if ("matched".equals(property)) {
useLegacyEphemeralDHKeys = false;
useSmartEphemeralDHKeys = true;
customizedDHKeySize = -1;
} else if ("legacy".equals(property)) {
useLegacyEphemeralDHKeys = true;
useSmartEphemeralDHKeys = false;
customizedDHKeySize = -1;
} else {
useLegacyEphemeralDHKeys = false;
useSmartEphemeralDHKeys = false;
try {
customizedDHKeySize = Integer.parseUnsignedInt(property);
if (customizedDHKeySize < 1024 ||
customizedDHKeySize > 8192 ||
(customizedDHKeySize & 0x3f) != 0) {
throw new IllegalArgumentException(
"Unsupported customized DH key size: " +
customizedDHKeySize + ". " +
"The key size must be multiple of 64, " +
"and range from 1024 to 8192 (inclusive)");
}
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(
"Invalid system property jdk.tls.ephemeralDHKeySize");
}
}
}
private DHEPossessionGenerator(boolean exportable) {
this.exportable = exportable;
}
@Override
public SSLPossession createPossession(HandshakeContext context) {
NamedGroup preferableNamedGroup = null;
if (!useLegacyEphemeralDHKeys &&
(context.clientRequestedNamedGroups != null) &&
(!context.clientRequestedNamedGroups.isEmpty())) {
preferableNamedGroup =
SupportedGroups.getPreferredGroup(
context.negotiatedProtocol,
context.algorithmConstraints,
NamedGroupType.NAMED_GROUP_FFDHE,
context.clientRequestedNamedGroups);
if (preferableNamedGroup != null) {
return new DHEPossession(preferableNamedGroup,
context.sslContext.getSecureRandom());
}
}
int keySize = exportable ? 512 : 1024;
if (!exportable) {
if (useLegacyEphemeralDHKeys) {
keySize = 768;
} else if (useSmartEphemeralDHKeys) {
PrivateKey key = null;
ServerHandshakeContext shc =
(ServerHandshakeContext)context;
if (shc.interimAuthn instanceof X509Possession) {
key = ((X509Possession)shc.interimAuthn).popPrivateKey;
}
if (key != null) {
int ks = KeyUtil.getKeySize(key);
keySize = ks <= 1024 ? 1024 : 2048;
}
} else if (customizedDHKeySize > 0) {
keySize = customizedDHKeySize;
}
}
return new DHEPossession(
keySize, context.sslContext.getSecureRandom());
}
}
private static final
class DHEKAGenerator implements SSLKeyAgreementGenerator {
static private DHEKAGenerator instance = new DHEKAGenerator();
private DHEKAGenerator() {
}
@Override
public SSLKeyDerivation createKeyDerivation(
HandshakeContext context) throws IOException {
DHEPossession dhePossession = null;
DHECredentials dheCredentials = null;
for (SSLPossession poss : context.handshakePossessions) {
if (!(poss instanceof DHEPossession)) {
continue;
}
DHEPossession dhep = (DHEPossession)poss;
for (SSLCredentials cred : context.handshakeCredentials) {
if (!(cred instanceof DHECredentials)) {
continue;
}
DHECredentials dhec = (DHECredentials)cred;
if (dhep.namedGroup != null && dhec.namedGroup != null) {
if (dhep.namedGroup.equals(dhec.namedGroup)) {
dheCredentials = (DHECredentials)cred;
break;
}
} else {
DHParameterSpec pps = dhep.publicKey.getParams();
DHParameterSpec cps = dhec.popPublicKey.getParams();
if (pps.getP().equals(cps.getP()) &&
pps.getG().equals(cps.getG())) {
dheCredentials = (DHECredentials)cred;
break;
}
}
}
if (dheCredentials != null) {
dhePossession = (DHEPossession)poss;
break;
}
}
if (dhePossession == null || dheCredentials == null) {
throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
"No sufficient DHE key agreement parameters negotiated");
}
return new DHEKAKeyDerivation(context,
dhePossession.privateKey, dheCredentials.popPublicKey);
}
private static final
class DHEKAKeyDerivation implements SSLKeyDerivation {
private final HandshakeContext context;
private final PrivateKey localPrivateKey;
private final PublicKey peerPublicKey;
DHEKAKeyDerivation(HandshakeContext context,
PrivateKey localPrivateKey,
PublicKey peerPublicKey) {
this.context = context;
this.localPrivateKey = localPrivateKey;
this.peerPublicKey = peerPublicKey;
}
@Override
public SecretKey deriveKey(String algorithm,
AlgorithmParameterSpec params) throws IOException {
if (!context.negotiatedProtocol.useTLS13PlusSpec()) {
return t12DeriveKey(algorithm, params);
} else {
return t13DeriveKey(algorithm, params);
}
}
private SecretKey t12DeriveKey(String algorithm,
AlgorithmParameterSpec params) throws IOException {
try {
KeyAgreement ka = JsseJce.getKeyAgreement("DiffieHellman");
ka.init(localPrivateKey);
ka.doPhase(peerPublicKey, true);
SecretKey preMasterSecret =
ka.generateSecret("TlsPremasterSecret");
SSLMasterKeyDerivation mskd =
SSLMasterKeyDerivation.valueOf(
context.negotiatedProtocol);
if (mskd == null) {
throw new SSLHandshakeException(
"No expected master key derivation for protocol: " +
context.negotiatedProtocol.name);
}
SSLKeyDerivation kd = mskd.createKeyDerivation(
context, preMasterSecret);
return kd.deriveKey("MasterSecret", params);
} catch (GeneralSecurityException gse) {
throw (SSLHandshakeException) new SSLHandshakeException(
"Could not generate secret").initCause(gse);
}
}
private SecretKey t13DeriveKey(String algorithm,
AlgorithmParameterSpec params) throws IOException {
try {
KeyAgreement ka = JsseJce.getKeyAgreement("DiffieHellman");
ka.init(localPrivateKey);
ka.doPhase(peerPublicKey, true);
SecretKey sharedSecret =
ka.generateSecret("TlsPremasterSecret");
HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg;
SSLKeyDerivation kd = context.handshakeKeyDerivation;
HKDF hkdf = new HKDF(hashAlg.name);
if (kd == null) {
byte[] zeros = new byte[hashAlg.hashLength];
SecretKeySpec ikm =
new SecretKeySpec(zeros, "TlsPreSharedSecret");
SecretKey earlySecret =
hkdf.extract(zeros, ikm, "TlsEarlySecret");
kd = new SSLSecretDerivation(context, earlySecret);
}
SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null);
return hkdf.extract(saltSecret, sharedSecret, algorithm);
} catch (GeneralSecurityException gse) {
throw (SSLHandshakeException) new SSLHandshakeException(
"Could not generate secret").initCause(gse);
}
}
}
}
}