/*++ Copyright (c) 1989-1997 Microsoft Corporation Module Name: nlmain.c Abstract: This file contains the initialization and dispatch routines for the LAN Manager portions of the MSV1_0 authentication package. Author: Jim Kelly 11-Apr-1991 Revision History: 25-Apr-1991 (cliffv) Added interactive logon support for PDK. Chandana Surlu 21-Jul-1996 Stolen from \\kernel\razzle3\src\security\msv1_0\nlmain.c Adam Barr 15-Dec-1997 Modified from private\security\msv_sspi\nlmain.c --*/ #include #include #include #define NLP_ALLOCATE #include "nlp.h" #undef NLP_ALLOCATE #include #include #include VOID NlpPutString( IN PUNICODE_STRING OutString, IN PUNICODE_STRING InString, IN PUCHAR *Where ) /*++ Routine Description: This routine copies the InString string to the memory pointed to by the Where parameter, and fixes the OutString string to point to that new copy. Parameters: OutString - A pointer to a destination NT string InString - A pointer to an NT string to be copied Where - A pointer to space to put the actual string for the OutString. The pointer is adjusted to point to the first byte following the copied string. Return Values: None. --*/ { ASSERT( OutString != NULL ); ASSERT( InString != NULL ); ASSERT( Where != NULL && *Where != NULL); ASSERT( *Where == ROUND_UP_POINTER( *Where, sizeof(WCHAR) ) ); #ifdef notdef KdPrint(("NlpPutString: %ld %Z\n", InString->Length, InString )); KdPrint((" InString: %lx %lx OutString: %lx Where: %lx\n", InString, InString->Buffer, OutString, *Where )); #endif if ( InString->Length > 0 ) { OutString->Buffer = (PWCH) *Where; OutString->MaximumLength = (USHORT)(InString->Length + sizeof(WCHAR)); RtlCopyUnicodeString( OutString, InString ); *Where += InString->Length; // *((WCHAR *)(*Where)) = L'\0'; *(*Where) = '\0'; *(*Where + 1) = '\0'; *Where += 2; } else { RtlInitUnicodeString(OutString, NULL); } #ifdef notdef KdPrint((" OutString: %ld %lx\n", OutString->Length, OutString->Buffer)); #endif return; } NTSTATUS NlpMakePrimaryCredential( IN PUNICODE_STRING LogonDomainName, IN PUNICODE_STRING UserName, IN PUNICODE_STRING CleartextPassword, OUT PMSV1_0_PRIMARY_CREDENTIAL *CredentialBuffer, OUT PULONG CredentialSize, IN BOOLEAN OwfPasswordProvided ) /*++ Routine Description: This routine makes a primary credential for the given user nam and password. Arguments: LogonDomainName - Is a string representing the domain in which the user's account is defined. UserName - Is a string representing the user's account name. The name may be up to 255 characters long. The name is treated case insensitive. CleartextPassword - Is a string containing the user's cleartext password. The password may be up to 255 characters long and contain any UNICODE value. CredentialBuffer - Returns a pointer to the specified credential allocated on the LsaHeap. It is the callers responsibility to deallocate this credential. CredentialSize - the size of the allocated credential buffer (in bytes). OwfPasswordProvided - If TRUE, then we assume the password is provided as the LM and NT OWF, passwords concatenated together. Return Value: STATUS_SUCCESS - Indicates the service completed successfully. STATUS_QUOTA_EXCEEDED - This error indicates that the logon could not be completed because the client does not have sufficient quota to allocate the return buffer. --*/ { PMSV1_0_PRIMARY_CREDENTIAL Credential; NTSTATUS Status; PUCHAR Where; CHAR LmPassword[LM20_PWLEN+1]; BOOLEAN LmPasswordPresent; STRING AnsiCleartextPassword; if (!OwfPasswordProvided) { // // Compute the Ansi version to the Cleartext password. // // The Ansi version of the Cleartext password is at most 14 bytes long, // exists in a trailing zero filled 15 byte buffer, // is uppercased. // AnsiCleartextPassword.Buffer = LmPassword; AnsiCleartextPassword.MaximumLength = sizeof(LmPassword); RtlZeroMemory( &LmPassword, sizeof(LmPassword) ); Status = RtlUpcaseUnicodeStringToOemString( &AnsiCleartextPassword, CleartextPassword, (BOOLEAN) FALSE ); if ( !NT_SUCCESS(Status) ) { RtlSecureZeroMemory( &LmPassword, sizeof(LmPassword) ); AnsiCleartextPassword.Length = 0; LmPasswordPresent = FALSE; } else { LmPasswordPresent = TRUE; } } // // Build the credential // *CredentialSize = sizeof(MSV1_0_PRIMARY_CREDENTIAL) + LogonDomainName->Length + sizeof(WCHAR) + UserName->Length + sizeof(WCHAR); Credential = ExAllocatePool ( NonPagedPool, *CredentialSize ); if ( Credential == NULL ) { KdPrint(("MSV1_0: NlpMakePrimaryCredential: No memory %ld\n", *CredentialSize )); return STATUS_QUOTA_EXCEEDED; } // // Put the LogonDomainName into the Credential Buffer. // Where = (PUCHAR)(Credential + 1); NlpPutString( &Credential->LogonDomainName, LogonDomainName, &Where ); // // Put the UserName into the Credential Buffer. // NlpPutString( &Credential->UserName, UserName, &Where ); if (OwfPasswordProvided) { RtlCopyMemory(&Credential->LmOwfPassword, CleartextPassword->Buffer, LM_OWF_PASSWORD_SIZE); Credential->LmPasswordPresent = TRUE; RtlCopyMemory(&Credential->NtOwfPassword, ((PUCHAR)CleartextPassword->Buffer) + LM_OWF_PASSWORD_SIZE, NT_OWF_PASSWORD_SIZE); Credential->NtPasswordPresent = TRUE; } else { // // Save the OWF encrypted versions of the passwords. // Status = RtlCalculateLmOwfPassword( LmPassword, &Credential->LmOwfPassword ); ASSERT( NT_SUCCESS(Status) ); Credential->LmPasswordPresent = LmPasswordPresent; Status = RtlCalculateNtOwfPassword( CleartextPassword, &Credential->NtOwfPassword ); ASSERT( NT_SUCCESS(Status) ); Credential->NtPasswordPresent = ( CleartextPassword->Length != 0 ); // // Don't leave passwords around in the pagefile // RtlSecureZeroMemory( &LmPassword, sizeof(LmPassword) ); } // // Return the credential to the caller. // *CredentialBuffer = Credential; return STATUS_SUCCESS; } 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]); } } #define MSV1_0_NTLMV2_OWF_LENGTH MSV1_0_NTLM3_RESPONSE_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}; // // first make a copy then upcase it // UserName.Length = min(UserName.MaximumLength, pUserName->Length); ASSERT(UserName.Length == pUserName->Length); memcpy(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 ); } #define MSV1_0_NTLMV2_RESPONSE_LENGTH MSV1_0_NTLM3_RESPONSE_LENGTH 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], OUT USER_SESSION_KEY* pUserSessionKey, OUT UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_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); // // 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 ); // now compute the session keys // HMAC(Kr, R) HMACMD5Init( &HMACMD5Context, Ntlmv2Owf, MSV1_0_NTLM3_OWF_LENGTH ); HMACMD5Update( &HMACMD5Context, Response, MSV1_0_NTLM3_RESPONSE_LENGTH ); ASSERT(MD5DIGESTLEN == MSV1_0_USER_SESSION_KEY_LENGTH); HMACMD5Final( &HMACMD5Context, (PUCHAR)pUserSessionKey ); ASSERT(MSV1_0_LANMAN_SESSION_KEY_LENGTH <= MSV1_0_USER_SESSION_KEY_LENGTH); RtlCopyMemory( LanmanSessionKey, pUserSessionKey, MSV1_0_LANMAN_SESSION_KEY_LENGTH ); } typedef struct { UCHAR Response[MSV1_0_NTLM3_RESPONSE_LENGTH]; UCHAR ChallengeFromClient[MSV1_0_CHALLENGE_LENGTH]; } MSV1_0_LM3_RESPONSE, *PMSV1_0_LM3_RESPONSE; #define NULL_SESSION_REQUESTED 0x10 NTSTATUS MspLm20GetChallengeResponse ( IN PVOID ProtocolSubmitBuffer, IN ULONG SubmitBufferSize, OUT PVOID *ProtocolReturnBuffer, OUT PULONG ReturnBufferSize, IN BOOLEAN OwfPasswordProvided ) /*++ Routine Description: This routine is the dispatch routine for LsaCallAuthenticationPackage() with a message type of MsV1_0Lm20GetChallengeResponse. It is called by the LanMan redirector to determine the Challenge Response to pass to a server when trying to establish a connection to the server. This routine is passed a Challenge from the server. This routine encrypts the challenge with either the specified password or with the password implied by the specified Logon Id. Two Challenge responses are returned. One is based on the Unicode password as given to the Authentication package. The other is based on that password converted to a multi-byte character set (e.g., ASCII) and upper cased. The redirector should use whichever (or both) challenge responses as it needs them. Arguments: The first four arguments to this routine are identical to those of LsaApCallPackage. Only the special attributes of these parameters as they apply to this routine are mentioned here. OwfPasswordProvided use is used to distinquish if the password is Owf or not. Return Value: STATUS_SUCCESS - Indicates the service completed successfully. STATUS_QUOTA_EXCEEDED - This error indicates that the logon could not be completed because the client does not have sufficient quota to allocate the return buffer. --*/ { NTSTATUS Status = STATUS_SUCCESS; PMSV1_0_GETCHALLENRESP_REQUEST GetRespRequest; PMSV1_0_GETCHALLENRESP_RESPONSE GetRespResponse; PMSV1_0_PRIMARY_CREDENTIAL Credential = NULL; PMSV1_0_PRIMARY_CREDENTIAL BuiltCredential = NULL; PVOID ClientBuffer = NULL; PUCHAR ClientStrings; // // Responses to return to the caller. // MSV1_0_LM3_RESPONSE LmResp = {0}; STRING LmResponseString; NT_RESPONSE NtResponse = {0}; STRING NtResponseString; UNICODE_STRING NullUnicodeString = {0}; USER_SESSION_KEY UserSessionKey; UCHAR LanmanSessionKey[MSV1_0_LANMAN_SESSION_KEY_LENGTH]; ULONG CredentialSize = 0; RtlZeroMemory( &UserSessionKey, sizeof(UserSessionKey) ); RtlZeroMemory( LanmanSessionKey, sizeof(LanmanSessionKey) ); // // If no credentials are associated with the client, a null session // will be used. For a downlevel server, the null session response is // a 1-byte null string (\0). Initialize LmResponseString to the // null session response. // RtlInitString( &LmResponseString, "" ); LmResponseString.Length = 1; // // Initialize the NT response to the NT null session credentials, // which are zero length. // RtlInitString( &NtResponseString, NULL ); // // Ensure the specified Submit Buffer is of reasonable size and // relocate all of the pointers to be relative to the LSA allocated // buffer. // if ( SubmitBufferSize < sizeof(MSV1_0_GETCHALLENRESP_REQUEST) ) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } GetRespRequest = (PMSV1_0_GETCHALLENRESP_REQUEST) ProtocolSubmitBuffer; ASSERT( GetRespRequest->MessageType == MsV1_0Lm20GetChallengeResponse ); // // If the caller wants information from the credentials of a specified // LogonId, get those credentials from the LSA. // // If there are no such credentials, // tell the caller to use the NULL session. // #define PRIMARY_CREDENTIAL_NEEDED \ (RETURN_PRIMARY_USERNAME | \ USE_PRIMARY_PASSWORD ) if ( ((GetRespRequest->ParameterControl & PRIMARY_CREDENTIAL_NEEDED) != 0 ) && ((GetRespRequest->ParameterControl & NULL_SESSION_REQUESTED) == 0)) { ASSERT(FALSE); } // // If the caller passed in a password to use, // use it to build a credential. // // The password is assumed to be the LM and NT OWF // passwords concatenated together. // if ( (GetRespRequest->ParameterControl & USE_PRIMARY_PASSWORD) == 0 ) { Status = NlpMakePrimaryCredential( &GetRespRequest->LogonDomainName, &GetRespRequest->UserName, &GetRespRequest->Password, &BuiltCredential, &CredentialSize, OwfPasswordProvided ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } // // Use the newly allocated credential to get the password information // from. // Credential = BuiltCredential; } // // Build the appropriate response. // if ( Credential != NULL ) { ASSERT(Credential->UserName.Length); SspGetLmv2Response( &Credential->NtOwfPassword, &Credential->UserName, &Credential->LogonDomainName, GetRespRequest->ChallengeToClient, LmResp.ChallengeFromClient, LmResp.Response, &UserSessionKey, LanmanSessionKey ); LmResponseString.Buffer = (UCHAR*) &LmResp; LmResponseString.Length = LmResponseString.MaximumLength = sizeof(LmResp); NtResponseString.Buffer = (CHAR*) L""; NtResponseString.Length = 0; NtResponseString.MaximumLength = sizeof(WCHAR); // // Compute the session keys // if ( GetRespRequest->ParameterControl & RETURN_NON_NT_USER_SESSION_KEY) { // // If the redir didn't negotiate an NT protocol with the server, // use the lanman session key. // if ( Credential->LmPasswordPresent ) { ASSERT( sizeof(UserSessionKey) >= sizeof(LanmanSessionKey) ); RtlCopyMemory( &UserSessionKey, &Credential->LmOwfPassword, sizeof(LanmanSessionKey) ); } if ( Credential->LmPasswordPresent ) { RtlCopyMemory( LanmanSessionKey, &Credential->LmOwfPassword, sizeof(LanmanSessionKey) ); } } else { if ( !Credential->NtPasswordPresent ) { RtlCopyMemory( &Credential->NtOwfPassword, &NlpNullNtOwfPassword, sizeof(Credential->NtOwfPassword) ); } } } // // Allocate a buffer to return to the caller. // *ReturnBufferSize = sizeof(MSV1_0_GETCHALLENRESP_RESPONSE) + Credential->LogonDomainName.Length + sizeof(WCHAR) + Credential->UserName.Length + sizeof(WCHAR) + NtResponseString.Length + sizeof(WCHAR) + LmResponseString.Length + sizeof(WCHAR); ClientBuffer = ExAllocatePool(NonPagedPool, *ReturnBufferSize); if (ClientBuffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } GetRespResponse = (PMSV1_0_GETCHALLENRESP_RESPONSE) ClientBuffer; // // Fill in the return buffer. // GetRespResponse->MessageType = MsV1_0Lm20GetChallengeResponse; RtlCopyMemory( GetRespResponse->UserSessionKey, &UserSessionKey, sizeof(UserSessionKey)); RtlCopyMemory( GetRespResponse->LanmanSessionKey, LanmanSessionKey, sizeof(LanmanSessionKey) ); ClientStrings = ((PUCHAR)ClientBuffer) + sizeof(MSV1_0_GETCHALLENRESP_RESPONSE); // // Copy the logon domain name (the string may be empty) // NlpPutString( &GetRespResponse->LogonDomainName, &Credential->LogonDomainName, &ClientStrings ); // // Copy the user name (the string may be empty) // NlpPutString( &GetRespResponse->UserName, &Credential->UserName, &ClientStrings ); // // Copy the Challenge Responses to the client buffer. // NlpPutString( (PUNICODE_STRING) &GetRespResponse->CaseSensitiveChallengeResponse, (PUNICODE_STRING) &NtResponseString, &ClientStrings ); NlpPutString( (PUNICODE_STRING) &GetRespResponse->CaseInsensitiveChallengeResponse, (PUNICODE_STRING)&LmResponseString, &ClientStrings ); *ProtocolReturnBuffer = ClientBuffer; Cleanup: // // If we weren't successful, free the buffer in the clients address space. // if ( !NT_SUCCESS(Status) && ( ClientBuffer != NULL ) ) { ExFreePool(ClientBuffer); } // // Cleanup locally used resources // if ( BuiltCredential != NULL ) { RtlZeroMemory(BuiltCredential, CredentialSize); ExFreePool(BuiltCredential); } // // Return status to the caller. // return Status; }