New test.
[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 // (C) 2004 Novell (http://www.novell.com)
8 //
9
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 using System;
32 using System.Collections;
33 using System.Globalization;
34 using System.IO;
35 using System.Security.Cryptography;
36
37 using Mono.Security.X509.Extensions;
38
39 namespace Mono.Security.X509 {
40         /*
41          * CertificateList  ::=  SEQUENCE  {
42          *      tbsCertList          TBSCertList,
43          *      signatureAlgorithm   AlgorithmIdentifier,
44          *      signature            BIT STRING  
45          * }
46          * 
47          * TBSCertList  ::=  SEQUENCE  {
48          *      version                 Version OPTIONAL,
49          *              -- if present, MUST be v2
50          *      signature               AlgorithmIdentifier,
51          *      issuer                  Name,
52          *      thisUpdate              Time,
53          *      nextUpdate              Time OPTIONAL,
54          *      revokedCertificates     SEQUENCE OF SEQUENCE  {
55          *              userCertificate         CertificateSerialNumber,
56          *              revocationDate          Time,
57          *              crlEntryExtensions      Extensions OPTIONAL
58          *                      -- if present, MUST be v2
59          *      }  OPTIONAL,
60          *      crlExtensions           [0] Extensions OPTIONAL }
61          *              -- if present, MUST be v2
62          */
63 #if INSIDE_CORLIB
64         internal
65 #else
66         public 
67 #endif
68         class X509Crl {
69
70                 public class X509CrlEntry {
71
72                         private byte[] sn;
73                         private DateTime revocationDate;
74                         private X509ExtensionCollection extensions;
75
76                         internal X509CrlEntry (byte[] serialNumber, DateTime revocationDate, X509ExtensionCollection extensions) 
77                         {
78                                 sn = serialNumber;
79                                 this.revocationDate = revocationDate;
80                                 if (extensions == null)
81                                         this.extensions = new X509ExtensionCollection ();
82                                 else
83                                         this.extensions = extensions;
84                         }
85
86                         internal X509CrlEntry (ASN1 entry) 
87                         {
88                                 sn = entry [0].Value;
89                                 Array.Reverse (sn);
90                                 revocationDate = ASN1Convert.ToDateTime (entry [1]);
91                                 extensions = new X509ExtensionCollection (entry [2]);
92                         }
93
94                         public byte[] SerialNumber {
95                                 get { return (byte[]) sn.Clone (); }
96                         }
97
98                         public DateTime RevocationDate {
99                                 get { return revocationDate; }
100                         }
101
102                         public X509ExtensionCollection Extensions {
103                                 get { return extensions; }
104                         }
105
106                         public byte[] GetBytes () 
107                         {
108                                 ASN1 sequence = new ASN1 (0x30);
109                                 sequence.Add (new ASN1 (0x02, sn));
110                                 sequence.Add (ASN1Convert.FromDateTime (revocationDate));
111                                 if (extensions.Count > 0)
112                                         sequence.Add (new ASN1 (extensions.GetBytes ()));
113                                 return sequence.GetBytes ();
114                         }
115                 }
116
117                 private string issuer;
118                 private byte version;
119                 private DateTime thisUpdate;
120                 private DateTime nextUpdate;
121                 private ArrayList entries;
122                 private string signatureOID;
123                 private byte[] signature;
124                 private X509ExtensionCollection extensions;
125                 private byte[] encoded;
126
127                 public X509Crl (byte[] crl) 
128                 {
129                         if (crl == null)
130                                 throw new ArgumentNullException ("crl");
131                         encoded = (byte[]) crl.Clone ();
132                         Parse (encoded);
133                 }
134
135                 private void Parse (byte[] crl) 
136                 {
137                         string e = "Input data cannot be coded as a valid CRL.";
138                         try {
139                                 // CertificateList  ::=  SEQUENCE  {
140                                 ASN1 encodedCRL = new ASN1 (encoded);
141                                 if ((encodedCRL.Tag != 0x30) || (encodedCRL.Count != 3))
142                                         throw new CryptographicException (e);
143
144                                 // CertificateList / TBSCertList,
145                                 ASN1 toBeSigned = encodedCRL [0];
146                                 if ((toBeSigned.Tag != 0x30) || (toBeSigned.Count < 3))
147                                         throw new CryptographicException (e);
148
149                                 int n = 0;
150                                 // CertificateList / TBSCertList / Version OPTIONAL, -- if present, MUST be v2
151                                 if (toBeSigned [n].Tag == 0x02) {
152                                         version = (byte) (toBeSigned [n++].Value [0] + 1);
153                                 }
154                                 else
155                                         version = 1; // DEFAULT
156                                 // CertificateList / TBSCertList / AlgorithmIdentifier,
157                                 signatureOID = ASN1Convert.ToOid (toBeSigned [n++][0]);
158                                 // CertificateList / TBSCertList / Name,
159                                 issuer = X501.ToString (toBeSigned [n++]);
160                                 // CertificateList / TBSCertList / Time,
161                                 thisUpdate = ASN1Convert.ToDateTime (toBeSigned [n++]);
162                                 // CertificateList / TBSCertList / Time OPTIONAL,
163                                 ASN1 next = toBeSigned [n++];
164                                 if ((next.Tag == 0x17) || (next.Tag == 0x18)) {
165                                         nextUpdate = ASN1Convert.ToDateTime (next);
166                                         next = toBeSigned [n++];
167                                 }
168                                 // CertificateList / TBSCertList / revokedCertificates  SEQUENCE OF SEQUENCE  {
169                                 entries = new ArrayList ();
170                                 // this is OPTIONAL so it may not be present if no entries exists
171                                 if ((next != null) && (next.Tag == 0x30)) {
172                                         ASN1 revokedCertificates = next;
173                                         for (int i=0; i < revokedCertificates.Count; i++) {
174                                                 entries.Add (new X509CrlEntry (revokedCertificates [i]));
175                                         }
176                                 } else {
177                                         n--;
178                                 }
179                                 // CertificateList / TBSCertList / crlExtensions [0] Extensions OPTIONAL }
180                                 ASN1 extns = toBeSigned [n];
181                                 if ((extns != null) && (extns.Tag == 0xA0) && (extns.Count == 1))
182                                         extensions = new X509ExtensionCollection (extns [0]);
183                                 else
184                                         extensions = new X509ExtensionCollection (null); // result in a read only object
185                                 // CertificateList / AlgorithmIdentifier
186                                 string signatureAlgorithm = ASN1Convert.ToOid (encodedCRL [1][0]);
187                                 if (signatureOID != signatureAlgorithm)
188                                         throw new CryptographicException (e + " [Non-matching signature algorithms in CRL]");
189
190                                 // CertificateList / BIT STRING 
191                                 byte[] bitstring = encodedCRL [2].Value;
192                                 // first byte contains unused bits in first byte
193                                 signature = new byte [bitstring.Length - 1];
194                                 Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
195                         }
196                         catch {
197                                 throw new CryptographicException (e);
198                         }
199                 }
200
201                 public ArrayList Entries {
202                         get { return ArrayList.ReadOnly (entries); }
203                 }
204
205                 public X509CrlEntry this [int index] {
206                         get { return (X509CrlEntry) entries [index]; }
207                 }
208
209                 public X509CrlEntry this [byte[] serialNumber] {
210                         get { return GetCrlEntry (serialNumber); }
211                 }
212
213                 public X509ExtensionCollection Extensions {
214                         get { return extensions; }
215                 }
216
217                 public string IssuerName {
218                         get { return issuer; }
219                 }
220
221                 public DateTime NextUpdate {
222                         get { return nextUpdate; }
223                 }
224
225                 public DateTime ThisUpdate {
226                         get { return thisUpdate; }
227                 }
228
229                 public string SignatureAlgorithm {
230                         get { return signatureOID; }
231                 }
232
233                 public byte[] Signature {
234                         get { 
235                                 if (signature == null)
236                                         return null;
237                                 return (byte[]) signature.Clone ();
238                         }
239                 }
240
241                 public byte Version {
242                         get { return version; }
243                 }
244
245                 public bool IsCurrent {
246                         get { return WasCurrent (DateTime.Now); }
247                 }
248
249                 public bool WasCurrent (DateTime instant) 
250                 {
251                         if (nextUpdate == DateTime.MinValue)
252                                 return (instant >= thisUpdate);
253                         else
254                                 return ((instant >= thisUpdate) && (instant <= nextUpdate));
255                 }
256
257                 public byte[] GetBytes () 
258                 {
259                         return (byte[]) encoded.Clone ();
260                 }
261
262                 private bool Compare (byte[] array1, byte[] array2) 
263                 {
264                         if ((array1 == null) && (array2 == null))
265                                 return true;
266                         if ((array1 == null) || (array2 == null))
267                                 return false;
268                         if (array1.Length != array2.Length)
269                                 return false;
270                         for (int i=0; i < array1.Length; i++) {
271                                 if (array1 [i] != array2 [i])
272                                         return false;
273                         }
274                         return true;
275                 }
276
277                 public X509CrlEntry GetCrlEntry (X509Certificate x509) 
278                 {
279                         if (x509 == null)
280                                 throw new ArgumentNullException ("x509");
281
282                         return GetCrlEntry (x509.SerialNumber);
283                 }
284
285                 public X509CrlEntry GetCrlEntry (byte[] serialNumber) 
286                 {
287                         if (serialNumber == null)
288                                 throw new ArgumentNullException ("serialNumber");
289
290                         for (int i=0; i < entries.Count; i++) {
291                                 X509CrlEntry entry = (X509CrlEntry) entries [i];
292                                 if (Compare (serialNumber, entry.SerialNumber))
293                                         return entry;
294                         }
295                         return null;
296                 }
297
298                 public bool VerifySignature (X509Certificate x509) 
299                 {
300                         if (x509 == null)
301                                 throw new ArgumentNullException ("x509");
302
303                         // 1. x509 certificate must be a CA certificate (unknown for v1 or v2 certs)
304                         if (x509.Version >= 3) {
305                                 // 1.1. Check for "cRLSign" bit in KeyUsage extension
306                                 X509Extension ext = x509.Extensions ["2.5.29.15"];
307                                 if (ext != null) {
308                                         KeyUsageExtension keyUsage = new KeyUsageExtension (ext);
309                                         if (!keyUsage.Support (KeyUsages.cRLSign))
310                                                 return false;
311                                 }
312                                 // 1.2. Check for ca = true in BasicConstraint
313                                 ext = x509.Extensions ["2.5.29.19"];
314                                 if (ext != null) {
315                                         BasicConstraintsExtension basicConstraints = new BasicConstraintsExtension (ext);
316                                         if (!basicConstraints.CertificateAuthority)
317                                                 return false;
318                                 }
319                         }
320                         // 2. CRL issuer must match CA subject name
321                         if (issuer != x509.SubjectName)
322                                 return false;
323                         // 3. Check the CRL signature with the CA certificate public key
324                         switch (signatureOID) {
325                                 case "1.2.840.10040.4.3":
326                                         return VerifySignature (x509.DSA);
327                                 default:
328                                         return VerifySignature (x509.RSA);
329                         }
330                 }
331
332                 private byte[] GetHash (string hashName) 
333                 {
334                         ASN1 encodedCRL = new ASN1 (encoded);
335                         byte[] toBeSigned = encodedCRL [0].GetBytes ();
336                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
337                         return ha.ComputeHash (toBeSigned);
338                 }
339
340                 internal bool VerifySignature (DSA dsa) 
341                 {
342                         if (signatureOID != "1.2.840.10040.4.3")
343                                 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID);
344                         DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
345                         // only SHA-1 is supported
346                         string hashName = "SHA1";
347                         v.SetHashAlgorithm (hashName);
348                         ASN1 sign = new ASN1 (signature);
349                         if ((sign == null) || (sign.Count != 2))
350                                 return false;
351                         // parts may be less than 20 bytes (i.e. first bytes were 0x00)
352                         byte[] part1 = sign [0].Value;
353                         byte[] part2 = sign [1].Value;
354                         byte[] sig = new byte [40];
355                         Buffer.BlockCopy (part1, 0, sig, (20 - part1.Length), part1.Length);
356                         Buffer.BlockCopy (part2, 0, sig, (40 - part2.Length), part2.Length);
357                         return v.VerifySignature (GetHash (hashName), sig);
358                 }
359
360                 internal bool VerifySignature (RSA rsa) 
361                 {
362                         RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
363                         string hashName = null;
364                         switch (signatureOID) {
365                                 // MD2 with RSA encryption 
366                                 case "1.2.840.113549.1.1.2":
367                                         // maybe someone installed MD2 ?
368                                         hashName = "MD2";
369                                         break;
370                                 // MD5 with RSA encryption 
371                                 case "1.2.840.113549.1.1.4":
372                                         hashName = "MD5";
373                                         break;
374                                 // SHA-1 with RSA Encryption 
375                                 case "1.2.840.113549.1.1.5":
376                                         hashName = "SHA1";
377                                         break;
378                                 default:
379                                         throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID);
380                         }
381                         v.SetHashAlgorithm (hashName);
382                         return v.VerifySignature (GetHash (hashName), signature);
383                 }
384
385                 public bool VerifySignature (AsymmetricAlgorithm aa) 
386                 {
387                         if (aa == null)
388                                 throw new ArgumentNullException ("aa");
389
390                         // only validate the signature (in case we don't have the CA certificate)
391                         if (aa is RSA)
392                                 return VerifySignature (aa as RSA);
393                         else if (aa is DSA)
394                                 return VerifySignature (aa as DSA);
395                         else
396                                 throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
397                 }
398
399                 static public X509Crl CreateFromFile (string filename) 
400                 {
401                         byte[] crl = null;
402                         using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
403                                 crl = new byte [fs.Length];
404                                 fs.Read (crl, 0, crl.Length);
405                                 fs.Close ();
406                         }
407                         return new X509Crl (crl);
408                 }
409         }
410 }