Add CI validation helpers with explicit signature selection and owned results
This commit is contained in:
1 parent
28f284be8f
commit
1f4d62ae95
13 files changed
+596
-3
No files matched your search
@@ -30,6 +30,14 @@ Built for and tested on **Windows 10 version 1507 (or newer) x64/ARM64**. 32-Bit
|
|||||||
- Reliable SAL annotations.
|
- Reliable SAL annotations.
|
||||||
- I made sure to enrich the majority of the code with correct, tested annotations for Code Analysis to help you spot potential accidental API misuse 😎
|
- I made sure to enrich the majority of the code with correct, tested annotations for Code Analysis to help you spot potential accidental API misuse 😎
|
||||||
|
|
||||||
|
## Authenticode validation helpers
|
||||||
|
|
||||||
|
The [CI validation helpers](docs/ci-validation-helpers.md) provide shared result
|
||||||
|
ownership/cleanup, file-object validation with explicit selection policy, and
|
||||||
|
algorithm-explicit embedded verification using CI. First-success selection is
|
||||||
|
an opt-in x64 experiment; existing APIs retain their behavior. Always enforce
|
||||||
|
the expected signer/trust policy separately from a successful CI return status.
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
- Add the `include` directory to your project's headers search path.
|
- Add the `include` directory to your project's headers search path.
|
||||||
|
|||||||
@@ -197,8 +197,11 @@ statuses and signing time. Do not interpret status success alone as selection
|
|||||||
of the company certificate. First successful does not necessarily mean first
|
of the company certificate. First successful does not necessarily mean first
|
||||||
listed: CI may reject the primary and continue.
|
listed: CI may reject the primary and continue.
|
||||||
|
|
||||||
`0x80` has additional policy and fallback effects. It has not been published as
|
`0x80` has additional policy and fallback effects. Domito exposes it only as
|
||||||
a supported Domito constant or a production default. The companion research
|
`DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL` and through the opt-in
|
||||||
|
`DomitoCiSignatureSelectionFirstSuccessfulExperimental` mode in
|
||||||
|
`DomitoValidateFileObjectEx`. It is not a production default or a stable signer
|
||||||
|
index. See the [validation helper contracts](ci-validation-helpers.md). The companion research
|
||||||
`SIGNATURE-SELECTION.md` provides the full static evidence and an alternative
|
`SIGNATURE-SELECTION.md` provides the full static evidence and an alternative
|
||||||
primary-signature path using `CiCheckSignedFile`.
|
primary-signature path using `CiCheckSignedFile`.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# CI validation helpers
|
||||||
|
|
||||||
|
These additive APIs are declared in `Domito.h` for Windows 10 compile targets.
|
||||||
|
They reuse CI for cryptographic verification. They do not implement RSA/ECDSA,
|
||||||
|
certificate-chain building or timestamp-signature verification themselves.
|
||||||
|
Raw CI declarations and existing digest/legacy helpers are unchanged.
|
||||||
|
|
||||||
|
## Result lifetime
|
||||||
|
|
||||||
|
Zero-initialize `DOMITO_CI_VALIDATION_RESULT` before first use. Each validation
|
||||||
|
call frees its previous signer/timestamp policies before reuse and resets the
|
||||||
|
result. Do not pass uninitialized storage or copy an owning result as a second
|
||||||
|
owner. On every exit, including a failed call, release it with
|
||||||
|
`DomitoFreeCiValidationResult`. The destructor accepts NULL and empty results.
|
||||||
|
All three APIs require PASSIVE_LEVEL. Referenced input objects/buffers must stay
|
||||||
|
valid and file bytes must stay stable throughout validation and consumption.
|
||||||
|
|
||||||
|
On success, `Hash`/`HashSize`/`HashAlgId` describe a known SHA-1/256/384/512 file
|
||||||
|
digest with a consistent length. Unused digest bytes are zero. This is not a
|
||||||
|
certificate thumbprint. On failure, digest bytes/length/algorithm and signing
|
||||||
|
time are zero; policy objects may retain owned diagnostic allocations. Input
|
||||||
|
flags/level forwarded to the file-object export remain recorded if it was called.
|
||||||
|
Those fields are not CI's final internally normalized policy flags.
|
||||||
|
|
||||||
|
Check the returned NTSTATUS first. Policy verification status, policy bits,
|
||||||
|
certificate identity, timestamps and the application's intended trust policy
|
||||||
|
remain separate observations/checks. Returning STATUS_SUCCESS does not pin a
|
||||||
|
company signer, reproduce WinVerifyTrust policy or prove driver-load acceptance.
|
||||||
|
No helper traverses the private chain layout or matches a certificate for you.
|
||||||
|
|
||||||
|
## File-object path
|
||||||
|
|
||||||
|
`DomitoValidateFileObjectEx` takes a referenced FILE_OBJECT, caller-selected
|
||||||
|
validation flags, a byte-sized requested signing level and a selection mode.
|
||||||
|
It supplies fresh 64-byte hash capacity and initialized policy/time outputs on
|
||||||
|
every call. It does not read, duplicate or acquire ownership of the file object.
|
||||||
|
|
||||||
|
- `DomitoCiSignatureSelectionNative` forwards the caller's flags unchanged.
|
||||||
|
This is native policy, not a guarantee of preferred-level selection: the
|
||||||
|
caller's other flags may already change the search/exit path.
|
||||||
|
- `DomitoCiSignatureSelectionFirstSuccessfulExperimental` additionally sets
|
||||||
|
`DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL` (0x80). On examined x64
|
||||||
|
builds this changes a successful-candidate early-exit predicate. It also
|
||||||
|
affects other policy/fallback paths. This explicit mode returns
|
||||||
|
STATUS_NOT_SUPPORTED on non-x64 builds. No runtime behavior on ARM64 is implied.
|
||||||
|
|
||||||
|
Neither mode automatically adds `0x20`. Broadening accepted roots is a separate
|
||||||
|
caller decision. Neither mode selects an arbitrary signer index or guarantees
|
||||||
|
that signature zero succeeds. A rejected primary may still lead to another signer.
|
||||||
|
|
||||||
|
```c
|
||||||
|
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||||
|
NTSTATUS status = DomitoValidateFileObjectEx(
|
||||||
|
fileObject,
|
||||||
|
DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE,
|
||||||
|
0,
|
||||||
|
DomitoCiSignatureSelectionFirstSuccessfulExperimental,
|
||||||
|
&result);
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
// Apply your expected-certificate/trust policy to result.PolicyInfo.
|
||||||
|
// result.SigningTime belongs to that selected verification result.
|
||||||
|
}
|
||||||
|
DomitoFreeCiValidationResult(&result);
|
||||||
|
```
|
||||||
|
|
||||||
|
The native kernel experiment on the supplied dual-SHA256 BthPS3 fixture returned
|
||||||
|
Microsoft for flags 0x20 and Nefarius for flags 0xA0 on a Windows 11 22631 x64
|
||||||
|
TESTSIGNING-enabled machine. It does not establish all older builds, TESTSIGNING-off
|
||||||
|
behavior or every side effect of 0x80. See [the flag evidence](ci-validate-file-object.md).
|
||||||
|
Do not rename this mode "require our company" or "select primary signature".
|
||||||
|
|
||||||
|
## Embedded PE-buffer path
|
||||||
|
|
||||||
|
`DomitoValidatePortableExecutableEmbeddedSignature` accepts a stable PE buffer
|
||||||
|
and an explicit CALG_SHA1/CALG_SHA_256/CALG_SHA_384/CALG_SHA_512. It calls
|
||||||
|
`DomitoCalculatePortableExecutableDigestEx` and supplies the resulting digest
|
||||||
|
and original first WIN_CERTIFICATE to `CiCheckSignedFile`. The digest allocation
|
||||||
|
is always freed, and only copied digest bytes/owned CI policies remain in the
|
||||||
|
result. Unsupported algorithm/layout and CI errors propagate without trying a
|
||||||
|
different algorithm or a catalog. An absent certificate returns
|
||||||
|
STATUS_INVALID_IMAGE_HASH; the helper cannot turn an unsigned file into a
|
||||||
|
successful catalog verification.
|
||||||
|
|
||||||
|
```c
|
||||||
|
DOMITO_CI_VALIDATION_RESULT result = {0};
|
||||||
|
NTSTATUS status = DomitoValidatePortableExecutableEmbeddedSignature(
|
||||||
|
stablePeBytes, peSize, CALG_SHA_256, &result);
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
// Enforce the expected certificate and intended trust policy here.
|
||||||
|
}
|
||||||
|
DomitoFreeCiValidationResult(&result);
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses the explicit digest helper's [supported PE layout subset](authenticode-digests.md):
|
||||||
|
bounded headers, contiguous nonoverlapping raw sections in file order and an EOF
|
||||||
|
certificate table. It is not an arbitrary PE/SIP parser. CI can select nested
|
||||||
|
signatures according to its algorithm rules, so the helper is deliberately named
|
||||||
|
**embedded**, not **force primary**. The matching SHA-256 request selected the
|
||||||
|
company primary in the tested BthPS3 layout; another layout may select another
|
||||||
|
signer or fail. General enumeration remains separate work.
|
||||||
|
|
||||||
|
## Support and validation
|
||||||
|
|
||||||
|
The functions are C-compatible and compile for x64/ARM64. The experimental
|
||||||
|
file-object mode is deliberately gated to x64; native forwarding/embedded code
|
||||||
|
compilation does not certify every architecture/build's CI runtime ABI. CI
|
||||||
|
imports are static and must exist on the target OS, as described in the
|
||||||
|
[availability matrix](ci-api-compatibility.md). Structure/chain interpretation
|
||||||
|
remains subject to the [mincrypt contracts](mincrypt-contracts.md).
|
||||||
|
|
||||||
|
The new host suite checks production code forwarding, hash invariants, failures,
|
||||||
|
partial allocations, cleanup, result reuse and the non-x64 conditional branch.
|
||||||
|
The existing digest suite independently compares Windows SIP and Crypt32 results.
|
||||||
|
Kernel compilation covers the full library, public C clients and layout checks.
|
||||||
|
CodeIntegrity-RE probe v2 now calls the new APIs; a further kernel run is needed
|
||||||
|
to validate these wrappers rather than merely their previously tested raw calls.
|
||||||
@@ -6,6 +6,13 @@ it should not be read as describing the corrected source unchanged.
|
|||||||
|
|
||||||
## Implemented with host tests or compilation
|
## Implemented with host tests or compilation
|
||||||
|
|
||||||
|
- Additive CI result/selection helpers now package native file-object validation,
|
||||||
|
opt-in x64 first-success selection and explicit embedded verification, with
|
||||||
|
shared policy/digest cleanup. Existing APIs retain their behavior. See the
|
||||||
|
[helper contracts](ci-validation-helpers.md). Host failure/forwarding tests and
|
||||||
|
x64/ARM64 builds cover the wrappers; WPP probe v2 consumes them and awaits a
|
||||||
|
returned kernel trace. The previous v1 trace establishes the raw native routes.
|
||||||
|
|
||||||
- **S01–S04:** subtraction-safe PE ranges, no fixed 4 MiB copy, bounded certificate
|
- **S01–S04:** subtraction-safe PE ranges, no fixed 4 MiB copy, bounded certificate
|
||||||
intervals, deterministic failure outputs, and digest cleanup. Compatibility
|
intervals, deterministic failure outputs, and digest cleanup. Compatibility
|
||||||
and explicit paths share the bounded hashing core.
|
and explicit paths share the bounded hashing core.
|
||||||
@@ -95,7 +102,7 @@ and compilation do not close the remaining work:
|
|||||||
|
|
||||||
## Checks
|
## Checks
|
||||||
|
|
||||||
Run the three host runners in `tests/README.md`. Kernel compilation also covers
|
Run the four host runners in `tests/README.md`. Kernel compilation also covers
|
||||||
the default modern x64/ARM64 structures, the pre-Windows-10 x64 compile variant,
|
the default modern x64/ARM64 structures, the pre-Windows-10 x64 compile variant,
|
||||||
and 14 assertions in the 26100 opt-in header plus 12 historical-view assertions. Those are build checks,
|
and 14 assertions in the 26100 opt-in header plus 12 historical-view assertions. Those are build checks,
|
||||||
not a claim that historical or ARM64 CI runtime ABI has been verified.
|
not a claim that historical or ARM64 CI runtime ABI has been verified.
|
||||||
@@ -158,6 +158,10 @@ later conversion failure. It is not owned by either policy object.
|
|||||||
Evidence: `MincryptFreePolicyInfo`, `MincryptCopyPolicyInfo`,
|
Evidence: `MincryptFreePolicyInfo`, `MincryptCopyPolicyInfo`,
|
||||||
`I_MincryptAddChainInfo`, `CiVerifyHashInCatalog`, and `CiGetCertPublisherName`.
|
`I_MincryptAddChainInfo`, `CiVerifyHashInCatalog`, and `CiGetCertPublisherName`.
|
||||||
|
|
||||||
|
The additive [validation helpers](ci-validation-helpers.md) package safe result
|
||||||
|
ownership, explicit file-object selection and embedded CI verification. They do
|
||||||
|
not change raw CI declarations or existing legacy-helper behavior.
|
||||||
|
|
||||||
## Multiple signatures and selected results
|
## Multiple signatures and selected results
|
||||||
|
|
||||||
`CiValidateFileObject` returns one selected signature result and its timestamp
|
`CiValidateFileObject` returns one selected signature result and its timestamp
|
||||||
|
|||||||
@@ -565,6 +565,13 @@ CiSetTrustedOriginClaimId(
|
|||||||
// See docs/ci-validate-file-object.md for evidence and version limitations.
|
// See docs/ci-validate-file-object.md for evidence and version limitations.
|
||||||
#define DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE 0x00000020UL
|
#define DOMITO_CI_VALIDATE_FLAG_BROADEN_ROOT_ACCEPTANCE 0x00000020UL
|
||||||
|
|
||||||
|
// Descriptive experimental bit, not an official or isolated selector flag.
|
||||||
|
// Participates in first-success guards in examined 22621/26100 x64 CI bodies;
|
||||||
|
// also changes default-path policy and fallback. One 22631 TESTSIGNING-enabled
|
||||||
|
// BthPS3 run confirms 0x20|0x80 returns its primary company signer. It is not an
|
||||||
|
// arbitrary signer index, guaranteed primary selection, or a production default.
|
||||||
|
#define DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL 0x00000080UL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a file object, verify the signature and provide information regarding
|
* Given a file object, verify the signature and provide information regarding
|
||||||
* the certificates that was used for signing (the entire certificate chain)
|
* the certificates that was used for signing (the entire certificate chain)
|
||||||
|
|||||||
@@ -254,6 +254,79 @@ DomitoFreePortableExecutableDigest(
|
|||||||
_In_ PVOID pDigestOut
|
_In_ PVOID pDigestOut
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||||
|
// Native forwards caller flags unchanged. The experimental x64 mode additionally
|
||||||
|
// sets 0x80; it stops after the first accepted candidate in examined CI builds,
|
||||||
|
// and also changes other policy/fallback paths. Neither mode selects a publisher
|
||||||
|
// or enumerates signatures. No root-acceptance flag is added automatically.
|
||||||
|
typedef enum _DOMITO_CI_SIGNATURE_SELECTION
|
||||||
|
{
|
||||||
|
DomitoCiSignatureSelectionNative = 0,
|
||||||
|
DomitoCiSignatureSelectionFirstSuccessfulExperimental = 1
|
||||||
|
} DOMITO_CI_SIGNATURE_SELECTION;
|
||||||
|
|
||||||
|
// Zero-initialize before first use. Both policies own their CI chain allocations,
|
||||||
|
// including possible diagnostic allocations on failure. Do not copy as an owner.
|
||||||
|
// Hash is a file digest, not a certificate thumbprint. Hash/algorithm/signing time
|
||||||
|
// are cleared on failure; policies may retain diagnostics. Check return NTSTATUS.
|
||||||
|
typedef struct _DOMITO_CI_VALIDATION_RESULT
|
||||||
|
{
|
||||||
|
MINCRYPT_POLICY_INFO PolicyInfo;
|
||||||
|
MINCRYPT_POLICY_INFO TimeStampPolicyInfo;
|
||||||
|
LARGE_INTEGER SigningTime;
|
||||||
|
UINT8 Hash[MINCRYPT_MAX_HASH_LENGTH];
|
||||||
|
UINT32 HashSize;
|
||||||
|
ALG_ID HashAlgId;
|
||||||
|
UINT32 FileObjectValidationFlags; // Flags passed to CI export; zero for embedded helper.
|
||||||
|
UINT8 RequestedSigningLevel; // File-object helper only.
|
||||||
|
} DOMITO_CI_VALIDATION_RESULT, *PDOMITO_CI_VALIDATION_RESULT;
|
||||||
|
|
||||||
|
// Safe for NULL or an empty/initialized result; resets all fields after cleanup.
|
||||||
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||||
|
EXTERN_C
|
||||||
|
void
|
||||||
|
DomitoFreeCiValidationResult(
|
||||||
|
_Inout_opt_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||||
|
);
|
||||||
|
|
||||||
|
// Referenced FILE_OBJECT at PASSIVE_LEVEL; keep file bytes stable. Previous result
|
||||||
|
// policies are released before reuse, including invalid-argument failures.
|
||||||
|
// Supplies a fresh 64-byte hash buffer on every call. FirstSuccessfulExperimental
|
||||||
|
// is opt-in and x64-only; other architectures return STATUS_NOT_SUPPORTED for it.
|
||||||
|
// Only one selected signer plus its timestamp policy is returned. The caller
|
||||||
|
// must enforce its expected certificate and intended trust policy separately.
|
||||||
|
_Success_(return >= 0)
|
||||||
|
_Must_inspect_result_
|
||||||
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||||
|
EXTERN_C
|
||||||
|
NTSTATUS
|
||||||
|
DomitoValidateFileObjectEx(
|
||||||
|
_In_ PFILE_OBJECT FileObject,
|
||||||
|
_In_ UINT32 ValidationFlags,
|
||||||
|
_In_ UINT8 RequestedSigningLevel,
|
||||||
|
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
|
||||||
|
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hash and verify the first WIN_CERTIFICATE in a stable PE buffer. Uses the
|
||||||
|
// explicit digest helper's supported contiguous-section/EOF-certificate subset.
|
||||||
|
// SHA-1/256/384/512 are explicit; no algorithm retry or catalog fallback occurs.
|
||||||
|
// CI may select a nested signature under its algorithm rules: this is not a
|
||||||
|
// universal force-primary API. Matching SHA-256 selects the company primary in
|
||||||
|
// the tested dual-signed BthPS3 fixture. Caller still checks the returned signer.
|
||||||
|
_Success_(return >= 0)
|
||||||
|
_Must_inspect_result_
|
||||||
|
_IRQL_requires_max_(PASSIVE_LEVEL)
|
||||||
|
EXTERN_C
|
||||||
|
NTSTATUS
|
||||||
|
DomitoValidatePortableExecutableEmbeddedSignature(
|
||||||
|
_In_reads_bytes_(PeSize) const UCHAR* PeBytes,
|
||||||
|
_In_ ULONG PeSize,
|
||||||
|
_In_ ALG_ID HashAlgId,
|
||||||
|
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
|
||||||
|
);
|
||||||
|
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||||
|
|
||||||
#define DOMITO_CI_SOURCE_EMBEDDED 1UL
|
#define DOMITO_CI_SOURCE_EMBEDDED 1UL
|
||||||
#define DOMITO_CI_SOURCE_CATALOG 2UL
|
#define DOMITO_CI_SOURCE_CATALOG 2UL
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#include "Domito.Internal.h"
|
||||||
|
|
||||||
|
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
||||||
|
|
||||||
|
static UINT32 DomitoCiDigestSize(ALG_ID Algorithm)
|
||||||
|
{
|
||||||
|
switch (Algorithm)
|
||||||
|
{
|
||||||
|
case CALG_SHA1: return MINCRYPT_SHA1_LENGTH;
|
||||||
|
case CALG_SHA_256: return MINCRYPT_SHA256_LENGTH;
|
||||||
|
case CALG_SHA_384: return MINCRYPT_SHA384_LENGTH;
|
||||||
|
case CALG_SHA_512: return MINCRYPT_SHA512_LENGTH;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DomitoClearCiDigest(DOMITO_CI_VALIDATION_RESULT* Result)
|
||||||
|
{
|
||||||
|
RtlZeroMemory(Result->Hash, sizeof(Result->Hash));
|
||||||
|
Result->HashSize = 0;
|
||||||
|
Result->HashAlgId = 0;
|
||||||
|
Result->SigningTime.QuadPart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Use_decl_annotations_
|
||||||
|
void DomitoFreeCiValidationResult(DOMITO_CI_VALIDATION_RESULT* Result)
|
||||||
|
{
|
||||||
|
PAGED_CODE();
|
||||||
|
if (!Result) return;
|
||||||
|
CiFreePolicyInfo(&Result->PolicyInfo);
|
||||||
|
CiFreePolicyInfo(&Result->TimeStampPolicyInfo);
|
||||||
|
RtlZeroMemory(Result, sizeof(*Result));
|
||||||
|
}
|
||||||
|
|
||||||
|
_Use_decl_annotations_
|
||||||
|
NTSTATUS DomitoValidateFileObjectEx(
|
||||||
|
PFILE_OBJECT FileObject, UINT32 ValidationFlags, UINT8 RequestedSigningLevel,
|
||||||
|
DOMITO_CI_SIGNATURE_SELECTION Selection, DOMITO_CI_VALIDATION_RESULT* Result)
|
||||||
|
{
|
||||||
|
PAGED_CODE();
|
||||||
|
if (!Result) return STATUS_INVALID_PARAMETER;
|
||||||
|
DomitoFreeCiValidationResult(Result);
|
||||||
|
if (!FileObject) return STATUS_INVALID_PARAMETER;
|
||||||
|
if (Selection != DomitoCiSignatureSelectionNative &&
|
||||||
|
Selection != DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||||
|
return STATUS_INVALID_PARAMETER;
|
||||||
|
if (Selection == DomitoCiSignatureSelectionFirstSuccessfulExperimental)
|
||||||
|
{
|
||||||
|
#if defined(_M_AMD64) || defined(_AMD64_)
|
||||||
|
ValidationFlags |= DOMITO_CI_VALIDATE_FLAG_FIRST_SUCCESS_EXPERIMENTAL;
|
||||||
|
#else
|
||||||
|
return STATUS_NOT_SUPPORTED;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
Result->FileObjectValidationFlags = ValidationFlags;
|
||||||
|
Result->RequestedSigningLevel = RequestedSigningLevel;
|
||||||
|
Result->HashSize = sizeof(Result->Hash);
|
||||||
|
NTSTATUS status = CiValidateFileObject(FileObject, ValidationFlags, RequestedSigningLevel,
|
||||||
|
&Result->PolicyInfo, &Result->TimeStampPolicyInfo, &Result->SigningTime,
|
||||||
|
Result->Hash, &Result->HashSize, &Result->HashAlgId);
|
||||||
|
if (NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
const UINT32 expected = DomitoCiDigestSize(Result->HashAlgId);
|
||||||
|
if (!expected) status = STATUS_NOT_SUPPORTED;
|
||||||
|
else if (Result->HashSize != expected || Result->HashSize > sizeof(Result->Hash))
|
||||||
|
status = STATUS_INVALID_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
if (!NT_SUCCESS(status)) DomitoClearCiDigest(Result);
|
||||||
|
else RtlZeroMemory(Result->Hash + Result->HashSize, sizeof(Result->Hash) - Result->HashSize);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Use_decl_annotations_
|
||||||
|
NTSTATUS DomitoValidatePortableExecutableEmbeddedSignature(
|
||||||
|
const UCHAR* PeBytes, ULONG PeSize, ALG_ID HashAlgId, DOMITO_CI_VALIDATION_RESULT* Result)
|
||||||
|
{
|
||||||
|
PAGED_CODE();
|
||||||
|
if (!Result) return STATUS_INVALID_PARAMETER;
|
||||||
|
DomitoFreeCiValidationResult(Result);
|
||||||
|
if (!PeBytes) return STATUS_INVALID_PARAMETER;
|
||||||
|
const UINT32 expected = DomitoCiDigestSize(HashAlgId);
|
||||||
|
if (!expected) return STATUS_NOT_SUPPORTED;
|
||||||
|
PVOID digest = nullptr;
|
||||||
|
ULONG digestSize = 0, securitySize = 0;
|
||||||
|
LPWIN_CERTIFICATE certificate = nullptr;
|
||||||
|
NTSTATUS status = DomitoCalculatePortableExecutableDigestEx(PeBytes, PeSize, HashAlgId,
|
||||||
|
&digestSize, &digest, &certificate, &securitySize);
|
||||||
|
if (NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
if (!certificate || !securitySize) status = STATUS_INVALID_IMAGE_HASH;
|
||||||
|
else if (!digest || digestSize != expected || digestSize > sizeof(Result->Hash))
|
||||||
|
status = STATUS_INVALID_BUFFER_SIZE;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status = CiCheckSignedFile(digest, digestSize, HashAlgId, certificate, securitySize,
|
||||||
|
&Result->PolicyInfo, &Result->SigningTime, &Result->TimeStampPolicyInfo);
|
||||||
|
if (NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
RtlCopyMemory(Result->Hash, digest, digestSize);
|
||||||
|
Result->HashSize = digestSize;
|
||||||
|
Result->HashAlgId = HashAlgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (digest) DomitoFreePortableExecutableDigest(digest);
|
||||||
|
if (!NT_SUCCESS(status)) DomitoClearCiDigest(Result);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
#endif // NTDDI_VERSION >= NTDDI_WIN10
|
||||||
@@ -122,6 +122,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Domito.CodeIntegrity.cpp" />
|
<ClCompile Include="Domito.CodeIntegrity.cpp" />
|
||||||
|
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp" />
|
||||||
<ClCompile Include="Domito.cpp" />
|
<ClCompile Include="Domito.cpp" />
|
||||||
<ClCompile Include="Domito.Memory.cpp" />
|
<ClCompile Include="Domito.Memory.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
<ClCompile Include="Domito.Memory.cpp">
|
<ClCompile Include="Domito.Memory.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Domito.CodeIntegrity.Validation.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="Domito.CodeIntegrity.cpp">
|
<ClCompile Include="Domito.CodeIntegrity.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ From an **x64 Visual Studio developer shell**, with the Windows SDK and Python 3
|
|||||||
python tests\run-digest-tests.py
|
python tests\run-digest-tests.py
|
||||||
python tests\run-common-tests.py
|
python tests\run-common-tests.py
|
||||||
python tests\run-ci-probe-tests.py
|
python tests\run-ci-probe-tests.py
|
||||||
|
python tests\run-ci-validation-tests.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Build outputs are temporary. No driver is installed or loaded. Test runners
|
Build outputs are temporary. No driver is installed or loaded. Test runners
|
||||||
@@ -40,6 +41,25 @@ cover nonidentity name-ordinal mappings, forwarders, empty slots, invalid spans,
|
|||||||
unbounded strings, and deterministic failure outputs. The export API requires a
|
unbounded strings, and deterministic failure outputs. The export API requires a
|
||||||
valid loader-mapped module; it is not an arbitrary file parser.
|
valid loader-mapped module; it is not an arbitrary file parser.
|
||||||
|
|
||||||
|
## CI validation result and selection helpers
|
||||||
|
|
||||||
|
`run-ci-validation-tests.py` compiles the production wrapper source unchanged
|
||||||
|
(apart from replacing its private include with test dependencies) and extracts
|
||||||
|
the public declarations/constants. It checks native flag/level forwarding,
|
||||||
|
64-byte capacity on every call, the opt-in x64 experimental bit, non-x64 mode
|
||||||
|
rejection, invalid inputs, returned hash invariants, deterministic failure
|
||||||
|
scalars, preserved diagnostic policies, and result cleanup/reuse. The embedded
|
||||||
|
path covers all four explicit algorithms, unsupported/unsigned/invalid layouts,
|
||||||
|
partial digest allocation cleanup, CI failure propagation and no catalog retry.
|
||||||
|
CI/hash-layout behavior is injected; real digest behavior is checked by the
|
||||||
|
existing Windows SIP/Crypt32 differential harness. A second host build exercises
|
||||||
|
the non-x64 conditional branch; real ARM64 compilation is a separate build check.
|
||||||
|
|
||||||
|
The first kernel trace used direct CI calls. Probe v2 in CodeIntegrity-RE now
|
||||||
|
consumes these library helpers; a returned v2 trace is needed to claim runtime
|
||||||
|
verification of the wrappers themselves. The earlier native result is not
|
||||||
|
silently relabelled as an API-wrapper test.
|
||||||
|
|
||||||
## File-object validation flag probe
|
## File-object validation flag probe
|
||||||
|
|
||||||
See [the flag documentation and VM procedure](../docs/ci-validate-file-object.md)
|
See [the flag documentation and VM procedure](../docs/ci-validate-file-object.md)
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
#define WIN32_NO_STATUS
|
||||||
|
#include <windows.h>
|
||||||
|
#undef WIN32_NO_STATUS
|
||||||
|
#include <ntstatus.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
#define NT_SUCCESS(s) (((NTSTATUS)(s)) >= 0)
|
||||||
|
#ifndef _IRQL_requires_max_
|
||||||
|
#define _IRQL_requires_max_(x)
|
||||||
|
#endif
|
||||||
|
#define PASSIVE_LEVEL 0
|
||||||
|
#define PAGED_CODE() ((void)0)
|
||||||
|
typedef void* PFILE_OBJECT;
|
||||||
|
typedef void* LPWIN_CERTIFICATE;
|
||||||
|
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 ULONG fakeDigestSize = 32;
|
||||||
|
static bool omitCertificate, omitDigest, partialDigestOnFailure;
|
||||||
|
static const UCHAR image[] = {1,2,3};
|
||||||
|
static ULONG observedFlags, observedLevel, observedCapacity;
|
||||||
|
static PFILE_OBJECT observedObject;
|
||||||
|
static ALG_ID observedEmbeddedAlgorithm;
|
||||||
|
static ULONG observedSecuritySize;
|
||||||
|
static void* observedCertificate;
|
||||||
|
|
||||||
|
static void Check(bool value, const char* message) {
|
||||||
|
++checks;
|
||||||
|
if (!value) { std::fprintf(stderr, "FAIL: %s\n", message); std::exit(1); }
|
||||||
|
}
|
||||||
|
static void* Allocate(SIZE_T size) {
|
||||||
|
void* value = std::malloc(size);
|
||||||
|
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);
|
||||||
|
*policy = {};
|
||||||
|
}
|
||||||
|
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->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) {
|
||||||
|
++validationCalls;
|
||||||
|
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;
|
||||||
|
return nativeStatus;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
*digest = omitDigest ? nullptr : Allocate(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) {
|
||||||
|
++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;
|
||||||
|
return embeddedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||||
|
#undef _M_AMD64
|
||||||
|
#undef _AMD64_
|
||||||
|
#endif
|
||||||
|
#include "ci-validation-under-test.inc"
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
// API return success does not silently reinterpret a policy status.
|
||||||
|
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);
|
||||||
|
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||||
|
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");
|
||||||
|
#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");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
#ifdef DOMITO_TEST_OTHER_ARCH
|
||||||
|
"non-x64"
|
||||||
|
#else
|
||||||
|
"x64"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Compile the actual production CI wrapper body against deterministic CI doubles."""
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
header = (root / 'include/Domito.h').read_text(encoding='utf-8-sig')
|
||||||
|
start = header.index('#if (NTDDI_VERSION >= NTDDI_WIN10)\n// Native forwards')
|
||||||
|
end = header.index('#define DOMITO_CI_SOURCE_EMBEDDED', start)
|
||||||
|
constants_header = (root / 'include/Domito.MinCrypt.h').read_text(encoding='utf-8-sig')
|
||||||
|
constants = '\n'.join(line for line in constants_header.splitlines()
|
||||||
|
if line.startswith(('#define DOMITO_CI_VALIDATE_FLAG_', '#define MINCRYPT_MAX_HASH_LENGTH', '#define MINCRYPT_SHA')))
|
||||||
|
source = (root / 'src/Domito.CodeIntegrity.Validation.cpp').read_text(encoding='utf-8-sig')
|
||||||
|
source = source.replace('#include "Domito.Internal.h"', '')
|
||||||
|
with tempfile.TemporaryDirectory(prefix='domito-ci-validation-') as temporary:
|
||||||
|
directory = Path(temporary)
|
||||||
|
(directory / 'ci-validation-api.inc').write_text(constants + '\n' + header[start:end], encoding='utf-8')
|
||||||
|
(directory / 'ci-validation-under-test.inc').write_text(source, encoding='utf-8')
|
||||||
|
for branch in ['x64', 'other']:
|
||||||
|
program = directory / (branch + '.exe')
|
||||||
|
flags = ['/DDOMITO_TEST_OTHER_ARCH'] if branch == 'other' else []
|
||||||
|
subprocess.run(['cl','/nologo','/EHsc','/W4','/WX','/std:c++17',
|
||||||
|
'/DNTDDI_VERSION=0x0A000008','/DNTDDI_WIN10=0x0A000000',
|
||||||
|
'/I' + str(directory), *flags, str(Path(__file__).with_name('ci-validation-tests.cpp')),
|
||||||
|
'/Fe' + str(program), '/Fo' + str(directory / (branch + '.obj'))],
|
||||||
|
cwd=directory, check=True)
|
||||||
|
subprocess.run([str(program)], check=True)
|
||||||
Reference in new issue
Block a user