Search code examples
powershellcertificaterootsign

How to sign file with non-exportable certificate in powershell?


I try to use this code in PowerShell:

$path = 'cert:\currentuser\my\' + 'Thumbprint_My_Certificate'
$cert=Get-ChildItem -Path $path
$choicec=$cert | Where-Object HasPrivateKey -eq 'true' | Where-Object { $_.Subject -eq "CN=CN_Name_My_Certificate" }
 Set-AuthenticodeSignature -FilePath C:\Users\myuser\Desktop\TestFilePS.txt -Certificate $choicec*

In that case I have this error:

Set-AuthenticodeSignature : Cannot bind argument to parameter 'Certificate' because it is null.
At line:1 char:94
+ ... ath C:\Users\myuser\Desktop\TestFilePS.txt -Certificate $choicec
+                                                                  ~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Set-AuthenticodeSignature], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.SetAuthenti
   codeSignatureCommand*

When I changed last command to

Set-AuthenticodeSignature -FilePath C:\Users\myuser\Desktop\TestFilePS.txt -Certificate $cert

I have this error:

Set-AuthenticodeSignature : Cannot sign code. The specified certificate is not suitable for code signing.
At line:1 char:1
+ Set-AuthenticodeSignature -FilePath C:\Users\myuser\Desktop\Test ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Set-AuthenticodeSignature], PSArgumentException
    + FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.SetAuthenticodeSignatureCommand

The output of $cert | fl * is as follows:


PSPath : Microsoft.PowerShell.Security\Certificate::currentuser\my\XXXThumbprint_My_CertificateXXX
PSParentPath             : Microsoft.PowerShell.Security\Certificate::currentuser\my
PSChildName              : XXXThumbprint_My_CertificateXXX
PSDrive                  : Cert
PSProvider               : Microsoft.PowerShell.Security\Certificate
PSIsContainer            : False
EnhancedKeyUsageList     : {Server Authentication (1.3.6.1.5.5.7.3.1), Client Authentication (1.3.6.1.5.5.7.3.2)}
DnsNameList              : {MyComputerName, MyDomain}
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             : 
IssuerName               : System.Security.Cryptography.X509Certificates.X500DistinguishedName
NotAfter                 : 09.05.2023 23:13:07
NotBefore                : 09.05.2021 23:13:07
HasPrivateKey            : True
PrivateKey               : System.Security.Cryptography.RSACryptoServiceProvider
PublicKey                : System.Security.Cryptography.X509Certificates.PublicKey
RawData                  : {48, 130, 6, 249...}
SerialNumber             : XXXSerial_Number_My_CertificateXX
SubjectName              : System.Security.Cryptography.X509Certificates.X500DistinguishedName
SignatureAlgorithm       : System.Security.Cryptography.Oid
Thumbprint               : XXXThumbprint_My_CertificateXXX
Version                  : 3
Handle                   : 2049462886560
Issuer                   : CN=CN_Name_My_Certificate, O=CompanyName
Subject                  : CN=CN_Name_My_Certificate, OU=OU_Name, O=CompanyName, L=L_Name

What am I doing wrong?


Solution

  • Assuming that you have requested the certifcate and it has been added to the Domain group policy "Trusted Publishers" section properly the process of signing itself is fairly easy. The PowerShell command follows the below syntax –

    Set-AuthenticodeSignature -FilePath LOCATIONOFSCRIPT -Certificate LOCATIONOFCERT –TimestampServer TSASERVER
    

    Where –

    LOCATIONOFSCRIPT = Path to the script to be signed

    LOCATIONOFCERT = Path to your user certificate

    TSASERVER = An external Time Stamp Authority that allows the script to continue to be trusted after the certificate used to sign it has expired

    So, as follows, assuming the certificate is correctly located in your personal store:

    $cert=(dir cert:currentuser\my\ -CodeSigningCert)
    
    $script = 'C:\Users\myuser\Desktop\TestFilePS.txt'
    
    # Alternative timestamp sources:
    #http://timestamp.comodoca.com/authenticode
    #http://timestamp.globalsign.com/scripts/timestamp.dll
    #http://tsa.starfieldtech.com
    #
    Try {
        Set-AuthenticodeSignature -FilePath $($script) -Certificate $cert -TimestampServer http://timestamp.digicert.com -Verbose -ErrorAction Stop
    
    }
    Catch { 
        $_
    }