/*
 * 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.asymmetric.keypair.openssl;

import static java.text.MessageFormat.format;

import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyPairGeneratorSpi;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;

/**
 * Implementation of {@link KeyPairGenerator} which uses OpenSSL.
 * <p>
 * This implementation has the following special features:
 * <ul>
 * <li>Private keys are generated using external OpenSSL command line interface.
 * The public key is extracted from the private key using an appropriate
 * {@link KeyFactory} instance. All the errors which can occur are reported as
 * {@link CryptoLibException} since the {@link KeyPairGenerator} API does not
 * support normal checked exceptions.</li>
 * <li>Only {@code RSA} algorithm is currently supported.</li>
 * <li>Method {@link #getProvider()} returns {@code null}.</li>
 * </ul>
 */
public final class OpenSSLKeyPairGenerator extends KeyPairGenerator {
    private static final OpenSSLFactory FACTORY =
        new OpenSSLFactoryImpl(ProcessFactoryImpl.getInstance());

    private final KeyPairGeneratorSpi spi;

    /**
     * Constructor. For internal use only.
     *
     * @param algorithm
     * @param spi
     */
    OpenSSLKeyPairGenerator(final String algorithm,
            final KeyPairGeneratorSpi spi) {
        super(algorithm);
        this.spi = spi;
    }

    /**
     * Creates a new instance for the specified algorithm.
     *
     * @param algorithm
     *            the algorithm
     * @return the instance
     * @throws NoSuchAlgorithmException
     *             algorithm is not supported or OpenSSL is not available.
     */
    public static OpenSSLKeyPairGenerator getInstance(
            final String algorithm) throws NoSuchAlgorithmException {
        return getInstance(FACTORY, algorithm);
    }

    /**
     * Creates a new instance for the specified {@link OpenSSLFactory} and
     * algorithm. For internal use only.
     *
     * @param factory
     *            the factory
     * @param algorithm
     *            the algorithm
     * @return the instance
     * @throws NoSuchAlgorithmException
     *             algorithm is not supported or OpenSSL is not available.
     */
    static OpenSSLKeyPairGenerator getInstance(
            final OpenSSLFactory factory, final String algorithm)
            throws NoSuchAlgorithmException {
        KeyPairGeneratorSpi spi;
        OpenSSL openSSL;
        try {
            openSSL = factory.getOpenSSL();
        } catch (OpenSSLException e) {
            throw new NoSuchAlgorithmException("OpenSSL is not available.",
                e);
        }
        if ("RSA".equals(algorithm)) {
            spi = RSAKeyPairGeneratorSpi.newInstance(openSSL);
        } else {
            throw new NoSuchAlgorithmException(
                format("Algorithm ''{0}'' is not supported.", algorithm));
        }
        return new OpenSSLKeyPairGenerator(algorithm, spi);
    }

    @Override
    public KeyPair generateKeyPair() {
        return spi.generateKeyPair();
    }

    @Override
    public void initialize(final AlgorithmParameterSpec params,
            final SecureRandom random)
            throws InvalidAlgorithmParameterException {
        spi.initialize(params, random);
    }

    @Override
    public void initialize(final int keysize, final SecureRandom random) {
        spi.initialize(keysize, random);
    }
}
