Added tests for Task.WhenAll w/ empty list
[mono.git] / mcs / class / Mono.Security / Mono.Security.X509 / X509CRL.cs
1 //
2 // X509CRL.cs: Handles X.509 certificates revocation lists.
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // Copyright (C) 2004,2006 Novell Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31 using System.Globalization;
32 using System.IO;
33 using System.Security.Cryptography;
34
35 using Mono.Security.X509.Extensions;
36
37 namespace Mono.Security.X509 {
38         /*
39          * CertificateList  ::=  SEQUENCE  {
40          *      tbsCertList          TBSCertList,
41          *      signatureAlgorithm   AlgorithmIdentifier,
42          *      signature            BIT STRING  
43          * }
44          * 
45          * TBSCertList  ::=  SEQUENCE  {
46          *      version                 Version OPTIONAL,
47          *              -- if present, MUST be v2
48          *      signature               AlgorithmIdentifier,
49          *      issuer                  Name,
50          *      thisUpdate              Time,
51          *      nextUpdate              Time OPTIONAL,
52          *      revokedCertificates     SEQUENCE OF SEQUENCE  {
53          *              userCertificate         CertificateSerialNumber,
54          *              revocationDate          Time,
55          *              crlEntryExtensions      Extensions OPTIONAL
56          *                      -- if present, MUST be v2
57          *      }  OPTIONAL,
58          *      crlExtensions           [0] Extensions OPTIONAL }
59          *              -- if present, MUST be v2
60          */
61 #if INSIDE_CORLIB
62         internal
63 #else
64         public 
65 #endif
66         class X509Crl {
67
68                 public class X509CrlEntry {
69
70                         private byte[] sn;
71                         private DateTime revocationDate;
72                         private X509ExtensionCollection extensions;
73
74                         internal X509CrlEntry (byte[] serialNumber, DateTime revocationDate, X509ExtensionCollection extensions) 
75                         {
76                                 sn = serialNumber;
77                                 this.revocationDate = revocationDate;
78                                 if (extensions == null)
79                                         this.extensions = new X509ExtensionCollection ();
80                                 else
81                                         this.extensions = extensions;
82                         }
83
84                         internal X509CrlEntry (ASN1 entry) 
85                         {
86                                 sn = entry [0].Value;
87                                 Array.Reverse (sn);
88                                 revocationDate = ASN1Convert.ToDateTime (entry [1]);
89                                 extensions = new X509ExtensionCollection (entry [2]);
90                         }
91
92                         public byte[] SerialNumber {
93                                 get { return (byte[]) sn.Clone (); }
94                         }
95
96                         public DateTime RevocationDate {
97                                 get { return revocationDate; }
98                         }
99
100                         public X509ExtensionCollection Extensions {
101                                 get { return extensions; }
102                         }
103
104                         public byte[] GetBytes () 
105                         {
106                                 ASN1 sequence = new ASN1 (0x30);
107                                 sequence.Add (new ASN1 (0x02, sn));
108                                 sequence.Add (ASN1Convert.FromDateTime (revocationDate));
109                                 if (extensions.Count > 0)
110                                         sequence.Add (new ASN1 (extensions.GetBytes ()));
111                                 return sequence.GetBytes ();
112                         }
113                 }
114
115                 private string issuer;
116                 private byte version;
117                 private DateTime thisUpdate;
118                 private DateTime nextUpdate;
119                 private ArrayList entries;
120                 private string signatureOID;
121                 private byte[] signature;
122                 private X509ExtensionCollection extensions;
123                 private byte[] encoded;
124                 private byte[] hash_value;
125
126                 public X509Crl (byte[] crl) 
127                 {
128                         if (crl == null)
129                                 throw new ArgumentNullException ("crl");
130                         encoded = (byte[]) crl.Clone ();
131                         Parse (encoded);
132                 }
133
134                 private void Parse (byte[] crl) 
135                 {
136                         string e = "Input data cannot be coded as a valid CRL.";
137                         try {
138                                 // CertificateList  ::=  SEQUENCE  {
139                                 ASN1 encodedCRL = new ASN1 (encoded);
140                                 if ((encodedCRL.Tag != 0x30) || (encodedCRL.Count != 3))
141                                         throw new CryptographicException (e);
142
143                                 // CertificateList / TBSCertList,
144                                 ASN1 toBeSigned = encodedCRL [0];
145                                 if ((toBeSigned.Tag != 0x30) || (toBeSigned.Count < 3))
146                                         throw new CryptographicException (e);
147
148                                 int n = 0;
149                                 // CertificateList / TBSCertList / Version OPTIONAL, -- if present, MUST be v2
150                                 if (toBeSigned [n].Tag == 0x02) {
151                                         version = (byte) (toBeSigned [n++].Value [0] + 1);
152                                 }
153                                 else
154                                         version = 1; // DEFAULT
155                                 // CertificateList / TBSCertList / AlgorithmIdentifier,
156                                 signatureOID = ASN1Convert.ToOid (toBeSigned [n++][0]);
157                                 // CertificateList / TBSCertList / Name,
158                                 issuer = X501.ToString (toBeSigned [n++]);
159                                 // CertificateList / TBSCertList / Time,
160                                 thisUpdate = ASN1Convert.ToDateTime (toBeSigned [n++]);
161                                 // CertificateList / TBSCertList / Time OPTIONAL,
162                                 ASN1 next = toBeSigned [n++];
163                                 if ((next.Tag == 0x17) || (next.Tag == 0x18)) {
164                                         nextUpdate = ASN1Convert.ToDateTime (next);
165                                         next = toBeSigned [n++];
166                                 }
167                                 // CertificateList / TBSCertList / revokedCertificates  SEQUENCE OF SEQUENCE  {
168                                 entries = new ArrayList ();
169                                 // this is OPTIONAL so it may not be present if no entries exists
170                                 if ((next != null) && (next.Tag == 0x30)) {
171                                         ASN1 revokedCertificates = next;
172                                         for (int i=0; i < revokedCertificates.Count; i++) {
173                                                 entries.Add (new X509CrlEntry (revokedCertificates [i]));
174                                         }
175                                 } else {
176                                         n--;
177                                 }
178                                 // CertificateList / TBSCertList / crlExtensions [0] Extensions OPTIONAL }
179                                 ASN1 extns = toBeSigned [n];
180                                 if ((extns != null) && (extns.Tag == 0xA0) && (extns.Count == 1))
181                                         extensions = new X509ExtensionCollection (extns [0]);
182                                 else
183                                         extensions = new X509ExtensionCollection (null); // result in a read only object
184                                 // CertificateList / AlgorithmIdentifier
185                                 string signatureAlgorithm = ASN1Convert.ToOid (encodedCRL [1][0]);
186                                 if (signatureOID != signatureAlgorithm)
187                                         throw new CryptographicException (e + " [Non-matching signature algorithms in CRL]");
188
189                                 // CertificateList / BIT STRING 
190                                 byte[] bitstring = encodedCRL [2].Value;
191                                 // first byte contains unused bits in first byte
192                                 signature = new byte [bitstring.Length - 1];
193                                 Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
194                         }
195                         catch {
196                                 throw new CryptographicException (e);
197                         }
198                 }
199
200                 public ArrayList Entries {
201                         get { return ArrayList.ReadOnly (entries); }
202                 }
203
204                 public X509CrlEntry this [int index] {
205                         get { return (X509CrlEntry) entries [index]; }
206                 }
207
208                 public X509CrlEntry this [byte[] serialNumber] {
209                         get { return GetCrlEntry (serialNumber); }
210                 }
211
212                 public X509ExtensionCollection Extensions {
213                         get { return extensions; }
214                 }
215
216                 public byte[] Hash {
217                         get {
218                                 if (hash_value == null) {
219                                         ASN1 encodedCRL = new ASN1 (encoded);
220                                         byte[] toBeSigned = encodedCRL [0].GetBytes ();
221                                         HashAlgorithm ha = HashAlgorithm.Create (GetHashName ());
222                                         hash_value = ha.ComputeHash (toBeSigned);
223                                 }
224                                 return hash_value;
225                         }
226                 }
227
228                 public string IssuerName {
229                         get { return issuer; }
230                 }
231
232                 public DateTime NextUpdate {
233                         get { return nextUpdate; }
234                 }
235
236                 public DateTime ThisUpdate {
237                         get { return thisUpdate; }
238                 }
239
240                 public string SignatureAlgorithm {
241                         get { return signatureOID; }
242                 }
243
244                 public byte[] Signature {
245                         get { 
246                                 if (signature == null)
247                                         return null;
248                                 return (byte[]) signature.Clone ();
249                         }
250                 }
251
252                 public byte[] RawData {
253                         get { return (byte[]) encoded.Clone (); }
254                 }
255
256                 public byte Version {
257                         get { return version; }
258                 }
259
260                 public bool IsCurrent {
261                         get { return WasCurrent (DateTime.Now); }
262                 }
263
264                 public bool WasCurrent (DateTime instant) 
265                 {
266                         if (nextUpdate == DateTime.MinValue)
267                                 return (instant >= thisUpdate);
268                         else
269                                 return ((instant >= thisUpdate) && (instant <= nextUpdate));
270                 }
271
272                 public byte[] GetBytes () 
273                 {
274                         return (byte[]) encoded.Clone ();
275                 }
276
277                 private bool Compare (byte[] array1, byte[] array2) 
278                 {
279                         if ((array1 == null) && (array2 == null))
280                                 return true;
281                         if ((array1 == null) || (array2 == null))
282                                 return false;
283                         if (array1.Length != array2.Length)
284                                 return false;
285                         for (int i=0; i < array1.Length; i++) {
286                                 if (array1 [i] != array2 [i])
287                                         return false;
288                         }
289                         return true;
290                 }
291
292                 public X509CrlEntry GetCrlEntry (X509Certificate x509) 
293                 {
294                         if (x509 == null)
295                                 throw new ArgumentNullException ("x509");
296
297                         return GetCrlEntry (x509.SerialNumber);
298                 }
299
300                 public X509CrlEntry GetCrlEntry (byte[] serialNumber) 
301                 {
302                         if (serialNumber == null)
303                                 throw new ArgumentNullException ("serialNumber");
304
305                         for (int i=0; i < entries.Count; i++) {
306                                 X509CrlEntry entry = (X509CrlEntry) entries [i];
307                                 if (Compare (serialNumber, entry.SerialNumber))
308                                         return entry;
309                         }
310                         return null;
311                 }
312
313                 public bool VerifySignature (X509Certificate x509) 
314                 {
315                         if (x509 == null)
316                                 throw new ArgumentNullException ("x509");
317
318                         // 1. x509 certificate must be a CA certificate (unknown for v1 or v2 certs)
319                         if (x509.Version >= 3) {
320                                 BasicConstraintsExtension basicConstraints = null;
321                                 // 1.2. Check for ca = true in BasicConstraint
322                                 X509Extension ext = x509.Extensions ["2.5.29.19"];
323                                 if (ext != null) {
324                                         basicConstraints = new BasicConstraintsExtension (ext);
325                                         if (!basicConstraints.CertificateAuthority)
326                                                 return false;
327                                 }
328                                 // 1.1. Check for "cRLSign" bit in KeyUsage extension
329                                 ext = x509.Extensions ["2.5.29.15"];
330                                 if (ext != null) {
331                                         KeyUsageExtension keyUsage = new KeyUsageExtension (ext);
332                                         if (!keyUsage.Support (KeyUsages.cRLSign)) {
333                                                 // 2nd chance if basicConstraints is CertificateAuthority
334                                                 // and KeyUsage support digitalSignature
335                                                 if ((basicConstraints == null) || !keyUsage.Support (KeyUsages.digitalSignature))
336                                                         return false;
337                                         }
338                                 }
339                         }
340                         // 2. CRL issuer must match CA subject name
341                         if (issuer != x509.SubjectName)
342                                 return false;
343                         // 3. Check the CRL signature with the CA certificate public key
344                         switch (signatureOID) {
345                                 case "1.2.840.10040.4.3":
346                                         return VerifySignature (x509.DSA);
347                                 default:
348                                         return VerifySignature (x509.RSA);
349                         }
350                 }
351
352                 private string GetHashName ()
353                 {
354                         switch (signatureOID) {
355                         // MD2 with RSA encryption 
356                         case "1.2.840.113549.1.1.2":
357                                 // maybe someone installed MD2 ?
358                                 return "MD2";
359                         // MD5 with RSA encryption 
360                         case "1.2.840.113549.1.1.4":
361                                 return "MD5";
362                         // SHA-1 with DSA
363                         case "1.2.840.10040.4.3":
364                         // SHA-1 with RSA Encryption 
365                         case "1.2.840.113549.1.1.5":
366                                 return "SHA1";
367                         default:
368                                 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID);
369                         }
370                 }
371
372                 internal bool VerifySignature (DSA dsa) 
373                 {
374                         if (signatureOID != "1.2.840.10040.4.3")
375                                 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID);
376                         DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
377                         // only SHA-1 is supported
378                         v.SetHashAlgorithm ("SHA1");
379                         ASN1 sign = new ASN1 (signature);
380                         if ((sign == null) || (sign.Count != 2))
381                                 return false;
382                         // parts may be less than 20 bytes (i.e. first bytes were 0x00)
383                         byte[] part1 = sign [0].Value;
384                         byte[] part2 = sign [1].Value;
385                         byte[] sig = new byte [40];
386                         // parts may be less than 20 bytes (i.e. first bytes were 0x00)
387                         // parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
388                         int s1 = System.Math.Max (0, part1.Length - 20);
389                         int e1 = System.Math.Max (0, 20 - part1.Length);
390                         Buffer.BlockCopy (part1, s1, sig, e1, part1.Length - s1);
391                         int s2 = System.Math.Max (0, part2.Length - 20);
392                         int e2 = System.Math.Max (20, 40 - part2.Length);
393                         Buffer.BlockCopy (part2, s2, sig, e2, part2.Length - s2);
394                         return v.VerifySignature (Hash, sig);
395                 }
396
397                 internal bool VerifySignature (RSA rsa) 
398                 {
399                         RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
400                         v.SetHashAlgorithm (GetHashName ());
401                         return v.VerifySignature (Hash, signature);
402                 }
403
404                 public bool VerifySignature (AsymmetricAlgorithm aa) 
405                 {
406                         if (aa == null)
407                                 throw new ArgumentNullException ("aa");
408
409                         // only validate the signature (in case we don't have the CA certificate)
410                         if (aa is RSA)
411                                 return VerifySignature (aa as RSA);
412                         else if (aa is DSA)
413                                 return VerifySignature (aa as DSA);
414                         else
415                                 throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
416                 }
417
418                 static public X509Crl CreateFromFile (string filename) 
419                 {
420                         byte[] crl = null;
421                         using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
422                                 crl = new byte [fs.Length];
423                                 fs.Read (crl, 0, crl.Length);
424                                 fs.Close ();
425                         }
426                         return new X509Crl (crl);
427                 }
428         }
429 }