/*++ Copyright (c) 2000 Microsoft Corporation Module Name : iisdigestprovider.cxx Abstract: IIS Digest authentication provider - version of Digest auth as implemented by IIS5 and IIS5.1 Author: Jaroslad - based on code from md5filt 10-Nov-2000 Environment: Win32 - User Mode Project: ULW3.DLL --*/ #include "precomp.hxx" #include "iisdigestprovider.hxx" #include "uuencode.hxx" # include #include #include #include #include // // lonsint.dll related heade files // #include #include #define DIGEST_AUTH "Digest" // // value names used by MD5 authentication. // must be in sync with MD5_AUTH_NAMES // enum MD5_AUTH_NAME { MD5_AUTH_USERNAME, MD5_AUTH_URI, MD5_AUTH_REALM, MD5_AUTH_NONCE, MD5_AUTH_RESPONSE, MD5_AUTH_ALGORITHM, MD5_AUTH_DIGEST, MD5_AUTH_OPAQUE, MD5_AUTH_QOP, MD5_AUTH_CNONCE, MD5_AUTH_NC, MD5_AUTH_LAST, }; // // Value names used by MD5 authentication. // must be in sync with MD5_AUTH_NAME // PSTR MD5_AUTH_NAMES[] = { "username", "uri", "realm", "nonce", "response", "algorithm", "digest", "opaque", "qop", "cnonce", "nc" }; // // Local function implementation // static LPSTR SkipWhite( IN OUT LPSTR p ) /*++ Routine Description: Skip white space and ',' Arguments: p - ptr to string Return Value: updated ptr after skiping white space --*/ { while ( SAFEIsSpace((UCHAR)(*p) ) || *p == ',' ) { ++p; } return p; } // // class IIS_DIGEST_AUTH_PROVIDER implementation // //static STRA * IIS_DIGEST_AUTH_PROVIDER::_pstraComputerDomain = NULL; //static HRESULT IIS_DIGEST_AUTH_PROVIDER::Initialize( DWORD dwInternalId ) /*++ Routine Description: Initialize IIS Digest SSPI provider Arguments: None Return Value: HRESULT --*/ { HRESULT hr; SetInternalId( dwInternalId ); _pstraComputerDomain = new STRA; if( _pstraComputerDomain == NULL ) { return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY); } // // Ignore errors that may occur while retrieving domain name // it is important but not critical information // client can always explicitly specify domain // GetLanGroupDomainName( *_pstraComputerDomain ); hr = IIS_DIGEST_CONN_CONTEXT::Initialize(); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Error initializing Digest Auth Prov. hr = %x\n", hr )); return hr; } return NO_ERROR; } //static VOID IIS_DIGEST_AUTH_PROVIDER::Terminate( VOID ) /*++ Routine Description: Terminate IIS SSPI Digest provider Arguments: None Return Value: None --*/ { if( _pstraComputerDomain != NULL ) { delete _pstraComputerDomain; } IIS_DIGEST_CONN_CONTEXT::Terminate(); } HRESULT IIS_DIGEST_AUTH_PROVIDER::DoesApply( IN W3_MAIN_CONTEXT * pMainContext, OUT BOOL * pfApplies ) /*++ Routine Description: Does the given request have credentials applicable to the Digest provider Arguments: pMainContext - Main context representing request pfApplies - Set to true if Digest is applicable Return Value: HRESULT --*/ { CHAR * pszAuthHeader = NULL; W3_METADATA * pMetaData = NULL; HRESULT hr = E_FAIL; STACK_STRA( strPackage, 64 ); DBG_ASSERT( pMainContext != NULL ); DBG_ASSERT( pfApplies != NULL ); *pfApplies = FALSE; // // Is using of Digest SSP enabled? // if ( g_pW3Server->QueryUseDigestSSP() ) { // // Digest SSP is enabled => IIS Digest cannot be used // return NO_ERROR; } // // Get the auth type // if ( FAILED( hr = pMainContext->QueryRequest()->GetAuthType( &strPackage ) ) ) { return hr; } // // No package, no auth // if ( strPackage.IsEmpty() ) { return NO_ERROR; } // // Is it Digest? // if ( strPackage.EqualsNoCase( DIGEST_AUTH ) ) { *pfApplies = TRUE; } return NO_ERROR; } HRESULT IIS_DIGEST_AUTH_PROVIDER::DoAuthenticate( IN W3_MAIN_CONTEXT * pMainContext ) /*++ Description: Do authentication work (we will be called if we apply) Arguments: pMainContext - Main context Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL; PCHAR pszAuthHeader = NULL; BOOL fQOPAuth = FALSE; BOOL fSt = FALSE; HANDLE hAccessTokenImpersonation = NULL; IIS_DIGEST_USER_CONTEXT * pUserContext = NULL; W3_METADATA * pMetaData = NULL; BOOL fSendAccessDenied = FALSE; STACK_STRA( straVerb, 10 ); STACK_STRU( strDigestUri, MAX_URL_SIZE + 1 ); STACK_STRU( strUrl, MAX_URL_SIZE + 1 ); STACK_STRA( straCurrentNonce, NONCE_SIZE + 1 ); LPSTR aValueTable[ MD5_AUTH_LAST ]; DIGEST_LOGON_INFO DigestLogonInfo; CHAR achDomain[ IIS_DNLEN + 1 ]; CHAR achNtUser[ 64 ]; STACK_STRA( straUserName, UNLEN + 1 ); STACK_STRA( straDomainName, IIS_DNLEN + 1 ); STACK_STRA( straMetabaseDomainName, IIS_DNLEN + 1 ); ULONG cbBytesCopied; DBG_ASSERT( pMainContext != NULL ); // // Get the part after the auth type // pszAuthHeader = pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization ); DBG_ASSERT( pszAuthHeader != NULL ); DBG_ASSERT( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 ); // // Skip the name of Authentication scheme // if ( pszAuthHeader[ sizeof(DIGEST_AUTH) ] == '\0' ) { DBG_ASSERT( pszAuthHeader[ sizeof(DIGEST_AUTH) - 1 ] != '\0' ); hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA ); goto ExitPoint; } pszAuthHeader = pszAuthHeader + sizeof(DIGEST_AUTH) - 1; if ( !IIS_DIGEST_CONN_CONTEXT::ParseForName( pszAuthHeader, MD5_AUTH_NAMES, MD5_AUTH_LAST, aValueTable ) ) { hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } // // Simple validation of received arguments // if ( aValueTable[ MD5_AUTH_USERNAME ] == NULL || aValueTable[ MD5_AUTH_REALM ] == NULL || aValueTable[ MD5_AUTH_URI ] == NULL || aValueTable[ MD5_AUTH_NONCE ] == NULL || aValueTable[ MD5_AUTH_RESPONSE ] == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } // // Verify quality of protection (qop) required by client // We only support "auth" type. If anything else is sent by client it will be ignored // if ( aValueTable[ MD5_AUTH_QOP ] != NULL ) { if ( _stricmp( aValueTable[ MD5_AUTH_QOP ], "auth" ) ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); goto ExitPoint; } // // qop="auth" has mandatory arguments CNONCE and NC // if ( aValueTable[ MD5_AUTH_CNONCE ] == NULL || aValueTable[ MD5_AUTH_NC ] == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } fQOPAuth = TRUE; } else { aValueTable[ MD5_AUTH_QOP ] = "none"; aValueTable[ MD5_AUTH_CNONCE ] = "none"; aValueTable[ MD5_AUTH_NC ] = "none"; } if ( FAILED( hr = straCurrentNonce.Copy( aValueTable[ MD5_AUTH_NONCE ] ) ) ) { goto ExitPoint; } // // Verify that the nonce is well-formed // if ( !IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce( straCurrentNonce ) ) { fSendAccessDenied = TRUE; hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } // // What is the request verb? // if ( FAILED( hr = pMainContext->QueryRequest()->GetVerbString( &straVerb ) ) ) { goto ExitPoint; } // // Check URI field match URL // if ( strlen(aValueTable[MD5_AUTH_URI]) > MAX_URL_SIZE ) { hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } // // Normalize DigestUri // hr = UlCleanAndCopyUrl( (PUCHAR)aValueTable[MD5_AUTH_URI], strlen( aValueTable[MD5_AUTH_URI] ), &cbBytesCopied, strDigestUri.QueryStr(), NULL ); if ( FAILED( hr ) ) { goto ExitPoint; } if ( FAILED( hr = pMainContext->QueryRequest()->GetUrl( &strUrl ) ) ) { goto ExitPoint; } if ( !strUrl.Equals( strDigestUri.QueryStr() ) ) { // // Note: RFC says that BAD REQUEST should be returned // but for now to be backward compatible with IIS5.1 // we will return ACCESS_DENIED // fSendAccessDenied = TRUE; hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *) QueryConnectionAuthContext( pMainContext ); if ( pDigestConnContext == NULL ) { // // Create new Authentication context // pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT(); if ( pDigestConnContext == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto ExitPoint; } hr = SetConnectionAuthContext( pMainContext, pDigestConnContext ); if ( FAILED( hr ) ) { goto ExitPoint; } } DBG_ASSERT( pDigestConnContext != NULL ); if ( FAILED( hr = pDigestConnContext->GenerateNonce( ) ) ) { goto ExitPoint; } pMetaData = pMainContext->QueryUrlContext()->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); if ( FAILED( hr = straMetabaseDomainName.CopyW( pMetaData->QueryDomainName() ) ) ) { goto ExitPoint; } if ( FAILED( hr = BreakUserAndDomain( aValueTable[ MD5_AUTH_USERNAME ], straMetabaseDomainName, straDomainName, straUserName ) ) ) { goto ExitPoint; } DigestLogonInfo.pszNtUser = straUserName.QueryStr(); DigestLogonInfo.pszDomain = straDomainName.QueryStr(); DigestLogonInfo.pszUser = aValueTable[ MD5_AUTH_USERNAME ]; DigestLogonInfo.pszRealm = aValueTable[ MD5_AUTH_REALM ]; DigestLogonInfo.pszURI = aValueTable[ MD5_AUTH_URI ]; DigestLogonInfo.pszMethod = straVerb.QueryStr(); DigestLogonInfo.pszNonce = straCurrentNonce.QueryStr(); DigestLogonInfo.pszCurrentNonce = pDigestConnContext->QueryNonce().QueryStr(); DigestLogonInfo.pszCNonce = aValueTable[ MD5_AUTH_CNONCE ]; DigestLogonInfo.pszQOP = aValueTable[ MD5_AUTH_QOP ]; DigestLogonInfo.pszNC = aValueTable[ MD5_AUTH_NC ]; DigestLogonInfo.pszResponse = aValueTable[ MD5_AUTH_RESPONSE ]; fSt = IISLogonDigestUserA( &DigestLogonInfo, IISSUBA_DIGEST , &hAccessTokenImpersonation ); if ( fSt == FALSE ) { DWORD dwRet = GetLastError(); if ( dwRet == ERROR_PASSWORD_MUST_CHANGE || dwRet == ERROR_PASSWORD_EXPIRED ) { return HRESULT_FROM_WIN32( dwRet ); } fSendAccessDenied = TRUE; hr = HRESULT_FROM_WIN32( dwRet ); goto ExitPoint; } // // Response from the client was correct but the nonce has expired, // if ( pDigestConnContext->IsExpiredNonce( straCurrentNonce, pDigestConnContext->QueryNonce() ) ) { // // User knows password but nonce that was used for // response calculation already expired // Respond to client with stale=TRUE // Only Digest header will be sent to client // ( it will prevent state information needed to be passed // from DoAuthenticate() to OnAccessDenied() ) // pDigestConnContext->SetStale( TRUE ); hr = SetDigestHeader( pMainContext, pDigestConnContext ); if ( FAILED( hr ) ) { goto ExitPoint; } // // Don't let anyone else send back authentication headers when // the 401 is sent // pMainContext->SetProviderHandled( TRUE ); // // We need to send a 401 response to continue the handshake. // We have already setup the WWW-Authenticate header // pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized, Http401BadLogon ); pMainContext->SetFinishedResponse(); pMainContext->SetErrorStatus( SEC_E_CONTEXT_EXPIRED ); hr = NO_ERROR; goto ExitPoint; } // // We successfully authenticated. // Create a user context and setup it up // DBG_ASSERT( hAccessTokenImpersonation != NULL ); pUserContext = new IIS_DIGEST_USER_CONTEXT( this ); if ( pUserContext == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); goto ExitPoint; } hr = pUserContext->Create( hAccessTokenImpersonation, aValueTable[MD5_AUTH_USERNAME] ); if ( FAILED( hr ) ) { pUserContext->DereferenceUserContext(); pUserContext = NULL; goto ExitPoint; } pMainContext->SetUserContext( pUserContext ); hr = NO_ERROR; ExitPoint: if ( FAILED( hr ) ) { if ( fSendAccessDenied ) { // // if ACCESS_DENIED then inform server to send 401 response // if SetStatus is not called then server will respond // with 500 Server Error // pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized, Http401BadLogon ); // // SetErrorStatus() and reset value of hr // pMainContext->SetErrorStatus( hr ); hr = NO_ERROR; } if ( hAccessTokenImpersonation != NULL ) { CloseHandle( hAccessTokenImpersonation ); hAccessTokenImpersonation = NULL; } } return hr; } HRESULT IIS_DIGEST_AUTH_PROVIDER::OnAccessDenied( IN W3_MAIN_CONTEXT * pMainContext ) /*++ Description: Add WWW-Authenticate Digest headers Arguments: pMainContext - main context Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; W3_METADATA * pMetaData = NULL; IIS_DIGEST_CONN_CONTEXT * pDigestConnContext = NULL; DBG_ASSERT( pMainContext != NULL ); // // 2 providers implement Digest but they are mutually exclusive // If DigestSSP is enabled then IIS-DIGEST cannot be used // if ( g_pW3Server->QueryUseDigestSSP() ) { // // Digest SSP is enabled => IIS Digest cannot be used // return NO_ERROR; } if( !W3_STATE_AUTHENTICATION::QueryIsDomainMember() ) { // // We are not a domain member, so do nothing // return NO_ERROR; } pDigestConnContext = (IIS_DIGEST_CONN_CONTEXT *) QueryConnectionAuthContext( pMainContext ); if ( pDigestConnContext == NULL ) { // // Create new Authentication context // it may get reused for next request // if connection is reused // pDigestConnContext = new IIS_DIGEST_CONN_CONTEXT(); if ( pDigestConnContext == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } hr = SetConnectionAuthContext( pMainContext, pDigestConnContext ); if ( FAILED( hr ) ) { return hr; } } return SetDigestHeader( pMainContext, pDigestConnContext ); } HRESULT IIS_DIGEST_AUTH_PROVIDER::SetDigestHeader( IN W3_MAIN_CONTEXT * pMainContext, IN IIS_DIGEST_CONN_CONTEXT * pDigestConnContext ) /*++ Description: Add WWW-Authenticate Digest headers Arguments: pMainContext - main context Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; BOOL fStale = FALSE; W3_METADATA * pMetaData = NULL; STACK_STRA( strOutputHeader, MAX_PATH + 1); STACK_STRA( strNonce, NONCE_SIZE + 1 ); DBG_ASSERT( pMainContext != NULL ); pMetaData = pMainContext->QueryUrlContext()->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); fStale = pDigestConnContext->QueryStale( ); // // Reset Stale so that it will not be used for next request // pDigestConnContext->SetStale( FALSE ); if ( FAILED( hr = pDigestConnContext->GenerateNonce() ) ) { return hr; } // // If a realm is configured, use it. Otherwise use host address of // request // STACK_STRA( straRealm, IIS_DNLEN + 1 ); STACK_STRU( strHostAddr, 256 ); if ( pMetaData->QueryRealm() != NULL ) { hr = straRealm.CopyW( pMetaData->QueryRealm() ); } else { hr = pMainContext->QueryRequest()->GetHostAddr( &strHostAddr ); if ( FAILED( hr ) ) { return hr; } hr = straRealm.CopyW( strHostAddr.QueryStr() ); } if ( FAILED( hr ) ) { return hr; } // // build WWW-Authenticate header // if ( FAILED( hr = strOutputHeader.Copy( "Digest qop=\"auth\", realm=\"" ) ) ) { return hr; } if ( FAILED( hr = strOutputHeader.Append( straRealm ) ) ) { return hr; } if ( FAILED( hr = strOutputHeader.Append( "\", nonce=\"" ) ) ) { return hr; } if ( FAILED( hr = strOutputHeader.Append( pDigestConnContext->QueryNonce() ) ) ) { return hr; } if ( FAILED( hr = strOutputHeader.Append( fStale ? "\", stale=true" : "\"" ) ) ) { return hr; } // // Add the header WWW-Authenticate to the response // hr = pMainContext->QueryResponse()->SetHeader( "WWW-Authenticate", 16, strOutputHeader.QueryStr(), strOutputHeader.QueryCCH() ); return hr; } //static HRESULT IIS_DIGEST_AUTH_PROVIDER::GetLanGroupDomainName( OUT STRA& straDomain ) /*++ Routine Description: Tries to retrieve the "LAN group"/domain this machine is a member of. Arguments: straDomain - receives current domain name Returns: HRESULT --*/ { // // NET_API_STATUS is equivalent to WIN32 errors // NET_API_STATUS dwStatus = 0; NETSETUP_JOIN_STATUS JoinStatus; LPWSTR pwszDomainInfo = NULL; HRESULT hr = E_FAIL; dwStatus = NetGetJoinInformation( NULL, &pwszDomainInfo, &JoinStatus ); if( dwStatus == NERR_Success) { if ( JoinStatus == NetSetupDomainName ) { // // we got a domain // DBG_ASSERT( pwszDomainInfo != NULL ); if ( FAILED( hr = straDomain.CopyW( pwszDomainInfo ) ) ) { goto ExitPoint; } } else { // // Domain information is not available // (maybe server is member of workgroup) // straDomain.Reset(); } } else { hr = HRESULT_FROM_WIN32( dwStatus ); goto ExitPoint; } hr = NO_ERROR; ExitPoint: if ( pwszDomainInfo != NULL ) { NetApiBufferFree( (LPVOID) pwszDomainInfo ); } return hr; } //static HRESULT IIS_DIGEST_AUTH_PROVIDER::BreakUserAndDomain( IN PCHAR pszFullName, IN STRA& straMetabaseConfiguredDomain, OUT STRA& straDomainName, OUT STRA& straUserName ) /*++ Routine Description: Breaks up the supplied account into a domain and username; if no domain is specified in the account, tries to use either domain configured in metabase or domain the computer is a part of. Arguments: straFullName - account, of the form domain\username or just username straMetabaseConfiguredDomain - auth domain configured in metabase straDomainName - filled in with domain to use for authentication straUserName - filled in with username on success Return Value: HRESULT --*/ { PCHAR pszSeparator = NULL; HRESULT hr = E_FAIL; if( pszFullName == NULL && pszFullName[0] == '\0' ) { return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } pszSeparator = (PCHAR) _mbschr( (PUCHAR) pszFullName, '\\' ); if ( pszSeparator != NULL ) { if ( FAILED( hr = straDomainName.Copy ( pszFullName, DIFF( pszSeparator - pszFullName ) ) ) ) { return hr; } pszFullName = pszSeparator + 1; } else { straDomainName.Reset(); } if ( FAILED( hr = straUserName.Copy ( pszFullName ) ) ) { return hr; } // // If no domain name was specified, try using the metabase-configured domain name; if that // is non-existent, try getting the name of the domain the computer is a part of // if ( straDomainName.IsEmpty() ) { if ( straMetabaseConfiguredDomain.IsEmpty() ) { if ( FAILED( hr = straDomainName.Copy ( QueryComputerDomain() ) ) ) { return hr; } } else { if ( FAILED( hr = straDomainName.Copy ( straMetabaseConfiguredDomain ) ) ) { return hr; } } } return NO_ERROR; } // // class IIS_DIGEST_USER_CONTEXT implementation // HANDLE IIS_DIGEST_USER_CONTEXT::QueryPrimaryToken( VOID ) /*++ Routine Description: Get primary token for this user Arguments: None Return Value: Token handle --*/ { DBG_ASSERT( _hImpersonationToken != NULL ); if ( _hPrimaryToken == NULL ) { if ( DuplicateTokenEx( _hImpersonationToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &_hPrimaryToken ) ) { DBG_ASSERT( _hPrimaryToken != NULL ); } } return _hPrimaryToken; } HRESULT IIS_DIGEST_USER_CONTEXT::Create( IN HANDLE hImpersonationToken, IN PSTR pszUserName ) /*++ Routine Description: Create an user context Arguments: Return Value: HRESULT --*/ { HRESULT hr = E_FAIL; DBG_ASSERT( pszUserName != NULL ); DBG_ASSERT( hImpersonationToken != NULL ); if ( hImpersonationToken == NULL || pszUserName == NULL ) { return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } _hImpersonationToken = hImpersonationToken; if ( FAILED( hr = _strUserName.CopyA(pszUserName) ) ) { return hr; } return NO_ERROR; } // // Class IIS_DIGEST_CONN_CONTEXT implementation // // Initialize static variables //static ALLOC_CACHE_HANDLER * IIS_DIGEST_CONN_CONTEXT::sm_pachIISDIGESTConnContext = NULL; //static const PCHAR IIS_DIGEST_CONN_CONTEXT::_pszSecret = "IISMD5"; //static const DWORD IIS_DIGEST_CONN_CONTEXT::_cchSecret = 6; //static HCRYPTPROV IIS_DIGEST_CONN_CONTEXT::s_hCryptProv = NULL; //static HRESULT IIS_DIGEST_CONN_CONTEXT::Initialize( VOID ) /*++ Description: Global IIS_DIGEST_CONN_CONTEXT initialization Arguments: None Return Value: HRESULT --*/ { ALLOC_CACHE_CONFIGURATION acConfig; // // Initialize allocation lookaside // acConfig.nConcurrency = 1; acConfig.nThreshold = 100; acConfig.cbSize = sizeof( IIS_DIGEST_CONN_CONTEXT ); DBG_ASSERT( sm_pachIISDIGESTConnContext == NULL ); sm_pachIISDIGESTConnContext = new ALLOC_CACHE_HANDLER( "IIS_DIGEST_CONTEXT", &acConfig ); if ( sm_pachIISDIGESTConnContext == NULL ) { HRESULT hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); DBGPRINTF(( DBG_CONTEXT, "Error initializing sm_pachIISDIGESTSecContext. hr = 0x%x\n", hr )); return hr; } // // Get a handle to the CSP we'll use for all our hash functions etc // if ( !CryptAcquireContext( &s_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) { HRESULT hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF((DBG_CONTEXT, "CryptAcquireContext() failed : 0x%x\n", GetLastError())); return hr; } return S_OK; } //static VOID IIS_DIGEST_CONN_CONTEXT::Terminate( VOID ) /*++ Routine Description: Destroy globals Arguments: None Return Value: None --*/ { DBG_ASSERT( sm_pachIISDIGESTConnContext != NULL ); delete sm_pachIISDIGESTConnContext; sm_pachIISDIGESTConnContext = NULL; if ( s_hCryptProv != NULL ) { CryptReleaseContext( s_hCryptProv, 0 ); s_hCryptProv = NULL; } } //static HRESULT IIS_DIGEST_CONN_CONTEXT::HashData( IN BUFFER& buffData, OUT BUFFER& buffHash ) /*++ Routine Description: Creates MD5 hash of input buffer Arguments: buffData - data to hash buffHash - buffer that receives hash; is assumed to be big enough to contain MD5 hash Return Value: HRESULT --*/ { HCRYPTHASH hHash = NULL; HRESULT hr = E_FAIL; DWORD cbHash = 0; DBG_ASSERT( buffHash.QuerySize() >= MD5_HASH_SIZE ); if ( !CryptCreateHash( s_hCryptProv, CALG_MD5, 0, 0, &hHash ) ) { //DBGPRINTF((DBG_CONTEXT, // "CryptCreateHash() failed : 0x%x\n", GetLastError())); hr = HRESULT_FROM_WIN32( GetLastError() ); goto ExitPoint; } if ( !CryptHashData( hHash, (PBYTE) buffData.QueryPtr(), buffData.QuerySize(), 0 ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto ExitPoint; } cbHash = buffHash.QuerySize(); if ( !CryptGetHashParam( hHash, HP_HASHVAL, (PBYTE) buffHash.QueryPtr(), &cbHash, 0 ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto ExitPoint; } hr = NO_ERROR; ExitPoint: if ( hHash != NULL ) { CryptDestroyHash( hHash ); } return hr; } //static BOOL IIS_DIGEST_CONN_CONTEXT::IsExpiredNonce( IN STRA& strRequestNonce, IN STRA& strPresentNonce ) /*++ Routine Description: Checks whether nonce is expired or not by looking at the timestamp on the nonce that came in with the request and comparing it with the timestamp on the latest nonce Arguments: strRequestNonce - nonce that came in with request strPresentNonce - latest nonce Return Value: TRUE if expired, FALSE if not --*/ { // // Timestamp is after first 2*RANDOM_SIZE bytes of nonce; also, note that // timestamp is time() mod NONCE_GRANULARITY, so all we have to do is simply // compare for equality to check that the request nonce hasn't expired // DBG_ASSERT( strRequestNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE ); DBG_ASSERT( strPresentNonce.QueryCCH() >= 2*RANDOM_SIZE + TIMESTAMP_SIZE ); if ( memcmp( strRequestNonce.QueryStr() + 2*RANDOM_SIZE, strPresentNonce.QueryStr() + 2*RANDOM_SIZE, TIMESTAMP_SIZE ) != 0 ) { return TRUE; } return FALSE; } //static BOOL IIS_DIGEST_CONN_CONTEXT::IsWellFormedNonce( IN STRA& strNonce ) /*++ Routine Description: Checks whether a nonce is "well-formed" by checking hash value, length etc Arguments: pszNonce - nonce to be checked Return Value: TRUE if nonce is well-formed, FALSE if not --*/ { if ( strNonce.QueryCCH()!= NONCE_SIZE ) { return FALSE; } // // Format of nonce :