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.
595 lines
14 KiB
595 lines
14 KiB
/*++
|
|
|
|
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"
|
|
|
|
HCRYPTPROV NlGlobalCryptProvider = (HCRYPTPROV)NULL;
|
|
|
|
|
|
#define NlPrint(x)
|
|
|
|
BOOLEAN
|
|
NlGenerateRandomBits(
|
|
PUCHAR Buffer,
|
|
ULONG BufferLen
|
|
);
|
|
|
|
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 (Netbios name)
|
|
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 );
|
|
|
|
//
|
|
// Validate the machine name length to avoid buffer overrun
|
|
//
|
|
|
|
if ( lpMachine == NULL || wcslen(lpMachine) > CNLEN ) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// 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 ) );
|
|
}
|
|
|