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

+105 -26
View File
@@ -10,34 +10,76 @@
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
#define DOMITO_CI_PROBE_HOST_TEST
typedef void* PFILE_OBJECT;
struct MINCRYPT_POLICY_INFO {
ULONG Size, VerificationStatus, PolicyBits;
struct MINCRYPT_POLICY_INFO
{
ULONG Size;
ULONG VerificationStatus;
ULONG PolicyBits;
void* ChainInfo;
};
static unsigned calls, frees, outstanding, checks;
static unsigned calls;
static unsigned frees;
static unsigned outstanding;
static unsigned checks;
static bool failureMode;
static int fileToken, chainToken;
static void Check(bool value, const char* label)
static int fileToken;
static int chainToken;
static void
Check(
bool value,
const char* label
)
{
++checks;
if (!value) { std::fprintf(stderr, "FAIL: %s\n", label); std::exit(1); }
if (!value)
{
std::fprintf(stderr, "FAIL: %s\n", label);
std::exit(1);
}
}
static NTSTATUS CiValidateFileObject(PFILE_OBJECT file, ULONG flags, ULONG level,
MINCRYPT_POLICY_INFO* policy, MINCRYPT_POLICY_INFO* timestamp,
LARGE_INTEGER* time, UCHAR* hash, UINT32* size, ALG_ID* algorithm)
static NTSTATUS
CiValidateFileObject(
PFILE_OBJECT file,
ULONG flags,
ULONG level,
MINCRYPT_POLICY_INFO* policy,
MINCRYPT_POLICY_INFO* timestamp,
LARGE_INTEGER* time,
UCHAR* hash,
UINT32* size,
ALG_ID* algorithm
)
{
const ULONG expectedFlags[] = {0, 0x20, 0x20, 0, 0, 0x20};
Check(calls < 6 && flags == expectedFlags[calls], "paired flags and order");
Check(file == &fileToken && level == 0, "same file and level zero");
Check(outstanding == 0, "previous outputs freed before next call");
Check(policy->Size == sizeof(*policy) && timestamp->Size == sizeof(*timestamp), "policy sizes");
Check(!policy->ChainInfo && !timestamp->ChainInfo && !policy->PolicyBits &&
!timestamp->PolicyBits, "fresh policy objects");
Check(
!policy->ChainInfo && !timestamp->ChainInfo && !policy->PolicyBits && !timestamp->PolicyBits,
"fresh policy objects"
);
Check(*size == (calls < 4 ? 64u : 63u), "capacity reset for every call");
Check(*algorithm == 0 && time->QuadPart == 0, "fresh scalar outputs");
for (unsigned i = 0; i < 64; ++i) Check(hash[i] == 0, "fresh hash buffer");
for (unsigned i = 0; i < 64; ++i)
{
Check(hash[i] == 0, "fresh hash buffer");
}
++calls;
if (*size < 64) { *size = 64; return STATUS_BUFFER_TOO_SMALL; }
if (*size < 64)
{
*size = 64;
return STATUS_BUFFER_TOO_SMALL;
}
policy->ChainInfo = timestamp->ChainInfo = &chainToken;
outstanding = 2;
policy->PolicyBits = 0xA5;
@@ -45,22 +87,38 @@ static NTSTATUS CiValidateFileObject(PFILE_OBJECT file, ULONG flags, ULONG level
policy->VerificationStatus = failureMode ? STATUS_INVALID_IMAGE_HASH : STATUS_SUCCESS;
timestamp->VerificationStatus = STATUS_SUCCESS;
// Also allocate on simulated failure to exercise unconditional cleanup.
if (failureMode) return STATUS_INVALID_IMAGE_HASH;
if (failureMode)
{
return STATUS_INVALID_IMAGE_HASH;
}
std::memset(hash, 0xCC, 32);
*size = 32;
*algorithm = 0x800C;
time->QuadPart = 123;
return STATUS_SUCCESS;
}
static void CiFreePolicyInfo(MINCRYPT_POLICY_INFO* policy)
static void
CiFreePolicyInfo(
MINCRYPT_POLICY_INFO* policy
)
{
++frees;
if (policy->ChainInfo) { Check(outstanding > 0, "owned chain"); --outstanding; }
if (policy->ChainInfo)
{
Check(outstanding > 0, "owned chain");
--outstanding;
}
*policy = {};
}
#include "ci-file-object-probe.cpp"
int main()
int
main()
{
for (unsigned pass = 0; pass < 2; ++pass)
{
@@ -70,27 +128,48 @@ int main()
std::memset(results, 0xFF, sizeof(results));
DomitoProbeCiValidateFileObject(&fileToken, results);
Check(calls == 6 && frees == 12 && outstanding == 0, "all calls and frees");
for (unsigned i = 0; i < 6; ++i)
{
const auto& r = results[i];
Check(r.HashGuardIntact, "buffer guard");
const NTSTATUS expected = i >= 4 ? STATUS_BUFFER_TOO_SMALL :
failureMode ? STATUS_INVALID_IMAGE_HASH : STATUS_SUCCESS;
const NTSTATUS expected = i >= 4 ? STATUS_BUFFER_TOO_SMALL
: failureMode ? STATUS_INVALID_IMAGE_HASH
: STATUS_SUCCESS;
Check(r.Status == expected, "status preserved");
if (i < 4) {
if (i < 4)
{
Check(r.PolicyBits == 0xA5 && r.TimeStampPolicyBits == 0x5A, "policy capture");
Check(r.HasChain && r.HasTimeStampChain, "chain presence captured before free");
}
if (NT_SUCCESS(r.Status)) {
Check(r.HashSize == 32 && r.HashAlgId == 0x800C && r.SigningTime.QuadPart == 123,
"digest metadata and signing time");
if (NT_SUCCESS(r.Status))
{
Check(
r.HashSize == 32 && r.HashAlgId == 0x800C && r.SigningTime.QuadPart == 123,
"digest metadata and signing time"
);
for (unsigned j = 0; j < 64; ++j)
{
Check(r.Hash[j] == (j < 32 ? 0xCC : 0), "digest copied and tail cleared");
} else {
for (auto byte : r.Hash) Check(byte == 0, "no digest from failed call");
}
}
else
{
for (auto byte : r.Hash)
{
Check(byte == 0, "no digest from failed call");
}
}
if (i >= 4)
{
Check(r.HashSize == 64, "required capacity captured");
}
if (i >= 4) Check(r.HashSize == 64, "required capacity captured");
}
}
std::printf("PASS: %u probe checks (mock CI; no runtime trust validation)\n", checks);
}