|
|
/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
crypto.c
Abstract:
routines for encrypting/decrypting resource data blob. uses crypto API to generate the key used to encrypt/decrypt the CO password. Key is stored as a crypto checkpoint associated with the resource.
Author:
Charlie Wickham (charlwi) 14-Feb-2001
Environment:
User Mode
Revision History:
--*/
#define UNICODE 1
#define _UNICODE 1
#include "clusres.h"
#include "clusrtl.h"
#include "netname.h"
#include <wincrypt.h>
#include <lm.h>
//
// defines
//
#define NN_GUID_STRING_BUFFER_LENGTH 37 // includes the terminating NULL
//
// header for encrypted data.
//
typedef struct _NETNAME_ENCRYPTED_DATA { DWORD Version; BYTE Data[0]; } NETNAME_ENCRYPTED_DATA, *PNETNAME_ENCRYPTED_DATA;
#define NETNAME_ENCRYPTED_DATA_VERSION 1
//
// Container name is the resource's GUID followed by this decoration
//
WCHAR KeyDecoration[] = L"-Netname Resource Data";
DWORD BuildKeyName( IN HRESOURCE ResourceHandle, IN LPWSTR KeyName, IN DWORD KeyNameChars )
/*++
Routine Description:
build the key name (resource GUID followed by decoration)
Arguments:
ResourceHandle - handle to the cluster resource (not the one given to us by resmon)
KeyName - buffer to receive the constructed name
KeyNameChars - size, in characteres, of KeyName
Return Value:
success, otherwise Win32 error
--*/
{ DWORD status; DWORD bytesReturned; DWORD charsReturned;
//
// sanity check
//
if ( KeyNameChars < ( NN_GUID_STRING_BUFFER_LENGTH + COUNT_OF( KeyDecoration ))) { return ERROR_INSUFFICIENT_BUFFER; }
//
// get our GUID (ID) to uniquely identify this resource throughout renames
//
status = ClusterResourceControl(ResourceHandle, NULL, CLUSCTL_RESOURCE_GET_ID, NULL, 0, KeyName, KeyNameChars * sizeof( WCHAR ), &bytesReturned);
charsReturned = bytesReturned / sizeof( WCHAR );
if ( status == ERROR_SUCCESS ) { if (( charsReturned + COUNT_OF( KeyDecoration )) <= KeyNameChars ) { wcscat( KeyName, KeyDecoration ); } else { status = ERROR_INSUFFICIENT_BUFFER; } }
return status; } // BuildKeyName
DWORD FindNNCryptoContainer( IN PNETNAME_RESOURCE Resource, OUT LPWSTR * ContainerName )
/*++
Routine Description:
find our key name in the list of crypto checkpoints associated with this resource.
Arguments:
Resource - pointer to resource context info
ContainerName - address of pointer that gets pointer to container name
Return Value:
success if it worked, otherwise Win32 error
--*/
{ DWORD status; DWORD bytesReturned; LPWSTR checkpointInfo = NULL; LPWSTR chkpt; WCHAR keyName[ NN_GUID_STRING_BUFFER_LENGTH + COUNT_OF( KeyDecoration ) ];
RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
//
// get our GUID (ID) to uniquely identify this resource throughout renames
//
status = ClusterResourceControl(Resource->ClusterResourceHandle, NULL, CLUSCTL_RESOURCE_GET_CRYPTO_CHECKPOINTS, NULL, 0, NULL, 0, &bytesReturned);
if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get size of crypto checkpoint info. status %1!u!.\n", status);
return status; }
if ( bytesReturned == 0 ) { return ERROR_FILE_NOT_FOUND; }
checkpointInfo = LocalAlloc( LMEM_FIXED, bytesReturned ); if ( checkpointInfo == NULL ) { status = ERROR_NOT_ENOUGH_MEMORY; (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't allocate memory for resource's crypto checkpoint info. status %1!u!.\n", status); return status; }
status = ClusterResourceControl(Resource->ClusterResourceHandle, NULL, CLUSCTL_RESOURCE_GET_CRYPTO_CHECKPOINTS, NULL, 0, checkpointInfo, bytesReturned, &bytesReturned);
if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get crypto checkpoint info. status %1!u!.\n", status);
goto cleanup; }
//
// build our key name and look for it by walking the list of checkpoints
//
status = BuildKeyName(Resource->ClusterResourceHandle, keyName, COUNT_OF( keyName )); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't build key name for crypto checkpoint. status %1!u!.\n", status);
goto cleanup; }
chkpt = wcsstr( checkpointInfo, keyName ); if ( chkpt == NULL ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't find key name (%1!ws!) in list of crypto checkpoints.\n", keyName);
status = ERROR_INVALID_DATA; goto cleanup; }
//
// find the beginning of the string or the buffer, get the size, and move
// our string to the beginning of the buffer (which is freed by the
// caller)
//
while ( chkpt != checkpointInfo && *chkpt != UNICODE_NULL ) { --chkpt; }
if ( chkpt != checkpointInfo ) { DWORD stringBytes;
++chkpt; stringBytes = (wcslen( chkpt ) + 1 ) * sizeof( WCHAR ); memmove( checkpointInfo, chkpt, stringBytes ); }
*ContainerName = checkpointInfo;
cleanup: if ( status != ERROR_SUCCESS && checkpointInfo != NULL ) { LocalFree( checkpointInfo ); *ContainerName = NULL; }
return status; } // FindNNCryptoContainer
//
// exported routines
//
DWORD EncryptNNResourceData( PNETNAME_RESOURCE Resource, LPWSTR MachinePwd, PBYTE * EncryptedInfo, PDWORD EncryptedInfoLength )
/*++
Routine Description:
encrypt the password, set a pointer to the encrypted data and store it in the registry.
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 ); DWORD provNameLength = 0; PCHAR provName = NULL; DWORD provTypeLength; WCHAR typeBuffer[ 256 ]; DWORD containerNameChars; PWCHAR containerName = NULL; WCHAR keyName[ NN_GUID_STRING_BUFFER_LENGTH + COUNT_OF( KeyDecoration ) ];
HCRYPTPROV cryptoProvider = 0; HCRYPTKEY encryptKey = 0;
RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
NETNAME_ENCRYPTED_DATA keyGenBuffer; // temp header buffer to generate key
PNETNAME_ENCRYPTED_DATA encryptedInfo = NULL; // final data area
//
// there shouldn't be a checkpoint on the resource but just in case, let's
// cleanup what might be there.
//
RemoveNNCryptoCheckpoint( Resource );
//
// get our GUID (ID) to uniquely identify this resource throughout renames
//
status = BuildKeyName( Resource->ClusterResourceHandle, keyName, COUNT_OF( keyName )); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get resource ID to build crypto container name. status %1!u!.\n", status);
return status; }
//
// get a handle to the full RSA provider
//
if ( !CryptAcquireContext(&cryptoProvider, keyName, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { status = GetLastError(); if ( status == NTE_BAD_KEYSET ) { success = CryptAcquireContext(&cryptoProvider, keyName, MS_ENHANCED_PROV, 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 a 1024 bit, exportable exchange key pair
//
if ( !CryptGenKey(cryptoProvider, AT_KEYEXCHANGE, ( 1024 << 16 ) | CRYPT_EXPORTABLE, &encryptKey)) {
status = GetLastError(); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Can't generate exchange key for encryption. status %1!u!.\n", status); goto cleanup; } }
//
// find the size we need for the buffer to receive the encrypted data
//
encDataLength = pwdLength; if ( CryptEncrypt(encryptKey, 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 = LocalAlloc( LMEM_FIXED, encInfoLength ); if ( encryptedInfo != NULL ) { wcscpy( (PWCHAR)encryptedInfo->Data, MachinePwd );
if ( CryptEncrypt(encryptKey, 0, TRUE, 0, encryptedInfo->Data, &pwdLength, encDataLength)) { encryptedInfo->Version = NETNAME_ENCRYPTED_DATA_VERSION;
status = ResUtilSetBinaryValue(Resource->ParametersKey, PARAM_NAME__RESOURCE_DATA, (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;
//
// it all worked; build the key container string and add a crypto
// checkpoint to the resource. Note that provider name is always returned
// as an ANSI string.
//
typeBuffer[ COUNT_OF( typeBuffer ) - 1 ] = UNICODE_NULL; _snwprintf( typeBuffer, COUNT_OF( typeBuffer ) - 1, L"%u", PROV_RSA_FULL );
if ( !CryptGetProvParam(cryptoProvider, PP_NAME, NULL, &provNameLength, 0)) { status = GetLastError(); (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get length of provider name. status %1!u!.\n", status); goto cleanup; }
provName = LocalAlloc( LMEM_FIXED, provNameLength ); if ( provName == NULL ) { status = ERROR_NOT_ENOUGH_MEMORY; (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't allocate memory for provider name. status %1!u!.\n", status); goto cleanup; }
if ( !CryptGetProvParam(cryptoProvider, PP_NAME, provName, &provNameLength, 0)) { status = GetLastError(); (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get provider name. status %1!u!.\n", status); goto cleanup; }
//
// add 2 for the slashes in the key name plus one for the trailing null
//
containerNameChars = wcslen( typeBuffer ) + provNameLength + wcslen( keyName ) + 3; containerName = LocalAlloc( LMEM_FIXED, containerNameChars * sizeof( WCHAR )); if ( containerName == NULL ) { status = ERROR_NOT_ENOUGH_MEMORY; (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't allocate memory for checkpoint name. status %1!u!.\n", status); goto cleanup; }
containerName[ containerNameChars - 1 ] = UNICODE_NULL; containerNameChars = _snwprintf(containerName, containerNameChars, L"%ws%\\%hs\\%ws", typeBuffer, provName, keyName );
status = ClusterResourceControl(Resource->ClusterResourceHandle, NULL, CLUSCTL_RESOURCE_ADD_CRYPTO_CHECKPOINT, (PVOID)containerName, ( containerNameChars + 1 ) * sizeof( WCHAR ), NULL, 0, NULL);
if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't set crypto checkpoint. status %1!u!.\n", status); }
cleanup:
if ( status != ERROR_SUCCESS && encryptedInfo != NULL ) { LocalFree( encryptedInfo ); *EncryptedInfo = NULL; }
if ( encryptKey != 0 ) { if ( !CryptDestroyKey( encryptKey )) { (NetNameLogEvent)(resourceHandle, LOG_WARNING, L"Couldn't destory encryption 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()); }
if ( provName != NULL ) { LocalFree( provName ); }
if ( containerName != NULL ) { LocalFree( containerName ); }
return status; } // EncryptNNResourceData
DWORD DecryptNNResourceData( PNETNAME_RESOURCE Resource, PBYTE EncryptedInfo, DWORD EncryptedInfoLength, LPWSTR MachinePwd )
/*++
Routine Description:
Reverse of encrypt routine - find our crypto checkpoint container and 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 = ERROR_SUCCESS; DWORD encDataLength = EncryptedInfoLength - sizeof( NETNAME_ENCRYPTED_DATA ); DWORD pwdByteLength; DWORD pwdBufferSize; PWCHAR machinePwd = NULL; PWCHAR containerName = NULL; DWORD providerType; PWCHAR providerName; PWCHAR keyName; PWCHAR p; // for scanning checkpointInfo
DWORD scanCount;
HCRYPTPROV cryptoProvider = 0; HCRYPTKEY encryptKey = 0;
RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
PNETNAME_ENCRYPTED_DATA encryptedInfo = (PNETNAME_ENCRYPTED_DATA)EncryptedInfo;
//
// find our container name in this resource's list of crypto checkpoints
//
status = FindNNCryptoContainer( Resource, &containerName ); if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't find resource's container in crypto checkpoint info. status %1!u!.\n", status);
return status; }
//
// break returned data into component parts
//
scanCount = swscanf( containerName, L"%d", &providerType ); if ( scanCount == 0 || scanCount == EOF ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Improperly formatted crypto checkpoint info \"%1!ws!\"\n", containerName);
status = ERROR_INVALID_PARAMETER; goto cleanup; }
p = containerName; while ( *p != L'\\' && *p != UNICODE_NULL ) ++p; // find backslash
if ( *p == UNICODE_NULL ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Improperly formatted crypto checkpoint info \"%1!ws!\"\n", containerName);
status = ERROR_INVALID_PARAMETER; goto cleanup; }
++p; // skip over slash
providerName = p; // remember beginning of provider name
while ( *p != L'\\' && *p != UNICODE_NULL ) ++p; // find backslash
if ( *p == UNICODE_NULL ) { (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Improperly formatted crypto checkpoint info \"%1!ws!\"\n", containerName);
status = ERROR_INVALID_PARAMETER; goto cleanup; }
*p++ = UNICODE_NULL; // terminate provider name and skip over NULL
keyName = p; // remember container name
//
// get a handle to what was checkpointed
//
if ( !CryptAcquireContext(&cryptoProvider, keyName, providerName, providerType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { status = GetLastError(); (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Can't acquire crypto context for container %1!ws! with provider " L"\"%2!u!\\%3!ws!\". status %4!u!.\n", keyName, providerType, providerName, status);
goto cleanup; }
//
// now get a handle to the exchange key
//
if ( ! CryptGetUserKey(cryptoProvider, AT_KEYEXCHANGE, &encryptKey)) { status = GetLastError(); (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't get size of crypto checkpoint info. 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 = LocalAlloc( LMEM_FIXED, pwdBufferSize ); if ( machinePwd != NULL ) { RtlCopyMemory( machinePwd, encryptedInfo->Data, encDataLength );
if ( CryptDecrypt(encryptKey, 0, TRUE, 0, (PBYTE)machinePwd, &encDataLength)) { p = machinePwd;
ASSERT( pwdByteLength == encDataLength ); wcscpy( MachinePwd, machinePwd );
while ( *p != UNICODE_NULL ) { *p++ = UNICODE_NULL; } } 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); }
cleanup:
if ( machinePwd != NULL) { LocalFree( machinePwd ); }
if ( encryptKey != 0 ) { if ( !CryptDestroyKey( encryptKey )) { (NetNameLogEvent)(resourceHandle, LOG_WARNING, L"Couldn't destory session key. status %1!u!.\n", GetLastError()); } }
if ( cryptoProvider != 0 ) { if ( !CryptReleaseContext( cryptoProvider, 0 )) { (NetNameLogEvent)(resourceHandle, LOG_WARNING, L"Can't release crypto context. status %1!u!.\n", GetLastError()); } }
if ( containerName != NULL ) { LocalFree( containerName ); }
return status; } // DecryptNNResourceData
VOID RemoveNNCryptoCheckpoint( PNETNAME_RESOURCE Resource )
/*++
Routine Description:
Remove any crypto checkpoints associated with this resource. Delete the crypto container.
Arguments:
Resource - pointer to resource context block
Return Value:
None
--*/
{ PWCHAR containerName = NULL; DWORD containerLength; DWORD status; WCHAR keyName[ NN_GUID_STRING_BUFFER_LENGTH + COUNT_OF( KeyDecoration ) ];
HCRYPTPROV cryptoProvider;
RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
//
// find our container name in this resource's list of crypto checkpoints
//
status = FindNNCryptoContainer( Resource, &containerName ); if ( status != ERROR_SUCCESS ) { return; }
//
// remove our container
//
containerLength = ( wcslen( containerName ) + 1 ) * sizeof( WCHAR ); status = ClusterResourceControl(Resource->ClusterResourceHandle, NULL, CLUSCTL_RESOURCE_DELETE_CRYPTO_CHECKPOINT, containerName, containerLength, NULL, 0, NULL);
if ( status != ERROR_SUCCESS ) { (NetNameLogEvent)(resourceHandle, LOG_WARNING, L"Couldn't remove crypto checkpoint \"%1!ws!\". status %2!u!.\n", containerName, status); }
//
// now delete the container; first, reconstruct the key name
//
status = BuildKeyName(Resource->ClusterResourceHandle, keyName, COUNT_OF( keyName )); if ( status == ERROR_SUCCESS ) {
if ( CryptAcquireContext(&cryptoProvider, keyName, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_DELETEKEYSET | CRYPT_MACHINE_KEYSET)) { (NetNameLogEvent)(resourceHandle, LOG_INFORMATION, L"Deleted crypto container \"%1!ws!\".\n", keyName); } else { status = GetLastError(); (NetNameLogEvent)(resourceHandle, LOG_ERROR, L"Couldn't delete crypto container \"%1!ws!\". status %2!08X!.\n", keyName, status); } } else { (NetNameLogEvent)(resourceHandle, LOG_WARNING, L"Couldn't build key container name to delete crypto container. status %1!u!.\n", status); }
if ( containerName != NULL ) { LocalFree( containerName ); }
} // RemoveNNCryptoCheckpoint
/* end crypto.c */
|