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;
		}
	}
}