Skip to content
WindowsDeep Dive Published Updated 7 min readViews unavailable

PowerShell Authenticode Signing Operations: Certificate Trust, Timestamping, and Release Integrity

Sign and validate PowerShell releases with Authenticode by checking code-signing certificates, execution-policy scope, timestamps, and post-sign changes.

PowerShell Authenticode signing ties a script or module file to a code-signing certificate and detects content changes after signing. It is a release-integrity and publisher-trust mechanism, not a substitute for source review, endpoint protection, application control, or a secure build pipeline. A valid signature means the file’s signed content has not changed and the signing certificate chains according to the verifier’s policy; it does not prove the script is safe or that the publisher’s private key was protected well.

Execution policy is a separate PowerShell loading rule and is explicitly not a security boundary. It can require signed scripts under AllSigned, while RemoteSigned treats downloaded and locally authored content differently; Group Policy can set higher-precedence policy. Administrators should not use Set-ExecutionPolicy Bypass as a fix for trust errors. Build a workflow that protects the signing key, signs the exact reviewed artifact, verifies the signature and trust on a clean test host, and preserves release evidence.

Define the signer and trust model first

Decide which organizational certificate authority issues code-signing certificates, which release roles may request them, where private keys are stored, whether hardware-backed custody is required, how revocation is published, and which endpoint stores trust the publisher. Separate development, test, and production signing identities if the organization’s release controls require different trust levels. Do not export a production private key into a developer workstation or store a PFX password in a script, pipeline variable without protection, or ticket.

Code signing and trust distribution are distinct. A script can contain a mathematically valid signature while the receiving endpoint does not trust its chain or publisher. Conversely, placing an untrusted certificate in a trust store may allow signed files from that publisher to run under a policy. Define how root/intermediate CA trust and publisher trust are established, how revocation is checked, and how certificate expiration and renewal affect already-released scripts. Use the organization’s PKI and endpoint policy rather than embedding certificate imports in the script being signed.

Inspect the execution-policy baseline

Capture policy at every scope before troubleshooting. The effective policy can be controlled by MachinePolicy or UserPolicy via Group Policy and may not change when a local administrator updates LocalMachine. Use a controlled Windows host and record PowerShell edition/version because certificate-store, remoting, and pipeline behavior can differ by environment.

$PSVersionTable | Select-Object PSVersion, PSEdition, OS
Get-ExecutionPolicy -List
Get-ExecutionPolicy

Do not treat Restricted, RemoteSigned, or AllSigned as a complete malware defense. Execution policies reduce accidental execution and can enforce signing expectations, but Microsoft documents that they are not a security boundary. If policy is centrally managed, change the governing GPO or deployment control through approval rather than attempting to override the effective value in a shell. Use application control, constrained administration, least privilege, and monitoring for stronger execution controls.

Select and validate a code-signing certificate

The release identity should use a certificate issued for code signing and trusted by the target systems. Check the Enhanced Key Usage, subject, issuer, expiration, private-key availability, chain status, revocation reachability, and access-control boundary. A certificate in the personal store is not automatically appropriate just because PowerShell can see it. Require an authorized signer’s review and ensure the certificate will remain available through the timestamping and release-validation workflow.

$certificates = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.NotAfter -gt (Get-Date).AddDays(30) } |
    Select-Object Subject, Thumbprint, NotBefore, NotAfter,
        HasPrivateKey, EnhancedKeyUsageList

$certificates | Format-Table -AutoSize

Review the selected thumbprint against the approved release record. The example filters out certificates expiring within 30 days as an operational warning, not as a universal threshold. A certificate must have an accessible private key for signing; do not print key material or export it for convenience. For service-based signing, use the organization’s approved secure signing service and ensure build identities cannot sign arbitrary content outside the release workflow.

Sign only the reviewed release artifact

Finish code review, tests, dependency checks, release packaging, and file normalization before signing. Any edit to signed content invalidates the signature, so the signed file must be the exact artifact that is published and deployed. A source file copied to a deployment directory after signing must be byte-for-byte identical; normalize line endings and avoid rewriting scripts during packaging.

$path = 'C:\Release\Invoke-Inventory.ps1'
$thumbprint = 'REPLACE_WITH_APPROVED_CERTIFICATE_THUMBPRINT'
$timestampUrl = 'https://timestamp.example.invalid'

$certificate = Get-ChildItem Cert:\CurrentUser\My |
    Where-Object Thumbprint -eq $thumbprint

if (-not $certificate -or -not $certificate.HasPrivateKey) {
    throw 'Approved signing certificate is missing or has no accessible private key.'
}

$signature = Set-AuthenticodeSignature `
    -FilePath $path `
    -Certificate $certificate `
    -HashAlgorithm SHA256 `
    -TimestampServer $timestampUrl

$signature | Format-List Status, StatusMessage, SignerCertificate, TimeStamperCertificate

The URL is deliberately a placeholder: replace it with a timestamp authority approved and reachable under organizational policy, and confirm its supported protocol with your PKI/release team. Do not copy the example placeholder into a production command. Set-AuthenticodeSignature modifies the file, so run it only on the final approved artifact in a controlled release workspace. Use a timestamp when organizational policy and the timestamp service support it so signature validation can account for certificate validity at signing time; verify current PKI policy rather than assuming timestamp presence cures revocation or trust errors.

Verify signature status and exact bytes after release

Immediately verify the signed file on the signer host and a clean target-like endpoint. Inspect the signature status, signer identity, certificate chain, timestamp certificate, and revocation behavior. Calculate a cryptographic hash for the published file and compare it with the release manifest. Validate a copy from the actual deployment path because a file transfer, content transformation, or line-ending rewrite can change signed bytes.

$path = 'C:\Release\Invoke-Inventory.ps1'
$signature = Get-AuthenticodeSignature -FilePath $path
$hash = Get-FileHash -Path $path -Algorithm SHA256

[pscustomobject]@{
    Path = $path
    SignatureStatus = $signature.Status
    StatusMessage = $signature.StatusMessage
    Signer = $signature.SignerCertificate.Subject
    SignerThumbprint = $signature.SignerCertificate.Thumbprint
    TimestampSigner = $signature.TimeStamperCertificate.Subject
    SHA256 = $hash.Hash
}

Valid is meaningful only in the context of the target’s trust stores, current revocation information, and policy. Investigate NotTrusted, HashMismatch, NotSigned, or UnknownError from the actual endpoint rather than suppressing the warning. A hash mismatch generally indicates content changed after signing; re-run the release review and sign the final file again instead of attempting to preserve a stale signature. A missing timestamp certificate can reflect unsigned timestamping or validation context, so inspect the full signature object and PKI logs.

Handle publisher prompts, revocation, and certificate rotation

Under AllSigned, users can receive a publisher prompt for a previously unseen signer. Define a managed trust-distribution path for approved publishers and make the publisher’s display name and certificate lifecycle part of the release documentation. Do not tell operators to click “Always Run” without verifying the certificate and release source. If a signing certificate is compromised, revoke it through the PKI process, stop releases, determine which files it signed, and distribute replacement trust and clean artifacts under incident response.

Certificate renewal can require a new public certificate to be trusted by endpoints even when the organization name stays the same. Test overlap between old and new signer identities, publisher store behavior, timestamp chain validation, and revocation checking. Do not remove old certificate trust until supported files, rollback packages, and offline systems are accounted for. Keep a manifest that maps release versions to signer thumbprint, timestamp evidence, hash, build identity, approval, and deployment rings.

Diagnose failed script execution without weakening controls

When a signed script will not run, capture exact PowerShell error text, $PSVersionTable, effective policy scopes, file origin/mark-of-the-web context when relevant, signature status, signer chain, timestamp status, and the identity used to launch it. Check Group Policy and certificate trust before changing the script. Distinguish parser/runtime failures from execution-policy rejection and Authenticode verification failure. Never remove a signature or lower system-wide policy just to make one deployment proceed.

For a trusted file that changed, return to the reviewed source and release pipeline. For a valid signature whose publisher is not trusted, confirm the signer thumbprint and distribute trust through the approved endpoint-management mechanism. For revocation or chain errors, check network and PKI availability, intermediate certificates, CRL/OCSP policy, system time, and certificate status. Do not disable revocation checking globally as a routine workaround.

Release acceptance and operational hygiene

Accept a signed release only when the approved code review and test results refer to the exact signed hash; the signing certificate is valid and authorized; target systems trust the intended publisher; signature verification passes on a clean endpoint; timestamp and revocation status meet policy; the deployment artifact remains byte-identical; and rollback content is similarly validated. Restrict private-key access, audit each signing operation, and periodically prove certificate recovery/rotation procedures.

Signing is a release control with a chain of custody. Protect the signing key as carefully as production credentials, make the publisher trust explicit, validate signatures where the scripts execute, and keep execution policy in its correct role as defense in depth rather than as the organization’s only security barrier.

Related:

Sources:

Comments