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)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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 SECURITY_DEP || MOONLIGHT
32 using System
.Collections
;
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];
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
;
55 private X509ChainElement bce_restriction
;
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 ();
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
{
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");
111 X509ChainStatusFlags flag
;
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
) {
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
))
143 // now check if exclude some verification for the "end result" (boolean)
144 foreach (X509ChainStatus cs
in status
) {
146 case X509ChainStatusFlags
.UntrustedRoot
:
147 case X509ChainStatusFlags
.PartialChain
:
148 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.AllowUnknownCertificateAuthority
) != 0);
150 case X509ChainStatusFlags
.NotTimeValid
:
151 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreNotTimeValid
) != 0);
153 // FIXME - from here we needs new test cases for all cases
154 case X509ChainStatusFlags
.NotTimeNested
:
155 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreNotTimeNested
) != 0);
157 case X509ChainStatusFlags
.InvalidBasicConstraints
:
158 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreInvalidBasicConstraints
) != 0);
160 case X509ChainStatusFlags
.InvalidPolicyConstraints
:
161 case X509ChainStatusFlags
.NoIssuanceChainPolicy
:
162 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreInvalidPolicy
) != 0);
164 case X509ChainStatusFlags
.InvalidNameConstraints
:
165 case X509ChainStatusFlags
.HasNotSupportedNameConstraint
:
166 case X509ChainStatusFlags
.HasNotPermittedNameConstraint
:
167 case X509ChainStatusFlags
.HasExcludedNameConstraint
:
168 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreInvalidName
) != 0);
170 case X509ChainStatusFlags
.InvalidExtension
:
172 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreWrongUsage
) != 0);
175 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
176 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
177 case X509ChainStatusFlags
.CtlNotTimeValid
:
178 result
&= ((ChainPolicy
.VerificationFlags
& X509VerificationFlags
.IgnoreCtlNotTimeValid
) != 0);
180 case X509ChainStatusFlags
.CtlNotSignatureValid
:
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);
192 // once we have one failure there's no need to check further
197 // every "problem" was excluded
203 // note: this call doesn't Reset the X509ChainPolicy
204 if ((status
!= null) && (status
.Length
!= 0))
206 if (elements
.Count
> 0)
217 bce_restriction
= null;
218 working_public_key
= null;
223 public static X509Chain
Create ()
225 return (X509Chain
) CryptoConfig
.CreateFromName ("X509Chain");
230 private X509Store roots
;
231 private X509Store cas
;
233 private X509Store Roots
{
236 roots
= new X509Store (StoreName
.Root
, location
);
237 roots
.Open (OpenFlags
.ReadOnly
);
243 private X509Store CertificateAuthorities
{
246 cas
= new X509Store (StoreName
.CertificateAuthority
, location
);
247 cas
.Open (OpenFlags
.ReadOnly
);
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
{
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
);
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
)
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
) {
317 // that's too restrictive, let's revert and try another thing...
321 return time_valid
[0];
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
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
))
359 // we're very limited to what we can do without certificate extensions
360 if (certificate
.Version
< 3)
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
))
368 string aki
= GetAuthorityKeyIdentifier (certificate
);
369 if (String
.IsNullOrEmpty (aki
))
371 // if both id are available then they must match
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)) {
395 // deal with the case where the chain == the root certificate
396 // (which isn't for RFC3280) part of the chain
398 elements
[0].UncompressFlags ();
401 // skip the root certificate when processing the chain (in 6.1.3)
404 // ... unless the chain is a partial one (then we start with that one)
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
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
--) {
431 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
432 PrepareForNextCertificate (i
);
436 // 6.1.3.a.3 - revocation checks
437 CheckRevocationOnChain (flag
);
439 // 6.1.5 - Wrap-up procedure
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
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
499 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
500 // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
502 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
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
;
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
;
528 // working_public_key_algorithm = certificate.PublicKey.Oid.Value;
532 // 6.1.4.k - Verify that the certificate is a CA certificate
533 X509BasicConstraintsExtension bce
= (X509BasicConstraintsExtension
) certificate
.Extensions
["2.5.29.19"];
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) {
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"];
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
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
);
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
) {
609 switch (ext
.Oid
.Value
) {
610 case "2.5.29.15": // X509KeyUsageExtension
611 case "2.5.29.19": // X509BasicConstraintsExtension
612 // we processed this extension
615 // note: Under Windows XP MS implementation seems to ignore
616 // certificate with unknown critical extensions.
617 element
.StatusFlags
|= X509ChainStatusFlags
.InvalidExtension
;
624 private bool IsSignedWith (X509Certificate2 signed
, AsymmetricAlgorithm pubkey
)
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
)
655 MX
.Extensions
.AuthorityKeyIdentifierExtension aki
= new MX
.Extensions
.AuthorityKeyIdentifierExtension (ext
);
656 byte[] id
= aki
.Identifier
;
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);
671 switch (ChainPolicy
.RevocationMode
) {
672 case X509RevocationMode
.Online
:
676 case X509RevocationMode
.Offline
:
679 case X509RevocationMode
.NoCheck
:
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
--) {
690 switch (ChainPolicy
.RevocationFlag
) {
691 case X509RevocationFlag
.EndCertificateOnly
:
694 case X509RevocationFlag
.EntireChain
:
697 case X509RevocationFlag
.ExcludeRoot
:
699 check
= (i
!= (elements
.Count
- 1));
700 // anyway, who's gonna sign that the root is invalid ?
704 X509ChainElement element
= elements
[i
];
706 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
708 unknown
|= ((element
.StatusFlags
& X509ChainStatusFlags
.NotSignatureValid
) != 0);
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
)
737 element
= elements
[ca
];
738 ca_cert
= element
.Certificate
;
740 if (result
== X509ChainStatusFlags
.RevocationStatusUnknown
)
741 result
= CheckRevocation (certificate
, ca_cert
, online
);
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"];
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"];
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
);
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
;
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
)))
817 foreach (MX
.X509Crl crl
in Roots
.Store
.Crls
) {
818 if (crl
.IssuerName
== subject
) {
819 if ((ski
.Length
== 0) || (ski
== GetAuthorityKeyIdentifier (crl
)))
826 private bool ProcessCrlExtensions (MX
.X509Crl crl
)
828 foreach (MX
.X509Extension ext
in crl
.Extensions
) {
831 case "2.5.29.20": // cRLNumber
832 case "2.5.29.35": // authorityKeyIdentifier
833 // we processed/know about this extension
843 private bool ProcessCrlEntryExtensions (MX
.X509Crl
.X509CrlEntry entry
)
845 foreach (MX
.X509Extension ext
in entry
.Extensions
) {
848 case "2.5.29.21": // cRLReason
849 // we processed/know about this extension
861 namespace System
.Security
.Cryptography
.X509Certificates
{
862 public class X509Chain
{
863 public bool Build (X509Certificate2 cert
)