/*++ Copyright (c) 1987-1991 Microsoft Corporation Module Name: ssiauth.c Abstract: Authentication related functions Author: Ported from Lan Man 2.0 Environment: User mode only. Contains NT-specific code. Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 12-Jul-1991 (cliffv) Ported to NT. Converted to NT style. --*/ // // Common include files. // #include // Include files common to entire service // // Include files specific to this .c file // #include // NERR_* LONG NlGlobalSessionCounter = 0; VOID NlMakeSessionKey( 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: 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 -- 8 byte (64 bit) number generated at both ends Return Value: TRUE: Success FALSE: Failure NT status code. --*/ { NTSTATUS Status; BLOCK_KEY BlockKey; NETLOGON_SESSION_KEY TempSessionKey; // // we will have a 112 bit key (64 bit encrypted rest padded with 0s) // RtlZeroMemory(SessionKey, sizeof(NETLOGON_SESSION_KEY)); // // 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 NlAssert( NT_SUCCESS( Status ) ); // // Further encrypt the encrypted "SessionKey" using upper 7 bytes // NlAssert( 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 NlAssert( NT_SUCCESS( Status ) ); return; } NTSTATUS NlCheckAuthenticator( IN OUT PSERVER_SESSION ServerSession, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator ) /*++ Routine Description: Verify that supplied Authenticator is valid. It is intended for use by the server side after initial authentication has succeeded. This routine will modify the seed by first adding the time-of-day received from the Authenticator and then by incrementing it. A ReturnAuthenticator is built based on the final seed. Arguments: ServerSession - Pointer to the ServerSession structure. The following fields are used: SsAuthenticationSeed - Supplies the seed used for authentication and returns the updated seed. SsSessionKey - The session key used for encryption. SsCheck - Is zeroed to indicate successful communication with the client. Authenticator - The authenticator passed by the caller. ReturnAuthenticator - The authenticator we'll return to the caller. Return Value: STATUS_SUCCESS; STATUS_ACCESS_DENIED; STATUS_TIME_DIFFERENCE_AT_DC; --*/ { NETLOGON_CREDENTIAL TargetCredential; #ifdef notdef // Doesn't work if caller in different time zone LARGE_INTEGER TimeNow; long timeofday; long timediff; // // First check if time-of-day is rational. // NtQuerySystemTime( &TimeNow ); RtlTimeToSecondsSince1970( &TimeNow, &timeofday ); timediff = timeofday - Authenticator->timestamp; if (timediff < 0) { timediff = Authenticator->timestamp - timeofday; } if (timediff > RATIONAL_TIME) { return STATUS_TIME_DIFFERENCE_AT_DC; } #endif // notdef #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlCheckAuthenticator: Seed = %lx %lx\n", ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0], ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1])); NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: SessionKey = %lx %lx %lx %lx\n", ((DWORD *) (&ServerSession->SsSessionKey))[0], ((DWORD *) (&ServerSession->SsSessionKey))[1], ((DWORD *) (&ServerSession->SsSessionKey))[2], ((DWORD *) (&ServerSession->SsSessionKey))[3])); NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Client Authenticator GOT = %lx %lx\n", ((DWORD *) (&Authenticator->Credential))[0], ((DWORD *) (&Authenticator->Credential))[1])); NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Time = %lx\n", ((DWORD *) (&Authenticator->timestamp))[0] )); #endif // BAD_ALIGNMENT // // modify the seed before computing auth_credential for verification // Two long words are added and overflow carry (if any) ignored // This will leave upper 4 bytes unchanged // *((unsigned long * ) &ServerSession->SsAuthenticationSeed) += Authenticator->timestamp; #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Seed + TIME = %lx %lx\n", ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0], ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1])); #endif // BAD_ALIGNMENT // // Compute TargetCredential to verify the one supplied in the Authenticator // NlComputeCredentials( &ServerSession->SsAuthenticationSeed, &TargetCredential, &ServerSession->SsSessionKey ); #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Client Authenticator MADE = %lx %lx\n", ((DWORD *) (&TargetCredential))[0], ((DWORD *) (&TargetCredential))[1])); #endif // BAD_ALIGNMENT // // verify the computed credentials with those supplied // Authenticator must have used seed + time_of_day as seed // if (RtlCompareMemory( &Authenticator->Credential, &TargetCredential, sizeof(TargetCredential)) != sizeof(TargetCredential)) { return STATUS_ACCESS_DENIED; } // // modify our seed before computing the ReturnAuthenticator. // The requestor will increment his seed if he matches this credentials. // (*((unsigned long * ) &ServerSession->SsAuthenticationSeed))++; // // compute ClientCredential to send back to requestor // NlComputeCredentials( &ServerSession->SsAuthenticationSeed, &ReturnAuthenticator->Credential, &ServerSession->SsSessionKey); #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Server Authenticator SEND = %lx %lx\n", ((DWORD *) (&ReturnAuthenticator->Credential))[0], ((DWORD *) (&ReturnAuthenticator->Credential))[1])); NlPrint((NL_CHALLENGE_RES, "NlCheckAuthenticator: Seed + time + 1= %lx %lx\n", ((DWORD *) (&ServerSession->SsAuthenticationSeed))[0], ((DWORD *) (&ServerSession->SsAuthenticationSeed))[1])); #endif // BAD_ALIGNMENT // // Indicate successful communication with the client // ServerSession->SsCheck = 0; ServerSession->SsPulseTimeoutCount = 0; ServerSession->SsFlags &= ~SS_PULSE_SENT; return STATUS_SUCCESS; } 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 14 byte (112 bit) encryption 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 NlAssert( 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 NlAssert( NT_SUCCESS(Status) ); return; } VOID NlComputeChallenge( OUT PNETLOGON_CREDENTIAL Challenge ) /*++ Routine Description: Generates a 64 bit challenge Make an 8 byte seed by filling in BigTime i.e. seconds since Jan 1 1970 in lower four bytes and a counter in upper four bytes. Counter is incremented after each use. This seed is used as encryption key to encrypt standard text which will be used as challenge. Arguments: Challenge - Returns the computed challenge Return Value: None. --*/ { NTSTATUS Status; char Seed[PWLEN]; LM_OWF_PASSWORD BigChallenge; LARGE_INTEGER TimeNow; RtlZeroMemory(Seed, sizeof(Seed) ); // // we need to remember ClientChallenge and RequestorName for future use // put these into shared seg SSISEG // NlGlobalSessionCounter is a global initialized to 0 at UAS init time // Status = NtQuerySystemTime( &TimeNow ); NlAssert( NT_SUCCESS(Status) ); Status = RtlTimeToSecondsSince1970( &TimeNow, ((unsigned long * ) Seed) ); NlAssert( NT_SUCCESS(Status) ); *((unsigned long * ) & Seed[4]) = NlGlobalSessionCounter++; // // Create ClientChallenge // // NOTE: RtlCalculateLmOwfPassword() will generate 16 byte txt // Status = RtlCalculateLmOwfPassword(Seed, &BigChallenge); NlAssert( NT_SUCCESS(Status) ); // // we need (or will use) only 8 bytes of this info // RtlCopyMemory(Challenge, &BigChallenge, sizeof(Challenge) ); return; } VOID NlBuildAuthenticator( IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed, IN PNETLOGON_SESSION_KEY SessionKey, OUT PNETLOGON_AUTHENTICATOR Authenticator ) /*++ Routine Description: Build the authenticator to be sent to primary. This routine will modify the seed by adding the time-of-day before computing the credentials. Arguments: AuthenticationSeed -- The current authentication seed. This seed will have the current time of day added to it prior to building the Authenticator. SessionKey - The Session Key used for encrypting the Authenticator. Authenticator - The Authenticator to pass to the PDC for the current call. Return Value: NT Status code --*/ { NTSTATUS Status; LARGE_INTEGER TimeNow; // // Use the current time of day to modify the authentication seed // RtlZeroMemory(Authenticator, sizeof(*Authenticator)); Status = NtQuerySystemTime( &TimeNow ); NlAssert( NT_SUCCESS(Status) ); Status = RtlTimeToSecondsSince1970( &TimeNow, &Authenticator->timestamp ); NlAssert( NT_SUCCESS(Status) ); // // Modify the AuthenticationSeed before computing auth_credential for // verification . // // Two long words are added and overflow carry (if any) ignored // This will leave upper 4 bytes unchanged // #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Old Seed = %lx %lx\n", ((DWORD *) (AuthenticationSeed))[0], ((DWORD *) (AuthenticationSeed))[1])); NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Time = %lx\n", ((DWORD *) (&Authenticator->timestamp))[0] )); #endif // BAD_ALIGNMENT *((unsigned long * ) AuthenticationSeed) += Authenticator->timestamp; #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: New Seed = %lx %lx\n", ((DWORD *) (AuthenticationSeed))[0], ((DWORD *) (AuthenticationSeed))[1])); NlPrint((NL_CHALLENGE_RES, "NlBuildAuthenticator: SessionKey = %lx %lx %lx %lx\n", ((DWORD *) (SessionKey))[0], ((DWORD *) (SessionKey))[1], ((DWORD *) (SessionKey))[2], ((DWORD *) (SessionKey))[3])); #endif // BAD_ALIGNMENT // // compute AuthenticationSeed to verify the one supplied by Requestor // NlComputeCredentials( AuthenticationSeed, &Authenticator->Credential, SessionKey); #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlBuildAuthenticator: Client Authenticator = %lx %lx\n", ((DWORD *) (&Authenticator->Credential))[0], ((DWORD *) (&Authenticator->Credential))[1])); #endif // BAD_ALIGNMENT return; } BOOL NlUpdateSeed( IN OUT PNETLOGON_CREDENTIAL AuthenticationSeed, IN PNETLOGON_CREDENTIAL TargetCredential, IN PNETLOGON_SESSION_KEY SessionKey ) /*++ Routine Description: Called by the initiator of a communication over the secure channel following a successful transaction. The PDC would have incremented the seed so we must do so also. We also verify that the incremented seed builds a credential identical to the one passed back by the PDC. Arguments: AuthenticationSeed - Pointer to the AuthenticationSeed to be incremented. TargetCredential - Supplies the Credential that the incremented AuthenticationSeed should encrypt to. SessionKey - Supplies the encryption key to use for the encryption. Return Value: TRUE: Success FALSE: Failure --*/ { NETLOGON_CREDENTIAL NewCredential; // // modify our AuthenticationSeed before computing NewCredential to check // those returned from primary (NewSeed = AuthenticationSeed+1) // (*((unsigned long * ) AuthenticationSeed))++; #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Seed + time + 1= %lx %lx\n", ((DWORD *) (AuthenticationSeed))[0], ((DWORD *) (AuthenticationSeed))[1])); #endif // BAD_ALIGNMENT // // Compute ClientCredential to check which came from primary // NlComputeCredentials(AuthenticationSeed, &NewCredential, SessionKey); #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Server Authenticator GOT = %lx %lx\n", ((DWORD *) (TargetCredential))[0], ((DWORD *) (TargetCredential))[1])); NlPrint((NL_CHALLENGE_RES,"NlUpdateSeed: Server Authenticator MADE = %lx %lx\n", ((DWORD *) (&NewCredential))[0], ((DWORD *) (&NewCredential))[1])); #endif // BAD_ALIGNMENT if (RtlCompareMemory( TargetCredential, &NewCredential, sizeof(NewCredential)) != sizeof(NewCredential)) { return FALSE; } // // Done // return TRUE; } VOID NlEncryptRC4( IN OUT PVOID Buffer, IN ULONG BufferSize, IN PSESSION_INFO SessionInfo ) /*++ Routine Description: Encrypt data using RC4 with the session key as the key. Arguments: Buffer -- Buffer containing the data to encrypt in place. BufferSize -- Size (in bytes) of Buffer. SessionInfo -- Info describing secure channel Return Value: NT status code --*/ { NTSTATUS NtStatus; DATA_KEY KeyData; CRYPT_BUFFER Data; // // Build a data buffer to describe the encryption key. // KeyData.Length = sizeof(NETLOGON_SESSION_KEY); KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY); KeyData.Buffer = (PVOID)&SessionInfo->SessionKey; NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ); // // Build a data buffer to decribe the encrypted data. // Data.Length = Data.MaximumLength = BufferSize; Data.Buffer = Buffer; // // Encrypt the data. // IF_DEBUG( ENCRYPT ) { NlPrint((NL_ENCRYPT, "NlEncryptRC4: Clear data\n" )); NlpDumpHexData( NL_ENCRYPT, (LPDWORD)Data.Buffer, Data.Length / sizeof(DWORD) ); } NtStatus = RtlEncryptData2( &Data, &KeyData ); NlAssert( NT_SUCCESS(NtStatus) ); IF_DEBUG( ENCRYPT ) { NlPrint((NL_ENCRYPT, "NlEncryptRC4: Encrypted data\n" )); NlpDumpHexData( NL_ENCRYPT, (LPDWORD)Data.Buffer, Data.Length / sizeof(DWORD) ); } } VOID NlDecryptRC4( IN OUT PVOID Buffer, IN ULONG BufferSize, IN PSESSION_INFO SessionInfo ) /*++ Routine Description: Decrypt data using RC4 with the session key as the key. Arguments: Buffer -- Buffer containing the data to decrypt in place. BufferSize -- Size (in bytes) of Buffer. SessionInfo -- Info describing secure channel Return Value: NT status code --*/ { NTSTATUS NtStatus; DATA_KEY KeyData; CRYPT_BUFFER Data; // // Build a data buffer to describe the encryption key. // KeyData.Length = sizeof(NETLOGON_SESSION_KEY); KeyData.MaximumLength = sizeof(NETLOGON_SESSION_KEY); KeyData.Buffer = (PVOID)&SessionInfo->SessionKey; NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_RC4_ENCRYPTION ); // // Build a data buffer to decribe the encrypted data. // Data.Length = Data.MaximumLength = BufferSize; Data.Buffer = Buffer; // // Encrypt the data. // IF_DEBUG( ENCRYPT ) { NlPrint((NL_ENCRYPT, "NlDecryptRC4: Encrypted data\n" )); NlpDumpHexData( NL_ENCRYPT, (LPDWORD)Data.Buffer, Data.Length / sizeof(DWORD) ); } NtStatus = RtlDecryptData2( &Data, &KeyData ); NlAssert( NT_SUCCESS(NtStatus) ); IF_DEBUG( ENCRYPT ) { NlPrint((NL_ENCRYPT, "NlDecryptRC4: Clear data\n" )); NlpDumpHexData( NL_ENCRYPT, (LPDWORD)Data.Buffer, Data.Length / sizeof(DWORD) ); } }