/*++ Copyright (c) 2001 Microsoft Corporation Module Name: crypto.c Abstract: routines for encrypting/decrypting random data blob. heavily modelled after service\cp\crypto.c Author: Charlie Wickham (charlwi) 14-Feb-2001 Environment: User Mode Revision History: --*/ #include "clusres.h" #include "clusrtl.h" #include "netname.h" #include #include // // header for encrypted data. // #define SALT_SIZE 16 #define IV_SIZE 8 typedef struct _NETNAME_ENCRYPTED_DATA { DWORD Version; struct _NETNAME_ENCRYPTION_INITIALIZATION_DATA { BYTE IV[IV_SIZE]; BYTE Salt[SALT_SIZE]; } InitData; BYTE Data[0]; } NETNAME_ENCRYPTED_DATA, *PNETNAME_ENCRYPTED_DATA; // current version for the CRYPTO_KEY_INFO struct #define NETNAME_ENCRYPTED_DATA_VERSION 1 DWORD GenSymKey( IN HCRYPTPROV hProv, IN BYTE *pbSalt, IN BYTE *pbIV, OUT HCRYPTKEY *phSymKey ) /*++ Routine Description: Generate a session key based on the specified Salt and IV. Arguments: hProv - Handle to the crypto provider (key container) pbSalt - Salt value pbIV - IV value phSymKey - Resulting symmetric key (CALG_RC2) Return Value: ERROR_SUCCESS if successful Win32 error code otherwise --*/ { HCRYPTHASH hHash = 0; DWORD cbPassword = 0; DWORD Status; if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { Status = GetLastError(); goto Ret; } if (!CryptHashData(hHash, pbSalt, SALT_SIZE, 0)) { Status = GetLastError(); goto Ret; } // derive the key from the hash if (!CryptDeriveKey(hProv, CALG_RC2, hHash, 0, phSymKey)) { Status = GetLastError(); goto Ret; } // set the IV on the key if (!CryptSetKeyParam(*phSymKey, KP_IV, pbIV, 0)) { Status = GetLastError(); goto Ret; } Status = ERROR_SUCCESS; Ret: if (hHash) CryptDestroyHash(hHash); return (Status); } // GenSymKey DWORD EncryptNetNameData( RESOURCE_HANDLE ResourceHandle, LPWSTR MachinePwd, PBYTE * EncryptedInfo, PDWORD EncryptedInfoLength, HKEY Key ) /*++ Routine Description: encrypt the password, set a pointer to the encrypted data and store it in the registry. There is a chicken/egg problem of sorts in that we have to generate the key before we can use it to encrypt data. This requires having a spot to store the Salt and IV. Since we have to hold all the info in one buffer for the registry write, we tempoarily use stack-based buffer (keyGenBuffer) for the header info. Once we know the size of the encrypted data, we can allocate the proper sized buffer (encryptedInfo), copy everything into it, and write the registry. It is this buffer that is handed back to the caller via the EncryptedInfo argument. Arguments: ResourceHandle - for logging to the cluster log MachinePwd - pointer to unicode string password EncryptedInfo - address of a pointer that receives a pointer to the encrypted blob EncryptedInfoLength - pointer to a DWORD that receives the length of the blob Key - handle to netname parameters key where the data is stored Return Value: ERROR_SUCCESS, otherwise Win32 error --*/ { DWORD status; DWORD encInfoLength; DWORD encDataLength = 0; BOOL success; DWORD pwdLength = ( wcslen( MachinePwd ) + 1 ) * sizeof( WCHAR ); HCRYPTPROV cryptoProvider = 0; HCRYPTKEY sessionKey = 0; NETNAME_ENCRYPTED_DATA keyGenBuffer; // temp header buffer to generate key PNETNAME_ENCRYPTED_DATA encryptedInfo = NULL; // final data area // // get a handle to the full RSA provider // if ( !CryptAcquireContext(&cryptoProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { status = GetLastError(); if ( status == NTE_BAD_KEYSET ) { success = CryptAcquireContext(&cryptoProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET); status = success ? ERROR_SUCCESS : GetLastError(); } if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't acquire crypto context for encrypt. status %1!u!.\n", status); return status; } } // // generate the session key. // if ( !CryptGenRandom(cryptoProvider, sizeof(struct _NETNAME_ENCRYPTION_INITIALIZATION_DATA), (PBYTE)&keyGenBuffer.InitData)) { status = GetLastError(); (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't generate seed data. status %1!u!.\n", status); goto cleanup; } keyGenBuffer.Version = NETNAME_ENCRYPTED_DATA_VERSION; status = GenSymKey(cryptoProvider, keyGenBuffer.InitData.Salt, keyGenBuffer.InitData.IV, &sessionKey); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't generate session key for encrypt. status %1!u!.\n", status); goto cleanup; } // // find the size we need for the buffer to receive the encrypted data // encDataLength = pwdLength; if ( CryptEncrypt(sessionKey, 0, TRUE, 0, NULL, &encDataLength, 0)) { // // alloc a buffer large enough to hold the data and copy the password into it. // ASSERT( encDataLength >= pwdLength ); encInfoLength = sizeof( NETNAME_ENCRYPTED_DATA ) + encDataLength; encryptedInfo = HeapAlloc( GetProcessHeap(), 0, encInfoLength ); if ( encryptedInfo != NULL ) { wcscpy( (PWCHAR)encryptedInfo->Data, MachinePwd ); if ( CryptEncrypt(sessionKey, 0, TRUE, 0, encryptedInfo->Data, &pwdLength, encDataLength)) { *encryptedInfo = keyGenBuffer; status = ResUtilSetBinaryValue(Key, PARAM_NAME__RANDOM, (const LPBYTE)encryptedInfo, encInfoLength, NULL, NULL); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't write %1!u! bytes of data to registry. status %2!u!.\n", encInfoLength, status); goto cleanup; } } else { status = GetLastError(); (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't encrypt %1!u! bytes. status %2!u!.\n", pwdLength, status); goto cleanup; } } else { status = ERROR_NOT_ENOUGH_MEMORY; (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't allocate %1!u! bytes for encrypted data. status %2!u!.\n", encInfoLength, status); goto cleanup; } } else { status = GetLastError(); (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't determine size of encrypted data buffer for %1!u! bytes of data. status %2!u!.\n", pwdLength, status); goto cleanup; } *EncryptedInfoLength = encInfoLength; *EncryptedInfo = (PBYTE)encryptedInfo; cleanup: if ( status != ERROR_SUCCESS && encryptedInfo != NULL ) { HeapFree( GetProcessHeap(), 0, encryptedInfo ); } if ( sessionKey != 0 ) { if ( !CryptDestroyKey( sessionKey )) { (NetNameLogEvent)(ResourceHandle, LOG_WARNING, L"Couldn't destory session key. status %1!u!.\n", GetLastError()); } } if ( !CryptReleaseContext( cryptoProvider, 0 )) { (NetNameLogEvent)(ResourceHandle, LOG_WARNING, L"Can't release crypto context. status %1!u!.\n", GetLastError()); } return status; } // EncryptNetNameData DWORD DecryptNetNameData( RESOURCE_HANDLE ResourceHandle, PBYTE EncryptedInfo, DWORD EncryptedInfoLength, LPWSTR MachinePwd ) /*++ Routine Description: Reverse of encrypt routine - decrypt random blob and hand back the password Arguments: ResourceHandle - used to log into the cluster log EncryptedInfo - pointer to encrypted info header and data EncryptedInfoLength - # of bytes in EncryptedInfo MachinePwd - pointer to buffer that receives the unicode password Return Value: ERROR_SUCCESS, otherwise Win32 error --*/ { DWORD status; DWORD encDataLength = EncryptedInfoLength - sizeof( NETNAME_ENCRYPTED_DATA ); BOOL success; DWORD pwdByteLength; DWORD pwdBufferSize; PWCHAR machinePwd = NULL; HCRYPTPROV cryptoProvider = 0; HCRYPTKEY sessionKey = 0; PNETNAME_ENCRYPTED_DATA encryptedInfo = (PNETNAME_ENCRYPTED_DATA)EncryptedInfo; // // get a handle to the full RSA provider // if ( !CryptAcquireContext(&cryptoProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { status = GetLastError(); if ( status == NTE_BAD_KEYSET ) { success = CryptAcquireContext(&cryptoProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET); status = success ? ERROR_SUCCESS : GetLastError(); } if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't acquire crypto context for decrypt. status %1!u!.\n", status); return status; } } status = GenSymKey(cryptoProvider, encryptedInfo->InitData.Salt, encryptedInfo->InitData.IV, &sessionKey); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't generate session key for decrypt. status %1!u!.\n", status); goto cleanup; } // // CryptDecrypt writes the decrypted data back into the buffer that was // holding the encrypted data. For this reason, allocate a new buffer that // will eventually contain the password. // pwdByteLength = ( LM20_PWLEN + 1 ) * sizeof( WCHAR ); pwdBufferSize = ( pwdByteLength > encDataLength ? pwdByteLength : encDataLength ); machinePwd = HeapAlloc( GetProcessHeap(), 0, pwdBufferSize ); if ( machinePwd != NULL ) { RtlCopyMemory( machinePwd, encryptedInfo->Data, encDataLength ); if ( CryptDecrypt(sessionKey, 0, TRUE, 0, (PBYTE)machinePwd, &encDataLength)) { ASSERT( pwdByteLength == encDataLength ); wcscpy( MachinePwd, machinePwd ); } else { status = GetLastError(); (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't decrypt %1!u! bytes of data. status %2!u!.\n", encDataLength, status); goto cleanup; } } else { status = ERROR_NOT_ENOUGH_MEMORY; (NetNameLogEvent)(ResourceHandle, LOG_ERROR, L"Can't allocate %1!u! bytes for decrypt. status %2!u!.\n", pwdBufferSize, status); goto cleanup; } cleanup: if ( machinePwd != NULL) { HeapFree( GetProcessHeap(), 0, machinePwd ); } if ( sessionKey != 0 ) { if ( !CryptDestroyKey( sessionKey )) { (NetNameLogEvent)(ResourceHandle, LOG_WARNING, L"Couldn't destory session key. status %1!u!.\n", GetLastError()); } } if ( !CryptReleaseContext( cryptoProvider, 0 )) { (NetNameLogEvent)(ResourceHandle, LOG_WARNING, L"Can't release crypto context. status %1!u!.\n", GetLastError()); } return status; } // DecryptNetNameData /* end crypto.c */