mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
492 lines
19 KiB
Java
492 lines
19 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.security.AlgorithmConstraints;
|
|
import java.security.CryptoPrimitive;
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.KeyFactory;
|
|
import java.security.KeyPair;
|
|
import java.security.KeyPairGenerator;
|
|
import java.security.PrivateKey;
|
|
import java.security.PublicKey;
|
|
import java.security.SecureRandom;
|
|
import java.security.interfaces.ECPublicKey;
|
|
import java.security.spec.ECParameterSpec;
|
|
import java.security.spec.ECPoint;
|
|
import java.security.spec.ECPublicKeySpec;
|
|
import java.util.EnumSet;
|
|
import javax.crypto.KeyAgreement;
|
|
import javax.crypto.SecretKey;
|
|
import javax.net.ssl.SSLHandshakeException;
|
|
import sun.security.ssl.NamedGroup.NamedGroupSpec;
|
|
import sun.security.ssl.X509Authentication.X509Credentials;
|
|
import sun.security.ssl.X509Authentication.X509Possession;
|
|
import sun.security.ssl.XDHKeyExchange.XDHECredentials;
|
|
import sun.security.ssl.XDHKeyExchange.XDHEPossession;
|
|
import sun.security.util.ECUtil;
|
|
|
|
final class ECDHKeyExchange {
|
|
static final SSLPossessionGenerator poGenerator =
|
|
new ECDHEPossessionGenerator();
|
|
static final SSLKeyAgreementGenerator ecdhKAGenerator =
|
|
new ECDHKAGenerator();
|
|
|
|
// TLSv1.3
|
|
static final SSLKeyAgreementGenerator ecdheKAGenerator =
|
|
new ECDHEKAGenerator();
|
|
|
|
// TLSv1-1.2, the KA gets more difficult with EC/XEC keys
|
|
static final SSLKeyAgreementGenerator ecdheXdhKAGenerator =
|
|
new ECDHEXDHKAGenerator();
|
|
|
|
static final class ECDHECredentials implements NamedGroupCredentials {
|
|
final ECPublicKey popPublicKey;
|
|
final NamedGroup namedGroup;
|
|
|
|
ECDHECredentials(ECPublicKey popPublicKey, NamedGroup namedGroup) {
|
|
this.popPublicKey = popPublicKey;
|
|
this.namedGroup = namedGroup;
|
|
}
|
|
|
|
@Override
|
|
public PublicKey getPublicKey() {
|
|
return popPublicKey;
|
|
}
|
|
|
|
@Override
|
|
public NamedGroup getNamedGroup() {
|
|
return namedGroup;
|
|
}
|
|
|
|
static ECDHECredentials valueOf(NamedGroup namedGroup,
|
|
byte[] encodedPoint) throws IOException, GeneralSecurityException {
|
|
|
|
if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_ECDHE) {
|
|
throw new RuntimeException(
|
|
"Credentials decoding: Not ECDHE named group");
|
|
}
|
|
|
|
if (encodedPoint == null || encodedPoint.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
ECParameterSpec parameters =
|
|
(ECParameterSpec)namedGroup.keAlgParamSpec;
|
|
ECPoint point = ECUtil.decodePoint(
|
|
encodedPoint, parameters.getCurve());
|
|
KeyFactory factory = KeyFactory.getInstance("EC");
|
|
ECPublicKey publicKey = (ECPublicKey)factory.generatePublic(
|
|
new ECPublicKeySpec(point, parameters));
|
|
return new ECDHECredentials(publicKey, namedGroup);
|
|
}
|
|
}
|
|
|
|
static final class ECDHEPossession implements NamedGroupPossession {
|
|
final PrivateKey privateKey;
|
|
final ECPublicKey publicKey;
|
|
final NamedGroup namedGroup;
|
|
|
|
ECDHEPossession(NamedGroup namedGroup, SecureRandom random) {
|
|
try {
|
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
|
|
kpg.initialize(namedGroup.keAlgParamSpec, random);
|
|
KeyPair kp = kpg.generateKeyPair();
|
|
privateKey = kp.getPrivate();
|
|
publicKey = (ECPublicKey)kp.getPublic();
|
|
} catch (GeneralSecurityException e) {
|
|
throw new RuntimeException(
|
|
"Could not generate ECDH keypair", e);
|
|
}
|
|
|
|
this.namedGroup = namedGroup;
|
|
}
|
|
|
|
ECDHEPossession(ECDHECredentials credentials, SecureRandom random) {
|
|
ECParameterSpec params = credentials.popPublicKey.getParams();
|
|
try {
|
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
|
|
kpg.initialize(params, random);
|
|
KeyPair kp = kpg.generateKeyPair();
|
|
privateKey = kp.getPrivate();
|
|
publicKey = (ECPublicKey)kp.getPublic();
|
|
} catch (GeneralSecurityException e) {
|
|
throw new RuntimeException(
|
|
"Could not generate ECDH keypair", e);
|
|
}
|
|
|
|
this.namedGroup = credentials.namedGroup;
|
|
}
|
|
|
|
@Override
|
|
public byte[] encode() {
|
|
return ECUtil.encodePoint(
|
|
publicKey.getW(), publicKey.getParams().getCurve());
|
|
}
|
|
|
|
// called by ClientHandshaker with either the server's static or
|
|
// ephemeral public key
|
|
SecretKey getAgreedSecret(
|
|
PublicKey peerPublicKey) throws SSLHandshakeException {
|
|
|
|
try {
|
|
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
|
|
ka.init(privateKey);
|
|
ka.doPhase(peerPublicKey, true);
|
|
return ka.generateSecret("TlsPremasterSecret");
|
|
} catch (GeneralSecurityException e) {
|
|
throw new SSLHandshakeException("Could not generate secret", e);
|
|
}
|
|
}
|
|
|
|
// called by ServerHandshaker
|
|
SecretKey getAgreedSecret(
|
|
byte[] encodedPoint) throws SSLHandshakeException {
|
|
try {
|
|
ECParameterSpec params = publicKey.getParams();
|
|
ECPoint point =
|
|
ECUtil.decodePoint(encodedPoint, params.getCurve());
|
|
KeyFactory kf = KeyFactory.getInstance("EC");
|
|
ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
|
|
PublicKey peerPublicKey = kf.generatePublic(spec);
|
|
return getAgreedSecret(peerPublicKey);
|
|
} catch (GeneralSecurityException | java.io.IOException e) {
|
|
throw new SSLHandshakeException("Could not generate secret", e);
|
|
}
|
|
}
|
|
|
|
// Check constraints of the specified EC public key.
|
|
void checkConstraints(AlgorithmConstraints constraints,
|
|
byte[] encodedPoint) throws SSLHandshakeException {
|
|
try {
|
|
|
|
ECParameterSpec params = publicKey.getParams();
|
|
ECPoint point =
|
|
ECUtil.decodePoint(encodedPoint, params.getCurve());
|
|
ECPublicKeySpec spec = new ECPublicKeySpec(point, params);
|
|
|
|
KeyFactory kf = KeyFactory.getInstance("EC");
|
|
ECPublicKey pubKey = (ECPublicKey)kf.generatePublic(spec);
|
|
|
|
// check constraints of ECPublicKey
|
|
if (!constraints.permits(
|
|
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), pubKey)) {
|
|
throw new SSLHandshakeException(
|
|
"ECPublicKey does not comply to algorithm constraints");
|
|
}
|
|
} catch (GeneralSecurityException | java.io.IOException e) {
|
|
throw new SSLHandshakeException(
|
|
"Could not generate ECPublicKey", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public PublicKey getPublicKey() {
|
|
return publicKey;
|
|
}
|
|
|
|
@Override
|
|
public NamedGroup getNamedGroup() {
|
|
return namedGroup;
|
|
}
|
|
|
|
@Override
|
|
public PrivateKey getPrivateKey() {
|
|
return privateKey;
|
|
}
|
|
}
|
|
|
|
private static final
|
|
class ECDHEPossessionGenerator implements SSLPossessionGenerator {
|
|
// Prevent instantiation of this class.
|
|
private ECDHEPossessionGenerator() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public SSLPossession createPossession(HandshakeContext context) {
|
|
|
|
NamedGroup preferableNamedGroup;
|
|
|
|
// Find most preferred EC or XEC groups
|
|
if ((context.clientRequestedNamedGroups != null) &&
|
|
(!context.clientRequestedNamedGroups.isEmpty())) {
|
|
preferableNamedGroup = NamedGroup.getPreferredGroup(
|
|
context.sslConfig,
|
|
context.negotiatedProtocol,
|
|
context.algorithmConstraints,
|
|
new NamedGroupSpec[] {
|
|
NamedGroupSpec.NAMED_GROUP_ECDHE,
|
|
NamedGroupSpec.NAMED_GROUP_XDH },
|
|
context.clientRequestedNamedGroups);
|
|
} else {
|
|
preferableNamedGroup = NamedGroup.getPreferredGroup(
|
|
context.sslConfig,
|
|
context.negotiatedProtocol,
|
|
context.algorithmConstraints,
|
|
new NamedGroupSpec[] {
|
|
NamedGroupSpec.NAMED_GROUP_ECDHE,
|
|
NamedGroupSpec.NAMED_GROUP_XDH });
|
|
}
|
|
|
|
if (preferableNamedGroup != null) {
|
|
return preferableNamedGroup.createPossession(
|
|
context.sslContext.getSecureRandom());
|
|
}
|
|
|
|
// no match found, cannot use this cipher suite.
|
|
//
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static final
|
|
class ECDHKAGenerator implements SSLKeyAgreementGenerator {
|
|
// Prevent instantiation of this class.
|
|
private ECDHKAGenerator() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public SSLKeyDerivation createKeyDerivation(
|
|
HandshakeContext context) throws IOException {
|
|
if (context instanceof ServerHandshakeContext) {
|
|
return createServerKeyDerivation(
|
|
(ServerHandshakeContext)context);
|
|
} else {
|
|
return createClientKeyDerivation(
|
|
(ClientHandshakeContext)context);
|
|
}
|
|
}
|
|
|
|
private SSLKeyDerivation createServerKeyDerivation(
|
|
ServerHandshakeContext shc) throws IOException {
|
|
X509Possession x509Possession = null;
|
|
ECDHECredentials ecdheCredentials = null;
|
|
for (SSLPossession poss : shc.handshakePossessions) {
|
|
if (!(poss instanceof X509Possession)) {
|
|
continue;
|
|
}
|
|
|
|
ECParameterSpec params =
|
|
((X509Possession)poss).getECParameterSpec();
|
|
if (params == null) {
|
|
continue;
|
|
}
|
|
|
|
NamedGroup ng = NamedGroup.valueOf(params);
|
|
if (ng == null) {
|
|
// unlikely, have been checked during cipher suite
|
|
// negotiation.
|
|
throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Unsupported EC server cert for ECDH key exchange");
|
|
}
|
|
|
|
for (SSLCredentials cred : shc.handshakeCredentials) {
|
|
if (!(cred instanceof ECDHECredentials)) {
|
|
continue;
|
|
}
|
|
if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
|
|
ecdheCredentials = (ECDHECredentials)cred;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ecdheCredentials != null) {
|
|
x509Possession = (X509Possession)poss;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (x509Possession == null) {
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No sufficient ECDHE key agreement parameters negotiated");
|
|
}
|
|
|
|
return new KAKeyDerivation("ECDH", shc,
|
|
x509Possession.popPrivateKey, ecdheCredentials.popPublicKey);
|
|
}
|
|
|
|
private SSLKeyDerivation createClientKeyDerivation(
|
|
ClientHandshakeContext chc) throws IOException {
|
|
ECDHEPossession ecdhePossession = null;
|
|
X509Credentials x509Credentials = null;
|
|
for (SSLPossession poss : chc.handshakePossessions) {
|
|
if (!(poss instanceof ECDHEPossession)) {
|
|
continue;
|
|
}
|
|
|
|
NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
|
|
for (SSLCredentials cred : chc.handshakeCredentials) {
|
|
if (!(cred instanceof X509Credentials)) {
|
|
continue;
|
|
}
|
|
|
|
PublicKey publicKey = ((X509Credentials)cred).popPublicKey;
|
|
if (!publicKey.getAlgorithm().equals("EC")) {
|
|
continue;
|
|
}
|
|
ECParameterSpec params =
|
|
((ECPublicKey)publicKey).getParams();
|
|
NamedGroup namedGroup = NamedGroup.valueOf(params);
|
|
if (namedGroup == null) {
|
|
// unlikely, should have been checked previously
|
|
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Unsupported EC server cert for ECDH key exchange");
|
|
}
|
|
|
|
if (ng.equals(namedGroup)) {
|
|
x509Credentials = (X509Credentials)cred;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (x509Credentials != null) {
|
|
ecdhePossession = (ECDHEPossession)poss;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ecdhePossession == null) {
|
|
throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No sufficient ECDH key agreement parameters negotiated");
|
|
}
|
|
|
|
return new KAKeyDerivation("ECDH", chc,
|
|
ecdhePossession.privateKey, x509Credentials.popPublicKey);
|
|
}
|
|
}
|
|
|
|
private static final
|
|
class ECDHEKAGenerator implements SSLKeyAgreementGenerator {
|
|
// Prevent instantiation of this class.
|
|
private ECDHEKAGenerator() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public SSLKeyDerivation createKeyDerivation(
|
|
HandshakeContext context) throws IOException {
|
|
ECDHEPossession ecdhePossession = null;
|
|
ECDHECredentials ecdheCredentials = null;
|
|
for (SSLPossession poss : context.handshakePossessions) {
|
|
if (!(poss instanceof ECDHEPossession)) {
|
|
continue;
|
|
}
|
|
|
|
NamedGroup ng = ((ECDHEPossession)poss).namedGroup;
|
|
for (SSLCredentials cred : context.handshakeCredentials) {
|
|
if (!(cred instanceof ECDHECredentials)) {
|
|
continue;
|
|
}
|
|
if (ng.equals(((ECDHECredentials)cred).namedGroup)) {
|
|
ecdheCredentials = (ECDHECredentials)cred;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ecdheCredentials != null) {
|
|
ecdhePossession = (ECDHEPossession)poss;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ecdhePossession == null) {
|
|
throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No sufficient ECDHE key agreement parameters negotiated");
|
|
}
|
|
|
|
return new KAKeyDerivation("ECDH", context,
|
|
ecdhePossession.privateKey, ecdheCredentials.popPublicKey);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A Generator for TLSv1-1.2 to create a ECDHE or a XDH KeyDerivation
|
|
* object depending on the negotiated group.
|
|
*/
|
|
private static final
|
|
class ECDHEXDHKAGenerator implements SSLKeyAgreementGenerator {
|
|
// Prevent instantiation of this class.
|
|
private ECDHEXDHKAGenerator() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public SSLKeyDerivation createKeyDerivation(
|
|
HandshakeContext context) throws IOException {
|
|
|
|
NamedGroupPossession namedGroupPossession = null;
|
|
NamedGroupCredentials namedGroupCredentials = null;
|
|
NamedGroup namedGroup = null;
|
|
|
|
// Find a possession/credential combo using the same named group
|
|
search:
|
|
for (SSLPossession poss : context.handshakePossessions) {
|
|
for (SSLCredentials cred : context.handshakeCredentials) {
|
|
if (((poss instanceof ECDHEPossession) &&
|
|
(cred instanceof ECDHECredentials)) ||
|
|
(((poss instanceof XDHEPossession) &&
|
|
(cred instanceof XDHECredentials)))) {
|
|
NamedGroupPossession p = (NamedGroupPossession)poss;
|
|
NamedGroupCredentials c = (NamedGroupCredentials)cred;
|
|
if (p.getNamedGroup() != c.getNamedGroup()) {
|
|
continue;
|
|
} else {
|
|
namedGroup = p.getNamedGroup();
|
|
}
|
|
namedGroupPossession = p;
|
|
namedGroupCredentials = c;
|
|
break search;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (namedGroupPossession == null) {
|
|
throw context.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No sufficient ECDHE/XDH key agreement " +
|
|
"parameters negotiated");
|
|
}
|
|
|
|
String alg;
|
|
switch (namedGroup.spec) {
|
|
case NAMED_GROUP_ECDHE:
|
|
alg = "ECDH";
|
|
break;
|
|
case NAMED_GROUP_XDH:
|
|
alg = "XDH";
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Unexpected named group type");
|
|
}
|
|
|
|
return new KAKeyDerivation(alg, context,
|
|
namedGroupPossession.getPrivateKey(),
|
|
namedGroupCredentials.getPublicKey());
|
|
}
|
|
}
|
|
}
|