Skip to content

More robust SCT creation for duplicate entries#886

Merged
phbnf merged 4 commits into
transparency-dev:mainfrom
phbnf:dedup-retrieve-entry
Jul 9, 2026
Merged

More robust SCT creation for duplicate entries#886
phbnf merged 4 commits into
transparency-dev:mainfrom
phbnf:dedup-retrieve-entry

Conversation

@phbnf

@phbnf phbnf commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Towards #884

This PR modifies builds SCTs from the leaf data of deduplicated entries, rather than the submitted entry. These should always match, but this defensive change prevents TesseraCT from signing an SCT that would not correspond to a leaf.

This PR also:

  • checks that the corresponding SCT matches with the submitted entry
  • simplifies function signatures, to only return the required data

@phbnf phbnf force-pushed the dedup-retrieve-entry branch from 73d66a1 to 5500b24 Compare June 26, 2026 13:16
phbnf added 2 commits July 7, 2026 12:36
… entry

When handling duplicate submissions, retrieve the full stored ctonly.Entry from log storage rather than only extracting the timestamp. Validate that IsPrecert, Certificate, and IssuerKeyHash match the submitted entry to guard against deduplication collisions or antispam driver errors. Once validated, reassign the submitted entry to the stored entry so that all inputs to SCT generation come coherently from storage. Optimize bundle extraction via ExtractEntryFromBundle by skipping over non-target tiles and ignoring extensions and fingerprints while preserving field documentation comments.

TAG=agy
CONV=50366255-7cb8-46d8-b354-53839f838955
…leaves

Update SCT generation so that signSCT accepts rfc6962.CertificateTimestamp directly rather than relying on intermediate data structures. For non-duplicate submissions and signing unit tests, derive the SCT signing input by generating the MerkleTreeLeaf via Tessera (entry.MerkleTreeLeaf) and unmarshalling it using ExtractCertificateTimestampFromLeaf. This guarantees 100% coherence between the data Tessera integrates into the Merkle tree and the data signed in the SCT while eliminating duplicated extension marshalling logic.

TAG=agy
CONV=50366255-7cb8-46d8-b354-53839f838955
@phbnf phbnf force-pushed the dedup-retrieve-entry branch from 5500b24 to abfa3e7 Compare July 7, 2026 12:36
Comment thread internal/ct/handlers.go Outdated
return http.StatusInternalServerError, []attribute.KeyValue{duplicateKey.Bool(index.IsDup)}, fmt.Errorf("could not resolve duplicate: %v", err)
}
if err := entrySCTsMatch(sctInput, entry, index.Index); err != nil {
return http.StatusInternalServerError, []attribute.KeyValue{duplicateKey.Bool(index.IsDup)}, fmt.Errorf("deduplicated entry in storage does not match submitted entry: %w", err)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the %w for the caller behave correctly, or is %v enough?

Comment thread internal/ct/handlers.go
func entrySCTsMatch(sct *rfc6962.CertificateTimestamp, e *ctonly.Entry, idx uint64) error {
extractedIdx, err := staticct.ParseCTExtensionsBytes(sct.Extensions)
if err != nil || extractedIdx != idx {
return fmt.Errorf("index mismatch: stored=%d, expected=%d", extractedIdx, idx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message seems like it could be misleading if err != nil?

Comment thread internal/types/staticct/staticct.go Outdated

// ParseCTExtensions parses base64-encoded CTExtensions into an index.
// Code is inspired by https://github.com/FiloSottile/sunlight/blob/main/tile.go.
func ParseCTExtensions(ext string) (uint64, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could rename to ParseCTExtensionsB64? Looks like the only place this is used is in the hammer (could also just make the hammer do the de-b64)

@phbnf phbnf force-pushed the dedup-retrieve-entry branch 3 times, most recently from a34a8ff to a87a0fc Compare July 8, 2026 14:54
@phbnf phbnf force-pushed the dedup-retrieve-entry branch from a87a0fc to 607ba53 Compare July 8, 2026 14:56
Comment thread internal/ct/handlers.go Outdated
Comment on lines +564 to +569
if sct == nil {
return errors.New("sct is nil")
}
if e == nil {
return errors.New("entry is nil")
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if you can avoid all these nil checks here (and in Sign below)?

Is it ever acceptable for sctInput == nil && err == nil at L524 or for entry at L477?

Could the funcs which return them return by-value?

(Can totally be a follow-up PR!)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never run into these edge cases with the current implementation, but we can't tell for sure what will call this method later, so I addd these defensive checks. I considered passing structs by value but ruled it out to avoid large copies since the cert is in there. After looking into it again, certs wouldn't be copied, only the reference to the byte array. Since copies will be small, it shouldn't be a problem. I've changed the signature of signSCT and DedupFuture. We can dereference the pointer for ctonly.Entry. EntryFromChain uses sync.Pool, so I'd apply extra care before returning a struct rather than a pointer.

I've left checks for sct.PrecertEntry and sct.X509Entry. They are not necessary with the current code paths, but better err on the safe side.

@phbnf phbnf marked this pull request as ready for review July 9, 2026 09:47
@phbnf phbnf requested a review from a team as a code owner July 9, 2026 09:47
@phbnf phbnf requested review from mhutchinson and removed request for a team July 9, 2026 09:47
Change ExtractCertificateTimestampFromLeaf, ExtractSCTInputFromBundle, DedupFuture, sctMatchesEntry, and Sign to return and accept rfc6962.CertificateTimestamp and ctonly.Entry by value rather than pointer.

This eliminates unnecessary defensive nil checks while preserving sync.Pool entry pooling and avoiding memory allocations during extraction.

TAG=agy
CONV=fdd8e21c-6548-461f-8ead-5ec8915de98d
@phbnf phbnf merged commit f1bbf4e into transparency-dev:main Jul 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants