8206925: Support the certificate_authorities extension

Reviewed-by: mullan
This commit is contained in:
Xue-Lei Andrew Fan 2020-05-27 09:46:40 -07:00
parent 6f5e8a2aa3
commit 17a298962c
8 changed files with 1087 additions and 96 deletions

View file

@ -0,0 +1,408 @@
/*
* Copyright (c) 2020, 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.nio.ByteBuffer;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.*;
import javax.net.ssl.SSLProtocolException;
import javax.security.auth.x500.X500Principal;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
import sun.security.ssl.SSLHandshake.HandshakeMessage;
/**
* Pack of the "certificate_authorities" extensions.
*/
final class CertificateAuthoritiesExtension {
static final HandshakeProducer chNetworkProducer =
new CHCertificateAuthoritiesProducer();
static final ExtensionConsumer chOnLoadConsumer =
new CHCertificateAuthoritiesConsumer();
static final HandshakeProducer crNetworkProducer =
new CRCertificateAuthoritiesProducer();
static final ExtensionConsumer crOnLoadConsumer =
new CRCertificateAuthoritiesConsumer();
static final SSLStringizer ssStringizer =
new CertificateAuthoritiesStringizer();
/**
* The "certificate_authorities" extension.
*/
static final class CertificateAuthoritiesSpec implements SSLExtensionSpec {
final List<byte[]> authorities; // certificate authorities
private CertificateAuthoritiesSpec(List<byte[]> authorities) {
this.authorities = authorities;
}
private CertificateAuthoritiesSpec(HandshakeContext hc,
ByteBuffer m) throws IOException {
if (m.remaining() < 3) { // 2: the length of the list
// 1: at least one byte authorities
throw hc.conContext.fatal(Alert.DECODE_ERROR,
new SSLProtocolException(
"Invalid certificate_authorities extension: " +
"insufficient data"));
}
int listLen = Record.getInt16(m);
if (listLen == 0) {
throw hc.conContext.fatal(Alert.DECODE_ERROR,
"Invalid certificate_authorities extension: " +
"no certificate authorities");
}
if (listLen > m.remaining()) {
throw hc.conContext.fatal(Alert.DECODE_ERROR,
"Invalid certificate_authorities extension: " +
"insufficient data");
}
this.authorities = new LinkedList<>();
while (listLen > 0) {
// opaque DistinguishedName<1..2^16-1>;
byte[] encoded = Record.getBytes16(m);
listLen -= (2 + encoded.length);
authorities.add(encoded);
}
}
private static List<byte[]> getEncodedAuthorities(
X509Certificate[] trustedCerts) {
List<byte[]> authorities = new ArrayList<>(trustedCerts.length);
int sizeAccount = 0;
for (X509Certificate cert : trustedCerts) {
X500Principal x500Principal = cert.getSubjectX500Principal();
byte[] encodedPrincipal = x500Principal.getEncoded();
sizeAccount += encodedPrincipal.length;
if (sizeAccount > 0xFFFF) { // the size limit of this extension
// If there too many trusts CAs such that they exceed the
// size limit of the extension, enabling this extension
// does not really make sense as there is no way to
// indicate the peer certificate selection accurately.
// In such cases, the extension is just ignored, rather
// than fatal close, for better compatibility and
// interoperability.
return Collections.emptyList();
}
if (encodedPrincipal.length != 0) {
authorities.add(encodedPrincipal);
}
}
return authorities;
}
X500Principal[] getAuthorities() {
X500Principal[] principals = new X500Principal[authorities.size()];
int i = 0;
for (byte[] encoded : authorities) {
principals[i++] = new X500Principal(encoded);
}
return principals;
}
@Override
public String toString() {
MessageFormat messageFormat = new MessageFormat(
"\"certificate authorities\": '['\n{0}']'", Locale.ENGLISH);
StringBuilder builder = new StringBuilder(512);
for (byte[] encoded : authorities) {
X500Principal principal = new X500Principal(encoded);
builder.append(principal.toString());
builder.append("\n");
}
Object[] messageFields = {
Utilities.indent(builder.toString())
};
return messageFormat.format(messageFields);
}
}
private static final
class CertificateAuthoritiesStringizer implements SSLStringizer {
@Override
public String toString(HandshakeContext hc, ByteBuffer buffer) {
try {
return (new CertificateAuthoritiesSpec(hc, buffer))
.toString();
} catch (IOException ioe) {
// For debug logging only, so please swallow exceptions.
return ioe.getMessage();
}
}
}
/**
* Network data producer of a "certificate_authorities" extension in
* the ClientHello handshake message.
*/
private static final
class CHCertificateAuthoritiesProducer implements HandshakeProducer {
// Prevent instantiation of this class.
private CHCertificateAuthoritiesProducer() {
// blank
}
@Override
public byte[] produce(ConnectionContext context,
HandshakeMessage message) throws IOException {
// The producing happens in client side only.
ClientHandshakeContext chc = (ClientHandshakeContext)context;
// Is it a supported and enabled extension?
if (!chc.sslConfig.isAvailable(
SSLExtension.CH_CERTIFICATE_AUTHORITIES)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore unavailable " +
"certificate_authorities extension");
}
return null; // ignore the extension
}
// Produce the extension.
X509Certificate[] caCerts =
chc.sslContext.getX509TrustManager().getAcceptedIssuers();
if (caCerts.length == 0) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"No available certificate authorities");
}
return null; // ignore the extension
}
List<byte[]> encodedCAs =
CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts);
if (encodedCAs.isEmpty()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"The number of CAs exceeds the maximum size" +
"of the certificate_authorities extension");
}
return null; // ignore the extension
}
CertificateAuthoritiesSpec spec =
new CertificateAuthoritiesSpec(encodedCAs);
int vectorLen = 0;
for (byte[] encoded : spec.authorities) {
vectorLen += encoded.length + 2;
}
byte[] extData = new byte[vectorLen + 2];
ByteBuffer m = ByteBuffer.wrap(extData);
Record.putInt16(m, vectorLen);
for (byte[] encoded : spec.authorities) {
Record.putBytes16(m, encoded);
}
// Update the context.
chc.handshakeExtensions.put(
SSLExtension.CH_CERTIFICATE_AUTHORITIES, spec);
return extData;
}
}
/**
* Network data consumer of a "certificate_authorities" extension in
* the ClientHello handshake message.
*/
private static final
class CHCertificateAuthoritiesConsumer implements ExtensionConsumer {
// Prevent instantiation of this class.
private CHCertificateAuthoritiesConsumer() {
// blank
}
@Override
public void consume(ConnectionContext context,
HandshakeMessage message, ByteBuffer buffer) throws IOException {
// The consuming happens in server side only.
ServerHandshakeContext shc = (ServerHandshakeContext)context;
// Is it a supported and enabled extension?
if (!shc.sslConfig.isAvailable(
SSLExtension.CH_CERTIFICATE_AUTHORITIES)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore unavailable " +
"certificate_authorities extension");
}
return; // ignore the extension
}
// Parse the extension.
CertificateAuthoritiesSpec spec =
new CertificateAuthoritiesSpec(shc, buffer);
// Update the context.
shc.peerSupportedAuthorities = spec.getAuthorities();
shc.handshakeExtensions.put(
SSLExtension.CH_CERTIFICATE_AUTHORITIES, spec);
// No impact on session resumption.
}
}
/**
* Network data producer of a "certificate_authorities" extension in
* the CertificateRequest handshake message.
*/
private static final
class CRCertificateAuthoritiesProducer implements HandshakeProducer {
// Prevent instantiation of this class.
private CRCertificateAuthoritiesProducer() {
// blank
}
@Override
public byte[] produce(ConnectionContext context,
HandshakeMessage message) throws IOException {
// The producing happens in server side only.
ServerHandshakeContext shc = (ServerHandshakeContext)context;
// Is it a supported and enabled extension?
if (!shc.sslConfig.isAvailable(
SSLExtension.CR_CERTIFICATE_AUTHORITIES)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore unavailable " +
"certificate_authorities extension");
}
return null; // ignore the extension
}
// Produce the extension.
X509Certificate[] caCerts =
shc.sslContext.getX509TrustManager().getAcceptedIssuers();
if (caCerts.length == 0) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"No available certificate authorities");
}
return null; // ignore the extension
}
List<byte[]> encodedCAs =
CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts);
if (encodedCAs.isEmpty()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"Too many certificate authorities to use " +
"the certificate_authorities extension");
}
return null; // ignore the extension
}
CertificateAuthoritiesSpec spec =
new CertificateAuthoritiesSpec(encodedCAs);
int vectorLen = 0;
for (byte[] encoded : spec.authorities) {
vectorLen += encoded.length + 2;
}
byte[] extData = new byte[vectorLen + 2];
ByteBuffer m = ByteBuffer.wrap(extData);
Record.putInt16(m, vectorLen);
for (byte[] encoded : spec.authorities) {
Record.putBytes16(m, encoded);
}
// Update the context.
shc.handshakeExtensions.put(
SSLExtension.CR_CERTIFICATE_AUTHORITIES, spec);
return extData;
}
}
/**
* Network data consumer of a "certificate_authorities" extension in
* the CertificateRequest handshake message.
*/
private static final
class CRCertificateAuthoritiesConsumer implements ExtensionConsumer {
// Prevent instantiation of this class.
private CRCertificateAuthoritiesConsumer() {
// blank
}
@Override
public void consume(ConnectionContext context,
HandshakeMessage message, ByteBuffer buffer) throws IOException {
// The consuming happens in client side only.
ClientHandshakeContext chc = (ClientHandshakeContext)context;
// Is it a supported and enabled extension?
if (!chc.sslConfig.isAvailable(
SSLExtension.CR_CERTIFICATE_AUTHORITIES)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore unavailable " +
"certificate_authorities extension");
}
return; // ignore the extension
}
// Parse the extension.
CertificateAuthoritiesSpec spec =
new CertificateAuthoritiesSpec(chc, buffer);
// Update the context.
chc.peerSupportedAuthorities = spec.getAuthorities();
chc.handshakeExtensions.put(
SSLExtension.CR_CERTIFICATE_AUTHORITIES, spec);
// No impact on session resumption.
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, 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
@ -200,14 +200,13 @@ final class CertificateRequest {
}
X500Principal[] getAuthorities() {
List<X500Principal> principals =
new ArrayList<>(authorities.size());
X500Principal[] principals = new X500Principal[authorities.size()];
int i = 0;
for (byte[] encoded : authorities) {
X500Principal principal = new X500Principal(encoded);
principals.add(principal);
principals[i++] = new X500Principal(encoded);
}
return principals.toArray(new X500Principal[0]);
return principals;
}
@Override
@ -504,14 +503,13 @@ final class CertificateRequest {
}
X500Principal[] getAuthorities() {
List<X500Principal> principals =
new ArrayList<>(authorities.size());
X500Principal[] principals = new X500Principal[authorities.size()];
int i = 0;
for (byte[] encoded : authorities) {
X500Principal principal = new X500Principal(encoded);
principals.add(principal);
principals[i++] = new X500Principal(encoded);
}
return principals.toArray(new X500Principal[0]);
return principals;
}
@Override

View file

@ -407,7 +407,27 @@ enum SSLExtension implements SSLStringizer {
null,
PskKeyExchangeModesExtension.chOnTradeAbsence,
PskKeyExchangeModesExtension.pkemStringizer),
CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities"),
CH_CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities",
SSLHandshake.CLIENT_HELLO,
ProtocolVersion.PROTOCOLS_OF_13,
CertificateAuthoritiesExtension.chNetworkProducer,
CertificateAuthoritiesExtension.chOnLoadConsumer,
null,
null,
null,
CertificateAuthoritiesExtension.ssStringizer),
CR_CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities",
SSLHandshake.CERTIFICATE_REQUEST,
ProtocolVersion.PROTOCOLS_OF_13,
CertificateAuthoritiesExtension.crNetworkProducer,
CertificateAuthoritiesExtension.crOnLoadConsumer,
null,
null,
null,
CertificateAuthoritiesExtension.ssStringizer),
OID_FILTERS (0x0030, "oid_filters"),
POST_HANDSHAKE_AUTH (0x0030, "post_handshake_auth"),
@ -725,6 +745,50 @@ enum SSLExtension implements SSLStringizer {
extensions.remove(CH_MAX_FRAGMENT_LENGTH);
}
// To switch on certificate_authorities extension in ClientHello.
//
// Note: Please be careful to enable this extension in ClientHello.
//
// In practice, if the server certificate cannot be validated by
// the underlying programs, the user may manually check the
// certificate in order to access the service. The certificate
// could be accepted manually, and the handshake continues. For
// example, the browsers provide the manual option to accept
// untrusted server certificate. If this extension is enabled in
// the ClientHello handshake message, and the server's certificate
// does not chain back to any of the CAs in the extension, then the
// server will terminate the handshake and close the connection.
// There is no chance for the client to perform the manual check.
// Therefore, enabling this extension in ClientHello may lead to
// unexpected compatibility issues for such cases.
//
// According to TLS 1.3 specification [RFC 8446] the maximum size
// of the certificate_authorities extension is 2^16 bytes. The
// maximum TLS record size is 2^14 bytes. If the handshake
// message is bigger than maximum TLS record size, it should be
// splitted into several records. In fact, some server
// implementations do not allow ClientHello messages bigger than
// the maximum TLS record size and will immediately abort the
// connection with a fatal alert. Therefore, if the client trusts
// too many certificate authorities, there may be unexpected
// interoperability issues.
//
// Furthermore, if the client trusts more CAs such that it exceeds
// the size limit of the extension, enabling this extension in
// client side does not really make sense any longer as there is
// no way to indicate the server certificate selection accurately.
//
// In general, a server does not use multiple certificates issued
// from different CAs. It is not expected to use this extension a
// lot in practice. When there is a need to use this extension
// in ClientHello handshake message, please take care of the
// potential compatibility and interoperability issues above.
enableExtension = Utilities.getBooleanProperty(
"jdk.tls.client.enableCAExtension", false);
if (!enableExtension) {
extensions.remove(CH_CERTIFICATE_AUTHORITIES);
}
defaults = Collections.unmodifiableCollection(extensions);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2020, 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
@ -230,12 +230,14 @@ enum X509Authentication implements SSLAuthentication {
if (chc.conContext.transport instanceof SSLSocketImpl) {
clientAlias = km.chooseClientAlias(
new String[] { keyType },
chc.peerSupportedAuthorities,
chc.peerSupportedAuthorities == null ? null :
chc.peerSupportedAuthorities.clone(),
(SSLSocket)chc.conContext.transport);
} else if (chc.conContext.transport instanceof SSLEngineImpl) {
clientAlias = km.chooseEngineClientAlias(
new String[] { keyType },
chc.peerSupportedAuthorities,
chc.peerSupportedAuthorities == null ? null :
chc.peerSupportedAuthorities.clone(),
(SSLEngine)chc.conContext.transport);
}
@ -284,10 +286,14 @@ enum X509Authentication implements SSLAuthentication {
String serverAlias = null;
if (shc.conContext.transport instanceof SSLSocketImpl) {
serverAlias = km.chooseServerAlias(keyType,
null, (SSLSocket)shc.conContext.transport);
shc.peerSupportedAuthorities == null ? null :
shc.peerSupportedAuthorities.clone(),
(SSLSocket)shc.conContext.transport);
} else if (shc.conContext.transport instanceof SSLEngineImpl) {
serverAlias = km.chooseEngineServerAlias(keyType,
null, (SSLEngine)shc.conContext.transport);
shc.peerSupportedAuthorities == null ? null :
shc.peerSupportedAuthorities.clone(),
(SSLEngine)shc.conContext.transport);
}
if (serverAlias == null) {