skip to content

Certificate Messages

Which handshake messages carry the chain, the order a server must send it in, and how a client signals the signatures it accepts. Interviewers ask because an incomplete chain fails on some clients.

part ofWeb protocols & securityoverview, primer and where to startread it →
on this pageshow

questions

6

In a TLS 1.3 handshake, which two messages does the server use to send its certificate chain and prove it holds the matching private key?

level: juniorimportance: must knowfreq 72%

answer

  1. two messages, not one
  2. one carries, one proves
  3. chain travels as certificate_list
  4. certificate(11) then certificate_verify(15)
  5. TLS 1.2 server signed elsewhere

basics

~20 s

Certificate carries the chain as a certificate_list of CertificateEntry structures. CertificateVerify carries a signature over the handshake transcript, made with the private key of the first certificate in that list. The chain alone proves nothing.

solid answer

~40 s

A TLS 1.3 server authenticates with two consecutive handshake messages. `Certificate`, handshake type `certificate(11)`, carries the chain: its body is a `certificate_list` of `CertificateEntry` structures, the first of which is the server's own end-entity certificate. `CertificateVerify`, type `certificate_verify(15)`, carries a signature computed over a value derived from the transcript of the handshake so far, using the private key belonging to that first certificate. The split matters because a certificate is a public document — anyone can copy and replay one — so only the transcript-bound signature shows the sender actually holds the key the certificate names. `Finished` closes the flight afterwards. In TLS 1.2 the server sent no `CertificateVerify` at all; its proof of possession lived elsewhere.

code

pseudocode · 13 lines
pseudocode
Certificate(11)
  certificate_list:
    entry[0]  end-entity certificate for the portal host
              extensions: (may carry a stapled status response)
    entry[1]  intermediate that signed entry[0]
    # certificate naming the trust anchor omitted on purpose

CertificateVerify(15)
  algorithm = ecdsa_secp256r1_sha256(0x0403)
  signature = sign(private key of entry[0],
                   value derived from handshake transcript so far)

Finished(20)

go deeper

for a junior

Recall the two names and their jobs: Certificate carries the chain, CertificateVerify carries the signature that proves the key is held. Being able to say why one message is not enough is the whole of the answer at this level.

for a middle

Explain the mechanics: the certificate_list of CertificateEntry structures, the first entry being the key that signs, and the signature covering the handshake transcript rather than the certificate bytes.

for a senior

Use the split diagnostically. Say which of the two messages a given symptom implicates, and state the TLS 1.2 versus 1.3 difference without being prompted, since half of deployed advice silently assumes one version.

for a principal

Frame it as where authentication evidence lives on the wire, and what that costs: every handshake pays for the chain in bytes and for the signature in a private-key operation, which is what makes handshake volume, not data volume, the scaling question.

## The two messages, and the order they arrive in A TLS 1.3 server that authenticates with a certificate does it with **two** consecutive handshake messages, not one: 1. **`Certificate`**, handshake type `certificate(11)`, carries the chain. Its body is a `certificate_list`: a sequence of `CertificateEntry` structures, each holding one certificate plus an extension block scoped to that entry. 2. **`CertificateVerify`**, handshake type `certificate_verify(15)`, carries a signature. The server signs a value derived from the transcript of every handshake message exchanged up to that point, using the private key belonging to the certificate in the **first** entry of the list. `Finished` then closes the server's flight, and only after that may application data flow. ## Why the chain on its own proves nothing A certificate is a public document. Every client that has ever connected holds a copy, a public log may hold one, and anyone who recorded an older handshake has one. Presenting it says only "a certificate authority signed this document"; it says nothing about whether the sender holds the private key the document names. That is exactly the gap `CertificateVerify` closes: - the signature is over the **handshake transcript**, which includes the client's own freshly generated `ClientHello`, so a signature lifted from a recorded handshake will not verify in this one; - it is made with the key of the **first** certificate in `certificate_list`, which is why the ordering rule exists — the first entry is the sender's own certificate, never an intermediate; - the message names the signature scheme that was used, and that scheme has to be one the client said it would accept. Without the second message, an attacker who simply replays a captured `Certificate` message would be indistinguishable from the real server. ## What each entry in the list holds - The **first** `CertificateEntry` is the server's end-entity certificate — the one whose key signs `CertificateVerify`. - Following entries are the intermediate certificates that link it upward, each normally certifying the entry immediately before it. - A certificate that names a trust anchor the peers are expected to hold already may be omitted from the list entirely. - Every entry carries its own extension block; that block is where TLS 1.3 places a stapled status response, per certificate rather than per handshake. ## TLS 1.2 kept the server's proof somewhere else | | TLS 1.2 | TLS 1.3 | |---|---|---| | Server sends `Certificate` | yes | yes | | Server sends `CertificateVerify` | no | yes | | Where the server proves key possession | in `server_key_exchange (12)` for ephemeral suites; implicitly, by decrypting the client's key material, for static RSA key transport | in `CertificateVerify` | | Who else sends `CertificateVerify` | the client only, answering a `CertificateRequest` | the client, answering a `CertificateRequest` | So "the server sends `CertificateVerify`" is a **TLS 1.3** statement. The guarantee existed in TLS 1.2 too, but it lived in a different message, and the name `CertificateVerify` referred to the client's message alone. Dropping the version is the most common inaccuracy on this material. ## Reading the pair when a handshake goes wrong Splitting the two jobs splits the failures, which is what makes the distinction practical rather than trivia: - a problem with **what was sent** — a missing intermediate, entries in the wrong order, a certificate naming no host the client asked for — is a `Certificate` problem, and is visible to anything that can print the chain the server actually put on the wire; - a problem with **the proof** — a signature that does not verify, or a scheme the client never offered — is a `CertificateVerify` problem, and re-issuing certificates will not touch it; - a server holding no certificate compatible with what the client offered fails before either message is built. ## What the pair is not - It is **not path validation**. The server puts a list on the wire; the client builds and checks a path from it up to an anchor it already trusts. Those are different operations with different failure modes. - It is **not a statement about revocation**. Neither message asserts the certificate is still valid today; a status response, when one is present at all, rides as an extension. - It is **not client authentication**. The client has its own `Certificate` and `CertificateVerify` pair, but sends it only in answer to a `CertificateRequest`. - It **does not carry the private key**. Nothing in either message reveals it. The signature is the only evidence of that key that ever crosses the wire.

  • If a TLS 1.2 server sent no CertificateVerify, where did it prove it held the private key?
    For ephemeral suites, in `server_key_exchange (12)`: the server signed the key-exchange parameters together with both peers' random values, and that signature was verified against the certificate it had just sent. Under static RSA key transport there was no signature at all — the server demonstrated possession by being able to decrypt the key material the client encrypted to its public key. TLS 1.3 removed that second path and moved the proof into one explicit message.
  • A TLS 1.3 server sends Certificate but its CertificateVerify never arrives. What has the client established?
    Nothing about the peer's identity. It holds a chain it may well be able to validate, but a chain is public data that any party can transmit. Without a transcript-bound signature made by the key in the first `CertificateEntry`, the client has no evidence that the party it is talking to holds that key, so the handshake cannot be completed.

saying these in an interview costs you the question

  • Says the certificate alone proves the server holds the private key
  • Thinks one message carries both the chain and the signature
  • Believes a TLS 1.2 server also sends CertificateVerify
  • Calls the chain a single certificate rather than a certificate_list
  • Thinks the private key is transmitted during the handshake
open as a page

In TLS, why does a server that omits an intermediate from its certificate_list fail for only some clients?

level: middleimportance: must knowfreq 62%

basics

~20 s

Because clients that already hold the missing intermediate, or can fetch it from a location named in the certificate, complete the path anyway. Clients with neither cannot reach a trust anchor and reject the chain, so the same server looks healthy and broken at once.

open as a page

In TLS 1.3, what does the server's CertificateVerify signature cover, and what does it prove that its Certificate message cannot?

level: middleimportance: should knowfreq 52%

basics

~20 s

It signs a value derived from the transcript of the handshake so far, not the certificate bytes. That binds possession of the first entry's private key to this specific handshake, which a copied Certificate message — public data anyone can replay — can never do.

open as a page

How does a TLS server serving several broker hostnames from one endpoint choose which certificate to send?

level: middleimportance: should knowfreq 48%

basics

~20 s

It reads the host_name the client put in the server_name(0) extension of its ClientHello and selects a configured certificate that names that host. With no extension, or an unknown name, it falls back to a default certificate and the client rejects the name mismatch.

open as a page

In TLS 1.3, why might a server with a valid, correctly chained certificate still be unable to authenticate to a client?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Because authentication needs a signature scheme both sides accept. The client's signature_algorithms(13) list, and signature_algorithms_cert(50) where present, constrain which certificate the server may select; a server whose only key or chain falls outside those lists cannot sign at all.

open as a page

In TLS, where does a stapled certificate status response travel, and which extension asks the server for one?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The client asks with status_request(5) in its ClientHello. TLS 1.2 answers with a separate CertificateStatus handshake message after Certificate; TLS 1.3 carries the response as an extension inside the CertificateEntry it describes, so any entry in the chain can carry one.

open as a page