fix(launcher): harden LA11 gate evidence

This commit is contained in:
Erik 2026-08-15 01:45:15 +02:00
parent accd01a008
commit 9f9c116792
7 changed files with 514 additions and 89 deletions

View file

@ -165,6 +165,84 @@ function Write-PayloadFile([string]$Root, [string]$Name, [string]$Content) {
$null = New-Item -ItemType Directory -Force -Path $directory
[IO.File]::WriteAllText($path, $Content, [Text.UTF8Encoding]::new($false))
}
function Get-ZipUInt16([byte[]]$Bytes, [int]$Offset) {
return [int]$Bytes[$Offset] -bor ([int]$Bytes[$Offset + 1] -shl 8)
}
function Get-ZipUInt32([byte[]]$Bytes, [int]$Offset) {
return [uint32]([uint32]$Bytes[$Offset] -bor
([uint32]$Bytes[$Offset + 1] -shl 8) -bor
([uint32]$Bytes[$Offset + 2] -shl 16) -bor
([uint32]$Bytes[$Offset + 3] -shl 24))
}
function Test-ZipExecutableName([string]$Name) {
return $Name -cin @(
'AcDream.App', 'acdream-headless', 'acdream-launcher', 'acdream-bake')
}
function Assert-ZipUnixMetadata([string]$Path) {
[byte[]]$bytes = [IO.File]::ReadAllBytes($Path)
$eocd = $bytes.Length - 22
if ($eocd -lt 0 -or (Get-ZipUInt32 $bytes $eocd) -ne 0x06054b50 -or
(Get-ZipUInt16 $bytes ($eocd + 20)) -ne 0) {
throw "Fixture ZIP end record is invalid: $Path"
}
$entryCount = Get-ZipUInt16 $bytes ($eocd + 10)
$centralSize = Get-ZipUInt32 $bytes ($eocd + 12)
[uint64]$cursor = Get-ZipUInt32 $bytes ($eocd + 16)
$centralEnd = $cursor + $centralSize
if ($centralEnd -ne $eocd) { throw "Fixture ZIP central bounds are invalid: $Path" }
$rawModes = @{}
for ($index = 0; $index -lt $entryCount; $index++) {
if ($cursor + 46 -gt $centralEnd -or
(Get-ZipUInt32 $bytes ([int]$cursor)) -ne 0x02014b50) {
throw "Fixture ZIP central entry is invalid: $Path"
}
if ($bytes[[int]$cursor + 5] -ne 3) {
throw "Fixture ZIP entry origin is not Unix: $Path"
}
$nameLength = Get-ZipUInt16 $bytes ([int]$cursor + 28)
$extraLength = Get-ZipUInt16 $bytes ([int]$cursor + 30)
$commentLength = Get-ZipUInt16 $bytes ([int]$cursor + 32)
$name = [Text.Encoding]::UTF8.GetString(
$bytes,
[int]$cursor + 46,
$nameLength)
$expectedMode = if (Test-ZipExecutableName $name) { 0x81ED } else { 0x81A4 }
$external = Get-ZipUInt32 $bytes ([int]$cursor + 38)
$expectedExternal = [uint32](([uint64]$expectedMode) -shl 16)
if ($external -ne $expectedExternal) {
throw "Fixture ZIP entry '$name' has wrong raw type/mode bits."
}
$rawModes[$name] = $expectedMode
$cursor += 46 + $nameLength + $extraLength + $commentLength
}
if ($cursor -ne $centralEnd) { throw "Fixture ZIP central length is invalid: $Path" }
Add-Type -AssemblyName System.IO.Compression
$stream = [IO.File]::OpenRead($Path)
try {
$archive = [IO.Compression.ZipArchive]::new(
$stream,
[IO.Compression.ZipArchiveMode]::Read,
$false,
[Text.Encoding]::UTF8)
try {
if ($archive.Entries.Count -ne $rawModes.Count) {
throw "Fixture ZIP entry count changed through ZipArchive: $Path"
}
foreach ($entry in $archive.Entries) {
$mode = ($entry.ExternalAttributes -shr 16) -band 0xffff
if (-not $rawModes.ContainsKey($entry.FullName) -or
$mode -ne $rawModes[$entry.FullName]) {
throw "ZipArchive reports wrong type/mode for '$($entry.FullName)'."
}
}
}
finally { $archive.Dispose() }
}
finally { $stream.Dispose() }
}
$payloadRoot = Join-Path $OutputDirectory 'deterministic-payloads'
$payloads = [ordered]@{
ClientWin = Join-Path $payloadRoot 'client-win'
@ -208,6 +286,9 @@ try {
[Globalization.CultureInfo]::CurrentUICulture = $culture
$destination = Join-Path $OutputDirectory "fixture-$cultureName"
& $fixture -OutputDirectory $destination @fixtureParameters
foreach ($zip in @(Get-ChildItem -LiteralPath $destination -Filter '*.zip' -File -Recurse)) {
Assert-ZipUnixMetadata $zip.FullName
}
$relativePaths = [string[]]@(Get-ChildItem -LiteralPath $destination -File -Recurse |
Where-Object { $_.Name -ne 'fixture-report.json' } |
ForEach-Object {
@ -235,9 +316,49 @@ $digestBytes = [Security.Cryptography.SHA256]::HashData(
[Text.Encoding]::UTF8.GetBytes($firstInventory))
$deterministicDigest = [Convert]::ToHexString($digestBytes).ToLowerInvariant()
$expectedCrossPlatformDigest =
'9c77b7204dd19e77fad62e572304d52e810afe2d0821c2ec57a692d27a0cc167'
'cc58d5717de6686690b7f01213c9d52a99aef49447ff645e134f8c97ec8e3a76'
if ($deterministicDigest -cne $expectedCrossPlatformDigest) {
throw 'Fixture artifact hashes differ from the pinned Windows/Linux contract.'
throw "Fixture artifact hashes differ from the pinned Windows/Linux contract: actual $deterministicDigest."
}
$nativeExtractionModesValidated = $false
if ($IsLinux) {
$unzip = @(Get-Command unzip -CommandType Application -ErrorAction Stop)[0].Source
$extractClient = Join-Path $OutputDirectory 'native-extract-client'
$extractLauncher = Join-Path $OutputDirectory 'native-extract-launcher'
$null = New-Item -ItemType Directory -Path $extractClient
$null = New-Item -ItemType Directory -Path $extractLauncher
& $unzip -qq (Join-Path $OutputDirectory 'fixture-en-US/A/client-linux-x64.zip') `
-d $extractClient
if ($LASTEXITCODE -ne 0) { throw 'Native client ZIP extraction failed.' }
& $unzip -qq (Join-Path $OutputDirectory 'fixture-en-US/A/launcher-linux-x64.zip') `
-d $extractLauncher
if ($LASTEXITCODE -ne 0) { throw 'Native launcher ZIP extraction failed.' }
$mode755 = [IO.UnixFileMode]::UserRead -bor [IO.UnixFileMode]::UserWrite -bor
[IO.UnixFileMode]::UserExecute -bor [IO.UnixFileMode]::GroupRead -bor
[IO.UnixFileMode]::GroupExecute -bor [IO.UnixFileMode]::OtherRead -bor
[IO.UnixFileMode]::OtherExecute
$mode644 = [IO.UnixFileMode]::UserRead -bor [IO.UnixFileMode]::UserWrite -bor
[IO.UnixFileMode]::GroupRead -bor [IO.UnixFileMode]::OtherRead
foreach ($path in @(
(Join-Path $extractClient 'AcDream.App'),
(Join-Path $extractClient 'acdream-headless'),
(Join-Path $extractLauncher 'acdream-launcher'),
(Join-Path $extractLauncher 'acdream-bake'))) {
if ([IO.File]::GetUnixFileMode($path) -ne $mode755) {
throw "Native extraction did not retain mode 0755: $path"
}
}
foreach ($path in @(
(Join-Path $extractClient 'nested/I.txt'),
(Join-Path $extractClient 'campaign-la-fixture-release.txt'),
(Join-Path $extractLauncher 'nested/Z.txt'),
(Join-Path $extractLauncher 'campaign-la-fixture-release.txt'))) {
if ([IO.File]::GetUnixFileMode($path) -ne $mode644) {
throw "Native extraction did not retain mode 0644: $path"
}
}
$nativeExtractionModesValidated = $true
}
$summary = [ordered]@{
@ -248,6 +369,9 @@ $summary = [ordered]@{
cultures = @('en-US', 'tr-TR', 'sv-SE')
fixtureArtifactSetSha256 = $deterministicDigest
crossPlatformExpectedSha256 = $expectedCrossPlatformDigest
zipOrigin = 'unix'
zipModesValidated = $true
nativeExtractionModesValidated = $nativeExtractionModesValidated
}
$summary | ConvertTo-Json -Depth 5 |
Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding utf8NoBOM