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

+200
View File
@@ -0,0 +1,200 @@
#pragma once
#include "Domito.h"
// Consumer helpers: see docs/consumer-helpers.md for ownership and trust limits.
typedef struct _DOMITO_BYTE_VIEW
{
const UCHAR* Data;
ULONG Size;
} DOMITO_BYTE_VIEW;
typedef struct _DOMITO_CERTIFICATE_SPKI
{
DOMITO_BYTE_VIEW Encoded; // Complete SPKI SEQUENCE, including tag and length.
DOMITO_BYTE_VIEW Content; // Legacy consumer convention: excludes that header.
} DOMITO_CERTIFICATE_SPKI;
typedef enum _DOMITO_CERTIFICATE_HASH_KIND
{
DomitoCertificateHashDer = 0,
DomitoCertificateHashSpkiDer = 1,
DomitoCertificateHashSpkiContentLegacy = 2
} DOMITO_CERTIFICATE_HASH_KIND;
// Bounded positional DER extraction, not X.509 validation. Views borrow CertData.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoGetCertificateSpki(
_In_reads_bytes_(CertSize) const UCHAR* CertData,
_In_ ULONG CertSize,
_Out_ DOMITO_CERTIFICATE_SPKI* Spki
);
// SHA-1/256/384/512. HashSize is capacity on entry, bytes on success, required
// size on BUFFER_TOO_SMALL, zero on other failures. Hash may be NULL to size.
// Input/output must not overlap. Hash contains zero bytes on operational failure.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoHashBuffer(
_In_reads_bytes_opt_(Size) const UCHAR* Bytes,
_In_ ULONG Size,
_In_ ALG_ID Algorithm,
_Out_writes_bytes_to_opt_(*HashSize, *HashSize) UCHAR* Hash,
_Inout_ ULONG* HashSize
);
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoHashCertificate(
_In_reads_bytes_(CertSize) const UCHAR* CertData,
_In_ ULONG CertSize,
_In_ DOMITO_CERTIFICATE_HASH_KIND Kind,
_In_ ALG_ID Algorithm,
_Out_writes_bytes_to_opt_(*HashSize, *HashSize) UCHAR* Hash,
_Inout_ ULONG* HashSize
);
// Strict length-delimited hex: no spaces, prefixes, separators or invalid digits.
// Nonoverlapping buffers. Decode clears its output on malformed input.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoDecodeHex(
_In_ const UNICODE_STRING* Hex,
_Out_writes_bytes_(ByteCount) UCHAR* Bytes,
_In_ ULONG ByteCount
);
// Writes uppercase hex plus NUL; HexChars includes terminator capacity.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoEncodeHex(
_In_reads_bytes_opt_(ByteCount) const UCHAR* Bytes,
_In_ ULONG ByteCount,
_Out_writes_(HexChars) WCHAR* Hex,
_In_ ULONG HexChars
);
// Resolve optional exports without creating a static import. Requires DomitoInit.
// ModuleName is the full loaded-module path used by FindModuleBaseAddress.
// Caller must keep that module mapped; this does not acquire a loader reference.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoFindModuleExportedFunctionAddress(
_In_ PANSI_STRING ModuleName,
_In_ PANSI_STRING FunctionName,
_Outptr_result_maybenull_ PVOID* FunctionAddress
);
// Results from either process-name API use one Domito allocation. The memory
// callback pair must remain unchanged until all allocations are released.
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
void
DomitoFreeProcessImageName(
_In_opt_ PUNICODE_STRING ImageName
);
// Caller holds a referenced PEPROCESS throughout the call. No PID re-lookup.
// Returns a copy owned by Domito; use DomitoFreeProcessImageName once.
// Name lookup does not establish the identity of a later reopened file.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoGetProcessImageNameEx(
_In_ PEPROCESS Process,
_Outptr_result_maybenull_ PUNICODE_STRING* ImageName
);
#if (NTDDI_VERSION >= NTDDI_WIN10)
// These wrappers do not require DomitoInit. Handle must be a trusted kernel
// handle with FILE_READ_DATA; it is borrowed, referenced for the call, not closed.
// Same initialized-result/cleanup rules and explicit flags as FileObjectEx.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoValidateFileHandleEx(
_In_ HANDLE FileHandle,
_In_ UINT32 ValidationFlags,
_In_ UINT8 RequestedSigningLevel,
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
);
// Absolute native path (e.g. \??\C:\...). Opens read-only, shares only reads,
// closes its own handle. The path can be reparsed; this validates the opened
// file, not process memory or a previous path identity. No fallback is implicit.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoValidateFilePathEx(
_In_ const UNICODE_STRING* Path,
_In_ UINT32 ValidationFlags,
_In_ UINT8 RequestedSigningLevel,
_In_ DOMITO_CI_SIGNATURE_SELECTION Selection,
_Inout_ DOMITO_CI_VALIDATION_RESULT* Result
);
typedef enum _DOMITO_CI_CHAIN_LAYOUT
{
// Explicit caller assertion: observed x64 +0x28 array/+0x30 count, 120-byte
// elements and a minimum 56-byte header. NOT selected by allocation Size.
DomitoCiChainLayoutObservedX64 = 1
} DOMITO_CI_CHAIN_LAYOUT;
typedef struct _DOMITO_CI_CHAIN_VIEW
{
const MINCRYPT_CHAIN_ELEMENT* Elements;
ULONG Count;
ULONG RootId; // Opaque CI identifier, not proof the last element is a root.
} DOMITO_CI_CHAIN_VIEW;
// Only accepts a readable, initialized CI-owned policy, never arbitrary pointers.
// Checks every exposed name/certificate span against CI's owned allocation.
// Leaf-first order is observed in sampled layouts. Last element is not promised
// to be a self-signed/trusted root. No issuer/subject byte equality is imposed.
// View borrows policy storage until CiFreePolicyInfo/result reuse. x64 only.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoGetCiChainView(
_In_ const MINCRYPT_POLICY_INFO* Policy,
_In_ DOMITO_CI_CHAIN_LAYOUT Layout,
_Out_ DOMITO_CI_CHAIN_VIEW* View
);
// Optional export, resolved at call time; requires DomitoInit. No static import
// of CiGetCertPublisherName. Extracts a display name, not certificate trust.
// Start with {0}; release even after failure using the dedicated free function.
// Never pass an owning previous string: free it before making another call.
_Must_inspect_result_
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
NTSTATUS
DomitoGetCertificatePublisherName(
_In_ const MINCERT_BLOB* Certificate,
_Out_ PUNICODE_STRING PublisherName
);
_IRQL_requires_max_(PASSIVE_LEVEL)
EXTERN_C
void
DomitoFreeCertificatePublisherName(
_Inout_ PUNICODE_STRING PublisherName
);
#endif