package org.bouncycastle.jce.provider.test;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.SimpleTestResult;
import org.bouncycastle.util.test.Test;
import org.bouncycastle.util.test.TestResult;

public class RSATest
    implements Test
{
    /**
     * a fake random number generator - we just want to make sure the random numbers
     * aren't random so that we get the same output, while still getting to test the
     * key generation facilities.
     */
    private class FixedSecureRandom
        extends SecureRandom
    {
        byte[]  seed = {
                (byte)0xaa, (byte)0xfd, (byte)0x12, (byte)0xf6, (byte)0x59,
                (byte)0xca, (byte)0xe6, (byte)0x34, (byte)0x89, (byte)0xb4,
                (byte)0x79, (byte)0xe5, (byte)0x07, (byte)0x6d, (byte)0xde,
                (byte)0xc2, (byte)0xf0, (byte)0x6c, (byte)0xb5, (byte)0x8f
        };

        public void nextBytes(
            byte[]  bytes)
        {
            int offset = 0;

            while ((offset + seed.length) < bytes.length)
            {
                System.arraycopy(seed, 0, bytes, offset, seed.length);
                offset += seed.length;
            }

            System.arraycopy(seed, 0, bytes, offset, bytes.length - offset);
        }
    }

    private boolean arrayEquals(
        byte[]  a,
        byte[]  b)
    {
        if (a.length != b.length)
        {
            return false;
        }

        for (int i = 0; i != a.length; i++)
        {
            if (a[i] != b[i])
            {
                return false;
            }
        }

        return true;
    }


    private RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
        new BigInteger("11", 16));

    private RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
        new BigInteger("b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7", 16),
        new BigInteger("11", 16),
        new BigInteger("9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89", 16),
        new BigInteger("c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16),
        new BigInteger("f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16),
        new BigInteger("b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16),
        new BigInteger("d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16),
        new BigInteger("b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16));

    private RSAPublicKeySpec isoPubKeySpec = new RSAPublicKeySpec(
        new BigInteger("0100000000000000000000000000000000bba2d15dbb303c8a21c5ebbcbae52b7125087920dd7cdf358ea119fd66fb064012ec8ce692f0a0b8e8321b041acd40b7", 16),
        new BigInteger("03", 16));

    private RSAPrivateKeySpec isoPrivKeySpec = new RSAPrivateKeySpec(
        new BigInteger("0100000000000000000000000000000000bba2d15dbb303c8a21c5ebbcbae52b7125087920dd7cdf358ea119fd66fb064012ec8ce692f0a0b8e8321b041acd40b7", 16),
        new BigInteger("2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac9f0783a49dd5f6c5af651f4c9d0dc9281c96a3f16a85f9572d7cc3f2d0f25a9dbf1149e4cdc32273faadd3fda5dcda7", 16));


    public TestResult perform()
    {
        try
        {
            KeyFactory          fact;
            byte[]              input = new byte[]
                                    { (byte)0x54, (byte)0x85, (byte)0x9b, (byte)0x34, (byte)0x2c, (byte)0x49, (byte)0xea, (byte)0x2a };
            byte[][]            output = new byte[][]
                                    {
                                        Hex.decode("8b427f781a2e59dd9def386f1956b996ee07f48c96880e65a368055ed8c0a8831669ef7250b40918b2b1d488547e72c84540e42bd07b03f14e226f04fbc2d929"),
                                        Hex.decode("2ec6e1a1711b6c7b8cd3f6a25db21ab8bb0a5f1d6df2ef375fa708a43997730ffc7c98856dbbe36edddcdd1b2d2a53867d8355af94fea3aeec128da908e08f4c"),
                                        Hex.decode("0850ac4e5a8118323200c8ed1e5aaa3d5e635172553ccac66a8e4153d35c79305c4440f11034ab147fccce21f18a50cf1c0099c08a577eb68237a91042278965"),
                                        Hex.decode("00319bb9becb49f3ed1bca26d0fcf09b0b0a508e4d0bd43b350f959b72cd25b3af47d608fdcd248eada74fbe19990dbeb9bf0da4b4e1200243a14e5cab3f7e610c")
                                    };
            SecureRandom        rand = new FixedSecureRandom();


            fact = KeyFactory.getInstance("RSA", "BC");

            PrivateKey  privKey = fact.generatePrivate(privKeySpec);
            PublicKey   pubKey = fact.generatePublic(pubKeySpec);

            //
            // No Padding
            //
            Cipher c = Cipher.getInstance("RSA", "BC");

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            byte[]  out = c.doFinal(input);

            if (!arrayEquals(out, output[0]))
            {
                return new SimpleTestResult(false, "NoPadding test failed on encrypt expected " + new String(Hex.encode(output[0])) + " got " + new String(Hex.encode(out)));
            }

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, input))
            {
                return new SimpleTestResult(false, "NoPadding test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            // No Padding - incremental
            //
            c = Cipher.getInstance("RSA", "BC");

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            c.update(input);

            out = c.doFinal();

            if (!arrayEquals(out, output[0]))
            {
                return new SimpleTestResult(false, "NoPadding test failed on encrypt expected " + new String(Hex.encode(output[0])) + " got " + new String(Hex.encode(out)));
            }

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, input))
            {
                return new SimpleTestResult(false, "NoPadding test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            // No Padding - maximum length
            //
            c = Cipher.getInstance("RSA", "BC");

            byte[]  modBytes = ((RSAPublicKey)pubKey).getModulus().toByteArray();
            byte[]  maxInput = new byte[modBytes.length - 1];

            maxInput[0] |= 0x7f;

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            out = c.doFinal(maxInput);

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, maxInput))
            {
                return new SimpleTestResult(false, "NoPadding test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            // PKCS1 V 1.5
            //
            c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            out = c.doFinal(input);

            if (!arrayEquals(out, output[1]))
            {
                return new SimpleTestResult(false, "PKCS1 test failed on encrypt expected " + new String(Hex.encode(output[1])) + " got " + new String(Hex.encode(out)));
            }

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, input))
            {
                return new SimpleTestResult(false, "PKCS1 test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            // OAEP
            //
            c = Cipher.getInstance("RSA/NONE/OAEPPadding", "BC");

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            out = c.doFinal(input);

            if (!arrayEquals(out, output[2]))
            {
                return new SimpleTestResult(false, "OAEP test failed on encrypt expected " + new String(Hex.encode(output[2])) + " got " + new String(Hex.encode(out)));
            }

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, input))
            {
                return new SimpleTestResult(false, "OAEP test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            // ISO9796-1
            //
            byte[]      isoInput =  Hex.decode("fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210");
            PrivateKey  isoPrivKey = fact.generatePrivate(isoPrivKeySpec);
            PublicKey   isoPubKey = fact.generatePublic(isoPubKeySpec);

            c = Cipher.getInstance("RSA/NONE/ISO9796-1Padding", "BC");

            c.init(Cipher.ENCRYPT_MODE, isoPrivKey);

            out = c.doFinal(isoInput);

            if (!arrayEquals(out, output[3]))
            {
                return new SimpleTestResult(false, "ISO9796-1 test failed on encrypt expected " + new String(Hex.encode(output[3])) + " got " + new String(Hex.encode(out)));
            }

            c.init(Cipher.DECRYPT_MODE, isoPubKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, isoInput))
            {
                return new SimpleTestResult(false, "ISO9796-1 test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            //
            //
            // generation with parameters test.
            //
            KeyPairGenerator keyPairGen =
                    KeyPairGenerator.getInstance("RSA", "BC");

            //
            // 768 bit RSA with e = 2^16-1
            //
            keyPairGen.initialize(
                new RSAKeyGenParameterSpec(768,
                BigInteger.valueOf(65537)),
                new SecureRandom());

            KeyPair kp = keyPairGen.generateKeyPair();

            pubKey = kp.getPublic();
            privKey = kp.getPrivate();

            c.init(Cipher.ENCRYPT_MODE, pubKey, rand);

            out = c.doFinal(input);

            c.init(Cipher.DECRYPT_MODE, privKey);

            out = c.doFinal(out);

            if (!arrayEquals(out, input))
            {
                return new SimpleTestResult(false, "key generation test failed on decrypt expected " + new String(Hex.encode(input)) + " got " + new String(Hex.encode(out)));
            }

            return new SimpleTestResult(true, getName() + ": Okay");
        }
        catch (Exception e)
        {
            return new SimpleTestResult(false, getName() + ": exception - " + e.toString());
        }
    }

    public String getName()
    {
        return "RSATest";
    }

    public static void main(
        String[]    args)
    {
        Security.addProvider(new BouncyCastleProvider());

        Test            test = new RSATest();
        TestResult      result = test.perform();

        System.out.println(result.toString());
    }
}
