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