mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
449 lines
18 KiB
Java
449 lines
18 KiB
Java
/*
|
|
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
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.InvalidKeySpecException;
|
|
import javax.crypto.interfaces.DHPublicKey;
|
|
import javax.crypto.spec.DHParameterSpec;
|
|
import javax.crypto.spec.DHPublicKeySpec;
|
|
import sun.security.action.GetPropertyAction;
|
|
import sun.security.ssl.NamedGroup.NamedGroupSpec;
|
|
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 NamedGroupCredentials {
|
|
final DHPublicKey popPublicKey;
|
|
final NamedGroup namedGroup;
|
|
|
|
DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) {
|
|
this.popPublicKey = popPublicKey;
|
|
this.namedGroup = namedGroup;
|
|
}
|
|
|
|
@Override
|
|
public PublicKey getPublicKey() {
|
|
return popPublicKey;
|
|
}
|
|
|
|
@Override
|
|
public NamedGroup getNamedGroup() {
|
|
return namedGroup;
|
|
}
|
|
|
|
static DHECredentials valueOf(NamedGroup ng,
|
|
byte[] encodedPublic) throws IOException, GeneralSecurityException {
|
|
|
|
if (ng.spec != NamedGroupSpec.NAMED_GROUP_FFDHE) {
|
|
throw new RuntimeException(
|
|
"Credentials decoding: Not FFDHE named group");
|
|
}
|
|
|
|
if (encodedPublic == null || encodedPublic.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
DHParameterSpec params = (DHParameterSpec)ng.keAlgParamSpec;
|
|
KeyFactory kf = KeyFactory.getInstance("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 NamedGroupPossession {
|
|
final PrivateKey privateKey;
|
|
final DHPublicKey publicKey;
|
|
final NamedGroup namedGroup;
|
|
|
|
DHEPossession(NamedGroup namedGroup, SecureRandom random) {
|
|
try {
|
|
KeyPairGenerator kpg =
|
|
KeyPairGenerator.getInstance("DiffieHellman");
|
|
kpg.initialize(namedGroup.keAlgParamSpec, 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 =
|
|
KeyPairGenerator.getInstance("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 =
|
|
KeyPairGenerator.getInstance("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;
|
|
}
|
|
|
|
// Generate and validate DHPublicKeySpec
|
|
private KeyPair generateDHKeyPair(
|
|
KeyPairGenerator kpg) throws GeneralSecurityException {
|
|
boolean doExtraValidation =
|
|
(!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName()));
|
|
boolean isRecovering = false;
|
|
for (int i = 0; i <= 2; i++) { // Try to recover from failure.
|
|
KeyPair kp = kpg.generateKeyPair();
|
|
// validate the Diffie-Hellman public key
|
|
if (doExtraValidation) {
|
|
DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic());
|
|
try {
|
|
KeyUtil.validate(spec);
|
|
} catch (InvalidKeyException ivke) {
|
|
if (isRecovering) {
|
|
throw ivke;
|
|
}
|
|
// otherwise, ignore the exception and try again
|
|
isRecovering = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return kp;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) {
|
|
if (key instanceof DHPublicKey dhKey) {
|
|
DHParameterSpec params = dhKey.getParams();
|
|
return new DHPublicKeySpec(dhKey.getY(),
|
|
params.getP(), params.getG());
|
|
}
|
|
try {
|
|
KeyFactory factory = KeyFactory.getInstance("DiffieHellman");
|
|
return factory.getKeySpec(key, DHPublicKeySpec.class);
|
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
// unlikely
|
|
throw new RuntimeException("Unable to get DHPublicKeySpec", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public byte[] encode() {
|
|
// Note: the DH public value is encoded as a big-endian integer
|
|
// and padded to the left with zeros to the size of p in bytes.
|
|
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;
|
|
}
|
|
|
|
@Override
|
|
public PublicKey getPublicKey() {
|
|
return publicKey;
|
|
}
|
|
|
|
@Override
|
|
public NamedGroup getNamedGroup() {
|
|
return namedGroup;
|
|
}
|
|
|
|
@Override
|
|
public PrivateKey getPrivateKey() {
|
|
return privateKey;
|
|
}
|
|
}
|
|
|
|
private static final class
|
|
DHEPossessionGenerator implements SSLPossessionGenerator {
|
|
// Flag to use smart ephemeral DH key which size matches the
|
|
// corresponding authentication key
|
|
private static final boolean useSmartEphemeralDHKeys;
|
|
|
|
// Flag to use legacy ephemeral DH key which size is 512 bits for
|
|
// exportable cipher suites, and 768 bits for others
|
|
private static final boolean useLegacyEphemeralDHKeys;
|
|
|
|
// The customized ephemeral DH key size for non-exportable
|
|
// cipher suites.
|
|
private static final int customizedDHKeySize;
|
|
|
|
// Is it for exportable cipher suite?
|
|
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 {
|
|
// DH parameter generation can be extremely slow, best to
|
|
// use one of the supported pre-computed DH parameters
|
|
// (see DHCrypt class).
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prevent instantiation of this class.
|
|
private DHEPossessionGenerator(boolean exportable) {
|
|
this.exportable = exportable;
|
|
}
|
|
|
|
// Used for ServerKeyExchange, TLS 1.2 and prior versions.
|
|
@Override
|
|
public SSLPossession createPossession(HandshakeContext context) {
|
|
NamedGroup preferableNamedGroup;
|
|
if (!useLegacyEphemeralDHKeys &&
|
|
(context.clientRequestedNamedGroups != null) &&
|
|
(!context.clientRequestedNamedGroups.isEmpty())) {
|
|
preferableNamedGroup = NamedGroup.getPreferredGroup(
|
|
context.sslConfig,
|
|
context.negotiatedProtocol,
|
|
context.algorithmConstraints,
|
|
new NamedGroupSpec [] {
|
|
NamedGroupSpec.NAMED_GROUP_FFDHE },
|
|
context.clientRequestedNamedGroups);
|
|
if (preferableNamedGroup != null) {
|
|
return new DHEPossession(preferableNamedGroup,
|
|
context.sslContext.getSecureRandom());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 768 bit ephemeral DH private keys used to be used in
|
|
* ServerKeyExchange except that exportable ciphers max out at 512
|
|
* bit modulus values. We still adhere to this behavior in legacy
|
|
* mode (system property "jdk.tls.ephemeralDHKeySize" is defined
|
|
* as "legacy").
|
|
*
|
|
* Only very old JDK releases don't support DH keys bigger than
|
|
* 1024 bits (JDK 1.5 and 6u/7u releases prior to adding support
|
|
* for DH keys > 1024 bits - see JDK-8062834). A 2048 bit
|
|
* DH key is always used for non-exportable cipher suites in
|
|
* default mode (when the system property
|
|
* "jdk.tls.ephemeralDHKeySize" is not defined).
|
|
*
|
|
* Applications may also want to customize the ephemeral DH key
|
|
* size to a fixed length for non-exportable cipher suites. This
|
|
* can be done by setting the system property
|
|
* "jdk.tls.ephemeralDHKeySize" to a valid positive integer between
|
|
* 1024 and 8192 bits, inclusive.
|
|
*
|
|
* Note that the minimum acceptable key size is 2048 bits except
|
|
* for exportable cipher suites or legacy mode.
|
|
*
|
|
* Note that per RFC 2246, the key size limit of DH is 512 bits for
|
|
* exportable cipher suites. Because of the weakness, exportable
|
|
* cipher suites are deprecated since TLS v1.1 and they are not
|
|
* enabled by default in Oracle provider. The legacy behavior is
|
|
* preserved and a 512 bit DH key is always used for exportable
|
|
* cipher suites.
|
|
*/
|
|
int keySize = exportable ? 512 : 2048; // default mode
|
|
if (!exportable) {
|
|
if (useLegacyEphemeralDHKeys) { // legacy mode
|
|
keySize = 768;
|
|
} else if (useSmartEphemeralDHKeys) { // matched mode
|
|
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);
|
|
|
|
// DH parameter generation can be extremely slow, make
|
|
// sure to use one of the supported pre-computed DH
|
|
// parameters.
|
|
//
|
|
// Old deployed applications may not be ready to
|
|
// support DH key sizes bigger than 2048 bits. Please
|
|
// DON'T use value other than 1024 and 2048 at present.
|
|
// May improve the underlying providers and key size
|
|
// limit in the future when the compatibility and
|
|
// interoperability impact is limited.
|
|
keySize = ks <= 1024 ? 1024 : 2048;
|
|
} // Otherwise, anonymous cipher suites, 2048-bit is used.
|
|
} else if (customizedDHKeySize > 0) { // customized mode
|
|
keySize = customizedDHKeySize;
|
|
}
|
|
}
|
|
|
|
return new DHEPossession(
|
|
keySize, context.sslContext.getSecureRandom());
|
|
}
|
|
}
|
|
|
|
private static final
|
|
class DHEKAGenerator implements SSLKeyAgreementGenerator {
|
|
private static final DHEKAGenerator instance = new DHEKAGenerator();
|
|
|
|
// Prevent instantiation of this class.
|
|
private DHEKAGenerator() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public SSLKeyDerivation createKeyDerivation(
|
|
HandshakeContext context) throws IOException {
|
|
DHEPossession dhePossession = null;
|
|
DHECredentials dheCredentials = null;
|
|
for (SSLPossession poss : context.handshakePossessions) {
|
|
if (!(poss instanceof DHEPossession dhep)) {
|
|
continue;
|
|
}
|
|
|
|
for (SSLCredentials cred : context.handshakeCredentials) {
|
|
if (!(cred instanceof DHECredentials dhec)) {
|
|
continue;
|
|
}
|
|
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) {
|
|
throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No sufficient DHE key agreement parameters negotiated");
|
|
}
|
|
|
|
return new KAKeyDerivation("DiffieHellman", context,
|
|
dhePossession.privateKey, dheCredentials.popPublicKey);
|
|
}
|
|
}
|
|
}
|