Add reusable certificate and file validation helpers

Fix process-name ownership and module lookup outputs, add explicit SPKI hashing and bounded chain access, and improve WDM integration. Deprecate superseded entry points with migration guidance and align branch code and tests with the library style.
This commit is contained in:
nefarius committed 2026-10-27 00:16:42 +01:00
1 parent 1f4d62ae95
commit a491bbd3f8
32 files changed
+5404 -1136

No files matched your search

+421 -117
View File
@@ -15,81 +15,207 @@
#define PAGED_CODE() ((void)0)
typedef void* PFILE_OBJECT;
typedef void* LPWIN_CERTIFICATE;
struct MINCRYPT_POLICY_INFO {
struct MINCRYPT_POLICY_INFO
{
ULONG Size;
NTSTATUS VerificationStatus;
ULONG PolicyBits;
void* ChainInfo;
};
#include "ci-validation-api.inc"
static unsigned checks, live, validationCalls, embeddedCalls, digestCalls, digestFrees;
static NTSTATUS nativeStatus = STATUS_SUCCESS, embeddedStatus = STATUS_SUCCESS, digestStatus = STATUS_SUCCESS;
static UINT32 nativeAlgorithm = CALG_SHA_256, nativeSize = 32;
static unsigned checks;
static unsigned live;
static unsigned validationCalls;
static unsigned embeddedCalls;
static unsigned digestCalls;
static unsigned digestFrees;
static NTSTATUS nativeStatus = STATUS_SUCCESS;
static NTSTATUS embeddedStatus = STATUS_SUCCESS;
static NTSTATUS digestStatus = STATUS_SUCCESS;
static UINT32 nativeAlgorithm = CALG_SHA_256;
static UINT32 nativeSize = 32;
static ULONG fakeDigestSize = 32;
static bool omitCertificate, omitDigest, partialDigestOnFailure;
static const UCHAR image[] = {1,2,3};
static ULONG observedFlags, observedLevel, observedCapacity;
static bool omitCertificate;
static bool omitDigest;
static bool partialDigestOnFailure;
static const UCHAR image[] = {1, 2, 3};
static ULONG observedFlags;
static ULONG observedLevel;
static ULONG observedCapacity;
static PFILE_OBJECT observedObject;
static ALG_ID observedEmbeddedAlgorithm;
static ULONG observedSecuritySize;
static void* observedCertificate;
static void Check(bool value, const char* message) {
static void
Check(
bool value,
const char* message
)
{
++checks;
if (!value) { std::fprintf(stderr, "FAIL: %s\n", message); std::exit(1); }
if (!value)
{
std::fprintf(stderr, "FAIL: %s\n", message);
std::exit(1);
}
}
static void* Allocate(SIZE_T size) {
static void*
Allocate(
SIZE_T size
)
{
void* value = std::malloc(size);
Check(value != nullptr, "host allocation"); ++live;
Check(value != nullptr, "host allocation");
++live;
return value;
}
static void Release(void* value) { Check(value && live, "single allocation owner"); --live; std::free(value); }
static void CiFreePolicyInfo(MINCRYPT_POLICY_INFO* policy) {
if (policy->ChainInfo) Release(policy->ChainInfo);
static void
Release(
void* value
)
{
Check(value && live, "single allocation owner");
--live;
std::free(value);
}
static void
CiFreePolicyInfo(
MINCRYPT_POLICY_INFO* policy
)
{
if (policy->ChainInfo)
{
Release(policy->ChainInfo);
}
*policy = {};
}
static void Populate(MINCRYPT_POLICY_INFO* policy, MINCRYPT_POLICY_INFO* timestamp) {
static void
Populate(
MINCRYPT_POLICY_INFO* policy,
MINCRYPT_POLICY_INFO* timestamp
)
{
Check(!policy->ChainInfo && !timestamp->ChainInfo, "old result freed before reuse");
policy->Size = timestamp->Size = sizeof(*policy);
policy->ChainInfo = Allocate(1); timestamp->ChainInfo = Allocate(1);
policy->PolicyBits = 0x123; timestamp->PolicyBits = 0x456;
policy->ChainInfo = Allocate(1);
timestamp->ChainInfo = Allocate(1);
policy->PolicyBits = 0x123;
timestamp->PolicyBits = 0x456;
policy->VerificationStatus = STATUS_INVALID_IMAGE_HASH;
}
static NTSTATUS CiValidateFileObject(PFILE_OBJECT object, UINT32 flags, UINT32 level,
MINCRYPT_POLICY_INFO* policy, MINCRYPT_POLICY_INFO* timestamp, LARGE_INTEGER* time,
UINT8* hash, UINT32* size, ALG_ID* algorithm) {
static NTSTATUS
CiValidateFileObject(
PFILE_OBJECT object,
UINT32 flags,
UINT32 level,
MINCRYPT_POLICY_INFO* policy,
MINCRYPT_POLICY_INFO* timestamp,
LARGE_INTEGER* time,
UINT8* hash,
UINT32* size,
ALG_ID* algorithm
)
{
++validationCalls;
observedObject = object; observedFlags = flags; observedLevel = level; observedCapacity = *size;
observedObject = object;
observedFlags = flags;
observedLevel = level;
observedCapacity = *size;
Check(*size == 64, "fresh native hash capacity");
Check(!time->QuadPart && !*algorithm, "native scalar outputs initialized");
for (unsigned i=0;i<64;++i) Check(!hash[i], "native digest starts empty");
Populate(policy, timestamp); time->QuadPart = 99;
std::memset(hash, 0x5a, 64); *size = nativeSize; *algorithm = nativeAlgorithm;
for (unsigned i = 0; i < 64; ++i)
{
Check(!hash[i], "native digest starts empty");
}
Populate(policy, timestamp);
time->QuadPart = 99;
std::memset(hash, 0x5a, 64);
*size = nativeSize;
*algorithm = nativeAlgorithm;
return nativeStatus;
}
static NTSTATUS DomitoCalculatePortableExecutableDigestEx(const UCHAR* bytes, ULONG size, UINT32 algorithm,
PULONG digestSize, PVOID* digest, LPWIN_CERTIFICATE* certificate, PULONG securitySize) {
static NTSTATUS
DomitoCalculatePortableExecutableDigestEx(
const UCHAR* bytes,
ULONG size,
UINT32 algorithm,
PULONG digestSize,
PVOID* digest,
LPWIN_CERTIFICATE* certificate,
PULONG securitySize
)
{
++digestCalls;
Check(bytes == image && size == sizeof(image), "same PE snapshot forwarded");
Check(algorithm == CALG_SHA1 || algorithm == CALG_SHA_256 || algorithm == CALG_SHA_384 || algorithm == CALG_SHA_512,
"supported digest choice forwarded");
if (NT_SUCCESS(digestStatus) || partialDigestOnFailure) {
Check(
algorithm == CALG_SHA1 || algorithm == CALG_SHA_256 || algorithm == CALG_SHA_384 || algorithm == CALG_SHA_512,
"supported digest choice forwarded"
);
if (NT_SUCCESS(digestStatus) || partialDigestOnFailure)
{
*digest = omitDigest ? nullptr : Allocate(64);
if (*digest) std::memset(*digest, 0xa5, 64);
if (*digest)
{
std::memset(*digest, 0xa5, 64);
}
*digestSize = fakeDigestSize;
*certificate = omitCertificate ? nullptr : const_cast<UCHAR*>(image);
*securitySize = omitCertificate ? 0 : 123;
}
return digestStatus;
}
static void DomitoFreePortableExecutableDigest(PVOID digest) { ++digestFrees; Release(digest); }
static NTSTATUS CiCheckSignedFile(PVOID hash, UINT32 size, ALG_ID algorithm, LPWIN_CERTIFICATE certificate,
ULONG securitySize, MINCRYPT_POLICY_INFO* policy, LARGE_INTEGER* time, MINCRYPT_POLICY_INFO* timestamp) {
static void
DomitoFreePortableExecutableDigest(
PVOID digest
)
{
++digestFrees;
Release(digest);
}
static NTSTATUS
CiCheckSignedFile(
PVOID hash,
UINT32 size,
ALG_ID algorithm,
LPWIN_CERTIFICATE certificate,
ULONG securitySize,
MINCRYPT_POLICY_INFO* policy,
LARGE_INTEGER* time,
MINCRYPT_POLICY_INFO* timestamp
)
{
++embeddedCalls;
observedEmbeddedAlgorithm = algorithm; observedSecuritySize = securitySize; observedCertificate = certificate;
for (UINT32 i=0;i<size;++i) Check(static_cast<UCHAR*>(hash)[i] == 0xa5, "computed digest forwarded unchanged");
Populate(policy, timestamp); time->QuadPart = 123;
observedEmbeddedAlgorithm = algorithm;
observedSecuritySize = securitySize;
observedCertificate = certificate;
for (UINT32 i = 0; i < size; ++i)
{
Check(static_cast<UCHAR*>(hash)[i] == 0xa5, "computed digest forwarded unchanged");
}
Populate(policy, timestamp);
time->QuadPart = 123;
return embeddedStatus;
}
@@ -99,113 +225,291 @@ static NTSTATUS CiCheckSignedFile(PVOID hash, UINT32 size, ALG_ID algorithm, LPW
#endif
#include "ci-validation-under-test.inc"
static void CheckEmptyDigest(const DOMITO_CI_VALIDATION_RESULT& result) {
static void
CheckEmptyDigest(
const DOMITO_CI_VALIDATION_RESULT& result
)
{
Check(!result.HashSize && !result.HashAlgId && !result.SigningTime.QuadPart, "failure digest/time cleared");
for (auto value : result.Hash) Check(!value, "failure hash bytes cleared");
for (auto value : result.Hash)
{
Check(!value, "failure hash bytes cleared");
}
}
static void Clear(DOMITO_CI_VALIDATION_RESULT* result) {
static void
Clear(
DOMITO_CI_VALIDATION_RESULT* result
)
{
DomitoFreeCiValidationResult(result);
DOMITO_CI_VALIDATION_RESULT empty = {};
Check(std::memcmp(result, &empty, sizeof(empty)) == 0, "full result cleared by destructor");
Check(live == 0, "no leaked policy/digest allocation");
}
int main() {
int
main()
{
DOMITO_CI_VALIDATION_RESULT result = {};
PFILE_OBJECT object = reinterpret_cast<PFILE_OBJECT>(static_cast<ULONG_PTR>(0x1234));
DomitoFreeCiValidationResult(nullptr); Clear(&result); Clear(&result);
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,nullptr)==STATUS_INVALID_PARAMETER, "null native result");
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,nullptr)==STATUS_INVALID_PARAMETER, "null embedded result");
DomitoFreeCiValidationResult(nullptr);
Clear(&result);
Clear(&result);
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, nullptr) == STATUS_INVALID_PARAMETER,
"null native result"
);
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, nullptr) ==
STATUS_INVALID_PARAMETER,
"null embedded result"
);
Check(!validationCalls && !embeddedCalls && !digestCalls, "null result made no validation calls");
for (UINT32 flags : {0UL,0x20UL,0xa0UL,0x80000024UL}) {
for (UINT8 level : {UINT8(0),UINT8(7),UINT8(255)}) {
result.HashSize=1; result.HashAlgId=0xdead;
Check(DomitoValidateFileObjectEx(object,flags,level,DomitoCiSignatureSelectionNative,&result)==STATUS_SUCCESS,"native status");
Check(observedObject==object && observedFlags==flags && observedLevel==level && observedCapacity==64,"native inputs unchanged");
Check(result.FileObjectValidationFlags==flags && result.RequestedSigningLevel==level,"export input controls recorded");
Check(result.HashSize==32 && result.HashAlgId==CALG_SHA_256 && result.Hash[0]==0x5a && result.SigningTime.QuadPart==99,"native result");
for (UINT32 flags : {0UL, 0x20UL, 0xa0UL, 0x80000024UL})
{
for (UINT8 level : {UINT8(0), UINT8(7), UINT8(255)})
{
result.HashSize = 1;
result.HashAlgId = 0xdead;
Check(
DomitoValidateFileObjectEx(object, flags, level, DomitoCiSignatureSelectionNative, &result) ==
STATUS_SUCCESS,
"native status"
);
Check(
observedObject == object && observedFlags == flags && observedLevel == level && observedCapacity == 64,
"native inputs unchanged"
);
Check(
result.FileObjectValidationFlags == flags && result.RequestedSigningLevel == level,
"export input controls recorded"
);
Check(
result.HashSize == 32 && result.HashAlgId == CALG_SHA_256 && result.Hash[0] == 0x5a &&
result.SigningTime.QuadPart == 99,
"native result"
);
// API return success does not silently reinterpret a policy status.
Check(result.PolicyInfo.VerificationStatus==STATUS_INVALID_IMAGE_HASH,"policy observation retained");
Check(result.PolicyInfo.VerificationStatus == STATUS_INVALID_IMAGE_HASH, "policy observation retained");
Clear(&result);
}
}
const unsigned beforeExperimental=validationCalls;
NTSTATUS experimental=DomitoValidateFileObjectEx(object,0x20,9,DomitoCiSignatureSelectionFirstSuccessfulExperimental,&result);
const unsigned beforeExperimental = validationCalls;
NTSTATUS experimental =
DomitoValidateFileObjectEx(object, 0x20, 9, DomitoCiSignatureSelectionFirstSuccessfulExperimental, &result);
#ifdef DOMITO_TEST_OTHER_ARCH
Check(experimental==STATUS_NOT_SUPPORTED && validationCalls==beforeExperimental,"experimental mode excluded on non-x64");
Check(
experimental == STATUS_NOT_SUPPORTED && validationCalls == beforeExperimental,
"experimental mode excluded on non-x64"
);
CheckEmptyDigest(result);
#else
Check(experimental==STATUS_SUCCESS && validationCalls==beforeExperimental+1,"experimental native call");
Check(observedFlags==0xa0 && observedLevel==9,"only experimental bit added");
Check(experimental == STATUS_SUCCESS && validationCalls == beforeExperimental + 1, "experimental native call");
Check(observedFlags == 0xa0 && observedLevel == 9, "only experimental bit added");
#endif
Clear(&result);
const unsigned beforeInvalid=validationCalls;
Check(DomitoValidateFileObjectEx(nullptr,0x20,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_PARAMETER,"null file object");
Check(DomitoValidateFileObjectEx(object,0x20,0,static_cast<DOMITO_CI_SIGNATURE_SELECTION>(-1),&result)==STATUS_INVALID_PARAMETER,"invalid selection");
Check(validationCalls==beforeInvalid,"invalid requests never call validation"); Clear(&result);
for (NTSTATUS failure : {STATUS_INVALID_IMAGE_HASH,STATUS_BUFFER_TOO_SMALL,STATUS_INSUFFICIENT_RESOURCES}) {
nativeStatus=failure;
Check(DomitoValidateFileObjectEx(object,0x20,0,DomitoCiSignatureSelectionNative,&result)==failure,"native failure preserved");
CheckEmptyDigest(result); Check(live==2 && result.PolicyInfo.PolicyBits==0x123,"failure policy diagnostics remain owned"); Clear(&result);
}
nativeStatus=STATUS_SUCCESS;
nativeSize=65;
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_BUFFER_SIZE,"oversized returned hash rejected");
CheckEmptyDigest(result); Clear(&result);
nativeSize=31;
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_BUFFER_SIZE,"algorithm length mismatch rejected"); Clear(&result);
nativeSize=32; nativeAlgorithm=0xdead;
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_NOT_SUPPORTED,"unknown returned algorithm rejected"); Clear(&result);
for (ALG_ID algorithm : {CALG_SHA1,CALG_SHA_256,CALG_SHA_384,CALG_SHA_512}) {
nativeAlgorithm=algorithm; nativeSize=DomitoCiDigestSize(algorithm);
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_SUCCESS,"known native digest sizes");
for (UINT32 i=result.HashSize;i<sizeof(result.Hash);++i) Check(!result.Hash[i],"unused native digest bytes cleared");
const unsigned beforeInvalid = validationCalls;
Check(
DomitoValidateFileObjectEx(nullptr, 0x20, 0, DomitoCiSignatureSelectionNative, &result) ==
STATUS_INVALID_PARAMETER,
"null file object"
);
Check(
DomitoValidateFileObjectEx(object, 0x20, 0, static_cast<DOMITO_CI_SIGNATURE_SELECTION>(-1), &result) ==
STATUS_INVALID_PARAMETER,
"invalid selection"
);
Check(validationCalls == beforeInvalid, "invalid requests never call validation");
Clear(&result);
for (NTSTATUS failure : {STATUS_INVALID_IMAGE_HASH, STATUS_BUFFER_TOO_SMALL, STATUS_INSUFFICIENT_RESOURCES})
{
nativeStatus = failure;
Check(
DomitoValidateFileObjectEx(object, 0x20, 0, DomitoCiSignatureSelectionNative, &result) == failure,
"native failure preserved"
);
CheckEmptyDigest(result);
Check(live == 2 && result.PolicyInfo.PolicyBits == 0x123, "failure policy diagnostics remain owned");
Clear(&result);
fakeDigestSize=DomitoCiDigestSize(algorithm);
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),algorithm,&result)==STATUS_SUCCESS,"embedded success");
Check(observedEmbeddedAlgorithm==algorithm && observedSecuritySize==123 && observedCertificate==image,"embedded certificate/algorithm forwarded");
Check(result.HashSize==fakeDigestSize && result.HashAlgId==algorithm && result.Hash[0]==0xa5 && result.SigningTime.QuadPart==123,"embedded result owns copied digest");
Check(!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live==2,"embedded metadata and digest cleanup"); Clear(&result);
}
nativeAlgorithm=CALG_SHA_256; nativeSize=32; fakeDigestSize=32;
nativeStatus=STATUS_MORE_ENTRIES;
Check(DomitoValidateFileObjectEx(object,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_MORE_ENTRIES,"nonnegative native status preserved");
Clear(&result); nativeStatus=STATUS_SUCCESS;
Check(DomitoValidatePortableExecutableEmbeddedSignature(nullptr,0,CALG_SHA_256,&result)==STATUS_INVALID_PARAMETER,"null PE");
unsigned beforeDigest=digestCalls;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),0xdead,&result)==STATUS_NOT_SUPPORTED,"unsupported algorithm");
Check(digestCalls==beforeDigest,"unsupported algorithm does not hash");
for (NTSTATUS failure : {STATUS_NOT_SUPPORTED,STATUS_INVALID_IMAGE_FORMAT,STATUS_INSUFFICIENT_RESOURCES}) {
digestStatus=failure;
unsigned beforeEmbedded=embeddedCalls;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==failure,"digest failure propagation");
Check(embeddedCalls==beforeEmbedded,"digest failure never calls CI"); CheckEmptyDigest(result); Clear(&result);
nativeStatus = STATUS_SUCCESS;
nativeSize = 65;
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
STATUS_INVALID_BUFFER_SIZE,
"oversized returned hash rejected"
);
CheckEmptyDigest(result);
Clear(&result);
nativeSize = 31;
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
STATUS_INVALID_BUFFER_SIZE,
"algorithm length mismatch rejected"
);
Clear(&result);
nativeSize = 32;
nativeAlgorithm = 0xdead;
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_NOT_SUPPORTED,
"unknown returned algorithm rejected"
);
Clear(&result);
for (ALG_ID algorithm : {CALG_SHA1, CALG_SHA_256, CALG_SHA_384, CALG_SHA_512})
{
nativeAlgorithm = algorithm;
nativeSize = DomitoCiDigestSize(algorithm);
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_SUCCESS,
"known native digest sizes"
);
for (UINT32 i = result.HashSize; i < sizeof(result.Hash); ++i)
{
Check(!result.Hash[i], "unused native digest bytes cleared");
}
Clear(&result);
fakeDigestSize = DomitoCiDigestSize(algorithm);
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), algorithm, &result) ==
STATUS_SUCCESS,
"embedded success"
);
Check(
observedEmbeddedAlgorithm == algorithm && observedSecuritySize == 123 && observedCertificate == image,
"embedded certificate/algorithm forwarded"
);
Check(
result.HashSize == fakeDigestSize && result.HashAlgId == algorithm && result.Hash[0] == 0xa5 &&
result.SigningTime.QuadPart == 123,
"embedded result owns copied digest"
);
Check(
!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live == 2,
"embedded metadata and digest cleanup"
);
Clear(&result);
}
partialDigestOnFailure=true;
unsigned beforeFree=digestFrees;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_INSUFFICIENT_RESOURCES,"partial digest failure");
Check(digestFrees==beforeFree+1,"partial digest allocation freed"); Clear(&result);
partialDigestOnFailure=false; digestStatus=STATUS_SUCCESS;
omitCertificate=true;
unsigned beforeEmbedded=embeddedCalls;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_INVALID_IMAGE_HASH,"unsigned PE rejected");
Check(embeddedCalls==beforeEmbedded,"unsigned PE does not call CI"); Clear(&result); omitCertificate=false;
omitDigest=true;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_INVALID_BUFFER_SIZE,"missing digest rejected"); Clear(&result); omitDigest=false;
fakeDigestSize=31;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_INVALID_BUFFER_SIZE,"digest helper length mismatch"); Clear(&result); fakeDigestSize=32;
embeddedStatus=STATUS_INVALID_IMAGE_HASH;
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_INVALID_IMAGE_HASH,"CI embedded failure propagated without fallback");
CheckEmptyDigest(result); Check(live==2 && result.TimeStampPolicyInfo.PolicyBits==0x456,"embedded failure diagnostics owned"); Clear(&result); embeddedStatus=STATUS_SUCCESS;
for (unsigned i=0;i<16;++i) {
Check(DomitoValidateFileObjectEx(object,0x20,0,DomitoCiSignatureSelectionNative,&result)==STATUS_SUCCESS,"repeated native");
Check(DomitoValidatePortableExecutableEmbeddedSignature(image,sizeof(image),CALG_SHA_256,&result)==STATUS_SUCCESS,"reuse native result as embedded result");
Check(!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live==2,"reuse resets metadata without leaking");
nativeAlgorithm = CALG_SHA_256;
nativeSize = 32;
fakeDigestSize = 32;
nativeStatus = STATUS_MORE_ENTRIES;
Check(
DomitoValidateFileObjectEx(object, 0, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_MORE_ENTRIES,
"nonnegative native status preserved"
);
Clear(&result);
nativeStatus = STATUS_SUCCESS;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(nullptr, 0, CALG_SHA_256, &result) ==
STATUS_INVALID_PARAMETER,
"null PE"
);
unsigned beforeDigest = digestCalls;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), 0xdead, &result) ==
STATUS_NOT_SUPPORTED,
"unsupported algorithm"
);
Check(digestCalls == beforeDigest, "unsupported algorithm does not hash");
for (NTSTATUS failure : {STATUS_NOT_SUPPORTED, STATUS_INVALID_IMAGE_FORMAT, STATUS_INSUFFICIENT_RESOURCES})
{
digestStatus = failure;
unsigned beforeEmbedded = embeddedCalls;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) == failure,
"digest failure propagation"
);
Check(embeddedCalls == beforeEmbedded, "digest failure never calls CI");
CheckEmptyDigest(result);
Clear(&result);
}
partialDigestOnFailure = true;
unsigned beforeFree = digestFrees;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_INSUFFICIENT_RESOURCES,
"partial digest failure"
);
Check(digestFrees == beforeFree + 1, "partial digest allocation freed");
Clear(&result);
partialDigestOnFailure = false;
digestStatus = STATUS_SUCCESS;
omitCertificate = true;
unsigned beforeEmbedded = embeddedCalls;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_INVALID_IMAGE_HASH,
"unsigned PE rejected"
);
Check(embeddedCalls == beforeEmbedded, "unsigned PE does not call CI");
Clear(&result);
omitCertificate = false;
omitDigest = true;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_INVALID_BUFFER_SIZE,
"missing digest rejected"
);
Clear(&result);
omitDigest = false;
fakeDigestSize = 31;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_INVALID_BUFFER_SIZE,
"digest helper length mismatch"
);
Clear(&result);
fakeDigestSize = 32;
embeddedStatus = STATUS_INVALID_IMAGE_HASH;
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_INVALID_IMAGE_HASH,
"CI embedded failure propagated without fallback"
);
CheckEmptyDigest(result);
Check(live == 2 && result.TimeStampPolicyInfo.PolicyBits == 0x456, "embedded failure diagnostics owned");
Clear(&result);
embeddedStatus = STATUS_SUCCESS;
for (unsigned i = 0; i < 16; ++i)
{
Check(
DomitoValidateFileObjectEx(object, 0x20, 0, DomitoCiSignatureSelectionNative, &result) == STATUS_SUCCESS,
"repeated native"
);
Check(
DomitoValidatePortableExecutableEmbeddedSignature(image, sizeof(image), CALG_SHA_256, &result) ==
STATUS_SUCCESS,
"reuse native result as embedded result"
);
Check(
!result.FileObjectValidationFlags && !result.RequestedSigningLevel && live == 2,
"reuse resets metadata without leaking"
);
}
// Invalid calls must release a previously valid result, too.
Check(DomitoValidateFileObjectEx(nullptr,0,0,DomitoCiSignatureSelectionNative,&result)==STATUS_INVALID_PARAMETER,"invalid reuse cleans old result"); Clear(&result);
std::printf("PASS: %u CI validation checks (%s architecture branch), no live allocations\n",checks,
Check(
DomitoValidateFileObjectEx(nullptr, 0, 0, DomitoCiSignatureSelectionNative, &result) ==
STATUS_INVALID_PARAMETER,
"invalid reuse cleans old result"
);
Clear(&result);
std::printf(
"PASS: %u CI validation checks (%s architecture branch), no live allocations\n",
checks,
#ifdef DOMITO_TEST_OTHER_ARCH
"non-x64"
#else