1 # This script uses Set-AuthenticodeSignature to sign a file
2 # or exe/dll/msi files in a directory using a pfx file.
3 # If PFX file is not present, script expects a base64 encode certificate
4 # in WINDOWS_CERTIFICATE environment variable.
5 # Password can be passed using WINDOWS_CERTIFICATE_PASSWORD environmnet variable.
6 # The script only signs previously unsigned files
7 
8 [CmdletBinding()]
9 param (
10     # A path to the certificate file. If missing, env:WINDOWS_CERTIFICATE will be used.
11     [string]$CertFile,
12     # A password for the certificate. If missing, env:WINDOWS_CERTIFICATE_PASSWORD will be used.
13     [string]$Password,
14     # A file to sign.
15     [string]$File,
16     # A directory to sign.
17     [string]$Directory
18 )
19 
20 # Configure the error behavior
21 $ErrorActionPreference = "Stop"
22 $PSDefaultParameterValues['*:ErrorAction']='Stop'
23 
24 # Sign a file, if it was previously unsigned
25 # We sign using SHA256, as we only support Windows 7+.
Set-FileSignature()26 function Set-FileSignature {
27     param (
28         [String]$InputFile,
29         $pfxCert
30     )
31     if((Get-AuthenticodeSignature $InputFile).Status -ne [System.Management.Automation.SignatureStatus]::Valid) {
32         Write-Host "Singning file $InputFile"
33 
34         Set-AuthenticodeSignature `
35             -FilePath $InputFile `
36             -Certificate $pfxCert `
37             -IncludeChain All `
38             -TimestampServer 'http://timestamp.digicert.com' `
39             -HashAlgorithm 'sha256' `
40             -Force
41     } else {
42         Write-Host "Skipping file $InputFile as it is already signed"
43     }
44 }
45 
46 # Sanity checks: only allow File or Directory
47 if ($File -eq "" -and $Directory -eq "") {
48     Write-Host "-File or -Directory should be provided"
49     exit 1
50 } elseif ($File -ne "" -and $Directory -ne "") {
51     Write-Host "Only one of -File or -Directory should be provided"
52     exit 1
53 }
54 
55 $cleanupPfx = $false
56 
57 # Check, if we need to retrieve PFX from environment
58 if($CertFile -eq "") {
59     Write-Host "Trying to read certificate file from env:WINDOWS_CERTIFICATE"
60 
61     if(Test-Path "env:WINDOWS_CERTIFICATE") {
62         $CertFile = "cert.pfx"
63 
64         [IO.File]::WriteAllBytes($CertFile, [System.Convert]::FromBase64String($env:WINDOWS_CERTIFICATE))
65 
66         $cleanupPfx = $true
67     } else {
68         Write-Host "No certificate is provided"
69         exit 1
70     }
71 }
72 
73 # Check, if we need to retrieve password from environment
74 if($Password -eq "") {
75     if (Test-Path "env:WINDOWS_CERTIFICATE_PASSWORD") {
76         $Password = $env:WINDOWS_CERTIFICATE_PASSWORD
77     }
78 }
79 
80 $pfx = $null
81 
82 # Retrieve the actual certificate
83 if($Password -ne "") {
84     $securePassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
85     $pfx = Get-PfxData -FilePath "$CertFile" -Password $securePassword
86 } else {
87     $pfx = Get-PfxData -FilePath "$CertFile"
88 }
89 
90 $pfxCert =  $pfx.EndEntityCertificates[0]
91 
92 # Perform the code signing.
93 if ($File -ne "") {
94     Set-FileSignature -InputFile $File -pfxCert $pfxCert
95 } else {
96     Get-ChildItem `
97         -Path "$Directory" `
98         -Include *.dll,*.exe,*.msi `
99         -Recurse `
100         -File | ForEach-Object {
101             Set-FileSignature -InputFile $_.FullName -pfxCert $pfxCert
102         }
103 }
104 
105 # Remove PFX file if it was created from environment.
106 if($cleanupPfx) {
107     Remove-Item "${CertFile}"
108 }
109