/**
 * Copyright (C) 2013-2015 Regents of the University of California.
 * Authors:
 *		Jeff Thompson <jefft0@remap.ucla.edu>
 *		Rafael Teixeira <monoman@gmail.com>
 * @author: From code in ndn-cxx by Yingdi Yu <yingdi@cs.ucla.edu>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Net.NamedData;
using Net.NamedData.Encoding;
using Net.NamedData.Security;
using Net.NamedData.Util;


namespace Net.NamedData.Security.Policy
{

	/**
	 * A PolicyManager is an abstract base class to represent the Policy for
	 * verifying data packets.
	 * You must create an object of a subclass.
	 */
	public abstract class PolicyManager
	{
		/**
		 * Check if the received data packet can escape from verification and be
		 * trusted as valid.
		 * @param data The received data packet.
		 * @return true if the data does not need to be verified to be trusted as
		 * valid, otherwise false.
		 */
		public abstract bool SkipVerifyAndTrust(Data data);

		/**
		 * Check if the received signed interest can escape from verification and be
		 * trusted as valid.
		 * @param interest The received interest.
		 * @return true if the interest does not need to be verified to be trusted as
		 * valid, otherwise false.
		 */
		public abstract bool SkipVerifyAndTrust(Interest interest);

		/**
		 * Check if this PolicyManager has a verification rule for the received data.
		 * @param data The received data packet.
		 * @return true if the data must be verified, otherwise false.
		 */
		public abstract bool RequireVerify(Data data);

		/**
		 * Check if this PolicyManager has a verification rule for the received interest.
		 * @param interest The received interest.
		 * @return true if the interest must be verified, otherwise false.
		 */
		public abstract bool RequireVerify(Interest interest);

		/**
		 * Check whether the received data packet complies with the verification
		 * Policy, and get the indication of the next verification step.
		 * @param data The Data object with the signature to check.
		 * @param stepCount The number of verification steps that have been done,
		 * used to track the verification progress.
		 * @param OnVerified If the signature is verified, this calls
		 * OnVerified(data).
		 * @param OnVerifyFailed If the signature check fails, this calls
		 * OnVerifyFailed(data).
		 * @return the indication of next verification step, null if there is no
		 * further step.
		 */
		public abstract ValidationRequest CheckVerificationPolicy
		  (Data data, int stepCount, IOnVerified onVerified,
		   IOnVerifyFailed onVerifyFailed);

		/**
		 * Check whether the received signed interest complies with the verification
		 * Policy, and get the indication of the next verification step.
		 * @param interest The interest with the signature to check.
		 * @param stepCount The number of verification steps that have been done, used
		 * to track the verification progress.
		 * @param OnVerified If the signature is verified, this calls
		 * OnVerified.onVerifiedInterest(interest).
		 * @param OnVerifyFailed If the signature check fails, this calls
		 * OnVerifyFailed.onVerifyInterestFailed(interest).
		 * @return the indication of next verification step, null if there is no
		 * further step.
		 */
		public abstract ValidationRequest CheckVerificationPolicy
		  (Interest interest, int stepCount, IOnVerifiedInterest onVerified,
		   IOnVerifyInterestFailed onVerifyFailed, WireFormat wireFormat);

		public ValidationRequest CheckVerificationPolicy
		  (Interest interest, int stepCount, IOnVerifiedInterest onVerified,
		   IOnVerifyInterestFailed onVerifyFailed)
		{
			return CheckVerificationPolicy
			  (interest, stepCount, onVerified, onVerifyFailed,
			   WireFormat.getDefaultWireFormat());
		}

		/**
		 * Check if the signing certificate name and data name satisfy the signing
		 * Policy.
		 * @param dataName The name of data to be signed.
		 * @param certificateName The name of signing certificate.
		 * @return true if the signing certificate can be used to Sign the data,
		 * otherwise false.
		 */
		public abstract bool CheckSigningPolicy(Name dataName, Name certificateName);

		/**
		 * Infer the signing Identity name according to the Policy. If the signing
		 * Identity cannot be inferred, return an empty name.
		 * @param dataName The name of data to be signed.
		 * @return The signing Identity or an empty name if cannot infer.
		 */
		public abstract Name InferSigningIdentity(Name dataName);

		/**
		 * Check the type of signature and use the publicKeyDer to verify the
		 * signedBlob using the appropriate signature algorithm.
		 * @param signature An object of a subclass of AbstractSignature, e.g.
		 * Sha256WithRsaSignature.
		 * @param signedBlob the SignedBlob with the signed portion to verify.
		 * @param publicKeyDer The DER-encoded public key used to verify the signature.
		 * This may be null if the signature type does not require a public key.
		 * @return True if the signature is verified, false if failed.
		 * @ if the signature type is not recognized or if
		 * publicKeyDer can't be decoded.
		 */
		protected static bool VerifySignature(Net.NamedData.AbstractSignature signature, SignedBlob signedBlob, Blob publicKeyDer)
		{
			if (signature is Sha256WithRsaSignature)
				return VerifySha256WithRsaSignature(signature.GetSignature(), signedBlob, publicKeyDer);
			else if (signature is Sha256WithEcdsaSignature)
				return VerifySha256WithEcdsaSignature(signature.GetSignature(), signedBlob, publicKeyDer);
			else if (signature is DigestSha256Signature)
				return VerifyDigestSha256Signature(signature.GetSignature(), signedBlob);
			else
				// We don't expect this to happen.
				throw new SecurityException("PolicyManager.verify: AbstractSignature type is unknown");
		}

		/**
		 * Verify the RSA signature on the SignedBlob using the given public key.
		 * @param signature The signature bits.
		 * @param signedBlob the SignedBlob with the signed portion to verify.
		 * @param publicKeyDer The DER-encoded public key used to verify the signature.
		 * @return true if the signature verifies, false if not.
		 */
		protected static bool VerifySha256WithRsaSignature
		  (Blob signature, SignedBlob signedBlob, Blob publicKeyDer)
		{
			KeyFactory keyFactory = null;
			try {
				keyFactory = KeyFactory.getInstance("RSA");
			} catch (NoSuchAlgorithmException exception) {
				// Don't expect this to happen.
				throw new SecurityException
				  ("RSA is not supported: " + exception.getMessage());
			}

			PublicKey publicKey = null;
			try {
				publicKey = keyFactory.generatePublic
				  (new X509EncodedKeySpec(publicKeyDer.getImmutableArray()));
			} catch (InvalidKeySpecException exception) {
				// Don't expect this to happen.
				throw new SecurityException
				  ("X509EncodedKeySpec is not supported: " + exception.getMessage());
			}

			AbstractSignature rsaSignature = null;
			try {
				rsaSignature = AbstractSignature.getInstance("SHA256withRSA");
			} catch (NoSuchAlgorithmException e) {
				// Don't expect this to happen.
				throw new SecurityException("SHA256withRSA algorithm is not supported");
			}

			try {
				rsaSignature.initVerify(publicKey);
			} catch (InvalidKeyException exception) {
				throw new SecurityException
				  ("InvalidKeyException: " + exception.getMessage());
			}
			try {
				rsaSignature.update(signedBlob.signedBuf());
				return rsaSignature.verify(signature.getImmutableArray());
			} catch (SignatureException exception) {
				throw new SecurityException
				  ("SignatureException: " + exception.getMessage());
			}
		}

		/**
		 * Verify the ECDSA signature on the SignedBlob using the given public key.
		 * @param signature The signature bits.
		 * @param signedBlob the SignedBlob with the signed portion to verify.
		 * @param publicKeyDer The DER-encoded public key used to verify the signature.
		 * @return true if the signature verifies, false if not.
		 */
		protected static bool VerifySha256WithEcdsaSignature
		  (Blob signature, SignedBlob signedBlob, Blob publicKeyDer)
		{
			KeyFactory keyFactory = null;
			try {
				keyFactory = KeyFactory.getInstance("EC");
			} catch (NoSuchAlgorithmException exception) {
				// Don't expect this to happen.
				throw new SecurityException
				  ("EC is not supported: " + exception.getMessage());
			}

			PublicKey publicKey = null;
			try {
				publicKey = keyFactory.generatePublic
				  (new X509EncodedKeySpec(publicKeyDer.getImmutableArray()));
			} catch (InvalidKeySpecException exception) {
				// Don't expect this to happen.
				throw new SecurityException
				  ("X509EncodedKeySpec is not supported: " + exception.getMessage());
			}

			AbstractSignature ecSignature = null;
			try {
				ecSignature = AbstractSignature.getInstance("SHA256withECDSA");
			} catch (NoSuchAlgorithmException e) {
				// Don't expect this to happen.
				throw new SecurityException("SHA256withECDSA algorithm is not supported");
			}

			try {
				ecSignature.initVerify(publicKey);
			} catch (InvalidKeyException exception) {
				throw new SecurityException
				  ("InvalidKeyException: " + exception.getMessage());
			}
			try {
				ecSignature.update(signedBlob.signedBuf());
				return ecSignature.verify(signature.getImmutableArray());
			} catch (SignatureException exception) {
				throw new SecurityException
				  ("SignatureException: " + exception.getMessage());
			}
		}

		/**
		 * Verify the DigestSha256 signature on the SignedBlob by verifying that the
		 * digest of SignedBlob equals the signature.
		 * @param signature The signature bits.
		 * @param signedBlob the SignedBlob with the signed portion to verify.
		 * @return true if the signature verifies, false if not.
		 */
		protected static bool VerifyDigestSha256Signature(Blob signature, SignedBlob signedBlob)
		{
			// Set signedPortionDigest to the digest of the signed portion of the signedBlob.
			byte[] signedPortionDigest = Common.digestSha256(signedBlob.signedBuf());

			return Arrays.equals(signedPortionDigest, signature.getImmutableArray());
		}
	}
}
