changeset 14455:821ac4be0e8f

8206925: Support the certificate_authorities extension Reviewed-by: mullan
author xuelei
date Wed, 27 May 2020 09:46:40 -0700
parents ba0e36b4275f
children 6a07e2cb5bdb
files src/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java src/share/classes/sun/security/ssl/CertificateRequest.java src/share/classes/sun/security/ssl/SSLExtension.java src/share/classes/sun/security/ssl/X509Authentication.java test/javax/net/ssl/templates/SSLContextTemplate.java test/sun/security/ssl/X509KeyManager/CertificateAuthorities.java test/sun/security/ssl/X509TrustManagerImpl/CacertsLimit.java test/sun/security/ssl/X509TrustManagerImpl/TooManyCAs.java
diffstat 8 files changed, 1083 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java	Wed May 27 09:46:40 2020 -0700
@@ -0,0 +1,406 @@
+/*
+ * 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(ByteBuffer m) throws IOException  {
+            if (m.remaining() < 3) {        // 2: the length of the list
+                                            // 1: at least one byte authorities
+                throw new SSLProtocolException(
+                    "Invalid certificate_authorities extension: " +
+                    "insufficient data");
+            }
+
+            int listLen = Record.getInt16(m);
+            if (listLen == 0) {
+                throw new SSLProtocolException(
+                    "Invalid certificate_authorities extension: " +
+                    "no certificate authorities");
+            }
+
+            if (listLen > m.remaining()) {
+                throw new SSLProtocolException(
+                    "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(ByteBuffer buffer) {
+            try {
+                return (new CertificateAuthoritiesSpec(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(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(buffer);
+
+            // Update the context.
+            chc.peerSupportedAuthorities = spec.getAuthorities();
+            chc.handshakeExtensions.put(
+                    SSLExtension.CR_CERTIFICATE_AUTHORITIES, spec);
+
+            // No impact on session resumption.
+        }
+    }
+}
--- a/src/share/classes/sun/security/ssl/CertificateRequest.java	Wed May 19 16:12:33 2021 +0100
+++ b/src/share/classes/sun/security/ssl/CertificateRequest.java	Wed May 27 09:46:40 2020 -0700
@@ -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 @@
         }
 
         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 @@
         }
 
         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
--- a/src/share/classes/sun/security/ssl/SSLExtension.java	Wed May 19 16:12:33 2021 +0100
+++ b/src/share/classes/sun/security/ssl/SSLExtension.java	Wed May 27 09:46:40 2020 -0700
@@ -387,7 +387,27 @@
                                 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"),
 
@@ -683,6 +703,50 @@
                 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);
         }
     }
--- a/src/share/classes/sun/security/ssl/X509Authentication.java	Wed May 19 16:12:33 2021 +0100
+++ b/src/share/classes/sun/security/ssl/X509Authentication.java	Wed May 27 09:46:40 2020 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 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
@@ -199,12 +199,14 @@
             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);
             }
 
@@ -253,10 +255,14 @@
             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) {
--- a/test/javax/net/ssl/templates/SSLContextTemplate.java	Wed May 19 16:12:33 2021 +0100
+++ b/test/javax/net/ssl/templates/SSLContextTemplate.java	Wed May 27 09:46:40 2020 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 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
@@ -34,9 +34,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Base64;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.*;
 
 /**
  * SSLContext template to speed up JSSE tests.
@@ -46,10 +44,9 @@
      * Create an instance of SSLContext for client use.
      */
     default SSLContext createClientSSLContext() throws Exception {
-        return createSSLContext(trustedCertStrs,
-                endEntityCertStrs, endEntityPrivateKeys,
-                endEntityPrivateKeyAlgs,
-                endEntityPrivateKeyNames,
+        return createSSLContext(
+                createClientKeyManager(),
+                createClientTrustManager(),
                 getClientContextParameters());
     }
 
@@ -57,14 +54,54 @@
      * Create an instance of SSLContext for server use.
      */
     default SSLContext createServerSSLContext() throws Exception {
-        return createSSLContext(trustedCertStrs,
-                endEntityCertStrs, endEntityPrivateKeys,
+        return createSSLContext(
+                createServerKeyManager(),
+                createServerTrustManager(),
+                getServerContextParameters());
+    }
+
+    /*
+     * Create an instance of KeyManager for client use.
+     */
+    default KeyManager createClientKeyManager() throws Exception {
+        return createKeyManager(
+                endEntityCertStrs,
+                endEntityPrivateKeys,
                 endEntityPrivateKeyAlgs,
                 endEntityPrivateKeyNames,
                 getServerContextParameters());
     }
 
     /*
+     * Create an instance of TrustManager for client use.
+     */
+    default TrustManager createClientTrustManager() throws Exception {
+        return createTrustManager(
+                trustedCertStrs,
+                getServerContextParameters());
+    }
+    /*
+     * Create an instance of KeyManager for server use.
+     */
+    default KeyManager createServerKeyManager() throws Exception {
+        return createKeyManager(
+                endEntityCertStrs,
+                endEntityPrivateKeys,
+                endEntityPrivateKeyAlgs,
+                endEntityPrivateKeyNames,
+                getServerContextParameters());
+    }
+
+    /*
+     * Create an instance of TrustManager for server use.
+     */
+    default TrustManager createServerTrustManager() throws Exception {
+        return createTrustManager(
+                trustedCertStrs,
+                getServerContextParameters());
+    }
+
+    /*
      * The parameters used to configure SSLContext.
      */
     static final class ContextParameters {
@@ -421,80 +458,107 @@
      * Create an instance of SSLContext with the specified trust/key materials.
      */
     default SSLContext createSSLContext(
-            String[] trustedMaterials,
+            KeyManager keyManager,
+            TrustManager trustManager,
+            ContextParameters params) throws Exception {
+
+        SSLContext context = SSLContext.getInstance(params.contextProtocol);
+        context.init(
+                new KeyManager[] {
+                        keyManager
+                    },
+                new TrustManager[] {
+                        trustManager
+                    },
+                null);
+
+        return  context;
+    }
+
+    /*
+     * Create an instance of KeyManager with the specified key materials.
+     */
+    default KeyManager createKeyManager(
             String[] keyMaterialCerts,
             String[] keyMaterialKeys,
             String[] keyMaterialKeyAlgs,
             String[] keyMaterialKeyNames,
             ContextParameters params) throws Exception {
 
-        KeyStore ts = null;     // trust store
-        KeyStore ks = null;     // key store
-        char passphrase[] = "passphrase".toCharArray();
+        char[] passphrase = "passphrase".toCharArray();
 
         // Generate certificate from cert string.
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
 
-        // Import the trused certs.
-        ByteArrayInputStream is;
-        if (trustedMaterials != null && trustedMaterials.length != 0) {
-            ts = KeyStore.getInstance("JKS");
-            ts.load(null, null);
-
-            Certificate[] trustedCert =
-                    new Certificate[trustedMaterials.length];
-            for (int i = 0; i < trustedMaterials.length; i++) {
-                String trustedCertStr = trustedMaterials[i];
-
-                is = new ByteArrayInputStream(trustedCertStr.getBytes());
-                try {
-                    trustedCert[i] = cf.generateCertificate(is);
-                } finally {
-                    is.close();
-                }
-
-                ts.setCertificateEntry("trusted-cert-" + i, trustedCert[i]);
-            }
-        }
-
         // Import the key materials.
         //
-        // Note that certification pathes bigger than one are not supported yet.
-        boolean hasKeyMaterials =
-            (keyMaterialCerts != null) && (keyMaterialCerts.length != 0) &&
-            (keyMaterialKeys != null) && (keyMaterialKeys.length != 0) &&
-            (keyMaterialKeyAlgs != null) && (keyMaterialKeyAlgs.length != 0) &&
-            (keyMaterialCerts.length == keyMaterialKeys.length) &&
-            (keyMaterialCerts.length == keyMaterialKeyAlgs.length);
-        if (hasKeyMaterials) {
-            ks = KeyStore.getInstance("JKS");
-            ks.load(null, null);
+        // Note that certification paths bigger than one are not supported yet.
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(null, null);
+        ByteArrayInputStream is;
+        for (int i = 0; i < keyMaterialCerts.length; i++) {
+            String keyCertStr = keyMaterialCerts[i];
 
-            for (int i = 0; i < keyMaterialCerts.length; i++) {
-                String keyCertStr = keyMaterialCerts[i];
-
-                // generate the private key.
-                PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(
+            // generate the private key.
+            PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(
                     Base64.getMimeDecoder().decode(keyMaterialKeys[i]));
-                KeyFactory kf =
+            KeyFactory kf =
                     KeyFactory.getInstance(keyMaterialKeyAlgs[i]);
-                PrivateKey priKey = kf.generatePrivate(priKeySpec);
+            PrivateKey priKey = kf.generatePrivate(priKeySpec);
+
+            // generate certificate chain
+            is = new ByteArrayInputStream(keyCertStr.getBytes());
+            Certificate keyCert = null;
+            try {
+                keyCert = cf.generateCertificate(is);
+            } finally {
+                is.close();
+            }
+
+            Certificate[] chain = new Certificate[] { keyCert };
+
+            // import the key entry.
+            ks.setKeyEntry("cert-" + keyMaterialKeyNames[i],
+                    priKey, passphrase, chain);
+        }
+
+        KeyManagerFactory kmf =
+                KeyManagerFactory.getInstance(params.kmAlgorithm);
+        kmf.init(ks, passphrase);
+
+        KeyManager[] km = kmf.getKeyManagers();
+
+        return km[0];
+    }
 
-                // generate certificate chain
-                is = new ByteArrayInputStream(keyCertStr.getBytes());
-                Certificate keyCert = null;
-                try {
-                    keyCert = cf.generateCertificate(is);
-                } finally {
-                    is.close();
-                }
+    /*
+     * Create an instance of TrustManager with the specified trust materials.
+     */
+    default TrustManager createTrustManager(
+            String[] trustedMaterials,
+            ContextParameters params) throws Exception {
+
+        // Generate certificate from cert string.
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+        // Import the trusted certs.
+        KeyStore ts = KeyStore.getInstance("PKCS12");
+        ts.load(null, null);
 
-                Certificate[] chain = new Certificate[] { keyCert };
+        Certificate[] trustedCert =
+                new Certificate[trustedMaterials.length];
+        ByteArrayInputStream is;
+        for (int i = 0; i < trustedMaterials.length; i++) {
+            String trustedCertStr = trustedMaterials[i];
 
-                // import the key entry.
-                ks.setKeyEntry("cert-" + keyMaterialKeyNames[i],
-                        priKey, passphrase, chain);
+            is = new ByteArrayInputStream(trustedCertStr.getBytes());
+            try {
+                trustedCert[i] = cf.generateCertificate(is);
+            } finally {
+                is.close();
             }
+
+            ts.setCertificateEntry("trusted-cert-" + i, trustedCert[i]);
         }
 
         // Create an SSLContext object.
@@ -502,17 +566,7 @@
                 TrustManagerFactory.getInstance(params.tmAlgorithm);
         tmf.init(ts);
 
-        SSLContext context = SSLContext.getInstance(params.contextProtocol);
-        if (hasKeyMaterials && ks != null) {
-            KeyManagerFactory kmf =
-                    KeyManagerFactory.getInstance(params.kmAlgorithm);
-            kmf.init(ks, passphrase);
-
-            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
-        } else {
-            context.init(null, tmf.getTrustManagers(), null);
-        }
-
-        return context;
+        TrustManager[] tms = tmf.getTrustManagers();
+        return tms[0];
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/ssl/X509KeyManager/CertificateAuthorities.java	Wed May 27 09:46:40 2020 -0700
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+//
+// SunJSSE does not support dynamic system properties, no way to re-use
+// system properties in samevm/agentvm mode.
+//
+
+/*
+ * @test
+ * @bug 8206925
+ * @summary Support the "certificate_authorities" extension
+ * @library /javax/net/ssl/templates
+ * @run main/othervm CertificateAuthorities
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=false
+ *     CertificateAuthorities
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=true
+ *     CertificateAuthorities
+ *
+ * @run main/othervm CertificateAuthorities NEED_CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=false
+ *     CertificateAuthorities NEED_CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=true
+ *     CertificateAuthorities NEED_CLIENT_AUTH
+ *
+ * @run main/othervm CertificateAuthorities WANT_CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=false
+ *     CertificateAuthorities WANT_CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.enableCAExtension=true
+ *     CertificateAuthorities WANT_CLIENT_AUTH
+ */
+
+import javax.net.ssl.SSLServerSocket;
+
+public final class CertificateAuthorities extends SSLSocketTemplate {
+    final ClientAuthMode clientAuthMode;
+
+    /*
+     * Run the test case.
+     */
+    public static void main(String[] args) throws Exception {
+        CertificateAuthorities testCase;
+        if (args.length != 0) {
+            testCase = new CertificateAuthorities(
+                    ClientAuthMode.valueOf(args[0]));
+        } else {
+            testCase = new CertificateAuthorities(
+                    ClientAuthMode.NO_CLIENT_AUTH);
+        }
+
+        testCase.run();
+    }
+
+    CertificateAuthorities(ClientAuthMode mode) {
+        this.clientAuthMode = mode;
+    }
+
+    @Override
+    protected void configureServerSocket(SSLServerSocket socket) {
+        if (clientAuthMode == ClientAuthMode.NEED_CLIENT_AUTH) {
+            socket.setNeedClientAuth(true);
+        } else if (clientAuthMode == ClientAuthMode.WANT_CLIENT_AUTH) {
+            socket.setWantClientAuth(true);
+        }
+    }
+
+    private static enum ClientAuthMode {
+        NEED_CLIENT_AUTH,
+        WANT_CLIENT_AUTH,
+        NO_CLIENT_AUTH
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/ssl/X509TrustManagerImpl/CacertsLimit.java	Wed May 27 09:46:40 2020 -0700
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ *
+ * 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 8206925
+ * @library /javax/net/ssl/templates
+ * @summary Support the certificate_authorities extension
+ */
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+public class CacertsLimit {
+    public static void main(String[] args) throws Exception {
+        for (String algorithm : new String[] {"SunX509", "PKIX"}) {
+            CacertsLimit.ensureLimit(algorithm);
+        }
+    }
+
+    private static void ensureLimit(String algorithm) throws Exception {
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+        tmf.init((KeyStore)null);
+        TrustManager[] tms = tmf.getTrustManagers();
+
+        if (tms == null || tms.length == 0) {
+            throw new Exception("No default key store used for trust manager");
+        }
+
+        if (!(tms[0] instanceof X509TrustManager)) {
+            throw new Exception(
+                "The trust manger is not an instance of X509TrustManager");
+        }
+
+        checkLimit(((X509TrustManager)tms[0]).getAcceptedIssuers());
+    }
+
+    private static void checkLimit(
+            X509Certificate[] trustedCerts) throws Exception {
+        int sizeAccount = 0;
+        for (X509Certificate cert : trustedCerts) {
+            X500Principal x500Principal = cert.getSubjectX500Principal();
+            byte[] encodedPrincipal = x500Principal.getEncoded();
+            sizeAccount += encodedPrincipal.length;
+            if (sizeAccount > 0xFFFF) {
+                throw new Exception(
+                        "There are too many trusted CAs in cacerts. The " +
+                        "certificate_authorities extension cannot be used " +
+                        "for TLS connections.  Please rethink about the size" +
+                        "of the cacerts, or have a release note for the " +
+                        "impacted behaviors");
+            } else if (sizeAccount > 0x4000) {
+                throw new Exception(
+                        "There are too many trusted CAs in cacerts. The " +
+                        "certificate_authorities extension cannot be " +
+                        "packaged in one TLS record, which would result in " +
+                        "interoperability issues.  Please rethink about the " +
+                        "size of the cacerts, or have a release note for " +
+                        "the impacted behaviors");
+            }
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/ssl/X509TrustManagerImpl/TooManyCAs.java	Wed May 27 09:46:40 2020 -0700
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ *
+ * 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 8206925
+ * @library /javax/net/ssl/templates
+ * @summary Support the certificate_authorities extension
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 TooManyCAs
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.client.enableCAExtension=true TooManyCAs
+ */
+import javax.net.ssl.*;
+import javax.security.auth.x500.X500Principal;
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * Check if the connection can be established if the client or server trusts
+ * more CAs such that it exceeds the size limit of the certificate_authorities
+ * extension (2^16).
+ */
+public class TooManyCAs implements SSLContextTemplate {
+
+    private static final String[][][] protocols = {
+            {{"TLSv1.3"}, {"TLSv1.3"}},
+            {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.3"}},
+            {{"TLSv1.3"}, {"TLSv1.3", "TLSv1.2"}},
+    };
+
+    private final String[] clientProtocols;
+    private final String[] serverProtocols;
+    private final boolean needClientAuth;
+
+    TooManyCAs(int index, boolean needClientAuth) {
+        this.clientProtocols = protocols[index][0];
+        this.serverProtocols = protocols[index][1];
+        this.needClientAuth = needClientAuth;
+    }
+
+    // Servers are configured before clients, increment test case after.
+    void configureClientSocket(SSLSocket clientSocket) {
+        System.err.print("Setting client protocol(s): ");
+        Arrays.stream(clientProtocols).forEachOrdered(System.err::print);
+        System.err.println();
+
+        clientSocket.setEnabledProtocols(clientProtocols);
+    }
+
+    void configureServerSocket(SSLServerSocket serverSocket) {
+        System.err.print("Setting server protocol(s): ");
+        Arrays.stream(serverProtocols).forEachOrdered(System.err::print);
+        System.err.println();
+
+        serverSocket.setEnabledProtocols(serverProtocols);
+        if (needClientAuth) {
+            serverSocket.setNeedClientAuth(true);
+        }
+    }
+
+    @Override
+    public TrustManager createClientTrustManager() throws Exception {
+        TrustManager trustManager =
+                SSLContextTemplate.super.createClientTrustManager();
+        return new BogusX509TrustManager(
+                (X509TrustManager)trustManager);
+    }
+
+    @Override
+    public TrustManager createServerTrustManager() throws Exception {
+        TrustManager trustManager =
+                SSLContextTemplate.super.createServerTrustManager();
+        return new BogusX509TrustManager(
+                (X509TrustManager)trustManager);
+    }
+
+    /*
+     * Run the test case.
+     */
+    public static void main(String[] args) throws Exception {
+        for (int i = 0; i < protocols.length; i++) {
+            (new TooManyCAs(i, false)).run();
+            (new TooManyCAs(i, true)).run();
+        }
+    }
+
+    private void run() throws Exception {
+        SSLServerSocket listenSocket = null;
+        SSLSocket serverSocket = null;
+        ClientSocket clientSocket = null;
+        try {
+            SSLServerSocketFactory serversocketfactory =
+                    createServerSSLContext().getServerSocketFactory();
+            listenSocket =
+                    (SSLServerSocket)serversocketfactory.createServerSocket(0);
+            listenSocket.setNeedClientAuth(false);
+            listenSocket.setEnableSessionCreation(true);
+            listenSocket.setUseClientMode(false);
+            configureServerSocket(listenSocket);
+
+            System.err.println("Starting client");
+            clientSocket = new ClientSocket(listenSocket.getLocalPort());
+            clientSocket.start();
+
+            System.err.println("Accepting client requests");
+            serverSocket = (SSLSocket)listenSocket.accept();
+
+            if (!clientSocket.isDone) {
+                System.err.println("Waiting 3 seconds for client ");
+                Thread.sleep(3000);
+            }
+
+            System.err.println("Sending data to client ...");
+            String serverData = "Hi, I am server";
+            BufferedWriter os = new BufferedWriter(
+                    new OutputStreamWriter(serverSocket.getOutputStream()));
+            os.write(serverData, 0, serverData.length());
+            os.newLine();
+            os.flush();
+        } finally {
+            if (listenSocket != null) {
+                listenSocket.close();
+            }
+
+            if (serverSocket != null) {
+                serverSocket.close();
+            }
+        }
+
+        if (clientSocket != null && clientSocket.clientException != null) {
+            throw clientSocket.clientException;
+        }
+    }
+
+    private class ClientSocket extends Thread{
+        boolean isDone = false;
+        int serverPort = 0;
+        Exception clientException;
+
+        public ClientSocket(int serverPort) {
+            this.serverPort = serverPort;
+        }
+
+        @Override
+        public void run() {
+            SSLSocket clientSocket = null;
+            String clientData = "Hi, I am client";
+            try {
+                System.err.println(
+                        "Connecting to server at port " + serverPort);
+                SSLSocketFactory sslSocketFactory =
+                        createClientSSLContext().getSocketFactory();
+                clientSocket = (SSLSocket)sslSocketFactory.createSocket(
+                        InetAddress.getLocalHost(), serverPort);
+                configureClientSocket(clientSocket);
+
+                System.err.println("Sending data to server ...");
+
+                BufferedWriter os = new BufferedWriter(
+                        new OutputStreamWriter(clientSocket.getOutputStream()));
+                os.write(clientData, 0, clientData.length());
+                os.newLine();
+                os.flush();
+
+                System.err.println("Reading data from server");
+                BufferedReader is = new BufferedReader(
+                        new InputStreamReader(clientSocket.getInputStream()));
+                String data = is.readLine();
+                System.err.println("Received Data from server: " + data);
+            } catch (Exception e) {
+                clientException = e;
+                System.err.println("unexpected client exception: " + e);
+            } finally {
+                if (clientSocket != null) {
+                    try {
+                        clientSocket.close();
+                        System.err.println("client socket closed");
+                    } catch (IOException ioe) {
+                        clientException = ioe;
+                    }
+                }
+
+                isDone = true;
+            }
+        }
+    }
+
+    // Construct a bogus trust manager which has more CAs such that exceed
+    // the size limit of the certificate_authorities extension (2^16).
+    private static final class BogusX509TrustManager
+            extends X509ExtendedTrustManager implements X509TrustManager {
+        private final X509ExtendedTrustManager tm;
+
+        private BogusX509TrustManager(X509TrustManager trustManager) {
+            this.tm = (X509ExtendedTrustManager)trustManager;
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain,
+               String authType, Socket socket) throws CertificateException {
+            tm.checkClientTrusted(chain, authType, socket);
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain,
+               String authType, Socket socket) throws CertificateException {
+            tm.checkServerTrusted(chain, authType, socket);
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain,
+            String authType, SSLEngine sslEngine) throws CertificateException {
+
+            tm.checkClientTrusted(chain, authType, sslEngine);
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain,
+            String authType, SSLEngine sslEngine) throws CertificateException {
+
+            tm.checkServerTrusted(chain, authType, sslEngine);
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain,
+               String authType) throws CertificateException {
+            tm.checkServerTrusted(chain, authType);
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain,
+               String authType) throws CertificateException {
+            tm.checkServerTrusted(chain, authType);
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            X509Certificate[] trustedCerts = tm.getAcceptedIssuers();
+            int sizeAccount = 0;
+            for (X509Certificate cert: trustedCerts) {
+                X500Principal x500Principal = cert.getSubjectX500Principal();
+                byte[] encodedPrincipal = x500Principal.getEncoded();
+                sizeAccount += encodedPrincipal.length;
+            }
+
+            // 0xFFFF: the size limit of the certificate_authorities extension
+            int duplicated = (0xFFFF + sizeAccount) / sizeAccount;
+            X509Certificate[] returnedCAs =
+                    new X509Certificate[trustedCerts.length * duplicated];
+            for (int i = 0; i < duplicated; i++) {
+                System.arraycopy(trustedCerts, 0,
+                    returnedCAs,
+                    i * trustedCerts.length + 0, trustedCerts.length);
+            }
+
+            return returnedCAs;
+        }
+    }
+}