/*++



   Copyright    (c)    1996    Microsoft Corporation

   Module  Name :

       comobj.cxx

   Abstract:

       This module defines DCOM Admin Ex APIs used for certificate information replication.

   Author:

       Philippe Choquier ( Phillich ) 23-Jun-97
       Alex Mallet (amallet) 17-Feb-1998 

--*/

#define UNICODE 

extern "C" {
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
}
#include <dbgutil.h>
#include <ole2.h>
#include <windows.h>
#include <stdio.h>
#define USE_CAPI2
#if defined(USE_CAPI2)
#include <wincrypt.h>
#endif

extern "C" {
#define SECURITY_WIN32
#include <sspi.h>
}
#include <spseal.h>
#include <issperr.h>
#include <schnlsp.h>
#include <md5.h>

#include <iadmw.h>
#include <admex.h>
#include <iiscnfgp.h>
#include <mdcommsg.h>
#include <replseed.hxx>
#include <iis64.h>

#include "comobj.hxx"

#define RETURNCODETOHRESULT(rc)                             \
            (((rc) < 0x10000)                               \
                ? HRESULT_FROM_WIN32(rc)                    \
                : (rc))

#define PAD4(a) (((a)+3)&~3)

#define MSB(a) (BYTE) (((a) & 0xFF00) >> 8)
#define LSB(a) (BYTE) ((a) & 0xFF)
#define LENGTH( msb, lsb ) (DWORD) ( (msb << 8) + lsb )
//
// Globals
//

DECLARE_DEBUG_PRINTS_OBJECT();
ULONG g_dwRefCount = 0;

#include <initguid.h>
DEFINE_GUID(IisADMExsGuid, 
0x784d8905, 0xaa8c, 0x11d2, 0x92, 0x5e, 0x00, 0xc0, 0x4f, 0x72, 0xd9, 0x0e);

CADMEXCOM::CADMEXCOM()
{
    m_dwRefCount = 0;
    m_pIMSAdminReplication = new CADMEXCOM_IMSAdminReplication(this);
    m_pIMSAdminCryptoCapabilities = new CADMEXCOM_IMSAdminCryptoCapabilities( this );
}


CADMEXCOM::~CADMEXCOM()
{
    if ( m_pIMSAdminReplication )
    {
        delete m_pIMSAdminReplication;
    }

    if ( m_pIMSAdminCryptoCapabilities )
    {
        delete m_pIMSAdminCryptoCapabilities;
    }
}


HRESULT
CADMEXCOM::QueryInterface(
    REFIID riid,
    void **ppObject)
{
    if (riid==IID_IUnknown ) {
        *ppObject = (IUnknown *) this;
        AddRef();
    }
    else if ( riid==IID_IMSAdminReplication) {
        *ppObject = (IMSAdminReplication *)m_pIMSAdminReplication;
        AddRef();
    }
    else if ( riid==IID_IMSAdminCryptoCapabilities) {
        *ppObject = (IMSAdminCryptoCapabilities *)m_pIMSAdminCryptoCapabilities;
        AddRef();
    }
    else {
        return E_NOINTERFACE;
    }

    return NO_ERROR;
}


ULONG
CADMEXCOM::AddRef(
    )
{
    DWORD dwRefCount;

    dwRefCount = InterlockedIncrement((long *)&m_dwRefCount);

    return dwRefCount;
}


ULONG
CADMEXCOM::Release(
    )
{
    DWORD dwRefCount;

    dwRefCount = InterlockedDecrement((long *)&m_dwRefCount);

    if( dwRefCount == 0 ) {
        delete this;
        return 0;
    }

    return dwRefCount;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                       SSL Information Replication Interface 

  In order to determine whether information needs to be replicated between two machines,
  the GetSignature() method is called; this is expected to return a "signature" that can be
  compared with the "signature" on the other machine - if the signatures match, they
  have the same information, else replication needs to occur. 

  To replicate, the Serialize() method is called to serialize all the config information
  into a buffer; this buffer is encrypted with a generated session key [because it contains
  the private keys for all the server certificates]. The data to generate the session key
  is stored in the metabase, in encrypted format.

  Propagate() doesn't do anything.

  DeSerialize() on the target machine regenerates the session key out of the metabase, uses it
  to decrypt the buffer generated by Serialize() and creates server certificates and
  CTLs in the appropriate places.

  The actual configuration information in the metabase itself is replicated during
  the (separate) metabase replication process.

  Each server instance has the following SSL information associated with it that 
  needs to be replicated :

  1. Server certificate, server private key, cert chain for server certificate

  2. Certificate Trust List, signing cert for CTL
  

  GetSignature() : 
  ----------------
  Identifying info for server certificates :
  <hash of cert stored under LM/W3SVC>:(instance_cert_information)?
  where instance_cert_information =  <instance #>=<hash of leaf cert>,<hash of issuer 1>,
                                      <hash of issuer of issuer 1> .... |
  End of instance cert information is marked by "|"
  
  Identifying info for CTL :
  [CTL_INFO for CTL stored under /LM/W3SVC]:(<instance #>=CTL_INFO)?
  where CTL_INFO = <CTL list identifier>,(<hash of cert in ctl>)+,<hash of CTL signer>
  
  Signature : MD5(Info for server certificates : Info for CTLs)


-----------------------------------------------------------------------------------------------*/

#define MB_ROOT_PATH L"/LM/W3SVC"

#define TIMEOUT_VALUE 30000 //NOTE - magic number !
#define INITIAL_BUFFER_SIZE 2048

#define MY_STORE_NAME L"MY"
#define CA_STORE_NAME L"CA"
#define ROOT_STORE_NAME L"ROOT"
#define TRUST_STORE_NAME L"TRUST"

#define COLON ':'
#define INSTANCE_TRAILER_BYTE '|'

#define CERT_HEADER "CERTS="
#define CERT_HEADER_SIZE (sizeof(CERT_HEADER) - 1)
#define CTL_HEADER "CTL="
#define CTL_HEADER_SIZE (sizeof(CTL_HEADER) - 1)

#define SIGNATURE 0
#define CONFIGURATION 1

#define REPLICATION_SESSION_KEY_CONTAINER L"IIS Replication Session Key"

#define MIN_REPLICATION_INFO_SIZE 4 //> <MSB><LSB>":"<some info>"|"

#define REPL_INTERNAL_ERROR RETURNCODETOHRESULT( ERROR_INVALID_PARAMETER )


// Array holding properties required to reconstruct cert context
static DWORD adwMetabaseCertProperties[] = { MD_SSL_CERT_HASH, BINARY_METADATA,
                                             MD_SSL_CERT_STORE_NAME, STRING_METADATA };

#define cNumCertMetabaseProperties sizeof(adwMetabaseCertProperties)/sizeof(DWORD)

// Array holding properties necessary to reconstruct CTL context
static DWORD adwMetabaseCTLProperties[] = { MD_SSL_CTL_IDENTIFIER, BINARY_METADATA,
                                            MD_SSL_CTL_STORE_NAME, STRING_METADATA };

#define cNumCTLMetabaseProperties sizeof(adwMetabaseCTLProperties)/sizeof(DWORD)

#if DBG
VOID
Dump(
    CHAR    *pchMessage,
    LPBYTE  pb,
    DWORD   dw
    )
{
    for ( UINT x = 0 ; x < dw ; ++x )
    {
        sprintf(pchMessage + x*3, "%02x ", pb[x] );
    }

    sprintf(pchMessage + x*3,"\n");
}
#endif

CADMEXCOM_IMSAdminReplication::CADMEXCOM_IMSAdminReplication( CADMEXCOM *pAdmExCom ) :
m_pAdmExCom( pAdmExCom ),
m_pMB( NULL ),
m_fGotSeed( FALSE )
{
    HRESULT hRes;
    COSERVERINFO *pcsiParam = NULL;
    IClassFactory * pcsfFactory = NULL;

    //
    // Retrieve class factory for metabase object
    //
    hRes = CoGetClassObject(CLSID_MSAdminBase_W, CLSCTX_SERVER, pcsiParam,
                            IID_IClassFactory, ( void ** ) &pcsfFactory );

    if ( FAILED(hRes) )
    {
        DBGPRINTF((DBG_CONTEXT, "CoGetClassObject failed : hRes : %x, Win32 : 0x%x\n",
                   hRes, HRESULTTOWIN32(hRes)));
        return;
    }

    //
    // Retrieve the actual metabase object interface
    //
    hRes = pcsfFactory->CreateInstance( NULL,
                                        IID_IMSAdminBase,
                                        (void **) &m_pMB );

    if ( FAILED(hRes) )
    {
        DBGPRINTF((DBG_CONTEXT, "CreateInstance failed : hRes : %x, Win32 : 0x%x\n",
                   hRes, HRESULTTOWIN32(hRes)));
        return;
    }
    pcsfFactory->Release();
}



CADMEXCOM_IMSAdminReplication::~CADMEXCOM_IMSAdminReplication()
{
    if ( m_pMB )
    {
        m_pMB->Release();
        m_pMB = NULL;
    }
}

HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminReplication::GetSignature( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][out] */ unsigned char __RPC_FAR *pbBuffer,
    /* [out] */ DWORD __RPC_FAR *pdwMDRequiredBufferSize)
/*++

Routine Description:

    Used to retrieve a "signature" for the certificate information to be retrieved; if the 
    signatures on two machines match, then it can be assumed that they have identical 
    SSL configurations.

Arguments:

    dwBufferSize - size of buffer passed in
    pbBuffer - buffer to be filled in with signature
    *pdwMDRequiredBufferSize - size of buffer required for data

Returns:

    HRESULT indicating success/failure.

--*/

{
    DBGPRINTF((DBG_CONTEXT,
               "GetSignature() called for SSL info replication\n"));

    HRESULT hRes = S_OK;
    hRes = GetConfigurationInformation( SIGNATURE,
                                        NULL,
                                        m_pMB,
                                        pbBuffer,
                                        dwBufferSize,
                                        pdwMDRequiredBufferSize );

#if DBG
    if ( FAILED( hRes ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "GetSignature() failed with Win32 error : 0x%x\n",
                   HRESULTTOWIN32( hRes )));
    }
    else
    {
        CHAR achSig[200];
        Dump( achSig, pbBuffer, *pdwMDRequiredBufferSize );
        DBGPRINTF((DBG_CONTEXT,
                   "GetSignature() returned %s\n",
                   achSig));
    }
#endif

    return hRes;
} //::GetSignature

HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminReplication::Serialize( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][out] */ unsigned char __RPC_FAR *pbBuffer,
    /* [out] */ DWORD __RPC_FAR *pdwMDRequiredBufferSize)
/*++

Routine Description:

    Serializes the SSL information for IIS.

Arguments:

    dwBufferSize - size of buffer pointed to by pbBuffer
    pbBuffer - buffer to receive serialized SSL configuration; can be NULL if
    caller wants to determine size of buffer necessary 
    pdwMDRequiredBufferSize - pointer to size of buffer necessary to hold the
    information; updated if pbBuffer is too small to hold the information

Returns:

    HRESULT indicating success/failure.

--*/

{
    HRESULT hRes = S_OK;
    HCRYPTKEY hSessionKey = NULL;

    DBGPRINTF((DBG_CONTEXT,
               "Serialize() called for SSL info replication\n"));
    hRes = RegenerateSessionKey( m_pMB,
                                 &hSessionKey );
    if ( S_OK == hRes )
    {
        m_fGotSeed = TRUE;

        hRes = GetConfigurationInformation( CONFIGURATION,
                                            &hSessionKey,
                                            m_pMB,
                                            pbBuffer,
                                            dwBufferSize,
                                            pdwMDRequiredBufferSize );
        
#if DBG
        if ( FAILED( hRes ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Serialize() failed with Win32 error : 0x%x\n",
                       HRESULTTOWIN32( hRes )));
        }
#endif
    }

    return hRes;
} //::Serialize

HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminReplication::Propagate(
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][in] */ unsigned char __RPC_FAR *pszBuffer)
/*++

Routine Description:

   Propagates data necessary to re-generate session key from metabase on source machine
   to metabase on target machine

Arguments:

    dwBufferSize - size of buffer pointed to by pbBuffer
    pbBuffer - name of target machine

Returns:

    HRESULT indicating success/failure.

--*/

{
    DBGPRINTF((DBG_CONTEXT,
               "Propagate() called for SSL info replication\n"));

    if ( !m_fGotSeed )
    {
        return S_FALSE;
    }
    else
    {
        return S_OK;
    }

}//::Propagate

HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminReplication::Propagate2(
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][in] */ unsigned char __RPC_FAR *pszBuffer,
    /* [in] */ DWORD dwSignatureMismatch )
{
    return S_OK;
}

        
HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminReplication::DeSerialize( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][in] */ unsigned char __RPC_FAR *pbBuffer)
/*++

Routine Description:

    Deserializes the SSL information produced by Serialize()

Arguments:

    dwBufferSize - size of buffer pointed to by pbBuffer
    pbBuffer - buffer holding serialized SSL configuration

Returns:

    HRESULT indicating success/failure.

Extended Description :

       The information for each instance is separate, with instance data being in the
       form 

       <Instance Data> = <MSB of instance #><LSB of instance #>":"<instance SSL info>"|"

       <Instance SSL Info> = <server certificate information><CTL information>

       <Server certificate information>  =  "" | 
       "CERTS="<SHA1 hash of first cert in chain><MSB of length of private key blob>
       <LSB of length of private key blob><private key blob of first cert>
       <MSB of length of serialized store containing chain><LSB of length of serialized store
       containing chain><serialized store>

        The SHA1 hash is added so that it's possible to figure out which cert in the store
        is the leaf cert when unserializing the buffer.

        <CTL information> = "" |
        "CTL="<hash of CTL signer><MSB of length of serialized store><LSB of length of serialized
        store><serialized store>

        The serialized store contains the CTL, the certs in the CTL and the signer of the CTL.
--*/
{
    HRESULT hRes = S_OK;
    BYTE *pbReplicationInfo = NULL;
    BYTE *pbPosition = NULL;
    BYTE *pbEnd = NULL;
    DWORD dwInstance = 0;
    HCRYPTKEY hSessionKey = NULL;

    DBGPRINTF((DBG_CONTEXT,
               "DeSerialize() called for SSL info replication\n"));

    //
    // No information to serialize
    //
    if ( dwBufferSize == 0 )
    {
        return S_OK;
    }

    //
    // Invalid buffer or buffer size 
    //
    if ( !pbBuffer  || dwBufferSize < MIN_REPLICATION_INFO_SIZE )
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // Extract the data for the session key under which the buffer is encrypted
    //

    hRes = RegenerateSessionKey( m_pMB, &hSessionKey );

    if ( hSessionKey )
    {
        //
        // Decrypt the buffer
        //
        hRes = DecryptBuffer( hSessionKey,
                              pbBuffer,
                              dwBufferSize,
                              &pbReplicationInfo,
                              &pbEnd );
        
        if( FAILED(hRes) )
        {
            DeleteSessionKey( &hSessionKey );
            hSessionKey = NULL;
        }
    }

    if( !hSessionKey )
    {
        //
        // Some of these functions return S_FALSE for some reason, 
        // preserve this behavior.
        //
        return (hRes != S_OK) ? hRes : E_FAIL;
    }
        
    //
    // Deserialize away
    //
    pbPosition = pbReplicationInfo;
    while ( pbPosition < pbEnd &&
            !FAILED( hRes = DeserializeInstanceInfo( &pbPosition,
                                                     pbEnd,
                                                     &dwInstance ) ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Successfully deserialized information for instance %d\n",
                   dwInstance));
    }

    if ( FAILED( hRes ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Failed deserializing information for instance %d\n",
                   dwInstance));
    }

    //
    // Cleanup
    //
    if ( pbReplicationInfo )
    {
        memset( pbReplicationInfo, 0, DIFF(pbEnd - pbReplicationInfo) );
        delete [] pbReplicationInfo;
    }

    DeleteSessionKey( &hSessionKey );

    //
    // Q : when should we delete the MB info ? Only on successful DeSerialize ?
    //
    if ( SUCCEEDED( hRes ) )
    {
        DeleteMBSessionKeyInfo( m_pMB );
    }

    return hRes;
}//::DeSerialize


HRESULT
GetConfigurationInformation( DWORD dwInfoType,
                             HCRYPTKEY *phSessionKey,
                             IMSAdminBase *pMB,
                             unsigned char __RPC_FAR *pbBuffer,
                             DWORD dwBufferSize,
                             DWORD __RPC_FAR *pdwMDRequiredBufferSize )
/*++

Routine Description:

    Used to retrieve replication information 

Arguments:

    dwInfoType - which kind of information to retrieve - signature or configuration
    phSessionKey - pointer to session key to be used to encrypt replication info; not
    used if dwInfoType == SIGNATURE
    pMB - pointer to metabase object
    pbBuffer - buffer to be filled in with information
    dwBufferSize - size of pbBuffer
    *pdwMDRequiredBufferSize - size of buffer required for data

Returns:

    HRESULT indicating success/failure.

--*/

{
    HRESULT hRes = S_OK;
    METADATA_HANDLE hMDHandle;
    DWORD dwObjectIndex = 0;
    TCHAR achMDName[METADATA_MAX_NAME_LEN];
    DWORD dwRepBufferSize = 0;
    PBYTE pbReplicationInfo = NULL;
    DWORD dwPosition = 0;
    BYTE *pbHashBuffer = NULL;
    DWORD cbHashSize = 0;

    if ( !pMB )
    {
        return S_FALSE;
    }

    //
    // Make sure we have a pointer to a session key if we need to use it
    //
    if ( dwInfoType != SIGNATURE && !phSessionKey )
    {
        return S_FALSE;
    }

    //
    // Signature is MD5 hash of information, so we know the buffer has to be at least
    // MD5_HASH_SIZE bytes in size
    //
    if ( dwInfoType == SIGNATURE )
    {
        if ( !pbBuffer || dwBufferSize < MD5_HASH_SIZE )
        {
            *pdwMDRequiredBufferSize = MD5_HASH_SIZE;
            return RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER );
        }
    }

    hRes = pMB->OpenKey( METADATA_MASTER_ROOT_HANDLE,
                         MB_ROOT_PATH,
                         METADATA_PERMISSION_READ,
                         TIMEOUT_VALUE,
                         &hMDHandle );

    if ( FAILED(hRes) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Failed to open mb path"));
        return hRes;
    }

    //
    // Try to retrieve default information stored at /LM/W3SVC
    //
    
    //
    // Bogus report of null pointer dereference of pbReplicationInfo
    // Windows Bug - 117759
    //
    /* INTRINSA suppress=null_pointers */
    if ( FAILED( hRes = GetInstanceReplicationInfo( dwInfoType,
                                                    L"",
                                                    pMB,
                                                    hMDHandle,
                                                    &pbReplicationInfo,
                                                    &dwRepBufferSize,
                                                    &dwPosition ) ) )
    {
        if ( pbReplicationInfo )
        {
            delete [] pbReplicationInfo;
        }
        pMB->CloseKey( hMDHandle );
        return hRes;
    }


    //
    // Iterate through all the subkeys of /LM/W3SVC
    //
    HRESULT hRes2 = S_OK;
    while ( SUCCEEDED(hRes2 = pMB->EnumKeys( hMDHandle,
                                             L"",
                                             achMDName,
                                             dwObjectIndex) ) )
    {
        //
        // If the name of the subkey is a number, it's a server instance.
        // Construct cert and CTL information
        //
        if ( IsNumber( achMDName ) )
        {
            if ( FAILED( hRes = GetInstanceReplicationInfo( dwInfoType,
                                                            achMDName,
                                                            pMB,
                                                            hMDHandle,
                                                            &pbReplicationInfo,
                                                            &dwRepBufferSize,
                                                            &dwPosition ) ) )
            {
                if ( pbReplicationInfo )
                {
                    memset( pbReplicationInfo, 0, dwRepBufferSize );
                    delete [] pbReplicationInfo;
                }
                pMB->CloseKey( hMDHandle );
                return hRes;
            }
        }
        dwObjectIndex++;
    }

    //
    // Error while iterating over keys
    //
    if ( hRes2 != RETURNCODETOHRESULT( ERROR_NO_MORE_ITEMS ) )
    {
        if ( pbReplicationInfo )
        {
            memset( pbReplicationInfo, 0, dwRepBufferSize );
            delete [] pbReplicationInfo;
        }
        pMB->CloseKey( hMDHandle );

        return hRes2;
    }

    if ( dwInfoType == SIGNATURE )
    {
        //
        // We have a buffer containing all the information that needs to be converted
        // into a signature by applying the MD5 hash algorithm to it
        //
        if ( !FAILED( hRes = GenerateHash( NULL,
                                           CALG_MD5,
                                           pbReplicationInfo,
                                           dwPosition, 
                                           &pbHashBuffer,
                                           &cbHashSize,
                                           NULL ) ) )
        {
            DBG_ASSERT( cbHashSize == MD5_HASH_SIZE );

            memcpy( pbBuffer, pbHashBuffer, cbHashSize );

            *pdwMDRequiredBufferSize = cbHashSize;
        }
    }
    else
    {
        //
        // Because the cert information contains a private key, use the
        // session key to encrypt the replication buffer

        if ( !FAILED( hRes = EncryptBuffer( *phSessionKey,
                                            &pbReplicationInfo,
                                            &dwRepBufferSize,
                                            &dwPosition ) ) )
        {
            //
            // check that output buffer is big enough to hold the encrypted data
            //
            if ( dwBufferSize >= dwPosition )
            {
                memcpy( pbBuffer, pbReplicationInfo, dwPosition );
                *pdwMDRequiredBufferSize = dwPosition;
            }
            else
            {
                *pdwMDRequiredBufferSize = dwPosition;
                hRes = RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER );
            }
        }
    }
                    
    if ( pbReplicationInfo )
    {
        //
        // zero it out, just to be paranoid
        //
        memset( pbReplicationInfo, 0, dwRepBufferSize );
        delete [] pbReplicationInfo;
    }

    if ( pbHashBuffer )
    {
        //
        // zero it out, just to be paranoid
        //
        memset( pbHashBuffer, 0, cbHashSize );
        delete [] pbHashBuffer;
    }

    pMB->CloseKey( hMDHandle );

    return hRes;
}

        
HRESULT GetInstanceReplicationInfo( DWORD dwInfoType,
                                    LPCWSTR pszInstanceNum,
                                    IN IMSAdminBase *pMB,
                                    IN METADATA_HANDLE hHandle,
                                    OUT BYTE **ppbReplicationBuffer,
                                    IN OUT DWORD *pdwBufferSize,
                                    IN OUT DWORD *pdwPosition )
/*++

Routine Description:

    Used to retrieve a replication information for a particular server instance. 
    Format of information : 
   
    <MSB of instance #><LSB of instance #>:<instance info>|

Arguments:

    dwInfoType - type of information - either signature or configuration
    pszInstanceNum - string rep of instance number
    pMB - metabase object
    hHandle - handle to path open for reading
    ppbReplicationBuffer - pointer to pointer to buffer that is filled in with info
    pdwBufferSize - pointer to current buffer size, updated on success
    pdwPosition - pointer to end of data in buffer, updated on success

Returns:

    HRESULT indicating success/failure.

--*/
{

#define INSTANCE_HEADER_LEN 3 // <MSB> <LSB> ':' 
#define INSTANCE_TRAILER_LEN 1 // '|'

    HRESULT hRes = NOERROR;
    DWORD dwInstance;
    BOOL fHasCTL = FALSE;
    BOOL fHasCert = FALSE;

    if ( !pMB )
    {
        return S_FALSE;
    }

    //
    // Special case : null instance string means instance zero ( = /LM/W3SVC )
    //
    if ( !wcscmp(L"", pszInstanceNum) )
    {
        dwInstance = 0;
    }
    else
    {
        dwInstance = _wtoi( pszInstanceNum );
    }

    
    fHasCert = MBPathHasCAPIInfo( pMB,
                                  hHandle,
                                  pszInstanceNum,
                                  adwMetabaseCertProperties,
                                  cNumCertMetabaseProperties );
    fHasCTL = MBPathHasCAPIInfo( pMB,
                                 hHandle,
                                 pszInstanceNum,
                                 adwMetabaseCTLProperties,
                                 cNumCTLMetabaseProperties );

    if ( fHasCTL || fHasCert )
    {
        //
        // Copy instance information into the buffer
        //
        if ( ( ( *pdwBufferSize - *pdwPosition ) > INSTANCE_HEADER_LEN ) || 
             ResizeBuffer( ppbReplicationBuffer, INSTANCE_HEADER_LEN , pdwBufferSize ) )
        {
            //
            // Instance header 
            //
            (*ppbReplicationBuffer)[(*pdwPosition)++] = MSB(dwInstance);
            (*ppbReplicationBuffer)[(*pdwPosition)++] = LSB(dwInstance);
            (*ppbReplicationBuffer)[(*pdwPosition)++] = COLON;
            
            //
            // Server certificate information
            //
            if ( fHasCert )
            {
                if ( FAILED( hRes = GetCertReplicationInfo( dwInfoType,
                                                            pMB,
                                                            hHandle,
                                                            pszInstanceNum,
                                                            ppbReplicationBuffer,
                                                            pdwBufferSize,
                                                            pdwPosition ) ) )
                {
                    return hRes;
                }
            }

            //
            // CTL information
            //
            if ( fHasCTL )
            {
                if ( FAILED( hRes = GetCTLReplicationInfo( dwInfoType,
                                                           pMB,
                                                           hHandle,
                                                           pszInstanceNum,
                                                           ppbReplicationBuffer,
                                                           pdwBufferSize,
                                                           pdwPosition ) ) )
                {
                    return hRes;
                }
            }
            
            if ( ( ( *pdwBufferSize - *pdwPosition ) > INSTANCE_TRAILER_LEN ) ||
                 ResizeBuffer( ppbReplicationBuffer, INSTANCE_TRAILER_LEN, pdwBufferSize ) )
            {
                //
                //Instance trailer
                //
                (*ppbReplicationBuffer)[(*pdwPosition)++] = INSTANCE_TRAILER_BYTE;
            }
            else
            {
                hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
            }
        }
        else
        {
            hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
        }
    }

    return (hRes);
}

HRESULT GetCertReplicationInfo( DWORD dwInfoType,
                                IN IMSAdminBase *pMB,
                                IN METADATA_HANDLE hHandle,
                                IN LPCWSTR pszPath,
                                OUT BYTE **ppbReplicationBuffer,
                                IN OUT DWORD *pdwBufferSize,
                                IN OUT DWORD *pdwPosition )
/*++

Routine Description:

    Used to retrieve replication information for a certificate 

Arguments:

    dwInfoType - type of information to retrieve - signature or configuration
    pMB - metabase object
    hHandle - handle to path open for reading
    pszPath - cert info path relative to hHandle
    ppbReplicationBuffer - pointer to pointer to buffer that is filled in with info
    pdwBufferSize - pointer to current buffer size, updated on success
    pdwPosition - pointer to end of data in buffer, updated on success

Returns:

    HRESULT indicating success/failure.

--*/
{
    PCCERT_CONTEXT pcServerCert = NULL;
    DWORD dwNumCerts = 0;
    LIST_ENTRY CertChain;
    BOOL fCompleteChain = FALSE;
    OPEN_CERT_STORE_INFO *pStoreInfo = NULL;
    HRESULT hRes = S_OK;

    if ( !pMB )
    {
        return S_FALSE;
    }

    InitializeListHead( &CertChain );

    //
    // try to retrieve the server certificate and its complete cert chain
    //
    if ( !FAILED( hRes = ReadServerCert( pMB,
                                         hHandle,
                                         pszPath,
                                         &pcServerCert,
                                         &pStoreInfo ) ) && 
         pcServerCert &&
         IsReplicableCert( pcServerCert ) && 
         !FAILED( hRes = ConstructCertChain( pcServerCert,
                                             pStoreInfo->pszStoreName,
                                             &CertChain,
                                             &fCompleteChain ) ) )

    {
        if ( dwInfoType == SIGNATURE )
        {
            hRes =  GetCertChainSignature( &CertChain,
                                           ppbReplicationBuffer,
                                           pdwBufferSize,
                                           pdwPosition );
        }
        else
        {
            hRes = SerializeCertChain( &CertChain,
                                       pStoreInfo,
                                       ppbReplicationBuffer,
                                       pdwBufferSize,
                                       pdwPosition );
        }
    }

    //
    // Clean up
    //
    if ( pcServerCert )
    {
        CertFreeCertificateContext( pcServerCert );
    }

    FreeCertChain( &CertChain );

    return hRes;
}



HRESULT GetCTLReplicationInfo( DWORD dwInfoType,
                                IN IMSAdminBase *pMB,
                                IN METADATA_HANDLE hHandle,
                                IN LPCWSTR pszPath,
                                OUT BYTE **ppbReplicationBuffer,
                                IN OUT DWORD *pdwBufferSize,
                                IN OUT DWORD *pdwPosition )
/*++

Routine Description:

    Used to retrieve replication information for a CTL

Arguments:

    dwInfoType - type of information to retrieve - signature or configuration
    pMB - metabase object
    hHandle - handle to path open for reading
    pszPath - cert info path relative to hHandle
    ppbReplicationBuffer - pointer to pointer to buffer that is filled in with info
    pdwBufferSize - pointer to current buffer size, updated on success
    pdwPosition - pointer to end of data in buffer, updated on success

Returns:

    HRESULT indicating success/failure.

--*/
{
    OPEN_CERT_STORE_INFO *pStoreInfo = NULL;
    HRESULT hRes;
    PCCTL_CONTEXT pcCTL = NULL;
    LIST_ENTRY CtlCerts;
    PCCERT_CONTEXT pcSignerCert = NULL;

    InitializeListHead( &CtlCerts );

    if ( S_OK == ( hRes = ReadServerCTL( pMB,
                                         hHandle,
                                         pszPath,
                                         &pcCTL ) ) &&
         !FAILED( hRes = BuildCTLDescription( pcCTL,
                                              &CtlCerts,
                                              &pcSignerCert ) ) )
    {
        if ( dwInfoType == SIGNATURE )
        {
            hRes = GetCTLSignature( pcCTL,
                                    &CtlCerts,
                                    pcSignerCert,
                                    ppbReplicationBuffer,
                                    pdwBufferSize,
                                    pdwPosition );
        }
        else
        {
            hRes = SerializeCTL( pcCTL,
                                 &CtlCerts,
                                 pcSignerCert,
                                 ppbReplicationBuffer,
                                 pdwBufferSize,
                                 pdwPosition );
        }
    }

    //
    // Cleanup
    //
    if ( pcCTL )
    {
        CertFreeCTLContext( pcCTL );
    }

    if ( pcSignerCert )
    {
        CertFreeCertificateContext( pcSignerCert );
    }

    FreeCertChain( &CtlCerts );

    return (hRes);
}

HRESULT SerializeCertChain( IN LIST_ENTRY *pChain,
                            IN OPEN_CERT_STORE_INFO *pStoreInfo,
                            OUT PBYTE *ppbChainBuffer,
                            IN OUT DWORD *pdwBufferSize,
                            IN OUT DWORD *pdwPosition )
/*++

Routine Description:

    Serializes the metabase information and certificate chain belonging to a server 
    certificate into a buffer. The first certificate in the chain is the actual server
    certificate. It does this by copying the chain into a CAPI store and serializing 
    the store.

    Format of buffer : 

    CERTS=<SHA1 hash of first cert in chain><MSB of length of private key blob><LSB of length of 
    private key blob><private key blob of first cert><key container info of private key>
    <MSB of length of serialized store containg chain><LSB of length of serialized store 
    containing chain><serialized store>

    The SHA1 hash is added so that it's possible to figure out which cert in the store
    is the leaf cert when unserializing the buffer.

Arguments:

    pChain - chain of CertChainEntry structures
    pStoreInfo - structure containing cert-related metabase information
    ppbChainBuffer - pointer to pointer to buffer that will hold the serialized store 
    on success
    pdwBufferSize - pointer to present size of buffer, updated if buffer is resized
    pdwPosition - pointer to where information is to be written to in buffer, updated
    on success

Returns:

    HRESULT indicating success/failure

--*/
{
    HCERTSTORE hMemStore;
    HRESULT hRes;
    HCRYPTPROV hProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD cbPrivateKey = 0;
    DWORD cbSpaceLeft = 0;
    DWORD cbSpaceRequired = 0;
    DWORD dwProvSize = 0;

    if ( !pChain )
    {
        return S_FALSE;
    }

    //
    // The certificates are stuffed into an in-memory store, which is then serialized
    //
    if ( !( hMemStore = CertOpenStore( CERT_STORE_PROV_MEMORY,
                                       0,
                                       NULL,
                                       0,
                                       0 ) ) )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    //
    // Go through the list and add each cert to the store
    //
    LIST_ENTRY *pLink;
    CertChainEntry *pChainEntry;
    DWORD dwSizeNeeded = 0;
    DWORD cCerts = 0;
    PCCERT_CONTEXT pcFirstCert = NULL;

    for ( pLink = pChain->Flink;
          pLink != pChain;
          pLink = pLink->Flink )
    {
        pChainEntry = CONTAINING_RECORD( pLink, CertChainEntry, ListEntry );

        if ( !CertAddCertificateContextToStore( hMemStore,
                                                pChainEntry->pcCert,
                                                CERT_STORE_ADD_REPLACE_EXISTING,
                                                NULL ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto EndSerializeChain;
        }
        cCerts++;
        if ( cCerts == 1 )
        {
            pcFirstCert = pChainEntry->pcCert;
        }
    }

    if ( !pcFirstCert )
    {
        CertCloseStore( hMemStore,
                        0 );
        return S_FALSE;
    }

    //
    // Calculate space requirements
    //
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;
    cbSpaceRequired = CERT_HEADER_SIZE + SHA1_HASH_SIZE;

    if ( cbSpaceRequired > cbSpaceLeft &&
         !ResizeBuffer( ppbChainBuffer,
                        cbSpaceRequired,
                        pdwBufferSize ) )
    {
        CertCloseStore( hMemStore,
                        0 );

        return RETURNCODETOHRESULT(ERROR_OUTOFMEMORY);
    }


    //
    // Append cert header
    //
    memcpy( *ppbChainBuffer + *pdwPosition, CERT_HEADER, CERT_HEADER_SIZE );
    (*pdwPosition) += CERT_HEADER_SIZE;

    //
    // Append the hash of the first cert
    //
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;
    if ( !CertGetCertificateContextProperty( pcFirstCert,
                                             CERT_SHA1_HASH_PROP_ID,
                                             (*ppbChainBuffer) + *pdwPosition,
                                             &cbSpaceLeft ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializeChain;
    }
    (*pdwPosition) += cbSpaceLeft;


    //
    // Serialize the private key for the cert
    //
    if ( FAILED( hRes = ExportAndSerializeServerPK( pcFirstCert,
                                                    ppbChainBuffer,
                                                    pdwBufferSize,
                                                    pdwPosition ) ) )
    {
        CertCloseStore( hMemStore,
                        0 );
        return (hRes);
    }

    //
    // Figure out how much space we need for the serialized store
    //
    CRYPT_DATA_BLOB StoreBlob;
    StoreBlob.pbData = NULL;
    StoreBlob.cbData = 0;
    if ( !CertSaveStore( hMemStore,
                         0,
                         CERT_STORE_SAVE_AS_STORE,
                         CERT_STORE_SAVE_TO_MEMORY,
                         (VOID *) &StoreBlob,
                         0 ) &&
         GetLastError() != ERROR_MORE_DATA )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializeChain;
    }


    //
    // Resize the buffer if necessary - space for bytes indicating length of 
    // serialized store, serialized store itself
    //
    cbSpaceRequired = 2 + StoreBlob.cbData;
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;

    if ( cbSpaceRequired > cbSpaceLeft )
    {
        if ( !ResizeBuffer( ppbChainBuffer,
                            cbSpaceRequired,
                            pdwBufferSize ) )
        {
            hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
            goto EndSerializeChain;
        }
    }


    //
    // Append the length of the serialized store
    //
    (*ppbChainBuffer)[(*pdwPosition)++] = MSB(StoreBlob.cbData);
    (*ppbChainBuffer)[(*pdwPosition)++] = LSB(StoreBlob.cbData);

    //
    // Actually serialize the store
    //
    StoreBlob.cbData = *pdwBufferSize - *pdwPosition;
    StoreBlob.pbData = *ppbChainBuffer + *pdwPosition;

    if ( !CertSaveStore( hMemStore,
                         0,
                         CERT_STORE_SAVE_AS_STORE,
                         CERT_STORE_SAVE_TO_MEMORY,
                         (VOID *) &StoreBlob,
                         0 ) )
    {
        hRes =  RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializeChain;
    }
    (*pdwPosition) += StoreBlob.cbData;


EndSerializeChain:

    CertCloseStore( hMemStore,
                    0 );

    return (hRes);
}


HRESULT SerializeCTL( PCCTL_CONTEXT pcCTL,
                      LIST_ENTRY *pCTLCertChain,
                      PCCERT_CONTEXT pcSigner,
                      PBYTE *ppbReplicationBuffer,
                      DWORD *pdwBufferSize,
                      DWORD *pdwPosition )
/*++

Routine Description:

    Serializes a CTL for replication. First, the CTL, the certs in the CTL and the CTL
    signer are added to an in-memory CAPI store; the store is then serialized to memory. 
    Format of buffer :

    CTL=<MSB of length of serialized store><LSB of length of serialized
    store><serialized store>

Arguments:

    pcCTL - CTL to serialize
    pCTLCertChain - chain of certs in CTL
    pcSigner - cert that signed the CTL
    ppbSignature - pointer to pointer to buffer that will hold the signature on success
    pdwBufferSize - pointer to present size of buffer, updated if buffer is resized
    pdwPosition - pointer to where information is to be written to in buffer, updated
    on success

Returns:

    HRESULT indicating success/failure

--*/
{
    HRESULT hRes = S_OK;
    HCERTSTORE hMemStore = NULL;

    //
    // Create the memory store
    //
    if ( !(hMemStore = CertOpenStore( CERT_STORE_PROV_MEMORY,
                                      0,
                                      NULL,
                                      0,
                                      0 ) ) )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    //
    // Add the CTL to the store
    //
    if ( !CertAddCTLContextToStore( hMemStore,
                                    pcCTL,
                                    CERT_STORE_ADD_ALWAYS,
                                    NULL ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        CertCloseStore( hMemStore,
                        0 );
        return hRes;
    }

    //
    // Add the signer to the store
    //
    if ( !CertAddCertificateContextToStore( hMemStore,
                                            pcSigner,
                                            CERT_STORE_ADD_ALWAYS,
                                            NULL ) ) 
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        CertCloseStore( hMemStore,
                        0 );
        return hRes;
    }


    //
    // Add all the certs in the chain to the store
    //
    LIST_ENTRY *pLink;
    CertChainEntry *pChainEntry;
    PCCERT_CONTEXT pcFirstCert = NULL;

    for ( pLink = pCTLCertChain->Flink;
          pLink != pCTLCertChain;
          pLink = pLink->Flink )
    {
        pChainEntry = CONTAINING_RECORD( pLink, CertChainEntry, ListEntry );

        if ( !CertAddCertificateContextToStore( hMemStore,
                                                pChainEntry->pcCert,
                                                CERT_STORE_ADD_REPLACE_EXISTING,
                                                NULL ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            CertCloseStore( hMemStore,
                            0 );
            return ( hRes );
        }
    }


    //
    // Figure out the size of the serialized store
    //
    CRYPT_DATA_BLOB StoreBlob;
    DWORD cbSpaceLeft = 0;
    DWORD cbSpaceNeeded = 0;
    
    StoreBlob.pbData = NULL;
    StoreBlob.cbData = 0;
    
    if ( !CertSaveStore( hMemStore,
                         0,
                         CERT_STORE_SAVE_AS_STORE,
                         CERT_STORE_SAVE_TO_MEMORY,
                         (VOID *) &StoreBlob,
                         0 ) &&
         GetLastError() != ERROR_MORE_DATA )
    {
        CertCloseStore( hMemStore,
                        0 );
        return RETURNCODETOHRESULT( GetLastError() );
    }
    

    //
    // need space for the CTL header, the bytes indicating the size of the store
    // and the store itself
    //
    cbSpaceNeeded = StoreBlob.cbData + CTL_HEADER_SIZE + 2;
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;
    
    if ( ( cbSpaceLeft > cbSpaceNeeded ) ||
         ResizeBuffer( ppbReplicationBuffer,
                       cbSpaceNeeded,
                       pdwBufferSize ) )
    {
        //
        // CTL header
        //
        memcpy( *ppbReplicationBuffer + *pdwPosition, CTL_HEADER, CTL_HEADER_SIZE );
        (*pdwPosition) += CTL_HEADER_SIZE;
        
        //
        // Bytes indicating length of serialized store
        //
        (*ppbReplicationBuffer)[(*pdwPosition)++] = MSB(StoreBlob.cbData);
        (*ppbReplicationBuffer)[(*pdwPosition)++] = LSB(StoreBlob.cbData);
        
        //
        // Actually serialize the store
        //
        StoreBlob.cbData = *pdwBufferSize - *pdwPosition;
        StoreBlob.pbData = *ppbReplicationBuffer + *pdwPosition;
        
        if ( CertSaveStore( hMemStore,
                            0,
                            CERT_STORE_SAVE_AS_STORE,
                            CERT_STORE_SAVE_TO_MEMORY,
                            (VOID *) &StoreBlob,
                            0 ) )
        {
            (*pdwPosition) += StoreBlob.cbData;
        }
        else
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Error 0x%x\n", GetLastError()));
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }
    }
    else
    {
        hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
    }

    if ( hMemStore )
    {
        CertCloseStore( hMemStore,
                        0 );
    }

    return (hRes);
}


HRESULT GetCertChainSignature( IN LIST_ENTRY *pChain,
                               OUT PBYTE *ppbSignature,
                               IN OUT DWORD *pdwBufferSize,
                               IN OUT DWORD *pdwPosition )
/*++

Routine Description:

    Constructs the signature for a chain of certificates. Signature for a chain is :
    CERTS=<# of certs><hash of C1><hash of C2>...<hash of CN>

Arguments:

    pChain - chain of CertChainEntry structures
    ppbSignature - pointer to pointer to buffer that will hold the signature on success
    pdwBufferSize - pointer to present size of buffer, updated if buffer is resized
    pdwPosition - pointer to where information is to be written to in buffer, updated
    on success

Returns:

    HRESULT indicating success/failure

--*/
{
    if ( !pChain )
    {
        return S_FALSE;
    }
    
    //
    // Go through the list and figure out how much space is needed
    //
    LIST_ENTRY *pLink;
    CertChainEntry *pChainEntry;
    DWORD dwSizeNeeded = 0;
    DWORD cCerts = 0;

    for ( pLink = pChain->Flink;
          pLink != pChain;
          pLink = pLink->Flink )
    {
        pChainEntry = CONTAINING_RECORD( pLink, CertChainEntry, ListEntry );

        dwSizeNeeded += SHA1_HASH_SIZE;
        cCerts++;
    }
    dwSizeNeeded += 1; //extra byte to hold # of certs 
    dwSizeNeeded += CERT_HEADER_SIZE;

    //
    // Resize the buffer if necessary 
    //
    if ( (*pdwBufferSize - *pdwPosition) < dwSizeNeeded )
    {
        if ( !ResizeBuffer( ppbSignature,
                            dwSizeNeeded,
                            pdwBufferSize ) )
        {
            return RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
        }
    }

    //
    // First few bytes are cert header
    //
    memcpy(*ppbSignature + *pdwPosition, CERT_HEADER, CERT_HEADER_SIZE );
    (*pdwPosition) += CERT_HEADER_SIZE;

    //
    // Next byte is # of cert hashes 
    //
    (*ppbSignature)[(*pdwPosition)++] = (BYTE) cCerts;

    //
    // Walk the chain and stuff in all the cert hashes
    //
    DWORD dwSize = SHA1_HASH_SIZE;
    DWORD cPresentCert = 0;
    for ( pLink = pChain->Flink;
          pLink != pChain;
          pLink = pLink->Flink )
    {
        pChainEntry = CONTAINING_RECORD( pLink, CertChainEntry, ListEntry );

        if ( !CertGetCertificateContextProperty( pChainEntry->pcCert, 
                                                 CERT_SHA1_HASH_PROP_ID,
                                                 (*ppbSignature) + *pdwPosition,
                                                 &dwSize ) )
        {
            return RETURNCODETOHRESULT( GetLastError() );
        }

        cPresentCert++;
        (*pdwPosition) += SHA1_HASH_SIZE;        
    }
      
    return S_OK;
}


HRESULT GetCTLSignature( PCCTL_CONTEXT pcCTL,
                         LIST_ENTRY *pCTLCertsChain,
                         PCCERT_CONTEXT pcSignerCert,
                         PBYTE *ppbReplicationBuffer,
                         DWORD *pdwBufferSize,
                         DWORD *pdwPosition )
/*++

Routine Description:

    Constructs the signature for a CTL. Signature for CTL is
    CTL=<hash of signer><hash of CTL>

Arguments:

    pcCTL - CTL whose signature is to be generated
    pCTLCertsChain - chain of certificates in CTL
    pcSignerCert - certificate that signed the CTL
    ppbReplicationBuffer - pointer to pointer to buffer that will hold the signature on success
    pdwBufferSize - pointer to present size of buffer, updated if buffer is resized
    pdwPosition - pointer to where information is to be written to in buffer, updated
    on success

Returns:

    HRESULT indicating success/failure

--*/
{
    DWORD cbSpaceLeft = *pdwBufferSize - *pdwPosition;
    DWORD cbSpaceNeeded = 2*SHA1_HASH_SIZE + CTL_HEADER_SIZE;

    HRESULT hRes = S_OK;

    if ( cbSpaceLeft > cbSpaceNeeded ||
         ResizeBuffer( ppbReplicationBuffer,
                       cbSpaceNeeded,
                       pdwBufferSize ) )
    {
        //
        //CTL header
        //
        memcpy( *ppbReplicationBuffer + *pdwPosition, CTL_HEADER, CTL_HEADER_SIZE );
        (*pdwPosition) += CTL_HEADER_SIZE;

        //
        // Hash of signer
        //
        cbSpaceLeft = *pdwBufferSize - *pdwPosition;
        if ( !CertGetCertificateContextProperty( pcSignerCert,
                                                 CERT_SHA1_HASH_PROP_ID,
                                                 *ppbReplicationBuffer + *pdwPosition,
                                                 &cbSpaceLeft ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }
        else 
        {
            DBG_ASSERT( cbSpaceLeft == SHA1_HASH_SIZE );

            (*pdwPosition) += cbSpaceLeft;

            //
            // Hash of CTL itself
            //
            cbSpaceLeft = *pdwBufferSize - *pdwPosition;
            if ( !CertGetCTLContextProperty( pcCTL,
                                             CERT_SHA1_HASH_PROP_ID,
                                             *ppbReplicationBuffer + *pdwPosition,
                                             &cbSpaceLeft ) )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
            }
            else
            {
                DBG_ASSERT( cbSpaceLeft == SHA1_HASH_SIZE );

                (*pdwPosition) += cbSpaceLeft;
            }
        }
    }
    else
    {
        hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
    }

    return (hRes);
}






HRESULT DeserializeInstanceInfo( IN OUT BYTE **ppbPosition,
                                 IN BYTE *pbEnd,
                                 OUT DWORD *pdwInstance )
/*++

Routine Description:

    Deserializes the SSL information for a single server instance

Arguments:

    ppbPosition - pointer to pointer to buffer to deserialize; updated after info for
    instance has been deserialized
    pbEnd - pointer to end of serialized information, to avoid running off the end
    pdwInstance - pointer to instance for which info is being deserialized; updated on
    success

Returns:

    HRESULT indicating success/failure.

--*/
{
    HRESULT hRes = S_OK;
    BYTE *pbPresent = NULL;
    BOOL fHasCertInfo = FALSE;
    BOOL fHasCTLInfo = FALSE;

    //
    // Sanity checks on arguments
    //
    if ( !ppbPosition || !*ppbPosition || !pbEnd ||
         pbEnd < *ppbPosition + MIN_REPLICATION_INFO_SIZE )
    {
        return REPL_INTERNAL_ERROR;
    }
    
    pbPresent = *ppbPosition;
    BYTE bMSB = *(pbPresent++);
    BYTE bLSB = *(pbPresent++);

    *pdwInstance = (DWORD) ( (bMSB << 8) + bLSB );

    DBGPRINTF((DBG_CONTEXT,
               "Deserializing info for instance %d\n",
               *pdwInstance));

    //
    // Next byte should be colon
    //
    if ( !(*pbPresent == COLON) )
    {
        return REPL_INTERNAL_ERROR;
    }
    pbPresent++;

    //
    // Next bit should either be CERT_HEADER or CTL_HEADER
    //
    if ( ( pbEnd > pbPresent + CERT_HEADER_SIZE ) && 
         !memcmp( pbPresent, CERT_HEADER, CERT_HEADER_SIZE ) )
    {
        fHasCertInfo = TRUE;

        //
        //deserialize cert
        //
        if ( FAILED( hRes = DeserializeServerCert( &pbPresent,
                                                   pbEnd ) ) )
        {
            return hRes;
        }
    }

    if ( ( pbEnd > pbPresent + CTL_HEADER_SIZE ) &&
         !memcmp( pbPresent, CTL_HEADER, CTL_HEADER_SIZE ) )
    {
        fHasCTLInfo = TRUE;

        //
        //deserialize CTL
        //
        if ( FAILED( hRes = DeserializeServerCTL( &pbPresent,
                                                  pbEnd ) ) )
        {
            return hRes;
        }
    }


    if ( !fHasCertInfo && !fHasCTLInfo )
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // Check for trailer byte
    //
    if ( !( *pbPresent == INSTANCE_TRAILER_BYTE ) )
    {
        return REPL_INTERNAL_ERROR;
    }
    pbPresent++;

    //
    // Update pointer to indicate where next deserialization has to
    // start
    //
    *ppbPosition = pbPresent;

    return hRes;
}

//
// This should be more focused, but the current prefix docs are quite vague
// about some of the suppression options.
//
// The error being suppressed is a memory leak on the internal error path
// see windows bug 48010 for details. Suppressing now, because this is 
// a broken an rarely used code path. And the error case is a virtual
// impossibility.
//
/* #pragma INTRINSA suppress=all */
HRESULT DeserializeServerCert( IN OUT BYTE **ppbBuffer,
                               IN BYTE *pbEnd )
/*++

Routine Description:

    Deserializes a server certificate [and its attendant chain] and puts the certs in
    the appropriate stores

Format of buffer :

    "CERTS="<SHA1 hash of first cert in chain><MSB of length of private key blob>
     <LSB of length of private key blob><private key info of first cert>
     <MSB of length of serialized store containing chain><LSB of length of serialized store 
     containing chain><serialized store>

Arguments:

     ppbBuffer - pointer to pointer to serialized info, updated on success
     pbEnd - pointer to end of buffer, to avoid overruns

Returns:

    HRESULT indicating success/failure


--*/

{
    HRESULT hRes = S_OK;
    BYTE *pbPresent = *ppbBuffer;
    BYTE rgbHash[SHA1_HASH_SIZE];
    DWORD cbPrivateKey = 0;
    DWORD cbStore = 0;
    BYTE bMSB = 0;
    BYTE bLSB = 0;
    HCERTSTORE hMemStore = NULL;
    CRYPT_KEY_PROV_INFO CKPI;

    pbPresent += CERT_HEADER_SIZE;

    if ( pbEnd <= pbPresent + SHA1_HASH_SIZE )
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // get the hash of the first cert in the chain [server cert]
    //
    memcpy( rgbHash, pbPresent, SHA1_HASH_SIZE );
    pbPresent += SHA1_HASH_SIZE;

    //
    // eXtract the private key
    //
    HCRYPTKEY hPrivateKey = NULL;

    if ( FAILED( hRes = DeserializeAndImportServerPK( &pbPresent,
                                                      pbEnd,
                                                      &hPrivateKey,
                                                      &CKPI ) ) )
    {
        return hRes;
    }


    //
    // figure out length of serialized store
    //
    if ( pbEnd <= pbPresent + 2 ) //two byte header indicating length of serialized store
    {
        return REPL_INTERNAL_ERROR;
    }
    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);

    cbStore = (DWORD) ( (bMSB << 8 ) + bLSB );

    if ( cbStore <= 0 || pbEnd <= pbPresent + cbStore )
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // Unserialize the store containing the certs in the chain
    //
    CRYPT_DATA_BLOB cdbSerializedStore;
    cdbSerializedStore.cbData = cbStore;
    cdbSerializedStore.pbData = pbPresent;

    if ( !( hMemStore = CertOpenStore( CERT_STORE_PROV_SERIALIZED,
                                       0,
                                       NULL,
                                       0,
                                       (PVOID) &cdbSerializedStore ) ) )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }
    pbPresent += cbStore;

    //
    // Deal with certs in the store
    //
    if ( FAILED( hRes = DistributeCerts( hMemStore,
                                         rgbHash,
                                         &CKPI ) ) )
    {
        CertCloseStore( hMemStore,
                        0 );
        return hRes;
    }

    //
    // Update 
    //
    *ppbBuffer = pbPresent;

    //
    // cleanup
    //
    if ( hPrivateKey )
    {
        CryptDestroyKey( hPrivateKey );
    }

    if ( hMemStore )
    {
        CertCloseStore( hMemStore,
                        0 );
    }

    if ( CKPI.cProvParam && CKPI.rgProvParam )
    {
        delete [] CKPI.rgProvParam;
    }

    return hRes;
}


HRESULT DeserializeServerCTL( IN OUT BYTE **ppbBuffer,
                              IN BYTE *pbEnd )
/*++

Routine Description:

    Deserializes a CTL, the certs in the CTL and the CTL signer and places them into the 
    appropriate stores. 
    CTL goes into the TRUST store. Signer goes into ROOT store if self-signed
    and MY store if not. Certs in CTL go into CA store.

    NOTE : currently, the signer will always be the IIS server cert, which is replicated
    separately, so we won't bother doing anything with the signer cert

Format of buffer :

    CTL=<MSB of length of serialized store><LSB of length of serialized
    store><serialized store>

Arguments:

     ppbBuffer - pointer to pointer to serialized info, updated on success
     pbEnd - pointer to end of buffer, to avoid overruns

Returns:

    HRESULT indicating success/failure
--*/

{
    HRESULT hRes = S_OK;
    BYTE *pbPresent = *ppbBuffer;
    DWORD cbStore = 0;
    BYTE bMSB = 0;
    BYTE bLSB = 0;
    HCERTSTORE hMemStore = NULL;
    HCERTSTORE hTrustStore = NULL;
    HCERTSTORE hCAStore = NULL;
    PCCTL_CONTEXT pCTL = NULL;
    PCCTL_CONTEXT pPrevCTL = NULL;
    DWORD dwNumCTLs = 0;
    PCCERT_CONTEXT pCert = NULL;
    PCCERT_CONTEXT pPrevCert = NULL;

    pbPresent += CTL_HEADER_SIZE;

    if ( pbEnd <= pbPresent + 2 ) //bytes indicating length of serialized store
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // figure out length of serialized store
    //
    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);

    cbStore = (DWORD) ( (bMSB << 8 ) + bLSB );

    if ( cbStore <= 0 || pbEnd <= pbPresent + cbStore )
    {
        return REPL_INTERNAL_ERROR;
    }

    //
    // Unserialize the store containing the CTL, certs in the CTL and signer
    //
    CRYPT_DATA_BLOB cdbSerializedStore;
    cdbSerializedStore.cbData = cbStore;
    cdbSerializedStore.pbData = pbPresent;

    if ( !( hMemStore = CertOpenStore( CERT_STORE_PROV_SERIALIZED,
                                       0,
                                       NULL,
                                       0,
                                       (PVOID) &cdbSerializedStore ) ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto end_deserialize_ctl;
    }
    pbPresent += cbStore;

    if ( !( hTrustStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                         0,
                                         NULL,
                                         CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                         TRUST_STORE_NAME ) ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto end_deserialize_ctl;
    }        


    if ( !( hCAStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                      0,
                                      NULL,
                                      CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                      CA_STORE_NAME ) ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto end_deserialize_ctl;
    }

                        
    //
    // Add all the CTLs in the deserialized store to the TRUST store
    //

    while ( ( pCTL = CertEnumCTLsInStore( hMemStore,
                                          pPrevCTL ) ) )
    {
        if ( !CertAddCTLContextToStore(  hTrustStore,
                                         pCTL,
                                         CERT_STORE_ADD_REPLACE_EXISTING,
                                         NULL ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto end_deserialize_ctl;
        }
        pPrevCTL = pCTL;
        dwNumCTLs++;
    }

    DBG_ASSERT( dwNumCTLs == 1 );


    //
    // Add all the certs in the CTL to the CA store
    //
    while ( pCert = CertEnumCertificatesInStore( hMemStore,
                                                 pPrevCert ) )
    {
        if ( !CertAddCertificateContextToStore( hCAStore,
                                                pCert,
                                                CERT_STORE_ADD_NEW,
                                                NULL ) &&
             GetLastError() != CRYPT_E_EXISTS )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto end_deserialize_ctl;
        }
        pPrevCert = pCert;
    }

    //
    // NOTE : in future may want to deal with signer cert as well
    //

    //
    // Update 
    //
    *ppbBuffer = pbPresent;

end_deserialize_ctl:
    //
    // cleanup
    //
    if ( hMemStore )
    {
        CertCloseStore( hMemStore,
                        0 );
    }

    if ( hTrustStore )
    {
        CertCloseStore( hTrustStore,
                        0 );
    }

    if ( hCAStore )
    {
        CertCloseStore( hCAStore,
                        0 );
    }

    return (hRes);
}

HRESULT ConstructCertChain( PCCERT_CONTEXT pcLeafCert,
                            LPWSTR pszLeafCertStore,
                            LIST_ENTRY *pCertChain,
                            PBOOL pfCompleteChain )
/*++

Routine Description:

    Constructs the complete cert chain for the leaf cert passed in

Arguments:

    pcLeafCert - cert for which chain is to be constructed
    pszLeafCertStore - name of store from which pcLeafCert came
    pCertChain - pointer to linked list containing cert chain. The first cert in the 
    list is pcLeafCert.
    pfCompleteChain - set to TRUE if we constructed a full cert chain ie the constructed chain
    ends with a self-signed cert

Returns:

    HRESULT indicating success/failure

Note: 2/18/98 - CAPI2 is supposed to have an API Real Soon Now that will construct a cert chain; 
      until then, we'll roll our own. 

--*/
{
    DBG_ASSERT( pcLeafCert );
    DBG_ASSERT( pCertChain );

    HCERTSTORE hMyStore = NULL;
    HCERTSTORE hCAStore = NULL;
    HCERTSTORE hRootStore = NULL;

    HRESULT    hr = NOERROR;

    CertChainEntry * pLink = NULL;

    PCCERT_CONTEXT pcIssuer = NULL;
    PCCERT_CONTEXT pcPresentLeaf = pcLeafCert;

    DWORD       dwFlags = 0;
    DWORD       dwStoresTried = 0;
    HCERTSTORE  hPresentStore = pcPresentLeaf->hCertStore;
    BOOL        fSystemStore = FALSE;
    LPWSTR      pszPresentStore = pszLeafCertStore;

    *pfCompleteChain = FALSE;

    //
    // Open all the stores we'll search for issuers - MY, CA and ROOT
    //
    hMyStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                              0,
                              NULL,
                              CERT_SYSTEM_STORE_LOCAL_MACHINE,
                              MY_STORE_NAME );

    if ( !hMyStore )
    {
        hr = RETURNCODETOHRESULT( GetLastError() );
        goto cleanup;
    }


    hCAStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                             0,
                             NULL,
                             CERT_SYSTEM_STORE_LOCAL_MACHINE,
                             CA_STORE_NAME );

    if ( !hCAStore )
    {
        hr = RETURNCODETOHRESULT( GetLastError() );
        goto cleanup;
    }

    hRootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                               0,
                               NULL,
                               CERT_SYSTEM_STORE_LOCAL_MACHINE,
                               ROOT_STORE_NAME );

    if ( !hRootStore )
    {
        hr = RETURNCODETOHRESULT( GetLastError() );
        goto cleanup;
    }


    //
    // First link in the chain is the leaf cert
    //
    pLink = new CertChainEntry;
    if( pLink == NULL )
    {
        hr = RETURNCODETOHRESULT( GetLastError() );
        goto cleanup;
    }

    pLink->pcCert = CertDuplicateCertificateContext( pcLeafCert );
    pLink->pszStoreName = mystrdup( pszLeafCertStore );

    InsertHeadList( pCertChain, &pLink->ListEntry );


    //
    // To build the chain, look for issuers in 4 stores : the store the cert came from,
    // and the "MY", "CA" and "ROOT" stores, cycling through the stores as necessary
    //
    *pfCompleteChain = FALSE;

    DBG_ASSERT( SUCCEEDED(hr) );

    while ( 1 )
    {
        //
        // Bail when we get to the top of a chain
        //
        if ( IsSelfSignedCert( pcPresentLeaf ) )
        {
            *pfCompleteChain = TRUE;
            break;
        }

        pcIssuer = CertGetIssuerCertificateFromStore( hPresentStore,
                                                      pcPresentLeaf,
                                                      NULL,
                                                      &dwFlags );

        //
        // Got an issuer in this store
        //
        if ( pcIssuer )
        {
            //
            // Add it to the list
            //
            pLink = new CertChainEntry;
            if( pLink == NULL )
            {
                hr = RETURNCODETOHRESULT( GetLastError() );
                goto cleanup;
            }

            pLink->pcCert = CertDuplicateCertificateContext( pcIssuer );
            pLink->pszStoreName = (fSystemStore ? pszPresentStore : 
                                   mystrdup( pszPresentStore ) );
            pLink->fDynName = !fSystemStore;

            //
            // Need to insert at the -end- of the list to keep ordering, where
            // first cert is the actual server cert
            //
            InsertTailList( pCertChain, &pLink->ListEntry );

            //
            // Set up for next round
            //
            dwStoresTried = 0;
            pcPresentLeaf = pLink->pcCert;
        }
        //
        // No issuer in this store, switch to next store to look in
        //
        else
        {

            dwStoresTried++;

            if ( dwStoresTried == 4 ) //we've tried all the stores, time to bail
            {
                break;
            }

            if ( hPresentStore == hMyStore )
            {
                hPresentStore = hCAStore;
                pszPresentStore = CA_STORE_NAME;
                fSystemStore = TRUE;
            }
            else if ( hPresentStore == hCAStore )
            {
                hPresentStore = hRootStore;
                pszPresentStore = ROOT_STORE_NAME;
                fSystemStore = TRUE;
            }
            else if ( hPresentStore == hRootStore )
            {
                hPresentStore = pcPresentLeaf->hCertStore;
                pszPresentStore = pszLeafCertStore;
                fSystemStore = FALSE;
            }
            else
            {
                hPresentStore = hMyStore;
                pszPresentStore = MY_STORE_NAME;
                fSystemStore = TRUE;
            }
        }
    } //while ( 1 )

    DBG_ASSERT( SUCCEEDED(hr) );

cleanup:

    //
    // Cleanup
    //
    if( hMyStore != NULL )
    {
        CertCloseStore( hMyStore, 0 );
    }

    if( hCAStore != NULL )
    {
        CertCloseStore( hCAStore, 0 );
    }

    if( hRootStore != NULL )
    {
        CertCloseStore( hRootStore, 0 );
    }

    return hr;
}


HRESULT BuildCTLDescription( IN PCCTL_CONTEXT pcCTL,
                             OUT LIST_ENTRY *pCTLCerts,
                             OUT PCCERT_CONTEXT *ppcSigner )
/*++

Routine Description:

   Builds a full description of a CTL - all the certs in the CTL as well as the CTL
   signer

Arguments:

   pcCTL - CTL whose description is to be built
   pCTLCerts - linked list that will have the certs in the CTL inserted into it
   ppcSigner - pointer to pointer to cert that signed the CTL

Returns:

   HRESULT indicating success/failure

--*/
{
    //
    // First try to get all the certs in the CTL
    //
    PCTL_INFO pCtlInfo = pcCTL->pCtlInfo;
    HRESULT hRes;

    if ( !pCtlInfo->cCTLEntry )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Herm. Nothing in CTL ? \n"));

        return S_FALSE;
    }
    else
    {
        HCERTSTORE ahStores[3];
        memset( ahStores, 0, sizeof(ahStores) );
        DWORD dwNumStores = sizeof(ahStores)/sizeof(HCERTSTORE);
        PCCERT_CONTEXT pcCert = NULL;
        BOOL fFoundCert = FALSE;
        DWORD dwIndex = 0;
        DWORD dwStoreIndex = 0;

        //
        // Try the MY, CA and ROOT stores for local machine
        //
        if ( !(ahStores[0] = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                         0,
                                         NULL,
                                         CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                         MY_STORE_NAME ) ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Failed to open MY store : 0x%x\n", GetLastError()));
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto ctl_cleanup;
        }


        if ( !(ahStores[1] = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                         0,
                                         NULL,
                                         CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                         CA_STORE_NAME ) ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Failed to open CA store : 0x%x\n", GetLastError()));
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto ctl_cleanup;
        }

        if ( !(ahStores[2] = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                         0,
                                         NULL,
                                         CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                         ROOT_STORE_NAME ) ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Failed to open ROOT store : 0x%x\n", GetLastError()));
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto ctl_cleanup;
        }

        //
        // Iterate through the certs in the CTL. For each cert, try to find it
        // by SHA1 hash in the supplied stores. 
        //
        
        for ( dwIndex = 0; dwIndex < pCtlInfo->cCTLEntry; dwIndex++ )
        {
            fFoundCert = FALSE;
            for ( dwStoreIndex = 0; dwStoreIndex < dwNumStores; dwStoreIndex++ )
            {
                if ( pcCert = CertFindCertificateInStore( ahStores[dwStoreIndex],
                                                          X509_ASN_ENCODING,
                                                          0,
                                                          CERT_FIND_SHA1_HASH,
                                   (VOID *) &(pCtlInfo->rgCTLEntry[dwIndex].SubjectIdentifier),
                                                          NULL ) )
                {
                    fFoundCert = TRUE;
                    break;
                }
            }

            //
            // Couldn't find one of the certs in the CTL, error
            //
            if ( !fFoundCert )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
                goto ctl_cleanup; 
            }
            else
            {
                CertChainEntry *pEntry = new CertChainEntry;
                
                if( pEntry == NULL )
                {
                    hRes = RETURNCODETOHRESULT( GetLastError() );
                    goto ctl_cleanup;
                }

                pEntry->pcCert = pcCert;
                pEntry->fDynName = FALSE;

                switch( dwStoreIndex )
                {
                case 0:
                    pEntry->pszStoreName = MY_STORE_NAME;
                    break;
                case 1:
                    pEntry->pszStoreName = CA_STORE_NAME;
                    break;
                case 2:
                    pEntry->pszStoreName = ROOT_STORE_NAME;
                    break;
                default:
                    //
                    // should never get here
                    DBG_ASSERT( TRUE );
                    break;
                }

                InsertTailList( pCTLCerts, &pEntry->ListEntry );
            }
        }

        //
        // Find the CTL signer
        //
        if ( !CryptMsgGetAndVerifySigner( (HCRYPTMSG) pcCTL->hCryptMsg,
                                          dwNumStores,
                                          ahStores,
                                          CMSG_TRUSTED_SIGNER_FLAG | CMSG_SIGNER_ONLY_FLAG,
                                          ppcSigner,
                                          NULL ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }

    ctl_cleanup:
        for ( dwStoreIndex = 0; dwStoreIndex < dwNumStores; dwStoreIndex++ )
        {
            if ( ahStores[dwStoreIndex] )
            {
                CertCloseStore( ahStores[dwStoreIndex],
                                0 );
            }
        }
    } //else 

    return (hRes);
}


VOID FreeCertChain( IN OUT LIST_ENTRY *pChain )
/*++

Routine Description:

    Free resources associated with a cert chain

Arguments:

    pChain - chain to be cleaned up

Returns:

    Nothing

--*/
{
    if ( !pChain )
    {
        return;
    }

    CertChainEntry *pChainEntry;

    while ( !IsListEmpty( pChain ) )
    {
        pChainEntry = CONTAINING_RECORD ( pChain->Flink,
                                          CertChainEntry,
                                          ListEntry );
        RemoveEntryList( &(pChainEntry->ListEntry) );

        if ( pChainEntry->pcCert )
        {
            CertFreeCertificateContext( pChainEntry->pcCert );
        }
        
        if ( pChainEntry->pszStoreName && pChainEntry->fDynName )
        {
            delete [] pChainEntry->pszStoreName;
        }
    }
}


BOOL IsSelfSignedCert( IN PCCERT_CONTEXT pCertContext )
/*++

Routine Description:

    Determines whether a cert is self-signed ie the top of a hierarchy

Arguments:

    pCertContext - cert to be checked

Returns:

    TRUE if cert is self-signed, FALSE otherwise

--*/

{
    //
    // Compare subject and issuer.
    //
    if(pCertContext->pCertInfo->Subject.cbData == pCertContext->pCertInfo->Issuer.cbData)
    {
        if(memcmp(pCertContext->pCertInfo->Subject.pbData,
                  pCertContext->pCertInfo->Issuer.pbData,  
                  pCertContext->pCertInfo->Issuer.cbData) == 0)
        {
            return TRUE;
        }
    }

    return FALSE;
}

BOOL IsReplicableCert( IN PCCERT_CONTEXT pCert )
/*++

Routine Description:

   Checks whether a cert can be replicated or not. Eg Fortezza certs can't be replicated
   because they use a machine-specific hardware token

Arguments:

    pCert - cert to be checked

Returns:

    TRUE if cert can be replicated, FALSE if not

--*/
{
    if ( IsFortezzaCert( pCert ) )
    {
        return FALSE;
    }

    return TRUE;
}

BOOL IsFortezzaCert( IN PCCERT_CONTEXT pCert )
/*++

Routine Description:

   Checks whether a cert is a Fortezza certificate.

Arguments:

    pCert - cert to be checked

Returns:

    TRUE if it is, FALSE if not 

--*/
{
    PSTR pszOid = NULL;

    // Q - is there a better way to do this?

    pszOid = pCert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId;

    if(pszOid)
    {
        if(strcmp(pszOid, szOID_INFOSEC_mosaicUpdatedSig)  == 0 ||
           strcmp(pszOid, szOID_INFOSEC_mosaicKMandUpdSig) == 0)
        {
            return TRUE;
        }
    }

    return FALSE;
}

HRESULT ReadServerCTL( IN IMSAdminBase *pMB,
                       IN METADATA_HANDLE hHandle,
                       IN LPCWSTR pszPath,
                       OUT PCCTL_CONTEXT *ppcCTL )
/*++

Routine Description:

    Tries to construct a certificate based on information in metabase

Arguments:

    pMB - pointer to MB object
    hHandle - handle opened for reading
    pszPath - path under which info is stored, relative to hHandle
    ppcCTL - pointer to pointer to CTL context, updated if CTL was found


Returns:

    HRESULT indicating success/failure

--*/

{
    DBG_ASSERT( pMB );

    METADATA_RECORD mdr;
    HRESULT hRes;
    LPWSTR pwszListIdentifier = NULL;
    DWORD dwIdentifierSize = 0;
    OPEN_CERT_STORE_INFO *pCertStoreInfo = NULL;
    HCERTSTORE hStore = NULL;

    //
    // Read CTL list identifier out of metabase
    //
    MD_SET_DATA_RECORD( &mdr, MD_SSL_CTL_IDENTIFIER,
                        METADATA_NO_ATTRIBUTES,
                        IIS_MD_UT_SERVER, BINARY_METADATA, NULL,
                        0 );

    if ( FAILED( hRes = RetrieveBlobFromMetabase( pMB,
                                                  hHandle,
                                                  pszPath,
                                                  &mdr,
                                                  0 ) ) )
    {
        return hRes;
    }
    else
    {
        pwszListIdentifier = (LPWSTR) mdr.pbMDData;
        dwIdentifierSize = mdr.dwMDDataLen;

#if DBG
        CHAR achListId[255];
        if ( WideCharToMultiByte( CP_ACP,
                                  0,
                                  pwszListIdentifier,
                                  -1,
                                  achListId,
                                  254,
                                  NULL,
                                  NULL ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "List id : %s\n", achListId));
        }
#endif
    }

    //
    // Read cert store info out of MB, and try to reconstruct CTL context
    //
    if ( (pCertStoreInfo = ReadCertStoreInfoFromMB( pMB,
                                                    hHandle,
                                                    pszPath,
                                                    TRUE ) ) )
    {
        if ( !( hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                        0,
                                        NULL,
                                        CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                        pCertStoreInfo->pszStoreName ) ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }            
        else
        {
            CTL_FIND_USAGE_PARA CtlFindUsagePara;
            memset(&CtlFindUsagePara, 0, sizeof(CtlFindUsagePara));
            
            CtlFindUsagePara.cbSize = sizeof(CtlFindUsagePara);
            CtlFindUsagePara.ListIdentifier.cbData = dwIdentifierSize;
            CtlFindUsagePara.ListIdentifier.pbData = (PBYTE) pwszListIdentifier;

            //
            // Try to find CTL in specified store
            //
            *ppcCTL = CertFindCTLInStore( hStore,
                                          X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                          0,
                                          CTL_FIND_USAGE,
                                          (LPVOID) &CtlFindUsagePara,
                                          NULL );
            
            if ( !*ppcCTL )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
            }
        }
    }
    else
    {
        hRes = S_FALSE;
    }


    if ( hStore )
    {
        CertCloseStore( hStore,
                        0 );
    }

    if ( pCertStoreInfo )
    {
        DeallocateCertStoreInfo( pCertStoreInfo );
    }

    if ( pwszListIdentifier )
    {
        delete [] pwszListIdentifier;
    }

    return hRes;

}

HRESULT ReadServerCert( IN IMSAdminBase *pMB,
                        IN METADATA_HANDLE hHandle,
                        IN LPCWSTR pszPath,
                        OUT PCCERT_CONTEXT *ppcCertContext,
                        OUT OPEN_CERT_STORE_INFO **ppStoreInfo )
/*++

Routine Description:

    Tries to construct a certificate based on information in metabase

Arguments:

    pMB - pointer to MB object
    hHandle - handle opened for reading
    pszPath - path under which info is stored, relative to hHandle
    ppcCertContext - pointer to pointer to cert context, updated if cert was found
    ppStoreInfo - pointer to pointer to cert store information stored in metabase; updated if
    successful

Returns:

    HRESULT indicating success/failure

--*/

{
    DBG_ASSERT( pMB );

    METADATA_RECORD mdr;
    PBYTE pbCertHash = NULL;
    DWORD dwHashSize = 0;
    HRESULT hRes;

    //
    // Get the hash of the cert
    //
    MD_SET_DATA_RECORD( &mdr, MD_SSL_CERT_HASH, METADATA_NO_ATTRIBUTES,
                        IIS_MD_UT_SERVER, BINARY_METADATA, NULL, 0 );
    

    if ( FAILED( hRes = RetrieveBlobFromMetabase( pMB,
                                                  hHandle,
                                                  pszPath,
                                                  &mdr,
                                                  SHA1_HASH_SIZE ) ) )
    {
        return hRes;
    }
    else
    {
        DBG_ASSERT( mdr.dwMDDataLen == SHA1_HASH_SIZE );

        pbCertHash = mdr.pbMDData;
        dwHashSize = mdr.dwMDDataLen;
    }

    //
    // Get all the info necessary to open the store in which the cert is
    //
    *ppStoreInfo = ReadCertStoreInfoFromMB( pMB,
                                            hHandle,
                                            pszPath,
                                            FALSE );

    if ( !*ppStoreInfo )
    {
        delete [] pbCertHash;
        return S_FALSE;
    }

    //
    // Got all the info, reconstruct the certificate
    //

#if 0
    //
    // Open the appropriate store
    //
    if ( !CryptAcquireContext( &m_hCryptProv,
                               m_pszContainer,
                               m_pszProvider,
                               m_dwProvType,
                               m_dwFlags ) )
    {
        goto EndReadServerCert;
    }
    
    
    if ( !(m_hCertStore = CertOpenSystemStore( m_hCryptProv,
                                               m_pszStoreName )))
    {
        m_dwStatus = CERT_ERR_CAPI;
        goto EndRetrieveCertContext;
    }
#else
    if ( !((*ppStoreInfo)->hCertStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                                       0,
                                                       NULL,
                                                       CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                                       (*ppStoreInfo)->pszStoreName ) ) )

    {
        DeallocateCertStoreInfo( *ppStoreInfo );
        delete [] pbCertHash;
        return S_FALSE;
    }

#endif 


    //
    // Try to find the cert in the store
    //
    CRYPT_HASH_BLOB HashBlob;
    HashBlob.cbData = dwHashSize;
    HashBlob.pbData = pbCertHash;
    
    *ppcCertContext = CertFindCertificateInStore( (*ppStoreInfo)->hCertStore,
                                                 X509_ASN_ENCODING,
                                                 0,
                                                 CERT_FIND_SHA1_HASH,
                                                 (VOID *) &HashBlob,
                                                 NULL );
    

    if ( !*ppcCertContext )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
    }
    else
    {
        hRes = S_OK;
    }

    //
    // Clean up
    //
    delete [] pbCertHash;

    return hRes;
}

BOOL ResizeBuffer( IN OUT BYTE **ppbBuffer,
                   IN DWORD dwMinResize,
                   IN OUT DWORD *pdwPresentSize )
/*++

Routine Description:

    Used to resize a buffer

Arguments:

    ppbBuffer - pointer to pointer to buffer to be resized, updated on success
    dwMinResize - minimum amount to resize by
    pdwPresentSize - pointer to buffer size, updated on success

Returns:

    TRUE if resize succeeds, FALSE if it fails

--*/

{
    DWORD dwActualResize = 0;
    DWORD dwDoubleSize = 2* (*pdwPresentSize);
    DWORD dwExtendedSize = *pdwPresentSize + dwMinResize;
    BYTE *pbNewBuffer = NULL;

    //
    // Amt of memory to resize to is maximum of
    // present size + dwMinResize, 2 * present size, INITIAL_BUFFER_SIZE
    //

    dwActualResize = ( INITIAL_BUFFER_SIZE > dwDoubleSize ? 
                      ( INITIAL_BUFFER_SIZE > dwExtendedSize ? 
                        INITIAL_BUFFER_SIZE : dwExtendedSize ) :
                       ( dwExtendedSize > dwDoubleSize  ? dwExtendedSize : dwDoubleSize ) );

    //
    // totally new buffer
    //
    if ( !*ppbBuffer )
    {
        
        *ppbBuffer = new BYTE[ dwActualResize ];

        if ( !*ppbBuffer )
        {
            SetLastError( ERROR_OUTOFMEMORY );
            return FALSE;
        }
        
    }
    else // resize of existing buffer
    {

        pbNewBuffer = new BYTE[ dwActualResize ];

        if ( !pbNewBuffer )
        {
            SetLastError( ERROR_OUTOFMEMORY );
            return FALSE;
        }

        //
        // Copy the old buffer
        //
        memcpy( pbNewBuffer, *ppbBuffer, *pdwPresentSize );

        //
        // Zero out the old buffer and delete it
        //
        memset( *ppbBuffer, 0, *pdwPresentSize );
        delete [] *ppbBuffer;


        *ppbBuffer = pbNewBuffer;
    }
    
    *pdwPresentSize = dwActualResize;
    
    return TRUE;
}


HRESULT RetrieveBlobFromMetabase( IN IMSAdminBase *pMB,
                                  IN METADATA_HANDLE hHandle,
                                  IN LPCWSTR pszPath,
                                  IN OUT PMETADATA_RECORD pMDR,
                                  IN DWORD dwSizeHint OPTIONAL)
/*++

Routine Description:

    Tries to retrieve a value of variable length from the metabase

Arguments:

    pMB - pointer to MB object
    hHandle - handle open for reading
    pszPath - path relative to hHandle
    pMDR - pointer to metadata record to be used when reading the value. The pbMDData member
    will be updated on success
    dwSizeHint - if caller has idea of how big value might be, can set this to number of 
    bytes to try first retrieval call with

Returns:

   HRESULT indicating whether value was read successfully

--*/
{
    HRESULT hRes = S_OK;
    DWORD dwRequiredSize = 0;

    //
    // If caller has a hint about how big the buffer might need to be, let's use it
    //
    if ( dwSizeHint )
    {
        pMDR->pbMDData = new UCHAR[dwSizeHint];

        if ( !(pMDR->pbMDData) )
        {
            return RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
        }
    }

    pMDR->dwMDDataLen = (dwSizeHint ? dwSizeHint : 0);

    hRes = pMB->GetData( hHandle,
                         pszPath,
                         pMDR,
                         &dwRequiredSize );
                             
                             
    if ( FAILED(hRes) )
    {
        //
        // If buffer wasn't big enough, let's try again ...
        //
        if ( HRESULTTOWIN32(hRes) ==  ERROR_INSUFFICIENT_BUFFER )
        {
            //
            // We were brought up well, so we'll clean stuff up
            //
            if ( dwSizeHint )
            {
                delete [] pMDR->pbMDData;
            }

            pMDR->pbMDData = new UCHAR[dwRequiredSize];
            if ( !(pMDR->pbMDData) )
            {
                return RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
            }
            pMDR->dwMDDataLen = dwRequiredSize;

            hRes = pMB->GetData( hHandle,
                                 pszPath,
                                 pMDR,
                                 &dwRequiredSize );

            if ( FAILED(hRes) )
            {
                //ah, sod it, can't do anymore
                delete [] pMDR->pbMDData;
                return hRes;
            }
        }
    }

    if ( FAILED(hRes) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "RetrieveBlobFromMB failed, 0x%x\n", HRESULTTOWIN32( hRes )));
    }

    return (hRes);
} //RetrieveBlobFromMetabase

OPEN_CERT_STORE_INFO* ReadCertStoreInfoFromMB( IN IMSAdminBase *pMB,
                                               IN METADATA_HANDLE hHandle,
                                               IN LPCWSTR pszPath,
                                               IN BOOL fCTL )
/*++

Routine Description:

    Read all the information necessary to open a CAPI store out of the metabase

Arguments:

    pMDObject - pointer to metabase object 
    hHandle - handle opened for reading
    pszPath - path relative to hHandle
    fCTL - bool indicating whether info is to be used to reconstruct a CTL or a cert 

Returns:

   Pointer to filled out OPEN_CERT_STORE_INFO structure on success, NULL on failure.
   Note that only some of the OPEN_CERT_STORE_INFO fields are -required-; currently,
   only the store name is required. 

--*/

{
    DBG_ASSERT( pMB );
    DBG_ASSERT( pszPath );

    BOOL fSuccess = FALSE;
    OPEN_CERT_STORE_INFO *pCertStoreInfo = NULL;

    pCertStoreInfo = AllocateCertStoreInfo();

    if ( !pCertStoreInfo )
    {
        return NULL;
    }

    DWORD dwReqDataLen = 0;
    METADATA_RECORD mdr;
     
    //
    //Try to retrieve container
    //
    MD_SET_DATA_RECORD(&mdr, 
                       (fCTL ? MD_SSL_CTL_CONTAINER : MD_SSL_CERT_CONTAINER), 
                       METADATA_NO_ATTRIBUTES, 
                       IIS_MD_UT_SERVER, STRING_METADATA, 
                       NULL,
                       0);
        
    if ( !FAILED( RetrieveBlobFromMetabase(pMB,
                                           hHandle,
                                           pszPath,
                                           &mdr) ) )
        
    {
        //
        // Metabase will return empty string if NULL string is stored
        //
        if ( !wcscmp( (LPWSTR) mdr.pbMDData, TEXT("")) )
        {
            delete [] mdr.pbMDData;
            pCertStoreInfo->pszContainer = NULL;
        }
        else
        {
            pCertStoreInfo->pszContainer = (LPWSTR) mdr.pbMDData;
        }
    }

    //
    //Try to retrieve cert provider
    //
    MD_SET_DATA_RECORD(&mdr, 
                       (fCTL ? MD_SSL_CTL_PROVIDER : MD_SSL_CERT_PROVIDER), 
                       METADATA_NO_ATTRIBUTES, 
                       IIS_MD_UT_SERVER, STRING_METADATA, 
                       NULL,
                       0);
        
    if ( !FAILED( RetrieveBlobFromMetabase(pMB,
                                           hHandle,
                                           pszPath,
                                           &mdr) ) )
         
    {
        //
        // Metabase will return empty string if NULL string is stored
        //
        if ( !wcscmp( (LPWSTR) mdr.pbMDData, TEXT("")) )
        {
            delete [] mdr.pbMDData;
            pCertStoreInfo->pszProvider = NULL;
        }
        else
        {
            pCertStoreInfo->pszProvider = (LPWSTR) mdr.pbMDData;
        }
    }

    //
    //Try to retrieve provider type
    //
    MD_SET_DATA_RECORD(&mdr,
                       (fCTL ? MD_SSL_CTL_PROVIDER_TYPE : MD_SSL_CERT_PROVIDER_TYPE),
                       METADATA_NO_ATTRIBUTES,
                       IIS_MD_UT_SERVER, DWORD_METADATA,
                       NULL,
                       0);

    if ( !FAILED( RetrieveBlobFromMetabase( pMB,
                                            hHandle,
                                            pszPath,
                                            &mdr ) ) )
    {
        pCertStoreInfo->dwProvType = * ( (DWORD * ) mdr.pbMDData );
    }

    //
    //Retrieve open flags
    //
    MD_SET_DATA_RECORD( &mdr,
                        (fCTL ? MD_SSL_CTL_OPEN_FLAGS : MD_SSL_CERT_OPEN_FLAGS),
                        METADATA_NO_ATTRIBUTES,
                        IIS_MD_UT_SERVER, DWORD_METADATA,
                        NULL,
                        0 );

    if ( !FAILED( RetrieveBlobFromMetabase( pMB,
                                            hHandle,
                                            pszPath,
                                            &mdr ) ) )
    {
        pCertStoreInfo->dwFlags = * ( (DWORD * ) mdr.pbMDData );
    }

    //
    //Try to retrieve store name
    //
    MD_SET_DATA_RECORD(&mdr, 
                       (fCTL ? MD_SSL_CTL_STORE_NAME : MD_SSL_CERT_STORE_NAME), 
                       METADATA_NO_ATTRIBUTES, 
                       IIS_MD_UT_SERVER, STRING_METADATA, 
                       NULL,
                       0);
    
    if ( FAILED( RetrieveBlobFromMetabase( pMB,
                                           hHandle,
                                           pszPath,
                                           &mdr ) ) )
    {
        goto EndReadStoreInfo;
    }
    else
    {
        //
        // Metabase will return empty string if NULL string is stored, but
        // empty name is -NOT- valid !
        //
        if ( !wcscmp( (LPWSTR) mdr.pbMDData, TEXT("")) )
        {
            delete [] mdr.pbMDData;
            goto EndReadStoreInfo;
        }
        else
        {
            pCertStoreInfo->pszStoreName = (LPWSTR) mdr.pbMDData;
        }
    }

    //
    // Everything succeeded
    //
    fSuccess = TRUE;

EndReadStoreInfo:

    if ( !fSuccess )
    {
        DeallocateCertStoreInfo( pCertStoreInfo );
        pCertStoreInfo = NULL;
    }

    return ( pCertStoreInfo );
}

HRESULT RegenerateSessionKey( IMSAdminBase *pMB,
                              OUT HCRYPTKEY *phKey )
/*++

Routine Description:

   Generate a session key to encrypt/decrypt replication information

Arguments:

   pMB - pointer to metabase object 
   phKey - pointer to key, filled in on success

Returns:

   HRESULT indicating success/failure

--*/

{
#ifdef NO_ENCRYPTION

    return S_OK;

#else 

    HCRYPTPROV hProv = NULL;
    HRESULT hRes = S_OK;
    BYTE *pbHash = NULL;
    DWORD cbHashSize = 0;
    HCRYPTHASH hHash = NULL;
    BOOL fOk = TRUE;
    METADATA_HANDLE hMDHandle;
    ALG_ID aiAlg = 0;
    BYTE *pbSeed = NULL;
    DWORD cbSeed = 0;
    DWORD cbRandomSize = 0;
    BYTE *pbRandom = NULL;
    BYTE bMajor = 0;
    BYTE bMinor = 0;
    DWORD i = 0;
    //
    // Retrieve the key seed
    //
    if ( FAILED( hRes = ReadSessionKeySeed( pMB,
                                            &pbSeed,
                                            &cbSeed ) ) ||
         !cbSeed ||
         cbSeed < SEED_HEADER_SIZE + 1 ) //total length must be greater than header

    {
        hRes = S_FALSE;
        goto regenerate_exit;
    }
    

    bMajor = pbSeed[i++];
    bMinor = pbSeed[i++];
    memcpy( &aiAlg, pbSeed + i, sizeof(ALG_ID) );
    i += sizeof(ALG_ID);
    cbRandomSize = (DWORD) pbSeed[i++];
    pbRandom = pbSeed + i;

    DBG_ASSERT( i == SEED_HEADER_SIZE );

    //
    // Make sure encoded size of random seed is same as the random seed we're actually going
    // to use, and non-zero
    if ( cbRandomSize <= 0 ||
         cbRandomSize != (cbSeed - SEED_HEADER_SIZE) )
    {
        hRes = S_FALSE;
        goto regenerate_exit;
        
    }
            
    //
    // clear out any old session keys
    //
    CryptAcquireContext( &hProv,
                         REPLICATION_SESSION_KEY_CONTAINER,
                         NULL,
                         PROV_RSA_FULL,
                         CRYPT_DELETEKEYSET | CRYPT_MACHINE_KEYSET );

    if ( fOk = CryptAcquireContext( &hProv,
                                    REPLICATION_SESSION_KEY_CONTAINER,
                                    NULL,
                                    PROV_RSA_FULL,
                                    CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET ) )
    {
        if ( SUCCEEDED( hRes = GenerateHash( &hProv,
                                             aiAlg,
                                             pbRandom,
                                             cbRandomSize,
                                             &pbHash,
                                             &cbHashSize,
                                             &hHash ) ) )
        {
            fOk = CryptDeriveKey( hProv,
                                  CALG_RC4,
                                  hHash,
                                  0,
                                  phKey );
        }
    }

    if ( SUCCEEDED(hRes) && !fOk )
    {
        hRes = (GetLastError() != ERROR_SUCCESS) ? 
                    RETURNCODETOHRESULT( GetLastError() ) : E_FAIL;
    }


#if 0
    //
    // Releasing the hProv will invalidate the key
    //
    if ( hProv )
    {
        CryptReleaseContext( hProv,
                             0 );
    }
#endif

regenerate_exit:

    if ( hHash )
    {
        CryptDestroyHash( hHash );
    }

    //
    // Clean out seed used to derive key
    //
    if ( pbSeed )
    {
        memset( pbSeed, 0, cbSeed );
        delete [] pbSeed;
    }

    //
    // Hash buffer contains hash of seed used to derive key, so zero it out as well
    //
    if ( pbHash )
    {
        memset( pbHash, 0, cbHashSize );
        delete [] pbHash;
    }

    return hRes;

#endif // NO_ENCRYPTION
}

VOID DeleteSessionKey( IN HCRYPTKEY *phKey )
{
#ifndef NO_ENCRYPTION
    DeleteKey( phKey,
               REPLICATION_SESSION_KEY_CONTAINER );
#endif
}

VOID DeleteKey( IN HCRYPTKEY *phKey,
                IN LPCWSTR pszContainer )
/*++

Routine Description:

   Delete a key

Arguments:

   phKey - pointer to key to delete 
   pszContainer - name of key container
Returns:

   Nothing

--*/

{
    //
    // delete the key container
    //
    HCRYPTPROV hProv;
    CryptAcquireContext( &hProv,
                         pszContainer,
                         NULL,
                         PROV_RSA_FULL,
                         CRYPT_DELETE_KEYSET | CRYPT_MACHINE_KEYSET );

    //
    // delete the actual key
    //
    if ( phKey )
    {
        CryptDestroyKey( *phKey );
        *phKey = NULL;
    }

}


HRESULT ReadSessionKeySeed( IN IMSAdminBase *pMB,
                            OUT BYTE **ppbSeed,
                            OUT DWORD *pcbSeed )
/*++

Routine Description:

    Reads session key seed information out of metabase

Arguments:

    pMB - pointer to metabase interface to use
    ppbSeed - pointer to pointer to buffer updated with session key seed
    pcbSeed - pointer to DWORD holding size of seed 

Returns:

    HRESULT status

--*/
{
    HRESULT hRes = S_OK;
    METADATA_HANDLE hMDHandle;
    *pcbSeed = 0;
    
    if ( FAILED( hRes = pMB->OpenKey( METADATA_MASTER_ROOT_HANDLE,
                                      MB_ROOT_PATH,
                                      METADATA_PERMISSION_READ,
                                      TIMEOUT_VALUE,
                                      &hMDHandle ) ) )
    {
        return hRes;
    }

    //
    // Figure out the size of the seed
    //
    METADATA_RECORD mdr;    
    MD_SET_DATA_RECORD( &mdr,
                        MD_SSL_REPLICATION_INFO,
                        METADATA_SECURE,
                        IIS_MD_UT_SERVER,
                        BINARY_METADATA,
                        0,
                        NULL );

    hRes = pMB->GetData( hMDHandle,
                         MB_REPLICATION_PATH,
                         &mdr,
                         pcbSeed );

    if ( HRESULTTOWIN32( hRes ) != ERROR_INSUFFICIENT_BUFFER )
    {
        return hRes;
    }

    //
    // We know the size, allocate some memory
    //
    DBG_ASSERT( *pcbSeed );

    *ppbSeed = NULL;
    *ppbSeed = new BYTE[*pcbSeed];

    if ( !(*ppbSeed) )
    {
        return RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
    }

    //
    // Get the random seed out of the metabase
    //

    MD_SET_DATA_RECORD( &mdr,
                        MD_SSL_REPLICATION_INFO,
                        METADATA_SECURE,
                        IIS_MD_UT_SERVER,
                        BINARY_METADATA,
                        *pcbSeed,
                        *ppbSeed );

    hRes = pMB->GetData( hMDHandle,
                         MB_REPLICATION_PATH,
                         &mdr,
                         pcbSeed );

    //
    // cleanup
    //
    pMB->CloseKey( hMDHandle );

    return hRes;
}

HRESULT DeleteMBSessionKeyInfo ( IN IMSAdminBase *pMB )
/*++

Routine Description:

   Delete the metabase information used to derive a session key

Arguments:

    None

Returns:

    HRESULT status

--*/
{
#ifdef NO_ENCRYPTION
    
    return S_OK;

#else

    METADATA_HANDLE hMDHandle;
    HRESULT hRes = S_OK;

    if ( FAILED( hRes = pMB->OpenKey( METADATA_MASTER_ROOT_HANDLE,
                                      MB_ROOT_PATH,
                                      METADATA_PERMISSION_WRITE,
                                      TIMEOUT_VALUE,
                                      &hMDHandle ) ) )
    {
        return hRes;
    }


    hRes = pMB->DeleteKey( hMDHandle,
                           MB_REPLICATION_PATH );


    pMB->CloseKey( hMDHandle );

    return hRes;

#endif //NO_ENCRYPTION

}

HRESULT ExportAndSerializeServerPK( IN PCCERT_CONTEXT pcCert,
                                    IN OUT BYTE **ppbChainBuffer,
                                    IN OUT DWORD *pdwBufferSize,
                                    IN OUT DWORD *pdwPosition )
/*++

Routine Description:

   Export and serialize a private key for a server certificate. Format of serialized buffer :

   <MSB of private key length><LSB of private key length><private key><key container info>

Arguments:

   pcCert - certificate whose private key is to be exported
   ppbChainBuffer - pointer to pointer to buffer that is to hold serialized key
   pdwBufferSize - size of buffer pointed to by *ppbChainBuffer
   pdwPosition - position in buffer 

Returns:

   HRESULT indicating success/failure

--*/

{
    HCRYPTKEY hKey = NULL;
    HCRYPTPROV hProv = NULL;
    DWORD cbSpaceLeft = 0;
    DWORD cbPrivateKey = 0;
    DWORD cbPKInfo = 0;
    DWORD cbSpaceRequired = 0;
    HRESULT hRes = S_OK;

    //
    // Figure out how much space we need for the private key blob; bit of a lengthy
    // procedure - first get the arguments needed to get a handle to the key container,
    // get a handle to the key container, get a handle to the exchange key in the key
    // container and finally figure out the key size. 
    //
    CRYPT_KEY_PROV_INFO *pKeyProvInfo = NULL;
    DWORD dwProvSize = 0;
    BYTE *pbPKInfo = NULL;

    //
    // Determine how much space we need
    //
    if ( !CertGetCertificateContextProperty( pcCert,
                                             CERT_KEY_PROV_INFO_PROP_ID,
                                             NULL,
                                             &dwProvSize ) &&
         GetLastError() != ERROR_MORE_DATA )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }

    pbPKInfo = new BYTE[dwProvSize];

    if ( !pbPKInfo )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }
    pKeyProvInfo = ( CRYPT_KEY_PROV_INFO *) pbPKInfo;

    //
    // Get the private key info
    //
    if ( !CertGetCertificateContextProperty( pcCert,
                                             CERT_KEY_PROV_INFO_PROP_ID,
                                             pKeyProvInfo,
                                             &dwProvSize ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error getting key info : 0x%x\n", GetLastError() ) );
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }

    if ( !CryptAcquireContext( &hProv,
                               pKeyProvInfo->pwszContainerName,
                               pKeyProvInfo->pwszProvName,
                               pKeyProvInfo->dwProvType,
                               pKeyProvInfo->dwFlags ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error getting handle to CSP : 0x%x\n", GetLastError() ) );
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }

    if ( !CryptGetUserKey( hProv,
                           pKeyProvInfo->dwKeySpec,
                           &hKey ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error getting user key : 0x%x\n", GetLastError() ) );
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;

    }

    if ( !CryptExportKey( hKey,
                          NULL, //BUGBUGBUG - this needs to be a real session key !
                          PRIVATEKEYBLOB,
                          0,
                          NULL,
                          &cbPrivateKey ) &&
         GetLastError() != ERROR_MORE_DATA )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error exporting key : 0x%x\n", GetLastError() ) );
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }

    
    cbPKInfo = SizeOfCKPI( pKeyProvInfo );


    cbSpaceRequired =  cbPrivateKey + 2 + //private key + bytes indicating length of key
                       cbPKInfo  + 2 ;  //private key container info + bytes indicating length
                
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;


    if ( cbSpaceLeft < cbSpaceRequired && !ResizeBuffer( ppbChainBuffer,
                                                         cbSpaceRequired,
                                                         pdwPosition ) )
    {
        hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
        goto EndSerializePK;
    }

    //
    // Append the length of the private key blob
    //
    (*ppbChainBuffer)[(*pdwPosition)++] = MSB(cbPrivateKey);
    (*ppbChainBuffer)[(*pdwPosition)++] = LSB(cbPrivateKey);

    //
    // Add the private key
    //
    cbSpaceLeft = *pdwBufferSize - *pdwPosition;
    if ( !CryptExportKey( hKey,
                          NULL, //BUGBUG - this should be a real session key !
                          PRIVATEKEYBLOB,
                          0,
                          *ppbChainBuffer + *pdwPosition,
                          &cbSpaceLeft ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error exporting key : 0x%x\n", GetLastError() ) );
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndSerializePK;
    }
    (*pdwPosition) += cbSpaceLeft;

    //
    // Length of serialized CRYPT_KEY_PROV_INFO structure
    //
    (*ppbChainBuffer)[(*pdwPosition)++] = MSB(cbPKInfo);
    (*ppbChainBuffer)[(*pdwPosition)++] = LSB(cbPKInfo);
    
    //
    // Add CRYPT_KEY_PROV_INFO structure
    //
    SerializeCKPI( *ppbChainBuffer + *pdwPosition,
                   pKeyProvInfo,
                   pdwPosition );


EndSerializePK:
    //
    //Cleanup
    //
    if ( hKey )
    {
        CryptDestroyKey( hKey );
    }

    if ( hProv )
    {
        CryptReleaseContext( hProv,
                             0 );
    }

    if ( pbPKInfo )
    {
        delete [] pbPKInfo;
    }

    return hRes;
}


HRESULT DeserializeAndImportServerPK( IN OUT BYTE **ppbBuffer,
                                      IN BYTE *pbEnd,
                                      OUT HCRYPTKEY *phKey,
                                      OUT CRYPT_KEY_PROV_INFO *pCKPI )
/*++

Routine Description:

    Deserialize and import the private key for a server certificate.  

Arguments:

   ppbBuffer - pointer to pointer to buffer containing serialized private key; updated on success
   pbEnd - pointer to end of buffer, to avoid overruns
   phKey - pointer to key handle, updated on success
   pCKPI - pointer to CRYPT_KEY_PROV_INFO structure, updated on success
Returns:

   HRESULT indicating success/failure

--*/

{
    HRESULT hRes = S_OK;
    BYTE *pbPresent = *ppbBuffer;
    BYTE bMSB = 0;
    BYTE bLSB = 0;
    DWORD cbPrivateKey = 0;
    DWORD cbPKInfo = 0;
    BYTE *pbPrivateKey = NULL;
    HCRYPTPROV hProv = NULL;

    //
    // figure out length of private key
    //
    if ( pbEnd <= pbPresent + 2 ) //two byte header indicating length of private key
    {
        return REPL_INTERNAL_ERROR;
    }
    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);
    cbPrivateKey =  (DWORD) ( ( bMSB << 8 ) + bLSB );
    
    if ( cbPrivateKey <= 0  || pbEnd <= pbPresent + cbPrivateKey )
    {
        return REPL_INTERNAL_ERROR;
    }
    pbPrivateKey = pbPresent;
    pbPresent += cbPrivateKey;

    //
    //  extract private key container info
    //

    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);
    cbPKInfo =  (DWORD) ( ( bMSB << 8 ) + bLSB );

    if ( cbPKInfo <= 0 || pbEnd <= pbPresent + cbPKInfo )
    {
        return REPL_INTERNAL_ERROR;
    }

    if ( !DeserializeCKPI( &pbPresent,
                           pCKPI ) )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    //
    // clean out old key container
    //
    CryptAcquireContext( &hProv,
                         pCKPI->pwszContainerName,
                         pCKPI->pwszProvName,
                         pCKPI->dwProvType,
                         pCKPI->dwFlags | CRYPT_DELETEKEYSET );

    //
    // import private key
    //
    if ( CryptAcquireContext( &hProv,
                              pCKPI->pwszContainerName,
                              pCKPI->pwszProvName,
                              pCKPI->dwProvType,
                              pCKPI->dwFlags | CRYPT_NEWKEYSET ) )
    {
        if ( !CryptImportKey( hProv,
                              pbPrivateKey,
                              cbPrivateKey,
                              NULL,
                              CRYPT_EXPORTABLE,
                              phKey ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Error : 0x%x\n", GetLastError()));
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }
        else
        {
            //
            // Set the ACL on the private key
            //
            if ( !SetLocalSystemSecurityOnKeyContainer( hProv ) )
            {
                DBGPRINTF((DBG_CONTEXT,
                           "Error : 0x%x\n", GetLastError()));
                hRes = RETURNCODETOHRESULT( GetLastError() );
            }
        }
    }
    else
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Error : 0x%x\n", GetLastError()));
        hRes = RETURNCODETOHRESULT( GetLastError() );
    }

    //
    // Update
    //
    *ppbBuffer = pbPresent;

    return hRes;
}




HRESULT EncryptBuffer( IN HCRYPTKEY hKey,
                       IN OUT PBYTE *ppbReplicationInfo,
                       IN OUT DWORD *pdwBufferSize,
                       IN OUT DWORD *pdwPosition )
/*++

Routine Description:

   Encrypt the replication information

Arguments:

   hKey - handle to key used to encrypt the data
   ppbReplicationInfo - pointer to pointer to buffer that holds data to be encrypted
   pdwBufferSize - pointer to present buffer size
   pdwPosition - pointer to position in buffer

Returns:

   HRESULT indicating success/failure

--*/
{
#ifdef NO_ENCRYPTION

    return S_OK;

#else

    DWORD cbOut = *pdwPosition;
    HRESULT hRes = S_OK;

    //
    // Figure out the size of the buffer we need
    //
    CryptEncrypt( hKey,
                  NULL,
                  TRUE,
                  0,
                  NULL,
                  &cbOut,
                  0 );

    if ( *pdwBufferSize > cbOut ||
         ResizeBuffer( ppbReplicationInfo, cbOut, pdwBufferSize ) )
    {
        cbOut = *pdwPosition;

        if ( !CryptEncrypt( hKey,
                            NULL,
                            TRUE,
                            0,
                            *ppbReplicationInfo, //encryption is in-place
                            &cbOut,
                            *pdwPosition ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
        }
        else
        {
            *pdwPosition = cbOut;
        }
    }
    else
    {
        hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
    }

    return hRes;
#endif // NO_ENCRYPTION
}


HRESULT DecryptBuffer( IN HCRYPTKEY hKey,
                       IN PBYTE pbEncrypted,
                       IN DWORD cbEncrypted,
                       OUT BYTE **ppbDecrypted,
                       OUT BYTE **ppbEndDecrypted )
/*++

Routine Description:

   Encrypt the replication information

Arguments:

   hKey - handle to key to be used to decrypt the data
   pbEncrypted - buffer holding encrypted data
   cbEncrypted - size of buffer pointed to by pbEncrypted
   ppbDecrypted - pointer to pointer to buffer to hold decrypted data. Allocated by
   this function, caller has to clean it up by calling delete []
   ppbEndDecrypted - pointer to pointer to end of ppbDecrypted

Returns:

   HRESULT indicating success/failure

--*/
{
#ifdef NO_ENCRYPTION

    *ppbDecrypted = new BYTE[cbEncrypted];
    memcpy( *ppbDecrypted, pbEncrypted, cbEncrypted );
    *ppbEndDecrypted = (*ppbDecrypted) + cbEncrypted;
    return S_OK;

#else

    HRESULT hRes = S_OK;
    *ppbDecrypted = NULL;
    DWORD cbDecrypted = 0;

    //
    // Decryption is done in-place, so make a copy of the encrypted buffer
    //
    *ppbDecrypted = new BYTE[cbEncrypted];

    if ( !*ppbDecrypted )
    {
        return ( RETURNCODETOHRESULT( ERROR_OUTOFMEMORY ) );
    }
    memcpy( *ppbDecrypted, pbEncrypted, cbEncrypted );
    cbDecrypted = cbEncrypted;

    //
    // Decrypt the data
    //
    if ( !CryptDecrypt( hKey,
                        NULL,
                        TRUE,
                        0,
                        *ppbDecrypted,
                        &cbDecrypted ) )
    {
        memset( *ppbDecrypted, 0, cbEncrypted );
        delete [] *ppbDecrypted;

        return (GetLastError() != ERROR_SUCCESS) ? 
                    RETURNCODETOHRESULT( GetLastError() ) : E_FAIL;
    }
    else
    {
        *ppbEndDecrypted = *ppbDecrypted + cbDecrypted;
    }

    return hRes;
#endif //NO_ENCRYPTION
}



OPEN_CERT_STORE_INFO* AllocateCertStoreInfo()
/*++

Routine Description:

   Allocate and initialize the structure used to hold info about cert stores

Arguments:

   None

Returns:

   Allocated and initialized structure that should be cleaned up with a call to 
   DeallocateCertStoreInfo()

--*/
{
    OPEN_CERT_STORE_INFO *pStoreInfo = new OPEN_CERT_STORE_INFO;

    if ( pStoreInfo )
    {
        memset(pStoreInfo, 0, sizeof(OPEN_CERT_STORE_INFO));
    }

    return pStoreInfo;
}

VOID DeallocateCertStoreInfo( OPEN_CERT_STORE_INFO *pInfo )
/*++

Routine Description:

    Clean up the structure used to track information about a cert store

Arguments:

    pInfo - pointer to OPEN_CERT_STORE_INFO structure to be cleaned up

Returns:

   Nothing

--*/

{
    if ( !pInfo )
    {
        return ;
    }

    if ( pInfo->pszContainer )
    {
        delete [] pInfo->pszContainer;
        pInfo->pszContainer = NULL;
    }

    if ( pInfo->pszProvider )
    {
        delete [] pInfo->pszProvider;
        pInfo->pszProvider = NULL;
    }

    if ( pInfo->pszStoreName )
    {
        delete [] pInfo->pszStoreName;
        pInfo->pszStoreName = NULL;
    }

    if ( pInfo->hCertStore )
    {
        CertCloseStore( pInfo->hCertStore,
                        0 );
        pInfo->hCertStore = NULL;
    }

    delete pInfo;
}

LPWSTR mystrdup(LPWSTR pszIn IN)
/*++

Routine Description:

    Makes a copy of string passed in using new[]


Arguments:

    pszIn - string to be copied

Returns:

    Pointer to copy of string on success, NULL on failure

--*/
{
    DBG_ASSERT( pszIn );

    LPWSTR pszOut = new WCHAR[ (wcslen(pszIn) + 1 ) * sizeof(WCHAR)];

    if ( !pszOut )
    {
        return NULL;
    }

    memcpy( pszOut, pszIn, ( wcslen(pszIn) + 1 ) * sizeof( WCHAR ) );
    
    return ( pszOut );
} //mystrdup


BOOL MBPathHasCAPIInfo( IN IMSAdminBase *pMB,
                        METADATA_HANDLE hHandle,
                        IN LPCWSTR pszPath,
                        IN DWORD *adwProperties,
                        IN DWORD cProperties )
/*++

Routine Description:

    Checks whether the given MB path has info associated with it necessary to 
    reconstruct a particular CAPI structure eg certificate context, CTL context

Arguments:

     pMB - pointer to metabase object 
     hHandle - handle open for reading
     pszPath - path to where CAPI info would be stored, relative to hHandle
     adwProperties - array of metabase properties that must exist and be readable for the
     given CAPI object 
     cProperties - number of elements in pdwProperties array [ = 2 * # of properties]
Returns:

    TRUE if cert info exists, FALSE if not

--*/
{

    DBG_ASSERT( pMB );
    DBG_ASSERT( pszPath );

    BOOL fAllData = TRUE;
    HRESULT hRes;

    //
    // Iterate through each property, trying to retrieve it with a buffer size of zero;
    // If retrieving a property fails for any reason other than a buffer that's too 
    // small, assume the property doesn't exist
    //
    for (DWORD i = 0; i < cProperties/2; i++)
    {
        DWORD dwSize = 0;
        METADATA_RECORD mdr;

        MD_SET_DATA_RECORD( &mdr,
                            adwProperties[2 * i],
                            METADATA_NO_ATTRIBUTES,
                            IIS_MD_UT_SERVER,
                            adwProperties[2*i + 1],
                            NULL,
                            0 );

        hRes = pMB->GetData( hHandle,
                             pszPath,
                             &mdr,
                             &dwSize );

        if ( HRESULTTOWIN32(hRes) !=  ERROR_INSUFFICIENT_BUFFER  )
        {
            fAllData = FALSE;
            break;
        }
    }

    return fAllData;
}


HRESULT GenerateHash( IN OPTIONAL HCRYPTPROV *phProv,
                      IN ALG_ID aiAlg,
                      IN PBYTE pbData,
                      IN DWORD cbData,
                      OUT BYTE **ppbHashBuffer,
                      OUT DWORD *pcbHash,
                      OUT OPTIONAL HCRYPTHASH *phHash )
/*++

Routine Description:

    Generates a hash of input data

Arguments:

     phProv - pointer to provider to be used; can be set to NULL
     aiAlg - hash algorithm to use
     pbData - buffer of data to be hashed
     cbData - size of data to be hashed
     ppbHashBuffer - pointer to pointer to buffer that receives the actual hash
     pcbHash - pointer to size of *ppbHashBuffer
     phHash - pointer to CRYPTHASH object, updated on sucess, ignored if NULL

Returns:

     HRESULT indicating success/failure

--*/

{
    HCRYPTPROV hProv = NULL;
    HCRYPTHASH hHash = NULL;
    HRESULT hRes = S_OK;
    DWORD cbSize = 0;

    if ( !phProv )
    {
        //
        // Get a handle to the CSP that will create the
        // hash
        if ( !CryptAcquireContext( &hProv,
                                   NULL,
                                   NULL,
                                   PROV_RSA_FULL,
                                   CRYPT_VERIFYCONTEXT ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto EndCreateHash;
        }
    }
    else
    {
        hProv = *phProv;
    }

    //
    // Get a handle to a  hash object
    //
    if ( !CryptCreateHash( hProv,
                           aiAlg,
                           0,
                           0,
                           &hHash ) )
    {
      hRes = RETURNCODETOHRESULT( GetLastError() );  
      goto EndCreateHash;
    }

    //
    // Hash the data
    //
    if ( !CryptHashData( hHash,
                         pbData,
                         cbData,
                         0 ) )
    {
        hRes =  RETURNCODETOHRESULT( GetLastError() );
        goto EndCreateHash;
    }


    //
    // Retrieve the size of the hash
    //
    cbSize = sizeof(DWORD);
    if ( !CryptGetHashParam( hHash,
                             HP_HASHSIZE,
                             (BYTE *) pcbHash,
                             &cbSize,
                             0 ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndCreateHash;
    }

    DBG_ASSERT( *pcbHash > 0 );

    *ppbHashBuffer = new BYTE[*pcbHash];

    if ( !*ppbHashBuffer )
    {
        hRes = RETURNCODETOHRESULT( ERROR_OUTOFMEMORY );
        goto EndCreateHash;
    }


    //
    // Retrieve the actual hashed data 
    //
    if ( !CryptGetHashParam( hHash,
                             HP_HASHVAL,
                             *ppbHashBuffer,
                             pcbHash,
                             0 ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndCreateHash;
    }

    if ( phHash )
    {
        *phHash = hHash;
    }

EndCreateHash:

    //
    //Cleanup
    //
    if ( hRes != S_OK )
    {
        if ( *ppbHashBuffer )
        {
            delete [] *ppbHashBuffer;
        }
    }

    if ( !phHash && hHash )
    {
        CryptDestroyHash( hHash );
    }

    if ( !phProv && hProv )
    {
        CryptReleaseContext( hProv,
                             0 );
    }

    return hRes;
}


HRESULT GenerateMD5Hash( IN OPTIONAL HCRYPTPROV *phProv,
                         IN PBYTE pbData,
                         IN DWORD cbData,
                         OUT PBYTE pbHashBuffer,
                         IN OUT DWORD *pdwHashBufferSize,
                         OUT OPTIONAL HCRYPTHASH *pHashObj )
/*++

Routine Description:

     Generates MD5 hash of data 

Arguments:

     phProv - pointer to provider to be used; can be set to NULL
     pbData - buffer of data to be hashed
     cbData - size of data to be hashed
     pbHashBuffer - buffer to receive hash
     pdwHashBufferSize - size of pbHashBuffer
     pHashObj - pointer to CRYPTHASH object, updated on sucess, ignored if NULL

Returns:

     HRESULT indicating success/failure

--*/

{
    HCRYPTPROV hProv = NULL;
    HCRYPTHASH hHash = NULL;
    HRESULT hRes = S_OK;


    if ( !phProv )
    {
        //
        // Get a handle to the CSP that will create the
        // hash
        if ( !CryptAcquireContext( &hProv,
                                   NULL,
                                   NULL,
                                   PROV_RSA_FULL,
                                   CRYPT_VERIFYCONTEXT ) )
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            goto EndCreateHash;
        }
    }
    else
    {
        hProv = *phProv;
    }

    //
    // Get a handle to an MD5 hash object
    //
    if ( !CryptCreateHash( hProv,
                           CALG_MD5,
                           0,
                           0,
                           &hHash ) )
    {
      hRes = RETURNCODETOHRESULT( GetLastError() );  
      goto EndCreateHash;
    }

    //
    // Hash the data
    //
    if ( !CryptHashData( hHash,
                         pbData,
                         cbData,
                         0 ) )
    {
        hRes =  RETURNCODETOHRESULT( GetLastError() );
        goto EndCreateHash;
    }

    //
    // Retrieve the hash
    //
    if ( !CryptGetHashParam( hHash,
                             HP_HASHVAL,
                             pbHashBuffer,
                             pdwHashBufferSize,
                             0 ) )
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
        goto EndCreateHash;
    }

    if ( pHashObj )
    {
        *pHashObj = hHash;
    }

EndCreateHash:

    //
    //Cleanup
    //
    if ( !pHashObj && hHash )
    {
        CryptDestroyHash( hHash );
    }

    if ( !phProv && hProv )
    {
        CryptReleaseContext( hProv,
                             0 );
    }

    return hRes;
}
         

BOOL IsNumber( LPCWSTR pszName )
{
    return ( _wtoi( pszName) > 0 ? TRUE : FALSE );
}

VOID SerializeCKPI( OUT BYTE *pbBuffer,
                    IN CRYPT_KEY_PROV_INFO *pInfo,
                    OUT DWORD *pdwPosition )
/*++

Routine Description:

     Serializes a CRYPT_KEY_PROV_INFO structure 

Arguments:

    pbBuffer - pointer to buffer that holds serialized info
    pInfo - pointer to CRYPT_KEY_PROV_INFO structure to be serialized
    pdwPosition - position in pbBuffer, updated after serialization

Returns:

    Nothing

--*/
{
    DWORD cbPos = 0;
    BYTE bMSB = 0;
    BYTE bLSB = 0;
    DWORD cbStr = 0;

    //
    // First bit : [MSB of length][LSB of length][pwszContainerName]
    //
    if ( pInfo->pwszContainerName )
    {
        cbStr = wcslen( pInfo->pwszContainerName ) * sizeof(WCHAR) + 2 ; //trailing nulls
        pbBuffer[cbPos++] = MSB(cbStr);
        pbBuffer[cbPos++] = LSB(cbStr);
        memcpy( pbBuffer + cbPos, pInfo->pwszContainerName, cbStr);
        cbPos += cbStr;
    }
    else
    {
        pbBuffer[cbPos++] = 0;
        pbBuffer[cbPos++] = 0;
    }

    //
    // [MSB of length][LSB of length][pwszProvName]
    //
    if ( pInfo->pwszProvName )
    {
        cbStr = wcslen( pInfo->pwszProvName ) * sizeof(WCHAR) + 2 ; //trailing nulls
        pbBuffer[cbPos++] = MSB(cbStr);
        pbBuffer[cbPos++] = LSB(cbStr);
        memcpy( pbBuffer + cbPos, pInfo->pwszProvName, cbStr);
        cbPos += cbStr;
    }
    else
    {
        pbBuffer[cbPos++] = 0;
        pbBuffer[cbPos++] = 0;
    }
    
    
    //
    // dwProvType
    //
    memcpy( pbBuffer + cbPos, &(pInfo->dwProvType), sizeof(DWORD) );
    cbPos += sizeof(DWORD);

    //
    // dwFlags
    //
    memcpy( pbBuffer + cbPos, &(pInfo->dwFlags), sizeof(DWORD) );
    cbPos += sizeof(DWORD);
    
    //
    // cProvParam
    //
    memcpy( pbBuffer + cbPos, &(pInfo->cProvParam), sizeof(DWORD) );
    cbPos += sizeof(DWORD);

    //
    // Each of the rgProvParam entries
    //
    for ( DWORD dwIndex = 0; dwIndex < pInfo->cProvParam; dwIndex++ )
    {
        CRYPT_KEY_PROV_PARAM ckpParam = pInfo->rgProvParam[dwIndex];

        //
        // dwParam
        //
        memcpy( pbBuffer + cbPos, &(ckpParam.dwParam), sizeof(DWORD) );
        cbPos += sizeof(DWORD);

        //
        // cbData
        //
        memcpy( pbBuffer + cbPos, &(ckpParam.cbData), sizeof(DWORD) );
        cbPos += sizeof(DWORD);
        
        //
        // pbData
        //
        memcpy( pbBuffer + cbPos, ckpParam.pbData, ckpParam.cbData );
        cbPos += ckpParam.cbData;

        //
        // dwFlags
        //
        memcpy( pbBuffer + cbPos, &(ckpParam.dwFlags), sizeof(DWORD) );
        cbPos += sizeof(DWORD);
    }

    //
    // dwKeySpec
    //
    memcpy( pbBuffer + cbPos, &(pInfo->dwKeySpec), sizeof(DWORD) );
    cbPos += sizeof(DWORD);
    
    (*pdwPosition) += cbPos;
}

BOOL DeserializeCKPI( IN OUT BYTE **ppbBuffer,
                      OUT CRYPT_KEY_PROV_INFO *pInfo )
/*++

Routine Description:

     Deserializes a CRYPT_KEY_PROV_INFO structure

Arguments:

     pBuffer - pointer to pointer to buffer containing serialized information, updated while
     processing occurs
     ppInfo - pointer to CRYPT_KEY_PROV_INFO structure to be deserialized, updated during
     processing
     

Returns:

     TRUE if successful, FALSE if not

--*/

{
    BYTE bMSB = 0;
    BYTE bLSB = 0;
    DWORD dwLength;
    BYTE *pbPresent = *ppbBuffer;

    //
    // [MSB of length][LSB of length][pwszContainerName]
    //
    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);
    dwLength = LENGTH( bMSB, bLSB );

    if ( dwLength )
    {
        pInfo->pwszContainerName = (LPWSTR) pbPresent;
        pbPresent += dwLength;
    }
    else
    {
        pInfo->pwszContainerName = NULL;
    }

    //
    // [MSB of length][LSB of length][pwszProvName]
    //
    bMSB = *(pbPresent++);
    bLSB = *(pbPresent++);
    dwLength = LENGTH( bMSB, bLSB );

    if ( dwLength )
    {
        pInfo->pwszProvName = (LPWSTR) pbPresent;
        pbPresent += dwLength;
    }
    else
    {
        pInfo->pwszProvName = NULL;
    }
    
    //
    // dwProvType
    //
    pInfo->dwProvType = *( (DWORD *) pbPresent );
    pbPresent += sizeof(DWORD);


    //
    // dwFlags
    //
    pInfo->dwFlags = *( (DWORD *) pbPresent );
    pbPresent += sizeof(DWORD);

    //
    // cProvParam
    //
    pInfo->cProvParam = *( (DWORD *) pbPresent );
    pbPresent += sizeof(DWORD);


    //
    // Each of the rgProvParam entries
    //
    if ( pInfo->cProvParam )
    {
        pInfo->rgProvParam = new CRYPT_KEY_PROV_PARAM[pInfo->cProvParam];

        if ( !pInfo->rgProvParam )
        {
            return FALSE;
        }

        for ( DWORD dwIndex = 0; dwIndex < pInfo->cProvParam; dwIndex++ )
        {
            CRYPT_KEY_PROV_PARAM ckpParam = pInfo->rgProvParam[dwIndex];
            
            ckpParam.dwParam = *((DWORD *) pbPresent);
            pbPresent += sizeof(DWORD);

            ckpParam.cbData = *((DWORD *) pbPresent);
            pbPresent += sizeof(DWORD);

            ckpParam.pbData = pbPresent;
            pbPresent += ckpParam.cbData;

            ckpParam.dwFlags = *((DWORD *) pbPresent);
            pbPresent += sizeof(DWORD);
        }
    }
    else
    {
        pInfo->rgProvParam = NULL;
    }

    //
    // dwKeySpec
    //
    pInfo->dwKeySpec = *( (DWORD *) pbPresent);
    pbPresent += sizeof(DWORD);

    //
    // Update
    //
    *ppbBuffer = pbPresent;
    return TRUE;
}


DWORD SizeOfCKPI( IN CRYPT_KEY_PROV_INFO *pInfo )
/*++

Routine Description:

     Calculates the amount of space needed to contain the information for 
     a key container 

Arguments:

     pInfo - pointer to CRYPT_KEY_PROV_INFO structure whose space requirements are to
     be calculated

Returns:

     Number of bytes required to hold CRYPT_KEY_PROV_INFO structure 

--*/

{
    if ( !pInfo )
    {
        return 0;
    }

    DWORD cb = 0;
    
    if ( pInfo->pwszContainerName )
    {
        //trailing nulls, length bytes
        cb += ( wcslen(pInfo->pwszContainerName) * sizeof(WCHAR) ) + 2 + 2; 
    }
    else
    {
        cb += 2; //length bytes
    }

    if ( pInfo->pwszProvName )
    {
        //trailing nulls, length bytes
        cb += ( wcslen(pInfo->pwszProvName) * sizeof(WCHAR) ) + 2 + 2; 
    }
    else
    {
        cb += 2; //length bytes
    }

    cb += 4*sizeof(DWORD); // cProvParam, dwProvType, dwFlags, dwKeySpec

    for ( DWORD dwIndex = 0 ; dwIndex < pInfo->cProvParam; dwIndex++ )
    {
        cb += ( pInfo->rgProvParam[dwIndex].cbData + 3 * sizeof(DWORD) ); 
    }

    return cb;
}

HRESULT DistributeCerts( IN HCERTSTORE hMemStore,
                         IN BYTE *rgbCertHash,
                         IN PCRYPT_KEY_PROV_INFO pCKPI )
/*++

Routine Description:

     Takes the certs in an in-memory store and distributes them to the appropriate 
     system stores.
     
     Self-signed certs go into the ROOT store, intermediate certs into the CA store and the 
     server cert into the MY store. It's possible that the server cert is self-signed, in 
     which case it goes into both the ROOT and MY stores.

Arguments:

     hMemStore - handle to in-memory store containing certs to be distributed
     rgbCertHash - buffer containing hash of server cert
     hServerPK - private key for server certificate

Returns:

     HRESULT indicating success/failure

--*/

{
    HCERTSTORE hMyStore = NULL;
    HCERTSTORE hCAStore = NULL;
    HCERTSTORE hRootStore = NULL;
    HRESULT hRes = S_OK;

    //
    // Open all the stores we'll use
    //
    hMyStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                              0,
                              NULL,
                              CERT_SYSTEM_STORE_LOCAL_MACHINE,
                              MY_STORE_NAME );

    if ( !hMyStore )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }


    hCAStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                              0,
                              NULL,
                              CERT_SYSTEM_STORE_LOCAL_MACHINE,
                              CA_STORE_NAME );
    
    if ( !hCAStore )
    {
        CertCloseStore( hMyStore,
                        0 );
        return RETURNCODETOHRESULT( GetLastError() );
    }
    
    hRootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
                                0,
                                NULL,
                                CERT_SYSTEM_STORE_LOCAL_MACHINE,
                                ROOT_STORE_NAME );
    
    if ( !hRootStore )
    {
        CertCloseStore( hMyStore,
                        0 );
        
        CertCloseStore( hCAStore,
                        0 );
        return RETURNCODETOHRESULT( GetLastError() );
    }
    

    //
    // Iterate through all the certs in the in-memory store, putting them into the 
    // appropriate place 
    //
    PCCERT_CONTEXT pCert = NULL;
    PCCERT_CONTEXT pPrevCert = NULL;
    
    while ( pCert = CertEnumCertificatesInStore( hMemStore,
                                                 pPrevCert ) )
    {
        BOOL fSelfSigned = FALSE;
        BOOL fServerCert = FALSE;
        
        //
        // Self-signed certs go into the Root store
        //
        if ( IsSelfSignedCert( pCert ) )
        {
            fSelfSigned = TRUE;
            
            if ( !CertAddCertificateContextToStore( hRootStore,
                                                    pCert,
                                                    CERT_STORE_ADD_NEW,
                                                    NULL ) &&
                 GetLastError() != CRYPT_E_EXISTS )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
                break;
            }
        }
        
        //
        // Server cert goes into MY store
        //
        if ( CertMatchesHash( pCert, 
                              rgbCertHash,
                              &fServerCert) )
        {
            if ( fServerCert )
            {
                //
                // Add private key to cert context and stuff cert into MY store
                //
                if ( !CertSetCertificateContextProperty( pCert,
                                                         CERT_KEY_PROV_INFO_PROP_ID,
                                                         0,
                                                         (PVOID) pCKPI ) ||
                     !CertAddCertificateContextToStore( hMyStore,
                                                        pCert,
                                                        CERT_STORE_ADD_REPLACE_EXISTING,
                                                        NULL ) )
                {
                    hRes = RETURNCODETOHRESULT( GetLastError() );
                    break;
                }
            }
        }
        else
        {
            hRes = RETURNCODETOHRESULT( GetLastError() );
            break;
        }    
        
        //
        // Everything else goes into the CA store
        //
        if ( !fSelfSigned && !fServerCert )
        {
            if ( !CertAddCertificateContextToStore( hCAStore,
                                                    pCert,
                                                    CERT_STORE_ADD_NEW,
                                                    NULL ) &&
                 GetLastError() != CRYPT_E_EXISTS )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
                break;
            }
        }
        
        pPrevCert = pCert;
    }
    
    //
    // clean up
    //
    if ( hMyStore )
    {
        CertCloseStore( hMyStore,
                        0 );
    }
    
    if ( hCAStore )
    {
        CertCloseStore( hCAStore,
                        0 );
    }
    
    if ( hRootStore )
    {
        CertCloseStore( hRootStore,
                        0 );
    }
    
    return hRes;
}


BOOL CertMatchesHash( IN PCCERT_CONTEXT pCert,
                      IN BYTE *rgbHash,
                      OUT BOOL *pfMatch)
/*++

Routine Description:

     Checks whether a cert has the same hash as the hash passed in

Arguments:

     pCert - cert to be checked
     rgbHash - hash pCert is to be checked against 
     pfMatch - bool that is set to TRUE if cert matches, FALSE if not

Returns:
 
     TRUE if check was successful, FALSE if not    

--*/
{
    *pfMatch = FALSE;
    BYTE rgbCertHash[SHA1_HASH_SIZE];
    DWORD dwSize = SHA1_HASH_SIZE;

    if ( CertGetCertificateContextProperty( pCert,
                                            CERT_SHA1_HASH_PROP_ID,
                                            rgbCertHash,
                                            &dwSize ) )
    {
        *pfMatch = ( memcmp( rgbCertHash, rgbHash, SHA1_HASH_SIZE ) ? FALSE : TRUE );
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}


//////////////

CADMEXCOM_IMSAdminCryptoCapabilities::CADMEXCOM_IMSAdminCryptoCapabilities( 
    CADMEXCOM*  pAdmExCom
    )
{
    m_pAdmExCom = pAdmExCom;
    m_hSchannel = NULL;
    m_hSspi = NULL;
    m_fHasCredHandle = FALSE;
}


CADMEXCOM_IMSAdminCryptoCapabilities::~CADMEXCOM_IMSAdminCryptoCapabilities( )
{
    if ( m_fHasCredHandle )
    {
        m_pfnFreeCredentialsHandle( &m_hCred );
    }

    if ( m_hSchannel != NULL )
    {
        FreeLibrary( m_hSchannel );
    }

    if ( m_hSspi != NULL )
    {
        FreeLibrary( m_hSspi );
    }
}


HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminCryptoCapabilities::GetProtocols( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][out] */ unsigned char __RPC_FAR *pbBuffer,
    /* [out] */ DWORD __RPC_FAR *pdwMDRequiredBufferSize)
{
    SecPkgCred_SupportedProtocols   SupportedProtocols;
    SECURITY_STATUS                 scRet;

    *pdwMDRequiredBufferSize = sizeof(DWORD);

    if ( dwBufferSize < sizeof(DWORD) )
    {
        return RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER );
    }

    if ( !LoadSchannel() )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    if ( FAILED( scRet = m_pfnQueryCredentialsAttributes( &m_hCred, 
                                                      SECPKG_ATTR_SUPPORTED_PROTOCOLS, 
                                                      &SupportedProtocols ) ) )
    {
        return scRet;
    }

    *(LPDWORD)pbBuffer = SupportedProtocols.grbitProtocol;

    return S_OK;
}


BOOL 
CADMEXCOM_IMSAdminCryptoCapabilities::LoadSchannel()
{
    SECURITY_STATUS             scRet;
    TimeStamp                   tsExpiry;
    OSVERSIONINFO               osv;
    BOOL                        fT;
    BOOL                        fWinNT;

    if ( m_hSchannel == NULL )
    {
        // Check the OS we are running on
        osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        fT = GetVersionEx( &osv );
        fWinNT = ( osv.dwPlatformId == VER_PLATFORM_WIN32_NT );

        if ( (m_hSchannel = LoadLibrary(L"schannel.dll")) != NULL )
        {
            m_pfnGetDefaultIssuers = (PFN_SSLGETDEFAULTISSUERS)GetProcAddress( m_hSchannel, 
                    "SslGetDefaultIssuers" );

            if ( m_pfnGetDefaultIssuers &&
                 ((m_hSspi = LoadLibrary( fWinNT ? L"security.dll" : L"secur32.dll" )) != NULL) )
            {
                m_pfnAcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN_W)
                            GetProcAddress( m_hSspi, "AcquireCredentialsHandleW" );
                m_pfnFreeCredentialsHandle = (FREE_CREDENTIALS_HANDLE_FN)
                            GetProcAddress( m_hSspi, "FreeCredentialsHandle" );
                m_pfnFreeContextBuffer = (FREE_CONTEXT_BUFFER_FN)
                            GetProcAddress( m_hSspi, "FreeContextBuffer" );
                m_pfnQueryCredentialsAttributes = (QUERY_CREDENTIALS_ATTRIBUTES_FN)
                            GetProcAddress( m_hSspi, "QueryCredentialsAttributesA" );

                if ( m_pfnAcquireCredentialsHandle &&
                     m_pfnFreeCredentialsHandle &&
                     m_pfnFreeContextBuffer &&
                     m_pfnQueryCredentialsAttributes )
                {
                    scRet = m_pfnAcquireCredentialsHandle(  
                                                        NULL,               // My name (ignored)
                                                        UNISP_NAME_W,       // Package
                                                        SECPKG_CRED_INBOUND,// Use
                                                        NULL,               // Logon Id (ign.)
                                                        NULL,               // auth data
                                                        NULL,               // dce-stuff
                                                        NULL,               // dce-stuff
                                                        &m_hCred,           // Handle
                                                        &tsExpiry );

                    if ( !FAILED(scRet) )
                    {
                        m_fHasCredHandle = TRUE;
                        return TRUE;
                    }
                }

                FreeLibrary( m_hSspi );
                m_hSspi = NULL;
            }

            FreeLibrary( m_hSchannel );
            m_hSchannel = NULL;
        }

        return FALSE;
    }

    return TRUE;
}


HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminCryptoCapabilities::GetMaximumCipherStrength( 
    /* [out] */ LPDWORD pdwMaximumCipherStrength )
{
    SecPkgCred_CipherStrengths  CipherStrengths;
    SECURITY_STATUS             scRet;

    if ( !LoadSchannel() )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    if ( FAILED( scRet = m_pfnQueryCredentialsAttributes( &m_hCred, 
                                                      SECPKG_ATTR_CIPHER_STRENGTHS, 
                                                      &CipherStrengths ) ) )
    {
        return scRet;
    }

    *pdwMaximumCipherStrength = CipherStrengths.dwMaximumCipherStrength;

    return S_OK;
}


HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminCryptoCapabilities::GetRootCertificates( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][out] */ unsigned char __RPC_FAR *pbBuffer,
    /* [out] */ DWORD __RPC_FAR *pdwMDRequiredBufferSize)
{
    DWORD   dwLen;

    if ( !LoadSchannel() ||
         !m_pfnGetDefaultIssuers( NULL, &dwLen ) )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    if ( dwBufferSize < dwLen )
    {
        *pdwMDRequiredBufferSize = dwLen;
        return RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER );
    }

    if ( m_pfnGetDefaultIssuers( pbBuffer, &dwLen ) )
    {
        *pdwMDRequiredBufferSize = dwLen;
        return S_OK;
    }

    return RETURNCODETOHRESULT( GetLastError() );
}


HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminCryptoCapabilities::GetSupportedAlgs( 
    /* [in] */ DWORD dwBufferSize,
    /* [size_is][out] */ DWORD __RPC_FAR *pbBuffer,
    /* [out] */ DWORD __RPC_FAR *pdwMDRequiredBufferSize)
{
    DWORD                       dwLen;
    SecPkgCred_SupportedAlgs    SupportedAlgs;
    SECURITY_STATUS             scRet;

    if ( !LoadSchannel() )
    {
        return RETURNCODETOHRESULT( GetLastError() );
    }

    if ( FAILED( scRet = m_pfnQueryCredentialsAttributes( &m_hCred, 
                                                      SECPKG_ATTR_SUPPORTED_ALGS, 
                                                      &SupportedAlgs ) ) )
    {
        return scRet;
    }

    *pdwMDRequiredBufferSize = sizeof(ALG_ID) * SupportedAlgs.cSupportedAlgs;

    if ( dwBufferSize < *pdwMDRequiredBufferSize )
    {
        return RETURNCODETOHRESULT( ERROR_INSUFFICIENT_BUFFER );
    }

    memcpy( pbBuffer, SupportedAlgs.palgSupportedAlgs, *pdwMDRequiredBufferSize );

    return S_OK;
}


HRESULT STDMETHODCALLTYPE 
CADMEXCOM_IMSAdminCryptoCapabilities::SetCAList( 
        /*[in] */ DWORD dwBufferSize,
        /*[in, size_is(dwBufferSize)] */ unsigned char __RPC_FAR *pbBuffer )
{
#if defined(USE_CAPI2)

    HCERTSTORE      hStore = NULL;
    HKEY            hKey = NULL;
    PCCERT_CONTEXT  pCtx;
    DWORD           cCert;
    PCCERT_CONTEXT* rgpCert;
    HRESULT         hRes = S_OK;
    DWORD           dwValueLen;

    //
    //  WARNING: this relies on a security hole in CAPI2, as we want to add certs to 
    //  ROOT store w/o any UI, and the only way to do this ( at least currently ) is
    //  to access the ROOT store as a registry store instead of system store.
    //  CAPI2 team may change this in the future, but they MUST provide a way to control
    //  the CA list used by schannel, currently in HKCU ROOT store.
    //

    if ( RegOpenKeyEx(
              HKEY_CURRENT_USER,
              L"Software\\Microsoft\\SystemCertificates\\ROOT",
              0,
              KEY_ALL_ACCESS,
              &hKey ) == ERROR_SUCCESS )
    {
        if ( (hStore = CertOpenStore( CERT_STORE_PROV_REG,
            0,
            NULL,
            0,
            (const void*)hKey )) == NULL )
        {
            RegCloseKey( hKey );
            hKey = NULL;
        }
    }

    if ( hStore )
    {
        //
        // delete existing certs
        //

        for ( pCtx = NULL, cCert = 0 ;
              pCtx = CertEnumCertificatesInStore( hStore, pCtx ) ; )
        {
            ++cCert;
        }
        if ( cCert && ( rgpCert = (PCCERT_CONTEXT*)LocalAlloc( LMEM_FIXED, 
                    cCert * sizeof(PCCERT_CONTEXT) ) ) )
        {
            for ( pCtx = NULL, cCert = 0 ;
                  pCtx = CertEnumCertificatesInStore( hStore, pCtx ) ; )
            {
                rgpCert[cCert++] = pCtx;
            }
            while ( cCert-- )
            {
                CertDeleteCertificateFromStore( rgpCert[cCert] );
            }
            LocalFree( rgpCert );
        }
    }
    else
    {
        hRes = RETURNCODETOHRESULT( GetLastError() );
    }

    if ( SUCCEEDED( hRes ) )
    {
        //
        // deserialize certs
        //

        for ( ; dwBufferSize ; )
        {
            dwValueLen = *(LPDWORD)pbBuffer;

            if ( CertAddSerializedElementToStore( hStore,
                pbBuffer + sizeof(DWORD),
                dwValueLen,
                CERT_STORE_ADD_REPLACE_EXISTING,
                0,
                CERT_STORE_CERTIFICATE_CONTEXT_FLAG,
                NULL,
                NULL ) == FALSE )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
                break;
            }

            pbBuffer += sizeof(DWORD) + PAD4(dwValueLen);

            dwBufferSize -= sizeof(DWORD) + PAD4(dwValueLen);
        }
    }

    if ( hStore )
    {
        CertCloseStore( hStore, CERT_CLOSE_STORE_FORCE_FLAG );
        RegCloseKey( hKey );
    }

#else

    HKEY        hKey;
    int         iV;
    CHAR        achName[128];
    DWORD       dwNameLen;
    DWORD       dwValueLen;
    LPSTR       pszName;
    DWORD       dwType;
    HRESULT     hRes = S_OK;
    DWORD       dwErr;

    if ( (dwErr = RegOpenKeyEx( HKEY_CURRENT_USER,
                       "Software\\Microsoft\\SystemCertificates\\ROOT\\Certificates",
                       0,
                       KEY_WRITE|KEY_READ,
                       &hKey )) == ERROR_SUCCESS )
    {
        //
        // delete existing values
        //

        for ( iV = 0 ; ; /*++iV*/ )
        {
            dwNameLen = sizeof( achName );
            if ( RegEnumValue( hKey,
                               iV,
                               achName,
                               &dwNameLen,
                               NULL,
                               &dwType,
                               NULL,
                               NULL ) != ERROR_SUCCESS )
            {
                break;
            }

            if ( RegDeleteValue( hKey, achName ) != ERROR_SUCCESS )
            {
                hRes = RETURNCODETOHRESULT( GetLastError() );
                break;
            }
        }

        if ( SUCCEEDED( hRes ) )
        {
            for ( ; dwBufferSize ; )
            {
                dwNameLen = *(LPDWORD)pbBuffer;
                pszName = (LPSTR)(pbBuffer + sizeof(DWORD));
                dwValueLen = *(LPDWORD)(pbBuffer += sizeof(DWORD) + PAD4(dwNameLen));

                if ( RegSetValueEx( hKey, 
                                    (LPSTR)pszName,
                                    NULL,
                                    REG_BINARY,
                                    pbBuffer + sizeof(DWORD),
                                    dwValueLen ) != ERROR_SUCCESS )
                {
                    hRes = RETURNCODETOHRESULT( GetLastError() );
                    break;
                }

                pbBuffer += sizeof(DWORD) + PAD4(dwValueLen);

                dwBufferSize -= sizeof(DWORD) + PAD4(dwNameLen) + sizeof(DWORD) + PAD4(dwValueLen);
            }
        }

        RegCloseKey( hKey );
    }
    else
    {
        hRes = RETURNCODETOHRESULT( dwErr );
    }

#endif

    return hRes;
}


BOOL
SetLocalSystemSecurityOnKeyContainer( IN HCRYPTPROV hProv )
/*++

    The function applies security to the key containers associated
    with the HCRYPTPROV which is passed in such that only
    Local System has Full Control.  Note that the owner
    is not set, which results in a default owner of Administrators.

    NB : This function was inherited wholesale from Jeff Spelman [jeffspel] in the CAPI
    group.

--*/
{
    SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
    PSID pLocalSystemSid = NULL;
    SECURITY_DESCRIPTOR sd;
    PACL pDacl = NULL;
    PACCESS_ALLOWED_ACE pAce = NULL;
    DWORD dwAclSize = 0;
    LONG lRetCode = 0;
    BOOL bSuccess = FALSE; // assume this function fails

    //
    // prepare a Sid representing the Local System account
    //

    if( !AllocateAndInitializeSid( &sia,
                                   1,
                                   SECURITY_LOCAL_SYSTEM_RID,
                                   0, 0, 0, 0, 0, 0, 0,
                                   &pLocalSystemSid ) )
    {
        goto cleanup;
    }

    //
    // compute size of new acl
    //

    dwAclSize = sizeof(ACL) +
        1 * ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
        GetLengthSid(pLocalSystemSid) ;

    //
    // allocate storage for Acl
    //

    if ( !(pDacl = (PACL) new BYTE [dwAclSize]) )
    {
        goto cleanup;
    }

    if( !InitializeAcl( pDacl, 
                        dwAclSize, 
                        ACL_REVISION ) )
    {
        goto cleanup;
    }

    if( !AddAccessAllowedAce( pDacl,
                              ACL_REVISION,
                              KEY_ALL_ACCESS,
                              pLocalSystemSid ) )
    {
        goto cleanup;
    }

    //
    // make it container inherit.
    //

    if( !GetAce( pDacl, 
                 0, 
                 (LPVOID *) &pAce ) )
    {
        goto cleanup;
    }

    pAce->Header.AceFlags = CONTAINER_INHERIT_ACE;

    if( !InitializeSecurityDescriptor( &sd, 
                                       SECURITY_DESCRIPTOR_REVISION ) )
    {
        goto cleanup;
    }

    if( !SetSecurityDescriptorDacl( &sd, 
                                    TRUE, 
                                    pDacl, 
                                    FALSE ) ) 
    {
        goto cleanup;
    }

    //
    // apply the security descriptor to the key container
    //

    if ( !CryptSetProvParam( hProv,
                             PP_KEYSET_SEC_DESCR,
                             (BYTE*)&sd,
                             (DWORD)DACL_SECURITY_INFORMATION ) )  
    {
        goto cleanup;
    }

    bSuccess = TRUE; // indicate success

cleanup:

    //
    // free allocated resources
    //

    if( pDacl != NULL )
    {
        delete [] pDacl;
    }

    if( pLocalSystemSid != NULL )
    {
        FreeSid( pLocalSystemSid );
    }

    return bSuccess;
}


extern "C" BOOL WINAPI 
DllMain( 
    HANDLE hModule, 
    DWORD dwReason, 
    LPVOID pV
    )
/*++

Routine Description:

    DLL init/terminate notification function

Arguments:

    hModule  - DLL handle
    dwReason - notification type
    LPVOID   - not used

Returns:

    TRUE if success, FALSE if failure

--*/
{
    switch ( dwReason )
    {
        case DLL_PROCESS_ATTACH:
#ifdef _NO_TRACING_
            CREATE_DEBUG_PRINT_OBJECT( "ADMEXS" );
#else
            CREATE_DEBUG_PRINT_OBJECT( "ADMEXS" , IisADMExsGuid);
#endif
            break;

        case DLL_PROCESS_DETACH:
            DELETE_DEBUG_PRINT_OBJECT( );
            break;
    }

    return TRUE;
}
