|
|
/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
iiscert.cxx
Abstract:
Code to handle retrieving/storing/using CAPI2 certificate contexts for IIS server certificates.
Author:
Alex Mallet (amallet) 02-Dec-1997
--*/
#include "tcpdllp.hxx"
#pragma hdrstop
#include <dbgutil.h>
#include <buffer.hxx>
#include <ole2.h>
#include <imd.h>
#include <mb.hxx>
//
// Local includes
//
#include "iiscert.hxx"
#include "capiutil.hxx"
#define SHA1_HASH_SIZE 20 //size of SHA1 hash
HCRYPTPROV IIS_SERVER_CERT::m_hFortezzaCSP = NULL; HCRYPTDEFAULTCONTEXT IIS_SERVER_CERT::m_hFortezzaCtxt = NULL;
IIS_SERVER_CERT::IIS_SERVER_CERT( IN IMDCOM *pMDObject, IN LPTSTR pszMBPath ) : m_strMBPath( pszMBPath ), m_pStoreInfo( NULL ), m_hCryptProv( NULL ), m_pCertContext( NULL ), m_fIsFortezzaCert( FALSE ) /*++
Routine Description:
Constructor for server cert that reads all necessary info out of metabase
Arguments:
pMDObject - pointer to metabase object pszMBPath - full path in metabase to information for this virtual server instance
Returns:
Nothing
--*/ { DBG_ASSERT( pMDObject ); DBG_ASSERT( pszMBPath );
DWORD dwSize = 0; METADATA_HANDLE hInstanceInfoHandle = NULL; int iLengthNeeded = 0; PBYTE pbCertHash = NULL; DWORD dwHashSize = 0; DWORD dwPathLength = 0; MB mb( pMDObject ); LPTSTR pszCSPString = NULL; LPTSTR pszCSPStringCopy = NULL; BOOL fProgPINEntry = FALSE;
m_dwStatus = CERT_ERR_NONE;
if ( !m_strMBPath.IsValid() ) { SetLastError( ERROR_OUTOFMEMORY ); goto EndRetrieveCertContext; }
if ( mb.Open( m_strMBPath.QueryStr(), METADATA_PERMISSION_READ )) { DWORD dwReqDataLen = 0; METADATA_RECORD mdr; DWORD dwFortezza = 0;
//
// Retrieve cert hash
//
MD_SET_DATA_RECORD(&mdr, MD_SSL_CERT_HASH, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, BINARY_METADATA, NULL, 0);
if ( !RetrieveBlobFromMetabase(&mb, NULL, &mdr, SHA1_HASH_SIZE ) )
{ m_dwStatus = CERT_ERR_MB; goto EndRetrieveCertContext; } else { DBG_ASSERT( mdr.dwMDDataLen == SHA1_HASH_SIZE );
pbCertHash = mdr.pbMDData; dwHashSize = mdr.dwMDDataLen; }
//
// Retrieve flag indicating whether it's a Fortezza cert or not
//
if ( !mb.GetDword( NULL, MD_SSL_CERT_IS_FORTEZZA, IIS_MD_UT_SERVER, &(dwFortezza), METADATA_NO_ATTRIBUTES ) ) { if ( GetLastError() != MD_ERROR_DATA_NOT_FOUND ) { m_dwStatus = CERT_ERR_MB; goto EndRetrieveCertContext; } else { m_fIsFortezzaCert = FALSE; } } else { m_fIsFortezzaCert = (BOOL) dwFortezza; }
//
// If it's a Fortezza cert and we're supposed to do programmatic PIN entry,
// we need to get a handle to the CSP ourselves by providing the
// PIN in the call to CryptAcquireContext(), to avoid the "Enter PIN" dialog
//
if ( m_fIsFortezzaCert && ( fProgPINEntry = UseProgrammaticPINEntry( &mb ) ) ) { LPTSTR pszPIN = NULL; LPTSTR pszSerialNumber = NULL; LPTSTR pszPersonality = NULL; DWORD cbPIN = 0; DWORD cbSerialNumber = 0; DWORD cbPersonality = 0; DWORD cbLen = 0; DWORD iPos = 0; if ( !RetrievePINInfo( &mb, &pszPIN, &pszSerialNumber, &pszPersonality ) ) { DBGPRINTF((DBG_CONTEXT, "Couldn't retrieve PIN info for Fortezza card\n")); m_dwStatus = CERT_ERR_MB; goto EndRetrieveCertContext; } DBG_ASSERT( pszPIN && pszSerialNumber && pszPersonality ); //
// Construct string to be passed to CryptAcquireContext - it's
// <card serial #>\n<personality>\n<PIN>.
//
cbPIN = strlen( pszPIN ); cbSerialNumber = strlen( pszSerialNumber ); cbPersonality = strlen( pszPersonality ); cbLen = cbPIN + cbSerialNumber + cbPersonality + 10; //add a few bytes for CR
//and null terminating char
pszCSPString = new CHAR[cbLen]; pszCSPStringCopy = new CHAR[cbLen];
if ( !pszCSPString || !pszCSPStringCopy ) { DBGPRINTF((DBG_CONTEXT, "Couldn't allocate memory for CSP string\n")); //
// Clean out the PIN info - the less time we leave it lying around,
// the better
//
memset( pszPIN, 0, cbPIN ); delete [] pszPIN; memset( pszSerialNumber, 0, cbSerialNumber ); delete [] pszSerialNumber; memset( pszPersonality, 0, cbPersonality ); delete [] pszPersonality; m_dwStatus = CERT_ERR_INTERNAL; goto EndRetrieveCertContext; } //
// Build the magic string that will unlock the Fortezza secret ...
//
strcpy( pszCSPString, pszSerialNumber ); strcat( pszCSPString, "\n" ); strcat( pszCSPString, pszPersonality ); strcat( pszCSPString, "\n" ); strcat( pszCSPString, pszPIN ); //
// Make a copy we can reuse later
//
strcpy( pszCSPStringCopy, pszCSPString );
//
// Clean out the PIN & serial number - the less time we leave it lying around,
// the better
//
memset( pszPIN, 0, cbPIN ); delete [] pszPIN; memset( pszSerialNumber, 0, cbSerialNumber ); delete [] pszSerialNumber; memset( pszPersonality, 0, cbPersonality ); delete [] pszPersonality; //
// Get a handle to the CSP, passing in the serial # etc and the
// CRYPT_SILENT flag to avoid any UI
//
if ( !CryptAcquireContext( &m_hCryptProv, pszCSPString, NULL, PROV_FORTEZZA, CRYPT_SILENT | CRYPT_MACHINE_KEYSET ) ) { DBGPRINTF((DBG_CONTEXT, "Couldn't get handle to Fortezza CSP : 0x%d\n", GetLastError()));
m_dwStatus = CERT_ERR_CAPI; goto EndRetrieveCertContext; }
if ( !IIS_SERVER_CERT::m_hFortezzaCSP ) { //
// There's apparently no way to duplicate an HCRYPTPROV handle,
// so we have to call CryptAcquireContext again. Also, rumour
// has it that the Fortezza CSP nulls out the string passed to it,
// so we have to use a copy of it
//
if ( !CryptAcquireContext( &IIS_SERVER_CERT::m_hFortezzaCSP, pszCSPStringCopy, NULL, PROV_FORTEZZA, CRYPT_SILENT | CRYPT_MACHINE_KEYSET ) ) { DBGPRINTF((DBG_CONTEXT, "Couldn't get handle to Fortezza CSP : 0x%d\n", GetLastError()));
m_dwStatus = CERT_ERR_CAPI; goto EndRetrieveCertContext; } //
// Install the handler used to verify Fortezza signatures.
//
CRYPT_DEFAULT_CONTEXT_MULTI_OID_PARA CDCMOP; LPSTR rgszOID[2];
CDCMOP.cOID = 2; CDCMOP.rgpszOID = rgszOID; CDCMOP.rgpszOID[0] = szOID_INFOSEC_mosaicUpdatedSig; CDCMOP.rgpszOID[1] = szOID_INFOSEC_mosaicKMandUpdSig;
if (!CryptInstallDefaultContext( IIS_SERVER_CERT::m_hFortezzaCSP, CRYPT_DEFAULT_CONTEXT_MULTI_CERT_SIGN_OID, &CDCMOP, CRYPT_DEFAULT_CONTEXT_PROCESS_FLAG, NULL, &IIS_SERVER_CERT::m_hFortezzaCtxt ) ) { DBGPRINTF((DBG_CONTEXT, "Failed to install Fortezza context : 0x%d\n", GetLastError())); m_dwStatus = CERT_ERR_CAPI; goto EndRetrieveCertContext; } } } } mb.Close();
//
// Read store name etc out of MB
//
m_pStoreInfo = ReadCertStoreInfoFromMB( pMDObject, m_strMBPath.QueryStr(), FALSE );
if ( !m_pStoreInfo ) { m_dwStatus = CERT_ERR_MB; goto EndRetrieveCertContext; }
//
// Open store in which to look for the cert
//
if ( !(m_pStoreInfo->hCertStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_A, 0, (fProgPINEntry ? m_hCryptProv : NULL ), CERT_SYSTEM_STORE_LOCAL_MACHINE, m_pStoreInfo->pszStoreName ) ) )
{ m_dwStatus = CERT_ERR_CAPI; goto EndRetrieveCertContext; }
//
// Try to find the cert in the store
//
CRYPT_HASH_BLOB HashBlob; HashBlob.cbData = dwHashSize; HashBlob.pbData = pbCertHash; m_pCertContext = CertFindCertificateInStore( m_pStoreInfo->hCertStore, X509_ASN_ENCODING, 0, CERT_FIND_SHA1_HASH, (VOID *) &HashBlob, NULL ); if ( !m_pCertContext ) { m_dwStatus = CERT_ERR_CERT_NOT_FOUND; goto EndRetrieveCertContext; } else { #if DBG
CHAR szSubjectName[1024]; if ( CertGetNameString( m_pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, szSubjectName, 1024 ) ) { DBGPRINTF((DBG_CONTEXT, "Retrieved cert for %s \n", szSubjectName)); } #endif
} if ( fProgPINEntry ) { //
// If this is a Fortezza cert, then we should set the
// CERT_KEY_PROV_HANDLE_PROP_ID property of the context to point to
// the HCRYPTPROV retrieved by CryptAcquireContext(). Why? Because
// if we don't set this property, Schannel will try to acquire the
// provider handle itself, and will NOT set the CRYPT_MACHINE_KEY
// flag when doing the call to CryptAcquireContext() and thus we
// will not be able to acquire the credential handle in SSPIFILT.
//
// Fortezza rocks
//
if ( !CertSetCertificateContextProperty( m_pCertContext, CERT_KEY_PROV_HANDLE_PROP_ID, CERT_STORE_NO_CRYPT_RELEASE_FLAG, (VOID*) m_hCryptProv ) ) { m_dwStatus = CERT_ERR_CAPI; goto EndRetrieveCertContext; } }
//
// If we got this far, everything is happy
//
m_dwStatus = CERT_ERR_NONE;
EndRetrieveCertContext:
//
// This is where all the cleanup that takes place only if we fail needs to
// happen
//
DBG_ASSERT( m_dwStatus < CERT_ERR_END );
if ( m_dwStatus != CERT_ERR_NONE ) { DBGPRINTF((DBG_CONTEXT, "IIS_SERVER cert constructor, Error occurred : 0x%x\n", GetLastError()));
if ( m_pCertContext != NULL ) { CertFreeCertificateContext( m_pCertContext ); m_pCertContext = NULL; }
if ( m_pStoreInfo ) { DeallocateCertStoreInfo( m_pStoreInfo ); m_pStoreInfo = NULL; }
if ( m_hCryptProv != NULL ) { CryptReleaseContext( m_hCryptProv, 0 ); m_hCryptProv = NULL; } }
//
// Cleanup we do regardless of success/failure
//
//
// clean out memory holding PIN
//
if ( pszCSPString ) { memset( pszCSPString, 0, strlen( pszCSPString ) ); delete [] pszCSPString; }
if ( pszCSPStringCopy ) { memset( pszCSPStringCopy, 0, strlen( pszCSPStringCopy ) ); delete [] pszCSPStringCopy; }
mb.Close();
} //IIS_SERVER_CERT::IIS_SERVER_CERT
IIS_SERVER_CERT::~IIS_SERVER_CERT() /*++
Routine Description: Destructor
Arguments : None
Returns :
Nothing --*/ { if ( m_pCertContext ) { CertFreeCertificateContext( m_pCertContext ); m_pCertContext = NULL; }
if ( m_pStoreInfo ) { DeallocateCertStoreInfo( m_pStoreInfo ); m_pStoreInfo = NULL; }
if ( m_hCryptProv ) { CryptReleaseContext( m_hCryptProv, 0 ); m_hCryptProv = NULL; } } //~IIS_SERVER_CERT::IIS_SERVER_CERT
BOOL IIS_SERVER_CERT::RetrievePINInfo( IN MB *pMB, OUT LPTSTR *ppszPIN, OUT LPTSTR *ppszSerialNumber, OUT LPTSTR *ppszPersonality ) /*++
Routine Description:
Retrieve PIN information for Fortezza certificates
Arguments:
pMB - pointer to MB object, open for reading ppszPIN - pointer to pointer to PIN for Fortezza card, updated on success ppszSerialNumber - pointer to pointer to card serial number, updated on success ppszPersonality - pointer to pointer to Fortezza "personality", updated on sucess
Returns:
BOOL indicating success/failure
--*/ { DBG_ASSERT( pMB && ppszPIN && ppszSerialNumber && ppszPersonality );
DWORD cbLen = 0; BOOL fSuccess = FALSE; DWORD dwReqDataLen = 0; METADATA_RECORD mdr;
//
// Retrieve PIN
//
cbLen = 0; if ( ( !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_PIN, IIS_MD_UT_SERVER, NULL, &cbLen, METADATA_SECURE ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER ) || !cbLen ) { goto end_pin_info; } *ppszPIN = new CHAR[cbLen + 1]; if ( !*ppszPIN || !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_PIN, IIS_MD_UT_SERVER, *ppszPIN, &cbLen, METADATA_SECURE ) ) { goto end_pin_info; } //
// Retrieve serial #
//
cbLen = 0; if ( ( !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_SERIAL_NUMBER, IIS_MD_UT_SERVER, NULL, &cbLen, METADATA_SECURE ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER ) || !cbLen ) { goto end_pin_info; } *ppszSerialNumber = new CHAR[cbLen + 1];
if ( !*ppszSerialNumber || !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_SERIAL_NUMBER, IIS_MD_UT_SERVER, *ppszSerialNumber, &cbLen, METADATA_SECURE ) ) { goto end_pin_info; }
//
// Retrieve personality
//
cbLen = 0; if ( ( !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_PERSONALITY, IIS_MD_UT_SERVER, NULL, &cbLen, METADATA_SECURE ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER ) || !cbLen ) { goto end_pin_info; } *ppszPersonality = new CHAR[cbLen + 1]; if ( !*ppszPersonality || !pMB->GetString( NULL, MD_SSL_CERT_FORTEZZA_PERSONALITY, IIS_MD_UT_SERVER, *ppszPersonality, &cbLen, METADATA_SECURE ) ) { goto end_pin_info; } fSuccess = TRUE;
end_pin_info:
if ( !fSuccess ) { DBGPRINTF((DBG_CONTEXT, "RetrievePINInfo failed : 0x%x\n", GetLastError()));
//
// Clean out all the PIN info, making sure to erase the memory
//
if ( *ppszPIN ) { /* INTRINSA suppress = uninitialized */ memset( *ppszPIN, 0, strlen(*ppszPIN) ); delete [] *ppszPIN; }
if ( *ppszSerialNumber ) { memset( *ppszSerialNumber, 0, strlen(*ppszSerialNumber) ); delete [] *ppszSerialNumber; }
if ( *ppszPersonality ) { memset( *ppszPersonality, 0, strlen(*ppszPersonality) ); delete [] *ppszPersonality; } }
return fSuccess; }
inline BOOL IIS_SERVER_CERT::IsValid() { return ( m_dwStatus == CERT_ERR_NONE ? TRUE : FALSE ) ; }
|