/*
 * 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.products.ov.utils;

import static java.util.Arrays.fill;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;

import org.apache.commons.codec.binary.Base64;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;

/**
 * Allows a password (string) to be encrypted and decrypted.
 */
public final class PasswordEncrypter {

    private final AsymmetricServiceAPI _asymmetricService;

    public PasswordEncrypter(final AsymmetricServiceAPI asymmetricService) {

        _asymmetricService = asymmetricService;
    }

    /**
     * Encrypts the received password using the private key.
     * <P>
     * If the received private key is {@code null} or an empty string, then this method will return the original password.
     *
     * @param plaintextPassword
     *            the password to be encrypted.
     * @param publicKeyToBeUsedToEncryptThePassword
     *            the public key to be used to encrypt the password (in PEM format).
     * @return the encrypted password.
     * @throws GeneralCryptoLibException
     */
    public String encryptPasswordIfEncryptionKeyAvailable(final char[] plaintextPassword,
            final String publicKeyToBeUsedToEncryptThePassword) throws GeneralCryptoLibException {

        if (publicKeyToBeUsedToEncryptThePassword == null || publicKeyToBeUsedToEncryptThePassword.isEmpty()) {

            return new String(plaintextPassword);

        } else {
            
            PublicKey publicKey = PemUtils.publicKeyFromPem(publicKeyToBeUsedToEncryptThePassword);
            CharBuffer charBuffer = CharBuffer.wrap(plaintextPassword);
            ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);
            String encryptedPassword;
            try {
                encryptedPassword =  Base64.encodeBase64String(_asymmetricService.encrypt(publicKey, bytes));
            } finally {
                fill(byteBuffer.array(), (byte)0);
                fill(bytes, (byte)0);
            }
            return encryptedPassword;
        }
    }

    /**
     * Decrypts the received encrypted password using the received private key (represented as a string in PEM format).
     *
     * @param encryptedPassword
     *            the encrypted password to be decrypted.
     * @param privateKeyAsPem
     *            private key in PEM format.
     * @return the decrypted password.
     * @throws GeneralCryptoLibException
     * @throws IOException
     */
    public char[] decryptPassword(final String encryptedPassword, final String privateKeyAsPem)
            throws GeneralCryptoLibException, IOException {

        return decryptPassword(encryptedPassword, PemUtils.privateKeyFromPem(privateKeyAsPem));
    }

    /**
     * Decrypt the received encrypted password using the received private key.
     *
     * @param encryptedPassword
     *            the encrypted password to be decrypted.
     * @param privateKey
     *            a private key.
     * @return the decrypted password.
     * @throws GeneralCryptoLibException
     * @throws IOException
     */
    public char[] decryptPassword(final String encryptedPassword, final PrivateKey privateKey)
            throws GeneralCryptoLibException, IOException {

        byte[] ciphertextAsBytes = Base64.decodeBase64(encryptedPassword);

        ByteBuffer byteBuffer = ByteBuffer.wrap(_asymmetricService.decrypt(privateKey, ciphertextAsBytes));
        char[] plaintextPassword;
        try {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
            try {
                plaintextPassword = new char[charBuffer.remaining()];
                charBuffer.get(plaintextPassword);
            } finally {
                fill(charBuffer.array(), ' ');
            }
        } finally {
            fill(byteBuffer.array(), (byte)0);
        }
        return plaintextPassword;
    }
}
