windows-nt/Source/XPSP1/NT/ds/security/base/lsa/server/efssrv.cxx
2020-09-26 16:20:57 +08:00

6990 lines
172 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1997-1999 Microsoft Corporation
Module Name:
efssrv.cxx
Abstract:
EFS (Encrypting File System) Server
Author:
Robert Reichel (RobertRe)
Robert Gu (RobertG)
Environment:
Revision History:
--*/
#include <lsapch.hxx>
extern "C" {
#include <nt.h>
#include <ntdef.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <stdio.h>
#include <efsstruc.h>
#include <lmaccess.h>
#include <lmcons.h>
#include <lmapibuf.h>
#include <userenv.h>
#include <userenvp.h>
#include "lsasrvp.h"
#include "debug.h"
#include "efssrv.hxx"
#include "userkey.h"
}
#define ALGORITHM_ID TEXT("AlgorithmID")
#define KEYCACHEPERIOD TEXT("KeyCacheValidationPeriod")
#define FIPSPOLICY TEXT("FipsAlgorithmPolicy")
#define EFSCONFIG TEXT("EfsConfiguration")
#define EFSLASTGOODCONFIG TEXT("LastGoodEfsConfiguration")
#define TRUSTEDPEOPLE TEXT("TrustedPeople")
//
// The following key GPOSTATUSKEY is a temp solution. GPO should provide an API to tell people
// if the GP propagation succeeded or not.
//
#define GPOSTATUSKEY TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\GPExtensions\\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}")
#define GPSTATUS TEXT("Status")
//
// EFS key
//
#define EFSMACHINEKEY TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\EFS")
#define EFSPOLKEY TEXT("SOFTWARE\\Policies\\Microsoft\\Windows NT\\CurrentVersion\\EFS")
#define POLICYUSEFIPS 1
#define DISABLEEFS 0x00000001
// Default cache length is 3600 seconds
#define MAXCACHELENGTH 86400 * 7 // Max cache period - 7 Days. We only check time valid.
#define MINCACHELENGTH 1800 // Min cache period - 0.5 Hours
#define TIME_UNIT 10000000 // 1 TIME_UNIT == 1 second
extern LONG RecoveryCertIsValidating;
extern HANDLE EfsWaitHandle;
///////////////////////////////////////////////////////////////////////////////
// /
// Define how long our FEKs can be. An FEK will be allocated into a fixed /
// size buffer, but only a certain number of bits of entropy may be used in /
// the export version. /
// /
///////////////////////////////////////////////////////////////////////////////
//
// This number affects the FEK generation algorithm. It represents the number
// of bits of entropy in the key.
//
const EXPORT_KEY_STRENGTH = 56;
const DOMESTIC_KEY_STRENGTH = 128;
const EXPORT_DESX_SALT_LENGTH = 9;
const DES3_KEY_STRENGTH = 168;
const AES_KEY_STRENGTH_256 = 256;
const DWORD WAITFORCERTVALIDATE = 10000;
#ifndef LSASRV_EXPORT
const DWORD KeyEntropy = DOMESTIC_KEY_STRENGTH;
#else
const DWORD KeyEntropy = EXPORT_KEY_STRENGTH;
#endif
LONG EFSDebugLevel = 0;
ALG_ID EfsAlgInForce = CALG_AES_256;
extern "C" BOOLEAN EfsDisabled = FALSE;
//
// Current recovery policy
//
RTL_RESOURCE RecoveryPolicyResource;
CURRENT_RECOVERY_POLICY CurrentRecoveryPolicy;
DWORD MissingRecoveryPolicyLogged = 0;
//
// Functions in EFSAPI.CXX
//
BOOLEAN
EncryptFSCTLData(
IN ULONG Fsctl,
IN ULONG Psc,
IN ULONG Csc,
IN PVOID EfsData,
IN ULONG EfsDataLength,
IN OUT PUCHAR Buffer,
IN OUT PULONG BufferLength
);
BOOLEAN
SendHandle(
IN HANDLE Handle,
IN OUT PUCHAR EfsData,
IN OUT PULONG EfsDataLength
);
BOOLEAN
SendEfs(
IN PEFS_KEY Fek,
IN PEFS_DATA_STREAM_HEADER Efs,
OUT PUCHAR EfsData,
OUT PULONG EfsDataLength
);
BOOLEAN
SendHandleAndEfs(
IN HANDLE Handle,
IN PEFS_DATA_STREAM_HEADER Efs,
IN OUT PUCHAR EfsData,
IN OUT PULONG EfsDataLength
);
//
// Function prototypes in this module
//
BOOLEAN
CreatePublicKeyInformationThumbprint(
IN PSID pUserSid,
IN PBYTE pbCertHash,
IN DWORD cbCertHash,
IN LPWSTR lpDisplayInformation OPTIONAL,
IN LPWSTR ContainerName OPTIONAL,
IN LPWSTR ProviderName OPTIONAL,
OUT PEFS_PUBLIC_KEY_INFO * PublicKeyInformation
);
PBYTE
EncryptFEK(
IN PEFS_KEY Fek,
IN HCRYPTKEY hRSAKey,
OUT PDWORD dwEncryptedFEKLength
);
PEFS_KEY
ExtractFek(
IN PEFS_USER_INFO pEfsUserInfo,
IN PENCRYPTED_KEY EncryptedKey,
IN BOOL CheckBits
);
DWORD
ConstructEncryptedKey(
IN PBYTE EncryptedFEK,
IN DWORD dwEncryptedFEKLength,
IN PEFS_PUBLIC_KEY_INFO PublicKeyInformation,
IN PEFS_KEY_SALT pEfsKeySalt,
OUT PENCRYPTED_KEY *EncryptedKey,
IN OUT PDWORD EncryptedKeySize
);
DWORD
ConstructKeyRing(
IN PEFS_KEY Fek,
IN DWORD KeyCount,
IN LPWSTR KeyNames[] OPTIONAL,
IN LPWSTR ProviderNames[] OPTIONAL,
IN PBYTE PublicKeys[],
IN DWORD PublicKeyLengths[],
IN PBYTE pbHashes[],
IN DWORD cbHashes[],
IN LPWSTR lpDisplayInformation[],
IN PSID pSid[],
IN BOOLEAN PublicKeyHandle,
OUT PENCRYPTED_KEYS *KeyRing,
OUT PDWORD KeyRingLength
);
DWORD
ReformatPolicyInformation(
PLSAPR_POLICY_DOMAIN_EFS_INFO PolicyEfsInfo,
PLSAPR_POLICY_DOMAIN_EFS_INFO * NewPolicyEfsInfo,
PBOOLEAN Reformatted
);
DWORD
InitRecoveryPolicy(
VOID
);
DWORD
ParseOldRecoveryData(
IN PLSAPR_POLICY_DOMAIN_EFS_INFO PolicyEfsInfo OPTIONAL,
OUT PCURRENT_RECOVERY_POLICY ParsedRecoveryPolicy
);
VOID
DumpPublicKeyInfo(
PEFS_PUBLIC_KEY_INFO PublicKeyInfo
);
void
DumpRecoveryKey(
PRECOVERY_KEY_1_1 pRecoveryKey
);
PEFS_DATA_STREAM_HEADER
AssembleEfsStream(
IN PENCRYPTED_KEYS pDDF,
IN DWORD cbDDF,
IN PENCRYPTED_KEYS pDRF,
IN DWORD cbDRF,
IN PEFS_KEY Fek
);
PENCRYPTED_KEY
GetEncryptedKeyByIndex(
PENCRYPTED_KEYS pEncryptedKeys,
DWORD KeyIndex
);
BOOL
DeleteEncryptedKeyByIndex(
IN PEFS_DATA_STREAM_HEADER pEfs,
IN DWORD KeyIndex,
IN PEFS_KEY Fek,
OUT PEFS_DATA_STREAM_HEADER * pNewEfs
);
BOOLEAN
EqualEncryptedKeys(
IN PENCRYPTED_KEYS SrcKeys,
IN PENCRYPTED_KEYS DstKeys,
IN DWORD cbDstKeys
);
//
// Server
//
VOID
EfsGetRegSettings(
VOID
)
/*++
Routine Description:
This routine is called during server initialization to set
the EFS encryption algorithm.
Arguments:
None.
Return Value:
None.
--*/
{
LONG rc;
HKEY EfsKey;
DWORD AlgId;
DWORD CacheLength;
DWORD EfsConfig;
DWORD SizeInfo;
DWORD Type;
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
TEXT("SYSTEM\\CurrentControlSet\\Control\\LSA"),
0,
GENERIC_READ,
&EfsKey
);
if (rc == ERROR_SUCCESS) {
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
FIPSPOLICY,
NULL,
&Type,
(PUCHAR) &AlgId,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if ( AlgId== POLICYUSEFIPS ) {
EfsAlgInForce = CALG_3DES;
}
}
RegCloseKey( EfsKey );
}
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
EFSMACHINEKEY,
0,
GENERIC_READ,
&EfsKey
);
if (rc == ERROR_SUCCESS) {
if (EfsAlgInForce == CALG_AES_256) {
//
// FIPS Policy does not say we have to use FIPS. Let's check if user says EFS
// should use specific algorithm.
//
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
ALGORITHM_ID,
NULL,
&Type,
(PUCHAR) &AlgId,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
switch (AlgId) {
case CALG_3DES:
EfsAlgInForce = CALG_3DES; //0x6603
break;
case CALG_DESX:
EfsAlgInForce = CALG_DESX; //0x6604
break;
case CALG_AES_256:
//
// Fall through intended
//
default:
// EfsAlgInForce = CALG_AES_256;//0x6610
break;
}
}
}
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
KEYCACHEPERIOD,
NULL,
&Type,
(PUCHAR) &CacheLength,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if ((CacheLength >= MINCACHELENGTH) && (CacheLength <= MAXCACHELENGTH)){
CACHE_CERT_VALID_TIME = CacheLength * TIME_UNIT;
}
}
//
// Check if EFS is disabled in Policy
//
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
EFSCONFIG,
NULL,
&Type,
(PUCHAR) &EfsConfig,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if (EfsConfig & DISABLEEFS){
EfsDisabled = TRUE;
}
}
RegCloseKey( EfsKey );
}
}
BOOL
EfsIsGpoGood(
VOID
)
/*++
Routine Description:
This is a temp workaround to check if GP propagation succeeded or not.
GP should provide an API to do this.
Arguments:
Not used.
Return Value:
None.
--*/
{
LONG rc;
HKEY PolKey;
DWORD SizeInfo;
DWORD PolStatus = 0;
DWORD Type;
BOOL GoodPol = TRUE;
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
GPOSTATUSKEY,
0,
GENERIC_READ,
&PolKey
);
if (rc == ERROR_SUCCESS) {
//
// Check if EFS is disabled in Policy
//
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
PolKey,
GPSTATUS,
NULL,
&Type,
(PUCHAR) &PolStatus,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if (PolStatus) {
//
//Last policy propagation failed
//
GoodPol = FALSE;
}
} else {
//
// Assuming last propagation failed
//
GoodPol = FALSE;
}
RegCloseKey( PolKey );
} else {
GoodPol = FALSE;
}
return GoodPol;
}
VOID
EfsRemoveKey(
VOID
)
/*++
Routine Description:
This routine removes EFS Last Good Policy Key.
Arguments:
No.
Return Value:
No.
--*/
{
LONG rc;
HKEY EfsKey;
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
EFSMACHINEKEY,
0,
GENERIC_READ | KEY_SET_VALUE,
&EfsKey
);
if (rc == ERROR_SUCCESS) {
//
// Delete the last good key
//
RegDeleteValue(
EfsKey,
EFSLASTGOODCONFIG
);
RegCloseKey( EfsKey );
}
}
BOOL
EfsApplyGoodPolicy(
IN BOOLEAN* pEfsDisabled
)
/*++
Routine Description:
This routine is a common routine to apply good policy data.
Arguments:
pEfsDisabled -- Point to the global EfsDisabled.
Return Value:
TRUE if we applied the data. FALSE if no data available.
--*/
{
LONG rc;
HKEY EfsKey;
HKEY EfsPolKey;
DWORD EfsConfig;
DWORD SizeInfo;
DWORD Type;
BOOL PolicyValueApplied = FALSE;
//
// Open EFS policy key
//
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
EFSPOLKEY,
0,
GENERIC_READ,
&EfsPolKey
);
if (rc == ERROR_SUCCESS) {
//
// Let's try to get the latest value
//
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsPolKey,
EFSCONFIG,
NULL,
&Type,
(PUCHAR) &EfsConfig,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
PolicyValueApplied = TRUE;
if (EfsConfig & DISABLEEFS){
if (!(*pEfsDisabled)) {
*pEfsDisabled = TRUE;
}
} else {
if (*pEfsDisabled) {
*pEfsDisabled = FALSE;
}
}
//
// We need to update our LAST Good key.
//
DWORD Disposition = 0;
rc = RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
EFSMACHINEKEY,
0,
TEXT("REG_SZ"),
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
&EfsKey,
&Disposition // address of disposition value buffer
);
if (rc == ERROR_SUCCESS) {
//
// OK. Let's upadte the value
//
RegSetValueEx(
EfsKey,
EFSLASTGOODCONFIG,
0,
REG_DWORD,
(CONST BYTE *)&EfsConfig,
sizeof(DWORD)
);
RegCloseKey( EfsKey );
}
}
RegCloseKey( EfsPolKey );
}
return (PolicyValueApplied);
}
VOID
EfsApplyLastPolicy(
IN BOOLEAN *pEfsDisabled
)
/*++
Routine Description:
This routine is called during boot init time.
Arguments:
pEfsDisabled -- Point to the global EfsDisabled. May be changed to a structure pointer later
to support more EFS policy vars.
Return Value:
None.
--*/
{
LONG rc;
HKEY EfsKey;
DWORD EfsConfig;
DWORD SizeInfo;
DWORD Type;
if (EfsIsGpoGood()) {
//
// We got a good policy.
//
BOOL PolicyValueApplied;
PolicyValueApplied = EfsApplyGoodPolicy(
pEfsDisabled
);
if (!PolicyValueApplied) {
//
// Policy key is missing or value removed. We need to delete the last good value.
// The last good value could be non-existing. It does not hurt to try again during
// the boot.
//
EfsRemoveKey();
}
} else {
//
// Last Policy propagation failed. Tried to get the last good one if there is one.
//
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
EFSMACHINEKEY,
0,
GENERIC_READ,
&EfsKey
);
if (rc == ERROR_SUCCESS) {
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
EFSLASTGOODCONFIG,
NULL,
&Type,
(PUCHAR) &EfsConfig,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if (EfsConfig & DISABLEEFS){
if (!(*pEfsDisabled)) {
*pEfsDisabled = TRUE;
}
} else {
if (*pEfsDisabled) {
*pEfsDisabled = FALSE;
}
}
}
RegCloseKey( EfsKey );
}
}
}
VOID
EfsGetPolRegSettings(
IN PVOID pEfsPolCallBack,
IN BOOLEAN timeExpired
)
/*++
Routine Description:
This routine is called during policy propagation.
Arguments:
pEfsDisabled -- Point to a structure EFS_POL_CALLBACK.
timeExpired -- FALSE if trigged by the event.
Return Value:
None.
--*/
{
LONG rc;
HKEY EfsKey;
DWORD EfsConfig;
DWORD SizeInfo;
DWORD Type;
BOOLEAN * CrntEfsDisabled = ((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsDisable;
if (timeExpired) {
//
// May be killed.
//
if (*(((PEFS_POL_CALLBACK)pEfsPolCallBack)->EfsPolicyEventHandle)) {
UnregisterGPNotification(*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle));
CloseHandle(*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle));
*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle) = 0;
}
if (EfsWaitHandle) {
RtlDeregisterWait(EfsWaitHandle);
EfsWaitHandle = 0;
}
return;
}
if (EfsIsGpoGood()) {
//
// We got a good policy.
//
BOOL PolicyValueApplied;
PolicyValueApplied = EfsApplyGoodPolicy(
CrntEfsDisabled
);
if (!PolicyValueApplied) {
//
// Policy key is missing or value removed. We need to delete the last good value.
//
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
EFSMACHINEKEY,
0,
GENERIC_READ | KEY_SET_VALUE,
&EfsKey
);
if (rc == ERROR_SUCCESS) {
//
// Set back the default value first.
//
SizeInfo = sizeof(DWORD);
rc = RegQueryValueEx(
EfsKey,
EFSCONFIG,
NULL,
&Type,
(PUCHAR) &EfsConfig,
&SizeInfo
);
if (rc == ERROR_SUCCESS) {
if (EfsConfig & DISABLEEFS){
if (!(*CrntEfsDisabled)) {
*CrntEfsDisabled = TRUE;
}
} else {
if (*CrntEfsDisabled) {
*CrntEfsDisabled = FALSE;
}
}
} else {
//
// No default value is treated as enable EFS
//
if (*CrntEfsDisabled) {
*CrntEfsDisabled = FALSE;
}
}
//
// Delete the last good key
//
RegDeleteValue(
EfsKey,
EFSLASTGOODCONFIG
);
RegCloseKey( EfsKey );
} else {
//
// No default key value. Enable EFS if not now.
//
if (*CrntEfsDisabled) {
*CrntEfsDisabled = FALSE;
}
}
}
}
if (EfsWaitHandle) {
//
// Unregister the last one
//
RtlDeregisterWait(EfsWaitHandle);
EfsWaitHandle = 0;
//
// Reset the notification event
//
if (*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle)) {
ResetEvent(*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle));
//
// Reregister for a new one
//
if (!NT_SUCCESS(RtlRegisterWait(
&EfsWaitHandle,
*(((PEFS_POL_CALLBACK)pEfsPolCallBack)->EfsPolicyEventHandle),
EfsGetPolRegSettings,
pEfsPolCallBack,
INFINITE,
WT_EXECUTEONLYONCE))){
//
// We couldn't use the thread pool.
//
UnregisterGPNotification(*(((PEFS_POL_CALLBACK)pEfsPolCallBack)->EfsPolicyEventHandle));
CloseHandle(*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle));
*(((PEFS_POL_CALLBACK) pEfsPolCallBack)->EfsPolicyEventHandle) = 0;
}
}
}
}
VOID
RecoveryInformationCallback(
POLICY_NOTIFICATION_INFORMATION_CLASS ChangedInfoClass
)
/*++
Routine Description:
Callback for when EFS Recovery policy information changes
Arguments:
ChangedInfoClass - The info class that changed.
Return Value:
None.
--*/
{
InitRecoveryPolicy();
return;
}
VOID
EfspRoleChangeCallback(
POLICY_NOTIFICATION_INFORMATION_CLASS ChangedInfoClass
)
/*++
Routine Description:
Callback for when the role of the machine in a domain changes.
Arguments:
ChangedInfoClass - The info class that changed.
Return Value:
None.
--*/
{
NTSTATUS Status;
PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo;
Status = LsarQueryInformationPolicy(LsapPolicyHandle,
PolicyPrimaryDomainInformation,
(PLSAPR_POLICY_INFORMATION *)&PrimaryDomainInfo
);
if (!NT_SUCCESS(Status)) {
DebugLog((DEB_ERROR, "Failed to query primary domain from Lsa, Status = 0x%lx\n", Status));
} else {
if (PrimaryDomainInfo->Sid != NULL) {
EfspInDomain = TRUE;
} else {
EfspInDomain = FALSE;
}
LsaFreeMemory( PrimaryDomainInfo );
}
return;
}
#if 0
//
// We may revisit this function in inheritance work. Keep it for now.
//
BOOL
GetPublicKey(
HCRYPTKEY hKey,
PBYTE * PublicKeyBlob,
PDWORD KeyLength
)
/*++
Routine Description:
Exports a public key
Arguments:
hKey - Supplies the key handle to be exported
PublicKeyBlob - Returns a buffer containing the exported key
KeyLength - Returns the length of the exported key buffer in bytes.
Return Value:
TRUE on success, FALSE otherwise.
--*/
{
*KeyLength = 0;
*PublicKeyBlob = NULL;
if (hKey == NULL) {
ASSERT( FALSE );
return( FALSE );
}
BOOL b = CryptExportKey( hKey, 0, PUBLICKEYBLOB, 0, NULL, KeyLength );
if (b) {
*PublicKeyBlob = (PBYTE) LsapAllocateLsaHeap( *KeyLength );
if (*PublicKeyBlob != NULL) {
b = CryptExportKey( hKey, 0, PUBLICKEYBLOB, 0, *PublicKeyBlob, KeyLength );
if (!b) {
LsapFreeLsaHeap( *PublicKeyBlob );
*PublicKeyBlob = NULL;
}
} else {
b = FALSE;
}
}
return( b );
}
#endif
inline
VOID
AcquireRecoveryPolicyReadLock()
{
BOOL b = RtlAcquireResourceShared( &RecoveryPolicyResource, TRUE );
ASSERT( b );
}
inline
VOID
ReleaseRecoveryPolicyReadLock()
{
RtlReleaseResource( &RecoveryPolicyResource );
}
inline
VOID
ReleaseRecoveryData()
{
ReleaseRecoveryPolicyReadLock();
}
DWORD
GetRecoveryData(
OUT PDWORD dwKeyCount,
OUT PDWORD dwPolicyStatus,
OUT PBYTE * pbPublicKeys[],
OUT DWORD * cbPublicKeys[],
OUT PBYTE * pbHashes[],
OUT DWORD * cbHashes[],
OUT LPWSTR * lpDisplayInfo[] OPTIONAL,
OUT PSID * pSid[] OPTIONAL
)
/*++
Routine Description:
This routine returns the current recovery data. It takes a read
lock on the recovery data so that it cannot be modified while in use.
This lock must be freed by calling ReleaseRecoveryData().
Arguments:
dwKeyCount - Returns the list of keys in the current recovery data.
dwPolicyStatus - The status of the recovery policy.
pbPublicKeys - Returns an array of pointers to exported public key blobs that will be used
to encrypt the FEK.
cbPublicKeys - Specifies the length (in bytes) of each of the keys returned
in the PublicKeys array.
pbHashes - Returns an array of pointers to the key hashes.
cbHashes - Specifies the length (in bytes) of each of the key hashes.
lpDisplayInfo - Recovery cert display information.
pSid - Sids of the recovery agents
Return Value:
ERROR_SUCCESS for succeed.
--*/
{
AcquireRecoveryPolicyReadLock();
//
// Verify that all of the cert contexts are still valid.
//
// If any of them fail, say that there is no recovery
// policy on the system.
//
DWORD i;
BOOLEAN fResult = TRUE;
LARGE_INTEGER TimeStamp;
if ( (*dwPolicyStatus = CurrentRecoveryPolicy.PolicyStatus) < RECOVERY_POLICY_OK) {
*dwKeyCount = CurrentRecoveryPolicy.dwKeyCount;
*pbPublicKeys = CurrentRecoveryPolicy.pbPublicKeys;
*cbPublicKeys = CurrentRecoveryPolicy.cbPublicKeys;
*pbHashes = CurrentRecoveryPolicy.pbHash;
*cbHashes = CurrentRecoveryPolicy.cbHash;
if (lpDisplayInfo) {
*lpDisplayInfo = CurrentRecoveryPolicy.lpDisplayInfo;
}
if (pSid) {
*pSid = CurrentRecoveryPolicy.pSid;
}
return( ERROR_SUCCESS );
}
TimeStamp.QuadPart = 0;
//
// Check if we need validate the certs again
//
if ((NT_SUCCESS( NtQuerySystemTime(&TimeStamp)) &&
(TimeStamp.QuadPart - CurrentRecoveryPolicy.TimeStamp.QuadPart > CACHE_CERT_VALID_TIME )) ||
(CurrentRecoveryPolicy.CertValidated == CERT_NOT_VALIDATED)){
//
// We only let one thread in here.
//
LONG IsCertBeingValidated;
IsCertBeingValidated = InterlockedExchange(&RecoveryCertIsValidating, 1);
if ((CurrentRecoveryPolicy.CertValidated == CERT_NOT_VALIDATED) && (IsCertBeingValidated == 1)) {
//
// If the recovery cert has not been validated and some other thread is validating,
// let's wait for a 10 seconds.
//
Sleep(WAITFORCERTVALIDATE);
if (CurrentRecoveryPolicy.CertValidated == CERT_NOT_VALIDATED) {
//
// Not validated yet. Let's try to grab the lock. Let other thread to wait.
//
IsCertBeingValidated = InterlockedExchange(&RecoveryCertIsValidating, 1);
}
}
if ( (IsCertBeingValidated != 1) || (CurrentRecoveryPolicy.CertValidated == CERT_NOT_VALIDATED) ) {
//
// No thread is validating the cert, let's do it
//
for (i=0; i<CurrentRecoveryPolicy.dwKeyCount; i++) {
//
// We only check the time in the cert
//
LONG CertTimeValid;
if (CertTimeValid = CertVerifyTimeValidity(
NULL,
CurrentRecoveryPolicy.pCertContext[i]->pCertInfo
)){
if ( CertTimeValid > 0 ) {
DebugLog((DEB_WARN, "Expired certificate in recovery policy\n"));
*dwPolicyStatus = RECOVERY_POLICY_EXPIRED_CERTS;
fResult = FALSE;
break;
} else {
DebugLog((DEB_WARN, "Expired certificate in recovery policy\n"));
*dwPolicyStatus = RECOVERY_POLICY_NOT_EFFECT_CERTS;
fResult = FALSE;
break;
}
}
}
//
// When policy is propagated, the write lock is acquired. When we get here, we are having read lock and no one
// is having the write lock. It is OK for threads stepping each other on writing CertValidated and TimeStamp here.
// We are checking the validation in hours, a fraction of a second window here can be ignored.
//
if (CurrentRecoveryPolicy.dwKeyCount && fResult) {
CurrentRecoveryPolicy.CertValidated = CERT_VALIDATED;
} else if ( CurrentRecoveryPolicy.dwKeyCount ) {
CurrentRecoveryPolicy.CertValidated = CERT_VALIDATION_FAILED;
}
if (CurrentRecoveryPolicy.CertValidated != CERT_NOT_VALIDATED) {
CurrentRecoveryPolicy.TimeStamp.QuadPart = TimeStamp.QuadPart;
}
if (IsCertBeingValidated != 1) {
InterlockedExchange(&RecoveryCertIsValidating, IsCertBeingValidated);
}
}
}
if (CurrentRecoveryPolicy.CertValidated == CERT_VALIDATED) {
*dwKeyCount = CurrentRecoveryPolicy.dwKeyCount;
*pbPublicKeys = CurrentRecoveryPolicy.pbPublicKeys;
*cbPublicKeys = CurrentRecoveryPolicy.cbPublicKeys;
*pbHashes = CurrentRecoveryPolicy.pbHash;
*cbHashes = CurrentRecoveryPolicy.cbHash;
if (lpDisplayInfo) {
*lpDisplayInfo = CurrentRecoveryPolicy.lpDisplayInfo;
}
if (pSid) {
*pSid = CurrentRecoveryPolicy.pSid;
}
} else {
*dwKeyCount = 0;
}
if ( ((RECOVERY_POLICY_EXPIRED_CERTS == *dwPolicyStatus) ||
(RECOVERY_POLICY_NOT_EFFECT_CERTS == *dwPolicyStatus)) &&
( 0 == MissingRecoveryPolicyLogged) ) {
DWORD eventID = EFS_INVALID_RECOVERY_POLICY_ERROR;
//
// Log the fail to get the recovery policy
//
MissingRecoveryPolicyLogged = 1;
EfsLogEntry(
EVENTLOG_ERROR_TYPE,
0,
eventID,
0,
0,
NULL,
NULL
);
}
return( ERROR_SUCCESS );
}
BOOLEAN
ConstructEFS(
PEFS_USER_INFO pEfsUserInfo,
PEFS_KEY Fek,
PEFS_DATA_STREAM_HEADER ParentEfsStreamHeader,
PEFS_DATA_STREAM_HEADER * EfsStreamHeader
)
/*++
Routine Description:
This routine will construct an EFS stream. It is intended to be used
whenever an entire EFS stream is required, such as when a new file is
created.
An EFS stream contains a header, a DDF (which contains current user key
information), and a DRF (which contains recovery information).
Arguments:
Fek - Supplies a pointer to a partially filled in EFS_KEY structure,
specifying the length of the desired key and the algorithm that
will be used with the key to encrypt the file.
It is important that the algorithm field be filled in, since this
key will be eventually encrypted in its entirety, and all the fields
must be present for that to work.
ParentEfsStreamHeader - Supplies the EFS stream from the containing directory,
if one exists. This parameter is not currently used, because we do
not support inheritance from directories to files (yet).
EfsStreamHeader - Returns a pointer to an EFS_DATA_STREAM_HEADER which is
the head of an EFS stream. This header is followed by variable length
data containing the actual EFS data.
Return Value:
--*/
{
LPWSTR ContainerName = NULL;
HCRYPTPROV hProv = 0;
HCRYPTKEY hUserKey = 0;
LPWSTR lpDisplayInformation = NULL;
LPWSTR ProviderName = NULL;
DWORD ProviderType = 0;
PUCHAR PublicKey = NULL;
HCRYPTKEY hWkUserKey = NULL;
DWORD rc;
PEFS_DATA_STREAM_HEADER EFS = NULL;
DWORD DRFLength = 0;
DWORD DDFLength = 0;
PENCRYPTED_KEYS pDRF = NULL;
PENCRYPTED_KEYS pDDF = NULL;
PBYTE pbHash;
DWORD cbHash;
BOOLEAN b = FALSE;
//
// To build the DDF, we need the user's current key from the registry.
// This routine will get the key information from the registry and open
// the context containing the key.
//
rc = GetCurrentKey(
pEfsUserInfo,
&hUserKey,
&hProv,
&ContainerName,
&ProviderName,
&ProviderType,
&lpDisplayInformation,
&pbHash,
&cbHash
);
if (ERROR_SUCCESS == rc) {
if (hUserKey) {
hWkUserKey = hUserKey;
} else {
//
// Use the key in the cache
//
ASSERT(pEfsUserInfo->pUserCache);
ASSERT(pEfsUserInfo->pUserCache->hUserKey);
hWkUserKey = pEfsUserInfo->pUserCache->hUserKey;
}
} else {
SetLastError( rc );
return( FALSE );
}
//
// Before we exit, make sure to clean up ContainerName, ProviderName, pbHash, hUserKey, hProv
//
rc = GenerateDRF( Fek, &pDRF, &DRFLength);
if (ERROR_SUCCESS == rc) {
LPWSTR lpWkContainerName;
LPWSTR lpWkDisplayInformation;
LPWSTR lpWkProviderName;
PBYTE pbWkHash;
DWORD cbWkHash;
if (hUserKey) {
//
// Do not use the cache
//
lpWkContainerName = ContainerName;
lpWkProviderName = ProviderName;
lpWkDisplayInformation = lpDisplayInformation;
pbWkHash = pbHash;
cbWkHash = cbHash;
} else {
//
// Use the cache
//
lpWkContainerName = pEfsUserInfo->pUserCache->ContainerName;
lpWkProviderName = pEfsUserInfo->pUserCache->ProviderName;
lpWkDisplayInformation = pEfsUserInfo->pUserCache->DisplayInformation;
pbWkHash = pEfsUserInfo->pUserCache->pbHash;
cbWkHash = pEfsUserInfo->pUserCache->cbHash;
}
rc = ConstructKeyRing(
Fek,
1,
&lpWkContainerName,
&lpWkProviderName,
(PBYTE *)&hWkUserKey,
NULL,
&pbWkHash,
&cbWkHash,
&lpWkDisplayInformation,
&(pEfsUserInfo->pTokenUser->User.Sid),
TRUE,
&pDDF,
&DDFLength
);
if (ERROR_SUCCESS == rc) {
DWORD EfsLength = DDFLength + DRFLength + sizeof( EFS_DATA_STREAM_HEADER );
//
// Efs has to be a multiple of 8 in length to encrypt properly.
//
EfsLength = (EfsLength + 7) & 0xfffffff8;
EFS = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( EfsLength );
if (EFS != NULL) {
memset( EFS, 0, sizeof( EFS_DATA_STREAM_HEADER ));
EFS->Length = EfsLength;
EFS->State = 0; // used by the server
EFS->EfsVersion = EFS_CURRENT_VERSION;
RPC_STATUS RpcStatus = UuidCreate ( &EFS->EfsId );
if (RpcStatus == ERROR_SUCCESS || RpcStatus == RPC_S_UUID_LOCAL_ONLY) {
//
// A "local-only" UUID is ok in this case
//
EFS->DataDecryptionField = (ULONG)sizeof(EFS_DATA_STREAM_HEADER );
memcpy( (PENCRYPTED_KEYS)((PBYTE)EFS + sizeof( EFS_DATA_STREAM_HEADER )), pDDF, DDFLength );
if ( 0 == DRFLength) {
EFS->DataRecoveryField = 0;
} else {
EFS->DataRecoveryField = (ULONG)(sizeof(EFS_DATA_STREAM_HEADER ) + DDFLength);
memcpy( (PENCRYPTED_KEYS)((PBYTE)EFS + sizeof( EFS_DATA_STREAM_HEADER ) + DDFLength ), pDRF, DRFLength );
}
/*
BOOLEAN f = EfspChecksumEfs( EFS, Fek );
ASSERT( f );
if (!f) {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
LsapFreeLsaHeap( EFS );
*EfsStreamHeader = NULL;
} else {
//
// Everything worked, return success.
//
*EfsStreamHeader = EFS;
b = TRUE;
}
*/
*EfsStreamHeader = EFS;
b = TRUE;
}
} else {
rc = GetLastError();
}
}
}
ReleaseRecoveryData();
if (pDDF) {
LsapFreeLsaHeap(pDDF);
}
if (pDRF) {
LsapFreeLsaHeap(pDRF);
}
if (ContainerName) {
//
// Defensive checking
//
LsapFreeLsaHeap( ContainerName );
}
if (ProviderName) {
LsapFreeLsaHeap( ProviderName );
}
if (lpDisplayInformation) {
LsapFreeLsaHeap( lpDisplayInformation );
}
if (PublicKey) {
LsapFreeLsaHeap( PublicKey );
}
if (pbHash) {
LsapFreeLsaHeap( pbHash );
}
if (hUserKey) {
CryptDestroyKey( hUserKey );
}
if (hProv) {
CryptReleaseContext( hProv, 0 );
}
if (EFSDebugLevel > 0) {
DumpEFS( *EfsStreamHeader );
}
SetLastError( rc );
#if DBG
if (!b) {
ASSERT( rc != ERROR_SUCCESS );
}
#endif
return( b );
}
DWORD
CopyEfsStream(
OUT PEFS_DATA_STREAM_HEADER * Target,
IN PEFS_DATA_STREAM_HEADER Source
)
/*++
Routine Description:
Makes a copy of the passed EFS stream. Allocates memory for the target
which must be freed.
Arguments:
Target - Takes a pointer which is filled in with a pointer to the copy
of the EFS stream. This pointer must be freed.
Return Value:
ERROR_SUCCESS, or ERROR_NOT_ENOUGH_MEMORY if we can't allocate memory for the
target buffer.
--*/
{
*Target = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( Source->Length );
if (*Target) {
memcpy( *Target, Source, Source->Length );
} else {
return( ERROR_NOT_ENOUGH_MEMORY );
}
return( ERROR_SUCCESS );
}
BOOLEAN
ConstructDirectoryEFS(
IN PEFS_USER_INFO pEfsUserInfo,
IN PEFS_KEY Fek,
OUT PEFS_DATA_STREAM_HEADER * EfsStreamHeader
)
/*++
Routine Description:
This routine constructs the EFS stream for a directory.
Arguments:
pEfsUserInfo - Supplies useful information about our caller.
Fek - Supplies the Fek to put into the EFS stream.
EfsStreamHeader - Returns a pointer to the new EFS stream.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
return( ConstructEFS( pEfsUserInfo, Fek, NULL, EfsStreamHeader ) );
}
DWORD
ConstructKeyRing(
IN PEFS_KEY Fek,
IN DWORD KeyCount,
IN LPWSTR KeyNames[] OPTIONAL,
IN LPWSTR ProviderNames[] OPTIONAL,
IN PBYTE PublicKeys[],
IN DWORD PublicKeyLengths[],
IN PBYTE pbHashes[],
IN DWORD cbHashes[],
IN LPWSTR lpDisplayInformation[],
IN PSID pSid[],
IN BOOLEAN PublicKeyHandle,
OUT PENCRYPTED_KEYS *KeyRing,
OUT PDWORD KeyRingLength
)
/*++
Routine Description:
This routine will construct a key ring (DDF or DRF) structure. A keyring
contains one or more ENCRYPTED_KEY structures, each of which represents
an encoding of the FEK with a different user key.
The caller is expected to call this routine twice, once to determine the
length of the structure, and a second time to actually create the key ring
structure.
Note that the passed keys do not need to exist in the current context,
and if we are building a DRF structure, most likely will not exist
in the current context.
Arguments:
Fek - Provides the unencrypted FEK for the file.
KeyCount - Provides the number of keys that are going to be placed in this
keyring.
KeyNames - Provides an array of NULL-terminated WCHAR strings, each naming
a key.
ProviderNames - Provides an array of providers that is parallel to the
KeyNames array.
PublicKeys - Provides an array of pointers to PUBLICKEYBLOB structures,
one for each named key.
PublicKeyLengths - Provides an array of lengths of the PUBLICKEYBLOB
structures pointed to by the PublicKeys array. It could also points to the key
handle.
pSid - Users' SIDs
PublicKeyHandle - Indicate if PublicKeys point to PUBLICKEYBLOB or key handles.
KeyRing - Returns a pointer to the constructed keyring. If this parameter
is NULL, only the length will be computed and returned.
KeyRingLength - Provides the size of the passed KeyRing buffer, or or if the
KeyRing pointer is NULL, the size of the buffer that must be passed in to
return the KeyRing.
Return Value:
ERROR_SUCCESS - Returned if successful.
ERROR_NOT_ENOUGH_MEMORY - Some attempt to allocate memory from the local
heap failed.
--*/
{
//
// For each Key passed in, import the public key blob
// and export the session key encrypted with that blob.
// The FEK will be encrypted with the same session key
// in each entry.
//
PEFS_KEY_SALT pEfsKeySalt = NULL;
PENCRYPTED_KEY * EncryptedKey = NULL;
BOOL GotPublicKey = TRUE;
EncryptedKey = (PENCRYPTED_KEY *)LsapAllocateLsaHeap( KeyCount * sizeof(PENCRYPTED_KEY) );
*KeyRing = NULL;
if (EncryptedKey == NULL) {
return( ERROR_NOT_ENOUGH_MEMORY );
}
PDWORD EncryptedKeySize = (PDWORD)LsapAllocateLsaHeap( KeyCount * sizeof( DWORD ));
if (EncryptedKeySize == NULL) {
LsapFreeLsaHeap( EncryptedKey );
return( ERROR_NOT_ENOUGH_MEMORY );
}
DWORD i;
DWORD rc = ERROR_SUCCESS;
for (i = 0 ; i<KeyCount ; i++) {
EncryptedKey[i] = NULL;
EncryptedKeySize[i] = 0;
}
for ( i = 0; i<KeyCount && rc==ERROR_SUCCESS ; i++ ) {
//
// Import the passed public key
//
HCRYPTKEY hXchgKey = 0;
if (PublicKeyHandle) {
GotPublicKey = TRUE;
hXchgKey = (HCRYPTKEY)PublicKeys[i];
} else {
GotPublicKey = CryptImportKey( hProvVerify, PublicKeys[i], PublicKeyLengths[i], 0, CRYPT_EXPORTABLE, &hXchgKey );
}
if (GotPublicKey) {
DWORD dwEncryptedFEKLength = 0;
PBYTE EncryptedFEK = EncryptFEK( Fek, hXchgKey, &dwEncryptedFEKLength );
if (EncryptedFEK == NULL) {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
} else {
PEFS_PUBLIC_KEY_INFO PublicKeyInformation = NULL;
LPWSTR KeyName;
LPWSTR ProviderName;
if (KeyNames && ProviderNames) {
KeyName = KeyNames[i];
ProviderName = ProviderNames[i];
} else {
KeyName = NULL;
ProviderName = NULL;
}
if (CreatePublicKeyInformationThumbprint(
pSid[i],
pbHashes[i],
cbHashes[i],
lpDisplayInformation[i],
KeyName,
ProviderName,
&PublicKeyInformation
)) {
if ( Fek->Entropy <= EXPORT_KEY_STRENGTH ){
DWORD SaltLength;
DWORD SaltBlockLength;
if (GetSaltLength(Fek->Algorithm, &SaltLength, &SaltBlockLength)){
pEfsKeySalt = (PEFS_KEY_SALT)LsapAllocateLsaHeap( sizeof( EFS_KEY_SALT ) + SaltBlockLength );
if (pEfsKeySalt){
pEfsKeySalt->Length = sizeof( EFS_KEY_SALT ) + SaltBlockLength;
pEfsKeySalt->SaltType = Fek->Algorithm;
RtlCopyMemory( (PBYTE)pEfsKeySalt + sizeof( EFS_KEY_SALT ),
EFS_KEY_DATA( Fek ),
SaltLength
);
}
}
} else {
pEfsKeySalt = NULL;
}
if (pEfsKeySalt || (Fek->Entropy > EXPORT_KEY_STRENGTH)) {
rc = ConstructEncryptedKey( EncryptedFEK,
dwEncryptedFEKLength,
PublicKeyInformation,
pEfsKeySalt,
&EncryptedKey[i],
&EncryptedKeySize[i]
);
}
//
// Clean up output from CreatePublicKeyInformation
//
LsapFreeLsaHeap( PublicKeyInformation );
if (pEfsKeySalt){
LsapFreeLsaHeap( pEfsKeySalt );
}
} else {
rc = GetLastError();
}
//
// Clean up output from EncryptFEK
//
LsapFreeLsaHeap( EncryptedFEK );
}
//
// If the we imported the key, don't need this key any more, get rid of it.
//
if (!PublicKeyHandle) {
CryptDestroyKey( hXchgKey );
}
} else {
//
// Couldn't import a public key, pick up error code
//
rc = GetLastError();
}
if (rc != ERROR_SUCCESS) {
//
// Something failed along the way, clean up all previous allocations
//
for (DWORD j = 0; j < i ; j++ ) {
if (EncryptedKey[j]) {
LsapFreeLsaHeap( EncryptedKey[j] );
}
}
LsapFreeLsaHeap( EncryptedKey );
LsapFreeLsaHeap( EncryptedKeySize );
return( rc );
}
}
//
// We successfully created all of the EncryptedKey structures. Assemble them
// all into a KeyRing and return the result.
//
*KeyRingLength = 0;
for (i=0 ; i<KeyCount ; i++) {
*KeyRingLength += EncryptedKeySize[i];
}
*KeyRingLength += (sizeof ( ENCRYPTED_KEYS ) - sizeof( ENCRYPTED_KEY ));
*KeyRing = (PENCRYPTED_KEYS)LsapAllocateLsaHeap( *KeyRingLength );
if (NULL != *KeyRing) {
(*KeyRing)->KeyCount = KeyCount;
PBYTE Base = (PBYTE) &((*KeyRing)->EncryptedKey[0]);
for (i=0 ; i<KeyCount ; i++) {
memcpy( Base, EncryptedKey[i], EncryptedKey[i]->Length );
Base += EncryptedKey[i]->Length;
}
} else {
*KeyRingLength = 0;
rc = ERROR_NOT_ENOUGH_MEMORY;
}
//
// Clean everything up and return
//
for (i = 0; i<KeyCount ; i++ ) {
LsapFreeLsaHeap( EncryptedKey[i] );
}
LsapFreeLsaHeap( EncryptedKey );
LsapFreeLsaHeap( EncryptedKeySize );
return( rc );
}
PEFS_KEY
GetFekFromEncryptedKeys(
IN OUT PEFS_USER_INFO pEfsUserInfo,
IN PENCRYPTED_KEYS Keys,
IN BOOL CheckBits,
OUT PDWORD KeyIndex
)
/*++
Routine Description:
This routine will attempt to decode the FEK from an ENCRYPTED_KEYS
structure. It will do this by iterating through all of the fields in the
DRF and attempting to use each one to decrypt the FEK.
Arguments:
pEfsUserInfo - User information.
Keys - Provides the ENCRYPTED_KEYS to be examined.
CheckBits - If we need to check international version or not. TRUE will check.
KeyIndex - Which encrypted key is used.
Return Value:
On success, returns a pointer to an FEK, which must be freed when no longer
needed. Returns NULL on error.
--*/
{
//
// Walk down the list of key names in the ENCRYTPED_KEYS
//
if (Keys != NULL) {
PENCRYPTED_KEY pEncryptedKey = &Keys->EncryptedKey[0];
ULONG keyCount = *(ULONG UNALIGNED*)&(Keys->KeyCount);
for (*KeyIndex=0 ; *KeyIndex<keyCount ; (*KeyIndex)++) {
PENCRYPTED_KEY pAlignedKey;
BOOLEAN freeAlignedKey;
DWORD retCode;
retCode = EfsAlignBlock(
pEncryptedKey,
(PVOID *)&pAlignedKey,
&freeAlignedKey
);
if (!pAlignedKey) {
//
// OOM. Treat it as not current.
//
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
PEFS_KEY Fek = ExtractFek( pEfsUserInfo, pAlignedKey, CheckBits );
if (Fek != NULL) {
//
// Decryption worked, return the key
//
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
return( Fek );
}
pEncryptedKey = (PENCRYPTED_KEY)( ((PBYTE)pEncryptedKey) + pAlignedKey->Length );
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
}
}
return( NULL );
}
DWORD
GetLengthEncryptedKeys(
IN PENCRYPTED_KEYS pEncryptedKeys
)
/*++
Routine Description:
Computes the total size in bytes of an ENCRYPTED_KEYS structure.
Arguments:
pEncryptedKeys - Supplies a pointer to an ENCRYPTED_KEYS structre.
Return Value:
The length in bytes of the passed structure.
--*/
{
DWORD cb=0;
ULONG keyCount = *((ULONG UNALIGNED *) &(pEncryptedKeys->KeyCount));
ULONG keyLength;
PENCRYPTED_KEY pEncryptedKey = &pEncryptedKeys->EncryptedKey[0];
for (DWORD i=0; i<keyCount ; i++) {
keyLength = *((ULONG UNALIGNED *) &(pEncryptedKey->Length));
cb += keyLength;
pEncryptedKey = (PENCRYPTED_KEY)( ((PBYTE)pEncryptedKey) + keyLength );
}
cb += sizeof( ENCRYPTED_KEYS ) - sizeof( ENCRYPTED_KEY );
return( cb );
}
BOOL
AppendEncryptedKeyToDDF(
IN PEFS_DATA_STREAM_HEADER EfsStream,
IN PENCRYPTED_KEY EncryptedKey,
IN PEFS_KEY Fek,
OUT PEFS_DATA_STREAM_HEADER * OutputEfs
)
/*++
Routine Description:
This routine will take an existing EFS stream and append the
passed encrypted key to the end of the DDF section. It does
not check to see if the key is already there or not.
Arguments:
EfsStream - The existing EFS stream.
EncryptedKey - The FEK encrypted with the new public key.
OutputEfs - Receives the new EFS stream to be placed on the
file.
Return Value:
--*/
{
BOOL b = FALSE;
//
// This is a simple append operation.
//
// The new size is the size of the old EFS stream
// plus the size of the new key. Allocate space for it.
//
DWORD EfsLength = EfsStream->Length + EncryptedKey->Length;
EfsLength = (EfsLength + 7) & 0xfffffff8;
*OutputEfs = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( EfsLength );
if (*OutputEfs) {
memset( *OutputEfs, 0, sizeof( EFS_DATA_STREAM_HEADER ));
//
// Copy the header
//
PEFS_DATA_STREAM_HEADER Efs = *OutputEfs;
*Efs = *EfsStream;
Efs->Length = EfsLength;
//
// Start copying the DDF at the base of the EFS
// structure. Copy the whole thing. Do ourselves a
// favor and don't assume that the DDF or DRF are in
// any particular order in the EFS structure.
//
PENCRYPTED_KEYS pDDF = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, EfsStream );
DWORD cbDDF = GetLengthEncryptedKeys( pDDF );
//
// Store away the offset to the beginning on the DDF
//
Efs->DataDecryptionField = (ULONG)sizeof( EFS_DATA_STREAM_HEADER );
PBYTE Base = (PBYTE)OFFSET_TO_POINTER( DataDecryptionField, Efs );
memcpy( Base, pDDF, cbDDF );
//
// Point to the new DDF, we need to fix it up a little
//
PENCRYPTED_KEYS pNewDDF = (PENCRYPTED_KEYS)Base;
pNewDDF->KeyCount++;
Base += cbDDF;
memcpy( Base, EncryptedKey, EncryptedKey->Length );
Base += EncryptedKey->Length;
//
// Now copy the DRF onto the end and we're done.
//
PENCRYPTED_KEYS pDRF = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, EfsStream );
if ((PVOID) pDRF == (PVOID) EfsStream) {
Efs->DataRecoveryField = 0;
} else {
DWORD cbDRF = GetLengthEncryptedKeys( pDRF );
Efs->DataRecoveryField = (ULONG)POINTER_TO_OFFSET( Base, Efs );
memcpy( Base, pDRF, cbDRF );
}
// Base += cbDRF
b = TRUE;
// memset( &(Efs->EfsHash), 0, MD5_HASH_SIZE );
// EfspChecksumEfs( Efs, Fek );
} else {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
}
return( b );
}
BOOLEAN
EfspCertInEFS(
IN PBYTE pbHash,
IN DWORD cbHash,
IN PEFS_DATA_STREAM_HEADER pEfsStream
)
/*++
Routine Description:
Searches the passed EFS stream for an entry with the same hash as passed.
Arguments:
pbHash - Supplies a pointer to the hash being queried
cbHash - Supplies the length in bytes of the hash being queried
pEfsStream - Supplies the EFS stream from the file being queried
Return Value:
TRUE if the passed hash is found, FALSE otherwise.
--*/
{
BOOLEAN fFound = FALSE;
DWORD KeyIndex;
//
// Check the hash in each entry in the DDF. If
// we get a match, return success.
//
PDDF Keys = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, pEfsStream );
PENCRYPTED_KEY pEncryptedKey = &Keys->EncryptedKey[0];
for (KeyIndex=0 ; KeyIndex<Keys->KeyCount ; KeyIndex++) {
PEFS_PUBLIC_KEY_INFO PublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)( (PUCHAR)pEncryptedKey + *(ULONG UNALIGNED *) &(pEncryptedKey->PublicKeyInfo) );
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)( (PUCHAR) PublicKeyInfo + *(ULONG UNALIGNED *) &(PublicKeyInfo->CertificateThumbprint.CertHashData));
if (*(DWORD UNALIGNED *)&(CertHashData->cbHash) == cbHash) {
PBYTE pbSrcHash = (PBYTE)CertHashData + *(ULONG UNALIGNED *) &(CertHashData->pbHash);
if (memcmp(pbSrcHash, pbHash, cbHash) == 0) {
fFound = TRUE;
break;
}
}
pEncryptedKey = NEXT_ENCRYPTED_KEY( pEncryptedKey );
}
return( fFound );
}
BOOLEAN
AddUserToEFS(
IN PEFS_DATA_STREAM_HEADER EfsStream,
IN PSID NewUserSid OPTIONAL,
IN PEFS_KEY Fek,
IN PBYTE pbCert,
IN DWORD cbCert,
OUT PEFS_DATA_STREAM_HEADER * NewEfs
)
/*++
Routine Description:
This routine adds a new encrypted key block to the DDF of the passed
EFS stream.
Arguments:
EfsStream - Takes a pointer to the EFS stream to be modified.
NewUserSid - Optionally supplies a pointer to the SID of the new user.
Fek - Supplies the FEK of the file being modified.
pbCert - Supplies a pointer to the certificate of the new user.
cbCert - Supplies the lenght in bytes of the certificate.
NewEfs - Returns a pointer to the new EFS stream.
Return Value:
TRUE on success, FALSE on failure. Call GetLastError() for more details.
--*/
{
DWORD rc;
PEFS_DATA_STREAM_HEADER Efs = NULL;
BOOLEAN b = FALSE;
PEFS_KEY_SALT pEfsKeySalt = NULL;
//
// Get the key information from the passed cert
//
PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(
X509_ASN_ENCODING,
pbCert,
cbCert
);
if (pCertContext != NULL) {
PBYTE pbHash;
DWORD cbHash;
pbHash = GetCertHashFromCertContext(
pCertContext,
&cbHash
);
if (pbHash) {
//
// See if this hash is already on the file. If so, return error.
//
if (!EfspCertInEFS( pbHash, cbHash, EfsStream )) {
//
// Now get the public key out of the cert so we can
// encrypt the FEK
//
PCERT_PUBLIC_KEY_INFO pSubjectPublicKeyInfo = &pCertContext->pCertInfo->SubjectPublicKeyInfo;
//
// Import the public key into a context
//
HCRYPTKEY hKey;
if (CryptImportPublicKeyInfo( hProvVerify, X509_ASN_ENCODING, pSubjectPublicKeyInfo, &hKey )) {
//
// Use the newly imported key to encrypt the FEK
//
DWORD dwEncryptedFEKLength = 0;
PBYTE EncryptedFEK = EncryptFEK( Fek, hKey, &dwEncryptedFEKLength );
if (EncryptedFEK != NULL) {
PEFS_PUBLIC_KEY_INFO PublicKeyInformation = NULL;
//
// This may come back NULL, but that's ok.
//
LPWSTR lpDisplayName = EfspGetCertDisplayInformation( pCertContext );
b = CreatePublicKeyInformationThumbprint(
NewUserSid,
pbHash,
cbHash,
lpDisplayName,
NULL,
NULL,
&PublicKeyInformation
);
if (lpDisplayName) {
LsapFreeLsaHeap( lpDisplayName );
}
if (b) {
if (Fek->Entropy <= EXPORT_KEY_STRENGTH) {
DWORD SaltLength;
DWORD SaltBlockLength;
if (GetSaltLength(Fek->Algorithm, &SaltLength, &SaltBlockLength)) {
pEfsKeySalt = (PEFS_KEY_SALT)LsapAllocateLsaHeap( sizeof( EFS_KEY_SALT ) + SaltBlockLength );
if (pEfsKeySalt) {
pEfsKeySalt->Length = sizeof( EFS_KEY_SALT ) + SaltBlockLength;
pEfsKeySalt->SaltType = Fek->Algorithm;
RtlCopyMemory( (PBYTE)pEfsKeySalt + sizeof( EFS_KEY_SALT ),
EFS_KEY_DATA( Fek ),
SaltLength
);
}
}
} else {
pEfsKeySalt = NULL;
}
if (pEfsKeySalt || (Fek->Entropy > EXPORT_KEY_STRENGTH)) {
DWORD EncryptedKeySize = 0;
PENCRYPTED_KEY EncryptedKey;
rc = ConstructEncryptedKey( EncryptedFEK,
dwEncryptedFEKLength,
PublicKeyInformation,
pEfsKeySalt,
&EncryptedKey,
&EncryptedKeySize
);
//
// We'll check the return code below
//
if (rc == ERROR_SUCCESS) {
b = AppendEncryptedKeyToDDF(
EfsStream,
EncryptedKey,
Fek,
NewEfs
) != 0;
LsapFreeLsaHeap( EncryptedKey );
} else {
SetLastError( rc );
}
if (pEfsKeySalt) {
LsapFreeLsaHeap( pEfsKeySalt );
}
} else {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
}
LsapFreeLsaHeap( PublicKeyInformation );
}
LsapFreeLsaHeap( EncryptedFEK );
}
CryptDestroyKey( hKey );
}
} else {
//
// Adding duplicate cert considered succeed
//
b = TRUE;
}
LsapFreeLsaHeap( pbHash );
}
CertFreeCertificateContext( pCertContext );
}
if (!b) {
//
// If we're not going to return success, clean up everything we were
// planning on returning.
//
if (*NewEfs != NULL) {
LsapFreeLsaHeap( *NewEfs );
}
}
return( b );
}
PENCRYPTED_KEY
GetEncryptedKeyByIndex(
PENCRYPTED_KEYS pEncryptedKeys,
DWORD KeyIndex
)
{
ASSERT( KeyIndex < *((ULONG UNALIGNED *)&(pEncryptedKeys->KeyCount)) );
PENCRYPTED_KEY pEncryptedKey = &pEncryptedKeys->EncryptedKey[0];
if (KeyIndex == 0) {
return( pEncryptedKey );
}
for (DWORD i=0; i<KeyIndex ; i++, pEncryptedKey = (PENCRYPTED_KEY)(((PBYTE)(pEncryptedKey)) + *(ULONG UNALIGNED *)&((PENCRYPTED_KEY)(pEncryptedKey))->Length)) ;
return( pEncryptedKey );
}
BOOL
UserKeyCurrent(
PEFS_USER_INFO pEfsUserInfo,
PDDF Ddf,
DWORD KeyIndex
)
/*++
Routine Description:
This routine checks to see if the key used to decrypt the file
is the user's current encryption key.
Arguments:
Ddf - Supplies the DDF of the file being accessed.
KeyIndex - Supplies the index of the key in the DDF that was
used to open the file.
Return Value:
TRUE if the key used corresponds to the user's encryption key.
FALSE otherwise.
--*/
{
BOOL b = TRUE;
DWORD rc = ERROR_SUCCESS;
PBYTE pbCurrentKeyHash = NULL;
DWORD cbCurrentKeyHash;
PBYTE pbHash;
DWORD cbHash;
PBYTE pbWkHash = NULL;
//
// Compare the current user key with the contents
// of the specified key, and see if they're in sync.
//
PENCRYPTED_KEY pEncryptedKey = GetEncryptedKeyByIndex( Ddf, KeyIndex );
PENCRYPTED_KEY pAlignedKey;
BOOLEAN freeAlignedKey;
rc = EfsAlignBlock(
pEncryptedKey,
(PVOID *)&pAlignedKey,
&freeAlignedKey
);
if (!pAlignedKey) {
//
// OOM. Treat it as current.
//
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return TRUE;
}
PEFS_PUBLIC_KEY_INFO pPublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)OFFSET_TO_POINTER( PublicKeyInfo, pAlignedKey );
if (pPublicKeyInfo->KeySourceTag != EfsCertificateThumbprint) {
//
// The user key may be current, but the key on the file isn't.
// Return FALSE so as to regenerate the EFS on this file.
//
DebugLog((DEB_WARN, "Updating downlevel file\n" ));
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
return( FALSE );
}
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, pPublicKeyInfo );
pbHash = (PBYTE)OFFSET_TO_POINTER( pbHash, CertHashData );
cbHash = CertHashData->cbHash;
if (pEfsUserInfo->pUserCache) {
//
// Check the current against the cache
//
pbWkHash = pEfsUserInfo->pUserCache->pbHash;
cbCurrentKeyHash = pEfsUserInfo->pUserCache->cbHash;
} else {
rc = GetCurrentHash(
pEfsUserInfo,
&pbCurrentKeyHash,
&cbCurrentKeyHash
);
if (rc == ERROR_SUCCESS) {
pbWkHash = pbCurrentKeyHash;
}
}
//
// Compare the hash stored in the current user key
// with the hash in the specified public key info.
//
if (rc == ERROR_SUCCESS) {
//
// Compare the thumbprint in the public key info against
// the user's current.
//
if (cbHash == cbCurrentKeyHash) {
if (memcmp(pbWkHash, pbHash, cbHash) != 0) {
b = FALSE;
}
} else {
b = FALSE;
}
}
if (pbCurrentKeyHash) {
LsapFreeLsaHeap( pbCurrentKeyHash );
}
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
//
// If we failed to get the current key hash and reach here, this means we also failed to create the hash.
// This could mean that we could not set the current key. We could not test if the hash could be the current.
// We could not replace the DDF with a current one anyway. We assume the passed in hash as the current.
// Could I be wrong here?
//
return( b );
}
BOOL
ReplaceUserKey(
PEFS_USER_INFO pEfsUserInfo,
PEFS_KEY Fek,
PEFS_DATA_STREAM_HEADER EfsStream,
DWORD KeyIndex,
PEFS_DATA_STREAM_HEADER * UpdatedEfs
)
/*++
Routine Description:
This routine will replace the user key specified by the passed KeyIndex
with another one that uses the current user EFS keys.
It assumes that we are in the context of the caller who owns this key.
Arguments:
Fek - Supplies the decrypted FEK for the file.
EfsStream - Supplies the EFS stream on the file.
KeyIndex - Supplies the index of the key to be replaced.
UpdatedEfs - Receives a pointer to the new EFS stream to be
placed on the file.
Return Value:
--*/
{
//
// Query the current user keys. This will give me
// back a hash and a container and provider name.
//
HCRYPTKEY hKey;
HCRYPTPROV hProv;
PBYTE pbHash;
DWORD cbHash;
PEFS_KEY_SALT pEfsKeySalt = NULL;
BOOL b = FALSE;
DWORD rc;
LPWSTR ContainerName;
LPWSTR ProviderName;
LPWSTR DisplayInfo;
DWORD ProviderType;
DebugLog((DEB_WARN, "Updating EFS stream\n" ));
PSID NewUserSid = pEfsUserInfo->pTokenUser->User.Sid;
rc = GetCurrentKey(
pEfsUserInfo,
&hKey,
&hProv,
&ContainerName,
&ProviderName,
&ProviderType,
&DisplayInfo,
&pbHash,
&cbHash
);
//
// Use this key information to encrypt the FEK
// and generate an encrypted key structure.
//
if (rc == ERROR_SUCCESS) {
DWORD dwEncryptedFEKLength = 0;
PBYTE EncryptedFEK;
HCRYPTKEY hLocalKey;
HCRYPTPROV hLocalProv;
PBYTE pbLocalHash;
DWORD cbLocalHash;
LPWSTR lpLocalContainerName;
LPWSTR lpLocalProviderName;
LPWSTR lpLocalDisplayInfo;
if (pbHash) {
pbLocalHash = pbHash;
cbLocalHash = cbHash;
hLocalKey = hKey;
hLocalProv = hProv;
lpLocalContainerName = ContainerName;
lpLocalProviderName = ProviderName;
lpLocalDisplayInfo = DisplayInfo;
} else {
ASSERT(pEfsUserInfo->pUserCache);
pbLocalHash = pEfsUserInfo->pUserCache->pbHash;
cbLocalHash = pEfsUserInfo->pUserCache->cbHash;
hLocalKey = pEfsUserInfo->pUserCache->hUserKey;
hLocalProv = pEfsUserInfo->pUserCache->hProv;
lpLocalContainerName = pEfsUserInfo->pUserCache->ContainerName;
lpLocalProviderName = pEfsUserInfo->pUserCache->ProviderName;
lpLocalDisplayInfo = pEfsUserInfo->pUserCache->DisplayInformation;
}
EncryptedFEK = EncryptFEK( Fek, hLocalKey, &dwEncryptedFEKLength );
if (EncryptedFEK != NULL) {
PEFS_PUBLIC_KEY_INFO PublicKeyInformation = NULL;
if (CreatePublicKeyInformationThumbprint(
NewUserSid,
pbLocalHash,
cbLocalHash,
lpLocalDisplayInfo,
lpLocalContainerName,
lpLocalProviderName,
&PublicKeyInformation
)) {
if ( Fek->Entropy <= EXPORT_KEY_STRENGTH ){
DWORD SaltLength;
DWORD SaltBlockLength;
if (GetSaltLength(Fek->Algorithm, &SaltLength, &SaltBlockLength)){
pEfsKeySalt = (PEFS_KEY_SALT)LsapAllocateLsaHeap( sizeof( EFS_KEY_SALT ) + SaltBlockLength );
if (pEfsKeySalt){
pEfsKeySalt->Length = sizeof( EFS_KEY_SALT ) + SaltBlockLength;
pEfsKeySalt->SaltType = Fek->Algorithm;
RtlCopyMemory( (PBYTE)pEfsKeySalt + sizeof( EFS_KEY_SALT ),
EFS_KEY_DATA( Fek ),
SaltLength
);
}
}
} else {
pEfsKeySalt = NULL;
}
if (pEfsKeySalt || (Fek->Entropy > EXPORT_KEY_STRENGTH)) {
//
// This should return an error
//
DWORD EncryptedKeySize = 0;
PENCRYPTED_KEY EncryptedKey;
rc = ConstructEncryptedKey( EncryptedFEK,
dwEncryptedFEKLength,
PublicKeyInformation,
pEfsKeySalt,
&EncryptedKey,
&EncryptedKeySize
);
//
// We'll check the return code below
//
if (rc == ERROR_SUCCESS) {
PEFS_DATA_STREAM_HEADER NewEfs = NULL;
if (AppendEncryptedKeyToDDF(
EfsStream,
EncryptedKey,
Fek,
&NewEfs
)) {
PEFS_DATA_STREAM_HEADER pNewEfs2 = NULL;
if (DeleteEncryptedKeyByIndex(
NewEfs,
KeyIndex,
Fek,
&pNewEfs2
)) {
*UpdatedEfs = pNewEfs2;
b = TRUE;
} else {
*UpdatedEfs = NULL; // paranoia
}
LsapFreeLsaHeap( NewEfs );
}
LsapFreeLsaHeap( EncryptedKey );
}
if (pEfsKeySalt){
LsapFreeLsaHeap( pEfsKeySalt );
}
}
LsapFreeLsaHeap( PublicKeyInformation );
}
LsapFreeLsaHeap( EncryptedFEK );
} else {
rc = GetLastError();
}
if (ContainerName) {
LsapFreeLsaHeap( ContainerName );
}
if (ProviderName) {
LsapFreeLsaHeap( ProviderName );
}
if (DisplayInfo) {
LsapFreeLsaHeap( DisplayInfo );
}
if (pbHash) {
LsapFreeLsaHeap( pbHash );
}
if (hKey) {
CryptDestroyKey( hKey );
}
if (hProv) {
CryptReleaseContext( hProv, 0 );
}
}
SetLastError( rc );
if (!b) {
DebugLog((DEB_ERROR, "Update failed, error = %x\n" ,GetLastError() ));
}
return( b );
}
BOOL
DeleteEncryptedKeyByIndex(
IN PEFS_DATA_STREAM_HEADER pEfs,
IN DWORD KeyIndex,
IN PEFS_KEY Fek,
OUT PEFS_DATA_STREAM_HEADER * pNewEfs
)
/*++
Routine Description:
This routine deletes the passed key from the DDF of the passed
EFS stream, and returns a new EFS stream. It does not deallocate
the original EFS stream.
Arguments:
pEfs - Supplies a pointer to the original EFS stream.
KeyIndex - Supplies the index of the key to delete.
pNewEfs - Returns a pointer to the new EFS stream allocated
out of heap.
Return Value:
TRUE on success, FALSE on failure. GetLastError() will return more information.
--*/
{
BOOL b = FALSE;
//
// Do this the lazy way: build the new DDF
// and copy it into the EFS stream.
//
PENCRYPTED_KEYS pDDF = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, pEfs );
//
// This is an over estimate, but it will do.
//
PENCRYPTED_KEYS pNewDDF = (PENCRYPTED_KEYS)LsapAllocateLsaHeap( GetLengthEncryptedKeys( pDDF ) );
if (pNewDDF) {
pNewDDF->KeyCount = pDDF->KeyCount - 1;
DWORD cbNewDDF = sizeof( ENCRYPTED_KEYS ) - sizeof( ENCRYPTED_KEY );
PBYTE Target = (PBYTE)(&pNewDDF->EncryptedKey[0]);
PBYTE Source = (PBYTE)(&pDDF->EncryptedKey[0]);
for (DWORD i=0; i<pDDF->KeyCount ; i++) {
if (i != KeyIndex) {
//
// We want this one. Copy it.
//
DWORD KeyLength = *((DWORD UNALIGNED*) &((PENCRYPTED_KEY)Source)->Length);
cbNewDDF += KeyLength;
memcpy( Target, Source, KeyLength );
Target = (PBYTE)NEXT_ENCRYPTED_KEY( Target );
Source = (PBYTE)NEXT_ENCRYPTED_KEY( Source );
} else {
//
// Skip this one.
//
Source = (PBYTE)NEXT_ENCRYPTED_KEY( Source );
}
}
//
// pNewDDF contains a pointer to our new DDF.
//
PENCRYPTED_KEYS pDRF = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, pEfs );
DWORD cbDRF;
if ((PVOID)pDRF == (PVOID)pEfs) {
//
// There was no DRF
//
cbDRF = 0;
pDRF = NULL;
} else {
cbDRF = GetLengthEncryptedKeys( pDRF );
}
*pNewEfs = AssembleEfsStream( pNewDDF, cbNewDDF, pDRF, cbDRF, Fek );
if (*pNewEfs) {
b = TRUE;
} else {
*pNewEfs = NULL; // paranoia
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
}
LsapFreeLsaHeap( pNewDDF );
} else {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
}
return( b );
}
PEFS_DATA_STREAM_HEADER
AssembleEfsStream(
IN PENCRYPTED_KEYS pDDF,
IN DWORD cbDDF,
IN PENCRYPTED_KEYS pDRF,
IN DWORD cbDRF,
IN PEFS_KEY Fek
)
/*++
Routine Description:
This routine takes the pieces of an EFS stream and assembles them into
an EFS stream.
Arguments:
pDDF - Supplies a pointer to the DDF for the new EFS stream.
cbDDF - Supplies the length in bytes of the DDF.
pDRF - Supplies a pointer to the DRF for the new EFS stream.
cbDRF - Supplies the length in bytes of the DRF.
Return Value:
Returns a pointer to a new EFS stream, or NULL. Caller is responsible
for freeing the returned memory.
--*/
{
//
// Compute the total size of the new EFS stream
//
DWORD cbNewEFS = sizeof( EFS_DATA_STREAM_HEADER ) + cbDDF + cbDRF;
cbNewEFS = (cbNewEFS + 7) & 0xfffffff8;
PEFS_DATA_STREAM_HEADER pNewEFS = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( cbNewEFS );
if (pNewEFS) {
memset( pNewEFS, 0, sizeof( EFS_DATA_STREAM_HEADER ) );
pNewEFS->Length = cbNewEFS;
pNewEFS->State = 0;
pNewEFS->EfsVersion = EFS_CURRENT_VERSION;
RPC_STATUS RpcStatus = UuidCreate( &pNewEFS->EfsId );
if (RpcStatus == ERROR_SUCCESS || RpcStatus == RPC_S_UUID_LOCAL_ONLY) {
//
// Copy in the DDF
//
PBYTE Base = (PBYTE)(((PBYTE)pNewEFS) + sizeof( EFS_DATA_STREAM_HEADER ));
pNewEFS->DataDecryptionField = (ULONG)POINTER_TO_OFFSET( Base, pNewEFS );
memcpy( Base, pDDF, cbDDF );
Base += cbDDF;
//
// Copy the DRF
//
if (pDRF) {
memcpy( Base, pDRF, cbDRF );
pNewEFS->DataRecoveryField = (ULONG)POINTER_TO_OFFSET( Base, pNewEFS );
} else {
pNewEFS->DataRecoveryField = 0;
}
// Base += cbDRF
// EfspChecksumEfs( pNewEFS, Fek );
} else {
//
// Couldn't get a UUID, fail
//
LsapFreeLsaHeap( pNewEFS );
pNewEFS = NULL;
}
}
return( pNewEFS );
}
BOOL
RecoveryInformationCurrent(
PEFS_DATA_STREAM_HEADER EfsStream
)
/*++
Routine Description:
This routine examines the recovery information in an EFS stream and determines if
the system recovery information has changed since this stream was generated. It
does this by comparing the certificate hashes stored in the current recovery
information with the certificate hashes stored in the passed DRF.
Arguments:
EfsStream - Supplies a pointer to the EFS stream to be examined.
Return Value:
TRUE - Recovery information is up to date.
FALSE - DRF must be regenerated with new recovery information.
--*/
{
//
// Assume that the entries in the DRF correspond to entries in the
// current recovery information array in order. That will simplify
// this operation considerably.
//
//
// Get a pointer to the DRF
//
PDRF pDrf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, EfsStream );
//
// For each entry in the DRF, compare hash to corresponding entry in recovery
// policy. Fail on first mismatch.
//
ULONG KeyCount = 0;
if ((PVOID)pDrf == (PVOID)EfsStream) {
//
// No DRF field.
//
pDrf = NULL;
}
//
// pDrf->KeyCount could be unaligned
//
if (pDrf) {
RtlCopyMemory(&KeyCount, &pDrf->KeyCount, sizeof(ULONG));
}
/*
//
// There may not be a recovery policy on this machine. If that's the case,
// we're just going to leave this file alone.
//
if (CurrentRecoveryPolicy.dwKeyCount == 0) {
return( TRUE );
}
*/
if (!pDrf && CurrentRecoveryPolicy.PolicyStatus < RECOVERY_POLICY_OK) {
//
// Current recovery policy has no valid recovery agent and the existing $EFS
// has no valid agent too.
//
return (TRUE);
}
if (CurrentRecoveryPolicy.dwKeyCount != KeyCount) {
return( FALSE );
}
ASSERT(pDrf);
if (!pDrf) {
//
// We should never get into this. This is only for the purpose of defensive.
//
return (TRUE);
}
PENCRYPTED_KEY pEncryptedKey = &pDrf->EncryptedKey[0];
for (ULONG i=0; i<KeyCount ; i++) {
PENCRYPTED_KEY pAlignedKey;
BOOLEAN freeAlignedKey;
(VOID) EfsAlignBlock(
pEncryptedKey,
(PVOID *)&pAlignedKey,
&freeAlignedKey
);
if (!pAlignedKey) {
//
// OOM. Treat it as not current.
//
return (FALSE);
}
PEFS_PUBLIC_KEY_INFO PublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)OFFSET_TO_POINTER( PublicKeyInfo, pAlignedKey );
ASSERT( PublicKeyInfo->KeySourceTag == EfsCertificateThumbprint );
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, PublicKeyInfo );
PBYTE pbHash = (PBYTE)OFFSET_TO_POINTER( pbHash, CertHashData );
DWORD cbHash = CertHashData->cbHash;
if ((cbHash != CurrentRecoveryPolicy.cbHash[i]) || (memcmp(pbHash, CurrentRecoveryPolicy.pbHash[i], cbHash) != 0)) {
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
return( FALSE );
} else {
pEncryptedKey = (PENCRYPTED_KEY)(((PBYTE)(pEncryptedKey)) + pAlignedKey->Length);
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
}
}
return( TRUE );
}
DWORD
UpdateRecoveryInformation(
PEFS_KEY Fek,
PEFS_DATA_STREAM_HEADER EfsStream,
PEFS_DATA_STREAM_HEADER * UpdatedEfs
)
/*++
Routine Description:
This routine will create a new EFS stream based on the passed in one.
The new EFS stream will contain a DRF based on the current recovery
policy. It is assumed that someone else has already verified that
the DRF is this stream is out of date, this routine will not do that.
Arguments:
Fek - The FEK for the file being updated.
EfsStream - Supplies the existing EFS stream for the file.
UpdatedEfs - Returns an updated EFS stream for the file, allocated out of heap.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
DWORD rc;
DWORD cbDDF = 0;
DWORD cbDRF = 0;
PENCRYPTED_KEYS pNewDRF;
*UpdatedEfs = NULL;
//
// Simply generate a new DRF and stick it onto the end of the existing EFS
// stream.
//
PDDF pDdf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, EfsStream );
cbDDF = GetLengthEncryptedKeys( pDdf );
rc = GenerateDRF( Fek, &pNewDRF, &cbDRF);
if (rc == ERROR_SUCCESS) {
*UpdatedEfs = AssembleEfsStream( pDdf, cbDDF, pNewDRF, cbDRF, Fek );
if (*UpdatedEfs == NULL) {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
LsapFreeLsaHeap( pNewDRF );
}
ReleaseRecoveryData();
return( rc );
}
DWORD
DecryptFek(
IN PEFS_USER_INFO pEfsUserInfo,
IN PEFS_DATA_STREAM_HEADER EfsStream,
OUT PEFS_KEY * Fek,
OUT PEFS_DATA_STREAM_HEADER * NewEfs,
IN ULONG OpenType
)
/*++
Routine Description:
This routine will extract the FEK from the passed Efs stream.
It will also check to see if the EFS stream is up to date w.r.t.
current keys and recovery policy, and if not, it will generate
a new one.
Arguments:
EfsStream - Supplies the EFS stream from the file being opened.
Fek - Returns the decrypted FEK from the EFS stream. This data is
allocated out of local heap and must be freed by the caller.
NewEfs - Returns a new EFS stream for the file if necessary, otherwise
returns NULL. This data is allocated out of local heap and must be
freed by the caller
OpenType - Whether this is a normal open or an open for recovery.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
DWORD rc = ERROR_SUCCESS;
DWORD KeyIndex;
PEFS_DATA_STREAM_HEADER UpdatedEfs = NULL;
BOOLEAN Recovery = FALSE;
BOOLEAN bEfsInvalid = FALSE;
BOOLEAN DRFIsCurrent = FALSE;
BOOLEAN ReqUpdateDRF = FALSE;
*Fek = NULL;
*NewEfs = NULL;
if (EfsStream->EfsVersion > EFS_CURRENT_VERSION) {
return ERROR_EFS_VERSION_NOT_SUPPORT;
}
__try {
#if DBG
UUID * EfsUuid = &EfsStream->EfsId;
WCHAR * StringUuid;
if (STATUS_SUCCESS == UuidToString ( EfsUuid, &StringUuid )) {
DebugLog((DEB_TRACE_EFS, "Found $EFS w/ id: %ws\n" ,StringUuid ));
RpcStringFree( &StringUuid) ;
}
#endif
//
// First try the DDF, and if we strike out there, the DRF.
//
PDDF Ddf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, EfsStream );
PDRF Drf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, EfsStream );
if ((PVOID)Drf == (PVOID)EfsStream) {
//
// No DRF field.
//
Drf = NULL;
}
*Fek = GetFekFromEncryptedKeys( pEfsUserInfo, (PENCRYPTED_KEYS)Ddf, TRUE, &KeyIndex );
if ((NULL == *Fek) && Drf) {
*Fek = GetFekFromEncryptedKeys( pEfsUserInfo, (PENCRYPTED_KEYS)Drf, TRUE, &KeyIndex );
Recovery = TRUE;
}
if (*Fek == NULL) {
//
// Bad keyset means that none of the keysets in the current
// context could decrypt this file.
//
if (GetLastError() == NTE_BAD_KEYSET) {
return ( ERROR_DECRYPTION_FAILED );
} else {
return ( GetLastError() );
}
} else {
//
// If we opened the file via the DDF, make sure
// that the entry we used is current for the current
// user.
//
if (!Recovery) {
if (!UserKeyCurrent( pEfsUserInfo, Ddf, KeyIndex )) {
//
// The index we used to open the file
// is not current. Replace with the current user
// key.
//
(VOID) ReplaceUserKey( pEfsUserInfo, *Fek, EfsStream, KeyIndex, &UpdatedEfs );
}
}
//
// Checksum the EFS stream to make sure it has not been tampered with.
// If it is changed, we will try to see if the DRF has been checnged or not.
//
/*
if (!EfspValidateEfsStream( EfsStream, *Fek )) {
//
// Checksum not match. See if the DRF changed
//
PENCRYPTED_KEYS pNewDRF;
DWORD cbDRF;
rc = GenerateDRF(*Fek, &pNewDRF, &cbDRF);
if ( ERROR_SUCCESS == rc ) {
//
// Let's see if the DRF matches
//
if (EqualEncryptedKeys(Drf, pNewDRF, cbDRF)) {
//
// DRF is not modified. We can't fix the modification
// Regenerate the check sum.
//
DRFIsCurrent = TRUE;
if (!UpdatedEfs) {
//
// If $EFS is updated above, we don't need to generate the check sum again.
//
UpdatedEfs = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( EfsStream->Length );
if (UpdatedEfs) {
RtlCopyMemory(UpdatedEfs, EfsStream, EfsStream->Length);
memset( &UpdatedEfs->EfsHash, 0, MD5_HASH_SIZE );
EfspChecksumEfs( UpdatedEfs, *Fek );
}
}
//
// This is the best effort. If we failed to get the memory in the above
// We will still try to let user open the file. But not fix the check sum
//
} else {
//
// Either the checksum is modified, or the DRF is modified.
// Do the check sum with the new DRF
//
ReqUpdateDRF = TRUE;
}
LsapFreeLsaHeap( pNewDRF );
}
ReleaseRecoveryData();
}
*/
//
// Regardless of whether we did a recovery or not,
// we still need to check to see if the recovery
// information is up to snuff.
//
if ( !RecoveryInformationCurrent( EfsStream ) ) {
//
// We may have fixed up the current user key
// above. If so, modify that EFS stream.
// Otherwise, use the one that the user
// passed in.
//
if (UpdatedEfs) {
PEFS_DATA_STREAM_HEADER Tmp;
rc = UpdateRecoveryInformation( *Fek, UpdatedEfs, &Tmp );
if (ERROR_SUCCESS == rc) {
LsapFreeLsaHeap( UpdatedEfs );
UpdatedEfs = Tmp;
}
} else {
rc = UpdateRecoveryInformation( *Fek, EfsStream, &UpdatedEfs );
}
}
//
// We successfully decrypted the file, but we may
// not have been able to update the various parts
// That's ok, we'll let the file decrypt.
//
// Note that, if there have been no updates,
// UpdatedEfs is NULL, so this is a safe thing to do.
//
*NewEfs = UpdatedEfs;
return ( ERROR_SUCCESS );
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
rc = GetExceptionCode();
//
// Clean up
//
if (UpdatedEfs) {
LsapFreeLsaHeap( UpdatedEfs );
}
if (*Fek != NULL) {
LsapFreeLsaHeap( *Fek );
*Fek = NULL;
}
}
return( rc );
}
DWORD
EfsGetFek(
IN PEFS_USER_INFO pEfsUserInfo,
IN PEFS_DATA_STREAM_HEADER EfsStream,
OUT PEFS_KEY * Fek
)
/*++
Routine Description:
This routine will extract the FEK from the passed Efs stream.
Arguments:
EfsStream - Supplies the EFS stream from the file being opened.
Fek - Returns the decrypted FEK from the EFS stream. This data is
allocated out of local heap and must be freed by the caller.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
DWORD rc = ERROR_SUCCESS;
DWORD KeyIndex;
*Fek = NULL;
__try {
//
// First try the DDF, and if we strike out there, the DRF.
//
PDDF Ddf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, EfsStream );
PDRF Drf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, EfsStream );
*Fek = GetFekFromEncryptedKeys( pEfsUserInfo, (PENCRYPTED_KEYS)Ddf, FALSE, &KeyIndex );
if ((NULL == *Fek) && ( (PVOID)Drf != (PVOID)EfsStream) ) {
*Fek = GetFekFromEncryptedKeys( pEfsUserInfo, (PENCRYPTED_KEYS)Drf, FALSE, &KeyIndex );
}
if (*Fek == NULL) {
return (rc = GetLastError() );
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
rc = GetExceptionCode();
if (*Fek != NULL) {
LsapFreeLsaHeap( *Fek );
*Fek = NULL;
}
}
return( rc );
}
PEFS_KEY
ExtractFek(
IN PEFS_USER_INFO pEfsUserInfo,
IN PENCRYPTED_KEY EncryptedKey,
IN BOOL CheckBits
)
/*++
Routine Description:
This routine will take the passed EncryptedKey structure and attempt
to decrypt the FEK encoded in the structure. It will do this by first
attempting to create context using the Provider and Container names
contained in the structure. If such a context exists, its key
exchange key is used to import the encrypted session key from the
structure.
Once the session key has been imported, it is used to decrypt the encrypted
FEK structure. All of the pieces are then used to reconstruct the key
integrity information structure, and if this verifies, then it is assumed
that the FEK has been decoded correctly.
Arguments:
pEfsUserInfo - User Info.
EncryptedKey - Supplies a pointer to an EncryptedKey from the file being opened.
CheckBits - TRUE will check the encryption bits against current version.
Return Value:
On success, returns a pointer to a decrypted FEK. On failure, returns NULL.
The returned pointer is allocated out of heap and must be freed.
--*/
{
//
// Obtain a context to the user's RSA key
//
DWORD LastError = ERROR_SUCCESS;
PEFS_KEY DecryptedFEK = NULL;
PEFS_PUBLIC_KEY_INFO PublicKeyInfo = (PEFS_PUBLIC_KEY_INFO) OFFSET_TO_POINTER(PublicKeyInfo, EncryptedKey);
HCRYPTPROV hProv = 0;
HCRYPTKEY hKey;
HCRYPTPROV hWkProv;
HCRYPTKEY hWkKey;
switch (PublicKeyInfo->KeySourceTag) {
case EfsCertificateThumbprint:
{
//
// See if there is a cert in the current context
// that corresponds to this thumbprint. If so,
// we're in business.
// The KeySourceTag has been changed a couple of times
// during the development. Now this is the only valid tag.
//
PBYTE pbHash;
DWORD cbHash;
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, PublicKeyInfo );
pbHash = (PBYTE)OFFSET_TO_POINTER( pbHash, CertHashData );
cbHash = CertHashData->cbHash;
LastError = GetKeyInfoFromCertHash(
pEfsUserInfo,
pbHash,
cbHash,
&hKey,
&hProv,
NULL,
NULL,
NULL,
NULL
);
if (LastError != ERROR_SUCCESS) {
SetLastError( LastError );
return( NULL );
}
break;
}
default:
return( NULL );
break;
}
if (hKey) {
//
// We are not using the cache.
//
hWkKey = hKey;
} else {
ASSERT(pEfsUserInfo->pUserCache);
hWkKey = pEfsUserInfo->pUserCache->hUserKey;
}
//
// Decrypt the FEK field with the session key
//
PBYTE EncryptedFEK = (PBYTE)OFFSET_TO_POINTER( EncryptedFEK, EncryptedKey );
//
// Copy the FEK into a temporary buffer for decryption
//
DWORD cbData = EncryptedKey->EncryptedFEKLength;
DecryptedFEK = (PEFS_KEY)LsapAllocateLsaHeap( cbData );
if (DecryptedFEK != NULL) {
memcpy( DecryptedFEK, EncryptedFEK, cbData );
BOOL Verified = FALSE;
if (CryptDecrypt( hWkKey, 0, TRUE, 0, (PBYTE)DecryptedFEK, &cbData )) {
//
// First, perform a sanity check: make sure the key we just decrypted has a length field
// that's reasonable. If not, we got back garbage.
//
if (EFS_KEY_SIZE( DecryptedFEK) <= cbData) {
//
// Check the Entropy and the salt here
//
PEFS_KEY_SALT pEfsKeySalt = (PEFS_KEY_SALT)OFFSET_TO_POINTER( EfsKeySalt, EncryptedKey );
if ( (KeyEntropy == EXPORT_KEY_STRENGTH) && CheckBits){
if ( DecryptedFEK->Entropy <= KeyEntropy ){
//
// Check the salt
//
DWORD SaltLength;
DWORD SaltBlockLength;
if (GetSaltLength(DecryptedFEK->Algorithm, &SaltLength,&SaltBlockLength)){
if ( pEfsKeySalt ){
Verified = (memcmp( EFS_KEY_DATA(DecryptedFEK), (PBYTE)pEfsKeySalt + sizeof(EFS_KEY_SALT), SaltLength ) == 0);
} else {
//
// This should not happen
//
ASSERT(FALSE);
Verified = FALSE;
}
} else {
//
// This algorithm has no salt
//
Verified = TRUE;
}
} else {
//
// Export version cannot decrypt files encrypted with longer keys
//
Verified = FALSE;
}
} else {
Verified = TRUE;
}
//robertg Now you have a pointer to the salt structure to play with. Set Verified == TRUE if
// everything checks out.
}
} else {
//
// If we got back a bad length error, that means that the plaintext of
// FEK was larger than the cyphertext. Assume that this can't happen,
// since the CryptDecrypt interface doesn't seem to be able to handle
// this situation.
//
ASSERT(GetLastError() != NTE_BAD_LEN);
LastError = GetLastError();
}
if (!Verified) {
LsapFreeLsaHeap( DecryptedFEK );
DecryptedFEK = NULL;
LastError = ERROR_DECRYPTION_FAILED;
}
} else {
LastError = ERROR_NOT_ENOUGH_MEMORY;
}
//
// Clean up what we allocated.
//
if (hKey) {
CryptDestroyKey( hKey );
CryptReleaseContext( hProv, 0 );
}
SetLastError( LastError );
return( DecryptedFEK );
}
BOOLEAN
GenerateFEK(
IN OUT PEFS_KEY *Key
)
/*++
Routine Description:
Generates a new File Encryption Key (FEK).
Arguments:
Key - Supplies a pointer to PEFS_KEY.
Return Value:
Error if not enough space can be located.
--*/
{
PBYTE KeyData;
ULONG KeyLength;
BOOL b = FALSE;
DWORD LiveKeyEntropy;
//
// Allocate the buffer for the EFS_KEY.
// Set the algorithm and key length here.
//
switch (EfsAlgInForce) {
case CALG_3DES:
//
// DES3 has no international version
//
KeyLength = DES3_KEYSIZE;
LiveKeyEntropy = DES3_KEY_STRENGTH;
break;
case CALG_DESX:
KeyLength = DESX_KEYSIZE - 8;
LiveKeyEntropy = KeyEntropy;
break;
case CALG_AES_256:
default:
KeyLength = AES_KEYSIZE_256;
LiveKeyEntropy = AES_KEY_STRENGTH_256;
break;
}
*Key = (PEFS_KEY)LsapAllocateLsaHeap( sizeof( EFS_KEY ) + KeyLength );
if ( NULL == *Key ){
SetLastError(ERROR_OUTOFMEMORY);
return FALSE;
}
(*Key)->KeyLength = KeyLength;
(*Key)->Algorithm = EfsAlgInForce;
KeyData = (PBYTE)(((PBYTE)*Key) + sizeof( EFS_KEY ));
if (b = CryptGenRandom( hProvVerify, (*Key)->KeyLength, KeyData )) {
(*Key)->Entropy = LiveKeyEntropy;
} else {
LsapFreeLsaHeap( *Key );
*Key = NULL;
}
return( b != 0);
}
DWORD
CreatePublicKeyInformationCertificate(
IN PSID pUserSid OPTIONAL,
PBYTE pbCert,
DWORD cbCert,
OUT PEFS_PUBLIC_KEY_INFO * PublicKeyInformation
)
{
DWORD PublicKeyInformationLength = 0;
DWORD UserSidLength = 0;
PWCHAR Base;
if (pUserSid != NULL) {
UserSidLength = GetLengthSid( pUserSid );
}
//
// Total size is the size of the public key info structure, the size of the
// cert hash data structure, the length of the thumbprint, and the lengths of the
// container name and provider name if they were passed.
//
PublicKeyInformationLength = sizeof( EFS_PUBLIC_KEY_INFO ) + UserSidLength + cbCert;
//
// Allocate and fill in the PublicKeyInformation structure
//
*PublicKeyInformation = (PEFS_PUBLIC_KEY_INFO)LsapAllocateLsaHeap( PublicKeyInformationLength );
if (*PublicKeyInformation == NULL) {
return( ERROR_NOT_ENOUGH_MEMORY );
}
(*PublicKeyInformation)->Length = PublicKeyInformationLength;
(*PublicKeyInformation)->KeySourceTag = (ULONG)EfsCertificate;
//
// Copy the string and SID data to the end of the structure.
//
Base = (PWCHAR)(*PublicKeyInformation);
Base = (PWCHAR)((PBYTE)Base + sizeof( EFS_PUBLIC_KEY_INFO ));
if (pUserSid != NULL) {
(*PublicKeyInformation)->PossibleKeyOwner = (ULONG)POINTER_TO_OFFSET( Base, *PublicKeyInformation );
CopySid( UserSidLength, (PSID)Base, pUserSid );
} else {
(*PublicKeyInformation)->PossibleKeyOwner = 0;
}
Base = (PWCHAR)((PBYTE)Base + UserSidLength);
(*PublicKeyInformation)->CertificateInfo.CertificateLength = cbCert;
(*PublicKeyInformation)->CertificateInfo.Certificate = (ULONG)POINTER_TO_OFFSET( Base, *PublicKeyInformation );
memcpy( (PBYTE)Base, pbCert, cbCert );
return( ERROR_SUCCESS );
}
BOOLEAN
CreatePublicKeyInformationThumbprint(
IN PSID pUserSid OPTIONAL,
IN PBYTE pbCertHash,
IN DWORD cbCertHash,
IN LPWSTR lpDisplayInformation OPTIONAL,
IN LPWSTR ContainerName OPTIONAL,
IN LPWSTR ProviderName OPTIONAL,
OUT PEFS_PUBLIC_KEY_INFO * PublicKeyInformation
)
/*++
Routine Description:
This routine creates an EFS_PUBLIC_KEY_INFO structure that can be
placed in an EFS stream.
Arguments:
ContainerName - The name of the container containing the public key. This
parameter is not optional if ProviderName is passed.
ProviderName - The name of the provider containing the public key. This
parameter is not optional if ContainerName is passed.
pbPublicKeyBlob - The actual public key blob exported from CryptAPI
KeySource - Data for the KeySource field in the public key structure
cbPublicKeyBlob - The length of the public key blob in bytes
PublicKeyInformation - Returns the filled in EFS_PUBLIC_KEY_INFO
structure.
Return Value:
ERROR_SUCCESS or ERROR_NOT_ENOUGH_MEMORY as appropriate
--*/
{
DWORD PublicKeyInformationLength = 0;
DWORD cbThumbprint = 0;
DWORD UserSidLength = 0;
PWCHAR Base;
if (pUserSid != NULL) {
UserSidLength = GetLengthSid( pUserSid );
}
//
// Total size is the size of the public key info structure, the size of the
// cert hash data structure, the length of the thumbprint, and the lengths of the
// container name and provider name if they were passed.
//
PublicKeyInformationLength = sizeof( EFS_PUBLIC_KEY_INFO ) + UserSidLength;
cbThumbprint = sizeof( EFS_CERT_HASH_DATA ) + cbCertHash;
if (ContainerName != NULL ) {
cbThumbprint += (wcslen( ContainerName ) + 1) * sizeof( WCHAR );
}
if (ProviderName != NULL ) {
cbThumbprint += (wcslen( ProviderName ) + 1) * sizeof( WCHAR );
}
if (lpDisplayInformation != NULL) {
cbThumbprint += (wcslen( lpDisplayInformation ) + 1) * sizeof( WCHAR );
}
PublicKeyInformationLength += cbThumbprint;
//
// Allocate and fill in the PublicKeyInformation structure
//
*PublicKeyInformation = (PEFS_PUBLIC_KEY_INFO)LsapAllocateLsaHeap( PublicKeyInformationLength );
if (*PublicKeyInformation == NULL) {
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
return( FALSE );
}
(*PublicKeyInformation)->Length = PublicKeyInformationLength;
//
// Mark the information as from CryptoAPI, since that's all we support right now.
//
(*PublicKeyInformation)->KeySourceTag = (ULONG)EfsCertificateThumbprint;
//
// Copy the string and SID data to the end of the structure.
//
Base = (PWCHAR)(*PublicKeyInformation);
Base = (PWCHAR)((PBYTE)Base + sizeof( EFS_PUBLIC_KEY_INFO ));
if (pUserSid != NULL) {
(*PublicKeyInformation)->PossibleKeyOwner = (ULONG)POINTER_TO_OFFSET( Base, *PublicKeyInformation );
CopySid( UserSidLength, (PSID)Base, pUserSid );
} else {
(*PublicKeyInformation)->PossibleKeyOwner = (ULONG)0;
}
Base = (PWCHAR)((PBYTE)Base + UserSidLength);
PEFS_CERT_HASH_DATA pCertHashData;
(*PublicKeyInformation)->CertificateThumbprint.ThumbprintLength = cbThumbprint;
(*PublicKeyInformation)->CertificateThumbprint.CertHashData = (ULONG)POINTER_TO_OFFSET( Base, *PublicKeyInformation );
pCertHashData = (PEFS_CERT_HASH_DATA)Base;
//
// Zero the header, eliminate the garbage if any of Container, Provide or Display
// Information is NULL.
//
RtlZeroMemory(pCertHashData, sizeof( EFS_CERT_HASH_DATA ));
Base = (PWCHAR)((PBYTE)Base + sizeof( EFS_CERT_HASH_DATA ));
//
// Copy the hash data to the end of the cert hash data block,
// and set the offset from the *beginning of the cert hash data block
// (not the beginning of the public key info structure)
//
pCertHashData->cbHash = cbCertHash;
pCertHashData->pbHash = (ULONG)POINTER_TO_OFFSET( Base, pCertHashData );
memcpy( (PBYTE)Base, pbCertHash, cbCertHash );
Base = (PWCHAR)((PBYTE)Base + cbCertHash);
//
// If we have Container/Provider hint info, copy them in now
//
if (ContainerName != NULL) {
pCertHashData->ContainerName = (ULONG)POINTER_TO_OFFSET( Base, pCertHashData );
wcscpy( (PWCHAR)Base, ContainerName );
//
// wcscpy copies trailing NULL characters, but wcslen doesn't include them in returned lengths,
// so add 1 to adjust.
//
Base += (wcslen( ContainerName ) + 1);
}
if (ProviderName != NULL) {
//
// Store the offset into the session key structure
//
pCertHashData->ProviderName = (ULONG)POINTER_TO_OFFSET( Base, pCertHashData );
wcscpy( (PWCHAR)Base, ProviderName );
Base += (wcslen( ProviderName ) + 1);
}
if (lpDisplayInformation != NULL) {
pCertHashData->lpDisplayInformation = (ULONG)POINTER_TO_OFFSET( Base, pCertHashData );
wcscpy( (PWCHAR)Base, lpDisplayInformation );
Base += (wcslen( lpDisplayInformation ) + 1);
}
return( TRUE );
}
DWORD
ConstructEncryptedKey(
PBYTE EncryptedFEK,
DWORD dwEncryptedFEKLength,
PEFS_PUBLIC_KEY_INFO PublicKeyInformation,
PEFS_KEY_SALT pEfsKeySalt OPTIONAL,
OUT PENCRYPTED_KEY *EncryptedKey,
OUT PDWORD EncryptedKeySize
)
/*++
Routine Description:
This routine constructs an ENCRYPTED_KEY structure from the passed
arguments.
Arguments:
EncryptedFEK - The encrypted FEK.
dwEncryptedFEKLength - The length of the encrypted FEK in bytes.
PublicKeyInformation - The public key information stucture containing
the public key.
pEfsKeySalt - Salt block.
EncryptedKey - Returns the encrypted key structure.
EncryptedKeySize - Supplies the length of the encrypted
key structure. Returns the actual length used or required.
Return Value:
ERROR_NOT_ENOUGH_MEMORY - Out of memory.
ERROR_SUCCESS - Success
--*/
{
//
// We now have all the information we need to construct the EncryptedKeys structure
// Compute the total size required
//
DWORD KeySize = sizeof( ENCRYPTED_KEY ) +
dwEncryptedFEKLength +
PublicKeyInformation->Length;
if (pEfsKeySalt){
KeySize += pEfsKeySalt->Length;
}
*EncryptedKey = (PENCRYPTED_KEY) LsapAllocateLsaHeap( KeySize );
if ( NULL == *EncryptedKey ) {
*EncryptedKeySize = 0;
return ERROR_NOT_ENOUGH_MEMORY;
}
*EncryptedKeySize = KeySize;
PBYTE Base;
(*EncryptedKey)->Length = *EncryptedKeySize;
Base = (PBYTE)(((PBYTE)*EncryptedKey) + sizeof( ENCRYPTED_KEY ));
//
// Copy in the public key info structure
//
memcpy( Base, PublicKeyInformation, PublicKeyInformation->Length );
//
// Save offset to what we just copied.
//
(*EncryptedKey)->PublicKeyInfo = (ULONG)POINTER_TO_OFFSET(Base, *EncryptedKey);
Base += PublicKeyInformation->Length;
//
// Copy the FEK, which is a completely encrypted structure
//
memcpy( Base, EncryptedFEK, dwEncryptedFEKLength );
(*EncryptedKey)->EncryptedFEK = (ULONG)POINTER_TO_OFFSET(Base, *EncryptedKey);
(*EncryptedKey)->EncryptedFEKLength = dwEncryptedFEKLength;
Base += dwEncryptedFEKLength;
//
// Copy the Salt Information
//
if (pEfsKeySalt){
memcpy( Base, pEfsKeySalt, pEfsKeySalt->Length );
(*EncryptedKey)->EfsKeySalt = (ULONG)POINTER_TO_OFFSET(Base, *EncryptedKey);
} else {
(*EncryptedKey)->EfsKeySalt = 0;
}
return( ERROR_SUCCESS );
}
PBYTE
EncryptFEK(
IN PEFS_KEY Fek,
IN HCRYPTKEY hRSAKey,
OUT PDWORD dwEncryptedFEKLength
)
{
DWORD rc=ERROR_SUCCESS;
*dwEncryptedFEKLength = EFS_KEY_SIZE( Fek );
//
// If CryptoAPI worked properly, we wouldn't need this, but it doesn't,
// so we do.
//
if (CryptEncrypt( hRSAKey, 0, TRUE, 0, NULL, dwEncryptedFEKLength, 0 )) {
DWORD BufferLength = (*dwEncryptedFEKLength < EFS_KEY_SIZE(Fek)) ? EFS_KEY_SIZE(Fek) : *dwEncryptedFEKLength;
PBYTE EncryptedFEK = (PBYTE)LsapAllocateLsaHeap( BufferLength );
if (EncryptedFEK != NULL) {
//
// Copy the FEK into our new buffer and encrypt it there.
//
memcpy( EncryptedFEK, Fek, EFS_KEY_SIZE( Fek ) );
//
// Reset the length of the data to be encrypted
//
*dwEncryptedFEKLength = EFS_KEY_SIZE( Fek );
if (CryptEncrypt( hRSAKey, 0, TRUE, 0, EncryptedFEK, dwEncryptedFEKLength, BufferLength )) {
return( EncryptedFEK );
} else {
rc = GetLastError();
DebugLog((DEB_ERROR, "EncryptFEK: 2nd CryptEncrypt failed, error = %x\n" , rc ));
}
//
// If we're here, we failed, clean up
//
LsapFreeLsaHeap( EncryptedFEK );
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
} else {
rc = GetLastError();
DebugLog((DEB_ERROR, "EncryptFEK: 1st CryptEncrypt failed, error = %x\n" , rc ));
}
if (rc != ERROR_SUCCESS) {
SetLastError(rc);
}
return( NULL );
}
BOOL
RemoveUsersFromEfsStream(
IN PEFS_DATA_STREAM_HEADER pEfsStream,
IN DWORD nHashes,
IN PENCRYPTION_CERTIFICATE_HASH * pHashes,
IN PEFS_KEY Fek,
OUT PEFS_DATA_STREAM_HEADER * pNewEfsStream
)
/*++
Routine Description:
This routine removes the passed users from the passed EFS
stream, and returns a new one to be applied to the file.
Arguments:
argument-name - Supplies | Returns description of argument.
.
.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
//
// First, see how many matches there are so we can compute
// the final size of the structure we're going to have to
// allocate.
//
DWORD cbSizeToDelete = 0;
DWORD nKeysToDelete = 0;
DWORD rc = ERROR_SUCCESS;
BOOL b = FALSE;
PENCRYPTED_KEYS pDDF = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, pEfsStream );
DWORD KeyCount = pDDF->KeyCount;
PDWORD KeyIndiciesToDelete = (PDWORD)LsapAllocateLsaHeap( KeyCount * sizeof( DWORD ));
if (KeyIndiciesToDelete) {
memset( KeyIndiciesToDelete, 0, KeyCount * sizeof( DWORD ));
//
// First pass: walk the list of keys in the DDF and compare each one to the
// keys in the list of hashes passed. Count the matches and keep track of the
// total size of the resulting structure.
//
PENCRYPTED_KEY pEncryptedKey = &pDDF->EncryptedKey[0];
for (DWORD i=0; i<KeyCount ; i++, pEncryptedKey = NEXT_ENCRYPTED_KEY( pEncryptedKey )) {
PENCRYPTED_KEY pAlignedKey;
BOOLEAN freeAlignedKey;
rc = EfsAlignBlock(
pEncryptedKey,
(PVOID *)&pAlignedKey,
&freeAlignedKey
);
if (!pAlignedKey) {
//
// OOM. Treat it as not current.
//
rc = ERROR_NOT_ENOUGH_MEMORY;
nKeysToDelete = 0;
break;
}
PEFS_PUBLIC_KEY_INFO pPublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)OFFSET_TO_POINTER( PublicKeyInfo, pAlignedKey );
ASSERT( pPublicKeyInfo->KeySourceTag == EfsCertificateThumbprint );
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, pPublicKeyInfo );
DWORD cbHash = CertHashData->cbHash;
PBYTE pbHash = (PBYTE)OFFSET_TO_POINTER( pbHash, CertHashData );
//
// Compare the hash data with all of the data in the array
//
__try{
for (DWORD j=0; j<nHashes ; j++) {
PENCRYPTION_CERTIFICATE_HASH pHash = pHashes[j];
PEFS_HASH_BLOB pHashBlob = pHash->pHash;
if (pHashBlob->cbData == cbHash ) {
if (memcmp( pHashBlob->pbData, pbHash, cbHash ) == 0) {
//
// We have a match. That means that this entry is going to be removed from
// the DDF when it is rebuilt.
//
cbSizeToDelete += pAlignedKey->Length;
KeyIndiciesToDelete[nKeysToDelete] = i;
nKeysToDelete++;
break;
}
}
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
//
// The passed in pHashes is bad
//
nKeysToDelete = 0;
rc = ERROR_INVALID_PARAMETER;
}
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
if (ERROR_INVALID_PARAMETER == rc) {
break;
}
}
if (nKeysToDelete != 0) {
//
// We made at least one match. The size of the new efs stream is the size of
// the old one minus the size of the stuff we're deleting.
//
DWORD cbNewEfsStream = pEfsStream->Length - cbSizeToDelete;
cbNewEfsStream = (cbNewEfsStream + 7) & 0xfffffff8;
*pNewEfsStream = (PEFS_DATA_STREAM_HEADER)LsapAllocateLsaHeap( cbNewEfsStream );
if (*pNewEfsStream) {
//
// Copy the old header to the new structure.
//
**pNewEfsStream = *pEfsStream;
((PEFS_DATA_STREAM_HEADER)*pNewEfsStream)->Length = cbNewEfsStream;
//
// Copy the old DDF to the new DDF, skipping the
// ones we want to delete
//
//
// Base is our target. Make it point to the end of the header, which
// is where we're going to start copying the new DDF.
//
PBYTE Base = (PBYTE) (((PBYTE)(*pNewEfsStream)) + sizeof( EFS_DATA_STREAM_HEADER ));
//
// Set the offset of the new DDF into the header.
//
(*pNewEfsStream)->DataDecryptionField = (ULONG)POINTER_TO_OFFSET( Base, *pNewEfsStream );
//
// Start to assemble the new DDF
//
PENCRYPTED_KEYS pNewDDF = (PENCRYPTED_KEYS)Base;
pNewDDF->KeyCount = KeyCount - nKeysToDelete;
Base = (PBYTE)(&pNewDDF->EncryptedKey[0]);
PBYTE Source = (PBYTE)(&pDDF->EncryptedKey[0]);
DWORD NextKeyIndexToDelete = 0;
for (DWORD i=0; i<KeyCount ; i++) {
if (KeyIndiciesToDelete[NextKeyIndexToDelete] != i) {
//
// We're not going to delete this one, copy it.
//
DWORD KeyLength = * (DWORD UNALIGNED *) &(((PENCRYPTED_KEY)Source)->Length);
memcpy( Base, Source, KeyLength );
Base = (PBYTE)NEXT_ENCRYPTED_KEY( Base );
Source = (PBYTE)NEXT_ENCRYPTED_KEY( Source );
} else {
//
// We're going to delete this one. Leave Base
// alone, but bump Source to the next key.
//
Source = (PBYTE)NEXT_ENCRYPTED_KEY( Source );
NextKeyIndexToDelete++;
}
}
//
// The new DDF is in place. Copy the recovery information
// from the old EFS stream into the new one.
//
// Base points to where the DRF needs to go, and Source
// points to where the old one is (in theory).
//
ASSERT( Source == (PBYTE)OFFSET_TO_POINTER( DataRecoveryField, pEfsStream ));
PENCRYPTED_KEYS pDRF = (PENCRYPTED_KEYS)Source;
//
// Set the offset of the new DRF into the new EFS stream.
//
if ((PVOID)pDRF == (PVOID)pEfsStream) {
//
// No DRF in the old $EFS
//
(*pNewEfsStream)->DataRecoveryField = 0;
} else {
(*pNewEfsStream)->DataRecoveryField = (ULONG)POINTER_TO_OFFSET( Base, *pNewEfsStream );
//
// We can copy the old DRF directly into the new one, since nothing in
// it is changing.
//
//
// Base now points to the top of an ENCRYPTED_KEYS structure.
// Fill in its header.
//
PENCRYPTED_KEYS pNewDRF = (PENCRYPTED_KEYS)Base;
RtlCopyMemory(&(pNewDRF->KeyCount), &(pDRF->KeyCount), sizeof(ULONG));
RtlCopyMemory(&KeyCount, &(pDRF->KeyCount), sizeof(ULONG));
//
// That was the header. Now start copying the
// encrypted keys themselves.
//
Base = (PBYTE)(&pNewDRF->EncryptedKey[0]);
Source = (PBYTE)(&pDRF->EncryptedKey[0]);
for (i=0; i<KeyCount ; i++) {
DWORD KeyLength = * (DWORD UNALIGNED *) &(((PENCRYPTED_KEY)Source)->Length);
memcpy( Base, Source, KeyLength );
Base = (PBYTE)NEXT_ENCRYPTED_KEY( Base );
Source = (PBYTE)NEXT_ENCRYPTED_KEY( Source );
}
}
b = TRUE;
// memset( &((*pNewEfsStream)->EfsHash), 0, MD5_HASH_SIZE );
// if (EfspChecksumEfs( *pNewEfsStream, Fek )) {
// b = TRUE;
// }
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
} else {
if (ERROR_SUCCESS == rc) {
b = TRUE;
}
}
LsapFreeLsaHeap( KeyIndiciesToDelete );
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
if (!b) {
//
// Something failed, clean up what we were going
// to return.
//
if (*pNewEfsStream) {
LsapFreeLsaHeap( *pNewEfsStream );
*pNewEfsStream = NULL; // paranoia
}
}
SetLastError(rc);
return( b );
}
BOOL
QueryCertsFromEncryptedKeys(
IN PENCRYPTED_KEYS pEncryptedKeys,
OUT PDWORD pnUsers,
OUT PENCRYPTION_CERTIFICATE_HASH ** pHashes
)
/*++
Routine Description:
This routine takes a set of encrypted keys and returns the data
that we wish to display about each one.
Arguments:
pEncryptedKeys - Supplies the array of encrypted keys.
pnUsers - Returns the number of users on the file.
pHashes - Returns the hash information about each user.
Return Value:
TRUE on success, FALSE on failure. Call GetLastError() for more details.
--*/
{
DWORD rc = ERROR_SUCCESS;
PENCRYPTION_CERTIFICATE_HASH pTmp = NULL;
//
// Walk the entries in the encrypted keys and return the information we want about each one.
//
DWORD KeyCount = * (DWORD UNALIGNED *) &(pEncryptedKeys->KeyCount);
*pnUsers = KeyCount;
PENCRYPTED_KEY pEncryptedKey = &pEncryptedKeys->EncryptedKey[0];
//
// *pHashes points to an array of pointers to ENCRYPTION_CERTIFICATE_HASH structures.
// There will be one entry for each Key on the file
//
*pHashes = (PENCRYPTION_CERTIFICATE_HASH *)MIDL_user_allocate( sizeof(PENCRYPTION_CERTIFICATE_HASH) * KeyCount );
if (*pHashes) {
memset( *pHashes, 0, sizeof(PENCRYPTION_CERTIFICATE_HASH) * KeyCount );
for (DWORD i=0;
i < KeyCount;
i++, pEncryptedKey = NEXT_ENCRYPTED_KEY( pEncryptedKey )
) {
PENCRYPTED_KEY pAlignedKey;
BOOLEAN freeAlignedKey;
rc = EfsAlignBlock(
pEncryptedKey,
(PVOID *)&pAlignedKey,
&freeAlignedKey
);
if (!pAlignedKey) {
//
// OOM. Treat it as not current.
//
rc = ERROR_NOT_ENOUGH_MEMORY;
break;
}
PEFS_PUBLIC_KEY_INFO pPublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)OFFSET_TO_POINTER( PublicKeyInfo, pAlignedKey );
ASSERT( pPublicKeyInfo->KeySourceTag == EfsCertificateThumbprint );
PENCRYPTION_CERTIFICATE_HASH pTmp = (PENCRYPTION_CERTIFICATE_HASH) MIDL_user_allocate( sizeof(ENCRYPTION_CERTIFICATE_HASH ));
if (pTmp) {
memset( pTmp, 0, sizeof( ENCRYPTION_CERTIFICATE_HASH ));
pTmp->cbTotalLength = sizeof( ENCRYPTION_CERTIFICATE_HASH );
if (pPublicKeyInfo->PossibleKeyOwner) {
PSID pUserSid = ( PSID )OFFSET_TO_POINTER( PossibleKeyOwner, pPublicKeyInfo );
pTmp->pUserSid = (SID *)MIDL_user_allocate( GetLengthSid( pUserSid ));
if (pTmp->pUserSid) {
CopySid( GetLengthSid( pUserSid ),
pTmp->pUserSid,
pUserSid
);
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
} else {
pTmp->pUserSid = NULL;
}
//
// Copy the hash
//
if (rc == ERROR_SUCCESS) {
pTmp->pHash = (PEFS_HASH_BLOB)MIDL_user_allocate( sizeof( EFS_HASH_BLOB ));
if (pTmp->pHash) {
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, pPublicKeyInfo );
pTmp->pHash->cbData = CertHashData->cbHash;
pTmp->pHash->pbData = (PBYTE)MIDL_user_allocate( pTmp->pHash->cbData );
if (pTmp->pHash->pbData) {
memcpy( pTmp->pHash->pbData, OFFSET_TO_POINTER( pbHash, CertHashData ), pTmp->pHash->cbData );
ASSERT( rc == ERROR_SUCCESS );
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
if (rc == ERROR_SUCCESS) {
//
// Allocate and fill in the display information field.
//
if (CertHashData->lpDisplayInformation) {
LPWSTR lpDisplayInformation = (LPWSTR)OFFSET_TO_POINTER( lpDisplayInformation, CertHashData );
pTmp->lpDisplayInformation = (LPWSTR)MIDL_user_allocate( (wcslen( lpDisplayInformation ) + 1) * sizeof( WCHAR ));
if (pTmp->lpDisplayInformation) {
wcscpy( pTmp->lpDisplayInformation, lpDisplayInformation );
(pTmp->lpDisplayInformation)[wcslen(lpDisplayInformation)] = UNICODE_NULL;
ASSERT( rc == ERROR_SUCCESS );
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
}
}
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
}
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
if (rc != ERROR_SUCCESS) {
//
// We couldn't successfully build this structure. Free up
// all the stuff we were going to return and drop out of the loop.
//
if (pTmp) {
if (pTmp->pHash) {
if (pTmp->pHash->pbData) {
MIDL_user_free( pTmp->pHash->pbData );
}
MIDL_user_free( pTmp->pHash );
}
if (pTmp->lpDisplayInformation) {
MIDL_user_free( pTmp->lpDisplayInformation );
}
if (pTmp->pUserSid) {
MIDL_user_free( pTmp->pUserSid );
}
MIDL_user_free( pTmp );
}
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
break;
} else {
(*pHashes)[i] = pTmp;
if (freeAlignedKey) {
LsapFreeLsaHeap( pAlignedKey );
}
}
}
if (rc != ERROR_SUCCESS) {
//
// Something failed along the way, walk down the list of ones
// we successfully allocated and free them up. Any partially
// allocated ones will have been cleaned up above.
//
DWORD i=0;
while ( (*pHashes)[i] ) {
pTmp = (*pHashes)[i];
ASSERT( pTmp->pHash );
ASSERT( pTmp->pHash->pbData );
MIDL_user_free( pTmp->pHash->pbData );
MIDL_user_free( pTmp->pHash );
if (pTmp->lpDisplayInformation) {
MIDL_user_free( pTmp->lpDisplayInformation );
}
if (pTmp->pUserSid) {
MIDL_user_free( pTmp->pUserSid );
}
MIDL_user_free( pTmp );
(*pHashes)[i] = NULL;
i++;
}
MIDL_user_free( *pHashes );
}
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
SetLastError( rc );
return( ERROR_SUCCESS == rc ? TRUE : FALSE );
}
BOOL
EfsErrorToNtStatus(
IN DWORD WinError,
OUT PNTSTATUS NtStatus
)
{
switch (WinError) {
case ERROR_ENCRYPTION_FAILED:
{
*NtStatus = STATUS_ENCRYPTION_FAILED;
break;
}
case NTE_BAD_KEYSET:
case CRYPT_E_NOT_FOUND:
case ERROR_DECRYPTION_FAILED:
{
*NtStatus = STATUS_DECRYPTION_FAILED;
break;
}
case ERROR_FILE_ENCRYPTED:
{
*NtStatus = STATUS_FILE_ENCRYPTED;
break;
}
case ERROR_NO_RECOVERY_POLICY:
{
*NtStatus = STATUS_NO_RECOVERY_POLICY;
break;
}
case ERROR_NO_EFS:
{
*NtStatus = STATUS_NO_EFS;
break;
}
case ERROR_WRONG_EFS:
{
*NtStatus = STATUS_WRONG_EFS;
break;
}
case ERROR_NO_USER_KEYS:
{
*NtStatus = STATUS_NO_USER_KEYS;
break;
}
case ERROR_FILE_NOT_ENCRYPTED:
{
*NtStatus = STATUS_FILE_NOT_ENCRYPTED;
break;
}
case ERROR_NOT_EXPORT_FORMAT:
{
*NtStatus = STATUS_NOT_EXPORT_FORMAT;
break;
}
case ERROR_OUTOFMEMORY:
{
*NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
case ERROR_ACCESS_DENIED:
{
*NtStatus = STATUS_ACCESS_DENIED;
break;
}
default:
{
DebugLog((DEB_WARN, "EfsErrorToNtStatus, unable to translate 0x%x\n" , WinError ));
return( FALSE );
break;
}
}
return( TRUE );
}
VOID
DumpBytes(
PBYTE Blob,
ULONG Length,
ULONG IndentLevel
)
{
const UINT Columns = 8;
UINT Rows = Length / Columns;
if (Length % Columns != 0) {
Rows++;
}
for (UINT j=0; j<Rows ; j++) {
for (UINT k=0; k<IndentLevel ; k++) {
DbgPrint("\t");
}
for (UINT i=0; i<Columns ; i++) {
DbgPrint("%02X ",Blob[ j*Columns + i ]);
if ((j*Columns + i) == Length) {
break;
}
}
DbgPrint("\n");
}
}
VOID
DumpPublicKeyInfo(
PEFS_PUBLIC_KEY_INFO PublicKeyInfo
)
{
DbgPrint("\t\tPublicKeyInfo:\n");
DbgPrint("\t\tLength = 0x%x\n",PublicKeyInfo->Length);
if (PublicKeyInfo->PossibleKeyOwner != NULL) {
PWCHAR SidString = ConvertSidToWideCharString( OFFSET_TO_POINTER( PossibleKeyOwner, PublicKeyInfo) );
DbgPrint("\t\tUserSid = %ws\n",SidString);
LsapFreeLsaHeap( SidString );
} else {
DbgPrint("\t\tUserSid = NULL\n");
}
switch (PublicKeyInfo->KeySourceTag) {
case EfsCryptoAPIContainer:
{
DbgPrint("\t\tTag = EfsCryptoAPIContainer\n");
DbgPrint("\t\tContainerName = %ws\n",OFFSET_TO_POINTER( ContainerInfo.ContainerName, PublicKeyInfo ));
DbgPrint("\t\tProviderName = %ws\n",OFFSET_TO_POINTER( ContainerInfo.ProviderName, PublicKeyInfo ));
DbgPrint("\t\tPublicKeyBlobLength = 0x%x\n",PublicKeyInfo->ContainerInfo.PublicKeyBlobLength);
DumpBytes( (PBYTE)OFFSET_TO_POINTER( ContainerInfo.PublicKeyBlob, PublicKeyInfo ), PublicKeyInfo->ContainerInfo.PublicKeyBlobLength, 2 );
break;
}
case EfsCertificateThumbprint:
{
DbgPrint("\t\tTag = EfsCertificateThumbprint\n");
PEFS_CERT_HASH_DATA CertHashData = (PEFS_CERT_HASH_DATA)OFFSET_TO_POINTER( CertificateThumbprint.CertHashData, PublicKeyInfo );
LPWSTR ContainerName = NULL;
if (CertHashData->ContainerName) {
LPWSTR ContainerName = (LPWSTR)OFFSET_TO_POINTER( ContainerName ,CertHashData);
DbgPrint("\t\tContainerName = %ws\n",ContainerName);
} else {
DbgPrint("\t\tContainerName = NULL\n");
}
LPWSTR ProviderName = NULL;
if (CertHashData->ProviderName) {
LPWSTR ProviderName = (LPWSTR)OFFSET_TO_POINTER( ProviderName ,CertHashData);
DbgPrint("\t\tProviderName = %ws\n",ProviderName);
} else {
DbgPrint("\t\tProviderName = NULL\n");
}
LPWSTR lpDisplayInformation = NULL;
if (CertHashData->lpDisplayInformation) {
LPWSTR lpDisplayInformation = (LPWSTR)OFFSET_TO_POINTER( lpDisplayInformation ,CertHashData);
DbgPrint("\t\tlpDisplayInformation = %ws\n",lpDisplayInformation);
} else {
DbgPrint("\t\tlpDisplayInformation = NULL\n");
}
DbgPrint("\t\tcbHash = 0x%x\n",CertHashData->cbHash );
DbgPrint("\t\tpbHash = \n");
PBYTE pbHash = (PBYTE)OFFSET_TO_POINTER( pbHash, CertHashData );
DumpBytes( pbHash, CertHashData->cbHash, 2);
break;
}
case EfsCertificate:
{
DbgPrint("KeySourceTag of EfsCertificate unexpected\n");
break;
}
default:
{
DbgPrint("Unknown KeySourceTag value: %d\n",PublicKeyInfo->KeySourceTag );
break;
}
}
}
VOID
DumpEncryptedKey(
PENCRYPTED_KEY EncryptedKey
)
{
DbgPrint("\tLength = 0x%x\n",EncryptedKey->Length);
PEFS_PUBLIC_KEY_INFO PublicKeyInfo = (PEFS_PUBLIC_KEY_INFO)OFFSET_TO_POINTER( PublicKeyInfo, EncryptedKey );
DumpPublicKeyInfo( PublicKeyInfo );
DbgPrint("\tEncryptedFEKLength = 0x%x\n",EncryptedKey->EncryptedFEKLength);
DbgPrint("\tEncryptedFEK = \n");
DumpBytes( (PBYTE)OFFSET_TO_POINTER( EncryptedFEK, EncryptedKey ), EncryptedKey->EncryptedFEKLength, 1 );
}
void
DumpRecoveryKey(
PRECOVERY_KEY_1_1 pRecoveryKey
)
{
DbgPrint("\nRecovery key @ 0x%x\n",pRecoveryKey);
DbgPrint("Length = 0x%x\n",pRecoveryKey->TotalLength);
DbgPrint("PublicKeyInfo:\n");
DumpPublicKeyInfo( &pRecoveryKey->PublicKeyInfo );
}
VOID
DumpEFS(
PEFS_DATA_STREAM_HEADER Efs
)
{
DbgPrint("\nEFS @ 0x%x:\n",Efs);
DbgPrint("Length = \t\t0x%x\n",Efs->Length);
DbgPrint("State = \t\t%d\n",Efs->State);
DbgPrint("EfsVersion = \t\t%d\n",Efs->EfsVersion);
DbgPrint("CryptoApiVersion = \t%d\n",Efs->CryptoApiVersion);
DbgPrint("Offset to DDF = \t0x%x\n",Efs->DataDecryptionField);
DbgPrint("Offset to DRF = \t0x%x\n",Efs->DataRecoveryField);
DbgPrint("Reserved = \t0x%x\n",Efs->Reserved);
DbgPrint("\nDDF:\n");
PENCRYPTED_KEYS Ddf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataDecryptionField, Efs );
DbgPrint("Number of keys = %d\n",Ddf->KeyCount );
UINT i;
PENCRYPTED_KEY EncryptedKey = &Ddf->EncryptedKey[0];
for (i=0; i<Ddf->KeyCount; i++) {
DbgPrint("\tKey %d:\n",i);
DumpEncryptedKey( EncryptedKey );
EncryptedKey = (PENCRYPTED_KEY)(((PBYTE)EncryptedKey) + EncryptedKey->Length);
}
DbgPrint("\nDRF:\n");
PENCRYPTED_KEYS Drf = (PENCRYPTED_KEYS)OFFSET_TO_POINTER( DataRecoveryField, Efs );
DbgPrint("Number of keys = %d\n",Drf->KeyCount );
EncryptedKey = &Drf->EncryptedKey[0];
for (i=0; i<Drf->KeyCount; i++) {
DbgPrint("\tKey %d:\n",i);
DumpEncryptedKey( EncryptedKey );
EncryptedKey = (PENCRYPTED_KEY)(((PBYTE)EncryptedKey) + EncryptedKey->Length);
}
}
#if 0
BOOLEAN
EfspChecksumEfs(
PEFS_DATA_STREAM_HEADER pEFS,
PEFS_KEY Fek
)
/*++
Routine Description:
This routine will checksum the passed EFS stream and fill in the checksum
field in the header. Assumes that the checksum field itself is set to 0.
Arguments:
pEFS - Supplies the EFS stream to be checksum'd. Assume that this structure
has been fully filled in.
Fek - Supplies a pointer to the FEK for the file.
Return Value:
TRUE on success, FALSE on failure. Sets LastErrror.
--*/
{
HCRYPTHASH hHash = 0;
DWORD dwHashedDataLength = MD5_HASH_SIZE;
BOOL b = FALSE;
DWORD rc = ERROR_SUCCESS;
if (CryptCreateHash( hProvVerify, CALG_MD5, 0, 0, &hHash )) {
if (CryptHashData( hHash, (PBYTE)pEFS, pEFS->Length, 0 )) {
if (CryptHashData( hHash, EFS_KEY_DATA( Fek ), Fek->KeyLength, 0 )) {
if (CryptGetHashParam( hHash, HP_HASHVAL, (PBYTE)(&pEFS->EfsHash), &dwHashedDataLength, 0 )) {
ASSERT( dwHashedDataLength == MD5_HASH_SIZE );
b = TRUE;
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
CryptDestroyHash( hHash );
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
SetLastError( rc );
return( b != 0);
}
BOOLEAN
EfspValidateEfsStream(
PEFS_DATA_STREAM_HEADER pEFS,
PEFS_KEY Fek
)
/*++
Routine Description:
This routine checks the checksum in an EFS stream.
Arguments:
pEFS - Supplies the EFS stream to be validated.
Fek - Supplies the FEK of the encrypted file.
Return Value:
TRUE on success, FALSE on failure.
--*/
{
DWORD dwHashSize = MD5_HASH_SIZE ;
UCHAR SavedChecksum[ MD5_HASH_SIZE ];
UCHAR NewCheckSum [ MD5_HASH_SIZE ];
BOOL b = FALSE;
HCRYPTHASH hHash = 0;
HCRYPTPROV hProv;
DWORD rc = ERROR_SUCCESS;
//
// We have to do the checksum with the checksum in the header
// zero'd out. Save the original in a local.
//
memcpy( SavedChecksum, &pEFS->EfsHash, MD5_HASH_SIZE );
memset( &pEFS->EfsHash, 0, MD5_HASH_SIZE );
if (CryptCreateHash( hProvVerify, CALG_MD5, 0, 0, &hHash )) {
if (CryptHashData( hHash, (PBYTE)pEFS, pEFS->Length, 0 )) {
if (CryptHashData( hHash, EFS_KEY_DATA( Fek ), Fek->KeyLength, 0 )) {
if (CryptGetHashParam( hHash, HP_HASHVAL, NewCheckSum, &dwHashSize, 0 )) {
ASSERT( dwHashSize == MD5_HASH_SIZE );
if (memcmp( NewCheckSum, SavedChecksum, MD5_HASH_SIZE ) == 0) {
b = TRUE;
} else {
rc = ERROR_ACCESS_DENIED;
}
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
CryptDestroyHash( hHash );
} else {
rc = GetLastError();
ASSERT( rc != ERROR_SUCCESS );
}
//
// Copy back the original
//
memcpy( &pEFS->EfsHash, SavedChecksum, MD5_HASH_SIZE );
SetLastError( rc );
return( b != 0);
}
#endif
BOOL
GetSaltLength(
ALG_ID AlgID,
DWORD *SaltLength,
DWORD *SaltBlockLength
)
/*++
Routine Description:
This routine returns the length of key salt
Arguments:
AlgID - Encryption Algorithm ID.
SaltLength - Bytes to be copied from key.
SaltBlockLength - Bytes of key salt block in $EFS
Return Value:
TRUE on success, FALSE on failure.
--*/
{
BOOL b = FALSE;
switch (AlgID){
case CALG_DESX:
*SaltLength = EXPORT_DESX_SALT_LENGTH;
*SaltBlockLength = (EXPORT_DESX_SALT_LENGTH + 4 ) & 0xfffffffc;
b = TRUE;
break;
default:
*SaltLength = 0;
*SaltBlockLength = 0;
break;
}
return b;
}
VOID
EfspUnloadUserProfile(
IN HANDLE hToken,
IN HANDLE hProfile
)
/*++
Routine Description:
Cleans up after a call to EfspLoadUserProfile. Returns impersonating our client.
Arguments:
hToken - The token handle returned from EfspLoadUserProfile. This handle will be closed!
hProfile - The profile handle returned from EfspLoadUserProfile. This handle will not
be modified.
Return Value:
None.
--*/
{
NTSTATUS Status;
if (!hToken) {
//
// SYSTEM context. The profile was not loaded by EFS.
//
return;
}
RevertToSelf();
(VOID) UnloadUserProfile (hToken, hProfile);
//
// Start impersonating again
//
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) &hToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS( Status )) {
DebugLog((DEB_ERROR, "EfspUnloadUserProfile: NtSetInformationThread returned %x\n" ,Status ));
}
NtClose( hToken );
return;
}
VOID
EfspFreeUserCache(
IN PUSER_CACHE pUserCache
)
{
if (pUserCache == NULL){
return;
}
if (pUserCache->pbHash) {
LsapFreeLsaHeap( pUserCache->pbHash );
}
if (pUserCache->ContainerName) {
LsapFreeLsaHeap( pUserCache->ContainerName );
}
if (pUserCache->DisplayInformation) {
LsapFreeLsaHeap( pUserCache->DisplayInformation );
}
if (pUserCache->ProviderName) {
LsapFreeLsaHeap( pUserCache->ProviderName );
}
if (pUserCache->pCertContext) {
CertFreeCertificateContext( pUserCache->pCertContext );
}
if (pUserCache->hUserKey) {
CryptDestroyKey( pUserCache->hUserKey );
}
if (pUserCache->hProv) {
CryptReleaseContext( pUserCache->hProv, 0 );
}
LsapFreeLsaHeap( pUserCache );
}
VOID
EfspReleaseUserCache(
IN PUSER_CACHE pUserCache
)
/*++
Routine Description:
Decrease the ref count increased by the EfspGetUserCache
Arguments:
pUserCache - Cache node
Return Value:
--*/
{
RtlEnterCriticalSection( &GuardCacheListLock );
pUserCache->UseRefCount-- ;
RtlLeaveCriticalSection( &GuardCacheListLock );
}
PUSER_CACHE
EfspGetUserCache(
IN OUT PEFS_USER_INFO pEfsUserInfo
)
/*++
Routine Description:
This routine will try to find the user's cert info in the cache list.
If return not NULL, this call must be balanced with a EfspReleaseUserCache(PUSER_CACHE pUserCache)
Arguments:
pEfsUserInfo - User Info
Return Value:
User cache list node, if match found in the cache.
NULL if not found.
--*/
{
PLIST_ENTRY pListHead, pLink;
PUSER_CACHE pUserCache;
//
// Check to see if there is cache available
//
pEfsUserInfo->UserCacheStop = FALSE;
RtlEnterCriticalSection( &GuardCacheListLock );
if (UserCacheList.Flink == &UserCacheList) {
//
// list empty
//
RtlLeaveCriticalSection( &GuardCacheListLock );
return NULL;
}
for (pLink = UserCacheList.Flink; pLink != &UserCacheList; pLink = pLink->Flink) {
pUserCache = CONTAINING_RECORD(pLink, USER_CACHE, CacheChain);
ASSERT( pLink );
ASSERT( pLink->Flink );
if ( (pEfsUserInfo->AuthId.LowPart == pUserCache->AuthId.LowPart) &&
(pEfsUserInfo->AuthId.HighPart == pUserCache->AuthId.HighPart)) {
//
// Find the cache node. Hold it
//
if (pUserCache->StopUseCount) {
//
// Free cache waiting
// When cache for a session is stopped, both interactive and non-interactive should be stopped
//
pEfsUserInfo->UserCacheStop = TRUE;
RtlLeaveCriticalSection( &GuardCacheListLock );
return NULL;
}
pUserCache->UseRefCount++;
RtlLeaveCriticalSection( &GuardCacheListLock );
return pUserCache;
}
}
RtlLeaveCriticalSection( &GuardCacheListLock );
return NULL;
}
BOOLEAN
EfspAddUserCache(
IN PUSER_CACHE pUserCache
)
/*++
Routine Description:
This routine will try to add the user's cert info in the cache list.
If return TRUE, this call must be balanced with a EfspReleaseUserCache(PUSER_CACHE pUserCache)
Arguments:
pUserCache - User Cache node.
Return Value:
TRUE if added successfully
FALSE if the list is full.
--*/
{
PLIST_ENTRY pListHead, pLink;
PUSER_CACHE pUserTmpCache;
RtlEnterCriticalSection( &GuardCacheListLock );
if (UserCacheListCount >= UserCacheListLimit) {
//
// Let's see if we can kick someone out.
//
pLink = UserCacheList.Blink;
while ( pLink != &UserCacheList ){
pUserTmpCache = CONTAINING_RECORD(pLink, USER_CACHE, CacheChain);
ASSERT( pLink );
ASSERT( pLink->Blink );
pLink = pLink->Blink;
if ( pUserTmpCache->UseRefCount <= 0 ){
//
// No one is using it. Let's remove it.
//
RemoveEntryList(&( pUserTmpCache->CacheChain ));
UserCacheListCount--;
EfspFreeUserCache( pUserTmpCache );
break;
}
}
if (UserCacheListCount >= UserCacheListLimit) {
RtlLeaveCriticalSection( &GuardCacheListLock );
return FALSE;
}
}
InsertHeadList(&UserCacheList, &( pUserCache->CacheChain ));
UserCacheListCount++;
RtlLeaveCriticalSection( &GuardCacheListLock );
return TRUE;
}
BOOLEAN
EfspGetUserInfo(
IN OUT PEFS_USER_INFO pEfsUserInfo
)
/*++
Routine Description:
This routine obtains all the interesting information about the user
that we're going to need later.
Arguments:
pEfsUserInfo - Supplies a pointer to an EfsUserInfo structure which
will be filled in.
Return Value:
return-value - Description of conditions needed to return value. - or -
None.
--*/
{
NTSTATUS Status;
BOOLEAN fReturn = FALSE;
DWORD rc = ERROR_SUCCESS;
memset( pEfsUserInfo, 0, sizeof( EFS_USER_INFO ));
Status = EfspGetUserName(pEfsUserInfo);
if (NT_SUCCESS( Status )) {
EfspIsDomainUser( pEfsUserInfo->lpDomainName, &pEfsUserInfo->bDomainAccount );
EfspIsSystem( pEfsUserInfo, &pEfsUserInfo->bIsSystem );
if (pEfsUserInfo->bIsSystem) {
pEfsUserInfo->bDomainAccount = FALSE;
}
fReturn = TRUE;
pEfsUserInfo->pUserCache = EfspGetUserCache( pEfsUserInfo );
} else {
rc = RtlNtStatusToDosError( Status );
}
SetLastError( rc );
return( fReturn );
}
VOID
EfspFreeUserInfo(
IN PEFS_USER_INFO pEfsUserInfo
)
/*++
Routine Description:
Frees the memory allocated by EfspGetUserInfo(). Does
not free the passed structure.
Arguments:
pEfsUserInfo - Supplies a pointer to the structure to be
de-allocated.
Return Value:
None.
--*/
{
if (pEfsUserInfo->lpUserName) {
LsapFreeLsaHeap( pEfsUserInfo->lpUserName );
}
if (pEfsUserInfo->lpDomainName) {
LsapFreeLsaHeap( pEfsUserInfo->lpDomainName );
}
if (pEfsUserInfo->lpProfilePath) {
LsapFreeLsaHeap( pEfsUserInfo->lpProfilePath );
}
if (pEfsUserInfo->pTokenUser) {
LsapFreeLsaHeap( pEfsUserInfo->pTokenUser );
}
if (pEfsUserInfo->lpUserSid) {
UNICODE_STRING Dummy;
Dummy.Buffer = pEfsUserInfo->lpUserSid;
RtlFreeUnicodeString(&Dummy);
}
if (pEfsUserInfo->lpKeyPath) {
LsapFreeLsaHeap( pEfsUserInfo->lpKeyPath );
}
if (pEfsUserInfo->pUserCache) {
/*
#if DBG
DbgPrint("Cache Ref Count Before Release = %ld\n",pEfsUserInfo->pUserCache->UseRefCount);
#endif
*/
EfspReleaseUserCache(pEfsUserInfo->pUserCache);
/*
#if DBG
DbgPrint("Cache Ref Count After Release = %ld\n",pEfsUserInfo->pUserCache->UseRefCount);
#endif
*/
}
return;
}
BOOL
EfspLoadUserProfile(
IN PEFS_USER_INFO pEfsUserInfo,
OUT PHANDLE hToken,
OUT PHANDLE hProfile
)
/*++
Routine Description:
This routine attempts to determine if the user's profile is loaded,
and if it is not, loads it.
Callers are expected to call EfspUnloadUserProfile() during their cleanup.
Arguments:
pEfsUserInfo - Supplies useful information about the current user.
hToken - Returns a handle to the user's token.
hProfile - Returns a handle to the user's profile.
Return Value:
TRUE if the profile is already loaded or if this routine loads it successfully,
FALSE otherwise.
--*/
{
DWORD rc = ERROR_SUCCESS;
BOOLEAN b = FALSE;
BOOL fReturn = FALSE;
LPWSTR lpServerName = NULL;
PUSER_INFO_3 lpUserInfo = NULL;
LPWSTR lpLocalProfilePath = NULL;
BOOLEAN DomainUser = pEfsUserInfo->bDomainAccount;
BOOLEAN IsSystem = pEfsUserInfo->bIsSystem;
LPWSTR lpDomainName = pEfsUserInfo->lpDomainName;
LPWSTR lpProfilePath = pEfsUserInfo->lpProfilePath;
LPWSTR lpUserName = pEfsUserInfo->lpUserName;
LPWSTR SidString = pEfsUserInfo->lpUserSid;
NTSTATUS Status;
PDOMAIN_CONTROLLER_INFO DomainControllerInfo = NULL;
*hToken = NULL;
*hProfile = NULL;
if (IsSystem) {
return TRUE;
}
if (pEfsUserInfo->InterActiveUser == USER_INTERACTIVE) {
return TRUE;
}
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
TRUE, // OpenAsSelf
hToken
);
if (NT_SUCCESS( Status )) {
LONG lRet;
HKEY phKeyCurrentUser;
lRet = RegOpenKeyExW(
HKEY_USERS,
SidString,
0, // dwOptions
MAXIMUM_ALLOWED,
&phKeyCurrentUser
);
if (ERROR_SUCCESS == lRet) {
//
// The profile is loaded. Ref it so it doesn't disappear.
//
PROFILEINFO pi;
ZeroMemory (&pi, sizeof(pi));
pi.dwSize = sizeof(pi);
pi.lpUserName = lpUserName;
pi.dwFlags = PI_LITELOAD;
//
// Cannot be impersonating when loading the profile
//
RevertToSelf();
__try {
fReturn = LoadUserProfile (*hToken, &pi);
} __except( EXCEPTION_EXECUTE_HANDLER ) {
fReturn = FALSE;
SetLastError( GetExceptionCode() );
}
if (!fReturn) {
rc = GetLastError();
} else {
*hProfile = pi.hProfile;
}
//
// Start impersonating again
//
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) hToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS( Status )) {
//
// Well, if we can't start impersonating again,
// we're not going to be able to continue the operation.
// So unload the profile and fail this whole thing.
//
if (fReturn) {
(VOID) UnloadUserProfile (*hToken, *hProfile);
}
fReturn = FALSE;
DebugLog((DEB_ERROR, "EfspLoadUserProfile: Unloading profile, NtSetInformationThread returned %x\n" ,Status ));
rc = RtlNtStatusToDosError( Status );
}
RegCloseKey( phKeyCurrentUser );
} else {
//
// The profile is not loaded. Load it.
//
if (IsSystem) {
lpLocalProfilePath = NULL;
DebugLog((DEB_TRACE_EFS, "Attempting to open stream from System context\n" ));
} else {
if (lpProfilePath != NULL) {
//
// We got the profile path from the logon information.
//
DebugLog((DEB_TRACE_EFS, "Got profile path %ws from logon information", lpProfilePath ));
//
// Do this up here so we can have common code below.
//
// RevertToSelf();
lpLocalProfilePath = lpProfilePath;
} else {
//
// We didn't get a profile path from the logon information,
// do it the slow way.
//
DebugLog((DEB_TRACE_EFS, "Attempting to compute profile information\n" ));
BOOLEAN fGotServerName = TRUE;
if (DomainUser) {
//
// Determine the name of the DC for this domain
//
rc = DsGetDcName(
NULL,
lpDomainName,
NULL,
NULL,
NULL,
&DomainControllerInfo
);
if (ERROR_SUCCESS == rc) {
lpServerName = DomainControllerInfo->DomainControllerName;
} else {
DebugLog((DEB_ERROR, "Failed to obtain DC Name from DsGetDcName, error = %d\n" ,rc ));
fGotServerName = FALSE;
}
} else {
//
// Local user, query the local machine
//
lpServerName = NULL;
}
if (fGotServerName) {
//
// Need to do this so that NetUserGetInfo will work.
// If we don't, the server may fail trying to impersonate us,
// and then the API will fail. If we revert, then it will only
// fail if the machine has been denied access.
//
// RevertToSelf();
rc = NetUserGetInfo( lpServerName, lpUserName, 3, (LPBYTE *)&lpUserInfo );
if (ERROR_SUCCESS == rc) {
lpLocalProfilePath = lpUserInfo->usri3_profile;
} else {
DebugLog((DEB_ERROR, "NetUserGetInfo failed, error = %d\n" ,rc ));
//
// Start impersonating again
//
/* No need to do this. We are still impersonating.
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) hToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS( Status )) {
fReturn = FALSE;
DebugLog((DEB_ERROR, "EfspLoadUserProfile: NtSetInformationThread returned %x\n" ,Status ));
rc = RtlNtStatusToDosError( Status );
}
*/
}
}
}
}
//
// Make sure we revert before calling LoadUserProfile
//
RevertToSelf();
//
// We have a profile path. Note that it may be NULL.
//
DebugLog((DEB_TRACE_EFS, "Loading profile path %ws\n" , (lpLocalProfilePath == NULL ? L"NULL" : lpLocalProfilePath)));
PROFILEINFO pi;
ZeroMemory (&pi, sizeof(pi));
pi.dwSize = sizeof(pi);
pi.lpUserName = lpUserName;
pi.lpProfilePath = lpLocalProfilePath;
pi.dwFlags = PI_LITELOAD;
__try {
fReturn = LoadUserProfile (*hToken, &pi);
} __except( EXCEPTION_EXECUTE_HANDLER ) {
fReturn = FALSE;
SetLastError( GetExceptionCode() );
}
if (!fReturn) {
rc = GetLastError();
DebugLog((DEB_ERROR, "LoadUserProfile failed, error = %d\n" ,rc ));
} else {
*hProfile = pi.hProfile;
}
//
// Start impersonating again, at least until LoadUserProfile
// stops turning off impersonation.
//
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) hToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS( Status )) {
fReturn = FALSE;
DebugLog((DEB_ERROR, "EfspLoadUserProfile: NtSetInformationThread returned %x\n" ,Status ));
rc = RtlNtStatusToDosError( Status );
}
if (lpUserInfo != NULL) {
NetApiBufferFree( lpUserInfo );
}
}
if (lpServerName) {
NetApiBufferFree( DomainControllerInfo );
}
if (!fReturn) {
//
// We did not succeed for some reason.
// Clean up what we were going to return.
//
NtClose( *hToken );
}
} else {
SetLastError( RtlNtStatusToDosError( Status ));
}
SetLastError( rc );
return( fReturn );
}
DWORD
GenerateDRF(
IN PEFS_KEY Fek,
OUT PENCRYPTED_KEYS *pNewDRF,
OUT DWORD *cbDRF
)
{
DWORD rc;
DWORD DRFKeyCount;
DWORD DRFStatus;
PBYTE * DRFPublicKeys;
DWORD * DRFPublicKeyLengths;
PBYTE * DRFCertHashes;
DWORD * DRFCertHashLengths;
LPWSTR * DRFDisplayInformation;
PSID * pDRFSid;
*pNewDRF = NULL;
*cbDRF = 0;
rc = GetRecoveryData(
&DRFKeyCount,
&DRFStatus,
&DRFPublicKeys,
&DRFPublicKeyLengths,
&DRFCertHashes,
&DRFCertHashLengths,
&DRFDisplayInformation,
&pDRFSid
);
if (rc == ERROR_SUCCESS) {
if (DRFKeyCount > 0) {
rc = ConstructKeyRing(
Fek,
DRFKeyCount,
NULL, // No key name information for recovery agents
NULL,
DRFPublicKeys,
DRFPublicKeyLengths,
DRFCertHashes,
DRFCertHashLengths,
DRFDisplayInformation,
pDRFSid,
FALSE,
pNewDRF,
cbDRF
);
} else {
//
// No DRF will be returned
//
if (DRFStatus < RECOVERY_POLICY_OK) {
//
// EFS will go ahead with encryption without DRF
//
rc = ERROR_SUCCESS;
} else {
rc = ERROR_BAD_RECOVERY_POLICY;
}
}
}
return rc;
}
BOOLEAN
EqualEncryptedKeys(
IN PENCRYPTED_KEYS SrcKeys,
IN PENCRYPTED_KEYS DstKeys,
IN DWORD cbDstKeys
)
/*++
Routine Description:
This routine compares two encrypted key arrays.
Arguments:
SrcKeys - Source key arrays.
DstKeys - Destination key arrays.
cbDstKeys - Destination key array size.
Return Value:
TRUE if the profile is already loaded or if this routine loads it successfully,
FALSE otherwise.
--*/
{
DWORD cbSrcKeys = 0;
ULONG KeyCount = *(ULONG UNALIGNED *) &(SrcKeys->KeyCount);
PENCRYPTED_KEY pEncryptedKey;
ULONG keyLength;
if (KeyCount != DstKeys->KeyCount ) {
return FALSE;
}
pEncryptedKey = &(SrcKeys->EncryptedKey[0]);
while ( KeyCount > 0 ) {
keyLength = * (ULONG UNALIGNED *) &(pEncryptedKey->Length);
cbSrcKeys += keyLength;
pEncryptedKey = (PENCRYPTED_KEY)( ((PBYTE)pEncryptedKey) + keyLength );
KeyCount--;
}
cbSrcKeys += (sizeof ( ENCRYPTED_KEYS ) - sizeof( ENCRYPTED_KEY ));
if ( cbSrcKeys != cbDstKeys ) {
return FALSE;
}
return RtlEqualMemory( SrcKeys, DstKeys, cbDstKeys);
}
DWORD
EfsGetCertNameFromCertContext(
PCCERT_CONTEXT CertContext,
LPWSTR * UserDispName
)
/*++
Routine Description:
Get the user name from the certificate
Arguments:
CertContext -- Cert Context
UserCertName -- User Common Name ( Caller is responsible to delete this memory )
Return Value:
ERROR_SUCCESS if succeed.
If No Name found. "USER_UNKNOWN is returned".
--*/
{
DWORD NameLength;
DWORD UserNameBufLen = 0;
DWORD BlobLen = 0;
PCERT_EXTENSION AlterNameExt = NULL;
BOOL b;
LPWSTR DNSName = NULL;
LPWSTR UPNName = NULL;
LPWSTR CommonName = NULL;
DWORD rc = ERROR_SUCCESS;
if ( NULL == UserDispName ){
return ERROR_SUCCESS;
}
*UserDispName = NULL;
AlterNameExt = CertFindExtension(
szOID_SUBJECT_ALT_NAME2,
CertContext->pCertInfo->cExtension,
CertContext->pCertInfo->rgExtension
);
if (AlterNameExt){
//
// Find the alternative name
//
b = CryptDecodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_ALT_NAME ,
AlterNameExt->Value.pbData,
AlterNameExt->Value.cbData,
0,
NULL,
&BlobLen
);
if (b){
//
// Let's decode it
//
CERT_ALT_NAME_INFO *AltNameInfo = NULL;
AltNameInfo = (CERT_ALT_NAME_INFO *) LsapAllocateLsaHeap( BlobLen );
if (AltNameInfo){
b = CryptDecodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_ALT_NAME,
AlterNameExt->Value.pbData,
AlterNameExt->Value.cbData,
0,
AltNameInfo,
&BlobLen
);
if (b){
//
// Now search for the UPN, SPN, DNS, EFS name
//
DWORD cAltEntry = AltNameInfo->cAltEntry;
DWORD ii = 0;
while (ii < cAltEntry){
if ((AltNameInfo->rgAltEntry[ii].dwAltNameChoice == CERT_ALT_NAME_OTHER_NAME ) &&
!strcmp(szOID_NT_PRINCIPAL_NAME, AltNameInfo->rgAltEntry[ii].pOtherName->pszObjId)
){
//
// We found the UPN name
//
CERT_NAME_VALUE* CertUPNName = NULL;
b = CryptDecodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
X509_UNICODE_ANY_STRING,
AltNameInfo->rgAltEntry[ii].pOtherName->Value.pbData,
AltNameInfo->rgAltEntry[ii].pOtherName->Value.cbData,
0,
NULL,
&BlobLen
);
if (b){
CertUPNName = (CERT_NAME_VALUE *) LsapAllocateLsaHeap(BlobLen);
if (CertUPNName){
b = CryptDecodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
X509_UNICODE_ANY_STRING,
AltNameInfo->rgAltEntry[ii].pOtherName->Value.pbData,
AltNameInfo->rgAltEntry[ii].pOtherName->Value.cbData,
0,
CertUPNName,
&BlobLen
);
if (b){
UPNName = (LPWSTR)LsapAllocateLsaHeap( CertUPNName->Value.cbData + sizeof(WCHAR) );
if (UPNName){
wcscpy(UPNName, (LPCWSTR) CertUPNName->Value.pbData);
}
}
LsapFreeLsaHeap(CertUPNName);
if (UPNName){
//
// Got the UPN name. Stop searching.
//
break;
}
}
}
} else {
//
// Check for other alternative name
//
if (AltNameInfo->rgAltEntry[ii].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME){
DNSName = AltNameInfo->rgAltEntry[ii].pwszDNSName;
}
}
ii++;
}
if ( NULL == UPNName ){
//
// No UPN name, let's get the other option
//
if (DNSName){
UPNName = (LPTSTR) LsapAllocateLsaHeap ( sizeof(WCHAR) * (wcslen( DNSName ) + 1));
if (UPNName){
wcscpy(UPNName, DNSName);
}
}
}
}
LsapFreeLsaHeap(AltNameInfo);
}
}
}
NameLength = CertGetNameString(
CertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0
);
if ( NameLength > 1){
//
// The display name exist. Go get the display name.
//
CommonName = (LPWSTR) LsapAllocateLsaHeap( sizeof(WCHAR) * NameLength);
if ( NULL == CommonName ){
if (UPNName){
LsapFreeLsaHeap( UPNName );
}
return ERROR_NOT_ENOUGH_MEMORY;
}
UserNameBufLen = NameLength;
NameLength = CertGetNameString(
CertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
CommonName,
UserNameBufLen
);
ASSERT (NameLength == UserNameBufLen);
}
if (CommonName || UPNName){
NameLength = 3;
if (CommonName){
NameLength += wcslen(CommonName);
}
if (UPNName){
NameLength += wcslen(UPNName);
}
*UserDispName = (LPWSTR)LsapAllocateLsaHeap(sizeof(WCHAR) * NameLength);
if (*UserDispName) {
if (CommonName){
wcscpy(*UserDispName, CommonName);
if (UPNName){
wcscat(*UserDispName, L"(");
wcscat(*UserDispName, UPNName);
wcscat(*UserDispName, L")");
}
} else {
wcscpy(*UserDispName, UPNName);
}
} else {
rc = ERROR_NOT_ENOUGH_MEMORY;
}
if (CommonName){
LsapFreeLsaHeap( CommonName );
}
if (UPNName){
LsapFreeLsaHeap( UPNName );
}
return rc;
}
return DISP_E_UNKNOWNNAME;
}
DWORD
EfsAddCertToTrustStoreStore(
IN PCCERT_CONTEXT pCert,
OUT DWORD *ImpersonationError
)
/*++
Routine Description:
This routine adds the cert to the LM Trusted store.
Arguments:
pCert -- The cert to be added.
ImpersonationError -- Error indicate that we could impersonate after revert to self. This should not be the case.
Return Value:
Win32 error code
--*/
{
NTSTATUS Status;
HANDLE hToken;
DWORD errCode = ERROR_SUCCESS;
HCERTSTORE localStore;
*ImpersonationError = 0;
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY | TOKEN_IMPERSONATE,
TRUE, // OpenAsSelf
&hToken
);
if (NT_SUCCESS( Status )) {
RevertToSelf();
localStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0, // dwEncodingType
0, // hCryptProv,
CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_MAXIMUM_ALLOWED_FLAG,
TRUSTEDPEOPLE
);
if (localStore) {
LPWSTR crntUserName;
LPWSTR userName;
PCCERT_CONTEXT userCert = NULL;
CERT_ENHKEY_USAGE certEnhKeyUsage;
LPSTR lpstr = szOID_EFS_CRYPTO;
certEnhKeyUsage.cUsageIdentifier = 1;
certEnhKeyUsage.rgpszUsageIdentifier = &lpstr;
errCode = EfsGetCertNameFromCertContext(
pCert,
&crntUserName
);
if (crntUserName) {
//
// Let's enumerate the certs in the store to see if we need to remove the old similar
// EFS cert.
//
do {
userCert = CertFindCertificateInStore(
localStore,
X509_ASN_ENCODING,
0, //CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
CERT_FIND_ENHKEY_USAGE,
&certEnhKeyUsage,
userCert
);
if (userCert) {
EfsGetCertNameFromCertContext(
userCert,
&userName
);
if (userName) {
if (!wcscmp(userName, crntUserName) ) {
//
// Find the name match. Remove it.
//
PCCERT_CONTEXT tmpCert;
tmpCert = CertDuplicateCertificateContext(userCert);
if (tmpCert) {
CertDeleteCertificateFromStore(tmpCert);
}
}
LsapFreeLsaHeap( userName );
}
}
} while (userCert);
if(!CertAddCertificateContextToStore(
localStore,
pCert,
CERT_STORE_ADD_NEW,
NULL) ) {
errCode = GetLastError();
}
LsapFreeLsaHeap( crntUserName );
} else {
errCode = GetLastError();
}
CertCloseStore( localStore, 0 );
}
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) &hToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS( Status )) {
ASSERT(FALSE);
*ImpersonationError = 1;
errCode = RtlNtStatusToDosError( Status );
}
} else {
errCode = RtlNtStatusToDosError( Status );
}
return errCode;
}
DWORD
EfsAlignBlock(
IN PVOID InPointer,
OUT PVOID *OutPointer,
OUT BOOLEAN *NewMemory
)
/*++
Routine Description:
This routine will align the structure with the first ULONG as the length of the structure
so that we don't get alignment faults.
Arguments:
InPointer -- Original Block
OutPointer -- Aligned Block
NewMemory -- If new memory block allocated
Return Value:
Win32 error code
--*/
{
if ( ((INT_PTR) InPointer & 0x03) == 0) {
*OutPointer = InPointer;
*NewMemory = FALSE;
return ERROR_SUCCESS;
}
ULONG length;
DWORD result=ERROR_SUCCESS;
RtlCopyMemory(&length, InPointer, sizeof (ULONG));
*OutPointer = (PENCRYPTED_KEY)LsapAllocateLsaHeap(length);
if (*OutPointer) {
RtlCopyMemory(*OutPointer, InPointer, length);
*NewMemory = TRUE;
} else {
*NewMemory = FALSE;
result = ERROR_NOT_ENOUGH_MEMORY;
}
return result;
}