Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1629 lines
43 KiB

/*++
Copyright (c) 1996, 1997 Microsoft Corporation
Module Name:
keybckup.cpp
Abstract:
This module contains routines associated with client side Key Backup
operations.
Author:
Scott Field (sfield) 16-Sep-97
--*/
#include <pch.cpp>
#pragma hdrstop
extern "C" {
#include <dsgetdc.h>
#include <msaudite.h>
}
#define DPAPI_SERVICE_NAME L"ProtectedStorage"
typedef struct _WZR_RPC_BINDING_LIST
{
LPCWSTR pszProtSeq;
LPCWSTR pszEndpoint;
} WZR_RPC_BINDING_LIST;
WZR_RPC_BINDING_LIST g_awzrBackupBindingList[] =
{
{ DPAPI_LOCAL_PROT_SEQ, DPAPI_LOCAL_ENDPOINT },
{ DPAPI_BACKUP_PROT_SEQ, DPAPI_BACKUP_ENDPOINT},
{ DPAPI_LEGACY_BACKUP_PROT_SEQ, DPAPI_LEGACY_BACKUP_ENDPOINT}
};
DWORD g_cwzrBackupBindingList = sizeof(g_awzrBackupBindingList)/sizeof(g_awzrBackupBindingList[0]);
DWORD
WINAPI
CPSGetDomainControllerName(
IN OUT LPWSTR wszDomainControllerName,
IN OUT DWORD *pcchDomainControllerName,
IN BOOL fRediscover
);
BOOL
GetDomainControllerNameByToken(
IN HANDLE hToken,
IN OUT LPWSTR wszDomainControllerName,
IN OUT PDWORD pcchDomainControllerName,
IN BOOL fRediscover
);
static const GUID guidRetrieve = BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID;
static const GUID guidRestore = BACKUPKEY_RESTORE_GUID;
static const GUID guidRestoreW2K = BACKUPKEY_RESTORE_GUID_W2K;
static const GUID guidBackup = BACKUPKEY_BACKUP_GUID;
DWORD
BackupRestoreData(
IN HANDLE hToken,
IN PMASTERKEY_STORED phMasterKey,
IN PBYTE pbDataIn,
IN DWORD cbDataIn,
OUT PBYTE *ppbDataOut,
OUT DWORD *pcbDataOut,
IN BOOL fBackup
)
{
return LocalBackupRestoreData(hToken,
phMasterKey,
pbDataIn,
cbDataIn,
ppbDataOut,
pcbDataOut,
fBackup?&guidBackup:&guidRestore);
}
//+---------------------------------------------------------------------------
//
// Function: LocalBackupRestoreData
//
// Synopsis: Connect to the user's DC and perform a master key backup or
// recovery operation.
//
// Arguments: [hToken] -- Handle to user token. This is used when
// generating audits.
//
// [phMasterKey] -- Master key that we're operating on.
//
// [pbDataIn] -- Input buffer. Only used for recovery
// [cbDataIn] -- operations.
//
// [ppbDataOut] -- Output buffer.
// [pcbDataOut] --
//
// [pguidAction] -- GUID describing operation to perform.
//
// Returns: ERROR_SUCCESS if the operation was successful, a Windows
// error code otherwise.
//
// History:
//
// Notes: The caller of this function MUST be impersonating a client
// user.
//
//----------------------------------------------------------------------------
DWORD
LocalBackupRestoreData(
IN HANDLE hToken,
IN PMASTERKEY_STORED phMasterKey,
IN PBYTE pbDataIn,
IN DWORD cbDataIn,
OUT PBYTE *ppbDataOut,
OUT DWORD *pcbDataOut,
IN const GUID *pguidAction
)
{
WCHAR FastBuffer[ 256 ];
LPWSTR SlowBuffer = NULL;
LPWSTR pszAuditComputerName = NULL;
LPWSTR pszComputerName;
DWORD cchComputerName;
static DWORD dwLastFailTickCount; // time for failure on last access
static LUID luidLastFailAuthId; // LUID associated with failed network
DWORD dwCandidateTickCount;
LUID luidCandidateAuthId; // LUID associated with client security context.
BOOL fRediscoverDC = FALSE;
DWORD dwLastError = ERROR_NETWORK_BUSY;
D_DebugLog((DEB_TRACE_API, "LocalBackupRestoreData\n"));
//
// impersonate the user, so we may
// 1. check the authentication ID to see if we've failed to hit the
// net as this user.
// 2. determine a domain controller computer name associated with
// the user.
// 3. backup or restore the requested material on behalf of the user.
//
if(!GetThreadAuthenticationId( GetCurrentThread(), &luidCandidateAuthId ))
return ERROR_NO_TOKEN;
//
// now, see if the network was previously unavailable (recently)
// for this user.
//
dwCandidateTickCount = GetTickCount();
if(memcmp(&luidCandidateAuthId, &luidLastFailAuthId, sizeof(LUID)) == 0) {
if( (dwLastFailTickCount + (5*1000*60)) > dwCandidateTickCount ) {
//BUGBUG: return ERROR_NETWORK_BUSY;
}
}
//
// we got far enough along that we update the failed network cache
// if something goes wrong from here.
//
network_call:
//
// get domain controller computer name associated with current
// security context.
// Try with fast static buffer first, fallback on dynamically allocated
// buffer if not large enough.
//
pszComputerName = FastBuffer;
cchComputerName = sizeof(FastBuffer) / sizeof( WCHAR );
dwLastError = CPSGetDomainControllerName(
pszComputerName,
&cchComputerName,
fRediscoverDC
);
if( dwLastError != ERROR_SUCCESS && (cchComputerName > (sizeof(FastBuffer) / sizeof(WCHAR) ))) {
SlowBuffer = (LPWSTR) SSAlloc( cchComputerName * sizeof(WCHAR) );
if( SlowBuffer ) {
pszComputerName = SlowBuffer;
dwLastError = CPSGetDomainControllerName(
pszComputerName,
&cchComputerName,
fRediscoverDC
);
}
}
if( dwLastError == ERROR_SUCCESS ) {
LPWSTR pszTargetMachine = pszComputerName;
pszAuditComputerName = pszComputerName;
// HACKHACK workaround picky RPC/Kerberos name format behavior that
// would otherwise prevent Kerberos from being used.
if( pszTargetMachine[ 0 ] == L'\\' && pszTargetMachine[ 1 ] == L'\\' )
pszTargetMachine += 2;
dwLastError = BackupKey(
pszTargetMachine, // target computer.
pguidAction,
pbDataIn,
cbDataIn,
ppbDataOut,
pcbDataOut,
0
);
}
//
// Audit success or failure
//
if((memcmp(pguidAction, &guidRestore, sizeof(GUID)) == 0) ||
(memcmp(pguidAction, &guidRestoreW2K, sizeof(GUID)) == 0))
{
// Grab the recovery key id
WCHAR wszBackupkeyGuid[MAX_GUID_SZ_CHARS];
PBACKUPKEY_RECOVERY_BLOB pBackupBlob = (PBACKUPKEY_RECOVERY_BLOB)phMasterKey->pbBBK;
wszBackupkeyGuid[0] = 0;
if((pBackupBlob) && (phMasterKey->cbBBK > sizeof(BACKUPKEY_RECOVERY_BLOB)))
{
MyGuidToStringW(&pBackupBlob->guidKey, wszBackupkeyGuid);
}
CPSAudit(hToken,
SE_AUDITID_DPAPI_RECOVERY,
phMasterKey->wszguidMasterKey, // Key Identifier
pszAuditComputerName, // Recovery Server
0, // Recovery Reason
wszBackupkeyGuid, // Recovery Key ID
dwLastError); // Failure Reason
}
else if(memcmp(pguidAction, &guidBackup, sizeof(GUID)) == 0)
{
// Attempting a remote backup
// Grab the recovery key id
WCHAR wszBackupkeyGuid[MAX_GUID_SZ_CHARS];
PBACKUPKEY_RECOVERY_BLOB pBackupBlob = (PBACKUPKEY_RECOVERY_BLOB)*ppbDataOut;
wszBackupkeyGuid[0] = 0;
if(( dwLastError == ERROR_SUCCESS ) &&
(pBackupBlob) &&
(*pcbDataOut > sizeof(BACKUPKEY_RECOVERY_BLOB)))
{
MyGuidToStringW(&pBackupBlob->guidKey, wszBackupkeyGuid);
}
CPSAudit(hToken,
SE_AUDITID_DPAPI_BACKUP,
phMasterKey->wszguidMasterKey, // Key Identifier
pszAuditComputerName, // Recovery Server
0,
wszBackupkeyGuid, // Recovery Key ID
dwLastError); // Failure Reason
}
if( SlowBuffer ) {
SSFree( SlowBuffer );
SlowBuffer = NULL;
}
//
// common failure path is ERROR_ACCESS_DENIED for delegation scenarios
// where target machine isn't trusted for delegation.
// don't bother retry for this case.
//
if( dwLastError != ERROR_SUCCESS && dwLastError != ERROR_ACCESS_DENIED ) {
//
// if it failed, try once again and force DC re-discovery.
//
if( !fRediscoverDC ) {
fRediscoverDC = TRUE;
goto network_call;
}
//
// one of the network operations failed, so update the
// last failure variables so that we don't bang the network
// over-and-over.
//
dwLastFailTickCount = dwCandidateTickCount;
CopyMemory( &luidLastFailAuthId, &luidCandidateAuthId, sizeof(LUID));
}
D_DebugLog((DEB_TRACE_API, "LocalBackupRestoreData returned 0x%x\n", dwLastError));
return dwLastError;
}
BOOL
GetDomainControllerNameByToken(
IN HANDLE hToken,
IN OUT LPWSTR wszDomainControllerName,
IN OUT PDWORD pcchDomainControllerName,
IN BOOL fRediscover
)
/*++
This routine obtains a domain controller computer name associated with
the account related to the hToken access token.
hToken should be opened for TOKEN_QUERY access.
wszDomainControllerName should be of size (UNCLEN+1)
--*/
{
PSID pSidUser = NULL; // sid of client user.
WCHAR szUserName[ UNLEN + 1 ];
DWORD cchUserName = sizeof(szUserName) / sizeof(WCHAR);
WCHAR szDomainName[ DNLEN + 1]; // domain we want a controller for.
DWORD cchDomainName = sizeof(szDomainName) / sizeof(WCHAR);
SID_NAME_USE snu;
PDOMAIN_CONTROLLER_INFOW pDomainInfo = NULL;
LPWSTR wszQueryResult = NULL;
NET_API_STATUS nas;
DWORD dwGetDcFlags = 0;
BOOL fSuccess = FALSE;
if(wszDomainControllerName == NULL || pcchDomainControllerName == NULL)
return FALSE;
//
// first, get the sid of the user associated with the specified access
// token.
//
if(!GetTokenUserSid(hToken, &pSidUser))
return FALSE;
//
// next, lookup the domain name associated with the specified account.
//
if(!LookupAccountSidW(
NULL,
pSidUser,
szUserName,
&cchUserName,
szDomainName,
&cchDomainName,
&snu
)) {
SSFree(pSidUser);
return FALSE;
}
if( fRediscover )
dwGetDcFlags |= DS_FORCE_REDISCOVERY;
nas = DsGetDcNameW(
NULL,
szDomainName,
NULL,
NULL,
DS_DIRECTORY_SERVICE_REQUIRED | // make sure backend is NT5
DS_IS_FLAT_NAME |
DS_RETURN_DNS_NAME |
dwGetDcFlags,
&pDomainInfo
);
if( nas == ERROR_SUCCESS )
wszQueryResult = pDomainInfo->DomainControllerName;
//
// if we made a successful query, copy it for the caller and indicate
// success if appropriate.
//
if(wszQueryResult) {
DWORD cchQueryResult = lstrlenW( wszQueryResult ) + 1;
if( *pcchDomainControllerName >= cchQueryResult ) {
CopyMemory(wszDomainControllerName, wszQueryResult, cchQueryResult * sizeof(WCHAR));
fSuccess = TRUE;
}
*pcchDomainControllerName = cchQueryResult;
}
if(pDomainInfo)
NetApiBufferFree(pDomainInfo);
if(pSidUser)
SSFree(pSidUser);
return fSuccess;
}
DWORD
WINAPI
CPSGetDomainControllerName(
IN OUT LPWSTR wszDomainControllerName,
IN OUT DWORD *pcchDomainControllerName,
IN BOOL fRediscover
)
/*++
This routine collects a domain controller computer name associated
with the current impersonated user (if one is being impersonated), or
the user associated with the pvContext outstanding client call if the
thread is not already impersonating a client.
--*/
{
HANDLE hToken = NULL;
DWORD dwLastError;
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken)) {
return GetLastError();
}
if(!GetDomainControllerNameByToken(
hToken,
wszDomainControllerName,
pcchDomainControllerName,
fRediscover
)) {
dwLastError = ERROR_BAD_NET_RESP;
goto cleanup;
}
dwLastError = ERROR_SUCCESS;
cleanup:
if(hToken)
CloseHandle(hToken);
return dwLastError;
}
#define BACKUP_KEY_PREFIX L"BK-"
#define BACKUP_KEY_PREFIX_LEN 3
#define BACKUP_PUBLIC_VERSION 1
typedef struct _BACKUP_PUBLIC_KEY
{
DWORD dwVersion;
DWORD cbPublic;
DWORD cbSignature;
} BACKUP_PUBLIC_KEY, *PBACKUP_PUBLIC_KEY;
//+---------------------------------------------------------------------------
//
// Function: RetrieveBackupPublicKeyFromStorage
//
// Synopsis: Read in the domain backup public key from user profile.
//
// Arguments: [hToken] -- Handle to user token.
//
// [pSidUser] -- Pointer to user SID.
//
// [pszFilePath] -- Path to DPAPI user storage directory.
// This is typically of the form:
// %userprofile%\Application Data\Microsoft\
// Protect\<user SID>.
//
// [ppbDataOut] -- Output buffer.
// [pcbDataOut]
//
// Returns: ERROR_SUCCESS if the operation was successful, a Windows
// error code otherwise.
//
// History:
//
// Notes: When this function completes successfully, the caller is
// responsible for freeing the output buffer, via a call to
// the SSFree function.
//
//----------------------------------------------------------------------------
DWORD
RetrieveBackupPublicKeyFromStorage(
IN HANDLE hToken,
IN PSID pSidUser,
IN LPWSTR pszFilePath,
OUT PBYTE *ppbDataOut,
OUT DWORD *pcbDataOut)
{
DWORD dwLastError = ERROR_SUCCESS;
WCHAR szUserName[ UNLEN + 1 ];
DWORD cchUserName = sizeof(szUserName) / sizeof(WCHAR);
WCHAR szDomainName[ BACKUP_KEY_PREFIX_LEN + DNLEN +1]; // domain we want a controller for.
DWORD cchDomainName = sizeof(szDomainName) / sizeof(WCHAR);
HANDLE hFile = NULL;
HANDLE hMap = NULL;
PBACKUP_PUBLIC_KEY pBackupPublic = NULL;
DWORD dwFileSizeLow;
SID_NAME_USE snu;
//
// Lookup the domain name associated with the specified account, and use
// it to generate the filename, which will be of the form: BK-<domain>.
//
wcscpy(szDomainName, BACKUP_KEY_PREFIX);
cchDomainName -= BACKUP_KEY_PREFIX_LEN;
if(!LookupAccountSidW(
NULL,
pSidUser,
szUserName,
&cchUserName,
szDomainName + BACKUP_KEY_PREFIX_LEN,
&cchDomainName,
&snu))
{
return GetLastError();
}
cchDomainName += BACKUP_KEY_PREFIX_LEN;
//
// Impersonate the user.
//
if(hToken)
{
if(!SetThreadToken(NULL, hToken))
{
return GetLastError();
}
}
//
// Attempt open the file.
//
dwLastError = OpenFileInStorageArea(
NULL,
GENERIC_READ,
pszFilePath,
szDomainName,
&hFile
);
if(ERROR_SUCCESS != dwLastError)
{
goto error;
}
dwFileSizeLow = GetFileSize( hFile, NULL );
if(dwFileSizeLow == INVALID_FILE_SIZE )
{
dwLastError = ERROR_INVALID_DATA;
goto error;
}
__try
{
hMap = CreateFileMappingU(
hFile,
NULL,
PAGE_READONLY,
0,
0,
NULL
);
if(NULL == hMap)
{
dwLastError = GetLastError();
goto error;
}
pBackupPublic = (PBACKUP_PUBLIC_KEY)MapViewOfFile( hMap, FILE_MAP_READ, 0, 0, 0 );
if(NULL == pBackupPublic)
{
dwLastError = GetLastError();
goto error;
}
if((pBackupPublic->dwVersion != BACKUP_PUBLIC_VERSION) ||
(dwFileSizeLow < sizeof(BACKUP_PUBLIC_KEY) + pBackupPublic->cbPublic + pBackupPublic->cbSignature))
{
dwLastError = ERROR_INVALID_DATA;
goto error;
}
//
// Verify the signature
//
dwLastError = LogonCredVerifySignature( NULL,
(PBYTE)(pBackupPublic + 1) + pBackupPublic->cbSignature,
pBackupPublic->cbPublic,
NULL,
(PBYTE)(pBackupPublic + 1),
pBackupPublic->cbSignature);
if(ERROR_SUCCESS != dwLastError)
{
goto error;
}
*ppbDataOut = (PBYTE)SSAlloc(pBackupPublic->cbPublic);
if(NULL == ppbDataOut)
{
dwLastError = STATUS_OBJECT_NAME_NOT_FOUND;
goto error;
}
CopyMemory(*ppbDataOut,
(PBYTE)(pBackupPublic + 1) + pBackupPublic->cbSignature,
pBackupPublic->cbPublic);
*pcbDataOut = pBackupPublic->cbPublic;
} __except (EXCEPTION_EXECUTE_HANDLER)
{
dwLastError = GetExceptionCode();
}
error:
if(pBackupPublic)
{
UnmapViewOfFile(pBackupPublic);
}
if(hMap)
{
CloseHandle(hMap);
}
if(hFile)
{
CloseHandle(hFile);
}
if(hToken)
{
RevertToSelf();
}
return dwLastError;
}
DWORD
WriteBackupPublicKeyToStorage(
IN HANDLE hToken,
IN PSID pSidUser,
IN LPWSTR wszFilePath,
IN PBYTE pbData,
IN DWORD cbData)
{
DWORD dwLastError = ERROR_SUCCESS;
WCHAR szUserName[ UNLEN + 1 ];
DWORD cchUserName = sizeof(szUserName) / sizeof(WCHAR);
WCHAR szDomainName[ BACKUP_KEY_PREFIX_LEN + DNLEN +1]; // domain we want a controller for.
DWORD cchDomainName = sizeof(szDomainName) / sizeof(WCHAR);
HANDLE hFile = NULL;
HANDLE hMap = NULL;
PBACKUP_PUBLIC_KEY pBackupPublic = NULL;
DWORD dwFileSizeLow;
SID_NAME_USE snu;
PBYTE pbSignature = NULL;
DWORD cbSignature;
//
// Lookup the domain name associated with the specified account, and use
// it to generate the filename, which will be of the form: BK-<domain>.
//
wcscpy(szDomainName, BACKUP_KEY_PREFIX);
cchDomainName -= BACKUP_KEY_PREFIX_LEN;
if(!LookupAccountSidW(
NULL,
pSidUser,
szUserName,
&cchUserName,
szDomainName + BACKUP_KEY_PREFIX_LEN,
&cchDomainName,
&snu))
{
return GetLastError();
}
cchDomainName += BACKUP_KEY_PREFIX_LEN;
//
// Impersonate the user.
//
if(hToken)
{
if(!SetThreadToken(NULL, hToken))
{
return GetLastError();
}
}
//
// Sign the public key, so that it can't be
// spoofed.
//
dwLastError = LogonCredGenerateSignature(
hToken,
pbData,
cbData,
NULL,
&pbSignature,
&cbSignature);
if(ERROR_SUCCESS != dwLastError)
{
goto error;
}
//
// Write the public key data and signature out to disk.
//
dwFileSizeLow = sizeof(BACKUP_PUBLIC_KEY) + cbData + cbSignature;
dwLastError = OpenFileInStorageArea(
NULL,
GENERIC_READ | GENERIC_WRITE,
wszFilePath,
szDomainName,
&hFile
);
if(ERROR_SUCCESS != dwLastError)
{
goto error;
}
__try
{
hMap = CreateFileMappingU(
hFile,
NULL,
PAGE_READWRITE,
0,
dwFileSizeLow,
NULL
);
if(NULL == hMap)
{
dwLastError = GetLastError();
goto error;
}
pBackupPublic = (PBACKUP_PUBLIC_KEY)MapViewOfFile( hMap, FILE_MAP_WRITE , 0, 0, dwFileSizeLow );
if(NULL == pBackupPublic)
{
dwLastError = GetLastError();
goto error;
}
pBackupPublic->dwVersion = BACKUP_PUBLIC_VERSION;
pBackupPublic->cbSignature = cbSignature;
pBackupPublic->cbPublic = cbData;
CopyMemory((PBYTE)(pBackupPublic+1), pbSignature, cbSignature);
CopyMemory((PBYTE)(pBackupPublic+1) + cbSignature, pbData, cbData);
} __except (EXCEPTION_EXECUTE_HANDLER)
{
dwLastError = GetExceptionCode();
}
error:
if(pBackupPublic)
{
UnmapViewOfFile(pBackupPublic);
}
if(hMap)
{
CloseHandle(hMap);
}
if(hFile)
{
CloseHandle(hFile);
}
if(pbSignature)
{
SSFree(pbSignature);
}
if(hToken)
{
RevertToSelf();
}
return dwLastError;
}
//+---------------------------------------------------------------------------
//
// Function: AttemptLocalBackup
//
// Synopsis: Backup the specified master key using a domain public key.
// Only connect to the DC if the fRetrieve parameter is set
// to TRUE.
//
// Arguments: [fRetrieve] -- Whether to retrieve the domain public key
// from the DC before performing the backup.
//
// [hToken] -- Handle to user token.
//
// [phMasterKey] -- Pointer to master key structure. This is
// used when obtaining the path to the user
// data directory and also for auditing.
//
// [pbMasterKey] -- Plaintext master key to backup.
// [cbMasterKey]
//
// [pbLocalKey] -- Plaintext local key to backup. BUGBUG - It's
// [cbLocalKey] still a bit of a mystery as to what this
// field is used for, given that this is a
// domain user account.
//
// [ppbBBK] -- Output buffer.
// [pcbBBK]
//
// Returns: ERROR_SUCCESS if the operation was successful, a Windows
// error code otherwise.
//
// History:
//
// Notes: When this function completes successfully, the caller is
// responsible for freeing the output buffer, via a call to
// the SSFree function.
//
//----------------------------------------------------------------------------
DWORD
AttemptLocalBackup(
IN BOOL fRetrieve,
IN HANDLE hToken,
IN PMASTERKEY_STORED phMasterKey,
IN PBYTE pbMasterKey,
IN DWORD cbMasterKey,
IN PBYTE pbLocalKey,
IN DWORD cbLocalKey,
OUT PBYTE * ppbBBK,
OUT DWORD * pcbBBK)
{
DWORD dwLastError = ERROR_SUCCESS;
PCCERT_CONTEXT pPublic = NULL;
PBYTE pbPublic = NULL;
DWORD cbPublic = 0;
HCRYPTPROV hProv = NULL;
HCRYPTKEY hPublicKey = NULL;
PBYTE pbPayloadKey = NULL;;
PBACKUPKEY_KEY_BLOB pKeyBlob = NULL;
DWORD cbKeyBlobData = 0;
DWORD cbKeyBlob = 0;
PBACKUPKEY_INNER_BLOB pInnerBlob = NULL;
DWORD cbInnerBlob = 0;
DWORD cbInnerBlobData = 0;
PBYTE pbData = NULL;
DWORD cbTemp = 0;
PBACKUPKEY_RECOVERY_BLOB pOuterBlob = NULL;
DWORD cbOuterBlob = 0;
PSID pSidUser = NULL; // sid of client user.
DWORD cbSid = 0;
WCHAR wszBackupKeyID[MAX_GUID_SZ_CHARS];
BYTE rgbThumbprint[A_SHA_DIGEST_LEN];
DWORD cbThumbprint;
wszBackupKeyID[0] = 0;
if(!GetTokenUserSid(hToken, &pSidUser))
{
dwLastError = GetLastError();
goto error;
}
if(fRetrieve)
{
// Attempt to retrieve the public from
// the DC.
//
// We impersonate when we do this
//
if (!SetThreadToken(NULL, hToken))
{
dwLastError = GetLastError();
goto error;
}
dwLastError = LocalBackupRestoreData(hToken,
phMasterKey,
pbMasterKey,
0,
&pbPublic,
&cbPublic,
&guidRetrieve);
//
// Revert back to ourself
//
if (!SetThreadToken(NULL, NULL))
{
if (ERROR_SUCCESS == dwLastError)
{
dwLastError = GetLastError();
goto error;
}
}
}
else
{
//
// We're attempting a backup, so first see if we have a local copy of
// the public.
//
dwLastError = RetrieveBackupPublicKeyFromStorage(hToken,
pSidUser,
phMasterKey->szFilePath,
&pbPublic,
&cbPublic);
}
if(ERROR_SUCCESS == dwLastError)
{
pPublic = CertCreateCertificateContext(X509_ASN_ENCODING,
pbPublic,
cbPublic);
if(NULL == pPublic)
{
dwLastError = GetLastError();
}
}
if(dwLastError != ERROR_SUCCESS)
{
goto error;
}
if(sizeof(GUID) == pPublic->pCertInfo->SerialNumber.cbData)
{
MyGuidToStringW((GUID *)pPublic->pCertInfo->SerialNumber.pbData, wszBackupKeyID);
}
if(fRetrieve)
{
// Writing the public key to disk is not critical,
// so we don't need to check for an error return.
WriteBackupPublicKeyToStorage(hToken,
pSidUser,
phMasterKey->szFilePath,
pbPublic,
cbPublic);
}
if(!CryptAcquireContext(&hProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT))
{
dwLastError = GetLastError();
goto error;
}
if(!CryptImportPublicKeyInfoEx(hProv,
pPublic->dwCertEncodingType,
&pPublic->pCertInfo->SubjectPublicKeyInfo,
CALG_RSA_KEYX,
NULL,
NULL,
&hPublicKey))
{
dwLastError = GetLastError();
goto error;
}
cbSid = GetLengthSid(pSidUser);
cbInnerBlobData = sizeof(BACKUPKEY_INNER_BLOB) +
cbLocalKey +
cbSid +
A_SHA_DIGEST_LEN;
//
// Round up to blocklen
//
cbInnerBlob = (cbInnerBlobData + (DES_BLOCKLEN - 1)) & ~(DES_BLOCKLEN-1);
cbTemp = sizeof(cbKeyBlob);
if(!CryptGetKeyParam(hPublicKey,
KP_BLOCKLEN,
(PBYTE)&cbKeyBlob,
&cbTemp,
0))
{
dwLastError = GetLastError();
goto error;
}
cbKeyBlob >>= 3; // convert from bits to bytes
cbOuterBlob = sizeof(BACKUPKEY_RECOVERY_BLOB) +
cbKeyBlob +
cbInnerBlob;
pOuterBlob = (PBACKUPKEY_RECOVERY_BLOB)SSAlloc(cbOuterBlob);
if(NULL == pOuterBlob)
{
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
goto error;
}
pKeyBlob = (PBACKUPKEY_KEY_BLOB)(pOuterBlob+1);
pInnerBlob = (PBACKUPKEY_INNER_BLOB)((PBYTE)pKeyBlob + cbKeyBlob);
// Initialize the payload key
cbKeyBlobData = sizeof(BACKUPKEY_KEY_BLOB) + cbMasterKey + DES3_KEYSIZE + DES_BLOCKLEN;
pKeyBlob->cbMasterKey = cbMasterKey;
pKeyBlob->cbPayloadKey = DES3_KEYSIZE + DES_BLOCKLEN;
pbPayloadKey = (PBYTE)(pKeyBlob+1) + cbMasterKey;
CopyMemory((PBYTE)(pKeyBlob+1), pbMasterKey, cbMasterKey);
//
// Generate a payload key
//
if(!RtlGenRandom(pbPayloadKey, pKeyBlob->cbPayloadKey))
{
dwLastError = GetLastError();
goto error;
}
// Populate the payload
pInnerBlob->dwPayloadVersion = BACKUPKEY_PAYLOAD_VERSION;
pInnerBlob->cbLocalKey = cbLocalKey;
pbData = (PBYTE)(pInnerBlob+1);
CopyMemory(pbData, pbLocalKey, cbLocalKey);
pbData += cbLocalKey;
CopyMemory(pbData, pSidUser, cbSid);
pbData += cbSid;
// Pad
if(cbInnerBlob > cbInnerBlobData)
{
if(!RtlGenRandom(pbData, cbInnerBlob - cbInnerBlobData))
{
dwLastError = GetLastError();
goto error;
}
pbData += cbInnerBlob - cbInnerBlobData;
}
// Generate the payload MAC
FMyPrimitiveSHA( (PBYTE)pInnerBlob,
cbInnerBlob - A_SHA_DIGEST_LEN,
pbData);
//
// Encrypt with 3DES CBC
//
{
DES3TABLE s3DESKey;
BYTE InputBlock[DES_BLOCKLEN];
DWORD iBlock;
DWORD cBlocks = cbInnerBlob/DES_BLOCKLEN;
BYTE feedback[ DES_BLOCKLEN ];
// initialize 3des key
//
if(cBlocks*DES_BLOCKLEN != cbInnerBlob)
{
// Master key must be a multiple of DES_BLOCKLEN
dwLastError = NTE_BAD_KEY;
goto error;
}
tripledes3key(&s3DESKey, pbPayloadKey);
//
// IV is derived from the DES_BLOCKLEN bytes of the calculated
// rgbSymKey, after the 3des key
CopyMemory(feedback, pbPayloadKey + DES3_KEYSIZE, DES_BLOCKLEN);
for(iBlock=0; iBlock < cBlocks; iBlock++)
{
CopyMemory(InputBlock,
((PBYTE)pInnerBlob)+iBlock*DES_BLOCKLEN,
DES_BLOCKLEN);
CBC(tripledes,
DES_BLOCKLEN,
((PBYTE)pInnerBlob)+iBlock*DES_BLOCKLEN,
InputBlock,
&s3DESKey,
ENCRYPT,
feedback);
}
}
//
// Encrypt master key and payload key to
// the public key
if(!CryptEncrypt(hPublicKey,
NULL,
TRUE,
0, // CRYPT_OAEP
(PBYTE)pKeyBlob,
&cbKeyBlobData,
cbKeyBlob))
{
dwLastError = GetLastError();
goto error;
}
if(cbKeyBlobData != cbKeyBlob)
{
CopyMemory((PBYTE)pKeyBlob + cbKeyBlobData,
pInnerBlob,
cbInnerBlob);
cbOuterBlob -= cbKeyBlob - cbKeyBlobData;
}
pOuterBlob->dwVersion = BACKUPKEY_RECOVERY_BLOB_VERSION;
pOuterBlob->cbEncryptedMasterKey = cbKeyBlobData;
pOuterBlob->cbEncryptedPayload = cbInnerBlob;
CopyMemory(&pOuterBlob->guidKey,
pPublic->pCertInfo->SubjectUniqueId.pbData,
sizeof(GUID));
*ppbBBK = (PBYTE)pOuterBlob;
*pcbBBK = cbOuterBlob;
pOuterBlob = NULL;
error:
if((fRetrieve) || (ERROR_SUCCESS == dwLastError))
{
// Only audit if we're attempting to do the long backup.
//SetThreadToken(NULL, hToken);
CPSAudit(hToken,
SE_AUDITID_DPAPI_BACKUP,
phMasterKey->wszguidMasterKey, // Key Identifier
L"", // Recovery Server
0,
wszBackupKeyID, // Recovery Key ID
dwLastError); // Failure Reason
//SetThreadToken(NULL, NULL);
}
if(pPublic)
{
CertFreeCertificateContext(pPublic);
}
if(pbPublic)
{
SSFree(pbPublic);
}
if(pOuterBlob)
{
SSFree(pOuterBlob);
}
if (pSidUser)
{
SSFree(pSidUser);
}
if(hPublicKey)
{
CryptDestroyKey(hPublicKey);
}
if(hProv)
{
CryptReleaseContext(hProv, 0);
}
return dwLastError;
}
BOOLEAN
CompareNameToDnsName(
LPCWSTR pszName,
LPCWSTR pszDnsName)
{
WCHAR szLocalName[MAX_COMPUTERNAME_LENGTH + 1];
UNICODE_STRING LocalName;
UNICODE_STRING Name;
PWSTR pszPeriod;
if(pszName == NULL && pszDnsName == NULL)
{
return TRUE;
}
if(pszName == NULL || pszDnsName == NULL)
{
return FALSE;
}
// Extract netbios name from dns name
wcsncpy(szLocalName, pszDnsName, MAX_COMPUTERNAME_LENGTH);
szLocalName[MAX_COMPUTERNAME_LENGTH] = L'\0';
if(pszPeriod = wcschr(szLocalName, L'.'))
{
*pszPeriod = L'\0';
}
// Compare strings.
RtlInitUnicodeString(&Name, pszName);
RtlInitUnicodeString(&LocalName, szLocalName);
return RtlEqualDomainName(&Name, &LocalName);
}
DWORD
WINAPI
BackupKey(
IN LPCWSTR pszComputerName,
IN const GUID *pguidActionAgent,
IN BYTE *pDataIn,
IN DWORD cbDataIn,
IN OUT BYTE **ppDataOut,
IN OUT DWORD *pcbDataOut,
IN DWORD dwParam
)
{
RPC_BINDING_HANDLE h = NULL;
WCHAR *pStringBinding = NULL;
BOOL fLocal = FALSE;
HANDLE hToken = NULL;
RPC_STATUS RpcStatus = RPC_S_OK;
DWORD dwRetVal = ERROR_INVALID_PARAMETER;
DWORD i;
WCHAR szLocalComputerName[MAX_COMPUTERNAME_LENGTH + 2];
DWORD BufSize = MAX_COMPUTERNAME_LENGTH + 2;
LPWSTR pszSPN = NULL;
#if DBG
D_DebugLog((DEB_TRACE, "BackupKey called\n"));
D_DebugLog((DEB_TRACE, " DC Name:%ls\n", pszComputerName));
if(memcmp(pguidActionAgent, &guidRetrieve, sizeof(GUID)) == 0)
{
D_DebugLog((DEB_TRACE, " Retrieve domain public key\n"));
}
else if(memcmp(pguidActionAgent, &guidRestore, sizeof(GUID)) == 0)
{
D_DebugLog((DEB_TRACE, " Restore master key\n"));
}
else if(memcmp(pguidActionAgent, &guidRestoreW2K, sizeof(GUID)) == 0)
{
D_DebugLog((DEB_TRACE, " Restore master key (Win2K)\n"));
}
else if(memcmp(pguidActionAgent, &guidBackup, sizeof(GUID)) == 0)
{
D_DebugLog((DEB_TRACE, " Backup master key (Win2K)\n"));
}
else
{
D_DebugLog((DEB_TRACE, " Unknown operation\n"));
}
#endif
*ppDataOut = NULL;
*pcbDataOut = 0;
//
// Is the user logged on locally--or alternatively, is this the
// user's recovery DC?
//
if (!(GetComputerNameW(szLocalComputerName, &BufSize)))
{
dwRetVal = GetLastError();
D_DebugLog((DEB_TRACE, "BackupKey returned 0x%x\n", dwRetVal));
return dwRetVal;
}
if(IsLocal())
{
// User is logged on locally.
fLocal = TRUE;
}
else
{
D_DebugLog((DEB_TRACE, "User is not logged on locally\n"));
if((pszComputerName == NULL) || CompareNameToDnsName(szLocalComputerName, pszComputerName))
{
D_DebugLog((DEB_TRACE, "This is the user's recovery DC\n"));
fLocal = TRUE;
}
}
//
// Build the SPN
//
pszSPN = (LPWSTR)LocalAlloc(LPTR, ( wcslen(pszComputerName) +
1 +
wcslen(DPAPI_SERVICE_NAME) +
1 ) * sizeof(WCHAR) );
if(pszSPN == NULL)
{
dwRetVal = ERROR_NOT_ENOUGH_MEMORY;
D_DebugLog((DEB_TRACE, "BackupKey returned 0x%x\n", dwRetVal));
return dwRetVal;
}
wcscpy(pszSPN, DPAPI_SERVICE_NAME);
wcscat(pszSPN, L"/");
wcscat(pszSPN, pszComputerName);
//
// Try all of the bindings
//
for (i = fLocal?0:1; i < g_cwzrBackupBindingList; i++)
{
RPC_SECURITY_QOS RpcQos;
if (RPC_S_OK != RpcNetworkIsProtseqValidW(
(unsigned short *)g_awzrBackupBindingList[i].pszProtSeq))
{
continue;
}
RpcStatus = RpcStringBindingComposeW(
NULL,
(unsigned short *)g_awzrBackupBindingList[i].pszProtSeq,
(unsigned short *)pszComputerName,
(unsigned short *)g_awzrBackupBindingList[i].pszEndpoint,
NULL,
&pStringBinding);
if (RPC_S_OK != RpcStatus)
{
continue;
}
RpcStatus = RpcBindingFromStringBindingW(
pStringBinding,
&h);
if (NULL != pStringBinding)
{
RpcStringFreeW(&pStringBinding);
}
if (RPC_S_OK != RpcStatus)
{
continue;
}
RpcStatus = RpcEpResolveBinding(
h,
BackupKey_v1_0_c_ifspec);
if (RPC_S_OK != RpcStatus)
{
continue;
}
//
// enable privacy and negotiated re-authentication.
// a fresh authentication is required in the event an existing connection
// to the target machine already existed which was made with non-default
// credentials.
//
ZeroMemory( &RpcQos, sizeof(RpcQos) );
RpcQos.Version = RPC_C_SECURITY_QOS_VERSION;
RpcQos.Capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
RpcQos.IdentityTracking = RPC_C_QOS_IDENTITY_DYNAMIC;
RpcQos.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE;
RpcStatus = RpcBindingSetAuthInfoExW(
h,
pszSPN,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_AUTHN_GSS_NEGOTIATE,
0,
0,
&RpcQos
);
if (RPC_S_OK != RpcStatus)
{
continue;
}
__try
{
dwRetVal = BackuprKey(
h,
(GUID*)pguidActionAgent,
pDataIn,
cbDataIn,
ppDataOut,
pcbDataOut,
dwParam
);
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
RpcStatus = _exception_code();
}
if (RPC_S_OK == RpcStatus)
{
break;
}
}
if((RPC_S_OK != RpcStatus) && (fLocal == FALSE))
{
//
// If we're going off machine, check to see if delegation is
// allowed. If it isn't, then this is probably the reason for the
// the failure, so return SEC_E_DELEGATION_REQUIRED.
//
LPUSER_INFO_1 pUserInfo = NULL;
wcscat(szLocalComputerName, L"$");
if((OpenThreadToken(GetCurrentThread(),
TOKEN_IMPERSONATE,
TRUE,
&hToken)) &&
SetThreadToken(NULL, NULL))
{
if (NERR_Success == NetUserGetInfo(
pszComputerName,
szLocalComputerName,
1,
(PBYTE *)&pUserInfo
)) {
if (!(UF_TRUSTED_FOR_DELEGATION & pUserInfo->usri1_flags))
{
D_DebugLog((DEB_TRACE, "Server is not trusted for delegation\n"));
RpcStatus = SEC_E_DELEGATION_REQUIRED;
}
NetApiBufferFree(pUserInfo);
}
//
// Impersonate again
// We are going to fail. No need to check the return value of SetThreadToken.
//
(void) SetThreadToken(NULL, hToken);
}
}
if(hToken)
{
CloseHandle(hToken);
}
if(RPC_S_OK != RpcStatus)
{
dwRetVal = RpcStatus;
}
if(h)
{
RpcBindingFree(&h);
}
if(pszSPN)
{
LocalFree(pszSPN);
}
D_DebugLog((DEB_TRACE, "BackupKey returned 0x%x\n", dwRetVal));
return dwRetVal;
}