@@ -75,24 +75,63 @@ function Convert-FileUriToPath {
7575 return $null
7676}
7777
78- function Resolve-SigningPublicKey {
78+ # Pinned production release signing public keys (trust roots). These stay empty
79+ # until the repository owner publishes production keys (see docs/release-trust.md,
80+ # "Remaining Owner Step"). While empty, release installs keep the opt-in behavior:
81+ # a signature is verified only when a key is supplied via the parameters/env vars
82+ # or ROCM_CLI_REQUIRE_SIGNATURE=1. Once populated, release-channel installs verify
83+ # signatures by default with these keys as trust roots. Two slots support
84+ # zero-downtime key rotation: a release signed with either the current or the
85+ # pre-staged next key verifies, so the next key is trusted before its first use.
86+ $PinnedReleasePublicKeyCurrent = @"
87+ -----BEGIN PUBLIC KEY-----
88+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxuyScR/BzV+kuXqWAHtE
89+ +9xiPCWURUYnsio9MOrf2Xe01mBngP7qPcF13+5nrfT3EnuxOn5rSCYwjOndlS+c
90+ KzOw6GZXJD/ZqeojnbXxxsxlftAQHHEke1WCtga5ZEFxOauTeB5nTV/IbjMAl2Xc
91+ M4PaudpFFH/6j/E3gongDmt0hWdpMLbaCcd3i1vMTEsaHZooNoAbJ/dIAHR/dDNM
92+ pScZAZoy0LL3Afhn5Hiv71trfbfnnboVSdhnCoMmisl6/sK55zR7VM8hWDTTowl3
93+ ultUtiz4emTfXDCb2RptOgoydBA+mu9z6O4eVF8S5dVr/S834SK6dD2fWHNnT0dc
94+ JwIDAQAB
95+ -----END PUBLIC KEY-----
96+ "@
97+ $PinnedReleasePublicKeyNext = " "
98+
99+ function Test-HasPinnedReleaseKeys {
100+ return (-not [string ]::IsNullOrWhiteSpace($PinnedReleasePublicKeyCurrent )) `
101+ -or (-not [string ]::IsNullOrWhiteSpace($PinnedReleasePublicKeyNext ))
102+ }
103+
104+ # Return the candidate signing public keys as an array of file paths. An explicit
105+ # parameter/env-provided key wins (an escape hatch for private mirrors); otherwise
106+ # the pinned production trust roots are used.
107+ function Resolve-SigningPublicKeys {
79108 param (
80109 [string ] $KeyPath ,
81110 [string ] $KeyPem ,
82111 [string ] $TempRoot
83112 )
84113
85114 if (-not [string ]::IsNullOrWhiteSpace($KeyPath )) {
86- return Resolve-InstallerPath $KeyPath
115+ return @ (( Resolve-InstallerPath $KeyPath ))
87116 }
88117
89118 if (-not [string ]::IsNullOrWhiteSpace($KeyPem )) {
90119 $path = Join-Path $TempRoot " rocm-cli-signing-public-key.pem"
91120 Set-Content - LiteralPath $path - Value $KeyPem - Encoding ascii
92- return $path
121+ return @ ( $path )
93122 }
94123
95- return " "
124+ $paths = @ ()
125+ $index = 0
126+ foreach ($pinned in @ ($PinnedReleasePublicKeyCurrent , $PinnedReleasePublicKeyNext )) {
127+ if (-not [string ]::IsNullOrWhiteSpace($pinned )) {
128+ $index ++
129+ $path = Join-Path $TempRoot " rocm-cli-pinned-key-$index .pem"
130+ Set-Content - LiteralPath $path - Value $pinned - Encoding ascii
131+ $paths += $path
132+ }
133+ }
134+ return $paths
96135}
97136
98137function Save-File {
@@ -136,14 +175,17 @@ function Confirm-ArchiveSignature {
136175 param (
137176 [string ] $ArchivePath ,
138177 [string ] $SignaturePath ,
139- [string ] $PublicKeyPath
178+ [string []] $PublicKeyPaths
140179 )
141180
142181 Confirm-Command openssl
143- & openssl dgst - sha256 - verify $PublicKeyPath - signature $SignaturePath $ArchivePath | Out-Null
144- if ($LASTEXITCODE -ne 0 ) {
145- Fail " signature verification failed"
182+ foreach ($key in $PublicKeyPaths ) {
183+ & openssl dgst - sha256 - verify $key - signature $SignaturePath $ArchivePath | Out-Null
184+ if ($LASTEXITCODE -eq 0 ) {
185+ return
186+ }
146187 }
188+ Fail " signature verification failed"
147189}
148190
149191function Get-ExpectedSha256 {
@@ -380,10 +422,17 @@ $sigPath = "$archivePath.sig"
380422$extractDir = Join-Path $tempRoot " extract"
381423$manifestPath = Join-Path $InstallDir " .rocm-cli-manifest"
382424$signatureRequired = $RequireSignature -or (Test-Truthy $env: ROCM_CLI_REQUIRE_SIGNATURE )
425+ # Production default: once release trust roots are pinned, release-channel installs
426+ # always verify. An unset or 0 ROCM_CLI_REQUIRE_SIGNATURE does not lower this
427+ # floor; pass -SigningPublicKeyPath/-Pem (or the env vars) to point at an
428+ # alternate key (e.g. a private mirror) instead.
429+ if ($Channel -eq " release" -and (Test-HasPinnedReleaseKeys )) {
430+ $signatureRequired = $true
431+ }
383432
384433try {
385434 New-Item - ItemType Directory - Force - Path $tempRoot , $extractDir | Out-Null
386- $signingPublicKey = Resolve-SigningPublicKey $SigningPublicKeyPath $SigningPublicKeyPem $tempRoot
435+ $signingPublicKeys = @ ( Resolve-SigningPublicKeys $SigningPublicKeyPath $SigningPublicKeyPem $tempRoot )
387436
388437 Write-Host " rocm-cli installer"
389438 Write-Host " repo: $Repo "
@@ -400,12 +449,12 @@ try {
400449 Fail " checksum verification failed"
401450 }
402451
403- if ($signatureRequired -or -not [ string ]::IsNullOrWhiteSpace( $signingPublicKey )) {
404- if ([ string ]::IsNullOrWhiteSpace( $signingPublicKey ) ) {
452+ if ($signatureRequired -or ( $signingPublicKeys .Count -gt 0 )) {
453+ if ($signingPublicKeys .Count -eq 0 ) {
405454 Fail " signature verification requires ROCM_CLI_SIGNING_PUBLIC_KEY_PATH, ROCM_CLI_SIGNING_PUBLIC_KEY_PEM, -SigningPublicKeyPath, or -SigningPublicKeyPem"
406455 }
407456 Save-File $sigUrl $sigPath " required signature sidecar is missing or unavailable"
408- Confirm-ArchiveSignature $archivePath $sigPath $signingPublicKey
457+ Confirm-ArchiveSignature $archivePath $sigPath $signingPublicKeys
409458 Write-Host " signature verified"
410459 }
411460
0 commit comments