Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2382 lines
79 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 2000
//
// File: auth.cxx
//
// Contents: Digest Access creation & validation
// Main entry points into this dll:
// DigestIsValid
// NonceValidate
// NonceInitialize
//
// History: KDamour 16Mar00 Created
//
//------------------------------------------------------------------------
#include "global.h"
#include <lmcons.h> // For Max Passwd Length PWLEN
#include <stdio.h>
//+--------------------------------------------------------------------
//
// Function: DigestCalculation
//
// Synopsis: Perform Digest Access Calculation
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: This routine can be used for both calculating and verifying
// the Digest Access nonce value. The Switching parameter is
// pDigest->type. Calling routine must provide the space for any
// returned hashed values (like pReqDigest).
//
// For clients, the cleartext password must be avilable to generate the
// session key.
//
// After the initial ISC/ASC completes, a copy of the sessionkey is kept in
// in the context and copied over to the Digest structure. The username, realm
// and password are not utilized from the UserCreds since we already have a
// sessionkey.
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestCalculation(
IN PDIGEST_PARAMETER pDigest,
IN PUSER_CREDENTIALS pUserCreds
)
{
NTSTATUS Status = STATUS_SUCCESS;
DebugLog((DEB_TRACE_FUNC, "DigestCalculation: Entering\n"));
Status = DigestIsValid(pDigest);
if (!NT_SUCCESS(Status))
{
return(Status);
}
switch (pDigest->typeDigest)
{
case DIGEST_CLIENT: // Called by clients to generate Response
case SASL_CLIENT: // Called by clients to generate Response
{
Status = DigestCalcChalRsp(pDigest, pUserCreds, FALSE);
break;
}
case DIGEST_SERVER:
case SASL_SERVER:
{
Status = DigestCalcChalRsp(pDigest, pUserCreds, TRUE);
break;
}
default:
{ // No Digest calculations for that yet
Status = SEC_E_UNSUPPORTED_FUNCTION;
DebugLog((DEB_ERROR, "NTDigest: Unsupported typeDigest = %d\n", pDigest->typeDigest));
break;
}
}
DebugLog((DEB_TRACE_FUNC, "DigestCalculation: Leaving Status 0x%x\n", Status));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestIsValid
//
// Synopsis: Simple checks for enough data for Digest calculation
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes:
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestIsValid(
IN PDIGEST_PARAMETER pDigest
)
{
NTSTATUS Status = STATUS_SUCCESS;
int i = 0;
DebugLog((DEB_TRACE_FUNC, "DigestIsValid: Entering\n"));
if (!pDigest)
{ // Fail on no Digest Parameters passed in
DebugLog((DEB_ERROR, "DigestIsValid: no digest pointer arg\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
// Check for proper struct format for strings
for (i=0; i< MD5_AUTH_LAST;i++)
{
if (!NT_SUCCESS(StringVerify(&(pDigest->refstrParam[i]))))
{
DebugLog((DEB_ERROR, "DigestIsValid: Digest String struct bad format\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
}
if (!NT_SUCCESS(StringVerify(&(pDigest->strSessionKey))))
{
DebugLog((DEB_ERROR, "DigestIsValid: Digest String struct bad format in SessionKey\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
// Do some required checks for field-value data
// Required Username-value, Realm-value, nonce, Method
// URI
if ((!pDigest->refstrParam[MD5_AUTH_NONCE].Length) ||
(!pDigest->refstrParam[MD5_AUTH_METHOD].Length) ||
(!pDigest->refstrParam[MD5_AUTH_URI].Length)
)
{
// Failed on a require field-value
DebugLog((DEB_ERROR, "DigestIsValid: required digest field missing\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
CleanUp:
DebugLog((DEB_TRACE_FUNC, "DigestIsValid: Leaving Status 0x%x\n", Status));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestFree
//
// Synopsis: Clear out the digest & free memory from Digest struct
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: This should be called when done with a DIGEST_PARAMTER object
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestFree(
IN PDIGEST_PARAMETER pDigest
)
{
NTSTATUS Status = STATUS_SUCCESS;
int i = 0;
DebugLog((DEB_TRACE_FUNC, "Entering DigestFree\n"));
if (!pDigest)
{
return STATUS_INVALID_PARAMETER;
}
StringFree(&pDigest->strSessionKey);
StringFree(&pDigest->strResponse);
StringFree(&pDigest->strUsernameEncoded);
StringFree(&pDigest->strRealmEncoded);
UnicodeStringFree(&pDigest->ustrRealm);
UnicodeStringFree(&pDigest->ustrUsername);
UnicodeStringFree(&pDigest->ustrCrackedAccountName);
UnicodeStringFree(&pDigest->ustrCrackedDomain);
UnicodeStringFree(&pDigest->ustrWorkstation);
UnicodeStringFree(&pDigest->ustrTrustedForest);
if (pDigest->pTrustSid)
{
#ifndef SECURITY_KERNEL
MIDL_user_free(pDigest->pTrustSid);
#else
ASSERT(FALSE); // Kernel mode will never have Domain/forest trust informtion set
#endif
pDigest->pTrustSid = NULL;
}
// Release any directive storage
// This was used to remove backslash encoding from directives
for (i = 0; i < MD5_AUTH_LAST; i++)
{
StringFree(&(pDigest->strDirective[i]));
}
DebugLog((DEB_TRACE_FUNC, "Leaving DigestFree\n"));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestInit
//
// Synopsis: Initialize a DIGEST_PARAMETER structure
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: This should be called when creating a DIGEST_PARAMTER object
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestInit(
IN PDIGEST_PARAMETER pDigest
)
{
NTSTATUS Status = STATUS_SUCCESS;
if (!pDigest)
{
return STATUS_INVALID_PARAMETER;
}
RtlZeroMemory(pDigest, sizeof(DIGEST_PARAMETER));
// Now allocate the fixed length output buffers
Status = StringAllocate(&(pDigest->strResponse), MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "NTDigest:DigestCalculation No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
return(Status);
}
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestCalcChalRsp
//
// Synopsis: Perform Digest Access Calculation for ChallengeResponse
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
// bIsChallenge - if TRUE then check Response provided (for HTTP Response)
// - if FALSE then calculate Response (for HTTP Request)
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: Called from DigestCalculation
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestCalcChalRsp(IN PDIGEST_PARAMETER pDigest,
IN PUSER_CREDENTIALS pUserCreds,
BOOL IsChallenge)
{
NTSTATUS Status = STATUS_SUCCESS;
STRING strHA2 = {0};
STRING strReqDigest = {0}; // Final request digest access value
STRING strcQOP = {0}; // String pointing to a constant CZ - no need to free up
DebugLog((DEB_TRACE_FUNC, "DigestCalcChalRsp: Entering\n"));
// Make sure that there is a Request-Digest to Compare to (IsChallenge TRUE) or
// Set (IsChallenge FALSE)
if (IsChallenge && (!(pDigest->refstrParam[MD5_AUTH_RESPONSE].Length)))
{
// Failed on a require field-value
DebugLog((DEB_ERROR, "DigestCalcChalRsp: No ChallengeResponse\n"));
Status = STATUS_INVALID_PARAMETER;
return(Status);
}
// Initialize local variables
Status = StringAllocate(&strHA2, MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
Status = StringAllocate(&strReqDigest, MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
// Establish which QOP utilized
if (pDigest->typeQOP == AUTH_CONF)
{
RtlInitString(&strcQOP, AUTHCONFSTR);
}
else if (pDigest->typeQOP == AUTH_INT)
{
RtlInitString(&strcQOP, AUTHINTSTR);
}
else if (pDigest->typeQOP == AUTH)
{
RtlInitString(&strcQOP, AUTHSTR);
}
// Check if already calculated H(A1) the session key
// Well for Algorithm=MD5 it is just H(username:realm:passwd)
if (!(pDigest->strSessionKey.Length))
{
// No Session Key calculated yet - create one & store it
#ifndef SECURITY_KERNEL
DebugLog((DEB_TRACE, "DigestCalcChalRsp: No session key calculated, generate one\n"));
Status = DigestCalcHA1(pDigest, pUserCreds);
if (!NT_SUCCESS(Status))
{
goto CleanUp;
}
#else // SECURITY_KERNEL
UNREFERENCED_PARAMETER(pUserCreds);
DebugLog((DEB_ERROR, "DigestCalcChalRsp: No session key available in context\n"));
Status = STATUS_NO_USER_SESSION_KEY;
goto CleanUp;
#endif // SECURITY_KERNEL
}
// We now have calculated H(A1)
// Calculate H(A2)
// For QOP unspecified or "auth" H(A2) = H( Method: URI)
// For QOP Auth-int or Auth-conf H(A2) = H( Method: URI: H(entity-body))
if ((pDigest->typeQOP == AUTH) || (pDigest->typeQOP == NO_QOP_SPECIFIED))
{
// Unspecified or Auth
DebugLog((DEB_TRACE, "DigestCalcChalRsp: H(A2) using AUTH/Unspecified\n"));
Status = DigestHash7(&(pDigest->refstrParam[MD5_AUTH_METHOD]),
&(pDigest->refstrParam[MD5_AUTH_URI]),
NULL, NULL, NULL, NULL, NULL,
TRUE, &strHA2);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp H(A2) failed : 0x%x\n", Status));
goto CleanUp;
}
}
else
{
// Auth-int or Auth-conf
DebugLog((DEB_TRACE, "DigestCalcChalRsp: H(A2) using AUTH-INT/CONF\n"));
if (pDigest->refstrParam[MD5_AUTH_HENTITY].Length == 0)
{
Status = STATUS_INVALID_PARAMETER;
DebugLog((DEB_ERROR, "DigestCalChalRsp HEntity Missing\n"));
goto CleanUp;
}
Status = DigestHash7(&(pDigest->refstrParam[MD5_AUTH_METHOD]),
&(pDigest->refstrParam[MD5_AUTH_URI]),
&(pDigest->refstrParam[MD5_AUTH_HENTITY]),
NULL, NULL, NULL, NULL,
TRUE, &strHA2);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp H(A2) auth-int failed : 0x%x\n", Status));
goto CleanUp;
}
}
// We now have calculated H(A2)
// Calculate Request-Digest
// For QOP of Auth, Auth-int, Auth-conf Req-Digest = H( H(A1): nonce: nc: cnonce: qop: H(A2))
// For QOP unspecified (old format) Req-Digest = H( H(A1): nonce: H(A2))
if (pDigest->typeQOP != NO_QOP_SPECIFIED)
{
// Auth, Auth-int, Auth-conf
if (pDigest->refstrParam[MD5_AUTH_NC].Length == 0)
{
Status = STATUS_INVALID_PARAMETER;
DebugLog((DEB_ERROR, "DigestCalcChalRsp NC Missing\n"));
goto CleanUp;
}
Status = DigestHash7(&(pDigest->strSessionKey),
&(pDigest->refstrParam[MD5_AUTH_NONCE]),
&(pDigest->refstrParam[MD5_AUTH_NC]),
&(pDigest->refstrParam[MD5_AUTH_CNONCE]),
&strcQOP,
&strHA2, NULL,
TRUE, &strReqDigest);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp Req-Digest failed : 0x%x\n", Status));
goto CleanUp;
}
}
else
{
// Unspecified backwards compat for RFC 2069
Status = DigestHash7(&(pDigest->strSessionKey),
&(pDigest->refstrParam[MD5_AUTH_NONCE]),
&strHA2,
NULL, NULL, NULL, NULL,
TRUE, &strReqDigest);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp Req-Digest old format failed : 0x%x\n", Status));
goto CleanUp;
}
}
if (IsChallenge)
{
// We now have the Request-Digest so just compare to see if they match!
if (!strncmp(pDigest->refstrParam[MD5_AUTH_RESPONSE].Buffer, strReqDigest.Buffer, 2*MD5_HASH_BYTESIZE))
{
DebugLog((DEB_TRACE, "DigestCalcChalRsp Request-Digest Matches!\n"));
Status = STATUS_SUCCESS;
}
else
{
DebugLog((DEB_TRACE, "DigestCalcChalRsp Request-Digest FAILED.\n"));
Status = STATUS_WRONG_PASSWORD;
}
}
else
{
// We are to calculate the response-value so just set it (Hash Size + NULL)
if (pDigest->strResponse.MaximumLength >= (MD5_HASH_HEX_SIZE + 1))
{
memcpy(pDigest->strResponse.Buffer, strReqDigest.Buffer, (MD5_HASH_HEX_SIZE + 1));
pDigest->strResponse.Length = MD5_HASH_HEX_SIZE; // No Count NULL
Status = STATUS_SUCCESS;
}
else
{
DebugLog((DEB_ERROR, "DigestCalcChalRsp Request-Digest Size too small.\n"));
Status = STATUS_BUFFER_TOO_SMALL;
}
}
CleanUp:
StringFree(&strHA2);
StringFree(&strReqDigest);
DebugLog((DEB_TRACE_FUNC, "DigestCalcChalRsp: Leaving Status 0x%x\n", Status));
return(Status);
}
#ifndef SECURITY_KERNEL
//+--------------------------------------------------------------------
//
// Function: DigestDecodeDirectiveStrings
//
// Synopsis: Processed parsed digest auth message and fill in string values
//
// Effects:
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes:
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestDecodeDirectiveStrings(
IN PDIGEST_PARAMETER pDigest
)
{
NTSTATUS Status = STATUS_SUCCESS;
DebugLog((DEB_TRACE_FUNC, "DigestDecodeDirectiveStrings Entering\n"));
if (!pDigest)
{
return STATUS_INVALID_PARAMETER;
}
// Free up any allocs on a retry for directive decoding
UnicodeStringFree(&pDigest->ustrUsername);
UnicodeStringFree(&pDigest->ustrRealm);
// Decode the username and realm directives
if (pDigest->typeCharset == UTF_8)
{
DebugLog((DEB_TRACE, "DigestDecodeDirectiveStrings: UTF-8 Character set decoding\n"));
Status = DecodeUnicodeString(&(pDigest->refstrParam[MD5_AUTH_USERNAME]),
CP_UTF8,
&pDigest->ustrUsername);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestDecodeDirectiveStrings: DecodeUnicodeString error 0x%x\n", Status));
goto CleanUp;
}
Status = DecodeUnicodeString(&(pDigest->refstrParam[MD5_AUTH_REALM]),
CP_UTF8,
&pDigest->ustrRealm);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestDecodeDirectiveStrings: DecodeUnicodeString error 0x%x\n", Status));
goto CleanUp;
}
}
else if (pDigest->typeCharset == ISO_8859_1)
{
DebugLog((DEB_TRACE, "DigestDecodeDirectiveStrings: ISO-8859-1 Character set decoding\n"));
Status = DecodeUnicodeString(&(pDigest->refstrParam[MD5_AUTH_USERNAME]),
CP_8859_1,
&pDigest->ustrUsername);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestDecodeDirectiveStrings: DecodeUnicodeString Username error 0x%x\n", Status));
goto CleanUp;
}
Status = DecodeUnicodeString(&(pDigest->refstrParam[MD5_AUTH_REALM]),
CP_8859_1,
&pDigest->ustrRealm);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestDecodeDirectiveStrings: DecodeUnicodeString Realm error 0x%x\n", Status));
goto CleanUp;
}
}
else
{
Status = STATUS_UNMAPPABLE_CHARACTER;
DebugLog((DEB_ERROR, "DigestDecodeDirectiveStrings: Unknown CharacterSet encoding for Digest parameters\n"));
goto CleanUp;
}
DebugLog((DEB_TRACE, "DigestDecodeDirectiveStrings: Processing username (%wZ) realm (%wZ)\n",
&pDigest->ustrUsername,
&pDigest->ustrRealm));
CleanUp:
DebugLog((DEB_TRACE_FUNC, "DigestDecodeStrings Leaving\n"));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestCalcHA1
//
// Synopsis: Determine H(A1) for Digest Access
//
// Effects: Will calculate the SessionKey and store it in pDigest
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: Called from DigestCalChalRsp
// Sessionkey is H(A1)
// Username and realm will be taken from the UserCreds
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestCalcHA1(IN PDIGEST_PARAMETER pDigest,
IN PUSER_CREDENTIALS pUserCreds)
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING ustrTempPasswd = {0};
STRING strHPwKey = {0};
STRING strBinaryHPwKey = {0};
STRING strHA0Base = {0};
STRING strHA0 = {0};
STRING strPasswd = {0};
STRING strUsername = {0};
STRING strRealm = {0};
PSTRING pstrAuthzID = NULL;
BOOL fDefChars = FALSE;
USHORT usHashOffset = 0;
BOOL fSASLMode = FALSE;
BOOL fValidHash = FALSE;
int i = 0;
DebugLog((DEB_TRACE_FUNC, "DigestCalcHA1: Entering\n"));
ASSERT(pDigest);
ASSERT(pUserCreds);
if (!pUserCreds)
{
// No username & domain passed in
Status = STATUS_INVALID_PARAMETER;
DebugLog((DEB_ERROR, "DigestCalcHA1: No username info passed in\n"));
goto CleanUp;
}
if ((pDigest->typeDigest == SASL_SERVER) || (pDigest->typeDigest == SASL_CLIENT))
{
fSASLMode = TRUE;
}
// Initialize local variables
Status = StringAllocate(&strBinaryHPwKey, MD5_HASH_BYTESIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
Status = StringAllocate(&strHA0Base, MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
Status = StringAllocate(&strHA0, MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
// Check outputs
if (pDigest->strSessionKey.MaximumLength <= MD5_HASH_HEX_SIZE)
{
StringFree(&(pDigest->strSessionKey));
Status = StringAllocate(&(pDigest->strSessionKey), MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
if ((pUserCreds->fIsValidDigestHash == TRUE) && (pUserCreds->wHashSelected > 0))
{
// selected pre-calculated hash - retrieve from userCreds
// read in precalc version number
if (pUserCreds->strDigestHash.Length < MD5_HASH_BYTESIZE)
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Header on Precalc hash\n"));
Status = SEC_E_NO_CREDENTIALS;
goto CleanUp;
}
// check if valid hash number
usHashOffset = pUserCreds->wHashSelected * MD5_HASH_BYTESIZE;
if (pUserCreds->strDigestHash.Length < (usHashOffset + MD5_HASH_BYTESIZE))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Pre-calc hash not found\n"));
Status = SEC_E_NO_CREDENTIALS;
goto CleanUp;
}
// extract pre-calc hash - this is the binary version of the hash
memcpy(strBinaryHPwKey.Buffer,
(pUserCreds->strDigestHash.Buffer + usHashOffset),
MD5_HASH_BYTESIZE);
strBinaryHPwKey.Length = MD5_HASH_BYTESIZE;
// all zero for hash indicates invalid hash calculated
for (i=0; i < (int)strBinaryHPwKey.Length; i++)
{
if (strBinaryHPwKey.Buffer[i])
{
fValidHash = TRUE;
break;
}
}
if (fValidHash == FALSE)
{
// This is not a defined hash
DebugLog((DEB_ERROR, "DigestCalcHA1: Invalid hash selected - not defined\n"));
Status = SEC_E_NO_CREDENTIALS;
goto CleanUp;
}
if (fSASLMode == TRUE)
{
// SASL mode keeps the Password hash in binary form
Status = StringDuplicate(&strHPwKey, &strBinaryHPwKey);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
else
{
Status = StringAllocate(&strHPwKey, MD5_HASH_HEX_SIZE);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
// HTTP mode needs to have HEX() version of password hash - RFC text is correct
BinToHex((LPBYTE)strBinaryHPwKey.Buffer, MD5_HASH_BYTESIZE, strHPwKey.Buffer);
strHPwKey.Length = MD5_HASH_HEX_SIZE; // Do not count the NULL at the end
}
#if DBG2
if (fSASLMode == TRUE)
{
STRING strTempPwKey = {0};
MyPrintBytes(strHPwKey.Buffer, strHPwKey.Length, &strTempPwKey);
DebugLog((DEB_TRACE, "DigestCalcHA1: SASL Pre-Calc H(%wZ:%wZ:************) is %Z\n",
&pUserCreds->ustrUsername, &pUserCreds->ustrRealm, &strTempPwKey));
StringFree(&strTempPwKey);
}
else
{
DebugLog((DEB_TRACE, "DigestCalcHA1: HTTP Pre-Calc H(%wZ:%wZ:************) is %Z\n",
&pUserCreds->ustrUsername, &pUserCreds->ustrRealm, &strHPwKey));
}
#endif
}
else if (pUserCreds->fIsValidPasswd == TRUE)
{
// copy over the passwd and decrypt if necessary
Status = UnicodeStringDuplicatePassword(&ustrTempPasswd, &(pUserCreds->ustrPasswd));
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in dup password, status 0x%0x\n", Status ));
goto CleanUp;
}
if ((pUserCreds->fIsEncryptedPasswd == TRUE) && (ustrTempPasswd.MaximumLength != 0))
{
g_LsaFunctions->LsaUnprotectMemory(ustrTempPasswd.Buffer, (ULONG)(ustrTempPasswd.MaximumLength));
}
// Need to encode the password for hash calculations
// We have the cleartext password in ustrTempPasswd,
// username in pContext->ustrAccountname,
// realm in pContext->ustrDomain
// Could do some code size optimization here in the future to shorten this up
if (pDigest->typeCharset == UTF_8)
{
// First check if OK to encode in ISO 8859-1, if not then use UTF-8
// All characters must be within ISO 8859-1 Character set else fail
fDefChars = FALSE;
Status = EncodeUnicodeString(&pUserCreds->ustrUsername, CP_8859_1, &strUsername, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding username\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_TRACE, "DigestCalcHA1: Can not encode Username in 8859-1, use UTF-8\n"));
StringFree(&strUsername);
Status = EncodeUnicodeString(&pUserCreds->ustrUsername, CP_UTF8, &strUsername, NULL);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding username\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
fDefChars = FALSE;
Status = EncodeUnicodeString(&pUserCreds->ustrRealm, CP_8859_1, &strRealm, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding realm\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_TRACE, "DigestCalcHA1: Can not encode realm in 8859-1, use UTF-8\n"));
StringFree(&strRealm);
Status = EncodeUnicodeString(&pUserCreds->ustrRealm, CP_UTF8, &strRealm, NULL);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding realm\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
fDefChars = FALSE;
Status = EncodeUnicodeString(&ustrTempPasswd, CP_8859_1, &strPasswd, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding passwd\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_TRACE, "DigestCalcHA1: Can not encode password in 8859-1, use UTF-8\n"));
if (strPasswd.Buffer && strPasswd.MaximumLength)
{
SecureZeroMemory(strPasswd.Buffer, strPasswd.MaximumLength);
}
StringFree(&strPasswd);
Status = EncodeUnicodeString(&ustrTempPasswd, CP_UTF8, &strPasswd, NULL);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding passwd\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
}
else
{
// All characters must be within ISO 8859-1 Character set else fail
Status = EncodeUnicodeString(&pUserCreds->ustrUsername, CP_8859_1, &strUsername, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding username\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding username in ISO 8859-1\n"));
Status = STATUS_UNMAPPABLE_CHARACTER;
goto CleanUp;
}
Status = EncodeUnicodeString(&pUserCreds->ustrRealm, CP_8859_1, &strRealm, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding realm\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding realm in ISO 8859-1\n"));
Status = STATUS_UNMAPPABLE_CHARACTER;
goto CleanUp;
}
Status = EncodeUnicodeString(&ustrTempPasswd, CP_8859_1, &strPasswd, &fDefChars);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding passwd\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
if (fDefChars == TRUE)
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Error in encoding passwd in ISO 8859-1\n"));
Status = STATUS_UNMAPPABLE_CHARACTER;
goto CleanUp;
}
DebugLog((DEB_TRACE, "DigestCalcHA1: Username, Realm, Password encoded in ISO 8859-1\n"));
}
if (!strUsername.Length)
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Must have non-zero length username\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
// If specified a no realm (NULL buffer, zero length) then used the REALM provided in the Challenge
// if Realm specified as NULL string (valid buffer, zero length), then use a "" NULL string realm
if (!pUserCreds->ustrRealm.Buffer)
{
ASSERT(!pUserCreds->ustrRealm.Length); // never have NULL buffer and a length
StringFree(&strRealm);
Status = StringDuplicate(&strRealm, &(pDigest->refstrParam[MD5_AUTH_REALM]));
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: Can not duplicate Challenge realm\n"));
goto CleanUp;
}
DebugLog((DEB_ERROR, "DigestCalcHA1: Realm defaulted to Challenge realm value\n"));
}
// Calculate H(A1) based on Algorithm type
// Auth is not specified or "MD5"
// Use H(A1Base) = H(username-value:realm-value:passwd)
if (fSASLMode == TRUE)
{
Status = DigestHash7(&strUsername,
&strRealm,
&strPasswd,
NULL, NULL, NULL, NULL,
FALSE, &strHPwKey);
}
else
{
Status = DigestHash7(&strUsername,
&strRealm,
&strPasswd,
NULL, NULL, NULL, NULL,
TRUE, &strHPwKey);
}
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1:DigestCalcHA1 H(PwKey) failed : 0x%x\n", Status));
goto CleanUp;
}
#if DBG2
if (fSASLMode == TRUE)
{
STRING strTempPwKey = {0};
MyPrintBytes(strHPwKey.Buffer, strHPwKey.Length, &strTempPwKey);
DebugLog((DEB_TRACE, "DigestCalcHA1: SASL Password Calc H(%Z:%Z:************) is %Z ******\n",
&strUsername, &strRealm, &strTempPwKey));
StringFree(&strTempPwKey);
}
else
{
DebugLog((DEB_TRACE, "DigestCalcHA1: HTTP Password Calc H(%Z:%Z:************) is %Z ******\n",
&strUsername, &strRealm, &strHPwKey));
}
#endif
}
else
{
Status = SEC_E_NO_CREDENTIALS;
DebugLog((DEB_ERROR, "DigestCalcHA1: No Pre-calc hash or password\n"));
goto CleanUp;
}
// Check if using SASL then need to add in the AuthzID
if (fSASLMode == TRUE)
{
// Set to use AuthzID otherwise keep the NULL
// set only if AuthzID contains data
if (pDigest->usFlags & FLAG_AUTHZID_PROVIDED)
{
pstrAuthzID = &(pDigest->refstrParam[MD5_AUTH_AUTHZID]);
}
}
DebugLog((DEB_TRACE, "DigestCalcHA1: Algorithm type %d\n", pDigest->typeAlgorithm));
// Now check if using MD5-SESS. We need to form
// H(A1) = H( H(PwKey) : nonce : cnonce [: authzID])
// otherwise simply set H(A1) = H(PwKey)
if (pDigest->typeAlgorithm == MD5_SESS)
{
DebugLog((DEB_TRACE, "DigestCalcHA1: First client-server auth\n"));
Status = DigestHash7(&strHPwKey,
&(pDigest->refstrParam[MD5_AUTH_NONCE]),
&(pDigest->refstrParam[MD5_AUTH_CNONCE]),
pstrAuthzID,
NULL, NULL, NULL,
TRUE, &(pDigest->strSessionKey));
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCalcHA1: SessionKey failed : 0x%x\n", Status));
goto CleanUp;
}
}
else
{ // Keep SessionKey = H(PwKey) for Algoithm = MD5
memcpy(pDigest->strSessionKey.Buffer, strHPwKey.Buffer, MD5_HASH_HEX_SIZE);
pDigest->strSessionKey.Length = MD5_HASH_HEX_SIZE; // Do not count the NULL terminator
}
DebugLog((DEB_TRACE, "DigestCalcHA1: SessionKey is %.10Z**********\n", &(pDigest->strSessionKey)));
Status = STATUS_SUCCESS;
CleanUp:
if (strBinaryHPwKey.Buffer && strBinaryHPwKey.MaximumLength)
{
SecureZeroMemory(strBinaryHPwKey.Buffer, strBinaryHPwKey.MaximumLength);
}
StringFree(&strBinaryHPwKey);
if (strHPwKey.Buffer && strHPwKey.MaximumLength)
{
SecureZeroMemory(strHPwKey.Buffer, strHPwKey.MaximumLength);
}
StringFree(&strHPwKey);
StringFree(&strHA0Base);
StringFree(&strHA0);
if (strPasswd.Buffer && strPasswd.MaximumLength)
{
SecureZeroMemory(strPasswd.Buffer, strPasswd.MaximumLength);
}
StringFree(&strPasswd);
StringFree(&strUsername);
StringFree(&strRealm);
if (ustrTempPasswd.Buffer && ustrTempPasswd.MaximumLength)
{ // Zero out password info just to be safe
SecureZeroMemory(ustrTempPasswd.Buffer, ustrTempPasswd.MaximumLength);
}
UnicodeStringFree(&ustrTempPasswd);
DebugLog((DEB_TRACE_FUNC, "DigestCalcHA1: Leaving\n"));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: DigestHash7
//
// Synopsis: Hash and Encode 7 STRINGS SOut = Hex(H(S1 S2 S3 S4 S5 S6 S7))
//
// Effects:
//
// Arguments: pS1,...,pS6 - STRINGS to hash, pS1 must be specified
// fHexOut - perform a Hex operation on output
// pSOut - STRING to hold Hex Encoded Hash
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: pSOut->MaximumLength must be atleast (MD5_HASH_BYTESIZE (or MD5_HASH_HEX_SIZE) + sizeof(NULL))
// Any pS# args which are NULL are skipped
// if pS# is not NULL
// Previously checked that pS# is non-zero length strings
// You most likely want Sx->Length = strlen(Sx) so as not to include NULL
// This function combines operations like H(S1 S2 S3), H(S1 S2 S3 S4 S5) ....
// It is assumed that the char ':' is to be included getween Sn and Sn+1
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestHash7(
IN PSTRING pS1,
IN PSTRING pS2,
IN PSTRING pS3,
IN PSTRING pS4,
IN PSTRING pS5,
IN PSTRING pS6,
IN PSTRING pS7,
IN BOOL fHexOut,
OUT PSTRING pSOut)
{
NTSTATUS Status = STATUS_SUCCESS;
HCRYPTHASH hHash = NULL;
BYTE bHashData[MD5_HASH_BYTESIZE];
DWORD cbHashData = MD5_HASH_BYTESIZE;
USHORT usSizeRequired = 0;
char *pbSeparator = COLONSTR;
DebugLog((DEB_TRACE_FUNC, "DigestHash7: Entering \n"));
ASSERT(pSOut);
if (fHexOut == TRUE)
{
usSizeRequired = MD5_HASH_HEX_SIZE;
}
else
{
usSizeRequired = MD5_HASH_BYTESIZE;
}
// Check if output is proper size or allocate one
if (!pSOut->Buffer)
{
Status = StringAllocate(pSOut, usSizeRequired);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestHash7: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
else
{
if (pSOut->MaximumLength < (usSizeRequired + 1))
{
// Output is not large enough to hold Hex(Hash)
DebugLog((DEB_ERROR, "DigestHash7: Output buffer too small\n"));
Status = STATUS_BUFFER_TOO_SMALL;
goto CleanUp;
}
}
if ( !CryptCreateHash( g_hCryptProv,
CALG_MD5,
0,
0,
&hHash ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptCreateHash failed : 0x%lx\n", GetLastError()));
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
if (pS1)
{
if ( !CryptHashData( hHash,
(const unsigned char *)pS1->Buffer,
pS1->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS2)
{
if ( !CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
if ( !CryptHashData( hHash,
(const unsigned char *)pS2->Buffer,
pS2->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS3)
{
(void)CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 );
if ( !CryptHashData( hHash,
(const unsigned char *)pS3->Buffer,
pS3->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS4)
{
(void)CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 );
if ( !CryptHashData( hHash,
(const unsigned char *)pS4->Buffer,
pS4->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS5)
{
(void)CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 );
if ( !CryptHashData( hHash,
(const unsigned char *)pS5->Buffer,
pS5->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS6)
{
(void)CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 );
if ( !CryptHashData( hHash,
(const unsigned char *)pS6->Buffer,
pS6->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if (pS7)
{
(void)CryptHashData( hHash,
(const unsigned char *)pbSeparator,
COLONSTR_LEN,
0 );
if ( !CryptHashData( hHash,
(const unsigned char *)pS7->Buffer,
pS7->Length,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptHashData failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
}
if ( !CryptGetHashParam( hHash,
HP_HASHVAL,
bHashData,
&cbHashData,
0 ) )
{
DebugLog((DEB_ERROR, "DigestHash7: CryptGetHashParam failed : 0x%lx\n", GetLastError()));
CryptDestroyHash( hHash );
hHash = NULL;
Status = STATUS_ENCRYPTION_FAILED;
goto CleanUp;
}
CryptDestroyHash( hHash );
hHash = NULL;
ASSERT(cbHashData == MD5_HASH_BYTESIZE);
if (fHexOut == TRUE)
{
BinToHex(bHashData, cbHashData, pSOut->Buffer);
pSOut->Length = MD5_HASH_HEX_SIZE; // Do not count the NULL at the end
}
else
{
memcpy(pSOut->Buffer, bHashData, cbHashData);
pSOut->Length = MD5_HASH_BYTESIZE; // Do not count the NULL at the end
}
CleanUp:
DebugLog((DEB_TRACE_FUNC, "DigestHash7: Leaving Status 0x%x\n", Status));
return(Status);
}
// Blob creation/extraction for GenericPassthrough Messages
//+--------------------------------------------------------------------
//
// Function: BlobEncodeRequest
//
// Synopsis: Encode the Digest Access Parameters fields into a BYTE Buffer
//
// Effects: Creates a Buffer allocation which calling function
// is responsible to delete with call to BlobFreeRequest()
//
// Arguments: pDigest - pointer to digest access data fields
//
// Returns:
//
// Notes: STATUS_SUCCESS for normal completion
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
BlobEncodeRequest(
PDIGEST_PARAMETER pDigest,
OUT BYTE **ppOutBuffer,
OUT USHORT *cbOutBuffer
)
{
NTSTATUS Status = STATUS_SUCCESS;
DebugLog((DEB_TRACE_FUNC, "BlobEncodeRequest: Entering\n"));
USHORT cbBuffer = 0;
BYTE *pBuffer = NULL;
char *pch = NULL;
PDIGEST_BLOB_REQUEST pHeader;
int i = 0;
USHORT cbValue = 0;
USHORT cbAccountName = 0;
USHORT cbCrackedDomain = 0;
USHORT cbWorkstation = 0;
// Now figure out how many bytes needed to hold field-value NULL terminated
for (i=0, cbBuffer = 0;i < DIGEST_BLOB_VALUES;i++)
{
if (pDigest->refstrParam[i].Buffer && pDigest->refstrParam[i].Length)
{ // may be able to just count str.length
cbBuffer = cbBuffer + strlencounted(pDigest->refstrParam[i].Buffer, pDigest->refstrParam[i].MaximumLength);
}
}
cbBuffer = cbBuffer + (DIGEST_BLOB_VALUES * sizeof(char)); // Account for the separating/terminating NULLs
// Now add in space for the DSCrackName accountname and domain
if (pDigest->ustrCrackedAccountName.Buffer && pDigest->ustrCrackedAccountName.Length)
{
cbAccountName = ustrlencounted((const short *)pDigest->ustrCrackedAccountName.Buffer,
pDigest->ustrCrackedAccountName.Length) * sizeof(WCHAR);
cbBuffer = cbBuffer + cbAccountName;
}
if (pDigest->ustrCrackedDomain.Buffer && pDigest->ustrCrackedDomain.Length)
{
cbCrackedDomain = ustrlencounted((const short *)pDigest->ustrCrackedDomain.Buffer,
pDigest->ustrCrackedDomain.Length) * sizeof(WCHAR);
cbBuffer = cbBuffer + cbCrackedDomain;
}
cbBuffer = cbBuffer + (2 * sizeof(WCHAR)); // Account for the separating/terminating NULLs
// Now add in space for the workstation/server name
if (g_ustrWorkstationName.Buffer && g_ustrWorkstationName.Length)
{
cbWorkstation = ustrlencounted((const short *)g_ustrWorkstationName.Buffer,
g_ustrWorkstationName.Length) * sizeof(WCHAR);
cbBuffer = cbBuffer + cbWorkstation;
}
cbBuffer = cbBuffer + sizeof(WCHAR); // Account for the separating/terminating NULL
cbValue = cbBuffer + (sizeof(DIGEST_BLOB_REQUEST));
pBuffer = (BYTE *)DigestAllocateMemory(cbValue);
if (!pBuffer)
{
DebugLog((DEB_ERROR, "BlobEncodeRequest out of memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
DebugLog((DEB_TRACE, "BlobEncodeRequest using %d bytes\n", cbValue));
*cbOutBuffer = cbValue; // Return number of bytes we are using for the encoding
// Now fill in the information
pHeader = (PDIGEST_BLOB_REQUEST)pBuffer;
pHeader->MessageType = VERIFY_DIGEST_MESSAGE;
pHeader->version = DIGEST_BLOB_VERSION;
pHeader->digest_type = (USHORT)pDigest->typeDigest;
pHeader->qop_type = (USHORT)pDigest->typeQOP;
pHeader->alg_type = (USHORT)pDigest->typeAlgorithm;
pHeader->charset_type = (USHORT)pDigest->typeCharset;
pHeader->name_format = (USHORT)pDigest->typeName; // Format of the username
pHeader->cbCharValues = cbBuffer;
pHeader->cbBlobSize = cbValue; // cbCharValues + charvalues
pHeader->cbAccountName = cbAccountName + sizeof(WCHAR); // string length includes NULL terminator
pHeader->cbCrackedDomain = cbCrackedDomain + sizeof(WCHAR);
pHeader->cbWorkstation = cbWorkstation + sizeof(WCHAR);
pHeader->usFlags = pDigest->usFlags;
// Simply copy over the first DIGEST_BLOB_VALUES that are arranged to be
for (i = 0,pch = &(pHeader->cCharValues); i < DIGEST_BLOB_VALUES;i++)
{
// Make sure that there is valid data to get length from
if (pDigest->refstrParam[i].Buffer && pDigest->refstrParam[i].Length)
{
cbValue = strlencounted(pDigest->refstrParam[i].Buffer, pDigest->refstrParam[i].MaximumLength);
// dont use .length since may include multiple NULLS
memcpy(pch, pDigest->refstrParam[i].Buffer, cbValue);
}
else
cbValue = 0;
pch += (cbValue + 1); // This will leave one NULL at end of field-value
}
// Now write out any results from DSCrackName
if (pDigest->ustrCrackedAccountName.Buffer && pDigest->ustrCrackedAccountName.Length)
{
memcpy(pch, pDigest->ustrCrackedAccountName.Buffer, cbAccountName);
}
pch += (cbAccountName + sizeof(WCHAR)); // This will leave one WCHAR NULL at end of CrackedAccountName
if (pDigest->ustrCrackedDomain.Buffer && pDigest->ustrCrackedDomain.Length)
{
memcpy(pch, pDigest->ustrCrackedDomain.Buffer, cbCrackedDomain);
}
pch += (cbCrackedDomain + sizeof(WCHAR)); // This will leave one WCHAR NULL at end of CrackedAccountName
if (g_ustrWorkstationName.Buffer && g_ustrWorkstationName.Length)
{
memcpy(pch, g_ustrWorkstationName.Buffer, cbWorkstation);
}
else
pch += (cbWorkstation + sizeof(WCHAR)); // This will leave one WCHAR NULL at end of Workstation name
*ppOutBuffer = pBuffer; // Pass off memory back to calling routine
DebugLog((DEB_TRACE, "BlobEncodeRequest: message_type 0x%x, version %d, CharValues %d, BlobSize %d\n",
pHeader->digest_type, pHeader->version, pHeader->cbCharValues, pHeader->cbBlobSize));
CleanUp:
DebugLog((DEB_TRACE_FUNC, "BlobEncodeRequest: Leaving Status 0x%x\n", Status));
return(Status);
}
//+--------------------------------------------------------------------
//
// Function: BlobDecodeRequest
//
// Synopsis: Decode the Digest Access Parameters fields from a BYTE Buffer
//
// Arguments: pBuffer - pointer to BlobEncodeRequestd buffer as input
// pDigest - pointer to Digest parameter struct to set STRINGS
// to point within pBuffer. No string memory is allocated
//
// Returns: NTSTATUS
//
// Notes:
// Currently only processes a single version of the packet. Check MessageType
// and version number if new message types are supported on the DC.
//
//---------------------------------------------------------------------
NTSTATUS NTAPI BlobDecodeRequest(
IN USHORT cbBuffer,
IN BYTE *pBuffer,
PDIGEST_PARAMETER pDigest
)
{
NTSTATUS Status = STATUS_SUCCESS;
DIGEST_BLOB_REQUEST Header;
PDIGEST_BLOB_REQUEST pHeader;
char *pch = NULL;
USHORT sLen = 0;
int i = 0; // counter
BOOL fKnownFormat = FALSE;
USHORT sMaxRead = 0;
PWCHAR pusTemp = NULL;
PWCHAR pusLoc = NULL;
USHORT usCnt = 0;
//UNREFERENCED_PARAMETER(cbBuffer);
DebugLog((DEB_TRACE_FUNC, "BlobDecodeRequest: Entering\n"));
if (!pBuffer || !pDigest)
{
DebugLog((DEB_ERROR, "BlobDecodeRequest: Invalid parameter\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
// Copy over the header for byte alignment
if (cbBuffer < sizeof(Header))
{
DebugLog((DEB_ERROR, "BlobDecodeRequest: Header block not present\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
memcpy((char *)&Header, (char *)pBuffer, sizeof(Header));
DebugLog((DEB_TRACE, "BlobDecodeRequest: message_type %lu version %d, CharValues %d, BlobSize %d\n",
Header.MessageType, Header.version, Header.cbCharValues, Header.cbBlobSize));
// Process the encoded message - use only the known MessageTypes and versions here on the DC
// This allows for expansion of protocols supported in the future
if ((Header.MessageType == VERIFY_DIGEST_MESSAGE) && (Header.version == DIGEST_BLOB_VERSION))
{
fKnownFormat = TRUE;
DebugLog((DEB_TRACE, "BlobDecodeRequest: Blob from server known type and version\n"));
}
if (!fKnownFormat)
{
DebugLog((DEB_ERROR, "BlobDecodeRequest: Not supported MessageType/Version\n"));
Status = STATUS_INVALID_PARAMETER;
goto CleanUp;
}
pDigest->typeDigest = (DIGEST_TYPE)Header.digest_type;
pDigest->typeQOP = (QOP_TYPE)Header.qop_type;
pDigest->typeAlgorithm = (ALGORITHM_TYPE)Header.alg_type;
pDigest->typeCharset = (CHARSET_TYPE)Header.charset_type;
pDigest->typeName = (NAMEFORMAT_TYPE)Header.name_format;
pDigest->usFlags = (USHORT)Header.usFlags;
DebugLog((DEB_TRACE, "BlobDecodeRequest: typeDigest 0x%x, typeQOP %d, typeAlgorithm %d, typeCharset %d NameFormat %d\n",
pDigest->typeDigest, pDigest->typeQOP, pDigest->typeAlgorithm, pDigest->typeCharset, pDigest->typeName));
pHeader = (PDIGEST_BLOB_REQUEST)pBuffer;
pch = &(pHeader->cCharValues); // strings start on last char of struct
sMaxRead = Header.cbCharValues;
for (i = 0; i < DIGEST_BLOB_VALUES;i++)
{
sLen = strlencounted(pch, sMaxRead);
if (!sLen)
{
// Null String no value skip to next
pch++;
sMaxRead--;
}
else
{ // Simple check to make sure that we do not copy way too much
if (sLen < (Header.cbCharValues))
{
DebugLog((DEB_TRACE, "BlobDecodeRequest: Setting Digest[%d] = %s\n", i, pch));
pDigest->refstrParam[i].Buffer = pch;
pDigest->refstrParam[i].Length = sLen;
pDigest->refstrParam[i].MaximumLength = sLen+1;
pch += (sLen + 1); // skip over field-value and NULL
sMaxRead -= (sLen + 1);
}
else
{
// This indicates failed NULL separators in BlobData
// Really should not happen unless encoded wrong
Status = STATUS_INTERNAL_DB_CORRUPTION;
memset(pDigest, 0, sizeof(DIGEST_PARAMETER)); // scrubbed all info
DebugLog((DEB_ERROR, "BlobDecodeRequest: NULL separator missing\n"));
goto CleanUp;
}
}
}
// Read in the values that DSCrackName on the server found out
// Need to place on SHORT boundary for Unicode string processing
usCnt = sMaxRead + (3 * sizeof(WCHAR));
pusTemp = (PWCHAR)DigestAllocateMemory(usCnt); // Force a NULL terminator just to be safe
if (!pusTemp)
{
Status = STATUS_NO_MEMORY;
DebugLog((DEB_ERROR, "BlobDecodeRequest: Memory Alloc Error\n"));
goto CleanUp;
}
// Format will be Unicode_account_name NULL Unicode_domain_name NULL Unicode_WorkstationName NULL [NULL NULL NULL]
memcpy((PCHAR)pusTemp, pch, sMaxRead);
// Read out the three unicode strings
Status = UnicodeStringWCharDuplicate(&(pDigest->ustrCrackedAccountName), pusTemp, 0);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"BlobDecodeRequest: Failed to duplicate Account Name: 0x%x\n",Status));
goto CleanUp;
}
pusLoc = pusTemp + (1 + (pDigest->ustrCrackedAccountName.Length / sizeof(WCHAR))); // Skip NULL
Status = UnicodeStringWCharDuplicate(&(pDigest->ustrCrackedDomain), pusLoc, 0);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"BlobDecodeRequest: Failed to duplicate Domain Name: 0x%x\n",Status));
goto CleanUp;
}
pusLoc = pusTemp + (2 + ((pDigest->ustrCrackedAccountName.Length + pDigest->ustrCrackedDomain.Length) / sizeof(WCHAR))); // Skip NULL
Status = UnicodeStringWCharDuplicate(&(pDigest->ustrWorkstation), pusLoc, 0);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"BlobDecodeRequest: Failed to duplicate Workstation Name: 0x%x\n",Status));
goto CleanUp;
}
DebugLog((DEB_TRACE,"BlobDecodeRequest: Cracked Account %wZ Domain %wZ Workstation %wZ\n",
&(pDigest->ustrCrackedAccountName),
&(pDigest->ustrCrackedDomain),
&(pDigest->ustrWorkstation)));
// If AuthzID directive was present but no length then create a valid buffer but zero length
if ((pDigest->usFlags & FLAG_AUTHZID_PROVIDED) &&
!pDigest->refstrParam[MD5_AUTH_AUTHZID].Buffer)
{
pDigest->refstrParam[MD5_AUTH_AUTHZID].Buffer = &(pHeader->cCharValues); // strings start on last char of struct;
pDigest->refstrParam[MD5_AUTH_AUTHZID].Length = 0;
pDigest->refstrParam[MD5_AUTH_AUTHZID].MaximumLength = 0;
}
CleanUp:
DebugLog((DEB_TRACE_FUNC, "BlobDecodeRequest: Leaving 0x%x\n", Status));
if (pusTemp)
{
DigestFreeMemory(pusTemp);
pusTemp = NULL;
}
return(Status);
}
// Free BYTE Buffer from BlobEncodeRequest
VOID NTAPI BlobFreeRequest(
BYTE *pBuffer
)
{
if (pBuffer)
{
DigestFreeMemory(pBuffer);
}
return;
}
#else // SECURITY_KERNEL
//+--------------------------------------------------------------------
//
// Function: DigestHash7 - kernel mode
//
// Synopsis: Hash and Encode 7 STRINGS SOut = Hex(H(S1 S2 S3 S4 S5 S6 S7))
//
// Effects:
//
// Arguments: pS1,...,pS6 - STRINGS to hash, pS1 must be specified
// fHexOut - perform a Hex operation on output
// pSOut - STRING to hold Hex Encoded Hash
//
// Returns: STATUS_SUCCESS for normal completion
//
// Notes: pSOut->MaximumLength must be atleast (MD5_HASH_BYTESIZE (or MD5_HASH_HEX_SIZE) + sizeof(NULL))
// Any pS# args which are NULL are skipped
// if pS# is not NULL
// Previously checked that pS# is non-zero length strings
// You most likely want Sx->Length = strlen(Sx) so as not to include NULL
// This function combines operations like H(S1 S2 S3), H(S1 S2 S3 S4 S5) ....
// It is assumed that the char ':' is to be included getween Sn and Sn+1
//
//
//---------------------------------------------------------------------
NTSTATUS NTAPI
DigestHash7(
IN PSTRING pS1,
IN PSTRING pS2,
IN PSTRING pS3,
IN PSTRING pS4,
IN PSTRING pS5,
IN PSTRING pS6,
IN PSTRING pS7,
IN BOOL fHexOut,
OUT PSTRING pSOut)
{
NTSTATUS Status = STATUS_SUCCESS;
MD5_CTX Md5Context;
USHORT usSizeRequired = 0;
char *pbSeparator = COLONSTR;
DebugLog((DEB_TRACE_FUNC, "DigestHash7: Entering \n"));
// Verify the size of the output digest is what we assume
ASSERT(MD5DIGESTLEN == MD5_HASH_BYTESIZE);
ASSERT(pSOut);
if (fHexOut == TRUE)
{
usSizeRequired = MD5_HASH_HEX_SIZE;
}
else
{
usSizeRequired = MD5_HASH_BYTESIZE;
}
// Check if output is proper size or allocate one
if (!pSOut->Buffer)
{
Status = StringAllocate(pSOut, usSizeRequired);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestHash7: No Memory\n"));
Status = SEC_E_INSUFFICIENT_MEMORY;
goto CleanUp;
}
}
else
{
if (pSOut->MaximumLength < (usSizeRequired + 1))
{
// Output is not large enough to hold Hex(Hash)
DebugLog((DEB_ERROR, "DigestHash7: Output buffer too small\n"));
Status = STATUS_BUFFER_TOO_SMALL;
goto CleanUp;
}
}
MD5Init(&Md5Context);
if (pS1)
{
MD5Update(&Md5Context, (const unsigned char *)pS1->Buffer, pS1->Length);
}
if (pS2)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS2->Buffer, pS2->Length);
}
if (pS3)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS3->Buffer, pS3->Length);
}
if (pS4)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS4->Buffer, pS4->Length);
}
if (pS5)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS5->Buffer, pS5->Length);
}
if (pS6)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS6->Buffer, pS6->Length);
}
if (pS7)
{
MD5Update(&Md5Context, (const unsigned char *)pbSeparator, COLONSTR_LEN);
MD5Update(&Md5Context, (const unsigned char *)pS7->Buffer, pS7->Length);
}
MD5Final(&Md5Context);
if (fHexOut == TRUE)
{
BinToHex((LPBYTE)Md5Context.digest, MD5_HASH_BYTESIZE, pSOut->Buffer);
pSOut->Length = MD5_HASH_HEX_SIZE; // Do not count the NULL at the end
}
else
{
RtlCopyMemory(pSOut->Buffer, Md5Context.digest, MD5_HASH_BYTESIZE);
pSOut->Length = MD5_HASH_BYTESIZE; // Do not count the NULL at the end
}
CleanUp:
DebugLog((DEB_TRACE_FUNC, "DigestHash7: Leaving Status 0x%x\n", Status));
return(Status);
}
#endif // SECURITY_KERNEL
// Generate the output buffer from a given Digest
NTSTATUS NTAPI
DigestCreateChalResp(
IN PDIGEST_PARAMETER pDigest,
IN PUSER_CREDENTIALS pUserCreds,
OUT PSecBuffer OutBuffer
)
{
NTSTATUS Status = STATUS_SUCCESS;
ULONG cbLenNeeded = 0;
STRING strcQOP = {0}; // string pointing to a constant value
STRING strcAlgorithm = {0};
BOOL fSASLMode = FALSE;
UINT uCodePage = CP_8859_1;
STRING strTempRealm = {0}; // Backslash encoded forms
STRING strTempUsername = {0};
STRING strRealm = {0};
STRING strUsername = {0};
PSTRING pstrUsername = NULL;
PSTRING pstrRealm = NULL;
PCHAR pczTemp = NULL;
PCHAR pczTemp2 = NULL;
DebugLog((DEB_TRACE_FUNC, "DigestCreateChalResp: Entering\n"));
// allocate the buffers for output - in the future can optimze to allocate exact amount needed
pczTemp = (PCHAR)DigestAllocateMemory((3 * NTDIGEST_SP_MAX_TOKEN_SIZE) + 1);
if (!pczTemp)
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: No memory for output buffers\n"));
goto CleanUp;
}
pczTemp2 = (PCHAR)DigestAllocateMemory(NTDIGEST_SP_MAX_TOKEN_SIZE + 1);
if (!pczTemp2)
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: No memory for output buffers\n"));
goto CleanUp;
}
//pczTemp[0] = '\0'; DigestAllocateMemory() should have zeroed the bytes
//pczTemp2[0] = '\0';
if ((pDigest->typeDigest == SASL_SERVER) || (pDigest->typeDigest == SASL_CLIENT))
{
fSASLMode = TRUE;
}
// Establish which QOP utilized
if (pDigest->typeQOP == AUTH_CONF)
{
RtlInitString(&strcQOP, AUTHCONFSTR);
}
else if (pDigest->typeQOP == AUTH_INT)
{
RtlInitString(&strcQOP, AUTHINTSTR);
}
else if (pDigest->typeQOP == AUTH)
{
RtlInitString(&strcQOP, AUTHSTR);
}
// Determine which code page to utilize
if (pDigest->typeCharset == UTF_8)
{
uCodePage = CP_UTF8;
}
else
{
uCodePage = CP_8859_1;
}
// if provided with UserCred then use them, otherwise use Digest directive values
if (pUserCreds)
{
#ifndef SECURITY_KERNEL
DebugLog((DEB_TRACE, "DigestCreateChalResp: UserCredentials presented - encode and output\n"));
Status = EncodeUnicodeString(&pUserCreds->ustrUsername, uCodePage, &strUsername, NULL);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN, "DigestCreateChalResp: Error in encoding username\n"));
goto CleanUp;
}
// Now encode the user directed fields (username, URI, realm)
Status = BackslashEncodeString(&strUsername, &strTempUsername);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: BackslashEncode failed status 0x%x\n", Status));
goto CleanUp;
}
pstrUsername = &strTempUsername;
// Make copy of the directive values for LSA to Usermode context
Status = StringDuplicate(&(pDigest->strUsernameEncoded), pstrUsername);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: Failed to copy over UsernameEncoded\n"));
goto CleanUp;
}
Status = StringReference(&(pDigest->refstrParam[MD5_AUTH_USERNAME]), &(pDigest->strUsernameEncoded));
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: Failed reference UsernameEncoded\n"));
goto CleanUp;
}
if (pUserCreds->ustrRealm.Buffer)
{
Status = EncodeUnicodeString(&pUserCreds->ustrRealm, uCodePage, &strRealm, NULL);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN, "DigestCreateChalResp: Error in encoding realm\n"));
goto CleanUp;
}
Status = BackslashEncodeString(&strRealm, &strTempRealm);
if (!NT_SUCCESS (Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: BackslashEncode failed status 0x%x\n", Status));
goto CleanUp;
}
pstrRealm = &strTempRealm;
Status = StringDuplicate(&(pDigest->strRealmEncoded), pstrRealm);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: Failed to copy over RealmEncoded\n"));
goto CleanUp;
}
Status = StringReference(&(pDigest->refstrParam[MD5_AUTH_REALM]), &(pDigest->strRealmEncoded));
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "DigestCreateChalResp: Failed reference RealmEncoded\n"));
goto CleanUp;
}
}
else
{
pstrRealm = &(pDigest->refstrParam[MD5_AUTH_REALM]);
DebugLog((DEB_ERROR, "DigestCreateChalResp: Realm defaulted to Challenge realm value\n"));
}
#else // SECURITY_KERNEL
DebugLog((DEB_ERROR, "DigestCreateChalResp: User credential processing not permitted\n"));
Status = STATUS_NOT_SUPPORTED;
goto CleanUp;
#endif // SECURITY_KERNEL
}
else
{
// No usercreds passed in so just use the current digest directive values
DebugLog((DEB_WARN, "DigestCreateChalResp: No UserCredentials - use provided digest\n"));
pstrUsername = &(pDigest->refstrParam[MD5_AUTH_USERNAME]);
pstrRealm = &(pDigest->refstrParam[MD5_AUTH_REALM]);
}
// Request-URI will be % encoded backslash is an excluded character so not backslash encoding
// Precalc the amount of space needed for output
cbLenNeeded = CB_CHALRESP; // MAX byte count for directives and symbols
cbLenNeeded += pstrUsername->Length;
cbLenNeeded += pstrRealm->Length;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_NONCE].Length;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_URI].Length;
cbLenNeeded += pDigest->strResponse.Length;
cbLenNeeded += strcAlgorithm.Length;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_CNONCE].Length;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_OPAQUE].Length;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_AUTHZID].Length;
cbLenNeeded += MAX_AUTH_LENGTH;
cbLenNeeded += pDigest->refstrParam[MD5_AUTH_NC].Length;
cbLenNeeded += strcQOP.Length;
if (cbLenNeeded > NTDIGEST_SP_MAX_TOKEN_SIZE)
{
Status = STATUS_BUFFER_TOO_SMALL;
DebugLog((DEB_ERROR, "DigestCreateChalResp: output exceed max size or buffer too small len is %d\n", cbLenNeeded));
goto CleanUp;
}
// In digest calc - already checked username,realm,nonce,method,uri
// Make sure there are values for the rest needed
if ((!pDigest->strResponse.Length) ||
(!pDigest->refstrParam[MD5_AUTH_NC].Length) ||
(!pDigest->refstrParam[MD5_AUTH_CNONCE].Length))
{
// Failed on a require field-value
Status = STATUS_INVALID_PARAMETER;
DebugLog((DEB_ERROR, "DigestCreateChalResp: Response, NC, or Cnonce is zero length\n"));
goto CleanUp;
}
if (pstrRealm->Length)
{
sprintf(pczTemp,
"username=\"%Z\",realm=\"%Z\",nonce=\"%Z\",%s=\"%Z\"",
pstrUsername,
pstrRealm,
&pDigest->refstrParam[MD5_AUTH_NONCE],
((fSASLMode == TRUE) ? DIGESTURI_STR: URI_STR),
&pDigest->refstrParam[MD5_AUTH_URI]);
}
else
{
sprintf(pczTemp,
"username=\"%Z\",realm=\"\",nonce=\"%Z\",%s=\"%Z\"",
pstrUsername,
&pDigest->refstrParam[MD5_AUTH_NONCE],
((fSASLMode == TRUE) ? DIGESTURI_STR: URI_STR),
&pDigest->refstrParam[MD5_AUTH_URI]);
}
if (pDigest->typeQOP != NO_QOP_SPECIFIED)
{
// Do not output cnonce and nc when QOP was not specified
// this happens only for HTTP mode. In SASL mode, we default to AUTH if not specified
sprintf(pczTemp2, ",cnonce=\"%Z\",nc=%Z",
&pDigest->refstrParam[MD5_AUTH_CNONCE],
&pDigest->refstrParam[MD5_AUTH_NC]);
strcat(pczTemp, pczTemp2);
}
if (fSASLMode == TRUE)
{
// Do not output algorithm - must be md5-sess and that is assumed
sprintf(pczTemp2, ",response=%Z", &pDigest->strResponse);
strcat(pczTemp, pczTemp2);
}
else
{
if (pDigest->typeAlgorithm == MD5_SESS)
{
sprintf(pczTemp2, ",algorithm=MD5-sess,response=\"%Z\"", &pDigest->strResponse);
strcat(pczTemp, pczTemp2);
}
else
{
sprintf(pczTemp2, ",response=\"%Z\"", &pDigest->strResponse);
strcat(pczTemp, pczTemp2);
}
}
// Attach QOP if specified - support older format for no QOP
// RFC has qop not quoted, but older IIS needed this quoted.
// Set ClientCompat bit off to conform to RFC
if (strcQOP.Length)
{
if ((fSASLMode == TRUE) || (!(pDigest->usFlags & FLAG_QUOTE_QOP)))
{
sprintf(pczTemp2, ",qop=%Z", &strcQOP);
strcat(pczTemp, pczTemp2);
}
else
{
sprintf(pczTemp2, ",qop=\"%Z\"", &strcQOP);
strcat(pczTemp, pczTemp2);
}
}
// Attach Cipher selected if required
if (pDigest->typeQOP == AUTH_CONF)
{
// FIX optimize these into a list for efficiency
if (pDigest->typeCipher == CIPHER_RC4)
{
sprintf(pczTemp2, ",cipher=%s", STR_CIPHER_RC4);
strcat(pczTemp, pczTemp2);
}
else if (pDigest->typeCipher == CIPHER_RC4_56)
{
sprintf(pczTemp2, ",cipher=%s", STR_CIPHER_RC4_56);
strcat(pczTemp, pczTemp2);
}
else if (pDigest->typeCipher == CIPHER_RC4_40)
{
sprintf(pczTemp2, ",cipher=%s", STR_CIPHER_RC4_40);
strcat(pczTemp, pczTemp2);
}
else if (pDigest->typeCipher == CIPHER_3DES)
{
sprintf(pczTemp2, ",cipher=%s", STR_CIPHER_3DES);
strcat(pczTemp, pczTemp2);
}
else if (pDigest->typeCipher == CIPHER_DES)
{
sprintf(pczTemp2, ",cipher=%s", STR_CIPHER_DES);
strcat(pczTemp, pczTemp2);
}
}
// Attach opaque data (but not on SASL)
if ((fSASLMode == FALSE) && pDigest->refstrParam[MD5_AUTH_OPAQUE].Length)
{
sprintf(pczTemp2, ",opaque=\"%Z\"", &pDigest->refstrParam[MD5_AUTH_OPAQUE]);
strcat(pczTemp, pczTemp2);
}
// Attach authzid data (only in SASL mode)
if ((fSASLMode == TRUE) && (pDigest->usFlags & FLAG_AUTHZID_PROVIDED))
{
if (pDigest->refstrParam[MD5_AUTH_AUTHZID].Length)
{
sprintf(pczTemp2, ",authzid=\"%Z\"", &pDigest->refstrParam[MD5_AUTH_AUTHZID]);
}
else
{
sprintf(pczTemp2, ",authzid=\"\"");
}
strcat(pczTemp, pczTemp2);
}
// Attach charset to indicate that UTF-8 character encoding is utilized
if (pDigest->typeCharset == UTF_8)
{
strcat(pczTemp, ",charset=utf-8");
}
// total buffer for Challenge (NULL is not included in output buffer - ref:Bug 310201)
cbLenNeeded = (USHORT)strlen(pczTemp);
if (cbLenNeeded > NTDIGEST_SP_MAX_TOKEN_SIZE)
{
ASSERT(0); // This never happen since tested MAX size of cbLenNeeded above
Status = STATUS_BUFFER_TOO_SMALL;
DebugLog((DEB_ERROR, "DigestCreateChalResp: output challengeResponse too long\n"));
goto CleanUp;
}
// Check on allocating output buffer
if (!OutBuffer->cbBuffer)
{
OutBuffer->pvBuffer = DigestAllocateMemory(cbLenNeeded);
if (!OutBuffer->pvBuffer)
{
Status = SEC_E_INSUFFICIENT_MEMORY;
DebugLog((DEB_ERROR, "DigestCreateChalResp: out of memory on challenge output\n"));
goto CleanUp;
}
OutBuffer->cbBuffer = cbLenNeeded;
}
if (cbLenNeeded > OutBuffer->cbBuffer)
{
Status = STATUS_BUFFER_TOO_SMALL;
DebugLog((DEB_ERROR, "DigestCreateChalResp: output buffer too small need %d len is %d\n",
cbLenNeeded, OutBuffer->cbBuffer));
goto CleanUp;
}
memcpy(OutBuffer->pvBuffer, pczTemp, cbLenNeeded);
OutBuffer->cbBuffer = cbLenNeeded;
OutBuffer->BufferType = SECBUFFER_TOKEN;
CleanUp:
if (pczTemp)
{
DigestFreeMemory(pczTemp);
pczTemp = NULL;
}
if (pczTemp2)
{
DigestFreeMemory(pczTemp2);
pczTemp2 = NULL;
}
StringFree(&strTempRealm);
StringFree(&strTempUsername);
StringFree(&strRealm);
StringFree(&strUsername);
DebugLog((DEB_TRACE_FUNC, "DigestCreateChalResp: Leaving status 0x%x\n", Status));
return(Status);
}
NTSTATUS
DigestPrint(PDIGEST_PARAMETER pDigest)
{
NTSTATUS Status = STATUS_SUCCESS;
int i = 0;
if (!pDigest)
{
return (STATUS_INVALID_PARAMETER);
}
if (pDigest->typeDigest == DIGEST_UNDEFINED)
{
DebugLog((DEB_TRACE, "Digest: DIGEST_UNDEFINED\n"));
}
if (pDigest->typeDigest == NO_DIGEST_SPECIFIED)
{
DebugLog((DEB_ERROR, "Digest: NO_DIGEST_SPECIFIED\n"));
}
if (pDigest->typeDigest == DIGEST_CLIENT)
{
DebugLog((DEB_TRACE, "Digest: DIGEST_CLIENT\n"));
}
if (pDigest->typeDigest == DIGEST_SERVER)
{
DebugLog((DEB_TRACE, "Digest: DIGEST_SERVER\n"));
}
if (pDigest->typeDigest == SASL_SERVER)
{
DebugLog((DEB_TRACE, "Digest: SASL_SERVER\n"));
}
if (pDigest->typeDigest == SASL_CLIENT)
{
DebugLog((DEB_TRACE, "Digest: SASL_CLIENT\n"));
}
if (pDigest->typeQOP == QOP_UNDEFINED)
{
DebugLog((DEB_ERROR, "Digest: QOP: Not QOP_UNDEFINED\n"));
}
if (pDigest->typeQOP == NO_QOP_SPECIFIED)
{
DebugLog((DEB_TRACE, "Digest: QOP: Not Specified\n"));
}
if (pDigest->typeQOP == AUTH)
{
DebugLog((DEB_TRACE, "Digest: QOP: AUTH\n"));
}
if (pDigest->typeQOP == AUTH_INT)
{
DebugLog((DEB_TRACE, "Digest: QOP: AUTH_INT\n"));
}
if (pDigest->typeQOP == AUTH_CONF)
{
DebugLog((DEB_TRACE, "Digest: QOP: AUTH_CONF\n"));
}
if (pDigest->typeAlgorithm == ALGORITHM_UNDEFINED)
{
DebugLog((DEB_ERROR, "Digest: Algorithm: ALGORITHM_UNDEFINED\n"));
}
if (pDigest->typeAlgorithm == NO_ALGORITHM_SPECIFIED)
{
DebugLog((DEB_TRACE, "Digest: Algorithm: NO_ALGORITHM_SPECIFIED\n"));
}
if (pDigest->typeAlgorithm == MD5)
{
DebugLog((DEB_TRACE, "Digest: Algorithm: MD5\n"));
}
if (pDigest->typeAlgorithm == MD5_SESS)
{
DebugLog((DEB_TRACE, "Digest: Algorithm: MD5_SESS\n"));
}
if (pDigest->typeCharset == ISO_8859_1)
{
DebugLog((DEB_TRACE, "Digest: CharSet: ISO-8859-1\n"));
}
if (pDigest->typeCharset == UTF_8)
{
DebugLog((DEB_TRACE, "Digest: CharSet: UTF-8\n"));
}
if (pDigest->usFlags & FLAG_CRACKNAME_ON_DC)
{
DebugLog((DEB_TRACE, "Digest: Flags: CrackName on DC\n"));
}
if (pDigest->usFlags & FLAG_AUTHZID_PROVIDED)
{
DebugLog((DEB_TRACE, "Digest: Flags: AuthzID info provided\n"));
}
if (pDigest->usFlags & FLAG_SERVERS_DOMAIN)
{
DebugLog((DEB_TRACE, "Digest: Flags: Server's DC\n"));
}
if (pDigest->usFlags & FLAG_BS_ENCODE_CLIENT_BROKEN)
{
DebugLog((DEB_TRACE, "Digest: Flags: Client BS encode compatibility\n"));
}
if (pDigest->usFlags & FLAG_AUTHZID_PROVIDED)
{
DebugLog((DEB_TRACE, "Digest: Flags: AuthzID provided\n"));
}
if (pDigest->usFlags & FLAG_NOBS_DECODE)
{
DebugLog((DEB_TRACE, "Digest: Flags: No Backslash Decoding\n"));
}
for (i=0; i < MD5_AUTH_LAST;i++)
{
if (pDigest->refstrParam[i].Buffer &&
pDigest->refstrParam[i].Length)
{
DebugLog((DEB_TRACE, "Digest: Digest[%d] = \"%Z\"\n", i, &pDigest->refstrParam[i]));
}
}
DebugLog((DEB_TRACE, "Digest: SessionKey %Z\n", &(pDigest->strSessionKey)));
DebugLog((DEB_TRACE, "Digest: Response %Z\n", &(pDigest->strResponse)));
DebugLog((DEB_TRACE, "Digest: Username %wZ\n", &(pDigest->ustrUsername)));
DebugLog((DEB_TRACE, "Digest: Realm %wZ\n", &(pDigest->ustrRealm)));
DebugLog((DEB_TRACE, "Digest: CrackedAccountName %wZ\n", &(pDigest->ustrCrackedAccountName)));
DebugLog((DEB_TRACE, "Digest: CrackedDomain %wZ\n", &(pDigest->ustrCrackedDomain)));
DebugLog((DEB_TRACE, "Digest: Workstation %wZ\n", &(pDigest->ustrWorkstation)));
return(Status);
}