Files
Domito/tests/ci-file-object-probe-tests.cpp
nefarius 3fbfe31d15 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.

Add CI validation helpers with explicit signature selection and owned results

Document runtime-confirmed CI signer selection and crypto reuse

Expand CI compatibility matrix to twenty verified samples

Extend CI export matrix with Windows 11 x64 and ARM64 samples

Document CI API availability and historical mincrypt layouts

Add explicit Authenticode workflows and correct CI helper contracts

Document CI validation flag 0x20 and add kernel probe

Harden CI digest parsing and failure cleanup
2026-10-27 00:17:25 +01:00

176 lines
4.6 KiB
C++

// Host tests validate the probe plumbing, NOT CI trust behavior.
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <ntstatus.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "ci-probe-constants.inc"
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
#define DOMITO_CI_PROBE_HOST_TEST
typedef void* PFILE_OBJECT;
struct MINCRYPT_POLICY_INFO
{
ULONG Size;
ULONG VerificationStatus;
ULONG PolicyBits;
void* ChainInfo;
};
static unsigned calls;
static unsigned frees;
static unsigned outstanding;
static unsigned checks;
static bool failureMode;
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);
}
}
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(*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");
}
++calls;
if (*size < 64)
{
*size = 64;
return STATUS_BUFFER_TOO_SMALL;
}
policy->ChainInfo = timestamp->ChainInfo = &chainToken;
outstanding = 2;
policy->PolicyBits = 0xA5;
timestamp->PolicyBits = 0x5A;
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;
}
std::memset(hash, 0xCC, 32);
*size = 32;
*algorithm = 0x800C;
time->QuadPart = 123;
return STATUS_SUCCESS;
}
static void
CiFreePolicyInfo(
MINCRYPT_POLICY_INFO* policy
)
{
++frees;
if (policy->ChainInfo)
{
Check(outstanding > 0, "owned chain");
--outstanding;
}
*policy = {};
}
#include "ci-file-object-probe.cpp"
int
main()
{
for (unsigned pass = 0; pass < 2; ++pass)
{
failureMode = pass != 0;
calls = frees = outstanding = 0;
DOMITO_CI_PROBE_RESULT results[DOMITO_CI_PROBE_RESULT_COUNT];
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;
Check(r.Status == expected, "status preserved");
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"
);
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");
}
}
if (i >= 4)
{
Check(r.HashSize == 64, "required capacity captured");
}
}
}
std::printf("PASS: %u probe checks (mock CI; no runtime trust validation)\n", checks);
}