Search code examples
windowspowershellcertificatecode-signingpki

How can I verify and run a signed PowerShell script only trusting the Issuing Certificate (root) and not the Signing Certificate (leaf) itself?


  • I have a Windows image that uses the PowerShell execution policy AllSigned
  • I also have a PowerShell script that is signed by a signing certificate issued by an internal CA
    • The certificate of the issuing CA is installed/trusted on the target machine
  • When I sign the PowerShell script, I am including the full certificate chain (IncludeChain = 'All')

The certificate chain looks like this:

|- Issuing CA Certificate
     |- Signing Certificate

The PowerShell script is signed by the Signing Certificate, but we are not installing that certificate on our target machine. We are only installing the Issuing CA Certificate into the Trusted Publishers and other certificate stores.

This method works when we sign our custom application binaries using the Signing Certificate (we use Windows Defender Application Control to ensure that any applications running on our target are signed by trusted publishers) but it does not work when running PowerShell scripts.

According to this MSDN community post, PowerShell uses Known Publisher rules, which state that the Signing Certificate itself has to be in the Trusted Publishers certificate store.

This is not ideal, as the signing certificate we use to sign the PowerShell scripts is not something we want to ship out, nor will it be valid anymore by the time our product ships.

I understand that if I use a timestamp server when signing the PowerShell scripts, that the signature will still be considered valid if the signature was generated within the validity window of the signing certificate, but this is not our preferred solution.

Ideally I would like to know if it is possible, and how, to have PowerShell use the Issuing CA Certificate to validate the signed PowerShell scripts. (i.e. Known Issuer rules)

In the case that it's not possible, I would like to know why Microsoft departed from the practice of allowing you to validate signatures without explicitly trusting the signing certificate (i.e. using the issuing certificate to validate it).

Any help would be greatly appreciated.


Solution

  • There is a difference between deployment and the running of PowerShell scripts.

    The confusion is that Windows Defender Application Control can use code signing 2 different ways, for 2 different reasons, and PowerShell has only ever supported one. Windows Defender Application Control can use code signing:

    1. With a trusted Issuing CA Certificate to authenticate applications. This is the situation when your company wants to share many internal applications among all employees easily. It is also used for "trusted" Microsoft Store applications.
    2. Because "normal" people don't blindly trust all applications and generic certificate authorities, you can instead deploy based off of trusting just the Signing Certificate for verification. (see Catalog Files). This is so that you could deploy applications with certificates that may not have an accessible CA. e.g. if you singed the application with an internal CA and you sell it to another company, or if you are using a self signed certificate.

    Windows Defender Application Control primary purpose is for Application deployment/control, and a byproduct is that it can do PowerShell scripts as well. Most "normal" applications can run with "invalid" or "broken" certificates with incomplete certificate chains. The certificate was only used to control the distribution of the code and validation that the application was not tampered with/changed, and has nothing to do with the active "running" of the code.

    PowerShell, on the other hand, when running with AllSigned, always validates the entire chain before running. It doesn't care about the distribution, it cares about what it runs. That means that the entire certificate chain needs to be present and trusted on the running machine. Yes, that means that if you sign with an internal CA, you need the Issuing CA Certificate, and the Signing Certificate distributed and trusted by the running party.

    This leads you to 3 options:

    1. Self signed certificate - This is ok for personal/development projects, and marginally better than distributing unsigned code.
    2. Internal CA certificate - This is ok for internal projects. In this case, yes, you would have to share the entire certificate chain if you wanted to distribute.
    3. Global/Public CA Certificate - This is the recommended method if you are distributing publicly/commercially. The Public CA's are trusted, and code signing certificates can be bought from places like DigiCert and can last 3 years. I know, for me, I would feel much more comfortable running code signed by a DigiCert over having to mess with internal or self signed certificates.