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.
5486 lines
149 KiB
5486 lines
149 KiB
/*++
|
|
|
|
Copyright (c) 1997, 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
keyman.cpp
|
|
|
|
Abstract:
|
|
|
|
This module contains routines to manage master keys on behalf of the
|
|
client. This includes retrieval, backup and restore.
|
|
|
|
|
|
Author:
|
|
|
|
Scott Field (sfield) 09-Sep-97
|
|
|
|
Revision History:
|
|
|
|
Scott Field (sfield) 01-Mar-98
|
|
Use files as the backing store.
|
|
Storage of all masterkey pieces a single atomic operation.
|
|
|
|
--*/
|
|
|
|
#include <pch.cpp>
|
|
#pragma hdrstop
|
|
|
|
#include <msaudite.h>
|
|
|
|
#define REENCRYPT_MASTER_KEY 1
|
|
#define ADD_MASTER_KEY_TO_CACHE 2
|
|
|
|
|
|
//
|
|
// preferred masterkey selection query/set
|
|
//
|
|
|
|
NTSTATUS
|
|
GetPreferredMasterKeyGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN OUT GUID *pguidMasterKey
|
|
);
|
|
|
|
BOOL
|
|
SetPreferredMasterKeyGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN GUID *pguidMasterKey
|
|
);
|
|
|
|
//
|
|
// masterkey creation and query
|
|
//
|
|
|
|
DWORD
|
|
CreateMasterKey(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
OUT GUID *pguidMasterKey,
|
|
IN BOOL fRequireBackup
|
|
);
|
|
|
|
BOOL
|
|
GetMasterKeyByGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN PSID pSid,
|
|
IN BOOL fMigrate,
|
|
IN GUID *pguidMasterKey,
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey,
|
|
OUT DWORD *pdwMasterKeyDisposition // refer to MK_DISP_ constants
|
|
);
|
|
|
|
BOOL
|
|
GetMasterKey(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN PSID pSid,
|
|
IN BOOL fMigrate,
|
|
IN WCHAR wszMasterKey[MAX_GUID_SZ_CHARS],
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey,
|
|
OUT DWORD *pdwMasterKeyDisposition
|
|
);
|
|
|
|
//
|
|
// helper functions used during key retrieval and storage.
|
|
//
|
|
|
|
BOOL
|
|
ReadMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
);
|
|
|
|
BOOL
|
|
WriteMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
);
|
|
|
|
BOOL
|
|
CheckToStompMasterKey(
|
|
IN PMASTERKEY_STORED_ON_DISK phMasterKeyCandidate, // masterkey to check if worthy to stomp over existing
|
|
IN HANDLE hFile, // file handle to existing masterkey
|
|
IN OUT BOOL *pfStomp // stomp the existing masterkey?
|
|
);
|
|
|
|
BOOL
|
|
DuplicateMasterKey(
|
|
IN PMASTERKEY_STORED phMasterKeyIn,
|
|
IN PMASTERKEY_STORED phMasterKeyOut
|
|
);
|
|
|
|
BOOL
|
|
CloseMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN BOOL fPersist // persist any changes to storage?
|
|
);
|
|
|
|
VOID
|
|
FreeMasterKey(
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
);
|
|
|
|
//
|
|
// low-level crypto enabled key persistence query/set
|
|
//
|
|
|
|
DWORD
|
|
DecryptMasterKeyFromStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
OUT BOOL *pfUpgradeEncryption,
|
|
OUT PBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey
|
|
);
|
|
|
|
DWORD
|
|
DecryptMasterKeyToMemory(
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN PBYTE pbMasterKeyIn,
|
|
IN DWORD cbMasterKeyIn,
|
|
OUT BOOL *pfUpgradeEncryption,
|
|
OUT PBYTE *ppbMasterKeyOut,
|
|
OUT DWORD *pcbMasterKeyOut
|
|
);
|
|
|
|
DWORD
|
|
EncryptMasterKeyToStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey
|
|
);
|
|
|
|
DWORD
|
|
EncryptMasterKeyToMemory(
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN DWORD cIterationCount,
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
OUT PBYTE *ppbMasterKeyOut,
|
|
OUT DWORD *pcbMasterKeyOut
|
|
);
|
|
|
|
DWORD
|
|
PersistMasterKeyToStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN PBYTE pbMasterKeyOut,
|
|
IN DWORD cbMasterKeyOut
|
|
);
|
|
|
|
DWORD
|
|
QueryMasterKeyFromStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN OUT PBYTE *ppbMasterKeyOut,
|
|
IN OUT DWORD *pcbMasterKeyOut
|
|
);
|
|
|
|
//
|
|
// per-user credential derivation
|
|
//
|
|
|
|
BOOL
|
|
GetMasterKeyUserEncryptionKey(
|
|
IN PVOID pvContext,
|
|
IN GUID *pCredentialID,
|
|
IN PSID pSid,
|
|
IN DWORD dwFlags,
|
|
IN OUT BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN]
|
|
);
|
|
|
|
BOOL
|
|
GetLocalKeyUserEncryptionKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN OUT BYTE rgbLKEncrytionKey[A_SHA_DIGEST_LEN]
|
|
);
|
|
|
|
//
|
|
// backup/restore operations.
|
|
//
|
|
|
|
BOOL
|
|
IsBackupMasterKeyRequired(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN OUT BOOL *pfPhaseTwo // is phase two required?
|
|
);
|
|
|
|
DWORD
|
|
BackupMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN LPBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
IN BOOL fPhaseTwo, // is phase two required?
|
|
IN BOOL fAsynchronous // asynchronous call?
|
|
);
|
|
|
|
DWORD
|
|
QueueBackupMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN PBYTE pbLocalKey,
|
|
IN DWORD cbLocalKey,
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
IN DWORD dwWaitTimeout // amount of time to wait for operation to complete
|
|
);
|
|
|
|
DWORD
|
|
RestoreMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PSID pSid,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwReason,
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey
|
|
);
|
|
|
|
|
|
//
|
|
// asyncrhonous work functions for:
|
|
// 1. Backup operations
|
|
// 2. Masterkey synchronization operations
|
|
//
|
|
|
|
DWORD
|
|
WINAPI
|
|
QueueBackupMasterKeyThreadFunc(
|
|
IN LPVOID lpThreadArgument
|
|
);
|
|
|
|
DWORD
|
|
WINAPI
|
|
QueueSyncMasterKeysThreadFunc(
|
|
IN LPVOID lpThreadArgument
|
|
);
|
|
|
|
|
|
//
|
|
// backup/restore policy operations
|
|
//
|
|
|
|
BOOL
|
|
InitializeMasterKeyPolicy(
|
|
IN PVOID pvContext,
|
|
IN MASTERKEY_STORED *phMasterKey,
|
|
OUT BOOL *fLocalAccount
|
|
);
|
|
|
|
BOOL
|
|
IsDomainBackupRequired(
|
|
IN PVOID pvContext
|
|
);
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
InitiateSynchronizeMasterKeys(
|
|
IN PVOID pvContext // server context
|
|
)
|
|
/*++
|
|
|
|
Force Synchronization of all masterkeys associated with the caller.
|
|
|
|
This can include per-machine keys if the call was made with the per-machine
|
|
flag turned on. Otherwise, the masterkeys associated with the client
|
|
user security context are synchronized.
|
|
|
|
Synchronization is required to support a variety of login credential
|
|
change scenarios:
|
|
|
|
1. Domain Administrator assigns new password to user.
|
|
2. User changes password locally.
|
|
3. User changes password from another machine on the network.
|
|
4. User which is primarily disconnected from the network requests new
|
|
password from Domain Administrator, connect to network long enough
|
|
to refresh Netlogon cache with new credential.
|
|
|
|
--*/
|
|
{
|
|
PQUEUED_SYNC pQueuedSync = NULL;
|
|
DWORD cbQueuedSync = sizeof(QUEUED_SYNC);
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "SynchronizeMasterKeys\n"));
|
|
|
|
pQueuedSync = (PQUEUED_SYNC)SSAlloc( cbQueuedSync );
|
|
if( pQueuedSync == NULL ) {
|
|
dwLastError = ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ZeroMemory( pQueuedSync, cbQueuedSync );
|
|
pQueuedSync->cbSize = cbQueuedSync;
|
|
|
|
//
|
|
// duplicate the outstanding server context.
|
|
//
|
|
|
|
dwLastError = CPSDuplicateContext(pvContext, &(pQueuedSync->pvContext));
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
goto cleanup;
|
|
|
|
#if 1
|
|
|
|
//
|
|
// create the worker thread to handle the synchronize request.
|
|
//
|
|
|
|
if( !QueueUserWorkItem(
|
|
QueueSyncMasterKeysThreadFunc,
|
|
pQueuedSync,
|
|
WT_EXECUTELONGFUNCTION
|
|
))
|
|
{
|
|
dwLastError = GetLastError();
|
|
}
|
|
|
|
#else
|
|
|
|
//
|
|
// Perform the synchronization in the current thread.
|
|
//
|
|
|
|
dwLastError = SynchronizeMasterKeys(pQueuedSync->pvContext, 0);
|
|
|
|
if(pQueuedSync->pvContext)
|
|
{
|
|
CPSFreeContext( pQueuedSync->pvContext );
|
|
}
|
|
|
|
if( pQueuedSync )
|
|
SSFree( pQueuedSync );
|
|
|
|
dwLastError = ERROR_SUCCESS;
|
|
|
|
#endif
|
|
|
|
|
|
cleanup:
|
|
|
|
if( dwLastError != ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// free resources locally since a thread was not successfully created;
|
|
// normally, the worker thread will free these resources.
|
|
//
|
|
|
|
if( pQueuedSync ) {
|
|
|
|
if( pQueuedSync->pvContext )
|
|
CPSFreeContext( pQueuedSync->pvContext );
|
|
|
|
SSFree( pQueuedSync );
|
|
}
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
QueueSyncMasterKeysThreadFunc(
|
|
IN LPVOID lpThreadArgument
|
|
)
|
|
/*++
|
|
|
|
This routine performs asyncronous masterkey synchronization associated
|
|
with the client security context that invoked the operation.
|
|
|
|
All masterkeys associated with the security context are queried which
|
|
in turn causes a re-encrypt/sync if necessary.
|
|
|
|
--*/
|
|
{
|
|
PQUEUED_SYNC pQueuedSync = (PQUEUED_SYNC)lpThreadArgument;
|
|
PVOID pvContext = NULL;
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
|
|
if( pQueuedSync == NULL ||
|
|
pQueuedSync->cbSize != sizeof(QUEUED_SYNC) ||
|
|
pQueuedSync->pvContext == NULL )
|
|
{
|
|
dwLastError = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
pvContext = pQueuedSync->pvContext;
|
|
|
|
dwLastError = SynchronizeMasterKeys(pvContext, 0);
|
|
|
|
cleanup:
|
|
|
|
RevertToSelf();
|
|
|
|
if( pvContext )
|
|
{
|
|
CPSFreeContext( pvContext );
|
|
}
|
|
|
|
if( pQueuedSync )
|
|
SSFree( pQueuedSync );
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ReencryptMasterKey
|
|
//
|
|
// Synopsis: Read in the specified (machine) master key file, encrypt it
|
|
// using the current DPAPI LSA secret, and write it back out.
|
|
// This routine is only called when updating the DPAPI LSA
|
|
// secret (e.g., by sysprep).
|
|
//
|
|
// Arguments: [pvContext] -- Server context.
|
|
//
|
|
// [pLogonId] -- User logon session.
|
|
//
|
|
// [pszUserStorageArea] -- Path to user profile.
|
|
//
|
|
// [pszFilename] -- Filename of the master key file.
|
|
//
|
|
// Returns: ERROR_SUCCESS if the operation was successful, a Windows
|
|
// error code otherwise.
|
|
//
|
|
// History:
|
|
//
|
|
// Notes: This function should only be called for machine master keys,
|
|
// since these are typically the only ones that are encrypted
|
|
// using the LSA secret.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
DWORD
|
|
WINAPI
|
|
ReencryptMasterKey(
|
|
PVOID pvContext,
|
|
PLUID pLogonId,
|
|
LPWSTR pszUserStorageArea,
|
|
LPWSTR pszFilename)
|
|
{
|
|
MASTERKEY_STORED hMasterKey;
|
|
DWORD cbFilePath;
|
|
LPBYTE pbMasterKey;
|
|
DWORD cbMasterKey;
|
|
GUID guidMasterKey;
|
|
BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN];
|
|
BOOL fUserCredentialValid;
|
|
GUID CredentialID;
|
|
DWORD dwLastError;
|
|
|
|
//
|
|
// Validate input parameters.
|
|
//
|
|
|
|
if((pszUserStorageArea == NULL) || (pszFilename == NULL))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(WSZ_BYTECOUNT(pszFilename) > sizeof(hMasterKey.wszguidMasterKey))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize master key memory block.
|
|
//
|
|
|
|
ZeroMemory( &hMasterKey, sizeof(hMasterKey) );
|
|
|
|
hMasterKey.fModified = TRUE;
|
|
|
|
cbFilePath = WSZ_BYTECOUNT(pszUserStorageArea);
|
|
hMasterKey.szFilePath = (LPWSTR)SSAlloc( cbFilePath );
|
|
if(hMasterKey.szFilePath == NULL)
|
|
{
|
|
dwLastError = ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
return dwLastError;
|
|
}
|
|
|
|
CopyMemory(hMasterKey.szFilePath, pszUserStorageArea, cbFilePath);
|
|
CopyMemory(hMasterKey.wszguidMasterKey, pszFilename, WSZ_BYTECOUNT(pszFilename));
|
|
|
|
|
|
//
|
|
// read the master key components into memory.
|
|
//
|
|
|
|
if(!ReadMasterKey( pvContext, &hMasterKey ))
|
|
{
|
|
D_DebugLog((DEB_WARN, "ReadMasterKey failed: 0x%x\n", GetLastError()));
|
|
CloseMasterKey(pvContext, &hMasterKey, FALSE);
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// read the master key from the cache
|
|
//
|
|
|
|
dwLastError = MyGuidFromStringW(hMasterKey.wszguidMasterKey, &guidMasterKey);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
CloseMasterKey(pvContext, &hMasterKey, FALSE);
|
|
return dwLastError;
|
|
}
|
|
|
|
pbMasterKey = NULL;
|
|
|
|
if(!SearchMasterKeyCache( pLogonId, &guidMasterKey, &pbMasterKey, &cbMasterKey ))
|
|
{
|
|
D_DebugLog((DEB_ERROR, "Master key %ls not found in cache!\n", hMasterKey.wszguidMasterKey));
|
|
CloseMasterKey(pvContext, &hMasterKey, FALSE);
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
|
|
|
|
//
|
|
// Get encryption key
|
|
//
|
|
|
|
ZeroMemory(&CredentialID, sizeof(CredentialID));
|
|
|
|
fUserCredentialValid = GetMasterKeyUserEncryptionKey(pvContext,
|
|
&CredentialID,
|
|
NULL,
|
|
USE_DPAPI_OWF | USE_ROOT_CREDENTIAL,
|
|
rgbMKEncryptionKey);
|
|
if(fUserCredentialValid)
|
|
{
|
|
hMasterKey.dwPolicy |= POLICY_DPAPI_OWF;
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_ERROR, "Unable to get user encryption key\n"));
|
|
CloseMasterKey(pvContext, &hMasterKey, FALSE);
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
|
|
|
|
//
|
|
// re-encrypt the masterkey.
|
|
//
|
|
|
|
dwLastError = EncryptMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_MASTER_KEY,
|
|
rgbMKEncryptionKey,
|
|
pbMasterKey,
|
|
cbMasterKey
|
|
);
|
|
|
|
SSFree(pbMasterKey);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
D_DebugLog((DEB_WARN, "Error encrypting master key!\n"));
|
|
CloseMasterKey(pvContext, &hMasterKey, FALSE);
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
//
|
|
// Save the master key to disk.
|
|
//
|
|
|
|
if(!CloseMasterKey(pvContext, &hMasterKey, TRUE))
|
|
{
|
|
D_DebugLog((DEB_WARN, "Error saving master key!\n"));
|
|
return ERROR_NOT_FOUND;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SynchronizeMasterKeys
|
|
//
|
|
// Synopsis: Enumerate all of the master keys, and update their encryption
|
|
// state as necessary.
|
|
//
|
|
// Arguments: [pvContext] -- Server context.
|
|
//
|
|
// [dwMode] -- Operation to perform on the master keys.
|
|
//
|
|
// Returns: ERROR_SUCCESS if the operation was successful, a Windows
|
|
// error code otherwise.
|
|
//
|
|
// History:
|
|
//
|
|
// Notes: By default, this function will read in each of the master
|
|
// keys belonging to the specified user. If necessary, a key
|
|
// recovery operation will be done, and the reencrypted key
|
|
// will be written back out to disk.
|
|
//
|
|
// If the dwMode parameter is non-zero, then one of the
|
|
// following operations will be done:
|
|
//
|
|
// ADD_MASTER_KEY_TO_CACHE
|
|
// Read each master key into the master key cache. Fail
|
|
// if any of the keys cannot be successfully read. This
|
|
// operation is done before the DPAPI LSA secret is updated.
|
|
//
|
|
// REENCRYPT_MASTER_KEY
|
|
// Re-encrypt each master key (from the cache), and write
|
|
// them back out to disk. This operation is performed after
|
|
// the DPAPI LSA secret is updated.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
DWORD
|
|
WINAPI
|
|
SynchronizeMasterKeys(
|
|
IN PVOID pvContext,
|
|
IN DWORD dwMode)
|
|
{
|
|
LPWSTR szUserStorageArea = NULL;
|
|
BOOL fImpersonated = FALSE;
|
|
DWORD cbUserStorageArea;
|
|
HANDLE hFindData = INVALID_HANDLE_VALUE;
|
|
// note: tis a shame that ? doesn't map to wildcard a single character...
|
|
// const WCHAR szFileName[] = L"????????-????-????-????-????????????";
|
|
const WCHAR szFileName[] = L"*";
|
|
LPWSTR szFileMatch = NULL;
|
|
DWORD cbFileMatch;
|
|
WIN32_FIND_DATAW FindFileData;
|
|
DWORD dwLastError;
|
|
PSID *apsidHistory = NULL;
|
|
DWORD cSids = 0;
|
|
DWORD iSid = 0;
|
|
LUID LogonId;
|
|
BOOL fLogonIdValid = FALSE;
|
|
GUID guidMasterKey;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "SynchronizeMasterKeys called\n"));
|
|
D_DebugLog((DEB_TRACE_API, " dwMode: 0x%x\n", dwMode));
|
|
|
|
|
|
//
|
|
// get LogonId associated with client security context.
|
|
//
|
|
|
|
dwLastError = CPSImpersonateClient( pvContext );
|
|
if( dwLastError == ERROR_SUCCESS )
|
|
{
|
|
if(GetThreadAuthenticationId(GetCurrentThread(), &LogonId))
|
|
{
|
|
fLogonIdValid = TRUE;
|
|
}
|
|
|
|
CPSRevertToSelf( pvContext );
|
|
}
|
|
|
|
|
|
//
|
|
// Get the sid history for this user, so
|
|
// we can sync all keys
|
|
//
|
|
dwLastError = CPSGetSidHistory(pvContext,
|
|
&apsidHistory,
|
|
&cSids);
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
for(iSid=0; iSid < cSids; iSid++)
|
|
{
|
|
//
|
|
// get the path to the per-user master key storage area on disk
|
|
//
|
|
|
|
dwLastError = CPSGetUserStorageArea( pvContext,
|
|
(iSid > 0)?apsidHistory[iSid]:NULL,
|
|
FALSE,
|
|
&szUserStorageArea );
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
if(dwLastError == ERROR_PATH_NOT_FOUND || dwLastError == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
dwLastError = ERROR_SUCCESS;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// build the wild card search path.
|
|
//
|
|
|
|
cbUserStorageArea = lstrlenW( szUserStorageArea ) * sizeof(WCHAR);
|
|
cbFileMatch = cbUserStorageArea + sizeof(szFileName);
|
|
|
|
szFileMatch = (LPWSTR)SSAlloc( cbFileMatch );
|
|
if(NULL == szFileMatch)
|
|
{
|
|
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
CopyMemory( szFileMatch, szUserStorageArea, cbUserStorageArea );
|
|
CopyMemory( ((LPBYTE)szFileMatch)+cbUserStorageArea, szFileName, sizeof(szFileName) );
|
|
|
|
|
|
//
|
|
// impersonate the client security context via the duplicated context.
|
|
//
|
|
|
|
dwLastError = CPSImpersonateClient( pvContext );
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
goto cleanup;
|
|
|
|
fImpersonated = TRUE;
|
|
|
|
//
|
|
// now enumerate the files looking for ones that look interesting.
|
|
//
|
|
|
|
hFindData = FindFirstFileW( szFileMatch, &FindFileData );
|
|
|
|
if( hFindData == INVALID_HANDLE_VALUE )
|
|
goto cleanup;
|
|
|
|
do {
|
|
LPBYTE pbMasterKey = NULL;
|
|
DWORD cbMasterKey = 0;
|
|
DWORD dwMasterKeyDisposition;
|
|
|
|
if( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
|
|
continue;
|
|
|
|
//
|
|
// ignore files which don't look like a textual GUID.
|
|
//
|
|
|
|
if( lstrlenW( FindFileData.cFileName ) != 36 )
|
|
continue;
|
|
|
|
if( FindFileData.cFileName[ 8 ] != L'-' ||
|
|
FindFileData.cFileName[ 13 ] != L'-' ||
|
|
FindFileData.cFileName[ 18 ] != L'-' ||
|
|
FindFileData.cFileName[ 23 ] != L'-' ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
switch(dwMode)
|
|
{
|
|
case ADD_MASTER_KEY_TO_CACHE:
|
|
|
|
//
|
|
// Add this master key to the master key cache. Abort the
|
|
// entire function if the operation is not successful for
|
|
// any reason.
|
|
//
|
|
|
|
if(!fLogonIdValid)
|
|
{
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
dwLastError = MyGuidFromStringW(FindFileData.cFileName, &guidMasterKey);
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
// Fetch the specified key.
|
|
if(!GetMasterKey(pvContext,
|
|
szUserStorageArea,
|
|
apsidHistory[iSid],
|
|
iSid > 0,
|
|
FindFileData.cFileName,
|
|
&pbMasterKey,
|
|
&cbMasterKey,
|
|
&dwMasterKeyDisposition))
|
|
{
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Add the key to the cache.
|
|
if(!InsertMasterKeyCache(&LogonId,
|
|
&guidMasterKey,
|
|
pbMasterKey,
|
|
cbMasterKey))
|
|
{
|
|
if(pbMasterKey)
|
|
{
|
|
RtlSecureZeroMemory( pbMasterKey, cbMasterKey );
|
|
SSFree( pbMasterKey );
|
|
}
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Scrub and free the master key.
|
|
if(pbMasterKey)
|
|
{
|
|
RtlSecureZeroMemory( pbMasterKey, cbMasterKey );
|
|
SSFree( pbMasterKey );
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case REENCRYPT_MASTER_KEY:
|
|
//
|
|
// The DPAPI LSA secret has changed, so read the master key
|
|
// from the cache and reencrypt it to storage. This mode will
|
|
// only be used for the local machine master keys.
|
|
//
|
|
|
|
if(!fLogonIdValid)
|
|
{
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
// Ignore the returned error code, since there's little
|
|
// we can do about it now...
|
|
ReencryptMasterKey(pvContext,
|
|
&LogonId,
|
|
szUserStorageArea,
|
|
FindFileData.cFileName);
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
//
|
|
// fetch the specified key; this will cause a credential re-sync
|
|
// if necessary.
|
|
//
|
|
|
|
if(GetMasterKey(
|
|
pvContext,
|
|
szUserStorageArea,
|
|
apsidHistory[iSid],
|
|
iSid > 0,
|
|
FindFileData.cFileName,
|
|
&pbMasterKey,
|
|
&cbMasterKey,
|
|
&dwMasterKeyDisposition
|
|
) )
|
|
|
|
{
|
|
// Scrub and free the master key.
|
|
if( pbMasterKey )
|
|
{
|
|
RtlSecureZeroMemory( pbMasterKey, cbMasterKey );
|
|
SSFree( pbMasterKey );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
} while( FindNextFileW( hFindData, &FindFileData ) );
|
|
|
|
dwLastError = ERROR_SUCCESS;
|
|
|
|
SSFree(szUserStorageArea);
|
|
szUserStorageArea = NULL;
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
if( pvContext )
|
|
{
|
|
if( fImpersonated )
|
|
CPSRevertToSelf( pvContext );
|
|
}
|
|
|
|
if( hFindData != INVALID_HANDLE_VALUE )
|
|
FindClose( hFindData );
|
|
|
|
if( szUserStorageArea )
|
|
SSFree( szUserStorageArea );
|
|
|
|
if( szFileMatch )
|
|
SSFree( szFileMatch );
|
|
|
|
if(apsidHistory)
|
|
SSFree( apsidHistory );
|
|
|
|
D_DebugLog((DEB_TRACE_API, "SynchronizeMasterKeys returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
VOID
|
|
DPAPISynchronizeMasterKeys(
|
|
IN HANDLE hUserToken)
|
|
{
|
|
CRYPT_SERVER_CONTEXT ServerContext;
|
|
BOOL fContextCreated = FALSE;
|
|
HANDLE hOldUser = NULL;
|
|
DWORD dwError;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPISynchronizeMasterKeys\n"));
|
|
|
|
//
|
|
// Create a server context.
|
|
//
|
|
|
|
if(hUserToken)
|
|
{
|
|
if(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_IMPERSONATE | TOKEN_READ,
|
|
TRUE,
|
|
&hOldUser))
|
|
{
|
|
hOldUser = NULL;
|
|
}
|
|
|
|
if(!ImpersonateLoggedOnUser(hUserToken))
|
|
{
|
|
dwError = GetLastError();
|
|
CloseHandle(hOldUser);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
dwError = CPSCreateServerContext(&ServerContext, NULL);
|
|
|
|
if(hOldUser)
|
|
{
|
|
if (!SetThreadToken(NULL, hOldUser))
|
|
{
|
|
if (ERROR_SUCCESS == dwError)
|
|
{
|
|
dwError = GetLastError();
|
|
fContextCreated = TRUE;
|
|
}
|
|
}
|
|
CloseHandle(hOldUser);
|
|
hOldUser = NULL;
|
|
}
|
|
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
fContextCreated = TRUE;
|
|
|
|
|
|
//
|
|
// Synchronize the master keys.
|
|
//
|
|
|
|
dwError = InitiateSynchronizeMasterKeys(&ServerContext);
|
|
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
if(fContextCreated)
|
|
{
|
|
CPSDeleteServerContext( &ServerContext );
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPISynchronizeMasterKeys returned 0x%x\n", dwError));
|
|
}
|
|
|
|
|
|
DWORD
|
|
GetSpecifiedMasterKey(
|
|
IN PVOID pvContext, // server context
|
|
IN OUT GUID *pguidMasterKey,
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey,
|
|
IN BOOL fSpecified // get specified pguidMasterKey key ?
|
|
)
|
|
/*++
|
|
|
|
This function returns the caller a decrypted master key.
|
|
If fSpecified is TRUE, the returned master key is the one specified by
|
|
the GUID pointed to by pguidMasterKey. Otherwise, the returned master key
|
|
is the preferred master key, and pguidMasterKey is filled with the GUID
|
|
value associated with the preferred master key.
|
|
|
|
The proper way to utilize the fSpecified parameter is to specify FALSE
|
|
when obtaining a masterkey associated with an encrypt operation;
|
|
specify TRUE and supply valid GUID in pguidMasterKey when doing a decrypt
|
|
operation. For an encrypt operation, the caller will store the GUID
|
|
returned in pguidMasterKey alongside any data encrypted with that master
|
|
key.
|
|
|
|
On success, the return value is ERROR_SUCCESS. The caller must free the buffer
|
|
pointed to by ppbMasterKey using SSFree() when finished with it. The
|
|
caller should keep this buffer around for the shortest possible time, to
|
|
avoid pagefile exposure.
|
|
|
|
On failure, the return value is not ERROR_SUCCESS. The caller need not free the
|
|
buffer pointed to ppbMasterKey.
|
|
|
|
--*/
|
|
{
|
|
|
|
LUID LogonId;
|
|
BOOL fCached = FALSE; // masterkey found in cache?
|
|
|
|
LPWSTR szUserStorageArea = NULL;
|
|
|
|
DWORD dwMasterKeyDisposition = 0;
|
|
|
|
DWORD dwLocalError;
|
|
DWORD dwLastError;
|
|
BOOL fSetPreferred = FALSE; // update preferred guid?
|
|
BOOL fSuccess = FALSE;
|
|
PSID *apsidHistory = NULL;
|
|
DWORD cSids = 0;
|
|
DWORD i;
|
|
NTSTATUS Status;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "GetSpecifiedMasterKey called\n"));
|
|
|
|
//
|
|
// get LogonId associated with client security context.
|
|
//
|
|
|
|
dwLastError = CPSImpersonateClient( pvContext );
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, "GetSpecifiedMasterKey returned 0x%x\n", dwLastError));
|
|
return dwLastError;
|
|
}
|
|
|
|
fSuccess = GetThreadAuthenticationId(GetCurrentThread(), &LogonId);
|
|
if( !fSuccess )
|
|
{
|
|
dwLastError = GetLastError();
|
|
}
|
|
CPSRevertToSelf( pvContext );
|
|
|
|
if( !fSuccess )
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, "GetSpecifiedMasterKey returned 0x%x\n", dwLastError));
|
|
return dwLastError;
|
|
}
|
|
|
|
fSuccess = FALSE;
|
|
|
|
|
|
//
|
|
// Get the preferred key GUID if no master key was specified by name.
|
|
//
|
|
|
|
if( !fSpecified )
|
|
{
|
|
//
|
|
// get the path to the per-user master key storage area on disk
|
|
//
|
|
|
|
dwLastError = CPSGetUserStorageArea( pvContext, NULL, TRUE, &szUserStorageArea );
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
D_DebugLog((DEB_WARN, "CPSGetUserStorageArea failed: 0x%x\n", dwLastError));
|
|
goto cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE, "Master key user path: %ls\n", szUserStorageArea));
|
|
|
|
|
|
//
|
|
// determine what is the preferred master key.
|
|
// if none exists, create one, and set it as being preferred.
|
|
//
|
|
|
|
Status = GetPreferredMasterKeyGuid( pvContext, szUserStorageArea, pguidMasterKey );
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
if(Status == STATUS_PASSWORD_EXPIRED)
|
|
{
|
|
GUID guidNewMasterKey;
|
|
|
|
// A preferred master key exists, but it has expired. Attempt to generate
|
|
// a new master key, but fall back to the old one if we're unable to
|
|
// create a proper backup for the new master key.
|
|
dwLastError = CreateMasterKey( pvContext, szUserStorageArea, &guidNewMasterKey, TRUE );
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
// Use new key.
|
|
memcpy(pguidMasterKey, &guidNewMasterKey, sizeof(GUID));
|
|
|
|
// update preferred guid.
|
|
fSetPreferred = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No preferred master key currently exists, so generate a new one.
|
|
dwLastError = CreateMasterKey( pvContext, szUserStorageArea, pguidMasterKey, FALSE );
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
// update preferred guid.
|
|
fSetPreferred = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// search cache for specified masterkey.
|
|
//
|
|
|
|
if(SearchMasterKeyCache( &LogonId, pguidMasterKey, ppbMasterKey, pcbMasterKey ))
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Master key found in cache.\n"));
|
|
|
|
fCached = TRUE;
|
|
fSuccess = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// get the path to the per-user master key storage area on disk
|
|
//
|
|
|
|
if(szUserStorageArea == NULL)
|
|
{
|
|
dwLastError = CPSGetUserStorageArea( pvContext, NULL, TRUE, &szUserStorageArea );
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
D_DebugLog((DEB_WARN, "CPSGetUserStorageArea failed: 0x%x\n", dwLastError));
|
|
goto cleanup;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE, "Master key user path: %ls\n", szUserStorageArea));
|
|
}
|
|
|
|
|
|
//
|
|
// If it's not in the cache, we need to load it.
|
|
// By default, we have the users primary sid
|
|
//
|
|
cSids = 1;
|
|
|
|
if(fSpecified)
|
|
{
|
|
//
|
|
// If the GUID was specified, we need to find it, so get
|
|
// the SID History so we can search all SIDS the user has
|
|
// been for this one.
|
|
//
|
|
dwLastError = CPSGetSidHistory(pvContext,
|
|
&apsidHistory,
|
|
&cSids);
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
D_DebugLog((DEB_WARN, "CPSGetSidHistory failed: 0x%x\n", dwLastError));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
for(i=0; i < cSids; i++)
|
|
{
|
|
|
|
if((fSpecified) && (i > 0))
|
|
{
|
|
// for sid's beyond the 0th one (the current user's sid),
|
|
// we need to grab the new storage area.
|
|
if(szUserStorageArea)
|
|
{
|
|
SSFree(szUserStorageArea);
|
|
szUserStorageArea = NULL;
|
|
}
|
|
dwLocalError = CPSGetUserStorageArea( pvContext,
|
|
apsidHistory[i],
|
|
FALSE,
|
|
&szUserStorageArea );
|
|
|
|
if(dwLocalError != ERROR_SUCCESS)
|
|
{
|
|
// There is no storage area for this SID, so try the next
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// get the master key.
|
|
//
|
|
|
|
fSuccess = GetMasterKeyByGuid(
|
|
pvContext,
|
|
szUserStorageArea,
|
|
(i > 0)?apsidHistory[i]:NULL,
|
|
i > 0,
|
|
pguidMasterKey,
|
|
ppbMasterKey,
|
|
pcbMasterKey,
|
|
&dwMasterKeyDisposition);
|
|
|
|
D_DebugLog((DEB_TRACE, "GetMasterKeyByGuid disposition: %s\n",
|
|
(dwMasterKeyDisposition == MK_DISP_OK) ? "Normal" :
|
|
(dwMasterKeyDisposition == MK_DISP_BCK_LCL) ? "Local backup" :
|
|
(dwMasterKeyDisposition == MK_DISP_BCK_DC) ? "DC backup" :
|
|
(dwMasterKeyDisposition == MK_DISP_STORAGE_ERR) ? "Storage error" :
|
|
(dwMasterKeyDisposition == MK_DISP_DELEGATION_ERR) ? "Delegation error" :
|
|
"Unknown error"));
|
|
|
|
if(!fSuccess)
|
|
{
|
|
if(MK_DISP_STORAGE_ERR != dwMasterKeyDisposition)
|
|
{
|
|
// The disposition was not a storage error, so the key does
|
|
// exist in this area, but there was some other error.
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// if this was an encrypt operation, and we failed to get at the preferred key,
|
|
// create a new key and set it preferred.
|
|
//
|
|
|
|
if(!fSuccess &&
|
|
!fSpecified &&
|
|
((dwMasterKeyDisposition == MK_DISP_STORAGE_ERR) ||
|
|
(dwMasterKeyDisposition == MK_DISP_DELEGATION_ERR) ))
|
|
{
|
|
dwLastError = CreateMasterKey( pvContext, szUserStorageArea, pguidMasterKey, FALSE );
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
fSuccess = GetMasterKeyByGuid(
|
|
pvContext,
|
|
szUserStorageArea,
|
|
NULL,
|
|
FALSE,
|
|
pguidMasterKey,
|
|
ppbMasterKey,
|
|
pcbMasterKey,
|
|
&dwMasterKeyDisposition
|
|
);
|
|
|
|
fSetPreferred = fSuccess;
|
|
}
|
|
|
|
if( fSuccess && fSetPreferred )
|
|
{
|
|
|
|
//
|
|
// masterkey creation succeeded, and usage of the key succeeded.
|
|
// set key as being preferred.
|
|
//
|
|
|
|
SetPreferredMasterKeyGuid( pvContext, szUserStorageArea, pguidMasterKey );
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
if(szUserStorageArea)
|
|
{
|
|
SSFree(szUserStorageArea);
|
|
}
|
|
|
|
if(apsidHistory)
|
|
{
|
|
SSFree(apsidHistory);
|
|
}
|
|
|
|
if(fSuccess)
|
|
{
|
|
//
|
|
// add entry to cache if it wasn't found there.
|
|
//
|
|
|
|
if( !fCached )
|
|
{
|
|
InsertMasterKeyCache( &LogonId, pguidMasterKey, *ppbMasterKey, *pcbMasterKey );
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "GetSpecifiedMasterKey returned 0x%x\n", ERROR_SUCCESS));
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
dwLastError = (DWORD)NTE_BAD_KEY_STATE;
|
|
}
|
|
|
|
if(MK_DISP_DELEGATION_ERR == dwMasterKeyDisposition)
|
|
{
|
|
dwLastError = (DWORD)SEC_E_DELEGATION_REQUIRED;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "GetSpecifiedMasterKey returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
CreateMasterKey(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
OUT GUID *pguidMasterKey,
|
|
IN BOOL fRequireBackup)
|
|
{
|
|
MASTERKEY_STORED hMasterKey;
|
|
DWORD cbFilePath;
|
|
|
|
BYTE pbMasterKey[ MASTERKEY_MATERIAL_SIZE ];
|
|
DWORD cbMasterKey = sizeof(pbMasterKey);
|
|
|
|
BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN]; // masterkey encryption key
|
|
|
|
BYTE pbLocalKey[ LOCALKEY_MATERIAL_SIZE ];
|
|
DWORD cbLocalKey = sizeof(pbLocalKey);
|
|
BYTE rgbLKEncryptionKey[A_SHA_DIGEST_LEN]; // localkey encryption key
|
|
|
|
BOOL fUserCredentialValid = FALSE;
|
|
|
|
DWORD dwLastError;
|
|
BOOL fSuccess = FALSE;
|
|
BOOL fLocalAccount = FALSE;
|
|
|
|
GUID CredentialID;
|
|
|
|
D_DebugLog((DEB_TRACE, "CreateMasterKey\n"));
|
|
|
|
ZeroMemory(&CredentialID, sizeof(CredentialID));
|
|
|
|
//
|
|
// generate new GUID
|
|
//
|
|
|
|
dwLastError = UuidCreate( pguidMasterKey );
|
|
|
|
if( dwLastError ) {
|
|
if( dwLastError == RPC_S_UUID_LOCAL_ONLY ) {
|
|
dwLastError = ERROR_SUCCESS;
|
|
} else {
|
|
return dwLastError;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// initialize masterkey
|
|
//
|
|
|
|
|
|
ZeroMemory( &hMasterKey, sizeof(hMasterKey));
|
|
hMasterKey.dwVersion = MASTERKEY_STORED_VERSION;
|
|
hMasterKey.fModified = TRUE;
|
|
|
|
//
|
|
// set initial (default) masterkey policy.
|
|
// Do this whenever we determine a new masterkey is created/selected.
|
|
// This allows us future flexibility if we want to pull policy bits
|
|
// from some admin defined place.
|
|
//
|
|
|
|
InitializeMasterKeyPolicy( pvContext, &hMasterKey , &fLocalAccount);
|
|
|
|
//
|
|
// copy path to key file into masterkey memory block.
|
|
//
|
|
|
|
cbFilePath = (lstrlenW( szUserStorageArea ) + 1) * sizeof(WCHAR);
|
|
hMasterKey.szFilePath = (LPWSTR)SSAlloc( cbFilePath );
|
|
if(hMasterKey.szFilePath == NULL)
|
|
return ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
|
|
CopyMemory(hMasterKey.szFilePath, szUserStorageArea, cbFilePath);
|
|
|
|
|
|
if( MyGuidToStringW( pguidMasterKey, hMasterKey.wszguidMasterKey ) != 0 )
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "Master key GUID:%ls\n", hMasterKey.wszguidMasterKey));
|
|
#endif
|
|
|
|
//
|
|
// generate random masterkey in memory.
|
|
//
|
|
|
|
if(!RtlGenRandom(pbMasterKey, cbMasterKey))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "Master key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", pbMasterKey, cbMasterKey);
|
|
#endif
|
|
|
|
|
|
//
|
|
// generate random localkey in memory.
|
|
//
|
|
|
|
if(!RtlGenRandom(pbLocalKey, cbLocalKey))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "Local key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", pbLocalKey, cbLocalKey);
|
|
#endif
|
|
|
|
|
|
//
|
|
// get current masterkey encryption key.
|
|
//
|
|
|
|
if(fLocalAccount)
|
|
{
|
|
fUserCredentialValid = GetMasterKeyUserEncryptionKey(pvContext,
|
|
&CredentialID,
|
|
NULL,
|
|
USE_DPAPI_OWF | USE_ROOT_CREDENTIAL,
|
|
rgbMKEncryptionKey);
|
|
if(fUserCredentialValid)
|
|
{
|
|
hMasterKey.dwPolicy |= POLICY_DPAPI_OWF;
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK Encryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
|
|
D_DebugLog((DEB_TRACE, "MK Encryption key GUID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", (PBYTE)&CredentialID, sizeof(CredentialID));
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "Unable to get SHA OWF user encryption key!\n"));
|
|
}
|
|
|
|
}
|
|
|
|
if(!fUserCredentialValid)
|
|
{
|
|
//
|
|
// If we couldn't use the DPAPI owf, then do something else
|
|
//
|
|
fUserCredentialValid = GetMasterKeyUserEncryptionKey(pvContext,
|
|
fLocalAccount?(&CredentialID):NULL,
|
|
NULL,
|
|
USE_ROOT_CREDENTIAL,
|
|
rgbMKEncryptionKey);
|
|
|
|
if(fUserCredentialValid)
|
|
{
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK Encryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
|
|
if(fLocalAccount)
|
|
{
|
|
D_DebugLog((DEB_TRACE, "MK Encryption key GUID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", (PBYTE)&CredentialID, sizeof(CredentialID));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "Unable to get NT OWF user encryption key!\n"));
|
|
}
|
|
}
|
|
|
|
//
|
|
// if the user credential is not intact or available, generate a random
|
|
// one for the time being. When fUserCredentialIntact is FALSE, we also
|
|
// do not attempt to backup/restore the key to phase 1 status.
|
|
// When fUserCredentialIntact eventually becomes TRUE, we will upgrade to
|
|
// phase 2 transparently.
|
|
//
|
|
|
|
if( !fUserCredentialValid )
|
|
{
|
|
|
|
//
|
|
// if no backup was specified in policy, we can't run with an
|
|
// random credential, as it won't be backed up to support temporary
|
|
// credential-less operation (eg: delegation).
|
|
//
|
|
|
|
if(fLocalAccount)
|
|
{
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(hMasterKey.dwPolicy & POLICY_NO_BACKUP)
|
|
{
|
|
dwLastError = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlGenRandom(rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK Encryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
#endif
|
|
}
|
|
|
|
|
|
if(fLocalAccount)
|
|
{
|
|
//
|
|
// Save the local backup information
|
|
//
|
|
|
|
LOCAL_BACKUP_DATA LocalBackupData;
|
|
|
|
LocalBackupData.dwVersion = MASTERKEY_BLOB_LOCALKEY_BACKUP;
|
|
CopyMemory(&LocalBackupData.CredentialID, &CredentialID, sizeof(CredentialID));
|
|
|
|
|
|
dwLastError = PersistMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
(PBYTE)&LocalBackupData,
|
|
sizeof(LocalBackupData)
|
|
);
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// get localkey user encryption key.
|
|
//
|
|
|
|
if(!GetLocalKeyUserEncryptionKey(pvContext, &hMasterKey, rgbLKEncryptionKey))
|
|
goto cleanup;
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "LK Encryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbLKEncryptionKey, sizeof(rgbLKEncryptionKey));
|
|
#endif
|
|
|
|
|
|
//
|
|
// now, encrypt and store the master key.
|
|
//
|
|
|
|
dwLastError = EncryptMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_MASTER_KEY,
|
|
rgbMKEncryptionKey,
|
|
pbMasterKey,
|
|
cbMasterKey
|
|
);
|
|
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
|
|
//
|
|
// now, encrypt and store the local key.
|
|
//
|
|
|
|
dwLastError = EncryptMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_LOCAL_KEY,
|
|
rgbLKEncryptionKey,
|
|
pbLocalKey,
|
|
cbLocalKey
|
|
);
|
|
}
|
|
|
|
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
BOOL fPhaseTwo = FALSE;
|
|
fSuccess = TRUE;
|
|
|
|
|
|
//
|
|
// after creation, do initial backup if necessary.
|
|
//
|
|
|
|
if(IsBackupMasterKeyRequired( &hMasterKey, &fPhaseTwo ))
|
|
{
|
|
DWORD dwBackupError;
|
|
|
|
|
|
dwBackupError = BackupMasterKey(
|
|
pvContext,
|
|
&hMasterKey,
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
fPhaseTwo, // phase two backup required?
|
|
fUserCredentialValid // async only if cred valid
|
|
);
|
|
|
|
if(dwBackupError != ERROR_SUCCESS)
|
|
{
|
|
if(!fUserCredentialValid || fRequireBackup)
|
|
{
|
|
//
|
|
// no valid credential, and backup failed, fail creation of
|
|
// this key.
|
|
//
|
|
|
|
dwLastError = SEC_E_DELEGATION_REQUIRED;
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
RtlSecureZeroMemory(pbMasterKey, sizeof(pbMasterKey));
|
|
RtlSecureZeroMemory(rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
RtlSecureZeroMemory(rgbLKEncryptionKey, sizeof(rgbLKEncryptionKey));
|
|
|
|
//
|
|
// note: it's possible for a race to occur closing the master key
|
|
// at this point, because the key may be backed up asynchronously.
|
|
// this isn't a problem because when a key is persisted to disk,
|
|
// we will not downgrade the backed up blob to non-backed up, as the
|
|
// CloseMasterKey() code includes logic to prevent that situation from
|
|
// occuring.
|
|
//
|
|
|
|
|
|
if(!CloseMasterKey(pvContext, &hMasterKey, fSuccess))
|
|
fSuccess = FALSE;
|
|
|
|
if(fSuccess)
|
|
return ERROR_SUCCESS;
|
|
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
dwLastError = ERROR_INVALID_PARAMETER;
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetMasterKeyByGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN PSID pSid,
|
|
IN BOOL fMigrate,
|
|
IN GUID *pguidMasterKey,
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey,
|
|
OUT DWORD *pdwMasterKeyDisposition // refer to MK_DISP_ constants
|
|
)
|
|
{
|
|
WCHAR wszguidMasterKey[MAX_GUID_SZ_CHARS];
|
|
|
|
*pdwMasterKeyDisposition = MK_DISP_UNKNOWN_ERR;
|
|
|
|
if( MyGuidToStringW( pguidMasterKey, wszguidMasterKey ) != 0 )
|
|
return FALSE;
|
|
|
|
return GetMasterKey(
|
|
pvContext,
|
|
szUserStorageArea,
|
|
pSid,
|
|
fMigrate,
|
|
wszguidMasterKey,
|
|
ppbMasterKey,
|
|
pcbMasterKey,
|
|
pdwMasterKeyDisposition
|
|
);
|
|
}
|
|
|
|
BOOL
|
|
GetMasterKey(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN PSID pSid,
|
|
IN BOOL fMigrate,
|
|
IN WCHAR wszMasterKey[MAX_GUID_SZ_CHARS],
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey,
|
|
OUT DWORD *pdwMasterKeyDisposition
|
|
)
|
|
{
|
|
MASTERKEY_STORED hMasterKey;
|
|
DWORD cbFilePath;
|
|
BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN]; // masterkey encryption key
|
|
DWORD dwLastError = (DWORD)NTE_BAD_KEY;
|
|
BOOL fUserCredentialValid;
|
|
BOOL fSuccess = FALSE;
|
|
BOOL fUpgradeEncryption = FALSE;
|
|
LPWSTR wszOldFilePath = NULL;
|
|
GUID CredentialID;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "GetMasterKey: %ls\n", wszMasterKey));
|
|
|
|
*pdwMasterKeyDisposition = MK_DISP_UNKNOWN_ERR;
|
|
|
|
ZeroMemory( &hMasterKey, sizeof(hMasterKey) );
|
|
|
|
hMasterKey.fModified = FALSE;
|
|
|
|
//
|
|
// copy path to key file into masterkey memory block.
|
|
//
|
|
|
|
cbFilePath = (lstrlenW( szUserStorageArea ) + 1) * sizeof(WCHAR);
|
|
hMasterKey.szFilePath = (LPWSTR)SSAlloc( cbFilePath );
|
|
if(hMasterKey.szFilePath == NULL)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_SERVER_MEMORY );
|
|
return FALSE;
|
|
}
|
|
|
|
CopyMemory(hMasterKey.szFilePath, szUserStorageArea, cbFilePath);
|
|
CopyMemory(hMasterKey.wszguidMasterKey, wszMasterKey, sizeof(hMasterKey.wszguidMasterKey));
|
|
|
|
//
|
|
// read the masterkey components into memory.
|
|
//
|
|
|
|
if(!ReadMasterKey( pvContext, &hMasterKey ))
|
|
{
|
|
D_DebugLog((DEB_WARN, "ReadMasterKey failed: 0x%x\n", GetLastError()));
|
|
SetLastError( (DWORD)NTE_BAD_KEY );
|
|
*pdwMasterKeyDisposition = MK_DISP_STORAGE_ERR;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// get current masterkey encryption key.
|
|
//
|
|
|
|
ZeroMemory(&CredentialID, sizeof(CredentialID));
|
|
|
|
fUserCredentialValid = GetMasterKeyUserEncryptionKey(pvContext,
|
|
&CredentialID,
|
|
pSid,
|
|
USE_ROOT_CREDENTIAL |
|
|
((hMasterKey.dwPolicy & POLICY_DPAPI_OWF)?USE_DPAPI_OWF:0),
|
|
rgbMKEncryptionKey);
|
|
|
|
if( fUserCredentialValid )
|
|
{
|
|
|
|
//
|
|
// retrieve and decrypt MK with current credential.
|
|
// if success, see if pending phase one/two backup required [make it so]
|
|
//
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK decryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbMKEncryptionKey, sizeof(rgbMKEncryptionKey));
|
|
#endif
|
|
|
|
dwLastError = DecryptMasterKeyFromStorage(
|
|
&hMasterKey,
|
|
REGVAL_MASTER_KEY,
|
|
rgbMKEncryptionKey,
|
|
&fUpgradeEncryption,
|
|
ppbMasterKey,
|
|
pcbMasterKey
|
|
);
|
|
|
|
#if DBG
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "Master key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", *ppbMasterKey, *pcbMasterKey);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "Decryption with current user MK failed\n"));
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "GetMasterKeyUserEncryptionKey failed: 0x%x\n", GetLastError()));
|
|
}
|
|
|
|
if( fUpgradeEncryption || fMigrate || (dwLastError != ERROR_SUCCESS ))
|
|
{
|
|
|
|
//
|
|
// if the MK fails to decrypt, attempt recovery.
|
|
// if recovery succeeds, re-encrypt MK with current credential.
|
|
//
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
|
|
|
|
dwLastError = RestoreMasterKey(
|
|
pvContext,
|
|
pSid,
|
|
&hMasterKey,
|
|
dwLastError,
|
|
ppbMasterKey,
|
|
pcbMasterKey
|
|
);
|
|
|
|
fUpgradeEncryption = TRUE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If this is a migration, we must get the current real user storage
|
|
// area, not the one that the key was retrieved from.
|
|
//
|
|
|
|
if((ERROR_SUCCESS == dwLastError) &&
|
|
(fMigrate))
|
|
{
|
|
wszOldFilePath = hMasterKey.szFilePath;
|
|
hMasterKey.szFilePath = NULL;
|
|
|
|
dwLastError = CPSGetUserStorageArea( pvContext,
|
|
NULL,
|
|
FALSE,
|
|
&hMasterKey.szFilePath );
|
|
}
|
|
|
|
//
|
|
// recovery succeeded, re-encrypt the masterkey if the user credential
|
|
// is valid.
|
|
// Also re-encrypt if fUpgradeEncryption indicates that we're
|
|
// not meeting current policy with this master key.
|
|
//
|
|
|
|
if( fUpgradeEncryption && (dwLastError == ERROR_SUCCESS ))
|
|
{
|
|
|
|
if( fUserCredentialValid )
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Update master key encryption.\n"));
|
|
|
|
dwLastError = EncryptMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_MASTER_KEY,
|
|
rgbMKEncryptionKey,
|
|
*ppbMasterKey,
|
|
*pcbMasterKey
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
D_DebugLog((DEB_WARN, "Error encrypting master key!\n"));
|
|
}
|
|
|
|
|
|
// Update the local backup information
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
{
|
|
LOCAL_BACKUP_DATA LocalBackupData;
|
|
|
|
if(hMasterKey.pbBK != NULL && hMasterKey.cbBK >= sizeof(LocalBackupData))
|
|
{
|
|
CopyMemory(&LocalBackupData, hMasterKey.pbBK, sizeof(LocalBackupData));
|
|
|
|
if(LocalBackupData.dwVersion == MASTERKEY_BLOB_LOCALKEY_BACKUP)
|
|
{
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "New MK encryption key GUID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", (PBYTE)&LocalBackupData.CredentialID, sizeof(LocalBackupData.CredentialID));
|
|
#endif
|
|
|
|
CopyMemory(&LocalBackupData.CredentialID, &CredentialID, sizeof(CredentialID));
|
|
|
|
PersistMasterKeyToStorage(
|
|
&hMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
(PBYTE)&LocalBackupData,
|
|
sizeof(LocalBackupData)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
|
|
//
|
|
// treat recovery failure as storage error so that a new key can
|
|
// be created for Protect operations.
|
|
//
|
|
if(dwLastError == SEC_E_DELEGATION_REQUIRED)
|
|
{
|
|
*pdwMasterKeyDisposition = MK_DISP_DELEGATION_ERR;
|
|
}
|
|
else
|
|
{
|
|
*pdwMasterKeyDisposition = MK_DISP_STORAGE_ERR;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if( dwLastError == ERROR_SUCCESS )
|
|
{
|
|
|
|
//
|
|
// after access, do backup if necessary.
|
|
// we check this each access to see if deferred backup required.
|
|
// (note: employ a back-off interval so we don't bang the network
|
|
// constantly when it isn't around).
|
|
//
|
|
|
|
BOOL fPhaseTwo;
|
|
|
|
if(fUserCredentialValid && IsBackupMasterKeyRequired( &hMasterKey, &fPhaseTwo ))
|
|
{
|
|
if(BackupMasterKey(
|
|
pvContext,
|
|
&hMasterKey,
|
|
*ppbMasterKey,
|
|
*pcbMasterKey,
|
|
fPhaseTwo, // phase two backup required?
|
|
TRUE // always asynchronous during key retrieve
|
|
) == ERROR_SUCCESS)
|
|
{
|
|
|
|
if(fPhaseTwo)
|
|
*pdwMasterKeyDisposition = MK_DISP_BCK_DC;
|
|
else
|
|
*pdwMasterKeyDisposition = MK_DISP_BCK_LCL;
|
|
}
|
|
}
|
|
|
|
if( *pdwMasterKeyDisposition == MK_DISP_UNKNOWN_ERR )
|
|
{
|
|
*pdwMasterKeyDisposition = MK_DISP_OK;
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
}
|
|
|
|
if(!CloseMasterKey(pvContext, &hMasterKey, fSuccess))
|
|
{
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
if(fSuccess && (NULL != wszOldFilePath))
|
|
{
|
|
LPWSTR wszDeleteFilePath = NULL;
|
|
// Delete the old key, now that the new one has been migrated.
|
|
|
|
wszDeleteFilePath = (LPWSTR)SSAlloc((wcslen(wszOldFilePath) +
|
|
wcslen(wszMasterKey) +
|
|
2) * sizeof(WCHAR));
|
|
if(NULL != wszDeleteFilePath)
|
|
{
|
|
wcscpy(wszDeleteFilePath, wszOldFilePath);
|
|
wcscat(wszDeleteFilePath, L"\\");
|
|
wcscat(wszDeleteFilePath, wszMasterKey);
|
|
|
|
DeleteFile(wszDeleteFilePath);
|
|
SSFree(wszDeleteFilePath);
|
|
}
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
BOOL
|
|
GetMasterKeyUserEncryptionKey(
|
|
IN PVOID pvContext,
|
|
OUT GUID *pCredentialID,
|
|
IN PSID pSid,
|
|
IN DWORD dwFlags,
|
|
IN OUT BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN]
|
|
)
|
|
/*++
|
|
|
|
This routine gets the key used to encrypt and decrypt the persisted
|
|
master key MK. This routine returns a copy of a function of the per-user
|
|
logon credential used during Windows NT logon.
|
|
|
|
If the function succeeds, the return value is TRUE, and the buffer
|
|
specified with by the rgbMKEncryptionKey parameter is filled with the
|
|
masterkey encryption key.
|
|
|
|
The return value is FALSE if the encryption key could not be obtained.
|
|
|
|
--*/
|
|
{
|
|
BOOL fLocalMachine = FALSE;
|
|
DWORD dwAccount = 0;
|
|
LPWSTR pszUserName = NULL;
|
|
DWORD cchUserName;
|
|
DWORD dwLastError;
|
|
BOOL fSystemCred = FALSE;
|
|
BOOL fSuccess = TRUE;
|
|
|
|
|
|
//
|
|
// see if the call is for shared, CRYPT_PROTECT_LOCAL_MACHINE
|
|
// disposition.
|
|
//
|
|
|
|
CPSOverrideToLocalSystem(
|
|
pvContext,
|
|
NULL, // don't change current over-ride BOOL
|
|
&fLocalMachine
|
|
);
|
|
|
|
CPSQueryWellKnownAccount(
|
|
pvContext,
|
|
&dwAccount);
|
|
|
|
|
|
//
|
|
// if the context specified per-machine, we know that it's a system credential.
|
|
// also, we don't need to get the user name in this scenario.
|
|
//
|
|
|
|
if(fLocalMachine || (dwAccount != 0))
|
|
{
|
|
fSystemCred = TRUE;
|
|
}
|
|
|
|
if( !fSystemCred )
|
|
{
|
|
|
|
if(pSid)
|
|
{
|
|
WCHAR wszTextualSid[MAX_PATH+1];
|
|
cchUserName = MAX_PATH+1;
|
|
if(!GetTextualSid(pSid, wszTextualSid, &cchUserName))
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
pszUserName = (LPWSTR)SSAlloc(cchUserName*sizeof(WCHAR));
|
|
if(NULL == pszUserName)
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
return FALSE;
|
|
}
|
|
wcscpy(pszUserName, wszTextualSid);
|
|
cchUserName = wcslen(pszUserName) + 1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// use the user name (actually Sid), as the mixing bytes.
|
|
//
|
|
|
|
dwLastError = CPSGetUserName(
|
|
pvContext,
|
|
&pszUserName,
|
|
&cchUserName
|
|
);
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
SetLastError( dwLastError );
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// pickup credential for the local system account.
|
|
//
|
|
|
|
if( fSystemCred )
|
|
{
|
|
|
|
dwLastError = CPSGetSystemCredential(
|
|
pvContext,
|
|
fLocalMachine,
|
|
rgbMKEncryptionKey
|
|
);
|
|
if(pCredentialID)
|
|
{
|
|
ZeroMemory(pCredentialID, sizeof(GUID));
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
dwLastError = CPSGetDerivedCredential(
|
|
pvContext,
|
|
pCredentialID,
|
|
dwFlags,
|
|
(PBYTE)pszUserName,
|
|
cchUserName * sizeof(WCHAR),
|
|
rgbMKEncryptionKey
|
|
);
|
|
}
|
|
|
|
if( pszUserName )
|
|
SSFree( pszUserName );
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
SetLastError( dwLastError );
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
BOOL
|
|
GetLocalKeyUserEncryptionKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN OUT BYTE rgbLKEncryptionKey[A_SHA_DIGEST_LEN]
|
|
)
|
|
/*++
|
|
|
|
This routine gets the key used to encrypt and decrypt the persisted
|
|
local key MK. This routine returns a copy of a function of the per-user
|
|
logon name or Sid. This is a fixed, derivable key which is required in
|
|
order to satisfy minimal stand-alone entropy.
|
|
|
|
If the function succeeds, the return value is TRUE, and the buffer
|
|
specified with by the rgbLKEncryptionKey parameter is filled with the
|
|
masterkey encryption key.
|
|
|
|
The return value is FALSE if the encryption key could not be obtained.
|
|
|
|
--*/
|
|
{
|
|
A_SHA_CTX shaContext;
|
|
LPWSTR wszUserName;
|
|
DWORD cchUserName; // includes terminal NULL
|
|
BOOL fSuccess = TRUE;
|
|
|
|
if( CPSGetUserName(
|
|
pvContext,
|
|
&wszUserName,
|
|
&cchUserName
|
|
) != ERROR_SUCCESS) {
|
|
return FALSE;
|
|
}
|
|
|
|
A_SHAInit( &shaContext );
|
|
A_SHAUpdate( &shaContext, (PBYTE)wszUserName, cchUserName * sizeof(WCHAR) );
|
|
|
|
|
|
//
|
|
// if it's above version 1, and it's local only policy, mix in LSA keys.
|
|
//
|
|
|
|
if( phMasterKey->dwVersion > 1 && phMasterKey->dwPolicy & POLICY_LOCAL_BACKUP ) {
|
|
BYTE rgbEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
DWORD dwLastError;
|
|
|
|
dwLastError = CPSGetSystemCredential(
|
|
pvContext,
|
|
TRUE,
|
|
rgbEncryptionKey
|
|
);
|
|
|
|
if( dwLastError == ERROR_SUCCESS ) {
|
|
|
|
A_SHAUpdate( &shaContext, rgbEncryptionKey, sizeof(rgbEncryptionKey) );
|
|
|
|
dwLastError = CPSGetSystemCredential(
|
|
pvContext,
|
|
FALSE,
|
|
rgbEncryptionKey
|
|
);
|
|
|
|
A_SHAUpdate( &shaContext, rgbEncryptionKey, sizeof(rgbEncryptionKey) );
|
|
|
|
}
|
|
|
|
RtlSecureZeroMemory( rgbEncryptionKey, sizeof(rgbEncryptionKey) );
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
|
|
A_SHAFinal( &shaContext, rgbLKEncryptionKey );
|
|
|
|
SSFree(wszUserName);
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
DWORD
|
|
DecryptMasterKeyToMemory(
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN PBYTE pbMasterKeyIn,
|
|
IN DWORD cbMasterKeyIn,
|
|
OUT BOOL *pfUpgradeEncryption,
|
|
OUT PBYTE *ppbMasterKeyOut,
|
|
OUT DWORD *pcbMasterKeyOut
|
|
)
|
|
{
|
|
PMASTERKEY_BLOB pMasterKeyBlob;
|
|
DWORD cbMasterKeyBlob = cbMasterKeyIn;
|
|
PMASTERKEY_INNER_BLOB pMasterKeyInnerBlob;
|
|
DWORD cIterationCount = 0;
|
|
DWORD cbMasterKeyBlobHeader;
|
|
|
|
PBYTE pbMasterKey;
|
|
DWORD cbMasterKey;
|
|
ALG_ID EncryptionAlg = CALG_RC4;
|
|
ALG_ID PKCS5Alg = CALG_HMAC;
|
|
|
|
|
|
BYTE rgbSymKey[A_SHA_DIGEST_LEN*2]; // big enough to handle 3des keys
|
|
BYTE rgbMacKey[A_SHA_DIGEST_LEN];
|
|
BYTE rgbMacCandidate[A_SHA_DIGEST_LEN];
|
|
|
|
DWORD dwLastError = (DWORD)NTE_BAD_KEY;
|
|
DWORD KeyBlocks = 1;
|
|
|
|
if(pfUpgradeEncryption)
|
|
{
|
|
*pfUpgradeEncryption = FALSE;
|
|
}
|
|
//
|
|
// Alloc, so we do not modify passed in data
|
|
//
|
|
pMasterKeyBlob = (PMASTERKEY_BLOB)SSAlloc( cbMasterKeyBlob );
|
|
if(pMasterKeyBlob == NULL)
|
|
return (DWORD)NTE_BAD_KEY;
|
|
|
|
CopyMemory( pMasterKeyBlob, pbMasterKeyIn, cbMasterKeyBlob );
|
|
|
|
|
|
if(pMasterKeyBlob->dwVersion > MASTERKEY_BLOB_VERSION)
|
|
goto cleanup;
|
|
|
|
if(MASTERKEY_BLOB_VERSION_W2K == pMasterKeyBlob->dwVersion)
|
|
{
|
|
pMasterKeyInnerBlob =
|
|
(PMASTERKEY_INNER_BLOB)(((PMASTERKEY_BLOB_W2K)pMasterKeyBlob) + 1);
|
|
cIterationCount = 0;
|
|
cbMasterKeyBlobHeader = sizeof(MASTERKEY_BLOB_W2K);
|
|
}
|
|
else
|
|
{
|
|
pMasterKeyInnerBlob = (PMASTERKEY_INNER_BLOB)(pMasterKeyBlob + 1);
|
|
cIterationCount = pMasterKeyBlob->IterationCount;
|
|
cbMasterKeyBlobHeader = sizeof(MASTERKEY_BLOB);
|
|
PKCS5Alg = (ALG_ID)pMasterKeyBlob->KEYGENAlg;
|
|
EncryptionAlg = (ALG_ID)pMasterKeyBlob->EncryptionAlg;
|
|
if(CALG_3DES == EncryptionAlg)
|
|
{
|
|
KeyBlocks = 2; // enough blocks for 3des
|
|
}
|
|
else
|
|
{
|
|
KeyBlocks = 1;
|
|
}
|
|
}
|
|
if(pfUpgradeEncryption)
|
|
{
|
|
if(!FIsLegacyCompliant())
|
|
{
|
|
//
|
|
// If we're not in legacy mode, upgrade the master key encryption
|
|
// if we're not using CALG_3DES or enough iterations
|
|
if((cIterationCount < GetIterationCount()) ||
|
|
(CALG_3DES != EncryptionAlg))
|
|
{
|
|
*pfUpgradeEncryption = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(cIterationCount)
|
|
{
|
|
DWORD j;
|
|
|
|
//
|
|
// derive symetric key via rgbMKEncryptionKey and random R2
|
|
// using PKCS#5 keying function PBKDF2
|
|
//
|
|
|
|
for(j=0; j < KeyBlocks; j++)
|
|
{
|
|
if(!PKCS5DervivePBKDF2( rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyBlob->R2,
|
|
MASTERKEY_R2_LEN,
|
|
PKCS5Alg,
|
|
cIterationCount,
|
|
j+1,
|
|
rgbSymKey + j*A_SHA_DIGEST_LEN))
|
|
goto cleanup;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// derive symetric key via rgbMKEncryptionKey and random R2
|
|
// using the weak W2K mechanism
|
|
//
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyBlob->R2,
|
|
MASTERKEY_R2_LEN,
|
|
rgbSymKey
|
|
))
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// decrypt data R3, MAC, pbMasterKey beyond masterkey blob
|
|
//
|
|
|
|
if(CALG_RC4 == EncryptionAlg)
|
|
{
|
|
|
|
RC4_KEYSTRUCT sRC4Key; //
|
|
// initialize rc4 key
|
|
//
|
|
|
|
rc4_key(&sRC4Key, A_SHA_DIGEST_LEN, rgbSymKey);
|
|
|
|
rc4(&sRC4Key,
|
|
cbMasterKeyBlob - cbMasterKeyBlobHeader,
|
|
(PBYTE)pMasterKeyInnerBlob);
|
|
}
|
|
else if (CALG_3DES == EncryptionAlg)
|
|
{
|
|
|
|
DES3TABLE s3DESKey;
|
|
BYTE InputBlock[DES_BLOCKLEN];
|
|
DWORD iBlock;
|
|
DWORD cBlocks = (cbMasterKeyBlob - cbMasterKeyBlobHeader)/DES_BLOCKLEN;
|
|
BYTE feedback[ DES_BLOCKLEN ];
|
|
// initialize 3des key
|
|
//
|
|
|
|
if(cBlocks*DES_BLOCKLEN != (cbMasterKeyBlob - cbMasterKeyBlobHeader))
|
|
{
|
|
// Master key must be a multiple of DES_BLOCKLEN
|
|
return (DWORD)NTE_BAD_KEY;
|
|
}
|
|
tripledes3key(&s3DESKey, rgbSymKey);
|
|
|
|
//
|
|
// IV is derived from the DES_BLOCKLEN bytes of the calculated
|
|
// rgbSymKey, after the 3des key
|
|
CopyMemory(feedback, rgbSymKey + DES3_KEYSIZE, DES_BLOCKLEN);
|
|
|
|
for(iBlock=0; iBlock < cBlocks; iBlock++)
|
|
{
|
|
CopyMemory(InputBlock,
|
|
((PBYTE)pMasterKeyInnerBlob)+iBlock*DES_BLOCKLEN,
|
|
DES_BLOCKLEN);
|
|
CBC(tripledes,
|
|
DES_BLOCKLEN,
|
|
((PBYTE)pMasterKeyInnerBlob)+iBlock*DES_BLOCKLEN,
|
|
InputBlock,
|
|
&s3DESKey,
|
|
DECRYPT,
|
|
feedback);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Unknown cipher....
|
|
return (DWORD)NTE_BAD_KEY;
|
|
}
|
|
//
|
|
// adjust cipher start point to include R3 and MAC.
|
|
//
|
|
if(MASTERKEY_BLOB_VERSION_W2K == pMasterKeyBlob->dwVersion)
|
|
{
|
|
pbMasterKey =
|
|
(PBYTE)(((PMASTERKEY_INNER_BLOB_W2K)pMasterKeyInnerBlob) + 1);
|
|
cbMasterKey = cbMasterKeyBlob - cbMasterKeyBlobHeader - sizeof(MASTERKEY_INNER_BLOB_W2K);
|
|
|
|
}
|
|
else
|
|
{
|
|
pbMasterKey = (PBYTE)(pMasterKeyInnerBlob + 1);
|
|
cbMasterKey = cbMasterKeyBlob - cbMasterKeyBlobHeader - sizeof(MASTERKEY_INNER_BLOB);
|
|
}
|
|
|
|
//
|
|
// derive MAC key via HMAC from rgbMKEncryptionKey and random R3.
|
|
//
|
|
|
|
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyInnerBlob->R3,
|
|
MASTERKEY_R3_LEN,
|
|
rgbMacKey
|
|
))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// use MAC key to derive result from pbMasterKey
|
|
//
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMacKey,
|
|
sizeof(rgbMacKey),
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
rgbMacCandidate // resultant MAC for verification.
|
|
))
|
|
goto cleanup;
|
|
|
|
//
|
|
// verify MAC equality
|
|
//
|
|
|
|
if(memcmp(pMasterKeyInnerBlob->MAC, rgbMacCandidate, A_SHA_DIGEST_LEN) != 0)
|
|
goto cleanup;
|
|
|
|
//
|
|
// give caller results.
|
|
//
|
|
|
|
*ppbMasterKeyOut = (LPBYTE)SSAlloc( cbMasterKey );
|
|
if(*ppbMasterKeyOut == NULL) {
|
|
dwLastError = ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
CopyMemory(*ppbMasterKeyOut, pbMasterKey, cbMasterKey);
|
|
*pcbMasterKeyOut = cbMasterKey;
|
|
|
|
dwLastError = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if(pMasterKeyBlob) {
|
|
RtlSecureZeroMemory(pMasterKeyBlob, cbMasterKeyBlob);
|
|
SSFree( pMasterKeyBlob );
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
DWORD
|
|
DecryptMasterKeyFromStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
OUT BOOL *pfUpgradeEncryption,
|
|
OUT PBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey
|
|
)
|
|
{
|
|
PBYTE pbRegData;
|
|
DWORD cbRegData;
|
|
|
|
//
|
|
// fetch blob from storage.
|
|
//
|
|
|
|
switch( dwMKLoc ) {
|
|
case REGVAL_MASTER_KEY:
|
|
pbRegData = phMasterKey->pbMK;
|
|
cbRegData = phMasterKey->cbMK;
|
|
break;
|
|
case REGVAL_LOCAL_KEY:
|
|
pbRegData = phMasterKey->pbLK;
|
|
cbRegData = phMasterKey->cbLK;
|
|
break;
|
|
case REGVAL_BACKUP_LCL_KEY:
|
|
pbRegData = phMasterKey->pbBK;
|
|
cbRegData = phMasterKey->cbBK;
|
|
break;
|
|
case REGVAL_BACKUP_DC_KEY:
|
|
pbRegData = phMasterKey->pbBBK;
|
|
cbRegData = phMasterKey->cbBBK;
|
|
break;
|
|
|
|
default:
|
|
return NTE_BAD_KEY;
|
|
}
|
|
|
|
if( cbRegData == 0 || pbRegData == NULL )
|
|
return (DWORD)NTE_BAD_KEY;
|
|
|
|
|
|
return DecryptMasterKeyToMemory(
|
|
rgbMKEncryptionKey,
|
|
pbRegData,
|
|
cbRegData,
|
|
pfUpgradeEncryption,
|
|
ppbMasterKey,
|
|
pcbMasterKey
|
|
);
|
|
}
|
|
|
|
|
|
DWORD
|
|
EncryptMasterKeyToStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey
|
|
)
|
|
/*++
|
|
|
|
Encrypt the pbMasterKey using rgbMKEncryptionKey, storing (persisting) the
|
|
result to the registry key and location specified by hMasterKey, wszMKLoc.
|
|
|
|
--*/
|
|
{
|
|
PBYTE pbMasterKeyOut = NULL;
|
|
DWORD cbMasterKeyOut;
|
|
DWORD dwLastError;
|
|
DWORD dwIterationCount = 1;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "EncryptMasterKeyToStorage\n"));
|
|
|
|
if(dwMKLoc == REGVAL_MASTER_KEY)
|
|
{
|
|
dwIterationCount = GetIterationCount();
|
|
}
|
|
|
|
dwLastError = EncryptMasterKeyToMemory(
|
|
rgbMKEncryptionKey,
|
|
dwIterationCount,
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
&pbMasterKeyOut,
|
|
&cbMasterKeyOut
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
dwLastError = PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
dwMKLoc,
|
|
pbMasterKeyOut,
|
|
cbMasterKeyOut
|
|
);
|
|
|
|
if( pbMasterKeyOut ) {
|
|
RtlSecureZeroMemory(pbMasterKeyOut, cbMasterKeyOut);
|
|
SSFree(pbMasterKeyOut);
|
|
}
|
|
|
|
cleanup:
|
|
|
|
D_DebugLog((DEB_TRACE_API, "EncryptMasterKeyToStorage returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
DWORD
|
|
PersistMasterKeyToStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN PBYTE pbMasterKeyOut,
|
|
IN DWORD cbMasterKeyOut
|
|
)
|
|
/*++
|
|
|
|
Persist the specified key output material to storage.
|
|
|
|
--*/
|
|
{
|
|
PBYTE *ppbData;
|
|
DWORD *pcbData;
|
|
|
|
//
|
|
// fetch blob from storage.
|
|
//
|
|
|
|
switch( dwMKLoc ) {
|
|
case REGVAL_MASTER_KEY:
|
|
ppbData = &(phMasterKey->pbMK);
|
|
pcbData = &(phMasterKey->cbMK);
|
|
break;
|
|
case REGVAL_LOCAL_KEY:
|
|
ppbData = &(phMasterKey->pbLK);
|
|
pcbData = &(phMasterKey->cbLK);
|
|
break;
|
|
case REGVAL_BACKUP_LCL_KEY:
|
|
ppbData = &(phMasterKey->pbBK);
|
|
pcbData = &(phMasterKey->cbBK);
|
|
break;
|
|
case REGVAL_BACKUP_DC_KEY:
|
|
ppbData = &(phMasterKey->pbBBK);
|
|
pcbData = &(phMasterKey->cbBBK);
|
|
break;
|
|
|
|
default:
|
|
return NTE_BAD_KEY;
|
|
}
|
|
|
|
|
|
if( pbMasterKeyOut == NULL && cbMasterKeyOut == 0 ) {
|
|
|
|
//
|
|
// discard existing block if present.
|
|
//
|
|
|
|
if( *ppbData ) {
|
|
RtlSecureZeroMemory( *ppbData, *pcbData );
|
|
SSFree( *ppbData );
|
|
}
|
|
|
|
*ppbData = NULL;
|
|
*pcbData = 0;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// free the in-memory buffer associated with this data block if one
|
|
// was allocated previously.
|
|
//
|
|
|
|
if( *ppbData ) {
|
|
RtlSecureZeroMemory( *ppbData, *pcbData );
|
|
|
|
if( *pcbData < cbMasterKeyOut ) {
|
|
SSFree( *ppbData );
|
|
*ppbData = (LPBYTE)SSAlloc( cbMasterKeyOut );
|
|
}
|
|
|
|
} else {
|
|
*ppbData = (LPBYTE)SSAlloc( cbMasterKeyOut );
|
|
}
|
|
|
|
*pcbData = 0;
|
|
|
|
if( *ppbData == NULL )
|
|
return ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
|
|
|
|
*pcbData = cbMasterKeyOut ;
|
|
CopyMemory( *ppbData, pbMasterKeyOut, cbMasterKeyOut );
|
|
|
|
//
|
|
// a change occured in the master key.
|
|
//
|
|
|
|
phMasterKey->fModified = TRUE;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DWORD
|
|
QueryMasterKeyFromStorage(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwMKLoc,
|
|
IN OUT PBYTE *ppbMasterKeyOut,
|
|
IN OUT DWORD *pcbMasterKeyOut
|
|
)
|
|
/*++
|
|
|
|
Query raw masterkey material from storage, returning a pointer to the
|
|
requested element for the caller.
|
|
|
|
On Success, the return value is ERROR_SUCCESS.
|
|
|
|
--*/
|
|
{
|
|
PBYTE pbData;
|
|
DWORD cbData;
|
|
|
|
//
|
|
// fetch blob from storage.
|
|
//
|
|
|
|
switch( dwMKLoc ) {
|
|
case REGVAL_MASTER_KEY:
|
|
pbData = phMasterKey->pbMK;
|
|
cbData = phMasterKey->cbMK;
|
|
break;
|
|
case REGVAL_LOCAL_KEY:
|
|
pbData = phMasterKey->pbLK;
|
|
cbData = phMasterKey->cbLK;
|
|
break;
|
|
case REGVAL_BACKUP_LCL_KEY:
|
|
pbData = phMasterKey->pbBK;
|
|
cbData = phMasterKey->cbBK;
|
|
break;
|
|
case REGVAL_BACKUP_DC_KEY:
|
|
pbData = phMasterKey->pbBBK;
|
|
cbData = phMasterKey->cbBBK;
|
|
break;
|
|
|
|
default:
|
|
return (DWORD)NTE_BAD_KEY;
|
|
}
|
|
|
|
|
|
if(cbData == 0 || pbData == NULL)
|
|
return (DWORD)NTE_BAD_KEY;
|
|
|
|
*ppbMasterKeyOut = pbData;
|
|
*pcbMasterKeyOut = cbData;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DWORD
|
|
EncryptMasterKeyToMemory(
|
|
IN BYTE rgbMKEncryptionKey[A_SHA_DIGEST_LEN],
|
|
IN DWORD cIterationCount,
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
OUT PBYTE *ppbMasterKeyOut,
|
|
OUT DWORD *pcbMasterKeyOut
|
|
)
|
|
{
|
|
PMASTERKEY_BLOB pMasterKeyBlob;
|
|
DWORD cbMasterKeyBlob;
|
|
DWORD cbMasterInnerKeyBlob;
|
|
PMASTERKEY_INNER_BLOB pMasterKeyInnerBlob;
|
|
PBYTE pbCipherBegin;
|
|
|
|
BYTE rgbMacKey[A_SHA_DIGEST_LEN];
|
|
|
|
DWORD dwLastError = (DWORD)NTE_BAD_KEY;
|
|
|
|
BOOL fLegacyBlob = (FIsLegacyCompliant() || (0 == cIterationCount));
|
|
|
|
ALG_ID EncryptionAlg = CALG_3DES;
|
|
ALG_ID PKCS5Alg = CALG_HMAC;
|
|
|
|
|
|
BYTE rgbSymKey[A_SHA_DIGEST_LEN*2]; // big enough to handle 3des keys
|
|
|
|
DWORD KeyBlocks = 1;
|
|
|
|
|
|
|
|
|
|
if(!fLegacyBlob)
|
|
{
|
|
|
|
cbMasterInnerKeyBlob = sizeof(MASTERKEY_INNER_BLOB) +
|
|
cbMasterKey ;
|
|
|
|
cbMasterKeyBlob = sizeof(MASTERKEY_BLOB) +
|
|
cbMasterInnerKeyBlob;
|
|
}
|
|
else
|
|
{
|
|
|
|
EncryptionAlg = CALG_RC4;
|
|
|
|
cbMasterInnerKeyBlob = sizeof(MASTERKEY_INNER_BLOB_W2K) +
|
|
cbMasterKey ;
|
|
|
|
cbMasterKeyBlob = sizeof(MASTERKEY_BLOB_W2K) +
|
|
cbMasterInnerKeyBlob;
|
|
|
|
}
|
|
|
|
if(CALG_3DES == EncryptionAlg)
|
|
{
|
|
KeyBlocks = 2;
|
|
|
|
if(cbMasterInnerKeyBlob%DES_BLOCKLEN)
|
|
{
|
|
return NTE_BAD_KEY;
|
|
}
|
|
}
|
|
|
|
pMasterKeyBlob = (PMASTERKEY_BLOB)SSAlloc( cbMasterKeyBlob );
|
|
if(pMasterKeyBlob == NULL)
|
|
return ERROR_NOT_ENOUGH_SERVER_MEMORY;
|
|
|
|
if(!fLegacyBlob)
|
|
{
|
|
pMasterKeyBlob->dwVersion = MASTERKEY_BLOB_VERSION;
|
|
pMasterKeyInnerBlob = (PMASTERKEY_INNER_BLOB)(pMasterKeyBlob + 1);
|
|
}
|
|
else
|
|
{
|
|
pMasterKeyBlob->dwVersion = MASTERKEY_BLOB_VERSION_W2K;
|
|
pMasterKeyInnerBlob =
|
|
(PMASTERKEY_INNER_BLOB)(((PMASTERKEY_BLOB_W2K)pMasterKeyBlob) + 1);
|
|
}
|
|
|
|
|
|
//
|
|
// generate random R2 for SymKey
|
|
//
|
|
|
|
if(!RtlGenRandom(pMasterKeyBlob->R2, MASTERKEY_R2_LEN))
|
|
goto cleanup;
|
|
|
|
//
|
|
// generate random R3 for MAC
|
|
//
|
|
|
|
if(!RtlGenRandom(pMasterKeyInnerBlob->R3, MASTERKEY_R3_LEN))
|
|
goto cleanup;
|
|
|
|
|
|
if(!fLegacyBlob)
|
|
{
|
|
DWORD j;
|
|
//
|
|
// derive symetric key via rgbMKEncryptionKey and random R2
|
|
//
|
|
|
|
for(j=0; j < KeyBlocks; j++)
|
|
{
|
|
if(!PKCS5DervivePBKDF2(
|
|
rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyBlob->R2,
|
|
MASTERKEY_R2_LEN,
|
|
PKCS5Alg,
|
|
cIterationCount,
|
|
j+1,
|
|
rgbSymKey+j*A_SHA_DIGEST_LEN
|
|
))
|
|
goto cleanup;
|
|
}
|
|
pMasterKeyBlob->IterationCount = cIterationCount;
|
|
pMasterKeyBlob->EncryptionAlg = EncryptionAlg;
|
|
pMasterKeyBlob->KEYGENAlg = PKCS5Alg;
|
|
|
|
pbCipherBegin = (PBYTE)(pMasterKeyInnerBlob+1);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// derive symetric key via rgbMKEncryptionKey and random R2
|
|
//
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyBlob->R2,
|
|
MASTERKEY_R2_LEN,
|
|
rgbSymKey
|
|
))
|
|
goto cleanup;
|
|
|
|
pbCipherBegin = (PBYTE)(((PMASTERKEY_INNER_BLOB_W2K)pMasterKeyInnerBlob)+1);
|
|
|
|
}
|
|
|
|
//
|
|
// derive MAC key via HMAC from rgbMKEncryptionKey and random R3.
|
|
//
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMKEncryptionKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pMasterKeyInnerBlob->R3,
|
|
MASTERKEY_R3_LEN,
|
|
rgbMacKey // resultant MAC key
|
|
))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// copy pbMasterKey following inner MAC'ish blob.
|
|
//
|
|
|
|
|
|
CopyMemory( pbCipherBegin, pbMasterKey, cbMasterKey );
|
|
|
|
//
|
|
// use MAC key to derive result from pbMasterKey
|
|
//
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbMacKey,
|
|
sizeof(rgbMacKey),
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
pMasterKeyInnerBlob->MAC // resultant MAC for verification.
|
|
))
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if(CALG_RC4 == EncryptionAlg)
|
|
{
|
|
|
|
RC4_KEYSTRUCT sRC4Key; //
|
|
// initialize rc4 key
|
|
//
|
|
|
|
rc4_key(&sRC4Key, A_SHA_DIGEST_LEN, rgbSymKey);
|
|
|
|
rc4(&sRC4Key,
|
|
cbMasterInnerKeyBlob,
|
|
(PBYTE)pMasterKeyInnerBlob);
|
|
}
|
|
else if (CALG_3DES == EncryptionAlg)
|
|
{
|
|
|
|
DES3TABLE s3DESKey;
|
|
BYTE InputBlock[DES_BLOCKLEN];
|
|
DWORD iBlock;
|
|
DWORD cBlocks = cbMasterInnerKeyBlob/DES_BLOCKLEN;
|
|
BYTE feedback[ DES_BLOCKLEN ];
|
|
// initialize 3des key
|
|
//
|
|
|
|
if(cBlocks*DES_BLOCKLEN != cbMasterInnerKeyBlob)
|
|
{
|
|
// Master key must be a multiple of DES_BLOCKLEN
|
|
return (DWORD)NTE_BAD_KEY;
|
|
}
|
|
tripledes3key(&s3DESKey, rgbSymKey);
|
|
|
|
//
|
|
// IV is derived from the DES_BLOCKLEN bytes of the calculated
|
|
// rgbSymKey, after the 3des key
|
|
CopyMemory(feedback, rgbSymKey + DES3_KEYSIZE, DES_BLOCKLEN);
|
|
|
|
|
|
for(iBlock=0; iBlock < cBlocks; iBlock++)
|
|
{
|
|
CopyMemory(InputBlock,
|
|
((PBYTE)pMasterKeyInnerBlob)+iBlock*DES_BLOCKLEN,
|
|
DES_BLOCKLEN);
|
|
CBC(tripledes,
|
|
DES_BLOCKLEN,
|
|
((PBYTE)pMasterKeyInnerBlob)+iBlock*DES_BLOCKLEN,
|
|
InputBlock,
|
|
&s3DESKey,
|
|
ENCRYPT,
|
|
feedback);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Unknown cipher....
|
|
return (DWORD)NTE_BAD_KEY;
|
|
}
|
|
|
|
|
|
*ppbMasterKeyOut = (PBYTE)pMasterKeyBlob;
|
|
*pcbMasterKeyOut = cbMasterKeyBlob;
|
|
|
|
pMasterKeyBlob = NULL; // prevent free of blob on success (caller does it).
|
|
|
|
dwLastError = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if(pMasterKeyBlob) {
|
|
RtlSecureZeroMemory(pMasterKeyBlob, cbMasterKeyBlob);
|
|
SSFree(pMasterKeyBlob);
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
BOOL
|
|
IsBackupMasterKeyRequired(
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN OUT BOOL *pfPhaseTwo // is phase two required?
|
|
)
|
|
/*++
|
|
|
|
Determine if we need to do a phase one or phase two backup.
|
|
|
|
Return value is TRUE if phase one or phase two backup required.
|
|
pfPhaseTwo set TRUE if phase two backup required.
|
|
|
|
Return value is FALSE when backup not required.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwMasterKeyPolicy;
|
|
PBYTE pbMasterKeyOut;
|
|
DWORD cbMasterKeyOut;
|
|
DWORD dwLastError;
|
|
|
|
|
|
dwMasterKeyPolicy = phMasterKey->dwPolicy;
|
|
|
|
if(dwMasterKeyPolicy & POLICY_NO_BACKUP)
|
|
return FALSE;
|
|
|
|
|
|
//
|
|
// evaluate what phase backup required based on policy.
|
|
//
|
|
|
|
*pfPhaseTwo = FALSE;
|
|
|
|
if(!(dwMasterKeyPolicy & POLICY_LOCAL_BACKUP)) {
|
|
|
|
dwLastError = QueryMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_DC_KEY,
|
|
&pbMasterKeyOut,
|
|
&cbMasterKeyOut
|
|
);
|
|
|
|
|
|
if(dwLastError != ERROR_SUCCESS) {
|
|
*pfPhaseTwo = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
dwLastError = QueryMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
&pbMasterKeyOut,
|
|
&cbMasterKeyOut
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsNT4Domain(void)
|
|
{
|
|
NTSTATUS Status;
|
|
LSA_HANDLE PolicyHandle = NULL;
|
|
OBJECT_ATTRIBUTES PolicyObjectAttributes;
|
|
PPOLICY_DNS_DOMAIN_INFO pDnsDomainInfo = NULL;
|
|
BOOL fRet = FALSE;
|
|
|
|
InitializeObjectAttributes( &PolicyObjectAttributes,
|
|
NULL, // Name
|
|
0, // Attributes
|
|
NULL, // Root
|
|
NULL); // Security Descriptor
|
|
|
|
Status = LsaOpenPolicy(NULL,
|
|
&PolicyObjectAttributes,
|
|
POLICY_VIEW_LOCAL_INFORMATION,
|
|
&PolicyHandle);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
Status = LsaQueryInformationPolicy(PolicyHandle,
|
|
PolicyDnsDomainInformation,
|
|
(PVOID *)&pDnsDomainInfo);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
if((pDnsDomainInfo != NULL) &&
|
|
(pDnsDomainInfo->DnsDomainName.Buffer == NULL))
|
|
{
|
|
fRet = TRUE;
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
if(pDnsDomainInfo)
|
|
LsaFreeMemory(pDnsDomainInfo);
|
|
|
|
if(PolicyHandle)
|
|
LsaClose(PolicyHandle);
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
DWORD
|
|
BackupMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN LPBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
IN BOOL fPhaseTwo, // is phase two required?
|
|
IN BOOL fAsynchronous // asynchronous call?
|
|
)
|
|
{
|
|
|
|
BYTE rgbLKEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
BYTE rgbBKEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
|
|
PBYTE pbLocalKey = NULL;
|
|
DWORD cbLocalKey = 0;
|
|
|
|
PBYTE pbBackupKeyPhaseOne = NULL;
|
|
DWORD cbBackupKeyPhaseOne = 0;
|
|
|
|
PBYTE pbBackupKeyPhaseTwo = NULL;
|
|
DWORD cbBackupKeyPhaseTwo = 0;
|
|
|
|
|
|
DWORD dwLastError = (DWORD)NTE_BAD_KEY;
|
|
|
|
|
|
BOOL fLegacy = FIsLegacyCompliant();
|
|
|
|
PCRYPT_SERVER_CONTEXT pServerContext = (PCRYPT_SERVER_CONTEXT)pvContext;
|
|
|
|
//
|
|
// get current localkey encryption key.
|
|
//
|
|
|
|
if(!GetLocalKeyUserEncryptionKey(pvContext, phMasterKey, rgbLKEncryptionKey))
|
|
goto cleanup;
|
|
|
|
//
|
|
// retrieve and decrypt LK with current credential.
|
|
//
|
|
|
|
dwLastError = DecryptMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_LOCAL_KEY,
|
|
rgbLKEncryptionKey,
|
|
NULL,
|
|
&pbLocalKey,
|
|
&cbLocalKey
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
|
|
//
|
|
// Are we running in an NT4 domain? If so, then force legacy mode so that
|
|
// the master key is backed up using the lsa secret scheme. Otherwise, the
|
|
// master key won't be recoverable following a password change.
|
|
//
|
|
|
|
if(FIsLegacyNt4Domain())
|
|
{
|
|
if(IsNT4Domain())
|
|
{
|
|
D_DebugLog((DEB_WARN,"NT4 domain detected, so force legacy backup mode!\n"));
|
|
fLegacy = TRUE;
|
|
}
|
|
}
|
|
|
|
if(fLegacy)
|
|
{
|
|
//
|
|
// derive BK encryption key from decrypted Local Key.
|
|
//
|
|
|
|
FMyPrimitiveSHA( pbLocalKey, cbLocalKey, rgbBKEncryptionKey );
|
|
|
|
//
|
|
// encrypt masterkey to phase one backup key, using encryption key derived
|
|
// from local key. do it in memory, such that we only commit it to disk if
|
|
// phase two backup key cannot be generated/persisted.
|
|
//
|
|
|
|
dwLastError = EncryptMasterKeyToMemory(
|
|
rgbBKEncryptionKey,
|
|
0,
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
&pbBackupKeyPhaseOne,
|
|
&cbBackupKeyPhaseOne
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
// Copy this in directly, so we do not set the modified flag
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// attempt phase two backup (if policy permits).
|
|
//
|
|
|
|
if( fPhaseTwo ) {
|
|
DWORD dwWaitTimeout;
|
|
|
|
dwLastError = ERROR_SUCCESS;
|
|
|
|
//
|
|
// We only attempt a local backup if we
|
|
// have user keying material. Otherwise,
|
|
// we directly contact the DC
|
|
//
|
|
if(fAsynchronous && (!fLegacy))
|
|
{
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
|
|
//
|
|
// Try to do this locally, without going
|
|
// off machine
|
|
dwLastError = AttemptLocalBackup(
|
|
FALSE,
|
|
pServerContext->hToken,
|
|
phMasterKey,
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
pbLocalKey,
|
|
cbLocalKey,
|
|
&pbBackupKeyPhaseTwo,
|
|
&cbBackupKeyPhaseTwo
|
|
);
|
|
}
|
|
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
dwLastError = PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_DC_KEY,
|
|
pbBackupKeyPhaseTwo,
|
|
cbBackupKeyPhaseTwo
|
|
);
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
// Zero out any local backup key that might
|
|
// be present
|
|
PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(fLegacy || (!fAsynchronous) || (ERROR_SUCCESS != dwLastError))
|
|
{
|
|
//
|
|
// We couldn't back up locally
|
|
// so we need to go off machine
|
|
//
|
|
if( fAsynchronous )
|
|
dwWaitTimeout = 2000;
|
|
else
|
|
dwWaitTimeout = 20000;
|
|
|
|
dwLastError = QueueBackupMasterKey(
|
|
pvContext,
|
|
phMasterKey,
|
|
pbLocalKey,
|
|
cbLocalKey,
|
|
pbMasterKey,
|
|
cbMasterKey,
|
|
dwWaitTimeout
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
if( !fPhaseTwo || dwLastError != ERROR_SUCCESS ) {
|
|
|
|
DWORD dwTempError = ERROR_SUCCESS;
|
|
|
|
//
|
|
// couldn't (or policy didn't allow) backup to phase two.
|
|
// persist phase one key, if one was generated
|
|
//
|
|
|
|
if(pbBackupKeyPhaseOne)
|
|
{
|
|
// This will overwrite our local backup data indicating which credential
|
|
// will be able to decrypt the master key. However, since we have a
|
|
// phase one backup key anyway, it doesn't matter.
|
|
//
|
|
// This should only happen if fLegacy is true
|
|
dwTempError = PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
pbBackupKeyPhaseOne,
|
|
cbBackupKeyPhaseOne
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// if it was async, prop correct error code back.
|
|
//
|
|
|
|
if( fAsynchronous || !fPhaseTwo ) {
|
|
dwLastError = dwTempError;
|
|
} else {
|
|
if( dwLastError == ERROR_SUCCESS && dwTempError != ERROR_SUCCESS )
|
|
dwLastError = dwTempError;
|
|
}
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
RtlSecureZeroMemory( rgbLKEncryptionKey, sizeof(rgbLKEncryptionKey) );
|
|
RtlSecureZeroMemory( rgbBKEncryptionKey, sizeof(rgbBKEncryptionKey) );
|
|
|
|
if(pbLocalKey) {
|
|
RtlSecureZeroMemory(pbLocalKey, cbLocalKey);
|
|
SSFree(pbLocalKey);
|
|
}
|
|
|
|
if(pbBackupKeyPhaseOne) {
|
|
RtlSecureZeroMemory(pbBackupKeyPhaseOne, cbBackupKeyPhaseOne);
|
|
SSFree(pbBackupKeyPhaseOne);
|
|
}
|
|
|
|
if(pbBackupKeyPhaseTwo) {
|
|
RtlSecureZeroMemory(pbBackupKeyPhaseTwo, cbBackupKeyPhaseTwo);
|
|
SSFree(pbBackupKeyPhaseTwo);
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
DWORD
|
|
QueueBackupMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN PBYTE pbLocalKey,
|
|
IN DWORD cbLocalKey,
|
|
IN PBYTE pbMasterKey,
|
|
IN DWORD cbMasterKey,
|
|
IN DWORD dwWaitTimeout // amount of time to wait for operation to complete
|
|
)
|
|
{
|
|
|
|
HANDLE hDuplicateToken = NULL;
|
|
PMASTERKEY_STORED phDuplicatedMasterKey = NULL;
|
|
PQUEUED_BACKUP pQueuedBackup = NULL;
|
|
HANDLE hEventThread = NULL;
|
|
HANDLE hEventSuccess = NULL;
|
|
HANDLE hDuplicateEvent = NULL;
|
|
HANDLE hDuplicateEvent2 = NULL;
|
|
DWORD dwLastError;
|
|
|
|
//
|
|
// allocate memory for the structure and any trailing contents.
|
|
//
|
|
|
|
pQueuedBackup = (PQUEUED_BACKUP)SSAlloc(
|
|
sizeof(QUEUED_BACKUP) +
|
|
cbMasterKey +
|
|
cbLocalKey
|
|
);
|
|
|
|
if( pQueuedBackup == NULL )
|
|
return ERROR_OUTOFMEMORY;
|
|
|
|
pQueuedBackup->cbSize = sizeof(QUEUED_BACKUP);
|
|
|
|
//
|
|
// duplicate the phase one backup blob.
|
|
//
|
|
|
|
pQueuedBackup->pbLocalKey = (LPBYTE)(pQueuedBackup+1);
|
|
pQueuedBackup->cbLocalKey = cbLocalKey;
|
|
|
|
CopyMemory(pQueuedBackup->pbLocalKey, pbLocalKey, cbLocalKey);
|
|
|
|
|
|
// BUGBUG: pQueueBackup should not be pagable or should be protected.
|
|
pQueuedBackup->pbMasterKey = pQueuedBackup->pbLocalKey + cbLocalKey;
|
|
pQueuedBackup->cbMasterKey = cbMasterKey;
|
|
|
|
CopyMemory(pQueuedBackup->pbMasterKey, pbMasterKey, cbMasterKey);
|
|
|
|
//
|
|
// make a duplicate of the client access token.
|
|
//
|
|
|
|
dwLastError = CPSDuplicateClientAccessToken( pvContext, &hDuplicateToken );
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
goto cleanup;
|
|
|
|
//
|
|
// duplicate the open masterkey
|
|
//
|
|
|
|
if(!DuplicateMasterKey( phMasterKey, &(pQueuedBackup->hMasterKey) )) {
|
|
dwLastError = ERROR_OUTOFMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
pQueuedBackup->hToken = hDuplicateToken;
|
|
phDuplicatedMasterKey = &(pQueuedBackup->hMasterKey);
|
|
|
|
|
|
hEventThread = CreateEventW( NULL, TRUE, FALSE, NULL );
|
|
|
|
if( hEventThread ) {
|
|
|
|
if( DuplicateHandle(
|
|
GetCurrentProcess(),
|
|
hEventThread,
|
|
GetCurrentProcess(),
|
|
&hDuplicateEvent,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS
|
|
)) {
|
|
|
|
pQueuedBackup->hEventThread = hDuplicateEvent;
|
|
} else {
|
|
hDuplicateEvent = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// create event which indicates success.
|
|
//
|
|
|
|
hEventSuccess = CreateEventW( NULL, TRUE, FALSE, NULL );
|
|
|
|
if( hEventSuccess ) {
|
|
|
|
if( DuplicateHandle(
|
|
GetCurrentProcess(),
|
|
hEventSuccess,
|
|
GetCurrentProcess(),
|
|
&hDuplicateEvent2,
|
|
0,
|
|
FALSE,
|
|
DUPLICATE_SAME_ACCESS
|
|
)) {
|
|
|
|
pQueuedBackup->hEventSuccess = hDuplicateEvent2;
|
|
} else {
|
|
hDuplicateEvent2 = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// finally, create the worker thread.
|
|
//
|
|
|
|
if( !QueueUserWorkItem(
|
|
QueueBackupMasterKeyThreadFunc,
|
|
pQueuedBackup,
|
|
WT_EXECUTELONGFUNCTION
|
|
)) {
|
|
|
|
dwLastError = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// if the thread is still active, we write the master key out.
|
|
//
|
|
|
|
if( hEventThread ) {
|
|
if(WAIT_OBJECT_0 != WaitForSingleObject( hEventThread, dwWaitTimeout ))
|
|
dwLastError = STILL_ACTIVE;
|
|
}
|
|
|
|
if( hEventSuccess && dwLastError == ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// check if operation succeeded.
|
|
// if not, indicate an error condition.
|
|
//
|
|
|
|
if(WAIT_OBJECT_0 != WaitForSingleObject( hEventSuccess, 0 ))
|
|
dwLastError = STILL_ACTIVE;
|
|
|
|
}
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// if thread creation failed, we cleanup resources that were handed
|
|
// to the thread, since it cannot possibly clean them up.
|
|
//
|
|
|
|
if( dwLastError != ERROR_SUCCESS && dwLastError != STILL_ACTIVE ) {
|
|
|
|
if( hDuplicateToken )
|
|
CloseHandle( hDuplicateToken );
|
|
|
|
if( hDuplicateEvent )
|
|
CloseHandle( hDuplicateEvent );
|
|
|
|
if( hDuplicateEvent2 )
|
|
CloseHandle( hDuplicateEvent2 );
|
|
|
|
if( phDuplicatedMasterKey )
|
|
CloseMasterKey( pvContext, phDuplicatedMasterKey, FALSE );
|
|
|
|
if( pQueuedBackup )
|
|
SSFree( pQueuedBackup );
|
|
}
|
|
|
|
if( hEventThread )
|
|
CloseHandle( hEventThread );
|
|
|
|
if( hEventSuccess )
|
|
CloseHandle( hEventSuccess );
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
QueueBackupMasterKeyThreadFunc(
|
|
IN LPVOID lpThreadArgument
|
|
)
|
|
{
|
|
PQUEUED_BACKUP pQueuedBackup = (PQUEUED_BACKUP)lpThreadArgument;
|
|
HANDLE hToken = NULL;
|
|
HANDLE hEventThread;
|
|
HANDLE hEventSuccess;
|
|
PMASTERKEY_STORED phMasterKey = NULL;
|
|
PBYTE pbBackupKeyPhaseOne = NULL;
|
|
DWORD cbBackupKeyPhaseOne = 0;
|
|
|
|
PBYTE pbBackupKeyPhaseTwo = NULL;
|
|
DWORD cbBackupKeyPhaseTwo = 0;
|
|
BOOL fImpersonated = FALSE;
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
|
|
BOOL fSuccess = FALSE;
|
|
BOOL fSuccessClose = FALSE;
|
|
|
|
BOOL fLegacy = FIsLegacyCompliant();
|
|
//
|
|
// check structure version.
|
|
//
|
|
|
|
if(pQueuedBackup == NULL || pQueuedBackup->cbSize != sizeof(QUEUED_BACKUP))
|
|
return ERROR_INVALID_PARAMETER;
|
|
|
|
hToken = pQueuedBackup->hToken;
|
|
hEventThread = pQueuedBackup->hEventThread;
|
|
hEventSuccess = pQueuedBackup->hEventSuccess;
|
|
|
|
phMasterKey = &(pQueuedBackup->hMasterKey);
|
|
|
|
|
|
|
|
|
|
|
|
if(!fLegacy)
|
|
{
|
|
|
|
//
|
|
// Public was not available, so
|
|
// we need to try to retrieve it
|
|
//
|
|
|
|
dwLastError = AttemptLocalBackup(TRUE,
|
|
hToken,
|
|
phMasterKey,
|
|
pQueuedBackup->pbMasterKey,
|
|
pQueuedBackup->cbMasterKey,
|
|
pQueuedBackup->pbLocalKey,
|
|
pQueuedBackup->cbLocalKey,
|
|
&pbBackupKeyPhaseTwo,
|
|
&cbBackupKeyPhaseTwo
|
|
);
|
|
}
|
|
|
|
//
|
|
// impersonate the client user.
|
|
//
|
|
|
|
fImpersonated = SetThreadToken( NULL, hToken );
|
|
if(!fImpersonated)
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if((ERROR_SUCCESS != dwLastError) || fLegacy)
|
|
{
|
|
BYTE rgbBKEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
|
|
//
|
|
// derive BK encryption key from decrypted Local Key.
|
|
//
|
|
|
|
FMyPrimitiveSHA( pQueuedBackup->pbLocalKey, pQueuedBackup->cbLocalKey, rgbBKEncryptionKey );
|
|
|
|
//
|
|
// encrypt masterkey to phase one backup key, using encryption key derived
|
|
// from local key. do it in memory, such that we only commit it to disk if
|
|
// phase two backup key cannot be generated/persisted.
|
|
//
|
|
|
|
dwLastError = EncryptMasterKeyToMemory(
|
|
rgbBKEncryptionKey,
|
|
0,
|
|
pQueuedBackup->pbMasterKey,
|
|
pQueuedBackup->cbMasterKey,
|
|
&pbBackupKeyPhaseOne,
|
|
&cbBackupKeyPhaseOne
|
|
);
|
|
RtlSecureZeroMemory(rgbBKEncryptionKey, sizeof(rgbBKEncryptionKey));
|
|
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
// Copy this in directly, so we do not set the modified flag
|
|
|
|
|
|
|
|
// Perform a legacy style backup
|
|
dwLastError = BackupRestoreData(
|
|
NULL,
|
|
phMasterKey,
|
|
pbBackupKeyPhaseOne,
|
|
cbBackupKeyPhaseOne,
|
|
&pbBackupKeyPhaseTwo,
|
|
&cbBackupKeyPhaseTwo,
|
|
TRUE // backup data
|
|
);
|
|
}
|
|
|
|
if( dwLastError == ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// perist phase two backup key to storage.
|
|
//
|
|
|
|
dwLastError = PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_DC_KEY,
|
|
pbBackupKeyPhaseTwo,
|
|
cbBackupKeyPhaseTwo
|
|
);
|
|
|
|
if( dwLastError == ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// successful phase two backup+persist, nuke phase one backup
|
|
// master key.
|
|
//
|
|
|
|
PersistMasterKeyToStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
fSuccess = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// always close/free master key. Only if impersonation succeeeded
|
|
// do we attempt to flush it out.
|
|
//
|
|
|
|
fSuccessClose = CloseMasterKey( NULL, phMasterKey, fSuccess ) ;
|
|
|
|
if( hEventSuccess ) {
|
|
if( fSuccess && fSuccessClose )
|
|
SetEvent( hEventSuccess );
|
|
|
|
CloseHandle( hEventSuccess );
|
|
}
|
|
|
|
if( fImpersonated )
|
|
RevertToSelf();
|
|
|
|
if( hToken )
|
|
CloseHandle(hToken);
|
|
|
|
if( hEventThread ) {
|
|
SetEvent( hEventThread );
|
|
CloseHandle( hEventThread );
|
|
}
|
|
if(pbBackupKeyPhaseOne) {
|
|
RtlSecureZeroMemory(pbBackupKeyPhaseOne, cbBackupKeyPhaseOne);
|
|
SSFree(pbBackupKeyPhaseOne);
|
|
}
|
|
if(pbBackupKeyPhaseTwo) {
|
|
RtlSecureZeroMemory(pbBackupKeyPhaseTwo, cbBackupKeyPhaseTwo);
|
|
SSFree(pbBackupKeyPhaseTwo);
|
|
}
|
|
|
|
if( lpThreadArgument )
|
|
SSFree( lpThreadArgument );
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
DWORD
|
|
RestoreMasterKey(
|
|
IN PVOID pvContext,
|
|
IN PSID pSid,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN DWORD dwReason,
|
|
OUT LPBYTE *ppbMasterKey,
|
|
OUT DWORD *pcbMasterKey
|
|
)
|
|
/*++
|
|
|
|
Recover the master key associated with the specified master key.
|
|
|
|
The current state of the masterkey dictates what level of recovery is
|
|
attempted.
|
|
|
|
--*/
|
|
{
|
|
static const GUID guidRestoreW2K = BACKUPKEY_RESTORE_GUID_W2K;
|
|
|
|
BYTE rgbLKEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
BYTE rgbBKEncryptionKey[ A_SHA_DIGEST_LEN ];
|
|
|
|
PBYTE pbLocalKey = NULL;
|
|
DWORD cbLocalKey = 0;
|
|
|
|
PBYTE pbBackupKeyPhaseOne = NULL;
|
|
DWORD cbBackupKeyPhaseOne = 0;
|
|
|
|
|
|
BOOL fAllocatedPhaseOne = FALSE;
|
|
|
|
DWORD dwLastError = (DWORD)NTE_BAD_KEY;
|
|
|
|
D_DebugLog((DEB_TRACE, "RestoreMasterKey:%ls\n", phMasterKey->wszguidMasterKey));
|
|
|
|
if(phMasterKey->pbBK)
|
|
{
|
|
|
|
LOCAL_BACKUP_DATA LocalBackupData;
|
|
|
|
// First, see if we have any local password-change recovery
|
|
// information.
|
|
|
|
if(phMasterKey->cbBK >= sizeof(LocalBackupData))
|
|
{
|
|
CopyMemory(&LocalBackupData, phMasterKey->pbBK, sizeof(LocalBackupData));
|
|
}
|
|
else
|
|
{
|
|
ZeroMemory(&LocalBackupData, sizeof(LocalBackupData));
|
|
}
|
|
|
|
if(MASTERKEY_BLOB_LOCALKEY_BACKUP == LocalBackupData.dwVersion)
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Attempt local recovery.\n"));
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK decryption key GUID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", (PBYTE)&LocalBackupData.CredentialID, sizeof(LocalBackupData.CredentialID));
|
|
#endif
|
|
|
|
if(GetMasterKeyUserEncryptionKey(pvContext,
|
|
&LocalBackupData.CredentialID,
|
|
pSid,
|
|
((phMasterKey->dwPolicy & POLICY_DPAPI_OWF)?USE_DPAPI_OWF:0),
|
|
rgbBKEncryptionKey))
|
|
{
|
|
|
|
//
|
|
// retrieve and decrypt MK with current credential.
|
|
//
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "MK decryption key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", rgbBKEncryptionKey, sizeof(rgbBKEncryptionKey));
|
|
#endif
|
|
|
|
dwLastError = DecryptMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_MASTER_KEY,
|
|
rgbBKEncryptionKey,
|
|
NULL,
|
|
ppbMasterKey,
|
|
pcbMasterKey
|
|
);
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, "Master key:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", *ppbMasterKey, *pcbMasterKey);
|
|
#endif
|
|
|
|
goto cleanup;
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "Unable to decrypt MK with local decryption key.\n"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_WARN, "Unable to locate local MK decryption key.\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if(phMasterKey->pbBBK) {
|
|
|
|
//
|
|
// do phase two recovery.
|
|
// undoing phase two backup blob gives us phase one backup blob.
|
|
//
|
|
|
|
dwLastError = CPSImpersonateClient( pvContext );
|
|
|
|
if( dwLastError == ERROR_SUCCESS ) {
|
|
|
|
dwLastError = BackupRestoreData(
|
|
((PCRYPT_SERVER_CONTEXT)pvContext)->hToken,
|
|
phMasterKey,
|
|
phMasterKey->pbBBK,
|
|
phMasterKey->cbBBK,
|
|
&pbBackupKeyPhaseOne,
|
|
&cbBackupKeyPhaseOne,
|
|
FALSE // do not backup data
|
|
);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
//
|
|
// Attempt a restore through the w2k restore port
|
|
//
|
|
dwLastError = LocalBackupRestoreData(
|
|
((PCRYPT_SERVER_CONTEXT)pvContext)->hToken,
|
|
phMasterKey,
|
|
phMasterKey->pbBBK,
|
|
phMasterKey->cbBBK,
|
|
&pbBackupKeyPhaseOne,
|
|
&cbBackupKeyPhaseOne,
|
|
&guidRestoreW2K);
|
|
}
|
|
if(dwLastError == ERROR_SUCCESS)
|
|
fAllocatedPhaseOne = TRUE;
|
|
|
|
|
|
|
|
CPSRevertToSelf( pvContext );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// try phase one blob.
|
|
//
|
|
|
|
dwLastError = QueryMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_BACKUP_LCL_KEY,
|
|
&pbBackupKeyPhaseOne,
|
|
&cbBackupKeyPhaseOne
|
|
);
|
|
|
|
}
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
|
|
//
|
|
// Check to see if this really is a phase one blob
|
|
//
|
|
|
|
if(cbBackupKeyPhaseOne < sizeof(DWORD))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
if(*((DWORD *)pbBackupKeyPhaseOne) != MASTERKEY_BLOB_RAW_VERSION)
|
|
{
|
|
//
|
|
// we successfully got an phase one blob.
|
|
// decrypt it to get the original masterkey.
|
|
//
|
|
|
|
|
|
//
|
|
// get current localkey encryption key.
|
|
//
|
|
|
|
if(!GetLocalKeyUserEncryptionKey(pvContext, phMasterKey, rgbLKEncryptionKey))
|
|
goto cleanup;
|
|
|
|
//
|
|
// retrieve and decrypt LK with current credential.
|
|
//
|
|
|
|
dwLastError = DecryptMasterKeyFromStorage(
|
|
phMasterKey,
|
|
REGVAL_LOCAL_KEY,
|
|
rgbLKEncryptionKey,
|
|
NULL,
|
|
&pbLocalKey,
|
|
&cbLocalKey
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
//
|
|
// derive BK encryption key from decrypted Local Key.
|
|
//
|
|
|
|
FMyPrimitiveSHA( pbLocalKey, cbLocalKey, rgbBKEncryptionKey );
|
|
|
|
|
|
//
|
|
// finally, decrypt BK using derived BKEncryptionKey
|
|
//
|
|
|
|
dwLastError = DecryptMasterKeyToMemory(
|
|
rgbBKEncryptionKey,
|
|
pbBackupKeyPhaseOne,
|
|
cbBackupKeyPhaseOne,
|
|
NULL,
|
|
ppbMasterKey,
|
|
pcbMasterKey
|
|
);
|
|
}
|
|
else
|
|
{
|
|
*ppbMasterKey = (PBYTE)SSAlloc(cbBackupKeyPhaseOne - sizeof(DWORD));
|
|
if(NULL == *ppbMasterKey)
|
|
{
|
|
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
CopyMemory(*ppbMasterKey,
|
|
pbBackupKeyPhaseOne + sizeof(DWORD),
|
|
cbBackupKeyPhaseOne - sizeof(DWORD));
|
|
*pcbMasterKey = cbBackupKeyPhaseOne - sizeof(DWORD);
|
|
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
RtlSecureZeroMemory( rgbLKEncryptionKey, sizeof(rgbLKEncryptionKey) );
|
|
RtlSecureZeroMemory( rgbBKEncryptionKey, sizeof(rgbBKEncryptionKey) );
|
|
|
|
if(pbLocalKey) {
|
|
RtlSecureZeroMemory(pbLocalKey, cbLocalKey);
|
|
SSFree(pbLocalKey);
|
|
}
|
|
|
|
if(fAllocatedPhaseOne && pbBackupKeyPhaseOne) {
|
|
RtlSecureZeroMemory(pbBackupKeyPhaseOne, cbBackupKeyPhaseOne);
|
|
SSFree(pbBackupKeyPhaseOne);
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE, "RestoreMasterKey returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
//
|
|
// per-user root level policy query, set
|
|
//
|
|
|
|
BOOL
|
|
InitializeMasterKeyPolicy(
|
|
IN PVOID pvContext,
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
OUT BOOL *fLocalAccount
|
|
)
|
|
{
|
|
DWORD dwMasterKeyPolicy = 0;
|
|
DWORD dwAccount = 0;
|
|
BOOL fLocalMachine = FALSE;
|
|
|
|
|
|
//
|
|
// get current top-level policy.
|
|
//
|
|
|
|
dwMasterKeyPolicy = phMasterKey->dwPolicy | GetMasterKeyDefaultPolicy();
|
|
|
|
*fLocalAccount = !IsDomainBackupRequired( pvContext );
|
|
|
|
|
|
|
|
if( !(dwMasterKeyPolicy & POLICY_NO_BACKUP) &&
|
|
!(dwMasterKeyPolicy & POLICY_LOCAL_BACKUP) )
|
|
{
|
|
|
|
//
|
|
// See if domain controller (phase two) backup is required/appropriate.
|
|
//
|
|
|
|
if( !(*fLocalAccount) )
|
|
{
|
|
phMasterKey->dwPolicy = dwMasterKeyPolicy;
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// see if the call is for shared, CRYPT_PROTECT_LOCAL_MACHINE
|
|
// disposition.
|
|
//
|
|
|
|
CPSOverrideToLocalSystem(
|
|
pvContext,
|
|
NULL, // don't change current over-ride BOOL
|
|
&fLocalMachine
|
|
);
|
|
|
|
CPSQueryWellKnownAccount(
|
|
pvContext,
|
|
&dwAccount);
|
|
|
|
|
|
//
|
|
// if the context specified per-machine, we know that it's a system credential.
|
|
// also, we don't need to get the user name in this scenario.
|
|
//
|
|
|
|
if(fLocalMachine || (dwAccount != 0))
|
|
{
|
|
//
|
|
// a SYSTEM (user or per-machine disposition) key is the focus
|
|
// of our attention; never back these up.
|
|
//
|
|
|
|
dwMasterKeyPolicy |= POLICY_NO_BACKUP;
|
|
dwMasterKeyPolicy &= ~POLICY_LOCAL_BACKUP;
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
//
|
|
// otherwise assume it's a key associated with a local account...
|
|
// (local only backup).
|
|
//
|
|
|
|
dwMasterKeyPolicy |= POLICY_LOCAL_BACKUP;
|
|
}
|
|
|
|
|
|
//
|
|
// don't persist a default value as this implies that somebody really
|
|
// specified a policy. (maximum forward compatibility).
|
|
//
|
|
|
|
phMasterKey->dwPolicy = dwMasterKeyPolicy;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
IsDomainBackupRequired(
|
|
IN PVOID pvContext
|
|
)
|
|
/*++
|
|
|
|
Determine if the current security context dictates whether domain controller
|
|
(phase two) based backup is required/appropriate.
|
|
|
|
--*/
|
|
{
|
|
|
|
PSID pSidUser = NULL;
|
|
DWORD dwSubauthorityCount;
|
|
|
|
PUSER_MODALS_INFO_2 pumi2 = NULL;
|
|
NET_API_STATUS nas;
|
|
|
|
BOOL fBackupRequired = FALSE; // assume backup not required.
|
|
BOOL fSuccess;
|
|
PCRYPT_SERVER_CONTEXT pServerContext = (PCRYPT_SERVER_CONTEXT)pvContext;
|
|
|
|
//
|
|
// get the Sid associated with the client security context.
|
|
// see if the Sid only has one subauthority. If so, no associated DC.
|
|
// see if current machine a DC. If so, backup is required.
|
|
//
|
|
|
|
fSuccess = GetTokenUserSid(pServerContext->hToken, &pSidUser);
|
|
|
|
if(!fSuccess)
|
|
goto cleanup;
|
|
|
|
//
|
|
// see if the Sid has only one subauthority. If so, no associated DC,
|
|
// no DC backup possible.
|
|
//
|
|
|
|
dwSubauthorityCount = *GetSidSubAuthorityCount( pSidUser );
|
|
|
|
if( dwSubauthorityCount == 1 ) {
|
|
fBackupRequired = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// if current machine is a domain controller, backup is required.
|
|
//
|
|
|
|
if(IsDomainController()) {
|
|
fBackupRequired = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// if the Sid contains local machine domain prefix Sid, backup is not
|
|
// required, as no DC is associated with the account.
|
|
//
|
|
|
|
nas = NetUserModalsGet( NULL, 2, (LPBYTE*)&pumi2 );
|
|
|
|
if(nas != NERR_Success)
|
|
goto cleanup;
|
|
|
|
if(!IsUserSidInDomain( pumi2->usrmod2_domain_id, pSidUser )) {
|
|
fBackupRequired = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// defaulted to backup not required.
|
|
//
|
|
|
|
fBackupRequired = FALSE;
|
|
|
|
cleanup:
|
|
|
|
if(pumi2)
|
|
NetApiBufferFree(pumi2);
|
|
|
|
if(pSidUser)
|
|
SSFree(pSidUser);
|
|
|
|
return fBackupRequired;
|
|
}
|
|
|
|
NTSTATUS
|
|
GetPreferredMasterKeyGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN OUT GUID *pguidMasterKey
|
|
)
|
|
/*++
|
|
|
|
Given a registry handle to the MasterKeys portion of the registry,
|
|
tells the caller what the preferred master key GUID is.
|
|
|
|
If a valid preferred key is configured, then the return value is
|
|
STATUS_SUCCESS, and the pguidMasterKey buffer is filled with the
|
|
GUID associated with the preferred master key.
|
|
|
|
If the preferred key is expired, then the return value is
|
|
STATUS_PASSWORD_EXPIRED, and the pguidMasterKey buffer is filled
|
|
with the GUID associated with the preferred master key. In this case,
|
|
the caller may not want to use this key, except perhaps if a new
|
|
master key cannot be successfully created.
|
|
|
|
On failure, an NTSTATUS error code is returned. The caller can assume
|
|
there is no preferred master key configured in this case, and a new one
|
|
is to be created and subsequently selected via SetPreferredMasterKeyGuid().
|
|
|
|
--*/
|
|
{
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
DWORD dwBytesRead;
|
|
|
|
MASTERKEY_PREFERRED_INFO sMKPreferred;
|
|
SYSTEMTIME stCurrentTime;
|
|
FILETIME ftCurrentTime;
|
|
unsigned __int64 CurrentTime;
|
|
unsigned __int64 ExpiryInterval;
|
|
|
|
DWORD dwLastError;
|
|
BOOL fSuccess;
|
|
|
|
dwLastError = OpenFileInStorageArea(
|
|
pvContext,
|
|
GENERIC_READ,
|
|
szUserStorageArea,
|
|
REGVAL_PREFERRED_MK,
|
|
&hFile
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS)
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// read the expiration and GUID from file into buffer.
|
|
//
|
|
|
|
fSuccess = ReadFile( hFile, &sMKPreferred, sizeof(sMKPreferred), &dwBytesRead, NULL );
|
|
|
|
CloseHandle( hFile );
|
|
|
|
if( !fSuccess )
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// validate data
|
|
//
|
|
|
|
if( dwBytesRead != sizeof(sMKPreferred) )
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy the GUID of the preferred master key to the output buffer.
|
|
//
|
|
|
|
CopyMemory(pguidMasterKey, &(sMKPreferred.guidPreferredKey), sizeof(GUID));
|
|
|
|
|
|
//
|
|
// see if the key has expired
|
|
//
|
|
|
|
GetSystemTime(&stCurrentTime);
|
|
SystemTimeToFileTime(&stCurrentTime, &ftCurrentTime);
|
|
|
|
if(CompareFileTime(&ftCurrentTime, &(sMKPreferred.ftPreferredKeyExpires)) >= 0)
|
|
{
|
|
// key has expired
|
|
return STATUS_PASSWORD_EXPIRED;
|
|
}
|
|
|
|
ExpiryInterval = MASTERKEY_EXPIRES_DAYS * 24 * 60 * 60;
|
|
ExpiryInterval *= 10000000;
|
|
|
|
CurrentTime = ((__int64)ftCurrentTime.dwHighDateTime << 32) + (__int64)ftCurrentTime.dwLowDateTime;
|
|
CurrentTime += ExpiryInterval;
|
|
|
|
ftCurrentTime.dwLowDateTime = (DWORD)(CurrentTime & 0xffffffff);
|
|
ftCurrentTime.dwHighDateTime = (DWORD)(CurrentTime >> 32);
|
|
|
|
if(CompareFileTime(&ftCurrentTime, &(sMKPreferred.ftPreferredKeyExpires)) < 0)
|
|
{
|
|
// expiry time is too far in the future
|
|
return STATUS_PASSWORD_EXPIRED;
|
|
}
|
|
|
|
|
|
//
|
|
// The key is current.
|
|
//
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
BOOL
|
|
SetPreferredMasterKeyGuid(
|
|
IN PVOID pvContext,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN GUID *pguidMasterKey
|
|
)
|
|
{
|
|
MASTERKEY_PREFERRED_INFO sMKPreferred;
|
|
SYSTEMTIME stCurrentTime;
|
|
FILETIME ftCurrentTime;
|
|
unsigned __int64 uTime;
|
|
unsigned __int64 oTime;
|
|
|
|
HANDLE hFile;
|
|
DWORD dwBytesWritten;
|
|
|
|
DWORD dwLastError;
|
|
BOOL fSuccess;
|
|
|
|
CopyMemory(&sMKPreferred.guidPreferredKey, pguidMasterKey, sizeof(GUID));
|
|
|
|
//
|
|
// set key expiration time.
|
|
//
|
|
|
|
GetSystemTime(&stCurrentTime);
|
|
SystemTimeToFileTime(&stCurrentTime, &(sMKPreferred.ftPreferredKeyExpires));
|
|
|
|
uTime = sMKPreferred.ftPreferredKeyExpires.dwLowDateTime;
|
|
uTime += ((unsigned __int64)sMKPreferred.ftPreferredKeyExpires.dwHighDateTime << 32) ;
|
|
|
|
//
|
|
// the compiler complains about integer constant overflow
|
|
// if we don't break it up..
|
|
//
|
|
|
|
oTime = MASTERKEY_EXPIRES_DAYS * 24 * 60 * 60;
|
|
oTime *= 10000000;
|
|
|
|
uTime += oTime;
|
|
|
|
sMKPreferred.ftPreferredKeyExpires.dwLowDateTime = (DWORD)(uTime & 0xffffffff);
|
|
sMKPreferred.ftPreferredKeyExpires.dwHighDateTime = (DWORD)(uTime >> 32);
|
|
|
|
|
|
dwLastError = OpenFileInStorageArea(
|
|
pvContext,
|
|
GENERIC_WRITE,
|
|
szUserStorageArea,
|
|
REGVAL_PREFERRED_MK,
|
|
&hFile
|
|
);
|
|
|
|
if(dwLastError != ERROR_SUCCESS) {
|
|
SetLastError(dwLastError);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// write the expiration and GUID from buffer into file.
|
|
//
|
|
|
|
fSuccess = WriteFile( hFile, &sMKPreferred, sizeof(sMKPreferred), &dwBytesWritten, NULL );
|
|
|
|
CloseHandle( hFile );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
OpenFileInStorageArea(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN DWORD dwDesiredAccess,
|
|
IN LPCWSTR szUserStorageArea,
|
|
IN LPCWSTR szFileName,
|
|
IN OUT HANDLE *phFile
|
|
)
|
|
{
|
|
LPWSTR szFilePath = NULL;
|
|
DWORD cbUserStorageArea;
|
|
DWORD cbFileName;
|
|
DWORD dwShareMode = 0;
|
|
DWORD dwCreationDistribution = OPEN_EXISTING;
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
|
|
*phFile = INVALID_HANDLE_VALUE;
|
|
|
|
if( dwDesiredAccess & GENERIC_READ ) {
|
|
dwShareMode |= FILE_SHARE_READ;
|
|
dwCreationDistribution = OPEN_EXISTING;
|
|
}
|
|
|
|
if( dwDesiredAccess & GENERIC_WRITE ) {
|
|
dwShareMode = 0;
|
|
dwCreationDistribution = OPEN_ALWAYS;
|
|
}
|
|
|
|
cbUserStorageArea = lstrlenW( szUserStorageArea ) * sizeof(WCHAR);
|
|
cbFileName = lstrlenW( szFileName ) * sizeof(WCHAR);
|
|
|
|
szFilePath = (LPWSTR)SSAlloc( cbUserStorageArea + cbFileName + sizeof(WCHAR) );
|
|
|
|
if( szFilePath == NULL )
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
CopyMemory(szFilePath, szUserStorageArea, cbUserStorageArea);
|
|
CopyMemory((LPBYTE)szFilePath+cbUserStorageArea, szFileName, cbFileName + sizeof(WCHAR));
|
|
|
|
if( pvContext )
|
|
dwLastError = CPSImpersonateClient( pvContext );
|
|
|
|
if( dwLastError == ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// TODO:
|
|
// apply security descriptor to file.
|
|
//
|
|
|
|
*phFile = CreateFileWithRetries(
|
|
szFilePath,
|
|
dwDesiredAccess,
|
|
dwShareMode,
|
|
NULL,
|
|
dwCreationDistribution,
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_FLAG_SEQUENTIAL_SCAN,
|
|
NULL
|
|
);
|
|
|
|
if( *phFile == INVALID_HANDLE_VALUE ) {
|
|
dwLastError = GetLastError();
|
|
}
|
|
|
|
if( pvContext )
|
|
CPSRevertToSelf( pvContext );
|
|
|
|
}
|
|
|
|
if(szFilePath)
|
|
SSFree(szFilePath);
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
HANDLE
|
|
CreateFileWithRetries(
|
|
IN LPCWSTR lpFileName,
|
|
IN DWORD dwDesiredAccess,
|
|
IN DWORD dwShareMode,
|
|
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
|
IN DWORD dwCreationDisposition,
|
|
IN DWORD dwFlagsAndAttributes,
|
|
IN HANDLE hTemplateFile
|
|
)
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
|
|
static const DWORD rgReadRetrys[] = { 1, 10, 50, 100, 1000, 0 };
|
|
static const DWORD rgWriteRetrys[] = { 1, 10, 20, 20, 50, 75, 100, 500, 1000, 0 };
|
|
|
|
const DWORD *prgRetries;
|
|
DWORD dwRetryIndex;
|
|
|
|
DWORD dwLastError = ERROR_SHARING_VIOLATION;
|
|
|
|
if( dwDesiredAccess & GENERIC_WRITE ) {
|
|
prgRetries = rgWriteRetrys;
|
|
} else {
|
|
prgRetries = rgReadRetrys;
|
|
}
|
|
|
|
for( dwRetryIndex = 0 ; prgRetries[ dwRetryIndex ] ; dwRetryIndex++ ) {
|
|
|
|
hFile = CreateFileU(
|
|
lpFileName,
|
|
dwDesiredAccess,
|
|
dwShareMode,
|
|
lpSecurityAttributes,
|
|
dwCreationDisposition,
|
|
dwFlagsAndAttributes,
|
|
hTemplateFile
|
|
);
|
|
|
|
if( hFile != INVALID_HANDLE_VALUE )
|
|
break;
|
|
|
|
dwLastError = GetLastError();
|
|
|
|
if( dwLastError == ERROR_SHARING_VIOLATION )
|
|
{
|
|
//
|
|
// sleep around for the designated period of time...
|
|
//
|
|
|
|
Sleep( prgRetries[dwRetryIndex] );
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
SetLastError( dwLastError );
|
|
|
|
return hFile;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
ReadMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
)
|
|
/*++
|
|
|
|
Read the masterkey specified by phMasterKey->wszguidMasterKey into memory.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
HANDLE hMap = NULL;
|
|
DWORD dwFileSizeLow;
|
|
|
|
PMASTERKEY_STORED_ON_DISK pMasterKeyRead = NULL;
|
|
DWORD cbguidMasterKey, cbguidMasterKey2;
|
|
PBYTE pbCurrentBlock;
|
|
WCHAR szGuidReadMasterKey[MAX_GUID_SZ_CHARS];
|
|
|
|
BOOL fSuccess = FALSE;
|
|
|
|
|
|
if( OpenFileInStorageArea(
|
|
pvContext,
|
|
GENERIC_READ,
|
|
phMasterKey->szFilePath,
|
|
phMasterKey->wszguidMasterKey,
|
|
&hFile
|
|
) != ERROR_SUCCESS)
|
|
goto cleanup;
|
|
|
|
dwFileSizeLow = GetFileSize( hFile, NULL );
|
|
if(dwFileSizeLow == INVALID_FILE_SIZE )
|
|
goto cleanup;
|
|
|
|
|
|
if( dwFileSizeLow < sizeof(MASTERKEY_STORED_ON_DISK) )
|
|
goto cleanup;
|
|
|
|
__try
|
|
{
|
|
hMap = CreateFileMappingU(
|
|
hFile,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if( hMap == NULL )
|
|
goto cleanup;
|
|
|
|
|
|
pMasterKeyRead = (PMASTERKEY_STORED_ON_DISK)MapViewOfFile( hMap, FILE_MAP_READ, 0, 0, 0 );
|
|
|
|
if(pMasterKeyRead == NULL)
|
|
goto cleanup;
|
|
|
|
|
|
if(pMasterKeyRead->dwVersion > MASTERKEY_STORED_VERSION)
|
|
goto cleanup;
|
|
|
|
//
|
|
// do some size validation
|
|
//
|
|
|
|
if((pMasterKeyRead->cbMK + pMasterKeyRead->cbLK +
|
|
pMasterKeyRead->cbBK + pMasterKeyRead->cbBBK) >
|
|
( dwFileSizeLow - sizeof(MASTERKEY_STORED_ON_DISK) )
|
|
)
|
|
goto cleanup;
|
|
|
|
//
|
|
// validate retrieved GUID matches requested GUID.
|
|
//
|
|
|
|
CopyMemory(szGuidReadMasterKey, pMasterKeyRead->wszguidMasterKey, MAX_GUID_SZ_CHARS * sizeof(WCHAR));
|
|
szGuidReadMasterKey[MAX_GUID_SZ_CHARS - 1] = L'\0';
|
|
|
|
cbguidMasterKey = (lstrlenW( phMasterKey->wszguidMasterKey ) + 1) * sizeof(WCHAR);
|
|
cbguidMasterKey2 = (lstrlenW( szGuidReadMasterKey ) + 1) * sizeof(WCHAR);
|
|
|
|
if(cbguidMasterKey != cbguidMasterKey2)
|
|
goto cleanup;
|
|
|
|
if(memcmp( phMasterKey->wszguidMasterKey, pMasterKeyRead->wszguidMasterKey, cbguidMasterKey) != 0)
|
|
goto cleanup;
|
|
|
|
phMasterKey->dwVersion = pMasterKeyRead->dwVersion;
|
|
|
|
//
|
|
// pickup master key policy
|
|
//
|
|
|
|
phMasterKey->dwPolicy = pMasterKeyRead->dwPolicy;
|
|
|
|
|
|
//
|
|
// copy useful components into new block so a single contiguous write
|
|
// can occur.
|
|
//
|
|
|
|
pbCurrentBlock = (LPBYTE)(pMasterKeyRead + 1);
|
|
|
|
if( pMasterKeyRead->cbMK )
|
|
{
|
|
phMasterKey->pbMK = (LPBYTE)SSAlloc( pMasterKeyRead->cbMK );
|
|
if(phMasterKey->pbMK == NULL)
|
|
goto cleanup;
|
|
|
|
phMasterKey->cbMK = pMasterKeyRead->cbMK;
|
|
|
|
CopyMemory(phMasterKey->pbMK, pbCurrentBlock, pMasterKeyRead->cbMK);
|
|
pbCurrentBlock += pMasterKeyRead->cbMK;
|
|
}
|
|
|
|
if( pMasterKeyRead->cbLK )
|
|
{
|
|
phMasterKey->pbLK = (LPBYTE)SSAlloc( pMasterKeyRead->cbLK );
|
|
if(phMasterKey->pbLK == NULL)
|
|
goto cleanup;
|
|
|
|
phMasterKey->cbLK = pMasterKeyRead->cbLK;
|
|
|
|
CopyMemory(phMasterKey->pbLK, pbCurrentBlock, pMasterKeyRead->cbLK);
|
|
pbCurrentBlock += pMasterKeyRead->cbLK;
|
|
}
|
|
|
|
if( pMasterKeyRead->cbBK )
|
|
{
|
|
phMasterKey->pbBK = (LPBYTE)SSAlloc( pMasterKeyRead->cbBK );
|
|
if(phMasterKey->pbBK == NULL)
|
|
goto cleanup;
|
|
|
|
phMasterKey->cbBK = pMasterKeyRead->cbBK;
|
|
|
|
CopyMemory(phMasterKey->pbBK, pbCurrentBlock, pMasterKeyRead->cbBK);
|
|
pbCurrentBlock += pMasterKeyRead->cbBK;
|
|
}
|
|
|
|
|
|
if( pMasterKeyRead->cbBBK )
|
|
{
|
|
phMasterKey->pbBBK = (LPBYTE)SSAlloc( pMasterKeyRead->cbBBK );
|
|
if(phMasterKey->pbBBK == NULL)
|
|
goto cleanup;
|
|
|
|
phMasterKey->cbBBK = pMasterKeyRead->cbBBK;
|
|
|
|
CopyMemory(phMasterKey->pbBBK, pbCurrentBlock, pMasterKeyRead->cbBBK);
|
|
}
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if( pMasterKeyRead )
|
|
UnmapViewOfFile( pMasterKeyRead );
|
|
|
|
if( hMap )
|
|
CloseHandle( hMap );
|
|
|
|
if( hFile != INVALID_HANDLE_VALUE )
|
|
CloseHandle( hFile );
|
|
|
|
if( !fSuccess )
|
|
FreeMasterKey( phMasterKey );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WriteMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
)
|
|
/*++
|
|
|
|
Persist the specified masterkey to storage.
|
|
|
|
if pvContext is NULL, the caller must be impersonating the user associated
|
|
with the masterkey.
|
|
|
|
--*/
|
|
{
|
|
PMASTERKEY_STORED_ON_DISK pMasterKeyToWrite;
|
|
DWORD cbMasterKeyToWrite;
|
|
|
|
PBYTE pbCurrentBlock;
|
|
HANDLE hFile;
|
|
|
|
BOOL fSuccess = FALSE;
|
|
|
|
if(phMasterKey->dwVersion > MASTERKEY_STORED_VERSION)
|
|
return FALSE;
|
|
|
|
|
|
cbMasterKeyToWrite = sizeof(MASTERKEY_STORED_ON_DISK) +
|
|
phMasterKey->cbMK +
|
|
phMasterKey->cbLK +
|
|
phMasterKey->cbBK +
|
|
phMasterKey->cbBBK ;
|
|
|
|
pMasterKeyToWrite = (PMASTERKEY_STORED_ON_DISK) SSAlloc( cbMasterKeyToWrite );
|
|
|
|
if(pMasterKeyToWrite == NULL)
|
|
return FALSE;
|
|
|
|
//
|
|
// copy useful components
|
|
//
|
|
|
|
pMasterKeyToWrite->dwVersion = phMasterKey->dwVersion;
|
|
CopyMemory(
|
|
pMasterKeyToWrite->wszguidMasterKey,
|
|
phMasterKey->wszguidMasterKey,
|
|
(MAX_GUID_SZ_CHARS * sizeof(WCHAR))
|
|
);
|
|
|
|
pMasterKeyToWrite->dwPolicy = phMasterKey->dwPolicy;
|
|
pMasterKeyToWrite->cbMK = phMasterKey->cbMK;
|
|
pMasterKeyToWrite->cbLK = phMasterKey->cbLK;
|
|
pMasterKeyToWrite->cbBK = phMasterKey->cbBK;
|
|
pMasterKeyToWrite->cbBBK = phMasterKey->cbBBK;
|
|
|
|
|
|
//
|
|
// overwrite non-useful components
|
|
//
|
|
|
|
pMasterKeyToWrite->fModified = FALSE;
|
|
pMasterKeyToWrite->szFilePath = 0;
|
|
pMasterKeyToWrite->pbMK = 0;
|
|
pMasterKeyToWrite->pbLK = 0;
|
|
pMasterKeyToWrite->pbBK = 0;
|
|
pMasterKeyToWrite->pbBBK = 0;
|
|
|
|
|
|
//
|
|
// copy useful components into new block so a single contiguous write
|
|
// can occur.
|
|
//
|
|
|
|
pbCurrentBlock = (LPBYTE)(pMasterKeyToWrite + 1);
|
|
|
|
if( phMasterKey->pbMK ) {
|
|
CopyMemory(pbCurrentBlock, phMasterKey->pbMK, phMasterKey->cbMK);
|
|
pbCurrentBlock += phMasterKey->cbMK;
|
|
}
|
|
|
|
if( phMasterKey->pbLK ) {
|
|
CopyMemory(pbCurrentBlock, phMasterKey->pbLK, phMasterKey->cbLK);
|
|
pbCurrentBlock += phMasterKey->cbLK;
|
|
}
|
|
|
|
|
|
if( phMasterKey->pbBK ) {
|
|
CopyMemory(pbCurrentBlock, phMasterKey->pbBK, phMasterKey->cbBK);
|
|
pbCurrentBlock += phMasterKey->cbBK;
|
|
}
|
|
|
|
if( phMasterKey->pbBBK ) {
|
|
CopyMemory(pbCurrentBlock, phMasterKey->pbBBK, phMasterKey->cbBBK);
|
|
|
|
}
|
|
|
|
if( OpenFileInStorageArea(
|
|
pvContext,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
phMasterKey->szFilePath,
|
|
phMasterKey->wszguidMasterKey,
|
|
&hFile
|
|
) == ERROR_SUCCESS) {
|
|
|
|
|
|
BOOL fWriteData;
|
|
DWORD dwBytesWritten;
|
|
|
|
CheckToStompMasterKey( pMasterKeyToWrite, hFile, &fWriteData );
|
|
|
|
if( fWriteData ) {
|
|
fSuccess = WriteFile(
|
|
hFile,
|
|
pMasterKeyToWrite,
|
|
cbMasterKeyToWrite,
|
|
&dwBytesWritten,
|
|
NULL
|
|
);
|
|
} else {
|
|
fSuccess = TRUE; // nothing to do, success
|
|
}
|
|
|
|
CloseHandle( hFile );
|
|
}
|
|
|
|
|
|
RtlSecureZeroMemory( pMasterKeyToWrite, cbMasterKeyToWrite);
|
|
SSFree( pMasterKeyToWrite );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
BOOL
|
|
CheckToStompMasterKey(
|
|
IN PMASTERKEY_STORED_ON_DISK phMasterKeyCandidate, // masterkey to check if worthy to stomp over existing
|
|
IN HANDLE hFile, // file handle to existing masterkey
|
|
IN OUT BOOL *pfStomp // stomp the existing masterkey?
|
|
)
|
|
{
|
|
HANDLE hMap = NULL;
|
|
PMASTERKEY_STORED_ON_DISK pMasterKeyRead = NULL;
|
|
BOOL fSuccess = FALSE;
|
|
|
|
*pfStomp = TRUE;
|
|
|
|
if( phMasterKeyCandidate->dwPolicy & POLICY_NO_BACKUP )
|
|
return TRUE;
|
|
|
|
if( phMasterKeyCandidate->dwPolicy & POLICY_LOCAL_BACKUP &&
|
|
phMasterKeyCandidate->cbBK )
|
|
return TRUE;
|
|
|
|
if( phMasterKeyCandidate->cbBBK )
|
|
return TRUE;
|
|
|
|
__try
|
|
{
|
|
hMap = CreateFileMapping(
|
|
hFile,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
|
|
if( hMap == NULL )
|
|
goto cleanup;
|
|
|
|
pMasterKeyRead = (PMASTERKEY_STORED_ON_DISK)MapViewOfFile( hMap, FILE_MAP_READ, 0, 0, 0 );
|
|
|
|
if(pMasterKeyRead == NULL)
|
|
goto cleanup;
|
|
|
|
if(pMasterKeyRead->dwVersion > MASTERKEY_STORED_VERSION)
|
|
goto cleanup;
|
|
|
|
//
|
|
// there's really only two cases where we don't allow stomping:
|
|
// candidate masterkey doesn't contain phase 1 and existing does,
|
|
// candidate masterkey doesn't contain phase 1 and existing one contains phase2.
|
|
// note: we allow stomping over a masterkey that contains a phase 2 with one
|
|
// that only contains a phase 1, because of a race condition that can occur
|
|
// during the backup operation; In this situation, it is better to have
|
|
// a phase 1 and let it get upgraded to phase 2 at a later time.
|
|
//
|
|
|
|
if( phMasterKeyCandidate->cbBK == 0 &&
|
|
(pMasterKeyRead->cbBK || pMasterKeyRead->cbBBK))
|
|
{
|
|
*pfStomp = FALSE;
|
|
}
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if( pMasterKeyRead )
|
|
UnmapViewOfFile( pMasterKeyRead );
|
|
|
|
if( hMap )
|
|
CloseHandle( hMap );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
BOOL
|
|
CloseMasterKey(
|
|
IN PVOID pvContext, // if NULL, caller is assumed to be impersonating
|
|
IN PMASTERKEY_STORED phMasterKey,
|
|
IN BOOL fPersist // persist any changes to storage?
|
|
)
|
|
/*++
|
|
|
|
Free the memory an optionally persist any changes associated with a
|
|
master key.
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = TRUE;
|
|
|
|
//
|
|
// if we were told to persist any changes, and changes were actually made,
|
|
// persist them out.
|
|
//
|
|
|
|
if( fPersist && phMasterKey->fModified )
|
|
fSuccess = WriteMasterKey( pvContext, phMasterKey );
|
|
|
|
//
|
|
// free memory.
|
|
//
|
|
|
|
FreeMasterKey( phMasterKey );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
VOID
|
|
FreeMasterKey(
|
|
IN PMASTERKEY_STORED phMasterKey
|
|
)
|
|
/*++
|
|
|
|
Free allocated memory associated with the specified master key.
|
|
|
|
--*/
|
|
{
|
|
if( phMasterKey->dwVersion > MASTERKEY_STORED_VERSION )
|
|
return;
|
|
|
|
if( phMasterKey->szFilePath )
|
|
SSFree( phMasterKey->szFilePath );
|
|
|
|
if( phMasterKey->pbMK ) {
|
|
RtlSecureZeroMemory( phMasterKey->pbMK, phMasterKey->cbMK );
|
|
SSFree( phMasterKey->pbMK );
|
|
}
|
|
|
|
if( phMasterKey->pbLK ) {
|
|
RtlSecureZeroMemory( phMasterKey->pbLK, phMasterKey->cbLK );
|
|
SSFree( phMasterKey->pbLK );
|
|
}
|
|
|
|
if( phMasterKey->pbBK ) {
|
|
RtlSecureZeroMemory( phMasterKey->pbBK, phMasterKey->cbBK );
|
|
SSFree( phMasterKey->pbBK );
|
|
}
|
|
|
|
if( phMasterKey->pbBBK ) {
|
|
RtlSecureZeroMemory( phMasterKey->pbBBK, phMasterKey->cbBBK );
|
|
SSFree( phMasterKey->pbBBK );
|
|
}
|
|
|
|
RtlSecureZeroMemory( phMasterKey, sizeof(MASTERKEY_STORED) );
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
DuplicateMasterKey(
|
|
IN PMASTERKEY_STORED phMasterKeyIn,
|
|
IN PMASTERKEY_STORED phMasterKeyOut
|
|
)
|
|
/*++
|
|
|
|
Duplicate the input masterkey to a new copy, setting the fModified flag
|
|
on the copy to FALSE.
|
|
|
|
This provides a mechanism to allow for deferring operations against a
|
|
master key.
|
|
|
|
--*/
|
|
{
|
|
BOOL fSuccess = FALSE;
|
|
|
|
if( phMasterKeyIn->dwVersion > MASTERKEY_STORED_VERSION )
|
|
return FALSE;
|
|
|
|
ZeroMemory( phMasterKeyOut, sizeof(MASTERKEY_STORED) );
|
|
|
|
phMasterKeyOut->dwVersion = phMasterKeyIn->dwVersion;
|
|
phMasterKeyOut->dwPolicy = phMasterKeyIn->dwPolicy;
|
|
phMasterKeyOut->fModified = FALSE;
|
|
|
|
if( lstrlenW( phMasterKeyIn->wszguidMasterKey ) > MAX_GUID_SZ_CHARS )
|
|
return FALSE;
|
|
|
|
CopyMemory(phMasterKeyOut->wszguidMasterKey, phMasterKeyIn->wszguidMasterKey, MAX_GUID_SZ_CHARS * sizeof(WCHAR));
|
|
|
|
if( phMasterKeyIn->szFilePath ) {
|
|
DWORD cbFilePath = (lstrlenW(phMasterKeyIn->szFilePath) + 1) * sizeof(WCHAR);
|
|
|
|
phMasterKeyOut->szFilePath = (LPWSTR)SSAlloc( cbFilePath );
|
|
if(phMasterKeyOut->szFilePath == NULL)
|
|
goto cleanup;
|
|
|
|
CopyMemory( phMasterKeyOut->szFilePath, phMasterKeyIn->szFilePath, cbFilePath );
|
|
}
|
|
|
|
if( phMasterKeyIn->pbMK ) {
|
|
phMasterKeyOut->cbMK = phMasterKeyIn->cbMK;
|
|
phMasterKeyOut->pbMK = (PBYTE)SSAlloc(phMasterKeyIn->cbMK);
|
|
if(phMasterKeyOut->pbMK == NULL)
|
|
goto cleanup;
|
|
|
|
CopyMemory( phMasterKeyOut->pbMK, phMasterKeyIn->pbMK, phMasterKeyIn->cbMK );
|
|
}
|
|
|
|
|
|
if( phMasterKeyIn->pbLK ) {
|
|
phMasterKeyOut->cbLK = phMasterKeyIn->cbLK;
|
|
phMasterKeyOut->pbLK = (PBYTE)SSAlloc(phMasterKeyIn->cbLK);
|
|
if(phMasterKeyOut->pbLK == NULL)
|
|
goto cleanup;
|
|
|
|
CopyMemory( phMasterKeyOut->pbLK, phMasterKeyIn->pbLK, phMasterKeyIn->cbLK );
|
|
}
|
|
|
|
if( phMasterKeyIn->pbBK ) {
|
|
phMasterKeyOut->cbBK = phMasterKeyIn->cbBK;
|
|
phMasterKeyOut->pbBK = (PBYTE)SSAlloc(phMasterKeyIn->cbBK);
|
|
if(phMasterKeyOut->pbBK == NULL)
|
|
goto cleanup;
|
|
|
|
CopyMemory( phMasterKeyOut->pbBK, phMasterKeyIn->pbBK, phMasterKeyIn->cbBK );
|
|
}
|
|
|
|
if( phMasterKeyIn->pbBBK ) {
|
|
phMasterKeyOut->cbBBK = phMasterKeyIn->cbBBK;
|
|
phMasterKeyOut->pbBBK = (PBYTE)SSAlloc(phMasterKeyIn->cbBBK);
|
|
if(phMasterKeyOut->pbBBK == NULL)
|
|
goto cleanup;
|
|
|
|
CopyMemory( phMasterKeyOut->pbBBK, phMasterKeyIn->pbBBK, phMasterKeyIn->cbBBK );
|
|
}
|
|
|
|
fSuccess = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if( !fSuccess )
|
|
FreeMasterKey( phMasterKeyOut );
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
BOOL
|
|
InitializeKeyManagement(
|
|
VOID
|
|
)
|
|
{
|
|
if(!InitializeKeyCache())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
TeardownKeyManagement(
|
|
VOID
|
|
)
|
|
{
|
|
|
|
DeleteKeyCache();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD
|
|
DpapiUpdateLsaSecret(
|
|
IN PVOID pvContext)
|
|
{
|
|
CRYPT_SERVER_CONTEXT SystemContext;
|
|
CRYPT_SERVER_CONTEXT SystemUserContext;
|
|
CRYPT_SERVER_CONTEXT LocalServiceContext;
|
|
CRYPT_SERVER_CONTEXT NetworkServiceContext;
|
|
LPWSTR pszUserStorageArea = NULL;
|
|
BOOL fSystemContextCreated = FALSE;
|
|
BOOL fSystemUserContextCreated = FALSE;
|
|
BOOL fLocalServiceContextCreated = FALSE;
|
|
BOOL fNetworkServiceContextCreated = FALSE;
|
|
BOOL fNewSecretCreated = TRUE;
|
|
GUID guidMasterKey;
|
|
BOOL fOverrideToLocalSystem;
|
|
DWORD dwRet;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DpapiUpdateLsaSecret\n"));
|
|
|
|
|
|
//
|
|
// TCB privilege must be held by the client in order to
|
|
// make this call. Verify that before doing anything else
|
|
//
|
|
|
|
dwRet = CPSImpersonateClient( pvContext );
|
|
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
HANDLE ClientToken;
|
|
|
|
dwRet = NtOpenThreadToken(
|
|
NtCurrentThread(),
|
|
TOKEN_QUERY,
|
|
TRUE,
|
|
&ClientToken
|
|
);
|
|
|
|
if ( NT_SUCCESS( dwRet ))
|
|
{
|
|
BOOLEAN Result = FALSE;
|
|
PRIVILEGE_SET RequiredPrivileges;
|
|
LUID_AND_ATTRIBUTES PrivilegeArray[1];
|
|
|
|
RequiredPrivileges.PrivilegeCount = 1;
|
|
RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|
RequiredPrivileges.Privilege[0].Luid = RtlConvertLongToLuid( SE_TCB_PRIVILEGE );
|
|
RequiredPrivileges.Privilege[0].Attributes = 0;
|
|
|
|
dwRet = NtPrivilegeCheck(
|
|
ClientToken,
|
|
&RequiredPrivileges,
|
|
&Result
|
|
);
|
|
|
|
if ( NT_SUCCESS( dwRet ) &&
|
|
Result == FALSE )
|
|
{
|
|
dwRet = STATUS_PRIVILEGE_NOT_HELD;
|
|
}
|
|
|
|
NtClose( ClientToken );
|
|
ClientToken = NULL;
|
|
}
|
|
|
|
CPSRevertToSelf( pvContext );
|
|
}
|
|
|
|
if(!NT_SUCCESS(dwRet))
|
|
{
|
|
D_DebugLog((DEB_ERROR, "DpapiUpdateLsaSecret: TCB privilege required!\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Enumerate through all of the master keys in the Protect\S-1-5-18
|
|
// directory, and load them all up in the master key cache.
|
|
//
|
|
|
|
D_DebugLog((DEB_TRACE, "Load system master keys into cache\n"));
|
|
|
|
dwRet = CPSCreateServerContext(&SystemContext, NULL);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
fSystemContextCreated = TRUE;
|
|
|
|
fOverrideToLocalSystem = TRUE;
|
|
CPSOverrideToLocalSystem(&SystemContext, &fOverrideToLocalSystem, NULL);
|
|
|
|
dwRet = SynchronizeMasterKeys(&SystemContext, ADD_MASTER_KEY_TO_CACHE);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Enumerate through all of the master keys in the Protect\S-1-5-18\User
|
|
// directory, and load them all up in the master key cache.
|
|
//
|
|
|
|
dwRet = CPSCreateServerContext(&SystemUserContext, NULL);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
fSystemUserContextCreated = TRUE;
|
|
|
|
dwRet = SynchronizeMasterKeys(&SystemUserContext, ADD_MASTER_KEY_TO_CACHE);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Enumerate through all of the master keys in the Protect\S-1-5-19
|
|
// directory, and load them all up in the master key cache.
|
|
//
|
|
|
|
dwRet = CPSCreateServerContext(&LocalServiceContext, NULL);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
fLocalServiceContextCreated = TRUE;
|
|
|
|
CPSSetWellKnownAccount(&LocalServiceContext, DP_ACCOUNT_LOCAL_SERVICE);
|
|
|
|
dwRet = SynchronizeMasterKeys(&LocalServiceContext, ADD_MASTER_KEY_TO_CACHE);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Enumerate through all of the master keys in the Protect\S-1-5-20
|
|
// directory, and load them all up in the master key cache.
|
|
//
|
|
|
|
dwRet = CPSCreateServerContext(&NetworkServiceContext, NULL);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
fNetworkServiceContextCreated = TRUE;
|
|
|
|
CPSSetWellKnownAccount(&NetworkServiceContext, DP_ACCOUNT_NETWORK_SERVICE);
|
|
|
|
dwRet = SynchronizeMasterKeys(&NetworkServiceContext, ADD_MASTER_KEY_TO_CACHE);
|
|
if(dwRet != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Regenerate the DPAPI_SYSTEM value.
|
|
//
|
|
|
|
D_DebugLog((DEB_TRACE, "Reset lsa secret\n"));
|
|
|
|
if(!UpdateSystemCredentials())
|
|
{
|
|
fNewSecretCreated = FALSE;
|
|
DebugLog((DEB_ERROR, "Unable to reset DPAPI_SYSTEM secret.\n"));
|
|
}
|
|
|
|
|
|
//
|
|
// Reencrypt and write back all of the master keys that are in the cache.
|
|
// Note that since this routine should only be called on brand-new machines
|
|
// that have just been setup using SYSPREP, the total number of master keys
|
|
// should always be exactly two. Thus, we shouldn't have to worry about
|
|
// overflowing the master key cache or anything like that.
|
|
//
|
|
|
|
D_DebugLog((DEB_TRACE, "Reencrypt system master keys\n"));
|
|
|
|
if(fNewSecretCreated)
|
|
{
|
|
SynchronizeMasterKeys(&SystemContext, REENCRYPT_MASTER_KEY);
|
|
SynchronizeMasterKeys(&SystemUserContext, REENCRYPT_MASTER_KEY);
|
|
SynchronizeMasterKeys(&LocalServiceContext, REENCRYPT_MASTER_KEY);
|
|
SynchronizeMasterKeys(&NetworkServiceContext, REENCRYPT_MASTER_KEY);
|
|
}
|
|
|
|
|
|
//
|
|
// Generate two new master keys, and mark them as preferred.
|
|
//
|
|
|
|
D_DebugLog((DEB_TRACE, "Generate new system master keys\n"));
|
|
|
|
dwRet = CPSGetUserStorageArea( &SystemContext,
|
|
NULL,
|
|
FALSE,
|
|
&pszUserStorageArea );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
dwRet = CreateMasterKey( &SystemContext, pszUserStorageArea, &guidMasterKey, FALSE );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
SetPreferredMasterKeyGuid( &SystemContext, pszUserStorageArea, &guidMasterKey );
|
|
}
|
|
|
|
SSFree(pszUserStorageArea);
|
|
pszUserStorageArea = NULL;
|
|
}
|
|
else if(dwRet == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
dwRet = ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
dwRet = CPSGetUserStorageArea( &SystemUserContext,
|
|
NULL,
|
|
FALSE,
|
|
&pszUserStorageArea );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
dwRet = CreateMasterKey( &SystemUserContext, pszUserStorageArea, &guidMasterKey, FALSE );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
SetPreferredMasterKeyGuid( &SystemUserContext, pszUserStorageArea, &guidMasterKey );
|
|
}
|
|
|
|
SSFree(pszUserStorageArea);
|
|
pszUserStorageArea = NULL;
|
|
}
|
|
else if(dwRet == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
dwRet = ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
dwRet = CPSGetUserStorageArea( &LocalServiceContext,
|
|
NULL,
|
|
FALSE,
|
|
&pszUserStorageArea );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
dwRet = CreateMasterKey( &LocalServiceContext, pszUserStorageArea, &guidMasterKey, FALSE );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
SetPreferredMasterKeyGuid( &LocalServiceContext, pszUserStorageArea, &guidMasterKey );
|
|
}
|
|
|
|
SSFree(pszUserStorageArea);
|
|
pszUserStorageArea = NULL;
|
|
}
|
|
else if(dwRet == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
dwRet = ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
dwRet = CPSGetUserStorageArea( &NetworkServiceContext,
|
|
NULL,
|
|
FALSE,
|
|
&pszUserStorageArea );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
dwRet = CreateMasterKey( &NetworkServiceContext, pszUserStorageArea, &guidMasterKey, FALSE );
|
|
if(dwRet == ERROR_SUCCESS)
|
|
{
|
|
SetPreferredMasterKeyGuid( &NetworkServiceContext, pszUserStorageArea, &guidMasterKey );
|
|
}
|
|
|
|
SSFree(pszUserStorageArea);
|
|
pszUserStorageArea = NULL;
|
|
}
|
|
else if(dwRet == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
dwRet = ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Cleanup.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
if(fSystemContextCreated)
|
|
{
|
|
CPSDeleteServerContext( &SystemContext );
|
|
}
|
|
|
|
if(fSystemUserContextCreated)
|
|
{
|
|
CPSDeleteServerContext( &SystemUserContext );
|
|
}
|
|
|
|
if(fLocalServiceContextCreated)
|
|
{
|
|
CPSDeleteServerContext( &LocalServiceContext );
|
|
}
|
|
|
|
if(fNetworkServiceContextCreated)
|
|
{
|
|
CPSDeleteServerContext( &NetworkServiceContext );
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DpapiUpdateLsaSecret returned 0x%x\n", dwRet));
|
|
|
|
return dwRet;
|
|
}
|
|
|