<!-- https://pkizerotohero.com/guides/code-signing-signtool-cosign/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Sign Windows programs with SignTool and RFC 3161 timestamps, sign release files with Sigstore cosign, and verify publisher, identity and integrity.

Source: https://pkizerotohero.com/guides/code-signing-signtool-cosign/ · Last reviewed: 2026-10-20

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](https://pkizerotohero.com/guides/#task-advanced)

Platforms

- Windows
- Linux
- macOS

Who it is for

- [Developer](https://pkizerotohero.com/start/#developer)
- [IT ops](https://pkizerotohero.com/start/#itops)
- [Expert](https://pkizerotohero.com/start/#expert)

Level

Practitioner · 6 steps

## 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](https://docs.sigstore.dev/cosign/system_config/installation/), 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](https://pkizerotohero.com/handbook/glossary/#code-signing) tells users and systems who published a file and that nobody changed it since. A [timestamp](https://pkizerotohero.com/handbook/glossary/#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:

```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

```powershell
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](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool):

- `/fd SHA256`: the file digest algorithm. Current SDKs require it.
- `/tr <URL>`: an [RFC 3161 time-stamping authority](https://pkizerotohero.com/handbook/glossary/#timestamping-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

```powershell
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:

```powershell
$s = Get-AuthenticodeSignature "C:\Program Files\GitHub CLI\gh.exe"
$s.Status
$s.SignerCertificate.NotAfter.ToString("yyyy-MM-dd")
$s.TimeStamperCertificate.Subject
```

```text
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](https://github.com/sigstore/cosign/releases) and check the published checksum. cosign also verifies itself, which shows what a keyless signature looks like:

```bash
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
```

```text
cosign-linux-amd64: OK
Verified OK
```

### 5. cosign: sign keyless in CI

[Sigstore](https://pkizerotohero.com/handbook/glossary/#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:

```yaml
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:

```bash
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
```

```text
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:

```text
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:

*Interactive tool: Certificate decoder (runs in the browser at [/tools/decoder/](https://pkizerotohero.com/tools/decoder/))*

## Why this works

A [digital signature](https://pkizerotohero.com/handbook/digital-signatures/) binds a hash of the file to a private key, and the certificate binds that key to a publisher ([code signing](https://pkizerotohero.com/handbook/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](https://pkizerotohero.com/handbook/certificate-transparency/) log entry prove the signature was made inside that window, so no long-lived key exists to steal.

## Go deeper

- [Microsoft: SignTool](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool): every sign and verify option.
- [Microsoft: Using SignTool to sign a file](https://learn.microsoft.com/en-us/windows/win32/seccrypto/using-signtool-to-sign-a-file): worked examples.
- [RFC 3161](https://www.rfc-editor.org/rfc/rfc3161): the Time-Stamp Protocol.
- [Sigstore: signing blobs](https://docs.sigstore.dev/cosign/signing/signing_with_blobs/) and [verifying](https://docs.sigstore.dev/cosign/verifying/verify/): bundles, identities and issuers.
- [CA/Browser Forum Code Signing Baseline Requirements](https://cabforum.org/working-groups/code-signing/requirements/): key protection, validity and time-stamping rules.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/code-signing-signtool-cosign/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 24: Signed and sealed: code and document signing](https://pkizerotohero.com/handbook/code-signing/)
- [Chapter 6: Digital signatures](https://pkizerotohero.com/handbook/digital-signatures/)
- [Chapter 17: Certificate Transparency](https://pkizerotohero.com/handbook/certificate-transparency/)

### Tools

- [Certificate decoder](https://pkizerotohero.com/tools/decoder/): Paste a PEM certificate and read every field in plain words.

### Topics

- [Hashes and signatures](https://pkizerotohero.com/topics/signing/)

## More guides: Labs and advanced

- [How to run a small private CA with step-ca (lab)](https://pkizerotohero.com/guides/private-ca-step-ca/)
- [How to build a test CA with OpenSSL (lab only)](https://pkizerotohero.com/guides/openssl-test-ca/)
- [How to use SSH certificates instead of known\_hosts](https://pkizerotohero.com/guides/ssh-certificates/)
- [How to test post-quantum hybrid TLS](https://pkizerotohero.com/guides/post-quantum-tls-test/)
