skip to content

Why can an interception point that decrypts kiosk traffic not simply present the booking service's own certificate?

level: juniorimportance: should knowfreq 50%

answer

  1. No private key, no impersonation
  2. The name is copied, the key is not
  3. Minted per host_name seen
  4. Signed by an already-trusted authority
  5. CertificateVerify is where copies fail

basics

~20 s

Because it does not hold that certificate's private key, and copied bytes fail at the signature step. So it generates its own key pair and mints a leaf for the requested host name, signed by an authority the client already trusts.

solid answer

~40 s

A certificate is useless without the matching private key: the interception point could copy the origin's leaf byte for byte into its `Certificate` message, but it could not produce the `CertificateVerify` signature over the transcript, and the client would abort with `decrypt_error(51)`. So it does the only thing available — it generates a fresh key pair and mints a leaf naming the host the client asked for, taken from `host_name` in the client's `server_name(0)` extension, and signs it with a private authority. The client's name check passes because the name was copied; everything else in that leaf — key, serial, validity, issuer — is generated. The whole scheme rests on that signing authority already being a trust anchor for the client; if it is not, the client rejects the chain with `unknown_ca(48)`.

code

pseudocode · 16 lines
pseudocode
on ClientHello received:
    requested = ClientHello.server_name(0).host_name
    if no requested name:
        fall back to the destination address

    if minted_leaf[requested] not cached:
        key_pair = generate new key pair
        leaf = build certificate with
                 subjectAltName = requested
                 public key      = key_pair.public
                 serial, validity = assigned by the private authority
        leaf.signature = sign(leaf, private_authority_key)
        minted_leaf[requested] = leaf

    send Certificate(minted_leaf[requested])
    send CertificateVerify(sign(transcript, key_pair.private))

go deeper

for a junior

Recall that a certificate is only usable by whoever holds its private key, so an intermediary that ends the connection must present one it generated for the name the client asked for.

for a middle

Explain the mechanics: the copied leaf fails at CertificateVerify, the name comes from host_name in server_name(0), and only the name is copied while key, serial, validity and issuer are generated.

for a senior

Show what you would check in an incident: the issuer of the leaf clients were served, whether a per-name leaf appears for each new host, and which alert a rejecting client actually sent.

for a principal

Consider what a fleet-wide acceptance rule buys and costs: every client whose trust decisions you control becomes interceptable, and every client that is stricter becomes an operational exception.

## Why the origin's own certificate cannot be reused A certificate is a public document. Anyone who connects to the booking service can collect the exact bytes of its end-entity leaf, and nothing stops an interception point putting those bytes into its own `Certificate` message. The next message is where it falls apart. In TLS 1.3 the server must follow `Certificate` with **`CertificateVerify`**, a signature over the handshake transcript made with the private key belonging to the leaf it just sent. The interception point does not have that key — it never left the origin — so it cannot produce a signature that verifies against the copied leaf. A client that checks the signature and finds it wrong ends the handshake with **`decrypt_error(51)`**, the alert for a handshake cryptographic operation that failed, including a signature that will not verify. So impersonating a name requires a key for that name. Since the origin's key is unavailable, one is generated. ## What gets minted, and from what On seeing a `ClientHello`, an interception point that intends to terminate reads the host the client asked for and produces a leaf for it: | Part of the minted leaf | Where it comes from | |---|---| | The host name (a `subjectAltName` entry) | Copied from `host_name` in the client's `server_name(0)` extension | | The public key | A fresh key pair generated by the interception point | | The serial number and validity window | Assigned by the private authority doing the signing | | The issuer and the signature | The private authority, not the origin's certificate authority | Only the name is copied; everything else is generated. That split is the whole trick and the whole tell: - The **name matching** a client performs passes, because the name is exactly what the client asked for. - The **issuer** differs from whatever normally signs for that host, which is the one difference visible to anyone who looks at the leaf they were served. - A leaf is needed **per host name**, because the name check is per connection; the interception point mints and caches them as new names appear. ## What still has to be true for the client to accept it The minted leaf is accepted only because of something that happened long before the connection: the signing authority is already a trust anchor for that client. If it is not, path building ends with no anchor and the client sends **`unknown_ca(48)`** — the alert for a chain that was received but could not be matched to a known trust anchor. Depending on what is wrong with the leaf itself, a client may instead send `bad_certificate(42)`. This is also why the mechanism has a hard edge: 1. It works for clients whose trust decisions someone else controls. 2. It fails for any client whose acceptance rules are stricter than "chains to something in my store". 3. It fails outright for any name the interception point cannot read, because it cannot mint a leaf for a name it never saw. ## Where the name comes from, and when it is missing The `server_name(0)` extension exists so that one address can serve many hosts: the client states the host it wants, in the `host_name` entry, before any certificate is chosen. An interception point reads exactly the same field, for exactly the same reason — it must choose a certificate too. When that field is absent or unreadable, the interception point has only the destination address to work with and can mint a leaf naming that address, which most clients will then reject against the name they actually asked for. The value of the extension to a termination point is precisely that the client volunteers the name up front, in the clear, in the first message. ## Version notes - In **TLS 1.2**, the server's `Certificate` message travelled before encryption began, so the minted leaf was visible to anyone watching the flight. - In **TLS 1.3**, the certificate flight is encrypted under handshake keys, so the minted leaf is visible to the client but not to a passive observer of the connection. - The `server_name(0)` extension itself is unchanged across both versions and is read the same way. The conclusion the candidate is being tested on does not move between versions: presenting a name requires holding a key for it, so an interception point that decrypts is always presenting a certificate it generated, not one the origin ever issued.

  • Where does the interception point get the name it puts in the minted leaf, and what happens when that source is missing?
    From the `host_name` entry in the client's `server_name(0)` extension, sent in the clear in the first message so a server can pick a certificate. Without it the interception point has only the destination address to name the leaf after, which the client will usually reject against the name it actually asked for.
  • What does the client do if the minting authority is not one it trusts?
    Path building finds no anchor for the chain, so the handshake fails and the client sends `unknown_ca(48)` — the alert for a chain received but not matched to a known trust anchor. The whole arrangement depends on a trust decision made before the connection, not on anything in the handshake.
  • Why does hostname verification still pass against a minted leaf?
    Because the name is the one part copied from what the client asked for. Verification compares the requested host against the names in the leaf, and that comparison was arranged to succeed; the issuer and public key, which do differ from the origin's, are not what that check looks at.

saying these in an interview costs you the question

  • It can just replay the origin's certificate bytes it captured earlier
  • Decrypting the traffic only needs the server's certificate, not its private key
  • The minted leaf is issued by the same authority as the origin's
  • Hostname checks fail against a minted leaf, which is how you spot one
  • One minted certificate covers every destination the client visits