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

package io.netty.handler.codec.quic;

import java.util.Collections;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.HashMap;
import io.netty.util.CharsetUtil;
import javax.net.ssl.SSLEngine;
import java.security.Principal;
import java.io.IOException;
import java.util.Base64;
import java.io.ByteArrayOutputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import java.util.HashSet;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLException;
import javax.security.auth.x500.X500Principal;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.X509ExtendedKeyManager;
import java.util.Set;
import java.util.Map;

final class BoringSSLCertificateCallback
{
    private static final byte[] BEGIN_PRIVATE_KEY;
    private static final byte[] END_PRIVATE_KEY;
    private static final byte TLS_CT_RSA_SIGN = 1;
    private static final byte TLS_CT_DSS_SIGN = 2;
    private static final byte TLS_CT_RSA_FIXED_DH = 3;
    private static final byte TLS_CT_DSS_FIXED_DH = 4;
    private static final byte TLS_CT_ECDSA_SIGN = 64;
    private static final byte TLS_CT_RSA_FIXED_ECDH = 65;
    private static final byte TLS_CT_ECDSA_FIXED_ECDH = 66;
    static final String KEY_TYPE_RSA = "RSA";
    static final String KEY_TYPE_DH_RSA = "DH_RSA";
    static final String KEY_TYPE_EC = "EC";
    static final String KEY_TYPE_EC_EC = "EC_EC";
    static final String KEY_TYPE_EC_RSA = "EC_RSA";
    private static final Map<String, String> DEFAULT_SERVER_KEY_TYPES;
    private static final Set<String> DEFAULT_CLIENT_KEY_TYPES;
    private static final long[] NO_KEY_MATERIAL_CLIENT_SIDE;
    private final QuicheQuicSslEngineMap engineMap;
    private final X509ExtendedKeyManager keyManager;
    private final String password;
    private final Map<String, String> serverKeyTypes;
    private final Set<String> clientKeyTypes;
    
    BoringSSLCertificateCallback(final QuicheQuicSslEngineMap engineMap, @Nullable final X509ExtendedKeyManager keyManager, final String password, final Map<String, String> serverKeyTypes, final Set<String> clientKeyTypes) {
        this.engineMap = engineMap;
        this.keyManager = keyManager;
        this.password = password;
        this.serverKeyTypes = ((serverKeyTypes != null) ? serverKeyTypes : BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES);
        this.clientKeyTypes = ((clientKeyTypes != null) ? clientKeyTypes : BoringSSLCertificateCallback.DEFAULT_CLIENT_KEY_TYPES);
    }
    
    long[] handle(final long ssl, final byte[] keyTypeBytes, final byte[][] asn1DerEncodedPrincipals, final String[] authMethods) {
        final QuicheQuicSslEngine engine = this.engineMap.get(ssl);
        if (engine == null) {
            return null;
        }
        try {
            if (this.keyManager == null) {
                if (engine.getUseClientMode()) {
                    return BoringSSLCertificateCallback.NO_KEY_MATERIAL_CLIENT_SIDE;
                }
                return null;
            }
            else {
                if (engine.getUseClientMode()) {
                    final Set<String> keyTypesSet = this.supportedClientKeyTypes(keyTypeBytes);
                    final String[] keyTypes = keyTypesSet.toArray(new String[0]);
                    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]);
                        }
                    }
                    return this.removeMappingIfNeeded(ssl, this.selectKeyMaterialClientSide(ssl, engine, keyTypes, issuers));
                }
                return this.removeMappingIfNeeded(ssl, this.selectKeyMaterialServerSide(ssl, engine, authMethods));
            }
        }
        catch (final SSLException e) {
            this.engineMap.remove(ssl);
            return null;
        }
        catch (final Throwable cause) {
            this.engineMap.remove(ssl);
            throw cause;
        }
    }
    
    private long[] removeMappingIfNeeded(final long ssl, final long[] result) {
        if (result == null) {
            this.engineMap.remove(ssl);
        }
        return result;
    }
    
    private long[] selectKeyMaterialServerSide(final long ssl, final QuicheQuicSslEngine engine, final String[] authMethods) throws SSLException {
        if (authMethods.length == 0) {
            throw new SSLHandshakeException("Unable to find key material");
        }
        final Set<String> typeSet = new HashSet<String>(this.serverKeyTypes.size());
        for (final String authMethod : authMethods) {
            final String type = this.serverKeyTypes.get(authMethod);
            if (type != null && typeSet.add(type)) {
                final String alias = this.chooseServerAlias(engine, type);
                if (alias != null) {
                    return this.selectMaterial(ssl, engine, alias);
                }
            }
        }
        throw new SSLHandshakeException("Unable to find key material for auth method(s): " + Arrays.toString(authMethods));
    }
    
    private long[] selectKeyMaterialClientSide(final long ssl, final QuicheQuicSslEngine engine, final String[] keyTypes, final X500Principal[] issuer) {
        final String alias = this.chooseClientAlias(engine, keyTypes, issuer);
        if (alias != null) {
            return this.selectMaterial(ssl, engine, alias);
        }
        return BoringSSLCertificateCallback.NO_KEY_MATERIAL_CLIENT_SIDE;
    }
    
    private long[] selectMaterial(final long ssl, final QuicheQuicSslEngine engine, final String alias) {
        final X509Certificate[] certificates = this.keyManager.getCertificateChain(alias);
        if (certificates == null || certificates.length == 0) {
            return null;
        }
        final byte[][] certs = new byte[certificates.length][];
        for (int i = 0; i < certificates.length; ++i) {
            try {
                certs[i] = certificates[i].getEncoded();
            }
            catch (final CertificateEncodingException e) {
                return null;
            }
        }
        final PrivateKey privateKey = this.keyManager.getPrivateKey(alias);
        long key;
        if (privateKey == BoringSSLKeylessPrivateKey.INSTANCE) {
            key = 0L;
        }
        else {
            final byte[] pemKey = toPemEncoded(privateKey);
            if (pemKey == null) {
                return null;
            }
            key = BoringSSL.EVP_PKEY_parse(pemKey, this.password);
        }
        final long chain = BoringSSL.CRYPTO_BUFFER_stack_new(ssl, certs);
        engine.setLocalCertificateChain(certificates);
        return new long[] { key, chain };
    }
    
    private static byte[] toPemEncoded(final PrivateKey key) {
        try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            out.write(BoringSSLCertificateCallback.BEGIN_PRIVATE_KEY);
            out.write(Base64.getEncoder().encode(key.getEncoded()));
            out.write(BoringSSLCertificateCallback.END_PRIVATE_KEY);
            return out.toByteArray();
        }
        catch (final IOException e) {
            return null;
        }
    }
    
    @Nullable
    private String chooseClientAlias(final QuicheQuicSslEngine engine, final String[] keyTypes, final X500Principal[] issuer) {
        return this.keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
    }
    
    @Nullable
    private String chooseServerAlias(final QuicheQuicSslEngine engine, final String type) {
        return this.keyManager.chooseEngineServerAlias(type, null, engine);
    }
    
    private Set<String> supportedClientKeyTypes(final byte[] clientCertificateTypes) {
        if (clientCertificateTypes == null) {
            return this.clientKeyTypes;
        }
        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;
    }
    
    @Nullable
    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;
            }
        }
    }
    
    static {
        BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
        END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
        (DEFAULT_SERVER_KEY_TYPES = new HashMap<String, String>()).put("RSA", "RSA");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("DHE_RSA", "RSA");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("ECDHE_RSA", "RSA");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("ECDHE_ECDSA", "EC");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("ECDH_RSA", "EC_RSA");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("ECDH_ECDSA", "EC_EC");
        BoringSSLCertificateCallback.DEFAULT_SERVER_KEY_TYPES.put("DH_RSA", "DH_RSA");
        DEFAULT_CLIENT_KEY_TYPES = Collections.unmodifiableSet((Set<? extends String>)new LinkedHashSet<String>(Arrays.asList("RSA", "DH_RSA", "EC", "EC_RSA", "EC_EC")));
        NO_KEY_MATERIAL_CLIENT_SIDE = new long[] { 0L, 0L };
    }
}
