How-to guide · Labs and advanced

How to sign code with signtool and cosign

In one sentence

You will sign and timestamp a Windows program with SignTool, sign a release file with Sigstore cosign, and verify both, including who signed and when.

Task
Labs and advanced
Platforms
  • Windows
  • Linux
  • macOS
Who it is for
Level
Practitioner · 6 steps
On this page
  1. Before you start
  2. Steps
  3. 1. SignTool: find your signing certificate
  4. 2. SignTool: sign and timestamp in one step
  5. 3. SignTool: verify the signature and the timestamp
  6. 4. cosign: install it and check it
  7. 5. cosign: sign keyless in CI
  8. 6. cosign: verify, naming who must have signed
  9. Check it worked
  10. If something goes wrong
  11. Why this works
  12. Go deeper

Before you start

  • For Windows programs: a publicly trusted code signing certificate and SignTool from the Windows SDK. The key cannot be a file: since 1 June 2023, the private keys of publicly trusted code signing certificates must be generated, stored and used in a hardware crypto module. In practice it sits on a USB token, in an HSM or in a cloud signing service.
  • For release files and containers: cosign, and for keyless signing an OpenID Connect identity, such as a GitHub Actions workflow.
  • A build pipeline where signing happens after tests, on the exact files you ship.

Why this matters: code signing tells users and systems who published a file and that nobody changed it since. A timestamp keeps that proof valid after the signing certificate expires, which now happens quickly: from 1 March 2026, a code signing certificate lives at most 460 days.

Steps

Steps 1 to 3 sign Windows programs with SignTool. Steps 4 to 6 sign release files and container images with cosign.

1. SignTool: find your signing certificate

SignTool picks the certificate from the Windows certificate store, where your token’s or HSM’s software registers it. List code signing certificates in PowerShell:

Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Format-List Subject, Thumbprint, NotAfter

Note the thumbprint. Selecting by thumbprint (/sha1) is safer than by name (/n), which matches any certificate whose subject contains the text.

2. SignTool: sign and timestamp in one step

signtool sign /sha1 <thumbprint> /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /d "Example App" app.exe

What each option does, from Microsoft’s SignTool reference:

  • /fd SHA256: the file digest algorithm. Current SDKs require it.
  • /tr <URL>: an RFC 3161 time-stamping authority. Use the one your CA documents. Without /tr or /t, “the signed file isn’t time stamped”.
  • /td SHA256: the digest algorithm requested from the TSA, required with /tr.
  • /t <URL> is the older, non-RFC 3161 time stamp protocol. Prefer /tr.

The TSA URL is plain HTTP on purpose: its answer is signed, so the transport does not need to be trusted. If your key lives in a cloud signing service, the provider supplies a SignTool plug-in used through /dlib, and the rest of the command stays the same.

3. SignTool: verify the signature and the timestamp

signtool verify /pa /tw /v app.exe

/pa uses the normal Authenticode policy instead of the driver policy, and /tw warns when the signature has no timestamp. PowerShell shows the same facts without the SDK. A real example, the GitHub CLI installed on our test machine:

$s = Get-AuthenticodeSignature "C:\Program Files\GitHub CLI\gh.exe"
$s.Status
$s.SignerCertificate.NotAfter.ToString("yyyy-MM-dd")
$s.TimeStamperCertificate.Subject
Valid
2026-02-25
CN=Microsoft Public RSA Time Stamping Authority, OU=nShield TSS ESN:A500-05E0-D947, OU=Microsoft America Operations, O=Microsoft Corporation, L=Redmond, S=Washington, C=US

The signing certificate expired in February 2026, yet on 20 October 2026 the status is still Valid. The timestamp proves the signature was made while the certificate was valid. Time-stamping certificates themselves may live up to 135 months under the CA/Browser Forum rules, which is what carries the proof forward.

4. cosign: install it and check it

Download cosign from its GitHub releases and check the published checksum. cosign also verifies itself, which shows what a keyless signature looks like:

grep ' cosign-linux-amd64$' cosign_checksums.txt | sha256sum -c
cosign verify-blob cosign-linux-amd64 --bundle cosign-linux-amd64.sigstore.json \
  --certificate-identity=keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer=https://accounts.google.com
cosign-linux-amd64: OK
Verified OK

5. cosign: sign keyless in CI

Sigstore signing needs no long-lived key. cosign creates a fresh key, proves your identity to the Fulcio CA with an OpenID Connect token, and receives a certificate valid for only 10 minutes. The signature is recorded in the public Rekor transparency log. In a GitHub Actions job, allow the workflow to request an identity token and sign the built file:

permissions:
  contents: read
  id-token: write          # lets cosign obtain the workflow's OIDC identity
steps:
  # ... build and test, then install cosign (for example with sigstore/cosign-installer, pinned to a commit)
  - run: cosign sign-blob --yes dist/app-1.4.2.tar.gz --bundle dist/app-1.4.2.tar.gz.sigstore.json

Publish the .sigstore.json bundle next to the file. It holds the signature, the certificate, the transparency log proof and a signed timestamp: everything needed to verify later, offline from your build system.

For images, sign by digest, never by tag, so the signature covers exactly one image: cosign sign --yes registry.example.com/app@sha256:<digest>. Where keyless signing is not possible, cosign also signs with a key you hold, including keys in cloud key services (--key azurekms://…, --key awskms://…); keep such keys out of files.

6. cosign: verify, naming who must have signed

Verification must name the identity and the issuer you expect. A real release, step-ca 0.30.2, signed by a GitHub Actions workflow:

cosign verify-blob step-ca_linux_amd64.tar.gz \
  --bundle step-ca_linux_amd64.tar.gz.sigstore.json \
  --certificate-identity "https://github.com/smallstep/workflows/.github/workflows/goreleaser.yml@refs/heads/main" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com
Verified OK

For a workflow identity, the identity is the workflow file’s URL with its Git reference, and the issuer is GitHub’s token service. For images, cosign verify takes the same two options and the image reference.

Check it worked

Test the failures as well as the success. With the real cosign bundle from step 4, a wrong expected identity and a changed file give:

Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "release@example.com", got "keyless@projectsigstore.iam.gserviceaccount.com"
Error: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature

On Windows, change one byte of a signed copy of your program and run Get-AuthenticodeSignature again: the status changes from Valid to HashMismatch.

If something goes wrong

  • SignTool Error: No certificates were found that met all the given criteria. The thumbprint or name does not match a certificate with a private key in that store, the token is not plugged in, or the certificate lacks the Code Signing EKU. Check with Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert.
  • SignTool complains about a missing digest algorithm. Add /fd SHA256 (and /td SHA256 with /tr).
  • Timestamping failed, signing succeeded. SignTool only warns. Make your build fail instead, and retry, because the signature will die with the certificate.
  • cosign reports expected SAN value … got …. The file was signed, but by someone else, or your expected identity has the wrong workflow path or Git reference. Do not loosen the check until you know which.
  • Users still see a SmartScreen warning. A valid signature identifies the publisher; Microsoft Defender SmartScreen also weighs reputation, which a new certificate or new file has not built up yet.

Paste a signing certificate into the decoder and confirm its Extended Key Usage says Code Signing:

Try it: Certificate decoderOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

PEM text, several PEM blocks, or a .pem, .crt, .cer or .der file (drop it on the box). Certificates are public, so they are safe to paste.

Why this works

A digital signature binds a hash of the file to a private key, and the certificate binds that key to a publisher (code signing). Authenticode adds an RFC 3161 countersignature from a TSA, so verifiers can check that the signature existed while the certificate was valid. Sigstore takes the idea further: the certificate lives minutes, and the signed timestamp and the transparency log entry prove the signature was made inside that window, so no long-lived key exists to steal.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9