/*
 * 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.channel.cc.commons.keymanagement;

import static java.nio.file.Files.createTempFile;
import static java.nio.file.Files.delete;
import static java.nio.file.Files.deleteIfExists;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.stores.StoresServiceAPI;
import com.scytl.cryptolib.api.stores.bean.KeyStoreType;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.cryptoapi.EncryptionParameters;
import com.scytl.cryptolib.mathematical.groups.impl.Exponent;
import com.scytl.cryptolib.mathematical.groups.impl.ZpGroupElement;
import com.scytl.cryptolib.mathematical.groups.impl.ZpSubgroup;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.channel.cc.commons.slogger.ServiceTransactionInfo;
import com.scytl.products.ov.channel.cc.commons.slogger.ServiceTransactionInfoProvider;
import com.scytl.products.ov.channel.cc.commons.util.FingerprintGenerator;

/**
 * Tests of {@link KeyManagerImpl}.
 */
public class KeyManagerImplTest {
    private static final PasswordProtection PASSWORD_PROTECTION = new PasswordProtection("password".toCharArray());

    private static final String ELECTION_EVENT_ID = "electionEventId";

    private static final String ELECTORAL_AUTHORITY_ID = "electoralAuthorityId";

    private static final String VERIFICATION_CARD_SET_ID = "verificationCardSetId";

    private static final String REQUEST_ID = "requestId";

    private static final String ENCRYPTION_PARAMETERS_JSON =
        "{\"encryptionParams\":{\"p\":\"AMoJqsCm6UG4B+FGO1Vh6qJcCJcMAVyZFIbZnTIZyD3XAQSTLdxVZxrXm5ZNQMajKAmjUfb3/BV84OgDZaNMXQziV2Uw6hQ70jUcswZ6ZfHAQBy+kwSozDM1LA6ndqp0JIGmPD7LqFIhv9ly7427ksgX1LjNR1tUInVaCS26dfP5wkZyCxwa9/3TapFpFugek+WSJqozaDSd89WAuqXrbBu0FMJWvBVvJapsjfJLEOXTHzdeWef9X5M4FY4Uk+/0WaDVZXy5JhcdoxuNU6Oy0/9buDrPfzlg5a26gpV5ei7XHfykYxdBIPUQoAmraSyUp/byhSDcQu6GJxkfknfAORU=\",\"q\":\"AJT/piLUjl6zN9D/Pc4GYsAG0KAS57H7xJ/mqb+ycbIl\",\"g\":\"ALNkOUZfTDbTMsCYUAKBAAzPdl7422rYGhca6GUAwEPQMlexuIZfIoft+ww54NDqGrUb4TFyw8Yy+y+9Ryb7AXNockro4XYfUjN6eiwOrPxfWG3r9BxqReWZRp74uQ2Ec1toVBrpHaXQNETOJpPBThMKKqrgIuPeAdMRM9U9viku8X0IDmHcqoNaI2o2fvFLFaxlzt5h4kzeJ0A5Twn8wwp5Uln0KcTBJf3H904Icq1u9WTvCTa1jQkoSzkNDQ1VABNFWBa/hW/RGAG7YqTurYHQB+9sMRpOiN10X+6ihdF5VgGpCgXfquKCSqa7I44vq2Vr9+Bm34Gjy+uDOJWrilg=\"}}";

    private static final String ALIAS = "alias";

    private AsymmetricServiceAPI asymmetricService;

    private PrivateKey caPrivateKey;

    private X509Certificate[] caCertificateChain;

    private NodeKeys nodeKeys;

    private ElectionSigningKeys electionSigningKeys;

    private MixingKeys mixingKeys;

    private ChoiceCodeKeys choiceCodeKeys;

    private KeyStoreSpi storeSpi;

    private StoresServiceAPI storesService;

    private Generator generator;

    private Database database;
    
    private Cache cache;

    private KeyManagerImpl manager;

    private ServiceTransactionInfoProvider serviceTransactionInfoProvider;

    @Before
    public void setUp()
            throws GeneralCryptoLibException, NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException,
            KeyManagementException, CertificateException, IOException, FingerprintGeneratorException {
        caPrivateKey = mock(PrivateKey.class);
        when(caPrivateKey.getAlgorithm()).thenReturn("RSA");
        PublicKey caPublicKey = mock(PublicKey.class);
        when(caPublicKey.getAlgorithm()).thenReturn("RSA");
        X509Certificate caCertificate = mock(X509Certificate.class);
        when(caCertificate.getPublicKey()).thenReturn(caPublicKey);
        caCertificateChain = new X509Certificate[] {caCertificate };

        PrivateKey encryptionPrivateKey = mock(PrivateKey.class);
        PublicKey encryptionPublicKey = mock(PublicKey.class);
        X509Certificate encryptionCertificate = mock(X509Certificate.class);
        when(encryptionCertificate.getPublicKey()).thenReturn(encryptionPublicKey);
        X509Certificate[] encryptionCertificateChain = {encryptionCertificate, caCertificate };

        PrivateKey logSigningPrivateKey = mock(PrivateKey.class);
        PublicKey logSigningPublicKey = mock(PublicKey.class);
        X509Certificate logSigningCertificate = mock(X509Certificate.class);
        when(logSigningCertificate.getPublicKey()).thenReturn(logSigningPublicKey);
        X509Certificate[] logSigningCertificateChain = {logSigningCertificate, caCertificate };

        PrivateKey logEncryptionPrivateKey = mock(PrivateKey.class);
        PublicKey logEncryptionPublicKey = mock(PublicKey.class);
        X509Certificate logEncryptionCertificate = mock(X509Certificate.class);
        when(logEncryptionCertificate.getPublicKey()).thenReturn(logEncryptionPublicKey);
        X509Certificate[] logEncryptionCertificateChain = {logEncryptionCertificate, caCertificate };

        nodeKeys = new NodeKeys.Builder().setCAKeys(caPrivateKey, caCertificateChain)
            .setEncryptionKeys(encryptionPrivateKey, encryptionCertificateChain)
            .setLogSigningKeys(logSigningPrivateKey, logSigningCertificateChain)
            .setLogEncryptionKeys(logEncryptionPrivateKey, logEncryptionCertificateChain).build();

        PrivateKey electionSigingPrivateKey = mock(PrivateKey.class);
        PublicKey electionSigingPublicKey = mock(PublicKey.class);
        X509Certificate electionSigningCertificate = mock(X509Certificate.class);
        when(electionSigningCertificate.getPublicKey()).thenReturn(electionSigingPublicKey);
        X500Principal subjectPrincipal = new X500Principal("");
        when(electionSigningCertificate.getSubjectX500Principal()).thenReturn(subjectPrincipal);
        X500Principal issuerPrincipal = new X500Principal("");
        when(electionSigningCertificate.getIssuerX500Principal()).thenReturn(issuerPrincipal);
        when(electionSigningCertificate.getIssuerDN()).thenReturn(issuerPrincipal);
        when(electionSigningCertificate.getSerialNumber()).thenReturn(BigInteger.ONE);
        when(electionSigningCertificate.getEncoded()).thenReturn("encoded".getBytes());

        X509Certificate[] electionSigningCertificateChain = {electionSigningCertificate, caCertificate };
        electionSigningKeys = new ElectionSigningKeys(electionSigingPrivateKey, electionSigningCertificateChain);

        ZpSubgroup group = new ZpSubgroup(BigInteger.valueOf(2), BigInteger.valueOf(7), BigInteger.valueOf(3));

        List<Exponent> mixingExponents = singletonList(new Exponent(group.getQ(), BigInteger.valueOf(2)));
        ElGamalPrivateKey mixingPrivateKey = new ElGamalPrivateKey(mixingExponents, group);
        List<ZpGroupElement> mixingElements = singletonList(new ZpGroupElement(BigInteger.valueOf(2), group));
        ElGamalPublicKey mixingPublicKey = new ElGamalPublicKey(mixingElements, group);
        byte[] mixingPublicKeySignature = {1, 2, 3 };
        mixingKeys = new MixingKeys(mixingPrivateKey, mixingPublicKey, mixingPublicKeySignature);

        List<Exponent> generationExponents = singletonList(new Exponent(group.getQ(), BigInteger.valueOf(3)));
        ElGamalPrivateKey generationPrivateKey = new ElGamalPrivateKey(generationExponents, group);
        List<ZpGroupElement> generationElements = singletonList(new ZpGroupElement(BigInteger.valueOf(3), group));
        ElGamalPublicKey generationPublicKey = new ElGamalPublicKey(generationElements, group);
        byte[] generationPublicKeySignature = {4, 5, 6 };
        List<Exponent> decryptionExponents = singletonList(new Exponent(group.getQ(), BigInteger.valueOf(4)));
        ElGamalPrivateKey decryptionPrivateKey = new ElGamalPrivateKey(decryptionExponents, group);
        List<ZpGroupElement> decryptionElements = singletonList(new ZpGroupElement(BigInteger.valueOf(4), group));
        ElGamalPublicKey decryptionPublicKey = new ElGamalPublicKey(decryptionElements, group);
        byte[] decryptionPublicKeySignature = {7, 8, 9 };
        choiceCodeKeys = new ChoiceCodeKeys.Builder()
            .setGenerationKeys(generationPrivateKey, generationPublicKey, generationPublicKeySignature)
            .setDecryptionKeys(decryptionPrivateKey, decryptionPublicKey, decryptionPublicKeySignature).build();

        asymmetricService = mock(AsymmetricServiceAPI.class);
        when(asymmetricService.verifySignature(any(byte[].class), eq(caPublicKey), any(byte[].class))).thenReturn(true);

        ServiceTransactionInfo serviceTransactionInfo = new ServiceTransactionInfo(REQUEST_ID);

        serviceTransactionInfoProvider = mock(ServiceTransactionInfoProvider.class);
        when(serviceTransactionInfoProvider.getServiceTransactionInfo()).thenReturn(serviceTransactionInfo);

        storeSpi = mock(KeyStoreSpi.class);
        doReturn(new PrivateKeyEntry(caPrivateKey, caCertificateChain)).when(storeSpi).engineGetEntry(ALIAS,
            PASSWORD_PROTECTION);
        doReturn(true).when(storeSpi).engineEntryInstanceOf(ALIAS, PrivateKeyEntry.class);

        KeyStoreDouble store = new KeyStoreDouble(storeSpi);
        store.load(null, null);

        storesService = mock(StoresServiceAPI.class);
        when(storesService.loadKeyStore(eq(KeyStoreType.PKCS12), any(InputStream.class),
            eq(PASSWORD_PROTECTION.getPassword()))).thenReturn(store);

        generator = mock(Generator.class);
        when(generator.generateNodeKeys(caPrivateKey, caCertificateChain)).thenReturn(nodeKeys);
        when(generator.generateElectionSigningKeys(eq(ELECTION_EVENT_ID), any(Date.class), any(Date.class),
            eq(nodeKeys))).thenReturn(electionSigningKeys);
        when(generator.generateMixingKeys(any(MixingKeysSpec.class), eq(electionSigningKeys))).thenReturn(mixingKeys);
        when(generator.generateChoiceCodeKeys(any(ChoiceCodeKeysSpec.class), eq(electionSigningKeys)))
            .thenReturn(choiceCodeKeys);

        database = mock(Database.class);
        when(database.loadNodeKeys(PASSWORD_PROTECTION)).thenReturn(nodeKeys);
        when(database.loadElectionSigningKeys(ELECTION_EVENT_ID)).thenReturn(electionSigningKeys);
        when(database.loadMixingKeys(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID)).thenReturn(mixingKeys);
        when(database.loadChoiceCodeKeys(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID)).thenReturn(choiceCodeKeys);

        cache = new CacheImpl(Duration.ofHours(1));
        
        manager = new KeyManagerImpl(asymmetricService, storesService, generator, database, cache,
            Mockito.mock(SecureLoggingWriter.class), Mockito.mock(FingerprintGenerator.class),
            serviceTransactionInfoProvider, "nodeId");
    }
    
    @After
    public void tearDown() {
        cache.shutdown();
    }

    @Test
    public void testActivateNodeKeys() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertTrue(manager.hasNodeKeysActivated());
        assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
        verify(database).loadNodeKeys(PASSWORD_PROTECTION);
        verify(database).setEncryptionKeys(nodeKeys.encryptionPrivateKey(), nodeKeys.encryptionPublicKey());
    }

    @Test(expected = KeyManagementException.class)
    public void testActivateNodeKeysError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        when(database.loadNodeKeys(PASSWORD_PROTECTION)).thenThrow(new KeyManagementException("test"));
        try {
            manager.activateNodeKeys(PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidPasswordException.class)
    public void testActivateNodeKeysInvalidPassword()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        when(database.loadNodeKeys(PASSWORD_PROTECTION)).thenThrow(new InvalidPasswordException("test"));
        try {
            manager.activateNodeKeys(PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyNotFoundException.class)
    public void testActivateNodeKeysNotFound()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        when(database.loadNodeKeys(PASSWORD_PROTECTION)).thenThrow(new KeyNotFoundException("test"));
        try {
            manager.activateNodeKeys(PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test
    public void testChoiceCodeKeysCaching() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.decryptionPrivateKey(),
            manager.getChoiceCodeDecryptionPrivateKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));

        assertEquals(choiceCodeKeys, cache.getChoiceCodeKeys(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
        
        assertEquals(choiceCodeKeys.decryptionPrivateKey(),
            manager.getChoiceCodeDecryptionPrivateKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
        
        verify(database, times(1)).loadChoiceCodeKeys(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID);
    }

    @Test
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtection()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        }
        assertTrue(manager.hasNodeKeysActivated());
        assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
        verify(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        verify(database).setEncryptionKeys(nodeKeys.encryptionPrivateKey(), nodeKeys.encryptionPublicKey());
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionAlreadyExist()
            throws KeyAlreadyExistsException, KeyManagementException, IOException {
        manager.startup();
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionDatabaseError()
            throws KeyAlreadyExistsException, KeyManagementException, IOException {
        manager.startup();
        doThrow(new KeyManagementException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionGeneratorError()
            throws KeyManagementException, IOException {
        manager.startup();
        when(generator.generateNodeKeys(caPrivateKey, caCertificateChain))
            .thenThrow(new KeyManagementException("test"));
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidKeyStoreException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionInvalidKeyStore()
            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException,
            IOException {
        manager.startup();
        doReturn(false).when(storeSpi).engineEntryInstanceOf(ALIAS, PrivateKeyEntry.class);
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidNodeCAException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionInvalidNodeCA()
            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException,
            IOException, GeneralCryptoLibException {
        manager.startup();
        when(asymmetricService.verifySignature(any(byte[].class), any(PublicKey.class), any(byte[].class)))
            .thenReturn(false);
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidPasswordException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionInvalidPassword()
            throws InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, IOException, GeneralCryptoLibException {
        manager.startup();
        when(storesService.loadKeyStore(any(), any(), any()))
            .thenThrow(new GeneralCryptoLibException(new IOException(new UnrecoverableKeyException("test"))));
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = IOException.class)
    public void testCreateAndActivateNodeKeysInputStreamStringPasswordProtectionIOException()
            throws GeneralCryptoLibException, InvalidKeyStoreException, InvalidPasswordException,
            InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException, IOException {
        manager.startup();
        when(storesService.loadKeyStore(any(), any(), any()))
            .thenThrow(new GeneralCryptoLibException(new IOException("test")));
        try (InputStream stream = new ByteArrayInputStream(new byte[0]);) {
            manager.createAndActivateNodeKeys(stream, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtection()
            throws NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        assertTrue(manager.hasNodeKeysActivated());
        assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
        verify(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        verify(database).setEncryptionKeys(nodeKeys.encryptionPrivateKey(), nodeKeys.encryptionPublicKey());
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionAlreadyExist()
            throws NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionDataBaseError()
            throws KeyAlreadyExistsException, KeyManagementException, NoSuchAlgorithmException, CertificateException,
            IOException {
        manager.startup();
        doThrow(new KeyManagementException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionGeneratorError()
            throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException {
        manager.startup();
        when(generator.generateNodeKeys(caPrivateKey, caCertificateChain))
            .thenThrow(new KeyManagementException("test"));
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidKeyStoreException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionInvalidKeyStore()
            throws NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyStoreException,
            InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        doReturn(false).when(storeSpi).engineEntryInstanceOf(ALIAS, PrivateKeyEntry.class);
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidNodeCAException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionInvalidNodeCA()
            throws GeneralCryptoLibException, NoSuchAlgorithmException, CertificateException, IOException,
            InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException, KeyAlreadyExistsException,
            KeyManagementException {
        manager.startup();
        when(asymmetricService.verifySignature(any(byte[].class), any(PublicKey.class), any(byte[].class)))
            .thenReturn(false);
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidPasswordException.class)
    public void testCreateAndActivateNodeKeysKeyStoreStringPasswordProtectionInvalidPassword()
            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException, CertificateException,
            IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        doThrow(new UnrecoverableEntryException("test")).when(storeSpi).engineGetEntry(ALIAS, PASSWORD_PROTECTION);
        KeyStore store = new KeyStoreDouble(storeSpi);
        store.load(null, null);
        try {
            manager.createAndActivateNodeKeys(store, ALIAS, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test
    public void testCreateAndActivateNodeKeysPathStringPasswordProtection()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            assertTrue(manager.hasNodeKeysActivated());
            assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
            verify(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
            verify(database).setEncryptionKeys(nodeKeys.encryptionPrivateKey(), nodeKeys.encryptionPublicKey());
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionAlreadyExist()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            doThrow(new KeyAlreadyExistsException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionDatabaseError()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            doThrow(new KeyManagementException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionGeneratorError()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            when(generator.generateNodeKeys(caPrivateKey, caCertificateChain))
                .thenThrow(new KeyManagementException("test"));
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = InvalidKeyStoreException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionInvalidKeyStore()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            doReturn(false).when(storeSpi).engineEntryInstanceOf(ALIAS, PrivateKeyEntry.class);
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = InvalidNodeCAException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionInvalidNodeCA()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            when(asymmetricService.verifySignature(any(), any(), any(byte[].class))).thenReturn(false);
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = InvalidPasswordException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionInvalidPassword()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            when(storesService.loadKeyStore(any(), any(), any()))
                .thenThrow(new GeneralCryptoLibException(new IOException(new UnrecoverableEntryException("test"))));
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = IOException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionIOError()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException, GeneralCryptoLibException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            when(storesService.loadKeyStore(any(), any(), any()))
                .thenThrow(new GeneralCryptoLibException(new IOException("test")));
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test(expected = NoSuchFileException.class)
    public void testCreateAndActivateNodeKeysPathStringPasswordProtectionNoSuchFile()
            throws IOException, InvalidKeyStoreException, InvalidPasswordException, InvalidNodeCAException,
            KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        Path file = createTempFile("keys", ".p12");
        try {
            delete(file);
            try {
                manager.createAndActivateNodeKeys(file, ALIAS, PASSWORD_PROTECTION);
            } finally {
                assertFalse(manager.hasNodeKeysActivated());
            }
        } finally {
            deleteIfExists(file);
        }
    }

    @Test
    public void testCreateAndActivateNodeKeysPrivateKeyX509CertificateArrayPasswordProtection()
            throws InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        manager.createAndActivateNodeKeys(caPrivateKey, caCertificateChain, PASSWORD_PROTECTION);
        assertTrue(manager.hasNodeKeysActivated());
        assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
        verify(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        verify(database).setEncryptionKeys(nodeKeys.encryptionPrivateKey(), nodeKeys.encryptionPublicKey());
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateAndActivateNodeKeysPrivateKeyX509CertificateArrayPasswordProtectionAlreadyExist()
            throws InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        try {
            manager.createAndActivateNodeKeys(caPrivateKey, caCertificateChain, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysPrivateKeyX509CertificateArrayPasswordProtectionDatabaseError()
            throws InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        doThrow(new KeyManagementException("test")).when(database).saveNodeKeys(nodeKeys, PASSWORD_PROTECTION);
        try {
            manager.createAndActivateNodeKeys(caPrivateKey, caCertificateChain, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateAndActivateNodeKeysPrivateKeyX509CertificateArrayPasswordProtectionGeneratorError()
            throws InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException {
        manager.startup();
        when(generator.generateNodeKeys(caPrivateKey, caCertificateChain))
            .thenThrow(new KeyManagementException("test"));
        try {
            manager.createAndActivateNodeKeys(caPrivateKey, caCertificateChain, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test(expected = InvalidNodeCAException.class)
    public void testCreateAndActivateNodeKeysPrivateKeyX509CertificateArrayPasswordProtectionInvalidNodeCA()
            throws InvalidNodeCAException, KeyAlreadyExistsException, KeyManagementException,
            GeneralCryptoLibException {
        manager.startup();
        when(asymmetricService.verifySignature(any(), any(), any(byte[].class))).thenReturn(false);
        try {
            manager.createAndActivateNodeKeys(caPrivateKey, caCertificateChain, PASSWORD_PROTECTION);
        } finally {
            assertFalse(manager.hasNodeKeysActivated());
        }
    }

    @Test
    public void testCreateChoiceCodeKeys()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, GeneralCryptoLibException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        ElGamalEncryptionParameters parameters = ElGamalEncryptionParameters.fromJson(ENCRYPTION_PARAMETERS_JSON);
        ChoiceCodeKeysSpec spec = new ChoiceCodeKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setVerificationCardSetId(VERIFICATION_CARD_SET_ID).setParameters(parameters).setGenerationLength(1)
            .setDecryptionLength(1).build();
        manager.createChoiceCodeKeys(spec);
        verify(database).loadElectionSigningKeys(ELECTION_EVENT_ID);
        verify(generator).generateChoiceCodeKeys(spec, electionSigningKeys);
        verify(database).saveChoiceCodeKeys(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID, choiceCodeKeys);
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateChoiceCodeKeysElectionSigningKeysAlreadyExist()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, GeneralCryptoLibException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        ElGamalEncryptionParameters parameters = ElGamalEncryptionParameters.fromJson(ENCRYPTION_PARAMETERS_JSON);
        ChoiceCodeKeysSpec spec = new ChoiceCodeKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setVerificationCardSetId(VERIFICATION_CARD_SET_ID).setParameters(parameters).setGenerationLength(1)
            .setDecryptionLength(1).build();
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveChoiceCodeKeys(ELECTION_EVENT_ID,
            VERIFICATION_CARD_SET_ID, choiceCodeKeys);
        manager.createChoiceCodeKeys(spec);
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateChoiceCodeKeysElectionSigningKeysDatabaseError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, GeneralCryptoLibException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        ElGamalEncryptionParameters parameters = ElGamalEncryptionParameters.fromJson(ENCRYPTION_PARAMETERS_JSON);
        ChoiceCodeKeysSpec spec = new ChoiceCodeKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setVerificationCardSetId(VERIFICATION_CARD_SET_ID).setParameters(parameters).setGenerationLength(1)
            .setDecryptionLength(1).build();
        doThrow(new KeyManagementException("test")).when(database).saveChoiceCodeKeys(ELECTION_EVENT_ID,
            VERIFICATION_CARD_SET_ID, choiceCodeKeys);
        manager.createChoiceCodeKeys(spec);
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateChoiceCodeKeysElectionSigningKeysGeneratorError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        ChoiceCodeKeysSpec spec = new ChoiceCodeKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setVerificationCardSetId(VERIFICATION_CARD_SET_ID).setParameters(parameters).setGenerationLength(1)
            .setDecryptionLength(1).build();
        when(generator.generateChoiceCodeKeys(spec, electionSigningKeys)).thenThrow(new KeyManagementException("test"));
        manager.createChoiceCodeKeys(spec);
    }

    @Test(expected = KeyNotFoundException.class)
    public void testCreateChoiceCodeKeysElectionSigningKeysNotFound()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        ChoiceCodeKeysSpec spec = new ChoiceCodeKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setVerificationCardSetId(VERIFICATION_CARD_SET_ID).setParameters(parameters).setGenerationLength(1)
            .setDecryptionLength(1).build();
        when(database.loadElectionSigningKeys(ELECTION_EVENT_ID)).thenThrow(new KeyNotFoundException("test"));
        manager.createChoiceCodeKeys(spec);
    }

    @Test
    public void testCreateElectionSigningKeysStringDateDate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        Date validFrom = new Date();
        Date validTo = new Date(validFrom.getTime() + 1000);
        manager.createElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo);
        verify(generator).generateElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo, nodeKeys);
        verify(database).saveElectionSigningKeys(ELECTION_EVENT_ID, electionSigningKeys);
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateElectionSigningKeysStringDateDateAlreadyExist()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveElectionSigningKeys(ELECTION_EVENT_ID,
            electionSigningKeys);
        Date validFrom = new Date();
        Date validTo = new Date(validFrom.getTime() + 1000);
        manager.createElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo);
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateElectionSigningKeysStringDateDateDatabaseError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        doThrow(new KeyManagementException("test")).when(database).saveElectionSigningKeys(ELECTION_EVENT_ID,
            electionSigningKeys);
        Date validFrom = new Date();
        Date validTo = new Date(validFrom.getTime() + 1000);
        manager.createElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo);
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateElectionSigningKeysStringDateDateGeneratorError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        Date validFrom = new Date();
        Date validTo = new Date(validFrom.getTime() + 1000);
        when(generator.generateElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo, nodeKeys))
            .thenThrow(new KeyManagementException("test"));
        manager.createElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo);
    }

    @Test
    public void testCreateElectionSigningKeysStringZonedDateTimeZonedDateTime()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        ZonedDateTime validFrom = ZonedDateTime.now();
        ZonedDateTime validTo = validFrom.plusSeconds(1);
        manager.createElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo);
        verify(generator).generateElectionSigningKeys(ELECTION_EVENT_ID, Date.from(validFrom.toInstant()),
            Date.from(validTo.toInstant()), nodeKeys);
    }

    @Test
    public void testCreateMixingKeys() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        MixingKeysSpec spec = new MixingKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID).setParameters(parameters).setLength(1).build();
        manager.createMixingKeys(spec);
        verify(database).loadElectionSigningKeys(ELECTION_EVENT_ID);
        verify(generator).generateMixingKeys(spec, electionSigningKeys);
        verify(database).saveMixingKeys(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID, mixingKeys);
    }

    @Test(expected = KeyNotFoundException.class)
    public void testCreateMixingKeysAlreadyElectionSigningKeysNotFound()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        MixingKeysSpec spec = new MixingKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID).setParameters(parameters).setLength(1).build();
        when(database.loadElectionSigningKeys(ELECTION_EVENT_ID)).thenThrow(new KeyNotFoundException("test"));
        manager.createMixingKeys(spec);
    }

    @Test(expected = KeyAlreadyExistsException.class)
    public void testCreateMixingKeysAlreadyExist()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        MixingKeysSpec spec = new MixingKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID).setParameters(parameters).setLength(1).build();
        doThrow(new KeyAlreadyExistsException("test")).when(database).saveMixingKeys(ELECTION_EVENT_ID,
            ELECTORAL_AUTHORITY_ID, mixingKeys);
        manager.createMixingKeys(spec);
    }

    @Test(expected = KeyManagementException.class)
    public void testCreateMixingKeysDatabaseError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        MixingKeysSpec spec = new MixingKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID).setParameters(parameters).setLength(1).build();
        doThrow(new KeyManagementException("test")).when(database).saveMixingKeys(ELECTION_EVENT_ID,
            ELECTORAL_AUTHORITY_ID, mixingKeys);
        manager.createMixingKeys(spec);
    }
    
    @Test(expected = KeyManagementException.class)
    public void testCreateMixingKeysGeneratorError()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException, FingerprintGeneratorException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        EncryptionParameters parameters = mock(EncryptionParameters.class);
        MixingKeysSpec spec = new MixingKeysSpec.Builder().setElectionEventId(ELECTION_EVENT_ID)
            .setElectoralAuthorityId(ELECTORAL_AUTHORITY_ID).setParameters(parameters).setLength(1).build();
        when(generator.generateMixingKeys(spec, electionSigningKeys)).thenThrow(new KeyNotFoundException("test"));
        manager.createMixingKeys(spec);
    }
    
    @Test
    public void testElectionSigningKeysCaching() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(electionSigningKeys.privateKey(),
            manager.getElectionSigningPrivateKey(ELECTION_EVENT_ID));

        assertEquals(electionSigningKeys, cache.getElectionSigningKeys(ELECTION_EVENT_ID));
        
        assertEquals(electionSigningKeys.privateKey(),
            manager.getElectionSigningPrivateKey(ELECTION_EVENT_ID));
        
        verify(database, times(1)).loadElectionSigningKeys(ELECTION_EVENT_ID);
    }
    
    @Test
    public void testGetChoiceCodeDecryptionPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.decryptionPrivateKey(),
            manager.getChoiceCodeDecryptionPrivateKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }
    
    @Test
    public void testGetChoiceCodeDecryptionPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.decryptionPublicKey(),
            manager.getChoiceCodeDecryptionPublicKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }
    
    @Test
    public void testGetChoiceCodeDecryptionPublicKeySignature()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertArrayEquals(choiceCodeKeys.decryptionPublicKeySignature(),
            manager.getChoiceCodeDecryptionPublicKeySignature(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testGetChoiceCodeEncryptionParameters()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.encryptionParameters(),
            manager.getChoiceCodeEncryptionParameters(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testGetChoiceCodeGenerationPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.generationPrivateKey(),
            manager.getChoiceCodeGenerationPrivateKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testGetChoiceCodeGenerationPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(choiceCodeKeys.generationPublicKey(),
            manager.getChoiceCodeGenerationPublicKey(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testGetChoiceCodeGenerationPublicKeySignature()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertArrayEquals(choiceCodeKeys.generationPublicKeySignature(),
            manager.getChoiceCodeGenerationPublicKeySignature(ELECTION_EVENT_ID, VERIFICATION_CARD_SET_ID));
    }

    @Test
    public void testGetElectionSigningCertificate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(electionSigningKeys.certificate(), manager.getElectionSigningCertificate(ELECTION_EVENT_ID));
    }

    @Test
    public void testGetElectionSigningPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(electionSigningKeys.privateKey(), manager.getElectionSigningPrivateKey(ELECTION_EVENT_ID));
    }
    
    @Test
    public void testGetElectionSigningPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(electionSigningKeys.publicKey(), manager.getElectionSigningPublicKey(ELECTION_EVENT_ID));
    }

    @Test
    public void testGetMixingPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(mixingKeys.privateKey(), manager.getMixingPrivateKey(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));
    }

    @Test
    public void testGetMixingPublicKey() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(mixingKeys.publicKey(), manager.getMixingPublicKey(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));
    }

    @Test
    public void testGetMixingPublicKeySignature()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertArrayEquals(mixingKeys.publicKeySignature(),
            manager.getMixingPublicKeySignature(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));
    }

    @Test
    public void testGetPlatformCACertificate() throws Exception {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        X509Certificate platformCACertificate = nodeKeys.caCertificateChain()[nodeKeys.caCertificateChain().length - 1];
        assertEquals(platformCACertificate, manager.getPlatformCACertificate());
    }

    @Test
    public void testHasValidElectionSigningKeysStringDateDate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + 1000);
        X509Certificate certificate = electionSigningKeys.certificate();
        when(certificate.getNotBefore()).thenReturn(notBefore);
        when(certificate.getNotAfter()).thenReturn(notAfter);

        Date validFrom = notBefore;
        Date validTo = notAfter;
        assertTrue(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));

        validFrom = new Date(notBefore.getTime() - 1);
        validTo = notAfter;
        assertFalse(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));

        validFrom = notBefore;
        validTo = new Date(notAfter.getTime() + 1);
        assertFalse(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));
    }

    @Test
    public void testHasValidElectionSigningKeysStringZonedDateTimeZonedDateTime()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + 1000);
        X509Certificate certificate = electionSigningKeys.certificate();
        when(certificate.getNotBefore()).thenReturn(notBefore);
        when(certificate.getNotAfter()).thenReturn(notAfter);

        ZoneId zoneId = ZoneId.of("UTC");
        ZonedDateTime validFrom = ZonedDateTime.ofInstant(notBefore.toInstant(), zoneId);
        ZonedDateTime validTo = ZonedDateTime.ofInstant(notAfter.toInstant(), zoneId);
        assertTrue(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));

        validFrom = validFrom.minusSeconds(1);
        assertFalse(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));

        validFrom = validFrom.plusSeconds(1);
        validTo = validTo.plusSeconds(1);
        assertFalse(manager.hasValidElectionSigningKeys(ELECTION_EVENT_ID, validFrom, validTo));
    }

    @Test
    public void testMixingKeysCaching() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(mixingKeys.privateKey(),
            manager.getMixingPrivateKey(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));

        assertEquals(mixingKeys, cache.getMixingKeys(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));
        
        assertEquals(mixingKeys.privateKey(),
            manager.getMixingPrivateKey(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID));
        
        verify(database, times(1)).loadMixingKeys(ELECTION_EVENT_ID, ELECTORAL_AUTHORITY_ID);
    }

    @Test
    public void testNodeCACertificate() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.caCertificate(), manager.nodeCACertificate());
    }

    @Test
    public void testNodeCAPrivateKey() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.caPrivateKey(), manager.nodeCAPrivateKey());
    }

    @Test
    public void testNodeCAPublicKey() throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.caPublicKey(), manager.nodeCAPublicKey());
    }

    @Test
    public void testNodeEncryptionCertificate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.encryptionCertificate(), manager.nodeEncryptionCertificate());
    }

    @Test
    public void testNodeEncryptionPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.encryptionPrivateKey(), manager.nodeEncryptionPrivateKey());
    }

    @Test
    public void testNodeEncryptionPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.encryptionPublicKey(), manager.nodeEncryptionPublicKey());
    }

    @Test
    public void testNodeLogEncryptionCertificate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logEncryptionCertificate(), manager.nodeLogEncryptionCertificate());
    }

    @Test
    public void testNodeLogEncryptionPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logEncryptionPrivateKey(), manager.nodeLogEncryptionPrivateKey());
    }

    @Test
    public void testNodeLogEncryptionPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logEncryptionPublicKey(), manager.nodeLogEncryptionPublicKey());
    }

    @Test
    public void testNodeLogSigningCertificate()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logSigningCertificate(), manager.nodeLogSigningCertificate());
    }

    @Test
    public void testNodeLogSigningPrivateKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logSigningPrivateKey(), manager.nodeLogSigningPrivateKey());
    }

    @Test
    public void testNodeLogSigningPublicKey()
            throws KeyNotFoundException, InvalidPasswordException, KeyManagementException {
        manager.startup();
        manager.activateNodeKeys(PASSWORD_PROTECTION);
        assertEquals(nodeKeys.logSigningPublicKey(), manager.nodeLogSigningPublicKey());
    }

    @Test
    public void testShutdown() {
        cache.shutdown();
        cache = mock(Cache.class);
        manager = new KeyManagerImpl(asymmetricService, storesService, generator, database, cache,
            Mockito.mock(SecureLoggingWriter.class), Mockito.mock(FingerprintGenerator.class),
            serviceTransactionInfoProvider, "nodeId");
        manager.startup();
        manager.shutdown();
        verify(cache).shutdown();
    }

    @Test
    public void testStartup() {
        cache.shutdown();
        cache = mock(Cache.class);
        manager = new KeyManagerImpl(asymmetricService, storesService, generator, database, cache,
            Mockito.mock(SecureLoggingWriter.class), Mockito.mock(FingerprintGenerator.class),
            serviceTransactionInfoProvider, "nodeId");
        manager.startup();
        verify(cache).startup();
    }

    private static class KeyStoreDouble extends KeyStore {
        public KeyStoreDouble(KeyStoreSpi keyStoreSpi) {
            super(keyStoreSpi, null, "PkCSS12");
        }
    }
}
