/*++ Copyright (c) 1998 Microsoft Corporation Module Name : iiscertmap.cxx Abstract: IIS Certificate mapping Author: Bilal Alam (BAlam) 19-Apr-2000 Environment: Win32 - User Mode Project: Stream Filter Worker Process --*/ #include "precomp.hxx" #include IIS_CERTIFICATE_MAPPING::IIS_CERTIFICATE_MAPPING( VOID ) : _pCert11Mapper( NULL ), _pCertWildcardMapper( NULL ), _cRefs( 1 ) { } IIS_CERTIFICATE_MAPPING::~IIS_CERTIFICATE_MAPPING( VOID ) { if ( _pCert11Mapper != NULL ) { delete _pCert11Mapper; _pCert11Mapper = NULL; } if ( _pCertWildcardMapper != NULL ) { delete _pCertWildcardMapper; _pCertWildcardMapper = NULL; } } HRESULT IIS_CERTIFICATE_MAPPING::Read11Mappings( DWORD dwSiteId ) /*++ Routine Description: Read 1-1 mappings from metabase Arguments: dwSiteId - Site ID Return Value: HRESULT --*/ { MB mb( g_pW3Server->QueryMDObject() ); WCHAR achMBPath[ 256 ]; HRESULT hr = NO_ERROR; // besides terminating '\0' one extra char is needed for leading slash WCHAR achMappingName[ ADMINDATA_MAX_NAME_LEN + 1 + 1 ]; BOOL fRet; DWORD dwIndex; STACK_BUFFER( buff, 1024 ); DWORD cbRequired; STACK_STRU( strTemp, 64 ); STACK_STRU( strUserName, 64 ); STACK_STRU( strPassword, 64 ); DWORD dwEnabled; CIisMapping * pCertMapping; // // Setup the metabase path to get at 1-1 mappings // _snwprintf( achMBPath, sizeof( achMBPath ) / sizeof( WCHAR ) - 1, L"/LM/W3SVC/%d/Cert11/Mappings", dwSiteId ); achMBPath[ sizeof( achMBPath ) / sizeof( WCHAR ) - 1 ] = '\0'; // // Open the metabase and read 1-1 mapping properties // fRet = mb.Open( achMBPath, METADATA_PERMISSION_READ ); if ( fRet ) { dwIndex = 0; achMappingName[ 0 ] = L'/'; for ( ; ; ) // loop un { dwEnabled = FALSE; // // We will need to prepend the object name with '/'. Hence // goofyness of sending an offseted pointed to name // fRet = mb.EnumObjects( L"", achMappingName + 1, dwIndex ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); break; } // // Get certificate blob // cbRequired = buff.QuerySize(); fRet = mb.GetData( achMappingName, MD_MAPCERT, IIS_MD_UT_SERVER, BINARY_METADATA, buff.QueryPtr(), &cbRequired, METADATA_NO_ATTRIBUTES ); if ( !fRet ) { if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { DBG_ASSERT( cbRequired > buff.QuerySize() ); if ( !buff.Resize( cbRequired ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } fRet = mb.GetData( achMappingName, MD_MAPCERT, IIS_MD_UT_SERVER, BINARY_METADATA, buff.QueryPtr(), &cbRequired, METADATA_NO_ATTRIBUTES ); if ( !fRet ) { DBG_ASSERT( GetLastError() != ERROR_INSUFFICIENT_BUFFER ); hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == MD_ERROR_DATA_NOT_FOUND ) { // if cert blob is not present we skip this entry goto NextEntry; } else { break; } } } else { hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == MD_ERROR_DATA_NOT_FOUND ) { // if cert blob is not present we skip this entry goto NextEntry; } else { break; } } } // // Get NT account name // if ( !mb.GetStr( achMappingName, MD_MAPNTACCT, IIS_MD_UT_SERVER, &strUserName ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == MD_ERROR_DATA_NOT_FOUND ) { // if account name is not present we skip this entry goto NextEntry; } else { break; } break; } // // Get NT password // if ( !mb.GetStr( achMappingName, MD_MAPNTPWD, IIS_MD_UT_SERVER, &strPassword ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == MD_ERROR_DATA_NOT_FOUND ) { // we assume default password - empty; strPassword.Reset(); } else { break; } } // // Is this mapping enabled? // if ( !mb.GetDword( achMappingName, MD_MAPENABLED, IIS_MD_UT_SERVER, &dwEnabled ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == MD_ERROR_DATA_NOT_FOUND ) { // we assume default 0 (FALSE); dwEnabled = 0; } else { break; } } // // If this mapping is enabled, add it to 1-1 mapper // if ( dwEnabled ) { if ( _pCert11Mapper == NULL ) { _pCert11Mapper = new CIisCert11Mapper(); if ( _pCert11Mapper == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } // // Reset() will configure default hierarchies // If hierarchies are not configured then comparison (CIisMapping::Cmp() function // implemented in iismap.cxx) will fail // (and mapper will always incorrectly map to first available 1to1 mapping) // if(!_pCert11Mapper->Reset()) { delete _pCert11Mapper; _pCert11Mapper = NULL; hr = HRESULT_FROM_WIN32( GetLastError() ); break; } } DBG_ASSERT( _pCert11Mapper != NULL ); pCertMapping = _pCert11Mapper->CreateNewMapping(); if ( pCertMapping == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } if ( !pCertMapping->MappingSetField( IISMDB_INDEX_CERT11_CERT, (PBYTE) buff.QueryPtr(), cbRequired, FALSE ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); break; } if ( !pCertMapping->MappingSetField( IISMDB_INDEX_CERT11_NT_ACCT, (PBYTE) strUserName.QueryStr(), strUserName.QueryCB() + sizeof (WCHAR), FALSE ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); delete pCertMapping; pCertMapping = NULL; break; } if ( !pCertMapping->MappingSetField( IISMDB_INDEX_CERT11_NT_PWD, (PBYTE) strPassword.QueryStr(), strPassword.QueryCB() + sizeof (WCHAR), FALSE ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); delete pCertMapping; pCertMapping = NULL; break; } if ( !((CIisAcctMapper*)_pCert11Mapper)->Add( pCertMapping, FALSE ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); delete pCertMapping; pCertMapping = NULL; break; } } NextEntry: dwIndex++; } } else { hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } return hr; } HRESULT IIS_CERTIFICATE_MAPPING::ReadWildcardMappings( DWORD dwSiteId ) /*++ Routine Description: Read wildcard mappings from metabase Arguments: dwSiteId - Site ID (duh) Return Value: HRESULT --*/ { MB mb( g_pW3Server->QueryMDObject() ); WCHAR achMBPath[ 256 ]; BOOL fRet; BYTE abBuffer[ 1024 ]; BUFFER buff( abBuffer, sizeof( abBuffer ) ); DWORD cbRequired; PUCHAR pSerializedMapping; // // Setup the metabase path to get at wildcard mappings // _snwprintf( achMBPath, sizeof( achMBPath ) / sizeof( WCHAR ) - 1, L"/LM/W3SVC/%d/", dwSiteId ); achMBPath[ sizeof( achMBPath ) / sizeof( WCHAR ) - 1 ] = '\0'; // // Open the metabase and read wildcard mappings // fRet = mb.Open( achMBPath, METADATA_PERMISSION_READ ); if ( fRet ) { cbRequired = buff.QuerySize(); fRet = mb.GetData( L"", MD_SERIAL_CERTW, IIS_MD_UT_SERVER, BINARY_METADATA, buff.QueryPtr(), &cbRequired, METADATA_NO_ATTRIBUTES ); if ( !fRet ) { if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { DBG_ASSERT( cbRequired > buff.QuerySize() ); if ( !buff.Resize( cbRequired ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } fRet = mb.GetData( L"", MD_SERIAL_CERTW, IIS_MD_UT_SERVER, BINARY_METADATA, buff.QueryPtr(), &cbRequired, METADATA_NO_ATTRIBUTES ); if ( !fRet ) { DBG_ASSERT( GetLastError() != ERROR_INSUFFICIENT_BUFFER ); return HRESULT_FROM_WIN32( GetLastError() ); } } else { return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } } // // Thanx to the man, I can just unserialize. XBF rocks ;-) // DBG_ASSERT( _pCertWildcardMapper == NULL ); _pCertWildcardMapper = new CIisRuleMapper(); if ( _pCertWildcardMapper == NULL ) { return HRESULT_FROM_WIN32( GetLastError() ); } if( !_pCertWildcardMapper->IsValid() ) { // // creation of _pCertWildcardMapper failed // assume out of memory // return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY ); } pSerializedMapping = (PUCHAR) buff.QueryPtr(); // Unserialize will change the value of pSerializedMapping // It will point to the end of the unserialized data // We don't need that modified pointer. But remember not to use // pSerializedMapping any more after Unserialize call // fRet = _pCertWildcardMapper->Unserialize( &pSerializedMapping, &cbRequired ); if ( !fRet ) { return HRESULT_FROM_WIN32( GetLastError() ); } } else { return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); } return NO_ERROR; } //static HRESULT IIS_CERTIFICATE_MAPPING::GetCertificateMapping( DWORD dwSiteId, IIS_CERTIFICATE_MAPPING ** ppCertificateMapping ) /*++ Routine Description: Read appropriate metabase configuration to get configured IIS certificate mapping Arguments: dwSiteId - Site ID (duh) ppCertificateMapping - Filled with certificate mapping descriptor on success Return Value: HRESULT --*/ { IIS_CERTIFICATE_MAPPING * pCertMapping = NULL; HRESULT hr = NO_ERROR; if ( ppCertificateMapping == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppCertificateMapping = NULL; // // Create a certificate mapping descriptor // pCertMapping = new IIS_CERTIFICATE_MAPPING(); if ( pCertMapping == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Finished; } // // Read 1-1 mappings // hr = pCertMapping->Read11Mappings( dwSiteId ); if ( FAILED( hr ) && hr != HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) { DBGPRINTF(( DBG_CONTEXT, "Error reading 1to1 Certificate Mappings. hr = %x\n", hr )); goto Finished; } hr = NO_ERROR; // // Read wildcards // hr = pCertMapping->ReadWildcardMappings( dwSiteId ); if ( FAILED( hr ) && hr != HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) ) { DBGPRINTF(( DBG_CONTEXT, "Error reading Wildcard Certificate Mappings. hr = %x\n", hr )); goto Finished; } hr = NO_ERROR; Finished: if ( FAILED( hr ) ) { if ( pCertMapping != NULL ) { delete pCertMapping; pCertMapping = NULL; } } else { DBG_ASSERT( pCertMapping != NULL ); *ppCertificateMapping = pCertMapping; } return hr; } HRESULT IIS_CERTIFICATE_MAPPING::DoMapCredential( W3_MAIN_CONTEXT * pMainContext, PBYTE pClientCertBlob, DWORD cbClientCertBlob, TOKEN_CACHE_ENTRY ** ppCachedToken, BOOL * pfClientCertDeniedByMapper ) { CIisMapping * pQuery; CIisMapping * pResult; WCHAR wszUserName[ UNLEN + IIS_DNLEN + 1 + 1 ]; LPWSTR pwszUserName; DWORD cbUserName; WCHAR wszPassword[ PWLEN + 1 ]; LPWSTR pwszPassword; DWORD cbPassword; CHAR * pszDomain; BOOL fMatch = FALSE; DWORD dwLogonError = NO_ERROR; HRESULT hr = S_OK; BOOL fPossibleUPNLogon = FALSE; // // add 1 to strUserDomainW for separator "\" // STACK_STRU( strUserDomainW, UNLEN + IIS_DNLEN + 1 + 1 ); STACK_STRU( strUserNameW, UNLEN + IIS_DNLEN + 1 + 1 ); STACK_STRU( strDomainNameW, IIS_DNLEN + 1 ); STACK_STRU( strPasswordW, PWLEN + 1 ); DBG_ASSERT( pClientCertBlob != NULL ); DBG_ASSERT( cbClientCertBlob != 0 ); DBG_ASSERT( ppCachedToken != NULL ); DBG_ASSERT( pfClientCertDeniedByMapper != NULL ); // // First try the 1-1 mapper // if ( _pCert11Mapper != NULL ) { // // Build a query mapping to check // pQuery = _pCert11Mapper->CreateNewMapping( pClientCertBlob, cbClientCertBlob ); if ( pQuery == NULL ) { return SEC_E_INTERNAL_ERROR; } // // no need to lock cert mapper because this is read only copy // used for mapping execution in worker process if ( _pCert11Mapper->FindMatch( pQuery, &pResult ) ) { // // Awesome. We found a match. Do the deed if the rule is // enabled // if ( pResult->MappingGetField( IISMDB_INDEX_CERT11_NT_ACCT, (PBYTE *)&pwszUserName, &cbUserName, FALSE ) && pResult->MappingGetField( IISMDB_INDEX_CERT11_NT_PWD, (PBYTE *)&pwszPassword, &cbPassword, FALSE ) ) { // // No need to check for Enabled (IISMDB_INDEX_CERT11_ENABLED) // since Read11Mappings() will build mapping table consisting only // of enabled mappings // // // Make copy of user and password // hr = strUserDomainW.Copy( pwszUserName ); if ( FAILED( hr ) ) { return hr; } hr = strPasswordW.Copy( pwszPassword ); if ( FAILED( hr ) ) { return hr; } fMatch = TRUE; } } delete pQuery; pQuery = NULL; } // // Try the wildcard mapper if we still haven't found a match // if ( !fMatch && _pCertWildcardMapper != NULL ) { PCCERT_CONTEXT pClientCert = CertCreateCertificateContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pClientCertBlob, cbClientCertBlob ); if ( pClientCert == NULL ) { DBG_ASSERT( pClientCert != NULL ); } // Wildcard mapper assumes that // achUserName buffer is greater than UNLEN+IIS_DNLEN+1 and // achPassword buffer is greater then PWLEN // else if ( !_pCertWildcardMapper->Match( (PCERT_CONTEXT) pClientCert, NULL, // legacy value wszUserName, wszPassword ) ) { // // If the mapping rule is denied then return // a NULL pointer through ppCachedToken with SEC_E_OK. // That indicated to caller that mapping was denied // if ( GetLastError() == ERROR_ACCESS_DENIED ) { *ppCachedToken = NULL; *pfClientCertDeniedByMapper = TRUE; if ( pClientCert != NULL ) { CertFreeCertificateContext( pClientCert ); pClientCert = NULL; } return SEC_E_OK; } } else { fMatch = TRUE; // // Copy user and password (user name may be fully qualified with domain name in it) // hr = strUserDomainW.Copy( wszUserName ); if ( FAILED( hr ) ) { return hr; } hr = strPasswordW.Copy( wszPassword ); if ( FAILED( hr ) ) { return hr; } } if ( pClientCert != NULL ) { CertFreeCertificateContext( pClientCert ); pClientCert = NULL; } } // // If we still haven't found a match, then return error // if ( fMatch ) { // // Split up the user name into domain/user if needed // Note: DefaultLogonDomain is not used for cert mapping at all // This is to keep behaviour equivalent with former versions of IIS // hr = W3_STATE_AUTHENTICATION::SplitUserDomain( strUserDomainW, &strUserNameW, &strDomainNameW, NULL, // no default domain &fPossibleUPNLogon ); if ( FAILED( hr ) ) { return hr; } // // We should have valid credentials to call LogonUser() // DBG_ASSERT( g_pW3Server->QueryTokenCache() != NULL ); hr = g_pW3Server->QueryTokenCache()->GetCachedToken( strUserNameW.QueryStr(), strDomainNameW.QueryStr(), strPasswordW.QueryStr(), LOGON32_LOGON_NETWORK_CLEARTEXT, FALSE, //don't use subauth fPossibleUPNLogon, pMainContext->QueryRequest()-> QueryRemoteSockAddress(), ppCachedToken, &dwLogonError ); if ( FAILED( hr ) ) { return SEC_E_UNKNOWN_CREDENTIALS; } // // If *ppCachedToken is NULL, then logon failed // if ( *ppCachedToken == NULL ) { // // Note: With IIS5 we used to log logon failure to event log // however it doesn't seem to be necessary because if logon/logoff auditing is enabled // then security log would have relevant information about the logon failure // DBG_ASSERT( dwLogonError != ERROR_SUCCESS ); return SEC_E_UNKNOWN_CREDENTIALS; } DBG_ASSERT( (*ppCachedToken)->CheckSignature() ); *pfClientCertDeniedByMapper = FALSE; return SEC_E_OK; } return SEC_E_UNKNOWN_CREDENTIALS; }