[sgen] Reenable gc-altstack test
[mono.git] / mcs / class / System / System.Security.Cryptography.X509Certificates / X509ChainImplMono.cs
1 //
2 // System.Security.Cryptography.X509Certificates.X509ChainImplMono
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 #if MONO_SECURITY_ALIAS
34 extern alias MonoSecurity;
35 using MX = MonoSecurity::Mono.Security.X509;
36 #else
37 using MX = Mono.Security.X509;
38 #endif
39
40 using System.Collections;
41 using System.Text;
42
43 namespace System.Security.Cryptography.X509Certificates {
44
45         internal class X509ChainImplMono : X509ChainImpl
46         {
47                 private StoreLocation location;
48                 private X509ChainElementCollection elements;
49                 private X509ChainPolicy policy;
50                 private X509ChainStatus[] status;
51
52                 static X509ChainStatus[] Empty = new X509ChainStatus [0];
53
54                 // RFC3280 variables
55                 private int max_path_length;
56                 private X500DistinguishedName working_issuer_name;
57 //              private string working_public_key_algorithm;
58                 private AsymmetricAlgorithm working_public_key;
59
60                 // other flags
61                 private X509ChainElement bce_restriction;
62
63                 // constructors
64
65                 public X509ChainImplMono ()
66                         : this (false)
67                 {
68                 }
69
70                 public X509ChainImplMono (bool useMachineContext) 
71                 {
72                         location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser;
73                         elements = new X509ChainElementCollection ();
74                         policy = new X509ChainPolicy ();
75                 }
76
77                 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")]
78                 public X509ChainImplMono (IntPtr chainContext)
79                 {
80                         // CryptoAPI compatibility (unmanaged handle)
81                         throw new NotSupportedException ();
82                 }
83
84                 public override bool IsValid {
85                         get { return true; }
86                 }
87
88                 public override IntPtr Handle {
89                         get { return IntPtr.Zero; }
90                 }
91
92                 // properties
93
94                 public override X509ChainElementCollection ChainElements {
95                         get { return elements; }
96                 }
97
98                 public override X509ChainPolicy ChainPolicy {
99                         get { return policy; }
100                         set { policy = value; }
101                 }
102
103                 public override X509ChainStatus[] ChainStatus {
104                         get { 
105                                 if (status == null)
106                                         return Empty;
107                                 return status;
108                         }
109                 } 
110
111                 // methods
112
113                 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")]
114                 public override bool Build (X509Certificate2 certificate)
115                 {
116                         if (certificate == null)
117                                 throw new ArgumentException ("certificate");
118
119                         Reset ();
120                         X509ChainStatusFlags flag;
121                         try {
122                                 flag = BuildChainFrom (certificate);
123                                 ValidateChain (flag);
124                         }
125                         catch (CryptographicException ce) {
126                                 throw new ArgumentException ("certificate", ce);
127                         }
128
129                         X509ChainStatusFlags total = X509ChainStatusFlags.NoError;
130                         ArrayList list = new ArrayList ();
131                         // build "global" ChainStatus from the ChainStatus of every ChainElements
132                         foreach (X509ChainElement ce in elements) {
133                                 foreach (X509ChainStatus cs in ce.ChainElementStatus) {
134                                         // we MUST avoid duplicates in the "global" list
135                                         if ((total & cs.Status) != cs.Status) {
136                                                 list.Add (cs);
137                                                 total |= cs.Status;
138                                         }
139                                 }
140                         }
141                         // and if required add some
142                         if (flag != X509ChainStatusFlags.NoError) {
143                                 list.Insert (0, new X509ChainStatus (flag));
144                         }
145                         status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus));
146
147                         // (fast path) this ignore everything we have checked
148                         if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
149                                 return true;
150
151                         bool result = true;
152                         // now check if exclude some verification for the "end result" (boolean)
153                         foreach (X509ChainStatus cs in status) {
154                                 switch (cs.Status) {
155                                 case X509ChainStatusFlags.UntrustedRoot:
156                                 case X509ChainStatusFlags.PartialChain:
157                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0);
158                                         break;
159                                 case X509ChainStatusFlags.NotTimeValid:
160                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0);
161                                         break;
162                                 // FIXME - from here we needs new test cases for all cases
163                                 case X509ChainStatusFlags.NotTimeNested:
164                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0);
165                                         break;
166                                 case X509ChainStatusFlags.InvalidBasicConstraints:
167                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0);
168                                         break;
169                                 case X509ChainStatusFlags.InvalidPolicyConstraints:
170                                 case X509ChainStatusFlags.NoIssuanceChainPolicy:
171                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0);
172                                         break;
173                                 case X509ChainStatusFlags.InvalidNameConstraints:
174                                 case X509ChainStatusFlags.HasNotSupportedNameConstraint:
175                                 case X509ChainStatusFlags.HasNotPermittedNameConstraint:
176                                 case X509ChainStatusFlags.HasExcludedNameConstraint:
177                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0);
178                                         break;
179                                 case X509ChainStatusFlags.InvalidExtension:
180                                         // not sure ?!?
181                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
182                                         break;
183                                 //
184                                 //      ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0)
185                                 //      ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0)
186                                 case X509ChainStatusFlags.CtlNotTimeValid:
187                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0);
188                                         break;
189                                 case X509ChainStatusFlags.CtlNotSignatureValid:
190                                         // ?
191                                         break;
192                                 //      ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0);
193                                 case X509ChainStatusFlags.CtlNotValidForUsage:
194                                         // FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others)
195                                         result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0);
196                                         break;
197                                 default:
198                                         result = false;
199                                         break;
200                                 }
201                                 // once we have one failure there's no need to check further
202                                 if (!result)
203                                         return false;
204                         }
205
206                         // every "problem" was excluded
207                         return true;
208                 }
209
210                 public override void Reset () 
211                 {
212                         // note: this call doesn't Reset the X509ChainPolicy
213                         if ((status != null) && (status.Length != 0))
214                                 status = null;
215                         if (elements.Count > 0)
216                                 elements.Clear ();
217                         if (user_root_store != null) {
218                                 user_root_store.Close ();
219                                 user_root_store = null;
220                         }
221                         if (root_store != null) {
222                                 root_store.Close ();
223                                 root_store = null;
224                         }
225                         if (user_ca_store != null) {
226                                 user_ca_store.Close ();
227                                 user_ca_store = null;
228                         }
229                         if (ca_store != null) {
230                                 ca_store.Close ();
231                                 ca_store = null;
232                         }
233                         roots = null;
234                         cas = null;
235                         collection = null;
236                         bce_restriction = null;
237                         working_public_key = null;
238                 }
239
240                 // private stuff
241
242                 private X509Certificate2Collection roots;
243                 private X509Certificate2Collection cas;
244                 private X509Store root_store;
245                 private X509Store ca_store;
246                 private X509Store user_root_store;
247                 private X509Store user_ca_store;
248
249                 private X509Certificate2Collection Roots {
250                         get {
251                                 if (roots == null) {
252                                         X509Certificate2Collection c = new X509Certificate2Collection ();
253                                         X509Store store = LMRootStore;
254                                         if (location == StoreLocation.CurrentUser)
255                                                 c.AddRange (UserRootStore.Certificates);
256                                         c.AddRange (store.Certificates);
257                                         roots = c;
258                                 }
259                                 return roots;
260                         }
261                 }
262
263                 private X509Certificate2Collection CertificateAuthorities {
264                         get {
265                                 if (cas == null) {
266                                         X509Certificate2Collection c = new X509Certificate2Collection ();
267                                         X509Store store = LMCAStore;
268                                         if (location == StoreLocation.CurrentUser)
269                                                 c.AddRange (UserCAStore.Certificates);
270                                         c.AddRange (store.Certificates);
271                                         cas = c;
272                                 }
273                                 return cas;
274                         }
275                 }
276
277                 private X509Store LMRootStore {
278                         get {
279                                 if (root_store == null) {
280                                         root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine);
281                                         try {
282                                                 root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
283                                         } catch {
284                                         }
285                                 }
286                                 return root_store;
287                         }
288                 }
289
290                 private X509Store UserRootStore {
291                         get {
292                                 if (user_root_store == null) {
293                                         user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser);
294                                         try {
295                                                 user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
296                                         } catch {
297                                         }
298                                 }
299                                 return user_root_store;
300                         }
301                 }
302
303                 private X509Store LMCAStore {
304                         get {
305                                 if (ca_store == null) {
306                                         ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine);
307                                         try {
308                                                 ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
309                                         } catch {
310                                         }
311                                 }
312                                 return ca_store;
313                         }
314                 }
315
316                 private X509Store UserCAStore {
317                         get {
318                                 if (user_ca_store == null) {
319                                         user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser);
320                                         try {
321                                                 user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
322                                         } catch {
323                                         }
324                                 }
325                                 return user_ca_store;
326                         }
327                 }
328                 // *** certificate chain/path building stuff ***
329
330                 private X509Certificate2Collection collection;
331
332                 // we search local user (default) or machine certificate store 
333                 // and in the extra certificate supplied in ChainPolicy.ExtraStore
334                 private X509Certificate2Collection CertificateCollection {
335                         get {
336                                 if (collection == null) {
337                                         collection = new X509Certificate2Collection (ChainPolicy.ExtraStore);
338                                         collection.AddRange (Roots);
339                                         collection.AddRange (CertificateAuthorities);
340                                 }
341                                 return collection;
342                         }
343                 }
344
345                 // This is a non-recursive chain/path building algorithm. 
346                 //
347                 // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they
348                 // affect the path building (other errors are verification errors).
349                 //
350                 // Note that the order match the one we need to match MS and not the one defined in RFC3280,
351                 // we also include the trusted root certificate (trust anchor in RFC3280) in the list.
352                 // (this isn't an issue, just keep that in mind if you look at the source and the RFC)
353                 private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate)
354                 {
355                         elements.Add (certificate);
356
357                         while (!IsChainComplete (certificate)) {
358                                 certificate = FindParent (certificate);
359
360                                 if (certificate == null)
361                                         return X509ChainStatusFlags.PartialChain;
362
363                                 if (elements.Contains (certificate))
364                                         return X509ChainStatusFlags.Cyclic;
365
366                                 elements.Add (certificate);
367                         }
368
369                         // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their
370                         // trustiness (what a cute word) in the trusted root collection
371                         if (!Roots.Contains (certificate))
372                                 elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot;
373
374                         return X509ChainStatusFlags.NoError;
375                 }
376
377
378                 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c)
379                 {
380                         switch (c.Count) {
381                         case 0:
382                                 return null;
383                         case 1:
384                                 return c [0];
385                         default:
386                                 // multiple candidate, keep only the ones that are still valid
387                                 X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false);
388                                 switch (time_valid.Count) {
389                                 case 0:
390                                         // that's too restrictive, let's revert and try another thing...
391                                         time_valid = c;
392                                         break;
393                                 case 1:
394                                         return time_valid [0];
395                                 default:
396                                         break;
397                                 }
398
399                                 // again multiple candidates, let's find the AKI that match the SKI (if we have one)
400                                 string aki = GetAuthorityKeyIdentifier (child);
401                                 if (String.IsNullOrEmpty (aki)) {
402                                         return time_valid [0]; // FIXME: out of luck, you get the first one
403                                 }
404                                 foreach (X509Certificate2 parent in time_valid) {
405                                         string ski = GetSubjectKeyIdentifier (parent);
406                                         // if both id are available then they must match
407                                         if (aki == ski)
408                                                 return parent;
409                                 }
410                                 return time_valid [0]; // FIXME: out of luck, you get the first one
411                         }
412                 }
413
414                 private X509Certificate2 FindParent (X509Certificate2 certificate)
415                 {
416                         X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false);
417                         string aki = GetAuthorityKeyIdentifier (certificate);
418                         if ((aki != null) && (aki.Length > 0)) {
419                                 subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false));
420                         }
421                         X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
422                         // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs)
423                         return certificate.Equals (parent) ? null : parent;
424                 }
425
426                 private bool IsChainComplete (X509Certificate2 certificate)
427                 {
428                         // the chain is complete if we have a self-signed certificate
429                         if (!IsSelfIssued (certificate))
430                                 return false;
431
432                         // we're very limited to what we can do without certificate extensions
433                         if (certificate.Version < 3)
434                                 return true;
435
436                         // check that Authority Key Identifier == Subject Key Identifier
437                         // e.g. it will be different if a self-signed certificate is part (not the end) of the chain
438                         string ski = GetSubjectKeyIdentifier (certificate);
439                         if (String.IsNullOrEmpty (ski))
440                                 return true;
441                         string aki = GetAuthorityKeyIdentifier (certificate);
442                         if (String.IsNullOrEmpty (aki))
443                                 return true;
444                         // if both id are available then they must match
445                         return (aki == ski);
446                 }
447
448                 // check for "self-issued" certificate - without verifying the signature
449                 // note that self-issued doesn't always mean it's a root certificate!
450                 private bool IsSelfIssued (X509Certificate2 certificate)
451                 {
452                         return (certificate.Issuer == certificate.Subject);
453                 }
454
455
456                 // *** certificate chain/path validation stuff ***
457
458                 // Currently a subset of RFC3280 (hopefully a full implementation someday)
459                 private void ValidateChain (X509ChainStatusFlags flag)
460                 {
461                         // 'n' should be the root certificate... 
462                         int n = elements.Count - 1;
463                         X509Certificate2 certificate = elements [n].Certificate;
464
465                         // ... and, if so, must be treated outside the chain... 
466                         if (((flag & X509ChainStatusFlags.PartialChain) == 0)) {
467                                 Process (n);
468                                 // deal with the case where the chain == the root certificate 
469                                 // (which isn't for RFC3280) part of the chain
470                                 if (n == 0) {
471                                         elements [0].UncompressFlags ();
472                                         return;
473                                 }
474                                 // skip the root certificate when processing the chain (in 6.1.3)
475                                 n--;
476                         }
477                         // ... unless the chain is a partial one (then we start with that one)
478
479                         // 6.1.1 - Inputs
480                         // 6.1.1.a - a prospective certificate path of length n (i.e. elements)
481                         // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime)
482                         // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy)
483                         // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain)
484                         // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API)
485                         // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API)
486                         // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API)
487
488                         // 6.1.2 - Initialization (incomplete)
489                         // 6.1.2.a-f - policy stuff, some TODO, some not supported
490                         // 6.1.2.g - working public key algorithm
491 //                      working_public_key_algorithm = certificate.PublicKey.Oid.Value;
492                         // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data
493                         working_public_key = certificate.PublicKey.Key;
494                         // 6.1.2.j - working issuer name
495                         working_issuer_name = certificate.IssuerName;
496                         // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and
497                         //           may be reduced to the value in the path length constraint field
498                         max_path_length = n;
499
500                         // 6.1.3 - Basic Certificate Processing
501                         // note: loop looks reversed (the list is) but we process this part just like RFC3280 does
502                         for (int i = n; i > 0; i--) {
503                                 Process (i);
504                                 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop)
505                                 PrepareForNextCertificate (i);
506                         }
507                         Process (0);
508
509                         // 6.1.3.a.3 - revocation checks
510                         CheckRevocationOnChain (flag);
511
512                         // 6.1.5 - Wrap-up procedure
513                         WrapUp ();
514                 }
515
516                 private void Process (int n)
517                 {
518                         X509ChainElement element = elements [n];
519                         X509Certificate2 certificate = element.Certificate;
520
521                         // pre-step: DSA certificates may inherit the parameters of their CA
522                         if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) {
523                                 if (certificate.MonoCertificate.KeyAlgorithmParameters == null) {
524                                         X509Certificate2 parent = elements [n+1].Certificate;
525                                         certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters;
526                                 }
527                         }
528
529                         bool root = (working_public_key == null);
530                         // 6.1.3.a.1 - check signature (with special case to deal with root certificates)
531                         if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) {
532                                 // another special case where only an end-entity is available and can't be verified.
533                                 // In this case we do not report an invalid signature (since this is unknown)
534                                 if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) {
535                                         element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid;
536                                 }
537                         }
538
539                         // 6.1.3.a.2 - check validity period
540                         if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
541                                 (ChainPolicy.VerificationTime > certificate.NotAfter)) {
542                                 element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
543                         }
544                         // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
545
546                         // note: most of them don't apply to the root certificate
547                         if (root) {
548                                 return;
549                         }
550
551                         // 6.1.3.a.3 - revocation check (we're doing at the last stage)
552                         // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job)
553
554                         // 6.1.3.a.4 - check certificate issuer name
555                         if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) {
556                                 // NOTE: this is not the "right" error flag, but it's the closest one defined
557                                 element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
558                         }
559
560                         if (!IsSelfIssued (certificate) && (n != 0)) {
561                                 // TODO 6.1.3.b - subject name in the permitted_subtrees ...
562                                 // TODO 6.1.3.c - subject name not within excluded_subtrees...
563
564                                 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint
565                                 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint
566                                 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint
567                                 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint
568                         }
569
570                         // TODO 6.1.3.d - check if certificate policies extension is present
571                         //if (false) {
572                                 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints
573                                 //      using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy
574
575                                 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy
576
577                         //} else {
578                                 // TODO 6.1.3.e - set valid_policy_tree to NULL
579                         //}
580
581                         // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL
582                 }
583
584                 // CTL == Certificate Trust List / NOT SUPPORTED
585                 // TODO - check for X509ChainStatusFlags.CtlNotTimeValid
586                 // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid
587                 // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage
588
589                 private void PrepareForNextCertificate (int n) 
590                 {
591                         X509ChainElement element = elements [n];
592                         X509Certificate2 certificate = element.Certificate;
593
594                         // TODO 6.1.4.a-b
595
596                         // 6.1.4.c
597                         working_issuer_name = certificate.SubjectName;
598                         // 6.1.4.d-e - our key includes both the public key and it's parameters
599                         working_public_key = certificate.PublicKey.Key;
600                         // 6.1.4.f
601 //                      working_public_key_algorithm = certificate.PublicKey.Oid.Value;
602
603                         // TODO 6.1.4.g-j
604
605                         // 6.1.4.k - Verify that the certificate is a CA certificate
606                         X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension);
607                         if (bce != null) {
608                                 if (!bce.CertificateAuthority) {
609                                         element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
610                                 }
611                         } else if (certificate.Version >= 3) {
612                                 // recent (v3+) CA certificates must include BCE
613                                 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
614                         }
615
616                         // 6.1.4.l - if the certificate isn't self-issued...
617                         if (!IsSelfIssued (certificate)) {
618                                 // ... verify that max_path_length > 0
619                                 if (max_path_length > 0) {
620                                         max_path_length--;
621                                 } else {
622                                         // to match MS the reported status must be against the certificate 
623                                         // with the BCE and not where the path is too long. It also means
624                                         // that this condition has to be reported only once
625                                         if (bce_restriction != null) {
626                                                 bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints;
627                                         }
628                                 }
629                         }
630
631                         // 6.1.4.m - if pathLengthConstraint is present...
632                         if ((bce != null) && (bce.HasPathLengthConstraint)) {
633                                 // ... and is less that max_path_length, set max_path_length to it's value
634                                 if (bce.PathLengthConstraint < max_path_length) {
635                                         max_path_length = bce.PathLengthConstraint;
636                                         bce_restriction = element;
637                                 }
638                         }
639
640                         // 6.1.4.n - if key usage extension is present...
641                         X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension);
642                         if (kue != null) {
643                                 // ... verify keyCertSign is set
644                                 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
645                                 if ((kue.KeyUsages & success) != success)
646                                         element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
647                         }
648
649                         // 6.1.4.o - recognize and process other critical extension present in the certificate
650                         ProcessCertificateExtensions (element);
651                 }
652
653                 private void WrapUp ()
654                 {
655                         X509ChainElement element = elements [0];
656                         X509Certificate2 certificate = element.Certificate;
657
658                         // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0
659                         if (IsSelfIssued (certificate)) {
660                                 // TODO... decrement explicit_policy by 1
661                         }
662
663                         // 6.1.5.b - TODO
664
665                         // 6.1.5.c,d,e - not required by the X509Chain implementation
666
667                         // 6.1.5.f - recognize and process other critical extension present in the certificate
668                         ProcessCertificateExtensions (element);
669
670                         // 6.1.5.g - TODO
671
672                         // uncompressed the flags into several elements
673                         for (int i = elements.Count - 1; i >= 0; i--) {
674                                 elements [i].UncompressFlags ();
675                         }
676                 }
677
678                 private void ProcessCertificateExtensions (X509ChainElement element)
679                 {
680                         foreach (X509Extension ext in element.Certificate.Extensions) {
681                                 if (ext.Critical) {
682                                         switch (ext.Oid.Value) {
683                                         case "2.5.29.15": // X509KeyUsageExtension
684                                         case "2.5.29.19": // X509BasicConstraintsExtension
685                                                 // we processed this extension
686                                                 break;
687                                         default:
688                                                 // note: Under Windows XP MS implementation seems to ignore 
689                                                 // certificate with unknown critical extensions.
690                                                 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension;
691                                                 break;
692                                         }
693                                 }
694                         }
695                 }
696
697                 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey)
698                 {
699                         if (pubkey == null)
700                                 return false;
701                         // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure
702                         MX.X509Certificate mx = signed.MonoCertificate;
703                         return (mx.VerifySignature (pubkey));
704                 }
705
706                 private string GetSubjectKeyIdentifier (X509Certificate2 certificate)
707                 {
708                         X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension);
709                         return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
710                 }
711
712                 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension
713                 static string GetAuthorityKeyIdentifier (X509Certificate2 certificate)
714                 {
715                         return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]);
716                 }
717
718                 // but anyway System.dll v2 doesn't expose CRL in any way so...
719                 static string GetAuthorityKeyIdentifier (MX.X509Crl crl)
720                 {
721                         return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]);
722                 }
723
724                 static string GetAuthorityKeyIdentifier (MX.X509Extension ext)
725                 {
726                         if (ext == null)
727                                 return String.Empty;
728                         MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext);
729                         byte[] id = aki.Identifier;
730                         if (id == null) 
731                                 return String.Empty;
732                         StringBuilder sb = new StringBuilder ();
733                         foreach (byte b in id)
734                                 sb.Append (b.ToString ("X02"));
735                         return sb.ToString ();
736                 }
737
738                 // we check the revocation only once we have built the complete chain
739                 private void CheckRevocationOnChain (X509ChainStatusFlags flag)
740                 {
741                         bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0);
742                         bool online;
743
744                         switch (ChainPolicy.RevocationMode) {
745                         case X509RevocationMode.Online:
746                                 // default
747                                 online = true;
748                                 break;
749                         case X509RevocationMode.Offline:
750                                 online = false;
751                                 break;
752                         case X509RevocationMode.NoCheck:
753                                 return;
754                         default:
755                                 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
756                         }
757
758                         bool unknown = partial;
759                         // from the root down to the end-entity
760                         for (int i = elements.Count - 1; i >= 0; i--) {
761                                 bool check = true;
762
763                                 switch (ChainPolicy.RevocationFlag) {
764                                 case X509RevocationFlag.EndCertificateOnly:
765                                         check = (i == 0);
766                                         break;
767                                 case X509RevocationFlag.EntireChain:
768                                         check = true;
769                                         break;
770                                 case X509RevocationFlag.ExcludeRoot:
771                                         // default
772                                         check = (i != (elements.Count - 1));
773                                         // anyway, who's gonna sign that the root is invalid ?
774                                         break;
775                                 }
776
777                                 X509ChainElement element = elements [i];
778
779                                 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature)
780                                 if (!unknown)
781                                         unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0);
782
783                                 if (unknown) {
784                                         // we can skip the revocation checks as we can't be sure of them anyway
785                                         element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown;
786                                         element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation;
787                                 } else if (check && !partial && !IsSelfIssued (element.Certificate)) {
788                                         // check for revocation (except for the trusted root and self-issued certs)
789                                         element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online);
790                                         // if revoked, then all others following in the chain are unknown...
791                                         unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0);
792                                 }
793                         }
794                 }
795
796                 // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas...
797                 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
798                 {
799                         X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown;
800                         X509ChainElement element = elements [ca];
801                         X509Certificate2 ca_cert = element.Certificate;
802
803                         // find the CRL from the "right" CA
804                         while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) {
805                                 // try with this self-issued
806                                 result = CheckRevocation (certificate, ca_cert, online);
807                                 if (result != X509ChainStatusFlags.RevocationStatusUnknown)
808                                         break;
809                                 ca++;
810                                 element = elements [ca];
811                                 ca_cert = element.Certificate;
812                         }
813                         if (result == X509ChainStatusFlags.RevocationStatusUnknown)
814                                 result = CheckRevocation (certificate, ca_cert, online);
815                         return result;
816                 }
817
818                 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
819                 {
820                         // change this if/when we support OCSP
821                         X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension);
822                         if (kue != null) {
823                                 // ... verify CrlSign is set
824                                 X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
825                                 if ((kue.KeyUsages & success) != success) {
826                                         // FIXME - we should try to find an alternative CA that has the CrlSign bit
827                                         return X509ChainStatusFlags.RevocationStatusUnknown;
828                                 }
829                         }
830
831                         MX.X509Crl crl = FindCrl (ca_cert);
832
833                         if ((crl == null) && online) {
834                                 // FIXME - download and install new CRL
835                                 // then you get a second chance
836                                 // crl = FindCrl (ca_cert, ref valid, ref out_of_date);
837
838                                 // We need to get the subjectAltName and an URI from there (or use OCSP)        
839                                 // X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension);
840                         }
841
842                         if (crl != null) {
843                                 // validate the digital signature on the CRL using the CA public key
844                                 // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates
845                                 // checks and we loose the "why" of the failure
846                                 // note #2: we do this before other tests as an invalid signature could be a hacked CRL
847                                 // (so anything within can't be trusted)
848                                 if (!crl.VerifySignature (ca_cert.PublicKey.Key)) {
849                                         return X509ChainStatusFlags.RevocationStatusUnknown;
850                                 }
851
852                                 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate);
853                                 if (entry != null) {
854                                         // We have an entry for this CRL that includes an unknown CRITICAL extension
855                                         // See [X.509 7.3] NOTE 4
856                                         if (!ProcessCrlEntryExtensions (entry))
857                                                 return X509ChainStatusFlags.Revoked;
858
859                                         // FIXME - a little more is involved
860                                         if (entry.RevocationDate <= ChainPolicy.VerificationTime)
861                                                 return X509ChainStatusFlags.Revoked;
862                                 }
863
864                                 // are we overdue for a CRL update ? if so we can't be sure of any certificate status
865                                 if (crl.NextUpdate < ChainPolicy.VerificationTime)
866                                         return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;
867
868                                 // we have a CRL that includes an unknown CRITICAL extension
869                                 // we put this check at the end so we do not "hide" any Revoked flags
870                                 if (!ProcessCrlExtensions (crl)) {
871                                         return X509ChainStatusFlags.RevocationStatusUnknown;
872                                 }
873                         } else {
874                                 return X509ChainStatusFlags.RevocationStatusUnknown;
875                         }
876
877                         return X509ChainStatusFlags.NoError;
878                 }
879
880                 static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store)
881                 {
882                         if (store == null)
883                                 return null;
884
885                         var crls = store.Crls;
886                         foreach (MX.X509Crl crl in crls) {
887                                 if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl)))
888                                         return crl;
889                         }
890                         return null; // No CRL found
891                 }
892
893                 private MX.X509Crl FindCrl (X509Certificate2 caCertificate)
894                 {
895                         string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None);
896                         string ski = GetSubjectKeyIdentifier (caCertificate);
897
898                         // consider that the LocalMachine directories could not exists... and cannot be created by the user
899                         MX.X509Crl result = CheckCrls (subject, ski, LMCAStore.Store);
900                         if (result != null)
901                                 return result;
902                         if (location == StoreLocation.CurrentUser) {
903                                 result = CheckCrls (subject, ski, UserCAStore.Store);
904                                 if (result != null)
905                                         return result;
906                         }
907
908                         // consider that the LocalMachine directories could not exists... and cannot be created by the user
909                         result = CheckCrls (subject, ski, LMRootStore.Store);
910                         if (result != null)
911                                 return result;
912                         if (location == StoreLocation.CurrentUser) {
913                                 result = CheckCrls (subject, ski, UserRootStore.Store);
914                                 if (result != null)
915                                         return result;
916                         }
917                         return null;
918                 }
919
920                 private bool ProcessCrlExtensions (MX.X509Crl crl)
921                 {
922                         foreach (MX.X509Extension ext in crl.Extensions) {
923                                 if (ext.Critical) {
924                                         switch (ext.Oid) {
925                                         case "2.5.29.20": // cRLNumber
926                                         case "2.5.29.35": // authorityKeyIdentifier
927                                                 // we processed/know about this extension
928                                                 break;
929                                         default:
930                                                 return false;
931                                         }
932                                 }
933                         }
934                         return true;
935                 }
936
937                 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry)
938                 {
939                         foreach (MX.X509Extension ext in entry.Extensions) {
940                                 if (ext.Critical) {
941                                         switch (ext.Oid) {
942                                         case "2.5.29.21": // cRLReason
943                                                 // we processed/know about this extension
944                                                 break;
945                                         default:
946                                                 return false;
947                                         }
948                                 }
949                         }
950                         return true;
951                 }
952         }
953 }
954 #endif