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.X509;
37
38 using System.Runtime.Serialization;
39 #if !MOONLIGHT
40 using Mono.Security.Authenticode;
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_1
54         public partial class X509Certificate {
55 #else
56         public partial class X509Certificate : IDeserializationCallback, ISerializable {
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 #if !MOONLIGHT
104                 [MonoTODO ("Incomplete - minimal validation in this version")]
105                 public static X509Certificate CreateFromSignedFile (string filename)
106                 {
107                         try {
108                                 AuthenticodeDeformatter a = new AuthenticodeDeformatter (filename);
109                                 if (a.SigningCertificate != null) {
110                                         return new X509Certificate (a.SigningCertificate.RawData);
111                                 }
112                         }
113                         catch (SecurityException) {
114                                 // don't wrap SecurityException into a COMException
115                                 throw;
116                         }
117                         catch (Exception e) {
118                                 string msg = Locale.GetText ("Couldn't extract digital signature from {0}.", filename);
119                                 throw new COMException (msg, e);
120                         }
121                         throw new CryptographicException (Locale.GetText ("{0} isn't signed.", filename));
122                 }
123
124 #endif // NET_2_1
125
126                 // constructors
127         
128                 // special constructor for Publisher (and related classes).
129                 // Dates strings are null
130                 internal X509Certificate (byte[] data, bool dates) 
131                 {
132                         if (data != null) {
133                                 Import (data, (string)null, X509KeyStorageFlags.DefaultKeySet);
134                                 hideDates = !dates;
135                         }
136                 }
137         
138                 public X509Certificate (byte[] data) : this (data, true)
139                 {
140                 }
141         
142                 public X509Certificate (IntPtr handle) 
143                 {
144                         if (handle == IntPtr.Zero)
145                                 throw new ArgumentException ("Invalid handle.");
146 #if NET_2_1
147                         // this works on Windows-only so it's of no use for Moonlight
148                         // even more since this ctor is [SecurityCritical]
149                         throw new NotSupportedException ();
150 #else
151                         InitFromHandle (handle);
152 #endif
153                 }
154
155 #if !MOONLIGHT
156                 [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
157                 private void InitFromHandle (IntPtr handle)
158                 {
159                         if (handle != IntPtr.Zero) {
160                                 // both Marshal.PtrToStructure and Marshal.Copy use LinkDemand (so they will always success from here)
161                                 CertificateContext cc = (CertificateContext) Marshal.PtrToStructure (handle, typeof (CertificateContext));
162                                 byte[] data = new byte [cc.cbCertEncoded];
163                                 Marshal.Copy (cc.pbCertEncoded, data, 0, (int)cc.cbCertEncoded);
164                                 x509 = new Mono.Security.X509.X509Certificate (data);
165                         }
166                         // for 1.x IntPtr.Zero results in an "empty" certificate instance
167                 }
168 #endif
169         
170                 public X509Certificate (System.Security.Cryptography.X509Certificates.X509Certificate cert) 
171                 {
172                         if (cert == null)
173                                 throw new ArgumentNullException ("cert");
174
175                         if (cert != null) {
176                                 byte[] data = cert.GetRawCertData ();
177                                 if (data != null)
178                                         x509 = new Mono.Security.X509.X509Certificate (data);
179                                 hideDates = false;
180                         }
181                 }
182
183
184                 // public methods
185         
186                 public virtual bool Equals (System.Security.Cryptography.X509Certificates.X509Certificate other)
187                 {
188                         if (other == null) {
189                                 return false;
190                         } else {
191                                 if (other.x509 == null) {
192                                         if (x509 == null)
193                                                 return true;
194                                         throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
195                                 }
196
197                                 byte[] raw = other.x509.RawData;
198                                 if (raw != null) {
199                                         if (x509 == null)
200                                                 return false;
201                                         if (x509.RawData == null)
202                                                 return false;
203                                         if (raw.Length == x509.RawData.Length) {
204                                                 for (int i = 0; i < raw.Length; i++) {
205                                                         if (raw[i] != x509.RawData [i])
206                                                                 return false;
207                                                 }
208                                                 // well no choice must be equals!
209                                                 return true;
210                                         }
211                                         else
212                                                 return false;
213                                 }
214                         }
215                         return ((x509 == null) || (x509.RawData == null));
216                 }
217         
218                 // LAMESPEC: This is the equivalent of the "thumbprint" that can be seen
219                 // in the certificate viewer of Windows. This is ALWAYS the SHA1 hash of
220                 // the certificate (i.e. it has nothing to do with the actual hash 
221                 // algorithm used to sign the certificate).
222                 public virtual byte[] GetCertHash () 
223                 {
224                         if (x509 == null)
225                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
226                         // we'll hash the cert only once and only if required
227                         if ((cachedCertificateHash == null) && (x509 != null)) {
228                                 SHA1 sha = SHA1.Create ();
229                                 cachedCertificateHash = sha.ComputeHash (x509.RawData);
230                         }
231                         return cachedCertificateHash;
232                 }
233         
234                 public virtual string GetCertHashString () 
235                 {
236                         // must call GetCertHash (not variable) or optimization wont work
237                         return tostr (GetCertHash ());
238                 }
239         
240                 // strangly there are no DateTime returning function
241                 public virtual string GetEffectiveDateString ()
242                 {
243                         if (hideDates)
244                                 return null;
245                         if (x509 == null)
246                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
247
248                         return x509.ValidFrom.ToLocalTime ().ToString ();
249                 }
250         
251                 // strangly there are no DateTime returning function
252                 public virtual string GetExpirationDateString () 
253                 {
254                         if (hideDates)
255                                 return null;
256                         if (x509 == null)
257                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
258
259                         return x509.ValidUntil.ToLocalTime ().ToString ();
260                 }
261         
262                 // well maybe someday there'll be support for PGP or SPKI ?
263                 public virtual string GetFormat () 
264                 {
265                         return "X509";  // DO NOT TRANSLATE
266                 }
267         
268                 public override int GetHashCode ()
269                 {
270                         if (x509 == null)
271                                 return 0;
272                         // the cert hash may not be (yet) calculated
273                         if (cachedCertificateHash == null)
274                                 GetCertHash();
275                 
276                         // return the integer of the first 4 bytes of the cert hash
277                         if ((cachedCertificateHash != null) && (cachedCertificateHash.Length >= 4))
278                                 return ((cachedCertificateHash[0] << 24) |(cachedCertificateHash[1] << 16) |
279                                         (cachedCertificateHash[2] << 8) | cachedCertificateHash[3]);
280                         else
281                                 return 0;
282                 }
283
284                 [Obsolete ("Use the Issuer property.")]
285                 public virtual string GetIssuerName () 
286                 {
287                         if (x509 == null)
288                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
289                         return x509.IssuerName;
290                 }
291         
292                 public virtual string GetKeyAlgorithm () 
293                 {
294                         if (x509 == null)
295                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
296                         return x509.KeyAlgorithm;
297                 }
298         
299                 public virtual byte[] GetKeyAlgorithmParameters () 
300                 {
301                         if (x509 == null)
302                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
303
304                         byte[] kap = x509.KeyAlgorithmParameters;
305                         if (kap == null)
306                                 throw new CryptographicException (Locale.GetText ("Parameters not part of the certificate"));
307
308                         return kap;
309                 }
310         
311                 public virtual string GetKeyAlgorithmParametersString () 
312                 {
313                         return tostr (GetKeyAlgorithmParameters ());
314                 }
315         
316                 [Obsolete ("Use the Subject property.")]
317                 public virtual string GetName ()
318                 {
319                         if (x509 == null)
320                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
321                         return x509.SubjectName;
322                 }
323         
324                 public virtual byte[] GetPublicKey () 
325                 {
326                         if (x509 == null)
327                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
328                         return x509.PublicKey;
329                 }
330         
331                 public virtual string GetPublicKeyString () 
332                 {
333                         return tostr (GetPublicKey ());
334                 }
335         
336                 public virtual byte[] GetRawCertData () 
337                 {
338                         if (x509 == null)
339                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
340                         return x509.RawData;
341                 }
342         
343                 public virtual string GetRawCertDataString () 
344                 {
345                         if (x509 == null)
346                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
347                         return tostr (x509.RawData);
348                 }
349         
350                 public virtual byte[] GetSerialNumber () 
351                 {
352                         if (x509 == null)
353                                 throw new CryptographicException (Locale.GetText ("Certificate instance is empty."));
354                         return x509.SerialNumber;
355                 }
356         
357                 public virtual string GetSerialNumberString () 
358                 {
359                         byte[] sn = GetSerialNumber ();
360                         Array.Reverse (sn);
361                         return tostr (sn);
362                 }
363         
364                 // to please corcompare ;-)
365                 public override string ToString () 
366                 {
367                         return base.ToString ();
368                 }
369         
370                 public virtual string ToString (bool fVerbose) 
371                 {
372                         if (!fVerbose || (x509 == null))
373                                 return base.ToString ();
374
375                         string nl = Environment.NewLine;
376                         StringBuilder sb = new StringBuilder ();
377                         sb.AppendFormat ("[Subject]{0}  {1}{0}{0}", nl, Subject);
378                         sb.AppendFormat ("[Issuer]{0}  {1}{0}{0}", nl, Issuer);
379                         sb.AppendFormat ("[Not Before]{0}  {1}{0}{0}", nl, GetEffectiveDateString ());
380                         sb.AppendFormat ("[Not After]{0}  {1}{0}{0}", nl, GetExpirationDateString ());
381                         sb.AppendFormat ("[Thumbprint]{0}  {1}{0}", nl, GetCertHashString ());
382                         sb.Append (nl);
383                         return sb.ToString ();
384                 }
385
386                 private static byte[] Load (string fileName)
387                 {
388                         byte[] data = null;
389                         using (FileStream fs = File.OpenRead (fileName)) {
390                                 data = new byte [fs.Length];
391                                 fs.Read (data, 0, data.Length);
392                                 fs.Close ();
393                         }
394                         return data;
395                 }
396         }
397 }