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