Skip to content

Commit f16cb3d

Browse files
Complete artifact signing module paths
Route MSIX signing through the shared portable signing provider so Artifact Signing and Key Vault cover that format too. Let the PowerShell module package signing helper use either Azure Key Vault or Artifact Signing parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3a85f07 commit f16cb3d

5 files changed

Lines changed: 151 additions & 46 deletions

File tree

‎PowerShell/package.ps1‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ param(
2222

2323
[string] $AzureKeyVaultTenantId,
2424

25+
[string] $ArtifactSigningEndpoint,
26+
27+
[string] $ArtifactSigningAccountName,
28+
29+
[string] $ArtifactSigningProfileName,
30+
31+
[string] $ArtifactSigningAccessToken,
32+
33+
[switch] $ArtifactSigningManagedIdentity,
34+
35+
[string] $ArtifactSigningTenantId,
36+
37+
[string] $ArtifactSigningClientId,
38+
39+
[string] $ArtifactSigningClientSecret,
40+
2541
[string] $TimestampServer,
2642

2743
[ValidateSet('Sha256', 'Sha384', 'Sha512')]
@@ -84,6 +100,14 @@ if ($SignModule) {
84100
-AzureKeyVaultClientId $AzureKeyVaultClientId `
85101
-AzureKeyVaultClientSecret $AzureKeyVaultClientSecret `
86102
-AzureKeyVaultTenantId $AzureKeyVaultTenantId `
103+
-ArtifactSigningEndpoint $ArtifactSigningEndpoint `
104+
-ArtifactSigningAccountName $ArtifactSigningAccountName `
105+
-ArtifactSigningProfileName $ArtifactSigningProfileName `
106+
-ArtifactSigningAccessToken $ArtifactSigningAccessToken `
107+
-ArtifactSigningManagedIdentity:$ArtifactSigningManagedIdentity `
108+
-ArtifactSigningTenantId $ArtifactSigningTenantId `
109+
-ArtifactSigningClientId $ArtifactSigningClientId `
110+
-ArtifactSigningClientSecret $ArtifactSigningClientSecret `
87111
-TimestampServer $TimestampServer `
88112
-HashAlgorithm $HashAlgorithm `
89113
-TimestampHashAlgorithm $TimestampHashAlgorithm | Out-Host

‎PowerShell/sign-module.ps1‎

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ param(
1616

1717
[string] $AzureKeyVaultTenantId,
1818

19+
[string] $ArtifactSigningEndpoint,
20+
21+
[string] $ArtifactSigningAccountName,
22+
23+
[string] $ArtifactSigningProfileName,
24+
25+
[string] $ArtifactSigningAccessToken,
26+
27+
[switch] $ArtifactSigningManagedIdentity,
28+
29+
[string] $ArtifactSigningTenantId,
30+
31+
[string] $ArtifactSigningClientId,
32+
33+
[string] $ArtifactSigningClientSecret,
34+
1935
[string] $TimestampServer,
2036

2137
[ValidateSet('Sha256', 'Sha384', 'Sha512')]
@@ -54,39 +70,96 @@ function Get-PsignModuleSigningTargets {
5470
return $targets.ToArray()
5571
}
5672

57-
function Assert-PsignModuleSigningParameters {
58-
foreach ($name in @(
59-
'AzureKeyVaultUrl',
60-
'AzureKeyVaultCertificate',
61-
'AzureKeyVaultClientId',
62-
'AzureKeyVaultClientSecret',
63-
'AzureKeyVaultTenantId',
64-
'TimestampServer'
65-
)) {
73+
function Test-TextValue {
74+
param([string] $Value)
75+
76+
return -not [string]::IsNullOrWhiteSpace($Value)
77+
}
78+
79+
function Assert-RequiredTextParameters {
80+
param([string[]] $Names)
81+
82+
foreach ($name in $Names) {
6683
if ([string]::IsNullOrWhiteSpace((Get-Variable -Name $name -ValueOnly))) {
6784
throw "$name is required when signing a PowerShell module release payload."
6885
}
6986
}
7087
}
7188

89+
function Assert-PsignModuleSigningParameters {
90+
Assert-RequiredTextParameters -Names @('TimestampServer')
91+
92+
$hasAzureKeyVault = (Test-TextValue $AzureKeyVaultUrl) -or
93+
(Test-TextValue $AzureKeyVaultCertificate)
94+
$hasArtifactSigning = (Test-TextValue $ArtifactSigningEndpoint) -or
95+
(Test-TextValue $ArtifactSigningAccountName) -or
96+
(Test-TextValue $ArtifactSigningProfileName)
97+
98+
if ($hasAzureKeyVault -eq $hasArtifactSigning) {
99+
throw "Provide exactly one cloud signing provider for the PowerShell module release payload: Azure Key Vault or Artifact Signing."
100+
}
101+
102+
if ($hasAzureKeyVault) {
103+
Assert-RequiredTextParameters -Names @(
104+
'AzureKeyVaultUrl',
105+
'AzureKeyVaultCertificate',
106+
'AzureKeyVaultClientId',
107+
'AzureKeyVaultClientSecret',
108+
'AzureKeyVaultTenantId'
109+
)
110+
return
111+
}
112+
113+
Assert-RequiredTextParameters -Names @(
114+
'ArtifactSigningEndpoint',
115+
'ArtifactSigningAccountName',
116+
'ArtifactSigningProfileName'
117+
)
118+
}
119+
72120
function Invoke-PsignModuleSigning {
73121
param(
74122
[Parameter(Mandatory = $true)]
75123
[string] $Target
76124
)
77125

78-
Set-PsignSignature `
79-
-LiteralPath $Target `
80-
-AzureKeyVaultUrl $AzureKeyVaultUrl `
81-
-AzureKeyVaultCertificate $AzureKeyVaultCertificate `
82-
-AzureKeyVaultClientId $AzureKeyVaultClientId `
83-
-AzureKeyVaultClientSecret $AzureKeyVaultClientSecret `
84-
-AzureKeyVaultTenantId $AzureKeyVaultTenantId `
85-
-TimestampServer $TimestampServer `
86-
-TimestampHashAlgorithm $TimestampHashAlgorithm `
87-
-HashAlgorithm $HashAlgorithm `
88-
-Force `
89-
-ErrorAction Stop | Out-Null
126+
$signArgs = @{
127+
LiteralPath = $Target
128+
TimestampServer = $TimestampServer
129+
TimestampHashAlgorithm = $TimestampHashAlgorithm
130+
HashAlgorithm = $HashAlgorithm
131+
Force = $true
132+
ErrorAction = 'Stop'
133+
}
134+
135+
if (Test-TextValue $AzureKeyVaultUrl) {
136+
$signArgs.AzureKeyVaultUrl = $AzureKeyVaultUrl
137+
$signArgs.AzureKeyVaultCertificate = $AzureKeyVaultCertificate
138+
$signArgs.AzureKeyVaultClientId = $AzureKeyVaultClientId
139+
$signArgs.AzureKeyVaultClientSecret = $AzureKeyVaultClientSecret
140+
$signArgs.AzureKeyVaultTenantId = $AzureKeyVaultTenantId
141+
} else {
142+
$signArgs.ArtifactSigningEndpoint = $ArtifactSigningEndpoint
143+
$signArgs.ArtifactSigningAccountName = $ArtifactSigningAccountName
144+
$signArgs.ArtifactSigningProfileName = $ArtifactSigningProfileName
145+
if (Test-TextValue $ArtifactSigningAccessToken) {
146+
$signArgs.ArtifactSigningAccessToken = $ArtifactSigningAccessToken
147+
}
148+
if ($ArtifactSigningManagedIdentity.IsPresent) {
149+
$signArgs.ArtifactSigningManagedIdentity = $true
150+
}
151+
if (Test-TextValue $ArtifactSigningTenantId) {
152+
$signArgs.ArtifactSigningTenantId = $ArtifactSigningTenantId
153+
}
154+
if (Test-TextValue $ArtifactSigningClientId) {
155+
$signArgs.ArtifactSigningClientId = $ArtifactSigningClientId
156+
}
157+
if (Test-TextValue $ArtifactSigningClientSecret) {
158+
$signArgs.ArtifactSigningClientSecret = $ArtifactSigningClientSecret
159+
}
160+
}
161+
162+
Set-PsignSignature @signArgs | Out-Null
90163
}
91164

92165
function Assert-PsignModuleSignatures {

‎crates/psign-portable-core/src/lib.rs‎

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,21 +1063,17 @@ fn sign_msix(request: &PortableSignRequest, output_path: &Path) -> Result<()> {
10631063
std::fs::read(&request.path).with_context(|| format!("read {}", request.path.display()))?;
10641064
let staged = stage_flat_msix_for_signature(&package, request.hash_algorithm)
10651065
.with_context(|| format!("stage {} for MSIX signing", request.path.display()))?;
1066-
let (signer_cert, private_key, chain) = load_signing_material(request)?;
1067-
let pkcs7 = pkcs7::create_msix_authenticode_pkcs7_der_rsa(
1068-
&staged,
1069-
&ext,
1070-
request.hash_algorithm.into(),
1071-
signer_cert,
1072-
chain,
1073-
private_key,
1074-
)
1075-
.with_context(|| {
1076-
format!(
1077-
"create portable MSIX Authenticode signature for {}",
1078-
request.path.display()
1079-
)
1080-
})?;
1066+
let provider = load_signing_provider(request)?;
1067+
let digest_algorithm = request.hash_algorithm.into();
1068+
let indirect = pkcs7::msix_spc_indirect_data(&staged, &ext, digest_algorithm)?;
1069+
let pkcs7 = provider
1070+
.create_authenticode_pkcs7(indirect, digest_algorithm)
1071+
.with_context(|| {
1072+
format!(
1073+
"create portable MSIX Authenticode signature for {}",
1074+
request.path.display()
1075+
)
1076+
})?;
10811077
let pkcs7 = maybe_timestamp_pkcs7(request, pkcs7)
10821078
.with_context(|| format!("timestamp {}", request.path.display()))?;
10831079
let mut p7x = b"PKCX".to_vec();

‎crates/psign-sip-digest/src/pkcs7.rs‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,23 @@ pub fn create_msix_authenticode_pkcs7_der_rsa(
343343
chain_certs: Vec<Certificate>,
344344
private_key: RsaPrivateKey,
345345
) -> Result<Vec<u8>> {
346+
let indirect =
347+
msix_spc_indirect_data(package_with_signature_part, extension, digest_algorithm)?;
348+
create_authenticode_pkcs7_der_rsa(
349+
indirect,
350+
digest_algorithm,
351+
signer_cert,
352+
chain_certs,
353+
private_key,
354+
)
355+
}
356+
357+
/// Build the Authenticode `SpcIndirectDataContent` for a cleartext MSIX / APPX package.
358+
pub fn msix_spc_indirect_data(
359+
package_with_signature_part: &[u8],
360+
extension: &str,
361+
digest_algorithm: AuthenticodeSigningDigest,
362+
) -> Result<SpcIndirectDataContent> {
346363
let (kind, appx_blob) =
347364
crate::msix_digest::msix_authenticode_digest_blob(package_with_signature_part, extension)?;
348365
if kind != digest_algorithm.pe_hash_kind() {
@@ -352,7 +369,7 @@ pub fn create_msix_authenticode_pkcs7_der_rsa(
352369
digest_algorithm
353370
));
354371
}
355-
let indirect = SpcIndirectDataContent {
372+
Ok(SpcIndirectDataContent {
356373
data: SpcAttributeTypeAndOptionalValue {
357374
value_type: SPC_MSI_SIGINFO_OBJID,
358375
value: Any::from_der(SPC_MSI_SIGINFO_VALUE_DER)
@@ -363,14 +380,7 @@ pub fn create_msix_authenticode_pkcs7_der_rsa(
363380
digest: OctetString::new(appx_blob)
364381
.map_err(|e| anyhow!("APPX SpcIndirectData digest OCTET STRING: {e}"))?,
365382
},
366-
};
367-
create_authenticode_pkcs7_der_rsa(
368-
indirect,
369-
digest_algorithm,
370-
signer_cert,
371-
chain_certs,
372-
private_key,
373-
)
383+
})
374384
}
375385

376386
/// Create PKCS#7 `ContentInfo(SignedData)` DER for a PE Authenticode signature using an RSA private key.

‎docs/portable-powershell-module.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ The portable cert store follows the same layout as `psign-tool cert-store`: `<ba
3333

3434
`Set-PsignSignature` supports `-IncludeChain Signer|NotRoot|All` (default `NotRoot`), optional `-ChainCertificatePath`, `-TimestampServer`, and `-TimestampHashAlgorithm Sha1|Sha256|Sha384|Sha512`.
3535

36+
Cloud signing is available through Azure Key Vault parameters (`-AzureKeyVaultUrl`, `-AzureKeyVaultCertificate`, and access token, managed identity, or client credentials) or Azure Artifact Signing / Trusted Signing parameters (`-ArtifactSigningEndpoint`, account/profile, and access token, managed identity, or client credentials). The portable module native library is built with both cloud-signing feature sets.
37+
3638
## Explicit trust
3739

3840
`Get-PsignSignature` validates digest binding by default and does not claim OS trust. Explicit portable trust can be requested with anchors:
@@ -86,7 +88,7 @@ pwsh -File .\PowerShell\package.ps1 -Configuration Release -NativeArtifactsRoot
8688

8789
The native artifact root should contain directories such as `psign-core-win-x64`, `psign-core-linux-x64`, and `psign-core-osx-arm64`, each containing the packaged native library name for that RID.
8890

89-
Release packaging can also sign the staged module payload before creating the `.nupkg`. The release workflow Authenticode-signs the module manifest, root script module, format file, and managed assemblies with `Set-PsignSignature`, then verifies them through the built `Devolutions.Psign` module, while preserving the separately signed Windows native `psign-core.dll` artifacts imported from the native signing job. The release ZIP remains only a transport archive for those signed files; it is not signed as a custom ZIP Authenticode package.
91+
Release packaging can also sign the staged module payload before creating the `.nupkg`. The release workflow Authenticode-signs the module manifest, root script module, format file, and managed assemblies with `Set-PsignSignature`, then verifies them through the built `Devolutions.Psign` module, while preserving the separately signed Windows native `psign-core.dll` artifacts imported from the native signing job. The packaging helper accepts Azure Key Vault or Azure Artifact Signing parameters; the release workflow currently supplies Azure Key Vault secrets. The release ZIP remains only a transport archive for those signed files; it is not signed as a custom ZIP Authenticode package.
9092

9193
## Migrating from built-in Authenticode cmdlets
9294

0 commit comments

Comments
 (0)