/*++

   Copyright    (c)    2001    Microsoft Corporation

   Module  Name :
     iisctl.cxx

   Abstract:
     IIS CTL (Certificate Trust List) handler
     This gets used only with SSL client certificates
 
   Author:
     Jaroslav Dunajsky

   Environment:
     Win32 - User Mode

   Project:
     Stream Filter Worker Process
--*/

#include "precomp.hxx"

IIS_CTL_HASH *      IIS_CTL::sm_pIisCtlHash;

IIS_CTL::IIS_CTL( 
    IN  CREDENTIAL_ID *         pCredentialId 
) : _pCredentialId( pCredentialId ),
    _pCtlContext( NULL ),
    _pCtlStore( NULL ),
    _cRefs( 1 )
{
    _dwSignature = IIS_CTL_SIGNATURE;
}

IIS_CTL::~IIS_CTL()
{
    if ( _pCtlContext != NULL )
    {
        CertFreeCTLContext( _pCtlContext );
        _pCtlContext = NULL;
    }
    
    if ( _pCtlStore != NULL )
    {
        _pCtlStore->DereferenceStore();
        _pCtlStore = NULL;
    }

    if ( _pCredentialId != NULL )
    {
        delete _pCredentialId;
        _pCredentialId = NULL;
    }

    
    _dwSignature = IIS_CTL_SIGNATURE_FREE;
}

//static
HRESULT
IIS_CTL::Initialize(
    VOID
)
/*++

Routine Description:

    Initialize IIS CTL globals

Arguments:

    None

Return Value:

    HRESULT

--*/
{
    sm_pIisCtlHash = new IIS_CTL_HASH();
    if ( sm_pIisCtlHash == NULL )
    {
        return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY );
    }
    
    return NO_ERROR;
}

//static
VOID
IIS_CTL::Terminate(
    VOID
)
/*++

Routine Description:

    Cleanup IIS Ctl globals

Arguments:

    None

Return Value:

    None

--*/
{
    if ( sm_pIisCtlHash != NULL )
    {
        delete sm_pIisCtlHash;
        sm_pIisCtlHash = NULL;
    }
}

//static
HRESULT
IIS_CTL::GetIisCtl(
    IN  WCHAR *          pszSslCtlIdentifier,
    IN  WCHAR *          pszSslCtlStoreName,
    OUT IIS_CTL **       ppIisCtl
)
/*++

Routine Description:

    Find a suitable Ctl for use with the site represented by
    given site SslConfig

Arguments:

    pzsSslCtlIdentifier - identifies CTL
    pszSslCtlStoreName -  store name where CTL is to be located (in the LOCAL_MACHINE context)
                        If NULL, then default "CA" store is assumed
    ppIisCtl -          pointer to IIS CTL

Return Value:

    HRESULT

--*/
{
    IIS_CTL *               pIisCtl = NULL;
    CREDENTIAL_ID *         pCredentialId = NULL;
    HRESULT                 hr = NO_ERROR;
    LK_RETCODE              lkrc;
    
    if ( ppIisCtl == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    *ppIisCtl = NULL;

    if ( pszSslCtlStoreName == NULL )
    {
        //
        // assume default store name
        //
        pszSslCtlStoreName = L"CA";
    }
    
    //
    // First build up a Credential ID to use in looking up in our
    // server cert cache
    //
    
    pCredentialId = new CREDENTIAL_ID;
    if ( pCredentialId == NULL )
    {
        return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY );
    }
    
    hr = IIS_CTL::BuildCredentialId( pszSslCtlIdentifier,
                                     pCredentialId );
    if ( FAILED( hr ) )
    {
        //
        // Regardless of error, we are toast because we couldn't find
        // a server cert
        //
        
        delete pCredentialId;
        return hr;
    }
    
    DBG_ASSERT( sm_pIisCtlHash != NULL );
    
    lkrc = sm_pIisCtlHash->FindKey( pCredentialId,
                                    &pIisCtl );
    if ( lkrc == LK_SUCCESS )
    {
        //
        // Server already contains a credential ID
        //
        
        delete pCredentialId;
        
        *ppIisCtl = pIisCtl; 
        
        return NO_ERROR;
    }    
    
    //
    // Ok.  It wasn't in our case, we need to do it there
    // 
    // if IIS_CTL construction succeeds then IIS_CTL 
    // takes ownership of pCredentialId and is responsible for freeing it
    //
    
    pIisCtl = new IIS_CTL( pCredentialId );
    if ( pIisCtl == NULL )
    {
        hr = HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY );

        delete pCredentialId;
        
        return hr;
    }
    
    hr = pIisCtl->SetupIisCtl( pszSslCtlIdentifier,
                               pszSslCtlStoreName );
    if ( FAILED( hr ) )
    {
        //
        // Server certificate owns the reference to pCredentialId now
        //
        
        delete pIisCtl;
        
        return hr;
    }
    
    //
    // Now try to add cert to hash.  
    //
    
    lkrc = sm_pIisCtlHash->InsertRecord( pIisCtl );

    //
    // Ignore the error.  If it didn't get added then we will naturally 
    // clean it up on the callers dereference
    //

    *ppIisCtl = pIisCtl;    
    
    return NO_ERROR;
}

//static
HRESULT
IIS_CTL::BuildCredentialId(
    IN  WCHAR *            pszSslCtlIdentifier,
    OUT CREDENTIAL_ID *    pCredentialId
)
/*++

Routine Description:

    Read the configured server cert and CTL hash.  This forms the identifier
    for the credentials we need for this site

Arguments:

    pszSslCtlIdentifier - CTL identifier
    pCredentialId - Filled with credential ID 

Return Value:

    HRESULT

--*/
{
    STACK_BUFFER(       buff, 64 );
    HRESULT             hr = NO_ERROR;

    if ( pCredentialId == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    if ( pszSslCtlIdentifier == NULL ||
         pszSslCtlIdentifier[0] == '\0' )
    {
        //
        // No CTL
        //
        
        return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
    }        
    else
    {
        //
        // Add to our credential ID
        //
        
        hr = pCredentialId->Append( (BYTE*) pszSslCtlIdentifier,
                                    (DWORD) wcslen( pszSslCtlIdentifier ) * sizeof(WCHAR)  );
        if ( FAILED( hr ) )
        {
            return hr;
        }    
    }    

    return NO_ERROR;
}


//private
//static
HRESULT
IIS_CTL::SetupIisCtl(
    IN WCHAR *       pszSslCtlIdentifier,
    IN WCHAR *       pszSslCtlStoreName
)
/*++

Routine Description:

    Build CTL context for the site based on SiteSslConfig Info
  
Arguments:

    pszSslCtlIdentifier - identifies CTL
    pszSslCtlStoreName  - store name where CTL is to be located (in the LOCAL_MACHINE context)


 
Return Value:

    HRESULT

--*/
{
    HRESULT             hr = E_FAIL;
    PCCTL_CONTEXT       pCtlContext = NULL;
    CERT_STORE *        pCertStore = NULL;
    STACK_STRU(         strStoreName, 256 );


    if ( pszSslCtlIdentifier == NULL ||
         pszSslCtlStoreName == NULL ||
         pszSslCtlStoreName == L'\0' )

    {
        // CTLs not configured
        _pCtlContext = NULL;
        return S_OK;
    }

    //
    // First get the desired store and store it away for later!
    //

    hr = strStoreName.Copy( pszSslCtlStoreName );
    if ( FAILED( hr ) )
    {
        goto Finished;
    }
    
    hr = CERT_STORE::OpenStore( strStoreName,
                                &pCertStore );
    if ( FAILED( hr ) )
    {
        goto Finished;
    }
    
    DBG_ASSERT( pCertStore != NULL );
    _pCtlStore = pCertStore;        

    
    CTL_FIND_USAGE_PARA CtlFindUsagePara;
    ZeroMemory( &CtlFindUsagePara, 
                sizeof(CtlFindUsagePara) );

    CtlFindUsagePara.cbSize = sizeof(CtlFindUsagePara);
    CtlFindUsagePara.ListIdentifier.cbData = 
            ( (DWORD) wcslen( pszSslCtlIdentifier ) + 1 ) * sizeof(WCHAR);
    CtlFindUsagePara.ListIdentifier.pbData = (PBYTE) pszSslCtlIdentifier;


    //
    // Try to find CTL in specified store
    //
    pCtlContext = CertFindCTLInStore( _pCtlStore->QueryStore(),
                                      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                      0,
                                      CTL_FIND_USAGE,
                                      (LPVOID) &CtlFindUsagePara,
                                      NULL );

    if ( pCtlContext == NULL )
    {
        
        hr = HRESULT_FROM_WIN32( GetLastError() );
        goto Finished;
 
    }
    _pCtlContext = pCtlContext;
    hr = S_OK;
    
Finished:
    return hr;
    
}

//static
LK_PREDICATE
IIS_CTL::CertStorePredicate(
    IN IIS_CTL *           pIisCtl,
    IN void *              pvState
)
/*++

  Description:

    DeleteIf() predicate used to find items which reference the 
    CERT_STORE pointed to by pvState    

  Arguments:

    pIisCtl - Server cert
    pvState - Points to CERT_STORE

  Returns:

    LK_PREDICATE   - LKP_PERFORM indicates removing the current 
                                 token from token cache

                     LKP_NO_ACTION indicates doing nothing.

--*/
{
    LK_PREDICATE          lkpAction;
    CERT_STORE *          pCtlStore;

    DBG_ASSERT( pIisCtl != NULL );
    
    pCtlStore = (CERT_STORE*) pvState;
    DBG_ASSERT( pCtlStore != NULL );
    
    if ( pIisCtl->_pCtlStore == pCtlStore ) 
    {
        //
        // Before we delete the cert, flush any site which is referencing
        // it
        //
        
        ENDPOINT_CONFIG::FlushByIisCtl( pIisCtl );
        
        lkpAction = LKP_PERFORM;
    }
    else
    {
        lkpAction = LKP_NO_ACTION;
    }

    return lkpAction;
} 

//static
HRESULT
IIS_CTL::FlushByStore(
    IN CERT_STORE *    pCertStore
)
/*++

Routine Description:

    Flush any server certs which reference the given store
    
Arguments:

    pCertStore - Cert store to check

Return Value:

    HRESULT

--*/
{
    DBG_ASSERT( sm_pIisCtlHash != NULL );
    
    sm_pIisCtlHash->DeleteIf( IIS_CTL::CertStorePredicate, 
                                  pCertStore );
                                  
    return NO_ERROR;
}



HRESULT
IIS_CTL::VerifyContainsCert(
    IN  PCCERT_CONTEXT  pChainTop,
    OUT BOOL *          pfContainsCert
)
/*++

Routine Description:

    Verify is CTL contains given certificate

Arguments:

    pChainTop - top certificate of the chain to be found in CTL
    pfContainsCert - TRUE if contains, FALSE if it doesn't
                     (valuse valid only if function returns SUCCESS)
Return Value:

    HRESULT
    
--*/

{
    HRESULT         hr = E_FAIL;
    const int       SHA1_HASH_SIZE = 20;
    BYTE            rgbChainTopHash[ SHA1_HASH_SIZE ];
    DWORD           cbSize = SHA1_HASH_SIZE;

    if ( _pCtlContext == NULL )
    {
        //
        // pCTLContext could be NULL if there was failure in building
        // CTL context in the SITE CONFIG setup
        //
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }

    //
    // get hash of the certificate to be verified
    //
    if ( !CertGetCertificateContextProperty( pChainTop,
                                             CERT_SHA1_HASH_PROP_ID,
                                             rgbChainTopHash,
                                             &cbSize ) )
    {
        hr = HRESULT_FROM_WIN32( GetLastError() );
        DBGPRINTF((DBG_CONTEXT,
                   "Failed to get cert hash for CTL check: 0x%x\n",
                   hr));
        return hr;
    }

    //
    // Iterate through all the cert hashes in the CTL and compare hashes
    //

    for ( DWORD dwIndex = 0; dwIndex< _pCtlContext->pCtlInfo->cCTLEntry; dwIndex++ )
    {
        CRYPT_DATA_BLOB CertInCTLHashBlob = 
                _pCtlContext->pCtlInfo->rgCTLEntry[dwIndex].SubjectIdentifier;
        //
        // CODEWORK: checking hash size is a bit simplistic way of
        // verifying that SHA1 hash is used in CTL
        //
        if ( CertInCTLHashBlob.cbData != SHA1_HASH_SIZE || 
             CertInCTLHashBlob.pbData == NULL )
        {
            //
            // hash in the CTL is no SHA1 because size is different
            // or invalid CTL
            //
            DBG_ASSERT( FALSE );
            return CRYPT_E_NOT_FOUND;            
        }
        if ( memcmp( CertInCTLHashBlob.pbData, 
                      rgbChainTopHash, 
                      SHA1_HASH_SIZE ) == 0 )
        {
            *pfContainsCert = TRUE;
            return S_OK;
        }
    }
    
    *pfContainsCert = FALSE;
    return S_OK;
}

//static
VOID
IIS_CTL::Cleanup(
    VOID
)
/*++

Routine Description:

    Cleanup must be called before Terminate
    
Arguments:

    none

Return Value:

    VOID

--*/
{
    sm_pIisCtlHash->Clear();
}