// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.ssl;

import java.util.Set;
import io.netty.util.internal.EmptyArrays;
import java.util.HashSet;
import javax.security.auth.x500.X500Principal;
import javax.net.ssl.SSLEngine;
import io.netty.internal.tcnative.SSL;
import javax.net.ssl.SSLSessionContext;
import io.netty.internal.tcnative.CertificateVerifier;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import io.netty.internal.tcnative.CertificateCallback;
import io.netty.internal.tcnative.SSLContext;
import javax.net.ssl.SSLException;
import java.security.cert.Certificate;
import java.util.Map;
import javax.net.ssl.SNIServerName;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
import java.security.PrivateKey;
import javax.net.ssl.TrustManagerFactory;
import java.security.cert.X509Certificate;

public final class ReferenceCountedOpenSslClientContext extends ReferenceCountedOpenSslContext
{
    private static final String[] SUPPORTED_KEY_TYPES;
    private final OpenSslSessionContext sessionContext;
    
    ReferenceCountedOpenSslClientContext(final X509Certificate[] trustCertCollection, final TrustManagerFactory trustManagerFactory, final X509Certificate[] keyCertChain, final PrivateKey key, final String keyPassword, final KeyManagerFactory keyManagerFactory, final Iterable<String> ciphers, final CipherSuiteFilter cipherFilter, final ApplicationProtocolConfig apn, final String[] protocols, final long sessionCacheSize, final long sessionTimeout, final boolean enableOcsp, final String keyStore, final String endpointIdentificationAlgorithm, final List<SNIServerName> serverNames, final ResumptionController resumptionController, final Map.Entry<SslContextOption<?>, Object>... options) throws SSLException {
        super(ciphers, cipherFilter, ReferenceCountedOpenSslContext.toNegotiator(apn), 0, keyCertChain, ClientAuth.NONE, protocols, false, endpointIdentificationAlgorithm, enableOcsp, true, serverNames, resumptionController, options);
        boolean success = false;
        try {
            this.sessionContext = newSessionContext(this, this.ctx, this.engines, trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, keyStore, sessionCacheSize, sessionTimeout, resumptionController, ReferenceCountedOpenSslContext.isJdkSignatureFallbackEnabled(options));
            success = true;
        }
        finally {
            if (!success) {
                this.release();
            }
        }
    }
    
    @Override
    public OpenSslSessionContext sessionContext() {
        return this.sessionContext;
    }
    
    static OpenSslSessionContext newSessionContext(final ReferenceCountedOpenSslContext thiz, final long ctx, final Map<Long, ReferenceCountedOpenSslEngine> engines, final X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory, final X509Certificate[] keyCertChain, final PrivateKey key, final String keyPassword, KeyManagerFactory keyManagerFactory, final String keyStore, final long sessionCacheSize, final long sessionTimeout, final ResumptionController resumptionController, final boolean fallbackToJdkProviders) throws SSLException {
        if ((key == null && keyCertChain != null) || (key != null && keyCertChain == null)) {
            throw new IllegalArgumentException("Either both keyCertChain and key needs to be null or none of them");
        }
        OpenSslKeyMaterialProvider keyMaterialProvider = null;
        try {
            try {
                if (keyManagerFactory == null && key != null && key.getEncoded() == null) {
                    if (!fallbackToJdkProviders) {
                        throw new SSLException("Private key requiring alternative signature provider detected (such as hardware security key, smart card, or remote signing service) but alternative key fallback is disabled.");
                    }
                    final OpenSslKeyMaterialManager materialManager;
                    keyMaterialProvider = ReferenceCountedOpenSslContext.setupSecurityProviderSignatureSource(thiz, ctx, keyCertChain, key, materialManager -> new OpenSslClientCertificateCallback(engines, materialManager));
                }
                else if (!OpenSsl.useKeyManagerFactory()) {
                    if (keyManagerFactory != null) {
                        throw new IllegalArgumentException("KeyManagerFactory not supported");
                    }
                    if (keyCertChain != null) {
                        ReferenceCountedOpenSslContext.setKeyMaterial(ctx, keyCertChain, key, keyPassword);
                    }
                }
                else {
                    if (keyManagerFactory == null && keyCertChain != null) {
                        keyManagerFactory = ReferenceCountedOpenSslContext.certChainToKeyManagerFactory(keyCertChain, key, keyPassword, keyStore);
                    }
                    if (keyManagerFactory != null) {
                        keyMaterialProvider = ReferenceCountedOpenSslContext.providerFor(keyManagerFactory, keyPassword);
                    }
                    if (keyMaterialProvider != null) {
                        final OpenSslKeyMaterialManager materialManager = new OpenSslKeyMaterialManager(keyMaterialProvider, thiz.hasTmpDhKeys);
                        SSLContext.setCertificateCallback(ctx, (CertificateCallback)new OpenSslClientCertificateCallback(engines, materialManager));
                    }
                }
            }
            catch (final Exception e) {
                throw new SSLException("failed to set certificate and key", e);
            }
            SSLContext.setVerify(ctx, 1, 10);
            try {
                if (trustCertCollection != null) {
                    trustManagerFactory = SslContext.buildTrustManagerFactory(trustCertCollection, trustManagerFactory, keyStore);
                }
                else if (trustManagerFactory == null) {
                    trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                    trustManagerFactory.init((KeyStore)null);
                }
                final X509TrustManager manager = ReferenceCountedOpenSslContext.chooseTrustManager(trustManagerFactory.getTrustManagers(), resumptionController);
                setVerifyCallback(ctx, engines, manager);
            }
            catch (final Exception e) {
                if (keyMaterialProvider != null) {
                    keyMaterialProvider.destroy();
                }
                throw new SSLException("unable to setup trustmanager", e);
            }
            final OpenSslClientSessionContext context = new OpenSslClientSessionContext(thiz, keyMaterialProvider);
            context.setSessionCacheEnabled(ReferenceCountedOpenSslClientContext.CLIENT_ENABLE_SESSION_CACHE);
            if (sessionCacheSize > 0L) {
                context.setSessionCacheSize((int)Math.min(sessionCacheSize, 2147483647L));
            }
            if (sessionTimeout > 0L) {
                context.setSessionTimeout((int)Math.min(sessionTimeout, 2147483647L));
            }
            if (ReferenceCountedOpenSslClientContext.CLIENT_ENABLE_SESSION_TICKET) {
                context.setTicketKeys(new OpenSslSessionTicketKey[0]);
            }
            keyMaterialProvider = null;
            return context;
        }
        finally {
            if (keyMaterialProvider != null) {
                keyMaterialProvider.destroy();
            }
        }
    }
    
    private static void setVerifyCallback(final long ctx, final Map<Long, ReferenceCountedOpenSslEngine> engines, final X509TrustManager manager) {
        if (ReferenceCountedOpenSslContext.useExtendedTrustManager(manager)) {
            SSLContext.setCertVerifyCallback(ctx, (CertificateVerifier)new ExtendedTrustManagerVerifyCallback(engines, (X509ExtendedTrustManager)manager));
        }
        else {
            SSLContext.setCertVerifyCallback(ctx, (CertificateVerifier)new TrustManagerVerifyCallback(engines, manager));
        }
    }
    
    static {
        SUPPORTED_KEY_TYPES = new String[] { "RSA", "DH_RSA", "EC", "EC_RSA", "EC_EC" };
    }
    
    static final class OpenSslClientSessionContext extends OpenSslSessionContext
    {
        OpenSslClientSessionContext(final ReferenceCountedOpenSslContext context, final OpenSslKeyMaterialProvider provider) {
            super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engines));
        }
    }
    
    private static final class TrustManagerVerifyCallback extends AbstractCertificateVerifier
    {
        private final X509TrustManager manager;
        
        TrustManagerVerifyCallback(final Map<Long, ReferenceCountedOpenSslEngine> engines, final X509TrustManager manager) {
            super(engines);
            this.manager = manager;
        }
        
        @Override
        void verify(final ReferenceCountedOpenSslEngine engine, final X509Certificate[] peerCerts, final String auth) throws Exception {
            this.manager.checkServerTrusted(peerCerts, auth);
        }
    }
    
    private static final class ExtendedTrustManagerVerifyCallback extends AbstractCertificateVerifier
    {
        private final X509ExtendedTrustManager manager;
        
        ExtendedTrustManagerVerifyCallback(final Map<Long, ReferenceCountedOpenSslEngine> engines, final X509ExtendedTrustManager manager) {
            super(engines);
            this.manager = manager;
        }
        
        @Override
        void verify(final ReferenceCountedOpenSslEngine engine, final X509Certificate[] peerCerts, final String auth) throws Exception {
            this.manager.checkServerTrusted(peerCerts, auth, engine);
        }
    }
    
    private static final class OpenSslClientCertificateCallback implements CertificateCallback
    {
        private final Map<Long, ReferenceCountedOpenSslEngine> engines;
        private final OpenSslKeyMaterialManager keyManagerHolder;
        
        OpenSslClientCertificateCallback(final Map<Long, ReferenceCountedOpenSslEngine> engines, final OpenSslKeyMaterialManager keyManagerHolder) {
            this.engines = engines;
            this.keyManagerHolder = keyManagerHolder;
        }
        
        public void handle(final long ssl, final byte[] keyTypeBytes, final byte[][] asn1DerEncodedPrincipals) throws Exception {
            final ReferenceCountedOpenSslEngine engine = this.engines.get(ssl);
            if (engine == null) {
                return;
            }
            try {
                final String[] keyTypes = supportedClientKeyTypes(keyTypeBytes);
                X500Principal[] issuers;
                if (asn1DerEncodedPrincipals == null) {
                    issuers = null;
                }
                else {
                    issuers = new X500Principal[asn1DerEncodedPrincipals.length];
                    for (int i = 0; i < asn1DerEncodedPrincipals.length; ++i) {
                        issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
                    }
                }
                this.keyManagerHolder.setKeyMaterialClientSide(engine, keyTypes, issuers);
            }
            catch (final Throwable cause) {
                engine.initHandshakeException(cause);
                if (cause instanceof Exception) {
                    throw (Exception)cause;
                }
                throw new SSLException(cause);
            }
        }
        
        private static String[] supportedClientKeyTypes(final byte[] clientCertificateTypes) {
            if (clientCertificateTypes == null) {
                return ReferenceCountedOpenSslClientContext.SUPPORTED_KEY_TYPES.clone();
            }
            final Set<String> result = new HashSet<String>(clientCertificateTypes.length);
            for (final byte keyTypeCode : clientCertificateTypes) {
                final String keyType = clientKeyType(keyTypeCode);
                if (keyType != null) {
                    result.add(keyType);
                }
            }
            return result.toArray(EmptyArrays.EMPTY_STRINGS);
        }
        
        private static String clientKeyType(final byte clientCertificateType) {
            switch (clientCertificateType) {
                case 1: {
                    return "RSA";
                }
                case 3: {
                    return "DH_RSA";
                }
                case 64: {
                    return "EC";
                }
                case 65: {
                    return "EC_RSA";
                }
                case 66: {
                    return "EC_EC";
                }
                default: {
                    return null;
                }
            }
        }
    }
}
