2 // System.Security.Cryptography.X509Certificates.X509Chain
5 // Sebastien Pouliot <sebastien@ximian.com>
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)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 extern alias MonoSecurity;
35 using System.Collections;
38 using MX = MonoSecurity::Mono.Security.X509;
40 namespace System.Security.Cryptography.X509Certificates {
42 public class X509Chain {
44 private StoreLocation location;
45 private X509ChainElementCollection elements;
46 private X509ChainPolicy policy;
47 private X509ChainStatus[] status;
49 static X509ChainStatus[] Empty = new X509ChainStatus [0];
52 private int max_path_length;
53 private X500DistinguishedName working_issuer_name;
54 // private string working_public_key_algorithm;
55 private AsymmetricAlgorithm working_public_key;
58 private X509ChainElement bce_restriction;
67 public X509Chain (bool useMachineContext)
69 location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
70 elements = new X509ChainElementCollection ();
71 policy = new X509ChainPolicy ();
74 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
75 public X509Chain (IntPtr chainContext)
77 // CryptoAPI compatibility (unmanaged handle)
78 throw new NotSupportedException ();
83 [MonoTODO ("Mono's X509Chain is fully managed. Always returns IntPtr.Zero.")]
84 public IntPtr ChainContext {
85 get { return IntPtr.Zero; }
88 public X509ChainElementCollection ChainElements {
89 get { return elements; }
92 public X509ChainPolicy ChainPolicy {
93 get { return policy; }
94 set { policy = value; }
97 public X509ChainStatus[] ChainStatus {
107 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
108 public bool Build (X509Certificate2 certificate)
110 if (certificate == null)
111 throw new ArgumentException ("certificate");
114 X509ChainStatusFlags flag;
116 flag = BuildChainFrom (certificate);
117 ValidateChain (flag);
119 catch (CryptographicException ce) {
120 throw new ArgumentException ("certificate", ce);
123 X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
124 ArrayList list = new ArrayList ();
125 // build "global" ChainStatus from the ChainStatus of every ChainElements
126 foreach (X509ChainElement ce in elements) {
127 foreach (X509ChainStatus cs in ce.ChainElementStatus) {
128 // we MUST avoid duplicates in the "global" list
129 if ((total & cs.Status) != cs.Status) {
135 // and if required add some
136 if (flag != X509ChainStatusFlags.NoError) {
137 list.Insert (0, new X509ChainStatus (flag));
139 status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
141 // (fast path) this ignore everything we have checked
142 if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
146 // now check if exclude some verification for the "end result" (boolean)
147 foreach (X509ChainStatus cs in status) {
149 case X509ChainStatusFlags.UntrustedRoot:
150 case X509ChainStatusFlags.PartialChain:
151 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
153 case X509ChainStatusFlags.NotTimeValid:
154 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
156 // FIXME - from here we needs new test cases for all cases
157 case X509ChainStatusFlags.NotTimeNested:
158 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
160 case X509ChainStatusFlags.InvalidBasicConstraints:
161 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
163 case X509ChainStatusFlags.InvalidPolicyConstraints:
164 case X509ChainStatusFlags.NoIssuanceChainPolicy:
165 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
167 case X509ChainStatusFlags.InvalidNameConstraints:
168 case X509ChainStatusFlags.HasNotSupportedNameConstraint:
169 case X509ChainStatusFlags.HasNotPermittedNameConstraint:
170 case X509ChainStatusFlags.HasExcludedNameConstraint:
171 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
173 case X509ChainStatusFlags.InvalidExtension:
175 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
178 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
179 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
180 case X509ChainStatusFlags.CtlNotTimeValid:
181 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
183 case X509ChainStatusFlags.CtlNotSignatureValid:
186 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
187 case X509ChainStatusFlags.CtlNotValidForUsage:
188 // FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
189 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
195 // once we have one failure there's no need to check further
200 // every "problem" was excluded
206 // note: this call doesn't Reset the X509ChainPolicy
207 if ((status != null) && (status.Length != 0))
209 if (elements.Count > 0)
211 if (user_root_store != null) {
212 user_root_store.Close ();
213 user_root_store = null;
215 if (root_store != null) {
219 if (user_ca_store != null) {
220 user_ca_store.Close ();
221 user_ca_store = null;
223 if (ca_store != null) {
230 bce_restriction = null;
231 working_public_key = null;
236 public static X509Chain Create ()
238 return (X509Chain) CryptoConfig.CreateFromName ("X509Chain");
243 private X509Certificate2Collection roots;
244 private X509Certificate2Collection cas;
245 private X509Store root_store;
246 private X509Store ca_store;
247 private X509Store user_root_store;
248 private X509Store user_ca_store;
250 private X509Certificate2Collection Roots {
253 X509Certificate2Collection c = new X509Certificate2Collection ();
254 X509Store store = LMRootStore;
255 if (location == StoreLocation.CurrentUser)
256 c.AddRange (UserRootStore.Certificates);
257 c.AddRange (store.Certificates);
264 private X509Certificate2Collection CertificateAuthorities {
267 X509Certificate2Collection c = new X509Certificate2Collection ();
268 X509Store store = LMCAStore;
269 if (location == StoreLocation.CurrentUser)
270 c.AddRange (UserCAStore.Certificates);
271 c.AddRange (store.Certificates);
278 private X509Store LMRootStore {
280 if (root_store == null) {
281 root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
283 root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
291 private X509Store UserRootStore {
293 if (user_root_store == null) {
294 user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
296 user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
300 return user_root_store;
304 private X509Store LMCAStore {
306 if (ca_store == null) {
307 ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
309 ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
317 private X509Store UserCAStore {
319 if (user_ca_store == null) {
320 user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
322 user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
326 return user_ca_store;
329 // *** certificate chain/path building stuff ***
331 private X509Certificate2Collection collection;
333 // we search local user (default) or machine certificate store
334 // and in the extra certificate supplied in ChainPolicy.ExtraStore
335 private X509Certificate2Collection CertificateCollection {
337 if (collection == null) {
338 collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
339 collection.AddRange (Roots);
340 collection.AddRange (CertificateAuthorities);
346 // This is a non-recursive chain/path building algorithm.
348 // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
349 // affect the path building (other errors are verification errors).
351 // Note that the order match the one we need to match MS and not the one defined in RFC3280,
352 // we also include the trusted root certificate (trust anchor in RFC3280) in the list.
353 // (this isn't an issue, just keep that in mind if you look at the source and the RFC)
354 private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
356 elements.Add (certificate);
358 while (!IsChainComplete (certificate)) {
359 certificate = FindParent (certificate);
361 if (certificate == null)
362 return X509ChainStatusFlags.PartialChain;
364 if (elements.Contains (certificate))
365 return X509ChainStatusFlags.Cyclic;
367 elements.Add (certificate);
370 // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
371 // trustiness (what a cute word) in the trusted root collection
372 if (!Roots.Contains (certificate))
373 elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;
375 return X509ChainStatusFlags.NoError;
379 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
387 // multiple candidate, keep only the ones that are still valid
388 X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
389 switch (time_valid.Count) {
391 // that's too restrictive, let's revert and try another thing...
395 return time_valid [0];
400 // again multiple candidates, let's find the AKI that match the SKI (if we have one)
401 string aki = GetAuthorityKeyIdentifier (child);
402 if (String.IsNullOrEmpty (aki)) {
403 return time_valid [0]; // FIXME: out of luck, you get the first one
405 foreach (X509Certificate2 parent in time_valid) {
406 string ski = GetSubjectKeyIdentifier (parent);
407 // if both id are available then they must match
411 return time_valid [0]; // FIXME: out of luck, you get the first one
415 private X509Certificate2 FindParent (X509Certificate2 certificate)
417 X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
418 string aki = GetAuthorityKeyIdentifier (certificate);
419 if ((aki != null) && (aki.Length > 0)) {
420 subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
422 X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
423 // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
424 return certificate.Equals (parent) ? null : parent;
427 private bool IsChainComplete (X509Certificate2 certificate)
429 // the chain is complete if we have a self-signed certificate
430 if (!IsSelfIssued (certificate))
433 // we're very limited to what we can do without certificate extensions
434 if (certificate.Version < 3)
437 // check that Authority Key Identifier == Subject Key Identifier
438 // e.g. it will be different if a self-signed certificate is part (not the end) of the chain
439 string ski = GetSubjectKeyIdentifier (certificate);
440 if (String.IsNullOrEmpty (ski))
442 string aki = GetAuthorityKeyIdentifier (certificate);
443 if (String.IsNullOrEmpty (aki))
445 // if both id are available then they must match
449 // check for "self-issued" certificate - without verifying the signature
450 // note that self-issued doesn't always mean it's a root certificate!
451 private bool IsSelfIssued (X509Certificate2 certificate)
453 return (certificate.Issuer == certificate.Subject);
457 // *** certificate chain/path validation stuff ***
459 // Currently a subset of RFC3280 (hopefully a full implementation someday)
460 private void ValidateChain (X509ChainStatusFlags flag)
462 // 'n' should be the root certificate...
463 int n = elements.Count - 1;
464 X509Certificate2 certificate = elements [n].Certificate;
466 // ... and, if so, must be treated outside the chain...
467 if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
469 // deal with the case where the chain == the root certificate
470 // (which isn't for RFC3280) part of the chain
472 elements [0].UncompressFlags ();
475 // skip the root certificate when processing the chain (in 6.1.3)
478 // ... unless the chain is a partial one (then we start with that one)
481 // 6.1.1.a - a prospective certificate path of length n (i.e. elements)
482 // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
483 // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
484 // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
485 // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
486 // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
487 // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)
489 // 6.1.2 - Initialization (incomplete)
490 // 6.1.2.a-f - policy stuff, some TODO, some not supported
491 // 6.1.2.g - working public key algorithm
492 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
493 // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
494 working_public_key = certificate.PublicKey.Key;
495 // 6.1.2.j - working issuer name
496 working_issuer_name = certificate.IssuerName;
497 // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
498 // may be reduced to the value in the path length constraint field
501 // 6.1.3 - Basic Certificate Processing
502 // note: loop looks reversed (the list is) but we process this part just like RFC3280 does
503 for (int i = n; i > 0; i--) {
505 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
506 PrepareForNextCertificate (i);
510 // 6.1.3.a.3 - revocation checks
511 CheckRevocationOnChain (flag);
513 // 6.1.5 - Wrap-up procedure
517 private void Process (int n)
519 X509ChainElement element = elements [n];
520 X509Certificate2 certificate = element.Certificate;
522 // pre-step: DSA certificates may inherit the parameters of their CA
523 if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
524 if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
525 X509Certificate2 parent = elements [n+1].Certificate;
526 certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
530 bool root = (working_public_key == null);
531 // 6.1.3.a.1 - check signature (with special case to deal with root certificates)
532 if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
533 // another special case where only an end-entity is available and can't be verified.
534 // In this case we do not report an invalid signature (since this is unknown)
535 if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
536 element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
540 // 6.1.3.a.2 - check validity period
541 if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
542 (ChainPolicy.VerificationTime > certificate.NotAfter)) {
543 element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
545 // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
547 // note: most of them don't apply to the root certificate
552 // 6.1.3.a.3 - revocation check (we're doing at the last stage)
553 // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)
555 // 6.1.3.a.4 - check certificate issuer name
556 if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
557 // NOTE: this is not the "right" error flag, but it's the closest one defined
558 element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
561 if (!IsSelfIssued (certificate) && (n != 0)) {
562 // TODO 6.1.3.b - subject name in the permitted_subtrees ...
563 // TODO 6.1.3.c - subject name not within excluded_subtrees...
565 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint
566 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
567 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
568 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
571 // TODO 6.1.3.d - check if certificate policies extension is present
573 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
574 // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
576 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
579 // TODO 6.1.3.e - set valid_policy_tree to NULL
582 // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
585 // CTL == Certificate Trust List / NOT SUPPORTED
586 // TODO - check for X509ChainStatusFlags.CtlNotTimeValid
587 // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
588 // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage
590 private void PrepareForNextCertificate (int n)
592 X509ChainElement element = elements [n];
593 X509Certificate2 certificate = element.Certificate;
598 working_issuer_name = certificate.SubjectName;
599 // 6.1.4.d-e - our key includes both the public key and it's parameters
600 working_public_key = certificate.PublicKey.Key;
602 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
606 // 6.1.4.k - Verify that the certificate is a CA certificate
607 X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
609 if (!bce.CertificateAuthority) {
610 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
612 } else if (certificate.Version >= 3) {
613 // recent (v3+) CA certificates must include BCE
614 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
617 // 6.1.4.l - if the certificate isn't self-issued...
618 if (!IsSelfIssued (certificate)) {
619 // ... verify that max_path_length > 0
620 if (max_path_length > 0) {
623 // to match MS the reported status must be against the certificate
624 // with the BCE and not where the path is too long. It also means
625 // that this condition has to be reported only once
626 if (bce_restriction != null) {
627 bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
632 // 6.1.4.m - if pathLengthConstraint is present...
633 if ((bce != null) && (bce.HasPathLengthConstraint)) {
634 // ... and is less that max_path_length, set max_path_length to it's value
635 if (bce.PathLengthConstraint < max_path_length) {
636 max_path_length = bce.PathLengthConstraint;
637 bce_restriction = element;
641 // 6.1.4.n - if key usage extension is present...
642 X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
644 // ... verify keyCertSign is set
645 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
646 if ((kue.KeyUsages & success) != success)
647 element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
650 // 6.1.4.o - recognize and process other critical extension present in the certificate
651 ProcessCertificateExtensions (element);
654 private void WrapUp ()
656 X509ChainElement element = elements [0];
657 X509Certificate2 certificate = element.Certificate;
659 // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
660 if (IsSelfIssued (certificate)) {
661 // TODO... decrement explicit_policy by 1
666 // 6.1.5.c,d,e - not required by the X509Chain implementation
668 // 6.1.5.f - recognize and process other critical extension present in the certificate
669 ProcessCertificateExtensions (element);
673 // uncompressed the flags into several elements
674 for (int i = elements.Count - 1; i >= 0; i--) {
675 elements [i].UncompressFlags ();
679 private void ProcessCertificateExtensions (X509ChainElement element)
681 foreach (X509Extension ext in element.Certificate.Extensions) {
683 switch (ext.Oid.Value) {
684 case "2.5.29.15": // X509KeyUsageExtension
685 case "2.5.29.19": // X509BasicConstraintsExtension
686 // we processed this extension
689 // note: Under Windows XP MS implementation seems to ignore
690 // certificate with unknown critical extensions.
691 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
698 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
702 // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
703 MX.X509Certificate mx = signed.MonoCertificate;
704 return (mx.VerifySignature (pubkey));
707 private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
709 X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
710 return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
713 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
714 static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
716 return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
719 // but anyway System.dll v2 doesn't expose CRL in any way so...
720 static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
722 return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
725 static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
729 MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
730 byte[] id = aki.Identifier;
733 StringBuilder sb = new StringBuilder ();
734 foreach (byte b in id)
735 sb.Append (b.ToString ("X02"));
736 return sb.ToString ();
739 // we check the revocation only once we have built the complete chain
740 private void CheckRevocationOnChain (X509ChainStatusFlags flag)
742 bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
745 switch (ChainPolicy.RevocationMode) {
746 case X509RevocationMode.Online:
750 case X509RevocationMode.Offline:
753 case X509RevocationMode.NoCheck:
756 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
759 bool unknown = partial;
760 // from the root down to the end-entity
761 for (int i = elements.Count - 1; i >= 0; i--) {
764 switch (ChainPolicy.RevocationFlag) {
765 case X509RevocationFlag.EndCertificateOnly:
768 case X509RevocationFlag.EntireChain:
771 case X509RevocationFlag.ExcludeRoot:
773 check = (i != (elements.Count - 1));
774 // anyway, who's gonna sign that the root is invalid ?
778 X509ChainElement element = elements [i];
780 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
782 unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
785 // we can skip the revocation checks as we can't be sure of them anyway
786 element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
787 element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
788 } else if (check && !partial && !IsSelfIssued (element.Certificate)) {
789 // check for revocation (except for the trusted root and self-issued certs)
790 element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
791 // if revoked, then all others following in the chain are unknown...
792 unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
797 // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
798 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
800 X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
801 X509ChainElement element = elements [ca];
802 X509Certificate2 ca_cert = element.Certificate;
804 // find the CRL from the "right" CA
805 while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
806 // try with this self-issued
807 result = CheckRevocation (certificate, ca_cert, online);
808 if (result != X509ChainStatusFlags.RevocationStatusUnknown)
811 element = elements [ca];
812 ca_cert = element.Certificate;
814 if (result == X509ChainStatusFlags.RevocationStatusUnknown)
815 result = CheckRevocation (certificate, ca_cert, online);
819 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
821 // change this if/when we support OCSP
822 X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
824 // ... verify CrlSign is set
825 X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
826 if ((kue.KeyUsages & success) != success) {
827 // FIXME - we should try to find an alternative CA that has the CrlSign bit
828 return X509ChainStatusFlags.RevocationStatusUnknown;
832 MX.X509Crl crl = FindCrl (ca_cert);
834 if ((crl == null) && online) {
835 // FIXME - download and install new CRL
836 // then you get a second chance
837 // crl = FindCrl (ca_cert, ref valid, ref out_of_date);
839 // We need to get the subjectAltName and an URI from there (or use OCSP)
840 // X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension);
844 // validate the digital signature on the CRL using the CA public key
845 // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
846 // checks and we loose the "why" of the failure
847 // note #2: we do this before other tests as an invalid signature could be a hacked CRL
848 // (so anything within can't be trusted)
849 if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
850 return X509ChainStatusFlags.RevocationStatusUnknown;
853 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
855 // We have an entry for this CRL that includes an unknown CRITICAL extension
856 // See [X.509 7.3] NOTE 4
857 if (!ProcessCrlEntryExtensions (entry))
858 return X509ChainStatusFlags.Revoked;
860 // FIXME - a little more is involved
861 if (entry.RevocationDate <= ChainPolicy.VerificationTime)
862 return X509ChainStatusFlags.Revoked;
865 // are we overdue for a CRL update ? if so we can't be sure of any certificate status
866 if (crl.NextUpdate < ChainPolicy.VerificationTime)
867 return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;
869 // we have a CRL that includes an unknown CRITICAL extension
870 // we put this check at the end so we do not "hide" any Revoked flags
871 if (!ProcessCrlExtensions (crl)) {
872 return X509ChainStatusFlags.RevocationStatusUnknown;
875 return X509ChainStatusFlags.RevocationStatusUnknown;
878 return X509ChainStatusFlags.NoError;
881 static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store)
886 var crls = store.Crls;
887 foreach (MX.X509Crl crl in crls) {
888 if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
891 return null; // No CRL found
894 private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
896 string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
897 string ski = GetSubjectKeyIdentifier (caCertificate);
899 // consider that the LocalMachine directories could not exists... and cannot be created by the user
900 MX.X509Crl result = CheckCrls (subject, ski, LMCAStore.Store);
903 if (location == StoreLocation.CurrentUser) {
904 result = CheckCrls (subject, ski, UserCAStore.Store);
909 // consider that the LocalMachine directories could not exists... and cannot be created by the user
910 result = CheckCrls (subject, ski, LMRootStore.Store);
913 if (location == StoreLocation.CurrentUser) {
914 result = CheckCrls (subject, ski, UserRootStore.Store);
921 private bool ProcessCrlExtensions (MX.X509Crl crl)
923 foreach (MX.X509Extension ext in crl.Extensions) {
926 case "2.5.29.20": // cRLNumber
927 case "2.5.29.35": // authorityKeyIdentifier
928 // we processed/know about this extension
938 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
940 foreach (MX.X509Extension ext in entry.Extensions) {
943 case "2.5.29.21": // cRLReason
944 // we processed/know about this extension
956 namespace System.Security.Cryptography.X509Certificates {
957 public class X509Chain {
958 public bool Build (X509Certificate2 cert)