As a DevOps engineer, I frequently come across talented developers that underestimate some security aspects of the deployments, for instance, just to name a couple: integrity and authenticity of the code or artefacts that we deploy.

Python and Powershell are powerful languages to develop quick and robust solutions are extremely popular between attackers, for this reason, our ecosystem should take security very seriously.

Security is now far beyond the (old) perimeter of the company’s premises and infrastructure, indeed network or systems is abstracted away with or without cloud/hybrid deployments and just the enforcing identity is not enough in most cases.

In my opinion, white-listing applications around code-signing and checking the integrity of our code it’s more effective and less painful than you can think a good habit to build on a daily basis.

Code Signing must be easy and it can be done at any step that it’s meaningful for you. Remember we can sign the script again if needed. It must become standard practice, not an exception.

Code Signing practice with a Self-Signed Certificate

Let’s start creating a self-signed certificate and use it for practice

New-SelfSignedCertForCodeSigning.ps1 #requires -runasadministrator # Paolo Frigo, https://www.scriptinglibray.com # This scripts generates a self-signed certificate for CodeSigning and exports to a PFX Format #SETTINGS $CertificateName = "Paolo's Signing Certificate" $OutPutPFXFilePath = "D:\MyNewSigningCertificate.pfx" $MyStrongPassword = ConvertTo-SecureString -String "MySuperStrongPassword!" -Force -AsPlainText New-SelfSignedCertificate -subject $CertificateName -Type CodeSigning | Export-PfxCertificate -FilePath $OutPutPFXFilePath -password $MyStrongPassword Write-Output "PFX Certificate `"$CertificateName`" exported: $OutPutPFXFilePath" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #requires -runasadministrator # Paolo Frigo, https://www.scriptinglibray.com # This scripts generates a self-signed certificate for CodeSigning and exports to a PFX Format #SETTINGS $CertificateName = "Paolo's Signing Certificate" $OutPutPFXFilePath = "D:\MyNewSigningCertificate.pfx" $MyStrongPassword = ConvertTo-SecureString -String "MySuperStrongPassword!" -Force -AsPlainText New-SelfSignedCertificate -subject $CertificateName -Type CodeSigning | Export-PfxCertificate -FilePath $OutPutPFXFilePath -password $MyStrongPassword Write-Output "PFX Certificate `"$CertificateName`" exported: $OutPutPFXFilePath"

How to check a pfx certificate

Ok, but how can we check if the certificate generated is correct without even install it?

PS D:\> certutil D:\MyNewSigningCertificate.pfx Enter PFX password: ================ Certificate 0 ================ ================ Begin Nesting Level 1 ================ Element 0: Serial Number: 476c896fcabb4093458a41b47fb01782 Issuer: CN=Paolo's Signing Certificate NotBefore: 15/02/2019 8:00 PM NotAfter: 15/02/2020 8:20 PM Subject: CN=Paolo's Signing Certificate Signature matches Public Key Root Certificate: Subject matches Issuer Cert Hash(sha1): 34c1dc5dacb3b0be01ef9eccff7356ff41bc69ec ---------------- End Nesting Level 1 ---------------- Provider = Microsoft Software Key Storage Provider Private key is NOT plain text exportable Encryption test passed CertUtil: -dump command completed successfully. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PS D : \ > certutil D : \ MyNewSigningCertificate . pfx Enter PFX password : === === === === === = Certificate 0 === === === === === = === === === === === = Begin Nesting Level 1 === === === === === = Element 0 : Serial Number : 476c896fcabb4093458a41b47fb01782 Issuer : CN = Paolo 's Signing Certificate NotBefore: 15/02/2019 8:00 PM NotAfter: 15/02/2020 8:20 PM Subject: CN=Paolo' s Signing Certificate Signature matches Public Key Root Certificate : Subject matches Issuer Cert Hash ( sha1 ) : 34c1dc5dacb3b0be01ef9eccff7356ff41bc69ec -- -- -- -- -- -- -- -- End Nesting Level 1 -- -- -- -- -- -- -- -- Provider = Microsoft Software Key Storage Provider Private key is NOT plain text exportable Encryption test passed CertUtil : -dump command completed successfully .

Looks good and it will expire in 1 year.

In the long run, purchasing a signing certificate can save us time, but for now a self-signed certificate it’s enough for our practice examples.

Let’s create a test.ps1 from:

set-content -value "get-date" -path "D:\ToBeSigned.ps1" 1 set-content -value "get-date" -path "D:\ToBeSigned.ps1"

Let’s check the content and run it:

PS D:\> Get-Content .\ToBeSigned.ps1 get-date PS D:\> .\ToBeSigned.ps1 Friday, 15 February 2019 9:45:42 PM 1 2 3 4 5 PS D : \ > Get-Content . \ ToBeSigned . ps1 get-date PS D : \ > . \ ToBeSigned . ps1 Friday , 15 February 2019 9 : 45 : 42 PM

How to sign a PowerShell script

Let’s sign this script:

PS D:\> $MyCertFromPfx = Get-PfxCertificate -FilePath D:\MyNewSigningCertificate.pfx Enter password: ********************** PS D:\> Set-AuthenticodeSignature -PSPath .\ToBeSigned.ps1 -Certificate $MyCertFromPfx Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned.ps1 PS D:\> Get-Content .\ToBeSigned.ps1 get-date # SIG # Begin signature block # MIIFkQYJKoZIhvcNAQcCoIIFgjCCBX4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6D5nCrWCw279VqfcdcjosB8f # qamgggMgMIIDHDCCAgSgAwIBAgIQekJUohkDH45CdbPt6qPJbzANBgkqhkiG9w0B # AQsFADAmMSQwIgYDVQQDDBtQYW9sbydzIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcN # MTkwMjE1MTA0MDQ5WhcNMjAwMjE1MTEwMDQ5WjAmMSQwIgYDVQQDDBtQYW9sbydz # IFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK # AoIBAQDFwAthDowBRMXz/3nm4cAE6hkmdg2KMZ0CWO6PsGFeuZVvrCJL0Bapn3i6 # EUiQceSXC4qjecTpMssrwuGSTb2xiSlolrXOPa+sIt268sjl7lwLQ5mSaXH3k19F # rbTDhj8AXMXOjhJBLfjtfg2xVvnulpdw8CZWsyYHjCr3vyqSzh0e5Wi2WYPYEs53 # 5Ez6qvfAX6mCzgI5a/52sARw7cxLx7iQTfWpYzD+W3cl7Vh6rbKce/v7fIeNFW/m # RZtCFQ710SMc3mu2hyRCbJy0wlYF1qY76/PnRnrVjeQ4VwNArVh4FO9YNiEhG+eM # zDWdmMzWub1RnFhqQ65Yofq0K6PFAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAT # BgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+Qg4e1OnSPFMRQj9DN8ZM4Db # 1WcwDQYJKoZIhvcNAQELBQADggEBAKiTAyGa+ZWfZA95ztOA9CevEnrIMSIjAE9b # kSSAItiQmfK9TUaMsu94hi0t8sDyWTlLGoWvFu6TVnPKa1S8W89pCoifTwaUwtCL # ETXQ5EmJe3t8zsnQ9tip5twLFfWwnHjI4W1Js0jQtX2PaRpNqQvivvQE4OCCWTwh # NXBkcB9OeBM97em789hGUcAIfg5TFV/v5bQ/n29SejAuJJq3c29HxqnbFw9vrIZy # ra0b/sicC5CNdA4aoly8x2tSHLtGI5TqrL9OOE2DB2hixXv4WQnSHKmjEt7nUUwP # OWrEqS8SbRkgcHbZ4baUAOFxYSsqXN59BlPVM0EorDK+z0CboU4xggHbMIIB1wIB # ATA6MCYxJDAiBgNVBAMMG1Bhb2xvJ3MgU2lnbmluZyBDZXJ0aWZpY2F0ZQIQekJU # ohkDH45CdbPt6qPJbzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUpMvzC2gMvwG+SRSnRoCPvMBX # XFUwDQYJKoZIhvcNAQEBBQAEggEASxICEc2t+4w7ZQS66VH/Etepr1JWKIqafL4T # 0h8mGPUs8FqFumMMPoz5yLYuG6R3akusvEAWkgeP62wzIeKk4HxjewrcTNiVZajT # nQeopKuZBHts4Pv8OUWODU7/oj4KNqSdB99bqcR3LvCljqMM7HGH2jrU6k7fFYSm # ULvIjvIl+g6OCWNeMGVrRpPwRHU3VEtphjrMNt1tyj+9bdydjtuVUxSqSA74hwYh # 6W8esgJ8Xg4w5+zJPOxh9ZqpSfxId2NYwzF68QOY9w1u16zQ3rMGYe0FAR9R5OLc # +NebJG/jNVMj4QPmbjam1uNUK4O5b3r1R6iAwoHTIBNJ3P0AeA== # SIG # End signature block 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 PS D : \ > $MyCertFromPfx = Get-PfxCertificate -FilePath D : \ MyNewSigningCertificate . pfx Enter password : * * * * * * * * * * * * * * * * * * * * * * PS D : \ > Set-AuthenticodeSignature -PSPath . \ ToBeSigned . ps1 -Certificate $MyCertFromPfx Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned . ps1 PS D : \ > Get-Content . \ ToBeSigned . ps1 get-date # SIG # Begin signature block # MIIFkQYJKoZIhvcNAQcCoIIFgjCCBX4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6D5nCrWCw279VqfcdcjosB8f # qamgggMgMIIDHDCCAgSgAwIBAgIQekJUohkDH45CdbPt6qPJbzANBgkqhkiG9w0B # AQsFADAmMSQwIgYDVQQDDBtQYW9sbydzIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcN # MTkwMjE1MTA0MDQ5WhcNMjAwMjE1MTEwMDQ5WjAmMSQwIgYDVQQDDBtQYW9sbydz # IFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK # AoIBAQDFwAthDowBRMXz/3nm4cAE6hkmdg2KMZ0CWO6PsGFeuZVvrCJL0Bapn3i6 # EUiQceSXC4qjecTpMssrwuGSTb2xiSlolrXOPa+sIt268sjl7lwLQ5mSaXH3k19F # rbTDhj8AXMXOjhJBLfjtfg2xVvnulpdw8CZWsyYHjCr3vyqSzh0e5Wi2WYPYEs53 # 5Ez6qvfAX6mCzgI5a/52sARw7cxLx7iQTfWpYzD+W3cl7Vh6rbKce/v7fIeNFW/m # RZtCFQ710SMc3mu2hyRCbJy0wlYF1qY76/PnRnrVjeQ4VwNArVh4FO9YNiEhG+eM # zDWdmMzWub1RnFhqQ65Yofq0K6PFAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAT # BgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+Qg4e1OnSPFMRQj9DN8ZM4Db # 1WcwDQYJKoZIhvcNAQELBQADggEBAKiTAyGa+ZWfZA95ztOA9CevEnrIMSIjAE9b # kSSAItiQmfK9TUaMsu94hi0t8sDyWTlLGoWvFu6TVnPKa1S8W89pCoifTwaUwtCL # ETXQ5EmJe3t8zsnQ9tip5twLFfWwnHjI4W1Js0jQtX2PaRpNqQvivvQE4OCCWTwh # NXBkcB9OeBM97em789hGUcAIfg5TFV/v5bQ/n29SejAuJJq3c29HxqnbFw9vrIZy # ra0b/sicC5CNdA4aoly8x2tSHLtGI5TqrL9OOE2DB2hixXv4WQnSHKmjEt7nUUwP # OWrEqS8SbRkgcHbZ4baUAOFxYSsqXN59BlPVM0EorDK+z0CboU4xggHbMIIB1wIB # ATA6MCYxJDAiBgNVBAMMG1Bhb2xvJ3MgU2lnbmluZyBDZXJ0aWZpY2F0ZQIQekJU # ohkDH45CdbPt6qPJbzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUpMvzC2gMvwG+SRSnRoCPvMBX # XFUwDQYJKoZIhvcNAQEBBQAEggEASxICEc2t+4w7ZQS66VH/Etepr1JWKIqafL4T # 0h8mGPUs8FqFumMMPoz5yLYuG6R3akusvEAWkgeP62wzIeKk4HxjewrcTNiVZajT # nQeopKuZBHts4Pv8OUWODU7/oj4KNqSdB99bqcR3LvCljqMM7HGH2jrU6k7fFYSm # ULvIjvIl+g6OCWNeMGVrRpPwRHU3VEtphjrMNt1tyj+9bdydjtuVUxSqSA74hwYh # 6W8esgJ8Xg4w5+zJPOxh9ZqpSfxId2NYwzF68QOY9w1u16zQ3rMGYe0FAR9R5OLc # +NebJG/jNVMj4QPmbjam1uNUK4O5b3r1R6iAwoHTIBNJ3P0AeA== # SIG # End signature block

How to check the signer certificate of a Powershell script

At this stage, “UnknownError” is expected and it’s a positive result, I will explain later why.

PS D:\> Get-AuthenticodeSignature .\ToBeSigned.ps1 Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned.ps1 1 2 3 4 5 6 7 8 9 PS D : \ > Get-AuthenticodeSignature . \ ToBeSigned . ps1 Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned . ps1

Integrity Check test tempering a PowerShell script

Let’s temper adding a simple space character to our signed PowerShell script to prove that the integrity check is effective:

PS D:\> Add-Content -Value " " -Path .\ToBeSigned.ps1 PS D:\> Get-AuthenticodeSignature .\ToBeSigned.ps1 Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- NotSigned ToBeSigned.ps1 PS D:\> get-content .\ToBeSigned.ps1 get-date # SIG # Begin signature block # MIIFkQYJKoZIhvcNAQcCoIIFgjCCBX4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6D5nCrWCw279VqfcdcjosB8f # qamgggMgMIIDHDCCAgSgAwIBAgIQekJUohkDH45CdbPt6qPJbzANBgkqhkiG9w0B # AQsFADAmMSQwIgYDVQQDDBtQYW9sbydzIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcN # MTkwMjE1MTA0MDQ5WhcNMjAwMjE1MTEwMDQ5WjAmMSQwIgYDVQQDDBtQYW9sbydz # IFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK # AoIBAQDFwAthDowBRMXz/3nm4cAE6hkmdg2KMZ0CWO6PsGFeuZVvrCJL0Bapn3i6 # EUiQceSXC4qjecTpMssrwuGSTb2xiSlolrXOPa+sIt268sjl7lwLQ5mSaXH3k19F # rbTDhj8AXMXOjhJBLfjtfg2xVvnulpdw8CZWsyYHjCr3vyqSzh0e5Wi2WYPYEs53 # 5Ez6qvfAX6mCzgI5a/52sARw7cxLx7iQTfWpYzD+W3cl7Vh6rbKce/v7fIeNFW/m # RZtCFQ710SMc3mu2hyRCbJy0wlYF1qY76/PnRnrVjeQ4VwNArVh4FO9YNiEhG+eM # zDWdmMzWub1RnFhqQ65Yofq0K6PFAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAT # BgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+Qg4e1OnSPFMRQj9DN8ZM4Db # 1WcwDQYJKoZIhvcNAQELBQADggEBAKiTAyGa+ZWfZA95ztOA9CevEnrIMSIjAE9b # kSSAItiQmfK9TUaMsu94hi0t8sDyWTlLGoWvFu6TVnPKa1S8W89pCoifTwaUwtCL # ETXQ5EmJe3t8zsnQ9tip5twLFfWwnHjI4W1Js0jQtX2PaRpNqQvivvQE4OCCWTwh # NXBkcB9OeBM97em789hGUcAIfg5TFV/v5bQ/n29SejAuJJq3c29HxqnbFw9vrIZy # ra0b/sicC5CNdA4aoly8x2tSHLtGI5TqrL9OOE2DB2hixXv4WQnSHKmjEt7nUUwP # OWrEqS8SbRkgcHbZ4baUAOFxYSsqXN59BlPVM0EorDK+z0CboU4xggHbMIIB1wIB # ATA6MCYxJDAiBgNVBAMMG1Bhb2xvJ3MgU2lnbmluZyBDZXJ0aWZpY2F0ZQIQekJU # ohkDH45CdbPt6qPJbzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUpMvzC2gMvwG+SRSnRoCPvMBX # XFUwDQYJKoZIhvcNAQEBBQAEggEASxICEc2t+4w7ZQS66VH/Etepr1JWKIqafL4T # 0h8mGPUs8FqFumMMPoz5yLYuG6R3akusvEAWkgeP62wzIeKk4HxjewrcTNiVZajT # nQeopKuZBHts4Pv8OUWODU7/oj4KNqSdB99bqcR3LvCljqMM7HGH2jrU6k7fFYSm # ULvIjvIl+g6OCWNeMGVrRpPwRHU3VEtphjrMNt1tyj+9bdydjtuVUxSqSA74hwYh # 6W8esgJ8Xg4w5+zJPOxh9ZqpSfxId2NYwzF68QOY9w1u16zQ3rMGYe0FAR9R5OLc # +NebJG/jNVMj4QPmbjam1uNUK4O5b3r1R6iAwoHTIBNJ3P0AeA== # SIG # End signature block PS D:\> Get-AuthenticodeSignature .\ToBeSigned.ps1 Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned.ps1 PS D:\> Set-AuthenticodeSignature -PSPath .\ToBeSigned.ps1 -Certificate $MyCertFromPfx Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- HashMismatch ToBeSigned.ps1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 PS D : \ > Add-Content -Value " " -Path . \ ToBeSigned . ps1 PS D : \ > Get-AuthenticodeSignature . \ ToBeSigned . ps1 Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- NotSigned ToBeSigned . ps1 PS D : \ > get-content . \ ToBeSigned . ps1 get-date # SIG # Begin signature block # MIIFkQYJKoZIhvcNAQcCoIIFgjCCBX4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6D5nCrWCw279VqfcdcjosB8f # qamgggMgMIIDHDCCAgSgAwIBAgIQekJUohkDH45CdbPt6qPJbzANBgkqhkiG9w0B # AQsFADAmMSQwIgYDVQQDDBtQYW9sbydzIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcN # MTkwMjE1MTA0MDQ5WhcNMjAwMjE1MTEwMDQ5WjAmMSQwIgYDVQQDDBtQYW9sbydz # IFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK # AoIBAQDFwAthDowBRMXz/3nm4cAE6hkmdg2KMZ0CWO6PsGFeuZVvrCJL0Bapn3i6 # EUiQceSXC4qjecTpMssrwuGSTb2xiSlolrXOPa+sIt268sjl7lwLQ5mSaXH3k19F # rbTDhj8AXMXOjhJBLfjtfg2xVvnulpdw8CZWsyYHjCr3vyqSzh0e5Wi2WYPYEs53 # 5Ez6qvfAX6mCzgI5a/52sARw7cxLx7iQTfWpYzD+W3cl7Vh6rbKce/v7fIeNFW/m # RZtCFQ710SMc3mu2hyRCbJy0wlYF1qY76/PnRnrVjeQ4VwNArVh4FO9YNiEhG+eM # zDWdmMzWub1RnFhqQ65Yofq0K6PFAgMBAAGjRjBEMA4GA1UdDwEB/wQEAwIHgDAT # BgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU+Qg4e1OnSPFMRQj9DN8ZM4Db # 1WcwDQYJKoZIhvcNAQELBQADggEBAKiTAyGa+ZWfZA95ztOA9CevEnrIMSIjAE9b # kSSAItiQmfK9TUaMsu94hi0t8sDyWTlLGoWvFu6TVnPKa1S8W89pCoifTwaUwtCL # ETXQ5EmJe3t8zsnQ9tip5twLFfWwnHjI4W1Js0jQtX2PaRpNqQvivvQE4OCCWTwh # NXBkcB9OeBM97em789hGUcAIfg5TFV/v5bQ/n29SejAuJJq3c29HxqnbFw9vrIZy # ra0b/sicC5CNdA4aoly8x2tSHLtGI5TqrL9OOE2DB2hixXv4WQnSHKmjEt7nUUwP # OWrEqS8SbRkgcHbZ4baUAOFxYSsqXN59BlPVM0EorDK+z0CboU4xggHbMIIB1wIB # ATA6MCYxJDAiBgNVBAMMG1Bhb2xvJ3MgU2lnbmluZyBDZXJ0aWZpY2F0ZQIQekJU # ohkDH45CdbPt6qPJbzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUpMvzC2gMvwG+SRSnRoCPvMBX # XFUwDQYJKoZIhvcNAQEBBQAEggEASxICEc2t+4w7ZQS66VH/Etepr1JWKIqafL4T # 0h8mGPUs8FqFumMMPoz5yLYuG6R3akusvEAWkgeP62wzIeKk4HxjewrcTNiVZajT # nQeopKuZBHts4Pv8OUWODU7/oj4KNqSdB99bqcR3LvCljqMM7HGH2jrU6k7fFYSm # ULvIjvIl+g6OCWNeMGVrRpPwRHU3VEtphjrMNt1tyj+9bdydjtuVUxSqSA74hwYh # 6W8esgJ8Xg4w5+zJPOxh9ZqpSfxId2NYwzF68QOY9w1u16zQ3rMGYe0FAR9R5OLc # +NebJG/jNVMj4QPmbjam1uNUK4O5b3r1R6iAwoHTIBNJ3P0AeA== # SIG # End signature block PS D : \ > Get-AuthenticodeSignature . \ ToBeSigned . ps1 Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 UnknownError ToBeSigned . ps1 PS D : \ > Set-AuthenticodeSignature -PSPath . \ ToBeSigned . ps1 -Certificate $MyCertFromPfx Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- HashMismatch ToBeSigned . ps1

What is the “Unknown Error” Status and how to fix it

Why the Unknown error when the PS1 was signed? Because the status is a result of a lookup on your certificate store / repository.

$myPfx = "D:\MyNewSigningCertificate.pfx" #How to import your Self signed PFX #Personal Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\My" -Password $MyStrongPassword #TrustedPublisher Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\Root" -Password $MyStrongPassword #Root Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\TrustedPublisher" -Password $MyStrongPassword 1 2 3 4 5 6 7 8 9 $myPfx = "D:\MyNewSigningCertificate.pfx" #How to import your Self signed PFX #Personal Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\My" -Password $MyStrongPassword #TrustedPublisher Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\Root" -Password $MyStrongPassword #Root Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\TrustedPublisher" -Password $MyStrongPassword

After installing the certificate in these certificate stores we will have this result:

PS D:\> Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\My" -Password $MyStrongPassword PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My Thumbprint Subject ---------- ------- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN=Paolo's Signing Certificate PS D:\> #TrustedPublisher PS D:\> Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\Root" -Password $MyStrongPassword PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root Thumbprint Subject ---------- ------- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN=Paolo's Signing Certificate PS D:\> #Root PS D:\> Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\TrustedPublisher" -Password $MyStrongPassword PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\TrustedPublisher Thumbprint Subject ---------- ------- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN=Paolo's Signing Certificate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 PS D : \ > Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\My" -Password $MyStrongPassword PSParentPath : Microsoft . PowerShell . Security \ Certificate :: LocalMachine \ My Thumbprint Subject -- -- -- -- -- -- -- -- - 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN = Paolo 's Signing Certificate PS D:\> #TrustedPublisher PS D:\> Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\Root" -Password $MyStrongPassword PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root Thumbprint Subject ---------- ------- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN=Paolo' s Signing Certificate PS D : \ > #Root PS D : \ > Import-PfxCertificate -FilePath $myPfx -CertStoreLocation "cert:\LocalMachine\TrustedPublisher" -Password $MyStrongPassword PSParentPath : Microsoft . PowerShell . Security \ Certificate :: LocalMachine \ TrustedPublisher Thumbprint Subject -- -- -- -- -- -- -- -- - 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN = Paolo ' s Signing Certificate

Finally, we will get now a VALID as a status result!

PS D:\> Get-AuthenticodeSignature .\ToBeSigned.ps1 Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned.ps1 1 2 3 4 5 6 7 8 9 PS D : \ > Get-AuthenticodeSignature . \ ToBeSigned . ps1 Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned . ps1

How to sign multiple scripts at once

Do we need to sign all ps1 scripts? No problem.

PS D:\> get-childitem *ps1 | Set-AuthenticodeSignature -Certificate $MyCertFromPfx Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 1.ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 2.ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 3.ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 4.ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 5.ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned.ps1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 PS D : \ > get-childitem * ps1 | Set-AuthenticodeSignature -Certificate $MyCertFromPfx Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 1 . ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 2 . ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 3 . ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 4 . ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid 5 . ps1 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned . ps1

Very nice and not as painful. Right?

How to find the certificate subject from the thumbprint

But who signed those scripts? Let’s find out!

Get-ChildItem Cert:\LocalMachine\TrustedPublisher\ | Where-object { $_.thumbprint -eq "06B2B0DF262023EDD14A0555C25B3C7F753236D2"} PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\TrustedPublisher Thumbprint Subject ---------- ------- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN=Paolo's Signing Certificate 1 2 3 4 5 6 7 8 Get-ChildItem Cert : \ LocalMachine \ TrustedPublisher \ | Where-object { $_ . thumbprint -eq "06B2B0DF262023EDD14A0555C25B3C7F753236D2" } PSParentPath : Microsoft . PowerShell . Security \ Certificate :: LocalMachine \ TrustedPublisher Thumbprint Subject -- -- -- -- -- -- -- -- - 06B2B0DF262023EDD14A0555C25B3C7F753236D2 CN = Paolo ' s Signing Certificate

How To add a TimeStamp

I’ve added a timestamp server as another step to provide more information and compliance from the security standpoint. Using a TimeStamp Server will tell us exactly when the code was signed. Another benefit is that if the certificate expires the timestamp signature will prevent it from failing until also the timestamp certificate who countersigned the file it’s still valid.

You will notice that the self-signed certificate created expires on 15/02/2020 10:00:49 PM and the timestamper server certificate will get us a “grace” period of few months until 30/12/2020 10:59:59 AM prevent it from failing.

Thanks to the twitter comment of Vegard (@Alsinet) that asked for it.

PS D:\> Set-AuthenticodeSignature .\ToBeSigned.ps1 -certificate $MyCertFromPfx -IncludeChain "All" -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" Directory: D:\ SignerCertificate Status Path ----------------- ------ ---- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned.ps1 1 2 3 4 5 6 7 8 9 PS D : \ > Set-AuthenticodeSignature . \ ToBeSigned . ps1 -certificate $MyCertFromPfx -IncludeChain "All" -TimestampServer "http://timestamp.verisign.com/scripts/timstamp.dll" Directory : D : \ SignerCertificate Status Path -- -- -- -- -- -- -- -- - -- -- -- -- -- 06B2B0DF262023EDD14A0555C25B3C7F753236D2 Valid ToBeSigned . ps1

How to check all the details of the signature

Before closing let’s print out all the details from our signed script and the certificate used and the timestamp.

Get-AuthenticodeSignature ToBeSigned.ps1 | select-object * SignerCertificate : [Subject] CN=Paolo's Signing Certificate [Issuer] CN=Paolo's Signing Certificate [Serial Number] 7A4254A219031F8E4275B3EDEAA3C96F [Not Before] 15/02/2019 9:40:49 PM [Not After] 15/02/2020 10:00:49 PM [Thumbprint] 06B2B0DF262023EDD14A0555C25B3C7F753236D2 TimeStamperCertificate : [Subject] CN=Symantec Time Stamping Services Signer - G4, O=Symantec Corporation, C=US [Issuer] CN=Symantec Time Stamping Services CA - G2, O=Symantec Corporation, C=US [Serial Number] 0ECFF438C8FEBF356E04D86A981B1A50 [Not Before] 18/10/2012 11:00:00 AM [Not After] 30/12/2020 10:59:59 AM [Thumbprint] 65439929B67973EB192D6FF243E6767ADF0834E4 Status : Valid StatusMessage : Signature verified. Path : D:\ToBeSigned.ps1 SignatureType : Authenticode IsOSBinary : False 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Get-AuthenticodeSignature ToBeSigned . ps1 | select-object * SignerCertificate : [ Subject ] CN = Paolo 's Signing Certificate [Issuer] CN=Paolo' s Signing Certificate [ Serial Number ] 7A4254A219031F8E4275B3EDEAA3C96F [ Not Before ] 15 / 02 / 2019 9 : 40 : 49 PM [ Not After ] 15 / 02 / 2020 10 : 00 : 49 PM [ Thumbprint ] 06B2B0DF262023EDD14A0555C25B3C7F753236D2 TimeStamperCertificate : [ Subject ] CN = Symantec Time Stamping Services Signer - G4 , O = Symantec Corporation , C = US [ Issuer ] CN = Symantec Time Stamping Services CA - G2 , O = Symantec Corporation , C = US [ Serial Number ] 0ECFF438C8FEBF356E04D86A981B1A50 [ Not Before ] 18 / 10 / 2012 11 : 00 : 00 AM [ Not After ] 30 / 12 / 2020 10 : 59 : 59 AM [ Thumbprint ] 65439929B67973EB192D6FF243E6767ADF0834E4 Status : Valid StatusMessage : Signature verified . Path : D : \ ToBeSigned . ps1 SignatureType : Authenticode IsOSBinary : False

More interesting docs to read

Wrap-up

After all the efforts of managing code through all the stages the software development our code is deployed, we for sure run a git pull from master to check if it’s already up to date, but how can be sure that the code is been reviewed and approved for my environment and haven’t been tampered? Code Integrity (not tampered), Owner Signature (identity) are the solution after reviewing e testing carefully the code. As usual visit my GitHub for these code examples.