Open zhihaoy opened 1 year ago
Does the cert have the private key accessible to your user? This has to be available to actually sign the file.
The document says:
Using the CodeSigningCert parameter with
Get-ChildItem
only returns certificates that have code-signing authority and contain a private key. If there is no private key, the certificates cannot be used for signing.
So I did
PS> Get-ChildItem Cert:\LocalMachine\My -CodeSigningCert
PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My
Thumbprint Subject EnhancedKeyUsageList
---------- ------- --------------------
694839C14DC54DF4EB54D8643F93BD6782C0B70A CN=DePaul University Code Signing
This reminds me. My certificate is password protected, but Set-AuthenticodeSignature
does not promote me for password.
Sorry I forgot that -CodeSigningCert
is meant to filter by this already. Unfortunately I've seen reports that while Windows things that a private key is available there are still extra mechanisms that might block it from getting the key. Things like an external HSM or yubikey needing to be present when it's requested. It could even be that the underlying mechanism just doesn't support ECDSA keys. Technically they should because the standard Authenticode is based on (PKCS 7/CMS) does but it was originally written in a time when RSA was pretty much the main key type.
You could try the following and see if it errors or not
[System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPrivateKey($codeCertificate)
This reminds me. My certificate is password protected, but Set-AuthenticodeSignature does not promote me for password.
Do you mean the pfx that was originally created had a password or are you using strong protection on the cert in the cert store itself?
You could try the following and see if it errors or not
[System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPrivateKey($codeCertificate)
It works fine.
PS> [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPrivateKey($codeCertificate)
KeySize : 256
LegalKeySizes : {System.Security.Cryptography.KeySizes, System.Security.Cryptography.KeySizes}
HashAlgorithm : SHA256
Key : System.Security.Cryptography.CngKey
KeyExchangeAlgorithm :
SignatureAlgorithm : ECDsa
This reminds me. My certificate is password protected, but Set-AuthenticodeSignature does not promote me for password.
Do you mean the pfx that was originally created had a password or are you using strong protection on the cert in the cert store itself?
Nevermind. To be used with signtool
for comparison, I created a .pfx
out of this certificate. The certificate itself has no concept of being password-protected.
I'm not sure what the problem is sorry, I would have to create my own ECDSA key and try it out. You can also try out my own module OpenAuthenticode (source code is here)and see if it works or not. It implements the signing functionality all in dotnet and isn't reliant on some C library where errors are somewhat hidden. I can't guarantee it will work as I haven't tested out ECDSA keys with it yet.
OpenAuthenticode can sign it, but signtool verify /pa the-signed.ps1
says
Index Algorithm Timestamp
========================================
SignTool Error: The signing certificate is not valid for the requested usage.
The same command gives a different error to the file I signed using signtool
itself:
Index Algorithm Timestamp
========================================
SignTool Error: WinVerifyTrust returned error: 0x80096010
The digital signature of the object did not verify.
This is strange now, because I used openssl
to generate the same kinds of certificates, and they work perfectly fine with signtool
. Those certificates have nothing special. Their algorithms are -name prime256v1
, and the code signing certificate uses -addext "extendedKeyUsage = codeSigning"
.
This is how did I create the two certificates (that gave me those errors in this thread) using PowerShell:
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -DnsName "add this to CA" -KeyAlgorithm "ECDSA_secP256r1"
new-selfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "fill what you want" -KeyAlgorithm "ECDSA_secP256r1" -Type CodeSigningCert -Signer $rootcert
I checked those OpenSSL certificates into my cert store, it shows:
But the -Type CodeSigningCert
I created using new-selfSignedCertificate
shows
Thanks for the code to generate these certs. I can confirm that Set-AuthenticodeSignature
actually signs the code it's just not able to verify the signature, just like Windows and signtool. You can see the same thing with Get-OpenAuthenticodeSignature
and through the GUI
$ Get-OpenAuthenticodeSignature C:\temp\test.ps1
Get-OpenAuthenticodeSignature: SignerInfo digest algorithm '2.16.840.1.101.3.4.2.1' is not valid for signature algorithm ''.
OpenAuthenticode can sign it, but signtool verify /pa the-signed.ps1 says
Yea Set-AuthenticodeSignature
will be using the registered powershell SIP provider to sign the file so makes sense they are producing the same data. The OpenAuthenticode
library uses the dotnet class SignedCms to do all the signature work with some custom code around storing it in the ps1 itself. I do need to add more validation to OpenAuthenticode
to pre-check the certificate but I mostly focused on just getting the signature work itself up and running before looking into that side more.
One thing I did notice is that the signature signed by Set-AuthenticodeSignature
has a digest encryption algorithm of ECC
whereas Set-OpenAuthenticodeSignature
has a value of sha256ECDSA
. The latter seems to be enough to provide a valid signature from a PKCS7/CMS perspective, it's just that the certificate used isn't seen as valid for its purpose as you've pointed out.
Unfortunately I'm not sure why it is being seen as invalid. I'll try and investigate some more and see what I can find.
Digging into this a little more because it's really buggin' me.
I have a similar problem. I'm using cfssl
to generate certificates against my Internal CA. I have a proper certificate chain setup with a root -> intermediate -> personal and the root certificate is trusted by all systems.
I've setup some garbage certificates and an environment:
PS C:\Users\user01> $file = gci \\local.network.path\subdir\profile.ps1
PS C:\Users\user01> $file.Exists
True
PS C:\Users\user01> $ecdsaCert,$rsaCert,$selfSignedCert = Get-ChildItem -Path Cert:/CurrentUser/My -CodeSigningCert
PS C:\Users\user01> $ecdsaCert,$rsaCert,$selfSignedCert | Format-List Subject,Thumbprint,HasPrivateKey,EnhancedKeyUsageList
Subject : CN=[ECDSA] Vedosis
Thumbprint : FD16C389450276F86455209F9107FA88FC8AC446
HasPrivateKey : True
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3), Secure Email (1.3.6.1.5.5.7.3.4), Secure Email (1.3.6.1.5.5.7.3.4)}
Subject : CN=[RSA] Vedosis
Thumbprint : A0929794A54A2EAC220D66F500E324DBD5C61BAC
HasPrivateKey : True
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3), Secure Email (1.3.6.1.5.5.7.3.4), Secure Email (1.3.6.1.5.5.7.3.4)}
Subject : CN=[SelfSigned] Vedosis
Thumbprint : 6D0044B84D1D81B829714FCCB58AAF9AD5CCD973
HasPrivateKey : True
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3)}
PS C:\Users\user01> Set-AuthenticodeSignature -File $file -Certificate $ecdsaCert
Directory: \\local.network.path\subdir
SignerCertificate Status StatusMessage Path
----------------- ------ ------------- ----
FD16C389450276F86455209F9107FA88FC8AC446 UnknownError The certificate is not valid for the requested usage. profile.ps1
PS C:\Users\user01> Set-AuthenticodeSignature -File $file -Certificate $rsaCert
Directory: \\local.network.path\subdir
SignerCertificate Status StatusMessage Path
----------------- ------ ------------- ----
A0929794A54A2EAC220D66F500E324DBD5C61BAC UnknownError The certificate is not valid for the requested usage. profile.ps1
PS C:\Users\user01> Set-AuthenticodeSignature -File $file -Certificate $selfSignedCert
Directory: \\local.network.path\subdir
SignerCertificate Status StatusMessage Path
----------------- ------ ------------- ----
6D0044B84D1D81B829714FCCB58AAF9AD5CCD973 Valid Signature verified. profile.ps1
What we see is by default, the Intermediate CA signed certs don't work, but the self signed does. I did some research into what I think the code is trying to do. It looks like Set-AuthenticodeSignature
is doing some things:
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/security/Authenticode.cs#L153 - Checking for CertIsGoodForSigning
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/security/SecuritySupport.cs#L607 - Implementation of CertIsGoodForSigning
with current certificate (a X509Certificate2
type certificate)
Needs to have (2) things:
Looking at the available certificates, I can see HasPrivateKey
is True
AND it contains OID Code Signing (1.3.6.1.5.5.7.3.3)
.
Just to verify there wasn't something exotic with the keys:
PS C:\Users\user01> [System.Security.Cryptography.X509Certificates.ECDSACertificateExtensions]::GetECDSAPrivateKey($ecdsaCert)
KeySize : 256
LegalKeySizes : {System.Security.Cryptography.KeySizes, System.Security.Cryptography.KeySizes}
HashAlgorithm : SHA256
Key : System.Security.Cryptography.CngKey
KeyExchangeAlgorithm :
SignatureAlgorithm : ECDsa
PS C:\Users\user01> [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($rsaCert)
LegalKeySizes : {System.Security.Cryptography.KeySizes}
Key : System.Security.Cryptography.CngKey
KeyExchangeAlgorithm : RSA
SignatureAlgorithm : RSA
KeySize : 2048
PS C:\Users\user01> [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($selfSignedCert)
LegalKeySizes : {System.Security.Cryptography.KeySizes}
Key : System.Security.Cryptography.CngKey
KeyExchangeAlgorithm : RSA
SignatureAlgorithm : RSA
KeySize : 2048
Doesn't look like it's the ECDSA key or the RSA key. I suspect it's something missing from the CSR and something within how the C# lib is evaluating either the OID or the presence of the key. I tried looking deeper into the differences between the self-signed and the Intermediate signed certs but didn't find anything that stood out to me:
PS C:\Users\brian.wilcox\Downloads> $ecdsaCert,$rsaCert,$selfSignedCert | Format-List -Property Subject,EnhancedKeyUsageList,*
Subject : CN=[ECDSA] Vedosis
PSPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My\FD16C389450276F86455209F9107FA88FC8AC446
PSParentPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My
PSChildName : FD16C389450276F86455209F9107FA88FC8AC446
PSDrive : Cert
PSProvider : Microsoft.PowerShell.Security\Certificate
PSIsContainer : False
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3), Secure Email (1.3.6.1.5.5.7.3.4), Secure Email (1.3.6.1.5.5.7.3.4)}
DnsNameList : {}
SendAsTrustedIssuer : False
EnrollmentPolicyEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
EnrollmentServerEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
PolicyId :
Archived : False
Extensions : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid…}
FriendlyName :
HasPrivateKey : True
PrivateKey :
IssuerName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
NotAfter : 9/12/2024 1:04:00 PM
NotBefore : 7/17/2023 1:04:00 PM
PublicKey : System.Security.Cryptography.X509Certificates.PublicKey
RawData : {48, 130, 2, 138…}
RawDataMemory : System.ReadOnlyMemory<Byte>[654]
SerialNumber : 76A84488B27370330558191A2D764D4190CFC92D
SignatureAlgorithm : System.Security.Cryptography.Oid
SubjectName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
Thumbprint : FD16C389450276F86455209F9107FA88FC8AC446
Version : 3
Handle : 1531194107056
Issuer : CN=IntermediateCA
SerialNumberBytes : System.ReadOnlyMemory<Byte>[20]
Subject : CN=[RSA] Vedosis
PSPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My\A0929794A54A2EAC220D66F500E324DBD5C61BAC
PSParentPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My
PSChildName : A0929794A54A2EAC220D66F500E324DBD5C61BAC
PSDrive : Cert
PSProvider : Microsoft.PowerShell.Security\Certificate
PSIsContainer : False
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3), Secure Email (1.3.6.1.5.5.7.3.4), Secure Email (1.3.6.1.5.5.7.3.4)}
DnsNameList : {}
SendAsTrustedIssuer : False
EnrollmentPolicyEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
EnrollmentServerEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
PolicyId :
Archived : False
Extensions : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid…}
FriendlyName :
HasPrivateKey : True
PrivateKey : System.Security.Cryptography.RSACng
IssuerName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
NotAfter : 9/12/2024 1:04:00 PM
NotBefore : 7/17/2023 1:04:00 PM
PublicKey : System.Security.Cryptography.X509Certificates.PublicKey
RawData : {48, 130, 3, 84…}
RawDataMemory : System.ReadOnlyMemory<Byte>[856]
SerialNumber : 4F9B03AFCEF99E29D1A8B7546BC9FEE23C354BA3
SignatureAlgorithm : System.Security.Cryptography.Oid
SubjectName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
Thumbprint : A0929794A54A2EAC220D66F500E324DBD5C61BAC
Version : 3
Handle : 1531194107184
Issuer : CN=IntermediateCA
SerialNumberBytes : System.ReadOnlyMemory<Byte>[20]
Subject : CN=[SelfSigned] Vedosis
PSPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My\6D0044B84D1D81B829714FCCB58AAF9AD5CCD973
PSParentPath : Microsoft.PowerShell.Security\Certificate::CurrentUser\My
PSChildName : 6D0044B84D1D81B829714FCCB58AAF9AD5CCD973
PSDrive : Cert
PSProvider : Microsoft.PowerShell.Security\Certificate
PSIsContainer : False
EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3)}
DnsNameList : {[SelfSigned] Vedosis}
SendAsTrustedIssuer : False
EnrollmentPolicyEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
EnrollmentServerEndPoint : Microsoft.CertificateServices.Commands.EnrollmentEndPointProperty
PolicyId :
Archived : False
Extensions : {System.Security.Cryptography.Oid, System.Security.Cryptography.Oid, System.Security.Cryptography.Oid}
FriendlyName :
HasPrivateKey : True
PrivateKey : System.Security.Cryptography.RSACng
IssuerName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
NotAfter : 7/17/2024 1:21:05 PM
NotBefore : 7/17/2023 1:01:05 PM
PublicKey : System.Security.Cryptography.X509Certificates.PublicKey
RawData : {48, 130, 3, 24…}
RawDataMemory : System.ReadOnlyMemory<Byte>[796]
SerialNumber : 23EAA6A52AC0F5844B553982432B231B
SignatureAlgorithm : System.Security.Cryptography.Oid
SubjectName : System.Security.Cryptography.X509Certificates.X500DistinguishedName
Thumbprint : 6D0044B84D1D81B829714FCCB58AAF9AD5CCD973
Version : 3
Handle : 1531194106160
Issuer : CN=[SelfSigned] Vedosis
Subject : CN=[SelfSigned] Vedosis
SerialNumberBytes : System.ReadOnlyMemory<Byte>[16]
Yeah, not really sure where to go here. I might try a certificate with ONLY code signing to see if that works, but ... What is goin' on here?
Oh, and as far as signtool
goes:
PS C:\Users\user01> signtool sign /sha1 FD16C389450276F86455209F9107FA88FC8AC446 /fd sha256 \\local.network.path\subdir\l_profile.ps1
Done Adding Additional Store
Successfully signed: \\local.network.path\subdir\l_profile.ps1
So, there's that.
Hi, I stumbled across this thread while researching the same issue. I have a code signing certificate from Sectigo which will sign my powershell scripts using Set-AuthenticodeSignature or using the SDK signtool.exe (also tried Set-OpenAuthnticodeSignature with different results and more feedback from the command) but in all cases, I get a script with a signature block, but they show as not signed through verification procedures.
Based on feedback above, I'm sharing some of my cert information below in hopes that it might help track down this issue. I'm stumped and unable to sign code until I can find a resolution. I appreciate any feedback you can provide.
[System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPrivateKey($cert)
KeySize : 384 LegalKeySizes : {System.Security.Cryptography.KeySizes, System.Security.Cryptography.KeySizes} HashAlgorithm : SHA256 Key : System.Security.Cryptography.CngKey KeyExchangeAlgorithm : SignatureAlgorithm : ECDsa
$cert | Format-List Subject,Thumbprint,HasPrivateKey,EnhancedKeyUsageList
Subject : CN=University of Minnesota, O=University of Minnesota, S=Minnesota, C=US Thumbprint : F0669EDD9C08C933956AF5EEDD7FDA7CFF4E6643 HasPrivateKey : True EnhancedKeyUsageList : {Code Signing (1.3.6.1.5.5.7.3.3)}
My issue:
KeyUsage "Digital Signature" was not checked while creating code signing certificate
And for parent CA, Code-Signing and Digital Signature must be checked as well
There seem to be several sharp edges in the PowerShell implementation of self-signed Authenticode code signing certificates.
Just as an added datapoint to confirm if you utilize any non-default OIDs in your certificate generation, you may need to tweak the config to make it compatible with Authenticode. This example is for a smartcard that requires a custom oid under "Application Policies" to have the driver map to a normal PIV slot , and requires re-iterating the EKU values as well:
Switch ($slot) {
# These oid values are specific to Pivkey / Taglio driver https://pivkey.zendesk.com/hc/en-us/articles/115000506843-Mapping-a-PIV-Certificate-using-an-OID
'9a' { $slot_oid = '1.3.6.1.4.1.44986.2.1.1' }
'9c' { $slot_oid = '1.3.6.1.4.1.44986.2.1.0' }
'9d' { $slot_oid = '1.3.6.1.4.1.44986.2.1.2' }
'9e' { $slot_oid = '1.3.6.1.4.1.44986.2.5.0' }
default { Write-Warning "Unable to determine slot id (9a,9c,9d,9e)"; exit $false }
}
Write-Debug "Slot: $slot, Slot_oid: $slot_oid"
[DateTime] $ValidThrough = (Get-Date)
# Check to see if there is one in the cert store and use it using -CodeSigningCert and -eku "1.3.6.1.5.5.7.3.2" for boolean &&
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert -eku "1.3.6.1.5.5.7.3.2" | Where-Object { $_.NotAfter -gt $ValidThrough -and $_HasPrivateKey} | Select-Object -First 1
Write-Debug "Certificate Store: ($cert)"
if ( -not $cert ) {
#Create self-signed cert
$params = @{
Subject = "CN=$env:USERNAME@$env:USERDOMAIN Smartcard PIV $slot"
KeyDescription = "CN=$env:USERNAME@$env:USERDOMAIN Smartcard PIV Usage"
FriendlyName = "$env:USERNAME PIV:$slot" #Friendlyname is smart on windows and will remove older duplicate mappings in case of collision
CertStoreLocation = 'Cert:\CurrentUser\My'
KeyUsage = 'DigitalSignature'
KeyUsageProperty = 'All'
Type = 'Custom'
TextExtension = @(
# .NET function for OID Mapping (name <-> oid: [Security.Cryptography.Oid]::new($OIDOrFriendlyName)
# 2.5.29.37 = Extended Key Usage (EKU) https://docs.redhat.com/en/documentation/red_hat_certificate_system/9/html/administration_guide/standard_x.509_v3_certificate_extensions#Standard_X.509_v3_Certificate_Extensions-extKeyUsage
# 1.3.6.1.5.5.7.3.1 = Server authentication
# 1.3.6.1.5.5.7.3.2 = Client authentication
# 1.3.6.1.5.5.7.3.3 = Code Signing
# 1.3.6.1.5.5.7.3.4 = Email
# 1.3.6.1.5.5.7.3.8 = Timestamping
# 1.3.6.1.5.5.7.3.9 = OCSP Signing
# 2.5.29.17 = Subject Alt Name (SAN) https://docs.redhat.com/en/documentation/red_hat_certificate_system/9/html/administration_guide/standard_x.509_v3_certificate_extensions#Standard_X.509_v3_Certificate_Extensions-subjectAltName
# 1.3.6.1.4.1.311.21.10 = Application Policies (Microsoft) https://www.alvestrand.no/objectid/1.3.6.1.4.1.311.html and https://learn.microsoft.com/en-us/powershell/module/pki/new-selfsignedcertificate?view=windowsserver2025-ps&wt.mc_id=ps-gethelp#examples
# The Application Policies OID value 1.3.6.1.4.1.44986.2.X.X = https://pivkey.zendesk.com/hc/en-us/articles/115000506843-Mapping-a-PIV-Certificate-using-an-OID
# This EKU example uses Client authentication and Code Signing (see EKU definitions above)
"2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.3",
# This SAN (Subject Alt Name) value is dynamically generated from the environment variables, may want to use FQDN here for some applications instead of $env:USERDOMAIN. FQDN may also be needed for Subject=CN value depending on verify routines/libraries
"2.5.29.17={text}upn=$env:USERNAME@$env:USERDOMAIN"
# Important: If you are using Application Policies, you also need to map in the OID values for your EKU configuration, or some validation will not work (e.g. Authenticode CodeSigning)
"1.3.6.1.4.1.311.21.10={text}oid=$slot_oid&oid=1.3.6.1.5.5.7.3.3&oid=1.3.6.1.5.5.7.3.2"
)
KeyAlgorithm = 'RSA'
KeyLength = 2048
HashAlgorithm = 'sha256'
Provider = 'Microsoft Smart Card Key Storage Provider'
KeyExportPolicy = 'NonExportable'
}
Write-Debug "Certificate Params: $params"
$cert = New-SelfSignedCertificate @params
Note: The "1.3.6.1.4.1.311.21.10={text}oid=$slot_oid&oid=1.3.6.1.5.5.7.3.3&oid=1.3.6.1.5.5.7.3.2"
includes both the custom oid for mapping ($slot_oid) and the normal eku oids for code signing and client authentication, or it will invalidate it's use for authenticode.
Prerequisites
Steps to reproduce
I created two ECDSA certificates using
New-SelfSignedCertificate
, one as the root CA and one as the code signing certificate. The code signing certificate hasEnhancedKeyUsageList
=Code Signing
. I signed the code signing certificate with the root CA. I exported the root CA. I added the root CA to the machine Trusted CA store. I find the code signing certificate, and save it in the variable$codeCertificate
. I run command:Expected behavior
The
.ps1
script is signed, just like whatsigntool
does.Actual behavior
Environment data