Secrets, PKI & CertificatesIII · Cryptography for Infrastructure EngineersCryptography
Digital signatures end to end: what is signed, by which key, and what a verifier learns
What you'll learn
- Identify the exact byte sequence a given signature covers, rather than the object it appears to protect
- State the three checks a complete verification performs and the facts a signature cannot establish
- Map the same signing mechanism onto certificates, SSH authentication, Git commits and build artefacts
- Distinguish the four distinct causes of a verification failure and choose the right fix for each
Prerequisites
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
Every trust decision in this course eventually reduces to one question: did the holder of a particular private key commit to a particular sequence of bytes? Signatures answer that and nothing else. Most confusion about certificates, signed commits and signed images comes from assuming a signature answers a larger question, so this lesson makes the small answer explicit and then builds the larger answers on top of it.
What is signed is never the object you think
Nobody signs a certificate, a commit or a container image. A signer canonicalises a precisely defined byte sequence, digests it, and applies the private key operation to the digest. The choice of that byte sequence is the design decision that determines what the signature is worth.
- A certificate. The signed bytes are the to-be-signed structure: version, serial, issuer, validity, subject, public key and extensions. The issuer’s signature and the outer wrapper are not covered, because they are what carries the signature.
- An SSH certificate. The signed bytes are the certificate body: the public key, the key identifier, the serial, the validity window, the principals, the critical options and the extensions. One CA signature covers all of it.
- A Git commit. The signed bytes are the commit object header and message, which contain the tree hash and the parent hashes. Because those are content-addressed digests, the signature transitively covers the entire snapshot and the entire history reachable from it.
- A container image. The signed bytes are the digest of the manifest, which lists the digests of the config and every layer. Again the signature covers a small structure that names everything else by hash.
Two patterns fall out. The signed structure always names the things it protects by digest rather than containing them, which is what keeps signatures small over arbitrarily large objects. And the fields that are outside the signed structure are, by definition, unauthenticated: an attacker cannot change a commit’s tree, but they can absolutely change which branch points at that commit.
The three checks, and the fourth that is not a check
sequenceDiagram
participant S as Signer
participant V as Verifier
S->>S: Canonicalise the bytes and digest them
S->>S: Apply the private key to the digest
S->>V: Object, signature, key identifier
V->>V: Recompute the canonical bytes and the digest
V->>V: Check the signature against the public key
V->>V: Check the public key is bound to a trusted identity
V->>V: Check policy for that identity and this action
A complete verification is three separate questions asked in order, and skipping any one of them is a class of production incident.
- Is the signature mathematically valid for this public key and these bytes? This is the only part most people picture, and it is the cheapest part.
- Whose key is that? A valid signature from an unknown key is
worth nothing. Something must bind the key to a name: a
certificate chain to a trust anchor, an
authorized_keysentry, an allowed-signers file, a pinned OIDC subject, a key fingerprint in a configuration file. - Is that identity allowed to do this, now? Validity dates, revocation state, principals, extended key usage, branch protection rules and admission policy all live here. This is the layer teams forget to build, which is how estates end up with correctly signed artefacts that nothing refuses to run.
The fourth question, when was this signed, is not answered by the signature at all. A signature carries no trustworthy time. If the answer matters, the time must come from somewhere else: a timestamp inside the signed bytes, a timestamping authority, or a transparency log entry made at the moment of signing. Treating the verifier’s own clock as the signing time is how expired-key arguments become unresolvable.
The same mechanism in four disguises
An SSH certificate is the most legible worked example, because a single command prints the entire signed structure and the identity of the key that signed it:
$ ssh-keygen -L -f alice-cert.pub
alice-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:/hOjaMxlXaTaWekzkFCIxR4PRU/c7eLkooB565Y7X6I
Signing CA: ED25519 SHA256:6x39cg8OAp7PZgisHreCLRO7g0vp6s0VZIw+DcTgKtE (using ssh-ed25519)
Key ID: "alice@runbook-lab"
Serial: 1003
Valid: from 2026-08-26T19:21:30 to 2026-08-26T20:21:30
Principals:
deploy
Read that as the three checks. The signature is over everything
listed; the Signing CA line names the key that made it, together
with the signature algorithm actually used; and the validity window,
the serial and the principal list are the policy the server will
apply. An SSH certificate has exactly one signature and no
intermediates, so there is no chain to build and no online
revocation protocol. Revocation is a key revocation list the server
loads, which makes revocation a deployment problem rather than a
network problem. The wire format is specified in an IETF draft,
draft-ietf-sshm-cert, and it is a draft rather than a published
standard.
X.509 certificates are the same idea with a chain: the leaf’s to-be-signed bytes are signed by an intermediate, whose to-be-signed bytes are signed by a root, and the verifier walks up until it reaches a key it was configured to trust. TLS then adds a second, different signature during the handshake, over the handshake transcript, to prove the server currently holds the private key rather than merely possessing a copy of somebody’s certificate.
Git signing binds a commit object to a key, and modern configurations use SSH keys for it. The verification side is where teams under-invest:
# Verification requires a mapping from identity to key. Without
# an allowed-signers file, git can check the maths and nothing else.
git config --get gpg.format
git config --get gpg.ssh.allowedSignersFile
git verify-commit HEAD
A verified commit tells you that the listed key signed those commit bytes. It does not tell you that the author line is truthful, that the person named still works there, or that this commit is the one your pipeline built. Those are policy questions answered by the allowed-signers mapping, by branch protection, and by pinning the built commit by hash.
Artefact signing completes the set. A signature over an image digest is verified against a pinned signer identity, and only an admission or deployment gate that refuses the unverified case turns it into a control.
Diagnosing a verification failure
Four failures produce similar-looking errors and need four different fixes. Establishing which one you have is the whole of the diagnosis.
| Failure | What it means | The fix |
|---|---|---|
| Signature invalid | The bytes changed, or the wrong key signed | Find the transformation, or find the correct key |
| Signer unknown | Valid signature, no binding to an identity | Add the key to the trust configuration deliberately |
| Signer untrusted | Bound, but not permitted for this action | Change policy, or use a permitted signer |
| Binding expired or revoked | Was permitted, is not now | Reissue, or complete the rotation that stalled |
The order matters. Teams reach for the last row first because expiry is familiar, and end up reissuing credentials to fix a problem that was a rewritten payload or a missing trust entry. Check whether the bytes verify against the key at all before you touch anything with a lifecycle.
Production discipline
- Write down the signed byte sequence. For any scheme you adopt or review, state precisely what is covered and what is not. The uncovered fields are where the attack goes.
- Deploy the verifier in the same change as the signer. Otherwise the signing lands, the enforcement never does, and the gap is invisible until an audit.
- Maintain the identity binding as a first-class artefact. Allowed-signers files, trusted CA lists and pinned subjects are configuration that needs review, ownership and a removal process for leavers.
- Get time from somewhere trustworthy. If signature age matters, add a timestamp inside the signed bytes or use a transparency log. Never infer it from file modification times.
- Practise the revocation path before you need it. For SSH certificates that means distributing a revocation list; for X.509 it means knowing what your clients actually check. Both are covered later in the course, and both fail when first attempted during an incident.
Cross-course references
- Linux for Production Sysadmins - Part XXVI (SSH) covers the client and server configuration that decides which signatures a host will accept during authentication.
- Kubernetes for Production Sysadmins - Part LXIV (SupplyChain) covers the admission side that turns an artefact signature into an enforced decision.
- Git, CI/CD & GitOps for Infrastructure Engineers - Part LXX (Provenance) covers the attestation that answers where an artefact came from, which is the policy layer on top of the signature described here.
Quiz
Knowledge check · 4 questions
Q1. git verify-commit reports a good signature on the tip of a branch. What has been established?
Q2. A digital signature records the time at which it was created, so a verifier can tell whether the key was still valid when the signing happened.
Q3. List the three checks a complete signature verification performs.
Q4. Work out which of the four verification failures you are looking at, and what to do.
A deployment gate on ca-1 rejects every image built after 09:15 UTC with a verification error. Images built before 09:15 still pass. The build pipeline was changed that morning to run on a new set of runners. The signing step reports success in every build log, and the registry shows a signature object attached to each rejected image.
Passing score: 75%. Answers are checked in this browser.