/*
* 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;
}
}