/* * Mentalis.org Security Library * * Copyright © 2002-2005, The KPD-Team * All rights reserved. * http://www.mentalis.org/ * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Neither the name of the KPD-Team, nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.IO; using System.Text; using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Runtime.InteropServices; using System.Collections.Specialized; using Org.Mentalis.Security.Cryptography; namespace Org.Mentalis.Security.Certificates { /// /// Defines a X509 v3 encoded certificate. /// public class Certificate : ICloneable { /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The full path to the PFX file. /// The password used to encrypt the private key. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified file. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(string file, string password) { return CertificateStore.CreateFromPfxFile(file, password).FindCertificate(); } /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The full path to the PFX file. /// The password used to encrypt the private key. /// true if the private keys associated with the certificates should be marked as exportable, false otherwise. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified file. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(string file, string password, bool exportable) { return CertificateStore.CreateFromPfxFile(file, password, exportable).FindCertificate(); } /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The full path to the PFX file. /// The password used to encrypt the private key. /// true if the private keys associated with the certificates should be marked as exportable, false otherwise. /// One of the values. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified file. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(string file, string password, bool exportable, KeysetLocation location) { return CertificateStore.CreateFromPfxFile(file, password, exportable, location).FindCertificate(); } /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The contents of a PFX file. /// The password used to encrypt the private key. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified bytes. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(byte[] file, string password) { return CertificateStore.CreateFromPfxFile(file, password).FindCertificate(); } /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The contents of a PFX file. /// The password used to encrypt the private key. /// true if the private keys associated with the certificates should be marked as exportable, false otherwise. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified bytes. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(byte[] file, string password, bool exportable) { return CertificateStore.CreateFromPfxFile(file, password, exportable).FindCertificate(); } /// /// Creates a new instance of the class by opening a PFX file and retrieving the first certificate from it. /// /// The contents of a PFX file. /// The password used to encrypt the private key. /// true if the private keys associated with the certificates should be marked as exportable, false otherwise. /// One of the values. /// One of the certificates in the PFX file. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified bytes. /// is invalid. /// /// Warning: this method returns the first Certificate it can find in the specified PFX file. /// Care should be taken to verify whether the correct Certificate instance is returned /// when using PFX files that contain more than one certificate. /// For more fine-grained control over which certificate is returned, use /// the CertificateStore.CreateFromPfxFile method to instantiate a CertificateStore object /// and then use the CertificateStore.FindCertificateBy*** methods. /// public static Certificate CreateFromPfxFile(byte[] file, string password, bool exportable, KeysetLocation location) { return CertificateStore.CreateFromPfxFile(file, password, exportable, location).FindCertificate(); } /// /// Creates a new instance of the class by opening a certificate file and retrieving the first certificate from it. /// /// The full path to the certificate file to open. /// One of the certificates in the certificate file. /// is a null reference (Nothing in Visual Basic). /// An error occurs while loading certificates from the specified file. public static Certificate CreateFromCerFile(string file) { return CertificateStore.CreateFromCerFile(file).FindCertificate(); } /// /// Creates a new instance of the class by reading a certificate from a certificate blob. /// /// The contents of the certificate file. /// A Certificate instance. /// if a null reference (Nothing in Visual Basic). /// An error occurs while loading the specified certificate. public static Certificate CreateFromCerFile(byte[] file) { if (file == null) throw new ArgumentNullException(); return CreateFromCerFile(file, 0, file.Length); } /// /// Creates a new instance of the class by reading a certificate from a certificate blob. /// /// The contents of the certificate file. /// The offset from which to start reading. /// The length of the certificate. /// A Certificate instance. /// if a null reference (Nothing in Visual Basic). /// An error occurs while loading the specified certificate. public static Certificate CreateFromCerFile(byte[] file, int offset, int size) { if (file == null) throw new ArgumentNullException(); if (offset < 0 || offset + size > file.Length) throw new ArgumentOutOfRangeException(); IntPtr data = Marshal.AllocHGlobal(size); Marshal.Copy(file, offset, data, size); IntPtr handle = SspiProvider.CertCreateCertificateContext(SecurityConstants.X509_ASN_ENCODING | SecurityConstants.PKCS_7_ASN_ENCODING, data, size); Marshal.FreeHGlobal(data); if (handle == IntPtr.Zero) throw new CertificateException("Unable to load the specified certificate."); else return new Certificate(handle); } /// /// Creates a new instance of the class by duplicating an existing instance. /// /// The X509Certificate instance to duplicate. /// A Certificate instance. /// is a null reference (Nothing in Visual Basic). public static Certificate CreateFromX509Certificate(X509Certificate certificate) { if (certificate == null) throw new ArgumentNullException(); return Certificate.CreateFromCerFile(certificate.GetRawCertData()); } /// /// Creates a new instance of the class by reading a certificate from a PEM encoded file. /// /// The path to the PEM file. /// A Certificate instance. /// This implementation only reads certificates from PEM files. It does not read the private key from the certificate file, if one is present. /// if a null reference (Nothing in Visual Basic). /// An error occurs while reading from the file. /// An error occurs while reading the certificate from the PEM blob. public static Certificate CreateFromPemFile(string filename) { return CreateFromPemFile(CertificateStore.GetFileContents(filename)); } /// /// Creates a new instance of the class by reading a certificate from a PEM encoded file. /// /// The contents of the PEM file. /// A Certificate instance. /// This implementation only reads certificates from PEM files. It does not read the private key from the certificate file, if one is present. /// if a null reference (Nothing in Visual Basic). /// An error occurs while reading the certificate from the PEM blob. public static Certificate CreateFromPemFile(byte[] file) { if (file == null) throw new ArgumentNullException(); string pemFile = Encoding.ASCII.GetString(file); string cert = GetCertString(pemFile, "CERTIFICATE"); if (cert == null) { cert = GetCertString(pemFile, "X509 CERTIFICATE"); if (cert == null) throw new CertificateException("The specified PEM file does not contain a certificate."); } byte[] certBuffer = Convert.FromBase64String(cert); return Certificate.CreateFromCerFile(certBuffer); } /// /// Extracts an encoded certificate from a PEM file. /// /// The PEM encoded certificate file. /// The delimiter to search for. /// The Base64 encoded certificate if successfull or a null reference otherwise. private static string GetCertString(string cert, string delimiter) { int start = cert.IndexOf("-----BEGIN " + delimiter + "-----"); if (start < 0) return null; int end = cert.IndexOf("-----END " + delimiter + "-----", start); if (end < 0) return null; int sl = delimiter.Length + 16; int length = end - (start + sl); return cert.Substring(start + sl, length); } /// /// Duplicates a given certificate. /// /// The certificate to duplicate. /// is a null reference (Nothing in Visual Basic). public Certificate(Certificate certificate) { if (certificate == null) throw new ArgumentNullException(); InitCertificate(certificate.Handle, true, null); } /// /// Initializes a new instance from a handle. /// /// The handle from which to initialize the state of the new instance. /// is invalid. public Certificate(IntPtr handle) : this(handle, false) {} /// /// Initializes a new instance from a handle. /// /// The handle from which to initialize the state of the new instance. /// true if the handle should be duplicated, false otherwise. /// is invalid. public Certificate(IntPtr handle, bool duplicate) { InitCertificate(handle, duplicate, null); } /// /// Initializes this instance from a handle. /// /// The handle from which to initialize the state of the new instance. /// true if the handle should be duplicated, false otherwise. /// The store that owns the certificate. /// is invalid. private void InitCertificate(IntPtr handle, bool duplicate, CertificateStore store) { if (handle == IntPtr.Zero) throw new ArgumentException("Invalid certificate handle!"); if (duplicate) m_Handle = SspiProvider.CertDuplicateCertificateContext(handle); else m_Handle = handle; m_Context = (CertificateContext)Marshal.PtrToStructure(handle, typeof(CertificateContext)); m_CertInfo = (CertificateInfo)Marshal.PtrToStructure(m_Context.pCertInfo, typeof(CertificateInfo)); if (store == null) { m_Store = null; } else { m_Store = store; } } /// /// Returns the structure associated with the certificate. /// /// A CertificateInfo instance. internal CertificateInfo GetCertificateInfo() { return m_CertInfo; } /// /// Initializes this instance from a handle. /// /// The handle from which to initialize the state of the new instance. /// The that contains the certificate. internal Certificate(IntPtr handle, CertificateStore store) { InitCertificate(handle, false, store); } /// /// Creates a copy of this . /// /// The Certificate this method creates, cast as an object. public object Clone() { return new Certificate(SspiProvider.CertDuplicateCertificateContext(Handle)); } /// /// Disposes of the certificate and frees unmanaged resources. /// ~Certificate() { if (Handle != IntPtr.Zero) { SspiProvider.CertFreeCertificateContext(Handle); m_Handle = IntPtr.Zero; } } /// /// Returns a string representation of the current object. /// /// A string representation of the current Certificate object. public override string ToString() { return this.GetType().FullName; } /// /// Returns a string representation of the current X509Certificate object, with extra information, if specified. /// /// true to produce the verbose form of the string representation; otherwise, false. /// A string representation of the current X509Certificate object. public string ToString(bool verbose) { if (verbose) { return "CERTIFICATE:\r\n" + " Format: X509\r\n" + " Name: " + GetName() + "\r\n" + " Issuing CA: " + GetIssuerName() + "\r\n" + " Key Algorithm: " + GetKeyAlgorithm() + "\r\n" + " Serial Number: " + GetSerialNumberString() + "\r\n" + " Key Alogrithm Parameters: " + GetKeyAlgorithmParametersString() + "\r\n" + " Public Key: " + GetPublicKeyString(); } else { return ToString(); } } /// /// Returns the hash value for the X.509v3 certificate as an array of bytes. /// /// The hash value for the X.509 certificate. /// An error occurs while retrieving the hash of the certificate. public byte[] GetCertHash() { return GetCertHash(HashType.Default); } /// /// Returns the hash value for the X.509v3 certificate as an array of bytes. /// /// One of the values. /// The hash value for the X.509 certificate. /// An error occurs while retrieving the hash of the certificate. public byte[] GetCertHash(HashType type) { byte[] ret; IntPtr hash = Marshal.AllocHGlobal(256); try { int size = 256; if (SspiProvider.CertGetCertificateContextProperty(Handle, (int)type, hash, ref size) == 0 || size <= 0 || size > 256) throw new CertificateException("An error occurs while retrieving the hash of the certificate."); ret = new byte[size]; Marshal.Copy(hash, ret, 0, size); } catch (Exception e) { throw e; } finally { Marshal.FreeHGlobal(hash); } return ret; } /// /// Returns the hash value for the X.509v3 certificate as a hexadecimal string. /// /// The hexadecimal string representation of the X.509 certificate hash value. /// An error occurs while retrieving the hash of the certificate. public string GetCertHashString() { return GetCertHashString(HashType.Default); } /// /// Returns the hash value for the X.509v3 certificate as a hexadecimal string. /// /// One of the values. /// The hexadecimal string representation of the X.509 certificate hash value. /// An error occurs while retrieving the hash of the certificate. public string GetCertHashString(HashType type) { return BytesToString(GetCertHash(type)); } /// /// Converts an array of bytes to its hexadecimal string representation. /// /// The bytes to convert. /// The hexadecimal representation of the byte array. private string BytesToString(byte[] buffer) { string ret = ""; for(int i = 0; i < buffer.Length; i++) { ret += buffer[i].ToString("X2"); } return ret; } /// /// Returns the effective date of this X.509v3 certificate. /// /// The effective date for this X.509 certificate. /// The effective date is the date after which the X.509 certificate is considered valid. public DateTime GetEffectiveDate() { return DateTime.FromFileTime(m_CertInfo.NotBefore); } /// /// Returns the expiration date of this X.509v3 certificate. /// /// The expiration date for this X.509 certificate. /// The expiration date is the date after which the X.509 certificate is no longer considered valid. public DateTime GetExpirationDate() { return DateTime.FromFileTime(m_CertInfo.NotAfter); } /// /// Returns the name of the certification authority that issued the X.509v3 certificate. /// /// The name of the certification authority that issued the X.509 certificate. // Thanks go out to Hernan de Lahitte for fixing a bug in this method. public string GetIssuerName() { int length = SspiProvider.CertGetNameString(Handle, SecurityConstants.CERT_NAME_SIMPLE_DISPLAY_TYPE, SecurityConstants.CERT_NAME_DISABLE_IE4_UTF8_FLAG | SecurityConstants.CERT_NAME_ISSUER_FLAG, IntPtr.Zero, IntPtr.Zero, 0); if (length <= 0) throw new CertificateException("An error occurs while requesting the issuer name."); IntPtr name = Marshal.AllocHGlobal(length); SspiProvider.CertGetNameString(Handle, SecurityConstants.CERT_NAME_SIMPLE_DISPLAY_TYPE, SecurityConstants.CERT_NAME_DISABLE_IE4_UTF8_FLAG | SecurityConstants.CERT_NAME_ISSUER_FLAG, IntPtr.Zero, name, length); string ret = Marshal.PtrToStringAnsi(name); Marshal.FreeHGlobal(name); return ret; } /// /// Returns the key algorithm information for this X.509v3 certificate. /// /// The key algorithm information for this X.509 certificate as a string. public string GetKeyAlgorithm() { return Marshal.PtrToStringAnsi(m_CertInfo.SignatureAlgorithmpszObjId); } /// /// Returns the key algorithm parameters for the X.509v3 certificate. /// /// The key algorithm parameters for the X.509 certificate as an array of bytes. public byte[] GetKeyAlgorithmParameters() { byte[] ret = new byte[m_CertInfo.SignatureAlgorithmParameterscbData]; if (ret.Length > 0) Marshal.Copy(m_CertInfo.SignatureAlgorithmParameterspbData, ret, 0, ret.Length); return ret; } /// /// Returns the key algorithm parameters for the X.509v3 certificate. /// /// The key algorithm parameters for the X.509 certificate as a hexadecimal string. public string GetKeyAlgorithmParametersString() { return BytesToString(GetKeyAlgorithmParameters()); } /// /// Returns the public key for the X.509v3 certificate. /// /// The public key for the X.509 certificate as an array of bytes. public byte[] GetPublicKey() { byte[] key = new byte[m_CertInfo.SubjectPublicKeyInfoPublicKeycbData]; Marshal.Copy(m_CertInfo.SubjectPublicKeyInfoPublicKeypbData, key, 0, key.Length); return key; } /// /// Returns the public key for the X.509v3 certificate. /// /// The public key for the X.509 certificate as a hexadecimal string. public string GetPublicKeyString() { return BytesToString(GetPublicKey()); } /// /// Returns the raw data for the entire X.509v3 certificate. /// /// A byte array containing the X.509 certificate data. public byte[] GetRawCertData() { byte[] ret = new byte[m_Context.cbCertEncoded]; Marshal.Copy(m_Context.pbCertEncoded, ret, 0, ret.Length); return ret; } /// /// Returns the raw data for the entire X.509v3 certificate. /// /// The X.509 certificate data as a hexadecimal string. public string GetRawCertDataString() { return BytesToString(GetRawCertData()); } /// /// Returns the serial number of the X.509v3 certificate. /// /// The serial number of the X.509 certificate as an array of bytes. public byte[] GetSerialNumber() { byte[] ret = new byte[m_CertInfo.SerialNumbercbData]; if (ret.Length > 0) { Marshal.Copy(m_CertInfo.SerialNumberpbData, ret, 0, ret.Length); Array.Reverse(ret); } return ret; } /// /// Returns the serial number of the X.509v3 certificate. /// /// The serial number of the X.509 certificate as a hexadecimal string. public string GetSerialNumberString() { return BytesToString(GetSerialNumber()); } /// /// Returns the length of the public key of the X.509v3 certificate. /// /// Returns the length of the public key in bits. If unable to determine the key's length, returns zero. public int GetPublicKeyLength() { return SspiProvider.CertGetPublicKeyLength(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(m_Context.pCertInfo.ToInt64() + 56)); } /// /// Returns a list of attributes of the X.509v3 certificate. /// /// A StringDictionary that contains the attributes. /// An error occurs while retrieving the attributes. public DistinguishedName GetDistinguishedName() { return new DistinguishedName(m_CertInfo.SubjectpbData, m_CertInfo.SubjectcbData); } /// /// Returns a list of extensions of the X.509v3 certificate. /// /// An array of Extension instances. public Extension[] GetExtensions() { Extension[] ret = new Extension[m_CertInfo.cExtension]; int extSize = 8 + IntPtr.Size * 2; IntPtr ptr = m_CertInfo.rgExtension; Type cest = typeof(CERT_EXTENSION); CERT_EXTENSION ce; for(int i = 0; i < m_CertInfo.cExtension; i++) { ce = (CERT_EXTENSION)Marshal.PtrToStructure(ptr, cest); ret[i] = new Extension(Marshal.PtrToStringAnsi(ce.pszObjId), ce.fCritical != 0, new byte[ce.ValuecbData]); Marshal.Copy(ce.ValuepbData, ret[i].EncodedValue, 0, ce.ValuecbData); ptr = new IntPtr(ptr.ToInt64() + extSize); } return ret; } /// /// Searches for a certificate extension. /// /// The extension to search for. /// An instance of the class -or- a null reference (Nothing in Visual Basic) if the specified extension could not be found. /// is a null reference (Nothing in Visual Basic). public Extension FindExtension(string oid) { if (oid == null) throw new ArgumentNullException(); IntPtr ret = SspiProvider.CertFindExtension(oid, m_CertInfo.cExtension, m_CertInfo.rgExtension); if (ret == IntPtr.Zero) { return null; } else { CERT_EXTENSION ce = (CERT_EXTENSION)Marshal.PtrToStructure(ret, typeof(CERT_EXTENSION)); Extension ext = new Extension(Marshal.PtrToStringAnsi(ce.pszObjId), ce.fCritical != 0, new byte[ce.ValuecbData]); Marshal.Copy(ce.ValuepbData, ext.EncodedValue, 0, ce.ValuecbData); return ext; } } /// /// Decodes the specified extension and returns an object of the specified type that is instantiated with the decoded bytes. /// /// The certificate extension to decode. /// One of the predefined constants specified in the Win32 CryptoAPI. Refer to the documentation of the CryptDecodeObject function for more information. /// A instance. See remarks. /// An object of the type . ///

/// The specified type should have a public constructor that takes an IntPtr and an int as parameters [in that order]. /// The IntPtr is a pointer to the decoded buffer and the int contains the number of decoded bytes. /// The type should not keep the IntPtr reference after construction of an instance, because the memory is freed when the DecodeExtension method returns. ///

/// One of the parameters is a null reference (Nothing in Visual Basic). /// An error occurs while decoding the certificate extension. public static object DecodeExtension(Extension extension, int oid, Type returnType) { return DecodeExtension(extension, new IntPtr(oid), returnType); } /// /// Decodes the specified extension and returns an object of the specified type that is instantiated with the decoded bytes. /// /// The certificate extension to decode. /// The Object Identifier of the structure. Refer to the documentation of the CryptDecodeObject function for more information. /// A instance. See remarks. /// An object of the type . ///

/// The specified type should have a public constructor that takes an IntPtr and an int as parameters [in that order]. /// The IntPtr is a pointer to the decoded buffer and the int contains the number of decoded bytes. /// The type should not keep the IntPtr reference after construction of an instance, because the memory is freed when the DecodeExtension method returns. ///

/// One of the parameters is a null reference (Nothing in Visual Basic). /// An error occurs while decoding the certificate extension. public static object DecodeExtension(Extension extension, string oid, Type returnType) { if (oid == null) throw new ArgumentNullException("oid"); IntPtr buffer = Marshal.StringToHGlobalAnsi(oid); try { return DecodeExtension(extension, buffer, returnType); } finally { Marshal.FreeHGlobal(buffer); } } /// /// Decodes the specified extension and returns an object of the specified type that is instantiated with the decoded bytes. /// /// The certificate extension to decode. /// The Object Identifier of the structure. /// A instance. See remarks. /// An object of the type . ///

/// The specified type should have a public constructor that takes an IntPtr and an int as parameters [in that order]. /// The IntPtr is a pointer to the decoded buffer and the int contains the number of decoded bytes. /// The type should not keep the IntPtr reference after construction of an instance, because the memory is freed when the DecodeExtension method returns. ///

/// One of the parameters is a null reference (Nothing in Visual Basic). /// An error occurs while decoding the certificate extension. protected static object DecodeExtension(Extension extension, IntPtr oid, Type returnType) { if (extension.EncodedValue == null || returnType == null) throw new ArgumentNullException(); int size = 0; if (SspiProvider.CryptDecodeObject(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, oid, extension.EncodedValue, extension.EncodedValue.Length, 0, IntPtr.Zero, ref size) == 0) throw new CertificateException("Could not decode the extension."); IntPtr buffer = Marshal.AllocHGlobal(size); try { if (SspiProvider.CryptDecodeObject(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, oid, extension.EncodedValue, extension.EncodedValue.Length, 0, buffer, ref size) == 0) throw new CertificateException("Could not decode the extension."); return Activator.CreateInstance(returnType, new object[] {buffer, size}); } catch (CertificateException ce) { throw ce; } catch (Exception e) { throw new CertificateException("Unable to instantiate the specified object type.", e); } finally { Marshal.FreeHGlobal(buffer); } } /// /// Returns the name of the current principal. /// /// The name of the current principal. /// The certificate does not have a name attribute. // Thanks go out to Jonni Faiga for notifying us about a bug in this method. public string GetName() { int size = 0; SspiProvider.CryptDecodeObject(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(SecurityConstants.X509_UNICODE_NAME), m_CertInfo.SubjectpbData, m_CertInfo.SubjectcbData, 0, IntPtr.Zero, ref size); if (size <= 0) throw new CertificateException("Unable to decode the name of the certificate."); IntPtr buffer = IntPtr.Zero; string ret = null; try { buffer = Marshal.AllocHGlobal(size); if (SspiProvider.CryptDecodeObject(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, new IntPtr(SecurityConstants.X509_UNICODE_NAME), m_CertInfo.SubjectpbData, m_CertInfo.SubjectcbData, 0, buffer, ref size) == 0) throw new CertificateException("Unable to decode the name of the certificate."); IntPtr attPointer = SspiProvider.CertFindRDNAttr(SecurityConstants.szOID_COMMON_NAME, buffer); if (attPointer == IntPtr.Zero) attPointer = SspiProvider.CertFindRDNAttr(SecurityConstants.szOID_RSA_unstructName, buffer); if (attPointer == IntPtr.Zero) attPointer = SspiProvider.CertFindRDNAttr(SecurityConstants.szOID_ORGANIZATION_NAME, buffer); if (attPointer != IntPtr.Zero) { RdnAttribute att = (RdnAttribute)Marshal.PtrToStructure(attPointer, typeof(RdnAttribute)); ret = Marshal.PtrToStringUni(att.pbData, att.cbData / 2); } } catch (CertificateException ce) { throw ce; } catch (Exception e) { throw new CertificateException("Could not get certificate attributes.", e); } finally { if (buffer != IntPtr.Zero) Marshal.FreeHGlobal(buffer); } if (ret == null) throw new CertificateException("Certificate does not have a name attribute."); else return ret; } /// /// Returns a list of intended key usages of the X.509v3 certificate. /// /// An integer that contains a list of intended key usages. /// Use the bitwise And operator to check whether a specific key usage is set. public int GetIntendedKeyUsage() { // returns one or more of the KeyUsage values IntPtr buffer = Marshal.AllocHGlobal(4); SspiProvider.CertGetIntendedKeyUsage(SecurityConstants.PKCS_7_ASN_ENCODING | SecurityConstants.X509_ASN_ENCODING, m_Context.pCertInfo, buffer, 4); byte[] mb = new byte[4]; Marshal.Copy(buffer, mb, 0, 4); Marshal.FreeHGlobal(buffer); return BitConverter.ToInt32(mb, 0); } /// /// Returns a list of enhanced key usages of the X.509v3 certificate. /// /// A StringCollection that contains a list of the enhanced key usages. /// An error occurs while retrieving the enhanced key usages. public StringCollection GetEnhancedKeyUsage() { StringCollection ret = new StringCollection(); int size = 0; SspiProvider.CertGetEnhancedKeyUsage(Handle, 0, IntPtr.Zero, ref size); if (size <= 0) return ret; IntPtr buffer = Marshal.AllocHGlobal(size); try { if (SspiProvider.CertGetEnhancedKeyUsage(Handle, 0, buffer, ref size) == 0) throw new CertificateException("Could not obtain the enhanced key usage."); TrustListUsage cu = (TrustListUsage)Marshal.PtrToStructure(buffer, typeof(TrustListUsage)); for(int i = 0; i < cu.cUsageIdentifier; i++) { IntPtr ip = Marshal.ReadIntPtr(cu.rgpszUsageIdentifier, i * IntPtr.Size); try { ret.Add(Marshal.PtrToStringAnsi(ip)); } catch {} } return ret; } finally { Marshal.FreeHGlobal(buffer); } } /// /// Returns a where the leaf certificate corresponds to this . /// /// The CertificateChain corresponding to this Certificate. /// An error occurs while building the certificate chain. public CertificateChain GetCertificateChain() { if (m_Chain == null) m_Chain = new CertificateChain(this, Store); return m_Chain; } /// /// Checks whether the has a private key associated with it. /// /// true if the certificate has a private key associated with it, false otherwise. public bool HasPrivateKey() { int handle = 0; int keyspec = 0; int free = 0; bool ret = false; if (SspiProvider.CryptAcquireCertificatePrivateKey(Handle, SecurityConstants.CRYPT_ACQUIRE_COMPARE_KEY_FLAG | SecurityConstants.CRYPT_ACQUIRE_SILENT_FLAG, IntPtr.Zero, ref handle, ref keyspec, ref free) != 0) { ret = true; } if (free != 0) SspiProvider.CryptReleaseContext(handle, 0); return ret; } /// /// Returns the name of the format of this X.509v3 certificate. /// /// The format of this X.509 certificate. /// The format X.509 is always returned in this implementation. public string GetFormat() { return "X509"; } /// /// Returns the hash code for the X.509v3 certificate as an integer. /// /// The hash code for the X.509 certificate as an integer. /// If the X.509 certificate hash is an array of more than 4 bytes, any byte after the fourth byte is not seen in this integer representation. public override int GetHashCode() { byte[] hash = GetCertHash(); byte[] buffer = new byte[4]; if (hash.Length < buffer.Length) Array.Copy(hash, 0, buffer, 0, hash.Length); else Array.Copy(hash, 0, buffer, 0, buffer.Length); return BitConverter.ToInt32(buffer, 0); } /// /// Compares two objects for equality. /// /// A Certificate object to compare to the current object. /// true if the current Certificate object is equal to the object specified by ; otherwise, false. public virtual bool Equals(Certificate other) { if (other == null) return false; return SspiProvider.CertCompareCertificate(SecurityConstants.X509_ASN_ENCODING | SecurityConstants.PKCS_7_ASN_ENCODING, m_Context.pCertInfo, other.m_Context.pCertInfo) != 0; } /// /// Compares a object and an object for equality. /// /// An X509Certificate object to compare to the current object. /// true if the current Certificate object is equal to the object specified by ; otherwise, false. public virtual bool Equals(X509Certificate other) { if (other == null) return false; return other.GetCertHashString() == this.GetCertHashString(); } /// /// Compares two objects for equality. /// /// A Certificate object to compare to the current object. /// true if the current Certificate object is equal to the object specified by ; otherwise, false. public override bool Equals(object other) { try { return Equals((Certificate)other); } catch { try { return Equals((X509Certificate)other); } catch { return false; } } } /// /// Returns an array of usages consisting of the intersection of the valid usages for all certificates in an array of certificates. /// /// Array of certificates to be checked for valid usage. /// An array of valid usages -or- a null reference (Nothing in Visual Basic) if all certificates support all usages. /// is a null reference (Nothing in Visual Basic). /// The array of certificates contains at least one invalid entry. /// An error occurs while determining the intersection of valid usages. public static string[] GetValidUsages(Certificate[] certificates) { if (certificates == null) throw new ArgumentNullException(); IntPtr buffer = IntPtr.Zero; IntPtr certs = Marshal.AllocHGlobal(certificates.Length * IntPtr.Size); try { for(int i = 0; i < certificates.Length; i++) { if (certificates[i] == null) throw new ArgumentException(); Marshal.WriteIntPtr(certs, i * IntPtr.Size, certificates[i].Handle); } int count = 0, bytes = 0; if (SspiProvider.CertGetValidUsages(certificates.Length, certs, ref count, buffer, ref bytes) == 0) throw new CertificateException("Unable to get the valid usages."); if (count == -1) return null; // every usage is valid buffer = Marshal.AllocHGlobal(bytes); if (SspiProvider.CertGetValidUsages(certificates.Length, certs, ref count, buffer, ref bytes) == 0) throw new CertificateException("Unable to get the valid usages."); string[] ret = new string[count]; for(int i = 0; i < count; i++) { ret[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(buffer, i * IntPtr.Size)); } return ret; } finally { Marshal.FreeHGlobal(certs); if (buffer != IntPtr.Zero) Marshal.FreeHGlobal(buffer); } } /// /// Saves the as a PFX encoded file. /// /// The filename of the new PFX file. /// The password to use when encrypting the private keys. /// true if the private keys should be exported [if possible], false otherwise. /// true if the parent certificates should be exported too [if possible], false otherwise. /// If the specified file already exists, the method will throw an exception. /// or is a null reference (Nothing in Visual Basic). /// An error occurs while writing the data to the file. /// An error occurs while exporting the certificate store
-or-

an error occurs while building the certificate chain

-or-

an error occurs while creating the store

-or-

an error occurs while adding the certificate to the store.
public void ToPfxFile(string filename, string password, bool withPrivateKeys, bool withParents) { CreateCertStore(withParents).ToPfxFile(filename, password, withPrivateKeys); } /// /// Saves the as a PFX encoded buffer. /// /// The password to use when encrypting the private keys. /// true if the private keys should be exported [if possible], false otherwise. /// true if the parent certificates should be exported too [if possible], false otherwise. /// An array of bytes that represents the PFX encoded certificate. /// is a null reference (Nothing in Visual Basic). /// An error occurs while exporting the certificate store
-or-

an error occurs while building the certificate chain

-or-

an error occurs while creating the store

-or-

an error occurs while adding the certificate to the store.
public byte[] ToPfxBuffer(string password, bool withPrivateKeys, bool withParents) { return CreateCertStore(withParents).ToPfxBuffer(password, withPrivateKeys); } /// /// Creates an in memory with this in it. /// /// true if the parent certificates should be included [if possible], false otherwise. /// A CertificateStore instance. private CertificateStore CreateCertStore(bool withParents) { CertificateStore store = new CertificateStore(); if (withParents) { Certificate[] c = this.GetCertificateChain().GetCertificates(); for(int i = 0; i < c.Length; i++) { store.AddCertificate(c[i]); } } else { store.AddCertificate(this); } return store; } /// /// Saves the as an encoded file. /// /// The file where to store the certificate. /// is a null reference (Nothing in Visual Basic). /// An error occurs while writing the data. /// If the specified file already exists, this method will throw an exception. public void ToCerFile(string filename) { SaveToFile(GetCertificateBuffer(), filename); } /// /// Saves the as an encoded buffer. /// /// An array of bytes that represents the encoded certificate. public byte[] ToCerBuffer() { return GetCertificateBuffer(); } /// /// Returns a buffer with the encoded certificate. /// /// An array of bytes. private byte[] GetCertificateBuffer() { byte[] buffer = new byte[m_Context.cbCertEncoded]; Marshal.Copy(m_Context.pbCertEncoded, buffer, 0, m_Context.cbCertEncoded); return buffer; } /// /// Writes a buffer with data to a file. /// /// The buffer to write. /// The filename to write the data to. /// is a null reference (Nothing in Visual Basic). /// An error occurs while writing the data. private void SaveToFile(byte[] buffer, string filename) { if (filename == null) throw new ArgumentNullException(); try { FileStream fs = File.Open(filename, FileMode.CreateNew, FileAccess.Write, FileShare.None); fs.Write(buffer, 0, buffer.Length); fs.Close(); } catch (Exception e) { throw new IOException("Could not write data to file.", e); } } /// /// Returns an X509Certificate object that corresponds to this . /// /// An X509Certificate instance. public X509Certificate ToX509() { return new X509Certificate(SspiProvider.CertDuplicateCertificateContext(Handle)); } /// /// Gets the handle of the Certificate. /// /// An IntPtr that represents the handle of the certificate. /// The handle returned by this property should not be closed. If the handle is closed by an external actor, the methods of the Certificate object may fail in undocumented ways [for instance, an Access Violation may occur]. public IntPtr Handle { get { return m_Handle; } } /// /// Gets the handle of the associated , if any. /// /// A CertificateStore instance -or- a null reference (Nothing in Visual Basic) is no store is associated with this certificate. internal CertificateStore Store { get { return m_Store; } set { if (m_Store != value) m_Chain = null; // force a recreation of the certificate chain, if necessary m_Store = value; } } /// /// Gets a value indicating whether the certificate is current, that is, has not expired. /// /// true if the certificate is current; otherwise, false. public bool IsCurrent { get { return SspiProvider.CertVerifyTimeValidity(IntPtr.Zero, m_Context.pCertInfo) == 0; } } /// /// Gets a value indicating whether the certificate can be used for encrypting and decrypting messages. /// /// true if the certificate can be used for data encryption; otherwise, false. public bool SupportsDataEncryption { get { return this.GetIntendedKeyUsage()==0 || ((this.GetIntendedKeyUsage() & SecurityConstants.CERT_DATA_ENCIPHERMENT_KEY_USAGE) != 0); } } /// /// Gets a value indicating whether the certificate can be used for digital signatures. /// /// true if the certificate can be used for digital signature; otherwise, false. public bool SupportsDigitalSignature { get { return this.GetIntendedKeyUsage()==0 || ((this.GetIntendedKeyUsage() & SecurityConstants.CERT_DIGITAL_SIGNATURE_KEY_USAGE) != 0); } } /// /// Creates a new Certificate from a string representation. /// /// A Base64-encoded representation of the certificate. /// A new Certificate. /// if a null reference (Nothing in Visual Basic). /// An error occurs while loading the specified certificate. /// The length of is less than 4 -or- the length of is not an even multiple of 4. public static Certificate CreateFromBase64String(string rawString) { if (rawString == null) throw new ArgumentNullException("rawString"); return Certificate.CreateFromCerFile(Convert.FromBase64String(rawString)); } /// /// Returns a Base64-encoded representation of the certificate. /// /// A Base64-encoded representation of the certificate. public string ToBase64String() { byte[] cert = this.ToCerBuffer(); return Convert.ToBase64String(cert, 0, cert.Length); } /// /// Converts the to a PEM encoded buffer. /// /// An array of bytes that represents the PEM encoded certificate. public byte[] ToPemBuffer() { return Encoding.ASCII.GetBytes("-----BEGIN CERTIFICATE-----\r\n" + ToBase64String() + "\r\n-----END CERTIFICATE-----\r\n"); } /// /// Gets the unique identifier associated with the key. /// /// A byte array containing the unique identifier associated with the key. public byte[] GetKeyIdentifier() { int size = 0; SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_IDENTIFIER_PROP_ID, null, ref size); byte[] ret = new byte[size]; SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_IDENTIFIER_PROP_ID, ret, ref size); return ret; } /// /// Gets the private key for the certificate. /// /// A System.Security.Cryptography.RSA containing the private key for the certificate. /// An error occurs while retrieving the RSA instance associated with the certificate. // Thanks go out to Hernan de Lahitte for fixing a bug in this method public RSA PrivateKey { get { //int ku = this.GetIntendedKeyUsage(); //if (!((ku & (int)KeyUsage.DataEncipherment) != 0 || (ku & (int)KeyUsage.KeyEncipherment) != 0)) // throw new CertificateException("This certificate is not suited for data encipherment or key encipherment."); int flags = 0, provider = 0, keySpec = 0, mustFree = 0; if (!Environment.UserInteractive) flags = SecurityConstants.CRYPT_ACQUIRE_SILENT_FLAG; if (SspiProvider.CryptAcquireCertificatePrivateKey(this.Handle, flags, IntPtr.Zero, ref provider, ref keySpec, ref mustFree) == 0) throw new CertificateException("Could not acquire private key."); if (mustFree != 0) SspiProvider.CryptReleaseContext(provider, 0); // we don't need it in the rest of our code if (Environment.UserInteractive) { flags = 0; } else { flags = SecurityConstants.CRYPT_FIND_SILENT_KEYSET_FLAG; } int length = 0; if (SspiProvider.CryptFindCertificateKeyProvInfo(this.Handle, flags, IntPtr.Zero) == 0 || SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_PROV_INFO_PROP_ID, null, ref length) == 0) throw new CertificateException("Could not query the associated private key."); IntPtr buffer = Marshal.AllocHGlobal(length); RSA privateKey = null; try { SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_PROV_INFO_PROP_ID, buffer, ref length); CRYPT_KEY_PROV_INFO provInfo = (CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(buffer, typeof(CRYPT_KEY_PROV_INFO)); CspParameters cspParams = new CspParameters(); cspParams.KeyContainerName = provInfo.pwszContainerName; cspParams.ProviderName = null; //provInfo.pwszProvName; cspParams.ProviderType = provInfo.dwProvType; cspParams.KeyNumber = provInfo.dwKeySpec; if((provInfo.dwFlags & 32) != 0) cspParams.Flags = CspProviderFlags.UseMachineKeyStore; privateKey = new RSACryptoServiceProvider(cspParams); } catch (CertificateException e) { throw e; } catch (Exception e) { throw new CertificateException("An error occurs while accessing the certificate's private key.", e); } finally { Marshal.FreeHGlobal(buffer); } return privateKey; } } /// /// Gets the public key derived from the certificate's data. This key cannot be used to sign or decrypt data. /// /// A System.Security.Cryptography.RSA that contains the public key derived from the certificate's data. /// An error occurs while retrieving the RSA instance associated with the certificate. public RSA PublicKey { get{ //int ku = this.GetIntendedKeyUsage(); //if (!((ku & (int)KeyUsage.DataEncipherment) != 0 || (ku & (int)KeyUsage.KeyEncipherment) != 0)) // throw new CertificateException("This certificate is not suited for data encipherment or key encipherment."); IntPtr buffer = IntPtr.Zero; int provider = CAPIProvider.ContainerHandle, key = 0; RSA publicKey = null; try { /* int flags = 0; if (!Environment.UserInteractive && Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 5) flags = SecurityConstants.CRYPT_SILENT;*/ /* if (SspiProvider.CryptAcquireContext(ref provider, IntPtr.Zero, null, SecurityConstants.PROV_RSA_FULL, flags) == 0) { if (SspiProvider.CryptAcquireContext(ref provider, IntPtr.Zero, null, SecurityConstants.PROV_RSA_FULL, flags | SecurityConstants.CRYPT_NEWKEYSET) == 0) { throw new CertificateException("Could not acquire crypto context."); } }*/ CERT_PUBLIC_KEY_INFO pki = new CERT_PUBLIC_KEY_INFO(m_CertInfo); int size = 0; if (SspiProvider.CryptImportPublicKeyInfoEx(provider, SecurityConstants.X509_ASN_ENCODING | SecurityConstants.PKCS_7_ASN_ENCODING, ref pki, 0, 0, IntPtr.Zero, ref key) == 0) throw new CertificateException("Could not obtain the handle of the public key."); if (SspiProvider.CryptExportKey(key, 0, SecurityConstants.PUBLICKEYBLOB, 0, IntPtr.Zero, ref size) == 0) throw new CertificateException("Could not get the size of the key."); buffer = Marshal.AllocHGlobal(size); if (SspiProvider.CryptExportKey(key, 0, SecurityConstants.PUBLICKEYBLOB, 0, buffer, ref size) == 0) throw new CertificateException("Could not export the key."); PUBLIC_KEY_BLOB pkb = (PUBLIC_KEY_BLOB)Marshal.PtrToStructure(buffer, typeof(PUBLIC_KEY_BLOB)); if (pkb.magic != 0x31415352) // make sure we're dealing with an RSA public key throw new CertificateException("This is not an RSA certificate."); // initialize the RSAParameters structure RSAParameters rsap = new RSAParameters(); rsap.Exponent = ConvertIntToByteArray(pkb.pubexp); IntPtr modulus = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(PUBLIC_KEY_BLOB))); rsap.Modulus = new byte[pkb.bitlen / 8]; Marshal.Copy(modulus, rsap.Modulus, 0, rsap.Modulus.Length); Array.Reverse(rsap.Modulus); // key is stored in reversed order // initialize the CspParameters structure CspParameters cp = new CspParameters(); cp.Flags = CspProviderFlags.UseMachineKeyStore; // create the RSA object publicKey = new RSACryptoServiceProvider(cp); publicKey.ImportParameters(rsap); } finally { if (key != 0) SspiProvider.CryptDestroyKey(key); /* if (provider != 0) SspiProvider.CryptReleaseContext(provider, 0);*/ if (buffer != IntPtr.Zero) Marshal.FreeHGlobal(buffer); } return publicKey; } } /* WARNING: following code doesn't always work [for instance during the SSL/TLS handshake] and is also much slower compared to the above method. /// /// Returns a CspParameters instance that contains the information of the public key of this certificate. /// /// A instance. // Thanks go out to Hernan de Lahitte for implementing this method. private CspParameters GetCspParameters() { int length = 0; int flags = (Environment.UserInteractive) ? 0 : SecurityConstants.CRYPT_FIND_SILENT_KEYSET_FLAG; //SspiProvider.CryptFindCertificateKeyProvInfo(this.Handle, flags, IntPtr.Zero) == 0 || if (SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_PROV_INFO_PROP_ID, null, ref length) == 0) throw new CertificateException("Could not query the associated public key."); IntPtr buffer = Marshal.AllocHGlobal(length); try { SspiProvider.CertGetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_PROV_INFO_PROP_ID, buffer, ref length); CRYPT_KEY_PROV_INFO provInfo = (CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(buffer, typeof(CRYPT_KEY_PROV_INFO)); CspParameters cspParams = new CspParameters(); cspParams.KeyContainerName = provInfo.pwszContainerName; cspParams.ProviderName = provInfo.pwszProvName; cspParams.ProviderType = provInfo.dwProvType; cspParams.KeyNumber = provInfo.dwKeySpec; if((provInfo.dwFlags & 32) != 0) cspParams.Flags = CspProviderFlags.UseMachineKeyStore; return cspParams; } finally { Marshal.FreeHGlobal(buffer); } }*/ /// /// Converts an integer to a series of bytes. /// /// The integer to convert. /// An array of bytes that represents the integer. /// This method returns the minimum required number of bytes to represent a specific integer number. internal byte[] ConvertIntToByteArray(int dwInput) { int tempByte, size = 0; byte[] ret, temp = new byte[8]; if (dwInput == 0) return new byte[1]; tempByte = dwInput; // fill the temp array while (tempByte > 0) { temp[size] = (byte)(tempByte & 255); tempByte = tempByte >> 8; size++; } // copy the bytes from the temp buffer to the ret buffer in big endian order ret = new byte[size]; if (BitConverter.IsLittleEndian) { for(int i = 0; i < size; i++) ret[i] = temp[size - i - 1]; } else { for(int i = 0; i < size; i++) ret[i] = temp[i]; } return ret; } /// /// Associates the certificate with a private key from a PVK file. /// /// The path to the PVK file to open. /// The password used to encrypt the private key. /// ///

The can be a null reference (Nothing in Visual Basic) if the private key is not encrypted.

///

The private key will not be exportable.

///
/// The PVK file is encrypted and is a null reference (Nothing in Visual Basic). /// The specified file could not be found. /// An error occurs while importing the private key. public void AssociateWithPrivateKey(string pvkFile, string password) { AssociateWithPrivateKey(pvkFile, password, false); } /// /// Associates the certificate with a private key from a PVK file. /// /// The path to the PVK file to open. /// The password used to encrypt the private key. /// true if the private key should be marked exportable, false otherwise. /// The can be a null reference (Nothing in Visual Basic) if the private key is not encrypted. /// The PVK file is encrypted and is a null reference (Nothing in Visual Basic). /// The specified file could not be found. /// An error occurs while importing the private key. public void AssociateWithPrivateKey(string pvkFile, string password, bool exportable) { try { if (!File.Exists(pvkFile)) throw new FileNotFoundException("The PVK file could not be found."); } catch (FileNotFoundException fnf) { throw fnf; } catch (Exception e) { throw new FileNotFoundException("The PVK file could not be found.", e); } byte[] buffer = new byte[24]; FileStream fs = File.Open(pvkFile, FileMode.Open, FileAccess.Read, FileShare.Read); fs.Read(buffer, 0, buffer.Length); if (BitConverter.ToUInt32(buffer, 0) != 0xB0B5F11E) throw new CertificateException("The specified file is not a valid PVK file."); int keytype = BitConverter.ToInt32(buffer, 8); int isEncrypted = BitConverter.ToInt32(buffer, 12); int saltLength = BitConverter.ToInt32(buffer, 16); int keyLen = BitConverter.ToInt32(buffer, 20); byte[] salt = new byte[saltLength]; byte[] blob = new byte[keyLen]; fs.Read(salt, 0, salt.Length); fs.Read(blob, 0, blob.Length); if (isEncrypted != 0) { // decrypt private key if (password == null) throw new ArgumentNullException(); byte[] pass = Encoding.ASCII.GetBytes(password); byte[] key = new byte[salt.Length + password.Length]; Array.Copy(salt, 0, key, 0, salt.Length); Array.Copy(pass, 0, key, salt.Length, pass.Length); byte[] pkb = TryDecrypt(blob, 8, blob.Length - 8, key, 16); if (pkb == null) { // decryption failed, try with an export key pkb = TryDecrypt(blob, 8, blob.Length - 8, key, 5); if (pkb == null) { throw new CertificateException("The PVK file could not be decrypted. [wrong password?]"); } } Array.Copy(pkb, 0, blob, 8, pkb.Length); Array.Clear(pkb, 0, pkb.Length); Array.Clear(pass, 0, pass.Length); Array.Clear(key, 0, key.Length); } int hKey = 0, flags = 0; if (exportable) flags = SecurityConstants.CRYPT_EXPORTABLE; int provider = CAPIProvider.ContainerHandle; if (SspiProvider.CryptImportKey(provider, blob, blob.Length, 0, flags, ref hKey) == 0) throw new CertificateException("Could not import the private key from the PVK file."); CRYPT_KEY_PROV_INFO kpi = new CRYPT_KEY_PROV_INFO(); kpi.pwszContainerName = SecurityConstants.KEY_CONTAINER; kpi.pwszProvName = null; kpi.dwProvType = SecurityConstants.PROV_RSA_FULL; kpi.dwFlags = 0; kpi.cProvParam = 0; kpi.rgProvParam = IntPtr.Zero; kpi.dwKeySpec = keytype; if (SspiProvider.CertSetCertificateContextProperty(this.Handle, SecurityConstants.CERT_KEY_PROV_INFO_PROP_ID, 0, ref kpi) == 0) throw new CertificateException("Could not associate the private key with the certificate."); SspiProvider.CryptDestroyKey(hKey); Array.Clear(blob, 0, blob.Length); } /// /// Exports the private key of this certificate to a PVK file. /// /// The path to the PVK file to create. /// The password used to encrypt the private key. /// An error occurs while exporting the private key. /// An error occurs while writing the PVK file. public void ExportPrivateKey(string pvkFile, string password) { if (!this.HasPrivateKey()) throw new CertificateException("The certificate does not have an associated private key."); // export key int flags = 0, provider = 0, keySpec = 0, mustFree = 0, privateKey = 0, size = 0; if (!Environment.UserInteractive) flags = SecurityConstants.CRYPT_ACQUIRE_SILENT_FLAG; if (SspiProvider.CryptAcquireCertificatePrivateKey(this.Handle, flags, IntPtr.Zero, ref provider, ref keySpec, ref mustFree) == 0) throw new CertificateException("Could not acquire private key."); if (SspiProvider.CryptGetUserKey(provider, keySpec, ref privateKey) == 0) throw new CertificateException("Could not retrieve a handle of the private key."); if (SspiProvider.CryptExportKey(privateKey, 0, SecurityConstants.PRIVATEKEYBLOB, 0, IntPtr.Zero, ref size) == 0 && Marshal.GetLastWin32Error() != SecurityConstants.ERROR_MORE_DATA) throw new CertificateException("Could not export the private key."); byte[] buffer = new byte[size]; if (SspiProvider.CryptExportKey(privateKey, 0, SecurityConstants.PRIVATEKEYBLOB, 0, buffer, ref size) == 0) throw new CertificateException("Could not export the private key."); if (mustFree != 0) SspiProvider.CryptReleaseContext(provider, 0); // initialize header fields and encrypt the data if necessary uint magic = 0xB0B5F11E; int reserved = 0, encrypted = (password == null ? 0 : 1), saltlen; byte[] salt; if (encrypted == 0) { salt = new byte[0]; } else { salt = new byte[16]; new RNGCryptoServiceProvider().GetBytes(salt); byte[] key = Encoding.ASCII.GetBytes(password); SHA1 sha1 = SHA1.Create(); sha1.TransformBlock(salt, 0, salt.Length, salt, 0); sha1.TransformFinalBlock(key, 0, key.Length); key = new byte[16]; Array.Copy(sha1.Hash, 0, key, 0, key.Length); ICryptoTransform rc4 = RC4.Create().CreateEncryptor(key, null); rc4.TransformBlock(buffer, 8, buffer.Length - 8, buffer, 8); rc4.Dispose(); sha1.Clear(); } saltlen = salt.Length; // write the PVK file FileStream fs = null; try { fs = File.Open(pvkFile, FileMode.Create, FileAccess.Write, FileShare.Read); fs.Write(BitConverter.GetBytes(magic), 0, 4); fs.Write(BitConverter.GetBytes(reserved), 0, 4); fs.Write(BitConverter.GetBytes(keySpec), 0, 4); fs.Write(BitConverter.GetBytes(encrypted), 0, 4); fs.Write(BitConverter.GetBytes(saltlen), 0, 4); fs.Write(BitConverter.GetBytes(size), 0, 4); if (salt.Length > 0) fs.Write(salt, 0, salt.Length); fs.Write(buffer, 0, buffer.Length); } catch (IOException ioe) { throw ioe; } catch (Exception e) { throw new IOException("An error occurs while writing the file.", e); } finally { if (fs != null) fs.Close(); } } // return null is a decryption error occurs /// /// Tries decrypting the PRIVATEKEYBLOB blob. /// /// The buffer to decrypt. /// The starting offset. /// The number of bytes to decrypt. /// The password used to encrypt the PVK file (the salt should be prepended to the password). /// The effective key length in bytes (16 for 128 bit encryption, 5 for 40 bit encryption). /// The decrypted buffer if successfull, or a null reference otherwise. private byte[] TryDecrypt(byte[] buffer, int offset, int length, byte[] password, int keyLen) { byte[] key = new byte[16]; Array.Copy(SHA1.Create().ComputeHash(password, 0, password.Length), 0, key, 0, keyLen); byte[] ret = RC4.Create().CreateDecryptor(key, null).TransformFinalBlock(buffer, offset, length); if (ret[0] != 0x52 || ret[1] != 0x53 || ret[2] != 0x41 || ret[3] != 0x32) // first four bytes must be 'RSA2' return null; return ret; } /// /// Verifies whether this certificate has been revoked or not. /// /// The encoded CRL to check against. /// An error occurs while verifying the certificate. /// is a null reference (Nothing in Visual Basic). /// true if the certificate is not on the CRL and therefore valid, or false if the certificate is on the CRL and therefore revoked. // Thanks go out to Gabriele Zannoni for implementing this method public bool VerifyRevocation(byte[] crl) { if (crl == null) throw new ArgumentNullException(); IntPtr crlContextHandle = SspiProvider.CertCreateCRLContext(SecurityConstants.X509_ASN_ENCODING | SecurityConstants.PKCS_7_ASN_ENCODING, crl, crl.Length); if (crlContextHandle == IntPtr.Zero) throw new ArgumentException("The parameter is invalid.", "crl"); IntPtr crle = IntPtr.Zero; if (SspiProvider.CertFindCertificateInCRL(Handle, crlContextHandle, 0, IntPtr.Zero, ref crle) == 0) throw new CertificateException("Unable to search the specified CRL for the certificate."); if (crle == IntPtr.Zero) return true; else return false; } /// /// The handle of the object. /// private IntPtr m_Handle; /// /// The handle of the object. /// private CertificateStore m_Store; /// /// A instance associated with this certificate. /// internal CertificateInfo m_CertInfo; /// /// A instance associated with this certificate. /// internal CertificateContext m_Context; /// /// A reference to the associated . /// private CertificateChain m_Chain = null; } }