2008-07-02 Andreas Nahr <ClassDevelopment@A-SoftTech.com>
[mono.git] / mcs / class / corlib / System.Security.Cryptography.X509Certificates / X509Certificate.cs
1 //
2 // X509Certificate.cs: Handles X.509 certificates.
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.IO;
31 using System.Runtime.InteropServices;
32 using System.Security.Permissions;
33 using System.Text;
34
35 using Mono.Security;
36 using Mono.Security.Authenticode;
37 using Mono.Security.X509;
38
39 #if NET_2_0
40 using System.Runtime.Serialization;
41 #endif
42
43 namespace System.Security.Cryptography.X509Certificates {
44
45         // References:
46         // a.   Internet X.509 Public Key Infrastructure Certificate and CRL Profile
47         //      http://www.ietf.org/rfc/rfc3280.txt
48         
49         // LAMESPEC: the MSDN docs always talks about X509v3 certificates
50         // and/or Authenticode certs. However this class works with older
51         // X509v1 certificates and non-authenticode (code signing) certs.
52         [Serializable]
53 #if NET_2_0
54         public partial class X509Certificate : IDeserializationCallback, ISerializable {
55 #else
56         public class X509Certificate {
57 #endif
58                 // typedef struct _CERT_CONTEXT {
59                 //      DWORD                   dwCertEncodingType;
60                 //      BYTE                    *pbCertEncoded;
61                 //      DWORD                   cbCertEncoded;
62                 //      PCERT_INFO              pCertInfo;
63                 //      HCERTSTORE              hCertStore;
64                 // } CERT_CONTEXT, *PCERT_CONTEXT;
65                 // typedef const CERT_CONTEXT *PCCERT_CONTEXT;
66                 [StructLayout (LayoutKind.Sequential)]
67                 internal struct CertificateContext {
68                         public UInt32 dwCertEncodingType;
69                         public IntPtr pbCertEncoded;
70                         public UInt32 cbCertEncoded;
71                         public IntPtr pCertInfo;
72                         public IntPtr hCertStore;
73                 }
74                 // NOTE: We only define the CryptoAPI structure (from WINCRYPT.H)
75                 // so we don't create any dependencies on Windows DLL in corlib
76
77                 private Mono.Security.X509.X509Certificate x509;
78                 private bool hideDates;
79                 private byte[] cachedCertificateHash;
80         
81                 // almost every byte[] returning function has a string equivalent
82                 // sadly the BitConverter insert dash between bytes :-(
83                 private string tostr (byte[] data) 
84                 {
85                         if (data != null) {
86                                 StringBuilder sb = new StringBuilder ();
87                                 for (int i = 0; i < data.Length; i++)
88                                         sb.Append (data[i].ToString ("X2"));
89                                 return sb.ToString ();
90                         }
91                         else
92                                 return null;
93                 }
94         
95                 // static methods
96         
97                 public static X509Certificate CreateFromCertFile (string filename) 
98                 {
99                         byte[] data = Load (filename);
100                         return new X509Certificate (data);
101                 }
102         
103                 [MonoTODO ("Incomplete - minimal validation in this version")]
104                 public static X509Certificate CreateFromSignedFile (string filename)
105                 {
106                         try {
107                                 AuthenticodeDeformatter a = new AuthenticodeDeformatter (filename);
108                                 if (a.SigningCertificate != null) {
109 #if !NET_2_0
110                                         // before 2.0 the signing certificate is returned only if the signature is valid
111                                         if (a.Reason != 0) {
112                                                 string msg = String.Format (Locale.GetText (
113                                                         "Invalid digital signature on {0}, reason #{1}."),
114                                                         filename, a.Reason);
115                                                 throw new COMException (msg);
116                                         }
117 #endif
118                                         return new X509Certificate (a.SigningCertificate.RawData);
119                                 }
120                         }
121                         catch (SecurityException) {
122                                 // don't wrap SecurityException into a COMException
123                                 throw;
124                         }
125 #if !NET_2_0
126                         catch (COMException) {
127                                 // don't wrap COMException into a COMException
128                                 throw;
129                         }
130 #endif
131                         catch (Exception e) {
132                                 string msg = Locale.GetText ("Couldn't extract digital signature from {0}.", filename);
133                                 throw new COMException (msg, e);
134                         }
135 #if NET_2_0
136                         throw new CryptographicException (Locale.GetText ("{0} isn't signed.", filename));
137 #else
138                         // if no signature is present return an empty certificate
139                         byte[] cert = null; // must not confuse compiler about null ;)
140                         return new X509Certificate (cert);
141 #endif
142                 }
143         
144                 // constructors
145         
146                 // special constructor for Publisher (and related classes).
147                 // Dates strings are null
148                 internal X509Certificate (byte[] data, bool dates) 
149                 {
150                         if (data != null) {
151 #if NET_2_0
152                                 Import (data, (string)null, X509KeyStorageFlags.DefaultKeySet);
153 #else
154                                 x509 = new Mono.Security.X509.X509Certificate (data);
155 #endif
156                                 hideDates = !dates;
157                         }
158                 }
159         
160                 public X509Certificate (byte[] data) : this (data, true)
161                 {
162                 }
163         
164                 public X509Certificate (IntPtr handle) 
165                 {
166 #if NET_2_0
167                         if (handle == IntPtr.Zero)
168                                 throw new ArgumentException ("Invalid handle.");
169 #endif
170                         InitFromHandle (handle);
171                 }
172
173                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
174                 private void InitFromHandle (IntPtr handle)
175                 {
176                         if (handle != IntPtr.Zero) {
177                                 // both Marshal.PtrToStructure and Marshal.Copy use LinkDemand (so they will always success from here)
178                                 CertificateContext cc = (CertificateContext) Marshal.PtrToStructure (handle, typeof (CertificateContext));
179                                 byte[] data = new byte [cc.cbCertEncoded];
180                                 Marshal.Copy (cc.pbCertEncoded, data, 0, (int)cc.cbCertEncoded);
181                                 x509 = new Mono.Security.X509.X509Certificate (data);
182                         }
183                         // for 1.x IntPtr.Zero results in an "empty" certificate instance
184                 }
185         
186                 public X509Certificate (System.Security.Cryptography.X509Certificates.X509Certificate cert) 
187                 {
188 #if NET_2_0
189                         if (cert == null)
190                                 throw new ArgumentNullException ("cert");
191 #endif
192
193                         if (cert != null) {
194                                 byte[] data = cert.GetRawCertData ();
195                                 if (data != null)
196                                         x509 = new Mono.Security.X509.X509Certificate (data);
197                                 hideDates = false;
198                         }
199                 }
200
201
202                 // public methods
203         
204                 public virtual bool Equals (System.Security.Cryptography.X509Certificates.X509Certificate other)
205                 {
206                         if (other == null) {
207                                 return false;
208                         } else {
209                                 if (other.x509 == null) {
210 #if NET_2_0
211                                         if (x509 == null)
212                                                 return true;
213                                         throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
214 #else
215                                         return (x509 == null);
216 #endif
217                                 }
218
219                                 byte[] raw = other.x509.RawData;
220                                 if (raw != null) {
221                                         if (x509 == null)
222                                                 return false;
223                                         if (x509.RawData == null)
224                                                 return false;
225                                         if (raw.Length == x509.RawData.Length) {
226                                                 for (int i = 0; i < raw.Length; i++) {
227                                                         if (raw[i] != x509.RawData [i])
228                                                                 return false;
229                                                 }
230                                                 // well no choice must be equals!
231                                                 return true;
232                                         }
233                                         else
234                                                 return false;
235                                 }
236                         }
237                         return ((x509 == null) || (x509.RawData == null));
238                 }
239         
240                 // LAMESPEC: This is the equivalent of the "thumbprint" that can be seen
241                 // in the certificate viewer of Windows. This is ALWAYS the SHA1 hash of
242                 // the certificate (i.e. it has nothing to do with the actual hash 
243                 // algorithm used to sign the certificate).
244                 public virtual byte[] GetCertHash () 
245                 {
246 #if NET_2_0
247                         if (x509 == null)
248                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
249 #endif
250                         // we'll hash the cert only once and only if required
251                         if ((cachedCertificateHash == null) && (x509 != null)) {
252                                 SHA1 sha = SHA1.Create ();
253                                 cachedCertificateHash = sha.ComputeHash (x509.RawData);
254                         }
255                         return cachedCertificateHash;
256                 }
257         
258                 public virtual string GetCertHashString () 
259                 {
260                         // must call GetCertHash (not variable) or optimization wont work
261                         return tostr (GetCertHash ());
262                 }
263         
264                 // strangly there are no DateTime returning function
265                 public virtual string GetEffectiveDateString ()
266                 {
267                         if (hideDates)
268                                 return null;
269 #if NET_2_0
270                         if (x509 == null)
271                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
272
273                         return x509.ValidFrom.ToLocalTime ().ToString ();
274 #else
275                         // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
276                         // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
277                         return x509.ValidFrom.ToUniversalTime ().AddHours (-8).ToString ();
278 #endif
279                 }
280         
281                 // strangly there are no DateTime returning function
282                 public virtual string GetExpirationDateString () 
283                 {
284                         if (hideDates)
285                                 return null;
286 #if NET_2_0
287                         if (x509 == null)
288                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
289
290                         return x509.ValidUntil.ToLocalTime ().ToString ();
291 #else
292                         // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
293                         // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
294                         return x509.ValidUntil.ToUniversalTime ().AddHours (-8).ToString ();
295 #endif
296                 }
297         
298                 // well maybe someday there'll be support for PGP or SPKI ?
299                 public virtual string GetFormat () 
300                 {
301                         return "X509";  // DO NOT TRANSLATE
302                 }
303         
304                 public override int GetHashCode ()
305                 {
306 #if NET_2_0
307                         if (x509 == null)
308                                 return 0;
309 #endif
310                         // the cert hash may not be (yet) calculated
311                         if (cachedCertificateHash == null)
312                                 GetCertHash();
313                 
314                         // return the integer of the first 4 bytes of the cert hash
315                         if ((cachedCertificateHash != null) && (cachedCertificateHash.Length >= 4))
316                                 return ((cachedCertificateHash[0] << 24) |(cachedCertificateHash[1] << 16) |
317                                         (cachedCertificateHash[2] << 8) | cachedCertificateHash[3]);
318                         else
319                                 return 0;
320                 }
321
322 #if NET_2_0
323                 [Obsolete ("Use the Issuer property.")]
324 #endif
325                 public virtual string GetIssuerName () 
326                 {
327 #if NET_2_0
328                         if (x509 == null)
329                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
330 #endif
331                         return x509.IssuerName;
332                 }
333         
334                 public virtual string GetKeyAlgorithm () 
335                 {
336 #if NET_2_0
337                         if (x509 == null)
338                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
339 #endif
340                         return x509.KeyAlgorithm;
341                 }
342         
343                 public virtual byte[] GetKeyAlgorithmParameters () 
344                 {
345 #if NET_2_0
346                         if (x509 == null)
347                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
348
349                         byte[] kap = x509.KeyAlgorithmParameters;
350                         if (kap == null)
351                                 throw new CryptographicException (Locale.GetText ("Parameters not part of the certificate"));
352
353                         return kap;
354 #else
355                         return x509.KeyAlgorithmParameters;
356 #endif
357                 }
358         
359                 public virtual string GetKeyAlgorithmParametersString () 
360                 {
361                         return tostr (GetKeyAlgorithmParameters ());
362                 }
363         
364 #if NET_2_0
365                 [Obsolete ("Use the Subject property.")]
366 #endif
367                 public virtual string GetName ()
368                 {
369 #if NET_2_0
370                         if (x509 == null)
371                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
372 #endif
373                         return x509.SubjectName;
374                 }
375         
376                 public virtual byte[] GetPublicKey () 
377                 {
378 #if NET_2_0
379                         if (x509 == null)
380                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
381 #endif
382                         return x509.PublicKey;
383                 }
384         
385                 public virtual string GetPublicKeyString () 
386                 {
387                         return tostr (GetPublicKey ());
388                 }
389         
390                 public virtual byte[] GetRawCertData () 
391                 {
392 #if NET_2_0
393                         if (x509 == null)
394                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
395                         return x509.RawData;
396 #else
397                         return ((x509 != null) ? x509.RawData : null);
398 #endif
399                 }
400         
401                 public virtual string GetRawCertDataString () 
402                 {
403 #if NET_2_0
404                         if (x509 == null)
405                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
406                         return tostr (x509.RawData);
407 #else
408                         return ((x509 != null) ? tostr (x509.RawData) : null);
409 #endif
410                 }
411         
412                 public virtual byte[] GetSerialNumber () 
413                 {
414 #if NET_2_0
415                         if (x509 == null)
416                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
417 #endif
418                         return x509.SerialNumber;
419                 }
420         
421                 public virtual string GetSerialNumberString () 
422                 {
423                         byte[] sn = GetSerialNumber ();
424 #if NET_2_0
425                         Array.Reverse (sn);
426 #endif
427                         return tostr (sn);
428                 }
429         
430                 // to please corcompare ;-)
431                 public override string ToString () 
432                 {
433                         return base.ToString ();
434                 }
435         
436                 public virtual string ToString (bool fVerbose) 
437                 {
438                         if (!fVerbose || (x509 == null))
439                                 return base.ToString ();
440
441                         string nl = Environment.NewLine;
442                         StringBuilder sb = new StringBuilder ();
443 #if NET_2_0
444                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, Subject);
445                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, Issuer);
446                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, GetEffectiveDateString ());
447                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, GetExpirationDateString ());
448                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}", nl, GetCertHashString ());
449 #else
450                         sb.Append ("CERTIFICATE:");
451                         sb.Append (nl);
452                         sb.Append ("\tFormat:  ");
453                         sb.Append (GetFormat ());
454                         if (x509.SubjectName != null) {
455                                 sb.Append (nl);
456                                 sb.Append ("\tName:  ");
457                                 sb.Append (GetName ());
458                         }
459                         if (x509.IssuerName != null) {
460                                 sb.Append (nl);
461                                 sb.Append ("\tIssuing CA:  ");
462                                 sb.Append (GetIssuerName ());
463                         }
464                         if (x509.SignatureAlgorithm != null) {
465                                 sb.Append (nl);
466                                 sb.Append ("\tKey Algorithm:  ");
467                                 sb.Append (GetKeyAlgorithm ());
468                         }
469                         if (x509.SerialNumber != null) {
470                                 sb.Append (nl);
471                                 sb.Append ("\tSerial Number:  ");
472                                 sb.Append (GetSerialNumberString ());
473                         }
474                         // Note: Algorithm is not spelled right as the actual 
475                         // MS implementation (we do exactly the same for the
476                         // comparison in the unit tests)
477                         if (x509.KeyAlgorithmParameters != null) {
478                                 sb.Append (nl);
479                                 sb.Append ("\tKey Alogrithm Parameters:  ");
480                                 sb.Append (GetKeyAlgorithmParametersString ());
481                         }
482                         if (x509.PublicKey != null) {
483                                 sb.Append (nl);
484                                 sb.Append ("\tPublic Key:  ");
485                                 sb.Append (GetPublicKeyString ());
486                         }
487                         sb.Append (nl);
488 #endif
489                         sb.Append (nl);
490                         return sb.ToString ();
491                 }
492
493                 private static byte[] Load (string fileName)
494                 {
495                         byte[] data = null;
496                         using (FileStream fs = File.OpenRead (fileName)) {
497                                 data = new byte [fs.Length];
498                                 fs.Read (data, 0, data.Length);
499                                 fs.Close ();
500                         }
501                         return data;
502                 }
503         }
504 }