- Install Ansible on Windows - Thu, Jul 20 2023
- Use Azure Bastion as a jump host for RDP and SSH - Tue, Apr 18 2023
- Azure Virtual Desktop: Getting started - Fri, Apr 14 2023
Digitally signing your PowerShell scripts with a Class 3 code-signing certificate increases security in two important ways:
- Authentication: People who use your scripts are confident that you authored the scripts.
- Integrity: People who use your scripts know they haven’t been tampered with, because they are digitally signed.
By the conclusion of this article, you'll understand how to obtain a code-signing certificate and apply it to your PowerShell scripts. Let's get started!
Script execution policies
As you probably know, PowerShell has some built-in safety features regarding script execution. First, by default, the .ps1 file type associates with Notepad. This prevents a user from inadvertently executing a PowerShell script by double clicking, say, an e-mail file attachment.
Second, depending on the Windows OS version, script execution is limited by default. Here's the rundown:
- Windows 8.1, Windows 10, and Windows Server 2012 RTM all restrict PowerShell script execution globally (Restricted execution policy).
- Windows Server 2012 R2 and Windows Server 2016 TP4 allow locally created scripts, but require digital signatures on all downloaded scripts (RemoteSigned execution policy).
We can use Set-ExecutionPolicy or Group Policy (among other methods) to change the script execution policy on a local or remote system. Besides Restricted and RemoteSigned, the other options are:
- AllSigned: Any script, locally created or downloaded, must be digitally signed to run.
- Unrestricted: Allows unsigned scripts to run, but prompts the user for confirmation first.
- Bypass: Allows unsigned scripts to run and does not prompt the user.
We can set different script execution policies on a single computer within five separate scopes:
- Process: Affects only the current PowerShell session.
- CurrentUser: Affects only the current user.
- LocalMachine: Affects all users on that computer.
For example, the following command sets the execution policy to Bypass temporarily, only for the current PowerShell runspace:
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
To set script execution policy via Group Policy, open the Group Policy Management Console and navigate to the following path:
Computer Configuration\Policies\Administrative Templates\Windows Components\Windows PowerShell
As you can see in the following image, you enable the Turn on Script Execution policy, specifying one of the following options:
- Allow only signed scripts: This is the AllSigned execution policy.
- Allow local scripts and remote signed scripts: This is the RemoteSigned execution policy.
- Allow all scripts: This is the Unrestricted execution policy.
Please understand that PowerShell's default file association and script execution policy aren't secure in themselves by any means. Instead, they represent easy ways to avoid accidental script execution and are a part of a larger IT security plan.
Obtain an Authenticode certificate
How you obtain your code-signing certificate depends upon a number of factors, including:
- Who's going to run your code? If you have a global audience, then you'll need a certificate signed by a globally trusted certificate authority (CA).
- Do you have an internal public key infrastructure (PKI)? If you're doing PowerShell scripting for your company and you already have a local PKI such as Active Directory Certificate Services (AD CS), you can request a corporate-trusted certificate from there.
- How much money do you have to spend? You'll find that purchasing a code-signing certificate from a globally trusted CA will run you several hundred dollars per year. It's no small investment!
- Are you just testing? I'll show you how to create a self-signed certificate that is trusted by your own computer and is suitable for testing and local development.
Authenticode is Microsoft's implementation of industry standard X.509 digital certificates. Thus, when you're shopping for a public cert, you'll want to search for Authenticode code-signing certs.
As of late March 2016, here are some one-year price quotes from some of the leading public CAs:
- DigiCert: $178
- Entrust: $299
- GlobalSign: $219
- GoDaddy: $169
- Symantec: $499
- Thawte: $299
- WoSign: $445
For our test, let's create a self-signed certificate. Windows PowerShell includes the New-SelfSignedCertificate cmdlet, but instead we'll use the makecert.exe utility.
To install makecert.exe, download the Windows 10 Software Development Kit (SDK) platform installer. Make sure to install only the .NET Framework 4.6 Software Development Kit component, as shown in the following image:
Before we create the certificate, let's review PowerShell's output when we violate script execution policy. Open an elevated PowerShell console and run the following command to see how your computer is set up:
Get-ExecutionPolicy -List
Use your favorite text editor to create a simple script called myscript.ps1, saving it in the location of your choice. The point here isn't the script itself, so here's some toy code to include:
Write-Host 'Script is running!' -BackgroundColor Black -ForegroundColor Yellow
We'll start by setting the execution policy to Restricted and then running the script. Here's the partial output from my Windows Server 2012 R2 development server:
Set-ExecutionPolicy -ExecutionPolicy Restricted -Force .\myscript.ps1 .\myscript.ps1 : File C:\myscript.ps1 cannot be loaded because running scripts is disabled on this system.
Okay. Now we'll do the same thing to test the AllSigned execution policy:
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Force .\myscript.ps1 .\myscript.ps1 : File C:\myscript.ps1 cannot be loaded. The file C:\myscript.ps1 is not digitally signed.
Create and apply the digital certificate
For convenience, you may want to add makecert.exe to your computer's PATH environment variable. On my Windows Server 2012 R2 box, I found the executable in the following location:
C:\Program Files (x86)\Windows Kits\10\bin\x64
In your elevated PowerShell console, navigate to the makecert.exe parent folder and run the following command to create a local CA:
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
You'll be prompted to create a private key password; do so.
Next, run the following command to create your actual code-signing certificate, using the previously created CA as a signing authority:
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
Again, you'll be prompted for a private key password. As you can see in the next screen capture, your two new certificates now reside in the local user's certificate store.
Finally, we get to the good part—signing our simple myscript.ps1 script! We use Set-AuthenticodeSignature for that task. Here's the code, and then I'll explain it:
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert Set-AuthenticodeSignature -FilePath '.\myscript.ps1' -Certificate $cert Directory: C:\ SignerCertificate Status Path ----------------- ------ ---- 26E57A2CC30A2370B6896821DB26B0F28CB79BB7 Valid myscript.ps1
Look at the following screenshot. Digitally signed scripts include a signature block at the end.
To test, we'll set our execution policy to AllSigned:
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Force
Then we'll run the script, specifying a at the prompt to avoid future confirmation prompts:
.\myscript.ps1 Do you want to run software from this untrusted publisher? File C:\myscript.ps1 is published by CN=PowerShell User 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"): a Script is running! PS C:\>
Next steps
We covered a great deal of ground in this article, but we've only scratched the topic's surface, as the saying goes.
For example, consider the following questions:
- How can I share my self-signed certificate with other scripters at my company?
- Do I need to generate a new signature every time I modify my script?
Don't worry—I'll leave you with some resources to help you move your understanding to the next level. In the meantime, I look forward to addressing your comments and questions.
Subscribe to 4sysops newsletter!
- PowerShell conceptual help: about_signing
- PowerShell conceptual help: about_execution_policies
- Export your code-signing certificate
- Sign PowerShell scripts with an AD CS PKI
Very informative!! Just curious though; will signing your PowerShell scripts keep malware like Powersniff from running? Keep up the great work!!
Hi Matthew, thanks for your kind words. Your question is complicated, but the short answer is that having a PowerShell script signing/execution policy in place in your network certainly helps. So many factors are at play with the PowerSniff malware: (1) If your users have a recent version of Office installed, then macro execution should be disabled by default; (2) If you’ve done a good job educating your users, then they won’t open unsolicited e-mail attachments; (3) If you’re doing client/user permissions correctly, then users aren’t logged onto their systems with administrative credentials. Code signing is just another layer of security to add on top of what you already have. Cheers, Tim
I would note that StartSSL offers code signing certs for just $60. And what is the reason to use deprecated makecert over native PowerShell cmdlet? And I don’t think that using SHA1 certificates is a good idea even for testing purposes.
Thanks for the clarification, Vadims. For our readers, here’s a link to the New-SelfSignedCertificate cmdlet he mentioned. Cheers, Tim
In addition, why you didn’t talk about signature timestamping? Actually, non-timestamped signatures are not much better than non-signed scripts, because they quickly become invalid. I realized that many bloggers who write about signing scripts in PowerShell completely miss that point.
This subject would make a nice article unto itself, Vadims–thanks. Here’s a link to an MSDN article on time-stamping Authenticode signatures. -Tim
Very good overview of security in PowerShell! Keep up the good work, Tim.
Thanks, Joseph. I enjoy your work very much. -Tim
@Vadims: very wise comments, so +1000 from me
Thanks, RaFi! I don’t think this subject is discussed as much as it should be. -Tim
Excellent guide. But now that the makecert utility is deprecated, would it be possible to publish an updated article of how to do this same procedure using the New-SelfSignedCertificate cmdlet please?
Hi,
you say “Then we’ll run the script, specifying a at the prompt to avoid future confirmation prompts”.
Why is that necessary if my certificate is generated with our own CA which is published to all computers in our domain?
How do I get powershell to accept my certificate with an intact certificate chain to our CA without having to “press a” on every machine I want to run the script?
Sven