/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.certificates.factory.builders;

import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.bean.extensions.AbstractCertificateExtension;
import com.scytl.cryptolib.certificates.constants.X509CertificateConstants;
import com.scytl.cryptolib.commons.configuration.Provider;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomInteger;

/**
 * Class which implements {@link X509CertificateBuilder} that uses BouncyCastle
 * as the cryptographic service provider.
 */
public class BouncyCastleX509CertificateBuilder implements
        X509CertificateBuilder {

    private static final String BUILD_CERTIFICATE_ERROR_MESSAGE =
        "Could not build certificate.";

    private static final String CREATE_CERTIFICATE_SIGNER_ERROR_MESSAGE =
        "Could not create signer for certificate issuer private key.";

    private final String _signatureAlgorithm;

    private final X509DistinguishedName _subjectDn;

    private final X509DistinguishedName _issuerDn;

    private X500NameBuilder _issuerX509NameBuilder;

    private X500NameBuilder _subjectX509NameBuilder;

    private final X509v3CertificateBuilder _certBuilder;

    private final Provider _provider;

    /**
     * Creates an instance of certificate builder with the provided components.
     *
     * @param signatureAlgorithm
     *            algorithm used to sign certificate with issuer private key.
     * @param provider
     *            name of cryptographic service {@link Provider} of
     *            certificates.
     * @param subjectPublicKey
     *            public key of certificate subject.
     * @param subjectDn
     *            subject distinguished name of certificate (NOTE: for
     *            self-signed certificates this is same as issuer distinguished
     *            name).
     * @param issuerDn
     *            issuer distinguished name of certificate.
     * @param validityDates
     *            dates of validity of certificate.
     * @param cryptoIntegerRandom
     *            encapsulates a SecureRandom to create the serialNumber.
     * @throws GeneralCryptoLibException
     *             if arguments are invalid.
     */
    public BouncyCastleX509CertificateBuilder(
            final String signatureAlgorithm, final Provider provider,
            final PublicKey subjectPublicKey,
            final X509DistinguishedName subjectDn,
            final X509DistinguishedName issuerDn,
            final ValidityDates validityDates,
            final CryptoRandomInteger cryptoIntegerRandom)
            throws GeneralCryptoLibException {

        _signatureAlgorithm = signatureAlgorithm;
        _provider = provider;
        _subjectDn = subjectDn;
        _issuerDn = issuerDn;

        // Create subject public key info instance with subject public key.
        SubjectPublicKeyInfo subjPubKeyInfo =
            SubjectPublicKeyInfo
                .getInstance(subjectPublicKey.getEncoded());

        // Generate the X509 name builders.
        generateX509NameBuilders();

        // Generate serial number.
        BigInteger serialNumber;
        try {
            serialNumber =
                cryptoIntegerRandom
                    .nextRandomByBits(X509CertificateConstants.CERTIFICATE_SERIAL_NUMBER_MAX_BIT_LENGTH);
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException(e);
        }

        // Create certificate builder instance.
        _certBuilder =
            new X509v3CertificateBuilder(_issuerX509NameBuilder.build(),
                serialNumber, validityDates.getNotBefore(),
                validityDates.getNotAfter(),
                _subjectX509NameBuilder.build(), subjPubKeyInfo);
    }

    @Override
    public X509Certificate build(final PrivateKey issuerPrivateKey) {

        // Create certificate signer instance with issuer private key.
        ContentSigner signer = null;

        JcaContentSignerBuilder jcaContentSignerBuilder =
            new JcaContentSignerBuilder(_signatureAlgorithm);

        try {

            if (Provider.DEFAULT != _provider) {
                jcaContentSignerBuilder.setProvider(_provider
                    .getProviderName());
            }

            signer = jcaContentSignerBuilder.build(issuerPrivateKey);

        } catch (OperatorCreationException e) {
            throw new CryptoLibException(
                CREATE_CERTIFICATE_SIGNER_ERROR_MESSAGE, e);
        }

        // Generate certificate.
        try {
            return new JcaX509CertificateConverter().setProvider(
                new BouncyCastleProvider()).getCertificate(
                _certBuilder.build(signer));
        } catch (CertificateException e) {
            throw new CryptoLibException(BUILD_CERTIFICATE_ERROR_MESSAGE,
                e);
        }

    }

    @Override
    public void addExtension(final AbstractCertificateExtension extension) {
        ExtensionBuilder extensionBuilder =
            new ExtensionBuilder(_certBuilder);

        extensionBuilder.addExtension(extension);
    }

    /**
     * Generates the X509 name builders.
     */
    private void generateX509NameBuilders() {
        generateIssuerX500Builder();
        generateSubjectX500Builder();
    }

    private void generateSubjectX500Builder() {
        _subjectX509NameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
        _subjectX509NameBuilder.addRDN(BCStyle.CN,
            _subjectDn.getCommonName());
        _subjectX509NameBuilder.addRDN(BCStyle.OU,
            _subjectDn.getOrganizationalUnit());
        _subjectX509NameBuilder.addRDN(BCStyle.O,
            _subjectDn.getOrganization());
        _subjectX509NameBuilder
            .addRDN(BCStyle.L, _subjectDn.getLocality());
        _subjectX509NameBuilder.addRDN(BCStyle.C, _subjectDn.getCountry());
    }

    private void generateIssuerX500Builder() {
        _issuerX509NameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
        _issuerX509NameBuilder.addRDN(BCStyle.CN,
            _issuerDn.getCommonName());
        _issuerX509NameBuilder.addRDN(BCStyle.OU,
            _issuerDn.getOrganizationalUnit());
        _issuerX509NameBuilder.addRDN(BCStyle.O,
            _issuerDn.getOrganization());
        _issuerX509NameBuilder.addRDN(BCStyle.L, _issuerDn.getLocality());
        _issuerX509NameBuilder.addRDN(BCStyle.C, _issuerDn.getCountry());
    }
}
