Powershell – Digitally signing a .ps1 script

To totally unlock this section you need to Log-in

A file is signed with a certificate. A digital certificate is usually issued by a Certification Authority (CA). These are important things to consider, so it better to focus these before going on with the Powershell code signing process for scripts.

But before going directly on the steps to digitally it is better to know which are the basic security levels available in a Powershell environment.

PowerShell supports a concept called “execution policies” in order to help deliver a more secure command line administration experience. Execution policies define the restrictions under which PowerShell loads files for execution and configuration. The four execution policies are Restricted, AllSigned, RemoteSigned, and Unrestricted.

PowerShell is configured to run in its most secure mode by default. This mode is the “Restricted” execution policy, in which PowerShell operates as an interactive shell only. The modes are:

  • Restricted (default execution policy, does not run scripts, interactive only).
  • AllSigned (runs scripts; all scripts and configuration files must be signed by a publisher that you trust; opens you to the risk of running signed (but malicious) scripts, after confirming that you trust the publisher).
  • RemoteSigned (runs scripts; all scripts and configuration files downloaded from communication applications such as Microsoft Outlook, Internet Explorer, Outlook Express and Windows Messenger must be signed by a publisher that you trust; opens you to the risk of running malicious scripts not downloaded from these applications, without prompting).
  • Unrestricted (runs scripts; all scripts and configuration files downloaded from communication applications such as Microsoft Outlook, Internet Explorer, Outlook Express and Windows Messenger run after confirming that you understand the file originated from the Internet; no digital signature is required; opens you to the risk of running unsigned, malicious scripts downloaded from these applications).

Changing Execution Policy

Run the following from a PowerShell prompt (AllSigned is an example):

Set-ExecutionPolicy AllSigned

This command requires administrator privileges. Changes to the execution policy are recognized immediately.

Restricted Execution Policy

If you’re reading this for the first time, PowerShell may have just displayed the error message as you tried to run a script:

The file C:\demo_script.ps1 cannot be loaded. The execution of scripts is disabled on this system. Please see “Get-Help about_signing” for more details.

The default execution policy of PowerShell is called “Restricted“. In this mode, PowerShell operates as an interactive shell only. It does not run scripts, and loads only configuration files signed by a publisher that you trust.

Script Signing Background

Adding a digital signature to a script requires that it be signed with a code signing certificate. Two types are suitable: those created by a certificate authority (such as Verisign etc.), and those created by a user (called self-signed certificates).

If your scripts are specific to your internal use, you maybe able to self-sign. You can also buy a code signing certificate from another certificate authority if you like.

For a self-signed certificate, a designated computer is the authority that creates the certificate: the benefits of self-signing include its zero cost as well as creation speed and convenience. The drawback is that the certificate must be installed on every computer that will be running the scripts, since other computers will not trust the computer used to create the certificate.

To create a self-signed certificate, the makecert.exe program is required, which is however available on any Windows 10 system.

Creating a self-signed Certificate

We have two approaches available to create self-signed certificates, using makecert.exe utility or using the New-SelfSignedCertificate cmdlet directly in Powershell.

Powershell Method

Open Windows PowerShell and run the following One-Liner to create a signing certificate.

New-SelfSignedCertificate -DnsName [email protected] -CertStoreLocation Cert:\CurrentUser\My\ -Type Codesigning

Now, you can find your certificate in your certificate store (Personal – Certificates). Run certmgr.msc.

certmgr.msc

Now the certificate must be exported and then imported into the Trusted Root Certification Authorities and Trusted Publishers.

Powershell - Digitally signing a .ps1 script

Powershell - Digitally signing a .ps1 script

Do not export the private key. No need for.

Powershell - Digitally signing a .ps1 script

Select CER (DER) Format.

Powershell - Digitally signing a .ps1 script

Save the file wherever you want and then import the certificate to the Trusted Root Authorities and Trusted Publishers.

Powershell - Digitally signing a .ps1 script

Makecert Method

Instead of Powershell (but it is recommended) we can use makecert command to create self-signed certificates. The following command will create a local certificate authority for your computer (you will be prompted for the private key password setup):

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

Now we will need to run the following makecert command from a Command Prompt again. It will generate a personal certificate using the above certificate authority (you will be prompted for the private key password created and set before):

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

After the above steps, verify from Powershell that the certificate was generated correctly:

PS C:\ > Get-ChildItem cert:\CurrentUser\My -codesign

Below are listed the most common OIDs needed for makecert to speficy the purpose of the new certificate to be used with the -eku switch:

1.3.6.1.5.5.7.3.1 Server authentication (i.e. Server SSL Certificate)
1.3.6.1.5.5.7.3.2 Client authentication (i.e. Client SSL Certificate)
1.3.6.1.5.5.7.3.3 Code signing (i.e. Authenticode)
1.3.6.1.5.5.7.3.4 Email Encryption and Signing
1.3.6.1.5.5.7.3.5 IPsec end system
1.3.6.1.5.5.7.3.6 IPsec tunnel
1.3.6.1.5.5.7.3.7 IPsec user
1.3.6.1.5.5.7.3.8 Timestamping
1.3.6.1.4.1.311.10.3.4 Encrypting File System (EFS)
1.3.6.1.4.1.311.10.3.12 Document Signing
1.3.6.1.5.5.8.2.2 Internet Key Exchange (IKE)
1.3.6.1.4.1.311.10.12.1 Any Application Policy

Signing the Powershell Code

Now, we can proceed signing the Powershell code (this is valid for both .ps1 and .psm1 files) by using the Set-AuthenticodeSignature cmdlet in Powershell, as follows:

Set-AuthenticodeSignature -FilePath C:\Scripts\demo-script1.ps1 -Certificate (Get-ChildItem -Path Cert:\CurrentUser\My\ -CodeSigningCert)

After that, we will get an output similar to this:

Directory: C:\Scripts\

SignerCertificate Status Path
—————– —— —-
A180G4B81AA81143AD2969114E26A2CC2D2AD65B Valid demo-script1.ps1

As you see, the status is valid, so the signing was successfully done. Please note that we recommend that you supply also the TimeStampServer parameter (not included in the above example). This will make sure the script works even though the certificate that signed it is expired. It will tell the system that the code signing certificate was valid at the time of signing.

If you do not use the TimeStampServer parameter, the script will stop to work when the certificate used for signing expires. There are multiple online sources for timestamping out there.

The signing process actually modifies the end of the script with a signature block. For example, if the demo-script1.ps1 consisted of the following code:

param ( [string] $You = $(read-host “Enter your first name”) )
write-host “$You so totally rocks”

After the script is signed, it looks like this:

param ( [string] $You = $(read-host “Enter your first name”) )
write-host “$You so totally rocks”

# SIG # Begin signature block
# MIIEMwYJKoZIhvcNAQcCoIIEJDCCBCACAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6vQAn5sf2qIxQqwWUDwTZnJj
…snip…
# m5ugggI9MIICOTCCAaagAwIBAgIQyLeyGZcGA4ZOGqK7VF45GDAJBgUrDgMCHQUA
# Dxoj+2keS9sRR6XPl/ASs68LeF8o9cM=
# SIG # End signature block

Testing your script

For testing your script, make sure the execution policy allows the running of .ps1 scripts.

Get-ExecutionPolicy

Remotesigned, AllSigned and Unrestricted are your friends. If the policy is set to Restricted then set it – for this testing environment – to AllSigned.

Set-ExecutionPolicy AllSigned

Possible Errors and Warnings

If you get the following error/warning, during the starting of the execution of the script, then you might have forget to import the new certificate to the Trusted Root Certificate Authorities.

C:\temp\demo-script.ps1 : File C:\temp\Script1.ps1 cannot be loaded. A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.

While, if you must confirm the execution of your script, then it looks like the following text:

Do you want to run software from this untrusted publisher?
File C:\temp\Script1.ps1 is published by [email protected] and is not
trusted on your system. Only run scripts from trusted publishers.
[V] Never run [D] Do not run [R] Run once [A] Always run [?] Help
(default is “D”):

This will mean that we have not still imported the certificate to the Trusted Publishers store.

Exporting and Running Signed Scripts Elsewhere

PowerShell will be unable to validate a signed script on computers other than the one where it was signed. Attempting to do so gives an error:

PS C:\ > .\demo-script.ps1
The file C:\foo.ps1 cannot be loaded. The signature of the certificate can not be verified.
At line:1 char:9
+ .\demo-script.ps1 <<<<

Signed scripts can be transported by exporting (from original computer) and importing (to the new computer) the Powershell certificates found in the Trusted Root Certification Authorities container. Optionally, the Trusted Publishers can also be moved to prevent the first-time prompt.

Login on the target machine as the user under which scripts will be running. Open MMC and add the Certificates snap-in for the current user, locating the Trusted Root Certification Authorities container.

Expand the container to find the Certificates store. Right-click on it and select All Tasks, Import. It is recommended to import the certificate also in Trusted Publishers store.

Your signed script should now run on the new computer. Note that Powershell will prompt you the first time it’s run unless you also import the Trusted Publishers certificate.

If we need to deploy the script in a domain environment, we will have to use the Group Policy Management Console, so creating a new policy, selecting the domain, right-clicking it, and clicking Create a GPO in this domain, and link it there.

After that, we can give it the name Certificates Policy, and click OK. Then we will have to select the policy (Certificates Policy) in the navigation pane, right-click it, and click Edit; once Group Policy Editor has started, we click Computer Configuration, click Policies, click Windows Settings, and then click Public Key Policies. Right-click Trusted Publishers, and then click Import (do the same for Trusted Root Certification Authorities also).

In the dialog box that will asks you for the certificate to import, select the certificate you exported earlier. Then click Next.

Once imported the certificate for our Powershell code into Trusted Root Certification Authorities and Trusted Publishers, we will be alble to confirm this by looking inside the relative certificate node in the Group Policy Editor .

So, the next time the policy is updated on computers in your domain, they will add this certificate as a Trusted Publisher and Trusted Root Certification Authority. You can now run scripts signed by this certificate without being asked if the certificate is trusted or not.

Category

1 thought on “Powershell – Digitally signing a .ps1 script”

Comments are closed.