New test.
[mono.git] / mcs / class / corlib / System.Security.Cryptography.X509Certificates / X509Certificate.cs
index 20bcce22e2dae4aca9eeb02c0ffc9e2a7290bb1e..c226033ad20e0f0e33f393a09dc27565e9eea7ad 100644 (file)
@@ -2,20 +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:
@@ -26,8 +50,31 @@ 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;
+               //      DWORD                   cbCertEncoded;
+               //      PCERT_INFO              pCertInfo;
+               //      HCERTSTORE              hCertStore;
+               // } CERT_CONTEXT, *PCERT_CONTEXT;
+               // typedef const CERT_CONTEXT *PCCERT_CONTEXT;
+               [StructLayout (LayoutKind.Sequential)]
+               internal struct CertificateContext {
+                       public UInt32 dwCertEncodingType;
+                       public IntPtr pbCertEncoded;
+                       public UInt32 cbCertEncoded;
+                       public IntPtr pCertInfo;
+                       public IntPtr hCertStore;
+               }
+               // NOTE: We only define the CryptoAPI structure (from WINCRYPT.H)
+               // so we don't create any dependencies on Windows DLL in corlib
+
                private Mono.Security.X509.X509Certificate x509;
                private bool hideDates;
                private byte[] cachedCertificateHash;
@@ -51,99 +98,40 @@ 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) 
-               {
-                       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;
-                       }
-               }
-       
-               // LAMESPEC: How does it differ from CreateFromCertFile ?
-               // It seems to get the certificate inside a PE file (maybe a CAB too ?)
+               [MonoTODO ("Incomplete - minimal validation in this version")]
                public static X509Certificate CreateFromSignedFile (string filename)
                {
-                       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);
+                               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);
+                               }
+
+                               // if no signature is present return an empty certificate
+                               byte[] cert = null; // must not confuse compiler about null ;)
+                               return new X509Certificate (cert);
                        }
-                       catch {
-                               return null;
+                       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);
                        }
                }
        
@@ -159,18 +147,33 @@ namespace System.Security.Cryptography.X509Certificates {
                        }
                }
        
-               public X509Certificate (byte[] data) : this (data, true) {}
+               public X509Certificate (byte[] data) : this (data, true)
+               {
+               }
        
-               [MonoTODO("Handle on CryptoAPI certificate")]
+               [SecurityPermission (SecurityAction.Demand, UnmanagedCode = true)]
                public X509Certificate (IntPtr handle) 
                {
-                       // normally a handle to CryptoAPI
-                       // How does Mono "handle this handle" ???
-                       throw new NotSupportedException ();
+                       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)
@@ -178,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)
@@ -186,6 +255,10 @@ namespace System.Security.Cryptography.X509Certificates {
                        if (cert != null) {
                                byte[] raw = cert.GetRawCertData ();
                                if (raw != null) {
+                                       if (x509 == null)
+                                               return false;
+                                       if (x509.RawData == null)
+                                               return false;
                                        if (raw.Length == x509.RawData.Length) {
                                                for (int i = 0; i < raw.Length; i++) {
                                                        if (raw[i] != x509.RawData [i])
@@ -198,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
@@ -208,7 +283,7 @@ namespace System.Security.Cryptography.X509Certificates {
                public virtual byte[] GetCertHash () 
                {
                        // we'll hash the cert only once and only if required
-                       if ((cachedCertificateHash == null) && (x509.RawData != null)) {
+                       if ((cachedCertificateHash == null) && (x509 != null)) {
                                SHA1 sha = SHA1.Create ();
                                cachedCertificateHash = sha.ComputeHash (x509.RawData);
                        }
@@ -222,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 ?
@@ -262,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;
@@ -283,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;
@@ -300,12 +387,12 @@ namespace System.Security.Cryptography.X509Certificates {
        
                public virtual byte[] GetRawCertData () 
                {
-                       return x509.RawData;
+                       return ((x509 != null) ? x509.RawData : null);
                }
        
                public virtual string GetRawCertDataString () 
                {
-                       return tostr (x509.RawData);
+                       return ((x509 != null) ? tostr (x509.RawData) : null);
                }
        
                public virtual byte[] GetSerialNumber () 
@@ -319,7 +406,7 @@ namespace System.Security.Cryptography.X509Certificates {
                }
        
                // to please corcompare ;-)
-               public override string ToString() 
+               public override string ToString () 
                {
                        return base.ToString ();
                }
@@ -373,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
+}