2010-04-06 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System / System.Security.Cryptography.X509Certificates / X509Chain.cs
blob678cb8f9baee41335edf3b0cf230f373ea721050
1 //
2 // System.Security.Cryptography.X509Certificates.X509Chain
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 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #if NET_2_0 && SECURITY_DEP
32 using System.Collections;
33 using System.Text;
35 using MX = Mono.Security.X509;
37 namespace System.Security.Cryptography.X509Certificates {
39 public class X509Chain {
41 private StoreLocation location;
42 private X509ChainElementCollection elements;
43 private X509ChainPolicy policy;
44 private X509ChainStatus[] status;
46 static X509ChainStatus[] Empty = new X509ChainStatus [0];
48 // RFC3280 variables
49 private int max_path_length;
50 private X500DistinguishedName working_issuer_name;
51 // private string working_public_key_algorithm;
52 private AsymmetricAlgorithm working_public_key;
54 // other flags
55 private X509ChainElement bce_restriction;
57 // constructors
59 public X509Chain ()
60 : this (false)
64 public X509Chain (bool useMachineContext)
66 location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
67 elements = new X509ChainElementCollection ();
68 policy = new X509ChainPolicy ();
71 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
72 public X509Chain (IntPtr chainContext)
74 // CryptoAPI compatibility (unmanaged handle)
75 throw new NotSupportedException ();
78 // properties
80 [MonoTODO ("Mono's X509Chain is fully managed. Always returns IntPtr.Zero.")]
81 public IntPtr ChainContext {
82 get { return IntPtr.Zero; }
85 public X509ChainElementCollection ChainElements {
86 get { return elements; }
89 public X509ChainPolicy ChainPolicy {
90 get { return policy; }
91 set { policy = value; }
94 public X509ChainStatus[] ChainStatus {
95 get {
96 if (status == null)
97 return Empty;
98 return status;
102 // methods
104 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
105 public bool Build (X509Certificate2 certificate)
107 if (certificate == null)
108 throw new ArgumentException ("certificate");
110 Reset ();
111 X509ChainStatusFlags flag;
112 try {
113 flag = BuildChainFrom (certificate);
114 ValidateChain (flag);
116 catch (CryptographicException ce) {
117 throw new ArgumentException ("certificate", ce);
120 X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
121 ArrayList list = new ArrayList ();
122 // build "global" ChainStatus from the ChainStatus of every ChainElements
123 foreach (X509ChainElement ce in elements) {
124 foreach (X509ChainStatus cs in ce.ChainElementStatus) {
125 // we MUST avoid duplicates in the "global" list
126 if ((total & cs.Status) != cs.Status) {
127 list.Add (cs);
128 total |= cs.Status;
132 // and if required add some
133 if (flag != X509ChainStatusFlags.NoError) {
134 list.Insert (0, new X509ChainStatus (flag));
136 status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
138 // (fast path) this ignore everything we have checked
139 if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
140 return true;
142 bool result = true;
143 // now check if exclude some verification for the "end result" (boolean)
144 foreach (X509ChainStatus cs in status) {
145 switch (cs.Status) {
146 case X509ChainStatusFlags.UntrustedRoot:
147 case X509ChainStatusFlags.PartialChain:
148 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
149 break;
150 case X509ChainStatusFlags.NotTimeValid:
151 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
152 break;
153 // FIXME - from here we needs new test cases for all cases
154 case X509ChainStatusFlags.NotTimeNested:
155 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
156 break;
157 case X509ChainStatusFlags.InvalidBasicConstraints:
158 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
159 break;
160 case X509ChainStatusFlags.InvalidPolicyConstraints:
161 case X509ChainStatusFlags.NoIssuanceChainPolicy:
162 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
163 break;
164 case X509ChainStatusFlags.InvalidNameConstraints:
165 case X509ChainStatusFlags.HasNotSupportedNameConstraint:
166 case X509ChainStatusFlags.HasNotPermittedNameConstraint:
167 case X509ChainStatusFlags.HasExcludedNameConstraint:
168 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
169 break;
170 case X509ChainStatusFlags.InvalidExtension:
171 // not sure ?!?
172 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
173 break;
175 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
176 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
177 case X509ChainStatusFlags.CtlNotTimeValid:
178 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
179 break;
180 case X509ChainStatusFlags.CtlNotSignatureValid:
181 // ?
182 break;
183 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
184 case X509ChainStatusFlags.CtlNotValidForUsage:
185 // FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
186 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
187 break;
188 default:
189 result = false;
190 break;
192 // once we have one failure there's no need to check further
193 if (!result)
194 return false;
197 // every "problem" was excluded
198 return true;
201 public void Reset ()
203 // note: this call doesn't Reset the X509ChainPolicy
204 if ((status != null) && (status.Length != 0))
205 status = null;
206 if (elements.Count > 0)
207 elements.Clear ();
208 if (roots != null) {
209 roots.Close ();
210 roots = null;
212 if (cas != null) {
213 cas.Close ();
214 cas = null;
216 collection = null;
217 bce_restriction = null;
218 working_public_key = null;
221 // static methods
223 public static X509Chain Create ()
225 return (X509Chain) CryptoConfig.CreateFromName ("X509Chain");
228 // private stuff
230 private X509Store roots;
231 private X509Store cas;
233 private X509Store Roots {
234 get {
235 if (roots == null) {
236 roots = new X509Store (StoreName.Root, location);
237 roots.Open (OpenFlags.ReadOnly);
239 return roots;
243 private X509Store CertificateAuthorities {
244 get {
245 if (cas == null) {
246 cas = new X509Store (StoreName.CertificateAuthority, location);
247 cas.Open (OpenFlags.ReadOnly);
249 return cas;
253 // *** certificate chain/path building stuff ***
255 private X509Certificate2Collection collection;
257 // we search local user (default) or machine certificate store
258 // and in the extra certificate supplied in ChainPolicy.ExtraStore
259 private X509Certificate2Collection CertificateCollection {
260 get {
261 if (collection == null) {
262 collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
263 if (Roots.Certificates.Count > 0)
264 collection.AddRange (Roots.Certificates);
265 if (CertificateAuthorities.Certificates.Count > 0)
266 collection.AddRange (CertificateAuthorities.Certificates);
268 return collection;
272 // This is a non-recursive chain/path building algorithm.
274 // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
275 // affect the path building (other errors are verification errors).
277 // Note that the order match the one we need to match MS and not the one defined in RFC3280,
278 // we also include the trusted root certificate (trust anchor in RFC3280) in the list.
279 // (this isn't an issue, just keep that in mind if you look at the source and the RFC)
280 private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
282 elements.Add (certificate);
284 while (!IsChainComplete (certificate)) {
285 certificate = FindParent (certificate);
287 if (certificate == null)
288 return X509ChainStatusFlags.PartialChain;
290 if (elements.Contains (certificate))
291 return X509ChainStatusFlags.Cyclic;
293 elements.Add (certificate);
296 // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
297 // trustiness (what a cute word) in the trusted root collection
298 if (!Roots.Certificates.Contains (certificate))
299 elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;
301 return X509ChainStatusFlags.NoError;
305 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
307 switch (c.Count) {
308 case 0:
309 return null;
310 case 1:
311 return c [0];
312 default:
313 // multiple candidate, keep only the ones that are still valid
314 X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
315 switch (time_valid.Count) {
316 case 0:
317 // that's too restrictive, let's revert and try another thing...
318 time_valid = c;
319 break;
320 case 1:
321 return time_valid [0];
322 default:
323 break;
326 // again multiple candidates, let's find the AKI that match the SKI (if we have one)
327 string aki = GetAuthorityKeyIdentifier (child);
328 if (String.IsNullOrEmpty (aki)) {
329 return time_valid [0]; // FIXME: out of luck, you get the first one
331 foreach (X509Certificate2 parent in time_valid) {
332 string ski = GetSubjectKeyIdentifier (parent);
333 // if both id are available then they must match
334 if (aki == ski)
335 return parent;
337 return time_valid [0]; // FIXME: out of luck, you get the first one
341 private X509Certificate2 FindParent (X509Certificate2 certificate)
343 X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
344 string aki = GetAuthorityKeyIdentifier (certificate);
345 if ((aki != null) && (aki.Length > 0)) {
346 subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
348 X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
349 // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
350 return certificate.Equals (parent) ? null : parent;
353 private bool IsChainComplete (X509Certificate2 certificate)
355 // the chain is complete if we have a self-signed certificate
356 if (!IsSelfIssued (certificate))
357 return false;
359 // we're very limited to what we can do without certificate extensions
360 if (certificate.Version < 3)
361 return true;
363 // check that Authority Key Identifier == Subject Key Identifier
364 // e.g. it will be different if a self-signed certificate is part (not the end) of the chain
365 string ski = GetSubjectKeyIdentifier (certificate);
366 if (String.IsNullOrEmpty (ski))
367 return true;
368 string aki = GetAuthorityKeyIdentifier (certificate);
369 if (String.IsNullOrEmpty (aki))
370 return true;
371 // if both id are available then they must match
372 return (aki == ski);
375 // check for "self-issued" certificate - without verifying the signature
376 // note that self-issued doesn't always mean it's a root certificate!
377 private bool IsSelfIssued (X509Certificate2 certificate)
379 return (certificate.Issuer == certificate.Subject);
383 // *** certificate chain/path validation stuff ***
385 // Currently a subset of RFC3280 (hopefully a full implementation someday)
386 private void ValidateChain (X509ChainStatusFlags flag)
388 // 'n' should be the root certificate...
389 int n = elements.Count - 1;
390 X509Certificate2 certificate = elements [n].Certificate;
392 // ... and, if so, must be treated outside the chain...
393 if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
394 Process (n);
395 // deal with the case where the chain == the root certificate
396 // (which isn't for RFC3280) part of the chain
397 if (n == 0) {
398 elements [0].UncompressFlags ();
399 return;
401 // skip the root certificate when processing the chain (in 6.1.3)
402 n--;
404 // ... unless the chain is a partial one (then we start with that one)
406 // 6.1.1 - Inputs
407 // 6.1.1.a - a prospective certificate path of length n (i.e. elements)
408 // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
409 // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
410 // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
411 // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
412 // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
413 // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)
415 // 6.1.2 - Initialization (incomplete)
416 // 6.1.2.a-f - policy stuff, some TODO, some not supported
417 // 6.1.2.g - working public key algorithm
418 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
419 // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
420 working_public_key = certificate.PublicKey.Key;
421 // 6.1.2.j - working issuer name
422 working_issuer_name = certificate.IssuerName;
423 // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
424 // may be reduced to the value in the path length constraint field
425 max_path_length = n;
427 // 6.1.3 - Basic Certificate Processing
428 // note: loop looks reversed (the list is) but we process this part just like RFC3280 does
429 for (int i = n; i > 0; i--) {
430 Process (i);
431 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
432 PrepareForNextCertificate (i);
434 Process (0);
436 // 6.1.3.a.3 - revocation checks
437 CheckRevocationOnChain (flag);
439 // 6.1.5 - Wrap-up procedure
440 WrapUp ();
443 private void Process (int n)
445 X509ChainElement element = elements [n];
446 X509Certificate2 certificate = element.Certificate;
448 // pre-step: DSA certificates may inherit the parameters of their CA
449 if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
450 if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
451 X509Certificate2 parent = elements [n+1].Certificate;
452 certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
456 bool root = (working_public_key == null);
457 // 6.1.3.a.1 - check signature (with special case to deal with root certificates)
458 if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
459 // another special case where only an end-entity is available and can't be verified.
460 // In this case we do not report an invalid signature (since this is unknown)
461 if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
462 element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
466 // 6.1.3.a.2 - check validity period
467 if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
468 (ChainPolicy.VerificationTime > certificate.NotAfter)) {
469 element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
471 // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
473 // note: most of them don't apply to the root certificate
474 if (root) {
475 return;
478 // 6.1.3.a.3 - revocation check (we're doing at the last stage)
479 // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)
481 // 6.1.3.a.4 - check certificate issuer name
482 if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
483 // NOTE: this is not the "right" error flag, but it's the closest one defined
484 element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
487 if (!IsSelfIssued (certificate) && (n != 0)) {
488 // TODO 6.1.3.b - subject name in the permitted_subtrees ...
489 // TODO 6.1.3.c - subject name not within excluded_subtrees...
491 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint
492 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
493 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
494 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
497 // TODO 6.1.3.d - check if certificate policies extension is present
498 //if (false) {
499 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
500 // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
502 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
504 //} else {
505 // TODO 6.1.3.e - set valid_policy_tree to NULL
508 // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
511 // CTL == Certificate Trust List / NOT SUPPORTED
512 // TODO - check for X509ChainStatusFlags.CtlNotTimeValid
513 // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
514 // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage
516 private void PrepareForNextCertificate (int n)
518 X509ChainElement element = elements [n];
519 X509Certificate2 certificate = element.Certificate;
521 // TODO 6.1.4.a-b
523 // 6.1.4.c
524 working_issuer_name = certificate.SubjectName;
525 // 6.1.4.d-e - our key includes both the public key and it's parameters
526 working_public_key = certificate.PublicKey.Key;
527 // 6.1.4.f
528 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
530 // TODO 6.1.4.g-j
532 // 6.1.4.k - Verify that the certificate is a CA certificate
533 X509BasicConstraintsExtension bce = (X509BasicConstraintsExtension) certificate.Extensions["2.5.29.19"];
534 if (bce != null) {
535 if (!bce.CertificateAuthority) {
536 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
538 } else if (certificate.Version >= 3) {
539 // recent (v3+) CA certificates must include BCE
540 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
543 // 6.1.4.l - if the certificate isn't self-issued...
544 if (!IsSelfIssued (certificate)) {
545 // ... verify that max_path_length > 0
546 if (max_path_length > 0) {
547 max_path_length--;
548 } else {
549 // to match MS the reported status must be against the certificate
550 // with the BCE and not where the path is too long. It also means
551 // that this condition has to be reported only once
552 if (bce_restriction != null) {
553 bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
558 // 6.1.4.m - if pathLengthConstraint is present...
559 if ((bce != null) && (bce.HasPathLengthConstraint)) {
560 // ... and is less that max_path_length, set max_path_length to it's value
561 if (bce.PathLengthConstraint < max_path_length) {
562 max_path_length = bce.PathLengthConstraint;
563 bce_restriction = element;
567 // 6.1.4.n - if key usage extension is present...
568 X509KeyUsageExtension kue = (X509KeyUsageExtension) certificate.Extensions["2.5.29.15"];
569 if (kue != null) {
570 // ... verify keyCertSign is set
571 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
572 if ((kue.KeyUsages & success) != success)
573 element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
576 // 6.1.4.o - recognize and process other critical extension present in the certificate
577 ProcessCertificateExtensions (element);
580 private void WrapUp ()
582 X509ChainElement element = elements [0];
583 X509Certificate2 certificate = element.Certificate;
585 // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
586 if (IsSelfIssued (certificate)) {
587 // TODO... decrement explicit_policy by 1
590 // 6.1.5.b - TODO
592 // 6.1.5.c,d,e - not required by the X509Chain implementation
594 // 6.1.5.f - recognize and process other critical extension present in the certificate
595 ProcessCertificateExtensions (element);
597 // 6.1.5.g - TODO
599 // uncompressed the flags into several elements
600 for (int i = elements.Count - 1; i >= 0; i--) {
601 elements [i].UncompressFlags ();
605 private void ProcessCertificateExtensions (X509ChainElement element)
607 foreach (X509Extension ext in element.Certificate.Extensions) {
608 if (ext.Critical) {
609 switch (ext.Oid.Value) {
610 case "2.5.29.15": // X509KeyUsageExtension
611 case "2.5.29.19": // X509BasicConstraintsExtension
612 // we processed this extension
613 break;
614 default:
615 // note: Under Windows XP MS implementation seems to ignore
616 // certificate with unknown critical extensions.
617 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
618 break;
624 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
626 if (pubkey == null)
627 return false;
628 // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
629 MX.X509Certificate mx = signed.MonoCertificate;
630 return (mx.VerifySignature (pubkey));
633 private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
635 X509SubjectKeyIdentifierExtension ski = (X509SubjectKeyIdentifierExtension) certificate.Extensions["2.5.29.14"];
636 return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
639 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
640 private string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
642 return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
645 // but anyway System.dll v2 doesn't expose CRL in any way so...
646 private string GetAuthorityKeyIdentifier (MX.X509Crl crl)
648 return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
651 private string GetAuthorityKeyIdentifier (MX.X509Extension ext)
653 if (ext == null)
654 return String.Empty;
655 MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
656 byte[] id = aki.Identifier;
657 if (id == null)
658 return String.Empty;
659 StringBuilder sb = new StringBuilder ();
660 foreach (byte b in id)
661 sb.Append (b.ToString ("X02"));
662 return sb.ToString ();
665 // we check the revocation only once we have built the complete chain
666 private void CheckRevocationOnChain (X509ChainStatusFlags flag)
668 bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
669 bool online;
671 switch (ChainPolicy.RevocationMode) {
672 case X509RevocationMode.Online:
673 // default
674 online = true;
675 break;
676 case X509RevocationMode.Offline:
677 online = false;
678 break;
679 case X509RevocationMode.NoCheck:
680 return;
681 default:
682 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
685 bool unknown = partial;
686 // from the root down to the end-entity
687 for (int i = elements.Count - 1; i >= 0; i--) {
688 bool check = true;
690 switch (ChainPolicy.RevocationFlag) {
691 case X509RevocationFlag.EndCertificateOnly:
692 check = (i == 0);
693 break;
694 case X509RevocationFlag.EntireChain:
695 check = true;
696 break;
697 case X509RevocationFlag.ExcludeRoot:
698 // default
699 check = (i != (elements.Count - 1));
700 // anyway, who's gonna sign that the root is invalid ?
701 break;
704 X509ChainElement element = elements [i];
706 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
707 if (!unknown)
708 unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
710 if (unknown) {
711 // we can skip the revocation checks as we can't be sure of them anyway
712 element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
713 element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
714 } else if (check && !partial && !IsSelfIssued (element.Certificate)) {
715 // check for revocation (except for the trusted root and self-issued certs)
716 element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
717 // if revoked, then all others following in the chain are unknown...
718 unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
723 // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
724 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
726 X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
727 X509ChainElement element = elements [ca];
728 X509Certificate2 ca_cert = element.Certificate;
730 // find the CRL from the "right" CA
731 while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
732 // try with this self-issued
733 result = CheckRevocation (certificate, ca_cert, online);
734 if (result != X509ChainStatusFlags.RevocationStatusUnknown)
735 break;
736 ca++;
737 element = elements [ca];
738 ca_cert = element.Certificate;
740 if (result == X509ChainStatusFlags.RevocationStatusUnknown)
741 result = CheckRevocation (certificate, ca_cert, online);
742 return result;
745 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
747 // change this if/when we support OCSP
748 X509KeyUsageExtension kue = (X509KeyUsageExtension) ca_cert.Extensions["2.5.29.15"];
749 if (kue != null) {
750 // ... verify CrlSign is set
751 X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
752 if ((kue.KeyUsages & success) != success) {
753 // FIXME - we should try to find an alternative CA that has the CrlSign bit
754 return X509ChainStatusFlags.RevocationStatusUnknown;
758 MX.X509Crl crl = FindCrl (ca_cert);
760 if ((crl == null) && online) {
761 // FIXME - download and install new CRL
762 // then you get a second chance
763 // crl = FindCrl (ca_cert, ref valid, ref out_of_date);
765 // We need to get the subjectAltName and an URI from there (or use OCSP)
766 // X509KeyUsageExtension subjectAltName = (X509KeyUsageExtension) ca_cert.Extensions["2.5.29.17"];
769 if (crl != null) {
770 // validate the digital signature on the CRL using the CA public key
771 // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
772 // checks and we loose the "why" of the failure
773 // note #2: we do this before other tests as an invalid signature could be a hacked CRL
774 // (so anything within can't be trusted)
775 if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
776 return X509ChainStatusFlags.RevocationStatusUnknown;
779 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
780 if (entry != null) {
781 // We have an entry for this CRL that includes an unknown CRITICAL extension
782 // See [X.509 7.3] NOTE 4
783 if (!ProcessCrlEntryExtensions (entry))
784 return X509ChainStatusFlags.Revoked;
786 // FIXME - a little more is involved
787 if (entry.RevocationDate <= ChainPolicy.VerificationTime)
788 return X509ChainStatusFlags.Revoked;
791 // are we overdue for a CRL update ? if so we can't be sure of any certificate status
792 if (crl.NextUpdate < ChainPolicy.VerificationTime)
793 return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;
795 // we have a CRL that includes an unknown CRITICAL extension
796 // we put this check at the end so we do not "hide" any Revoked flags
797 if (!ProcessCrlExtensions (crl)) {
798 return X509ChainStatusFlags.RevocationStatusUnknown;
800 } else {
801 return X509ChainStatusFlags.RevocationStatusUnknown;
804 return X509ChainStatusFlags.NoError;
807 private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
809 string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
810 string ski = GetSubjectKeyIdentifier (caCertificate);
811 foreach (MX.X509Crl crl in CertificateAuthorities.Store.Crls) {
812 if (crl.IssuerName == subject) {
813 if ((ski.Length == 0) || (ski == GetAuthorityKeyIdentifier (crl)))
814 return crl;
817 foreach (MX.X509Crl crl in Roots.Store.Crls) {
818 if (crl.IssuerName == subject) {
819 if ((ski.Length == 0) || (ski == GetAuthorityKeyIdentifier (crl)))
820 return crl;
823 return null;
826 private bool ProcessCrlExtensions (MX.X509Crl crl)
828 foreach (MX.X509Extension ext in crl.Extensions) {
829 if (ext.Critical) {
830 switch (ext.Oid) {
831 case "2.5.29.20": // cRLNumber
832 case "2.5.29.35": // authorityKeyIdentifier
833 // we processed/know about this extension
834 break;
835 default:
836 return false;
840 return true;
843 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
845 foreach (MX.X509Extension ext in entry.Extensions) {
846 if (ext.Critical) {
847 switch (ext.Oid) {
848 case "2.5.29.21": // cRLReason
849 // we processed/know about this extension
850 break;
851 default:
852 return false;
856 return true;
860 #elif NET_2_0 && !MOONLIGHT
861 namespace System.Security.Cryptography.X509Certificates {
862 public class X509Chain {
863 public bool Build (X509Certificate2 cert)
865 return false;
869 #endif