/*
 * Copyright (c) 1999, 2018, 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.
 */

/*
 * Copyright 2019 Azul Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package org.openjsse.sun.security.ssl;

import java.io.*;
import java.net.Socket;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import org.openjsse.javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSession;
import org.openjsse.javax.net.ssl.SSLSocket;
import javax.net.ssl.ExtendedSSLSession;
import sun.security.action.GetPropertyAction;
import sun.security.provider.certpath.AlgorithmChecker;
import org.openjsse.sun.security.validator.Validator;

/**
 * Implementation of an SSLContext.
 *
 * Implementation note: Instances of this class and the child classes are
 * immutable, except that the context initialization (SSLContext.init()) may
 * reset the key, trust managers and source of secure random.
 */

public abstract class SSLContextImpl extends SSLContextSpi {

    private final EphemeralKeyManager ephemeralKeyManager;
    private final SSLSessionContextImpl clientCache;
    private final SSLSessionContextImpl serverCache;

    private boolean isInitialized;

    private X509ExtendedKeyManager keyManager;
    private X509TrustManager trustManager;
    private SecureRandom secureRandom;

    // DTLS cookie exchange manager
    private volatile HelloCookieManager.Builder helloCookieManagerBuilder;

    private final boolean clientEnableStapling = Utilities.getBooleanProperty(
            "jdk.tls.client.enableStatusRequestExtension", true);
    private final boolean serverEnableStapling = Utilities.getBooleanProperty(
            "jdk.tls.server.enableStatusRequestExtension", false);
    private static final Collection<CipherSuite> clientCustomizedCipherSuites =
            getCustomizedCipherSuites("jdk.tls.client.cipherSuites");
    private static final Collection<CipherSuite> serverCustomizedCipherSuites =
            getCustomizedCipherSuites("jdk.tls.server.cipherSuites");

    private volatile StatusResponseManager statusResponseManager;

    SSLContextImpl() {
        ephemeralKeyManager = new EphemeralKeyManager();
        clientCache = new SSLSessionContextImpl();
        serverCache = new SSLSessionContextImpl();
    }

    @Override
    protected void engineInit(KeyManager[] km, TrustManager[] tm,
                                SecureRandom sr) throws KeyManagementException {
        isInitialized = false;
        keyManager = chooseKeyManager(km);

        if (tm == null) {
            try {
                TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                        TrustManagerFactory.getDefaultAlgorithm());
                tmf.init((KeyStore)null);
                tm = tmf.getTrustManagers();
            } catch (Exception e) {
                // eat
            }
        }
        trustManager = chooseTrustManager(tm);

        if (sr == null) {
            secureRandom = JsseJce.getSecureRandom();
        } else {
            if (OpenJSSE.isFIPS() &&
                        (sr.getProvider() != OpenJSSE.cryptoProvider)) {
                throw new KeyManagementException
                    ("FIPS mode: SecureRandom must be from provider "
                    + OpenJSSE.cryptoProvider.getName());
            }
            secureRandom = sr;
        }

        /*
         * The initial delay of seeding the random number generator
         * could be long enough to cause the initial handshake on our
         * first connection to timeout and fail. Make sure it is
         * primed and ready by getting some initial output from it.
         */
        if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
            SSLLogger.finest("trigger seeding of SecureRandom");
        }
        secureRandom.nextInt();
        if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
            SSLLogger.finest("done seeding of SecureRandom");
        }

        isInitialized = true;
    }

    private X509TrustManager chooseTrustManager(TrustManager[] tm)
            throws KeyManagementException {
        // We only use the first instance of X509TrustManager passed to us.
        for (int i = 0; tm != null && i < tm.length; i++) {
            if (tm[i] instanceof X509TrustManager) {
                if (OpenJSSE.isFIPS() &&
                        !(tm[i] instanceof X509TrustManagerImpl)) {
                    throw new KeyManagementException
                        ("FIPS mode: only OpenJSSE TrustManagers may be used");
                }

                if (tm[i] instanceof X509ExtendedTrustManager) {
                    return (X509TrustManager)tm[i];
                } else {
                    return new AbstractTrustManagerWrapper(
                                        (X509TrustManager)tm[i]);
                }
            }
        }

        // nothing found, return a dummy X509TrustManager.
        return DummyX509TrustManager.INSTANCE;
    }

    private X509ExtendedKeyManager chooseKeyManager(KeyManager[] kms)
            throws KeyManagementException {
        for (int i = 0; kms != null && i < kms.length; i++) {
            KeyManager km = kms[i];
            if (!(km instanceof X509KeyManager)) {
                continue;
            }
            if (OpenJSSE.isFIPS()) {
                // In FIPS mode, require that one of OpenJSSE's own keymanagers
                // is used. Otherwise, we cannot be sure that only keys from
                // the FIPS token are used.
                if ((km instanceof X509KeyManagerImpl)
                            || (km instanceof SunX509KeyManagerImpl)) {
                    return (X509ExtendedKeyManager)km;
                } else {
                    // throw exception, we don't want to silently use the
                    // dummy keymanager without telling the user.
                    throw new KeyManagementException
                        ("FIPS mode: only OpenJSSE KeyManagers may be used");
                }
            }
            if (km instanceof X509ExtendedKeyManager) {
                return (X509ExtendedKeyManager)km;
            }

            if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
                SSLLogger.warning(
                    "X509KeyManager passed to SSLContext.init():  need an " +
                    "X509ExtendedKeyManager for SSLEngine use");
            }
            return new AbstractKeyManagerWrapper((X509KeyManager)km);
        }

        // nothing found, return a dummy X509ExtendedKeyManager
        return DummyX509KeyManager.INSTANCE;
    }

    abstract SSLEngine createSSLEngineImpl();
    abstract SSLEngine createSSLEngineImpl(String host, int port);

    @Override
    protected SSLEngine engineCreateSSLEngine() {
        if (!isInitialized) {
            throw new IllegalStateException("SSLContext is not initialized");
        }
        return createSSLEngineImpl();
    }

    @Override
    protected SSLEngine engineCreateSSLEngine(String host, int port) {
        if (!isInitialized) {
            throw new IllegalStateException("SSLContext is not initialized");
        }
        return createSSLEngineImpl(host, port);
    }

    @Override
    protected SSLSocketFactory engineGetSocketFactory() {
        if (!isInitialized) {
            throw new IllegalStateException("SSLContext is not initialized");
        }
       return new SSLSocketFactoryImpl(this);
    }

    @Override
    protected SSLServerSocketFactory engineGetServerSocketFactory() {
        if (!isInitialized) {
            throw new IllegalStateException("SSLContext is not initialized");
        }
        return new SSLServerSocketFactoryImpl(this);
    }

    @Override
    protected SSLSessionContext engineGetClientSessionContext() {
        return clientCache;
    }

    @Override
    protected SSLSessionContext engineGetServerSessionContext() {
        return serverCache;
    }

    SecureRandom getSecureRandom() {
        return secureRandom;
    }

    X509ExtendedKeyManager getX509KeyManager() {
        return keyManager;
    }

    X509TrustManager getX509TrustManager() {
        return trustManager;
    }

    EphemeralKeyManager getEphemeralKeyManager() {
        return ephemeralKeyManager;
    }

    // Used for DTLS in server mode only.
    HelloCookieManager getHelloCookieManager(ProtocolVersion protocolVersion) {
        if (helloCookieManagerBuilder == null) {
            synchronized (this) {
                if (helloCookieManagerBuilder == null) {
                    helloCookieManagerBuilder =
                            new HelloCookieManager.Builder(secureRandom);
                }
            }
        }

        return helloCookieManagerBuilder.valueOf(protocolVersion);
    }

    StatusResponseManager getStatusResponseManager() {
        if (serverEnableStapling && statusResponseManager == null) {
            synchronized (this) {
                if (statusResponseManager == null) {
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
                        SSLLogger.finest(
                                "Initializing StatusResponseManager");
                    }
                    statusResponseManager = new StatusResponseManager();
                }
            }
        }

        return statusResponseManager;
    }

    // Get supported protocols.
    abstract List<ProtocolVersion> getSupportedProtocolVersions();

    // Get default protocols for server mode.
    abstract List<ProtocolVersion> getServerDefaultProtocolVersions();

    // Get default protocols for client mode.
    abstract List<ProtocolVersion> getClientDefaultProtocolVersions();

    // Get supported CipherSuite list.
    abstract List<CipherSuite> getSupportedCipherSuites();

    // Get default CipherSuite list for server mode.
    abstract List<CipherSuite> getServerDefaultCipherSuites();

    // Get default CipherSuite list for client mode.
    abstract List<CipherSuite> getClientDefaultCipherSuites();

    // Is the context for DTLS protocols?
    abstract boolean isDTLS();

    // Get default protocols.
    List<ProtocolVersion> getDefaultProtocolVersions(boolean roleIsServer) {
        return roleIsServer ? getServerDefaultProtocolVersions()
                            : getClientDefaultProtocolVersions();
    }

    // Get default CipherSuite list.
    List<CipherSuite> getDefaultCipherSuites(boolean roleIsServer) {
        return roleIsServer ? getServerDefaultCipherSuites()
                            : getClientDefaultCipherSuites();
    }

    /**
     * Return whether a protocol list is the original default enabled
     * protocols.  See: SSLSocket/SSLEngine.setEnabledProtocols()
     */
    boolean isDefaultProtocolVesions(List<ProtocolVersion> protocols) {
        return (protocols == getServerDefaultProtocolVersions()) ||
               (protocols == getClientDefaultProtocolVersions());
    }

    /**
     * Return whether a protocol list is the original default enabled
     * protocols.  See: SSLSocket/SSLEngine.setEnabledProtocols()
     */
    boolean isDefaultCipherSuiteList(List<CipherSuite> cipherSuites) {
        return (cipherSuites == getServerDefaultCipherSuites()) ||
               (cipherSuites == getClientDefaultCipherSuites());
    }

    /**
     * Return whether client or server side stapling has been enabled
     * for this SSLContextImpl
     * @param isClient true if the caller is operating in a client side role,
     * false if acting as a server.
     * @return true if stapling has been enabled for the specified role, false
     * otherwise.
     */
    boolean isStaplingEnabled(boolean isClient) {
        return isClient ? clientEnableStapling : serverEnableStapling;
    }

    /*
     * Return the list of all available CipherSuites that are supported
     * using currently installed providers.
     */
    private static List<CipherSuite> getApplicableSupportedCipherSuites(
            List<ProtocolVersion> protocols) {

        return getApplicableCipherSuites(
                CipherSuite.allowedCipherSuites(), protocols);
    }

    /*
     * Return the list of all available CipherSuites that are default enabled
     * in client or server side.
     */
    private static List<CipherSuite> getApplicableEnabledCipherSuites(
            List<ProtocolVersion> protocols, boolean isClient) {

        if (isClient) {
            if (!clientCustomizedCipherSuites.isEmpty()) {
                return getApplicableCipherSuites(
                        clientCustomizedCipherSuites, protocols);
            }
        } else {
            if (!serverCustomizedCipherSuites.isEmpty()) {
                return getApplicableCipherSuites(
                        serverCustomizedCipherSuites, protocols);
            }
        }

        return getApplicableCipherSuites(
                CipherSuite.defaultCipherSuites(), protocols);
    }

    /*
     * Return the list of available CipherSuites which are applicable to
     * the specified protocols.
     */
    private static List<CipherSuite> getApplicableCipherSuites(
            Collection<CipherSuite> allowedCipherSuites,
            List<ProtocolVersion> protocols) {
        TreeSet<CipherSuite> suites = new TreeSet<>();
        if (protocols != null && (!protocols.isEmpty())) {
            for (CipherSuite suite : allowedCipherSuites) {
                if (!suite.isAvailable()) {
                    continue;
                }

                boolean isSupported = false;
                for (ProtocolVersion protocol : protocols) {
                    if (!suite.supports(protocol) ||
                            !suite.bulkCipher.isAvailable()) {
                        continue;
                    }

                    if (SSLAlgorithmConstraints.DEFAULT.permits(
                            EnumSet.of(CryptoPrimitive.KEY_AGREEMENT),
                            suite.name, null)) {
                        suites.add(suite);
                        isSupported = true;
                    } else if (SSLLogger.isOn &&
                            SSLLogger.isOn("ssl,sslctx,verbose")) {
                        SSLLogger.fine(
                                "Ignore disabled cipher suite: " + suite.name);
                    }

                    break;
                }

                if (!isSupported && SSLLogger.isOn &&
                        SSLLogger.isOn("ssl,sslctx,verbose")) {
                    SSLLogger.finest(
                            "Ignore unsupported cipher suite: " + suite);
                }
            }
        }

        return new ArrayList<>(suites);
    }

    /*
     * Get the customized cipher suites specified by the given system property.
     */
    private static Collection<CipherSuite> getCustomizedCipherSuites(
            String propertyName) {

        String property = GetPropertyAction.privilegedGetProperty(propertyName);
        if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
            SSLLogger.fine(
                    "System property " + propertyName + " is set to '" +
                    property + "'");
        }
        if (property != null && property.length() != 0) {
            // remove double quote marks from beginning/end of the property
            if (property.length() > 1 && property.charAt(0) == '"' &&
                    property.charAt(property.length() - 1) == '"') {
                property = property.substring(1, property.length() - 1);
            }
        }

        if (property != null && property.length() != 0) {
            String[] cipherSuiteNames = property.split(",");
            Collection<CipherSuite> cipherSuites =
                        new ArrayList<>(cipherSuiteNames.length);
            for (int i = 0; i < cipherSuiteNames.length; i++) {
                cipherSuiteNames[i] = cipherSuiteNames[i].trim();
                if (cipherSuiteNames[i].isEmpty()) {
                    continue;
                }

                CipherSuite suite;
                try {
                    suite = CipherSuite.nameOf(cipherSuiteNames[i]);
                } catch (IllegalArgumentException iae) {
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
                        SSLLogger.fine(
                                "Unknown or unsupported cipher suite name: " +
                                cipherSuiteNames[i]);
                    }

                    continue;
                }

                if (suite != null && suite.isAvailable()) {
                    cipherSuites.add(suite);
                } else {
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
                        SSLLogger.fine(
                                "The current installed providers do not " +
                                "support cipher suite: " + cipherSuiteNames[i]);
                    }
                }
            }

            return cipherSuites;
        }

        return Collections.emptyList();
    }


    private static List<ProtocolVersion> getAvailableProtocols(
            ProtocolVersion[] protocolCandidates) {

        List<ProtocolVersion> availableProtocols =
                Collections.<ProtocolVersion>emptyList();
        if (protocolCandidates != null && protocolCandidates.length != 0) {
            availableProtocols = new ArrayList<>(protocolCandidates.length);
            for (ProtocolVersion p : protocolCandidates) {
                if (p.isAvailable) {
                    availableProtocols.add(p);
                }
            }
        }

        return availableProtocols;
    }

    /*
     * The SSLContext implementation for SSL/(D)TLS algorithm
     *
     * SSL/TLS protocols specify the forward compatibility and version
     * roll-back attack protections, however, a number of SSL/TLS server
     * vendors did not implement these aspects properly, and some current
     * SSL/TLS servers may refuse to talk to a TLS 1.1 or later client.
     *
     * Considering above interoperability issues, OpenJSSE will not set
     * TLS 1.1 and TLS 1.2 as the enabled protocols for client by default.
     *
     * For SSL/TLS servers, there is no such interoperability issues as
     * SSL/TLS clients. In OpenJSSE, TLS 1.1 or later version will be the
     * enabled protocols for server by default.
     *
     * We may change the behavior when popular TLS/SSL vendors support TLS
     * forward compatibility properly.
     *
     * SSLv2Hello is no longer necessary.  This interoperability option was
     * put in place in the late 90's when SSLv3/TLS1.0 were relatively new
     * and there were a fair number of SSLv2-only servers deployed.  Because
     * of the security issues in SSLv2, it is rarely (if ever) used, as
     * deployments should now be using SSLv3 and TLSv1.
     *
     * Considering the issues of SSLv2Hello, we should not enable SSLv2Hello
     * by default. Applications still can use it by enabling SSLv2Hello with
     * the series of setEnabledProtocols APIs.
     */

    /*
     * The base abstract SSLContext implementation for the Transport Layer
     * Security (TLS) protocols.
     *
     * This abstract class encapsulates supported and the default server
     * SSL/TLS parameters.
     *
     * @see SSLContext
     */
    private abstract static class AbstractTLSContext extends SSLContextImpl {
        private static final List<ProtocolVersion> supportedProtocols;
        private static final List<ProtocolVersion> serverDefaultProtocols;

        private static final List<CipherSuite> supportedCipherSuites;
        private static final List<CipherSuite> serverDefaultCipherSuites;

        static {
            if (OpenJSSE.isFIPS()) {
                supportedProtocols = Arrays.asList(
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10
                );

                serverDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10
                });
            } else {
                supportedProtocols = Arrays.asList(
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30,
                    ProtocolVersion.SSL20Hello
                );

                serverDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30,
                    ProtocolVersion.SSL20Hello
                });
            }

            supportedCipherSuites = getApplicableSupportedCipherSuites(
                    supportedProtocols);
            serverDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    serverDefaultProtocols, false);
        }

        @Override
        List<ProtocolVersion> getSupportedProtocolVersions() {
            return supportedProtocols;
        }

        @Override
        List<CipherSuite> getSupportedCipherSuites() {
            return supportedCipherSuites;
        }

        @Override
        List<ProtocolVersion> getServerDefaultProtocolVersions() {
            return serverDefaultProtocols;
        }

        @Override
        List<CipherSuite> getServerDefaultCipherSuites() {
            return serverDefaultCipherSuites;
        }

        @Override
        SSLEngine createSSLEngineImpl() {
            return new SSLEngineImpl(this);
        }

        @Override
        SSLEngine createSSLEngineImpl(String host, int port) {
            return new SSLEngineImpl(this, host, port);
        }

        @Override
        boolean isDTLS() {
            return false;
        }

        static ProtocolVersion[] getSupportedProtocols() {
            if (OpenJSSE.isFIPS()) {
                return new ProtocolVersion[] {
                        ProtocolVersion.TLS13,
                        ProtocolVersion.TLS12,
                        ProtocolVersion.TLS11,
                        ProtocolVersion.TLS10
                };
            } else {
                return new ProtocolVersion[]{
                        ProtocolVersion.TLS13,
                        ProtocolVersion.TLS12,
                        ProtocolVersion.TLS11,
                        ProtocolVersion.TLS10,
                        ProtocolVersion.SSL30,
                        ProtocolVersion.SSL20Hello
                };
            }
        }
    }

    /*
     * The SSLContext implementation for SSLv3 and TLS10 algorithm
     *
     * @see SSLContext
     */
    public static final class TLS10Context extends AbstractTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            if (OpenJSSE.isFIPS()) {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS10
                });
            } else {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30
                });
            }

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for TLS11 algorithm
     *
     * @see SSLContext
     */
    public static final class TLS11Context extends AbstractTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            if (OpenJSSE.isFIPS()) {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10
                });
            } else {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30
                });
            }

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);

        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for TLS12 algorithm
     *
     * @see SSLContext
     */
    public static final class TLS12Context extends AbstractTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            if (OpenJSSE.isFIPS()) {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10
                });
            } else {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30
                });
            }

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for TLS1.3 algorithm
     *
     * @see SSLContext
     */
    public static final class TLS13Context extends AbstractTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            if (OpenJSSE.isFIPS()) {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10
                });
            } else {
                clientDefaultProtocols = getAvailableProtocols(
                        new ProtocolVersion[] {
                    ProtocolVersion.TLS13,
                    ProtocolVersion.TLS12,
                    ProtocolVersion.TLS11,
                    ProtocolVersion.TLS10,
                    ProtocolVersion.SSL30
                });
            }

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The interface for the customized SSL/(D)TLS SSLContext.
     *
     * @see SSLContext
     */
    private static class CustomizedSSLProtocols {
        private static final String JDK_TLS_CLIENT_PROTOCOLS =
                "jdk.tls.client.protocols";
        private static final String JDK_TLS_SERVER_PROTOCOLS =
                "jdk.tls.server.protocols";
        static IllegalArgumentException reservedException = null;
        static final ArrayList<ProtocolVersion> customizedClientProtocols =
                new ArrayList<>();
        static final ArrayList<ProtocolVersion> customizedServerProtocols =
                new ArrayList<>();

        // Don't want a java.lang.LinkageError for illegal system property.
        //
        // Please don't throw exception in this static block.  Otherwise,
        // java.lang.LinkageError may be thrown during the instantiation of
        // the provider service. Instead, please handle the initialization
        // exception in the caller's constructor.
        static {
            populate(JDK_TLS_CLIENT_PROTOCOLS, customizedClientProtocols);
            populate(JDK_TLS_SERVER_PROTOCOLS, customizedServerProtocols);
        }

        private static void populate(String propname,
                ArrayList<ProtocolVersion> arrayList) {
            String property = GetPropertyAction.privilegedGetProperty(propname);
            if (property == null) {
                return;
            }

            if (property.length() != 0) {
                // remove double quote marks from beginning/end of the property
                if (property.length() > 1 && property.charAt(0) == '"' &&
                        property.charAt(property.length() - 1) == '"') {
                    property = property.substring(1, property.length() - 1);
                }
            }

            if (property.length() != 0) {
                String[] protocols = property.split(",");
                for (int i = 0; i < protocols.length; i++) {
                    protocols[i] = protocols[i].trim();
                    // Is it a supported protocol name?
                    ProtocolVersion pv =
                            ProtocolVersion.nameOf(protocols[i]);
                    if (pv == null) {
                        reservedException = new IllegalArgumentException(
                            propname + ": " + protocols[i] +
                            " is not a supported SSL protocol name");
                    }

                    if (OpenJSSE.isFIPS() &&
                            ((pv == ProtocolVersion.SSL30) ||
                             (pv == ProtocolVersion.SSL20Hello))) {
                        reservedException = new IllegalArgumentException(
                                propname + ": " + pv +
                                " is not FIPS compliant");

                        break;
                    }

                    // ignore duplicated protocols
                    if (!arrayList.contains(pv)) {
                        arrayList.add(pv);
                    }
                }
            }
        }
    }

    /*
     * The SSLContext implementation for customized TLS protocols
     *
     * @see SSLContext
     */
    private static class CustomizedTLSContext extends AbstractTLSContext {

        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<ProtocolVersion> serverDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;
        private static final List<CipherSuite> serverDefaultCipherSuites;
        private static final IllegalArgumentException reservedException;

        // Don't want a java.lang.LinkageError for illegal system property.
        //
        // Please don't throw exception in this static block.  Otherwise,
        // java.lang.LinkageError may be thrown during the instantiation of
        // the provider service. Instead, let's handle the initialization
        // exception in constructor.
        static {
            reservedException = CustomizedSSLProtocols.reservedException;
            if (reservedException == null) {
                clientDefaultProtocols = customizedProtocols(true,
                        CustomizedSSLProtocols.customizedClientProtocols);
                serverDefaultProtocols = customizedProtocols(false,
                        CustomizedSSLProtocols.customizedServerProtocols);

                clientDefaultCipherSuites =
                        getApplicableEnabledCipherSuites(
                                clientDefaultProtocols, true);
                serverDefaultCipherSuites =
                        getApplicableEnabledCipherSuites(
                                serverDefaultProtocols, false);

            } else {
                // unlikely to be used
                clientDefaultProtocols = null;
                serverDefaultProtocols = null;
                clientDefaultCipherSuites = null;
                serverDefaultCipherSuites = null;
            }
        }

        private static List<ProtocolVersion> customizedProtocols(
                boolean client, List<ProtocolVersion> customized) {
            List<ProtocolVersion> refactored = new ArrayList<>();
            for (ProtocolVersion pv : customized) {
                if (!pv.isDTLS) {
                    refactored.add(pv);
                }
            }

            // Use the default enabled protocols if no customization
            ProtocolVersion[] candidates;
            if (refactored.isEmpty()) {
                if (client) {
                    candidates = getProtocols();
                } else {
                    candidates = getSupportedProtocols();
                }
            } else {
                // Use the customized TLS protocols.
                candidates =
                    refactored.toArray(new ProtocolVersion[refactored.size()]);
            }

            return getAvailableProtocols(candidates);
        }

        static ProtocolVersion[] getProtocols() {
            if (OpenJSSE.isFIPS()) {
                return new ProtocolVersion[]{
                        ProtocolVersion.TLS13,
                        ProtocolVersion.TLS12,
                        ProtocolVersion.TLS11,
                        ProtocolVersion.TLS10
                };
            } else {
                return new ProtocolVersion[]{
                        ProtocolVersion.TLS13,
                        ProtocolVersion.TLS12,
                        ProtocolVersion.TLS11,
                        ProtocolVersion.TLS10,
                        ProtocolVersion.SSL30
                };
            }
        }

        protected CustomizedTLSContext() {
            if (reservedException != null) {
                throw reservedException;
            }
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<ProtocolVersion> getServerDefaultProtocolVersions() {
            return serverDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }

        @Override
        List<CipherSuite> getServerDefaultCipherSuites() {
            return serverDefaultCipherSuites;
        }


    }

    /*
     * The SSLContext implementation for default "TLS" algorithm
     *
     * @see SSLContext
     */
    public static final class TLSContext extends CustomizedTLSContext {
        // use the default constructor and methods
    }

    // lazy initialization holder class idiom for static default parameters
    //
    // See Effective Java Second Edition: Item 71.
    private static final class DefaultManagersHolder {
        private static final String NONE = "NONE";
        private static final String P11KEYSTORE = "PKCS11";

        private static final TrustManager[] trustManagers;
        private static final KeyManager[] keyManagers;

        private static final Exception reservedException;

        static {
            Exception reserved = null;
            TrustManager[] tmMediator;
            try {
                tmMediator = getTrustManagers();
            } catch (Exception e) {
                reserved = e;
                tmMediator = new TrustManager[0];
            }
            trustManagers = tmMediator;

            if (reserved == null) {
                KeyManager[] kmMediator;
                try {
                    kmMediator = getKeyManagers();
                } catch (Exception e) {
                    reserved = e;
                    kmMediator = new KeyManager[0];
                }
                keyManagers = kmMediator;
            } else {
                keyManagers = new KeyManager[0];
            }

            reservedException = reserved;
        }

        private static TrustManager[] getTrustManagers() throws Exception {
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            if ("OpenJSSE".equals(tmf.getProvider().getName())) {
                // The implementation will load the default KeyStore
                // automatically.  Cached trust materials may be used
                // for performance improvement.
                tmf.init((KeyStore)null);
            } else {
                // Use the explicitly specified KeyStore for third party's
                // TrustManagerFactory implementation.
                KeyStore ks = TrustStoreManager.getTrustedKeyStore();
                tmf.init(ks);
            }

            return tmf.getTrustManagers();
        }

        private static KeyManager[] getKeyManagers() throws Exception {

            final Map<String,String> props = new HashMap<>();
            AccessController.doPrivileged(
                        new PrivilegedExceptionAction<Object>() {
                @Override
                public Object run() throws Exception {
                    props.put("keyStore",  System.getProperty(
                                "javax.net.ssl.keyStore", ""));
                    props.put("keyStoreType", System.getProperty(
                                "javax.net.ssl.keyStoreType",
                                KeyStore.getDefaultType()));
                    props.put("keyStoreProvider", System.getProperty(
                                "javax.net.ssl.keyStoreProvider", ""));
                    props.put("keyStorePasswd", System.getProperty(
                                "javax.net.ssl.keyStorePassword", ""));
                    return null;
                }
            });

            final String defaultKeyStore = props.get("keyStore");
            String defaultKeyStoreType = props.get("keyStoreType");
            String defaultKeyStoreProvider = props.get("keyStoreProvider");
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) {
                SSLLogger.fine("keyStore is : " + defaultKeyStore);
                SSLLogger.fine("keyStore type is : " +
                                        defaultKeyStoreType);
                SSLLogger.fine("keyStore provider is : " +
                                        defaultKeyStoreProvider);
            }

            if (P11KEYSTORE.equals(defaultKeyStoreType) &&
                    !NONE.equals(defaultKeyStore)) {
                throw new IllegalArgumentException("if keyStoreType is "
                    + P11KEYSTORE + ", then keyStore must be " + NONE);
            }

            FileInputStream fs = null;
            KeyStore ks = null;
            char[] passwd = null;
            try {
                if (defaultKeyStore.length() != 0 &&
                        !NONE.equals(defaultKeyStore)) {
                    fs = AccessController.doPrivileged(
                            new PrivilegedExceptionAction<FileInputStream>() {
                        @Override
                        public FileInputStream run() throws Exception {
                            return new FileInputStream(defaultKeyStore);
                        }
                    });
                }

                String defaultKeyStorePassword = props.get("keyStorePasswd");
                if (defaultKeyStorePassword.length() != 0) {
                    passwd = defaultKeyStorePassword.toCharArray();
                }

                /**
                 * Try to initialize key store.
                 */
                if ((defaultKeyStoreType.length()) != 0) {
                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) {
                        SSLLogger.finest("init keystore");
                    }
                    if (defaultKeyStoreProvider.length() == 0) {
                        ks = KeyStore.getInstance(defaultKeyStoreType);
                    } else {
                        ks = KeyStore.getInstance(defaultKeyStoreType,
                                            defaultKeyStoreProvider);
                    }

                    // if defaultKeyStore is NONE, fs will be null
                    ks.load(fs, passwd);
                }
            } finally {
                if (fs != null) {
                    fs.close();
                    fs = null;
                }
            }

            /*
             * Try to initialize key manager.
             */
            if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) {
                SSLLogger.fine("init keymanager of type " +
                    KeyManagerFactory.getDefaultAlgorithm());
            }
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                KeyManagerFactory.getDefaultAlgorithm());

            if (P11KEYSTORE.equals(defaultKeyStoreType)) {
                kmf.init(ks, null); // do not pass key passwd if using token
            } else {
                kmf.init(ks, passwd);
            }

            return kmf.getKeyManagers();
        }
    }

    // lazy initialization holder class idiom for static default parameters
    //
    // See Effective Java Second Edition: Item 71.
    private static final class DefaultSSLContextHolder {

        private static final SSLContextImpl sslContext;
        static Exception reservedException = null;

        static {
            SSLContextImpl mediator = null;
            if (DefaultManagersHolder.reservedException != null) {
                reservedException = DefaultManagersHolder.reservedException;
            } else {
                try {
                    mediator = new DefaultSSLContext();
                } catch (Exception e) {
                    reservedException = e;
                }
            }

            sslContext = mediator;
        }
    }

    /*
     * The SSLContext implementation for default "Default" algorithm
     *
     * @see SSLContext
     */
    public static final class DefaultSSLContext extends CustomizedTLSContext {

        // public constructor for SSLContext.getInstance("Default")
        public DefaultSSLContext() throws Exception {
            if (DefaultManagersHolder.reservedException != null) {
                throw DefaultManagersHolder.reservedException;
            }

            try {
                super.engineInit(DefaultManagersHolder.keyManagers,
                        DefaultManagersHolder.trustManagers, null);
            } catch (Exception e) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) {
                    SSLLogger.fine("default context init failed: ", e);
                }
                throw e;
            }
        }

        @Override
        protected void engineInit(KeyManager[] km, TrustManager[] tm,
            SecureRandom sr) throws KeyManagementException {
            throw new KeyManagementException
                ("Default SSLContext is initialized automatically");
        }

        static SSLContextImpl getDefaultImpl() throws Exception {
            if (DefaultSSLContextHolder.reservedException != null) {
                throw DefaultSSLContextHolder.reservedException;
            }

            return DefaultSSLContextHolder.sslContext;
        }
    }

    /*
     * The base abstract SSLContext implementation for the Datagram Transport
     * Layer Security (DTLS) protocols.
     *
     * This abstract class encapsulates supported and the default server DTLS
     * parameters.
     *
     * @see SSLContext
     */
    private abstract static class AbstractDTLSContext extends SSLContextImpl {
        private static final List<ProtocolVersion> supportedProtocols;
        private static final List<ProtocolVersion> serverDefaultProtocols;

        private static final List<CipherSuite> supportedCipherSuites;
        private static final List<CipherSuite> serverDefaultCipherSuites;

        static {
            // Both DTLSv1.0 and DTLSv1.2 can be used in FIPS mode.
            supportedProtocols = Arrays.asList(
                ProtocolVersion.DTLS12,
                ProtocolVersion.DTLS10
            );

            // available protocols for server mode
            serverDefaultProtocols = getAvailableProtocols(
                    new ProtocolVersion[] {
                ProtocolVersion.DTLS12,
                ProtocolVersion.DTLS10
            });

            supportedCipherSuites = getApplicableSupportedCipherSuites(
                    supportedProtocols);
            serverDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    serverDefaultProtocols, false);
        }

        @Override
        List<ProtocolVersion> getSupportedProtocolVersions() {
            return supportedProtocols;
        }

        @Override
        List<CipherSuite> getSupportedCipherSuites() {
            return supportedCipherSuites;
        }

        @Override
        List<ProtocolVersion> getServerDefaultProtocolVersions() {
            return serverDefaultProtocols;
        }

        @Override
        List<CipherSuite> getServerDefaultCipherSuites() {
            return serverDefaultCipherSuites;
        }

        @Override
        SSLEngine createSSLEngineImpl() {
            return new SSLEngineImpl(this);
        }

        @Override
        SSLEngine createSSLEngineImpl(String host, int port) {
            return new SSLEngineImpl(this, host, port);
        }

        @Override
        boolean isDTLS() {
            return true;
        }
    }

    /*
     * The SSLContext implementation for DTLSv1.0 algorithm.
     *
     * @see SSLContext
     */
    public static final class DTLS10Context extends AbstractDTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            // available protocols for client mode
            clientDefaultProtocols = getAvailableProtocols(
                    new ProtocolVersion[] {
                ProtocolVersion.DTLS10
            });

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for DTLSv1.2 algorithm.
     *
     * @see SSLContext
     */
    public static final class DTLS12Context extends AbstractDTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;

        static {
            // available protocols for client mode
            clientDefaultProtocols = getAvailableProtocols(
                    new ProtocolVersion[] {
                ProtocolVersion.DTLS12,
                ProtocolVersion.DTLS10
            });

            clientDefaultCipherSuites = getApplicableEnabledCipherSuites(
                    clientDefaultProtocols, true);
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for customized TLS protocols
     *
     * @see SSLContext
     */
    private static class CustomizedDTLSContext extends AbstractDTLSContext {
        private static final List<ProtocolVersion> clientDefaultProtocols;
        private static final List<ProtocolVersion> serverDefaultProtocols;
        private static final List<CipherSuite> clientDefaultCipherSuites;
        private static final List<CipherSuite> serverDefaultCipherSuites;

        private static IllegalArgumentException reservedException = null;

        // Don't want a java.lang.LinkageError for illegal system property.
        //
        // Please don't throw exception in this static block.  Otherwise,
        // java.lang.LinkageError may be thrown during the instantiation of
        // the provider service. Instead, let's handle the initialization
        // exception in constructor.
        static {
            reservedException = CustomizedSSLProtocols.reservedException;
            if (reservedException == null) {
                clientDefaultProtocols = customizedProtocols(true,
                        CustomizedSSLProtocols.customizedClientProtocols);
                serverDefaultProtocols = customizedProtocols(false,
                        CustomizedSSLProtocols.customizedServerProtocols);

                clientDefaultCipherSuites =
                        getApplicableEnabledCipherSuites(
                                clientDefaultProtocols, true);
                serverDefaultCipherSuites =
                        getApplicableEnabledCipherSuites(
                                serverDefaultProtocols, false);

            } else {
                // unlikely to be used
                clientDefaultProtocols = null;
                serverDefaultProtocols = null;
                clientDefaultCipherSuites = null;
                serverDefaultCipherSuites = null;
            }
        }

        private static List<ProtocolVersion> customizedProtocols(boolean client,
                List<ProtocolVersion> customized) {
            List<ProtocolVersion> refactored = new ArrayList<>();
            for (ProtocolVersion pv : customized) {
                if (pv.isDTLS) {
                    refactored.add(pv);
                }
            }

            ProtocolVersion[] candidates;
            // Use the default enabled protocols if no customization
            if (refactored.isEmpty()) {
                candidates = new ProtocolVersion[]{
                        ProtocolVersion.DTLS12,
                        ProtocolVersion.DTLS10
                };
                if (!client)
                    return Arrays.asList(candidates);
            } else {
                // Use the customized TLS protocols.
                candidates =
                        new ProtocolVersion[customized.size()];
                candidates = customized.toArray(candidates);
            }

            return getAvailableProtocols(candidates);
        }

        protected CustomizedDTLSContext() {
            if (reservedException != null) {
                throw reservedException;
            }
        }

        @Override
        List<ProtocolVersion> getClientDefaultProtocolVersions() {
            return clientDefaultProtocols;
        }

        @Override
        List<ProtocolVersion> getServerDefaultProtocolVersions() {
            return serverDefaultProtocols;
        }

        @Override
        List<CipherSuite> getClientDefaultCipherSuites() {
            return clientDefaultCipherSuites;
        }

        @Override
        List<CipherSuite> getServerDefaultCipherSuites() {
            return serverDefaultCipherSuites;
        }
    }

    /*
     * The SSLContext implementation for default "DTLS" algorithm
     *
     * @see SSLContext
     */
    public static final class DTLSContext extends CustomizedDTLSContext {
        // use the default constructor and methods
    }

}

final class AbstractTrustManagerWrapper extends X509ExtendedTrustManager
            implements X509TrustManager {

    // the delegated trust manager
    private final X509TrustManager tm;

    AbstractTrustManagerWrapper(X509TrustManager tm) {
        this.tm = tm;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        tm.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        tm.checkServerTrusted(chain, authType);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return tm.getAcceptedIssuers();
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType,
                Socket socket) throws CertificateException {
        tm.checkClientTrusted(chain, authType);
        checkAdditionalTrust(chain, authType, socket, true);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType,
            Socket socket) throws CertificateException {
        tm.checkServerTrusted(chain, authType);
        checkAdditionalTrust(chain, authType, socket, false);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType,
            javax.net.ssl.SSLEngine engine) throws CertificateException {
        tm.checkClientTrusted(chain, authType);
        checkAdditionalTrust(chain, authType, engine, true);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType,
            javax.net.ssl.SSLEngine engine) throws CertificateException {
        tm.checkServerTrusted(chain, authType);
        checkAdditionalTrust(chain, authType, engine, false);
    }

    private void checkAdditionalTrust(X509Certificate[] chain,
            String authType, Socket socket,
            boolean checkClientTrusted) throws CertificateException {
        if (socket != null && socket.isConnected() &&
                                    socket instanceof SSLSocket) {

            SSLSocket sslSocket = (SSLSocket)socket;
            SSLSession session = sslSocket.getHandshakeSession();
            if (session == null) {
                throw new CertificateException("No handshake session");
            }

            // check endpoint identity
            String identityAlg = sslSocket.getSSLParameters().
                                        getEndpointIdentificationAlgorithm();
            if (identityAlg != null && identityAlg.length() != 0) {
                X509TrustManagerImpl.checkIdentity(session, chain,
                                    identityAlg, checkClientTrusted);
            }

            // try the best to check the algorithm constraints
            AlgorithmConstraints constraints;
            if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) {
                if (session instanceof ExtendedSSLSession) {
                    ExtendedSSLSession extSession =
                                    (ExtendedSSLSession)session;
                    String[] peerSupportedSignAlgs =
                            extSession.getLocalSupportedSignatureAlgorithms();

                    constraints = new SSLAlgorithmConstraints(
                                    sslSocket, peerSupportedSignAlgs, true);
                } else {
                    constraints =
                            new SSLAlgorithmConstraints(sslSocket, true);
                }
            } else {
                constraints = new SSLAlgorithmConstraints(sslSocket, true);
            }

            checkAlgorithmConstraints(chain, constraints, checkClientTrusted);
        }
    }

    private void checkAdditionalTrust(X509Certificate[] chain,
            String authType, javax.net.ssl.SSLEngine engine,
            boolean checkClientTrusted) throws CertificateException {
        if (engine != null) {
            SSLSession session = engine.getHandshakeSession();
            if (session == null) {
                throw new CertificateException("No handshake session");
            }

            // check endpoint identity
            String identityAlg = engine.getSSLParameters().
                                        getEndpointIdentificationAlgorithm();
            if (identityAlg != null && identityAlg.length() != 0) {
                X509TrustManagerImpl.checkIdentity(session, chain,
                                    identityAlg, checkClientTrusted);
            }

            // try the best to check the algorithm constraints
            AlgorithmConstraints constraints;
            if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) {
                if (session instanceof ExtendedSSLSession) {
                    ExtendedSSLSession extSession =
                                    (ExtendedSSLSession)session;
                    String[] peerSupportedSignAlgs =
                            extSession.getLocalSupportedSignatureAlgorithms();

                    constraints = new SSLAlgorithmConstraints(
                                    (org.openjsse.javax.net.ssl.SSLEngine)engine, peerSupportedSignAlgs, true);
                } else {
                    constraints =
                            new SSLAlgorithmConstraints((org.openjsse.javax.net.ssl.SSLEngine)engine, true);
                }
            } else {
                constraints = new SSLAlgorithmConstraints((org.openjsse.javax.net.ssl.SSLEngine)engine, true);
            }

            checkAlgorithmConstraints(chain, constraints, checkClientTrusted);
        }
    }

    private void checkAlgorithmConstraints(X509Certificate[] chain,
            AlgorithmConstraints constraints,
            boolean checkClientTrusted) throws CertificateException {
        try {
            // Does the certificate chain end with a trusted certificate?
            int checkedLength = chain.length - 1;

            Collection<X509Certificate> trustedCerts = new HashSet<>();
            X509Certificate[] certs = tm.getAcceptedIssuers();
            if ((certs != null) && (certs.length > 0)){
                Collections.addAll(trustedCerts, certs);
            }

            if (trustedCerts.contains(chain[checkedLength])) {
                    checkedLength--;
            }

            // A forward checker, need to check from trust to target
            if (checkedLength >= 0) {
                AlgorithmChecker checker =
                    new AlgorithmChecker(constraints, null,
                            (checkClientTrusted ? Validator.VAR_TLS_CLIENT :
                                        Validator.VAR_TLS_SERVER));
                checker.init(false);
                for (int i = checkedLength; i >= 0; i--) {
                    X509Certificate cert = chain[i];
                    // We don't care about the unresolved critical extensions.
                    checker.check(cert, Collections.<String>emptySet());
                }
            }
        } catch (CertPathValidatorException cpve) {
            throw new CertificateException(
                "Certificates do not conform to algorithm constraints", cpve);
        }
    }
}

// Dummy X509TrustManager implementation, rejects all peer certificates.
// Used if the application did not specify a proper X509TrustManager.
final class DummyX509TrustManager extends X509ExtendedTrustManager
            implements X509TrustManager {

    static final X509TrustManager INSTANCE = new DummyX509TrustManager();

    private DummyX509TrustManager() {
        // empty
    }

    /*
     * Given the partial or complete certificate chain
     * provided by the peer, build a certificate path
     * to a trusted root and return if it can be
     * validated and is trusted for client SSL authentication.
     * If not, it throws an exception.
     */
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation avaiable");
    }

    /*
     * Given the partial or complete certificate chain
     * provided by the peer, build a certificate path
     * to a trusted root and return if it can be
     * validated and is trusted for server SSL authentication.
     * If not, it throws an exception.
     */
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation available");
    }

    /*
     * Return an array of issuer certificates which are trusted
     * for authenticating peers.
     */
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType,
                Socket socket) throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation available");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType,
            Socket socket) throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation available");
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType,
            javax.net.ssl.SSLEngine engine) throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation available");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType,
            javax.net.ssl.SSLEngine engine) throws CertificateException {
        throw new CertificateException(
            "No X509TrustManager implementation available");
    }
}

/*
 * A wrapper class to turn a X509KeyManager into an X509ExtendedKeyManager
 */
final class AbstractKeyManagerWrapper extends X509ExtendedKeyManager {

    private final X509KeyManager km;

    AbstractKeyManagerWrapper(X509KeyManager km) {
        this.km = km;
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return km.getClientAliases(keyType, issuers);
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers,
            Socket socket) {
        return km.chooseClientAlias(keyType, issuers, socket);
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return km.getServerAliases(keyType, issuers);
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers,
            Socket socket) {
        return km.chooseServerAlias(keyType, issuers, socket);
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return km.getCertificateChain(alias);
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return km.getPrivateKey(alias);
    }

    // Inherit chooseEngineClientAlias() and chooseEngineServerAlias() from
    // X509ExtendedKeymanager. It defines them to return null;
}


// Dummy X509KeyManager implementation, never returns any certificates/keys.
// Used if the application did not specify a proper X509TrustManager.
final class DummyX509KeyManager extends X509ExtendedKeyManager {

    static final X509ExtendedKeyManager INSTANCE = new DummyX509KeyManager();

    private DummyX509KeyManager() {
        // empty
    }

    /*
     * Get the matching aliases for authenticating the client side of a secure
     * socket given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return null;
    }

    /*
     * Choose an alias to authenticate the client side of a secure
     * socket given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String chooseClientAlias(String[] keyTypes, Principal[] issuers,
            Socket socket) {
        return null;
    }

    /*
     * Choose an alias to authenticate the client side of an
     * engine given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String chooseEngineClientAlias(
            String[] keyTypes, Principal[] issuers, javax.net.ssl.SSLEngine engine) {
        return null;
    }

    /*
     * Get the matching aliases for authenticating the server side of a secure
     * socket given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return null;
    }

    /*
     * Choose an alias to authenticate the server side of a secure
     * socket given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers,
            Socket socket) {
        return null;
    }

    /*
     * Choose an alias to authenticate the server side of an engine
     * given the public key type and the list of
     * certificate issuer authorities recognized by the peer (if any).
     */
    @Override
    public String chooseEngineServerAlias(
            String keyType, Principal[] issuers, javax.net.ssl.SSLEngine engine) {
        return null;
    }

    /**
     * Returns the certificate chain associated with the given alias.
     *
     * @param alias the alias name
     *
     * @return the certificate chain (ordered with the user's certificate first
     * and the root certificate authority last)
     */
    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return null;
    }

    /*
     * Returns the key associated with the given alias, using the given
     * password to recover it.
     *
     * @param alias the alias name
     *
     * @return the requested key
     */
    @Override
    public PrivateKey getPrivateKey(String alias) {
        return null;
    }
}