You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2266 lines
58 KiB
2266 lines
58 KiB
/*++
|
|
|
|
Copyright (c) 1996-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
session.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines to support communication with the LSA
|
|
(Local Security Authority) to permit querying of active sessions.
|
|
|
|
This module also supports calling into the LSA to retrieve a credential
|
|
derived from a logged on user specified by Logon Identifier.
|
|
|
|
Author:
|
|
|
|
Scott Field (sfield) 02-Mar-97
|
|
|
|
Pete Skelly (petesk) 09-May-00 - added credential history and signature code
|
|
|
|
--*/
|
|
|
|
|
|
#include <pch.cpp>
|
|
#pragma hdrstop
|
|
#include <ntmsv1_0.h>
|
|
#include <crypt.h>
|
|
#include <userenv.h>
|
|
#include <userenvp.h>
|
|
#include "debug.h"
|
|
#include "passrec.h"
|
|
|
|
#define HMAC_K_PADSIZE (64)
|
|
|
|
#define CREDENTIAL_HISTORY_VERSION 1
|
|
|
|
#define CREDENTIAL_HISTORY_SALT_SIZE 16 // 128 bits
|
|
|
|
#define DEFAULT_KEY_GEN_ALG CALG_HMAC
|
|
#define DEFAULT_ENCRYPTION_ALG CALG_3DES
|
|
|
|
|
|
typedef struct _CREDENTIAL_HISTORY_HEADER
|
|
{
|
|
DWORD dwVersion;
|
|
GUID CredentialID;
|
|
DWORD dwPreviousCredOffset;
|
|
} CREDENTIAL_HISTORY_HEADER, *PCREDENTIAL_HISTORY_HEADER;
|
|
|
|
typedef struct _CREDENTIAL_HISTORY
|
|
{
|
|
CREDENTIAL_HISTORY_HEADER Header;
|
|
DWORD dwFlags;
|
|
DWORD KeyGenAlg;
|
|
DWORD cIterationCount; // pbkdf2 iteration count
|
|
DWORD cbSid; // sid is used as mixing bytes
|
|
DWORD KeyEncrAlg;
|
|
DWORD cbShaOwf;
|
|
DWORD cbNtOwf;
|
|
BYTE Salt[CREDENTIAL_HISTORY_SALT_SIZE];
|
|
} CREDENTIAL_HISTORY, *PCREDENTIAL_HISTORY;
|
|
|
|
|
|
typedef struct _CREDENTIAL_HISTORY_MAP
|
|
{
|
|
PSID pUserSid;
|
|
WCHAR wszFilePath[MAX_PATH+1];
|
|
HANDLE hHistoryFile;
|
|
HANDLE hMapping;
|
|
DWORD dwMapSize;
|
|
PBYTE pMapping;
|
|
struct _CREDENTIAL_HISTORY_MAP *pNext;
|
|
|
|
} CREDENTIAL_HISTORY_MAP, *PCREDENTIAL_HISTORY_MAP;
|
|
|
|
|
|
RTL_CRITICAL_SECTION g_csCredHistoryCache;
|
|
|
|
|
|
DWORD
|
|
OpenCredentialHistoryMap(
|
|
HANDLE hUserToken,
|
|
LPWSTR pszProfilePath,
|
|
PCREDENTIAL_HISTORY_MAP *ppMap,
|
|
PCREDENTIAL_HISTORY *ppCurrent);
|
|
|
|
PCREDENTIAL_HISTORY
|
|
GetPreviousCredentialHistory(
|
|
PCREDENTIAL_HISTORY_MAP pMap,
|
|
PCREDENTIAL_HISTORY pCurrent
|
|
);
|
|
|
|
DWORD
|
|
CloseCredentialHistoryMap(PCREDENTIAL_HISTORY_MAP pMap,
|
|
BOOL fReader);
|
|
|
|
DWORD
|
|
DestroyCredentialHistoryMap(PCREDENTIAL_HISTORY_MAP pMap);
|
|
|
|
VOID
|
|
DeriveWithHMAC_SHA1(
|
|
IN PBYTE pbKeyMaterial, // input key material
|
|
IN DWORD cbKeyMaterial,
|
|
IN PBYTE pbData, // input mixing data
|
|
IN DWORD cbData,
|
|
IN OUT BYTE rgbHMAC[A_SHA_DIGEST_LEN] // output buffer
|
|
);
|
|
|
|
DWORD
|
|
DecryptCredentialHistory(PCREDENTIAL_HISTORY pCredential,
|
|
BYTE rgbDecryptingCredential[A_SHA_DIGEST_LEN],
|
|
BYTE rgbShaOwf[A_SHA_DIGEST_LEN],
|
|
BYTE rgbNTOwf[A_SHA_DIGEST_LEN]);
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
RetrieveCurrentDerivedCredential(
|
|
IN LUID *pLogonId,
|
|
IN BOOL fDPOWF,
|
|
IN PBYTE pbMixingBytes,
|
|
IN DWORD cbMixingBytes,
|
|
IN OUT BYTE rgbDerivedCredential[A_SHA_DIGEST_LEN]
|
|
)
|
|
{
|
|
NTSTATUS ntstatus;
|
|
DWORD rc = ERROR_SUCCESS;
|
|
NTSTATUS AuthPackageStatus;
|
|
|
|
PMSV1_0_DERIVECRED_REQUEST pDeriveCredentialRequest;
|
|
DWORD cbDeriveCredentialRequest;
|
|
PMSV1_0_DERIVECRED_RESPONSE pDeriveCredentialResponse;
|
|
ULONG DeriveCredentialResponseLength;
|
|
UNICODE_STRING PackageName;
|
|
HANDLE hToken = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
RtlInitUnicodeString(&PackageName, L"MICROSOFT_AUTHENTICATION_PACKAGE_V1_0");
|
|
|
|
|
|
//
|
|
// must specify mixing bytes.
|
|
//
|
|
|
|
if( cbMixingBytes == 0 || pbMixingBytes == NULL )
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Ask authentication package to provide derived credential associated
|
|
// with the specified logon identifier.
|
|
// note: submit buffer must be a single contiguous block.
|
|
//
|
|
|
|
cbDeriveCredentialRequest = sizeof(MSV1_0_DERIVECRED_REQUEST) + cbMixingBytes;
|
|
pDeriveCredentialRequest = (MSV1_0_DERIVECRED_REQUEST *)SSAlloc( cbDeriveCredentialRequest );
|
|
if( pDeriveCredentialRequest == NULL )
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
pDeriveCredentialRequest->MessageType = MsV1_0DeriveCredential;
|
|
CopyMemory( &(pDeriveCredentialRequest->LogonId), pLogonId, sizeof(LUID) );
|
|
pDeriveCredentialRequest->DeriveCredType = fDPOWF?MSV1_0_DERIVECRED_TYPE_SHA1_V2:MSV1_0_DERIVECRED_TYPE_SHA1;
|
|
pDeriveCredentialRequest->DeriveCredInfoLength = cbMixingBytes;
|
|
|
|
CopyMemory(pDeriveCredentialRequest->DeriveCredSubmitBuffer, pbMixingBytes, cbMixingBytes);
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &hToken))
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
|
|
RevertToSelf();
|
|
|
|
// Make this call as local system
|
|
|
|
ntstatus = LsaICallPackage(
|
|
&PackageName,
|
|
pDeriveCredentialRequest,
|
|
cbDeriveCredentialRequest,
|
|
(PVOID *)&pDeriveCredentialResponse,
|
|
&DeriveCredentialResponseLength,
|
|
&AuthPackageStatus
|
|
);
|
|
|
|
SSFree( pDeriveCredentialRequest );
|
|
|
|
if (!SetThreadToken(NULL, hToken))
|
|
{
|
|
rc = GetLastError();
|
|
}
|
|
CloseHandle(hToken);
|
|
|
|
if(!NT_SUCCESS(ntstatus))
|
|
{
|
|
return RtlNtStatusToDosError(ntstatus);
|
|
}
|
|
|
|
if (ERROR_SUCCESS == rc)
|
|
{
|
|
|
|
CopyMemory( rgbDerivedCredential,
|
|
pDeriveCredentialResponse->DeriveCredReturnBuffer,
|
|
pDeriveCredentialResponse->DeriveCredInfoLength
|
|
);
|
|
|
|
}
|
|
|
|
|
|
RtlSecureZeroMemory( pDeriveCredentialResponse->DeriveCredReturnBuffer,
|
|
pDeriveCredentialResponse->DeriveCredInfoLength
|
|
);
|
|
|
|
LsaIFreeReturnBuffer( pDeriveCredentialResponse );
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
DWORD
|
|
QueryDerivedCredential(
|
|
IN OUT GUID *CredentialID,
|
|
IN LUID *pLogonId,
|
|
IN DWORD dwFlags,
|
|
IN PBYTE pbMixingBytes,
|
|
IN DWORD cbMixingBytes,
|
|
IN OUT BYTE rgbDerivedCredential[A_SHA_DIGEST_LEN]
|
|
)
|
|
{
|
|
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
PCREDENTIAL_HISTORY pCurrent = NULL;
|
|
PCREDENTIAL_HISTORY pNext = NULL;
|
|
BYTE rgbCurrentDerivedCredential[A_SHA_DIGEST_LEN];
|
|
BYTE rgbCurrentShaOWF[A_SHA_DIGEST_LEN];
|
|
BYTE rgbCurrentNTOWF[A_SHA_DIGEST_LEN];
|
|
|
|
PCREDENTIAL_HISTORY_MAP pHistoryMap= NULL;
|
|
WCHAR wszTextualSid[MAX_PATH + 1];
|
|
WCHAR szProfilePath[MAX_PATH + 1];
|
|
DWORD cchTextualSid = 0;
|
|
BOOL fIsRoot = TRUE;
|
|
|
|
//
|
|
// Get path to user's profile data. This will typically look something
|
|
// like "c:\Documents and Settings\<user>\Application Data".
|
|
//
|
|
|
|
dwLastError = PRGetProfilePath(NULL,
|
|
szProfilePath);
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, "DPAPIChangePassword returned 0x%x\n", dwLastError));
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
//
|
|
// work-around the fact that much of this (new) code is not thread-safe.
|
|
//
|
|
|
|
RtlEnterCriticalSection(&g_csCredHistoryCache);
|
|
|
|
//
|
|
// Open history file
|
|
//
|
|
|
|
dwLastError = OpenCredentialHistoryMap(NULL, szProfilePath, &pHistoryMap, &pCurrent);
|
|
|
|
|
|
while((ERROR_SUCCESS == dwLastError) &&
|
|
(pCurrent) &&
|
|
(0 == (dwFlags & USE_ROOT_CREDENTIAL)))
|
|
{
|
|
|
|
//
|
|
// We're looking for a specific credential ID
|
|
//
|
|
if((NULL != CredentialID) &&
|
|
(0 == memcmp(&pCurrent->Header.CredentialID, CredentialID, sizeof(GUID))))
|
|
{
|
|
// found it,
|
|
break;
|
|
}
|
|
|
|
|
|
pNext = GetPreviousCredentialHistory(pHistoryMap, pCurrent);
|
|
|
|
if(NULL == pNext)
|
|
{
|
|
if(NULL != CredentialID)
|
|
{
|
|
// If we're looking for a specific credential, but
|
|
// couldn't find it, then return an error
|
|
dwLastError = NTE_BAD_KEY;
|
|
}
|
|
else
|
|
{
|
|
// no credential id was specified, so default to the oldest one.
|
|
dwLastError = ERROR_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// get the textual sid
|
|
//
|
|
cchTextualSid = MAX_PATH;
|
|
|
|
if(!GetTextualSid((PBYTE)(pNext + 1), wszTextualSid, &cchTextualSid))
|
|
{
|
|
dwLastError = ERROR_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
if(fIsRoot)
|
|
{
|
|
//
|
|
// crack the next credential using the current
|
|
// credentials
|
|
//
|
|
dwLastError = RetrieveCurrentDerivedCredential(pLogonId,
|
|
(0 != (pNext->dwFlags & USE_DPAPI_OWF)), // always use
|
|
(PBYTE)wszTextualSid,
|
|
cchTextualSid*sizeof(WCHAR),
|
|
rgbCurrentDerivedCredential);
|
|
|
|
fIsRoot = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// calculate the current derived credential used to decrypt the
|
|
// next credential history structure by using the decrypted OWF
|
|
// from the previous pass
|
|
|
|
DeriveWithHMAC_SHA1((0 != (pNext->dwFlags & USE_DPAPI_OWF))?rgbCurrentShaOWF:rgbCurrentNTOWF,
|
|
A_SHA_DIGEST_LEN,
|
|
(PBYTE)wszTextualSid,
|
|
cchTextualSid*sizeof(WCHAR),
|
|
rgbCurrentDerivedCredential);
|
|
|
|
//
|
|
// we don't need the OWF anymore, so zap it.
|
|
//
|
|
RtlSecureZeroMemory(rgbCurrentShaOWF, A_SHA_DIGEST_LEN);
|
|
RtlSecureZeroMemory(rgbCurrentNTOWF, A_SHA_DIGEST_LEN);
|
|
}
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// used the derived credential to decrypt
|
|
// the data blob of pNext
|
|
//
|
|
|
|
dwLastError = DecryptCredentialHistory(pNext,
|
|
rgbCurrentDerivedCredential,
|
|
rgbCurrentShaOWF,
|
|
rgbCurrentNTOWF);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
break;
|
|
}
|
|
|
|
pCurrent = pNext;
|
|
pNext = NULL;
|
|
|
|
}
|
|
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
if(fIsRoot)
|
|
{
|
|
//
|
|
// crack the next credential using the current
|
|
// credentials
|
|
//
|
|
dwLastError = RetrieveCurrentDerivedCredential(pLogonId,
|
|
(0 != (dwFlags & USE_DPAPI_OWF)),
|
|
pbMixingBytes,
|
|
cbMixingBytes,
|
|
rgbDerivedCredential);
|
|
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
if((CredentialID != NULL) &&
|
|
(0 != (dwFlags & USE_ROOT_CREDENTIAL)))
|
|
{
|
|
CopyMemory(CredentialID, &pCurrent->Header.CredentialID, sizeof(GUID));
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// calculate the current derived credential used to decrypt the
|
|
// next credential history structure by using the decrypted OWF
|
|
// from the previous pass
|
|
|
|
DeriveWithHMAC_SHA1((0 != (dwFlags & USE_DPAPI_OWF))?rgbCurrentShaOWF:rgbCurrentNTOWF,
|
|
A_SHA_DIGEST_LEN,
|
|
pbMixingBytes,
|
|
cbMixingBytes,
|
|
rgbDerivedCredential);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Clear out any owf's we may have lying around
|
|
//
|
|
RtlSecureZeroMemory(rgbCurrentShaOWF, A_SHA_DIGEST_LEN);
|
|
RtlSecureZeroMemory(rgbCurrentNTOWF, A_SHA_DIGEST_LEN);
|
|
|
|
|
|
if(pHistoryMap)
|
|
{
|
|
CloseCredentialHistoryMap(pHistoryMap, TRUE);
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&g_csCredHistoryCache);
|
|
|
|
return dwLastError;
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
DeriveWithHMAC_SHA1(
|
|
IN PBYTE pbKeyMaterial, // input key material
|
|
IN DWORD cbKeyMaterial,
|
|
IN PBYTE pbData, // input mixing data
|
|
IN DWORD cbData,
|
|
IN OUT BYTE rgbHMAC[A_SHA_DIGEST_LEN] // output buffer
|
|
)
|
|
{
|
|
unsigned __int64 rgbKipad[ HMAC_K_PADSIZE/sizeof(unsigned __int64) ];
|
|
unsigned __int64 rgbKopad[ HMAC_K_PADSIZE/sizeof(unsigned __int64) ];
|
|
A_SHA_CTX sSHAHash;
|
|
DWORD dwBlock;
|
|
|
|
// truncate
|
|
if( cbKeyMaterial > HMAC_K_PADSIZE )
|
|
{
|
|
cbKeyMaterial = HMAC_K_PADSIZE;
|
|
}
|
|
|
|
ZeroMemory(rgbKipad, sizeof(rgbKipad));
|
|
ZeroMemory(rgbKopad, sizeof(rgbKopad));
|
|
|
|
CopyMemory(rgbKipad, pbKeyMaterial, cbKeyMaterial);
|
|
CopyMemory(rgbKopad, pbKeyMaterial, cbKeyMaterial);
|
|
|
|
// Kipad, Kopad are padded sMacKey. Now XOR across...
|
|
for( dwBlock = 0; dwBlock < (HMAC_K_PADSIZE/sizeof(unsigned __int64)) ; dwBlock++ )
|
|
{
|
|
rgbKipad[dwBlock] ^= 0x3636363636363636;
|
|
rgbKopad[dwBlock] ^= 0x5C5C5C5C5C5C5C5C;
|
|
}
|
|
|
|
// prepend Kipad to data, Hash to get H1
|
|
A_SHAInit(&sSHAHash);
|
|
A_SHAUpdate(&sSHAHash, (PBYTE)rgbKipad, sizeof(rgbKipad));
|
|
A_SHAUpdate(&sSHAHash, pbData, cbData);
|
|
|
|
|
|
// Finish off the hash
|
|
A_SHAFinal(&sSHAHash, rgbHMAC);
|
|
|
|
// prepend Kopad to H1, hash to get HMAC
|
|
// note: done in place to avoid buffer copies
|
|
|
|
// final hash: output value into passed-in buffer
|
|
A_SHAInit(&sSHAHash);
|
|
A_SHAUpdate(&sSHAHash, (PBYTE)rgbKopad, sizeof(rgbKopad));
|
|
A_SHAUpdate(&sSHAHash, rgbHMAC, A_SHA_DIGEST_LEN);
|
|
A_SHAFinal(&sSHAHash, rgbHMAC);
|
|
|
|
|
|
RtlSecureZeroMemory( rgbKipad, sizeof(rgbKipad) );
|
|
RtlSecureZeroMemory( rgbKopad, sizeof(rgbKopad) );
|
|
RtlSecureZeroMemory( &sSHAHash, sizeof(sSHAHash) );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD
|
|
DecryptCredentialHistory(PCREDENTIAL_HISTORY pCredential,
|
|
BYTE rgbDecryptingCredential[A_SHA_DIGEST_LEN],
|
|
BYTE rgbShaOwf[A_SHA_DIGEST_LEN],
|
|
BYTE rgbNTOwf[A_SHA_DIGEST_LEN])
|
|
{
|
|
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
DWORD j;
|
|
BYTE rgbSymKey[A_SHA_DIGEST_LEN*2]; // big enough to handle 3des keys
|
|
|
|
//
|
|
// Derive the protection key
|
|
//
|
|
|
|
for(j=0; j < 2; j++)
|
|
{
|
|
if(!PKCS5DervivePBKDF2( rgbDecryptingCredential,
|
|
A_SHA_DIGEST_LEN,
|
|
pCredential->Salt,
|
|
CREDENTIAL_HISTORY_SALT_SIZE,
|
|
pCredential->KeyGenAlg,
|
|
pCredential->cIterationCount,
|
|
j+1,
|
|
rgbSymKey + j*A_SHA_DIGEST_LEN))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if (CALG_3DES == pCredential->KeyEncrAlg)
|
|
{
|
|
|
|
DES3TABLE s3DESKey;
|
|
DWORD iBlock;
|
|
BYTE ResultBlock[2*A_SHA_DIGEST_LEN+DES_BLOCKLEN ];
|
|
|
|
//
|
|
// Round up blocks. it's assumed that the total block size was verified
|
|
// earlier
|
|
//
|
|
DWORD cBlocks = (pCredential->cbShaOwf + pCredential->cbNtOwf + DES_BLOCKLEN - 1)/DES_BLOCKLEN;
|
|
BYTE feedback[ DES_BLOCKLEN ];
|
|
// initialize 3des key
|
|
//
|
|
if((pCredential->cbShaOwf != A_SHA_DIGEST_LEN) ||
|
|
(pCredential->cbNtOwf != A_SHA_DIGEST_LEN))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
tripledes3key(&s3DESKey, rgbSymKey);
|
|
|
|
//
|
|
// IV is derived from the DES_BLOCKLEN bytes of the calculated
|
|
// rgbSymKey, after the 3des key
|
|
CopyMemory(feedback, rgbSymKey + DES3_KEYSIZE, DES_BLOCKLEN);
|
|
|
|
for(iBlock=0; iBlock < cBlocks; iBlock++)
|
|
{
|
|
CBC(tripledes,
|
|
DES_BLOCKLEN,
|
|
ResultBlock+iBlock*DES_BLOCKLEN,
|
|
((PBYTE)(pCredential + 1) + pCredential->cbSid)+iBlock*DES_BLOCKLEN,
|
|
&s3DESKey,
|
|
DECRYPT,
|
|
feedback);
|
|
}
|
|
CopyMemory(rgbShaOwf, ResultBlock, A_SHA_DIGEST_LEN);
|
|
CopyMemory(rgbNTOwf, ResultBlock + A_SHA_DIGEST_LEN, A_SHA_DIGEST_LEN);
|
|
RtlSecureZeroMemory(ResultBlock, sizeof(ResultBlock));
|
|
|
|
}
|
|
else
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
EncryptCredentialHistory(BYTE rgbEncryptingCredential[A_SHA_DIGEST_LEN],
|
|
DWORD dwFlags,
|
|
BYTE SHAOwfToEncrypt[A_SHA_DIGEST_LEN],
|
|
BYTE NTOwfToEncrypt[A_SHA_DIGEST_LEN],
|
|
PCREDENTIAL_HISTORY *ppCredential,
|
|
DWORD *pcbCredential)
|
|
{
|
|
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
DWORD j;
|
|
BYTE rgbSymKey[A_SHA_DIGEST_LEN*2]; // big enough to handle 3des keys
|
|
PCREDENTIAL_HISTORY pCred = NULL;
|
|
DWORD cbCred = 0;
|
|
HANDLE hToken = NULL;
|
|
PSID pSidUser = NULL;
|
|
BYTE ResultBuffer[A_SHA_DIGEST_LEN * 2]; // 2 * A_SHA_DIGEST_LEN
|
|
|
|
DWORD cBlocks = 0;
|
|
DWORD cbBlock = 0;
|
|
|
|
|
|
|
|
cbBlock = DES_BLOCKLEN;
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
if(!GetTokenUserSid(hToken, &pSidUser))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
cBlocks = sizeof(ResultBuffer)/DES_BLOCKLEN; // this should be 5
|
|
|
|
|
|
cbCred= sizeof(CREDENTIAL_HISTORY) +
|
|
GetLengthSid(pSidUser) +
|
|
cBlocks*cbBlock;
|
|
|
|
|
|
|
|
pCred = (PCREDENTIAL_HISTORY)LocalAlloc(LMEM_ZEROINIT, cbCred);
|
|
|
|
if(NULL == pCred)
|
|
{
|
|
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto error;
|
|
}
|
|
pCred->dwFlags = dwFlags;
|
|
|
|
|
|
|
|
pCred->KeyGenAlg = DEFAULT_KEY_GEN_ALG;
|
|
pCred->cIterationCount = GetIterationCount();
|
|
pCred->KeyEncrAlg = DEFAULT_ENCRYPTION_ALG;
|
|
|
|
pCred->cbShaOwf = A_SHA_DIGEST_LEN;
|
|
pCred->cbNtOwf = A_SHA_DIGEST_LEN;
|
|
|
|
|
|
pCred->cbSid = GetLengthSid(pSidUser);
|
|
|
|
CopyMemory((PBYTE)(pCred+1), (PBYTE)pSidUser, pCred->cbSid);
|
|
|
|
if(!RtlGenRandom(pCred->Salt, CREDENTIAL_HISTORY_SALT_SIZE))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
for(j=0; j < 2; j++)
|
|
{
|
|
if(!PKCS5DervivePBKDF2( rgbEncryptingCredential,
|
|
A_SHA_DIGEST_LEN,
|
|
pCred->Salt,
|
|
CREDENTIAL_HISTORY_SALT_SIZE,
|
|
pCred->KeyGenAlg,
|
|
pCred->cIterationCount,
|
|
j+1,
|
|
rgbSymKey + j*A_SHA_DIGEST_LEN))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
}
|
|
if (CALG_3DES == pCred->KeyEncrAlg)
|
|
{
|
|
|
|
DES3TABLE s3DESKey;
|
|
DWORD iBlock;
|
|
BYTE feedback[ DES_BLOCKLEN ];
|
|
|
|
// initialize 3des key
|
|
tripledes3key(&s3DESKey, rgbSymKey);
|
|
|
|
//
|
|
// IV is derived from the DES_BLOCKLEN bytes of the calculated
|
|
// rgbSymKey, after the 3des key
|
|
CopyMemory(feedback, rgbSymKey + DES3_KEYSIZE, DES_BLOCKLEN);
|
|
|
|
|
|
|
|
CopyMemory(ResultBuffer, SHAOwfToEncrypt, A_SHA_DIGEST_LEN);
|
|
CopyMemory(ResultBuffer+A_SHA_DIGEST_LEN, NTOwfToEncrypt, A_SHA_DIGEST_LEN);
|
|
|
|
for(iBlock=0; iBlock < cBlocks; iBlock++)
|
|
{
|
|
|
|
CBC(tripledes,
|
|
DES_BLOCKLEN,
|
|
((PBYTE)(pCred + 1) + pCred->cbSid)+iBlock*DES_BLOCKLEN,
|
|
ResultBuffer + iBlock*DES_BLOCKLEN,
|
|
&s3DESKey,
|
|
ENCRYPT,
|
|
feedback);
|
|
}
|
|
|
|
|
|
RtlSecureZeroMemory(ResultBuffer, sizeof(ResultBuffer));
|
|
}
|
|
else
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
*ppCredential = pCred;
|
|
*pcbCredential = cbCred;
|
|
|
|
pCred = NULL;
|
|
error:
|
|
|
|
if(pCred)
|
|
{
|
|
LocalFree(pCred);
|
|
}
|
|
|
|
if(hToken)
|
|
{
|
|
CloseHandle(hToken);
|
|
}
|
|
|
|
if(pSidUser)
|
|
{
|
|
SSFree(pSidUser);
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
#define PRODUCT_ROOT_STRING L"\\Microsoft\\Protect\\"
|
|
|
|
#define HISTORY_FILENAME L"CREDHIST"
|
|
|
|
DWORD
|
|
CreateCredentialHistoryMap(
|
|
HANDLE hUserToken,
|
|
LPWSTR pszProfilePath,
|
|
PCREDENTIAL_HISTORY_MAP *ppMap,
|
|
BOOL fRead)
|
|
{
|
|
PCREDENTIAL_HISTORY_MAP pMap = NULL;
|
|
|
|
PCREDENTIAL_HISTORY_MAP pCached = NULL;
|
|
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
DWORD dwHighFileSize = 0;
|
|
|
|
DWORD cbUserStorageRoot;
|
|
HANDLE hTemporaryMapping = NULL;
|
|
|
|
WCHAR szFilePath[MAX_PATH + 1];
|
|
PWSTR pszCreationStartPoint;
|
|
|
|
NTSTATUS Status;
|
|
|
|
if(NULL == ppMap)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
pMap = (PCREDENTIAL_HISTORY_MAP)LocalAlloc(LPTR, sizeof(CREDENTIAL_HISTORY_MAP));
|
|
if(NULL == pMap)
|
|
{
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
|
|
//
|
|
// Obtain the user's SID.
|
|
//
|
|
|
|
if(hUserToken)
|
|
{
|
|
if(!GetTokenUserSid(hUserToken, &pMap->pUserSid))
|
|
{
|
|
dwError = GetLastError();
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HANDLE hToken;
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
|
|
{
|
|
dwError = ERROR_NO_TOKEN;
|
|
goto error;
|
|
}
|
|
|
|
if(!GetTokenUserSid(hToken, &pMap->pUserSid))
|
|
{
|
|
dwError = GetLastError();
|
|
CloseHandle(hToken);
|
|
goto error;
|
|
}
|
|
|
|
CloseHandle(hToken);
|
|
}
|
|
|
|
|
|
//
|
|
// Open map file
|
|
//
|
|
|
|
if(wcslen(pszProfilePath) + wcslen(PRODUCT_ROOT_STRING) + wcslen(HISTORY_FILENAME) > MAX_PATH)
|
|
{
|
|
dwError = ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
wcscpy(szFilePath, pszProfilePath);
|
|
|
|
|
|
// Build the path in a separate buffer just in case we have to create
|
|
// the directory.
|
|
pszCreationStartPoint = szFilePath + wcslen(szFilePath) + sizeof(WCHAR);
|
|
wcscat(szFilePath, PRODUCT_ROOT_STRING);
|
|
|
|
// Copy the path plus the filename over to the map structure.
|
|
wcscpy(pMap->wszFilePath, szFilePath);
|
|
wcscat(pMap->wszFilePath, HISTORY_FILENAME);
|
|
|
|
|
|
//
|
|
// Create the history file.
|
|
//
|
|
|
|
dwError = ERROR_SUCCESS;
|
|
|
|
while(TRUE)
|
|
{
|
|
pMap->hHistoryFile = CreateFileWithRetries(
|
|
pMap->wszFilePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0, // cannot share this file when open
|
|
NULL,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_FLAG_RANDOM_ACCESS,
|
|
NULL
|
|
);
|
|
|
|
if(INVALID_HANDLE_VALUE == pMap->hHistoryFile)
|
|
{
|
|
dwError = GetLastError();
|
|
|
|
if(dwError == ERROR_PATH_NOT_FOUND)
|
|
{
|
|
// Create the DPAPI directory, and then try to create the file
|
|
// again.
|
|
Status = DPAPICreateNestedDirectories(szFilePath,
|
|
pszCreationStartPoint);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
dwError = ERROR_SUCCESS;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
pMap->dwMapSize = GetFileSize(pMap->hHistoryFile, &dwHighFileSize);
|
|
|
|
if((-1 == pMap->dwMapSize) ||
|
|
(dwHighFileSize != 0))
|
|
{
|
|
dwError = ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
|
|
//
|
|
// If this map is too small, we need to create a new header
|
|
//
|
|
if(pMap->dwMapSize < sizeof(CREDENTIAL_HISTORY_HEADER))
|
|
{
|
|
|
|
PCREDENTIAL_HISTORY_HEADER pHeader = NULL;
|
|
|
|
pMap->dwMapSize = sizeof(CREDENTIAL_HISTORY_HEADER);
|
|
|
|
hTemporaryMapping = CreateFileMapping(pMap->hHistoryFile,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
pMap->dwMapSize,
|
|
NULL);
|
|
|
|
if(NULL == hTemporaryMapping)
|
|
{
|
|
dwError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
pHeader = (PCREDENTIAL_HISTORY_HEADER)(PBYTE)MapViewOfFile(hTemporaryMapping,
|
|
FILE_MAP_WRITE,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if(NULL == pHeader)
|
|
{
|
|
dwError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
//
|
|
// Write a fresh header into the cred history file
|
|
//
|
|
pHeader->dwPreviousCredOffset = 0;
|
|
pHeader->dwVersion = CREDENTIAL_HISTORY_VERSION;
|
|
dwError = UuidCreate( &pHeader->CredentialID );
|
|
|
|
FlushViewOfFile(pHeader, pMap->dwMapSize);
|
|
|
|
UnmapViewOfFile(pHeader);
|
|
|
|
if(ERROR_SUCCESS != dwError)
|
|
{
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
*ppMap = pMap;
|
|
pMap = NULL;
|
|
|
|
|
|
error:
|
|
|
|
if(pMap)
|
|
{
|
|
DestroyCredentialHistoryMap(pMap);
|
|
}
|
|
|
|
if(hTemporaryMapping)
|
|
{
|
|
CloseHandle(hTemporaryMapping);
|
|
}
|
|
|
|
return dwError;
|
|
}
|
|
|
|
|
|
DWORD
|
|
OpenCredentialHistoryMap(
|
|
HANDLE hUserToken,
|
|
LPWSTR pszProfilePath,
|
|
PCREDENTIAL_HISTORY_MAP *ppMap,
|
|
PCREDENTIAL_HISTORY *ppCurrent)
|
|
{
|
|
PCREDENTIAL_HISTORY_MAP pMap = NULL;
|
|
PCREDENTIAL_HISTORY pCurrent = NULL;
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
DWORD dwHighFileSize = 0;
|
|
|
|
WCHAR szFilePath[MAX_PATH+1];
|
|
DWORD cbUserStorageRoot;
|
|
HANDLE hTemporaryMapping = NULL;
|
|
|
|
|
|
|
|
if((NULL == ppMap) ||
|
|
(NULL == ppCurrent))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
dwError = CreateCredentialHistoryMap(
|
|
hUserToken,
|
|
pszProfilePath,
|
|
&pMap,
|
|
TRUE);
|
|
|
|
if(ERROR_SUCCESS != dwError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if(NULL == pMap->hMapping)
|
|
{
|
|
//
|
|
// Open a read-only mapping of the file
|
|
//
|
|
pMap->hMapping = CreateFileMapping(pMap->hHistoryFile,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
dwHighFileSize,
|
|
pMap->dwMapSize,
|
|
NULL);
|
|
|
|
if(NULL == pMap->hMapping)
|
|
{
|
|
dwError = GetLastError();
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
if(NULL == pMap->pMapping)
|
|
{
|
|
|
|
pMap->pMapping = (PBYTE)MapViewOfFile(pMap->hMapping,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if(NULL == pMap->pMapping)
|
|
{
|
|
dwError = GetLastError();
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
pCurrent = GetPreviousCredentialHistory(pMap, NULL);
|
|
|
|
if(NULL == pCurrent)
|
|
{
|
|
dwError = ERROR_INVALID_PARAMETER;
|
|
goto error;
|
|
}
|
|
|
|
*ppMap = pMap;
|
|
pMap = NULL;
|
|
|
|
*ppCurrent = pCurrent;
|
|
|
|
|
|
error:
|
|
|
|
|
|
if(pMap)
|
|
{
|
|
CloseCredentialHistoryMap(pMap, TRUE);
|
|
}
|
|
|
|
return dwError;
|
|
}
|
|
|
|
PCREDENTIAL_HISTORY
|
|
GetPreviousCredentialHistory(
|
|
PCREDENTIAL_HISTORY_MAP pMap,
|
|
PCREDENTIAL_HISTORY pCurrent
|
|
)
|
|
{
|
|
PCREDENTIAL_HISTORY pPrevious = NULL;
|
|
DWORD cbSize = 0;
|
|
|
|
if((NULL == pMap) ||
|
|
(NULL == pMap->pMapping))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if(pMap->dwMapSize < sizeof(CREDENTIAL_HISTORY_HEADER))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if(NULL == pCurrent)
|
|
{
|
|
pPrevious = (PCREDENTIAL_HISTORY)((PBYTE)pMap->pMapping +
|
|
pMap->dwMapSize - sizeof(CREDENTIAL_HISTORY_HEADER));
|
|
}
|
|
else
|
|
{
|
|
if(((PBYTE)pCurrent < pMap->pMapping) ||
|
|
((PBYTE)pCurrent - pMap->pMapping >= (__int64)pMap->dwMapSize) ||
|
|
((PBYTE)pCurrent - pMap->pMapping < (__int64)pCurrent->Header.dwPreviousCredOffset))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pPrevious = (PCREDENTIAL_HISTORY)((PBYTE)pCurrent - pCurrent->Header.dwPreviousCredOffset);
|
|
|
|
cbSize = sizeof(CREDENTIAL_HISTORY) + pPrevious->cbSid;
|
|
|
|
if(cbSize > pCurrent->Header.dwPreviousCredOffset)
|
|
{
|
|
return NULL;
|
|
}
|
|
cbSize = pCurrent->Header.dwPreviousCredOffset - cbSize;
|
|
if(cbSize < pPrevious->cbShaOwf + pPrevious->cbNtOwf)
|
|
{
|
|
return NULL;
|
|
}
|
|
if(cbSize % DES_BLOCKLEN)
|
|
{
|
|
return NULL;
|
|
}
|
|
if(!IsValidSid((PSID)(pPrevious+1)))
|
|
{
|
|
return NULL;
|
|
}
|
|
if(GetLengthSid((PSID)(pPrevious+1)) != pPrevious->cbSid)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// validate found credential history
|
|
if(pPrevious->Header.dwVersion != CREDENTIAL_HISTORY_VERSION)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return pPrevious;
|
|
}
|
|
|
|
DWORD
|
|
DestroyCredentialHistoryMap(PCREDENTIAL_HISTORY_MAP pMap)
|
|
{
|
|
if(NULL == pMap)
|
|
{
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
if (pMap->pUserSid)
|
|
{
|
|
SSFree(pMap->pUserSid);
|
|
pMap->pUserSid = NULL;
|
|
}
|
|
|
|
if(pMap->pMapping)
|
|
{
|
|
UnmapViewOfFile(pMap->pMapping);
|
|
pMap->pMapping = NULL;
|
|
}
|
|
|
|
if(pMap->hMapping)
|
|
{
|
|
CloseHandle(pMap->hMapping);
|
|
pMap->hMapping = NULL;
|
|
}
|
|
|
|
|
|
if(pMap->hHistoryFile)
|
|
{
|
|
CloseHandle(pMap->hHistoryFile);
|
|
pMap->hHistoryFile = NULL;
|
|
}
|
|
LocalFree(pMap);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
DWORD
|
|
CloseCredentialHistoryMap(PCREDENTIAL_HISTORY_MAP pMap, BOOL fReader)
|
|
{
|
|
if(NULL == pMap)
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(pMap->pMapping)
|
|
{
|
|
UnmapViewOfFile(pMap->pMapping);
|
|
pMap->pMapping = NULL;
|
|
}
|
|
|
|
if(pMap->hMapping)
|
|
{
|
|
CloseHandle(pMap->hMapping);
|
|
pMap->hMapping = NULL;
|
|
}
|
|
|
|
|
|
if(pMap->hHistoryFile)
|
|
{
|
|
CloseHandle(pMap->hHistoryFile);
|
|
pMap->hHistoryFile = NULL;
|
|
}
|
|
return DestroyCredentialHistoryMap(pMap);
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
AppendCredentialHistoryMap(
|
|
PCREDENTIAL_HISTORY_MAP pMap,
|
|
PCREDENTIAL_HISTORY pCredHistory,
|
|
DWORD cbCredHistory
|
|
)
|
|
{
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
DWORD dwHighFileSize = 0;
|
|
DWORD dwLowFileSize = 0;
|
|
|
|
HANDLE hWriteMapping = NULL;
|
|
LPVOID pWriteMapping = NULL;
|
|
PCREDENTIAL_HISTORY_HEADER pHeader = NULL;
|
|
|
|
|
|
|
|
if((NULL == pCredHistory) ||
|
|
(NULL == pMap))
|
|
{
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
dwLowFileSize = pMap->dwMapSize + cbCredHistory;
|
|
|
|
if(dwLowFileSize < pMap->dwMapSize)
|
|
{
|
|
//we wrapped, so fail.
|
|
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
// Create a new mapping
|
|
|
|
|
|
hWriteMapping = CreateFileMapping(pMap->hHistoryFile,
|
|
NULL,
|
|
PAGE_READWRITE,
|
|
0,
|
|
dwLowFileSize,
|
|
NULL);
|
|
if(NULL == hWriteMapping)
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
pWriteMapping = (PCREDENTIAL_HISTORY_HEADER)(PBYTE)MapViewOfFile(hWriteMapping,
|
|
FILE_MAP_WRITE,
|
|
0,
|
|
0,
|
|
dwLowFileSize);
|
|
|
|
if(NULL == pWriteMapping)
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
//
|
|
// Append the rest of the current entry
|
|
//
|
|
CopyMemory((PBYTE)pWriteMapping + pMap->dwMapSize,
|
|
(PBYTE)pCredHistory + sizeof(CREDENTIAL_HISTORY_HEADER),
|
|
cbCredHistory - sizeof(CREDENTIAL_HISTORY_HEADER));
|
|
|
|
|
|
pHeader = (PCREDENTIAL_HISTORY_HEADER)((PBYTE)pWriteMapping +
|
|
pMap->dwMapSize +
|
|
cbCredHistory -
|
|
sizeof(CREDENTIAL_HISTORY_HEADER));
|
|
|
|
|
|
//
|
|
// Write a fresh header into the cred history file
|
|
//
|
|
pHeader->dwPreviousCredOffset = cbCredHistory;
|
|
pHeader->dwVersion = CREDENTIAL_HISTORY_VERSION;
|
|
dwLastError = UuidCreate( &pHeader->CredentialID );
|
|
|
|
pMap->dwMapSize = dwLowFileSize;
|
|
|
|
//
|
|
// Flush and close write mapping
|
|
//
|
|
|
|
if(ERROR_SUCCESS == dwLastError)
|
|
{
|
|
if(!FlushViewOfFile(pWriteMapping, pMap->dwMapSize))
|
|
{
|
|
dwLastError = GetLastError();
|
|
}
|
|
}
|
|
|
|
UnmapViewOfFile(pWriteMapping);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
CloseHandle(hWriteMapping);
|
|
hWriteMapping = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Remap the read mapping to bump up the size
|
|
|
|
if(pMap->pMapping)
|
|
{
|
|
UnmapViewOfFile(pMap->pMapping);
|
|
pMap->pMapping = NULL;
|
|
}
|
|
|
|
|
|
|
|
if(pMap->hMapping)
|
|
{
|
|
CloseHandle(pMap->hMapping);
|
|
pMap->hMapping = NULL;
|
|
}
|
|
|
|
|
|
|
|
pMap->hMapping = CreateFileMapping(pMap->hHistoryFile,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
dwHighFileSize,
|
|
pMap->dwMapSize,
|
|
NULL);
|
|
|
|
if(NULL == pMap->hMapping)
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
pMap->pMapping = (PBYTE)MapViewOfFile(pMap->hMapping,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if(NULL == pMap->pMapping)
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
if(hWriteMapping)
|
|
{
|
|
CloseHandle(hWriteMapping);
|
|
}
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
DWORD
|
|
DPAPIChangePassword(
|
|
HANDLE hUserToken,
|
|
BYTE OldPasswordShaOWF[A_SHA_DIGEST_LEN],
|
|
BYTE OldPasswordNTOWF[A_SHA_DIGEST_LEN],
|
|
BYTE NewPasswordOWF[A_SHA_DIGEST_LEN])
|
|
{
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
|
|
PCREDENTIAL_HISTORY_MAP pMap = NULL;
|
|
PCREDENTIAL_HISTORY pHistory = NULL;
|
|
|
|
DWORD cbHistory = 0;
|
|
WCHAR wszUserSid[MAX_PATH+1];
|
|
DWORD cchUserSid = 0;
|
|
BYTE NewEncryptingCred[A_SHA_DIGEST_LEN];
|
|
WCHAR szProfilePath[MAX_PATH + 1];
|
|
|
|
HANDLE hOldUser = NULL;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPIChangePassword\n"));
|
|
|
|
|
|
//
|
|
// Get path to user's profile data. This will typically look something
|
|
// like "c:\Documents and Settings\<user>\Application Data".
|
|
//
|
|
|
|
dwLastError = PRGetProfilePath(hUserToken,
|
|
szProfilePath);
|
|
|
|
if( dwLastError != ERROR_SUCCESS )
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, "DPAPIChangePassword returned 0x%x\n", dwLastError));
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
//
|
|
// work-around the fact that much of this (new) code is not thread-safe.
|
|
//
|
|
|
|
RtlEnterCriticalSection(&g_csCredHistoryCache);
|
|
|
|
|
|
dwLastError = CreateCredentialHistoryMap(
|
|
hUserToken,
|
|
szProfilePath,
|
|
&pMap,
|
|
FALSE);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
|
|
//
|
|
// Get textual SID of user.
|
|
//
|
|
|
|
cchUserSid = MAX_PATH;
|
|
|
|
if(!GetUserTextualSid(hUserToken,
|
|
wszUserSid,
|
|
&cchUserSid))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
|
|
//
|
|
// Encrypt the credential history goo.
|
|
//
|
|
|
|
if(hUserToken)
|
|
{
|
|
if(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_IMPERSONATE | TOKEN_READ,
|
|
TRUE,
|
|
&hOldUser))
|
|
{
|
|
hOldUser = NULL;
|
|
}
|
|
|
|
if(!ImpersonateLoggedOnUser(hUserToken))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
DeriveWithHMAC_SHA1(NewPasswordOWF,
|
|
A_SHA_DIGEST_LEN,
|
|
(PBYTE)wszUserSid,
|
|
cchUserSid*sizeof(WCHAR),
|
|
NewEncryptingCred);
|
|
|
|
|
|
dwLastError = EncryptCredentialHistory(NewEncryptingCred,
|
|
USE_DPAPI_OWF,
|
|
OldPasswordShaOWF,
|
|
OldPasswordNTOWF,
|
|
&pHistory,
|
|
&cbHistory);
|
|
|
|
if(hOldUser)
|
|
{
|
|
//
|
|
// This code probably never gets executed. hOldUser should be NULL, as this routine
|
|
// looks like called in the SYSTEM context.
|
|
// Even if the hOldUser is not NULL, the failure of SetThreadToken should no prevent
|
|
// continue execution of this routine. Let's ignore the return value of SetThreadToken
|
|
// here.
|
|
//
|
|
|
|
(void) SetThreadToken(NULL, hOldUser);
|
|
}
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
|
|
//
|
|
// Update the CREDHIST file.
|
|
//
|
|
|
|
dwLastError = AppendCredentialHistoryMap(pMap, pHistory, cbHistory);
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
if(hOldUser)
|
|
{
|
|
CloseHandle(hOldUser);
|
|
hOldUser = NULL;
|
|
}
|
|
|
|
if(pMap)
|
|
{
|
|
CloseCredentialHistoryMap(pMap, FALSE);
|
|
}
|
|
|
|
RtlLeaveCriticalSection(&g_csCredHistoryCache);
|
|
|
|
if(pHistory)
|
|
{
|
|
RtlSecureZeroMemory(pHistory, cbHistory);
|
|
LocalFree(pHistory);
|
|
}
|
|
RtlSecureZeroMemory(NewEncryptingCred, A_SHA_DIGEST_LEN);
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPIChangePassword returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define SIGNATURE_SALT_SIZE (16)
|
|
#define CRED_SIGNATURE_VERSION 1
|
|
|
|
|
|
typedef struct _CRED_SIGNATURE
|
|
{
|
|
DWORD dwVersion;
|
|
GUID CredentialID;
|
|
DWORD cIterations;
|
|
BYTE Salt[SIGNATURE_SALT_SIZE];
|
|
DWORD cbSid;
|
|
DWORD cbSignature;
|
|
} CRED_SIGNATURE, *PCRED_SIGNATURE;
|
|
|
|
|
|
DWORD
|
|
LogonCredGenerateSignatureKey(
|
|
IN LUID *pLogonId,
|
|
IN DWORD dwFlags,
|
|
IN PBYTE pbCurrentOWF,
|
|
IN PCRED_SIGNATURE pSignature,
|
|
OUT BYTE rgbSignatureKey[A_SHA_DIGEST_LEN])
|
|
{
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
BYTE rgbDerivedCredential[A_SHA_DIGEST_LEN];
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredGenerateSignatureKey\n"));
|
|
|
|
if(NULL == pbCurrentOWF)
|
|
{
|
|
dwLastError = QueryDerivedCredential(&pSignature->CredentialID,
|
|
pLogonId,
|
|
dwFlags,
|
|
(PBYTE)(pSignature+1),
|
|
pSignature->cbSid,
|
|
rgbDerivedCredential);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// D_DebugLog((DEB_TRACE_BUFFERS, "Input CurrentOWF:\n"));
|
|
// D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pbCurrentOWF, A_SHA_DIGEST_LEN);
|
|
|
|
DeriveWithHMAC_SHA1(pbCurrentOWF,
|
|
A_SHA_DIGEST_LEN,
|
|
(PBYTE)(pSignature+1),
|
|
pSignature->cbSid,
|
|
rgbDerivedCredential);
|
|
|
|
|
|
if(dwFlags & USE_ROOT_CREDENTIAL)
|
|
{
|
|
ZeroMemory(&pSignature->CredentialID, sizeof(GUID));
|
|
}
|
|
|
|
}
|
|
|
|
// D_DebugLog((DEB_TRACE_BUFFERS, "Computed DerivedCredential:\n"));
|
|
// D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", rgbDerivedCredential, sizeof(rgbDerivedCredential));
|
|
|
|
if(!PKCS5DervivePBKDF2( rgbDerivedCredential,
|
|
A_SHA_DIGEST_LEN,
|
|
pSignature->Salt,
|
|
SIGNATURE_SALT_SIZE,
|
|
CALG_HMAC,
|
|
pSignature->cIterations,
|
|
1,
|
|
rgbSignatureKey))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
// D_DebugLog((DEB_TRACE_BUFFERS, "Computed SignatureKey:\n"));
|
|
// D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", rgbSignatureKey, A_SHA_DIGEST_LEN);
|
|
|
|
error:
|
|
|
|
RtlSecureZeroMemory(rgbDerivedCredential, A_SHA_DIGEST_LEN);
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredGenerateSignatureKey returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
DWORD
|
|
LogonCredGenerateSignature(
|
|
IN HANDLE hUserToken,
|
|
IN PBYTE pbData,
|
|
IN DWORD cbData,
|
|
IN PBYTE pbCurrentOWF,
|
|
OUT PBYTE *ppbSignature,
|
|
OUT DWORD *pcbSignature)
|
|
{
|
|
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
LUID LogonId;
|
|
|
|
PCRED_SIGNATURE pSignature = NULL;
|
|
DWORD cbSignature = 0;
|
|
BYTE rgbSignatureKey[A_SHA_DIGEST_LEN];
|
|
PSID pUserSid = NULL;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredGenerateSignature\n"));
|
|
|
|
cbSignature = sizeof(CRED_SIGNATURE);
|
|
|
|
if(!GetTokenAuthenticationId(hUserToken, &LogonId))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
if(!GetTokenUserSid(hUserToken, &pUserSid))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_BUFFERS, "User SID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", (PBYTE)pUserSid, GetLengthSid(pUserSid));
|
|
|
|
cbSignature += GetLengthSid(pUserSid);
|
|
|
|
cbSignature += A_SHA_DIGEST_LEN;
|
|
|
|
pSignature = (PCRED_SIGNATURE)LocalAlloc(LMEM_ZEROINIT, cbSignature);
|
|
|
|
if(NULL == pSignature)
|
|
{
|
|
dwLastError = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto error;
|
|
}
|
|
|
|
pSignature->cIterations = GetIterationCount();
|
|
|
|
|
|
pSignature->dwVersion = CRED_SIGNATURE_VERSION;
|
|
|
|
pSignature->cbSid = GetLengthSid(pUserSid);
|
|
|
|
|
|
pSignature->cbSignature = A_SHA_DIGEST_LEN;
|
|
|
|
CopyMemory((PBYTE)(pSignature+1),
|
|
pUserSid,
|
|
pSignature->cbSid);
|
|
|
|
if(!RtlGenRandom(pSignature->Salt, SIGNATURE_SALT_SIZE))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
// D_DebugLog((DEB_TRACE_BUFFERS, "Generated Salt:\n"));
|
|
// D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pSignature->Salt, SIGNATURE_SALT_SIZE);
|
|
|
|
|
|
dwLastError = LogonCredGenerateSignatureKey(&LogonId,
|
|
USE_ROOT_CREDENTIAL | USE_DPAPI_OWF,
|
|
pbCurrentOWF,
|
|
pSignature,
|
|
rgbSignatureKey);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbSignatureKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pbData,
|
|
cbData,
|
|
(PBYTE)(pSignature+1) + pSignature->cbSid))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_TRACE_BUFFERS, "Computed Signature:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", (PBYTE)(pSignature+1) + pSignature->cbSid, A_SHA_DIGEST_LEN);
|
|
|
|
*ppbSignature = (PBYTE)pSignature;
|
|
*pcbSignature = cbSignature;
|
|
pSignature = NULL;
|
|
|
|
}
|
|
|
|
error:
|
|
|
|
if(pSignature)
|
|
{
|
|
RtlSecureZeroMemory(pSignature, cbSignature);
|
|
LocalFree(pSignature);
|
|
}
|
|
|
|
if(pUserSid)
|
|
{
|
|
SSFree(pUserSid);
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredGenerateSignature returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
DWORD
|
|
LogonCredVerifySignature(
|
|
IN HANDLE hUserToken, // optional
|
|
IN PBYTE pbData,
|
|
IN DWORD cbData,
|
|
IN PBYTE pbCurrentOWF,
|
|
IN PBYTE pbSignature,
|
|
IN DWORD cbSignature)
|
|
{
|
|
DWORD dwLastError = ERROR_SUCCESS;
|
|
LUID LogonId;
|
|
HANDLE hOldUser = NULL;
|
|
BOOL fIsMember = FALSE;
|
|
|
|
PCRED_SIGNATURE pSignature = (PCRED_SIGNATURE)pbSignature;
|
|
|
|
BYTE rgbSignatureKey[A_SHA_DIGEST_LEN];
|
|
BYTE rgbSignatureHash[A_SHA_DIGEST_LEN];
|
|
PSID pUserSid = NULL;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredVerifySignature\n"));
|
|
|
|
if(hUserToken)
|
|
{
|
|
if(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_IMPERSONATE | TOKEN_READ,
|
|
TRUE,
|
|
&hOldUser))
|
|
{
|
|
hOldUser = NULL;
|
|
}
|
|
|
|
if(!ImpersonateLoggedOnUser(hUserToken))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
if(!GetThreadAuthenticationId(GetCurrentThread(), &LogonId))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Verify passed in credential
|
|
//
|
|
if((NULL == pSignature) ||
|
|
(sizeof(CRED_SIGNATURE) > cbSignature) ||
|
|
(pSignature->dwVersion != CRED_SIGNATURE_VERSION) ||
|
|
(pSignature->cbSid + pSignature->cbSignature + sizeof(CRED_SIGNATURE) > cbSignature) ||
|
|
(pSignature->cbSignature != A_SHA_DIGEST_LEN))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
if(!IsValidSid((PSID)(pSignature+1)))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
if(pSignature->cbSid != GetLengthSid((PSID)(pSignature+1)))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_BUFFERS, "User SID:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", (PBYTE)(pSignature+1), pSignature->cbSid);
|
|
|
|
if(!CheckTokenMembership( NULL,
|
|
(PSID)(pSignature+1),
|
|
&fIsMember ))
|
|
{
|
|
dwLastError = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
|
|
if(!fIsMember)
|
|
{
|
|
dwLastError = ERROR_INVALID_ACCESS;
|
|
goto error;
|
|
}
|
|
|
|
dwLastError = LogonCredGenerateSignatureKey(&LogonId,
|
|
USE_DPAPI_OWF,
|
|
pbCurrentOWF,
|
|
pSignature,
|
|
rgbSignatureKey);
|
|
|
|
if(ERROR_SUCCESS != dwLastError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if(!FMyPrimitiveHMACParam(
|
|
rgbSignatureKey,
|
|
A_SHA_DIGEST_LEN,
|
|
pbData,
|
|
cbData,
|
|
rgbSignatureHash))
|
|
{
|
|
dwLastError = ERROR_INVALID_DATA;
|
|
goto error;
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_BUFFERS, "Computed Signature:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", rgbSignatureHash, A_SHA_DIGEST_LEN);
|
|
|
|
D_DebugLog((DEB_TRACE_BUFFERS, "Input Signature:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", (PBYTE)(pSignature+1) + pSignature->cbSid, pSignature->cbSignature);
|
|
|
|
if(0 != memcmp(rgbSignatureHash, (PBYTE)(pSignature+1) + pSignature->cbSid, pSignature->cbSignature))
|
|
{
|
|
D_DebugLog((DEB_ERROR, "LogonCredVerifySignature: signature did not verify!\n"));
|
|
dwLastError = ERROR_INVALID_ACCESS;
|
|
goto error;
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
if(hOldUser)
|
|
{
|
|
//
|
|
// We have already done our job. SetThreadToken failure should be ignored here.
|
|
// Further, this code might never been executed. LogonCredVerifySignature() is called
|
|
// from two places. If hUserToken==NULL, -> hOldUser = NULL (the thread was impersonating)
|
|
// If hUserToken != NULL, the caller is in SYSTEM, OpenThreadToken would fail -> hOldUser == NULL.
|
|
// Either way, hOldUser should be NULL.
|
|
//
|
|
|
|
(void) SetThreadToken(NULL, hOldUser);
|
|
CloseHandle(hOldUser);
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "LogonCredVerifySignature returned 0x%x\n", dwLastError));
|
|
|
|
return dwLastError;
|
|
}
|
|
|
|
|
|
DWORD
|
|
DPAPINotifyPasswordChange(
|
|
IN PUNICODE_STRING NetbiosDomainName,
|
|
IN PUNICODE_STRING UserName,
|
|
IN PUNICODE_STRING OldPassword,
|
|
IN PUNICODE_STRING NewPassword)
|
|
{
|
|
DWORD Status = ERROR_SUCCESS;
|
|
BYTE OldPasswordShaOWF[A_SHA_DIGEST_LEN];
|
|
BYTE OldPasswordNTOWF[A_SHA_DIGEST_LEN];
|
|
BYTE NewPasswordOWF[A_SHA_DIGEST_LEN];
|
|
HANDLE hThreadToken = NULL;
|
|
HANDLE hUserToken = NULL;
|
|
|
|
PWSTR pszTargetName = NULL;
|
|
PWSTR pszCurrentName = NULL;
|
|
DWORD cchCurrentName;
|
|
|
|
PSID pUserSid = NULL;
|
|
PSID pCurrentSid = NULL;
|
|
DWORD cbSid;
|
|
SID_NAME_USE SidType;
|
|
PWSTR pszDomainName = NULL;
|
|
DWORD cchDomainName;
|
|
BOOL fSameUser = FALSE;
|
|
BOOL fLocalAccount = FALSE;
|
|
PROFILEINFOW ProfileInfo;
|
|
BOOL fProfileLoaded = FALSE;
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPINotifyPasswordChange\n"));
|
|
|
|
//
|
|
// Validate input parameters.
|
|
//
|
|
|
|
if((NetbiosDomainName == NULL) ||
|
|
(UserName == NULL) ||
|
|
(NewPassword == NULL))
|
|
{
|
|
Status = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(NetbiosDomainName->Buffer)
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, " Domain:%ls\n", NetbiosDomainName->Buffer));
|
|
}
|
|
if(UserName->Buffer)
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, " Username:%ls\n", UserName->Buffer));
|
|
}
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
if(OldPassword)
|
|
{
|
|
D_DebugLog((DEB_TRACE_API, " Old password:%ls\n", OldPassword->Buffer));
|
|
}
|
|
D_DebugLog((DEB_TRACE_API, " New password:%ls\n", NewPassword->Buffer));
|
|
#endif
|
|
|
|
|
|
//
|
|
// Save off the token for the current thread, and revert back to the
|
|
// local system account.
|
|
//
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_QUERY | TOKEN_IMPERSONATE,
|
|
FALSE,
|
|
&hThreadToken))
|
|
{
|
|
hThreadToken = NULL;
|
|
}
|
|
|
|
RevertToSelf();
|
|
|
|
|
|
//
|
|
// Get SID of user whose password is being changed.
|
|
//
|
|
|
|
cbSid = 0;
|
|
|
|
if(!LookupAccountName(NetbiosDomainName->Buffer,
|
|
UserName->Buffer,
|
|
NULL,
|
|
&cbSid,
|
|
NULL,
|
|
&cchDomainName,
|
|
&SidType))
|
|
{
|
|
Status = GetLastError();
|
|
|
|
if(Status != ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
pUserSid = LocalAlloc(LPTR, cbSid);
|
|
if(pUserSid == NULL)
|
|
{
|
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
pszDomainName = (PWSTR)LocalAlloc(LPTR, cchDomainName * sizeof(WCHAR));
|
|
if(pszDomainName == NULL)
|
|
{
|
|
Status = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
if(!LookupAccountName(NetbiosDomainName->Buffer,
|
|
UserName->Buffer,
|
|
pUserSid,
|
|
&cbSid,
|
|
pszDomainName,
|
|
&cchDomainName,
|
|
&SidType))
|
|
{
|
|
Status = GetLastError();
|
|
|
|
if(Status != ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Determine if we're already logged on as the user whose password
|
|
// is being changed. If we are, then we can skip loading the user
|
|
// profile, etc.
|
|
//
|
|
|
|
if(hThreadToken != NULL)
|
|
{
|
|
if(!GetTokenUserSid(hThreadToken, &pCurrentSid))
|
|
{
|
|
Status = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if(EqualSid(pCurrentSid, pUserSid))
|
|
{
|
|
fSameUser = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Create logon token for user whose password is being changed.
|
|
//
|
|
|
|
if(fSameUser)
|
|
{
|
|
hUserToken = hThreadToken;
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Logging on as user whose password is being changed.\n"));
|
|
|
|
if(!LogonUser(UserName->Buffer,
|
|
NetbiosDomainName->Buffer,
|
|
NewPassword->Buffer,
|
|
LOGON32_LOGON_INTERACTIVE,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hUserToken))
|
|
{
|
|
Status = GetLastError();
|
|
D_DebugLog((DEB_ERROR, "Unable to log on as user whose password is being changed (0x%x).\n", Status));
|
|
goto cleanup;
|
|
}
|
|
|
|
memset(&ProfileInfo, 0, sizeof(ProfileInfo));
|
|
ProfileInfo.dwSize = sizeof(ProfileInfo);
|
|
ProfileInfo.dwFlags = PI_NOUI;
|
|
ProfileInfo.lpUserName = UserName->Buffer;
|
|
|
|
if(!LoadUserProfileW(hUserToken, &ProfileInfo))
|
|
{
|
|
Status = GetLastError();
|
|
D_DebugLog((DEB_ERROR, "Error loading user profile (0x%x).\n", Status));
|
|
goto cleanup;
|
|
}
|
|
|
|
fProfileLoaded = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Is this a local account?
|
|
//
|
|
|
|
if(NetbiosDomainName->Buffer)
|
|
{
|
|
WCHAR szMachineName[MAX_COMPUTERNAME_LENGTH + 1];
|
|
DWORD cchMachineName;
|
|
|
|
cchMachineName = MAX_COMPUTERNAME_LENGTH + 1;
|
|
|
|
if(!GetComputerName(szMachineName, &cchMachineName))
|
|
{
|
|
Status = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (CSTR_EQUAL == CompareString(
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
NetbiosDomainName->Buffer,
|
|
-1, // cchCount1
|
|
szMachineName,
|
|
-1 // cchCount2
|
|
))
|
|
{
|
|
fLocalAccount = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
if(fLocalAccount)
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Local account\n"));
|
|
|
|
if(OldPassword == NULL)
|
|
{
|
|
Status = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Compute hashes of old and new passwords.
|
|
//
|
|
|
|
ZeroMemory(OldPasswordShaOWF, A_SHA_DIGEST_LEN);
|
|
ZeroMemory(OldPasswordNTOWF, A_SHA_DIGEST_LEN);
|
|
ZeroMemory(NewPasswordOWF, A_SHA_DIGEST_LEN);
|
|
|
|
FMyPrimitiveSHA(
|
|
(PBYTE)OldPassword->Buffer,
|
|
OldPassword->Length,
|
|
OldPasswordShaOWF);
|
|
|
|
Status = RtlCalculateNtOwfPassword(
|
|
OldPassword,
|
|
(PLM_OWF_PASSWORD)OldPasswordNTOWF);
|
|
|
|
if(Status != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
FMyPrimitiveSHA(
|
|
(PBYTE)NewPassword->Buffer,
|
|
NewPassword->Length,
|
|
NewPasswordOWF);
|
|
|
|
#ifdef COMPILED_BY_DEVELOPER
|
|
D_DebugLog((DEB_TRACE, " Old password SHA OWF:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", OldPasswordShaOWF, A_SHA_DIGEST_LEN);
|
|
D_DebugLog((DEB_TRACE, " Old password NT OWF:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", OldPasswordNTOWF, A_SHA_DIGEST_LEN);
|
|
D_DebugLog((DEB_TRACE, " New password SHA OWF:\n"));
|
|
D_DPAPIDumpHexData(DEB_TRACE, " ", NewPasswordOWF, A_SHA_DIGEST_LEN);
|
|
#endif
|
|
|
|
|
|
//
|
|
// Encrypt the CREDHIST file with the new password and append the new password to
|
|
// the end of the file.
|
|
//
|
|
|
|
Status = DPAPIChangePassword(hUserToken,
|
|
OldPasswordShaOWF,
|
|
OldPasswordNTOWF,
|
|
NewPasswordOWF);
|
|
|
|
if(Status != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Re-synchronize the master keys.
|
|
//
|
|
|
|
DPAPISynchronizeMasterKeys(hUserToken);
|
|
|
|
|
|
//
|
|
// Encrypt the new password with the recovery public key, and store it
|
|
// in the RECOVERY file. This will allow us to recover the password using the
|
|
// recovery floppy, should the user forget it.
|
|
//
|
|
|
|
Status = RecoverChangePasswordNotify(hUserToken,
|
|
OldPasswordShaOWF,
|
|
NewPassword);
|
|
if(Status != ERROR_SUCCESS)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
D_DebugLog((DEB_TRACE, "Domain account\n"));
|
|
|
|
//
|
|
// Re-synchronize the master keys.
|
|
//
|
|
|
|
DPAPISynchronizeMasterKeys(hUserToken);
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
|
if(pUserSid) LocalFree(pUserSid);
|
|
if(pCurrentSid) LocalFree(pCurrentSid);
|
|
if(pszDomainName) LocalFree(pszDomainName);
|
|
|
|
if(pszTargetName) LocalFree(pszTargetName);
|
|
if(pszCurrentName) LocalFree(pszCurrentName);
|
|
|
|
if(hUserToken && fProfileLoaded)
|
|
{
|
|
UnloadUserProfile(hUserToken, ProfileInfo.hProfile);
|
|
}
|
|
|
|
if(hUserToken && (hUserToken != hThreadToken))
|
|
{
|
|
CloseHandle(hUserToken);
|
|
}
|
|
|
|
if(hThreadToken)
|
|
{
|
|
if(!ImpersonateLoggedOnUser(hThreadToken))
|
|
{
|
|
// Unable to impersonate user.
|
|
if(Status == ERROR_SUCCESS)
|
|
{
|
|
Status = GetLastError();
|
|
}
|
|
}
|
|
CloseHandle(hThreadToken);
|
|
}
|
|
|
|
D_DebugLog((DEB_TRACE_API, "DPAPINotifyPasswordChange returned 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|