/*++ Copyright (c) 1996 Microsoft Corporation Module Name: nmutil.c Abstract: Miscellaneous utility routines for the Node Manager component. Author: Mike Massa (mikemas) 26-Oct-1996 Revision History: --*/ #define UNICODE 1 #include "service.h" #include "nmp.h" #include #include #include PVOID NmpEncryptedClusterKey = NULL; DWORD NmpEncryptedClusterKeyLength = 0; DWORD NmpQueryString( IN HDMKEY Key, IN LPCWSTR ValueName, IN DWORD ValueType, IN LPWSTR *StringBuffer, IN OUT LPDWORD StringBufferSize, OUT LPDWORD StringSize ) /*++ Routine Description: Reads a REG_SZ or REG_MULTI_SZ registry value. If the StringBuffer is not large enough to hold the data, it is reallocated. Arguments: Key - Open key for the value to be read. ValueName - Unicode name of the value to be read. ValueType - REG_SZ or REG_MULTI_SZ. StringBuffer - Buffer into which to place the value data. StringBufferSize - Pointer to the size of the StringBuffer. This parameter is updated if StringBuffer is reallocated. StringSize - The size of the data returned in StringBuffer, including the terminating null character. Return Value: The status of the registry query. Notes: To avoid deadlock with DM, must not be called with NM lock held. --*/ { DWORD status; DWORD valueType; WCHAR *temp; DWORD oldBufferSize = *StringBufferSize; BOOL noBuffer = FALSE; if (*StringBufferSize == 0) { noBuffer = TRUE; } *StringSize = *StringBufferSize; status = DmQueryValue( Key, ValueName, &valueType, (LPBYTE) *StringBuffer, StringSize ); if (status == NO_ERROR) { if (!noBuffer ) { if (valueType == ValueType) { return(NO_ERROR); } else { return(ERROR_INVALID_PARAMETER); } } status = ERROR_MORE_DATA; } if (status == ERROR_MORE_DATA) { temp = MIDL_user_allocate(*StringSize); if (temp == NULL) { *StringSize = 0; return(ERROR_NOT_ENOUGH_MEMORY); } if (!noBuffer) { MIDL_user_free(*StringBuffer); } *StringBuffer = temp; *StringBufferSize = *StringSize; status = DmQueryValue( Key, ValueName, &valueType, (LPBYTE) *StringBuffer, StringSize ); if (status == NO_ERROR) { if (valueType == ValueType) { return(NO_ERROR); } else { *StringSize = 0; return(ERROR_INVALID_PARAMETER); } } } return(status); } // NmpQueryString // // Routines to support the common network configuration code. // VOID ClNetPrint( IN ULONG LogLevel, IN PCHAR FormatString, ... ) { CHAR buffer[256]; DWORD bytes; va_list argList; va_start(argList, FormatString); bytes = FormatMessageA( FORMAT_MESSAGE_FROM_STRING, FormatString, 0, 0, buffer, sizeof(buffer), &argList ); va_end(argList); if (bytes != 0) { ClRtlLogPrint(LogLevel, "%1!hs!", buffer); } return; } // ClNetPrint VOID ClNetLogEvent( IN DWORD LogLevel, IN DWORD MessageId ) { CsLogEvent(LogLevel, MessageId); return; } // ClNetLogEvent VOID ClNetLogEvent1( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1 ) { CsLogEvent1(LogLevel, MessageId, Arg1); return; } // ClNetLogEvent1 VOID ClNetLogEvent2( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1, IN LPCWSTR Arg2 ) { CsLogEvent2(LogLevel, MessageId, Arg1, Arg2); return; } // ClNetLogEvent2 VOID ClNetLogEvent3( IN DWORD LogLevel, IN DWORD MessageId, IN LPCWSTR Arg1, IN LPCWSTR Arg2, IN LPCWSTR Arg3 ) { CsLogEvent3(LogLevel, MessageId, Arg1, Arg2, Arg3); return; } // ClNetLogEvent3 BOOLEAN NmpLockedEnterApi( NM_STATE RequiredState ) { if (NmpState >= RequiredState) { NmpActiveThreadCount++; CL_ASSERT(NmpActiveThreadCount != 0); return(TRUE); } return(FALSE); } // NmpLockedEnterApi BOOLEAN NmpEnterApi( NM_STATE RequiredState ) { BOOLEAN mayEnter; NmpAcquireLock(); mayEnter = NmpLockedEnterApi(RequiredState); NmpReleaseLock(); return(mayEnter); } // NmpEnterApi VOID NmpLockedLeaveApi( VOID ) { CL_ASSERT(NmpActiveThreadCount > 0); NmpActiveThreadCount--; if ((NmpActiveThreadCount == 0) && (NmpState == NmStateOfflinePending)) { SetEvent(NmpShutdownEvent); } return; } // NmpLockedLeaveApi VOID NmpLeaveApi( VOID ) { NmpAcquireLock(); NmpLockedLeaveApi(); NmpReleaseLock(); return; } // NmpLeaveApi // // Routines to provide a cluster shared key for signing and encrypting // data. // DWORD NmpGetLogonId( OUT LUID * LogonId ) { HANDLE tokenHandle = NULL; TOKEN_STATISTICS tokenInfo; DWORD bytesReturned; BOOL success = FALSE; DWORD status; if (LogonId == NULL) { status = STATUS_UNSUCCESSFUL; goto error_exit; } if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &tokenHandle )) { status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to open process token, status %1!u!.\n", status ); goto error_exit; } if (!GetTokenInformation( tokenHandle, TokenStatistics, &tokenInfo, sizeof(tokenInfo), &bytesReturned )) { status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to get token information, status %1!u!.\n", status ); goto error_exit; } RtlCopyMemory(LogonId, &(tokenInfo.AuthenticationId), sizeof(LUID)); status = STATUS_SUCCESS; error_exit: if (tokenHandle != NULL) { CloseHandle(tokenHandle); } return(status); } // NmpGetLogonId DWORD NmpConnectToLsaPrivileged( OUT HANDLE * LsaHandle, OUT BOOLEAN * Trusted ) /*++ Routine Description: Connect to LSA. If running as a service, there is no need to enable the TCB privilege. The fix for bug 337751 allows the cluster service account to issue a MSV1_0_XXX requests even if it does not have a trusted connection to LSA. If not running as a service, try to elevate the privilege to TCB to connect trusted. Fail the call if TCB privilege cannot be enabled. If TCB is enabled and was previously not enabled, it is restored prior to returning. Callers that need to connect to LSA without requiring service logon or TCB can simply use LsaConnectUntrusted. Arguments: LsaHandle - returns LSA handle. Must be cleaned up by caller Trusted - returns whether connection is trusted Return value: Win32 error code. --*/ { DWORD status; BOOLEAN wasEnabled = FALSE; BOOLEAN trusted = FALSE; DWORD ignore; STRING name; HANDLE lsaHandle = NULL; // // Try to turn on TCB privilege if running in console mode. // if (!CsRunningAsService) { status = RtlAdjustPrivilege(SE_TCB_PRIVILEGE, TRUE, FALSE, &wasEnabled); if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); #if CLUSTER_BETA ClRtlLogPrint(LOG_NOISE, "[NM] Failed to turn on TCB privilege, status %1!u!.\n", status ); #endif // CLUSTER_BETA return(status); } else { #if CLUSTER_BETA ClRtlLogPrint(LOG_NOISE, "[NM] Turned on TCB privilege, wasEnabled = %1!ws!.\n", (wasEnabled) ? L"TRUE" : L"FALSE" ); #endif // CLUSTER_BETA trusted = TRUE; } } // // Establish contact with LSA. // if (trusted) { RtlInitString(&name, "ClusSvcNM"); status = LsaRegisterLogonProcess(&name, &lsaHandle, &ignore); // // Turn off TCB privilege // if (!wasEnabled) { DWORD subStatus; subStatus = RtlAdjustPrivilege( SE_TCB_PRIVILEGE, FALSE, FALSE, &wasEnabled ); if (!NT_SUCCESS(subStatus)) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to disable TCB privilege, " "status %1!u!.\n", subStatus ); } else { #if CLUSTER_BETA ClRtlLogPrint(LOG_NOISE, "[NM] Turned off TCB privilege.\n" ); #endif // CLUSTER_BETA } } } else { status = LsaConnectUntrusted(&lsaHandle); } if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to obtain LSA logon handle in %1!ws! mode, " "status %2!u!.\n", (trusted) ? L"trusted" : L"untrusted", status ); } else { *LsaHandle = lsaHandle; *Trusted = trusted; } return(status); } // NmpConnectToLsaPrivileged DWORD NmpDeriveClusterKey( IN PVOID MixingBytes, IN DWORD MixingBytesSize, OUT PVOID * Key, OUT DWORD * KeyLength ) /*++ Routine Description: Derive the cluster key using mixing bytes. Allocate a buffer for the key and return it. Arguments: Key - set to buffer containing key KeyLength - length of resulting key --*/ { LUID logonId; BOOLEAN trusted = FALSE; HANDLE lsaHandle = NULL; STRING name; DWORD packageId = 0; DWORD requestSize; PMSV1_0_DERIVECRED_REQUEST request = NULL; DWORD responseSize; PMSV1_0_DERIVECRED_RESPONSE response = NULL; PUCHAR key; DWORD keyLength; DWORD status = STATUS_SUCCESS; DWORD subStatus = STATUS_SUCCESS; status = NmpGetLogonId(&logonId); if (!NT_SUCCESS(status)) { ClRtlLogPrint( LOG_UNUSUAL, "[NM] Failed to determine logon ID, status %1!u!.\n", status ); goto error_exit; } status = NmpConnectToLsaPrivileged(&lsaHandle, &trusted); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to connect to LSA, status %1!u!.\n", status ); goto error_exit; } // // Lookup the authentication package. // RtlInitString( &name, MSV1_0_PACKAGE_NAME ); status = LsaLookupAuthenticationPackage(lsaHandle, &name, &packageId); if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to local authentication package with " "name %1!ws!, status %2!u!.\n", name.Buffer, status ); goto error_exit; } // // Build the derive credentials request with the provided // mixing bytes. // requestSize = sizeof(MSV1_0_DERIVECRED_REQUEST) + MixingBytesSize; request = LocalAlloc(LMEM_FIXED, requestSize); if (request == NULL) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate LSA request of size %1!u! bytes.\n", requestSize ); status = ERROR_NOT_ENOUGH_MEMORY; goto error_exit; } request->MessageType = MsV1_0DeriveCredential; RtlCopyMemory(&(request->LogonId), &logonId, sizeof(logonId)); request->DeriveCredType = MSV1_0_DERIVECRED_TYPE_SHA1; request->DeriveCredInfoLength = MixingBytesSize; RtlCopyMemory( &(request->DeriveCredSubmitBuffer[0]), MixingBytes, MixingBytesSize ); // // Make the call through LSA to the authentication package. // status = LsaCallAuthenticationPackage( lsaHandle, packageId, request, requestSize, &response, &responseSize, &subStatus ); if (!NT_SUCCESS(status)) { status = LsaNtStatusToWinError(status); subStatus = LsaNtStatusToWinError(subStatus); ClRtlLogPrint(LOG_UNUSUAL, "[NM] DeriveCredential call to authentication " "package failed, status %1!u!, auth package " "status %2!u!.\n", status, subStatus ); goto error_exit; } // // Allocate a non-LSA buffer to store the key. // keyLength = response->DeriveCredInfoLength; key = MIDL_user_allocate(keyLength); if (key == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer for cluster " "key of size %1!u!.\n", keyLength ); goto error_exit; } // // Store the derived credentials in the key buffer. // RtlCopyMemory(key, &(response->DeriveCredReturnBuffer[0]), keyLength); // // Zero the derived credential buffer to be extra paranoid. // RtlZeroMemory( &(response->DeriveCredReturnBuffer[0]), response->DeriveCredInfoLength ); status = STATUS_SUCCESS; *Key = key; *KeyLength = keyLength; error_exit: if (lsaHandle != NULL) { LsaDeregisterLogonProcess(lsaHandle); lsaHandle = NULL; } if (request != NULL) { LocalFree(request); request = NULL; } if (response != NULL) { LsaFreeReturnBuffer(response); response = NULL; } return(status); } // NmpDeriveClusterKey DWORD NmpGetClusterKey( OUT PVOID KeyBuffer, IN OUT DWORD * KeyBufferLength ) /*++ Routine Description: Decrypt and copy the shared cluster key into the buffer provided. Arguments: KeyBuffer - buffer to which key should be copied KeyBufferLength - IN: length of KeyBuffer OUT: required buffer size, if input buffer length is insufficient Return value: ERROR_INSUFFICIENT_BUFFER if KeyBuffer is too small. ERROR_FILE_NOT_FOUND if NmpEncryptedClusterKey has not yet been generated. ERROR_SUCCESS on success. Notes: Acquires and releases NM lock. Since NM lock is implemented as a critical section, calling thread is permitted to already hold NM lock. --*/ { DWORD status; BOOL DecryptingDataSucceeded = FALSE; BOOL Success; DATA_BLOB DataIn; DATA_BLOB DataOut; RtlZeroMemory(&DataOut, sizeof(DataOut)); NmpAcquireLock(); if (NmpEncryptedClusterKey == NULL) { status = ERROR_FILE_NOT_FOUND; ClRtlLogPrint(LOG_UNUSUAL, "[NM] The cluster key has not yet been derived.\n" ); } else{ // // Decrypt cluster key // DataIn.pbData = NmpEncryptedClusterKey; DataIn.cbData = NmpEncryptedClusterKeyLength; Success = CryptUnprotectData(&DataIn, // data to be decrypted NULL, NULL, NULL, NULL, 0, // flags &DataOut // decrypted data ); if (!Success) { status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to decrypt data using CryptUnprotectData, " "status %1!u!.\n", status ); goto error_exit; } DecryptingDataSucceeded = TRUE; if (KeyBuffer == NULL || DataOut.cbData > *KeyBufferLength) { status = ERROR_INSUFFICIENT_BUFFER; } else { RtlCopyMemory(KeyBuffer, DataOut.pbData, DataOut.cbData); status = ERROR_SUCCESS; } *KeyBufferLength = DataOut.cbData; } error_exit: NmpReleaseLock(); if (DecryptingDataSucceeded) { // // Zero the encrypted data before releasing the memory. // RtlSecureZeroMemory(DataOut.pbData, DataOut.cbData); } if (DataOut.pbData != NULL) { LocalFree(DataOut.pbData); } // To be extra secure. RtlSecureZeroMemory(&DataOut, sizeof(DataOut)); return(status); } // NmpGetClusterKey DWORD NmpRederiveClusterKey( VOID ) /*++ Routine Description: Forces rederivation of cluster key. Must be called during cluster initialization to generate cluster key the first time. Otherwise called when cluster password changes, since the cluster key is based on the cluster service account password. Notes: Acquires and releases NM lock. --*/ { DWORD status; BOOLEAN lockAcquired; PVOID key = NULL; DWORD keyLength = 0; PVOID oldEncryptedKey = NULL; DWORD oldEncryptedKeyLength = 0; PVOID mixingBytes = NULL; DWORD mixingBytesSize; NmpAcquireLock(); lockAcquired = TRUE; NmpLockedEnterApi(NmStateOnlinePending); // // Form the mixing bytes. // if (NmpClusterInstanceId == NULL) { status = ERROR_INVALID_PARAMETER; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Need cluster instance id in order to derive " "cluster key, status %1!u!.\n", status ); goto error_exit; } mixingBytesSize = NM_WCSLEN(NmpClusterInstanceId); mixingBytes = MIDL_user_allocate(mixingBytesSize); if (mixingBytes == NULL) { status = ERROR_NOT_ENOUGH_MEMORY; ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer of size %1!u! " "for mixing bytes to derive cluster key.\n", mixingBytesSize ); goto error_exit; } RtlCopyMemory(mixingBytes, NmpClusterInstanceId, mixingBytesSize); // // Make a copy of the old encrypted key to detect changes. // if (NmpEncryptedClusterKey != NULL) { CL_ASSERT(NmpEncryptedClusterKeyLength > 0); oldEncryptedKey = MIDL_user_allocate(NmpEncryptedClusterKeyLength); if (oldEncryptedKey == NULL) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to allocate buffer for cluster " "key copy.\n" ); status = ERROR_NOT_ENOUGH_MEMORY; goto error_exit; } oldEncryptedKeyLength = NmpEncryptedClusterKeyLength; RtlCopyMemory(oldEncryptedKey, NmpEncryptedClusterKey, NmpEncryptedClusterKeyLength ); } NmpReleaseLock(); lockAcquired = FALSE; status = NmpDeriveClusterKey( mixingBytes, mixingBytesSize, &key, &keyLength ); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to derive cluster key, " "status %1!u!.\n", status ); goto error_exit; } NmpAcquireLock(); lockAcquired = TRUE; // // Make sure another thread didn't beat us in obtaining a key. // We replace the cluster key with the generated key if it is // not different from the old key (or somebody set it to NULL). // if (NmpEncryptedClusterKey != NULL && (oldEncryptedKey == NULL || NmpEncryptedClusterKeyLength != oldEncryptedKeyLength || RtlCompareMemory( NmpEncryptedClusterKey, oldEncryptedKey, oldEncryptedKeyLength ) != oldEncryptedKeyLength ) ) { // // Keep the current NmpEncryptedClusterKey. // } else { // // Encrypt derived credentials and store them // if (NmpEncryptedClusterKey != NULL) { RtlSecureZeroMemory(NmpEncryptedClusterKey, NmpEncryptedClusterKeyLength); LocalFree(NmpEncryptedClusterKey); } status = NmpProtectData(key, keyLength, &NmpEncryptedClusterKey, &NmpEncryptedClusterKeyLength ); if (status != ERROR_SUCCESS) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Failed to encrypt data using CryptProtectData, " "status %1!u!.\n", status ); goto error_exit; } } error_exit: if (lockAcquired) { NmpLockedLeaveApi(); NmpReleaseLock(); } else { NmpLeaveApi(); } if (oldEncryptedKey != NULL) { MIDL_user_free(oldEncryptedKey); } if (key != NULL) { RtlSecureZeroMemory(key, keyLength); MIDL_user_free(key); key = NULL; keyLength = 0; } if (mixingBytes != NULL) { MIDL_user_free(mixingBytes); mixingBytes = NULL; } return(status); } // NmpRederiveClusterKey VOID NmpFreeClusterKey( VOID ) /*++ Routine Description: Called during NmShutdown. --*/ { if (NmpEncryptedClusterKey != NULL) { RtlSecureZeroMemory(NmpEncryptedClusterKey, NmpEncryptedClusterKeyLength); LocalFree(NmpEncryptedClusterKey); NmpEncryptedClusterKey = NULL; NmpEncryptedClusterKeyLength = 0; } return; } // NmpFreeClusterKey DWORD NmpSetLsaProcessOptions( IN ULONG ProcessOptions ) /*++ Routine Description: Set LSA options for this process. Arguments: ProcessOptions - MSV1_0_OPTION_XXX process option bit flags Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. Notes: --*/ { DWORD ReturnStatus; NTSTATUS Status; BOOLEAN trusted = FALSE; HANDLE hLsa = NULL; LSA_STRING LsaStringBuf; char *AuthPackage = MSV1_0_PACKAGE_NAME; ULONG PackageId; ULONG cbOptionsRequest, cbResponse; MSV1_0_SETPROCESSOPTION_REQUEST OptionsRequest; PVOID Response = NULL; ULONG ResponseSize; NTSTATUS SubStatus; ReturnStatus = NmpConnectToLsaPrivileged(&hLsa, &trusted); if (ReturnStatus != ERROR_SUCCESS) { ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to connect to the LSA server while setting " "process options, status %1!u!.\n", ReturnStatus ); goto ErrorExit; } RtlInitString(&LsaStringBuf, AuthPackage); Status = LsaLookupAuthenticationPackage( hLsa, &LsaStringBuf, // MSV1_0 authentication package &PackageId // output: authentication package identifier. ); if (Status != STATUS_SUCCESS) { ReturnStatus = LsaNtStatusToWinError(Status); ClRtlLogPrint( LOG_CRITICAL, "[NM] Authentication package lookup failed while " "setting LSA process options, status %1!u!.\n", ReturnStatus ); goto ErrorExit; } cbOptionsRequest = sizeof(OptionsRequest); ZeroMemory(&OptionsRequest, sizeof(OptionsRequest)); OptionsRequest.MessageType = MsV1_0SetProcessOption; OptionsRequest.ProcessOptions = ProcessOptions; Status = LsaCallAuthenticationPackage( hLsa, PackageId, &OptionsRequest, cbOptionsRequest, &Response, &ResponseSize, &SubStatus ); if (Status != STATUS_SUCCESS) { ReturnStatus = LsaNtStatusToWinError(Status); ClRtlLogPrint( LOG_CRITICAL, "[NM] Failed to set LSA process options (1) to %1!x! , " "status %2!u!.\n", ProcessOptions, ReturnStatus ); goto ErrorExit; } else if (LsaNtStatusToWinError(SubStatus) != ERROR_SUCCESS) { ReturnStatus = LsaNtStatusToWinError(SubStatus); ClRtlLogPrint( LOG_CRITICAL, "[NM] Failed to set LSA process options (2) to %1!x! , " "status %2!u!.\n", ProcessOptions, ReturnStatus ); goto ErrorExit; } ReturnStatus = ERROR_SUCCESS; ErrorExit: if (hLsa != NULL) { LsaDeregisterLogonProcess(hLsa); hLsa = NULL; } return ReturnStatus; } // NmpSetLsaProcessOptions // Helper routines used for encryption on the wire data // currently used only by dmsync.c to secure transfer of checkpoints // Here the pattern how this routines should be used // (Crafted to produce the least amout of code change in dm to encrypt/decrypt data going over an RPC pipe) // // Encryption: // NmCryptor_Init(Cryptor) // // NmCryptor_PrepareEncryptionBuffer(Cryptor, Buffer, Size); // PayloadSize bytes of data into Cryptor->PayloadBuffer (say X bytes)> // NmCryptor_Encrypt(&Cryptor, X); // EncryptedBuffer, Cryptor->EncryptedSize> // // ... repeat PrepareEncryptionBuffer/Encrypt for as many time as you need // // NmCryptor_Destroy(Cryptor) // // Decryption: // // NmCryptor_Init(Cryptor) // // // NmCryptor_Decrypt(Cryptor, Buffer, Size); // PayloadBuffer, Cryptor->PayloadSize> // // ... repeat NmCryptor_Decrypt for as many packets you have to decrypt ... // // NmCryptor_Destroy(Cryptor) typedef struct _NMP_CRYPTOR_HEADER { BYTE Salt[16]; // random goo which is mixed with the shared secret to produce a key BYTE SaltQuickSig[16]; // simple xor of salt bytes with 0xFF used as a quick test whether stuff is encrypted or not BYTE SaltSlowSig[16]; // encrypted salt bytes to check for the valid encryption key } NM_CRYPTOR_HEADER, *PNM_CRYPTOR_HEADER; VOID NmCryptor_Init( IN OUT PNM_CRYPTOR Cryptor, IN BOOL EnableEncryption) { ZeroMemory(Cryptor, sizeof(*Cryptor)); Cryptor->EncryptionDisabled = !EnableEncryption; } BOOL NmpVerifyQuickSig(IN PNM_CRYPTOR_HEADER hdr) { int i; if (hdr->SaltQuickSig[0] != 'Q' || hdr->SaltQuickSig[1] != 'S') { return FALSE; } for (i = 2; i < sizeof(hdr->Salt); ++i) { if (hdr->SaltQuickSig[i] != (hdr->Salt[i] ^ 0xFF) ) { return FALSE; } } return TRUE; } VOID NmpMakeQuickSig(IN PNM_CRYPTOR_HEADER hdr) { int i; for (i = 2; i < sizeof(hdr->Salt); ++i) { hdr->SaltQuickSig[i] = hdr->Salt[i] ^ 0xFF; } hdr->SaltQuickSig[0] = 'Q'; // to make it easier to spot in the sniff hdr->SaltQuickSig[1] = 'S'; // } DWORD NmpCryptor_PrepareKey( IN OUT PNM_CRYPTOR Cryptor, IN BOOL GenerateSalt) /*++ Routine Description: Calls prepares the key for encrption/decryption. Creates a symmetric key by mixing random 128bit salt with cluster password derived secret Arguments: GenerateSalt - if true, salt is generated, if false, salt is read from a buffer Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. --*/ { DWORD Status = ERROR_SUCCESS; DWORD ClusterKeyLen = 0; PBYTE ClusterKey = NULL; PNM_CRYPTOR_HEADER hdr = (PNM_CRYPTOR_HEADER)Cryptor->EncryptedBuffer; if (GenerateSalt) { // Check that a buffer is big enough // to hold encryption header if (Cryptor->EncryptedSize < sizeof(*hdr)) { ClRtlLogPrint(LOG_CRITICAL, "[NM] Cryptor: No room for the header, buffer size is only %1!u!.\n", Cryptor->EncryptedSize ); return ERROR_INSUFFICIENT_BUFFER; } } else { // Otherwise, do a quick check whether the incoming data // is encrypted if (Cryptor->EncryptedSize < sizeof(*hdr)) { Cryptor->EncryptionDisabled = TRUE; return ERROR_SUCCESS; } if ( !NmpVerifyQuickSig(hdr) ) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Cryptor: Data is not encrypted.\n" ); Cryptor->EncryptionDisabled = TRUE; return ERROR_SUCCESS; } } Status = NmpCreateCSPHandle(&Cryptor->CryptProv); if (Status != ERROR_SUCCESS) { return Status; } if (GenerateSalt) { if (!CryptGenRandom(Cryptor->CryptProv, sizeof(hdr->Salt), hdr->Salt)){ Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[NM] Cryptor: CryptGenRandom failed %1!u!.\n", Status ); goto exit_gracefully; } } Status = NmpDeriveSessionKey( Cryptor->CryptProv, CALG_RC4, 128 << 16, // algorithm, key length hdr->Salt, sizeof(hdr->Salt), &Cryptor->CryptKey); if (Status != ERROR_SUCCESS) { goto exit_gracefully; // error logged by NmpDeriveSessionKey } if (GenerateSalt) { DWORD Len = sizeof(hdr->SaltSlowSig); // Encrypt SlowSig so that the receiver can verify the validity // of an encryption key of a sender memcpy(hdr->SaltSlowSig, hdr->Salt, sizeof(hdr->SaltSlowSig)); if (!CryptEncrypt(Cryptor->CryptKey, 0, FALSE, 0, // hash,final,flags hdr->SaltSlowSig, &Len, Len)) { Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to Encrypt signature, status %1!u!.\n", Status ); goto exit_gracefully; } // now make quick signature (to test whether the incoming data is encrypted) NmpMakeQuickSig(hdr); } else { DWORD Len = sizeof(hdr->SaltSlowSig); // Verify encrypted portion of the signature if (!CryptDecrypt(Cryptor->CryptKey, 0, FALSE, 0, // hash,final,flags hdr->SaltSlowSig, &Len)) { Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to Decrypt signature, status %1!u!.\n", Status ); goto exit_gracefully; } if (memcmp(hdr->Salt, hdr->SaltSlowSig, sizeof(hdr->SaltSlowSig)) != 0) { Status = ERROR_DECRYPTION_FAILED; ClRtlLogPrint(LOG_CRITICAL, "[NM] Signatures don't match.\n" ); goto exit_gracefully; } } Cryptor->KeyGenerated = TRUE; exit_gracefully: return Status; } DWORD NmCryptor_Decrypt( IN OUT PNM_CRYPTOR Cryptor, IN OUT PVOID Buffer, IN DWORD BufferSize) /*++ Routine Description: Decrypts the supplied buffer Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. Notes: If the routine succeeds, use Cryptor->PayloadBuffer Cryptor->PayloadSize fields to get to the decrypted data --*/ { DWORD Status = ERROR_SUCCESS; Cryptor->PayloadBuffer = (PBYTE)Buffer; Cryptor->PayloadSize = BufferSize; Cryptor->EncryptedBuffer = Cryptor->PayloadBuffer; Cryptor->EncryptedSize = BufferSize; if (Cryptor->EncryptionDisabled) { return ERROR_SUCCESS; } if (!Cryptor->KeyGenerated) { Status = NmpCryptor_PrepareKey(Cryptor, FALSE); if (Status != ERROR_SUCCESS) { return Status; } if (Cryptor->EncryptionDisabled) { ClRtlLogPrint(LOG_UNUSUAL, "[NM] Cryptor received unencrypted data.\n" ); return ERROR_SUCCESS; } Cryptor->PayloadBuffer = Cryptor->PayloadBuffer + sizeof(NM_CRYPTOR_HEADER); Cryptor->PayloadSize -= sizeof(NM_CRYPTOR_HEADER); } if (!CryptDecrypt(Cryptor->CryptKey, 0, FALSE, 0, Cryptor->PayloadBuffer, &Cryptor->PayloadSize)) { Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to Decrypt buffer, status %1!u!.\n", Status ); } return Status; } VOID NmCryptor_PrepareEncryptionBuffer( IN OUT PNM_CRYPTOR Cryptor, IN OUT PVOID Buffer, IN DWORD BufferSize) /*++ Routine Description: Supplies the information about the buffer ti be used for encryption Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. Notes: When the function returns, use Cryptor->PayloadBuffer Cryptor->PayloadSize to figure out where to put the data to be encrypted --*/ { Cryptor->PayloadBuffer = (PBYTE)Buffer; Cryptor->PayloadSize = BufferSize; Cryptor->EncryptedBuffer = Cryptor->PayloadBuffer; Cryptor->EncryptedSize = BufferSize; if (Cryptor->EncryptionDisabled || Cryptor->KeyGenerated) { return; } if (NmpIsNT5NodeInCluster == FALSE) { // Create room for the header Cryptor->PayloadBuffer += sizeof(NM_CRYPTOR_HEADER); Cryptor->PayloadSize -= sizeof(NM_CRYPTOR_HEADER); } else { Cryptor->EncryptionDisabled = TRUE; } } DWORD NmCryptor_Encrypt( IN OUT PNM_CRYPTOR Cryptor, DWORD DataSize) /*++ Routine Description: Encrypts DataSize bytes Return Value: ERROR_SUCCESS if successful Win32 error code otherwise. Notes: Input data are in the buffer pointed by Cryptor->PayloadSize (prepared by NmCryptor_PrepareEncryptionBuffer routine) Output data are in: Cryptor->EncryptedBuffer == Whatever buffer was supplied to NmCryptor_PrepareEncryptionBuffer Cryptor->EncryptedSize --*/ { DWORD Status = ERROR_SUCCESS; if (Cryptor->EncryptionDisabled) { Cryptor->EncryptedSize = DataSize; return ERROR_SUCCESS; } if (!Cryptor->KeyGenerated) { Status = NmpCryptor_PrepareKey(Cryptor, TRUE); if (Status != ERROR_SUCCESS) { return Status; } } if (!CryptEncrypt(Cryptor->CryptKey, 0, FALSE, 0, Cryptor->PayloadBuffer, &DataSize, Cryptor->EncryptedSize)) { Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to Encrypt buffer, status %1!u!.\n", Status ); } Cryptor->EncryptedSize = DataSize + (int)(Cryptor->PayloadBuffer - Cryptor->EncryptedBuffer); return Status; } VOID NmCryptor_Destroy( PNM_CRYPTOR Cryptor) { if (Cryptor == NULL) { return; } if (Cryptor->CryptKey) { CryptDestroyKey(Cryptor->CryptKey); } if (Cryptor->CryptProv) { CryptReleaseContext(Cryptor->CryptProv, 0); } } void NmpFreeNetworkMulticastKey( PNM_NETWORK_MULTICASTKEY networkMulticastKey ) { if (networkMulticastKey != NULL) { if (networkMulticastKey->EncryptedMulticastKey != NULL) { MIDL_user_free(networkMulticastKey->EncryptedMulticastKey); } if (networkMulticastKey->MAC != NULL) { MIDL_user_free(networkMulticastKey->MAC); } if (networkMulticastKey->Salt != NULL) { MIDL_user_free(networkMulticastKey->Salt); } MIDL_user_free(networkMulticastKey); } } // NmpFreeNetworkMulticastKey() DWORD NmpGetMulticastKeyFromNMLeader( IN DWORD LeaderNodeId, IN LPWSTR NodeIdString, IN LPWSTR NetworkId, IN PNM_NETWORK_MULTICASTKEY * MulticastKey ) /*++ Routine Description: Issue a retrieve request for network multicast key to NM leader. Arguments: Return Value: ERROR_SUCCESS if the request is successful. Win32 error code on failure. Notes: This routine mimics GumpUpdateRemoteNode. In Longhorn, there should be one generic async RPC call wrapper. --*/ { DWORD Status; HANDLE hEventHandle; BOOL result; PNM_NODE Node = NULL; HANDLE handleArr[2]; RPC_ASYNC_STATE AsyncState; RPC_BINDING_HANDLE rpcBinding; // // Prepare for async RPC. We do this here to avoid hitting a failure // after the update is already in progress. // ZeroMemory((PVOID) &AsyncState, sizeof(RPC_ASYNC_STATE)); AsyncState.u.hEvent = CreateEvent( NULL, // no attributes TRUE, // manual reset FALSE, // initial state unsignalled NULL // no object name ); if (AsyncState.u.hEvent == NULL) { Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: Failed to allocate event object for async " "RPC call, status %1!u!\n", Status ); return (Status); } // // Initialize the async RPC tracking information // hEventHandle = AsyncState.u.hEvent; AsyncState.u.hEvent = NULL; Status = RpcAsyncInitializeHandle(&AsyncState, sizeof(RPC_ASYNC_STATE)); AsyncState.u.hEvent = hEventHandle; if (Status != RPC_S_OK) { ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: Failed to initialize async RPC status " "block, status %1!u!\n", Status ); goto error_exit; } AsyncState.UserInfo = NULL; AsyncState.NotificationType = RpcNotificationTypeEvent; result = ResetEvent(AsyncState.u.hEvent); CL_ASSERT(result != 0); // // Now hook onto NM node state down event mechanism to detect node downs. // Node = NmReferenceNodeById(LeaderNodeId); CL_ASSERT(Node != NULL); if (Node == NULL) { Status = GetLastError(); ClRtlLogPrint(LOG_UNUSUAL, "[GUM] NmpGetMulticastKeyFromNMLeader: Failed to reference leader " "node id %1!u!, status %2!u!\n", LeaderNodeId, Status ); goto error_exit; } handleArr[0] = AsyncState.u.hEvent; handleArr[1] = NmGetNodeStateDownEvent(Node); // // Get the RPC binding handle for the leader node. // // Note that there is a race condition here with // ClMsgCleanup. The Session array can be freed // before we dereference Session. This would lead // to an unfortunate AV exception, but it would not // be tragic since the service is already terminating // abnormally if ClMsgCleanup is executing. // if (Session != NULL) { rpcBinding = Session[LeaderNodeId]; } else { ClRtlLogPrint(LOG_UNUSUAL, "[GUM] NmpGetMulticastKeyFromNMLeader: No RPC " "binding handle for leader node id %1!u!.\n", LeaderNodeId ); Status = ERROR_CLUSTER_NODE_UNREACHABLE; goto error_exit; } try { // // Get multicast key from the leader // Status = NmRpcGetNetworkMulticastKey( &AsyncState, rpcBinding, NodeIdString, NetworkId, MulticastKey ); if (Status == RPC_S_OK) { DWORD RpcStatus; // // The call is pending. Wait for completion. // Status = WaitForMultipleObjects( 2, handleArr, FALSE, INFINITE ); if (Status != WAIT_OBJECT_0) { // // Something went wrong. // Either this is a rpc failure or, the target node went down. // CL_ASSERT(Status != WAIT_OBJECT_0); Status = GetLastError(); ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: Wait for NmRpcGetNetworkMulticastKey" " failed, status %1!u!\n", Status ); // // Cancel the call, just to be safe. // RpcStatus = RpcAsyncCancelCall( &AsyncState, TRUE // Abortive cancel ); if (RpcStatus != RPC_S_OK) { ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: RpcAsyncCancelCall()= " " %1!u!\n", RpcStatus ); } CL_ASSERT(RpcStatus == RPC_S_OK); // // Wait for the call to complete. // Status = WaitForSingleObject( AsyncState.u.hEvent, INFINITE ); if (Status != WAIT_OBJECT_0) { ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: WaitForSingleObject()= " " %1!u!\n", Status ); } CL_ASSERT(Status == WAIT_OBJECT_0); } // // The call should now be complete. Get the // completion status. Any RPC error will be // returned in 'RpcStatus'. If there was no // RPC error, then any application error will // be returned in 'Status'. // RpcStatus = RpcAsyncCompleteCall( &AsyncState, &Status ); if (RpcStatus != RPC_S_OK) { ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: Failed to get " "completion status for async RPC call," "status %1!u!\n", RpcStatus ); Status = RpcStatus; } } else { // // An error was returned synchronously. // ClRtlLogPrint(LOG_CRITICAL, "[GUM] NmpGetMulticastKeyFromNMLeader: NmRpcGetNetworkMulticastKey() " "failed synchronously, status %1!u!\n", Status ); } } except (I_RpcExceptionFilter(RpcExceptionCode())) { Status = GetExceptionCode(); } error_exit: if (AsyncState.u.hEvent != NULL) { CloseHandle(AsyncState.u.hEvent); } if (Node != NULL) { OmDereferenceObject(Node); } return(Status); } // NmpGetMulticastKeyFromNMLeader #ifdef MULTICAST_DEBUG DWORD NmpDbgPrintData(LPCWSTR InfoStr, PVOID Data, DWORD DataLen ) { DWORD i; ClRtlLogPrint( LOG_NOISE, "\n\n%1!ws!\n", InfoStr); for (i=0; i