Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/dotnet-tool.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,20 @@ jobs:
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: |
cargo build --locked --release -p psign --bin psign-tool --target "${{ matrix.target }}"
$target = '${{ matrix.target }}'
$cargoArgs = @(
'build',
'--locked',
'--release',
'-p',
'psign',
'--bin',
'psign-tool',
'--target',
$target
)

cargo @cargoArgs

- name: Verify Windows version info
if: contains(matrix.target, 'windows-msvc')
Expand Down
99 changes: 71 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: Release version to build/publish (for example 0.1.0)
description: Release version to build/publish (for example 0.2.0)
required: true
type: string
publish_nuget:
Expand Down Expand Up @@ -217,7 +217,20 @@ jobs:
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: |
cargo build --locked --release -p psign --bin psign-tool --target "${{ matrix.target }}"
$target = '${{ matrix.target }}'
$cargoArgs = @(
'build',
'--locked',
'--release',
'-p',
'psign',
'--bin',
'psign-tool',
'--target',
$target
)

cargo @cargoArgs

- name: Verify Windows version info
if: contains(matrix.target, 'windows-msvc')
Expand Down Expand Up @@ -285,7 +298,7 @@ jobs:

sign_windows:
name: Sign and package Windows artifacts
runs-on: windows-2022
runs-on: ubuntu-latest
needs:
- preflight
- build
Expand All @@ -304,6 +317,12 @@ jobs:
name: psign-tool-windows-arm64.zip-unsigned-bin
path: work/win-arm64

- name: Download Linux x64 psign-tool
uses: actions/download-artifact@v8
with:
name: psign-tool-linux-x64.zip
path: work/linux-x64

- name: Resolve signing mode
id: signing_mode
shell: pwsh
Expand Down Expand Up @@ -345,20 +364,34 @@ jobs:
"should_sign=$($shouldSign.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append

Write-Host "Windows signing dry-run mode: $dryRun"
Write-Host "Windows binaries will be signed: $shouldSign"
Write-Host "Windows binaries will be signed with Linux psign-tool: $shouldSign"

- name: Install AzureSignTool
- name: Add Linux psign-tool to PATH
if: steps.signing_mode.outputs.should_sign == 'true'
shell: pwsh
run: |
$toolPath = Join-Path $env:USERPROFILE '.dotnet\tools\AzureSignTool.exe'
if (Test-Path -Path $toolPath) {
dotnet tool update --global AzureSignTool
$toolDir = Join-Path $env:RUNNER_TEMP 'psign-tool-self-sign'
if (Test-Path -Path $toolDir) {
Remove-Item -Path $toolDir -Recurse -Force
}
else {
dotnet tool install --global AzureSignTool

New-Item -Path $toolDir -ItemType Directory -Force | Out-Null

$toolArchivePath = 'work/linux-x64/psign-tool-linux-x64.zip'
if (-not (Test-Path -Path $toolArchivePath)) {
throw "Built Linux psign-tool archive was not found at $toolArchivePath"
}

Expand-Archive -Path $toolArchivePath -DestinationPath $toolDir -Force
$sourceToolPath = Join-Path $toolDir 'psign-tool'
if (-not (Test-Path -Path $sourceToolPath)) {
throw "Built Linux psign-tool executable was not found at $sourceToolPath"
}

chmod +x $sourceToolPath
$toolDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
& $sourceToolPath --version

- name: Code sign Windows executables
if: steps.signing_mode.outputs.should_sign == 'true'
shell: pwsh
Expand All @@ -370,28 +403,38 @@ jobs:
CODE_SIGNING_CERTIFICATE_NAME: ${{ secrets.CODE_SIGNING_CERTIFICATE_NAME }}
CODE_SIGNING_TIMESTAMP_SERVER: ${{ vars.CODE_SIGNING_TIMESTAMP_SERVER }}
run: |
$toolPath = Join-Path $env:USERPROFILE '.dotnet\tools\AzureSignTool.exe'

& $toolPath sign `
-kvt "$env:AZURE_TENANT_ID" `
-kvu "$env:CODE_SIGNING_KEYVAULT_URL" `
-kvi "$env:CODE_SIGNING_CLIENT_ID" `
-kvs "$env:CODE_SIGNING_CLIENT_SECRET" `
-kvc "$env:CODE_SIGNING_CERTIFICATE_NAME" `
-tr "$env:CODE_SIGNING_TIMESTAMP_SERVER" `
-v `
psign-tool --mode portable --verbose sign `
--azure-key-vault-tenant-id "$env:AZURE_TENANT_ID" `
--azure-key-vault-url "$env:CODE_SIGNING_KEYVAULT_URL" `
--azure-key-vault-client-id "$env:CODE_SIGNING_CLIENT_ID" `
--azure-key-vault-client-secret "$env:CODE_SIGNING_CLIENT_SECRET" `
--azure-key-vault-certificate "$env:CODE_SIGNING_CERTIFICATE_NAME" `
--timestamp-url "$env:CODE_SIGNING_TIMESTAMP_SERVER" `
--timestamp-digest sha256 `
--digest sha256 `
--exit-codes azure `
"work/win-x64/psign-tool.exe"

& $toolPath sign `
-kvt "$env:AZURE_TENANT_ID" `
-kvu "$env:CODE_SIGNING_KEYVAULT_URL" `
-kvi "$env:CODE_SIGNING_CLIENT_ID" `
-kvs "$env:CODE_SIGNING_CLIENT_SECRET" `
-kvc "$env:CODE_SIGNING_CERTIFICATE_NAME" `
-tr "$env:CODE_SIGNING_TIMESTAMP_SERVER" `
-v `
if ($LASTEXITCODE -ne 0) {
throw "psign-tool signing failed for win-x64 with exit code $LASTEXITCODE"
}

psign-tool --mode portable --verbose sign `
--azure-key-vault-tenant-id "$env:AZURE_TENANT_ID" `
--azure-key-vault-url "$env:CODE_SIGNING_KEYVAULT_URL" `
--azure-key-vault-client-id "$env:CODE_SIGNING_CLIENT_ID" `
--azure-key-vault-client-secret "$env:CODE_SIGNING_CLIENT_SECRET" `
--azure-key-vault-certificate "$env:CODE_SIGNING_CERTIFICATE_NAME" `
--timestamp-url "$env:CODE_SIGNING_TIMESTAMP_SERVER" `
--timestamp-digest sha256 `
--digest sha256 `
--exit-codes azure `
"work/win-arm64/psign-tool.exe"

if ($LASTEXITCODE -ne 0) {
throw "psign-tool signing failed for win-arm64 with exit code $LASTEXITCODE"
}

- name: Package Windows artifacts
shell: pwsh
run: |
Expand Down
15 changes: 8 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ repository = "https://github.com/Devolutions/psign"

[package]
name = "psign"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
description = "Rust port of the Windows SDK signtool.exe (Authenticode sign/verify/timestamp) with portable digest helpers."
license.workspace = true
readme = "README.md"
repository.workspace = true

[features]
default = []
default = [
"azure-kv-sign",
"artifact-signing-rest",
"timestamp-http",
"timestamp-server",
]
## Azure Key Vault signing (`AuthenticatorDigestSign` callback + REST); enables Azure-shaped CLI flags on `sign`.
azure-kv-sign = [
"dep:psign-azure-kv-rest",
Expand Down
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,44 @@ Canonical repository: <https://github.com/Devolutions/psign>.
cargo build
```

At the repo root, **`cargo build`** targets **`default-members`**, including the unified **`psign-tool`** executable from `src\main.rs` plus the portable digest / trust / package / REST crates. On Windows, **`cargo build -p psign --bin psign-tool`** remains the explicit way to build only that executable. Optional Cargo features: **`azure-kv-sign`** (Key Vault digest callback), **`artifact-signing-rest`** (**`artifact-signing-submit`** LRO against **`*.codesigning.azure.net`**), **`timestamp-http`** (portable RFC3161 HTTP POST), and **`timestamp-server`** (local RFC3161 test server).
At the repo root, **`cargo build`** targets **`default-members`**, including the unified **`psign-tool`** executable from `src\main.rs` plus the portable digest / trust / package / REST crates. On Windows, **`cargo build -p psign --bin psign-tool`** remains the explicit way to build only that executable. Default Cargo features include **`azure-kv-sign`** (Key Vault digest callback), **`artifact-signing-rest`** (**`artifact-signing-submit`** LRO against **`*.codesigning.azure.net`**), **`timestamp-http`** (portable RFC3161 HTTP POST), and **`timestamp-server`** (local RFC3161 test server); use **`--no-default-features`** for a minimal build.

## Dotnet tool package (.NET 10+)
## Dotnet tool package from NuGet.org (.NET 10+)

`psign-tool` can be distributed as a RID-specific dotnet tool package:
`psign-tool` is published as the RID-specific
[`Devolutions.Psign.Tool`](https://www.nuget.org/packages/Devolutions.Psign.Tool)
dotnet tool package:

```powershell
dotnet tool install -g Devolutions.Psign.Tool
psign-tool --help
```

One-shot execution:
Update an existing global install:

```powershell
dotnet tool update -g Devolutions.Psign.Tool
```

One-shot execution from NuGet.org:

```powershell
dotnet tool exec Devolutions.Psign.Tool -- --help
dnx Devolutions.Psign.Tool --help
```

For repository-local tool manifests, omit `-g`:

```powershell
dotnet new tool-manifest
dotnet tool install Devolutions.Psign.Tool
dotnet tool run psign-tool -- --help
```

Create local dotnet tool packages from prebuilt release artifacts:

```powershell
pwsh ./nuget/pack-psign-dotnet-tool.ps1 -Version 0.1.0 -ArtifactsRoot ./dist -OutputDir ./dist/nuget
pwsh ./nuget/pack-psign-dotnet-tool.ps1 -Version 0.2.0 -ArtifactsRoot ./dist -OutputDir ./dist/nuget
```

The package is built from native `psign-tool` artifacts for `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`, plus an `any` fallback package for unsupported runtimes.
Expand Down
2 changes: 1 addition & 1 deletion crates/psign-authenticode-trust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "psign-authenticode-trust"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
description = "Portable Authenticode PKCS#7 trust verification (anchors, chain, EKU) using picky-rs"
license.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/psign-azure-kv-rest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "psign-azure-kv-rest"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
description = "Azure Key Vault certificate metadata + keys/sign REST (portable, blocking HTTP)"
license.workspace = true
Expand Down
20 changes: 16 additions & 4 deletions crates/psign-azure-kv-rest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ pub fn kv_jws_alg(kind: KvPublicKeyKind, hash: KvHashAlg) -> Result<String> {
}
}

fn kv_base64url_decode(value: &str) -> Result<Vec<u8>> {
base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(value.trim())
.context("signature base64url decode")
}

#[derive(Deserialize)]
struct KeyVaultSignResponse {
value: String,
Expand All @@ -231,7 +237,7 @@ pub fn kv_sign_digest(
) -> Result<Vec<u8>> {
let body = serde_json::json!({
"alg": jws_alg,
"value": base64::engine::general_purpose::STANDARD.encode(digest),
"value": base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest),
});
let rsp = http
.post(sign_url)
Expand All @@ -247,9 +253,7 @@ pub fn kv_sign_digest(
));
}
let parsed: KeyVaultSignResponse = rsp.json().context("Key Vault sign JSON")?;
base64::engine::general_purpose::STANDARD
.decode(parsed.value.trim())
.context("signature base64 decode")
kv_base64url_decode(&parsed.value)
}

/// Resolve **`kid`**, infer JWS alg from certificate **`cer`**, POST **`sign`**.
Expand Down Expand Up @@ -347,4 +351,12 @@ mod tests {
"ES512"
);
}

#[test]
fn key_vault_signature_value_uses_base64url_without_padding() {
let raw = [0xff, 0xee, 0xdd, 0xcc, 0xbb];
let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(raw);
assert_eq!(encoded, "_-7dzLs");
assert_eq!(kv_base64url_decode(&encoded).unwrap(), raw);
}
}
2 changes: 1 addition & 1 deletion crates/psign-azure-kv-rest/tests/kv_rest_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fn fetch_certificate_and_sign_digest_against_mock_kv() {
.create();

let sig_bytes = vec![0xdeu8, 0xad, 0xbe, 0xef];
let sig_b64 = base64::engine::general_purpose::STANDARD.encode(&sig_bytes);
let sig_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&sig_bytes);

let _m_sign = server
.mock(
Expand Down
2 changes: 1 addition & 1 deletion crates/psign-codesigning-rest/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "psign-codesigning-rest"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
description = "Azure Code Signing data-plane CertificateProfileOperations Sign LRO (portable, blocking HTTP)"
license.workspace = true
Expand Down
Loading
Loading