/*
 * Copyright 2002-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * imitations under the License.
 */

package test;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Unit tests to test the varoius block ciphers.
 * 
 * The class generates cipher data with the openSSL block cipher functions and
 * compares the results with cipher data generated by the BouncyCastle
 * implementation and vices versa.
 * 
 * Created on 01.10.2005
 * 
 * @author Werner Dittmann (werner@apache.org)
 * 
 */

public class OpenSSLTestBlock extends TestCase {

    SecretKey encryptionKey = null;

    Cipher cipherEncJuice = null;

    Cipher cipherEncBC = null;

    Cipher cipherDecJuice = null;

    Cipher cipherDecBC = null;

    byte[] plainData = null;

    byte[] cipherDataJuice = null;

    byte[] cipherDataBC = null;

    byte[] newPlainDataJuice = null;

    byte[] newPlainDataBC = null;

    public static boolean areEqual(byte[] a, byte[] b) {
        if (a.length != b.length) {
            return false;
        }

        for (int i = 0; i != a.length; i++) {
            if (a[i] != b[i]) {
                System.out.println("mismatch at index: " + i);
                System.out.println("a: " + a[i] + ", b: " + b[i]);
                return false;
            }
        }

        return true;
    }

    public static void dumpArray(byte[] a) {
        for (int i = 0; i < a.length; i++) {
            if ((i % 20) == 0)
                System.out.println("");
            int ii = a[i];
            System.out.print(" " + ii);
        }
        System.out.println("");
    }

    public OpenSSLTestBlock(String name) {
        super(name);
        try {
            Class<?> c = Class
                    .forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
            java.security.Security.addProvider((java.security.Provider) c
                    .newInstance());
            c = Class
                    .forName("org.apache.security.juice.provider.JuiCEProviderOpenSSL");
            java.security.Security.addProvider((java.security.Provider) c
                    .newInstance());
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    protected void setUp() {
        cipherDataJuice = null;
        cipherDataBC = null;
        newPlainDataJuice = null;
        newPlainDataBC = null;

        plainData = new byte[1024];

        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
    }

    protected void setCiphers(String cipherName, String keygenName,
            int keyLength) throws RuntimeException {

        try {
            /*
             * at first generate a key for the cipher under tes, then initialize
             * cipher: use the openSSL implementation and the standard BC
             * implementation (as reference)
             */
            KeyGenerator keyGen = KeyGenerator.getInstance(keygenName, "BC");
            keyGen.init(keyLength);
            encryptionKey = keyGen.generateKey();

            cipherEncJuice = Cipher.getInstance(cipherName, "JuiCE");
            cipherDecJuice = Cipher.getInstance(cipherName, "JuiCE");
            assertNotNull("Cipher " + cipherName + " not found", cipherEncJuice);

            cipherEncBC = Cipher.getInstance(cipherName, "BC");
            cipherDecBC = Cipher.getInstance(cipherName, "BC");

            /*
             * Check if cipher providers are correct, i.e the correct provider
             * was chosen.
             */

            assertEquals("Wrong class of BC provider", cipherEncBC
                    .getProvider().getClass().getName(),
                    "org.bouncycastle.jce.provider.BouncyCastleProvider");
            assertEquals("Wrong class of JuiCE provider:", cipherEncJuice
                    .getProvider().getClass().getName(),
                    "org.apache.security.juice.provider.JuiCEProviderOpenSSL");
            /*
             * Initialize the Ciphers
             */
            try {
                IvParameterSpec ivp = null;
                cipherEncJuice.init(Cipher.ENCRYPT_MODE, encryptionKey);
                /*
                 * If encryption mode generated an IV: get it, use it for
                 * decryption _and_ for setup of following cipher that is under
                 * test (BC provider is reference impl.)
                 */
                byte[] iv = cipherEncJuice.getIV();
                if (iv != null) {
                    ivp = new IvParameterSpec(iv);
                }
                cipherDecBC.init(Cipher.DECRYPT_MODE, encryptionKey, ivp);

                cipherEncBC.init(Cipher.ENCRYPT_MODE, encryptionKey, ivp, null);
                cipherDecJuice.init(Cipher.DECRYPT_MODE, encryptionKey, ivp,
                        null);
            } catch (InvalidKeyException e) {
                throw new RuntimeException(e.getMessage());
            } catch (InvalidAlgorithmParameterException e) {
                throw new RuntimeException(e.getMessage());
            }

        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e.getMessage());
        } catch (NoSuchProviderException e) {
            throw new RuntimeException(e.getMessage());
        } catch (NoSuchPaddingException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    void encrypt() {
        try {
            cipherDataJuice = cipherEncJuice.doFinal(plainData);
            cipherDataBC = cipherEncBC.doFinal(plainData);
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    void decrypt() {
        try {
            newPlainDataJuice = cipherDecJuice.doFinal(cipherDataJuice);
            newPlainDataBC = cipherDecBC.doFinal(cipherDataBC);
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /*
     * Use the cipher data of openSSL and decrypt with plain BC and vice versa.
     */
    void crossDecrypt() {
        newPlainDataJuice = null;
        newPlainDataBC = null;
        try {
            newPlainDataJuice = cipherDecJuice.doFinal(cipherDataBC);
            newPlainDataBC = cipherDecBC.doFinal(cipherDataJuice);
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void performStandardTest() {

        encrypt();
        /*
         * Now assert that both encryptions delivered the same result Don't do
         * this test anymore because some padding, e.g. ISO10126 insert random
         * bytes for padding - thus this test will fail
         */
        // assertTrue("Cipher data mismatch", areEqual(cipherDataJuice,
        // cipherDataBC));
        decrypt();
        /*
         * Now assert that both decryptions delivered the same result
         */
        assertTrue("Decrypted plain data mismatch", Arrays.equals(
                newPlainDataJuice, newPlainDataBC));

        /*
         * Now assert that openSSL decryption delivered the original result
         */
        assertTrue("Decrypted plain data mismatch(original)", Arrays.equals(
                newPlainDataJuice, plainData));

        crossDecrypt();
        /*
         * Now assert that both decryptions delivered the same result
         */
        assertTrue("Cross decrypted plain data mismatch", Arrays.equals(
                newPlainDataJuice, newPlainDataBC));

        /*
         * Now assert that cross decryption delivered the original result Here
         * we use the results of decrypting the openSSL cipher result with
         * original BC algorithm.
         */
        assertTrue("Decrypted plain data mismatch(original, cross)", areEqual(
                newPlainDataJuice, plainData));

    }

    public void testAES128ECB() {

        /*
         * If nothing spefied, ECB is default. With NOPADDING the buffer size
         * must be a multiple of the cipher's blocksize (AES has blocksize of 16
         * bytes)
         */
        setCiphers("AES/ECB/NOPADDING", "AES", 128);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES128CBC() {

        setCiphers("AES/CBC/PKCS7PADDING", "AES", 128);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES128CBC_ISO10126() {

        setCiphers("AES/CBC/ISO10126PADDING", "AES", 128);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES128OFB() {

        setCiphers("AES/OFB/NOPADDING", "AES", 128);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES128CFB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("AES/CFB/NOPADDING", "AES", 128);
        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    // TODO Currently disable, produces a divide by zero exception in
    // BouncyCastle
    // public void testAES128CFB1() {
    //
    // /*
    // * If nothing spefied, ECB is default
    // */
    // setCiphers("AES/CFB1/NOPADDING", "AES", 128);
    // plainData = new byte[1024];
    // for (int i = 0; i < plainData.length; i++) {
    // plainData[i] = (byte) i;
    // }
    // performStandardTest();
    //        
    // plainData = new byte[1023];
    // for (int i = 0; i < plainData.length; i++) {
    // plainData[i] = (byte) i;
    // }
    // performStandardTest();
    // }

    public void testAES128CFB8() {

        /*
         * If nothing spefied, ECB is default
         */
        try {
            setCiphers("AES/CFB8/NOPADDING", "AES", 128);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage() + ", skipping test");
            return;
        }
        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES192ECB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("AES", "AES", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES192CBC() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("AES/CBC/PKCS7PADDING", "AES", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES256ECB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("AES", "AES", 256);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testAES256CBC() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("AES/CBC/PKCS7PADDING", "AES", 256);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDES_ECB() {
        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DES", "DES", 64);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDES_CBC() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DES/CBC/PKCS5PADDING", "DES", 64);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDESEde_ECB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DESEDE", "DESEDE", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDES_OFB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DES/OFB/NOPADDING", "DES", 64);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDES_CFB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DES/CFB/NOPADDING", "DES", 64);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDESEde_CBC() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DESEDE/CBC/PKCS5PADDING", "DESEDE", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDESEde_OFB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DESEDE/OFB/NOPADDING", "DESEDE", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    public void testDESEde_CFB() {

        /*
         * If nothing spefied, ECB is default
         */
        setCiphers("DESEDE/CFB/NOPADDING", "DESEDE", 192);

        plainData = new byte[1024];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();

        plainData = new byte[1023];
        for (int i = 0; i < plainData.length; i++) {
            plainData[i] = (byte) i;
        }
        performStandardTest();
    }

    /**
     * JUnit suite <p/>
     * 
     * @return a junit test suite
     */
    public static Test suite() {
        return new TestSuite(OpenSSLTestBlock.class);
    }

    /**
     * Main method <p/>
     * 
     * @param args
     *            command line args
     */
    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }
}
