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 " `n Cleaning 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 " `n Validating 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 ) `n This 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 " `n Creating 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 " `n Initializing NuGet package signing process..." - ForegroundColor Cyan
196
+
197
+ # Validate tools existence
198
+ Write-Host " `n Verifying 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 " `n Certificate 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 " `n Processing NuGet packages..."
261
+ $nugetPackages = Get-ChildItem - Path $directories.Unsigned - Filter " *.nupkg"
262
+ foreach ($package in $nugetPackages ) {
263
+ Write-Host " `n Signing 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 " `n Copying 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 / 1 KB )
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 / 1 KB )
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