Signtool allows me to sign code but Set-AuthenticodeSignature says the "certificate is not suitable for code signing"

I had the same problem and the answer I figured out was that I had to create two certificates. First, a trusted root certificate authority using

makecert -n "CN=PowerShell Local Certificate Root" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer -ss Root -sr localMachine

And then a personal certificate from the above certificate authority using

makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer

Once these are created, use

$cert = @(Get-ChildItem cert:\CurrentUser\My -CodeSigning)[0]

for signing (assuming you have only one codesigning certificate). For example, if the script's name is xyz.ps1, use this command in PowerShell

Set-AuthenticodeSignature path/to/xyz.ps1 $cert

According to get-help certificate -CodeSigningCert dynamic parameter from the certificate provider gets only those certificates with code-signing authority.

Now why signtool can sign and not Set-AuthenticodeSignature, the explanation is maybe in Introduction to Code Signing Microsoft document.

Here is my version of generation of Certification authority :

# Gen-CACert.ps1
clear-host

$scriptBlock = {.\Makecert -n `"CN=PowerShell Authorite de certification`"  <# Sujet du certificat (conforme à la norme X50 #>`
                           -a sha1                                          <# Algorithme utilisé #>`
                           -eku 1.3.6.1.5.5.7.3.3                           <# Option du certificat (signature de code) #>`
                           -r                                               <# Certificat auto signé #>`
                           <# -ss `"$($args[0])`"                              Dossier de stockage du certificat #>`
                           -ss `"root`"                                     <# Dossier de stockage du certificat #>`
                           -sr localMachine                                 <# Magasin de stockage localmachine ou currentuser (defaut) #>`
                           -sv `"$($args[0]).pvk`"                          <# Nom du fichier contenant la clef privée #>`
                           `"$($args[0]).cer`"}                             <# Nom du fichier certificat #>

$PoshCARoot = "PoshCARoot"
Invoke-Command -ScriptBlock $scriptBlock  -ArgumentList $PoshCARoot

Here is my version of generation of dev certificate :

# Gen-DevCert.ps1
clear-host

$scriptBlock = {.\Makecert  -pe                            <# La clef privée est exportable #>`
                            -n `"CN=PowerShell Dev Team`"  <# Sujet du certificat (conforme à la norme X509 #>`
                            -a sha1                        <# Algorithme utilisé #>`
                            -eku 1.3.6.1.5.5.7.3.3         <# Option du certificat (signature de code) #>`
                            -ss `"My`"                     <# Dossier de stockage du certificat #>`
                            -sr currentuser                <# Magasin de stockage localmachine ou currentuser (defaut) #>`
                            -iv `"$($args[0]).pvk`"        <# Clef privée de l'autorité #>`
                            -ic `"$($args[0]).cer`"        <# Certificat de l'autorité #>`
                            `"$($args[1]).cer`"}           <# Nom du fichier certificat #>

$PoshCARoot = "PoshCARoot"
$PoshDevTeam = "PoshDevTeam"
Invoke-Command -ScriptBlock $scriptBlock  -ArgumentList $PoshCARoot,$PoshDevTeam

The issue is the signing certificate is malformed and missing the correct KUs & EKUs.

To resolve, create a self-signed CA using openssl and the openssl.cnf linked to below, a code signing ICA signed by the self-signed CA, and finally a code signing cert signed by the ICA


Pre-built openssl.cnf contains all info & commands required beginning on Line 430:

  1. Prerequisite:
    • Windows: Install OpenVPN (includes openssl-utils)
      (add to System PATH: %ProgramFiles%\OpenVPN\bin)
    • BSD/Linux: Install openssl || openssl-utils || Compile
  2. Create CA:
    # CA key should have a secure passphrase of at least 20 characters, containing 
    # at least 2 uppercase, 2 lowercase, 2 numbers, and 2 symbols
    
    
    # PreReqs: Create files crlnumber, index, rand, & serial
      mkdir cert crl 
      echo 01 > crl\crlnumber ; echo > index ; echo > rand ; echo 00 > serial
    
    
    # Create CA:
      openssl req -x509 -new -sha512 -days 3650 -newkey rsa:4096 -keyout "CA.key.pem" -out "CA.crt.pem" -config "openssl.cnf" -extensions v3_ca
    
  3. Create ICA:
    # ICA key should have a secure passphrase of at least 20 characters, containing 
    # at least 2 uppercase, 2 lowercase, 2 numbers, and 2 symbols
    
    
    # Request:
      openssl req -out "code-signing-ICA.csr" -new -days 3650 -sha512 -newkey rsa:4096 -keyout "code-signing-ICA.key" -config "openssl.cnf" -extensions v3_signing_ica
    
    # Sign:
      openssl x509 -req -sha512 -days 3650 -in "code-signing-ICA.csr" -CA "CA.crt.pem" -CAkey "CA.key.pem" -CAserial "serial" -out "code-signing-ICA.crt.pem" -extfile "openssl.cnf" -extensions v3_signing_ica
    
      # Create Concatenated CA - ICA Cert Chain:
        # Windows:
          cmd /c type "code-signing-ICA.crt.pem" "CA.crt.pem" > "code-signing-ICA-Chain.crt.pem"
    
        # BSD/Linux:
          cat "code-signing-ICA.crt.pem" "CA.crt.pem" > "code-signing-ICA-Chain.crt.pem"
    
  4. Create Signing Cert:
    # Request:
      openssl req -out "code-signing.csr" -new -days 3650 -sha512 -newkey rsa:2048 -keyout "code-signing.key.pem" -config "openssl.cnf" -extensions v3_codesign
    
    # Sign:
      openssl x509 -req -sha512 -days 3650 -in "code-signing.csr" -CA "code-signing-ICA-chain.crt.pem" -CAkey "code-signing-ICA.key.pem" -CAserial "serial" -out "code-signing.crt.pem" -extfile "openssl.cnf" -extensions v3_codesign
    
    # Export:
      openssl pkcs12 -export -out "code-signing.p12" -inkey "code-signing.key.pem" -in "code-signing.crt.pem" -certfile "code-signing-ICA-chain.crt.pem"
    


OpenSSL KUs & EKUs

Code signing certificates should have the following set:

  • keyUsage  = critical, nonRepudiation, digitalSignature
    
    • nonRepudiation:
      Certificate may be used to sign data as above but the certificate public key may be used to provide non-repudiation services
      (Prevents the signing entity from falsely denying some action)
    • digitalSignature:
      Certificate may be used to apply a digital signature
      (Used for entity authentication & data origin authentication with integrity)

  • extendedKeyUsage  = critical, codeSigning, msCodeInd, msCodeCom, mcCTLSign, timeStamping
    
    • codeSigning:
      Code Signing
    • msCodeInd:
      Microsoft Individual Code Signing (authenticode)
    • msCodeCom:
      Microsoft Commerical Code Signing (authenticode)
    • mcCTLSign:
      Microsoft Trust List Signing
    • timeStamping:
      Trusted Timestamping


SignTool

Prerequisites:

  1. Install Windows SDK
  2. WinKey+Rsysdm.cpl → OK
    AdvancedEnvironment Variables...System variablesPathEdit...
  3. Add to PATH: %ProgramFiles(x86)%\Windows Kits\10\bin\10.0.15063.0\x64
    • Ensure \10\bin\10.0.15063.0\x64 reflects the proper path for your Windows version

# Establish $TS variable:
  Set-Variable -Name TS -Value "http://sha256timestamp.ws.symantec.com/sha256/timestamp" -Scope "Global"

# Sign:
  SignTool sign /s "MY" /fd "SHA256" /ph /td "SHA256" /tr $TS "Path\to\File"
  • sign:
    Sign files using an embedded signature
  • /s<name>:
    Specify the Store to open when searching for the cert (Default: MY Store)
  • /fd:
    Specifies the file digest algorithm to use for creating file signatures (Default: SHA1)
  • /ph:
    Generate page hashes for executable files if supported
  • /td<alg>:
    Used with /tr or /tseal to request a digest algorithm used by the RFC3161 timestamp server
  • /tr<URL>:
    Specifies the RFC3161 timestamp server's URL (warning is generated if timestamping fails)
    • If /tr or /t is not specified, the signed file will not be timestamped


PowerShell

# Establish $cert variable:
  $cert = Get-PfxCertificate -FilePath "Path\to\Signing\Cert"

# Establish $TS variable (if not already set above):
  Set-Variable -Name TS -Value "http://sha256timestamp.ws.symantec.com/sha256/timestamp" -Scope "Global"

# Sign:
  Set-AuthenticodeSignature -HashAlgorithm "sha256" -IncludeChain "all" -FilePath "File"  -Certificate $cert -TimestampServer $TS
  • Set-AuthenticodeSignature:
    Adds an Authenticode signature to a PowerShell script or other files
  • -HashAlgorithm:
    Specifies hashing algorithm used to compute the digital signature
    (PowerShell 2: sha1 || PowerShell 3+: sha256)
  • -IncludeChain <String>:
    Determines which certs in the chain of trust are included in the digital signature (default: NotRoot); Acceptable values:
    • Signer: Includes only the signer's certificate
    • NotRoot: Includes all certs in the certificate chain, except for the root authority
    • All: Includes all certs in the certificate chain
  • -Certificate <X509Certificate>:
    Certificate that will be used to sign the script or file; enter a variable that stores an object representing the certificate or an expression that gets the certificate
    • To find a cert, use Get-PfxCertificate or Get-ChildItem in the Certificate [Cert:] drive; command fails if certificate isn't valid or does not have codeSigning authority