Skip to main content
RunBook Academy

Secrets, PKI & CertificatesV · X.509 Certificates in DepthX509

Certificate signing requests: what a CSR proves and what it does not

Intermediate⏱ ~23 min🧪 Lab requiredopenssl

What you'll learn

  • Describe the structure of a PKCS#10 request and identify the part that is signed
  • Explain proof of possession precisely, including the three things it does not establish
  • Predict which requested extensions survive into an issued certificate and which do not
  • Identify the workflows that destroy proof of possession before issuance even begins

Prerequisites

Practice

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

Not yet marked complete on this device.

A certificate signing request is the most misunderstood file in operational PKI. Engineers treat it as an order form, and a CA treats it as a single narrow piece of evidence surrounded by a much larger question the request cannot answer. Understanding which is which explains why a CA sometimes ignores what you asked for, why domain validation exists, and why a well-meaning vendor who sends you a ready made key pair has quietly removed the only guarantee the format provides.

What is actually inside the request

A CSR is a PKCS#10 structure with the same three-part shape as a certificate: a body, an algorithm identifier and a signature. The body holds a subject name, the public key, and a set of attributes in which requested extensions are carried. The signature is made with the private key that matches the public key inside the body. In other words the request is self-signed, and the only key involved is the one being certified.

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out app.key
openssl req -new -key app.key -sha256 -subj "/CN=app.lab.example" \
  -addext "subjectAltName=DNS:app.lab.example,DNS:www.app.lab.example" \
  -out app.csr

Two files come out of that pair of commands and their handling could not be more different. app.key is the secret: it should be created on the host that will use it, readable by one account, and never copied. The app.csr file contains no secret at all. It carries a public key, a name and a signature, and it is safe to attach to a ticket or paste into a portal. If a procedure asks you to send a private key anywhere, the procedure is wrong; the CSR exists precisely so that you do not have to.

The one thing the signature proves

When a CA receives the request it verifies the signature using the public key found inside the same request. That sounds circular, and in a useful way it is. If the signature verifies, then whoever produced the file held the private key matching that public key at the moment they produced it. This is proof of possession, and it is the entire cryptographic content of a CSR.

Be precise about the limits, because each one has produced a real incident somewhere.

  • It does not prove exclusivity. Possession is not custody. If the key was generated on a shared build agent, or restored from a backup that three teams can read, the signature is still perfectly valid.
  • It does not prove currency. The proof is about the moment of signing. A CSR generated six months ago by an engineer who has since left still verifies today.
  • It does not prove the key is sound. A weak or compromised key signs a CSR exactly as convincingly as a strong one. Key quality is enforced by the CA’s own policy on algorithm and size, not by the signature.

The thing it does not prove, and who does prove it

A CSR can ask for any name. Nothing in the format prevents a request for api.example.com being generated by someone with no relationship to that domain, and the signature on such a request verifies perfectly. Entitlement to a name is simply not a property the request can carry, because the requester is the only party attesting to anything.

Entitlement therefore always comes from outside the request, and the mechanism differs by the kind of authority involved. A public CA performs domain control validation, proving that the requester can change something only the domain holder could change. A private CA in a corporate estate usually substitutes an approval workflow, a service catalogue entry, or an automation identity that is only allowed to request names within its own scope. A workload identity system replaces the human step with an attestation from the platform about which workload is asking.

flowchart TD
    A["Requester generates a key pair"] --> B["CSR signed by that private key"]
    B --> C["CA verifies the CSR signature"]
    C --> D["Proof of possession established"]
    B --> E["CA checks entitlement out of band"]
    E --> F["Domain control, approval or attestation"]
    D --> G["CA applies its own issuance profile"]
    F --> G
    G --> H["Certificate issued"]

The diagram shows the two independent gates every issuance passes through. The left branch is cryptographic and comes from the request. The right branch is procedural and comes from somewhere else entirely. Both must succeed, and no amount of care with the CSR can substitute for the right-hand branch. This is also the honest answer to the question of why certificate issuance cannot be made purely self-service: the part that matters is the part the requester cannot supply.

Requested extensions are requests, not instructions

Inspecting a request shows how the format itself signals this. Printing the CSR built above yields, among other output:

        Requested Extensions:
            X509v3 Subject Alternative Name:
                DNS:app.lab.example, DNS:www.app.lab.example

The heading is the point. Not “Extensions” but “Requested Extensions”. The CA is under no obligation to honour any of them, and in practice mature authorities do not. When the same request was submitted to a laboratory issuing CA that supplies its own extension file, the issued certificate carried the names from the CA’s profile rather than from the request:

openssl x509 -req -in app.csr -CA srv-ca.crt -CAkey srv-ca.key \
  -CAcreateserial -sha256 -days 90 -extfile app.ext -out app.crt
openssl x509 -in app.crt -noout -ext subjectAltName

Here the operator running the CA wrote app.ext, and OpenSSL will not carry requested extensions from the request into the issued certificate unless the CA operator explicitly configures it to. The default is to ignore them. That default is a security control, not an inconvenience: if requested extensions were honoured automatically, a request could ask for basicConstraints with the CA boolean asserted and mint itself an authority.

The practical consequence is that you must always inspect the issued certificate rather than assuming it reflects what you asked for. A request naming four hostnames and a certificate carrying one is a completely normal outcome of a CA profile that only validated one of them, and the first place it will be noticed is a client failing on a name that the requester is certain was included.

A second habit follows from the same reasoning. Old requests are routinely kept and resubmitted at renewal, because regenerating one feels like unnecessary work. It is not: a stored CSR pins you to the key that signed it, so every renewal made from the same file extends the life of a key that has been sitting on disk since the first issuance. It also freezes the requested names, so a service that has acquired an alias since the original request quietly keeps failing on it. Generate the request fresh at each renewal, and decide deliberately whether the key is being reused or replaced rather than having that decision made for you by a file nobody remembers creating.

The request is also not always something you write by hand. Automated clients build one for you, and in a great many estates no engineer ever sees a CSR again after the first manual issuance. That is a good outcome, but it does not change the analysis: the automation still holds a private key, still signs a request with it, and still cannot vouch for its own entitlement to the names it asks for.

Production discipline

  1. Generate the key where it will live. Every hop a private key takes widens the set of people who have held it, and none of those hops is recorded anywhere the next operator will look.
  2. Diff the issued certificate against the request. Compare the SAN entries and the extensions you asked for with what came back, before the certificate reaches a load balancer.
  3. Keep entitlement out of the requester’s hands. Whatever establishes the right to a name, whether validation, approval or attestation, must be something the requesting host cannot forge for itself.
  4. Never enable automatic copying of requested extensions. If your CA supports it, leave it off and drive issuance from profiles you control, so that a request cannot ask for authority it has not been granted.

Cross-course references

  • Kubernetes for Production Sysadmins - Part LXXVI (Certs) covers the cluster PKI, where an approving controller performs exactly the entitlement check this lesson describes before any signer is invoked.
  • Git, CI/CD & GitOps for Infrastructure Engineers - Part XLIII (OIDC) covers workload identity federation, the attestation that replaces human approval for an automated certificate request.
  • Linux for Production Sysadmins - Part LXXII (Secrets) covers private keys, passphrases and agents on the host, which decides who can actually use the key a request was signed with.

Quiz

Knowledge check · 4 questions

  1. Q1. A CSR requesting four hostnames is submitted, and the certificate that comes back carries only one of them in its SAN. Which explanation is correct?

  2. Q2. Sending the private key to the certificate authority alongside the CSR gives the authority stronger proof that the requester holds the key.

  3. Q3. Name three things that proof of possession from a CSR does not establish about the private key involved.

  4. Q4. Assess an issuance workflow that has been in use for two years and describe what you would change and in what order.

    A platform team requests certificates by opening a ticket. A member of the security team generates the key pair and the CSR on a shared administrative jump host, submits the CSR to the internal CA, and returns a zip file containing the certificate, the chain and the private key over internal chat. The workflow has never produced an incident. The jump host is backed up nightly to a share readable by two other teams.

Passing score: 75%. Answers are checked in this browser.