/*
 * 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.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

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

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 OpenSSLTestPBE extends TestCase {

    SecretKey encryptionKeyBC = null;
    SecretKey encryptionKeyJuice = 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 OpenSSLTestPBE(String name) {
        super(name);
        Class<?> c = null;
        java.security.Provider prov = null;
        try {
            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());
            prov = (java.security.Provider)c.newInstance();        
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    protected void setUp() {
    }

    protected void setCiphers(String cipherName) {

        try {
            /*
             * at first generate a key for the cipher under test, then initialize
             * cipher: use the openSSL implementation and the standard BC
             * implementation (as reference)
             */
            char[] secret = new char[12];
            "secretterces".getChars(0, 12, secret, 0);            
            PBEKeySpec pks = new PBEKeySpec(secret, "NatriumChlorid".getBytes(), 100);
            try {
                encryptionKeyBC = SecretKeyFactory.getInstance(cipherName, "BC").generateSecret(pks);
                encryptionKeyJuice = SecretKeyFactory.getInstance(cipherName, "JuiCE").generateSecret(pks);
            } catch (InvalidKeySpecException e1) {
                fail("invalid key specification");
                e1.printStackTrace();
            }
            assertTrue("Generated secret keys are not equal", Arrays.equals(
                    encryptionKeyBC.getEncoded(), encryptionKeyJuice.getEncoded()));

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

            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;
                cipherEncBC.init(Cipher.ENCRYPT_MODE, encryptionKeyBC);
                /*
                 * 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 = cipherEncBC.getIV();
                if (iv != null) {
                    ivp = new IvParameterSpec(iv);
                }
                cipherDecBC.init(Cipher.DECRYPT_MODE, encryptionKeyBC);

                cipherEncJuice.init(Cipher.ENCRYPT_MODE, encryptionKeyJuice);
                cipherDecJuice.init(Cipher.DECRYPT_MODE, encryptionKeyJuice);
            } catch (InvalidKeyException 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 testPBE_SHA1_AES128() {

        setCiphers("PBEWITHSHAAND128BITAES-CBC-BC");
        
        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 testPBE_MD5_AES128_OPENSSL() {

        setCiphers("PBEWITHMD5AND128BITAES-CBC-OPENSSL");
        
        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 testPBE_SHA1_AES192() {

        setCiphers("PBEWITHSHAAND192BITAES-CBC-BC");
        
        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 testPBE_SHA1_AES256() {

        setCiphers("PBEWITHSHAAND256BITAES-CBC-BC");
        
        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 testPBE_MD5_DES() {

        setCiphers("PBEWITHMD5ANDDES");
        
        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 testPBE_SHA1_DES() {

        setCiphers("PBEWITHSHA1ANDDES");
        
        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 testPBE_SHA1_DES3() {

        setCiphers("PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
        
        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 testPBE_SHA1_RC2() {

        setCiphers("PBEWITHSHA1ANDRC2");
        
        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 testPBE_MD5_RC2() {

        setCiphers("PBEWITHMD5ANDRC2");
        
        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 testPBE_RC4_40() {

        setCiphers("PBEWITHSHAAND40BITRC4");
        
        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 testPBE_RC4_128() {

        setCiphers("PBEWITHSHAAND128BITRC4");
        
        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(OpenSSLTestPBE.class);
    }

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