package org.limewire.security.certificate;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class CertificateVerifierImpl implements CertificateVerifier {
    private static final Log LOG = LogFactory.getLog(CertificateVerifierImpl.class);

    private KeyStoreProvider keyStoreProvider;

    private RootCAProvider rootCAProvider;

    @Inject
    public CertificateVerifierImpl(KeyStoreProvider keyStoreProvider, RootCAProvider rootCAProvider) {
        this.keyStoreProvider = keyStoreProvider;
        this.rootCAProvider = rootCAProvider;
    }

    public boolean isValid(Certificate certificate) {
        KeyStore ks;
        try {
            ks = keyStoreProvider.getKeyStore();
        } catch (IOException ex) {
            LOG.error("IOException getting keyStore", ex);
            return false;
        }
        try {
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");

            List<Certificate> certList = new ArrayList<Certificate>();
            certList.add(certificate);
            populateCertList(ks, certificate, certList);
            CertPath certPath = certFactory.generateCertPath(certList);
            // Set the Trust anchor
            TrustAnchor anchor = new TrustAnchor(rootCAProvider.getCertificate(), null);
            // Set the PKIX parameters
            PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
            params.setRevocationEnabled(false);
            // Validate and obtain results
            try {
                certPathValidator.validate(certPath, params);
            } catch (CertPathValidatorException ex) {
                LOG.error("Validation failure, cert[" + ex.getIndex() + "] :", ex);
                return false;
            }
        } catch (GeneralSecurityException ex) {
            LOG.error("CertificateException caught.", ex);
            return false;
        }
        return true;
    }

    /**
     * Looks at the certificate, looks up the issuer, and tries to find it in
     * the key store. When found, adds it to the end of the list. Then calls
     * itself recursively until it reaches the top of the signing chain.
     */
    private void populateCertList(KeyStore keyStore, Certificate certificate,
            List<Certificate> certs) {
        // We expect these to be x.509, otherwise we just return
        if (certificate instanceof X509Certificate) {
            X509Certificate x509 = (X509Certificate) certificate;
            String dn = x509.getIssuerDN().getName();
            // Find the 'CN='
            String cn = "";
            for (String token : dn.split(",")) {
                if (token.trim().startsWith("CN=")) {
                    cn = token.trim().substring(3);
                    break;
                }
            }
            // Break if this cert is self-signed, indicates it's the top of the
            // chain
            if (x509.getIssuerDN().getName().equals(x509.getSubjectDN().getName()))
                return;
            // System.out.println(cn);
            try {
                Certificate issuerCert = keyStore.getCertificate(cn);
                if (issuerCert == null) {
                    LOG.error("Could not find certificate alias '" + cn + "'");
                    return;
                }
                certs.add(issuerCert);
                populateCertList(keyStore, issuerCert, certs);
            } catch (KeyStoreException ex) {
                LOG.error("KeyStoreException caught while walking chain.", ex);
                return;
            }
        }
    }
}
