Back to index...
/*
 * 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.
        }
    }
}
Back to index...