Preserve TLS 1.3 Certificate transcript bytes - #1
Open
hiroTamada wants to merge 3 commits into
Open
Conversation
certificateMsgTLS13 does not implement handshakeMessageWithOriginalBytes, so
transcriptMsg re-marshals the parsed Certificate via marshalCertificate() when
feeding the TLS 1.3 handshake transcript. That re-marshal is not guaranteed to
be byte-identical to the peer's encoding: only leaf OCSP/SCT are re-emitted (in
a fixed order), and any other per-certificate extension or non-canonical length
encoding is lost. When a server's Certificate message doesn't round-trip
exactly, the client's transcript diverges from the server's and the handshake
fails at CertificateVerify with:
tls: invalid signature by the server certificate: crypto/rsa: verification error
This is reproducible against real production HTTPS endpoints whose leaf
certificate message carries an extension beyond OCSP/SCT. It only affects the
uncompressed-certificate path; compressed certificates already transcript their
raw CompressedCertificate bytes.
clientHelloMsg, serverHelloMsg and certificateRequestMsgTLS13 already cache
their original wire bytes for exactly this reason (added in the June 2025
originalBytes work); certificateMsgTLS13 was the one TLS 1.3 transcript message
left out. Capture `original` in unmarshal and return it from originalBytes(),
and nil it in the TestMarshalUnmarshal round-trip like the sibling messages.
Adds a regression test that builds a Certificate whose leaf carries an extension
marshalCertificate drops, and asserts transcriptMsg hashes the original wire
bytes rather than the (divergent) re-marshal.
hiroTamada
marked this pull request as ready for review
August 11, 2026 00:32
hiroTamada
requested review from
rgarcia and
ulziibay-kernel
and removed request for
rgarcia
August 11, 2026 00:33
ulziibay-kernel
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Preserve the exact wire bytes of TLS 1.3
Certificatemessages for handshake transcript hashing.uTLS currently parses and then re-marshals these messages before adding them to the transcript. Unknown per-certificate extensions can be dropped during that round trip, causing
CertificateVerifyto fail against an otherwise valid server signature.This change:
transcriptMsghash those bytes instead of a lossy re-marshal;CertificateEntryextension;github.com/kernel/utlsso Kernel can depend on the fork directly.Unknown extensions remain ignored semantically. Certificate and signature verification are not weakened.
Upstream
The protocol fix is also proposed in refraction-networking/utls#409. The temporary
v1.8.2-kernel.1release applies the same fix to upstream v1.8.2 for the current downstream pin.Tests
go test ./...tls: invalid signature by the server certificateNote
Medium Risk
Changes TLS 1.3 handshake transcript construction (security-sensitive), but only to hash the peer’s bytes and fix verification failures; verification semantics are not relaxed.
Overview
Fixes TLS 1.3 handshakes that fail CertificateVerify when the server’s
Certificatemessage includes per-entry extensions that are not re-emitted on marshal (only OCSP/SCT are preserved today).certificateMsgTLS13now keeps the exact received bytes and exposesoriginalBytes()sotranscriptMsghashes the peer encoding instead of a lossy re-marshal—matching the existing pattern for ClientHello, ServerHello, and CertificateRequest. Round-trip marshal/unmarshal tests clear the neworiginalfield like the other message types.Regression coverage adds a wire fixture with an unknown leaf
CertificateEntryextension plus unit tests for transcript hashing and a full client replay handshake.The Go module and imports are renamed from
github.com/refraction-networking/utlstogithub.com/kernel/utlsso downstream can depend on this fork directly.Reviewed by Cursor Bugbot for commit 7bc214f. Bugbot is set up for automated code reviews on this repo. Configure here.