«

»

Jun 22 2015

HOWTO: Implement PowerShell Certificates End-To-End

I needed to run a PowerShell script on a few dozen machines scattered across just as many disconnected networks. I wanted to ensure that if anyone in the future attempted to make changes to the script that it would no longer execute.  This means learning how to implement PowerShell certificates.  After much Googling I found that there was no good end-to-end guide on implementing certificates.  After much trial and error, I have figured out how to implement PowerShell certificates in such a way that you do NOT need to purchase a commercial certificate while still being able to run the script on remote systems.  I figured I would share the process in the hopes that I can save the next person the frustration I had.

Disclaimer:  These steps are presented without any warranty, express or implied.  As far as I have been able to determine, this process should drastically improve the security of your scripts without otherwise introducing any new security issues.  However as I am still learning about certificates, I may have missed something.  If you do find such a security concern, please let me know as I’d love to know what I missed!

Note: The commands below use the “pki” module for PowerShell 4 and therefore requires Windows 8.1 / Windows 2012

If a modern OS is not available, these same steps can be completed through a combination of legacy tools (makecert.exe and certmgr.msc)

Specific steps on completing this with a legacy OS are not covered in this document

How the Certificate Creation Script Works

  • Creates a custom self-signed certificate on the local machine where the script authoring takes place
  • The entire key (public+private) is exported for archival and safekeeping
  • The public key of this certificate is then exported and immediately reimported into both the Root and Trusted Publisher certificate stores on the authoring computer/user
    This makes this certificate implicitly trusted on the authoring computer which makes it eligible to be used to sign a PowerShell script
  • The newly created certificate is then used to sign a custom PowerShell script
  • The public certificate is then imported onto the target/remote system where the script is intended to be executed
    The target system is assumed to be running an ExecutionPolicy of “AllSigned” which requires that all scripts must be signed by an approved entity before it is executed

Let’s start with our script.  We have a file called c:\ssl\testscript1.ps1 that contains the command we wish to execute on the remote system.

image

On the target system, we verify that the execution policy is in fact set to AllSigned.  We copy the script to the target system and try to execute it.
We receive the error “[script] is not digitally signed.”

image

The rest of this article describes how to get this script to run on the remote system without compromising security.

Note: This script will automatically download and import the New-SelfSignedCertificateEx from the Microsoft Script Gallery

The first thing we need to do is copy, paste and save the custom PowerShell module I have created to automate these steps.  You can find that at the end of this HOWTO

  • Once downloaded, open an administrator PowerShell prompt, navigate to the downloaded file and type:

  • Next type

  • No parameters are required.  However the script will ask you for the name and expiry of the certificate to be generated
  • The script will then generate the certificate, export the public/private key for disaster recovery and also export the public key to be imported into other systems

image

  • We can see that the script added the new certificate to the Personal certificate store on the local computer and exported both the private and public keys as shown in the explorer view below
  • It also placed a password on the exported private key certificate.  Be sure to keep the password known as it will be required to restore the certificate

Note: New-SelfSignedCertificateEx.ps1 is the third party script downloaded from the Microsoft script gallery

image

  • If we open Certificate Manager (certmgr.msc), we can see the certificate present

image

  • Next we need to import the new public part of the certificate into both the Root and the TrustedPublishers store for the CurrentUserThis step is necessary so that PowerShell will be able to successfully sign the script using this certificate.

We use “CurrentUser” rather than “LocalMachine” to limit the scope of impact should this certificate be compromised.

You will get a warning dialog box asking for confirmation to install the certificate.  Choose Yes

image

image

  • Run the command below to review the code signing certificates now available

image

  • We are now ready to sign our script.  Type:

image

Note: If you do not add the certificate to both the Root and Trusted Publishers store, the status of this operation will report as “UnknownError”

If we view our script now, we’ll see that a “signature” block has been added to the file

image

At this point, you now need to distribute the publio key to the machines that will run your script.  Remember it must go into both the Root and TrustedPublisher stores.  This can be done through Group Policy.  In my case however since it’s only a relatively few number of machines with no common management network, I used the Import-CustomCertificate function included.

With the public certificate present on the remote system, the script now executes as desired even with the ExecutionPolicy set to “AllSigned”. This is useful because now if anyone attempts to change so much as one character in the script, it will fail to execute.

image

 

PSCertificates.ps1

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">