Files
fips/packaging/windows/build-zip.ps1
T
Johnathan Corgan 7ac3bc51ea Restrict C:\ProgramData\fips to SYSTEM and Administrators
install-service.ps1 created C:\ProgramData\fips without setting an ACL,
so it inherited C:\ProgramData's access: any local user could read the
node's identity key there, and could create a fips.yaml, fips.key,
hosts, peers.allow or peers.deny in the directory before one existed,
for the service, running as LocalSystem, to read. The service log is
written there too.

The installer now builds an ACL that grants only SYSTEM and
Administrators and does not inherit, and creates a new directory with
that ACL already applied, using the .NET call each PowerShell edition
provides. Created first and restricted afterwards, the empty directory
would carry C:\ProgramData's access in between, and any local user
could turn it into a junction in that time with no privilege and
without changing its owner.

An existing directory is refused if it is a link or junction or is
owned by an account other than SYSTEM, Administrators or the installing
administrator, since a user who created it could swap it for a link to
another directory. Otherwise the installer makes Administrators the
owner, refuses the directory if it has since become a link and any link
or folder inside it, applies the ACL, and resets the owner and ACL of
each existing file. That check runs before the ACL is applied, because
applying it rewrites the inherited entries of everything below the
directory, and runs again after it and after the reset. Every icacls
call acts on a link rather than its target and has its exit code
checked. Files created in the directory later, by the service or
fipsctl keygen, take the same ACL. Well-known SIDs are used so the step
works on non-English Windows.

A user who created the directory keeps full control of it through the
entry C:\ProgramData's CREATOR OWNER grant leaves for their account,
and taking ownership changes only the owner. So each refusal tells the
operator to copy out anything needed and delete the directory, taking
ownership only if that is what it takes to delete it, and an icacls
failure says to delete the directory or the file, or simply retry if
the file has gone.

Packaging tests check the order of these steps and that they come
before any path inside the directory is touched; that the ACL is built
with both SIDs and both inheritance flags and passed to the directory's
creation; the refusal conditions themselves (the owner test, the
directory link test at the start and in each recheck, and a link or
folder entry each refused on its own); that every icacls call acts on
links and is checked; and that takeown is never offered as the
recovery. The README and the fips reference say that reading the
directory now needs an elevated prompt (an unelevated foreground run
may skip the config there or fail with an access error), that the
installer should run before fipsctl keygen, and that it should run
again after files are moved into the directory, since a moved file
keeps its old permissions.
2026-09-24 23:39:56 +00:00

152 lines
4.6 KiB
PowerShell

# Build a Windows ZIP package for FIPS.
#
# Usage: powershell -File packaging/windows/build-zip.ps1 [-Version <version>] [-NoBuild]
# Output: deploy/fips-<version>-windows-x86_64.zip
#
# Prerequisites: Rust toolchain installed
param(
[string]$Version = "",
[switch]$NoBuild
)
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$PackagingDir = Split-Path -Parent $ScriptDir
$ProjectRoot = Split-Path -Parent $PackagingDir
# Derive version from Cargo.toml if not provided
if (-not $Version) {
$cargoToml = Get-Content "$ProjectRoot\Cargo.toml" -Raw
if ($cargoToml -match 'version\s*=\s*"([^"]+)"') {
$Version = $Matches[1]
} else {
Write-Error "Could not determine version from Cargo.toml"
exit 1
}
}
$Arch = "x86_64"
$PkgName = "fips-$Version-windows-$Arch"
$DeployDir = "$ProjectRoot\deploy"
$StagingDir = "$env:TEMP\fips-staging-$([guid]::NewGuid().ToString('N'))"
$BinaryDir = "$ProjectRoot\target\release"
Write-Host "Building FIPS v$Version for Windows $Arch..."
# Build release binaries
if (-not $NoBuild) {
Push-Location $ProjectRoot
cargo build --release
if ($LASTEXITCODE -ne 0) {
Write-Error "cargo build failed"
exit 1
}
Pop-Location
}
# Verify binaries exist
$Binaries = @("fips.exe", "fipsctl.exe", "fipstop.exe")
foreach ($bin in $Binaries) {
if (-not (Test-Path "$BinaryDir\$bin")) {
Write-Error "Missing binary: $BinaryDir\$bin"
exit 1
}
}
# Create staging directory
New-Item -ItemType Directory -Force -Path $StagingDir | Out-Null
# Copy binaries
foreach ($bin in $Binaries) {
Copy-Item "$BinaryDir\$bin" "$StagingDir\$bin"
}
# Copy config
Copy-Item "$PackagingDir\common\fips.yaml" "$StagingDir\fips.yaml"
Copy-Item "$PackagingDir\common\hosts" "$StagingDir\hosts"
# Copy helper scripts
Copy-Item "$ScriptDir\install-service.ps1" "$StagingDir\install-service.ps1"
Copy-Item "$ScriptDir\uninstall-service.ps1" "$StagingDir\uninstall-service.ps1"
# Create README
@"
FIPS v$Version for Windows
==========================
Quick Start (foreground mode):
.\fips.exe -c fips.yaml
Windows Service:
# Install (requires Administrator)
powershell -File install-service.ps1
# Manage
sc start fips
sc stop fips
# Uninstall
powershell -File uninstall-service.ps1
TUN Support:
Download wintun.dll from https://www.wintun.net/ and place it
in the same directory as fips.exe. Running the daemon requires
Administrator privileges for TUN creation.
Control Socket:
The control socket uses TCP on localhost:21210.
fipsctl and fipstop connect to this port automatically.
Configuration:
The service reads C:\ProgramData\fips\fips.yaml, where
install-service.ps1 puts it, and keeps fips.key, hosts,
peers.allow and peers.deny beside it. Edit fips.yaml there
before starting the service.
install-service.ps1 restricts C:\ProgramData\fips to SYSTEM
and Administrators before writing into it. Reading or editing
files there, fipsctl keygen, fipsctl address with no argument,
and a foreground fips.exe run that relies on
C:\ProgramData\fips\fips.yaml all need an elevated prompt.
Unelevated, a foreground run may skip that file without
saying so, or may fail with an access error.
A foreground run takes -c <file>, or reads
C:\ProgramData\fips\fips.yaml and then, as per-user overrides
the service does not read, %APPDATA%\fips\fips.yaml,
%USERPROFILE%\.fips.yaml and .\fips.yaml. The key file sits
beside the last config loaded.
fipsctl keygen writes to C:\ProgramData\fips by default and
needs an elevated prompt. Run install-service.ps1 before it:
a directory that keygen creates first carries
C:\ProgramData's access until the installer restricts it.
A file moved into C:\ProgramData\fips keeps its old
permissions. That includes a fips.yaml or fips.key moved from
%APPDATA%\fips as the daemon's warning suggests, so run
install-service.ps1 again after moving files there.
Logs:
The service logs to C:\ProgramData\fips\fips.log, rolled at
10 MiB with four old files kept. A foreground run logs to the
console.
"@ | Out-File -FilePath "$StagingDir\README.txt" -Encoding UTF8
# Create ZIP
New-Item -ItemType Directory -Force -Path $DeployDir | Out-Null
$ZipPath = "$DeployDir\$PkgName.zip"
if (Test-Path $ZipPath) { Remove-Item $ZipPath }
Compress-Archive -Path "$StagingDir\*" -DestinationPath $ZipPath
# Cleanup
Remove-Item -Recurse -Force $StagingDir
Write-Host ""
Write-Host "Package built: deploy\$PkgName.zip"
Write-Host " Size: $([math]::Round((Get-Item $ZipPath).Length / 1MB, 2)) MB"
Write-Host ""
Write-Host "Extract and run: .\fips.exe -c fips.yaml"