2004-02-24 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.X509 / X509Certificate.cs
1 //
2 // X509Certificates.cs: Handles X.509 certificates.
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.Security.Cryptography;
12 using SSCX = System.Security.Cryptography.X509Certificates;
13 using System.Text;
14
15 namespace Mono.Security.X509 {
16
17         // References:
18         // a.   Internet X.509 Public Key Infrastructure Certificate and CRL Profile
19         //      http://www.ietf.org/rfc/rfc3280.txt
20         // b.   ITU ASN.1 standards (free download)
21         //      http://www.itu.int/ITU-T/studygroups/com17/languages/
22
23         public class X509Certificate {
24
25                 private ASN1 decoder;
26
27                 private byte[] m_encodedcert;
28                 private DateTime m_from;
29                 private DateTime m_until;
30                 private string m_issuername;
31                 private string m_keyalgo;
32                 private byte[] m_keyalgoparams;
33                 private string m_subject;
34                 private byte[] m_publickey;
35                 private byte[] signature;
36                 private string m_signaturealgo;
37                 private byte[] m_signaturealgoparams;
38                 
39                 // from http://www.ietf.org/rfc/rfc2459.txt
40                 //
41                 //Certificate  ::=  SEQUENCE  {
42                 //     tbsCertificate       TBSCertificate,
43                 //     signatureAlgorithm   AlgorithmIdentifier,
44                 //     signature            BIT STRING  }
45                 //
46                 //TBSCertificate  ::=  SEQUENCE  {
47                 //     version         [0]  Version DEFAULT v1,
48                 //     serialNumber         CertificateSerialNumber,
49                 //     signature            AlgorithmIdentifier,
50                 //     issuer               Name,
51                 //     validity             Validity,
52                 //     subject              Name,
53                 //     subjectPublicKeyInfo SubjectPublicKeyInfo,
54                 //     issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
55                 //                          -- If present, version shall be v2 or v3
56                 //     subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
57                 //                          -- If present, version shall be v2 or v3
58                 //     extensions      [3]  Extensions OPTIONAL
59                 //                          -- If present, version shall be v3 --  }
60                 private int version;
61                 private byte[] serialnumber;
62
63                 private byte[] issuerUniqueID;
64                 private byte[] subjectUniqueID;
65                 private X509Extensions extensions;
66
67                 // that's were the real job is!
68                 private void Parse (byte[] data) 
69                 {
70                         string e = "Input data cannot be coded as a valid certificate.";
71                         try {
72                                 decoder = new ASN1 (data);
73                                 // Certificate 
74                                 if (decoder.Tag != 0x30)
75                                         throw new CryptographicException (e);
76                                 // Certificate / TBSCertificate
77                                 if (decoder [0].Tag != 0x30)
78                                         throw new CryptographicException (e);
79
80                                 ASN1 tbsCertificate = decoder [0];
81
82                                 int tbs = 0;
83                                 // Certificate / TBSCertificate / Version
84                                 ASN1 v = decoder [0][tbs];
85                                 version = 1;                    // DEFAULT v1
86                                 if (v.Tag == 0xA0) {
87                                         // version (optional) is present only in v2+ certs
88                                         version += v.Value [0]; // zero based
89                                         tbs++;
90                                 }
91
92                                 // Certificate / TBSCertificate / CertificateSerialNumber
93                                 ASN1 sn = decoder [0][tbs++];
94                                 if (sn.Tag != 0x02) 
95                                         throw new CryptographicException (e);
96                                 serialnumber = sn.Value;
97                                 Array.Reverse (serialnumber, 0, serialnumber.Length);
98                 
99                                 // Certificate / TBSCertificate / AlgorithmIdentifier
100                                 ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30); 
101                 
102                                 ASN1 issuer = tbsCertificate.Element (tbs++, 0x30); 
103                                 m_issuername = X501.ToString (issuer);
104                 
105                                 ASN1 validity = tbsCertificate.Element (tbs++, 0x30);
106                                 ASN1 notBefore = validity [0];
107                                 m_from = ASN1Convert.ToDateTime (notBefore);
108                                 ASN1 notAfter = validity [1];
109                                 m_until = ASN1Convert.ToDateTime (notAfter);
110                 
111                                 ASN1 subject = tbsCertificate.Element (tbs++, 0x30);
112                                 m_subject = X501.ToString (subject);
113                 
114                                 ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30);
115                 
116                                 ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30);
117                                 ASN1 algo = algorithm.Element (0, 0x06);
118                                 m_keyalgo = ASN1Convert.ToOID (algo);
119                                 // parameters ANY DEFINED BY algorithm OPTIONAL
120                                 // so we dont ask for a specific (Element) type and return DER
121                                 ASN1 parameters = algorithm [1];
122                                 m_keyalgoparams = ((algorithm.Count > 1) ? parameters.GetBytes () : null);
123                 
124                                 ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03); 
125                                 // we must drop th first byte (which is the number of unused bits
126                                 // in the BITSTRING)
127                                 int n = subjectPublicKey.Length - 1;
128                                 m_publickey = new byte [n];
129                                 Array.Copy (subjectPublicKey.Value, 1, m_publickey, 0, n);
130
131                                 // signature processing
132                                 byte[] bitstring = decoder [2].Value;
133                                 // first byte contains unused bits in first byte
134                                 signature = new byte [bitstring.Length - 1];
135                                 Array.Copy (bitstring, 1, signature, 0, signature.Length);
136
137                                 algorithm = decoder [1];
138                                 algo = algorithm.Element (0, 0x06);
139                                 m_signaturealgo = ASN1Convert.ToOID (algo);
140                                 parameters = algorithm [1];
141                                 if (parameters != null)
142                                         m_signaturealgoparams = parameters.GetBytes ();
143                                 else
144                                         m_signaturealgoparams = null;
145
146                                 // Certificate / TBSCertificate / issuerUniqueID
147                                 ASN1 issuerUID = tbsCertificate.Element (tbs, 0xA1);
148                                 if (issuerUID != null) {
149                                         tbs++;
150                                         issuerUniqueID = issuerUID.Value;
151                                 }
152
153                                 // Certificate / TBSCertificate / subjectUniqueID
154                                 ASN1 subjectUID = tbsCertificate.Element (tbs, 0xA2);
155                                 if (subjectUID != null) {
156                                         tbs++;
157                                         subjectUniqueID = subjectUID.Value;
158                                 }
159
160                                 // Certificate / TBSCertificate / Extensions
161                                 ASN1 extns = tbsCertificate.Element (tbs, 0xA3);
162                                 if ((extns != null) && (extns.Count == 1))
163                                         extensions = new X509Extensions (extns [0]);
164                                 else
165                                         extensions = new X509Extensions (null);
166
167                                 // keep a copy of the original data
168                                 m_encodedcert = (byte[]) data.Clone ();
169                         }
170                         catch {
171                                 throw new CryptographicException (e);
172                         }
173                 }
174
175                 // constructors
176
177                 public X509Certificate (byte[] data) 
178                 {
179                         if (data != null)
180                                 Parse (data);
181                 }
182
183                 private byte[] GetUnsignedBigInteger (byte[] integer) 
184                 {
185                         if (integer [0] == 0x00) {
186                                 // this first byte is added so we're sure it's an unsigned integer
187                                 // however we can't feed it into RSAParameters or DSAParameters
188                                 int length = integer.Length - 1;
189                                 byte[] uinteger = new byte [length];
190                                 Array.Copy (integer, 1, uinteger, 0, length);
191                                 return uinteger;
192                         }
193                         else
194                                 return integer;
195                 }
196
197                 // public methods
198
199                 public DSA DSA {
200                         get { 
201                                 DSAParameters dsaParams = new DSAParameters ();
202                                 // for DSA m_publickey contains 1 ASN.1 integer - Y
203                                 ASN1 pubkey = new ASN1 (m_publickey);
204                                 if ((pubkey == null) || (pubkey.Tag != 0x02))
205                                         return null;
206                                 dsaParams.Y = GetUnsignedBigInteger (pubkey.Value);
207
208                                 ASN1 param = new ASN1 (m_keyalgoparams);
209                                 if ((param == null) || (param.Tag != 0x30) || (param.Count < 3))
210                                         return null;
211                                 if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02))
212                                         return null;
213                                 dsaParams.P = GetUnsignedBigInteger (param [0].Value);
214                                 dsaParams.Q = GetUnsignedBigInteger (param [1].Value);
215                                 dsaParams.G = GetUnsignedBigInteger (param [2].Value);
216
217                                 // BUG: MS BCL 1.0 can't import a key which 
218                                 // isn't the same size as the one present in
219                                 // the container.
220                                 DSACryptoServiceProvider dsa = new DSACryptoServiceProvider (dsaParams.Y.Length << 3);
221                                 dsa.ImportParameters (dsaParams);
222                                 return (DSA) dsa; 
223                         }
224                 }
225
226                 public X509Extensions Extensions {
227                         get { return extensions; }
228                 }
229
230                 public byte[] Hash {
231                         get {
232                                 HashAlgorithm hash = null;
233                                 switch (m_signaturealgo) {
234                                         case "1.2.840.113549.1.1.2":    // MD2 with RSA encryption 
235                                                 // maybe someone installed MD2 ?
236                                                 hash = HashAlgorithm.Create ("MD2");
237                                                 break;
238                                         case "1.2.840.113549.1.1.4":    // MD5 with RSA encryption 
239                                                 hash = MD5.Create ();
240                                                 break;
241                                         case "1.2.840.113549.1.1.5":    // SHA-1 with RSA Encryption 
242                                         case "1.3.14.3.2.29":           // SHA1 with RSA signature 
243                                         case "1.2.840.10040.4.3":       // SHA1-1 with DSA
244                                                 hash = SHA1.Create ();
245                                                 break;
246                                         default:
247                                                 return null;
248                                 }
249                                 try {
250                                         byte[] toBeSigned = decoder [0].GetBytes ();
251                                         return hash.ComputeHash (toBeSigned, 0, toBeSigned.Length);
252                                 }
253                                 catch {
254                                         return null;
255                                 }
256                         }
257                 }
258
259                 public virtual string IssuerName {
260                         get { return m_issuername; }
261                 }
262
263                 public virtual string KeyAlgorithm {
264                         get { return m_keyalgo; }
265                 }
266
267                 public virtual byte[] KeyAlgorithmParameters {
268                         get { return m_keyalgoparams; }
269                 }
270
271                 public virtual byte[] PublicKey {
272                         get { return m_publickey; }
273                 }
274
275                 public virtual RSA RSA {
276                         get { 
277                                 RSAParameters rsaParams = new RSAParameters ();
278                                 // for RSA m_publickey contains 2 ASN.1 integers
279                                 // the modulus and the public exponent
280                                 ASN1 pubkey = new ASN1 (m_publickey);
281                                 ASN1 modulus = pubkey [0];
282                                 if ((modulus == null) || (modulus.Tag != 0x02))
283                                         return null;
284                                 ASN1 exponent = pubkey [1];
285                                 if (exponent.Tag != 0x02)
286                                         return null;
287
288                                 rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value);
289                                 rsaParams.Exponent = exponent.Value;
290
291                                 // BUG: MS BCL 1.0 can't import a key which 
292                                 // isn't the same size as the one present in
293                                 // the container.
294                                 int keySize = (rsaParams.Modulus.Length << 3);
295                                 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider (keySize);
296                                 rsa.ImportParameters (rsaParams);
297                                 return (RSA)rsa; 
298                         }
299                 }
300                 
301                 public virtual byte[] RawData {
302                         get { return (byte[]) m_encodedcert.Clone (); }
303                 }
304
305                 public virtual byte[] SerialNumber {
306                         get { return serialnumber; }
307                 }
308
309                 public virtual byte[] Signature {
310                         get { 
311                                 switch (m_signaturealgo) {
312                                         case "1.2.840.113549.1.1.2":    // MD2 with RSA encryption 
313                                         case "1.2.840.113549.1.1.4":    // MD5 with RSA encryption 
314                                         case "1.2.840.113549.1.1.5":    // SHA-1 with RSA Encryption 
315                                         case "1.3.14.3.2.29":           // SHA1 with RSA signature
316                                                 return signature;
317                                         case "1.2.840.10040.4.3":       // SHA-1 with DSA
318                                                 ASN1 sign = new ASN1 (signature);
319                                                 if ((sign == null) || (sign.Count != 2))
320                                                         return null;
321                                                 // parts may be less than 20 bytes (i.e. first bytes were 0x00)
322                                                 byte[] part1 = sign [0].Value;
323                                                 byte[] part2 = sign [1].Value;
324                                                 byte[] sig = new byte [40];
325                                                 Array.Copy (part1, 0, sig, (20 - part1.Length), part1.Length);
326                                                 Array.Copy (part2, 0, sig, (40 - part2.Length), part2.Length);
327                                                 return sig;
328                                         default:
329                                                 throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
330                                 }
331                         }
332                 }
333
334                 public virtual string SignatureAlgorithm {
335                         get { return m_signaturealgo; }
336                 }
337
338                 public virtual byte[] SignatureAlgorithmParameters {
339                         get { return m_signaturealgoparams; }
340                 }
341
342                 public virtual string SubjectName {
343                         get { return m_subject; }
344                 }
345
346                 public virtual DateTime ValidFrom {
347                         get { return m_from; }
348                 }
349
350                 public virtual DateTime ValidUntil {
351                         get { return m_until; }
352                 }
353
354                 public int Version {
355                         get { return version; }
356                 }
357
358                 public bool IsCurrent {
359                         get { return WasCurrent (DateTime.UtcNow); }
360                 }
361
362                 public bool WasCurrent (DateTime date) 
363                 {
364                         return ((date > ValidFrom) && (date <= ValidUntil));
365                 }
366
367                 private byte[] GetHash (string hashName) 
368                 {
369                         byte[] toBeSigned = decoder [0].GetBytes ();
370                         HashAlgorithm ha = HashAlgorithm.Create (hashName);
371                         return ha.ComputeHash (toBeSigned);
372                 }
373
374                 public bool VerifySignature (DSA dsa) 
375                 {
376                         // signatureOID is check by both this.Hash and this.Signature
377                         DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
378                         // only SHA-1 is supported
379                         v.SetHashAlgorithm ("SHA1");
380                         return v.VerifySignature (this.Hash, this.Signature);
381                 }
382
383                 internal bool VerifySignature (RSA rsa) 
384                 {
385                         RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
386                         switch (m_signaturealgo) {
387                                 // MD2 with RSA encryption 
388                                 case "1.2.840.113549.1.1.2":
389                                         // maybe someone installed MD2 ?
390                                         v.SetHashAlgorithm ("MD2");
391                                         break;
392                                 // MD5 with RSA encryption 
393                                 case "1.2.840.113549.1.1.4":
394                                         v.SetHashAlgorithm ("MD5");
395                                         break;
396                                 // SHA-1 with RSA Encryption 
397                                 case "1.2.840.113549.1.1.5":
398                                 case "1.3.14.3.2.29":
399                                         v.SetHashAlgorithm ("SHA1");
400                                         break;
401                                 default:
402                                         throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
403                         }
404                         return v.VerifySignature (this.Hash, this.Signature);
405                 }
406
407                 public bool VerifySignature (AsymmetricAlgorithm aa) 
408                 {
409                         if (aa is RSA)
410                                 return VerifySignature (aa as RSA);
411                         else if (aa is DSA)
412                                 return VerifySignature (aa as DSA);
413                         else 
414                                 throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
415                 }
416
417                 public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature) 
418                 {
419                         RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA;
420                         return r.VerifyHash (hash, hashAlgorithm, signature);
421                 }
422
423                 public bool IsSelfSigned {
424                         get { 
425                                 if (m_issuername == m_subject)
426                                         return VerifySignature (RSA); 
427                                 else
428                                         return false;
429                         }
430                 }
431         }
432 }