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 that are extremely popular among 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
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?
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:
1 |
set-content -value "get-date" -path "D:\ToBeSigned.ps1" |
Let’s check the content and run it:
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:
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.
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:
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.
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:
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!
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.
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!
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 a 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 that 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 timestamped 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.
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.
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
- https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/ms537361(v=vs.85)
- https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing?view=powershell-6
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.
I do not see the -type param on the New-Self… cmdlet.
my $psversion = 5.1.14409.1018
OS = MS Windows Server 2012 R2 Datacenter
i am running powershell ISE as administrator.
I do have the cmdlet but only with these params:
SYNTAX
New-SelfSignedCertificate [-CertStoreLocation ] [-CloneCert ] [-DnsName ] [-Confirm]
[-WhatIf] []
what am i missing? TIA !
Hi Marcelo,
Yes, there are important differences between powershell versions. I always need to check the documentation (with get-help) and test if the cmdlet I’m using or the scripts are working as expected.
Please have a look and compare the documentation with these 2 links:
https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps
https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=winserver2012-ps
I do not see the -type param on the New-Self… cmdlet.
my $psversion = 5.1.14409.1018
OS = MS Windows Server 2012 R2 Datacenter
i am running powershell ISE as administrator.
I do have the cmdlet but only with these params:
SYNTAX
New-SelfSignedCertificate [-CertStoreLocation ] [-CloneCert ] [-DnsName ] [-Confirm]
[-WhatIf] []
what am i missing? TIA !