-
Notifications
You must be signed in to change notification settings - Fork 1
Security and Package Verification
BootstrapMate downloads a document from a URL and installs whatever that document lists, with a fully elevated token. This page states exactly which of those bytes are verified, how, and what is trusted without being checked.
Everything BootstrapMate acts on arrives over the network. The manifest comes from a URL
supplied by policy, by the registry, by a command-line switch, or from a URL compiled into
the binary. The manifest names package URLs, and those may point anywhere — there is no
requirement that they share a host with the manifest. Whoever can change the manifest
document, or serve a different response for a package URL, chooses what gets installed.
Whoever can change the manifest URL — which is a registry value under HKLM — chooses the
manifest.
The consequence of a successful substitution is total. BootstrapMate runs as Administrator
(and, under its normal deployment, as SYSTEM from a scheduled task), and it hands each
downloaded file to msiexec, to powershell.exe -ExecutionPolicy Bypass -NoProfile, to
installer.exe, to choco, or executes it directly — all inheriting that elevated token.
An attacker-supplied file is not sandboxed, sampled, or reviewed; it is executed. The
Authenticode gate described below exists to close exactly one part of this: it makes the
question "did a publisher I trust produce this binary" answerable independently of "did the
manifest host tell me to install it".
Downloaded installers of type msi and exe are checked with Authenticode before they are
executed.
SignatureVerifier.VerifyFile P/Invokes WinVerifyTrust with the
WINTRUST_ACTION_GENERIC_VERIFY_V2 action GUID
({00AAC56B-CD44-11d0-8CC2-00C04FC295EE}), WTD_UI_NONE, WTD_CHOICE_FILE and
WTD_SAFER_FLAG. A non-zero result means the file is unsigned, tampered with, or does not
chain to a trusted root, and yields the status Untrusted with the detail
WinVerifyTrust=0x{hr:X8}.
If the trust check passes and an expected publisher is configured, the signer certificate's
subject is read with X509Certificate.CreateFromSignedFile, the CN= value is extracted
(falling back to the whole subject if no CN is present), and the expected value must appear
inside it, compared case-insensitively. This is a substring test, not an exact compare —
Example matches Example Corporation. A failure yields the status PublisherMismatch.
The result is then turned into a decision:
| Status |
AllowUnsigned off |
AllowUnsigned on |
|---|---|---|
Trusted |
Allow | Allow |
Untrusted |
Deny | Allow, with a warning in the log |
PublisherMismatch |
Deny | Deny |
A publisher mismatch is never bypassed. AllowUnsigned covers only the missing-or-untrusted
case; an explicit mismatch is treated as a tampering signal.
On a deny the run logs Signature check failed for {file}: {reason} and
Refusing to install {file} — {reason}, then throws
Refusing to install {file}: signature verification failed. That exception is caught by the
per-item handler, so the item is marked failed, its downloaded file is kept in
C:\ProgramData\ManagedBootstrap\cache for inspection, and the run continues to the next
item — and still exits 0. See Command Line Reference.
Three settings control it, all documented in Preferences:
-
VerifyPackageSignatures— enabled by default. Setting it to0skips verification entirely for every item. -
ExpectedPublisher— when set, requires the signer to contain this string. -
AllowUnsigned— permits an untrusted or unsigned installer.
A manifest item may override the last two per item with its own expectedPublisher and
allowUnsigned fields; see Manifest Reference. An item-level
allowUnsigned: true therefore weakens a machine-wide policy for that one item, which is
worth knowing if you treat the manifest as less trusted than your policy.
Revocation is deliberately not checked. The verifier passes WTD_REVOKE_NONE. The
source records the reason: BootstrapMate runs during OOBE (out-of-box experience) and the
Autopilot ESP (Enrollment Status Page), where networking is often unavailable, and an online
OCSP or CRL lookup would add latency and could hard-fail a valid installer when the
revocation endpoint is unreachable. The chain to a trusted root is still verified. A
certificate that has been revoked but not expired will still pass.
There is no hash verification of downloads. Nothing in the tool computes or compares a
digest of a downloaded file against a value in the manifest. The hash field that appears
in the shipped example manifest is never read. If you need content pinning, Authenticode is
the only mechanism available here.
Only msi and exe are gated. Items of type ps1/powershell, nupkg and pkg are
downloaded and executed without any signature check:
- A
ps1item is run aspowershell.exe -ExecutionPolicy Bypass -NoProfile -File, so the machine's execution policy does not constrain it either. - A
nupkgitem goes to sbin-installer, or falls back to Chocolatey. The Chocolatey command line includes--ignore-checksums, so Chocolatey's own checksum validation is disabled too. - A
pkgitem goes to sbin-installer, whose own verification behaviour is outside this tool.
This is the boundary to hold in mind: for MSI and EXE, provenance is checked and the manifest is only trusted to choose an installer. For scripts and for the two package formats above, the manifest is trusted completely — it names a URL and the bytes at that URL are run with SYSTEM privileges, unexamined.
The manifest itself is not verified. It is fetched over HTTP(S) and parsed. There is no signature over the manifest document, no pinning of its host, and no validation beyond JSON or YAML well-formedness.
Use HTTPS for the manifest URL and for every package URL. Nothing in the tool requires it:
the URL is passed to HttpClient as given, and an http:// URL is fetched without
complaint. Over plain HTTP, both the manifest and any Authorization header attached to
that request travel in the clear, and any network position between the device and the host
can substitute the manifest — which, for the unsigned item types above, is equivalent to
remote code execution as SYSTEM.
TLS trust is the platform's: certificate validation follows the machine's trust store and
schannel defaults, and the tool neither pins a certificate nor relaxes validation. Requests
carry User-Agent: BootstrapMate/{version}.
Redirects are followed, because a default HttpClient follows them. The FollowRedirects
setting does not change this — see Serving Manifests and Packages.
The AuthorizationHeader value is sent verbatim as the Authorization header on the
manifest request, and on a package download only when the package URL's host matches the
manifest URL's host, compared case-insensitively. Anything hosted elsewhere is fetched
without it and the run logs Authorization header withheld for cross-host download: {url}
at Debug level.
That scoping is a security property, not an optimisation: it stops an org credential being handed to whatever third-party host a manifest entry happens to name. Keep it in mind when you split a manifest and its payloads across hosts — an authenticated package host that is not the manifest host will receive anonymous requests and reject them.
The header lives in the registry, and under policy it is readable by anything running with sufficient privilege on the device. Scope the token it carries accordingly: read-only, to the paths that serve your manifest and packages, and rotatable.