windows-nt/Source/XPSP1/NT/ds/netapi/netlib/joincrypt.c

592 lines
14 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1987-1996 Microsoft Corporation
Module Name:
joincrypt.c
Abstract:
Authentication related functions required by netjoin
Author:
kumarp 29-May-1999
Notes:
The functions in this file used to be made avaialbe by
including net\svcdlls\logonsrv\server\ssiauth.c. This led to several
problems due to which, the functions are now copied from that file
into this separate file.
--*/
#pragma hdrstop
#define WKSTA_NETLOGON
#define NETSETUP_JOIN
#include <netsetp.h>
#include <crypt.h>
#include <ntsam.h>
#include <logonmsv.h>
#include <lmshare.h>
#include <wincrypt.h>
#include <netlogon.h>
#include <logonp.h>
#include <logonmsv.h>
#include <ssi.h>
#include <wchar.h>
#include "joinp.h"
LONG NlGlobalSessionCounter = 0;
HCRYPTPROV NlGlobalCryptProvider = (HCRYPTPROV)NULL;
#define NlPrint(x)
#define NlpDumpBuffer(x,y,z)
BOOLEAN
NlGenerateRandomBits(
PUCHAR Buffer,
ULONG BufferLen
);
#define NlQuerySystemTime( _Time ) GetSystemTimeAsFileTime( (LPFILETIME)(_Time) )
VOID
NlComputeChallenge(
OUT PNETLOGON_CREDENTIAL Challenge
);
VOID
NlComputeCredentials(
IN PNETLOGON_CREDENTIAL Challenge,
OUT PNETLOGON_CREDENTIAL Credential,
IN PNETLOGON_SESSION_KEY SessionKey
);
NTSTATUS
NlMakeSessionKey(
IN ULONG NegotiatedFlags,
IN PNT_OWF_PASSWORD CryptKey,
IN PNETLOGON_CREDENTIAL ClientChallenge,
IN PNETLOGON_CREDENTIAL ServerChallenge,
OUT PNETLOGON_SESSION_KEY SessionKey
)
/*++
Routine Description:
Build an encryption key for use in authentication for
this RequestorName.
Arguments:
NegotiatedFlags - Determines the strength of the key.
CryptKey -- The OWF password of the user account being used.
ClientChallenge -- 8 byte (64 bit) number generated by caller
ServerChallenge -- 8 byte (64 bit) number generated by primary
SessionKey -- 16 byte (128 bit) number generated at both ends
If the key strength is weak, the last 64 bits will be zero.
Return Value:
TRUE: Success
FALSE: Failure
NT status code.
--*/
{
NTSTATUS Status;
BLOCK_KEY BlockKey;
NETLOGON_SESSION_KEY TempSessionKey;
#ifndef NETSETUP_JOIN
PCHECKSUM_BUFFER CheckBuffer = NULL;
PCHECKSUM_FUNCTION Check;
#endif // NETSETUP_JOIN
//
// Start with a zero key
//
RtlZeroMemory(SessionKey, sizeof(NETLOGON_SESSION_KEY));
#ifdef NETSETUP_JOIN
UNREFERENCED_PARAMETER( NegotiatedFlags );
#else // NETSETUP_JOIN
//
// If the caller wants a strong key,
// Compute it.
//
if ( NegotiatedFlags & NETLOGON_SUPPORTS_STRONG_KEY ) {
// PCRYPTO_SYSTEM CryptSystem;
UCHAR LocalChecksum[sizeof(*SessionKey)];
// ULONG OutputSize;
//
// Initialize the checksum routines.
//
Status = CDLocateCheckSum( KERB_CHECKSUM_MD5_HMAC, &Check);
if (!NT_SUCCESS(Status)) {
NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to load checksum routines: 0x%x\n", Status));
goto Cleanup;
}
ASSERT(Check->CheckSumSize <= sizeof(LocalChecksum));
Status = Check->InitializeEx(
(LPBYTE)CryptKey,
sizeof( *CryptKey ),
0, // no message type
&CheckBuffer );
if (!NT_SUCCESS(Status)) {
NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to initialize checksum routines: 0x%x\n", Status));
goto Cleanup;
}
//
// Sum in the client challenge, a constant, and the server challenge
//
Check->Sum( CheckBuffer,
sizeof(*ClientChallenge),
(PUCHAR)ClientChallenge );
Check->Sum( CheckBuffer,
sizeof(*ServerChallenge),
(PUCHAR)ServerChallenge );
//
// Finish the checksum
//
(void) Check->Finalize(CheckBuffer, LocalChecksum);
//
// Copy the checksum into the message.
//
ASSERT( sizeof(LocalChecksum) >= sizeof(*SessionKey) );
RtlCopyMemory( SessionKey, LocalChecksum, sizeof(*SessionKey) );
//
// Compute weaker (but backward compatible key)
//
} else {
#endif // NETSETUP_JOIN
//
// we will have a 128 bit key (64 bit encrypted rest padded with 0s)
//
// SessionKey = C + P (arithmetic sum ignore carry)
//
*((unsigned long * ) SessionKey) =
*((unsigned long * ) ClientChallenge) +
*((unsigned long * ) ServerChallenge);
*((unsigned long * )((LPBYTE)SessionKey + 4)) =
*((unsigned long * )((LPBYTE)ClientChallenge + 4)) +
*((unsigned long * )((LPBYTE)ServerChallenge + 4));
//
// CryptKey is our 16 byte key to be used as described in codespec
// use first 7 bytes of CryptKey for first encryption
//
RtlCopyMemory( &BlockKey, CryptKey, BLOCK_KEY_LENGTH );
Status = RtlEncryptBlock(
(PCLEAR_BLOCK) SessionKey, // Clear text
&BlockKey, // Key
(PCYPHER_BLOCK) &TempSessionKey); // Cypher Block
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
//
// Further encrypt the encrypted "SessionKey" using upper 7 bytes
//
ASSERT( LM_OWF_PASSWORD_LENGTH == 2*BLOCK_KEY_LENGTH+2 );
RtlCopyMemory( &BlockKey,
((PUCHAR)CryptKey) + 2 + BLOCK_KEY_LENGTH,
BLOCK_KEY_LENGTH );
Status = RtlEncryptBlock(
(PCLEAR_BLOCK) &TempSessionKey, // Clear text
&BlockKey, // Key
(PCYPHER_BLOCK) SessionKey); // Cypher Block
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
#ifndef NETSETUP_JOIN
}
#endif // NETSETUP_JOIN
Cleanup:
#ifndef NETSETUP_JOIN
if (CheckBuffer != NULL) {
Status = Check->Finish(&CheckBuffer);
if (!NT_SUCCESS(Status)) {
NlPrint(( NL_CRITICAL,"NlMakeSessionKey: Failed to finish checksum: 0x%x\n", Status));
}
}
#endif // NETSETUP_JOIN
return Status;
}
VOID
NlComputeChallenge(
OUT PNETLOGON_CREDENTIAL Challenge
)
/*++
Routine Description:
Generates a 64 bit challenge
Arguments:
Challenge - Returns the computed challenge
Return Value:
None.
--*/
{
//
// Use an ideal random bit generator.
//
if (!NlGenerateRandomBits( (LPBYTE)Challenge, sizeof(*Challenge) )) {
NlPrint((NL_CRITICAL, "Can't NlGenerateRandomBits\n" ));
}
return;
}
VOID
NlComputeCredentials(
IN PNETLOGON_CREDENTIAL Challenge,
OUT PNETLOGON_CREDENTIAL Credential,
IN PNETLOGON_SESSION_KEY SessionKey
)
/*++
Routine Description:
Calculate the credentials by encrypting the 8 byte
challenge with first 7 bytes of sessionkey and then
further encrypting it by next 7 bytes of sessionkey.
Arguments:
Challenge - Supplies the 8 byte (64 bit) challenge
Credential - Returns the 8 byte (64 bit) number generated
SessionKey - Supplies 14 byte (112 bit) encryption key
The buffer is 16 bytes (128 bits) long. For a weak key, the trailing 8 bytes
are zero. For a strong key, this routine ingored that trailing 2 bytes of
useful key.
Return Value:
NONE
--*/
{
NTSTATUS Status;
BLOCK_KEY BlockKey;
CYPHER_BLOCK IntermediateBlock;
RtlZeroMemory(Credential, sizeof(*Credential));
//
// use first 7 bytes of SessionKey for first encryption
//
RtlCopyMemory( &BlockKey, SessionKey, BLOCK_KEY_LENGTH );
Status = RtlEncryptBlock( (PCLEAR_BLOCK) Challenge, // Cleartext
&BlockKey, // Key
&IntermediateBlock ); // Cypher Block
ASSERT( NT_SUCCESS(Status) );
//
// further encrypt the encrypted Credential using next 7 bytes
//
RtlCopyMemory( &BlockKey,
((PUCHAR)SessionKey) + BLOCK_KEY_LENGTH,
BLOCK_KEY_LENGTH );
Status = RtlEncryptBlock( (PCLEAR_BLOCK) &IntermediateBlock, // Cleartext
&BlockKey, // Key
Credential ); // Cypher Block
ASSERT( NT_SUCCESS(Status) );
return;
}
BOOLEAN
NlGenerateRandomBits(
PUCHAR Buffer,
ULONG BufferLen
)
/*++
Routine Description:
Generates random bits
Arguments:
pBuffer - Buffer to fill
cbBuffer - Number of bytes in buffer
Return Value:
Status of the operation.
--*/
{
if( !CryptGenRandom( NlGlobalCryptProvider, BufferLen, ( LPBYTE )Buffer ) )
{
NlPrint((NL_CRITICAL, "CryptGenRandom failed with %lu\n", GetLastError() ));
return FALSE;
}
return TRUE;
}
NET_API_STATUS
NET_API_FUNCTION
NetpValidateMachineAccount(
IN LPWSTR lpDc,
IN LPWSTR lpDomain,
IN LPWSTR lpMachine,
IN LPWSTR lpPassword
)
/*++
Routine Description:
Performs validation that the machine account exists and has the same password we expect
The internals of this function were lifted completely from SimulateFullSync() in
..\svcdlls\logonsrv\server\nltest.c,
Arguments:
lpDc -- Name of the Dc
lpDomain -- Name of the domain
lpMachine -- Current machine
lpPassword -- Password that should be on the account.
Returns:
NERR_Success -- Success
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
NETLOGON_CREDENTIAL ServerChallenge;
NETLOGON_CREDENTIAL ClientChallenge;
NETLOGON_CREDENTIAL ComputedServerCredential;
NETLOGON_CREDENTIAL ReturnedServerCredential;
NETLOGON_CREDENTIAL AuthenticationSeed;
NETLOGON_SESSION_KEY SessionKey;
WCHAR AccountName[SSI_ACCOUNT_NAME_LENGTH+1];
UNICODE_STRING Password;
NT_OWF_PASSWORD NtOwfPassword;
UNREFERENCED_PARAMETER( lpDomain );
ASSERT( lpPassword );
//
// initialize Crypto Provider.
// (required for NlComputeChallenge).
//
if ( !CryptAcquireContext(
&NlGlobalCryptProvider,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT
))
{
NlGlobalCryptProvider = (HCRYPTPROV)NULL;
return (NET_API_STATUS)GetLastError();
}
//
// Prepare our challenge
//
NlComputeChallenge( &ClientChallenge );
//
// free cryptographic service provider.
//
if ( NlGlobalCryptProvider ) {
CryptReleaseContext( NlGlobalCryptProvider, 0 );
NlGlobalCryptProvider = (HCRYPTPROV)NULL;
}
//
// Get the primary's challenge
//
Status = I_NetServerReqChallenge(lpDc,
lpMachine,
&ClientChallenge,
&ServerChallenge );
if ( !NT_SUCCESS( Status ) ) {
goto ValidateMachineAccountError;
}
Password.Length = Password.MaximumLength = wcslen(lpPassword) * sizeof(WCHAR);
Password.Buffer = lpPassword;
//
// Compute the NT OWF password for this user.
//
Status = RtlCalculateNtOwfPassword( &Password, &NtOwfPassword );
if ( !NT_SUCCESS( Status ) ) {
goto ValidateMachineAccountError;
}
//
// Actually compute the session key given the two challenges and the
// password.
//
NlMakeSessionKey(
#if(_WIN32_WINNT >= 0x0500)
0,
#endif
&NtOwfPassword,
&ClientChallenge,
&ServerChallenge,
&SessionKey );
//
// Prepare credentials using our challenge.
//
NlComputeCredentials( &ClientChallenge,
&AuthenticationSeed,
&SessionKey );
//
// Send these credentials to primary. The primary will compute
// credentials using the challenge supplied by us and compare
// with these. If both match then it will compute credentials
// using its challenge and return it to us for verification
//
wcscpy( AccountName, lpMachine );
wcscat( AccountName, SSI_ACCOUNT_NAME_POSTFIX);
Status = I_NetServerAuthenticate( lpDc,
AccountName,
WorkstationSecureChannel,
lpMachine,
&AuthenticationSeed,
&ReturnedServerCredential );
if ( !NT_SUCCESS( Status ) ) {
goto ValidateMachineAccountError;
}
//
// The DC returned a server credential to us,
// ensure the server credential matches the one we would compute.
//
NlComputeCredentials( &ServerChallenge,
&ComputedServerCredential,
&SessionKey);
if (RtlCompareMemory( &ReturnedServerCredential,
&ComputedServerCredential,
sizeof(ReturnedServerCredential)) !=
sizeof(ReturnedServerCredential)) {
Status = STATUS_ACCESS_DENIED;
}
ValidateMachineAccountError:
if ( Status == STATUS_ACCESS_DENIED ) {
Status = STATUS_LOGON_FAILURE;
}
if ( !NT_SUCCESS( Status ) ) {
NetpLog(( "Failed to validate machine account for %ws against %ws: 0x%lx\n",
lpMachine, lpDc, Status ));
}
return( RtlNtStatusToDosError( Status ) );
}