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.
 
 
 
 
 
 

1376 lines
50 KiB

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright(C) 2002 Microsoft Corporation
//
// File: sysprep.cxx
//
//----------------------------------------------------------------------------
#include "..\pch\headers.hxx"
#include "security.hxx"
#include "sysprep.hxx"
static WCHAR gwszSysprepKey[] = L"TSSK"; // Task Scheduler Sysprep Key
static WCHAR gwszSysprepIdentity[] = L"TSSI"; // Task Scheduler Sysprep Identity Data
// needed by ScavengeSASecurityDBase
extern SERVICE_STATUS g_SvcStatus;
#define SERVICE_RUNNING 0x00000004
//+---------------------------------------------------------------------------
//
// Function: GetUniqueSPSName
//
// Synopsis: calls NewWorkItem a few times trying to get a unique file name out of it
//
// Arguments: ITaskScheduler *pITaskScheduler, IUnknown** pITask
// WCHAR* pwszTaskName, to receive the name that was actually
// used in the creation of the task
//
// Returns: Various HRESULTs
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT GetUniqueSPSName(ITaskScheduler* pITaskScheduler, ITask** ppITask, WCHAR* pwszTaskName)
{
HRESULT hr = E_FAIL;
// okay, so we're not distinguishing between errors, code's simpler
// and the only expectable error is "already exists"
for (int i = 0; (i < 16) && FAILED(hr); i++)
{
if (FAILED(StringCchPrintf(pwszTaskName, 20, L"$~$Sys%X$", i)))
break;
hr = pITaskScheduler->NewWorkItem(pwszTaskName,
CLSID_CTask,
IID_ITask,
(IUnknown**)ppITask);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: PrepSysPrepTask
//
// Synopsis: Creates a task to be run which will call run the sysprep
// code in the local system account
//
// Arguments: Task** ppITaskToRun, to receive pointer to ITask interface
// task is to activated by calling the Run() method
// WCHAR* pwszTaskName, to receive the name that was actually
// used in the creation of the task
//
// Returns: Various HRESULTs
//
// Notes: None.
//
//----------------------------------------------------------------------------
HRESULT PrepSysPrepTask(ITask** ppITaskToRun, WCHAR* pwszTaskName)
{
HRESULT hr = E_FAIL;
WCHAR applicationName[MAX_PATH +1];
WCHAR argument[MAX_PATH +20];
DWORD expandedSize;
expandedSize = ExpandEnvironmentStrings(L"\"%SystemRoot%\\System32\\rundll32.exe\"", applicationName, MAX_PATH +1);
if ((0 == expandedSize) || expandedSize > (MAX_PATH +1))
return HRESULT_FROM_WIN32(GetLastError());
expandedSize = ExpandEnvironmentStrings(L"\"%SystemRoot%\\System32\\SchedSvc.dll\",SysPrepCallback", argument, MAX_PATH +20);
if ((0 == expandedSize) || expandedSize > (MAX_PATH +1))
return HRESULT_FROM_WIN32(GetLastError());
TASK_TRIGGER tigger;
tigger.wStartHour = 10;
tigger.wStartMinute = 20;
tigger.wBeginYear = 1957;
tigger.wBeginMonth = 6;
tigger.wBeginDay = 14;
tigger.wEndYear = 2001;
tigger.wEndMonth = 10;
tigger.wEndDay = 8 ;
tigger.MinutesDuration = 0;
tigger.MinutesInterval = 0;
tigger.rgFlags = 0;
tigger.TriggerType = TASK_TIME_TRIGGER_ONCE;
tigger.Reserved2 = 0;
tigger.wRandomMinutesInterval = 0;
tigger.cbTriggerSize = sizeof(TASK_TRIGGER);
tigger.Reserved1 = 0;
ITaskScheduler *pITaskScheduler = NULL;
ITask *pITask = NULL;
IPersistFile *pIPersistFile = NULL;
ITaskTrigger* pTrigger = NULL;
WORD idontcare;
hr = CoCreateInstance( CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITaskScheduler);
if (SUCCEEDED(hr) &&
SUCCEEDED(hr = GetUniqueSPSName(pITaskScheduler, &pITask, pwszTaskName)) &&
SUCCEEDED(hr = pITask->SetApplicationName(applicationName)) &&
SUCCEEDED(hr = pITask->SetParameters(argument)) &&
SUCCEEDED(hr = pITask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_DISABLED)) &&
SUCCEEDED(hr = pITask->SetAccountInformation(L"", NULL)) &&
SUCCEEDED(hr = pITask->CreateTrigger(&idontcare, &pTrigger)) &&
SUCCEEDED(hr = pTrigger->SetTrigger(&tigger)) &&
SUCCEEDED(hr = pITask->QueryInterface(IID_IPersistFile, (void **)&pIPersistFile)) &&
SUCCEEDED(hr = pIPersistFile->Save(NULL, TRUE)))
{
// return it to the caller
*ppITaskToRun = pITask;
}
else
if (pITask)
pITask->Release();
if (pITaskScheduler)
pITaskScheduler->Release();
if (pIPersistFile)
pIPersistFile->Release();
if (pTrigger)
pTrigger->Release();
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: SaveSysprepInfo
//
// Synopsis: Saves job identity and credential key information prior to Sysprep
// so that it can be used to convert old data after Sysprep.
//
// Arguments: None.
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT SaveSysprepInfo(void)
{
//
// Initialize security
//
InitSS();
SetMysteryDWORDValue();
HRESULT hr = PreProcessNetScheduleJobs();
if (FAILED(hr))
{
// ignore, we still need to do the other processing
}
//
// Obtain a provider handle to the CSP (for use with Crypto API).
//
HCRYPTPROV hCSP = NULL;
hr = GetCSPHandle(&hCSP);
if (SUCCEEDED(hr))
{
if (hCSP)
{
//
// We've got the handle, now save the important stuff
//
hr = SaveSysprepKeyInfo(hCSP);
if (SUCCEEDED(hr))
{
hr = SaveSysprepIdentityInfo(hCSP);
}
CloseCSPHandle(hCSP);
}
else
{
hr = E_FAIL;
}
}
//
// Done doing security stuff
//
UninitSS();
if (FAILED(hr))
{
CHECK_HRESULT(hr);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: SaveSysprepKeyInfo
//
// Synopsis: Stores the computed CredentialKey as a secret prior to sysprep so that it can be
// retrieved and used to decrypt credentials after sysprep, so those credentials may
// be encrypted again using the post-sysprep key.
//
// Arguments: hCSP - handle to the crypto service provider
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT SaveSysprepKeyInfo(HCRYPTPROV hCSP)
{
//
// Generate the encryption key
//
RC2_KEY_INFO RC2KeyInfo;
HRESULT hr = ComputeCredentialKey(hCSP, &RC2KeyInfo);
if (SUCCEEDED(hr))
{
//
// Write the key to LSA
//
hr = WriteLsaData(sizeof(gwszSysprepKey), gwszSysprepKey, sizeof(RC2KeyInfo), (BYTE*)&RC2KeyInfo);
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: SaveSysprepIdentityInfo
//
// Synopsis: Obtains the credential index and NULL password setting for each
// existing job identity and stores that information along with the
// associated filename in an LSA secret so that the identities can
// be rehashed and stored in the identity database, postsysprep,
// still associated with the correct credential.
//
// Arguments: hCSP - handle to the crypto service provider
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT SaveSysprepIdentityInfo(HCRYPTPROV hCSP)
{
WCHAR* pwszTasksFolder = NULL;
HANDLE hFileEnum = INVALID_HANDLE_VALUE;
DWORD cbSAI;
DWORD cbSAC;
BYTE* pbSAI = NULL;
BYTE* pbSAC = NULL;
//
// First, read the security database so we can do the lookups
//
HRESULT hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
if (SUCCEEDED(hr))
{
if (cbSAI <= SAI_HEADER_SIZE)
{
//
// Database empty, nothing to do.
//
}
else
{
//
// Enumerate job objects in the task's folder directory.
//
hr = GetTasksFolder(&pwszTasksFolder);
if (SUCCEEDED(hr))
{
WCHAR wszSearchPath[MAX_PATH + 1];
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder);
if (SUCCEEDED(hr))
{
hr = StringCchCat(wszSearchPath, MAX_PATH + 1, EXTENSION_WILDCARD TSZ_JOB);
if (SUCCEEDED(hr))
{
WIN32_FIND_DATA fd;
DWORD dwRet = 0;
if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE)
{
//
// Either no jobs (this is OK), or an error occurred.
//
dwRet = GetLastError();
if (dwRet != ERROR_FILE_NOT_FOUND)
hr = _HRESULT_FROM_WIN32(dwRet);
}
else
{
//
// Must concatenate the filename returned from the enumeration onto the folder path
// before computing the hash. Prepare for doing that repeatedly by taking the path,
// adding a slash, and remembering the next character position.
//
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder);
if (SUCCEEDED(hr))
{
DWORD iConcatenation = lstrlenW(pwszTasksFolder);
wszSearchPath[iConcatenation++] = L'\\';
//
// Allocate buffer used to collect identity data that will be written to the LSA secret
//
BYTE rgbIdentityData[MAX_SECRET_SIZE];
BYTE* pbCurrentData = (BYTE*)&rgbIdentityData;
DWORD cbIdentityData = 0;
//
// Process each found file
//
BYTE rgbIdentity[HASH_DATA_SIZE];
BOOL bIsPasswordNull;
DWORD dwCredentialIndex;
DWORD cbFileName;
while (dwRet != ERROR_NO_MORE_FILES)
{
//
// Truncate the existing name after the folder path,
// then concatenate the new filename onto it.
//
wszSearchPath[iConcatenation] = L'\0';
if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName)))
{
//
// Hash the job into a unique identity.
//
if (SUCCEEDED(HashJobIdentity(hCSP, wszSearchPath, rgbIdentity)))
{
//
// Find the identity in the SAI for this job
//
hr = SAIFindIdentity(rgbIdentity,
cbSAI,
pbSAI,
&dwCredentialIndex,
&bIsPasswordNull,
NULL,
NULL,
NULL);
//
// S_OK means the identity was found; S_FALSE means it wasn't
// Other codes are errors. We process only the S_OKs, of course.
//
if (S_OK == hr)
{
cbFileName = (lstrlenW(fd.cFileName) + 1) * sizeof(WCHAR);
if ((cbIdentityData + cbFileName + sizeof(BOOL) + sizeof(DWORD)) > MAX_SECRET_SIZE)
{
//
// this should _never_ happen, as we shouldn't be able to exceed the size
// given that we only add data for jobs that have already been found in
// the SAI, and we are collecting less data than was stored there
//
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
break;
}
CopyMemory(pbCurrentData, fd.cFileName, cbFileName);
pbCurrentData += cbFileName;
CopyMemory(pbCurrentData, &bIsPasswordNull, sizeof(BOOL));
pbCurrentData += sizeof(BOOL);
CopyMemory(pbCurrentData, &dwCredentialIndex, sizeof(DWORD));
pbCurrentData += sizeof(DWORD);
cbIdentityData += (cbFileName + sizeof(BOOL) + sizeof(DWORD));
}
else
{
hr = S_OK; // OK, we failed this one, go on to the next
}
}
}
if (!FindNextFile(hFileEnum, &fd))
{
dwRet = GetLastError();
if (dwRet != ERROR_NO_MORE_FILES)
hr = _HRESULT_FROM_WIN32(dwRet);
break;
}
}
if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) != hr)
{
hr = WriteLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity, cbIdentityData, (BYTE*)&rgbIdentityData);
}
}
}
}
}
}
}
}
if (pbSAI) LocalFree(pbSAI);
if (pbSAC) LocalFree(pbSAC);
if (pwszTasksFolder)
delete [] pwszTasksFolder;
if (hFileEnum != INVALID_HANDLE_VALUE)
FindClose(hFileEnum);
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: PreProcessNetScheduleJobs
//
// Synopsis: Netschedule jobs (AT jobs) are signed internally with a hash so
// that the service can validate their authenticity at run time.
// Sysprep changes important data used by the Crypto API such that
// the generated hash will be different and can never match the hash
// stored in the AT job. In order to be runnable again, the AT jobs
// must be signed again with the new hash.
//
// *** WARNING ***
// This means that if someone could drop a bogus AT job into the tasks
// folder, it would automatically get signed as a result of running sysprep.
// In order to prevent this, check all AT jobs prior to sysprep and
// eliminate any that are not valid.
//
// Arguments: None
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT PreProcessNetScheduleJobs(void)
{
HANDLE hFileEnum = INVALID_HANDLE_VALUE;
WCHAR* pwszTasksFolder = NULL;
HRESULT hr = GetTasksFolder(&pwszTasksFolder);
if (SUCCEEDED(hr))
{
WCHAR wszSearchPath[MAX_PATH + 1];
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder);
if (SUCCEEDED(hr))
{
hr = StringCchCat(wszSearchPath, MAX_PATH + 1, L"\\" TSZ_AT_JOB_PREFIX L"*" TSZ_DOTJOB);
if (SUCCEEDED(hr))
{
WIN32_FIND_DATA fd;
DWORD dwRet = 0;
if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE)
{
//
// Either no jobs (this is OK), or an error occurred.
//
dwRet = GetLastError();
if (dwRet != ERROR_FILE_NOT_FOUND)
hr = _HRESULT_FROM_WIN32(dwRet);
}
else
{
//
// Must concatenate the filename returned from the enumeration onto the folder path
// before loading the job. Prepare for doing that repeatedly by taking the path,
// adding a slash, and remembering the next character position.
//
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder);
if (SUCCEEDED(hr))
{
DWORD iConcatenation = lstrlenW(pwszTasksFolder);
wszSearchPath[iConcatenation++] = L'\\';
//
// Process each found file
//
while (dwRet != ERROR_NO_MORE_FILES)
{
//
// Truncate the existing name after the folder path,
// then concatenate the new filename onto it.
//
wszSearchPath[iConcatenation] = L'\0';
if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName)))
{
//
// Load and check signature of each job
//
CJob* pJob = CJob::Create();
if (!pJob)
{
hr = E_OUTOFMEMORY;
break;
}
if (SUCCEEDED(pJob->Load(wszSearchPath, 0)))
{
if (!pJob->VerifySignature())
{
if (!DeleteFile(wszSearchPath))
{
dwRet = GetLastError();
// go on anyway
}
}
}
pJob->Release();
}
if (!FindNextFile(hFileEnum, &fd))
{
dwRet = GetLastError();
if (dwRet != ERROR_NO_MORE_FILES)
hr = _HRESULT_FROM_WIN32(dwRet);
break;
}
}
}
}
if (hFileEnum != INVALID_HANDLE_VALUE)
FindClose(hFileEnum);
}
}
}
if (pwszTasksFolder)
delete [] pwszTasksFolder;
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: GetSysprepIdentityInfo
//
// Synopsis: Retrieves the stored job identity data so new hashes can be calculated.
//
// Arguments: pcbIdentityData - pointer to count of bytes in the identity data
// ppIdentityData - pointer to pointer to retrieved identity data
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT GetSysprepIdentityInfo(DWORD* pcbIdentityData, BYTE** ppIdentityData)
{
*ppIdentityData = NULL;
//
// Retrieve job identity data from LSA, if it exists
//
HRESULT hr = ReadLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity, pcbIdentityData, ppIdentityData);
if (FAILED(hr))
{
if (*ppIdentityData != NULL)
{
LocalFree(*ppIdentityData);
*ppIdentityData = NULL;
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: GetSysprepKeyInfo
//
// Synopsis: Retrieves the stored pre-sysprep CredentialKey so that it can be used to decrypt
// credentials after sysprep, so those credentials may be encrypted again using the
// post-sysprep key.
//
// Arguments: pcbRC2KeyInfo - pointer to count of bytes in key data
// ppRC2KeyInfo - pointer to pointer to retrieved key
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT GetSysprepKeyInfo(DWORD* pcbRC2KeyInfo, RC2_KEY_INFO** ppRC2KeyInfo)
{
*ppRC2KeyInfo = NULL;
//
// Get the key from LSA
//
HRESULT hr = ReadLsaData(sizeof(gwszSysprepKey), gwszSysprepKey, pcbRC2KeyInfo, (BYTE**)ppRC2KeyInfo);
if (SUCCEEDED(hr))
{
//
// Check the size. We know exactly how big this should be, so make sure it is.
//
if (*pcbRC2KeyInfo != sizeof(RC2_KEY_INFO))
{
*pcbRC2KeyInfo = 0;
LocalFree(*ppRC2KeyInfo);
*ppRC2KeyInfo = NULL;
hr = E_FAIL;
}
}
else // failed!
{
if (*ppRC2KeyInfo != NULL)
{
LocalFree(*ppRC2KeyInfo);
*ppRC2KeyInfo = NULL;
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ConvertSysprepInfo
//
// Synopsis: Converts credentials stored prior to sysprep so they may be decrypted after sysprep.
// It does this by retrieving the pre-sysprep key, decrypting the credentials with it, and then
// encrypting them using the new post-sysprep key.
//
// Arguments: None
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT ConvertSysprepInfo(void)
{
//
// Initialize these up here so they will have values if we exit early
//
HCRYPTPROV hCSP = NULL;
BYTE* pbSAI = NULL;
DWORD cbSAI = 0;
BYTE* pbSAC = NULL;
DWORD cbSAC = 0;
RC2_KEY_INFO* pRC2KeyPreSysprep = NULL;
DWORD cbRC2KeyPreSysprep = 0;
RC2_KEY_INFO RC2KeyPostSysprep;
//
// Initialize security
//
InitSS();
SetMysteryDWORDValue();
//
// Set this as it is relied on by ScavengeSASecurityDBase
//
g_SvcStatus.dwCurrentState = SERVICE_RUNNING;
//
// Get the tasks folder for use by ConvertNetScheduleJobs, ConvertIdentityData, and ScavengeSASecurityDBase
//
HRESULT hr = GetTasksFolder(&g_TasksFolderInfo.ptszPath);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Obtain a provider handle to the CSP (for use with Crypto API)
//
hr = GetCSPHandle(&hCSP);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Get the pre-sysprep key from LSA
//
hr = GetSysprepKeyInfo(&cbRC2KeyPreSysprep, &pRC2KeyPreSysprep);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Generate the post-sysprep key
//
hr = ComputeCredentialKey(hCSP, &RC2KeyPostSysprep);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// The Net Schedule conversions are independent of the rest of the logic,
// so do them first and get them out of the way.
//
hr = ConvertNetScheduleJobs();
if (FAILED(hr))
{
// ignore, we still can do our other conversions
hr = S_OK;
}
hr = ConvertNetScheduleCredentialData(pRC2KeyPreSysprep, &RC2KeyPostSysprep);
if (FAILED(hr))
{
// ignore, we still can do our other conversions
hr = S_OK;
}
//
// Read SAI & SAC databases.
// It is not necessary to guard security db access with a critsec as elsewhere in the code,
// as the service will not be running during MiniSetup and there will be no other threads accessing this data.
//
hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Do some validations on the database
//
if (cbSAI <= SAI_HEADER_SIZE || pbSAI == NULL ||
cbSAC <= SAC_HEADER_SIZE || pbSAC == NULL)
{
goto ErrorExit;
}
//
// Place updated entries into SAI for all jobs
//
hr = ConvertIdentityData(hCSP, &cbSAI, &pbSAI, &cbSAC, &pbSAC);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Place updated entries into SAC for all credentials
//
hr = ConvertCredentialData(pRC2KeyPreSysprep, &RC2KeyPostSysprep, &cbSAI, &pbSAI, &cbSAC, &pbSAC);
//
// Clearing key content at earliest opportunity
//
SecureZeroMemory(&RC2KeyPostSysprep, sizeof(RC2KeyPostSysprep));
if (SUCCEEDED(hr))
{
//
// Update security database with new identities and converted credentials
//
hr = WriteSecurityDBase(cbSAI, pbSAI, cbSAC, pbSAC);
if (SUCCEEDED(hr))
{
//
// Allow scavenger cleanup to delete old identities
//
ScavengeSASecurityDBase();
}
}
ErrorExit:
//
// Delete the secrets
//
DeleteLsaData(sizeof(gwszSysprepKey), gwszSysprepKey);
DeleteLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity);
//
// Clean up
//
if (g_TasksFolderInfo.ptszPath)
delete [] g_TasksFolderInfo.ptszPath;
if (hCSP) CloseCSPHandle(hCSP);
if (pbSAI) LocalFree(pbSAI);
if (pbSAC) LocalFree(pbSAC);
if (pRC2KeyPreSysprep) LocalFree(pRC2KeyPreSysprep);
//
// Log an error & reset the SA security dbases SAI & SAC if corruption is detected.
//
if (hr == SCHED_E_ACCOUNT_DBASE_CORRUPT)
{
//
// Reset SAI & SAC by writing four bytes of zeros into each.
// Ignore the return code. No recourse if this fails.
//
DWORD dwZero = 0;
WriteSecurityDBase(sizeof(dwZero), (BYTE*)&dwZero, sizeof(dwZero), (BYTE*)&dwZero);
}
//
// Done doing security stuff
//
UninitSS();
return(hr);
}
//+---------------------------------------------------------------------------
//
// Function: ConvertNetScheduleJobs
//
// Synopsis: Netschedule jobs (AT jobs) are signed internally with a hash so
// that the service can validate their authenticity at run time.
// Sysprep changes important data used by the Crypto API such that
// the generated hash will be different and can never match the hash
// stored in the AT job. In order to be runnable again, the AT jobs
// must be signed again with the new hash.
//
// *** WARNING ***
// This means that if someone could drop a bogus AT job into the tasks
// folder, it would automatically get signed as a result of running sysprep.
// In order to prevent this, PreProcessNetScheduleJobs has already checked
// all AT jobs prior to sysprep and eliminated any that were not valid.
// Arguments: None
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT ConvertNetScheduleJobs(void)
{
HRESULT hr = S_OK;
HANDLE hFileEnum = INVALID_HANDLE_VALUE;
WCHAR wszSearchPath[MAX_PATH + 1];
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
if (SUCCEEDED(hr))
{
hr = StringCchCat(wszSearchPath, MAX_PATH + 1, L"\\" TSZ_AT_JOB_PREFIX L"*" TSZ_DOTJOB);
if (SUCCEEDED(hr))
{
WIN32_FIND_DATA fd;
DWORD dwRet = 0;
if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE)
{
//
// Either no jobs (this is OK), or an error occurred.
//
dwRet = GetLastError();
if (dwRet != ERROR_FILE_NOT_FOUND)
hr = _HRESULT_FROM_WIN32(dwRet);
}
else
{
//
// Must concatenate the filename returned from the enumeration onto the folder path
// before loading the job. Prepare for doing that repeatedly by taking the path,
// adding a slash, and remembering the next character position.
//
hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
if (SUCCEEDED(hr))
{
DWORD iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath);
wszSearchPath[iConcatenation++] = L'\\';
//
// Process each found file
//
while (dwRet != ERROR_NO_MORE_FILES)
{
//
// Truncate the existing name after the folder path,
// then concatenate the new filename onto it.
//
wszSearchPath[iConcatenation] = L'\0';
if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName)))
{
//
// Load, sign, save, and release the job
//
CJob* pJob = CJob::Create();
if (!pJob)
{
hr = E_OUTOFMEMORY;
break;
}
if (SUCCEEDED(pJob->Load(wszSearchPath, 0)))
{
if (SUCCEEDED(pJob->Sign()))
{
pJob->SaveWithRetry(pJob->GetFileName(),
FALSE,
SAVEP_VARIABLE_LENGTH_DATA |
SAVEP_PRESERVE_NET_SCHEDULE);
}
}
pJob->Release();
}
if (!FindNextFile(hFileEnum, &fd))
{
dwRet = GetLastError();
if (dwRet != ERROR_NO_MORE_FILES)
hr = _HRESULT_FROM_WIN32(dwRet);
break;
}
}
}
}
if (hFileEnum != INVALID_HANDLE_VALUE)
FindClose(hFileEnum);
}
}
return hr;
}
//+---------------------------------------------------------------------------
//
// Function: ConvertIdentityData
//
// Synopsis: Create new job identity entries in SAI representing the existing
// jobs on the system. It does this by retrieving information stored
// pre-sysprep associating each existing job with its credential, then
// creating and storing a new identity to reference that credential.
//
// Arguments: hCSP - handle to crypto service provider
// pcbSAI - pointer to dword containing count of bytes in SAI
// ppbSAI - pointer to pointer to SAI data
// pcbSAC - pointer to dword containing count of bytes in SAC
// ppbSAC - pointer to pointer to SAC data
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT ConvertIdentityData(HCRYPTPROV hCSP, DWORD* pcbSAI, BYTE** ppbSAI, DWORD* pcbSAC, BYTE** ppbSAC)
{
//
// Get the job identity data from LSA
//
BYTE* pbIdentityData = NULL;
DWORD cbIdentityData = 0;
HRESULT hr = GetSysprepIdentityInfo(&cbIdentityData, &pbIdentityData);
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Insert updated hashes into SAI for pre-sysprep jobs
//
if (pbIdentityData)
{
//
// Get the tasks folder and prepare buffer for use in producing job path
//
WCHAR wszJobPath[MAX_PATH + 1];
hr = StringCchCopy(wszJobPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath);
if (SUCCEEDED(hr))
{
DWORD iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath);
wszJobPath[iConcatenation++] = L'\\';
//
// Process each stored task
//
BYTE* pbCurrentData = pbIdentityData;
BYTE* pbLastByte = (BYTE*)(pbIdentityData + cbIdentityData - 1);
WCHAR* pwszFileName = NULL;
BOOL bIsPasswordNull;
DWORD dwCredentialIndex;
BYTE rgbIdentity[HASH_DATA_SIZE];
BYTE* pbIdentitySet = NULL;
while (pbCurrentData <= pbLastByte)
{
pwszFileName = (WCHAR*) pbCurrentData;
pbCurrentData += ((lstrlenW(pwszFileName) + 1) * sizeof(WCHAR));
CopyMemory(&bIsPasswordNull, pbCurrentData, sizeof(BOOL));
pbCurrentData += sizeof(BOOL);
CopyMemory(&dwCredentialIndex, pbCurrentData, sizeof(DWORD));
if ((pbCurrentData + sizeof(DWORD)) > (pbLastByte + 1))
{
//
// the first DWORD after pbCurrentData is beyond the first BYTE after pbLastByte;
// this means that the DWORD pointed to by pbCurrentData is partially or wholly
// beyond the end of the data -- something is corrupt
//
hr = E_FAIL;
goto ErrorExit;
}
//
// Truncate the existing name after the folder path,
// then concatenate the new filename onto it.
//
wszJobPath[iConcatenation] = L'\0';
if (SUCCEEDED(StringCchCat(wszJobPath, MAX_PATH + 1, pwszFileName)))
{
//
// Hash the job into a unique identity.
//
if (SUCCEEDED(HashJobIdentity(hCSP, (LPCWSTR)&wszJobPath, rgbIdentity)))
{
//
// Store a NULL password by flipping the last bit of the hash data.
//
if (bIsPasswordNull)
{
LAST_HASH_BYTE(rgbIdentity) ^= 1;
}
//
// Insert the job identity into the SAI identity set associated
// with this credential.
//
hr = SAIIndexIdentity(*pcbSAI,
*ppbSAI,
dwCredentialIndex,
0,
NULL,
NULL,
&pbIdentitySet);
if (hr == S_FALSE)
{
//
// The SAC & SAI databases are out of sync. Should *never* occur.
//
ASSERT_SECURITY_DBASE_CORRUPT();
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
goto ErrorExit;
}
else if (SUCCEEDED(hr))
{
hr = SAIInsertIdentity(rgbIdentity,
pbIdentitySet,
pcbSAI,
ppbSAI);
}
else
{
hr = S_OK; // OK, we failed this one, go on to the next
}
}
}
pbCurrentData += sizeof(DWORD);
}
}
}
ErrorExit:
if (pbIdentityData)
LocalFree(pbIdentityData);
return(hr);
}
//+---------------------------------------------------------------------------
//
// Function: ConvertCredentialData
//
// Synopsis: Converts credentials stored prior to sysprep so they may be decrypted after sysprep.
// It does this by retrieving the pre-sysprep key, decrypting the credentials with it, and then
// encrypting them using the new post-sysprep key.
//
// Arguments: pRC2KeyPreSysprep - pointer to presysprep key
// pRC2KeyPostSysprep - pointer to postsysprep key
// pcbSAI - pointer to dword containing count of bytes in SAI
// ppbSAI - pointer to pointer to SAI data
// pcbSAC - pointer to dword containing count of bytes in SAC
// ppbSAC - pointer to pointer to SAC data
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT ConvertCredentialData(RC2_KEY_INFO* pRC2KeyPreSysprep,
RC2_KEY_INFO* pRC2KeyPostSysprep,
DWORD* pcbSAI,
BYTE** ppbSAI,
DWORD* pcbSAC,
BYTE** ppbSAC)
{
HRESULT hr = S_OK;
HRESULT hRes2 = S_OK; // for hresults that we don't want to affect our return code
BYTE* pbEncryptedData = NULL;
DWORD cbEncryptedData = 0;
BYTE* pbSACEnd = *ppbSAC + *pcbSAC;
BYTE* pbCredential = *ppbSAC + USN_SIZE; // Advance past USN.
DWORD cbCredential = 0;
//
// Read credential count
//
DWORD dwCredentialCount = 0;
CopyMemory(&dwCredentialCount, pbCredential, sizeof(dwCredentialCount));
pbCredential += sizeof(dwCredentialCount);
//
// Loop through all credentials, decrypting, encrypting, and updating each one
//
JOB_CREDENTIALS jc;
RC2_KEY_INFO RC2KeyPreSysprepCopy;
RC2_KEY_INFO RC2KeyPostSysprepCopy;
for (DWORD dwCredentialIndex = 0;
(dwCredentialIndex < dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) < *pcbSAC);
dwCredentialIndex++
)
{
//
// Ensure sufficient space remains in the buffer.
//
if ((pbCredential + sizeof(cbCredential)) > pbSACEnd)
{
ASSERT_SECURITY_DBASE_CORRUPT();
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
goto ErrorExit;
}
CopyMemory(&cbCredential, pbCredential, sizeof(cbCredential));
pbCredential += sizeof(cbCredential);
//
// Check remaining buffer size again
//
if ((pbCredential + HASH_DATA_SIZE) > pbSACEnd)
{
ASSERT_SECURITY_DBASE_CORRUPT();
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
goto ErrorExit;
}
//
// Check remaining buffer size yet again
//
if ((pbCredential + cbCredential) > pbSACEnd)
{
ASSERT_SECURITY_DBASE_CORRUPT();
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
goto ErrorExit;
}
//
// The start of the credential refers to the credential identity.
// Skip over this to refer to the encrypted bits. Copy the pbCredential
// and cbCredential for this dwCredentialIndex so that it can be
// manipulated without altering the original buffer.
//
cbEncryptedData = cbCredential - HASH_DATA_SIZE;
pbEncryptedData = new BYTE[cbEncryptedData];
CopyMemory(pbEncryptedData, pbCredential + HASH_DATA_SIZE, cbEncryptedData);
//
// The decryption process (CBC call) clobbers the key, so make a fresh
// copy from the source each time through the loop
//
CopyMemory(&RC2KeyPreSysprepCopy, pRC2KeyPreSysprep, sizeof(RC2_KEY_INFO));
//
// Decrypt credential using the pre-sysprep key
//
// *** Important ***
//
// The encrypted credentials passed are decrypted *in-place*.
// Therefore, buffer content has been compromised;
// plus, the decrypted data must be zeroed immediately
// following decryption (even in a failure case).
//
hRes2 = DecryptCredentials(RC2KeyPreSysprepCopy, cbEncryptedData, pbEncryptedData, &jc);
//
// Don't leave the plain-text password on the heap.
//
SecureZeroMemory(pbEncryptedData, cbEncryptedData);
if (SUCCEEDED(hRes2))
{
//
// The encryption process (CBC call) clobbers the key, so make a fresh
// copy from the source each time through the loop
//
CopyMemory(&RC2KeyPostSysprepCopy, pRC2KeyPostSysprep, sizeof(RC2_KEY_INFO));
//
// Encrypt credential using the post-sysprep key
//
hRes2 = EncryptCredentials(RC2KeyPostSysprepCopy,
jc.wszAccount,
jc.wszDomain,
jc.wszPassword,
NULL,
&cbEncryptedData,
&pbEncryptedData);
//
// Don't leave the plain-text password on the stack
//
ZERO_PASSWORD(jc.wszPassword);
jc.ccPassword = 0;
if (SUCCEEDED(hRes2))
{
//
// Update the old encrypted credential with the newly encrypted credential
//
hr = SACUpdateCredential(cbEncryptedData,
pbEncryptedData,
cbCredential,
pbCredential,
pcbSAC,
ppbSAC);
if (FAILED(hr))
{
goto ErrorExit; // a failure to update leaves the SAC data
// in an unknown state, so we need to bail
}
}
}
//
// Clean up
//
delete pbEncryptedData;
pbEncryptedData = NULL;
//
// Advance to next credential.
//
pbCredential += (HASH_DATA_SIZE + cbEncryptedData);
}
//
// Still more integrity checking.
// Did we reach the end of the buffer exactly when we thought we should?
// If not, something is wrong somewhere.
//
if ((dwCredentialIndex == dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) != *pcbSAC) ||
(dwCredentialIndex != dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) > *pcbSAC))
{
//
// The database appears to be truncated.
//
ASSERT_SECURITY_DBASE_CORRUPT();
hr = SCHED_E_ACCOUNT_DBASE_CORRUPT;
goto ErrorExit;
}
ErrorExit:
if (pbEncryptedData) LocalFree(pbEncryptedData);
return(hr);
}
//+---------------------------------------------------------------------------
//
// Function: ConvertNetScheduleCredentialData
//
// Synopsis: Converts Net Schedule credential stored prior to sysprep so it may be decrypted after sysprep.
// It does this by using the pre-sysprep key to decrypt the credential with it, and then
// encrypting it using the new post-sysprep key.
//
// Arguments: pRC2KeyPreSysprep - pointer to presysprep key
// pRC2KeyPostSysprep - pointer to postsysprep key
//
// Returns: HRESULT
//
//----------------------------------------------------------------------------
HRESULT ConvertNetScheduleCredentialData(RC2_KEY_INFO* pRC2KeyPreSysprep, RC2_KEY_INFO* pRC2KeyPostSysprep)
{
BYTE* pbEncryptedData = NULL;
DWORD cbEncryptedData = 0;
//
// Read the Net Schedule account from LSA
//
HRESULT hr = ReadLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, &cbEncryptedData, &pbEncryptedData);
if (FAILED(hr))
{
goto ErrorExit;
}
if (hr == S_FALSE || cbEncryptedData <= sizeof(DWORD))
{
//
// The information was specified previously but has been reset since.
// NOTE: This will be the case if the value has been reset back to LocalSystem,
// as it merely stores a dword = 0x00000000 in that case
//
//
// Do nothing -- there is no data to convert
//
}
else
{
//
// The decryption process (CBC call) clobbers the key, so make a copy for use
//
RC2_KEY_INFO RC2KeyPreSysprepCopy;
CopyMemory(&RC2KeyPreSysprepCopy, pRC2KeyPreSysprep, sizeof(RC2_KEY_INFO));
JOB_CREDENTIALS jc;
hr = DecryptCredentials(RC2KeyPreSysprepCopy,
cbEncryptedData,
pbEncryptedData,
&jc);
SecureZeroMemory(&RC2KeyPreSysprepCopy, sizeof(RC2_KEY_INFO));
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Don't leave the plain-text password on the heap.
//
SecureZeroMemory(pbEncryptedData, cbEncryptedData);
//
// The encryption process (CBC call) clobbers the key, so make a copy for use
//
RC2_KEY_INFO RC2KeyPostSysprepCopy;
CopyMemory(&RC2KeyPostSysprepCopy, pRC2KeyPostSysprep, sizeof(RC2_KEY_INFO));
//
// Encrypt credential using the post-sysprep key
//
hr = EncryptCredentials(RC2KeyPostSysprepCopy,
jc.wszAccount,
jc.wszDomain,
jc.wszPassword,
NULL,
&cbEncryptedData,
&pbEncryptedData);
SecureZeroMemory(&RC2KeyPostSysprepCopy, sizeof(RC2_KEY_INFO));
if (FAILED(hr))
{
goto ErrorExit;
}
//
// Don't leave the plain-text password on the stack
//
ZERO_PASSWORD(jc.wszPassword);
hr = WriteLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, cbEncryptedData, pbEncryptedData);
}
ErrorExit:
if (pbEncryptedData != NULL)
LocalFree(pbEncryptedData);
return(hr);
}