Documentation
TLS client authentication handshake
How certificates managed by certies are used during a TLS handshake, including the cryptographic signing and verification checks.
The pieces
certies creates a CA private key and certificate, a client private key, and a client certificate containing the client public key signed by the CA. The CA private key signs certificates. The client private key proves possession during a TLS connection.
Public key signatures
A digital signature scheme has three core operations:
(public_key, private_key) = KeyGen()
signature = Sign(private_key, message)
valid = Verify(public_key, message, signature)
The private key creates signatures. The public key verifies them. Verification does not reveal the private key and cannot create new signatures.
Certificate signing
A certificate is a signed statement. For a client certificate, the statement includes the subject, public key, serial, validity window, usage, and issuer.
subject: CN=alice/laptop
public key: client_public_key
serial: 02
validity: not_before..not_after
usage: client authentication
issuer: certies CA
The CA signs the certificate's TBSCertificate, meaning "to be signed certificate":
digest = SHA256(TBSCertificate)
signature = Sign(ca_private_key, digest)
The server receives the certificate and verifies it with the CA public key from ca.crt:
digest = SHA256(TBSCertificate)
Verify(ca_public_key, digest, signature)
If verification succeeds, the server knows the CA signed the certificate contents, including the client's public key, subject, serial, dates, and usage extensions.
RSA signatures, briefly
For RSA signing, the private key contains a private exponent d and modulus n. The public key contains exponent e and the same modulus n.
s = m^d mod n
m = s^e mod n
Real TLS certificates do not sign raw messages this way. They sign a structured hash encoding, commonly using RSA-PSS or PKCS#1 v1.5 depending on the certificate signature algorithm. The key relationship is that only the private exponent can produce a signature that verifies with the public exponent.
An RSA CA can sign a client certificate containing an ECDSA public key. The CA signature algorithm and the client key algorithm are separate.
ECDSA signatures, briefly
ECDSA uses elliptic curve group arithmetic. For P-256, there is a standard generator point G of large prime order n.
d where 1 <= d < n
Q = dG
The private key is the integer d. The public key is the curve point Q. Computing Q from d is easy; recovering d from Q is intended to be infeasible.
To sign a message hash z, ECDSA chooses a fresh secret nonce k and computes:
R = kG
r = R.x mod n
s = k^-1 * (z + r*d) mod n
signature = (r, s)
Verification uses only the public key:
w = s^-1 mod n
u1 = z*w mod n
u2 = r*w mod n
X = u1*G + u2*Q
valid if X.x mod n == r
Substituting Q = dG shows why this works:
X = (z/s)G + (r/s)dG
X = ((z + r*d)/s)G
s = k^-1 * (z + r*d)
(z + r*d)/s = k
X = kG = R
The nonce k must never be reused with the same private key. Reusing it across two signatures can reveal the private key.
Server certificate authentication
Most TLS handshakes authenticate the server first. The client verifies the server chain, hostname, validity period, key usage, and revocation status if configured. This prevents the client from sending secrets to an unauthenticated server.
Client certificate authentication
With mutual TLS, the server also asks the client for a certificate. The server verifies that the client certificate was signed by a trusted CA, is within its validity period, has clientAuth extended key usage, is not revoked, and maps to an allowed identity.
The CA signature proves the certificate was issued by the CA. It does not prove the connecting client has the private key. That proof comes from the TLS CertificateVerify message.
CertificateVerify
During the handshake, both sides build a transcript of handshake messages:
transcript_hash = Hash(ClientHello || ServerHello || ... || Certificate)
For client authentication, the client signs a context-specific value derived from that transcript hash:
signature = Sign(client_private_key, context || transcript_hash)
The server verifies the signature with the client public key from the client certificate:
Verify(client_public_key, context || transcript_hash, signature)
If verification succeeds, the server knows the peer has the private key corresponding to the public key in the CA-signed certificate.
Why the transcript is signed
The client signs the handshake transcript, not just a random challenge. This binds the proof to this exact connection, including random values, protocol negotiation, cipher suite negotiation, key exchange messages, and certificates sent so far.
Key exchange and traffic keys
Certificate signatures authenticate identities. They do not directly encrypt application data. Modern TLS uses ephemeral Diffie-Hellman key exchange, usually ECDHE:
client_private = a
client_public = aG
server_private = b
server_public = bG
client computes: a(server_public) = a(bG) = abG
server computes: b(client_public) = b(aG) = abG
An observer sees aG and bG, but cannot feasibly compute abG. TLS feeds the shared secret into a key derivation function:
traffic_keys = KDF(shared_secret, transcript_hash, labels)
Those traffic keys protect application data with symmetric encryption such as AES-GCM or ChaCha20-Poly1305.
Finished messages
After certificate verification and key exchange, each side sends a Finished message. This is a MAC over the handshake transcript using keys derived from the handshake secret:
finished_key = KDF(handshake_secret, "finished")
verify_data = HMAC(finished_key, transcript_hash)
If either side has a different transcript or derived a different secret, Finished verification fails.
Where revocation fits
Revocation is a policy check. The CRL is itself signed by the CA:
crl_signature = Sign(ca_private_key, CRL_data)
The server verifies the CRL signature, checks that the CRL is currently valid, and checks whether the client certificate serial appears in the revoked list. In certies, index.txt is the durable revocation database and crl.pem is the signed artifact generated for servers.
Summary
Mutual TLS combines CA signature checks, certificate validity checks, CRL policy checks, CertificateVerify private-key proof, Diffie-Hellman key exchange, and Finished-message transcript verification. Together, these checks let a server authenticate a client as the holder of a specific CA-issued certificate.