Certificate Transparency makes public TLS certificate issuance observable. It does not determine whether a domain owner authorized an issuance, replace certificate-chain validation, or guarantee that every certificate everywhere is logged. A misissued certificate can have valid transparency evidence. The benefit is a public trail that monitors and affected owners can investigate.
A promise is not an inclusion proof
An SCT, or Signed Certificate Timestamp, is a log’s signed promise to incorporate a submitted certificate or precertificate within its Maximum Merge Delay. It is not another certificate and is not itself proof that the entry already exists in the tree. Its timestamp records the log’s statement, not necessarily the exact issuance instant.
A Merkle inclusion proof connects a particular serialized leaf to a particular tree root and size. The verifier must authenticate that root through the relevant signed log statement and trusted log key. Accepting whatever root accompanies an untrusted proof establishes no useful external fact: an attacker can construct an entirely different tree.
Consistency proofs answer another question: whether a later tree extends an earlier tree without changing its existing prefix. One inclusion proof cannot establish that history. Signed checkpoints, consistency verification, and comparisons between independently observed views help expose a log that equivocates. Hashes make alterations detectable under these checks; they do not physically prevent a malicious operator from changing data.
Chrome applies its own CT policy to publicly trusted TLS certificates, considering SCT delivery, accepted log states, counts, and operator diversity. That is not equivalent to searching a public log during every page load, nor to deciding whether the owner requested the certificate. RFC 9162 specifies version 2 and obsoletes RFC 6962; publication alone does not establish which protocol a particular deployed log supports.
When it breaks
A valid SCT from an unacceptable log, an insufficient set of SCTs, or an incorrect delivery mechanism may fail the target client’s policy. Conversely, satisfying CT does not fix an expired, untrusted, or wrongly named certificate. Keep normal TLS validation, CT compliance, and issuance authorization as separate decisions.
Domain monitoring also needs interpretation. A newly observed name may belong to an authorized CDN, wildcard, or renewal. Compare issuer, names, fingerprints, and your issuance inventory before escalating; unexplained issuance needs investigation and corrective action, not an assumption that logging already blocked it.
Public logging can reveal certificate names. Do not submit private certificates or sensitive internal naming solely to try an experiment. A monitor’s incomplete coverage or stale data also cannot prove absence. Operational monitoring needs a declared scope and freshness, rather than treating an empty search result as a universal guarantee.
Check it quickly
Save this as ct_probe.go and run go run ct_probe.go with Go 1.23 or newer. This offline exercise uses RFC 6962’s SHA-256 Merkle construction with three synthetic strings, not real certificates or serialized CT leaves. It verifies only the middle leaf at index one in this fixed tree.
package main
import (
"crypto/sha256"
"fmt"
"runtime"
)
func check(ok bool, message string) {
if !ok {
panic(message)
}
}
func leaf(data string) [32]byte {
return sha256.Sum256(append([]byte{0}, []byte(data)...))
}
func branch(left, right [32]byte) [32]byte {
var data [65]byte
data[0] = 1
copy(data[1:33], left[:])
copy(data[33:], right[:])
return sha256.Sum256(data[:])
}
// Only verifies index 1 of a three-leaf fixture; not a general CT verifier.
func middleOfThree(data string, proof [][32]byte, root [32]byte) bool {
return len(proof) == 2 && branch(branch(proof[0], leaf(data)), proof[1]) == root
}
func main() {
a, b, c := leaf("fixture-A"), leaf("fixture-B"), leaf("fixture-C")
root := branch(branch(a, b), c)
check(fmt.Sprintf("%x", root) == "111b905d16e9388f6e9079aaec820ab176d649b9daecabcdb3aca45fe4b6b646", "root differs from independent fixture vector")
check(fmt.Sprintf("%x", leaf("")) == "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d", "empty leaf vector differs")
check(leaf("") != sha256.Sum256(nil), "an empty tree and an empty leaf are different")
proof := [][32]byte{a, c}
check(middleOfThree("fixture-B", proof, root), "correct inclusion proof must pass")
check(!middleOfThree("fixture-X", proof, root), "altered entry must fail")
badProof := append([][32]byte(nil), proof...)
badProof[0][0] ^= 1
check(!middleOfThree("fixture-B", badProof, root), "altered sibling must fail")
check(!middleOfThree("fixture-B", [][32]byte{c, a}, root), "sibling order matters")
check(!middleOfThree("fixture-B", proof[:1], root), "short proof must fail")
check(!middleOfThree("fixture-B", append(proof, b), root), "extra proof element must fail")
badRoot := root
badRoot[0] ^= 1
check(!middleOfThree("fixture-B", proof, badRoot), "wrong trusted root must fail")
nextRoot := branch(branch(a, b), branch(c, leaf("fixture-D")))
check(nextRoot != root, "appending an entry changes this fixture root")
check(!middleOfThree("fixture-B", proof, nextRoot), "old proof must not verify against the new root")
fmt.Println("correct proof accepted; altered entry, sibling, order, length, and root rejected")
fmt.Println("empty tree differs from empty leaf; fourth entry changes root; old proof fails")
fmt.Printf("three-leaf fixture root: %x\n", root)
fmt.Println(runtime.Version())
}
A leaf hashes a 0x00 byte followed by its data; an internal node hashes a 0x01 byte followed by its two child hashes. These prefixes provide domain separation. For three leaves, the left subtree contains A and B, while C stands alone on the right. An empty tree and a single empty leaf also have different hashes.
The correct proof contains A’s hash and C’s hash. Recombining them with B reproduces the expected root, which was independently cross-checked with Python’s hashlib. Changing B, a sibling, their order, proof length, or the expected root fails. Appending D changes this fixture’s root, and the old three-leaf proof no longer verifies against it.
All assertions passed with Go 1.27.0 on macOS. The hardcoded test root represents an already trusted three-leaf checkpoint only within this exercise. No SCT signature, real log key, certificate encoding, network endpoint, generic inclusion verifier, or consistency proof is implemented. In particular, observing a changed root does not prove append-only growth. Browser policy enforcement and live misissuance monitoring remain separate, untested operations.
Further reading
Sources
- RFC 6962: Certificate Transparency, version-1 Merkle hashing, SCT promises, and proof distinctions, checked 2026-09-09.
- RFC 9162: Certificate Transparency version 2, protocol scope, misissuance limits, and version history, checked 2026-09-09.
- Chrome Certificate Transparency policy, client-specific SCT and log acceptance requirements, checked 2026-09-09.