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