2 // X509CRL.cs: Handles X.509 certificates revocation lists.
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2004 Novell (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System.Collections;
33 using System.Globalization;
35 using System.Security.Cryptography;
37 using Mono.Security.X509.Extensions;
39 namespace Mono.Security.X509 {
41 * CertificateList ::= SEQUENCE {
42 * tbsCertList TBSCertList,
43 * signatureAlgorithm AlgorithmIdentifier,
44 * signature BIT STRING
47 * TBSCertList ::= SEQUENCE {
48 * version Version OPTIONAL,
49 * -- if present, MUST be v2
50 * signature AlgorithmIdentifier,
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
60 * crlExtensions [0] Extensions OPTIONAL }
61 * -- if present, MUST be v2
70 public class X509CrlEntry {
73 private DateTime revocationDate;
74 private X509ExtensionCollection extensions;
76 internal X509CrlEntry (byte[] serialNumber, DateTime revocationDate, X509ExtensionCollection extensions)
79 this.revocationDate = revocationDate;
80 if (extensions == null)
81 this.extensions = new X509ExtensionCollection ();
83 this.extensions = extensions;
86 internal X509CrlEntry (ASN1 entry)
90 revocationDate = ASN1Convert.ToDateTime (entry [1]);
91 extensions = new X509ExtensionCollection (entry [2]);
94 public byte[] SerialNumber {
95 get { return (byte[]) sn.Clone (); }
98 public DateTime RevocationDate {
99 get { return revocationDate; }
102 public X509ExtensionCollection Extensions {
103 get { return extensions; }
106 public byte[] GetBytes ()
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 ();
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;
127 public X509Crl (byte[] crl)
130 throw new ArgumentNullException ("crl");
131 encoded = (byte[]) crl.Clone ();
135 private void Parse (byte[] crl)
137 string e = "Input data cannot be coded as a valid CRL.";
139 // CertificateList ::= SEQUENCE {
140 ASN1 encodedCRL = new ASN1 (encoded);
141 if ((encodedCRL.Tag != 0x30) || (encodedCRL.Count != 3))
142 throw new CryptographicException (e);
144 // CertificateList / TBSCertList,
145 ASN1 toBeSigned = encodedCRL [0];
146 if ((toBeSigned.Tag != 0x30) || (toBeSigned.Count < 3))
147 throw new CryptographicException (e);
150 // CertificateList / TBSCertList / Version OPTIONAL, -- if present, MUST be v2
151 if (toBeSigned [n].Tag == 0x02) {
152 version = (byte) (toBeSigned [n++].Value [0] + 1);
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++];
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.Tag == 0x30) {
172 ASN1 revokedCertificates = next;
173 for (int i=0; i < revokedCertificates.Count; i++) {
174 entries.Add (new X509CrlEntry (revokedCertificates [i]));
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]);
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]");
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);
197 throw new CryptographicException (e);
201 public ArrayList Entries {
202 get { return ArrayList.ReadOnly (entries); }
205 public X509CrlEntry this [int index] {
206 get { return (X509CrlEntry) entries [index]; }
209 public X509CrlEntry this [byte[] serialNumber] {
210 get { return GetCrlEntry (serialNumber); }
213 public X509ExtensionCollection Extensions {
214 get { return extensions; }
217 public string IssuerName {
218 get { return issuer; }
221 public DateTime NextUpdate {
222 get { return nextUpdate; }
225 public DateTime ThisUpdate {
226 get { return thisUpdate; }
229 public string SignatureAlgorithm {
230 get { return signatureOID; }
233 public byte[] Signature {
235 if (signature == null)
237 return (byte[]) signature.Clone ();
241 public byte Version {
242 get { return version; }
245 public bool IsCurrent {
246 get { return WasCurrent (DateTime.Now); }
249 public bool WasCurrent (DateTime instant)
251 if (nextUpdate == DateTime.MinValue)
252 return (instant >= thisUpdate);
254 return ((instant >= thisUpdate) && (instant <= nextUpdate));
257 public byte[] GetBytes ()
259 return (byte[]) encoded.Clone ();
262 private bool Compare (byte[] array1, byte[] array2)
264 if ((array1 == null) && (array2 == null))
266 if ((array1 == null) || (array2 == null))
268 if (array1.Length != array2.Length)
270 for (int i=0; i < array1.Length; i++) {
271 if (array1 [i] != array2 [i])
277 public X509CrlEntry GetCrlEntry (X509Certificate x509)
280 throw new ArgumentNullException ("x509");
282 return GetCrlEntry (x509.SerialNumber);
285 public X509CrlEntry GetCrlEntry (byte[] serialNumber)
287 if (serialNumber == null)
288 throw new ArgumentNullException ("serialNumber");
290 for (int i=0; i < entries.Count; i++) {
291 X509CrlEntry entry = (X509CrlEntry) entries [i];
292 if (Compare (serialNumber, entry.SerialNumber))
298 public bool VerifySignature (X509Certificate x509)
301 throw new ArgumentNullException ("x509");
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"];
308 KeyUsageExtension keyUsage = new KeyUsageExtension (ext);
309 if (!keyUsage.Support (KeyUsages.cRLSign))
312 // 1.2. Check for ca = true in BasicConstraint
313 ext = x509.Extensions ["2.5.29.19"];
315 BasicConstraintsExtension basicConstraints = new BasicConstraintsExtension (ext);
316 if (!basicConstraints.CertificateAuthority)
320 // 2. CRL issuer must match CA subject name
321 if (issuer != x509.SubjectName)
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);
328 return VerifySignature (x509.RSA);
332 private byte[] GetHash (string hashName)
334 ASN1 encodedCRL = new ASN1 (encoded);
335 byte[] toBeSigned = encodedCRL [0].GetBytes ();
336 HashAlgorithm ha = HashAlgorithm.Create (hashName);
337 return ha.ComputeHash (toBeSigned);
340 internal bool VerifySignature (DSA dsa)
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))
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);
360 internal bool VerifySignature (RSA rsa)
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 ?
370 // MD5 with RSA encryption
371 case "1.2.840.113549.1.1.4":
374 // SHA-1 with RSA Encryption
375 case "1.2.840.113549.1.1.5":
379 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID);
381 v.SetHashAlgorithm (hashName);
382 return v.VerifySignature (GetHash (hashName), signature);
385 public bool VerifySignature (AsymmetricAlgorithm aa)
388 throw new ArgumentNullException ("aa");
390 // only validate the signature (in case we don't have the CA certificate)
392 return VerifySignature (aa as RSA);
394 return VerifySignature (aa as DSA);
396 throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
399 static public X509Crl CreateFromFile (string filename)
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);
407 return new X509Crl (crl);