8350807: Certificates using MD5 algorithm that are disabled by default are incorrectly allowed in TLSv1.3 when re-enabled

Reviewed-by: mullan
This commit is contained in:
Artur Barashev 2025-04-17 13:45:47 +00:00 committed by Sean Mullan
parent dda4b5a4ad
commit abb23828f9
16 changed files with 351 additions and 182 deletions

View File

@ -99,13 +99,7 @@ final class CertSignAlgsExtension {
}
// Produce the extension.
if (chc.localSupportedCertSignAlgs == null) {
chc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
chc.sslConfig,
chc.algorithmConstraints, chc.activeProtocols,
CERTIFICATE_SCOPE);
}
SignatureScheme.updateHandshakeLocalSupportedAlgs(chc);
int vectorLen = SignatureScheme.sizeInRecord() *
chc.localSupportedCertSignAlgs.size();
@ -245,15 +239,8 @@ final class CertSignAlgsExtension {
}
// Produce the extension.
if (shc.localSupportedCertSignAlgs == null) {
shc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints,
List.of(shc.negotiatedProtocol),
CERTIFICATE_SCOPE);
}
// localSupportedCertSignAlgs has been already updated when we set
// the negotiated protocol.
int vectorLen = SignatureScheme.sizeInRecord()
* shc.localSupportedCertSignAlgs.size();
byte[] extData = new byte[vectorLen + 2];

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, 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
@ -697,46 +697,6 @@ final class CertificateMessage {
}
}
/**
* When a failure happens during certificate checking from an
* {@link X509TrustManager}, determine what TLS alert description
* to use.
*
* @param cexc The exception thrown by the {@link X509TrustManager}
*
* @return A byte value corresponding to a TLS alert description number.
*/
private static Alert getCertificateAlert(
ClientHandshakeContext chc, CertificateException cexc) {
// The specific reason for the failure will determine how to
// set the alert description value
Alert alert = Alert.CERTIFICATE_UNKNOWN;
Throwable baseCause = cexc.getCause();
if (baseCause instanceof CertPathValidatorException cpve) {
Reason reason = cpve.getReason();
if (reason == BasicReason.REVOKED) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_REVOKED;
} else if (
reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_UNKNOWN;
} else if (reason == BasicReason.ALGORITHM_CONSTRAINED) {
alert = Alert.UNSUPPORTED_CERTIFICATE;
} else if (reason == BasicReason.EXPIRED) {
alert = Alert.CERTIFICATE_EXPIRED;
} else if (reason == BasicReason.INVALID_SIGNATURE ||
reason == BasicReason.NOT_YET_VALID) {
alert = Alert.BAD_CERTIFICATE;
}
}
return alert;
}
}
/**
@ -1329,37 +1289,57 @@ final class CertificateMessage {
return certs;
}
/**
* When a failure happens during certificate checking from an
* {@link X509TrustManager}, determine what TLS alert description
* to use.
*
* @param cexc The exception thrown by the {@link X509TrustManager}
*
* @return A byte value corresponding to a TLS alert description number.
*/
private static Alert getCertificateAlert(
ClientHandshakeContext chc, CertificateException cexc) {
// The specific reason for the failure will determine how to
// set the alert description value
Alert alert = Alert.CERTIFICATE_UNKNOWN;
}
Throwable baseCause = cexc.getCause();
if (baseCause instanceof CertPathValidatorException cpve) {
Reason reason = cpve.getReason();
if (reason == BasicReason.REVOKED) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_REVOKED;
} else if (
reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_UNKNOWN;
/**
* When a failure happens during certificate checking from an
* {@link X509TrustManager}, determine what TLS alert description
* to use.
*
* @param cexc The exception thrown by the {@link X509TrustManager}
* @return A byte value corresponding to a TLS alert description number.
*/
private static Alert getCertificateAlert(
ClientHandshakeContext chc, CertificateException cexc) {
// The specific reason for the failure will determine how to
// set the alert description value
Alert alert = Alert.CERTIFICATE_UNKNOWN;
Throwable baseCause = cexc.getCause();
if (baseCause instanceof CertPathValidatorException cpve) {
Reason reason = cpve.getReason();
if (reason == BasicReason.REVOKED) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_REVOKED;
} else if (reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) {
alert = chc.staplingActive ?
Alert.BAD_CERT_STATUS_RESPONSE :
Alert.CERTIFICATE_UNKNOWN;
} else if (reason == BasicReason.EXPIRED) {
alert = Alert.CERTIFICATE_EXPIRED;
} else if (reason == BasicReason.INVALID_SIGNATURE
|| reason == BasicReason.NOT_YET_VALID) {
alert = Alert.BAD_CERTIFICATE;
} else if (reason == BasicReason.ALGORITHM_CONSTRAINED) {
alert = Alert.UNSUPPORTED_CERTIFICATE;
// Per TLSv1.3 RFC we MUST abort the handshake with a
// "bad_certificate" alert if we reject certificate
// because of the signature using MD5 or SHA1 algorithm.
if (chc.negotiatedProtocol != null
&& chc.negotiatedProtocol.useTLS13PlusSpec()) {
final String exMsg = cexc.getMessage().toUpperCase();
if (exMsg.contains("MD5WITH")
|| exMsg.contains("SHA1WITH")) {
alert = Alert.BAD_CERTIFICATE;
}
}
}
return alert;
}
return alert;
}
}

View File

@ -636,25 +636,11 @@ final class CertificateRequest {
// The producing happens in server side only.
ServerHandshakeContext shc = (ServerHandshakeContext) context;
if (shc.localSupportedSignAlgs == null) {
shc.localSupportedSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
HANDSHAKE_SCOPE);
}
if (shc.localSupportedCertSignAlgs == null) {
shc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
CERTIFICATE_SCOPE);
}
// According to TLSv1.2 RFC, CertificateRequest message must
// contain signature schemes supported for both:
// handshake signatures and certificate signatures.
// localSupportedSignAlgs and localSupportedCertSignAlgs have been
// already updated when we set the negotiated protocol.
List<SignatureScheme> certReqSignAlgs =
new ArrayList<>(shc.localSupportedSignAlgs);
certReqSignAlgs.retainAll(shc.localSupportedCertSignAlgs);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2025, 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
@ -825,6 +825,10 @@ final class ClientHello {
"Negotiated protocol version: " + negotiatedProtocol.name);
}
// Protocol version is negotiated, update locally supported
// signature schemes according to the protocol being used.
SignatureScheme.updateHandshakeLocalSupportedAlgs(context);
// Consume the handshake message for the specific protocol version.
if (negotiatedProtocol.isDTLS) {
if (negotiatedProtocol.useTLS13PlusSpec()) {

View File

@ -42,7 +42,6 @@ import sun.security.ssl.SessionTicketExtension.SessionTicketSpec;
import sun.security.util.HexDumpEncoder;
import static sun.security.ssl.SSLExtension.*;
import static sun.security.ssl.SignatureScheme.CERTIFICATE_SCOPE;
/**
* Pack of the "pre_shared_key" extension.
@ -445,13 +444,7 @@ final class PreSharedKeyExtension {
// localSupportedCertSignAlgs field is populated. This is particularly
// important when client authentication was used in an initial session,
// and it is now being resumed.
if (shc.localSupportedCertSignAlgs == null) {
shc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
CERTIFICATE_SCOPE);
}
SignatureScheme.updateHandshakeLocalSupportedAlgs(shc);
// Validate the required client authentication.
if (result &&

View File

@ -25,9 +25,6 @@
package sun.security.ssl;
import static sun.security.ssl.SignatureScheme.CERTIFICATE_SCOPE;
import static sun.security.ssl.SignatureScheme.HANDSHAKE_SCOPE;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.AlgorithmConstraints;
@ -272,22 +269,6 @@ final class ServerHello {
"Not resumption, and no new session is allowed");
}
if (shc.localSupportedSignAlgs == null) {
shc.localSupportedSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
HANDSHAKE_SCOPE);
}
if (shc.localSupportedCertSignAlgs == null) {
shc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
CERTIFICATE_SCOPE);
}
SSLSessionImpl session =
new SSLSessionImpl(shc, CipherSuite.C_NULL);
session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize);
@ -522,22 +503,6 @@ final class ServerHello {
"Not resumption, and no new session is allowed");
}
if (shc.localSupportedSignAlgs == null) {
shc.localSupportedSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
HANDSHAKE_SCOPE);
}
if (shc.localSupportedCertSignAlgs == null) {
shc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints, shc.activeProtocols,
CERTIFICATE_SCOPE);
}
SSLSessionImpl session =
new SSLSessionImpl(shc, CipherSuite.C_NULL);
session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize);
@ -959,6 +924,10 @@ final class ServerHello {
"Negotiated protocol version: " + serverVersion.name);
}
// Protocol version is negotiated, update locally supported
// signature schemes according to the protocol being used.
SignatureScheme.updateHandshakeLocalSupportedAlgs(chc);
// TLS 1.3 key share extension may have produced client
// possessions for TLS 1.3 key exchanges.
//
@ -1010,6 +979,10 @@ final class ServerHello {
"Negotiated protocol version: " + serverVersion.name);
}
// Protocol version is negotiated, update locally supported
// signature schemes according to the protocol being used.
SignatureScheme.updateHandshakeLocalSupportedAlgs(chc);
if (serverHello.serverRandom.isVersionDowngrade(chc)) {
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"A potential protocol version downgrade attack");

View File

@ -40,7 +40,6 @@ import javax.net.ssl.SSLSessionContext;
import static sun.security.ssl.SSLExtension.CH_SESSION_TICKET;
import static sun.security.ssl.SSLExtension.SH_SESSION_TICKET;
import static sun.security.ssl.SignatureScheme.CERTIFICATE_SCOPE;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
@ -353,13 +352,7 @@ final class SessionTicketExtension {
return new byte[0];
}
if (chc.localSupportedCertSignAlgs == null) {
chc.localSupportedCertSignAlgs =
SignatureScheme.getSupportedAlgorithms(
chc.sslConfig,
chc.algorithmConstraints, chc.activeProtocols,
CERTIFICATE_SCOPE);
}
SignatureScheme.updateHandshakeLocalSupportedAlgs(chc);
return chc.resumingSession.getPskIdentity();
}

View File

@ -187,13 +187,7 @@ final class SignatureAlgorithmsExtension {
}
// Produce the extension.
if (chc.localSupportedSignAlgs == null) {
chc.localSupportedSignAlgs =
SignatureScheme.getSupportedAlgorithms(
chc.sslConfig,
chc.algorithmConstraints, chc.activeProtocols,
HANDSHAKE_SCOPE);
}
SignatureScheme.updateHandshakeLocalSupportedAlgs(chc);
int vectorLen = SignatureScheme.sizeInRecord() *
chc.localSupportedSignAlgs.size();
@ -417,18 +411,14 @@ final class SignatureAlgorithmsExtension {
}
// Produce the extension.
List<SignatureScheme> sigAlgs =
SignatureScheme.getSupportedAlgorithms(
shc.sslConfig,
shc.algorithmConstraints,
List.of(shc.negotiatedProtocol),
HANDSHAKE_SCOPE);
int vectorLen = SignatureScheme.sizeInRecord() * sigAlgs.size();
// localSupportedSignAlgs has been already updated when we
// set the negotiated protocol.
int vectorLen = SignatureScheme.sizeInRecord()
* shc.localSupportedSignAlgs.size();
byte[] extData = new byte[vectorLen + 2];
ByteBuffer m = ByteBuffer.wrap(extData);
Record.putInt16(m, vectorLen);
for (SignatureScheme ss : sigAlgs) {
for (SignatureScheme ss : shc.localSupportedSignAlgs) {
Record.putInt16(m, ss.id);
}

View File

@ -374,9 +374,40 @@ enum SignatureScheme {
&& (namedGroup == null || namedGroup.isPermitted(constraints));
}
// Helper method to update all locally supported signature schemes for
// a given HandshakeContext.
static void updateHandshakeLocalSupportedAlgs(HandshakeContext hc) {
// To improve performance we only update when necessary.
// No need to do anything if we already computed the local supported
// algorithms and either there is no negotiated protocol yet or the
// only active protocol ends up to be the negotiated protocol.
if (hc.localSupportedSignAlgs != null
&& hc.localSupportedCertSignAlgs != null
&& (hc.negotiatedProtocol == null
|| hc.activeProtocols.size() == 1)) {
return;
}
List<ProtocolVersion> protocols = hc.negotiatedProtocol != null ?
List.of(hc.negotiatedProtocol) :
hc.activeProtocols;
hc.localSupportedSignAlgs = getSupportedAlgorithms(
hc.sslConfig,
hc.algorithmConstraints,
protocols,
HANDSHAKE_SCOPE);
hc.localSupportedCertSignAlgs = getSupportedAlgorithms(
hc.sslConfig,
hc.algorithmConstraints,
protocols,
CERTIFICATE_SCOPE);
}
// Get local supported algorithm collection complying to algorithm
// constraints and SSL scopes.
static List<SignatureScheme> getSupportedAlgorithms(
private static List<SignatureScheme> getSupportedAlgorithms(
SSLConfiguration config,
SSLAlgorithmConstraints constraints,
List<ProtocolVersion> activeProtocols,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2025, 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
@ -105,6 +105,7 @@ public class CriticalSubjectAltName implements HostnameVerifier {
(SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket sslServerSocket =
(SSLServerSocket) sslssf.createServerSocket(serverPort);
sslServerSocket.setEnabledProtocols(new String[]{"TLSv1.2"});
serverPort = sslServerSocket.getLocalPort();
/*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, 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
@ -245,7 +245,7 @@ public class SSLSocketTemplate extends SSLContextTemplate {
//
// The server side takes care of the issue if the server cannot
// get started in 90 seconds. The client side would just ignore
// the test case if the serer is not ready.
// the test case if the server is not ready.
boolean serverIsReady =
serverCondition.await(90L, TimeUnit.SECONDS);
if (!serverIsReady) {
@ -378,7 +378,7 @@ public class SSLSocketTemplate extends SSLContextTemplate {
* Check various exception conditions.
*/
if ((local != null) && (remote != null)) {
// If both failed, return the curthread's exception.
// If both failed, return the current thread's exception.
local.addSuppressed(remote);
exception = local;
} else if (local != null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025, 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
@ -893,7 +893,7 @@ public class DNSIdentities {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
if (keyCertStr != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025, 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
@ -901,7 +901,7 @@ public class IPAddressIPIdentities {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
if (keyCertStr != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025, 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
@ -900,7 +900,7 @@ public class IPIdentities {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
if (keyCertStr != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025, 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
@ -893,7 +893,7 @@ public class Identities {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
SSLContext ctx = SSLContext.getInstance("TLS");
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
if (keyCertStr != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

View File

@ -0,0 +1,231 @@
/*
* Copyright (c) 2025, 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.
*
* 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.
*/
/*
* @test
* @bug 8350807
* @summary Certificates using MD5 algorithm that are disabled by default are
* incorrectly allowed in TLSv1.3 when re-enabled.
* @modules java.base/sun.security.x509
* java.base/sun.security.util
* @library /javax/net/ssl/templates
* /test/lib
* @run main/othervm MD5NotAllowedInTLS13CertificateSignature
*/
import static jdk.test.lib.Asserts.assertEquals;
import static jdk.test.lib.Asserts.assertTrue;
import static jdk.test.lib.Utils.runAndCheckException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory;
import jdk.test.lib.security.CertificateBuilder;
import jdk.test.lib.security.SecurityUtils;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.KeyIdentifier;
import sun.security.x509.SerialNumber;
import sun.security.x509.X500Name;
public class MD5NotAllowedInTLS13CertificateSignature extends
SSLSocketTemplate {
private final String protocol;
private X509Certificate trustedCert;
private X509Certificate serverCert;
private X509Certificate clientCert;
private KeyPair serverKeys;
private KeyPair clientKeys;
protected MD5NotAllowedInTLS13CertificateSignature(String protocol)
throws Exception {
super();
this.protocol = protocol;
setupCertificates();
}
public static void main(String[] args) throws Exception {
// MD5 is disabled by default in java.security config file,
// re-enable it for our test.
SecurityUtils.removeFromDisabledAlgs(
"jdk.certpath.disabledAlgorithms", List.of("MD5"));
SecurityUtils.removeFromDisabledAlgs(
"jdk.tls.disabledAlgorithms", List.of("MD5withRSA"));
// Should fail on TLSv1.3 and up.
runAndCheckException(
// The conditions to reproduce the bug being fixed only met when
// 'TLS' is specified, i.e. when older versions of protocol are
// supported besides TLSv1.3.
() -> new MD5NotAllowedInTLS13CertificateSignature("TLS").run(),
serverEx -> {
Throwable clientEx = serverEx.getSuppressed()[0];
assertTrue(clientEx instanceof SSLHandshakeException);
assertEquals(clientEx.getMessage(), "(bad_certificate) "
+ "PKIX path validation failed: "
+ "java.security.cert.CertPathValidatorException: "
+ "Algorithm constraints check failed on signature"
+ " algorithm: MD5withRSA");
});
// Should run fine on TLSv1.2.
new MD5NotAllowedInTLS13CertificateSignature("TLSv1.2").run();
}
@Override
public SSLContext createServerSSLContext() throws Exception {
return getSSLContext(
trustedCert, serverCert, serverKeys.getPrivate(), protocol);
}
@Override
public SSLContext createClientSSLContext() throws Exception {
return getSSLContext(
trustedCert, clientCert, clientKeys.getPrivate(), protocol);
}
private static SSLContext getSSLContext(
X509Certificate trustedCertificate, X509Certificate keyCertificate,
PrivateKey privateKey, String protocol)
throws Exception {
// create a key store
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null);
// import the trusted cert
ks.setCertificateEntry("TLS Signer", trustedCertificate);
// generate certificate chain
Certificate[] chain = new Certificate[2];
chain[0] = keyCertificate;
chain[1] = trustedCertificate;
// import the key entry.
final char[] passphrase = "passphrase".toCharArray();
ks.setKeyEntry("Whatever", privateKey, passphrase, chain);
// Using PKIX TrustManager - this is where MD5 signature check is done.
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
tmf.init(ks);
// create SSL context
SSLContext ctx = SSLContext.getInstance(protocol);
// Using "SunX509" which doesn't check peer supported signature
// algorithms, so we check against local supported signature
// algorithms which constitutes the fix being tested.
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return ctx;
}
// Certificate-building helper methods.
// Certificates are signed with signature using MD5WithRSA algorithm.
private void setupCertificates() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair caKeys = kpg.generateKeyPair();
this.serverKeys = kpg.generateKeyPair();
this.clientKeys = kpg.generateKeyPair();
this.trustedCert = createTrustedCert(caKeys);
this.serverCert = customCertificateBuilder(
"O=Some-Org, L=Some-City, ST=Some-State, C=US",
serverKeys.getPublic(), caKeys.getPublic())
.addBasicConstraintsExt(false, false, -1)
.build(trustedCert, caKeys.getPrivate(), "MD5WithRSA");
this.clientCert = customCertificateBuilder(
"CN=localhost, OU=SSL-Client, O=Some-Org, L=Some-City, ST=Some-State, C=US",
clientKeys.getPublic(), caKeys.getPublic())
.addBasicConstraintsExt(false, false, -1)
.build(trustedCert, caKeys.getPrivate(), "MD5WithRSA");
}
private static X509Certificate createTrustedCert(KeyPair caKeys)
throws Exception {
SecureRandom random = new SecureRandom();
KeyIdentifier kid = new KeyIdentifier(caKeys.getPublic());
GeneralNames gns = new GeneralNames();
GeneralName name = new GeneralName(new X500Name(
"O=Some-Org, L=Some-City, ST=Some-State, C=US"));
gns.add(name);
BigInteger serialNumber = BigInteger.valueOf(
random.nextLong(1000000) + 1);
return customCertificateBuilder(
"O=Some-Org, L=Some-City, ST=Some-State, C=US",
caKeys.getPublic(), caKeys.getPublic())
.setSerialNumber(serialNumber)
.addExtension(new AuthorityKeyIdentifierExtension(kid, gns,
new SerialNumber(serialNumber)))
.addBasicConstraintsExt(true, true, -1)
.build(null, caKeys.getPrivate(), "MD5WithRSA");
}
private static CertificateBuilder customCertificateBuilder(
String subjectName, PublicKey publicKey, PublicKey caKey)
throws CertificateException, IOException {
SecureRandom random = new SecureRandom();
CertificateBuilder builder = new CertificateBuilder()
.setSubjectName(subjectName)
.setPublicKey(publicKey)
.setNotAfter(
Date.from(Instant.now().minus(1, ChronoUnit.HOURS)))
.setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS)))
.setSerialNumber(
BigInteger.valueOf(random.nextLong(1000000) + 1))
.addSubjectKeyIdExt(publicKey)
.addAuthorityKeyIdExt(caKey);
builder.addKeyUsageExt(
new boolean[]{true, true, true, true, true, true});
return builder;
}
}