/*
* 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.Threading;
using System.Collections;
using System.Runtime.InteropServices;
namespace Org.Mentalis.Security.Certificates {
///
/// Defines a chain of certificates.
///
public class CertificateChain {
///
/// Initializes a new instance from a .
///
/// The certificate for which a chain is being built.
/// will always be the end certificate.
/// is a null reference (Nothing in Visual Basic).
/// An error occurs while building the certificate chain.
public CertificateChain(Certificate cert) : this(cert, null) {}
///
/// Initializes a new instance from a .
///
/// The certificate for which a chain is being built.
/// Any additional store to be searched for supporting certificates and CTLs.
/// will always be the end certificate.
/// is a null reference (Nothing in Visual Basic).
/// An error occurs while building the certificate chain.
public CertificateChain(Certificate cert, CertificateStore additional) : this(cert, additional, CertificateChainOptions.Default) {}
///
/// Initializes a new instance from a .
///
/// The certificate for which a chain is being built.
/// Any additional store to be searched for supporting certificates and CTLs.
/// Additional certificate chain options.
/// will always be the end certificate.
/// is a null reference (Nothing in Visual Basic).
/// An error occurs while building the certificate chain.
public CertificateChain(Certificate cert, CertificateStore additional, CertificateChainOptions options) {
if (cert == null)
throw new ArgumentNullException();
IntPtr addstore = additional == null ? IntPtr.Zero : additional.Handle;
ChainParameters para = new ChainParameters();
para.cbSize = Marshal.SizeOf(typeof(ChainParameters));
para.RequestedUsagecUsageIdentifier = 0;
para.RequestedUsagedwType = 0;
para.RequestedUsagergpszUsageIdentifier = IntPtr.Zero;
if (SspiProvider.CertGetCertificateChain(IntPtr.Zero, cert.Handle, IntPtr.Zero, addstore, ref para, (int)options, IntPtr.Zero, ref m_Handle) == 0)
throw new CertificateException("Unable to find the certificate chain.");
m_Certificate = cert;
}
///
/// Disposes of the certificate chain.
///
~CertificateChain() {
if (m_Handle != IntPtr.Zero) {
SspiProvider.CertFreeCertificateChain(m_Handle);
m_Handle = IntPtr.Zero;
}
}
///
/// Returns the certificate for which this chain was built.
///
protected Certificate Certificate {
get {
return m_Certificate;
}
}
///
/// Returns the list of certificates in this .
///
/// An array of instances.
///
/// The certificate with index 0 is the end certificate in the chain, the certificate with the highest index is the root certificate [if it can be found].
///
// Thanks go out to Hernan de Lahitte for notifying us about a bug in this method.
public virtual Certificate[] GetCertificates() {
ArrayList ret = new ArrayList();
IntPtr cert = ((Certificate)this.Certificate.Clone()).Handle;
int dwVerificationFlags;
IntPtr store;
CertificateStoreCollection csc = this.Certificate.Store as CertificateStoreCollection;
if (csc != null) {
csc = new CertificateStoreCollection(csc);
} else {
csc = new CertificateStoreCollection(new CertificateStore[0]);
csc.AddStore(new CertificateStore(this.Certificate.m_Context.hCertStore, true));
}
csc.AddStore(new CertificateStore(CertificateStore.RootStore));
store = csc.Handle;
while(cert != IntPtr.Zero) {
ret.Add(new Certificate(cert, true));
dwVerificationFlags = 0;
cert = SspiProvider.CertGetIssuerCertificateFromStore(store, cert, IntPtr.Zero, ref dwVerificationFlags);
}
return (Certificate[])ret.ToArray(typeof(Certificate));
}
///
/// Verifies the end according to the SSL policy rules.
///
/// The server that returned the certificate -or- a null reference if the certificate is a client certificate.
/// One of the values.
/// One of the values.
/// An error occurs while verifying the certificate.
public virtual CertificateStatus VerifyChain(string server, AuthType type) {
return VerifyChain(server, type, VerificationFlags.None);
}
///
/// Verifies the end according to the SSL policy rules.
///
/// The server that returned the certificate -or- a null reference if the certificate is a client certificate.
/// One of the values.
/// One or more of the values. VerificationFlags values can be combined with the OR operator.
/// One of the values.
/// An error occurs while verifying the certificate.
public virtual CertificateStatus VerifyChain(string server, AuthType type, VerificationFlags flags) {
// Convert the server string to a wide string memory pointer
IntPtr serverName = IntPtr.Zero;
IntPtr dataPtr = IntPtr.Zero;
try {
if (server == null) {
serverName = IntPtr.Zero;
} else {
serverName = Marshal.StringToHGlobalUni(server);
}
// create a HTTPSPolicyCallbackData and get a memory pointer to the structure
SslPolicyParameters data = new SslPolicyParameters();
data.cbSize = Marshal.SizeOf(typeof(SslPolicyParameters));
data.dwAuthType = (int)type;
data.pwszServerName = serverName;
data.fdwChecks = (int)flags;
dataPtr = Marshal.AllocHGlobal(data.cbSize);
Marshal.StructureToPtr(data, dataPtr, false);
// create a CERT_CHAIN_POLICY_PARA
ChainPolicyParameters para = new ChainPolicyParameters();
para.cbSize = Marshal.SizeOf(typeof(ChainPolicyParameters));
para.dwFlags = (int)flags;
para.pvExtraPolicyPara = dataPtr;
// create a CERT_CHAIN_POLICY_STATUS
ChainPolicyStatus status = new ChainPolicyStatus();
status.cbSize = Marshal.SizeOf(typeof(ChainPolicyStatus));
// verify the certificate
if (SspiProvider.CertVerifyCertificateChainPolicy(new IntPtr(SecurityConstants.CERT_CHAIN_POLICY_SSL), m_Handle, ref para, ref status) == 0)
throw new CertificateException("Unable to verify the certificate.");
if (Enum.IsDefined(typeof(CertificateStatus), status.dwError))
return (CertificateStatus)status.dwError;
else
return CertificateStatus.OtherError;
} finally {
// clean up
if (dataPtr != IntPtr.Zero)
Marshal.FreeHGlobal(dataPtr);
if (serverName != IntPtr.Zero)
Marshal.FreeHGlobal(serverName);
}
}
///
/// Verifies the end according to the SSL policy rules.
///
/// The server that returned the certificate -or- a null reference if the certificate is a client certificate.
/// One of the values.
/// One or more of the values. VerificationFlags values can be combined with the OR operator.
/// An optional CRL to check. This parameter can be null (Nothing in Visual Basic).
/// One of the values.
/// An error occurs while verifying the certificate.
/// Only the leaf certificate is checked against the CRL.
// Thanks go out to Gabriele Zannoni for implementing this method
public virtual CertificateStatus VerifyChain(string server, AuthType type, VerificationFlags flags, byte[] crl) {
CertificateStatus status = VerifyChain(server, type, flags);
if (status != CertificateStatus.ValidCertificate || crl == null)
return status;
try {
if (!m_Certificate.VerifyRevocation(crl))
return CertificateStatus.Revoked;
else
return status;
} catch {
return CertificateStatus.RevocationFailure;
}
}
///
/// Begins verification of the end according to the SSL policy rules.
///
/// The server that returned the certificate -or- a null reference if the certificate is a client certificate.
/// One of the values.
/// One or more of the values. VerificationFlags values can be combined with the OR operator.
/// The delegate.
/// An object that contains state information for this request.
/// An that references the asynchronous connection.
/// An error occurs while queuing the verification request.
public virtual IAsyncResult BeginVerifyChain(string server, AuthType type, VerificationFlags flags, AsyncCallback callback, object asyncState) {
CertificateVerificationResult ret = new CertificateVerificationResult(this, server, type, flags, callback, asyncState);
if (!ThreadPool.QueueUserWorkItem(new WaitCallback(this.StartVerification), ret))
throw new CertificateException("Could not schedule the certificate chain for verification.");
return ret;
}
///
/// Ends a pending asynchronous certificate verification request.
///
/// Stores state information for this asynchronous operation as well as any user-defined data.
/// One of the values.
/// is a null reference (Nothing in Visual Basic).
/// The parameter was not returned by a call to the method.
/// EndVerifyChain was previously called for the asynchronous chain verification.
/// An error occurs while verifying the certificate chain.
public virtual CertificateStatus EndVerifyChain(IAsyncResult ar) {
if (ar == null)
throw new ArgumentNullException();
CertificateVerificationResult result;
try {
result = (CertificateVerificationResult)ar;
} catch {
throw new ArgumentException();
}
if (result.Chain != this)
throw new ArgumentException();
if (result.HasEnded)
throw new InvalidOperationException();
if (result.ThrowException != null)
throw result.ThrowException;
result.HasEnded = true;
return result.Status;
}
///
/// Verifies a certificate chain and calls a delegate when finished.
///
/// Stores state information for this asynchronous operation as well as any user-defined data.
protected void StartVerification(object state) {
if (state == null)
return;
CertificateVerificationResult result;
try {
result = (CertificateVerificationResult)state;
} catch {
return;
}
CertificateStatus ret;
try {
ret = VerifyChain(result.Server, result.Type, result.Flags);
} catch (CertificateException ce) {
result.VerificationCompleted(ce, CertificateStatus.OtherError);
return;
} catch (Exception e) {
result.VerificationCompleted(new CertificateException("Could not verify the certificate chain.", e), CertificateStatus.OtherError);
return;
}
result.VerificationCompleted(null, ret);
}
///
/// The handle of the certificate chain.
///
private IntPtr m_Handle;
///
/// The end certificate that was used to build the chain.
///
private Certificate m_Certificate;
}
}