package gnu.testlet.gnu.crypto.jce;

// --------------------------------------------------------------------------
// $Id: TestOfCipher.java,v 1.7 2005/10/06 04:24:19 rsdio Exp $
//
// Copyright (C) 2002, 2003 Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License or (at your
// option) any later version.
//
// GNU Crypto 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 General Public License
// along with this program; see the file COPYING.  If not, write to the
//
//    Free Software Foundation Inc.,
//    51 Franklin Street, Fifth Floor,
//    Boston, MA 02110-1301
//    USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module.  An independent module is a module which is
// not derived from or based on this library.  If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so.  If you do not wish to
// do so, delete this exception statement from your version.
// --------------------------------------------------------------------------

// Tags: GNU-CRYPTO

import gnu.crypto.Registry;
import gnu.crypto.cipher.CipherFactory;
import gnu.crypto.cipher.IBlockCipher;
import gnu.crypto.jce.GnuCrypto;
import gnu.crypto.mode.IMode;
import gnu.crypto.mode.ModeFactory;
import gnu.crypto.pad.IPad;
import gnu.crypto.pad.PadFactory;
import gnu.testlet.TestHarness;
import gnu.testlet.Testlet;

import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * <p>Conformance tests for the JCE Provider implementations of the Cipher SPI
 * classes.</p>
 *
 * @version $Revision: 1.7 $
 */
public class TestOfCipher implements Testlet {

   // Constants and variables.
   // -----------------------------------------------------------------------

   // Constructors.
   // -----------------------------------------------------------------------

   // default 0-arguments constructor

   // Class methods.
   // -----------------------------------------------------------------------

   // Instance methods.
   // -----------------------------------------------------------------------

   public void test(TestHarness harness) {
      setUp();

      testUnknownCipher(harness);
      testEquality(harness);
      testPadding(harness);
      testPartial(harness);
      testDoFinal(harness);
   }

   /** Should fail with an unknown algorithm. */
   public void testUnknownCipher(TestHarness harness) {
      harness.checkPoint("testUnknownCipher");
      try {
         Cipher.getInstance("Godot", Registry.GNU_CRYPTO);
         harness.fail("testUnknownCipher()");
      } catch (Exception x) {
         harness.check(true);
      }
   }

   /**
    * Tests if the result of using a cipher through gnu.crypto Factory classes
    * yields same value as using instances obtained the JCE way.
    */
   public void testEquality(TestHarness harness) {
      harness.checkPoint("testEquality");
      String cipherName = null, modeName;
      IMode gnu = null;
      Cipher jce = null;
      HashMap attrib = new HashMap();
      byte[] pt = null;
//      byte[] iv = null;
      byte[] ct1 = null, ct2 = null;
      byte[] cpt1 = null, cpt2 = null;
      Iterator ci, mi;
      int bs;
      try {
         for (ci = CipherFactory.getNames().iterator(); ci.hasNext(); ) {
            cipherName = (String) ci.next();
            IBlockCipher cipher = CipherFactory.getInstance(cipherName);
            bs = cipher.defaultBlockSize();
            for (mi = ModeFactory.getNames().iterator(); mi.hasNext(); ) {
               modeName = (String) mi.next();
               gnu = ModeFactory.getInstance(modeName, cipher, bs);
               jce = Cipher.getInstance(cipherName + "/" + modeName
                  + "/NoPadding", Registry.GNU_CRYPTO);
               pt = new byte[bs];
               for (int i = 0; i < bs; i++) {
                  pt[i] = (byte) i;
               }
               attrib.put(IBlockCipher.CIPHER_BLOCK_SIZE, new Integer(bs));
               attrib.put(IMode.IV, pt);
               for (Iterator ks = cipher.keySizes(); ks.hasNext(); ) {
                  byte[] kb = new byte[((Integer) ks.next()).intValue()];
                  for (int i = 0; i < kb.length; i++) {
                     kb[i] = (byte) i;
                  }
                  attrib.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
                  attrib.put(IBlockCipher.KEY_MATERIAL, kb);
                  gnu.reset();
                  gnu.init(attrib);
                  ct1 = new byte[bs];
                  gnu.update(pt, 0, ct1, 0);
                  jce.init(Cipher.ENCRYPT_MODE,
                     new SecretKeySpec(kb, cipherName),
                     new IvParameterSpec(pt));
                  ct2 = new byte[bs];
                  jce.doFinal(pt, 0, bs, ct2, 0);
                  harness.check(Arrays.equals(ct1, ct2), "testEquality("+cipherName+")");

                  attrib.put(IMode.STATE, new Integer(IMode.DECRYPTION));
                  cpt1 = new byte[bs];
                  gnu.reset();
                  gnu.init(attrib);
                  gnu.update(ct1, 0, cpt1, 0);
                  harness.check(Arrays.equals(pt, cpt1), "testEquality("+cipherName+")");

                  jce.init(Cipher.DECRYPT_MODE,
                     new SecretKeySpec(kb, cipherName),
                     new IvParameterSpec(pt));
                  cpt2 = new byte[bs];
                  jce.doFinal(ct2, 0, bs, cpt2, 0);
                  harness.check(Arrays.equals(pt, cpt2), "testEquality("+cipherName+")");

                  harness.check(Arrays.equals(cpt1, cpt2), "testEquality("+cipherName+")");
               }
            }
         }
      } catch (Exception x) {
         harness.debug(x);
         harness.fail("testEquality("+cipherName+"): " + String.valueOf(x));
      }
   }

   /**
    * Test that the padding results in the same cipher/plaintexts for instances
    * derived from both the GNU Factory and the JCE one.
    */
   public void testPadding(TestHarness harness) {
      harness.checkPoint("testPadding");
      String padName = null;
      IMode gnu = ModeFactory.getInstance("ECB", "AES", 16);
      IPad pad;
      Cipher jce;
      byte[] kb = new byte[32];
      for (int i = 0; i < kb.length; i++) {
         kb[i] = (byte) i;
      }
      byte[] pt = new byte[42];
      for (int i = 0; i < pt.length; i++) {
         pt[i] = (byte) i;
      }
      byte[] ppt = new byte[48]; // padded plaintext.
      System.arraycopy(pt, 0, ppt, 0, 42);
      byte[] ct1 = new byte[48], ct2 = new byte[48];
      byte[] cpt1 = new byte[42], cpt2 = new byte[42];
      HashMap attrib = new HashMap();
      attrib.put(IBlockCipher.KEY_MATERIAL, kb);
      try {
         for (Iterator it = PadFactory.getNames().iterator(); it.hasNext(); ) {
            padName = (String) it.next();
            // skip EME-PKCS1-V1.5 padding since it's not a true block cipher
            // padding algorithm
            if (padName.equalsIgnoreCase(Registry.EME_PKCS1_V1_5_PAD)) {
               continue;
            }
            pad = PadFactory.getInstance(padName);
            pad.reset();
            pad.init(16);

            byte[] padding = pad.pad(pt, 0, pt.length);
            System.arraycopy(padding, 0, ppt, 42, padding.length);
            attrib.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
            gnu.reset();
            gnu.init(attrib);
            for (int i = 0; i < ppt.length; i += 16) {
               gnu.update(ppt, i, ct1, i);
            }

            jce = Cipher.getInstance("AES/ECB/" + padName, Registry.GNU_CRYPTO);
            jce.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kb, "AES"));
            jce.doFinal(pt, 0, pt.length, ct2, 0);

            harness.check(Arrays.equals(ct1, ct2), "testPadding("+padName+")");

            attrib.put(IMode.STATE, new Integer(IMode.DECRYPTION));
            gnu.reset();
            gnu.init(attrib);
            byte[] pcpt = new byte[48];
            for (int i = 0; i < ct1.length; i += 16) {
               gnu.update(ct1, i, pcpt, i);
            }
            int trim = pad.unpad(pcpt, 0, pcpt.length);
            System.arraycopy(pcpt, 0, cpt1, 0, pcpt.length-trim);

            jce.init(Cipher.DECRYPT_MODE, new SecretKeySpec(kb, "AES"));
            jce.doFinal(ct2, 0, ct2.length, cpt2, 0);

            harness.check(Arrays.equals(cpt1, cpt2), "testPadding("+padName+")");

            harness.check(Arrays.equals(cpt1, pt), "testPadding("+padName+")");
         }
      } catch (Exception x) {
         harness.debug(x);
         harness.fail("testPadding("+padName+"): " + String.valueOf(x));
      }
   }

   /** Test the update() methods with incomplete blocks. */
   public void testPartial(TestHarness harness) {
      harness.checkPoint("testPartial");
      String cipherName = null;
      Cipher full, part1, part2;
      IBlockCipher gnu;
      byte[] pt;
      byte[] kb;
      byte[] ct1, ct2, ct3, ct4;
      int i, blockSize;
      try {
         for (Iterator it = CipherFactory.getNames().iterator(); it.hasNext(); ) {
            cipherName = (String) it.next();
            gnu = CipherFactory.getInstance(cipherName);
            full = Cipher.getInstance(cipherName, Registry.GNU_CRYPTO);
            part1 = Cipher.getInstance(cipherName, Registry.GNU_CRYPTO);
            part2 = Cipher.getInstance(cipherName, Registry.GNU_CRYPTO);
//            pt = new byte[gnu.defaultBlockSize()];
            blockSize = gnu.defaultBlockSize();
            pt = new byte[2 * blockSize];
            for (i = 0; i < pt.length; i++) {
               pt[i] = (byte) i;
            }
            kb = new byte[gnu.defaultKeySize()];
            for (i = 0; i < kb.length; i++) {
               kb[i] = (byte) i;
            }
            full.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kb, cipherName));
//            ct1 = full.doFinal(pt);
            ct1 = full.doFinal(pt, blockSize, blockSize);

            part1.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kb, cipherName));
//            for (i = 0; i < pt.length - 1; i++) {
            for (i = blockSize; i < pt.length - 1; i++) {
               part1.update(pt, i, 1);
            }
            ct2 = part1.doFinal(pt, i, 1);

            harness.check(Arrays.equals(ct1, ct2), "testPartial1("+cipherName+")");

            // this is tricky: only the update of the last byte should return
            // a full block.  also, the doFinal() should return an empty byte[]
            part2.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kb, cipherName));
//            for (i = 0; i < pt.length - 1; i++) {
            for (i = blockSize; i < pt.length - 1; i++) {
               part2.update(pt, i, 1);
            }
            ct3 = part2.update(pt, i, 1);
            harness.check(Arrays.equals(ct3, ct2), "testPartial2("+cipherName+")");

            ct4 = part2.doFinal();
            harness.check(ct4 != null && ct4.length == 0, "testPartial3("+cipherName+")");
         }
      } catch (Exception x) {
         harness.debug(x);
         harness.fail("testPartial("+cipherName+"): " + String.valueOf(x));
      }
   }

   /** doFinal() with a short block and no padding should invariably fail. */
   public void testDoFinal(TestHarness harness) {
      harness.checkPoint("testDoFinal");
      String cipherName = null;
      Cipher jce;
      IBlockCipher gnu;
      byte[] pt;
      byte[] kb;
      Iterator it;
      for (it = CipherFactory.getNames().iterator(); it.hasNext(); ) {
         try {
            cipherName = (String) it.next();
            gnu = CipherFactory.getInstance(cipherName);
            jce = Cipher.getInstance(cipherName, Registry.GNU_CRYPTO);
            pt = new byte[gnu.defaultBlockSize() - 1];
            for (int i = 0; i < pt.length; i++) {
               pt[i] = (byte) i;
            }
            kb = new byte[gnu.defaultKeySize()];
            for (int i = 0; i < kb.length; i++) {
               kb[i] = (byte) i;
            }
            jce.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kb, cipherName));
            jce.doFinal(pt);
            harness.fail("testDoFinal("+cipherName+")");
         } catch (IllegalBlockSizeException ibse) {
            harness.check(true, "testDoFinal("+cipherName+")");
         } catch (Exception x) {
            harness.debug(x);
            harness.fail("testDoFinal("+cipherName+"): " + String.valueOf(x));
         }
      }
   }

   // helper methods ----------------------------------------------------------

   private void setUp() {
      Security.addProvider(new GnuCrypto());
   }
}
