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