Added tests for Task.WhenAll w/ empty list
[mono.git] / mcs / class / System / System.Security.Cryptography.X509Certificates / X509Chain.cs
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 // Copyright (C) 2011 Xamarin Inc. (http://www.xamarin.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 #if SECURITY_DEP
32
33 extern alias MonoSecurity;
34
35 using System.Collections;
36 using System.Text;
37
38 using MX = MonoSecurity::Mono.Security.X509;
39
40 namespace System.Security.Cryptography.X509Certificates {
41
42         public class X509Chain {
43
44                 private StoreLocation location;
45                 private X509ChainElementCollection elements;
46                 private X509ChainPolicy policy;
47                 private X509ChainStatus[] status;
48
49                 static X509ChainStatus[] Empty = new X509ChainStatus [0];
50
51                 // RFC3280 variables
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;
56
57                 // other flags
58                 private X509ChainElement bce_restriction;
59
60                 // constructors
61
62                 public X509Chain ()
63                         : this (false)
64                 {
65                 }
66
67                 public X509Chain (bool useMachineContext) 
68                 {
69                         location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
70                         elements = new X509ChainElementCollection ();
71                         policy = new X509ChainPolicy ();
72                 }
73
74                 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
75                 public X509Chain (IntPtr chainContext)
76                 {
77                         // CryptoAPI compatibility (unmanaged handle)
78                         throw new NotSupportedException ();
79                 }
80
81                 // properties
82
83                 [MonoTODO ("Mono's X509Chain is fully managed. Always returns IntPtr.Zero.")]
84                 public IntPtr ChainContext {
85                         get { return IntPtr.Zero; }
86                 }
87
88                 public X509ChainElementCollection ChainElements {
89                         get { return elements; }
90                 }
91
92                 public X509ChainPolicy ChainPolicy {
93                         get { return policy; }
94                         set { policy = value; }
95                 }
96
97                 public X509ChainStatus[] ChainStatus {
98                         get { 
99                                 if (status == null)
100                                         return Empty;
101                                 return status;
102                         }
103                 } 
104
105                 // methods
106
107                 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
108                 public bool Build (X509Certificate2 certificate)
109                 {
110                         if (certificate == null)
111                                 throw new ArgumentException ("certificate");
112
113                         Reset ();
114                         X509ChainStatusFlags flag;
115                         try {
116                                 flag = BuildChainFrom (certificate);
117                                 ValidateChain (flag);
118                         }
119                         catch (CryptographicException ce) {
120                                 throw new ArgumentException ("certificate", ce);
121                         }
122
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) {
130                                                 list.Add (cs);
131                                                 total |= cs.Status;
132                                         }
133                                 }
134                         }
135                         // and if required add some
136                         if (flag != X509ChainStatusFlags.NoError) {
137                                 list.Insert (0, new X509ChainStatus (flag));
138                         }
139                         status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
140
141                         // (fast path) this ignore everything we have checked
142                         if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
143                                 return true;
144
145                         bool result = true;
146                         // now check if exclude some verification for the "end result" (boolean)
147                         foreach (X509ChainStatus cs in status) {
148                                 switch (cs.Status) {
149                                 case X509ChainStatusFlags.UntrustedRoot:
150                                 case X509ChainStatusFlags.PartialChain:
151                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
152                                         break;
153                                 case X509ChainStatusFlags.NotTimeValid:
154                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
155                                         break;
156                                 // FIXME - from here we needs new test cases for all cases
157                                 case X509ChainStatusFlags.NotTimeNested:
158                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
159                                         break;
160                                 case X509ChainStatusFlags.InvalidBasicConstraints:
161                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
162                                         break;
163                                 case X509ChainStatusFlags.InvalidPolicyConstraints:
164                                 case X509ChainStatusFlags.NoIssuanceChainPolicy:
165                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
166                                         break;
167                                 case X509ChainStatusFlags.InvalidNameConstraints:
168                                 case X509ChainStatusFlags.HasNotSupportedNameConstraint:
169                                 case X509ChainStatusFlags.HasNotPermittedNameConstraint:
170                                 case X509ChainStatusFlags.HasExcludedNameConstraint:
171                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
172                                         break;
173                                 case X509ChainStatusFlags.InvalidExtension:
174                                         // not sure ?!?
175                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
176                                         break;
177                                 //
178                                 //      ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
179                                 //      ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
180                                 case X509ChainStatusFlags.CtlNotTimeValid:
181                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
182                                         break;
183                                 case X509ChainStatusFlags.CtlNotSignatureValid:
184                                         // ?
185                                         break;
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);
190                                         break;
191                                 default:
192                                         result = false;
193                                         break;
194                                 }
195                                 // once we have one failure there's no need to check further
196                                 if (!result)
197                                         return false;
198                         }
199
200                         // every "problem" was excluded
201                         return true;
202                 }
203
204                 public void Reset () 
205                 {
206                         // note: this call doesn't Reset the X509ChainPolicy
207                         if ((status != null) && (status.Length != 0))
208                                 status = null;
209                         if (elements.Count > 0)
210                                 elements.Clear ();
211                         if (user_root_store != null) {
212                                 user_root_store.Close ();
213                                 user_root_store = null;
214                         }
215                         if (root_store != null) {
216                                 root_store.Close ();
217                                 root_store = null;
218                         }
219                         if (user_ca_store != null) {
220                                 user_ca_store.Close ();
221                                 user_ca_store = null;
222                         }
223                         if (ca_store != null) {
224                                 ca_store.Close ();
225                                 ca_store = null;
226                         }
227                         roots = null;
228                         cas = null;
229                         collection = null;
230                         bce_restriction = null;
231                         working_public_key = null;
232                 }
233
234                 // static methods
235
236                 public static X509Chain Create ()
237                 {
238                         return (X509Chain) CryptoConfig.CreateFromName ("X509Chain");
239                 }
240
241                 // private stuff
242
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;
249
250                 private X509Certificate2Collection Roots {
251                         get {
252                                 if (roots == null) {
253                                         X509Certificate2Collection c = new X509Certificate2Collection ();
254                                         X509Store store = LMRootStore;
255                                         if (location == StoreLocation.CurrentUser)
256                                                 c.AddRange (UserRootStore.Certificates);
257                                         c.AddRange (store.Certificates);
258                                         roots = c;
259                                 }
260                                 return roots;
261                         }
262                 }
263
264                 private X509Certificate2Collection CertificateAuthorities {
265                         get {
266                                 if (cas == null) {
267                                         X509Certificate2Collection c = new X509Certificate2Collection ();
268                                         X509Store store = LMCAStore;
269                                         if (location == StoreLocation.CurrentUser)
270                                                 c.AddRange (UserCAStore.Certificates);
271                                         c.AddRange (store.Certificates);
272                                         cas = c;
273                                 }
274                                 return cas;
275                         }
276                 }
277
278                 private X509Store LMRootStore {
279                         get {
280                                 if (root_store == null) {
281                                         root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
282                                         try {
283                                                 root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
284                                         } catch {
285                                         }
286                                 }
287                                 return root_store;
288                         }
289                 }
290
291                 private X509Store UserRootStore {
292                         get {
293                                 if (user_root_store == null) {
294                                         user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
295                                         try {
296                                                 user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
297                                         } catch {
298                                         }
299                                 }
300                                 return user_root_store;
301                         }
302                 }
303
304                 private X509Store LMCAStore {
305                         get {
306                                 if (ca_store == null) {
307                                         ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
308                                         try {
309                                                 ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
310                                         } catch {
311                                         }
312                                 }
313                                 return ca_store;
314                         }
315                 }
316
317                 private X509Store UserCAStore {
318                         get {
319                                 if (user_ca_store == null) {
320                                         user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
321                                         try {
322                                                 user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
323                                         } catch {
324                                         }
325                                 }
326                                 return user_ca_store;
327                         }
328                 }
329                 // *** certificate chain/path building stuff ***
330
331                 private X509Certificate2Collection collection;
332
333                 // we search local user (default) or machine certificate store 
334                 // and in the extra certificate supplied in ChainPolicy.ExtraStore
335                 private X509Certificate2Collection CertificateCollection {
336                         get {
337                                 if (collection == null) {
338                                         collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
339                                         collection.AddRange (Roots);
340                                         collection.AddRange (CertificateAuthorities);
341                                 }
342                                 return collection;
343                         }
344                 }
345
346                 // This is a non-recursive chain/path building algorithm. 
347                 //
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).
350                 //
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)
355                 {
356                         elements.Add (certificate);
357
358                         while (!IsChainComplete (certificate)) {
359                                 certificate = FindParent (certificate);
360
361                                 if (certificate == null)
362                                         return X509ChainStatusFlags.PartialChain;
363
364                                 if (elements.Contains (certificate))
365                                         return X509ChainStatusFlags.Cyclic;
366
367                                 elements.Add (certificate);
368                         }
369
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;
374
375                         return X509ChainStatusFlags.NoError;
376                 }
377
378
379                 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
380                 {
381                         switch (c.Count) {
382                         case 0:
383                                 return null;
384                         case 1:
385                                 return c [0];
386                         default:
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) {
390                                 case 0:
391                                         // that's too restrictive, let's revert and try another thing...
392                                         time_valid = c;
393                                         break;
394                                 case 1:
395                                         return time_valid [0];
396                                 default:
397                                         break;
398                                 }
399
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
404                                 }
405                                 foreach (X509Certificate2 parent in time_valid) {
406                                         string ski = GetSubjectKeyIdentifier (parent);
407                                         // if both id are available then they must match
408                                         if (aki == ski)
409                                                 return parent;
410                                 }
411                                 return time_valid [0]; // FIXME: out of luck, you get the first one
412                         }
413                 }
414
415                 private X509Certificate2 FindParent (X509Certificate2 certificate)
416                 {
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));
421                         }
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;
425                 }
426
427                 private bool IsChainComplete (X509Certificate2 certificate)
428                 {
429                         // the chain is complete if we have a self-signed certificate
430                         if (!IsSelfIssued (certificate))
431                                 return false;
432
433                         // we're very limited to what we can do without certificate extensions
434                         if (certificate.Version < 3)
435                                 return true;
436
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))
441                                 return true;
442                         string aki = GetAuthorityKeyIdentifier (certificate);
443                         if (String.IsNullOrEmpty (aki))
444                                 return true;
445                         // if both id are available then they must match
446                         return (aki == ski);
447                 }
448
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)
452                 {
453                         return (certificate.Issuer == certificate.Subject);
454                 }
455
456
457                 // *** certificate chain/path validation stuff ***
458
459                 // Currently a subset of RFC3280 (hopefully a full implementation someday)
460                 private void ValidateChain (X509ChainStatusFlags flag)
461                 {
462                         // 'n' should be the root certificate... 
463                         int n = elements.Count - 1;
464                         X509Certificate2 certificate = elements [n].Certificate;
465
466                         // ... and, if so, must be treated outside the chain... 
467                         if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
468                                 Process (n);
469                                 // deal with the case where the chain == the root certificate 
470                                 // (which isn't for RFC3280) part of the chain
471                                 if (n == 0) {
472                                         elements [0].UncompressFlags ();
473                                         return;
474                                 }
475                                 // skip the root certificate when processing the chain (in 6.1.3)
476                                 n--;
477                         }
478                         // ... unless the chain is a partial one (then we start with that one)
479
480                         // 6.1.1 - Inputs
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)
488
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
499                         max_path_length = n;
500
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--) {
504                                 Process (i);
505                                 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
506                                 PrepareForNextCertificate (i);
507                         }
508                         Process (0);
509
510                         // 6.1.3.a.3 - revocation checks
511                         CheckRevocationOnChain (flag);
512
513                         // 6.1.5 - Wrap-up procedure
514                         WrapUp ();
515                 }
516
517                 private void Process (int n)
518                 {
519                         X509ChainElement element = elements [n];
520                         X509Certificate2 certificate = element.Certificate;
521
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;
527                                 }
528                         }
529
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;
537                                 }
538                         }
539
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;
544                         }
545                         // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
546
547                         // note: most of them don't apply to the root certificate
548                         if (root) {
549                                 return;
550                         }
551
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)
554
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;
559                         }
560
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...
564
565                                 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint
566                                 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
567                                 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
568                                 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
569                         }
570
571                         // TODO 6.1.3.d - check if certificate policies extension is present
572                         //if (false) {
573                                 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
574                                 //      using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
575
576                                 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
577
578                         //} else {
579                                 // TODO 6.1.3.e - set valid_policy_tree to NULL
580                         //}
581
582                         // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
583                 }
584
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
589
590                 private void PrepareForNextCertificate (int n) 
591                 {
592                         X509ChainElement element = elements [n];
593                         X509Certificate2 certificate = element.Certificate;
594
595                         // TODO 6.1.4.a-b
596
597                         // 6.1.4.c
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;
601                         // 6.1.4.f
602 //                      working_public_key_algorithm = certificate.PublicKey.Oid.Value;
603
604                         // TODO 6.1.4.g-j
605
606                         // 6.1.4.k - Verify that the certificate is a CA certificate
607                         X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
608                         if (bce != null) {
609                                 if (!bce.CertificateAuthority) {
610                                         element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
611                                 }
612                         } else if (certificate.Version >= 3) {
613                                 // recent (v3+) CA certificates must include BCE
614                                 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
615                         }
616
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) {
621                                         max_path_length--;
622                                 } else {
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;
628                                         }
629                                 }
630                         }
631
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;
638                                 }
639                         }
640
641                         // 6.1.4.n - if key usage extension is present...
642                         X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
643                         if (kue != null) {
644                                 // ... verify keyCertSign is set
645                                 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
646                                 if ((kue.KeyUsages & success) != success)
647                                         element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
648                         }
649
650                         // 6.1.4.o - recognize and process other critical extension present in the certificate
651                         ProcessCertificateExtensions (element);
652                 }
653
654                 private void WrapUp ()
655                 {
656                         X509ChainElement element = elements [0];
657                         X509Certificate2 certificate = element.Certificate;
658
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
662                         }
663
664                         // 6.1.5.b - TODO
665
666                         // 6.1.5.c,d,e - not required by the X509Chain implementation
667
668                         // 6.1.5.f - recognize and process other critical extension present in the certificate
669                         ProcessCertificateExtensions (element);
670
671                         // 6.1.5.g - TODO
672
673                         // uncompressed the flags into several elements
674                         for (int i = elements.Count - 1; i >= 0; i--) {
675                                 elements [i].UncompressFlags ();
676                         }
677                 }
678
679                 private void ProcessCertificateExtensions (X509ChainElement element)
680                 {
681                         foreach (X509Extension ext in element.Certificate.Extensions) {
682                                 if (ext.Critical) {
683                                         switch (ext.Oid.Value) {
684                                         case "2.5.29.15": // X509KeyUsageExtension
685                                         case "2.5.29.19": // X509BasicConstraintsExtension
686                                                 // we processed this extension
687                                                 break;
688                                         default:
689                                                 // note: Under Windows XP MS implementation seems to ignore 
690                                                 // certificate with unknown critical extensions.
691                                                 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
692                                                 break;
693                                         }
694                                 }
695                         }
696                 }
697
698                 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
699                 {
700                         if (pubkey == null)
701                                 return false;
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));
705                 }
706
707                 private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
708                 {
709                         X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
710                         return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
711                 }
712
713                 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
714                 static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
715                 {
716                         return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
717                 }
718
719                 // but anyway System.dll v2 doesn't expose CRL in any way so...
720                 static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
721                 {
722                         return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
723                 }
724
725                 static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
726                 {
727                         if (ext == null)
728                                 return String.Empty;
729                         MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
730                         byte[] id = aki.Identifier;
731                         if (id == null) 
732                                 return String.Empty;
733                         StringBuilder sb = new StringBuilder ();
734                         foreach (byte b in id)
735                                 sb.Append (b.ToString ("X02"));
736                         return sb.ToString ();
737                 }
738
739                 // we check the revocation only once we have built the complete chain
740                 private void CheckRevocationOnChain (X509ChainStatusFlags flag)
741                 {
742                         bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
743                         bool online;
744
745                         switch (ChainPolicy.RevocationMode) {
746                         case X509RevocationMode.Online:
747                                 // default
748                                 online = true;
749                                 break;
750                         case X509RevocationMode.Offline:
751                                 online = false;
752                                 break;
753                         case X509RevocationMode.NoCheck:
754                                 return;
755                         default:
756                                 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
757                         }
758
759                         bool unknown = partial;
760                         // from the root down to the end-entity
761                         for (int i = elements.Count - 1; i >= 0; i--) {
762                                 bool check = true;
763
764                                 switch (ChainPolicy.RevocationFlag) {
765                                 case X509RevocationFlag.EndCertificateOnly:
766                                         check = (i == 0);
767                                         break;
768                                 case X509RevocationFlag.EntireChain:
769                                         check = true;
770                                         break;
771                                 case X509RevocationFlag.ExcludeRoot:
772                                         // default
773                                         check = (i != (elements.Count - 1));
774                                         // anyway, who's gonna sign that the root is invalid ?
775                                         break;
776                                 }
777
778                                 X509ChainElement element = elements [i];
779
780                                 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
781                                 if (!unknown)
782                                         unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
783
784                                 if (unknown) {
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);
793                                 }
794                         }
795                 }
796
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)
799                 {
800                         X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
801                         X509ChainElement element = elements [ca];
802                         X509Certificate2 ca_cert = element.Certificate;
803
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)
809                                         break;
810                                 ca++;
811                                 element = elements [ca];
812                                 ca_cert = element.Certificate;
813                         }
814                         if (result == X509ChainStatusFlags.RevocationStatusUnknown)
815                                 result = CheckRevocation (certificate, ca_cert, online);
816                         return result;
817                 }
818
819                 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
820                 {
821                         // change this if/when we support OCSP
822                         X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
823                         if (kue != null) {
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;
829                                 }
830                         }
831
832                         MX.X509Crl crl = FindCrl (ca_cert);
833
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);
838
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);
841                         }
842
843                         if (crl != null) {
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;
851                                 }
852
853                                 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
854                                 if (entry != null) {
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;
859
860                                         // FIXME - a little more is involved
861                                         if (entry.RevocationDate <= ChainPolicy.VerificationTime)
862                                                 return X509ChainStatusFlags.Revoked;
863                                 }
864
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;
868
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;
873                                 }
874                         } else {
875                                 return X509ChainStatusFlags.RevocationStatusUnknown;
876                         }
877
878                         return X509ChainStatusFlags.NoError;
879                 }
880
881                 static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store)
882                 {
883                         if (store == null)
884                                 return null;
885
886                         var crls = store.Crls;
887                         foreach (MX.X509Crl crl in crls) {
888                                 if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
889                                         return crl;
890                         }
891                         return null; // No CRL found
892                 }
893
894                 private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
895                 {
896                         string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
897                         string ski = GetSubjectKeyIdentifier (caCertificate);
898
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);
901                         if (result != null)
902                                 return result;
903                         if (location == StoreLocation.CurrentUser) {
904                                 result = CheckCrls (subject, ski, UserCAStore.Store);
905                                 if (result != null)
906                                         return result;
907                         }
908
909                         // consider that the LocalMachine directories could not exists... and cannot be created by the user
910                         result = CheckCrls (subject, ski, LMRootStore.Store);
911                         if (result != null)
912                                 return result;
913                         if (location == StoreLocation.CurrentUser) {
914                                 result = CheckCrls (subject, ski, UserRootStore.Store);
915                                 if (result != null)
916                                         return result;
917                         }
918                         return null;
919                 }
920
921                 private bool ProcessCrlExtensions (MX.X509Crl crl)
922                 {
923                         foreach (MX.X509Extension ext in crl.Extensions) {
924                                 if (ext.Critical) {
925                                         switch (ext.Oid) {
926                                         case "2.5.29.20": // cRLNumber
927                                         case "2.5.29.35": // authorityKeyIdentifier
928                                                 // we processed/know about this extension
929                                                 break;
930                                         default:
931                                                 return false;
932                                         }
933                                 }
934                         }
935                         return true;
936                 }
937
938                 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
939                 {
940                         foreach (MX.X509Extension ext in entry.Extensions) {
941                                 if (ext.Critical) {
942                                         switch (ext.Oid) {
943                                         case "2.5.29.21": // cRLReason
944                                                 // we processed/know about this extension
945                                                 break;
946                                         default:
947                                                 return false;
948                                         }
949                                 }
950                         }
951                         return true;
952                 }
953         }
954 }
955 #else
956 namespace System.Security.Cryptography.X509Certificates {
957         public class X509Chain {
958                 public bool Build (X509Certificate2 cert)
959                 {
960                         return false;
961                 }
962         }
963 }
964 #endif
965