

package gov.va.med.cds.util;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Enumeration;
import java.util.Properties;

import javax.crypto.spec.SecretKeySpec;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import gov.va.med.cds.exception.EncryptionException;
import gov.va.med.cds.junit.runners.BeforeTestsOnce;
import gov.va.med.cds.junit.runners.Suite;
import gov.va.med.cds.junit.runners.SuiteAwareRunner;


@RunWith( SuiteAwareRunner.class )
public class CipherUtilTest
{
    private static final String CHIPHER_PLAIN_TEXT_PROPERTIES = "./src/test/resources/cipherUtil/plainText.properties";
    private static final String CIPHER_KEYSTORE = "./src/test/resources/cipherUtil/.keystore";
    private static final String CIPHER_SECRET_KEY_SER = "./src/test/resources/cipherUtil/filename.ser";

    //files below are created do not check them in
    private static final String CIPHER_ENCRYPTED_KEY_STORE_PROPERTIES = "./src/test/resources/cipherUtil/encryptedTextKeyStore.properties";
    private static final String CIPHER_ENCRYPTED_PROPERTIES = "./src/test/resources/cipherUtil/encryptedText.properties";
    private static final String OUT_KEY_STORE_PROPERTIES = "./src/test/resources/cipherUtil/outKeyStore.properties";

    static SecretKeySpec key = null;
    static String encryptedString = null;


    @BeforeTestsOnce
    @Suite( groups = "checkintest", order = 1 )
    public void testGenerateKey( )
        throws Exception
    {
        key = CipherUtilities.generateKeySpec();
        Assert.assertNotNull( key );
    }


    @Test
    @Suite( groups = "checkintest", order = 2 )
    public void testEncrypt( )
        throws Exception
    {
        String toEncrypt = "password";
        encryptedString = CipherUtilities.encrypt( key, toEncrypt );
        Assert.assertNotNull( encryptedString );
    }

	private static final byte[] KEY = {104, 122, 80, 48, 57, 111, 76, 80, 50, 48, 79, 50, 75, 99, 66, 82};
    private static final byte[] EKEY = { 83, -88, 66, 69, 29, -48, 35, -9, 46, 117, -61, -13, 48, -85, 32, -65 };
	

	@Test
	@Suite( groups = "checkintest" )
	public void testEncryptCDS( )
	    throws Exception
    {
        String toEncrypt = "password";
        byte[] keySpec = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
        encryptedString = CipherUtilities.encrypt( toEncrypt, keySpec );
        Assert.assertNotNull( encryptedString );
        String actual = CipherUtilities.decrypt( keySpec, encryptedString );
        Assert.assertEquals( toEncrypt, actual ); 
        
        // Use the default initialization vector defined in CipherUtilities
        encryptedString = CipherUtilities.encrypt( toEncrypt, EKEY );
        Assert.assertNotNull( encryptedString );
        actual = CipherUtilities.decrypt( EKEY, encryptedString );
        Assert.assertEquals( toEncrypt, actual ); 
        
        // Use a supplied initialization vector
        encryptedString = CipherUtilities.encrypt( toEncrypt, EKEY, new byte[16] );
        Assert.assertNotNull( encryptedString );
        actual = CipherUtilities.decrypt( EKEY, new byte[16], encryptedString );
        Assert.assertEquals( toEncrypt, actual ); 
        
        // Use a different key and a different supplied initialization vector
        encryptedString = CipherUtilities.encrypt( toEncrypt, KEY, "BecauseOfFortify".getBytes() );
        Assert.assertNotNull( encryptedString );
        actual = CipherUtilities.decrypt( KEY, "BecauseOfFortify".getBytes(), encryptedString );
        Assert.assertEquals( toEncrypt, actual ); 
    }
   
	
    @Test( expected = EncryptionException.class )
    @Suite( groups = "checkintest" )
    public void testEncryptDecryptCDS( )
        throws Exception
    {
        String toEncrypt = "password";
        encryptedString = CipherUtilities.encrypt( toEncrypt, KEY, new byte[16] );
        Assert.assertNotNull( encryptedString );
        // Should throw exception because the initialization vectors don't match
        String actual = CipherUtilities.decrypt( KEY, "BecauseOfFortify".getBytes(), encryptedString );
    }
    
    
    @Test
    @Suite( groups = "checkintest", order = 3 )
    public void testDecrypt( )
        throws Exception
    {
        String expected = "password";
        String actual = CipherUtilities.decrypt( key, encryptedString );
        Assert.assertEquals( expected, actual );
    }

    
    @Test
    @Suite( groups = "checkintest", order = 4 )
    public void testUseSerializedKey( )
        throws Exception
    {
        String encryptedPwd = "{AES}sEbIutwx816M2d4SueIRmw\\=\\=\r\n";

        File file = new File( CIPHER_SECRET_KEY_SER );
        ObjectInputStream in = new ObjectInputStream( new FileInputStream( file ) );
        javax.crypto.spec.SecretKeySpec savedKey = ( javax.crypto.spec.SecretKeySpec )in.readObject();
        in.close();

        String actual = CipherUtilities.decrypt( savedKey, encryptedPwd );
        Assert.assertEquals( "password", actual );
    }


    @Test
    @Suite( groups = "checkintest", order = 5 )
    public void testLoadPlainTextProperties( )
        throws Exception
    {
        File file = new File( CIPHER_SECRET_KEY_SER );
        ObjectInputStream in = new ObjectInputStream( new FileInputStream( file ) );
        javax.crypto.spec.SecretKeySpec savedKey = ( javax.crypto.spec.SecretKeySpec )in.readObject();
        in.close();

        InputStream propInputStream = new FileInputStream( CHIPHER_PLAIN_TEXT_PROPERTIES );
        Properties props = new Properties();
        props.load( propInputStream );
        propInputStream.close();

        Enumeration<?> e = props.propertyNames();
        String encryptedPwd = null;
        while ( e.hasMoreElements() )
        {
            String propKey = ( String )e.nextElement();
            if ( propKey.endsWith( ".password" ) )
            {
                String propValue = props.getProperty( propKey );
                encryptedPwd = CipherUtilities.encrypt( savedKey, propValue );
                props.setProperty( propKey, encryptedPwd );
            }
        }

        props.store( new FileOutputStream( new File( CIPHER_ENCRYPTED_PROPERTIES ) ), "its all good" );
        
        // check that password has been encrypted
        Properties puAfterEncription = new Properties();
        puAfterEncription.load(new FileInputStream( CIPHER_ENCRYPTED_PROPERTIES ));
        Assert.assertTrue(puAfterEncription.getProperty("cds.test.jdbc.password").startsWith( encryptedPwd ));
    }


    @Test
    @Suite( groups = "checkintest", order = 6 )
    public void testLoadEncryptedTextProperties( )
        throws Exception
    {
        File file = new File( CIPHER_SECRET_KEY_SER );
        ObjectInputStream in = new ObjectInputStream( new FileInputStream( file ) );
        javax.crypto.spec.SecretKeySpec savedKey = ( javax.crypto.spec.SecretKeySpec )in.readObject();
        in.close();

        InputStream propInputStream = new FileInputStream( CIPHER_ENCRYPTED_PROPERTIES );
        Properties props = new Properties();
        props.load( propInputStream );
        propInputStream.close();

        Enumeration<?> e = props.propertyNames();
        while ( e.hasMoreElements() )
        {
            String propKey = ( String )e.nextElement();
            if ( propKey.endsWith( ".password" ) )
            {
                String propValue = props.getProperty( propKey );
                String decrypted = CipherUtilities.decrypt( savedKey, propValue );
                props.setProperty( propKey, decrypted );
                Assert.assertEquals( "password", decrypted );
            }
        }
    }


    @Test
    @Suite( groups = "slowtest", order = 7 )
    public void testLoadPlainTextPropertiesUsingKeyStore( )
        throws Exception
    {
        javax.crypto.spec.SecretKeySpec savedKey = CipherUtilities.configureKeyStore( CIPHER_KEYSTORE, "shutup" );

        InputStream propInputStream = new FileInputStream( CHIPHER_PLAIN_TEXT_PROPERTIES );
        Properties props = new Properties();
        props.load( propInputStream );
        propInputStream.close();

        Enumeration<?> e = props.propertyNames();
        while ( e.hasMoreElements() )
        {
            String propKey = ( String )e.nextElement();
            if ( propKey.endsWith( ".password" ) )
            {
                String propValue = props.getProperty( propKey );
                props.setProperty( propKey, CipherUtilities.encrypt( savedKey, propValue ) );
            }
        }

        props.store( new FileOutputStream( new File( CIPHER_ENCRYPTED_KEY_STORE_PROPERTIES ) ), "its all good" );
        
        // check that password has been encrypted
        Properties puAfterEncription = new Properties();
        puAfterEncription.load(new FileInputStream( CIPHER_ENCRYPTED_KEY_STORE_PROPERTIES ));
        Assert.assertTrue(puAfterEncription.getProperty("cds.test.jdbc.password").startsWith( "{AES}" ));
    }


    @Test
    @Suite( groups = "slowtest", order = 8 )
    public void testLoadEncryptedTextPropertiesUsingKeyStore( )
        throws Exception
    {

        javax.crypto.spec.SecretKeySpec savedKey = CipherUtilities.configureKeyStore( CIPHER_KEYSTORE, "shutup" );

        InputStream propInputStream = new FileInputStream( CIPHER_ENCRYPTED_KEY_STORE_PROPERTIES );
        Properties props = new Properties();
        props.load( propInputStream );
        propInputStream.close();

        Enumeration<?> e = props.propertyNames();
        while ( e.hasMoreElements() )
        {
            String propKey = ( String )e.nextElement();
            if ( propKey.endsWith( ".password" ) )
            {
                String propValue = props.getProperty( propKey );
                props.setProperty( propKey, CipherUtilities.decrypt( savedKey, propValue ) );
            }
        }

        props.store( new FileOutputStream( new File( OUT_KEY_STORE_PROPERTIES ) ), "its all really good" );
        // check that password has been encrypted
        Properties puAfterEncription = new Properties();
        puAfterEncription.load(new FileInputStream( CIPHER_ENCRYPTED_KEY_STORE_PROPERTIES ));
        Assert.assertTrue(puAfterEncription.getProperty("cds.test.jdbc.password").startsWith( "{AES}" ));
    }
    
    @Test
    @Suite( groups = "checkintest" )
    public void testEncryptPassword( )
        throws Exception
    {
        String toEncrypt = "Gwi45f7a2Wsjkrmn";
        String encryptedString = CipherUtilities.encrypt( toEncrypt, EKEY );
        Assert.assertNotNull( encryptedString );
        String encryptedString2 = CipherUtilities.encrypt( toEncrypt, EKEY, new byte[16] );
        Assert.assertNotNull( encryptedString2 );
        String encryptedString3 = CipherUtilities.encrypt( toEncrypt, EKEY, "BecauseOfFortify".getBytes() );
        Assert.assertNotNull( encryptedString2 );
        //Only equal when CipherUtilities uses a matching iv
//        Assert.assertEquals( encryptedString, encryptedString2 );            
    }
    
    
    @Test
    @Suite( groups = "checkintest" )
    public void testDecryptPassword( )
        throws Exception
    {
        String toDecrypt = "zWB7wxHzC4zRfGY1CSzDncb8V5TX6XmBJp01HuGbNoU=";
        String actual = CipherUtilities.decrypt( EKEY, toDecrypt );
        Assert.assertNotNull( actual );
        String actual2 = CipherUtilities.decrypt( EKEY, new byte[16], toDecrypt );
        Assert.assertNotNull( actual2 );
        //Only equal when CipherUtilities uses a matching iv
//        Assert.assertEquals( actual, actual2 );            
    }
    

    
}
