2 // X509CRL.cs: Handles X.509 certificates revocation lists.
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2004,2006 Novell Inc. (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System
.Collections
;
31 using System
.Globalization
;
33 using System
.Security
.Cryptography
;
35 using Mono
.Security
.X509
.Extensions
;
37 namespace Mono
.Security
.X509
{
39 * CertificateList ::= SEQUENCE {
40 * tbsCertList TBSCertList,
41 * signatureAlgorithm AlgorithmIdentifier,
42 * signature BIT STRING
45 * TBSCertList ::= SEQUENCE {
46 * version Version OPTIONAL,
47 * -- if present, MUST be v2
48 * signature AlgorithmIdentifier,
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
58 * crlExtensions [0] Extensions OPTIONAL }
59 * -- if present, MUST be v2
68 public class X509CrlEntry
{
71 private DateTime revocationDate
;
72 private X509ExtensionCollection extensions
;
74 internal X509CrlEntry (byte[] serialNumber
, DateTime revocationDate
, X509ExtensionCollection extensions
)
77 this.revocationDate
= revocationDate
;
78 if (extensions
== null)
79 this.extensions
= new X509ExtensionCollection ();
81 this.extensions
= extensions
;
84 internal X509CrlEntry (ASN1 entry
)
88 revocationDate
= ASN1Convert
.ToDateTime (entry
[1]);
89 extensions
= new X509ExtensionCollection (entry
[2]);
92 public byte[] SerialNumber
{
93 get { return (byte[]) sn.Clone (); }
96 public DateTime RevocationDate
{
97 get { return revocationDate; }
100 public X509ExtensionCollection Extensions
{
101 get { return extensions; }
104 public byte[] GetBytes ()
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 ();
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
;
126 public X509Crl (byte[] crl
)
129 throw new ArgumentNullException ("crl");
130 encoded
= (byte[]) crl
.Clone ();
134 private void Parse (byte[] crl
)
136 string e
= "Input data cannot be coded as a valid CRL.";
138 // CertificateList ::= SEQUENCE {
139 ASN1 encodedCRL
= new ASN1 (encoded
);
140 if ((encodedCRL
.Tag
!= 0x30) || (encodedCRL
.Count
!= 3))
141 throw new CryptographicException (e
);
143 // CertificateList / TBSCertList,
144 ASN1 toBeSigned
= encodedCRL
[0];
145 if ((toBeSigned
.Tag
!= 0x30) || (toBeSigned
.Count
< 3))
146 throw new CryptographicException (e
);
149 // CertificateList / TBSCertList / Version OPTIONAL, -- if present, MUST be v2
150 if (toBeSigned
[n
].Tag
== 0x02) {
151 version
= (byte) (toBeSigned
[n
++].Value
[0] + 1);
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
++];
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
]));
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]);
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]");
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
);
196 throw new CryptographicException (e
);
200 public ArrayList Entries
{
201 get { return ArrayList.ReadOnly (entries); }
204 public X509CrlEntry
this [int index
] {
205 get { return (X509CrlEntry) entries [index]; }
208 public X509CrlEntry
this [byte[] serialNumber
] {
209 get { return GetCrlEntry (serialNumber); }
212 public X509ExtensionCollection Extensions
{
213 get { return extensions; }
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
);
228 public string IssuerName
{
229 get { return issuer; }
232 public DateTime NextUpdate
{
233 get { return nextUpdate; }
236 public DateTime ThisUpdate
{
237 get { return thisUpdate; }
240 public string SignatureAlgorithm
{
241 get { return signatureOID; }
244 public byte[] Signature
{
246 if (signature
== null)
248 return (byte[]) signature
.Clone ();
252 public byte[] RawData
{
253 get { return (byte[]) encoded.Clone (); }
256 public byte Version
{
257 get { return version; }
260 public bool IsCurrent
{
261 get { return WasCurrent (DateTime.Now); }
264 public bool WasCurrent (DateTime instant
)
266 if (nextUpdate
== DateTime
.MinValue
)
267 return (instant
>= thisUpdate
);
269 return ((instant
>= thisUpdate
) && (instant
<= nextUpdate
));
272 public byte[] GetBytes ()
274 return (byte[]) encoded
.Clone ();
277 private bool Compare (byte[] array1
, byte[] array2
)
279 if ((array1
== null) && (array2
== null))
281 if ((array1
== null) || (array2
== null))
283 if (array1
.Length
!= array2
.Length
)
285 for (int i
=0; i
< array1
.Length
; i
++) {
286 if (array1
[i
] != array2
[i
])
292 public X509CrlEntry
GetCrlEntry (X509Certificate x509
)
295 throw new ArgumentNullException ("x509");
297 return GetCrlEntry (x509
.SerialNumber
);
300 public X509CrlEntry
GetCrlEntry (byte[] serialNumber
)
302 if (serialNumber
== null)
303 throw new ArgumentNullException ("serialNumber");
305 for (int i
=0; i
< entries
.Count
; i
++) {
306 X509CrlEntry entry
= (X509CrlEntry
) entries
[i
];
307 if (Compare (serialNumber
, entry
.SerialNumber
))
313 public bool VerifySignature (X509Certificate x509
)
316 throw new ArgumentNullException ("x509");
318 // 1. x509 certificate must be a CA certificate (unknown for v1 or v2 certs)
319 if (x509
.Version
>= 3) {
320 // 1.1. Check for "cRLSign" bit in KeyUsage extension
321 X509Extension ext
= x509
.Extensions
["2.5.29.15"];
323 KeyUsageExtension keyUsage
= new KeyUsageExtension (ext
);
324 if (!keyUsage
.Support (KeyUsages
.cRLSign
))
327 // 1.2. Check for ca = true in BasicConstraint
328 ext
= x509
.Extensions
["2.5.29.19"];
330 BasicConstraintsExtension basicConstraints
= new BasicConstraintsExtension (ext
);
331 if (!basicConstraints
.CertificateAuthority
)
335 // 2. CRL issuer must match CA subject name
336 if (issuer
!= x509
.SubjectName
)
338 // 3. Check the CRL signature with the CA certificate public key
339 switch (signatureOID
) {
340 case "1.2.840.10040.4.3":
341 return VerifySignature (x509
.DSA
);
343 return VerifySignature (x509
.RSA
);
347 private string GetHashName ()
349 switch (signatureOID
) {
350 // MD2 with RSA encryption
351 case "1.2.840.113549.1.1.2":
352 // maybe someone installed MD2 ?
354 // MD5 with RSA encryption
355 case "1.2.840.113549.1.1.4":
358 case "1.2.840.10040.4.3":
359 // SHA-1 with RSA Encryption
360 case "1.2.840.113549.1.1.5":
363 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID
);
367 internal bool VerifySignature (DSA dsa
)
369 if (signatureOID
!= "1.2.840.10040.4.3")
370 throw new CryptographicException ("Unsupported hash algorithm: " + signatureOID
);
371 DSASignatureDeformatter v
= new DSASignatureDeformatter (dsa
);
372 // only SHA-1 is supported
373 v
.SetHashAlgorithm ("SHA1");
374 ASN1 sign
= new ASN1 (signature
);
375 if ((sign
== null) || (sign
.Count
!= 2))
377 // parts may be less than 20 bytes (i.e. first bytes were 0x00)
378 byte[] part1
= sign
[0].Value
;
379 byte[] part2
= sign
[1].Value
;
380 byte[] sig
= new byte [40];
381 // parts may be less than 20 bytes (i.e. first bytes were 0x00)
382 // parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
383 int s1
= System
.Math
.Max (0, part1
.Length
- 20);
384 int e1
= System
.Math
.Max (0, 20 - part1
.Length
);
385 Buffer
.BlockCopy (part1
, s1
, sig
, e1
, part1
.Length
- s1
);
386 int s2
= System
.Math
.Max (0, part2
.Length
- 20);
387 int e2
= System
.Math
.Max (20, 40 - part2
.Length
);
388 Buffer
.BlockCopy (part2
, s2
, sig
, e2
, part2
.Length
- s2
);
389 return v
.VerifySignature (Hash
, sig
);
392 internal bool VerifySignature (RSA rsa
)
394 RSAPKCS1SignatureDeformatter v
= new RSAPKCS1SignatureDeformatter (rsa
);
395 v
.SetHashAlgorithm (GetHashName ());
396 return v
.VerifySignature (Hash
, signature
);
399 public bool VerifySignature (AsymmetricAlgorithm aa
)
402 throw new ArgumentNullException ("aa");
404 // only validate the signature (in case we don't have the CA certificate)
406 return VerifySignature (aa
as RSA
);
408 return VerifySignature (aa
as DSA
);
410 throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa
.ToString ());
413 static public X509Crl
CreateFromFile (string filename
)
416 using (FileStream fs
= File
.Open (filename
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
)) {
417 crl
= new byte [fs
.Length
];
418 fs
.Read (crl
, 0, crl
.Length
);
421 return new X509Crl (crl
);