Skip to content

Commit 197673c

Browse files
committed
ci: added a signing script to sign build assets
1 parent 5dd512e commit 197673c

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed

build/sign.ps1

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# Helper Functions
2+
function Sign-SingleFile {
3+
[CmdletBinding()]
4+
param(
5+
[Parameter(Mandatory = $true)]
6+
[string]$FilePath,
7+
8+
[Parameter(Mandatory = $true)]
9+
[string]$Thumbprint,
10+
11+
[Parameter(Mandatory = $true)]
12+
[string]$SignToolPath,
13+
14+
[Parameter(Mandatory = $true)]
15+
[string]$TimestampServer
16+
)
17+
18+
$signParams = @(
19+
"sign", "/fd", "SHA256",
20+
"/sha1", $Thumbprint,
21+
"/t", $TimestampServer,
22+
$FilePath
23+
)
24+
25+
$output = & $SignToolPath @signParams 2>&1
26+
if ($LASTEXITCODE -ne 0) {
27+
$output | ForEach-Object { Write-Host $_ }
28+
throw "Signing failed for file: $FilePath"
29+
}
30+
}
31+
32+
function Clean-Directory {
33+
[CmdletBinding()]
34+
param(
35+
[Parameter(Mandatory = $true)]
36+
[string]$BaseDirectory
37+
)
38+
39+
Write-Host "`nCleaning up working directories..." -ForegroundColor Yellow
40+
41+
# Only clean unsigned and signed directories
42+
$dirsToClean = @(
43+
Join-Path $BaseDirectory "unsigned"
44+
Join-Path $BaseDirectory "signed"
45+
)
46+
47+
foreach ($dir in $dirsToClean) {
48+
if (Test-Path $dir) {
49+
Write-Host "Removing: $dir"
50+
Remove-Item $dir -Recurse -Force
51+
}
52+
}
53+
Write-Host "✓ Cleanup completed"
54+
}
55+
56+
function Test-RequiredAssets {
57+
[CmdletBinding()]
58+
param(
59+
[Parameter(Mandatory = $true)]
60+
[string]$WorkingDirectory,
61+
62+
[Parameter(Mandatory = $true)]
63+
[string]$NuGetPackagesZip,
64+
65+
[Parameter(Mandatory = $true)]
66+
[string]$SymbolsPackagesZip
67+
)
68+
69+
Write-Host "`nValidating required build assets..."
70+
$requiredFiles = @{
71+
$NuGetPackagesZip = "NuGet packages"
72+
$SymbolsPackagesZip = "Symbol packages"
73+
}
74+
75+
foreach ($required in $requiredFiles.GetEnumerator()) {
76+
$found = Get-ChildItem -Path $WorkingDirectory -Filter $required.Key -ErrorAction SilentlyContinue
77+
if (-not $found) {
78+
throw "Required build asset not found: $($required.Key)`nThis file should contain $($required.Value)"
79+
}
80+
Write-Host " ✅ Found $($required.Value) in: $($found.Name)" -ForegroundColor Green
81+
}
82+
}
83+
84+
function Initialize-DirectoryStructure {
85+
[CmdletBinding()]
86+
param(
87+
[Parameter(Mandatory = $true)]
88+
[string]$BaseDirectory
89+
)
90+
91+
$directories = @{
92+
WorkingDir = $BaseDirectory
93+
Unsigned = Join-Path $BaseDirectory "unsigned"
94+
Signed = Join-Path $BaseDirectory "signed"
95+
Libraries = Join-Path $BaseDirectory "signed\libraries"
96+
Packages = Join-Path $BaseDirectory "signed\packages"
97+
}
98+
99+
Write-Host "`nCreating directory structure..."
100+
# Only create the directories we'll manage
101+
$directories.Keys | Where-Object { $_ -ne 'WorkingDir' } | ForEach-Object {
102+
$dir = $directories[$_]
103+
if (-not (Test-Path $dir)) {
104+
New-Item -ItemType Directory -Path $dir -Force | Out-Null
105+
Write-Host "✓ Created: $dir"
106+
}
107+
}
108+
109+
return $directories
110+
}
111+
112+
<#
113+
.SYNOPSIS
114+
Signs NuGet and Symbol packages using a smart card certificate.
115+
116+
.DESCRIPTION
117+
Signs NuGet packages (*.nupkg) and their corresponding symbol packages (*.snupkg) using a hardware-based certificate.
118+
The script processes the contents of two required zip files ('Nuget Packages.zip' and 'Symbols Packages.zip'),
119+
signs all assemblies within the NuGet packages, repacks them, and then signs both the NuGet and Symbol packages.
120+
121+
How to use:
122+
1. Create a release folder on your machine e.g. ../releases/1.12
123+
2. Download the build assets "Nuget Packages.zip" and "Symbols Packages.zip" from the latest SDK build action
124+
to the newly created folder.
125+
3. Start a Powershell terminal, and load the script by running the following command:
126+
> . \.Yubico.NET.SDK\build\sign.ps1
127+
4. The script can be invoked by following the examples below.
128+
129+
.PARAMETER Thumbprint
130+
The thumbprint of the signing certificate stored on the smart card.
131+
132+
.PARAMETER WorkingDirectory
133+
The directory containing the zip files and where the signing process will take place.
134+
135+
.PARAMETER SignToolPath
136+
Optional. Path to signtool.exe. Defaults to "signtool.exe" (expects it in PATH).
137+
138+
.PARAMETER NuGetPath
139+
Optional. Path to nuget.exe. Defaults to "nuget.exe" (expects it in PATH).
140+
141+
.PARAMETER TimestampServer
142+
Optional. URL of the timestamp server. Defaults to "http://timestamp.digicert.com".
143+
144+
.PARAMETER NuGetPackagesZip
145+
Optional. Name of the NuGet packages zip file. Defaults to "Nuget Packages.zip".
146+
147+
.PARAMETER SymbolsPackagesZip
148+
Optional. Name of the symbols packages zip file. Defaults to "Symbols Packages.zip".
149+
150+
.PARAMETER CleanWorkingDirectory
151+
Optional switch. If specified, cleans the working directories before processing.
152+
153+
.EXAMPLE
154+
Invoke-NuGetPackageSigning -Thumbprint "0123456789ABCDEF" -WorkingDirectory "C:\Signing"
155+
156+
.EXAMPLE
157+
Invoke-NuGetPackageSigning -Thumbprint "0123456789ABCDEF" -WorkingDirectory "C:\Signing" -CleanWorkingDirectory -NuGetPath "C:\Tools\nuget.exe"
158+
159+
.NOTES
160+
Requires:
161+
- A smart card with the signing certificate
162+
- signtool.exe (Windows SDK)
163+
- nuget.exe
164+
- PowerShell 5.1 or later
165+
#>
166+
function Invoke-NuGetPackageSigning {
167+
[CmdletBinding()]
168+
param(
169+
[Parameter(Mandatory = $true)]
170+
[string]$Thumbprint,
171+
172+
[Parameter(Mandatory = $true)]
173+
[string]$WorkingDirectory,
174+
175+
[Parameter(Mandatory = $false)]
176+
[string]$SignToolPath = "signtool.exe",
177+
178+
[Parameter(Mandatory = $false)]
179+
[string]$NuGetPath = "nuget.exe",
180+
181+
[Parameter(Mandatory = $false)]
182+
[string]$TimestampServer = "http://timestamp.digicert.com",
183+
184+
[Parameter(Mandatory = $false)]
185+
[string]$NuGetPackagesZip = "Nuget Packages.zip",
186+
187+
[Parameter(Mandatory = $false)]
188+
[string]$SymbolsPackagesZip = "Symbols Packages.zip",
189+
190+
[Parameter(Mandatory = $false)]
191+
[switch]$CleanWorkingDirectory
192+
)
193+
194+
try {
195+
Write-Host "`nInitializing NuGet package signing process..." -ForegroundColor Cyan
196+
197+
# Validate tools existence
198+
Write-Host "`nVerifying required tools..."
199+
if (-not (Test-Path $SignToolPath)) {
200+
throw "SignTool not found at path: $SignToolPath"
201+
}
202+
Write-Host "✓ SignTool found at: $SignToolPath"
203+
204+
if (-not (Get-Command $NuGetPath -ErrorAction SilentlyContinue)) {
205+
throw "NuGet not found at path: $NuGetPath"
206+
}
207+
Write-Host "✓ NuGet found at: $NuGetPath"
208+
209+
# Verify certificate is available and log details
210+
$cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $Thumbprint }
211+
if (-not $cert) {
212+
throw "Certificate with thumbprint $Thumbprint not found in current user store"
213+
}
214+
215+
Write-Host "`nCertificate Details:" -ForegroundColor Cyan
216+
Write-Host " Subject: $($cert.Subject)"
217+
Write-Host " Issuer: $($cert.Issuer)"
218+
Write-Host " Thumbprint: $($cert.Thumbprint)"
219+
Write-Host " Valid From: $($cert.NotBefore)"
220+
Write-Host " Valid To: $($cert.NotAfter)"
221+
Write-Host " Provider: $($cert.PrivateKey.CspKeyContainerInfo.ProviderName)"
222+
Write-Host " Key Storage: $($cert.PrivateKey.CspKeyContainerInfo.HardwareDevice ? 'Hardware' : 'Software')"
223+
Write-Host " Key Spec: $($cert.PrivateKey.CspKeyContainerInfo.KeyNumber)`n"
224+
225+
if ($cert.NotAfter -le (Get-Date).AddMonths(1)) {
226+
Write-Warning "Certificate will expire within one month on $($cert.NotAfter)"
227+
}
228+
229+
# Clean if requested
230+
if ($CleanWorkingDirectory) {
231+
Clean-Directory -BaseDirectory $WorkingDirectory
232+
}
233+
234+
# Initialize directory structure
235+
$directories = Initialize-DirectoryStructure -BaseDirectory $WorkingDirectory
236+
237+
# Validate required zip files
238+
Test-RequiredAssets -WorkingDirectory $WorkingDirectory -NuGetPackagesZip $NuGetPackagesZip -SymbolsPackagesZip $SymbolsPackagesZip
239+
240+
# Process each zip file
241+
Write-Host "`n📦 Processing ZIP files..." -ForegroundColor Yellow
242+
$zipFiles = Get-ChildItem -Path $WorkingDirectory -Filter "*.zip"
243+
foreach ($zip in $zipFiles) {
244+
Write-Host "`n 🔄 Processing: $($zip.Name)" -ForegroundColor Cyan
245+
246+
$extractPath = Join-Path $directories.Unsigned ([System.IO.Path]::GetFileNameWithoutExtension($zip.Name))
247+
Write-Host " 📂 Extracting to: $extractPath" -ForegroundColor Gray
248+
Expand-Archive -Path $zip.FullName -DestinationPath $extractPath -Force
249+
250+
Write-Host " 📋 Copying packages to unsigned directory" -ForegroundColor Gray
251+
$packages = Get-ChildItem -Path $extractPath -Recurse -Include *.nupkg, *.snupkg
252+
foreach ($package in $packages) {
253+
Write-Host " Copying: $($package.Name)"
254+
Copy-Item -Path $package.FullName -Destination $directories.Unsigned -Force
255+
}
256+
Write-Host "✓ Copied $($packages.Count) package(s)"
257+
}
258+
259+
# First process nupkg files to sign their contents
260+
Write-Host "`nProcessing NuGet packages..."
261+
$nugetPackages = Get-ChildItem -Path $directories.Unsigned -Filter "*.nupkg"
262+
foreach ($package in $nugetPackages) {
263+
Write-Host "`nSigning contents of: $($package.Name)"
264+
265+
$extractPath = Join-Path $directories.Libraries ([System.IO.Path]::GetFileNameWithoutExtension($package.Name))
266+
Write-Host "Extracting to: $extractPath"
267+
Expand-Archive -Path $package.FullName -DestinationPath $extractPath -Force
268+
269+
Write-Host "Cleaning package structure"
270+
Get-ChildItem -Path $extractPath -Recurse -Include "_rels", "package" | Remove-Item -Force -Recurse
271+
Get-ChildItem -Path $extractPath -Recurse -Filter '[Content_Types].xml' | Remove-Item -Force
272+
273+
Write-Host "Signing assemblies..."
274+
$dlls = Get-ChildItem -Path $extractPath -Include "*.dll" -Recurse
275+
foreach ($dll in $dlls) {
276+
# Get the parent directory name (framework target) and the file name
277+
$frameworkDir = Split-Path (Split-Path $dll.FullName -Parent) -Leaf
278+
$fileName = Split-Path $dll.FullName -Leaf
279+
Write-Host " ✍️ Signing: ..\$frameworkDir\$fileName" -ForegroundColor Gray
280+
Sign-SingleFile -FilePath $dll.FullName -Thumbprint $Thumbprint -SignToolPath $SignToolPath -TimestampServer $TimestampServer
281+
}
282+
283+
Write-Host "Repacking signed content..."
284+
Get-ChildItem -Path $extractPath -Recurse -Filter "*.nuspec" |
285+
ForEach-Object {
286+
Write-Host " Packing: $($_.Name)"
287+
& $NuGetPath pack $_.FullName -OutputDirectory $directories.Packages
288+
}
289+
}
290+
291+
# Copy symbol packages to output directory
292+
Write-Host "`nCopying symbol packages..."
293+
$symbolPackages = Get-ChildItem -Path $directories.Unsigned -Filter "*.snupkg"
294+
foreach ($package in $symbolPackages) {
295+
Write-Host " Copying: $($package.Name)"
296+
Copy-Item -Path $package.FullName -Destination $directories.Packages -Force
297+
}
298+
299+
# Sign all final packages (both nupkg and snupkg)
300+
Write-Host "`n🔏 Signing final packages..." -ForegroundColor Cyan
301+
$finalPackages = Get-ChildItem -Path $directories.Packages -Include *.nupkg, *.snupkg -Recurse
302+
foreach ($package in $finalPackages) {
303+
Write-Host " ✒️ Signing package: $($package.Name)" -ForegroundColor White
304+
$nugetSignParams = @(
305+
"sign", $package.FullName,
306+
"-CertificateFingerprint", $Thumbprint,
307+
"-Timestamper", $TimestampServer,
308+
"-NonInteractive"
309+
)
310+
& $NuGetPath @nugetSignParams
311+
}
312+
313+
# Print summary of signed packages
314+
Write-Host "`n📊 Signed Packages Summary:" -ForegroundColor Yellow
315+
Write-Host " NuGet Packages:" -ForegroundColor White
316+
Get-ChildItem -Path $directories.Packages -Filter "*.nupkg" | ForEach-Object {
317+
$size = "{0:N2}" -f ($_.Length / 1KB)
318+
Write-Host " 📦 $($_.Name) [$size KB]" -ForegroundColor Gray
319+
}
320+
321+
Write-Host " Symbol Packages:" -ForegroundColor White
322+
Get-ChildItem -Path $directories.Packages -Filter "*.snupkg" | ForEach-Object {
323+
$size = "{0:N2}" -f ($_.Length / 1KB)
324+
Write-Host " 🔍 $($_.Name) [$size KB]" -ForegroundColor Gray
325+
}
326+
327+
Write-Host "`n✨ Package signing process completed successfully! ✨" -ForegroundColor Green
328+
return $directories.Packages
329+
}
330+
catch {
331+
Write-Host "`n❌ Error occurred:" -ForegroundColor Red
332+
Write-Error $_.Exception.Message
333+
Clean-Directory -BaseDirectory $WorkingDirectory
334+
throw
335+
}
336+
}

0 commit comments

Comments
 (0)