15

I have a self-signed code signing certificate, made with the directions from this answer, that works fine when used with signtool.exe, however if I try to sign using Set-AuthenticodeSignature, it fails.

Why can I sign using signtool, but not using Set-AuthenticodeSignature?

  • signtool:
    Signtool sign /v /n "VetWeb" SetupRDPPermissions.ps1
    
      The following certificate was selected:
        Issued to: VetWeb
        Issued by: VetWeb CA
        Expires:   Sat Dec 31 18:59:59 2039
        SHA1 hash: 84136EBF8D2603C2CD6668C955F920C6C6482EE4
    
      Done Adding Additional Store
      Successfully signed: SetupRDPPermissions.ps1
    
      Number of files successfully Signed: 1
      Number of warnings: 0
    

  • Set-AuthenticodeSignature:
    $cert = @(Get-Childitem cert:\CurrentUser\My | Where-Object -FilterScript {$_.Subject -eq 'CN=VetWeb'})[0]
    
    Set-AuthenticodeSignature SetupRDPPermissions.ps1 $cert
    
      Set-AuthenticodeSignature : Cannot sign code. The specified certificate is not suitable for code signing.
        At line:1 char:26
        + Set-AuthenticodeSignature <<<<  SetupRDPPermissions.ps1 $cert
          + CategoryInfo          : InvalidArgument: (:) [Set-AuthenticodeSignature], PSArgumentException
          + FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.SetAuthenticodeSignatureCommand
    
    • Get-Childitem cert:\CurrentUser\My -CodeSigningCert returns no results

  •  $cert | Format-List *
    
       PSPath             : Microsoft.PowerShell.Security\Certificate::CurrentUser\My\84136EBF8D2603C2CD6668C955F920C6C6482EE4
       PSParentPath       : Microsoft.PowerShell.Security\Certificate::CurrentUser\My
       PSChildName        : 84136EBF8D2603C2CD6668C955F920C6C6482EE4
       PSDrive            : cert
       PSProvider         : Microsoft.PowerShell.Security\Certificate
       PSIsContainer      : False
       Archived           : False
       Extensions         : {System.Security.Cryptography.Oid}
       FriendlyName       :
       IssuerName         : System.Security.Cryptography.X509Certificates.X500DistinguishedName
       NotAfter           : 12/31/2039 5:59:59 PM
       NotBefore          : 6/1/2012 1:49:31 PM
       HasPrivateKey      : True
       PrivateKey         : System.Security.Cryptography.RSACryptoServiceProvider
       PublicKey          : System.Security.Cryptography.X509Certificates.PublicKey
       RawData            : {48, 130, 1, 235...}
       SerialNumber       : CF330347F35AC0B4427AFFA82DB51238
       SubjectName        : System.Security.Cryptography.X509Certificates.X500DistinguishedName
       SignatureAlgorithm : System.Security.Cryptography.Oid
       Thumbprint         : 84136EBF8D2603C2CD6668C955F920C6C6482EE4
       Version            : 3
       Handle             : 479608336
       Issuer             : CN=VetWeb CA
       Subject            : CN=VetWeb
    
JW0914
  • 177
  • 1
  • 7
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431

4 Answers4

11

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
Arpit Chauhan
  • 445
  • 6
  • 18
  • It should be noted that the root certificate should be created whilst being an administrator and the personal certificate whilst being the user with whom you need to sign the script . – kkuilla Nov 21 '17 at 16:52
5

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
JW0914
  • 177
  • 1
  • 7
  • *"Anyone in need of a signing cert likely has a router with a web admin, maybe a NAS with a web admin, a VPN server, etc., all of which should have SSL certs for TLS encryption."* Except I was not doing any of that, I was trying to code sign a powershell script. – Scott Chamberlain Oct 12 '17 at 16:17
  • @ScottChamberlain In order to sign an executable file, one needs a SSL or GPG signing cert. You missed the point I was making... anyone running a server in their home needs SSL certs for that server, whether it's a web server for a router admin page or a VPN server, and because of this, it's far more secure to have the central CA sign an ICA which will only issue code signing cert(s), then utilize that ICA to sign the CSR. It's not secure, and completely counterproductive, to generate a self-signed code signing cert, as there's no chain of trust, of which will create issues down the road. – JW0914 Oct 13 '17 at 18:52
4

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
JPBlanc
  • 70,406
  • 17
  • 130
  • 175
  • 3
    The key part to figuring out what was wrong was that SignTool lets you sign with a empty `eku` but `Set-AuthenticodeSignature` must have the eku `1.3.6.1.5.5.7.3.3` applied to it. – Scott Chamberlain Dec 01 '14 at 20:42
0

With Set-AuthenticodeSignature, the signing certificate must contains Code Signing (1.3.6.1.5.5.7.3.3) in the Enhanced Key Usage

enter image description here

I use OpenSSL on Windows to create a CA certificate and a signing certificate to do the job.

  1. Generate private key for the signing certificate

    openssl genrsa -des3 -out mycert.private.key 2048

  2. Create a certificate signing request from the private key

    openssl req -key mycert.private.key -new -out mycert.csr

  3. Create CA certificate

    openssl req -x509 -sha256 -days 10950 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt

  4. Create an extension file for signing certificate

    authorityKeyIdentifier=keyid,issuer

    basicConstraints=CA:FALSE

    extendedKeyUsage = 1.3.6.1.5.5.7.3.3

    subjectAltName = @alt_names

    [alt_names]

    DNS.1 = yourdns.net

  5. Sign the certificate with CA certificate

    openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in mycert.csr -out mycert.crt -days 10950 -CAcreateserial -extfile mycert.ext

  6. Export a PFX file that contains the certificate and the private key

    openssl pkcs12 -export -in nzml.crt -inkey mycert.private.key -out mycert.pfx

  7. Import the CA certificate (rootCA.crt) in the trusted root certification authorities store of the dev computer

Adamy
  • 2,789
  • 3
  • 27
  • 25