/*++ Copyright (c) 2001 Microsoft Corporation Module Name: ntlmsspv2.c Abstract: NTLM v2 specific modules Author: Larry Zhu (LZhu) 29-August-2001 Environment: User Mode Revision History: --*/ #include "ntlmsspv2.h" #include #include SECURITY_STATUS SspNtStatusToSecStatus( IN NTSTATUS NtStatus, IN SECURITY_STATUS DefaultStatus ) /*++ Routine Description: Convert an NtStatus code to the corresponding Security status code. For particular errors that are required to be returned as is (for setup code) don't map the errors. Arguments: NtStatus - NT status to convert DefaultStatus - default security status if NtStatus is not mapped Return Value: Returns security status code. --*/ { SECURITY_STATUS SecStatus; // // Check for security status and let them through // if (HRESULT_FACILITY(NtStatus) == FACILITY_SECURITY) { return (NtStatus); } switch (NtStatus) { case STATUS_SUCCESS: SecStatus = SEC_E_OK; break; case STATUS_NO_MEMORY: case STATUS_INSUFFICIENT_RESOURCES: SecStatus = SEC_E_INSUFFICIENT_MEMORY; break; case STATUS_NETLOGON_NOT_STARTED: case STATUS_DOMAIN_CONTROLLER_NOT_FOUND: case STATUS_NO_LOGON_SERVERS: case STATUS_NO_SUCH_DOMAIN: case STATUS_BAD_NETWORK_PATH: case STATUS_TRUST_FAILURE: case STATUS_TRUSTED_RELATIONSHIP_FAILURE: case STATUS_NETWORK_UNREACHABLE: SecStatus = SEC_E_NO_AUTHENTICATING_AUTHORITY; break; case STATUS_NO_SUCH_LOGON_SESSION: SecStatus = SEC_E_UNKNOWN_CREDENTIALS; break; case STATUS_INVALID_PARAMETER: case STATUS_PARTIAL_COPY: SecStatus = SEC_E_INVALID_TOKEN; break; case STATUS_PRIVILEGE_NOT_HELD: SecStatus = SEC_E_NOT_OWNER; break; case STATUS_INVALID_HANDLE: SecStatus = SEC_E_INVALID_HANDLE; break; case STATUS_BUFFER_TOO_SMALL: SecStatus = SEC_E_BUFFER_TOO_SMALL; break; case STATUS_NOT_SUPPORTED: SecStatus = SEC_E_UNSUPPORTED_FUNCTION; break; case STATUS_OBJECT_NAME_NOT_FOUND: case STATUS_NO_TRUST_SAM_ACCOUNT: SecStatus = SEC_E_TARGET_UNKNOWN; break; case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT: case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT: case STATUS_TRUSTED_DOMAIN_FAILURE: SecStatus = NtStatus; break; case STATUS_LOGON_FAILURE: case STATUS_NO_SUCH_USER: case STATUS_ACCOUNT_DISABLED: case STATUS_ACCOUNT_RESTRICTION: case STATUS_ACCOUNT_LOCKED_OUT: case STATUS_WRONG_PASSWORD: case STATUS_ACCOUNT_EXPIRED: case STATUS_PASSWORD_EXPIRED: case STATUS_PASSWORD_MUST_CHANGE: case STATUS_LOGON_TYPE_NOT_GRANTED: SecStatus = SEC_E_LOGON_DENIED; break; case STATUS_NAME_TOO_LONG: case STATUS_ILL_FORMED_PASSWORD: SecStatus = SEC_E_INVALID_TOKEN; break; case STATUS_TIME_DIFFERENCE_AT_DC: SecStatus = SEC_E_TIME_SKEW; break; case STATUS_SHUTDOWN_IN_PROGRESS: SecStatus = SEC_E_SHUTDOWN_IN_PROGRESS; break; case STATUS_INTERNAL_ERROR: SecStatus = SEC_E_INTERNAL_ERROR; ASSERT(FALSE); break; default: SecStatus = DefaultStatus; break; } return (SecStatus); } NTSTATUS SspInitUnicodeStringNoAlloc( IN PCSTR pszSource, IN OUT UNICODE_STRING* pDestination ) /*++ Routine Description: Initialize unicode string. This routine does not allocate memory. Arguments: pszSource - source string pDestination - unicode string Return Value: NTSTATUS --*/ { STRING OemString; RtlInitString(&OemString, pszSource); return SspOemStringToUnicodeString(pDestination, &OemString, FALSE); } VOID SspFreeStringEx( IN OUT STRING* pString ) /*++ Routine Description: Free string. Arguments: pString - string to free Return Value: none --*/ { if (pString->MaximumLength && pString->Buffer) { _fmemset(pString->Buffer, 0, pString->Length); SspFree(pString->Buffer); pString->MaximumLength = pString->Length = 0; pString->Buffer = NULL; } } VOID SspFreeUnicodeString( IN OUT UNICODE_STRING* pUnicodeString ) /*++ Routine Description: Free unicode string. Arguments: pUnicodeString - unicode string to free Return Value: none --*/ { SspFreeStringEx((STRING *) pUnicodeString); } MSV1_0_AV_PAIR* SspAvlInit( IN VOID* pAvList ) /*++ Routine Description: Initialize AV pair list Arguments: pAvList - first pair of AV pair list Return Value: AV list --*/ { MSV1_0_AV_PAIR* pAvPair; pAvPair = (MSV1_0_AV_PAIR*) pAvList; if (!pAvPair) { return NULL; } pAvPair->AvId = MsvAvEOL; pAvPair->AvLen = 0; return pAvPair; } MSV1_0_AV_PAIR* SspAvlAdd( IN MSV1_0_AV_PAIR* pAvList, IN MSV1_0_AVID AvId, IN UNICODE_STRING* pString, IN ULONG cAvList ) /*++ Routine Description: add an AV pair to a list, ssumes buffer is long enough! Arguments: pAvList - first pair of AV pair list AvId - AV pair to add pString - value of pair cAvList - max size of AV list Return Value: av pair added, NULL on failure --*/ { MSV1_0_AV_PAIR* pCurPair; // // find the EOL // pCurPair = SspAvlGet(pAvList, MsvAvEOL, cAvList); if (pCurPair == NULL) { return NULL; } // // check for enough space in the av list buffer, then append the new AvPair // (assume the buffer is long enough!) // if ( (((UCHAR*) pCurPair) - ((UCHAR*)pAvList)) + sizeof(MSV1_0_AV_PAIR) * 2 + pString->Length > cAvList) { return NULL; } pCurPair->AvId = (USHORT) AvId; pCurPair->AvLen = (USHORT) pString->Length; _fmemcpy(pCurPair + 1, pString->Buffer, pCurPair->AvLen); // // top it off with a new EOL // pCurPair = (MSV1_0_AV_PAIR*) ((UCHAR*) pCurPair + sizeof(MSV1_0_AV_PAIR) + pCurPair->AvLen); pCurPair->AvId = MsvAvEOL; pCurPair->AvLen = 0; return pCurPair; } MSV1_0_AV_PAIR* SspAvlGet( IN MSV1_0_AV_PAIR* pAvList, IN MSV1_0_AVID AvId, IN ULONG cAvList ) /*++ Routine Description: Find a particular AV pair by ID Arguments: pAvList - first pair of AV pair list AvId - AV pair to find cAvList - size of AV list Return Value: av pair found, NULL if not found --*/ { MSV1_0_AV_PAIR* pAvPair; pAvPair = pAvList; while (TRUE) { if (pAvPair->AvId == AvId) { return pAvPair; } if (pAvPair->AvId == MsvAvEOL) { return NULL; } cAvList -= (pAvPair->AvLen + sizeof(MSV1_0_AV_PAIR)); if (cAvList <= 0) { return NULL; } pAvPair = (MSV1_0_AV_PAIR*) ((UCHAR*) pAvPair + pAvPair->AvLen + sizeof(MSV1_0_AV_PAIR)); } } ULONG SspAvlLen( IN MSV1_0_AV_PAIR* pAvList, IN ULONG cAvList ) /*++ Routine Description: Find length of a AV list Arguments: pAvList - first pair of AV pair list cAvList - target info output Return Value: Length of av list --*/ { MSV1_0_AV_PAIR* pCurPair; // // find the EOL // pCurPair = SspAvlGet(pAvList, MsvAvEOL, cAvList); if (pCurPair == NULL) { return 0; } // // compute length (not forgetting the EOL pair) // return (ULONG)(((UCHAR*) pCurPair - (UCHAR*) pAvList) + sizeof(MSV1_0_AV_PAIR)); } NTSTATUS SspCreateTargetInfo( IN UNICODE_STRING* pTargetName, OUT STRING* pTargetInfo ) /*++ Routine Description: Create a target info from target name Arguments: pTargetName - name of the target, this can be a domain name followed by a server name pTargetInfo - target info output Return Value: NTSTATUS --*/ { UNICODE_STRING DomainName = {0}; UNICODE_STRING ServerName = {0}; ULONG i = 0; MSV1_0_AV_PAIR* pAV; // // check length of name to make sure it fits in my buffer // if (pTargetName->Length > (DNS_MAX_NAME_LENGTH + CNLEN + 2) * sizeof(WCHAR)) { return STATUS_INVALID_PARAMETER; } // // init AV list in temp buffer // pAV = SspAvlInit(pTargetInfo->Buffer); if (!pAV) { return STATUS_INVALID_PARAMETER; } // // see if there's a NULL in the middle of the server name that indicates // that it's really a domain name followed by a server name // DomainName = *pTargetName; for (i = 0; i < (DomainName.Length / sizeof(WCHAR)); i++) { if (DomainName.Buffer[i] == L'\0') { // // take length of domain name without the NULL // DomainName.Length = (USHORT) i * sizeof(WCHAR); // // adjust server name and length to point after the domain name // ServerName.Length = (USHORT) (pTargetName->Length - (i + 1) * sizeof(WCHAR)); ServerName.Buffer = pTargetName->Buffer + (i + 1); break; } } // // strip off possible trailing null after the server name // for (i = 0; i < (ServerName.Length / sizeof(WCHAR)); i++) { if (ServerName.Buffer[i] == L'\0') { ServerName.Length = (USHORT) i * sizeof(WCHAR); break; } } // // put both names in the AV list (if both exist) // if (!SspAvlAdd(pAV, MsvAvNbDomainName, &DomainName, pTargetInfo->MaximumLength)) { return STATUS_INVALID_PARAMETER; } if ((ServerName.Length > 0) && !SspAvlAdd(pAV, MsvAvNbComputerName, &ServerName, pTargetInfo->MaximumLength)) { return STATUS_INVALID_PARAMETER; } // // make the request point at AV list instead of names. // pTargetInfo->Length = (USHORT) SspAvlLen(pAV, pTargetInfo->MaximumLength); pTargetInfo->Buffer = (CHAR*) pAV; return STATUS_SUCCESS; } NTSTATUS SsprHandleNtlmv2ChallengeMessage( IN SSP_CREDENTIAL* pCredential, IN ULONG cbChallengeMessage, IN CHALLENGE_MESSAGE* pChallengeMessage, IN OUT ULONG* pNegotiateFlags, IN OUT ULONG* pcbAuthenticateMessage, OUT AUTHENTICATE_MESSAGE* pAuthenticateMessage, OUT USER_SESSION_KEY* pContextSessionKey ) /*++ Routine Description: Handle challenge message and generate authentication message and context session key Arguments: pCredential - client credentials cbChallengeMessage - challenge message size pChallengeMessage - challenge message pNegotiateFlags - negotiate flags pcbAuthenticateMessage - size of authentication message pAuthenticateMessage - authentication message pContextSessionKey - context session key Return Value: NTSTATUS --*/ { NTSTATUS NtStatus = STATUS_UNSUCCESSFUL; ULONG cbAuthenticateMessage = 0; UCHAR* pWhere = NULL; BOOLEAN DoUnicode = TRUE; // // use a scratch buffer to avoid memory allocation in bootssp // CHAR ScrtachBuff[sizeof(MSV1_0_NTLMV2_RESPONSE) + sizeof(DWORD) + NTLMV2_RESPONSE_LENGTH] = {0}; STRING LmChallengeResponse = {0}; STRING NtChallengeResponse = {0}; STRING DatagramSessionKey = {0}; USHORT Ntlmv2ResponseSize = 0; MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response = NULL; LM_SESSION_KEY LanmanSessionKey = {0}; STRING TargetInfo = {0}; UCHAR DatagramKey[sizeof(USER_SESSION_KEY)] ={0}; USER_SESSION_KEY NtUserSessionKey = {0}; // // use pre-allocated buffers to avoid memory allocation in bootssp // // to be consistent with LSA/SSPI, allow DNS names in szDomainName and // szWorkstation // CHAR szUserName[(UNLEN + 4) * sizeof(WCHAR)] = {0}; CHAR szDomainName[(DNSLEN + 4) * sizeof(WCHAR)] = {0}; CHAR szWorkstation[(DNSLEN + 4) * sizeof(WCHAR)] = {0}; STRING UserName = {0, sizeof(szUserName), szUserName}; STRING DomainName = {0, sizeof(szDomainName), szDomainName}; STRING Workstation = {0, sizeof(szWorkstation), szWorkstation}; // // responses to return to the caller // LM_RESPONSE LmResponse = {0}; NT_RESPONSE NtResponse = {0}; USER_SESSION_KEY ContextSessionKey = {0}; ULONG NegotiateFlags = 0; if (!pCredential || !pChallengeMessage || !pNegotiateFlags || !pcbAuthenticateMessage || !pContextSessionKey) { return STATUS_INVALID_PARAMETER; } SspPrint((SSP_NTLMV2, "Entering SsprHandleNtlmv2ChallengeMessage: NegotiateFlags %#x\n", *pNegotiateFlags)); NegotiateFlags = *pNegotiateFlags; NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Username, (UNICODE_STRING *) &UserName); if (NT_SUCCESS(NtStatus)) { NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Domain, (UNICODE_STRING *) &DomainName); } if (NT_SUCCESS(NtStatus)) { NtStatus = SspInitUnicodeStringNoAlloc(pCredential->Workstation, (UNICODE_STRING *) &Workstation); } if (NT_SUCCESS(NtStatus)) { NtStatus = !_fstrcmp(NTLMSSP_SIGNATURE, (char *) pChallengeMessage->Signature) && pChallengeMessage->MessageType == NtLmChallenge ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; } if (NT_SUCCESS(NtStatus)) { if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_UNICODE) { NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE; NegotiateFlags &= ~NTLMSSP_NEGOTIATE_OEM; DoUnicode = TRUE; } else if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_OEM) { NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM; NegotiateFlags &= ~NTLMSSP_NEGOTIATE_UNICODE; DoUnicode = FALSE; } else { NtStatus = STATUS_INVALID_PARAMETER; } } if (NT_SUCCESS(NtStatus)) { if (!DoUnicode) { SspUpcaseUnicodeString((UNICODE_STRING *) &UserName); SspUpcaseUnicodeString((UNICODE_STRING *) &DomainName); SspUpcaseUnicodeString((UNICODE_STRING *) &Workstation); } if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) { NegotiateFlags &= ~NTLMSSP_NEGOTIATE_LM_KEY; } else // (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2)) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM2); } if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM)) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_NTLM); } if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_KEY_EXCH); } if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY)) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_LM_KEY); } if ((NegotiateFlags & NTLMSSP_NEGOTIATE_DATAGRAM) && (NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN |NTLMSSP_NEGOTIATE_SEAL))) { NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH; } if (!(pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_56)) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_56); } if ((pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_128) == 0) { NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_128); } if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) { NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; } else { NegotiateFlags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN; } if (pChallengeMessage->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) { NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO; SspPrint((SSP_NTLMV2, "SsprHandleNtlmv2ChallengeMessage: NTLMSSP_NEGOTIATE_TARGET_INFO negotiated\n")); NtStatus = SspConvertRelativeToAbsolute( pChallengeMessage, cbChallengeMessage, &pChallengeMessage->TargetInfo, DoUnicode, TRUE, // NULL target info OK &TargetInfo ); } else { UNICODE_STRING TargetName = {0}; UCHAR TargetInfoBuffer[3 * sizeof(MSV1_0_AV_PAIR) + (DNS_MAX_NAME_LENGTH + CNLEN + 2) * sizeof(WCHAR)]; SspPrint((SSP_NTLMV2, "SsprHandleNtlmv2ChallengeMessage: NTLMSSP_NEGOTIATE_TARGET_INFO NOT negotiated\n")); NegotiateFlags &= ~(NTLMSSP_NEGOTIATE_TARGET_INFO); TargetInfo.Length = 0; TargetInfo.MaximumLength = sizeof(TargetInfoBuffer); TargetInfo.Buffer = TargetInfoBuffer; NtStatus = SspConvertRelativeToAbsolute( pChallengeMessage, cbChallengeMessage, &pChallengeMessage->TargetName, DoUnicode, TRUE, // NULL TargetName ok (STRING*) &TargetName ); if (NT_SUCCESS(NtStatus)) { NtStatus = SspCreateTargetInfo(&TargetName, &TargetInfo); } } } if (NT_SUCCESS(NtStatus)) { Ntlmv2ResponseSize = sizeof(MSV1_0_NTLMV2_RESPONSE) + TargetInfo.Length; NtStatus = Ntlmv2ResponseSize <= sizeof(ScrtachBuff) ? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES; } if (NT_SUCCESS(NtStatus)) { // C_ASSERT(sizeof(MSV1_0_NTLMV2_RESPONSE) == sizeof(LM_RESPONSE)); pNtlmv2Response = (MSV1_0_NTLMV2_RESPONSE *) ScrtachBuff; NtStatus = SspLm20GetNtlmv2ChallengeResponse( pCredential->NtPassword, (UNICODE_STRING *) &UserName, (UNICODE_STRING *) &DomainName, &TargetInfo, pChallengeMessage->Challenge, pNtlmv2Response, (MSV1_0_LMV2_RESPONSE *) &LmResponse, &NtUserSessionKey, &LanmanSessionKey ); } if (NT_SUCCESS(NtStatus)) { NtChallengeResponse.Buffer = (CHAR *) pNtlmv2Response; NtChallengeResponse.Length = Ntlmv2ResponseSize; LmChallengeResponse.Buffer = (CHAR *) &LmResponse; LmChallengeResponse.Length = sizeof(LmResponse); // // prepare to send encrypted randomly generated session key // DatagramSessionKey.Buffer = (CHAR *) DatagramKey; DatagramSessionKey.Length = DatagramSessionKey.MaximumLength = 0; // // Generate the session key, or encrypt the previosly generated random // one, from various bits of info. Fill in session key if needed. // NtStatus = SspMakeSessionKeys( NegotiateFlags, &LmChallengeResponse, &NtUserSessionKey, &LanmanSessionKey, &DatagramSessionKey, &ContextSessionKey ); } if (NT_SUCCESS(NtStatus) && !DoUnicode) { NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &DomainName, &DomainName); if (NT_SUCCESS(NtStatus)) { NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &UserName, &UserName); } if (NT_SUCCESS(NtStatus)) { NtStatus = SspUpcaseUnicodeStringToOemString((UNICODE_STRING *) &Workstation, &Workstation); } } if (NT_SUCCESS(NtStatus)) { cbAuthenticateMessage = sizeof(*pAuthenticateMessage) + LmChallengeResponse.Length + NtChallengeResponse.Length + DomainName.Length + UserName.Length + Workstation.Length + DatagramSessionKey.Length; NtStatus = cbAuthenticateMessage <= *pcbAuthenticateMessage ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL; if (NtStatus == STATUS_BUFFER_TOO_SMALL) { *pcbAuthenticateMessage = cbAuthenticateMessage; } } if (NT_SUCCESS(NtStatus)) { _fmemset(pAuthenticateMessage, 0, cbAuthenticateMessage); // // Build the authenticate message // _fstrcpy((char *) pAuthenticateMessage->Signature, NTLMSSP_SIGNATURE); pAuthenticateMessage->MessageType = NtLmAuthenticate; pWhere = (UCHAR *) (pAuthenticateMessage + 1); // // Copy the strings needing 2 byte alignment. // SspCopyStringAsString32( pAuthenticateMessage, &DomainName, &pWhere, &pAuthenticateMessage->DomainName ); SspCopyStringAsString32( pAuthenticateMessage, &UserName, &pWhere, &pAuthenticateMessage->UserName ); SspCopyStringAsString32( pAuthenticateMessage, &Workstation, &pWhere, &pAuthenticateMessage->Workstation ); // // Copy the strings not needing special alignment. // SspCopyStringAsString32( pAuthenticateMessage, (STRING *) &LmChallengeResponse, &pWhere, &pAuthenticateMessage->LmChallengeResponse ); SspCopyStringAsString32( pAuthenticateMessage, (STRING *) &NtChallengeResponse, &pWhere, &pAuthenticateMessage->NtChallengeResponse ); SspCopyStringAsString32( pAuthenticateMessage, (STRING *) &DatagramSessionKey, &pWhere, &pAuthenticateMessage->SessionKey ); pAuthenticateMessage->NegotiateFlags = NegotiateFlags; *pcbAuthenticateMessage = cbAuthenticateMessage; *pContextSessionKey = ContextSessionKey; *pNegotiateFlags = NegotiateFlags; } SspPrint((SSP_NTLMV2, "Leaving SsprHandleNtlmv2ChallengeMessage %#x\n", NtStatus)); return NtStatus; } NTSTATUS SspGenerateChallenge( UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH] ) /*++ Routine Description: Generate a challenge. Arguments: ChallengeFromClient - challenge from client Return Value: NTSTATUS --*/ { NTSTATUS NtStatus; MD5_CTX Md5Context; FILETIME CurTime; ULONG ulRandom; C_ASSERT(sizeof(ULONG) * 2 == MSV1_0_CHALLENGE_LENGTH); C_ASSERT(MD5DIGESTLEN >= MSV1_0_CHALLENGE_LENGTH); SspPrint((SSP_NTLMV2, "SspGenerateChallenge\n")); #ifdef USE_CONSTANT_CHALLENGE _fmemset(ChallengeFromClient, 0, MSV1_0_CHALLENGE_LENGTH); return STATUS_SUCCESS; #endif ulRandom = rand(); _fmemcpy(ChallengeFromClient, &ulRandom, sizeof(ULONG)); ulRandom = rand(); _fmemcpy(ChallengeFromClient + sizeof(ULONG), &ulRandom, sizeof(ULONG)); NtStatus = SspGetSystemTimeAsFileTime(&CurTime); if (!NT_SUCCESS(NtStatus)) { return NtStatus; } MD5Init(&Md5Context); MD5Update(&Md5Context, ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH); MD5Update(&Md5Context, (UCHAR*)&CurTime, sizeof(CurTime)); MD5Final(&Md5Context); // // only take the first half of the MD5 hash // _fmemcpy(ChallengeFromClient, Md5Context.digest, MSV1_0_CHALLENGE_LENGTH); return NtStatus; } NTSTATUS SspConvertRelativeToAbsolute( IN VOID* pMessageBase, IN ULONG cbMessageSize, IN STRING32* pStringToRelocate, IN BOOLEAN AlignToWchar, IN BOOLEAN AllowNullString, OUT STRING* pOutputString ) /*++ Routine Description: Convert relative string to absolute string Arguments: pMessageBase - message base cbMessageSize - mssage size pStringToRelocate - relative string AlignToWchar - align to wide char AllowNullString - allow null string pOutputString - output string Return Value: NTSTATUS --*/ { ULONG Offset; // // If the buffer is allowed to be null, // check that special case. // if (AllowNullString && (pStringToRelocate->Length == 0)) { pOutputString->MaximumLength = pOutputString->Length = pStringToRelocate->Length; pOutputString->Buffer = NULL; return STATUS_SUCCESS; } // // Ensure the string in entirely within the message. // Offset = (ULONG)pStringToRelocate->Buffer; if (Offset >= cbMessageSize || Offset + pStringToRelocate->Length > cbMessageSize) { return STATUS_INVALID_PARAMETER; } // // Ensure the buffer is properly aligned. // if (AlignToWchar && (!COUNT_IS_ALIGNED(Offset, ALIGN_WCHAR) || !COUNT_IS_ALIGNED(pStringToRelocate->Length, ALIGN_WCHAR))) { return STATUS_INVALID_PARAMETER; } // // Finally make the pointer absolute. // pOutputString->Buffer = (CHAR*)(pMessageBase) + Offset; pOutputString->MaximumLength = pOutputString->Length = pStringToRelocate->Length ; return STATUS_SUCCESS; } NTSTATUS SspUpcaseUnicodeStringToOemString( IN UNICODE_STRING* pUnicodeString, OUT STRING* pOemString ) /*++ Routine Description: Upcase unicode string and convert it to oem string. Arguments: pUnicodeString - uncide string pOemString - OEM string Return Value: NTSTATUS --*/ { ULONG i; // // use a scratch buffer: the strings we encounter are among // username/domainname/workstationname, hence the length are // UNLEN maximum // CHAR Buffer[2 * (UNLEN + 4)] = {0}; STRING OemString = {0, sizeof(Buffer), Buffer}; if (OemString.MaximumLength < pUnicodeString->Length) { return STATUS_INSUFFICIENT_RESOURCES; } // // upcase the unicode string and put it into OemString // OemString.Length = pUnicodeString->Length; for (i = 0; i < pUnicodeString->Length / sizeof(WCHAR); i++) { ((UNICODE_STRING*)(&OemString))->Buffer[i] = RtlUpcaseUnicodeChar(pUnicodeString->Buffer[i]); } return SspUnicodeStringToOemString((STRING*)(pUnicodeString), (UNICODE_STRING*)(&OemString), FALSE); } VOID SspUpcaseUnicodeString( IN OUT UNICODE_STRING* pUnicodeString ) /*++ Routine Description: Upcase unicode string, modifying string in place. Arguments: pUnicodeString - string Return Value: none --*/ { ULONG i; for (i = 0; i < pUnicodeString->Length / sizeof(WCHAR); i++) { pUnicodeString->Buffer[i] = RtlUpcaseUnicodeChar(pUnicodeString->Buffer[i]); } } NTSTATUS SspGetSystemTimeAsFileTime( OUT FILETIME* pSystemTimeAsFileTime ) /*++ Routine Description: Get system time as FILETIME Arguments: pSystemTimeAsFileTime system time as FILETIME Return Value: NTSTATUS --*/ { SspPrint((SSP_NTLMV2, "SspGetSystemTimeAsFileTime\n")); #ifdef USE_CONSTANT_CHALLENGE _fmemset(pSystemTimeAsFileTime, 0, sizeof(*pSystemTimeAsFileTime)); return STATUS_SUCCESS; #else return BlGetSystemTimeAsFileTime(pSystemTimeAsFileTime); #endif } NTSTATUS SspLm20GetNtlmv2ChallengeResponse( IN NT_OWF_PASSWORD* pNtOwfPassword, IN UNICODE_STRING* pUserName, IN UNICODE_STRING* pLogonDomainName, IN STRING* pTargetInfo, IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH], OUT MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response, OUT MSV1_0_LMV2_RESPONSE* pLmv2Response, OUT USER_SESSION_KEY* pNtUserSessionKey, OUT LM_SESSION_KEY* pLmSessionKey ) /*++ Routine Description: Get NTLMv2 response and session keys. This route fills in time stamps and challenge from client. Arguments: pNtOwfPassword - NT OWF pUserName - user name pLogonDomainName - logon domain name pTargetInfo - target info ChallengeToClient - challenge to client pNtlmv2Response - NTLM v2 response pLmv2Response - LM v2 response pNtUserSessionKey - NT user session key pLmSessionKey - LM session key Return Value: NTSTATUS --*/ { NTSTATUS NtStatus; SspPrint((SSP_API, "Entering SspLm20GetNtlmv2ChallengeResponse\n")); // // fill in version numbers, timestamp, and client's challenge // pNtlmv2Response->RespType = 1; pNtlmv2Response->HiRespType = 1; pNtlmv2Response->Flags = 0; pNtlmv2Response->MsgWord = 0; NtStatus = SspGetSystemTimeAsFileTime((FILETIME*)(&pNtlmv2Response->TimeStamp)); if (NT_SUCCESS(NtStatus)) { NtStatus = SspGenerateChallenge(pNtlmv2Response->ChallengeFromClient); } if (NT_SUCCESS(NtStatus)) { _fmemcpy(pNtlmv2Response->Buffer, pTargetInfo->Buffer, pTargetInfo->Length); // // Calculate Ntlmv2 response, filling in response field // SspGetNtlmv2Response( pNtOwfPassword, pUserName, pLogonDomainName, pTargetInfo->Length, ChallengeToClient, pNtlmv2Response, pNtUserSessionKey, pLmSessionKey ); // // Use same challenge to compute the LMV2 response // _fmemcpy(pLmv2Response->ChallengeFromClient, pNtlmv2Response->ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH); // // Calculate LMV2 response // SspGetLmv2Response( pNtOwfPassword, pUserName, pLogonDomainName, ChallengeToClient, pLmv2Response->ChallengeFromClient, pLmv2Response->Response ); } SspPrint((SSP_API, "Leaving SspLm20GetNtlmv2ChallengeResponse %#x\n", NtStatus)); return NtStatus; } VOID SspGetNtlmv2Response( IN NT_OWF_PASSWORD* pNtOwfPassword, IN UNICODE_STRING* pUserName, IN UNICODE_STRING* pLogonDomainName, IN ULONG TargetInfoLength, IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH], IN OUT MSV1_0_NTLMV2_RESPONSE* pNtlmv2Response, OUT USER_SESSION_KEY* pNtUserSessionKey, OUT LM_SESSION_KEY* pLmSessionKey ) /*++ Routine Description: Get NTLM v2 response. Arguments: pNtOwfPassword - NT OWF pUserName - user name pLogonDomainName - logon domain name TargetInfoLength - target info length ChallengeToClient - challenge to client pNtlmv2Response - NTLM v2 response response - response pNtUserSessionKey - NT user session key pLmSessionKey - LM session key Return Value: none --*/ { HMACMD5_CTX HMACMD5Context; UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH]; C_ASSERT(MD5DIGESTLEN == MSV1_0_NTLMV2_RESPONSE_LENGTH); C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY)); C_ASSERT(sizeof(LM_SESSION_KEY) <= sizeof(USER_SESSION_KEY)); SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n")); // // get Ntlmv2 OWF // SspCalculateNtlmv2Owf( pNtOwfPassword, pUserName, pLogonDomainName, Ntlmv2Owf ); HMACMD5Init( &HMACMD5Context, Ntlmv2Owf, MSV1_0_NTLMV2_OWF_LENGTH ); HMACMD5Update( &HMACMD5Context, ChallengeToClient, MSV1_0_CHALLENGE_LENGTH ); HMACMD5Update( &HMACMD5Context, &pNtlmv2Response->RespType, (MSV1_0_NTLMV2_INPUT_LENGTH + TargetInfoLength) ); HMACMD5Final( &HMACMD5Context, pNtlmv2Response->Response ); // // now compute the session keys // HMAC(Kr, R) // HMACMD5Init( &HMACMD5Context, Ntlmv2Owf, MSV1_0_NTLMV2_OWF_LENGTH ); HMACMD5Update( &HMACMD5Context, pNtlmv2Response->Response, MSV1_0_NTLMV2_RESPONSE_LENGTH ); HMACMD5Final( &HMACMD5Context, (UCHAR*)(pNtUserSessionKey) ); _fmemcpy(pLmSessionKey, pNtUserSessionKey, sizeof(LM_SESSION_KEY)); } VOID SspCopyStringAsString32( IN VOID* pMessageBuffer, IN STRING* pInString, IN OUT UCHAR** ppWhere, OUT STRING32* pOutString32 ) /*++ Routine Description: Copy string as STRING32 Arguments: pMessageBuffer - STRING32 base pInString - input STRING ppWhere - next empty spot in pMessageBuffer pOutString32 - output STRING32 Return Value: none --*/ { // // Copy the data to the Buffer // if (pInString->Buffer != NULL) { _fmemcpy(*ppWhere, pInString->Buffer, pInString->Length); } // // Build a descriptor to the newly copied data // pOutString32->Length = pOutString32->MaximumLength = pInString->Length; pOutString32->Buffer = (ULONG)(*ppWhere - (UCHAR*)(pMessageBuffer)); // // Update Where to point past the copied data // *ppWhere += pInString->Length; } VOID SspCalculateNtlmv2Owf( IN NT_OWF_PASSWORD* pNtOwfPassword, IN UNICODE_STRING* pUserName, IN UNICODE_STRING* pLogonDomainName, OUT UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH] ) /*++ Routine Description: Calculate Ntlm v2 OWF, salted with username and logon domain name Arguments: pNtOwfPassword - NT OWF pUserName - user name pLogonDomainName - logon domain name Ntlmv2Owf - NTLM v2 OWF Return Value: none --*/ { HMACMD5_CTX HMACMD5Context; // // reserve a scratch buffer // WCHAR szUserName[(UNLEN + 4)] = {0}; UNICODE_STRING UserName = {0, sizeof(szUserName), szUserName}; SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n")); // // first make a copy then upcase it // UserName.Length = min(UserName.MaximumLength, pUserName->Length); ASSERT(UserName.Length == pUserName->Length); _fmemcpy(UserName.Buffer, pUserName->Buffer, UserName.Length); SspUpcaseUnicodeString(&UserName); // // Calculate Ntlmv2 OWF -- HMAC(MD4(P), (UserName, LogonDomainName)) // HMACMD5Init( &HMACMD5Context, (UCHAR *) pNtOwfPassword, sizeof(*pNtOwfPassword) ); HMACMD5Update( &HMACMD5Context, (UCHAR *) UserName.Buffer, UserName.Length ); HMACMD5Update( &HMACMD5Context, (UCHAR *) pLogonDomainName->Buffer, pLogonDomainName->Length ); HMACMD5Final( &HMACMD5Context, Ntlmv2Owf ); } VOID SspGetLmv2Response( IN NT_OWF_PASSWORD* pNtOwfPassword, IN UNICODE_STRING* pUserName, IN UNICODE_STRING* pLogonDomainName, IN UCHAR ChallengeToClient[MSV1_0_CHALLENGE_LENGTH], IN UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH], OUT UCHAR Response[MSV1_0_NTLMV2_RESPONSE_LENGTH] ) /*++ Routine Description: Get LMv2 response Arguments: pNtOwfPassword - NT OWF pUserName - user name pLogonDomainName - logon domain name ChallengeToClient - challenge to client pLmv2Response - Lm v2 response Routine - response Return Value: NTSTATUS --*/ { HMACMD5_CTX HMACMD5Context; UCHAR Ntlmv2Owf[MSV1_0_NTLMV2_OWF_LENGTH]; C_ASSERT(MD5DIGESTLEN == MSV1_0_NTLMV2_RESPONSE_LENGTH); SspPrint((SSP_NTLMV2, "SspGetLmv2Response\n")); // // get Ntlmv2 OWF // SspCalculateNtlmv2Owf( pNtOwfPassword, pUserName, pLogonDomainName, Ntlmv2Owf ); // // Calculate Ntlmv2 Response // HMAC(Ntlmv2Owf, (ChallengeToClient, ChallengeFromClient)) // HMACMD5Init( &HMACMD5Context, Ntlmv2Owf, MSV1_0_NTLMV2_OWF_LENGTH ); HMACMD5Update( &HMACMD5Context, ChallengeToClient, MSV1_0_CHALLENGE_LENGTH ); HMACMD5Update( &HMACMD5Context, ChallengeFromClient, MSV1_0_CHALLENGE_LENGTH ); HMACMD5Final( &HMACMD5Context, Response ); return; } NTSTATUS SspMakeSessionKeys( IN ULONG NegotiateFlags, IN STRING* pLmChallengeResponse, IN USER_SESSION_KEY* pNtUserSessionKey, // from the DC or GetChalResp IN LM_SESSION_KEY* pLanmanSessionKey, // from the DC of GetChalResp OUT STRING* pDatagramSessionKey, // this is the session key sent over wire OUT USER_SESSION_KEY* pContextSessionKey // session key in context ) /*++ Routine Description: Make NTLMv2 context session key and DatagramSessionKey. Arguments: NegotiateFlags - negotiate flags pLmChallengeResponse - LM challenge response pNtUserSessionKey - NtUserSessionKey pLanmanSessionKey - LanmanSessionKey pDatagramSessionKey - DatagramSessionKey pContextSessionKey - NTLMv2 conext session key Return Value: NTSTATUS --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; UCHAR pLocalSessionKey[sizeof(USER_SESSION_KEY)] = {0}; SspPrint((SSP_NTLMV2, "Entering SspMakeSessionKeys\n")); if (!(NegotiateFlags & (NTLMSSP_NEGOTIATE_SIGN| NTLMSSP_NEGOTIATE_SEAL))) { _fmemcpy(pContextSessionKey, pNtUserSessionKey, sizeof(pLocalSessionKey)); return STATUS_SUCCESS; } if (NegotiateFlags & NTLMSSP_NEGOTIATE_NTLM2) { _fmemcpy(pLocalSessionKey, pNtUserSessionKey, sizeof(pLocalSessionKey)); } else if(NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) { LM_OWF_PASSWORD LmKey; LM_RESPONSE LmResponseKey; BYTE pTemporaryResponse[LM_RESPONSE_LENGTH] = {0}; if (pLmChallengeResponse->Length > LM_RESPONSE_LENGTH) { return STATUS_NOT_SUPPORTED; } _fmemcpy(pTemporaryResponse, pLmChallengeResponse->Buffer, pLmChallengeResponse->Length); _fmemcpy(&LmKey, pLanmanSessionKey, sizeof(LM_SESSION_KEY)); _fmemset((UCHAR*)(&LmKey) + sizeof(LM_SESSION_KEY), NTLMSSP_KEY_SALT, LM_OWF_PASSWORD_LENGTH - sizeof(LM_SESSION_KEY) ); NtStatus = CalculateLmResponse( (LM_CHALLENGE *) pTemporaryResponse, &LmKey, &LmResponseKey ); if (!NT_SUCCESS(NtStatus)) { return NtStatus; } _fmemcpy(pLocalSessionKey, &LmResponseKey, sizeof(USER_SESSION_KEY)); } else { _fmemcpy(pLocalSessionKey, pNtUserSessionKey, sizeof(USER_SESSION_KEY)); } if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) { struct RC4_KEYSTRUCT Rc4Key; rc4_key( &Rc4Key, sizeof(USER_SESSION_KEY), pLocalSessionKey ); if (pDatagramSessionKey == NULL) { rc4( &Rc4Key, sizeof(USER_SESSION_KEY), (UCHAR*) pContextSessionKey ); } else { pDatagramSessionKey->Length = pDatagramSessionKey->MaximumLength = sizeof(USER_SESSION_KEY); _fmemcpy(pDatagramSessionKey->Buffer, pContextSessionKey, sizeof(USER_SESSION_KEY)); rc4( &Rc4Key, sizeof(USER_SESSION_KEY), (UCHAR*)(pDatagramSessionKey->Buffer) ); } } else { _fmemcpy(pContextSessionKey, pLocalSessionKey, sizeof(USER_SESSION_KEY)); } SspPrint((SSP_NTLMV2, "Leaving SspMakeSessionKeys %#x\n", NtStatus)); return NtStatus; } VOID SspMakeNtlmv2SKeys( IN USER_SESSION_KEY* pUserSessionKey, IN ULONG NegotiateFlags, IN ULONG SendNonce, IN ULONG RecvNonce, OUT NTLMV2_DERIVED_SKEYS* pNtlmv2Keys ) /*++ Routine Description: Derive all NTLMv2 session keys Arguments: pUserSessionKey - NTLMv2 user session key NegotiateFlags - negotiate flags SendNonce - send message sequence number RecvNonce - receive message sequence number pNtlmv2Keys - derived NTLMv2 session keys Return Value: none --*/ { MD5_CTX Md5Context; C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY)); SspPrint((SSP_NTLMV2, "SspMakeSessionKeys\n")); if (NegotiateFlags & NTLMSSP_NEGOTIATE_128) { pNtlmv2Keys->KeyLen = 16; } else if (NegotiateFlags & NTLMSSP_NEGOTIATE_56) { pNtlmv2Keys->KeyLen = 7; } else { pNtlmv2Keys->KeyLen = 5; } // // make client to server encryption key // MD5Init(&Md5Context); MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), pNtlmv2Keys->KeyLen); MD5Update(&Md5Context, (UCHAR*)(CSSEALMAGIC), sizeof(CSSEALMAGIC)); MD5Final(&Md5Context); _fmemcpy(&pNtlmv2Keys->SealSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY)); // // make server to client encryption key // MD5Init(&Md5Context); MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), pNtlmv2Keys->KeyLen); MD5Update(&Md5Context, (UCHAR*)(SCSEALMAGIC), sizeof(SCSEALMAGIC)); MD5Final(&Md5Context); _fmemcpy(&pNtlmv2Keys->UnsealSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY)); // // make client to server signing key -- always 128 bits! // MD5Init(&Md5Context); MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), sizeof(USER_SESSION_KEY)); MD5Update(&Md5Context, (UCHAR*)(CSSIGNMAGIC), sizeof(CSSIGNMAGIC)); MD5Final(&Md5Context); _fmemcpy(&pNtlmv2Keys->SignSessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY)); // // make server to client signing key // MD5Init(&Md5Context); MD5Update(&Md5Context, (UCHAR*)(pUserSessionKey), sizeof(USER_SESSION_KEY)); MD5Update(&Md5Context, (UCHAR*)(SCSIGNMAGIC), sizeof(SCSIGNMAGIC)); MD5Final(&Md5Context); _fmemcpy(&pNtlmv2Keys->VerifySessionKey, Md5Context.digest, sizeof(USER_SESSION_KEY)); // // set pointers to different key schedule and nonce for each direction // key schedule will be filled in later... // pNtlmv2Keys->pSealRc4Sched = &pNtlmv2Keys->SealRc4Sched; pNtlmv2Keys->pUnsealRc4Sched = &pNtlmv2Keys->UnsealRc4Sched; pNtlmv2Keys->pSendNonce = &pNtlmv2Keys->SendNonce; pNtlmv2Keys->pRecvNonce = &pNtlmv2Keys->RecvNonce; pNtlmv2Keys->SendNonce = SendNonce; pNtlmv2Keys->RecvNonce = RecvNonce; rc4_key(&pNtlmv2Keys->SealRc4Sched, sizeof(USER_SESSION_KEY), (UCHAR*)(&pNtlmv2Keys->SealSessionKey)); rc4_key(&pNtlmv2Keys->UnsealRc4Sched, sizeof(USER_SESSION_KEY), (UCHAR*)(&pNtlmv2Keys->UnsealSessionKey)); } NTSTATUS SspSignSealHelper( IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys, IN ULONG NegotiateFlags, IN eSignSealOp Op, IN ULONG MessageSeqNo, IN OUT SecBufferDesc* pMessage, OUT NTLMSSP_MESSAGE_SIGNATURE* pSig, OUT NTLMSSP_MESSAGE_SIGNATURE** ppSig ) /*++ Routine Description: Helper function for signing/sealing/unsealing/verifying. Arguments: pNtlmv2Keys - key materials NegotiateFlags - negotiate Flags Op - which operation to performance MessageSeqNo - message sequence number pMessage - message buffer descriptor pSig - result signature ppSig - address of the signature token in message buffer descriptor pMessage Return Value: SECURITY_STATUS --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; HMACMD5_CTX HMACMD5Context; UCHAR TempSig[MD5DIGESTLEN]; NTLMSSP_MESSAGE_SIGNATURE Sig; int Signature; ULONG i; PUCHAR pKey; // ptr to key to use for encryption PUCHAR pSignKey; // ptr to key to use for signing PULONG pNonce; // ptr to nonce to use struct RC4_KEYSTRUCT* pRc4Sched; // ptr to key schedule to use NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // aligned copy of input sig data SspPrint((SSP_NTLMV2, "Entering SspSignSealHelper NegotiateFlags %#x, eSignSealOp %d\n", NegotiateFlags, Op)); // // pre-initialize to null in case of failure. // *ppSig = NULL; Signature = -1; for (i = 0; i < pMessage->cBuffers; i++) { if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_TOKEN) { Signature = i; break; } } if (Signature == -1) { NtStatus = STATUS_INVALID_PARAMETER; } if (NT_SUCCESS(NtStatus)) { if (pMessage->pBuffers[Signature].cbBuffer < sizeof(NTLMSSP_MESSAGE_SIGNATURE)) { NtStatus = STATUS_INVALID_PARAMETER; } } if (NT_SUCCESS(NtStatus)) { *ppSig = (NTLMSSP_MESSAGE_SIGNATURE*)(pMessage->pBuffers[Signature].pvBuffer); _fmemcpy(&AlignedSig, *ppSig, sizeof(AlignedSig)); // // If sequence detect wasn't requested, put on an empty security token. // Don't do the check if Seal/Unseal is called // if (!(NegotiateFlags & NTLMSSP_NEGOTIATE_SIGN) && (Op == eSign || Op == eVerify)) { _fmemset(pSig, 0, sizeof(NTLMSSP_MESSAGE_SIGNATURE)); pSig->Version = NTLM_SIGN_VERSION; NtStatus = STATUS_SUCCESS; } } if (NT_SUCCESS(NtStatus)) { switch (Op) { case eSeal: pSignKey = pNtlmv2Keys->SignSessionKey; // if NTLM2 pKey = pNtlmv2Keys->SealSessionKey; pRc4Sched = pNtlmv2Keys->pSealRc4Sched; pNonce = pNtlmv2Keys->pSendNonce; break; case eUnseal: pSignKey = pNtlmv2Keys->VerifySessionKey; // if NTLM2 pKey = pNtlmv2Keys->UnsealSessionKey; pRc4Sched = pNtlmv2Keys->pUnsealRc4Sched; pNonce = pNtlmv2Keys->pRecvNonce; break; case eSign: pSignKey = pNtlmv2Keys->SignSessionKey; // if NTLM2 pKey = pNtlmv2Keys->SealSessionKey; // might be used to encrypt the signature pRc4Sched = pNtlmv2Keys->pSealRc4Sched; pNonce = pNtlmv2Keys->pSendNonce; break; case eVerify: pSignKey = pNtlmv2Keys->VerifySessionKey; // if NTLM2 pKey = pNtlmv2Keys->UnsealSessionKey; // might be used to decrypt the signature pRc4Sched = pNtlmv2Keys->pUnsealRc4Sched; pNonce = pNtlmv2Keys->pRecvNonce; break; default: NtStatus = (STATUS_INVALID_LEVEL); break; } } // // Either we can supply the sequence number, or the application can supply // the message sequence number. // if (NT_SUCCESS(NtStatus)) { Sig.Version = NTLM_SIGN_VERSION; if ((NegotiateFlags & NTLMSSP_APP_SEQ) == 0) { Sig.Nonce = *pNonce; // use our sequence number (*pNonce) += 1; } else { if (Op == eSeal || Op == eSign || MessageSeqNo != 0) { Sig.Nonce = MessageSeqNo; } else { Sig.Nonce = AlignedSig.Nonce; } // // if using RC4, must rekey for each packet RC4 is used for seal, // unseal; and for encrypting the HMAC hash if key exchange was // negotiated (we use just HMAC if no key exchange, so that a good // signing option exists with no RC4 encryption needed) // if (Op == eSeal || Op == eUnseal || NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) { MD5_CTX Md5ContextReKey; C_ASSERT(MD5DIGESTLEN == sizeof(USER_SESSION_KEY)); MD5Init(&Md5ContextReKey); MD5Update(&Md5ContextReKey, pKey, sizeof(USER_SESSION_KEY)); MD5Update(&Md5ContextReKey, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce)); MD5Final(&Md5ContextReKey); rc4_key(pRc4Sched, sizeof(USER_SESSION_KEY), Md5ContextReKey.digest); } } // // using HMAC hash, init it with the key // HMACMD5Init(&HMACMD5Context, pSignKey, sizeof(USER_SESSION_KEY)); // // include the message sequence number // HMACMD5Update(&HMACMD5Context, (unsigned char*)&Sig.Nonce, sizeof(Sig.Nonce)); for (i = 0; i < pMessage->cBuffers; i++) { if (((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_DATA) && (pMessage->pBuffers[i].cbBuffer != 0)) { // // decrypt (before checksum...) if it's not READ_ONLY // if ((Op == eUnseal) && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY)) { rc4( pRc4Sched, pMessage->pBuffers[i].cbBuffer, (UCHAR*)(pMessage->pBuffers[i].pvBuffer) ); } HMACMD5Update( &HMACMD5Context, (UCHAR*)(pMessage->pBuffers[i].pvBuffer), pMessage->pBuffers[i].cbBuffer ); // // Encrypt if its not READ_ONLY // if ((Op == eSeal) && !(pMessage->pBuffers[i].BufferType & SECBUFFER_READONLY)) { rc4( pRc4Sched, pMessage->pBuffers[i].cbBuffer, (UCHAR*)(pMessage->pBuffers[i].pvBuffer) ); } } } HMACMD5Final(&HMACMD5Context, TempSig); // // use RandomPad and Checksum fields for 8 bytes of MD5 hash // _fmemcpy(&Sig.RandomPad, TempSig, 8); // // if we're using crypto for KEY_EXCH, may as well use it for signing too // if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) { rc4( pRc4Sched, 8, (UCHAR*)(&Sig.RandomPad) ); } _fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE)); } SspPrint((SSP_NTLMV2, "Leaving SspSignSealHelper %#x\n", NtStatus)); return NtStatus; } SECURITY_STATUS SspNtlmv2MakeSignature( IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys, IN ULONG NegotiateFlags, IN ULONG fQOP, IN ULONG MessageSeqNo, IN OUT SecBufferDesc* pMessage ) /*++ Routine Description: Make signature of a message Arguments: pNtlmv2Keys - key materials NegotiateFlags - negotiate Flags fQOP - quality of protection MessageSeqNo - message Sequence Number pMessage - message buffer descriptor Return Value: SECURITY_STATUS --*/ { NTSTATUS Status = STATUS_SUCCESS; NTLMSSP_MESSAGE_SIGNATURE Sig; NTLMSSP_MESSAGE_SIGNATURE *pSig; Status = SspSignSealHelper( pNtlmv2Keys, NegotiateFlags, eSign, MessageSeqNo, pMessage, &Sig, &pSig ); if (NT_SUCCESS(Status)) { _fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE)); } return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR); } SECURITY_STATUS SspNtlmv2VerifySignature( IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys, IN ULONG NegotiateFlags, IN ULONG MessageSeqNo, IN OUT SecBufferDesc* pMessage, OUT ULONG* pfQOP ) /*++ Routine Description: Verify signature of a message Arguments: pNtlmv2Keys - key materials NegotiateFlags - negotiate Flags MessageSeqNo - message Sequence Number pMessage - message buffer descriptor pfQOP - quality of protection Return Value: SECURITY_STATUS --*/ { NTSTATUS Status = STATUS_SUCCESS; NTLMSSP_MESSAGE_SIGNATURE Sig; NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer with sig in it NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // Aligned sig buffer. Status = SspSignSealHelper( pNtlmv2Keys, NegotiateFlags, eVerify, MessageSeqNo, pMessage, &Sig, &pSig ); if (NT_SUCCESS(Status)) { _fmemcpy(&AlignedSig, pSig, sizeof(AlignedSig)); if (AlignedSig.Version != NTLM_SIGN_VERSION) { return SEC_E_INVALID_TOKEN; } // // validate the signature... // if (AlignedSig.CheckSum != Sig.CheckSum) { return SEC_E_MESSAGE_ALTERED; } // // with MD5 sig, this now matters! // if (AlignedSig.RandomPad != Sig.RandomPad) { return SEC_E_MESSAGE_ALTERED; } if (AlignedSig.Nonce != Sig.Nonce) { return SEC_E_OUT_OF_SEQUENCE; } } return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR); } SECURITY_STATUS SspNtlmv2SealMessage( IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys, IN ULONG NegotiateFlags, IN ULONG fQOP, IN ULONG MessageSeqNo, IN OUT SecBufferDesc* pMessage ) /*++ Routine Description: Seal a message Arguments: pNtlmv2Keys - key materials NegotiateFlags - negotiate Flags fQOP - quality of protection MessageSeqNo - message Sequence Number pMessage - message buffer descriptor Return Value: SECURITY_STATUS --*/ { NTSTATUS Status = STATUS_SUCCESS; NTLMSSP_MESSAGE_SIGNATURE Sig; NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer where sig goes ULONG i; Status = SspSignSealHelper( pNtlmv2Keys, NegotiateFlags, eSeal, MessageSeqNo, pMessage, &Sig, &pSig ); if (NT_SUCCESS(Status)) { _fmemcpy(pSig, &Sig, sizeof(NTLMSSP_MESSAGE_SIGNATURE)); // // for gss style sign/seal, strip the padding as RC4 requires none. // (in fact, we rely on this to simplify the size computation in // DecryptMessage). if we support some other block cipher, need to rev // the NTLM_ token version to make blocksize // for (i = 0; i < pMessage->cBuffers; i++) { if ((pMessage->pBuffers[i].BufferType & 0xFF) == SECBUFFER_PADDING) { // // no padding required! // pMessage->pBuffers[i].cbBuffer = 0; break; } } } return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR); } SECURITY_STATUS SspNtlmv2UnsealMessage( IN NTLMV2_DERIVED_SKEYS* pNtlmv2Keys, IN ULONG NegotiateFlags, IN ULONG MessageSeqNo, IN OUT SecBufferDesc* pMessage, OUT ULONG* pfQOP ) /*++ Routine Description: Unseal a message Arguments: pNtlmv2Keys - key materials NegotiateFlags - negotiate Flags MessageSeqNo - message Sequence Number pMessage - message buffer descriptor pfQOP - quality of protection Return Value: SECURITY_STATUS --*/ { NTSTATUS Status = STATUS_SUCCESS; NTLMSSP_MESSAGE_SIGNATURE Sig; NTLMSSP_MESSAGE_SIGNATURE* pSig; // pointer to buffer where sig goes NTLMSSP_MESSAGE_SIGNATURE AlignedSig; // aligned buffer. SecBufferDesc* pMessageBuffers = pMessage; ULONG Index; SecBuffer* pSignatureBuffer = NULL; SecBuffer* pStreamBuffer = NULL; SecBuffer* pDataBuffer = NULL; SecBufferDesc ProcessBuffers; SecBuffer wrap_bufs[2]; // // Find the body and signature SecBuffers from pMessage // for (Index = 0; Index < pMessageBuffers->cBuffers; Index++) { if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_TOKEN) { pSignatureBuffer = &pMessageBuffers->pBuffers[Index]; } else if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_STREAM) { pStreamBuffer = &pMessageBuffers->pBuffers[Index]; } else if ((pMessageBuffers->pBuffers[Index].BufferType & ~SECBUFFER_ATTRMASK) == SECBUFFER_DATA) { pDataBuffer = &pMessageBuffers->pBuffers[Index]; } } if (pStreamBuffer != NULL) { if (pSignatureBuffer != NULL) { return SEC_E_INVALID_TOKEN; } // // for version 1 NTLM blobs, padding is never present, since RC4 is // stream cipher // wrap_bufs[0].cbBuffer = sizeof(NTLMSSP_MESSAGE_SIGNATURE); wrap_bufs[1].cbBuffer = pStreamBuffer->cbBuffer - sizeof(NTLMSSP_MESSAGE_SIGNATURE); if (pStreamBuffer->cbBuffer < wrap_bufs[0].cbBuffer) { return SEC_E_INVALID_TOKEN; } wrap_bufs[0].BufferType = SECBUFFER_TOKEN; wrap_bufs[0].pvBuffer = pStreamBuffer->pvBuffer; wrap_bufs[1].BufferType = SECBUFFER_DATA; wrap_bufs[1].pvBuffer = (PBYTE)wrap_bufs[0].pvBuffer + wrap_bufs[0].cbBuffer; if (pDataBuffer == NULL) { return SEC_E_INVALID_TOKEN; } pDataBuffer->cbBuffer = wrap_bufs[1].cbBuffer; pDataBuffer->pvBuffer = wrap_bufs[1].pvBuffer; ProcessBuffers.cBuffers = 2; ProcessBuffers.pBuffers = wrap_bufs; ProcessBuffers.ulVersion = SECBUFFER_VERSION; } else { ProcessBuffers = *pMessageBuffers; } Status = SspSignSealHelper( pNtlmv2Keys, NegotiateFlags, eUnseal, MessageSeqNo, &ProcessBuffers, &Sig, &pSig ); if (NT_SUCCESS(Status)) { _fmemcpy(&AlignedSig, pSig, sizeof(AlignedSig)); if (AlignedSig.Version != NTLM_SIGN_VERSION) { return SEC_E_INVALID_TOKEN; } // // validate the signature... // if (AlignedSig.CheckSum != Sig.CheckSum) { return SEC_E_MESSAGE_ALTERED; } if (AlignedSig.RandomPad != Sig.RandomPad) { return SEC_E_MESSAGE_ALTERED; } if (AlignedSig.Nonce != Sig.Nonce) { return SEC_E_OUT_OF_SEQUENCE; } } return SspNtStatusToSecStatus(Status, SEC_E_INTERNAL_ERROR); }