Files
lserp_cs_6.0/插件库/Lskj.AgentBridge/Deployment/Sign-LserpAgentPetHost.ps1
2026-08-14 14:28:28 +08:00

138 lines
5.3 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)][string]$HostDirectory,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[A-Fa-f0-9]{40}$')]
[string]$CertificateThumbprint,
[ValidateSet('CurrentUser', 'LocalMachine')]
[string]$CertificateStoreLocation = 'CurrentUser',
[Parameter(Mandatory = $true)][string]$TimestampUrl,
[string]$SignToolPath = ''
)
Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'
function Test-RegularFile([string]$Path, [long]$MaximumBytes) {
if (-not [IO.File]::Exists($Path)) { return $false }
$item = Get-Item -LiteralPath $Path -Force
return $item.Length -gt 0 -and $item.Length -le $MaximumBytes -and
(($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0)
}
function Find-SignTool([string]$ExplicitPath) {
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
$resolved = [IO.Path]::GetFullPath($ExplicitPath)
if (Test-RegularFile $resolved 128MB) { return $resolved }
throw 'signtool_invalid'
}
$command = Get-Command signtool.exe -ErrorAction SilentlyContinue
if ($null -ne $command -and (Test-RegularFile $command.Source 128MB)) {
return $command.Source
}
$programFilesX86 = [Environment]::GetFolderPath(
[Environment+SpecialFolder]::ProgramFilesX86)
foreach ($kitsVersion in @('10', '8.1')) {
$binRoot = Join-Path $programFilesX86 ("Windows Kits\{0}\bin" -f $kitsVersion)
if (-not [IO.Directory]::Exists($binRoot)) { continue }
$candidates = @(Get-ChildItem -LiteralPath $binRoot -Directory -Force |
Sort-Object Name -Descending | ForEach-Object {
Join-Path $_.FullName 'x86\signtool.exe'
Join-Path $_.FullName 'x64\signtool.exe'
})
$candidates += @(Join-Path $binRoot 'x86\signtool.exe')
foreach ($candidate in $candidates) {
if (Test-RegularFile $candidate 128MB) { return $candidate }
}
}
throw 'signtool_not_found'
}
if ($env:OS -ne 'Windows_NT') { throw 'windows_required' }
$timestampUri = $null
if (-not [Uri]::TryCreate($TimestampUrl, [UriKind]::Absolute, [ref]$timestampUri) -or
$timestampUri.Scheme -ne 'https' -or $timestampUri.UserInfo -or
$timestampUri.Fragment) {
throw 'https_timestamp_url_required'
}
$hostRoot = [IO.Path]::GetFullPath($HostDirectory).TrimEnd(
[char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar
if (-not [IO.Directory]::Exists($hostRoot)) { throw 'host_directory_missing' }
$rootItem = Get-Item -LiteralPath $hostRoot -Force
if (($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw 'host_directory_reparse_forbidden'
}
$criticalRelativePaths = @(
'Lskj.AgentPet.Host.exe',
'Lskj.AgentPet.Host.dll',
'Lskj.AgentPet.Host.Core.dll',
'lserp-agent-cli.exe'
)
$criticalPaths = @()
foreach ($relative in $criticalRelativePaths) {
$full = [IO.Path]::GetFullPath((Join-Path $hostRoot $relative))
if (-not $full.StartsWith($hostRoot, [StringComparison]::OrdinalIgnoreCase) -or
-not (Test-RegularFile $full 512MB)) {
throw 'host_critical_binary_missing'
}
$criticalPaths += $full
}
$normalizedThumbprint = $CertificateThumbprint.ToUpperInvariant()
$certificatePath = "Cert:\{0}\My\{1}" -f `
$CertificateStoreLocation, $normalizedThumbprint
if (-not (Test-Path -LiteralPath $certificatePath)) {
throw 'host_signing_certificate_missing'
}
$certificate = Get-Item -LiteralPath $certificatePath
$codeSigningOid = '1.3.6.1.5.5.7.3.3'
$hasCodeSigningEku = @($certificate.EnhancedKeyUsageList | Where-Object {
$_.ObjectId.Value -eq $codeSigningOid
}).Count -gt 0
$now = [DateTime]::UtcNow
if (-not $certificate.HasPrivateKey -or -not $hasCodeSigningEku -or
$certificate.NotBefore.ToUniversalTime() -gt $now -or
$certificate.NotAfter.ToUniversalTime() -le $now) {
throw 'host_signing_certificate_invalid'
}
$signTool = Find-SignTool $SignToolPath
$signed = @()
foreach ($criticalPath in $criticalPaths) {
$arguments = @(
'sign', '/nologo', '/sha1', $certificate.Thumbprint,
'/s', 'My', '/fd', 'SHA256', '/tr', $timestampUri.AbsoluteUri,
'/td', 'SHA256', $criticalPath
)
if ($CertificateStoreLocation -eq 'LocalMachine') {
$arguments = @(
'sign', '/nologo', '/sm', '/sha1', $certificate.Thumbprint,
'/s', 'My', '/fd', 'SHA256', '/tr', $timestampUri.AbsoluteUri,
'/td', 'SHA256', $criticalPath
)
}
& $signTool @arguments | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'host_authenticode_signing_failed' }
$signature = Get-AuthenticodeSignature -LiteralPath $criticalPath
if ($signature.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
$null -eq $signature.SignerCertificate -or
$signature.SignerCertificate.Thumbprint.ToUpperInvariant() -ne
$normalizedThumbprint -or
$null -eq $signature.TimeStamperCertificate) {
throw 'host_authenticode_verification_failed'
}
$signed += [ordered]@{
file = [IO.Path]::GetFileName($criticalPath)
sha256 = (Get-FileHash -LiteralPath $criticalPath -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
[ordered]@{
schemaVersion = '1.0'
certificateThumbprint = $normalizedThumbprint
timestampUrl = $timestampUri.AbsoluteUri
signed = $signed
} | ConvertTo-Json -Depth 4 -Compress