/*
 * 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 RC cipher functions and
 * compares the results with cipher data generated by the BouncyCastle
 * implementation and vices versa.
 * 
 * Created on Feb, 2006
 * 
 * @author Werner Dittmann (werner@apache.org)
 * 
 */

public class OpenSSLTestRC 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 = (int) a[i];
            System.out.print(" " + ii);
        }
        System.out.println("");
    }

    public OpenSSLTestRC(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) {

        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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvalidAlgorithmParameterException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    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 testRC2_ECB_40() {

        /*
         * 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("RC2/ECB/NOPADDING", "RC2", 40);

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

    public void testRC2_ECB_64() {

        /*
         * 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("RC2/ECB/NOPADDING", "RC2", 64);

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

    public void testRC2_ECB_128() {

        /*
         * 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("RC2/ECB/NOPADDING", "RC2", 128);

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

    public void testRC2_CBC_40() {

        setCiphers("RC2/CBC/PKCS7PADDING", "RC2", 40);

        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 testRC2_CBC_64() {

        setCiphers("RC2/CBC/PKCS7PADDING", "RC2", 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 testRC2_CBC_128() {

        setCiphers("RC2/CBC/PKCS7PADDING", "RC2", 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 testRC2_CFB_64() {

        setCiphers("RC2/CFB/NOPADDING", "RC2", 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 testRC2_OFB_64() {

        setCiphers("RC2/OFB/NOPADDING", "RC2", 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 testRC4_40() {

        setCiphers("RC4", "RC4", 40);

        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 testRC4_64() {

        setCiphers("RC4", "RC4", 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 testRC4_128() {

        setCiphers("RC4", "RC4", 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();
    }

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

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