using System;
using System.Text;
using CryptoSysPKI;

/* 
**************************** COPYRIGHT NOTICE****************************
* Copyright (C) 2002-5 DI Management Services Pty Limited. 
* All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>

*   Last updated:
*   $Date: 2005/08/24 15:53:00 $

* This code is provided as a suggested C# interface to the CryptoSys PKI.
* Use at your own risk. Make your own tests and check that this code
* does what you expect. Please report any bugs to <www.di-mgt.com.au>
* This copyright notice must always be left intact.
************************ END OF COPYRIGHT NOTICE*************************
*/
namespace RsaExample
{
    /// <summary>
    /// Example code to demonstrate RSA methods:
    /// RsaEncryptBytes,
    /// RsaDecryptBytes,
    /// RsaSignText, and
    /// RsaVerifySignedText.
    /// </summary>
    class ShowExample
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            ShowEncryptAndDecrypt();
            ShowSignAndVerify();
        }

        static void ShowEncryptAndDecrypt()
        {
            string certFile;
            string priKeyFile;
            byte[] outputData;
            byte[] inputData;
            StringBuilder sbPassword;

            // ENCRYPTION
            // Required input:
            // (a) // byte array of "plaintext"
            inputData = System.Text.Encoding.Default.GetBytes("Hello world!");
            // (b) // Certificate (which has recipient's public key)
            certFile = "BobRSASignByCarl.cer";
            // Output: byte array of "ciphertext"
            outputData = RsaEncryptBytes(inputData, certFile);
            // Display output in hex format
            Console.WriteLine("ENC={0}", Cnv.ToHex(outputData));

            // DECRYPTION
            // Required input:
            // (a) // byte array of "ciphertext"
            inputData = outputData;
            // (b) // file containing encrypted private key
            priKeyFile = "BobPrivRSAEncrypt.epk";
            // (c) password for encrypted private key
            // NB we use a StringBuilder for security reasons
            sbPassword  = new StringBuilder("password");

            // Output: byte array of "plaintext"
            outputData = RsaDecryptBytes(inputData, priKeyFile, sbPassword.ToString());
            // Display output in hex format
            Console.WriteLine("MSG(hex) ={0}", Cnv.ToHex(outputData));
            // Convert back to a string (assuming we are expecting a string)
            Console.WriteLine("MSG(Ansi)={0}", System.Text.Encoding.Default.GetString(outputData));

            // Finally, wipe the password string
            Wipe.String(sbPassword);
        }

        static void ShowSignAndVerify()
        {
            string certFile;
            string priKeyFile;
            string messageText;
            byte[] inputData;
            byte[] outputData;
            StringBuilder sbPassword;

            // SIGNING
            // Required input:
            // (a) text to be signed
            messageText = "Hello world!";
            // (b) file containing encrypted private key
            priKeyFile = "AlicePrivRSASign.epk";
            // (c) password
            // NB we use a StringBuilder for security reasons
            sbPassword = new StringBuilder("password");

            // Output: signature block byte array
            outputData = RsaSignText(messageText, priKeyFile, sbPassword.ToString());
            // Display output in hex format
            Console.WriteLine("SIG={0}", Cnv.ToHex(outputData));

            // VERIFICATION
            // Required input:
            // (a) the RSA signature block
            inputData = outputData;
            // (b) Certificate (which has signer's public key)
            certFile =  "AliceRSASignByCarl.cer";
            // (c) Copy of text that has been signed

            // Output: verified or not
            bool isok = RsaVerifySignedText(inputData, certFile, messageText);
            Console.WriteLine("Verification {0}", (isok ? "OK" : "FAILED!"));

            // Finally, wipe the password string
            Wipe.String(sbPassword);
        }

        /// <summary>
        /// Encrypts a message using RSA key from recipient's X.509 certificate
        /// </summary>
        /// <param name="inputData">Message in byte array</param>
        /// <param name="certFile">Name of X.509 certificate file</param>
        /// <returns>Encrypted data as byte array; or empty array if error</returns>
        /// <remarks>Message must be at least xx bytes shorter than modulus length</remarks>
        public static byte[] RsaEncryptBytes(byte[] inputData, string certFile)
        {
            StringBuilder sbPublicKey;
            byte[] block;
            byte[] outputData;
            int klen;
            // 1. Read the public key from the recipient's X.509 certificate
            sbPublicKey = Rsa.GetPublicKeyFromCert(certFile);
            if (sbPublicKey.Length == 0)
            {
                Console.WriteLine("ERROR reading certificate");
                return new byte[0];
            }
            // 2. Make an RSA data block of same length in bytes as key
            // using EME-PKCS1-v1_5 encoding
            klen = Rsa.KeyBytes(sbPublicKey.ToString());
            if (klen <= 0)
            {
                Console.WriteLine("ERROR: invalid public key");
                return new byte[0];
            }
            block = Rsa.EncodeMsg(klen, inputData, Rsa.EncodeFor.Encryption);
            // 3. Encrypt with RSA public key
            outputData = Rsa.RawPublic(block, sbPublicKey.ToString());
            if (outputData.Length == 0)
            {
                Console.WriteLine("ERROR: failed to encrypt");
                return new byte[0];
            }
            // 4. Clean up
            Wipe.Data(block);
            Wipe.String(sbPublicKey);

            // 5. Output "ciphertext"
            return outputData;
        }
        /// <summary>
        /// Decrypt RSA-encrypted message using key from recipient's private key file
        /// </summary>
        /// <param name="inputData">Encrypted data block</param>
        /// <param name="priKeyFile">Name of recipient's private key file</param>
        /// <param name="password">Password for encrypted private key</param>
        /// <returns>Decrypted message data; or an empty array if error</returns>
        public static byte[] RsaDecryptBytes(byte[] inputData, string priKeyFile, string password)
        {
            StringBuilder sbPrivateKey;
            byte[] block;
            byte[] outputData;

            // 1. Read in the private key from the encrypted key file
            sbPrivateKey = Rsa.ReadEncPrivateKey(priKeyFile, password);
            if (sbPrivateKey.ToString().Length == 0)
            {
                Console.WriteLine("ERROR reading private key file");
                return new byte[0];
            }
            // 2. Decrypt with private key
            block = Rsa.RawPrivate(inputData, sbPrivateKey.ToString());
            if (block.Length == 0)
            {
                Console.WriteLine("Decryption error");
                return new byte[0];
            }
            // 3. Extract the message from the encryption block
            outputData = Rsa.DecodeMsg(block, Rsa.EncodeFor.Encryption);
            if (outputData.Length == 0)
            {
                Console.WriteLine("Decryption error");
                return new byte[0];
            }
            // 4. Clean up - NB should do this on error, too.
            Wipe.Data(block);
            Wipe.String(sbPrivateKey);

            // 5. Output "plaintext"
            return outputData;
        }


        /// <summary>
        /// Signs a message using the sender's private key
        /// </summary>
        /// <param name="messageText">Text of message to be signed</param>
        /// <param name="priKeyFile">Name of sender's private key file</param>
        /// <param name="password">Password for encrypyed private key file</param>
        /// <returns>Signature block; or an empty array if error</returns>
        public static byte[] RsaSignText(string messageText, string priKeyFile, string password)
        {
            StringBuilder sbPrivateKey;
            byte[] msg;
            byte[] block;
            byte[] outputData;
            int klen;

            // 1. Read in the private key from the encrypted key file
            sbPrivateKey = Rsa.ReadEncPrivateKey(priKeyFile, password);
            if (sbPrivateKey.Length == 0)
            {
                Console.WriteLine("ERROR reading private key file");
                return new byte[0];
            }

            // 2. Convert message string to byte array
            msg = System.Text.Encoding.Default.GetBytes(messageText);

            // 3. Make an RSA data block of same length in bytes as key
            // using EMSA-PKCS1-v1_5 encoding
            klen = Rsa.KeyBytes(sbPrivateKey.ToString());
            if (klen <= 0)
            {
                Console.WriteLine("ERROR reading private key file");
                return new byte[0];
            }
            block = Rsa.EncodeMsg(klen, msg, Rsa.EncodeFor.Signature);
            if (block.Length == 0)
            {
                Console.WriteLine("ERROR: could not encode data block");
                return new byte[0];
            }

            // 4. Sign with RSA private key
            outputData = Rsa.RawPrivate(block, sbPrivateKey.ToString());

            // 5. Clear up
            Wipe.Data(block);
            Wipe.Data(msg);
            Wipe.String(sbPrivateKey);

            // 6. Output signature block
            return outputData;
        }

        /// <summary>
        /// Verifies whether or not an RSA signature is valid
        /// </summary>
        /// <param name="sigBlock">RSA signature block</param>
        /// <param name="certFile">Name of sender's X.509 certificate file</param>
        /// <param name="messageText">Text of message that has been signed</param>
        /// <returns><b>true</b> if signature is valid; <b>false</b> otherwise</returns>
        public static bool RsaVerifySignedText(byte[] sigBlock, string certFile, string messageText)
        {
            StringBuilder sbPublicKey;
            byte[] msg;
            byte[] block;
            byte[] bcheck;
            int klen;
            bool isok;

            // 1. Read the public key from the recipient's X.509 certificate
            sbPublicKey = Rsa.GetPublicKeyFromCert(certFile);
            if (sbPublicKey.Length == 0)
            {
                Console.WriteLine("ERROR reading certificate");
                return false;
            }
            // 2. Check that signature block and key length are identical
            klen = Rsa.KeyBytes(sbPublicKey.ToString());
            if (klen != sigBlock.Length)
            {
                Console.WriteLine("ERROR: RSA signature and key are different lengths");
                return false;
            }

            // 3. Decrypt signature block with RSA public key
            block = Rsa.RawPublic(sigBlock, sbPublicKey.ToString());

            // 4. Create an independent Encoded block from message
            msg = System.Text.Encoding.Default.GetBytes(messageText);
            bcheck = Rsa.EncodeMsg(klen, msg, Rsa.EncodeFor.Signature);
            // And compare to see if they are the same
            // using a home-grown byte comparison function
            isok = ByteArraysAreEqual(bcheck, block);

            // 5. Clean up
            Wipe.Data(bcheck);
            Wipe.Data(block);
            Wipe.Data(msg);
            Wipe.String(sbPublicKey);

            // 6. Output whether verified or not
            return isok;
        }
            
        private static bool ByteArraysAreEqual(byte[] data1, byte[] data2)
        {   // Thanks to Jon Skeet http://www.pobox.com/~skeet
            // If both are null, they're equal
            if (data1==null && data2==null)
            {
                return true;
            }
            // If either but not both are null, they're not equal
            if (data1==null || data2==null)
            {
                return false;
            }
            if (data1.Length != data2.Length)
            {
                return false;
            }
            for (int i=0; i < data1.Length; i++)
            {
                if (data1[i] != data2[i])
                {
                    return false;
                }
            }
            return true;
        }
    }
}