ch.ge.ve.commons.crypto.ballot.BallotCipherServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for ch.ge.ve.commons.crypto.ballot.BallotCipherServiceTest.java

Source

package ch.ge.ve.commons.crypto.ballot;

/*-
 * #%L
 * Common crypto utilities
 * %%
 * Copyright (C) 2015 - 2016 Rpublique et Canton de Genve
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import ch.ge.ve.commons.crypto.exceptions.AuthenticationTagMismatchException;
import ch.ge.ve.commons.crypto.exceptions.ProtocolNotRespectedException;
import ch.ge.ve.commons.crypto.utils.CertificateUtils;
import ch.ge.ve.commons.crypto.utils.CipherFactory;
import ch.ge.ve.commons.properties.PropertyConfigurationException;
import ch.ge.ve.commons.properties.PropertyConfigurationService;
import com.google.common.base.Strings;
import org.apache.log4j.Logger;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Before;
import org.junit.Test;

import javax.crypto.Cipher;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import static ch.ge.ve.commons.crypto.ballot.BallotCiphersProvider.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

/**
 * Tests for the {@link BallotCipherService} class
 */
public class BallotCipherServiceTest {
    private final Logger log = Logger.getLogger(this.getClass());

    private BallotCiphersProvider ballotCiphersProvider;
    private BallotCipherService ballotCipherService;
    private PropertyConfigurationService propertyConfigurationService;

    @Before
    public void setUp() {
        ballotCiphersProvider = mock(BallotCiphersProvider.class);
        propertyConfigurationService = mock(PropertyConfigurationService.class);

        ballotCipherService = new BallotCipherService(ballotCiphersProvider, propertyConfigurationService);

        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * plain ballot encryption should work
     */
    @Test
    public void testEncryptBallot() throws Exception {
        initBallotCiphersProviderMock();

        StringBuilder sb = new StringBuilder();
        sb.append("GE;6699:9903");
        sb.append("370001;0;37000101;p=DEP");
        sb.append(Strings.repeat(";1001", 545));

        AuthenticatedBallot authenticatedBallot = ballotCipherService
                .encryptBallotThenWrapForAuthentication(sb.toString(), 1);

        assertThat("The encrypted ballot byte array length should be less than 4000 chars",
                authenticatedBallot.getAuthenticatedEncryptedBallot().length, lessThan(4000));
        assertThat("The encrypted ballot key should be 256 bytes long", authenticatedBallot.getWrappedKey().length,
                is(256));
        assertThat("The encryption authentication tag should be 128bit / 16 bytes long",
                authenticatedBallot.getTag().length, is(16));
        assertThat("The authenticated ballot should have the proper index attached",
                authenticatedBallot.getBallotIndex(), is(1));
    }

    /**
     * encryption followed by decryption of a ballot should return the same object
     */
    @Test
    public void testEncryptAndDecryptBallot() throws Exception {
        initBallotCiphersProviderMock();

        String plainText = "plainText";

        AuthenticatedBallot authenticatedBallot = ballotCipherService
                .encryptBallotThenWrapForAuthentication(plainText, 1);

        when(ballotCiphersProvider.getBallotKeyCipherPrivateKey()).thenReturn(null);
        EncryptedBallotAndWrappedKey encryptedBallotAndWrappedKey = ballotCipherService
                .verifyAuthenticationThenUnwrap(authenticatedBallot);

        initializePrivateKey();
        String decryptedText = ballotCipherService.decryptBallot(encryptedBallotAndWrappedKey);

        assertThat("decryptBallot(encryptBallotThenWrapForAuthentication(...)) should be identical", decryptedText,
                equalTo(plainText));
    }

    /**
     * verification of an altered ballot cipher should fail
     */
    @Test(expected = AuthenticationTagMismatchException.class)
    public void testDecryptBallotWithTagMismatch() throws Exception {
        initBallotCiphersProviderMock();

        String plainText = "plainText";

        AuthenticatedBallot authenticatedBallot = ballotCipherService
                .encryptBallotThenWrapForAuthentication(plainText, 1);

        when(ballotCiphersProvider.getBallotKeyCipherPrivateKey()).thenReturn(null);

        // alter the tag
        // The tag doesn't need to be kept in the Authenticated ballot anymore, since it is included in the cipher text
        //        authenticatedBallot.getTag()[0] = (byte)(authenticatedBallot.getTag()[0] ^ (byte)1);
        int length = authenticatedBallot.getAuthenticatedEncryptedBallot().length;
        authenticatedBallot.getAuthenticatedEncryptedBallot()[length
                - 1] = (byte) (authenticatedBallot.getAuthenticatedEncryptedBallot()[length - 1] ^ (byte) 1);

        // should raise an exception
        ballotCipherService.verifyAuthenticationThenUnwrap(authenticatedBallot);
    }

    /**
     * stress test the encrypt and decrypt methods; verification are done manually by analysing the provided logs
     */
    @Test
    public void testEncryptDecryptPerf() throws Exception {
        initBallotCiphersProviderMock();

        int nbIterations = 200;

        List<String> inputs = new ArrayList<String>(3);
        inputs.add("testString");
        inputs.add("somewhat long string");
        inputs.add(Strings.repeat("testString", nbIterations));

        String result = String.format("| %-15s | %-15s | %-8s (%3dx) | %-15s | %-8s (%3dx) | %-15s | %-8s (%3dx) |",
                "input length", "encrypt mean", "encrypt", nbIterations, "verify mean", "verify", nbIterations,
                "decryptBallot mean", "decryptBallot", nbIterations);
        log.info(result);
        for (String input : inputs) {
            iterateEncryptDecrypt(input, nbIterations);
        }
    }

    private void iterateEncryptDecrypt(String input, int nbIterations)
            throws ClassNotFoundException, GeneralSecurityException, InvalidCipherTextException, IOException,
            ProtocolNotRespectedException, AuthenticationTagMismatchException {
        List<AuthenticatedBallot> authenticatedBallots = new ArrayList<AuthenticatedBallot>();

        // Run a first encryption to initialize the cipher, we want to discard the initialization time
        AuthenticatedBallot initBallot = ballotCipherService.encryptBallotThenWrapForAuthentication("test", 0);

        final long startEncrypt = System.currentTimeMillis();
        for (int i = 0; i < nbIterations; i++) {
            authenticatedBallots.add(ballotCipherService.encryptBallotThenWrapForAuthentication(input, i));
        }
        final long endEncrypt = System.currentTimeMillis();

        when(ballotCiphersProvider.getBallotKeyCipherPrivateKey()).thenReturn(null);
        // Again, a first run to initialize the cipher
        EncryptedBallotAndWrappedKey initVerif = ballotCipherService.verifyAuthenticationThenUnwrap(initBallot);

        List<EncryptedBallotAndWrappedKey> encryptedBallotAndWrappedKeys = new ArrayList<EncryptedBallotAndWrappedKey>();
        final long startVerify = System.currentTimeMillis();
        for (AuthenticatedBallot authenticatedBallot : authenticatedBallots) {
            encryptedBallotAndWrappedKeys
                    .add(ballotCipherService.verifyAuthenticationThenUnwrap(authenticatedBallot));
        }
        final long endVerify = System.currentTimeMillis();

        initializePrivateKey();

        String init = ballotCipherService.decryptBallot(initVerif);
        assertThat(init, equalTo("test"));

        List<String> retrievedPlaintexts = new ArrayList<String>();
        final long startDecrypt = System.currentTimeMillis();
        for (EncryptedBallotAndWrappedKey encryptedBallotAndWrappedKey : encryptedBallotAndWrappedKeys) {
            retrievedPlaintexts.add(ballotCipherService.decryptBallot(encryptedBallotAndWrappedKey));
        }
        final long endDecrypt = System.currentTimeMillis();

        assertThat("retrieved plaintexts should all be equal to the input", retrievedPlaintexts,
                everyItem(equalTo(input)));

        final long totalEncryptionTime = endEncrypt - startEncrypt;
        final long meanEncryptionTime = totalEncryptionTime / authenticatedBallots.size();
        assertThat(meanEncryptionTime, lessThan(500l));

        final long totalVerificationTime = endVerify - startVerify;
        final long meanVerificationTime = totalVerificationTime / authenticatedBallots.size();
        assertThat(meanVerificationTime, lessThan(500l));

        final long totalDecryptionTime = endDecrypt - startDecrypt;
        final long meanDecryptionTime = totalDecryptionTime / authenticatedBallots.size();
        assertThat(meanDecryptionTime, lessThan(1000l));

        String result = String.format("| %15d | %15d | %15d | %15d | %15d | %15d | %15d |", input.length(),
                meanEncryptionTime, totalEncryptionTime, meanVerificationTime, totalVerificationTime,
                meanDecryptionTime, totalDecryptionTime);
        log.info(result);
    }

    private void initBallotCiphersProviderMock()
            throws GeneralSecurityException, IOException, ClassNotFoundException, PropertyConfigurationException {
        // Instantiate ballotCipher
        PropertyConfigurationService propertyConfigurationService1 = new PropertyConfigurationService();
        String ballotCipherAlgo = propertyConfigurationService1.getConfigValue(BALLOT_CRYPTING_ALGORITHM);
        String ballotBlockmode = propertyConfigurationService1.getConfigValue(BALLOT_CRYPTING_BLOCK_MODE);
        Cipher ballotCipher = new CipherFactory(propertyConfigurationService1)
                .getInstance(ballotCipherAlgo + ballotBlockmode);
        when(ballotCiphersProvider.getBallotCipher()).thenReturn(ballotCipher);

        // Define ballotCipher key size
        int ballotCipherSize = 256;
        when(ballotCiphersProvider.getBallotCipherSize()).thenReturn(ballotCipherSize);

        // Instantiate ballotKeyCipher

        String ballotKeyCipherAlgo = propertyConfigurationService1.getConfigValue(BALLOT_KEY_CRYPTING_ALGORITHM);
        String ballotKeyCipherBlockmode = propertyConfigurationService1
                .getConfigValue(BALLOT_KEY_CRYPTING_BLOCKMODE);
        Cipher ballotKeyCipher = new CipherFactory(propertyConfigurationService1)
                .getInstance(ballotKeyCipherAlgo + ballotKeyCipherBlockmode);
        when(ballotCiphersProvider.getBallotKeyCipher()).thenReturn(ballotKeyCipher);

        String ballotIntegrityAlgo = propertyConfigurationService1
                .getConfigValue(BALLOT_INTEGRITY_CHECK_CRYPTING_ALGORITHM);
        String ballotIntegrityBlockmode = propertyConfigurationService1
                .getConfigValue(BALLOT_INTEGRITY_CHECK_CRYPTING_BLOCK_MODE);
        Cipher integrityCipher = new CipherFactory(propertyConfigurationService1)
                .getInstance(ballotIntegrityAlgo + ballotIntegrityBlockmode);
        when(ballotCiphersProvider.getIntegrityCipher(propertyConfigurationService)).thenReturn(integrityCipher);

        when(ballotCiphersProvider.getMacLength()).thenReturn(128);

        // Instantiate public key
        final InputStream publicKeyStream = BallotCipherServiceTest.class.getResourceAsStream("/ctrl.der");
        java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X509");
        X509Certificate certPublic = (X509Certificate) cf.generateCertificate(publicKeyStream);
        Key ballotKeyCipherPublicKey = certPublic.getPublicKey();
        when(ballotCiphersProvider.getBallotKeyCipherPublicKey()).thenReturn(ballotKeyCipherPublicKey);

        // Instantiate secret key
        final InputStream integrityKeyStream = BallotCipherServiceTest.class.getResourceAsStream("/integrity.key");
        ObjectInputStream ois = new ObjectInputStream(integrityKeyStream);
        Key integrityCheckSecretKey = (Key) ois.readObject();
        ois.close();
        when(ballotCiphersProvider.getIntegrityCheckSecretKey()).thenReturn(integrityCheckSecretKey);
    }

    private void initializePrivateKey() throws KeyStoreException, IOException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException {
        // Instantiate private key
        final InputStream privateKeyStream = BallotCipherServiceTest.class.getResourceAsStream("/ctrl.p12");
        KeyStore caKs = CertificateUtils.createPKCS12KeyStore();
        caKs.load(privateKeyStream, "testtest".toCharArray());
        Key ballotKeyCipherPrivateKey = caKs.getKey("ctrl", "testtest".toCharArray());
        when(ballotCiphersProvider.getBallotKeyCipherPrivateKey()).thenReturn(ballotKeyCipherPrivateKey);
    }

    /**
     * loadBallotKeyCipherPrivateKey should be delegated to BallotCiphersProvider
     */
    @Test
    public void testLoadBallotKeyCipherPrivateKey() throws Exception {
        ballotCipherService.loadBallotKeyCipherPrivateKey("password");
        verify(ballotCiphersProvider, times(1)).loadBallotKeyCipherPrivateKey("password");
    }

    /**
     * setPrivateKeyFileName should be delegated to BallotCiphersProvider
     */
    @Test
    public void testSetPrivateKeyFileName() throws Exception {
        ballotCipherService.setPrivateKeyFileName("priv_key");
        verify(ballotCiphersProvider, times(1)).invalidatePrivateKeyCache();
    }

    /**
     * setPublicKeyFileName should be delegated to BallotCiphersProvider
     */
    @Test
    public void testSetPublicKeyFileName() throws Exception {
        ballotCipherService.setPublicKeyFileName("pub_key");
        verify(ballotCiphersProvider, times(1)).invalidatePublicKeyCache();
    }

    /**
     * setIntegrityKeyFileName should be delegated to BallotCiphersProvider
     */
    @Test
    public void testSetIntegrityKeyFileName() throws Exception {
        ballotCipherService.setIntegrityKeyFileName("integrity_key");
        verify(ballotCiphersProvider, times(1)).invalidateIntegrityKeyCache();
    }
}