// Based on http://www.thecodeproject.com/useritems/Porting_Java_Public_Key.asp
// Parameters:
//    public_key - X.509 standard SubjectPublicKeyInfo key in binary format
//    data_file - the data file whose signature to verify
//    signature - the signature of data_file in binary format

/** <p>Title: RSA Security</p>
* Description:	This class generates a RSA private and public key, reinstantiates
* the keys	from the corresponding key files.It	also generates compatible .Net Public Key,
* which we	will read later	in C# program using	.Net Securtiy Framework
* The reinstantiated keys are used	to sign	and	verify the given data.</p>
*
* @author Shaheryar
* @version	1.0
*/

import java.security.*;
import java.security.spec.*;
import java.io.*;
import java.security.interfaces.*;
import java.security.cert.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;

public class VerifySignature {

	private KeyPairGenerator keyGen; //Key pair generator	for	RSA
	public PrivateKey	privateKey;	// Private Key Class
	public PublicKey publicKey; // Public	Key	Class
	public KeyPair keypair; // KeyPair Class
	private Signature	sign; // Signature,	used to	sign the data
	/**
	* Default Constructor. Instantiates the key paths and signature algorithm.
	*/
	public VerifySignature() {
		try	{

			//Get	the	instance of	Signature Engine.
			sign = Signature.getInstance("SHA1withRSA");
		}
		catch (NoSuchAlgorithmException	nsa) {
			System.out.println(""	+ nsa.getMessage());
		}
	}

	/**
	* Initializes the public	and	private	keys.
	*/
	private void initializePublicKey(String publickey_fn)	{
		try	{
			//Read key files back	and	decode them	from BASE64
			byte[] publicKeyBytes	= readKeyBytesFromFile(publickey_fn);

			// Convert back to public	and	private	key	objects
			KeyFactory keyFactory	= KeyFactory.getInstance("RSA");
			EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
			publicKey	= keyFactory.generatePublic(publicKeySpec);

		}
		catch (IOException io) {
			System.out.println(
				"Public/ Private Key File	Not	found."+ io.getCause());
		}
		catch (InvalidKeySpecException e) {
			System.out.println(
				"Invalid Key Specs. Not valid	Key	files."+ e.getCause());
		}
		catch (NoSuchAlgorithmException	e) {
			System.out.println(
				"There is	no such	algorithm. Please check	the	JDK	ver."+ e.getCause());
		}
	}

	/**
	* Verifies the signature	for	the	given bytes	using the public key.
	* @param	signature Signature
	* @param	data Data that was signed
	* @return boolean True if valid signature else false
	*/
	public boolean verifySignature(String publickey_fn, byte[]	signature, byte[] data)	{
		try	{
			initializePublicKey(publickey_fn);
			sign.initVerify(publicKey);
			sign.update(data);
			return sign.verify(signature);
		}
		catch (SignatureException e) {
			e.printStackTrace();
		}
		catch (InvalidKeyException e) {
		}

		return false;
	}

	/**
	* Returns the contents of the file in a byte	array.
	* @param	fileName File Name
	* @return byte[]	Teh	data read from a given file	as a byte array.
	*/
	private byte[] readKeyBytesFromFile(String fileName) throws IOException{
		File file	= new File(fileName);
		InputStream is = new FileInputStream(file);

		// Get the size of the file
		long length =	file.length();

		// You cannot	create an array	using a	long type.
		// It	needs to be	an int type.
		// Before	converting to an int type, check
		// to	ensure that	file is	not	larger than	Integer.MAX_VALUE.
		if (length > Integer.MAX_VALUE) {
			// File	is too large
		}

		// Create	the	byte array to hold the data
		byte[] bytes = new byte[ (int) length];

		// Read in the bytes
		int offset = 0;
		int numRead =	0;
		while	(offset	< bytes.length
			&&	(numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)	{
				offset += numRead;
			}

			// Ensure	all	the	bytes have been	read in
			if (offset < bytes.length) {
				throw new IOException("Key File	Error: Could not completely	read file "	+ file.getName());
			}

			// Close the input stream	and	return bytes
			is.close();
			return bytes;


	}


	public static	void main(String args[])
	{
		VerifySignature	sm = new VerifySignature();

		/*
		Uncomment next line	for	first time when	you	run	the	code,it	will generate the keys.
		Afterwards,the application will	read the generated key files from the given	location.
		If you want	to generate	the	key	files each time, then you should keep it uncommented always.
		*/
		if(	args.length	!= 3 )
		{
			System.out.println("\nUsage:    RSAPubKeyData.exe publickey_fn data_fn signature_fn");
			return;
		}


		String publickeyfn = args[0];
		String datafn = args[1];
		String signaturefn = args[2];


		byte[] data	= sm.readBytesFromFile(datafn);
		byte[] signature = sm.readBytesFromFile(signaturefn);

		if( sm.verifySignature(publickeyfn,signature,data) )
		{
			System.out.println("The signature is valid.");
		}
		else
		{
			System.out.println("The signature is not valid.");
		}
	}

	private byte[] readBytesFromFile(String fileName)
	{
		try
		{
			File file	= new File(fileName);
			InputStream is = new FileInputStream(file);

			// Get the size of the file
			long length =	file.length();

			// You cannot	create an array	using a	long type.
			// It	needs to be	an int type.
			// Before	converting to an int type, check
			// to	ensure that	file is	not	larger than	Integer.MAX_VALUE.
			if (length > Integer.MAX_VALUE) {
				// File	is too large
			}

			// Create	the	byte array to hold the data
			byte[] bytes = new byte[ (int) length];

			// Read in the bytes
			int offset = 0;
			int numRead =	0;
			while	(offset	< bytes.length
				&&	(numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)	{
					offset += numRead;
				}

				// Ensure	all	the	bytes have been	read in
				if (offset < bytes.length) {
					throw new IOException("Key File	Error: Could not completely	read file "	+ file.getName());
				}

				// Close the input stream	and	return bytes
				is.close();
				return bytes;
		}catch(IOException ioe)
		{
			System.out.println("Exception	occured	while writing file"+ioe.getMessage());
		}
		byte[] bytes = new byte[ 1];
		return bytes; 
	}
}