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.
31 #if SECURITY_DEP || MOONLIGHT
33 using System.Collections;
36 using MX = Mono.Security.X509;
38 namespace System.Security.Cryptography.X509Certificates {
40 public class X509Chain {
42 private StoreLocation location;
43 private X509ChainElementCollection elements;
44 private X509ChainPolicy policy;
45 private X509ChainStatus[] status;
47 static X509ChainStatus[] Empty = new X509ChainStatus [0];
50 private int max_path_length;
51 private X500DistinguishedName working_issuer_name;
52 // private string working_public_key_algorithm;
53 private AsymmetricAlgorithm working_public_key;
56 private X509ChainElement bce_restriction;
65 public X509Chain (bool useMachineContext)
67 location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
68 elements = new X509ChainElementCollection ();
69 policy = new X509ChainPolicy ();
72 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
73 public X509Chain (IntPtr chainContext)
75 // CryptoAPI compatibility (unmanaged handle)
76 throw new NotSupportedException ();
81 [MonoTODO ("Mono's X509Chain is fully managed. Always returns IntPtr.Zero.")]
82 public IntPtr ChainContext {
83 get { return IntPtr.Zero; }
86 public X509ChainElementCollection ChainElements {
87 get { return elements; }
90 public X509ChainPolicy ChainPolicy {
91 get { return policy; }
92 set { policy = value; }
95 public X509ChainStatus[] ChainStatus {
105 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
106 public bool Build (X509Certificate2 certificate)
108 if (certificate == null)
109 throw new ArgumentException ("certificate");
112 X509ChainStatusFlags flag;
114 flag = BuildChainFrom (certificate);
115 ValidateChain (flag);
117 catch (CryptographicException ce) {
118 throw new ArgumentException ("certificate", ce);
121 X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
122 ArrayList list = new ArrayList ();
123 // build "global" ChainStatus from the ChainStatus of every ChainElements
124 foreach (X509ChainElement ce in elements) {
125 foreach (X509ChainStatus cs in ce.ChainElementStatus) {
126 // we MUST avoid duplicates in the "global" list
127 if ((total & cs.Status) != cs.Status) {
133 // and if required add some
134 if (flag != X509ChainStatusFlags.NoError) {
135 list.Insert (0, new X509ChainStatus (flag));
137 status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
139 // (fast path) this ignore everything we have checked
140 if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
144 // now check if exclude some verification for the "end result" (boolean)
145 foreach (X509ChainStatus cs in status) {
147 case X509ChainStatusFlags.UntrustedRoot:
148 case X509ChainStatusFlags.PartialChain:
149 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
151 case X509ChainStatusFlags.NotTimeValid:
152 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
154 // FIXME - from here we needs new test cases for all cases
155 case X509ChainStatusFlags.NotTimeNested:
156 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
158 case X509ChainStatusFlags.InvalidBasicConstraints:
159 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
161 case X509ChainStatusFlags.InvalidPolicyConstraints:
162 case X509ChainStatusFlags.NoIssuanceChainPolicy:
163 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
165 case X509ChainStatusFlags.InvalidNameConstraints:
166 case X509ChainStatusFlags.HasNotSupportedNameConstraint:
167 case X509ChainStatusFlags.HasNotPermittedNameConstraint:
168 case X509ChainStatusFlags.HasExcludedNameConstraint:
169 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
171 case X509ChainStatusFlags.InvalidExtension:
173 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
176 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
177 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
178 case X509ChainStatusFlags.CtlNotTimeValid:
179 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
181 case X509ChainStatusFlags.CtlNotSignatureValid:
184 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
185 case X509ChainStatusFlags.CtlNotValidForUsage:
186 // FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
187 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
193 // once we have one failure there's no need to check further
198 // every "problem" was excluded
204 // note: this call doesn't Reset the X509ChainPolicy
205 if ((status != null) && (status.Length != 0))
207 if (elements.Count > 0)
209 if (user_root_store != null) {
210 user_root_store.Close ();
211 user_root_store = null;
213 if (root_store != null) {
217 if (user_ca_store != null) {
218 user_ca_store.Close ();
219 user_ca_store = null;
221 if (ca_store != null) {
228 bce_restriction = null;
229 working_public_key = null;
234 public static X509Chain Create ()
236 return (X509Chain) CryptoConfig.CreateFromName ("X509Chain");
241 private X509Certificate2Collection roots;
242 private X509Certificate2Collection cas;
243 private X509Store root_store;
244 private X509Store ca_store;
245 private X509Store user_root_store;
246 private X509Store user_ca_store;
248 private X509Certificate2Collection Roots {
251 X509Certificate2Collection c = new X509Certificate2Collection ();
252 X509Store store = LMRootStore;
253 if (location == StoreLocation.CurrentUser)
254 c.AddRange (UserRootStore.Certificates);
255 c.AddRange (store.Certificates);
262 private X509Certificate2Collection CertificateAuthorities {
265 X509Certificate2Collection c = new X509Certificate2Collection ();
266 X509Store store = LMCAStore;
267 if (location == StoreLocation.CurrentUser)
268 c.AddRange (UserCAStore.Certificates);
269 c.AddRange (store.Certificates);
276 private X509Store LMRootStore {
278 if (root_store == null) {
279 root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
281 root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
289 private X509Store UserRootStore {
291 if (user_root_store == null) {
292 user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
294 user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
298 return user_root_store;
302 private X509Store LMCAStore {
304 if (ca_store == null) {
305 ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
307 ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
315 private X509Store UserCAStore {
317 if (user_ca_store == null) {
318 user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
320 user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
324 return user_ca_store;
327 // *** certificate chain/path building stuff ***
329 private X509Certificate2Collection collection;
331 // we search local user (default) or machine certificate store
332 // and in the extra certificate supplied in ChainPolicy.ExtraStore
333 private X509Certificate2Collection CertificateCollection {
335 if (collection == null) {
336 collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
337 collection.AddRange (Roots);
338 collection.AddRange (CertificateAuthorities);
344 // This is a non-recursive chain/path building algorithm.
346 // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
347 // affect the path building (other errors are verification errors).
349 // Note that the order match the one we need to match MS and not the one defined in RFC3280,
350 // we also include the trusted root certificate (trust anchor in RFC3280) in the list.
351 // (this isn't an issue, just keep that in mind if you look at the source and the RFC)
352 private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
354 elements.Add (certificate);
356 while (!IsChainComplete (certificate)) {
357 certificate = FindParent (certificate);
359 if (certificate == null)
360 return X509ChainStatusFlags.PartialChain;
362 if (elements.Contains (certificate))
363 return X509ChainStatusFlags.Cyclic;
365 elements.Add (certificate);
368 // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
369 // trustiness (what a cute word) in the trusted root collection
370 if (!Roots.Contains (certificate))
371 elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;
373 return X509ChainStatusFlags.NoError;
377 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
385 // multiple candidate, keep only the ones that are still valid
386 X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
387 switch (time_valid.Count) {
389 // that's too restrictive, let's revert and try another thing...
393 return time_valid [0];
398 // again multiple candidates, let's find the AKI that match the SKI (if we have one)
399 string aki = GetAuthorityKeyIdentifier (child);
400 if (String.IsNullOrEmpty (aki)) {
401 return time_valid [0]; // FIXME: out of luck, you get the first one
403 foreach (X509Certificate2 parent in time_valid) {
404 string ski = GetSubjectKeyIdentifier (parent);
405 // if both id are available then they must match
409 return time_valid [0]; // FIXME: out of luck, you get the first one
413 private X509Certificate2 FindParent (X509Certificate2 certificate)
415 X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
416 string aki = GetAuthorityKeyIdentifier (certificate);
417 if ((aki != null) && (aki.Length > 0)) {
418 subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
420 X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
421 // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
422 return certificate.Equals (parent) ? null : parent;
425 private bool IsChainComplete (X509Certificate2 certificate)
427 // the chain is complete if we have a self-signed certificate
428 if (!IsSelfIssued (certificate))
431 // we're very limited to what we can do without certificate extensions
432 if (certificate.Version < 3)
435 // check that Authority Key Identifier == Subject Key Identifier
436 // e.g. it will be different if a self-signed certificate is part (not the end) of the chain
437 string ski = GetSubjectKeyIdentifier (certificate);
438 if (String.IsNullOrEmpty (ski))
440 string aki = GetAuthorityKeyIdentifier (certificate);
441 if (String.IsNullOrEmpty (aki))
443 // if both id are available then they must match
447 // check for "self-issued" certificate - without verifying the signature
448 // note that self-issued doesn't always mean it's a root certificate!
449 private bool IsSelfIssued (X509Certificate2 certificate)
451 return (certificate.Issuer == certificate.Subject);
455 // *** certificate chain/path validation stuff ***
457 // Currently a subset of RFC3280 (hopefully a full implementation someday)
458 private void ValidateChain (X509ChainStatusFlags flag)
460 // 'n' should be the root certificate...
461 int n = elements.Count - 1;
462 X509Certificate2 certificate = elements [n].Certificate;
464 // ... and, if so, must be treated outside the chain...
465 if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
467 // deal with the case where the chain == the root certificate
468 // (which isn't for RFC3280) part of the chain
470 elements [0].UncompressFlags ();
473 // skip the root certificate when processing the chain (in 6.1.3)
476 // ... unless the chain is a partial one (then we start with that one)
479 // 6.1.1.a - a prospective certificate path of length n (i.e. elements)
480 // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
481 // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
482 // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
483 // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
484 // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
485 // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)
487 // 6.1.2 - Initialization (incomplete)
488 // 6.1.2.a-f - policy stuff, some TODO, some not supported
489 // 6.1.2.g - working public key algorithm
490 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
491 // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
492 working_public_key = certificate.PublicKey.Key;
493 // 6.1.2.j - working issuer name
494 working_issuer_name = certificate.IssuerName;
495 // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
496 // may be reduced to the value in the path length constraint field
499 // 6.1.3 - Basic Certificate Processing
500 // note: loop looks reversed (the list is) but we process this part just like RFC3280 does
501 for (int i = n; i > 0; i--) {
503 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
504 PrepareForNextCertificate (i);
508 // 6.1.3.a.3 - revocation checks
509 CheckRevocationOnChain (flag);
511 // 6.1.5 - Wrap-up procedure
515 private void Process (int n)
517 X509ChainElement element = elements [n];
518 X509Certificate2 certificate = element.Certificate;
520 // pre-step: DSA certificates may inherit the parameters of their CA
521 if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
522 if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
523 X509Certificate2 parent = elements [n+1].Certificate;
524 certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
528 bool root = (working_public_key == null);
529 // 6.1.3.a.1 - check signature (with special case to deal with root certificates)
530 if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
531 // another special case where only an end-entity is available and can't be verified.
532 // In this case we do not report an invalid signature (since this is unknown)
533 if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
534 element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
538 // 6.1.3.a.2 - check validity period
539 if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
540 (ChainPolicy.VerificationTime > certificate.NotAfter)) {
541 element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
543 // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
545 // note: most of them don't apply to the root certificate
550 // 6.1.3.a.3 - revocation check (we're doing at the last stage)
551 // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)
553 // 6.1.3.a.4 - check certificate issuer name
554 if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
555 // NOTE: this is not the "right" error flag, but it's the closest one defined
556 element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
559 if (!IsSelfIssued (certificate) && (n != 0)) {
560 // TODO 6.1.3.b - subject name in the permitted_subtrees ...
561 // TODO 6.1.3.c - subject name not within excluded_subtrees...
563 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint
564 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
565 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
566 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
569 // TODO 6.1.3.d - check if certificate policies extension is present
571 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
572 // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
574 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
577 // TODO 6.1.3.e - set valid_policy_tree to NULL
580 // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
583 // CTL == Certificate Trust List / NOT SUPPORTED
584 // TODO - check for X509ChainStatusFlags.CtlNotTimeValid
585 // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
586 // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage
588 private void PrepareForNextCertificate (int n)
590 X509ChainElement element = elements [n];
591 X509Certificate2 certificate = element.Certificate;
596 working_issuer_name = certificate.SubjectName;
597 // 6.1.4.d-e - our key includes both the public key and it's parameters
598 working_public_key = certificate.PublicKey.Key;
600 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
604 // 6.1.4.k - Verify that the certificate is a CA certificate
605 X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
607 if (!bce.CertificateAuthority) {
608 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
610 } else if (certificate.Version >= 3) {
611 // recent (v3+) CA certificates must include BCE
612 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
615 // 6.1.4.l - if the certificate isn't self-issued...
616 if (!IsSelfIssued (certificate)) {
617 // ... verify that max_path_length > 0
618 if (max_path_length > 0) {
621 // to match MS the reported status must be against the certificate
622 // with the BCE and not where the path is too long. It also means
623 // that this condition has to be reported only once
624 if (bce_restriction != null) {
625 bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
630 // 6.1.4.m - if pathLengthConstraint is present...
631 if ((bce != null) && (bce.HasPathLengthConstraint)) {
632 // ... and is less that max_path_length, set max_path_length to it's value
633 if (bce.PathLengthConstraint < max_path_length) {
634 max_path_length = bce.PathLengthConstraint;
635 bce_restriction = element;
639 // 6.1.4.n - if key usage extension is present...
640 X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
642 // ... verify keyCertSign is set
643 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
644 if ((kue.KeyUsages & success) != success)
645 element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
648 // 6.1.4.o - recognize and process other critical extension present in the certificate
649 ProcessCertificateExtensions (element);
652 private void WrapUp ()
654 X509ChainElement element = elements [0];
655 X509Certificate2 certificate = element.Certificate;
657 // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
658 if (IsSelfIssued (certificate)) {
659 // TODO... decrement explicit_policy by 1
664 // 6.1.5.c,d,e - not required by the X509Chain implementation
666 // 6.1.5.f - recognize and process other critical extension present in the certificate
667 ProcessCertificateExtensions (element);
671 // uncompressed the flags into several elements
672 for (int i = elements.Count - 1; i >= 0; i--) {
673 elements [i].UncompressFlags ();
677 private void ProcessCertificateExtensions (X509ChainElement element)
679 foreach (X509Extension ext in element.Certificate.Extensions) {
681 switch (ext.Oid.Value) {
682 case "2.5.29.15": // X509KeyUsageExtension
683 case "2.5.29.19": // X509BasicConstraintsExtension
684 // we processed this extension
687 // note: Under Windows XP MS implementation seems to ignore
688 // certificate with unknown critical extensions.
689 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
696 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
700 // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
701 MX.X509Certificate mx = signed.MonoCertificate;
702 return (mx.VerifySignature (pubkey));
705 private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
707 X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
708 return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
711 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
712 static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
714 return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
717 // but anyway System.dll v2 doesn't expose CRL in any way so...
718 static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
720 return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
723 static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
727 MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
728 byte[] id = aki.Identifier;
731 StringBuilder sb = new StringBuilder ();
732 foreach (byte b in id)
733 sb.Append (b.ToString ("X02"));
734 return sb.ToString ();
737 // we check the revocation only once we have built the complete chain
738 private void CheckRevocationOnChain (X509ChainStatusFlags flag)
740 bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
743 switch (ChainPolicy.RevocationMode) {
744 case X509RevocationMode.Online:
748 case X509RevocationMode.Offline:
751 case X509RevocationMode.NoCheck:
754 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
757 bool unknown = partial;
758 // from the root down to the end-entity
759 for (int i = elements.Count - 1; i >= 0; i--) {
762 switch (ChainPolicy.RevocationFlag) {
763 case X509RevocationFlag.EndCertificateOnly:
766 case X509RevocationFlag.EntireChain:
769 case X509RevocationFlag.ExcludeRoot:
771 check = (i != (elements.Count - 1));
772 // anyway, who's gonna sign that the root is invalid ?
776 X509ChainElement element = elements [i];
778 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
780 unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
783 // we can skip the revocation checks as we can't be sure of them anyway
784 element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
785 element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
786 } else if (check && !partial && !IsSelfIssued (element.Certificate)) {
787 // check for revocation (except for the trusted root and self-issued certs)
788 element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
789 // if revoked, then all others following in the chain are unknown...
790 unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
795 // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
796 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
798 X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
799 X509ChainElement element = elements [ca];
800 X509Certificate2 ca_cert = element.Certificate;
802 // find the CRL from the "right" CA
803 while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
804 // try with this self-issued
805 result = CheckRevocation (certificate, ca_cert, online);
806 if (result != X509ChainStatusFlags.RevocationStatusUnknown)
809 element = elements [ca];
810 ca_cert = element.Certificate;
812 if (result == X509ChainStatusFlags.RevocationStatusUnknown)
813 result = CheckRevocation (certificate, ca_cert, online);
817 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
819 // change this if/when we support OCSP
820 X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
822 // ... verify CrlSign is set
823 X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
824 if ((kue.KeyUsages & success) != success) {
825 // FIXME - we should try to find an alternative CA that has the CrlSign bit
826 return X509ChainStatusFlags.RevocationStatusUnknown;
830 MX.X509Crl crl = FindCrl (ca_cert);
832 if ((crl == null) && online) {
833 // FIXME - download and install new CRL
834 // then you get a second chance
835 // crl = FindCrl (ca_cert, ref valid, ref out_of_date);
837 // We need to get the subjectAltName and an URI from there (or use OCSP)
838 // X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension);
842 // validate the digital signature on the CRL using the CA public key
843 // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
844 // checks and we loose the "why" of the failure
845 // note #2: we do this before other tests as an invalid signature could be a hacked CRL
846 // (so anything within can't be trusted)
847 if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
848 return X509ChainStatusFlags.RevocationStatusUnknown;
851 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
853 // We have an entry for this CRL that includes an unknown CRITICAL extension
854 // See [X.509 7.3] NOTE 4
855 if (!ProcessCrlEntryExtensions (entry))
856 return X509ChainStatusFlags.Revoked;
858 // FIXME - a little more is involved
859 if (entry.RevocationDate <= ChainPolicy.VerificationTime)
860 return X509ChainStatusFlags.Revoked;
863 // are we overdue for a CRL update ? if so we can't be sure of any certificate status
864 if (crl.NextUpdate < ChainPolicy.VerificationTime)
865 return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;
867 // we have a CRL that includes an unknown CRITICAL extension
868 // we put this check at the end so we do not "hide" any Revoked flags
869 if (!ProcessCrlExtensions (crl)) {
870 return X509ChainStatusFlags.RevocationStatusUnknown;
873 return X509ChainStatusFlags.RevocationStatusUnknown;
876 return X509ChainStatusFlags.NoError;
879 static MX.X509Crl CheckCrls (string subject, string ski, ArrayList crls)
881 foreach (MX.X509Crl crl in crls) {
882 if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
885 return null; // No CRL found
888 private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
890 string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
891 string ski = GetSubjectKeyIdentifier (caCertificate);
893 // consider that the LocalMachine directories could not exists... and cannot be created by the user
894 MX.X509Crl result = (LMCAStore.Store == null) ? null : CheckCrls (subject, ski, LMCAStore.Store.Crls);
897 if (location == StoreLocation.CurrentUser) {
898 result = CheckCrls (subject, ski, UserCAStore.Store.Crls);
903 // consider that the LocalMachine directories could not exists... and cannot be created by the user
904 result = (LMRootStore.Store == null) ? null : CheckCrls (subject, ski, LMRootStore.Store.Crls);
907 if (location == StoreLocation.CurrentUser) {
908 result = CheckCrls (subject, ski, UserRootStore.Store.Crls);
915 private bool ProcessCrlExtensions (MX.X509Crl crl)
917 foreach (MX.X509Extension ext in crl.Extensions) {
920 case "2.5.29.20": // cRLNumber
921 case "2.5.29.35": // authorityKeyIdentifier
922 // we processed/know about this extension
932 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
934 foreach (MX.X509Extension ext in entry.Extensions) {
937 case "2.5.29.21": // cRLReason
938 // we processed/know about this extension
950 namespace System.Security.Cryptography.X509Certificates {
951 public class X509Chain {
952 public bool Build (X509Certificate2 cert)