1 //
2 // System.Security.Cryptography.X509Certificates.X509ChainImplMono
3 //
4 // Author:
5 //	Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
9 // Copyright (C) 2011 Xamarin Inc. (http://www.xamarin.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 
31 #if SECURITY_DEP
32 
33 #if MONO_SECURITY_ALIAS
34 extern alias MonoSecurity;
35 using MX = MonoSecurity::Mono.Security.X509;
36 #else
37 using MX = Mono.Security.X509;
38 #endif
39 
40 using System.Collections;
41 using System.Text;
42 
43 namespace System.Security.Cryptography.X509Certificates {
44 
45 	internal class X509ChainImplMono : X509ChainImpl
46 	{
47 		private StoreLocation location;
48 		private X509ChainElementCollection elements;
49 		private X509ChainPolicy policy;
50 		private X509ChainStatus[] status;
51 
52 		static X509ChainStatus[] Empty = new X509ChainStatus [0];
53 
54 		// RFC3280 variables
55 		private int max_path_length;
56 		private X500DistinguishedName working_issuer_name;
57 //		private string working_public_key_algorithm;
58 		private AsymmetricAlgorithm working_public_key;
59 
60 		// other flags
61 		private X509ChainElement bce_restriction;
62 
63 		// constructors
64 
X509ChainImplMono()65 		public X509ChainImplMono ()
66 			: this (false)
67 		{
68 		}
69 
X509ChainImplMono(bool useMachineContext)70 		public X509ChainImplMono (bool useMachineContext)
71 		{
72 			location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
73 			elements = new X509ChainElementCollection ();
74 			policy = new X509ChainPolicy ();
75 		}
76 
77 		[MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
X509ChainImplMono(IntPtr chainContext)78 		public X509ChainImplMono (IntPtr chainContext)
79 		{
80 			// CryptoAPI compatibility (unmanaged handle)
81 			throw new NotSupportedException ();
82 		}
83 
84 		public override bool IsValid {
85 			get { return true; }
86 		}
87 
88 		public override IntPtr Handle {
89 			get { return IntPtr.Zero; }
90 		}
91 
92 		// properties
93 
94 		public override X509ChainElementCollection ChainElements {
95 			get { return elements; }
96 		}
97 
98 		public override X509ChainPolicy ChainPolicy {
99 			get { return policy; }
100 			set { policy = value; }
101 		}
102 
103 		public override X509ChainStatus[] ChainStatus {
104 			get {
105 				if (status == null)
106 					return Empty;
107 				return status;
108 			}
109 		}
110 
111 		// methods
112 
113 		[MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
Build(X509Certificate2 certificate)114 		public override bool Build (X509Certificate2 certificate)
115 		{
116 			if (certificate == null)
117 				throw new ArgumentException ("certificate");
118 
119 			Reset ();
120 			X509ChainStatusFlags flag;
121 			try {
122 				flag = BuildChainFrom (certificate);
123 				ValidateChain (flag);
124 			}
125 			catch (CryptographicException ce) {
126 				throw new ArgumentException ("certificate", ce);
127 			}
128 
129 			X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
130 			ArrayList list = new ArrayList ();
131 			// build "global" ChainStatus from the ChainStatus of every ChainElements
132 			foreach (X509ChainElement ce in elements) {
133 				foreach (X509ChainStatus cs in ce.ChainElementStatus) {
134 					// we MUST avoid duplicates in the "global" list
135 					if ((total & cs.Status) != cs.Status) {
136 						list.Add (cs);
137 						total |= cs.Status;
138 					}
139 				}
140 			}
141 			// and if required add some
142 			if (flag != X509ChainStatusFlags.NoError) {
143 				list.Insert (0, new X509ChainStatus (flag));
144 			}
145 			status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
146 
147 			// (fast path) this ignore everything we have checked
148 			if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
149 				return true;
150 
151 			bool result = true;
152 			// now check if exclude some verification for the "end result" (boolean)
153 			foreach (X509ChainStatus cs in status) {
154 				switch (cs.Status) {
155 				case X509ChainStatusFlags.UntrustedRoot:
156 				case X509ChainStatusFlags.PartialChain:
157 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
158 					break;
159 				case X509ChainStatusFlags.NotTimeValid:
160 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
161 					break;
162 				// FIXME - from here we needs new test cases for all cases
163 				case X509ChainStatusFlags.NotTimeNested:
164 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
165 					break;
166 				case X509ChainStatusFlags.InvalidBasicConstraints:
167 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
168 					break;
169 				case X509ChainStatusFlags.InvalidPolicyConstraints:
170 				case X509ChainStatusFlags.NoIssuanceChainPolicy:
171 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
172 					break;
173 				case X509ChainStatusFlags.InvalidNameConstraints:
174 				case X509ChainStatusFlags.HasNotSupportedNameConstraint:
175 				case X509ChainStatusFlags.HasNotPermittedNameConstraint:
176 				case X509ChainStatusFlags.HasExcludedNameConstraint:
177 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
178 					break;
179 				case X509ChainStatusFlags.InvalidExtension:
180 					// not sure ?!?
181 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
182 					break;
183 				//
184 				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
185 				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
186 				case X509ChainStatusFlags.CtlNotTimeValid:
187 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
188 					break;
189 				case X509ChainStatusFlags.CtlNotSignatureValid:
190 					// ?
191 					break;
192 				//	((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
193 				case X509ChainStatusFlags.CtlNotValidForUsage:
194 					// FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
195 					result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
196 					break;
197 				default:
198 					result = false;
199 					break;
200 				}
201 				// once we have one failure there's no need to check further
202 				if (!result)
203 					return false;
204 			}
205 
206 			// every "problem" was excluded
207 			return true;
208 		}
209 
Reset()210 		public override void Reset ()
211 		{
212 			// note: this call doesn't Reset the X509ChainPolicy
213 			if ((status != null) && (status.Length != 0))
214 				status = null;
215 			if (elements.Count > 0)
216 				elements.Clear ();
217 			if (user_root_store != null) {
218 				user_root_store.Close ();
219 				user_root_store = null;
220 			}
221 			if (root_store != null) {
222 				root_store.Close ();
223 				root_store = null;
224 			}
225 			if (user_ca_store != null) {
226 				user_ca_store.Close ();
227 				user_ca_store = null;
228 			}
229 			if (ca_store != null) {
230 				ca_store.Close ();
231 				ca_store = null;
232 			}
233 			roots = null;
234 			cas = null;
235 			collection = null;
236 			bce_restriction = null;
237 			working_public_key = null;
238 		}
239 
240 		// private stuff
241 
242 		private X509Certificate2Collection roots;
243 		private X509Certificate2Collection cas;
244 		private X509Store root_store;
245 		private X509Store ca_store;
246 		private X509Store user_root_store;
247 		private X509Store user_ca_store;
248 
249 		private X509Certificate2Collection Roots {
250 			get {
251 				if (roots == null) {
252 					X509Certificate2Collection c = new X509Certificate2Collection ();
253 					X509Store store = LMRootStore;
254 					if (location == StoreLocation.CurrentUser)
255 						c.AddRange (UserRootStore.Certificates);
256 					c.AddRange (store.Certificates);
257 					roots = c;
258 				}
259 				return roots;
260 			}
261 		}
262 
263 		private X509Certificate2Collection CertificateAuthorities {
264 			get {
265 				if (cas == null) {
266 					X509Certificate2Collection c = new X509Certificate2Collection ();
267 					X509Store store = LMCAStore;
268 					if (location == StoreLocation.CurrentUser)
269 						c.AddRange (UserCAStore.Certificates);
270 					c.AddRange (store.Certificates);
271 					cas = c;
272 				}
273 				return cas;
274 			}
275 		}
276 
277 		private X509Store LMRootStore {
278 			get {
279 				if (root_store == null) {
280 					root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
281 					try {
282 						root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
283 					} catch {
284 					}
285 				}
286 				return root_store;
287 			}
288 		}
289 
290 		private X509Store UserRootStore {
291 			get {
292 				if (user_root_store == null) {
293 					user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
294 					try {
295 						user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
296 					} catch {
297 					}
298 				}
299 				return user_root_store;
300 			}
301 		}
302 
303 		private X509Store LMCAStore {
304 			get {
305 				if (ca_store == null) {
306 					ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
307 					try {
308 						ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
309 					} catch {
310 					}
311 				}
312 				return ca_store;
313 			}
314 		}
315 
316 		private X509Store UserCAStore {
317 			get {
318 				if (user_ca_store == null) {
319 					user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
320 					try {
321 						user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
322 					} catch {
323 					}
324 				}
325 				return user_ca_store;
326 			}
327 		}
328 		// *** certificate chain/path building stuff ***
329 
330 		private X509Certificate2Collection collection;
331 
332 		// we search local user (default) or machine certificate store
333 		// and in the extra certificate supplied in ChainPolicy.ExtraStore
334 		private X509Certificate2Collection CertificateCollection {
335 			get {
336 				if (collection == null) {
337 					collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
338 					collection.AddRange (Roots);
339 					collection.AddRange (CertificateAuthorities);
340 				}
341 				return collection;
342 			}
343 		}
344 
345 		// This is a non-recursive chain/path building algorithm.
346 		//
347 		// At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
348 		// affect the path building (other errors are verification errors).
349 		//
350 		// Note that the order match the one we need to match MS and not the one defined in RFC3280,
351 		// we also include the trusted root certificate (trust anchor in RFC3280) in the list.
352 		// (this isn't an issue, just keep that in mind if you look at the source and the RFC)
BuildChainFrom(X509Certificate2 certificate)353 		private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
354 		{
355 			elements.Add (certificate);
356 
357 			while (!IsChainComplete (certificate)) {
358 				certificate = FindParent (certificate);
359 
360 				if (certificate == null)
361 					return X509ChainStatusFlags.PartialChain;
362 
363 				if (elements.Contains (certificate))
364 					return X509ChainStatusFlags.Cyclic;
365 
366 				elements.Add (certificate);
367 			}
368 
369 			// roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
370 			// trustiness (what a cute word) in the trusted root collection
371 			if (!Roots.Contains (certificate))
372 				elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;
373 
374 			return X509ChainStatusFlags.NoError;
375 		}
376 
377 
SelectBestFromCollection(X509Certificate2 child, X509Certificate2Collection c)378 		private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
379 		{
380 			switch (c.Count) {
381 			case 0:
382 				return null;
383 			case 1:
384 				return c [0];
385 			default:
386 				// multiple candidate, keep only the ones that are still valid
387 				X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
388 				switch (time_valid.Count) {
389 				case 0:
390 					// that's too restrictive, let's revert and try another thing...
391 					time_valid = c;
392 					break;
393 				case 1:
394 					return time_valid [0];
395 				default:
396 					break;
397 				}
398 
399 				// again multiple candidates, let's find the AKI that match the SKI (if we have one)
400 				string aki = GetAuthorityKeyIdentifier (child);
401 				if (String.IsNullOrEmpty (aki)) {
402 					return time_valid [0]; // FIXME: out of luck, you get the first one
403 				}
404 				foreach (X509Certificate2 parent in time_valid) {
405 					string ski = GetSubjectKeyIdentifier (parent);
406 					// if both id are available then they must match
407 					if (aki == ski)
408 						return parent;
409 				}
410 				return time_valid [0]; // FIXME: out of luck, you get the first one
411 			}
412 		}
413 
FindParent(X509Certificate2 certificate)414 		private X509Certificate2 FindParent (X509Certificate2 certificate)
415 		{
416 			X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
417 			string aki = GetAuthorityKeyIdentifier (certificate);
418 			if ((aki != null) && (aki.Length > 0)) {
419 				subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
420 			}
421 			X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
422 			// if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
423 			return certificate.Equals (parent) ? null : parent;
424 		}
425 
IsChainComplete(X509Certificate2 certificate)426 		private bool IsChainComplete (X509Certificate2 certificate)
427 		{
428 			// the chain is complete if we have a self-signed certificate
429 			if (!IsSelfIssued (certificate))
430 				return false;
431 
432 			// we're very limited to what we can do without certificate extensions
433 			if (certificate.Version < 3)
434 				return true;
435 
436 			// check that Authority Key Identifier == Subject Key Identifier
437 			// e.g. it will be different if a self-signed certificate is part (not the end) of the chain
438 			string ski = GetSubjectKeyIdentifier (certificate);
439 			if (String.IsNullOrEmpty (ski))
440 				return true;
441 			string aki = GetAuthorityKeyIdentifier (certificate);
442 			if (String.IsNullOrEmpty (aki))
443 				return true;
444 			// if both id are available then they must match
445 			return (aki == ski);
446 		}
447 
448 		// check for "self-issued" certificate - without verifying the signature
449 		// note that self-issued doesn't always mean it's a root certificate!
IsSelfIssued(X509Certificate2 certificate)450 		private bool IsSelfIssued (X509Certificate2 certificate)
451 		{
452 			return (certificate.Issuer == certificate.Subject);
453 		}
454 
455 
456 		// *** certificate chain/path validation stuff ***
457 
458 		// Currently a subset of RFC3280 (hopefully a full implementation someday)
ValidateChain(X509ChainStatusFlags flag)459 		private void ValidateChain (X509ChainStatusFlags flag)
460 		{
461 			// 'n' should be the root certificate...
462 			int n = elements.Count - 1;
463 			X509Certificate2 certificate = elements [n].Certificate;
464 
465 			// ... and, if so, must be treated outside the chain...
466 			if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
467 				Process (n);
468 				// deal with the case where the chain == the root certificate
469 				// (which isn't for RFC3280) part of the chain
470 				if (n == 0) {
471 					elements [0].UncompressFlags ();
472 					return;
473 				}
474 				// skip the root certificate when processing the chain (in 6.1.3)
475 				n--;
476 			}
477 			// ... unless the chain is a partial one (then we start with that one)
478 
479 			// 6.1.1 - Inputs
480 			// 6.1.1.a - a prospective certificate path of length n (i.e. elements)
481 			// 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
482 			// 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
483 			// 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
484 			// 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
485 			// 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
486 			// 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)
487 
488 			// 6.1.2 - Initialization (incomplete)
489 			// 6.1.2.a-f - policy stuff, some TODO, some not supported
490 			// 6.1.2.g - working public key algorithm
491 //			working_public_key_algorithm = certificate.PublicKey.Oid.Value;
492 			// 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
493 			working_public_key = certificate.PublicKey.Key;
494 			// 6.1.2.j - working issuer name
495 			working_issuer_name = certificate.IssuerName;
496 			// 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
497 			//	     may be reduced to the value in the path length constraint field
498 			max_path_length = n;
499 
500 			// 6.1.3 - Basic Certificate Processing
501 			// note: loop looks reversed (the list is) but we process this part just like RFC3280 does
502 			for (int i = n; i > 0; i--) {
503 				Process (i);
504 				// 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
505 				PrepareForNextCertificate (i);
506 			}
507 			Process (0);
508 
509 			// 6.1.3.a.3 - revocation checks
510 			CheckRevocationOnChain (flag);
511 
512 			// 6.1.5 - Wrap-up procedure
513 			WrapUp ();
514 		}
515 
Process(int n)516 		private void Process (int n)
517 		{
518 			X509ChainElement element = elements [n];
519 			X509Certificate2 certificate = element.Certificate;
520 
521 			// pre-step: DSA certificates may inherit the parameters of their CA
522 			if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
523 				if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
524 					X509Certificate2 parent = elements [n+1].Certificate;
525 					certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
526 				}
527 			}
528 
529 			bool root = (working_public_key == null);
530 			// 6.1.3.a.1 - check signature (with special case to deal with root certificates)
531 			if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
532 				// another special case where only an end-entity is available and can't be verified.
533 				// In this case we do not report an invalid signature (since this is unknown)
534 				if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
535 					element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
536 				}
537 			}
538 
539 			// 6.1.3.a.2 - check validity period
540 			if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
541 				(ChainPolicy.VerificationTime > certificate.NotAfter)) {
542 				element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
543 			}
544 			// TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
545 
546 			// note: most of them don't apply to the root certificate
547 			if (root) {
548 				return;
549 			}
550 
551 			// 6.1.3.a.3 - revocation check (we're doing at the last stage)
552 			// note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)
553 
554 			// 6.1.3.a.4 - check certificate issuer name
555 			if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
556 				// NOTE: this is not the "right" error flag, but it's the closest one defined
557 				element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
558 			}
559 
560 			if (!IsSelfIssued (certificate) && (n != 0)) {
561 				// TODO 6.1.3.b - subject name in the permitted_subtrees ...
562 				// TODO 6.1.3.c - subject name not within excluded_subtrees...
563 
564 				// TODO - check for X509ChainStatusFlags.InvalidNameConstraint
565 				// TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
566 				// TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
567 				// TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
568 			}
569 
570 			// TODO 6.1.3.d - check if certificate policies extension is present
571 			//if (false) {
572 				// TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
573 				//	using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
574 
575 				// TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
576 
577 			//} else {
578 				// TODO 6.1.3.e - set valid_policy_tree to NULL
579 			//}
580 
581 			// TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
582 		}
583 
584 		// CTL == Certificate Trust List / NOT SUPPORTED
585 		// TODO - check for X509ChainStatusFlags.CtlNotTimeValid
586 		// TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
587 		// TODO - check for X509ChainStatusFlags.CtlNotValidForUsage
588 
PrepareForNextCertificate(int n)589 		private void PrepareForNextCertificate (int n)
590 		{
591 			X509ChainElement element = elements [n];
592 			X509Certificate2 certificate = element.Certificate;
593 
594 			// TODO 6.1.4.a-b
595 
596 			// 6.1.4.c
597 			working_issuer_name = certificate.SubjectName;
598 			// 6.1.4.d-e - our key includes both the public key and it's parameters
599 			working_public_key = certificate.PublicKey.Key;
600 			// 6.1.4.f
601 //			working_public_key_algorithm = certificate.PublicKey.Oid.Value;
602 
603 			// TODO 6.1.4.g-j
604 
605 			// 6.1.4.k - Verify that the certificate is a CA certificate
606 			X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
607 			if (bce != null) {
608 				if (!bce.CertificateAuthority) {
609 					element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
610 				}
611 			} else if (certificate.Version >= 3) {
612 				// recent (v3+) CA certificates must include BCE
613 				element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
614 			}
615 
616 			// 6.1.4.l - if the certificate isn't self-issued...
617 			if (!IsSelfIssued (certificate)) {
618 				// ... verify that max_path_length > 0
619 				if (max_path_length > 0) {
620 					max_path_length--;
621 				} else {
622 					// to match MS the reported status must be against the certificate
623 					// with the BCE and not where the path is too long. It also means
624 					// that this condition has to be reported only once
625 					if (bce_restriction != null) {
626 						bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
627 					}
628 				}
629 			}
630 
631 			// 6.1.4.m - if pathLengthConstraint is present...
632 			if ((bce != null) && (bce.HasPathLengthConstraint)) {
633 				// ... and is less that max_path_length, set max_path_length to it's value
634 				if (bce.PathLengthConstraint < max_path_length) {
635 					max_path_length = bce.PathLengthConstraint;
636 					bce_restriction = element;
637 				}
638 			}
639 
640 			// 6.1.4.n - if key usage extension is present...
641 			X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
642 			if (kue != null) {
643 				// ... verify keyCertSign is set
644 				X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
645 				if ((kue.KeyUsages & success) != success)
646 					element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
647 			}
648 
649 			// 6.1.4.o - recognize and process other critical extension present in the certificate
650 			ProcessCertificateExtensions (element);
651 		}
652 
WrapUp()653 		private void WrapUp ()
654 		{
655 			X509ChainElement element = elements [0];
656 			X509Certificate2 certificate = element.Certificate;
657 
658 			// 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
659 			if (IsSelfIssued (certificate)) {
660 				// TODO... decrement explicit_policy by 1
661 			}
662 
663 			// 6.1.5.b - TODO
664 
665 			// 6.1.5.c,d,e - not required by the X509Chain implementation
666 
667 			// 6.1.5.f - recognize and process other critical extension present in the certificate
668 			ProcessCertificateExtensions (element);
669 
670 			// 6.1.5.g - TODO
671 
672 			// uncompressed the flags into several elements
673 			for (int i = elements.Count - 1; i >= 0; i--) {
674 				elements [i].UncompressFlags ();
675 			}
676 		}
677 
ProcessCertificateExtensions(X509ChainElement element)678 		private void ProcessCertificateExtensions (X509ChainElement element)
679 		{
680 			foreach (X509Extension ext in element.Certificate.Extensions) {
681 				if (ext.Critical) {
682 					switch (ext.Oid.Value) {
683 					case "2.5.29.15": // X509KeyUsageExtension
684 					case "2.5.29.19": // X509BasicConstraintsExtension
685 						// we processed this extension
686 						break;
687 					default:
688 						// note: Under Windows XP MS implementation seems to ignore
689 						// certificate with unknown critical extensions.
690 						element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
691 						break;
692 					}
693 				}
694 			}
695 		}
696 
IsSignedWith(X509Certificate2 signed, AsymmetricAlgorithm pubkey)697 		private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
698 		{
699 			if (pubkey == null)
700 				return false;
701 			// Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
702 			var mx = signed.MonoCertificate;
703 			return (mx.VerifySignature (pubkey));
704 		}
705 
GetSubjectKeyIdentifier(X509Certificate2 certificate)706 		private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
707 		{
708 			X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
709 			return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
710 		}
711 
712 		// System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
GetAuthorityKeyIdentifier(X509Certificate2 certificate)713 		static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
714 		{
715 			return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
716 		}
717 
718 		// but anyway System.dll v2 doesn't expose CRL in any way so...
GetAuthorityKeyIdentifier(MX.X509Crl crl)719 		static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
720 		{
721 			return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
722 		}
723 
GetAuthorityKeyIdentifier(MX.X509Extension ext)724 		static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
725 		{
726 			if (ext == null)
727 				return String.Empty;
728 			var aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
729 			byte[] id = aki.Identifier;
730 			if (id == null)
731 				return String.Empty;
732 			StringBuilder sb = new StringBuilder ();
733 			foreach (byte b in id)
734 				sb.Append (b.ToString ("X02"));
735 			return sb.ToString ();
736 		}
737 
738 		// we check the revocation only once we have built the complete chain
CheckRevocationOnChain(X509ChainStatusFlags flag)739 		private void CheckRevocationOnChain (X509ChainStatusFlags flag)
740 		{
741 			bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
742 			bool online;
743 
744 			switch (ChainPolicy.RevocationMode) {
745 			case X509RevocationMode.Online:
746 				// default
747 				online = true;
748 				break;
749 			case X509RevocationMode.Offline:
750 				online = false;
751 				break;
752 			case X509RevocationMode.NoCheck:
753 				return;
754 			default:
755 				throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
756 			}
757 
758 			bool unknown = partial;
759 			// from the root down to the end-entity
760 			for (int i = elements.Count - 1; i >= 0; i--) {
761 				bool check = true;
762 
763 				switch (ChainPolicy.RevocationFlag) {
764 				case X509RevocationFlag.EndCertificateOnly:
765 					check = (i == 0);
766 					break;
767 				case X509RevocationFlag.EntireChain:
768 					check = true;
769 					break;
770 				case X509RevocationFlag.ExcludeRoot:
771 					// default
772 					check = (i != (elements.Count - 1));
773 					// anyway, who's gonna sign that the root is invalid ?
774 					break;
775 				}
776 
777 				X509ChainElement element = elements [i];
778 
779 				// we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
780 				if (!unknown)
781 					unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
782 
783 				if (unknown) {
784 					// we can skip the revocation checks as we can't be sure of them anyway
785 					element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
786 					element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
787 				} else if (check && !partial && !IsSelfIssued (element.Certificate)) {
788 					// check for revocation (except for the trusted root and self-issued certs)
789 					element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
790 					// if revoked, then all others following in the chain are unknown...
791 					unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
792 				}
793 			}
794 		}
795 
796 		// This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
CheckRevocation(X509Certificate2 certificate, int ca, bool online)797 		private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
798 		{
799 			X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
800 			X509ChainElement element = elements [ca];
801 			X509Certificate2 ca_cert = element.Certificate;
802 
803 			// find the CRL from the "right" CA
804 			while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
805 				// try with this self-issued
806 				result = CheckRevocation (certificate, ca_cert, online);
807 				if (result != X509ChainStatusFlags.RevocationStatusUnknown)
808 					break;
809 				ca++;
810 				element = elements [ca];
811 				ca_cert = element.Certificate;
812 			}
813 			if (result == X509ChainStatusFlags.RevocationStatusUnknown)
814 				result = CheckRevocation (certificate, ca_cert, online);
815 			return result;
816 		}
817 
CheckRevocation(X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)818 		private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
819 		{
820 			// change this if/when we support OCSP
821 			X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
822 			if (kue != null) {
823 				// ... verify CrlSign is set
824 				X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
825 				if ((kue.KeyUsages & success) != success) {
826 					// FIXME - we should try to find an alternative CA that has the CrlSign bit
827 					return X509ChainStatusFlags.RevocationStatusUnknown;
828 				}
829 			}
830 
831 			MX.X509Crl crl = FindCrl (ca_cert);
832 
833 			if ((crl == null) && online) {
834 				// FIXME - download and install new CRL
835 				// then you get a second chance
836 				// crl = FindCrl (ca_cert, ref valid, ref out_of_date);
837 
838 				// We need to get the subjectAltName and an URI from there (or use OCSP)
839 				// X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension);
840 			}
841 
842 			if (crl != null) {
843 				// validate the digital signature on the CRL using the CA public key
844 				// note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
845 				// checks and we loose the "why" of the failure
846 				// note #2: we do this before other tests as an invalid signature could be a hacked CRL
847 				// (so anything within can't be trusted)
848 				if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
849 					return X509ChainStatusFlags.RevocationStatusUnknown;
850 				}
851 
852 				MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
853 				if (entry != null) {
854 					// We have an entry for this CRL that includes an unknown CRITICAL extension
855 					// See [X.509 7.3] NOTE 4
856 					if (!ProcessCrlEntryExtensions (entry))
857 						return X509ChainStatusFlags.Revoked;
858 
859 					// FIXME - a little more is involved
860 					if (entry.RevocationDate <= ChainPolicy.VerificationTime)
861 						return X509ChainStatusFlags.Revoked;
862 				}
863 
864 				// are we overdue for a CRL update ? if so we can't be sure of any certificate status
865 				if (crl.NextUpdate < ChainPolicy.VerificationTime)
866 					return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;
867 
868 				// we have a CRL that includes an unknown CRITICAL extension
869 				// we put this check at the end so we do not "hide" any Revoked flags
870 				if (!ProcessCrlExtensions (crl)) {
871 					return X509ChainStatusFlags.RevocationStatusUnknown;
872 				}
873 			} else {
874 				return X509ChainStatusFlags.RevocationStatusUnknown;
875 			}
876 
877 			return X509ChainStatusFlags.NoError;
878 		}
879 
CheckCrls(string subject, string ski, MX.X509Store store)880 		static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store)
881 		{
882 			if (store == null)
883 				return null;
884 
885 			var crls = store.Crls;
886 			foreach (MX.X509Crl crl in crls) {
887 				if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
888 					return crl;
889 			}
890 			return null; // No CRL found
891 		}
892 
FindCrl(X509Certificate2 caCertificate)893 		private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
894 		{
895 			string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
896 			string ski = GetSubjectKeyIdentifier (caCertificate);
897 
898 			// consider that the LocalMachine directories could not exists... and cannot be created by the user
899 			MX.X509Crl result = CheckCrls (subject, ski, LMCAStore.Store);
900 			if (result != null)
901 				return result;
902 			if (location == StoreLocation.CurrentUser) {
903 				result = CheckCrls (subject, ski, UserCAStore.Store);
904 				if (result != null)
905 					return result;
906 			}
907 
908 			// consider that the LocalMachine directories could not exists... and cannot be created by the user
909 			result = CheckCrls (subject, ski, LMRootStore.Store);
910 			if (result != null)
911 				return result;
912 			if (location == StoreLocation.CurrentUser) {
913 				result = CheckCrls (subject, ski, UserRootStore.Store);
914 				if (result != null)
915 					return result;
916 			}
917 			return null;
918 		}
919 
ProcessCrlExtensions(MX.X509Crl crl)920 		private bool ProcessCrlExtensions (MX.X509Crl crl)
921 		{
922 			foreach (MX.X509Extension ext in crl.Extensions) {
923 				if (ext.Critical) {
924 					switch (ext.Oid) {
925 					case "2.5.29.20": // cRLNumber
926 					case "2.5.29.35": // authorityKeyIdentifier
927 						// we processed/know about this extension
928 						break;
929 					default:
930 						return false;
931 					}
932 				}
933 			}
934 			return true;
935 		}
936 
ProcessCrlEntryExtensions(MX.X509Crl.X509CrlEntry entry)937 		private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
938 		{
939 			foreach (MX.X509Extension ext in entry.Extensions) {
940 				if (ext.Critical) {
941 					switch (ext.Oid) {
942 					case "2.5.29.21": // cRLReason
943 						// we processed/know about this extension
944 						break;
945 					default:
946 						return false;
947 					}
948 				}
949 			}
950 			return true;
951 		}
952 	}
953 }
954 #endif
955