New test.
[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                         catch (Exception e) {
126                                 string msg = Locale.GetText ("Couldn't extract digital signature from {0}.", filename);
127                                 throw new COMException (msg, e);
128                         }
129 #if NET_2_0
130                         throw new CryptographicException (Locale.GetText ("{0} isn't signed.", filename));
131 #else
132                         // if no signature is present return an empty certificate
133                         byte[] cert = null; // must not confuse compiler about null ;)
134                         return new X509Certificate (cert);
135 #endif
136                 }
137         
138                 // constructors
139         
140                 // special constructor for Publisher (and related classes).
141                 // Dates strings are null
142                 internal X509Certificate (byte[] data, bool dates) 
143                 {
144                         if (data != null) {
145 #if NET_2_0
146                                 Import (data, (string)null, X509KeyStorageFlags.DefaultKeySet);
147 #else
148                                 x509 = new Mono.Security.X509.X509Certificate (data);
149 #endif
150                                 hideDates = !dates;
151                         }
152                 }
153         
154                 public X509Certificate (byte[] data) : this (data, true)
155                 {
156                 }
157         
158                 public X509Certificate (IntPtr handle) 
159                 {
160 #if NET_2_0
161                         if (handle == IntPtr.Zero)
162                                 throw new ArgumentException ("Invalid handle.");
163 #endif
164                         InitFromHandle (handle);
165                 }
166
167                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
168                 private void InitFromHandle (IntPtr handle)
169                 {
170                         if (handle != IntPtr.Zero) {
171                                 // both Marshal.PtrToStructure and Marshal.Copy use LinkDemand (so they will always success from here)
172                                 CertificateContext cc = (CertificateContext) Marshal.PtrToStructure (handle, typeof (CertificateContext));
173                                 byte[] data = new byte [cc.cbCertEncoded];
174                                 Marshal.Copy (cc.pbCertEncoded, data, 0, (int)cc.cbCertEncoded);
175                                 x509 = new Mono.Security.X509.X509Certificate (data);
176                         }
177                         // for 1.x IntPtr.Zero results in an "empty" certificate instance
178                 }
179         
180                 public X509Certificate (System.Security.Cryptography.X509Certificates.X509Certificate cert) 
181                 {
182 #if NET_2_0
183                         if (cert == null)
184                                 throw new ArgumentNullException ("cert");
185 #endif
186
187                         if (cert != null) {
188                                 byte[] data = cert.GetRawCertData ();
189                                 if (data != null)
190                                         x509 = new Mono.Security.X509.X509Certificate (data);
191                                 hideDates = false;
192                         }
193                 }
194
195
196                 // public methods
197         
198                 public virtual bool Equals (System.Security.Cryptography.X509Certificates.X509Certificate cert)
199                 {
200                         if (cert == null) {
201                                 return false;
202                         } else {
203                                 if (cert.x509 == null) {
204 #if NET_2_0
205                                         if (x509 == null)
206                                                 return true;
207                                         throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
208 #else
209                                         return (x509 == null);
210 #endif
211                                 }
212
213                                 byte[] raw = cert.x509.RawData;
214                                 if (raw != null) {
215                                         if (x509 == null)
216                                                 return false;
217                                         if (x509.RawData == null)
218                                                 return false;
219                                         if (raw.Length == x509.RawData.Length) {
220                                                 for (int i = 0; i < raw.Length; i++) {
221                                                         if (raw[i] != x509.RawData [i])
222                                                                 return false;
223                                                 }
224                                                 // well no choice must be equals!
225                                                 return true;
226                                         }
227                                         else
228                                                 return false;
229                                 }
230                         }
231                         return ((x509 == null) || (x509.RawData == null));
232                 }
233         
234                 // LAMESPEC: This is the equivalent of the "thumbprint" that can be seen
235                 // in the certificate viewer of Windows. This is ALWAYS the SHA1 hash of
236                 // the certificate (i.e. it has nothing to do with the actual hash 
237                 // algorithm used to sign the certificate).
238                 public virtual byte[] GetCertHash () 
239                 {
240 #if NET_2_0
241                         if (x509 == null)
242                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
243 #endif
244                         // we'll hash the cert only once and only if required
245                         if ((cachedCertificateHash == null) && (x509 != null)) {
246                                 SHA1 sha = SHA1.Create ();
247                                 cachedCertificateHash = sha.ComputeHash (x509.RawData);
248                         }
249                         return cachedCertificateHash;
250                 }
251         
252                 public virtual string GetCertHashString () 
253                 {
254                         // must call GetCertHash (not variable) or optimization wont work
255                         return tostr (GetCertHash ());
256                 }
257         
258                 // strangly there are no DateTime returning function
259                 public virtual string GetEffectiveDateString ()
260                 {
261                         if (hideDates)
262                                 return null;
263 #if NET_2_0
264                         if (x509 == null)
265                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
266
267                         return x509.ValidFrom.ToString ();
268 #else
269                         // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
270                         // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
271                         return x509.ValidFrom.ToUniversalTime ().AddHours (-8).ToString ();
272 #endif
273                 }
274         
275                 // strangly there are no DateTime returning function
276                 public virtual string GetExpirationDateString () 
277                 {
278                         if (hideDates)
279                                 return null;
280 #if NET_2_0
281                         if (x509 == null)
282                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
283
284                         return x509.ValidUntil.ToString ();
285 #else
286                         // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
287                         // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
288                         return x509.ValidUntil.ToUniversalTime ().AddHours (-8).ToString ();
289 #endif
290                 }
291         
292                 // well maybe someday there'll be support for PGP or SPKI ?
293                 public virtual string GetFormat () 
294                 {
295                         return "X509";  // DO NOT TRANSLATE
296                 }
297         
298                 public override int GetHashCode ()
299                 {
300 #if NET_2_0
301                         if (x509 == null)
302                                 return 0;
303 #endif
304                         // the cert hash may not be (yet) calculated
305                         if (cachedCertificateHash == null)
306                                 GetCertHash();
307                 
308                         // return the integer of the first 4 bytes of the cert hash
309                         if ((cachedCertificateHash != null) && (cachedCertificateHash.Length >= 4))
310                                 return ((cachedCertificateHash[0] << 24) |(cachedCertificateHash[1] << 16) |
311                                         (cachedCertificateHash[2] << 8) | cachedCertificateHash[3]);
312                         else
313                                 return 0;
314                 }
315
316 #if NET_2_0
317                 [Obsolete ("Use the Issuer property.")]
318 #endif
319                 public virtual string GetIssuerName () 
320                 {
321 #if NET_2_0
322                         if (x509 == null)
323                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
324 #endif
325                         return x509.IssuerName;
326                 }
327         
328                 public virtual string GetKeyAlgorithm () 
329                 {
330 #if NET_2_0
331                         if (x509 == null)
332                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
333 #endif
334                         return x509.KeyAlgorithm;
335                 }
336         
337                 public virtual byte[] GetKeyAlgorithmParameters () 
338                 {
339 #if NET_2_0
340                         if (x509 == null)
341                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
342
343                         byte[] kap = x509.KeyAlgorithmParameters;
344                         if (kap == null)
345                                 throw new CryptographicException (Locale.GetText ("Parameters not part of the certificate"));
346
347                         return kap;
348 #else
349                         return x509.KeyAlgorithmParameters;
350 #endif
351                 }
352         
353                 public virtual string GetKeyAlgorithmParametersString () 
354                 {
355                         return tostr (GetKeyAlgorithmParameters ());
356                 }
357         
358 #if NET_2_0
359                 [Obsolete ("Use the Subject property.")]
360 #endif
361                 public virtual string GetName ()
362                 {
363 #if NET_2_0
364                         if (x509 == null)
365                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
366 #endif
367                         return x509.SubjectName;
368                 }
369         
370                 public virtual byte[] GetPublicKey () 
371                 {
372 #if NET_2_0
373                         if (x509 == null)
374                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
375 #endif
376                         return x509.PublicKey;
377                 }
378         
379                 public virtual string GetPublicKeyString () 
380                 {
381                         return tostr (GetPublicKey ());
382                 }
383         
384                 public virtual byte[] GetRawCertData () 
385                 {
386 #if NET_2_0
387                         if (x509 == null)
388                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
389                         return x509.RawData;
390 #else
391                         return ((x509 != null) ? x509.RawData : null);
392 #endif
393                 }
394         
395                 public virtual string GetRawCertDataString () 
396                 {
397 #if NET_2_0
398                         if (x509 == null)
399                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
400                         return tostr (x509.RawData);
401 #else
402                         return ((x509 != null) ? tostr (x509.RawData) : null);
403 #endif
404                 }
405         
406                 public virtual byte[] GetSerialNumber () 
407                 {
408 #if NET_2_0
409                         if (x509 == null)
410                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
411 #endif
412                         return x509.SerialNumber;
413                 }
414         
415                 public virtual string GetSerialNumberString () 
416                 {
417                         byte[] sn = GetSerialNumber ();
418 #if NET_2_0
419                         Array.Reverse (sn);
420 #endif
421                         return tostr (sn);
422                 }
423         
424                 // to please corcompare ;-)
425                 public override string ToString () 
426                 {
427                         return base.ToString ();
428                 }
429         
430                 public virtual string ToString (bool details) 
431                 {
432                         if (!details || (x509 == null))
433                                 return base.ToString ();
434
435                         string nl = Environment.NewLine;
436                         StringBuilder sb = new StringBuilder ();
437 #if NET_2_0
438                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, Subject);
439                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, Issuer);
440                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, GetEffectiveDateString ());
441                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, GetExpirationDateString ());
442                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}", nl, GetCertHashString ());
443 #else
444                         sb.Append ("CERTIFICATE:");
445                         sb.Append (nl);
446                         sb.Append ("\tFormat:  ");
447                         sb.Append (GetFormat ());
448                         if (x509.SubjectName != null) {
449                                 sb.Append (nl);
450                                 sb.Append ("\tName:  ");
451                                 sb.Append (GetName ());
452                         }
453                         if (x509.IssuerName != null) {
454                                 sb.Append (nl);
455                                 sb.Append ("\tIssuing CA:  ");
456                                 sb.Append (GetIssuerName ());
457                         }
458                         if (x509.SignatureAlgorithm != null) {
459                                 sb.Append (nl);
460                                 sb.Append ("\tKey Algorithm:  ");
461                                 sb.Append (GetKeyAlgorithm ());
462                         }
463                         if (x509.SerialNumber != null) {
464                                 sb.Append (nl);
465                                 sb.Append ("\tSerial Number:  ");
466                                 sb.Append (GetSerialNumberString ());
467                         }
468                         // Note: Algorithm is not spelled right as the actual 
469                         // MS implementation (we do exactly the same for the
470                         // comparison in the unit tests)
471                         if (x509.KeyAlgorithmParameters != null) {
472                                 sb.Append (nl);
473                                 sb.Append ("\tKey Alogrithm Parameters:  ");
474                                 sb.Append (GetKeyAlgorithmParametersString ());
475                         }
476                         if (x509.PublicKey != null) {
477                                 sb.Append (nl);
478                                 sb.Append ("\tPublic Key:  ");
479                                 sb.Append (GetPublicKeyString ());
480                         }
481                         sb.Append (nl);
482 #endif
483                         sb.Append (nl);
484                         return sb.ToString ();
485                 }
486
487                 private static byte[] Load (string fileName)
488                 {
489                         byte[] data = null;
490                         using (FileStream fs = File.OpenRead (fileName)) {
491                                 data = new byte [fs.Length];
492                                 fs.Read (data, 0, data.Length);
493                                 fs.Close ();
494                         }
495                         return data;
496                 }
497         }
498 }