New test.
[mono.git] / mcs / class / corlib / System.Security.Cryptography.X509Certificates / X509Certificate.cs
index 8aa433afea457efecfd37ce6f43ec1ca0fffab8a..c226033ad20e0f0e33f393a09dc27565e9eea7ad 100644 (file)
@@ -2,21 +2,44 @@
 // X509Certificates.cs: Handles X.509 certificates.
 //
 // Author:
-//     Sebastien Pouliot (spouliot@motus.com)
+//     Sebastien Pouliot  <sebastien@ximian.com>
 //
 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-using System;
 using System.IO;
 using System.Runtime.InteropServices;
-using System.Security.Cryptography;
+using System.Security.Permissions;
 using System.Text;
 
 using Mono.Security;
 using Mono.Security.Authenticode;
 using Mono.Security.X509;
 
+#if NET_2_0
+using System.Runtime.Serialization;
+#endif
+
 namespace System.Security.Cryptography.X509Certificates {
 
        // References:
@@ -27,8 +50,12 @@ namespace System.Security.Cryptography.X509Certificates {
        // and/or Authenticode certs. However this class works with older
        // X509v1 certificates and non-authenticode (code signing) certs.
        [Serializable]
+#if NET_2_0
+       [ComVisible (true)]
+       public class X509Certificate : IDeserializationCallback, ISerializable {
+#else
        public class X509Certificate {
-       
+#endif
                // typedef struct _CERT_CONTEXT {
                 //     DWORD                   dwCertEncodingType;
                 //     BYTE                    *pbCertEncoded;
@@ -71,111 +98,41 @@ namespace System.Security.Cryptography.X509Certificates {
                public static X509Certificate CreateFromCertFile (string filename) 
                {
                        byte[] data = null;
-                       FileStream fs = new FileStream (filename, FileMode.Open);
-                       try {
+                       using (FileStream fs = File.OpenRead (filename)) {
                                data = new byte [fs.Length];
                                fs.Read (data, 0, data.Length);
-                       }
-                       finally {
                                fs.Close ();
                        }
-       
                        return new X509Certificate (data);
                }
        
-               /*static private int ReadWord (Stream s) 
-               {
-                       int word = s.ReadByte ();
-                       word = (s.ReadByte () << 8) + word;
-                       return word;
-               }
-       
-               static private int ReadDWord (Stream s) 
-               {
-                       int b1 = s.ReadByte ();
-                       int b2 = s.ReadByte ();
-                       int b3 = s.ReadByte ();
-                       int b4 = s.ReadByte ();
-                       return (b4 << 24) + (b3 << 16) + (b2 << 8) + b1;
-               }
-       
-               // http://www.mycgiserver.com/~ultraschall/files/pefile.htm
-               static private byte[] GetAuthenticodeSignature (string fileName) 
+               [MonoTODO ("Incomplete - minimal validation in this version")]
+               public static X509Certificate CreateFromSignedFile (string filename)
                {
-                       FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
                        try {
-                               // MZ - DOS header
-                               if (ReadWord (fs) != 0x5a4d)
-                                       return null;
-                               // find offset of PE header
-                               fs.Seek (60, SeekOrigin.Begin);
-                               int peOffset = ReadDWord (fs);
-       
-                               // PE - NT header
-                               fs.Seek (peOffset, SeekOrigin.Begin);
-                               if (ReadWord (fs) != 0x4550)
-                                       return null;
-       
-                               fs.Seek (150, SeekOrigin.Current);
-       
-                               // IMAGE_DIRECTORY_ENTRY_SECURITY
-                               int secOffset = ReadDWord (fs);
-                               if (secOffset == 0)
-                                       return null;
-                               int secSize = ReadDWord (fs);
-                               if (secSize == 0)
-                                       return null;
-       
-                               // Authenticode signature
-                               fs.Seek (secOffset, SeekOrigin.Begin);
-                               if (ReadDWord (fs) != secSize)
-                                       return null;
-                               if (ReadDWord (fs) != 0x00020200)
-                                       return null;
-                               
-                               byte[] signature = new byte [secSize - 8];
-                               fs.Read (signature, 0, signature.Length);
-                               fs.Close ();
-                               return signature;
-                       }
-                       catch {
-                               fs.Close ();
-                               return null;
-                       }
-               }*/
+                               AuthenticodeDeformatter a = new AuthenticodeDeformatter (filename);
+                               if (a.SigningCertificate != null) {
+                                       if (a.Reason != 0) {
+                                               string msg = String.Format (Locale.GetText (
+                                                       "Invalid digital signature on {0}, reason #{1}."),
+                                                       filename, a.Reason);
+                                               throw new COMException (msg);
+                                       }
+                                       return new X509Certificate (a.SigningCertificate.RawData);
+                               }
 
-               // LAMESPEC: How does it differ from CreateFromCertFile ?
-               // It seems to get the certificate inside a PE file (maybe a CAB too ?)
-               [MonoTODO ("Incomplete - no validation in this version")]
-               public static X509Certificate CreateFromSignedFile (string filename)
-               {
-                       AuthenticodeDeformatter a = new AuthenticodeDeformatter (filename);
-                       if ((a.Certificates != null) && (a.Certificates.Count > 0)) {
-                               return new X509Certificate (a.Certificates [0].RawData);
-                       }
-                       else {
                                // if no signature is present return an empty certificate
                                byte[] cert = null; // must not confuse compiler about null ;)
                                return new X509Certificate (cert);
                        }
-/*
-                       byte[] signature = GetAuthenticodeSignature (filename);
-                       if (signature == null)
-                               throw new COMException ("File doesn't have a signature", -2146762496);
-       
-                       // this is a big bad ASN.1 structure
-                       // Reference: http://www.cs.auckland.ac.nz/~pgut001/pubs/authenticode.txt
-                       // next we must find the last certificate inside the structure
-                       try {
-                               ASN1 sign = new ASN1 (signature);
-                               // we don't have to understand much of it to get the certificate
-                               ASN1 certs = sign [1][0][3];
-                               byte[] lastCert = certs [certs.Count - 1].GetBytes();
-                               return new X509Certificate (lastCert);
+                       catch (SecurityException) {
+                               // don't wrap SecurityException into a COMException
+                               throw;
+                       }
+                       catch (Exception e) {
+                               string msg = String.Format (Locale.GetText ("Couldn't extract digital signature from {0}."), filename);
+                               throw new COMException (msg, e);
                        }
-                       catch {
-                               return null;
-                       }*/
                }
        
                // constructors
@@ -190,18 +147,33 @@ namespace System.Security.Cryptography.X509Certificates {
                        }
                }
        
-               public X509Certificate (byte[] data) : this (data, true) {}
+               public X509Certificate (byte[] data) : this (data, true)
+               {
+               }
        
+               [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
                public X509Certificate (IntPtr handle) 
                {
-                       CertificateContext cc = (CertificateContext) Marshal.PtrToStructure (handle, typeof (CertificateContext));
-                       byte[] data = new byte [cc.cbCertEncoded];
-                       Marshal.Copy (cc.pbCertEncoded, data, 0, (int)cc.cbCertEncoded);
-                       x509 = new Mono.Security.X509.X509Certificate (data);
+                       if (handle != IntPtr.Zero) {
+                               CertificateContext cc = (CertificateContext) Marshal.PtrToStructure (handle, typeof (CertificateContext));
+                               byte[] data = new byte [cc.cbCertEncoded];
+                               Marshal.Copy (cc.pbCertEncoded, data, 0, (int)cc.cbCertEncoded);
+                               x509 = new Mono.Security.X509.X509Certificate (data);
+                       }
+#if NET_2_0
+                       else
+                               throw new ArgumentException ("Invalid handle.");
+#endif
+                       // IntPtr.Zero results in an "empty" certificate instance
                }
        
                public X509Certificate (System.Security.Cryptography.X509Certificates.X509Certificate cert) 
                {
+#if NET_2_0
+                       if (cert == null)
+                               throw new ArgumentNullException ();
+#endif
+
                        if (cert != null) {
                                byte[] data = cert.GetRawCertData ();
                                if (data != null)
@@ -209,7 +181,73 @@ namespace System.Security.Cryptography.X509Certificates {
                                hideDates = false;
                        }
                }
-       
+
+#if NET_2_0
+               [MonoTODO]
+               public X509Certificate ()
+               {
+               }
+
+               [MonoTODO]
+               public X509Certificate (byte[] rawData, string password)
+               {
+                       Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO]
+               public X509Certificate (byte[] rawData, SecureString password)
+               {
+                       Import (rawData, password, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO]
+               public X509Certificate (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       Import (rawData, password, keyStorageFlags);
+               }
+
+               [MonoTODO]
+               public X509Certificate (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       Import (rawData, password, keyStorageFlags);
+               }
+
+               [MonoTODO]
+               public X509Certificate (string fileName)
+               {
+                       Import (fileName, (string)null, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO]
+               public X509Certificate (string fileName, string password)
+               {
+                       Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO]
+               public X509Certificate (string fileName, SecureString password)
+               {
+                       Import (fileName, password, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO]
+               public X509Certificate (string fileName, string password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       Import (fileName, password, keyStorageFlags);
+               }
+
+               [MonoTODO]
+               public X509Certificate (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       Import (fileName, password, keyStorageFlags);
+               }
+
+               [MonoTODO]
+               public X509Certificate (SerializationInfo info, StreamingContext context)
+               {
+               }
+#endif
+
                // public methods
        
                public virtual bool Equals (System.Security.Cryptography.X509Certificates.X509Certificate cert)
@@ -233,7 +271,9 @@ namespace System.Security.Cryptography.X509Certificates {
                                                return false;
                                }
                        }
-                       return (x509.RawData == null);
+                       else
+                               return false;
+                       return x509 == null || (x509.RawData == null);
                }
        
                // LAMESPEC: This is the equivalent of the "thumbprint" that can be seen
@@ -257,25 +297,31 @@ namespace System.Security.Cryptography.X509Certificates {
                }
        
                // strangly there are no DateTime returning function
-               // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
-               // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
                public virtual string GetEffectiveDateString ()
                {
                        if (hideDates)
                                return null;
-                       DateTime dt = x509.ValidFrom.ToUniversalTime().AddHours (-8);
-                       return dt.ToString (); //"yyyy-MM-dd HH:mm:ss");
+#if NET_2_0
+                       return x509.ValidFrom.ToString ();
+#else
+                       // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
+                       // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
+                       return x509.ValidFrom.ToUniversalTime ().AddHours (-8).ToString ();
+#endif
                }
        
                // strangly there are no DateTime returning function
-               // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
-               // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
                public virtual string GetExpirationDateString () 
                {
                        if (hideDates)
                                return null;
-                       DateTime dt = x509.ValidUntil.ToUniversalTime().AddHours (-8);
-                       return dt.ToString (); //"yyyy-MM-dd HH:mm:ss");
+#if NET_2_0
+                       return x509.ValidUntil.ToString ();
+#else
+                       // LAMESPEC: Microsoft returns the local time from Pacific Time (GMT-8)
+                       // BUG: This will not be corrected in Framework 1.1 and also affect WSE 1.0
+                       return x509.ValidUntil.ToUniversalTime ().AddHours (-8).ToString ();
+#endif
                }
        
                // well maybe someday there'll be support for PGP or SPKI ?
@@ -297,7 +343,10 @@ namespace System.Security.Cryptography.X509Certificates {
                        else
                                return 0;
                }
-       
+
+#if NET_2_0
+               [Obsolete ("Use the Issuer property.")]
+#endif
                public virtual string GetIssuerName () 
                {
                        return x509.IssuerName;
@@ -318,6 +367,9 @@ namespace System.Security.Cryptography.X509Certificates {
                        return tostr (x509.KeyAlgorithmParameters);
                }
        
+#if NET_2_0
+               [Obsolete ("Use the Subject property.")]
+#endif
                public virtual string GetName ()
                {
                        return x509.SubjectName;
@@ -408,5 +460,155 @@ namespace System.Security.Cryptography.X509Certificates {
                        else
                                return base.ToString ();
                }
+
+#if NET_2_0
+               public string Issuer {
+                       get { return x509.IssuerName; }
+               }
+
+               public string Subject {
+                       get { return x509.SubjectName; }
+               }
+
+               [ComVisible (false)]
+               public override bool Equals (object obj) 
+               {
+                       X509Certificate x = (obj as X509Certificate);
+                       if (x != null)
+                               return this.Equals (x);
+                       return false;
+               }
+
+               [MonoTODO ("incomplete")]
+               [ComVisible (false)]
+               public virtual byte[] Export (X509ContentType contentType)
+               {
+                       return Export (contentType, (byte[])null);
+               }
+
+               [MonoTODO ("incomplete")]
+               [ComVisible (false)]
+               public virtual byte[] Export (X509ContentType contentType, string password)
+               {
+                       return Export (contentType, Encoding.UTF8.GetBytes (password));
+               }
+
+               [MonoTODO ("incomplete")]
+               public virtual byte[] Export (X509ContentType contentType, SecureString password)
+               {
+                       return Export (contentType, password.GetBuffer ());
+               }
+
+               [MonoTODO ("export!")]
+               internal byte[] Export (X509ContentType contentType, byte[] password)
+               {
+                       try {
+                               switch (contentType) {
+                               case X509ContentType.Cert:
+                                       return x509.RawData;
+                               default:
+                                       throw new NotSupportedException ();
+                               }
+                       }
+                       finally {
+                               // protect password
+                               if (password != null)
+                                       Array.Clear (password, 0, password.Length);
+                       }
+               }
+
+               [MonoTODO]
+               void IDeserializationCallback.OnDeserialization (object sender)
+               {
+               }
+
+               [ComVisible (false)]
+               public virtual void Import (byte[] rawData)
+               {
+                       Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO ("missing KeyStorageFlags support")]
+               [ComVisible (false)]
+               public virtual void Import (byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       if (password == null) {
+                               x509 = new Mono.Security.X509.X509Certificate (rawData);
+                               // TODO - PKCS12 without password
+                       } else {
+                               // try PKCS#12
+                               try {
+                                       PKCS12 pfx = new PKCS12 (rawData, password);
+                                       if (pfx.Certificates.Count > 0) {
+                                               x509 = pfx.Certificates [0];
+                                       } else {
+                                               x509 = null;
+                                       }
+                               }
+                               catch {
+                                       // it's possible to supply a (unrequired/unusued) password
+                                       // fix bug #79028
+                                       x509 = new Mono.Security.X509.X509Certificate (rawData);
+                               }
+                       }
+               }
+
+               [MonoTODO ("SecureString is incomplete")]
+               public virtual void Import (byte[] rawData, SecureString password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       Import (rawData, (string)null, keyStorageFlags);
+               }
+
+               [ComVisible (false)]
+               public virtual void Import (string fileName)
+               {
+                       byte[] rawData = Load (fileName);
+                       Import (rawData, (string)null, X509KeyStorageFlags.DefaultKeySet);
+               }
+
+               [MonoTODO ("missing KeyStorageFlags support")]
+               [ComVisible (false)]
+               public virtual void Import (string fileName, string password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       byte[] rawData = Load (fileName);
+                       Import (rawData, password, keyStorageFlags);
+               }
+
+               [MonoTODO ("SecureString is incomplete")]
+               public virtual void Import (string fileName, SecureString password, X509KeyStorageFlags keyStorageFlags)
+               {
+                       byte[] rawData = Load (fileName);
+                       Import (rawData, (string)null, keyStorageFlags);
+               }
+
+               private byte[] Load (string fileName)
+               {
+                       byte[] data = null;
+                       using (FileStream fs = new FileStream (fileName, FileMode.Open)) {
+                               data = new byte [fs.Length];
+                               fs.Read (data, 0, data.Length);
+                               fs.Close ();
+                       }
+                       return data;
+               }
+
+               [MonoTODO]
+               void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
+               {
+               }
+
+               [MonoTODO]
+               [ComVisible (false)]
+               public virtual void Reset ()
+               {
+               }
+
+               // properties
+
+               [ComVisible (false)]
+               public IntPtr Handle {
+                       get { return (IntPtr) 0; }
+               }
+#endif
        }
-}
\ No newline at end of file
+}