package org.bouncycastle.cms.test;

import java.security.Security;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertificateException;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import junit.framework.Assert;
import junit.framework.TestCase;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;

public class GOSTR3410_2012_256CmsSignVerifyDetached
    extends TestCase
{
    /**
     * Test GOST 2012-256 signature with id-tc26-gost-3410-2012-256-paramSetB key parameters generated by
     * <a href="https://www.rutoken.ru/support/download/pkcs/">rtpkcs11ecp</a> library.
     */
    private static final String SIGNATURE = "MIIDDAYJKoZIhvcNAQcCoIIC/TCCAvkCAQExDDAKBggqhQMHAQECAjALBgkqhkiG9w0BBwGgggGTMIIB" +
        "jzCCATygAwIBAgIVANpDv+oXKyFqD3f8s/iV0sgLZxMuMAoGCCqFAwcBAQMCMCsxCzAJBgNVBAYTAlJV" +
        "MQswCQYDVQQDDAJDQTEPMA0GA1UECAwGTW9zY293MB4XDTI0MDIwNzEyNTIwMFoXDTI1MDIwNzEyNTIw" +
        "MFowZTEQMA4GA1UEAwwHSXZhbm9mZjELMAkGA1UEBhMCUlUxFDASBgNVBAUTCzEyMzEyMzEyMzEyMR0w" +
        "GwYJKoZIhvcNAQkBFg5pdmFub3ZAbWFpbC5ydTEPMA0GA1UECAwGTW9zY293MF4wFwYIKoUDBwEBAQEw" +
        "CwYJKoUDBwECAQECA0MABEC8jIpHpWxBuYhMdgbly1RJR0ECHcL1SMklZX3u5TNdOjs66n8U5y9nt5vR" +
        "KGdvecbPo5cYIlEojrprtlDuALjsMAoGCCqFAwcBAQMCA0EAAJvuewDPWkDfDFEC0L/o+6BipHCcz0Qg" +
        "Mr4TU7XRXKcVkxVD8SjIc4SaWL/f/htpNIdvP91EeYDlFoOwNqDhHDGCAUAwggE8AgEBMEQwKzELMAkG" +
        "A1UEBhMCUlUxCzAJBgNVBAMMAkNBMQ8wDQYDVQQIDAZNb3Njb3cCFQDaQ7/qFyshag93/LP4ldLIC2cT" +
        "LjAKBggqhQMHAQECAqCBlDAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0y" +
        "NDAyMDcxMjUyMDBaMCkGCSqGSIb3DQEJNDEcMBowCgYIKoUDBwEBAgKhDAYIKoUDBwEBAQEFADAvBgkq" +
        "hkiG9w0BCQQxIgQg7A98XV1Tkli/l3PXrT/6cGLM/m8odS26UHEGHEPBYgMwDAYIKoUDBwEBAQEFAARA" +
        "CcueaEjcNVIccepoDiU9aCbHPPF7A3zGw90Zl11T9ITBH4jNOMi4IGVn90ANgiinwKIuiu9mWMk9Y/mc" +
        "jcCkQw==";

    /**
     * Expires in 2045.
     */
    private static final String CA_CERTIFICATE = "MIIBTzCB+wIJAMGuFHcbok4sMAwGCCqFAwcBAQMCBQAwKzELMAkGA1UEBhMCUlUx"
        + "CzAJBgNVBAMMAkNBMQ8wDQYDVQQIDAZNb3Njb3cwHhcNMTgwNjA2MTAyNzA4WhcN"
        + "NDUxMDIyMTAyNzA4WjArMQswCQYDVQQGEwJSVTELMAkGA1UEAwwCQ0ExDzANBgNV"
        + "BAgMBk1vc2NvdzBmMB8GCCqFAwcBAQEBMBMGByqFAwICIwEGCCqFAwcBAQICA0MA"
        + "BECM6iQnPgDs6K2jmUVLHf4V63xwO2j4vO2X2kNQVELu2bROK+wBaNWkTX5TW+IO"
        + "9gLZFioYMSEK2LxsIO3Zf+JeMAwGCCqFAwcBAQMCBQADQQATx6Ksy1KUuvfa2q8X"
        + "kfo3pDN1x1aGo4AmQolzEpbXvzbyMy3vk+VOqegdd8KP4E3x43zaTmHmnu/G1v20"
        + "VzwO";

    private static final byte[] SIGNED_DATA = {0x01, 0x02, 0x03};

    public void setUp()
        throws Exception
    {
        Security.addProvider(new BouncyCastleProvider());
    }

    public void testGost3410_2012_256()
        throws Exception
    {
        byte[] detachedCms = Base64.decode(SIGNATURE);
        byte[] rootCertificate = Base64.decode(CA_CERTIFICATE);
        List<X509CertificateHolder> trustedCertificates = new ArrayList<X509CertificateHolder>();
        trustedCertificates.add(new X509CertificateHolder(rootCertificate));

        boolean isSignatureValid = verifyDetached(SIGNED_DATA, detachedCms, trustedCertificates);

        Assert.assertTrue(isSignatureValid);
    }

    private static boolean verifyDetached(byte[] data, byte[] detachedCms,
                                          List<X509CertificateHolder> trustedCertificates)
        throws CMSException
    {
        CMSSignedData cmsSignedData = new CMSSignedData(new CMSProcessableByteArray(data), detachedCms);

        boolean result = false;
        try
        {
            HashSet<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
            for (X509CertificateHolder trustedCert : trustedCertificates)
            {
                TrustAnchor trustAnchor = new TrustAnchor(getX509Certificate(trustedCert), null);
                trustAnchors.add(trustAnchor);
            }

            CertPathBuilder certPathBuilder =
                CertPathBuilder.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME);
            Store<X509CertificateHolder> cmsCertStore = cmsSignedData.getCertificates();
            SignerInformationStore signers = cmsSignedData.getSignerInfos();

            for (SignerInformation signer : signers.getSigners())
            {
                Collection<X509CertificateHolder> signerCertCollection = cmsCertStore.getMatches(signer.getSID());

                for (X509CertificateHolder signerCert : signerCertCollection)
                {
                    // Validate signer's signature
                    SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder()
                        .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                        .build(signerCert);
                    if (!signer.verify(verifier))
                    {
                        return false;
                    }

                    // Validate signer's certificate chain
                    X509CertSelector constraints = new X509CertSelector();
                    constraints.setCertificate(getX509Certificate(signerCert));
                    PKIXBuilderParameters params = new PKIXBuilderParameters(trustAnchors, constraints);

                    JcaCertStoreBuilder certStoreBuilder = new JcaCertStoreBuilder();
                    certStoreBuilder.addCertificate(signerCert);

                    params.addCertStore(certStoreBuilder.build());
                    params.setRevocationEnabled(false);
                    certPathBuilder.build(params);
                    result = true;
                }
            }
        }
        catch (Exception e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
            result = false;
        }

        return result;
    }

    private static X509Certificate getX509Certificate(X509CertificateHolder certificateHolder)
        throws CertificateException
    {
        return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
            .getCertificate(certificateHolder);
    }
}
