/*++ 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 header files // #include #include // // 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::sm_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 = NO_ERROR; SetInternalId( dwInternalId ); sm_pstraComputerDomain = new STRA; if( sm_pstraComputerDomain == NULL ) { return HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY); } hr = GetLanGroupDomainName( *sm_pstraComputerDomain ); if ( FAILED( hr ) ) { DBGPRINTF(( DBG_CONTEXT, "Warning: Error calling GetLanGroupDomainName(). hr = %x\n", hr )); // // Ignore errors that may occur while retrieving domain name // it is important but not critical information // client can always explicitly specify domain // hr = NO_ERROR; } 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( sm_pstraComputerDomain != NULL ) { delete sm_pstraComputerDomain; sm_pstraComputerDomain = NULL; } 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 --*/ { LPCSTR pszAuthHeader; if ( pMainContext == NULL || pfApplies == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfApplies = FALSE; // // Is using of Digest SSP enabled? // if ( g_pW3Server->QueryUseDigestSSP() ) { // // Digest SSP is enabled => IIS Digest cannot be used // return NO_ERROR; } if( !W3_STATE_AUTHENTICATION::sm_fLocalSystem ) { if( W3_STATE_AUTHENTICATION::sm_lLocalSystemEvent == 0 ) { if( !InterlockedExchange( &W3_STATE_AUTHENTICATION::sm_lLocalSystemEvent, 1 ) ) { // // The process token does not have SeTcbPrivilege // g_pW3Server->LogEvent( W3_EVENT_SUBAUTH_LOCAL_SYSTEM, 0, NULL ); } } return NO_ERROR; } if( W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent == 1 ) { return NO_ERROR; } // // Get auth type // pszAuthHeader = pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization ); // // No package, no auth // if ( pszAuthHeader == NULL ) { return NO_ERROR; } // // Is it Digest? // if ( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 ) { *pfApplies = TRUE; } return NO_ERROR; } HRESULT IIS_DIGEST_AUTH_PROVIDER::DoAuthenticate( IN W3_MAIN_CONTEXT * pMainContext, OUT BOOL * // unused ) /*++ 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; LPCSTR pszAuthHeader = NULL; STACK_STRA( straAuthHeader, 128 ); BOOL fQOPAuth = FALSE; BOOL fSt = FALSE; HANDLE hAccessTokenImpersonation = NULL; IIS_DIGEST_USER_CONTEXT * pUserContext = NULL; W3_REQUEST * pW3Request = NULL; BOOL fSendAccessDenied = FALSE; SECURITY_STATUS secStatus = SEC_E_OK; STACK_STRA( straVerb, 10 ); STACK_STRU( strDigestUri, MAX_PATH + 1 ); STACK_STRU( strUrl, MAX_PATH + 1 ); STACK_STRA( straCurrentNonce, NONCE_SIZE + 1 ); LPSTR aValueTable[ MD5_AUTH_LAST ]; DIGEST_LOGON_INFO DigestLogonInfo; STACK_STRA( straUserName, UNLEN + 1 ); STACK_STRA( straDomainName, IIS_DNLEN + 1 ); ULONG cbBytesCopied; DBG_ASSERT( pMainContext != NULL ); // // make copy of Authorization Header // (this function will be modifying the string) // hr = straAuthHeader.Copy( pMainContext->QueryRequest()->GetHeader( HttpHeaderAuthorization ) ); if ( FAILED( hr ) ) { goto ExitPoint; } pszAuthHeader = straAuthHeader.QueryStr(); DBG_ASSERT( pszAuthHeader != NULL ); // // If DoAuthenticate() for Digest is called then DIGEST_AUTH string must // have been at the beggining of the pszAuthHeader // DBG_ASSERT( _strnicmp( pszAuthHeader, DIGEST_AUTH, sizeof(DIGEST_AUTH) - 1 ) == 0 ); // // Skip the name of Authentication scheme // pszAuthHeader = pszAuthHeader + sizeof(DIGEST_AUTH) - 1; if ( !IIS_DIGEST_CONN_CONTEXT::ParseForName( (PSTR) pszAuthHeader, MD5_AUTH_NAMES, MD5_AUTH_LAST, aValueTable ) ) { DBGPRINTF(( DBG_CONTEXT, "Failed parsing of Authorization header for Digest Auth\n" )); 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 ) { DBGPRINTF(( DBG_CONTEXT, "Invalid Digest Authorization Header (Username, realm, URI, nonce or response is missing).\n" )); 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 ], QOP_AUTH ) ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); DBGPRINTF(( DBG_CONTEXT, "Unknown qop=%s value in Digest Authorization header. hr = %x\n", aValueTable[ MD5_AUTH_QOP ], hr )); goto ExitPoint; } // // If qop="auth" is used in header then CNONCE and NC are mandatory // if ( aValueTable[ MD5_AUTH_CNONCE ] == NULL || aValueTable[ MD5_AUTH_NC ] == NULL ) { DBGPRINTF(( DBG_CONTEXT, "Invalid Digest Authorization Header (cnonce or nc is missing).\n" )); hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); goto ExitPoint; } fQOPAuth = TRUE; } else { aValueTable[ MD5_AUTH_QOP ] = VALUE_NONE; aValueTable[ MD5_AUTH_CNONCE ] = VALUE_NONE; aValueTable[ MD5_AUTH_NC ] = VALUE_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; DBGPRINTF(( DBG_CONTEXT, "Invalid Digest Authorization Header (nonce is not well formed).\n" )); 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 ( ! strDigestUri.QueryBuffer()->Resize( (strlen(aValueTable[MD5_AUTH_URI]) + 1) * sizeof(WCHAR) ) ) { 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; } // // after modyfing string data in internal buffer call SyncWithBuffer // to synchronize string length // strDigestUri.SyncWithBuffer(); if ( FAILED( hr = pMainContext->QueryRequest()->GetUrl( &strUrl ) ) ) { goto ExitPoint; } if ( !strUrl.Equals( strDigestUri ) ) { // // 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; DBGPRINTF(( DBG_CONTEXT, "URI in Digest Authorization header doesn't match the requested URI.\n" )); 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; } if ( FAILED( hr = BreakUserAndDomain( aValueTable[ MD5_AUTH_USERNAME ], 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 ]; pW3Request = pMainContext->QueryRequest(); DBG_ASSERT( pW3Request != NULL ); // // Register the remote IP address with LSA so that it can be logged // if( pW3Request->QueryRemoteAddressType() == AF_INET ) { secStatus = SecpSetIPAddress( ( PUCHAR )pW3Request->QueryRemoteSockAddress(), sizeof( SOCKADDR_IN ) ); } else if( pW3Request->QueryRemoteAddressType() == AF_INET6 ) { secStatus = SecpSetIPAddress( ( PUCHAR )pW3Request->QueryRemoteSockAddress(), sizeof( SOCKADDR_IN6 ) ); } else { DBG_ASSERT( FALSE ); } if( FAILED( secStatus ) ) { hr = secStatus; goto ExitPoint; } fSt = IISLogonDigestUserA( &DigestLogonInfo, IISSUBA_DIGEST , &hAccessTokenImpersonation ); if ( fSt == FALSE ) { DWORD dwRet = GetLastError(); if ( dwRet == ERROR_PASSWORD_MUST_CHANGE || dwRet == ERROR_PASSWORD_EXPIRED ) { hr = HRESULT_FROM_WIN32( dwRet ); goto ExitPoint; } if( dwRet == ERROR_PROC_NOT_FOUND ) { if( W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent == 0 ) { if( !InterlockedExchange( &W3_STATE_AUTHENTICATION::sm_lSubAuthDigestEvent, 1 ) ) { // // The registry key for iissuba is not configured correctly // g_pW3Server->LogEvent( W3_EVENT_SUBAUTH_REGISTRY_CONFIGURATION_DC, 0, NULL ); } } hr = HRESULT_FROM_WIN32( dwRet ); goto ExitPoint; } 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 ) ) { goto ExitPoint; } else { // // hAccessTokenImpestonation is now owned by pUserContext // hAccessTokenImpersonation = NULL; } 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; } if ( pUserContext != NULL ) { pUserContext->DereferenceUserContext(); pUserContext = 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; 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::sm_lSubAuthDigestEvent == 1 ) { // // IIS subauth is not configured correctly on DC // 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(), (USHORT)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, 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 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 getting the name of the domain // the computer is a part of // if ( straDomainName.IsEmpty() ) { if ( FAILED( hr = straDomainName.Copy ( QueryComputerDomain() ) ) ) { 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, 0, 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 ); } if ( FAILED( hr = _strUserName.CopyA(pszUserName) ) ) { return hr; } // // IIS_DIGEST_USER_CONTEXT is taking over ownership of // hImpersonationToken // _hImpersonationToken = hImpersonationToken; 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::sm_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; HRESULT hr = E_FAIL; // // 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 || !sm_pachIISDIGESTConnContext->IsValid() ) { if( sm_pachIISDIGESTConnContext != NULL ) { delete sm_pachIISDIGESTConnContext; sm_pachIISDIGESTConnContext = NULL; } hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF(( DBG_CONTEXT, "Error initializing sm_pachIISDIGESTSecContext. hr = 0x%x\n", hr )); goto Failed; } // // Get a handle to the CSP we'll use for all our hash functions etc // if ( !CryptAcquireContext( &sm_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) { hr = HRESULT_FROM_WIN32( GetLastError() ); DBGPRINTF((DBG_CONTEXT, "CryptAcquireContext() failed : 0x%x\n", GetLastError())); goto Failed; } return S_OK; Failed: Terminate(); return hr; } //static VOID IIS_DIGEST_CONN_CONTEXT::Terminate( VOID ) /*++ Routine Description: Destroy globals Arguments: None Return Value: None --*/ { if ( sm_pachIISDIGESTConnContext != NULL ) { delete sm_pachIISDIGESTConnContext; sm_pachIISDIGESTConnContext = NULL; } if ( sm_hCryptProv != NULL ) { CryptReleaseContext( sm_hCryptProv, 0 ); sm_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( sm_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 :