//
// System.Security.Cryptography.X509Certificates.X509ChainImplMono
//
// Author:
//	Sebastien Pouliot  <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
// Copyright (C) 2011 Xamarin Inc. (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#if SECURITY_DEP

#if MONO_SECURITY_ALIAS
extern alias MonoSecurity;
using MX = MonoSecurity::Mono.Security.X509;
#else
using MX = Mono.Security.X509;
#endif

using System.Collections;
using System.Text;

namespace System.Security.Cryptography.X509Certificates {

	internal class X509ChainImplMono : X509ChainImpl
	{
		private StoreLocation location;
		private X509ChainElementCollection elements;
		private X509ChainPolicy policy;
		private X509ChainStatus[] status;

		static X509ChainStatus[] Empty = new X509ChainStatus [0];

		// RFC3280 variables
		private int max_path_length;
		private X500DistinguishedName working_issuer_name;
//		private string working_public_key_algorithm;
		private AsymmetricAlgorithm working_public_key;

		// other flags
		private X509ChainElement bce_restriction;

		// constructors

		public X509ChainImplMono ()
			: this (false)
		{
		}

		public X509ChainImplMono (bool useMachineContext) 
		{
			location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
			elements = new X509ChainElementCollection ();
			policy = new X509ChainPolicy ();
		}

		[MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
		public X509ChainImplMono (IntPtr chainContext)
		{
			// CryptoAPI compatibility (unmanaged handle)
			throw new NotSupportedException ();
		}

		public override bool IsValid {
			get { return true; }
		}

		public override IntPtr Handle {
			get { return IntPtr.Zero; }
		}

		// properties

		public override X509ChainElementCollection ChainElements {
			get { return elements; }
		}

		public override X509ChainPolicy ChainPolicy {
			get { return policy; }
			set { policy = value; }
		}

		public override X509ChainStatus[] ChainStatus {
			get { 
				if (status == null)
					return Empty;
				return status;
			}
		} 

		// methods

		[MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
		public override bool Build (X509Certificate2 certificate)
		{
			if (certificate == null)
				throw new ArgumentException ("certificate");

			Reset ();
			X509ChainStatusFlags flag;
			try {
				flag = BuildChainFrom (certificate);
				ValidateChain (flag);
			}
			catch (CryptographicException ce) {
				throw new ArgumentException ("certificate", ce);
			}

			X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
			ArrayList list = new ArrayList ();
			// build "global" ChainStatus from the ChainStatus of every ChainElements
			foreach (X509ChainElement ce in elements) {
				foreach (X509ChainStatus cs in ce.ChainElementStatus) {
					// we MUST avoid duplicates in the "global" list
					if ((total & cs.Status) != cs.Status) {
						list.Add (cs);
						total |= cs.Status;
					}
				}
			}
			// and if required add some
			if (flag != X509ChainStatusFlags.NoError) {
				list.Insert (0, new X509ChainStatus (flag));
			}
			status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));

			// (fast path) this ignore everything we have checked
			if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
				return true;

			bool result = true;
			// now check if exclude some verification for the "end result" (boolean)
			foreach (X509ChainStatus cs in status) {
				switch (cs.Status) {
				case X509ChainStatusFlags.UntrustedRoot:
				case X509ChainStatusFlags.PartialChain:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
					break;
				case X509ChainStatusFlags.NotTimeValid:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
					break;
				// FIXME - from here we needs new test cases for all cases
				case X509ChainStatusFlags.NotTimeNested:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
					break;
				case X509ChainStatusFlags.InvalidBasicConstraints:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
					break;
				case X509ChainStatusFlags.InvalidPolicyConstraints:
				case X509ChainStatusFlags.NoIssuanceChainPolicy:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
					break;
				case X509ChainStatusFlags.InvalidNameConstraints:
				case X509ChainStatusFlags.HasNotSupportedNameConstraint:
				case X509ChainStatusFlags.HasNotPermittedNameConstraint:
				case X509ChainStatusFlags.HasExcludedNameConstraint:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
					break;
				case X509ChainStatusFlags.InvalidExtension:
					// not sure ?!?
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
					break;
				//
				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
				case X509ChainStatusFlags.CtlNotTimeValid:
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
					break;
				case X509ChainStatusFlags.CtlNotSignatureValid:
					// ?
					break;
				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
				case X509ChainStatusFlags.CtlNotValidForUsage:
					// FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
					break;
				default:
					result = false;
					break;
				}
				// once we have one failure there's no need to check further
				if (!result)
					return false;
			}

			// every "problem" was excluded
			return true;
		}

		public override void Reset () 
		{
			// note: this call doesn't Reset the X509ChainPolicy
			if ((status != null) && (status.Length != 0))
				status = null;
			if (elements.Count > 0)
				elements.Clear ();
			if (user_root_store != null) {
				user_root_store.Close ();
				user_root_store = null;
			}
			if (root_store != null) {
				root_store.Close ();
				root_store = null;
			}
			if (user_ca_store != null) {
				user_ca_store.Close ();
				user_ca_store = null;
			}
			if (ca_store != null) {
				ca_store.Close ();
				ca_store = null;
			}
			roots = null;
			cas = null;
			collection = null;
			bce_restriction = null;
			working_public_key = null;
		}

		// private stuff

		private X509Certificate2Collection roots;
		private X509Certificate2Collection cas;
		private X509Store root_store;
		private X509Store ca_store;
		private X509Store user_root_store;
		private X509Store user_ca_store;

		private X509Certificate2Collection Roots {
			get {
				if (roots == null) {
					X509Certificate2Collection c = new X509Certificate2Collection ();
					X509Store store = LMRootStore;
					if (location == StoreLocation.CurrentUser)
						c.AddRange (UserRootStore.Certificates);
					c.AddRange (store.Certificates);
					roots = c;
				}
				return roots;
			}
		}

		private X509Certificate2Collection CertificateAuthorities {
			get {
				if (cas == null) {
					X509Certificate2Collection c = new X509Certificate2Collection ();
					X509Store store = LMCAStore;
					if (location == StoreLocation.CurrentUser)
						c.AddRange (UserCAStore.Certificates);
					c.AddRange (store.Certificates);
					cas = c;
				}
				return cas;
			}
		}

		private X509Store LMRootStore {
			get {
				if (root_store == null) {
					root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
					try {
						root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
					} catch {
					}
				}
				return root_store;
			}
		}

		private X509Store UserRootStore {
			get {
				if (user_root_store == null) {
					user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
					try {
						user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
					} catch {
					}
				}
				return user_root_store;
			}
		}

		private X509Store LMCAStore {
			get {
				if (ca_store == null) {
					ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
					try {
						ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
					} catch {
					}
				}
				return ca_store;
			}
		}

		private X509Store UserCAStore {
			get {
				if (user_ca_store == null) {
					user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
					try {
						user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
					} catch {
					}
				}
				return user_ca_store;
			}
		}
		// *** certificate chain/path building stuff ***

		private X509Certificate2Collection collection;

		// we search local user (default) or machine certificate store 
		// and in the extra certificate supplied in ChainPolicy.ExtraStore
		private X509Certificate2Collection CertificateCollection {
			get {
				if (collection == null) {
					collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
					collection.AddRange (Roots);
					collection.AddRange (CertificateAuthorities);
				}
				return collection;
			}
		}

		// This is a non-recursive chain/path building algorithm. 
		//
		// At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
		// affect the path building (other errors are verification errors).
		//
		// Note that the order match the one we need to match MS and not the one defined in RFC3280,
		// we also include the trusted root certificate (trust anchor in RFC3280) in the list.
		// (this isn't an issue, just keep that in mind if you look at the source and the RFC)
		private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
		{
			elements.Add (certificate);

			while (!IsChainComplete (certificate)) {
				certificate = FindParent (certificate);

				if (certificate == null)
					return X509ChainStatusFlags.PartialChain;

				if (elements.Contains (certificate))
					return X509ChainStatusFlags.Cyclic;

				elements.Add (certificate);
			}

			// roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
			// trustiness (what a cute word) in the trusted root collection
			if (!Roots.Contains (certificate))
				elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;

			return X509ChainStatusFlags.NoError;
		}


		private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
		{
			switch (c.Count) {
			case 0:
				return null;
			case 1:
				return c [0];
			default:
				// multiple candidate, keep only the ones that are still valid
				X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
				switch (time_valid.Count) {
				case 0:
					// that's too restrictive, let's revert and try another thing...
					time_valid = c;
					break;
				case 1:
					return time_valid [0];
				default:
					break;
				}

				// again multiple candidates, let's find the AKI that match the SKI (if we have one)
				string aki = GetAuthorityKeyIdentifier (child);
				if (String.IsNullOrEmpty (aki)) {
					return time_valid [0]; // FIXME: out of luck, you get the first one
				}
				foreach (X509Certificate2 parent in time_valid) {
					string ski = GetSubjectKeyIdentifier (parent);
					// if both id are available then they must match
					if (aki == ski)
						return parent;
				}
				return time_valid [0]; // FIXME: out of luck, you get the first one
			}
		}

		private X509Certificate2 FindParent (X509Certificate2 certificate)
		{
			X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
			string aki = GetAuthorityKeyIdentifier (certificate);
			if ((aki != null) && (aki.Length > 0)) {
				subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
			}
			X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
			// if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
			return certificate.Equals (parent) ? null : parent;
		}

		private bool IsChainComplete (X509Certificate2 certificate)
		{
			// the chain is complete if we have a self-signed certificate
			if (!IsSelfIssued (certificate))
				return false;

			// we're very limited to what we can do without certificate extensions
			if (certificate.Version < 3)
				return true;

			// check that Authority Key Identifier == Subject Key Identifier
			// e.g. it will be different if a self-signed certificate is part (not the end) of the chain
			string ski = GetSubjectKeyIdentifier (certificate);
			if (String.IsNullOrEmpty (ski))
				return true;
			string aki = GetAuthorityKeyIdentifier (certificate);
			if (String.IsNullOrEmpty (aki))
				return true;
			// if both id are available then they must match
			return (aki == ski);
		}

		// check for "self-issued" certificate - without verifying the signature
		// note that self-issued doesn't always mean it's a root certificate!
		private bool IsSelfIssued (X509Certificate2 certificate)
		{
			return (certificate.Issuer == certificate.Subject);
		}


		// *** certificate chain/path validation stuff ***

		// Currently a subset of RFC3280 (hopefully a full implementation someday)
		private void ValidateChain (X509ChainStatusFlags flag)
		{
			// 'n' should be the root certificate... 
			int n = elements.Count - 1;
			X509Certificate2 certificate = elements [n].Certificate;

			// ... and, if so, must be treated outside the chain... 
			if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
				Process (n);
				// deal with the case where the chain == the root certificate 
				// (which isn't for RFC3280) part of the chain
				if (n == 0) {
					elements [0].UncompressFlags ();
					return;
				}
				// skip the root certificate when processing the chain (in 6.1.3)
				n--;
			}
			// ... unless the chain is a partial one (then we start with that one)

			// 6.1.1 - Inputs
			// 6.1.1.a - a prospective certificate path of length n (i.e. elements)
			// 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
			// 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
			// 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
			// 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
			// 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
			// 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)

			// 6.1.2 - Initialization (incomplete)
			// 6.1.2.a-f - policy stuff, some TODO, some not supported
			// 6.1.2.g - working public key algorithm
//			working_public_key_algorithm = certificate.PublicKey.Oid.Value;
			// 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
			working_public_key = certificate.PublicKey.Key;
			// 6.1.2.j - working issuer name
			working_issuer_name = certificate.IssuerName;
			// 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
			//	     may be reduced to the value in the path length constraint field
			max_path_length = n;

			// 6.1.3 - Basic Certificate Processing
			// note: loop looks reversed (the list is) but we process this part just like RFC3280 does
			for (int i = n; i > 0; i--) {
				Process (i);
				// 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
				PrepareForNextCertificate (i);
			}
			Process (0);

			// 6.1.3.a.3 - revocation checks
			CheckRevocationOnChain (flag);

			// 6.1.5 - Wrap-up procedure
			WrapUp ();
		}

		private void Process (int n)
		{
			X509ChainElement element = elements [n];
			X509Certificate2 certificate = element.Certificate;

			// pre-step: DSA certificates may inherit the parameters of their CA
			if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
				if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
					X509Certificate2 parent = elements [n+1].Certificate;
					certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
				}
			}

			bool root = (working_public_key == null);
			// 6.1.3.a.1 - check signature (with special case to deal with root certificates)
			if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
				// another special case where only an end-entity is available and can't be verified.
				// In this case we do not report an invalid signature (since this is unknown)
				if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
					element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
				}
			}

			// 6.1.3.a.2 - check validity period
			if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
				(ChainPolicy.VerificationTime > certificate.NotAfter)) {
				element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
			}
			// TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)

			// note: most of them don't apply to the root certificate
			if (root) {
				return;
			}

			// 6.1.3.a.3 - revocation check (we're doing at the last stage)
			// note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)

			// 6.1.3.a.4 - check certificate issuer name
			if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
				// NOTE: this is not the "right" error flag, but it's the closest one defined
				element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
			}

			if (!IsSelfIssued (certificate) && (n != 0)) {
				// TODO 6.1.3.b - subject name in the permitted_subtrees ...
				// TODO 6.1.3.c - subject name not within excluded_subtrees...

				// TODO - check for X509ChainStatusFlags.InvalidNameConstraint
				// TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
				// TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
				// TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
			}

			// TODO 6.1.3.d - check if certificate policies extension is present
			//if (false) {
				// TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
				//	using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy

				// TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy

			//} else {
				// TODO 6.1.3.e - set valid_policy_tree to NULL
			//}

			// TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
		}

		// CTL == Certificate Trust List / NOT SUPPORTED
		// TODO - check for X509ChainStatusFlags.CtlNotTimeValid
		// TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
		// TODO - check for X509ChainStatusFlags.CtlNotValidForUsage

		private void PrepareForNextCertificate (int n) 
		{
			X509ChainElement element = elements [n];
			X509Certificate2 certificate = element.Certificate;

			// TODO 6.1.4.a-b

			// 6.1.4.c
			working_issuer_name = certificate.SubjectName;
			// 6.1.4.d-e - our key includes both the public key and it's parameters
			working_public_key = certificate.PublicKey.Key;
			// 6.1.4.f
//			working_public_key_algorithm = certificate.PublicKey.Oid.Value;

			// TODO 6.1.4.g-j

			// 6.1.4.k - Verify that the certificate is a CA certificate
			X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
			if (bce != null) {
				if (!bce.CertificateAuthority) {
					element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
				}
			} else if (certificate.Version >= 3) {
				// recent (v3+) CA certificates must include BCE
				element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
			}

			// 6.1.4.l - if the certificate isn't self-issued...
			if (!IsSelfIssued (certificate)) {
				// ... verify that max_path_length > 0
				if (max_path_length > 0) {
					max_path_length--;
				} else {
					// to match MS the reported status must be against the certificate 
					// with the BCE and not where the path is too long. It also means
					// that this condition has to be reported only once
					if (bce_restriction != null) {
						bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
					}
				}
			}

			// 6.1.4.m - if pathLengthConstraint is present...
			if ((bce != null) && (bce.HasPathLengthConstraint)) {
				// ... and is less that max_path_length, set max_path_length to it's value
				if (bce.PathLengthConstraint < max_path_length) {
					max_path_length = bce.PathLengthConstraint;
					bce_restriction = element;
				}
			}

			// 6.1.4.n - if key usage extension is present...
			X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
			if (kue != null) {
				// ... verify keyCertSign is set
				X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
				if ((kue.KeyUsages & success) != success)
					element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
			}

			// 6.1.4.o - recognize and process other critical extension present in the certificate
			ProcessCertificateExtensions (element);
		}

		private void WrapUp ()
		{
			X509ChainElement element = elements [0];
			X509Certificate2 certificate = element.Certificate;

			// 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
			if (IsSelfIssued (certificate)) {
				// TODO... decrement explicit_policy by 1
			}

			// 6.1.5.b - TODO

			// 6.1.5.c,d,e - not required by the X509Chain implementation

			// 6.1.5.f - recognize and process other critical extension present in the certificate
			ProcessCertificateExtensions (element);

			// 6.1.5.g - TODO

			// uncompressed the flags into several elements
			for (int i = elements.Count - 1; i >= 0; i--) {
				elements [i].UncompressFlags ();
			}
		}

		private void ProcessCertificateExtensions (X509ChainElement element)
		{
			foreach (X509Extension ext in element.Certificate.Extensions) {
				if (ext.Critical) {
					switch (ext.Oid.Value) {
					case "2.5.29.15": // X509KeyUsageExtension
					case "2.5.29.19": // X509BasicConstraintsExtension
						// we processed this extension
						break;
					default:
						// note: Under Windows XP MS implementation seems to ignore 
						// certificate with unknown critical extensions.
						element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
						break;
					}
				}
			}
		}

		private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
		{
			if (pubkey == null)
				return false;
			// Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
			var mx = signed.MonoCertificate;
			return (mx.VerifySignature (pubkey));
		}

		private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
		{
			X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
			return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
		}

		// System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
		static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
		{
			return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
		}

		// but anyway System.dll v2 doesn't expose CRL in any way so...
		static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
		{
			return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
		}

		static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
		{
			if (ext == null)
				return String.Empty;
			var aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
			byte[] id = aki.Identifier;
			if (id == null)	
				return String.Empty;
			StringBuilder sb = new StringBuilder ();
			foreach (byte b in id)
				sb.Append (b.ToString ("X02"));
			return sb.ToString ();
		}

		// we check the revocation only once we have built the complete chain
		private void CheckRevocationOnChain (X509ChainStatusFlags flag)
		{
			bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
			bool online;

			switch (ChainPolicy.RevocationMode) {
			case X509RevocationMode.Online:
				// default
				online = true;
				break;
			case X509RevocationMode.Offline:
				online = false;
				break;
			case X509RevocationMode.NoCheck:
				return;
			default:
				throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
			}

			bool unknown = partial;
			// from the root down to the end-entity
			for (int i = elements.Count - 1; i >= 0; i--) {
				bool check = true;

				switch (ChainPolicy.RevocationFlag) {
				case X509RevocationFlag.EndCertificateOnly:
					check = (i == 0);
					break;
				case X509RevocationFlag.EntireChain:
					check = true;
					break;
				case X509RevocationFlag.ExcludeRoot:
					// default
					check = (i != (elements.Count - 1));
					// anyway, who's gonna sign that the root is invalid ?
					break;
				}

				X509ChainElement element = elements [i];

				// we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
				if (!unknown)
					unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);

				if (unknown) {
					// we can skip the revocation checks as we can't be sure of them anyway
					element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
					element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
				} else if (check && !partial && !IsSelfIssued (element.Certificate)) {
					// check for revocation (except for the trusted root and self-issued certs)
					element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
					// if revoked, then all others following in the chain are unknown...
					unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
				}
			}
		}

		// This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
		private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
		{
			X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
			X509ChainElement element = elements [ca];
			X509Certificate2 ca_cert = element.Certificate;

			// find the CRL from the "right" CA
			while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
				// try with this self-issued
				result = CheckRevocation (certificate, ca_cert, online);
				if (result != X509ChainStatusFlags.RevocationStatusUnknown)
					break;
				ca++;
				element = elements [ca];
				ca_cert = element.Certificate;
			}
			if (result == X509ChainStatusFlags.RevocationStatusUnknown)
				result = CheckRevocation (certificate, ca_cert, online);
			return result;
		}

		private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
		{
			// change this if/when we support OCSP
			X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
			if (kue != null) {
				// ... verify CrlSign is set
				X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
				if ((kue.KeyUsages & success) != success) {
					// FIXME - we should try to find an alternative CA that has the CrlSign bit
					return X509ChainStatusFlags.RevocationStatusUnknown;
				}
			}

			MX.X509Crl crl = FindCrl (ca_cert);

			if ((crl == null) && online) {
				// FIXME - download and install new CRL
				// then you get a second chance
				// crl = FindCrl (ca_cert, ref valid, ref out_of_date);

				// We need to get the subjectAltName and an URI from there (or use OCSP)	
				// X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension);
			}

			if (crl != null) {
				// validate the digital signature on the CRL using the CA public key
				// note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
				// checks and we loose the "why" of the failure
				// note #2: we do this before other tests as an invalid signature could be a hacked CRL
				// (so anything within can't be trusted)
				if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
					return X509ChainStatusFlags.RevocationStatusUnknown;
				}

				MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
				if (entry != null) {
					// We have an entry for this CRL that includes an unknown CRITICAL extension
					// See [X.509 7.3] NOTE 4
					if (!ProcessCrlEntryExtensions (entry))
						return X509ChainStatusFlags.Revoked;

					// FIXME - a little more is involved
					if (entry.RevocationDate <= ChainPolicy.VerificationTime)
						return X509ChainStatusFlags.Revoked;
				}

				// are we overdue for a CRL update ? if so we can't be sure of any certificate status
				if (crl.NextUpdate < ChainPolicy.VerificationTime)
					return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;

				// we have a CRL that includes an unknown CRITICAL extension
				// we put this check at the end so we do not "hide" any Revoked flags
				if (!ProcessCrlExtensions (crl)) {
					return X509ChainStatusFlags.RevocationStatusUnknown;
				}
			} else {
				return X509ChainStatusFlags.RevocationStatusUnknown;
			}

			return X509ChainStatusFlags.NoError;
		}

		static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store)
		{
			if (store == null)
				return null;

			var crls = store.Crls;
			foreach (MX.X509Crl crl in crls) {
				if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
					return crl;
			}
			return null; // No CRL found
		}

		private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
		{
			string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
			string ski = GetSubjectKeyIdentifier (caCertificate);

			// consider that the LocalMachine directories could not exists... and cannot be created by the user
			MX.X509Crl result = CheckCrls (subject, ski, LMCAStore.Store);
			if (result != null)
				return result;
			if (location == StoreLocation.CurrentUser) {
				result = CheckCrls (subject, ski, UserCAStore.Store);
				if (result != null)
					return result;
			}

			// consider that the LocalMachine directories could not exists... and cannot be created by the user
			result = CheckCrls (subject, ski, LMRootStore.Store);
			if (result != null)
				return result;
			if (location == StoreLocation.CurrentUser) {
				result = CheckCrls (subject, ski, UserRootStore.Store);
				if (result != null)
					return result;
			}
			return null;
		}

		private bool ProcessCrlExtensions (MX.X509Crl crl)
		{
			foreach (MX.X509Extension ext in crl.Extensions) {
				if (ext.Critical) {
					switch (ext.Oid) {
					case "2.5.29.20": // cRLNumber
					case "2.5.29.35": // authorityKeyIdentifier
						// we processed/know about this extension
						break;
					default:
						return false;
					}
				}
			}
			return true;
		}

		private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
		{
			foreach (MX.X509Extension ext in entry.Extensions) {
				if (ext.Critical) {
					switch (ext.Oid) {
					case "2.5.29.21": // cRLReason
						// we processed/know about this extension
						break;
					default:
						return false;
					}
				}
			}
			return true;
		}
	}
}
#endif