Skip to content

wfe: New endpoint for serving standalone MTCs - #9102

Open
ezekiel wants to merge 7 commits into
mainfrom
ezekiel/wfe-standalone-mtcs
Open

ezekiel wants to merge 7 commits into
mainfrom
ezekiel/wfe-standalone-mtcs

Conversation

@ezekiel

@ezekiel ezekiel commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

This change updates the WFE GetOrder handler to include a "merkle-certificate" key with a mtc-retrieval URI value in orders returned to ACME clients when the wfe instance is configured with an MTCB service, and the order contains critical MTC fields.

The change also adds a WFE MerkleCertificate handler for the mtc-retrieval URI, which asks the MTCB to return a Standalone certificate and hands it over to the client ( PEM-encoded ).

Claude helped trace the otel span naming bug, and proposed that fix.

Fixes #9035

This change updates the WFE GetOrder handler to include a
"merkle-certificate" key with a mtc-retrieval URI value in orders
returned to ACME clients when the wfe instance is configured with an
MTCB service, and the order contains critical MTC fields.

The change also adds a WFE MerkleCertificate handler for the
mtc-retrieval URI, which asks the MTCB to return a Standalone
certificate and hands it over to the client ( PEM-encoded ).
@ezekiel ezekiel self-assigned this Oct 15, 2026
The order information returned through the mockRA should only include
one or the other of a classic certificate serial number, or merkle tree
certificate info, not both.
Claude was directed at failing integration test code. It traced the
issue and added a helper function for WithSpanNameFormatter as the fix,
I re-wrote its comment.
@ezekiel
ezekiel marked this pull request as ready for review October 19, 2026 16:19
@ezekiel
ezekiel requested a review from a team as a code owner October 19, 2026 16:20
@ezekiel
ezekiel requested a review from jsha October 19, 2026 16:20
@github-actions

Copy link
Copy Markdown
Contributor

@ezekiel, this PR appears to contain configuration and/or SQL schema changes. Please ensure that a corresponding deployment ticket has been filed with the new values.

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

@ezekiel, this PR appears to contain configuration and/or SQL schema changes. Please ensure that a corresponding deployment ticket has been filed with the new values.

@jsha jsha changed the title wfe: New endpoint for serving standlone MTCs wfe: New endpoint for serving standalone MTCs Oct 19, 2026
Orders presented to clients should not have Certificate or
MerkleCertificate fields, they just have Certificate, with an
appropriate URL for the certificate type.

This change also makes MTC / merkle tree certificate naming more
consistent within the change, and does not abbreviate as "MerkleCert",
etc.
@ezekiel

ezekiel commented Oct 20, 2026 •

Copy link
Copy Markdown
Member Author

With the merge of #9109 , this PR has an incoming merge of main.

@jsha jsha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for working on this, excited to see it go out.

This PR has the WFE talking to the MTCB directly. I think that's okay to land as-is, but as a followup I think it would be nicer for the call to go WFE->RA->MTCB. That's an extra hop in the gRPC call graph but it is consistent with the choice we made for GetOrder, where the RA holds the knowledge about MTCB. As a practical matter, it means we only need to configure one service with access to MTCB, not two.

Comment thread wfe2/wfe.go
}),
))
)
// Detect the lack of at least one PAIR of curly braces as a signal that

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PAIR is emphasized here; why?

Comment thread wfe2/wfe.go
}
}

// MerkleTreeCertificate is used by client to request a copy of their merkle tree certificate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// MerkleTreeCertificate is used by client to request a copy of their merkle tree certificate.
// MerkleTreeCertificate is used by clients to request a copy of their merkle tree certificate.

Comment thread wfe2/wfe.go

// wfe instance not configured with an MTCB Client
if wfe.mtcb == nil {
wfe.sendError(response, logEvent, probs.Malformed("Not Supported"), nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO this is a little better as a 404 for now.

Comment thread wfe2/wfe.go

reqAcctID, err := parsePositiveInt(request.PathValue("acctID"))
if err != nil {
wfe.sendError(response, logEvent, probs.Malformed("Request URL does not include a valid accountID"), nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also 404. See #9042 and #9034.

Comment thread wfe2/wfe.go
Comment on lines +1928 to +1929
// If the order is not yet valid, then we did not find what the requester
// wants.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO this and the below cases fall under CONTRIBUTING.md's "No need to gratuitously comment clear code." They're self-explanatory error cases, no comment needed.

Comment thread wfe2/wfe.go
wfe.HandleFunc(m, renewalInfoPath, wfe.RenewalInfo, "GET", "POST")

// POST-as-GETable ACME endpoints
wfe.HandleFunc(m, mtcPath+"{acctID}/{orderID}/{logID}/{serial}", wfe.MerkleTreeCertificate, "POST")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
wfe.HandleFunc(m, mtcPath+"{acctID}/{orderID}/{logID}/{serial}", wfe.MerkleTreeCertificate, "POST")
wfe.HandleFunc(m, mtcPath+"{acctID}/{orderID}/{mtcLogID}/{serial}", wfe.MerkleTreeCertificate, "POST")

We use the fully-typed-out "mtcLogID" in other places in the codebase and I think it helps (a) to be consistent and (b) to be explicit, for instance this isn't the ID of some other type of log.

Comment thread wfe2/wfe.go
return
}

if standalone.CertDER == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

core.IsAnyNilOrZero for consistency, even though there's only one parameter.

Comment thread wfe2/wfe.go
Comment on lines +1975 to +1976
// And PEM-encode the retrieved certificate for return to the client.
standalonePEM := pem.EncodeToMemory(&pem.Block{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This comment is also unneeded: pem.EncodeToMemory tells us that this is PEM-encoding something.

Comment thread wfe2/wfe.go
// certificate will likely exceed this threshold. Since we know the length
// we can reliably set it ourselves and not worry.
response.Header().Set("Content-Length", strconv.Itoa(len(standalonePEM)))
response.Header().Set("Content-Type", "application/pem-certificate-chain")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://ietf-plants-wg.github.io/merkle-tree-certs/draft-ietf-plants-merkle-tree-certs.html#name-using-acme-with-merkle-tree

When downloading either certificate (Section 7.4.2 of [RFC8555]), ACME clients supporting Merkle Tree certificates SHOULD send "application/pem-certificate-chain-with-properties" in their Accept header (Section 12.5.1 of [RFC9110]). ACME servers issuing Merkle Tree certificates SHOULD then respond with that content type to include a CertificatePropertyList.

We don't have the code for CertificatePropertyList yet but might as well start using the Content-Type.

Comment thread wfe2/wfe.go
Comment on lines +1981 to +1985
// NOTE(@ezekiel): Similar to Certificate(), we must explicitly set the
// Content-Length header here. The Go HTTP library will only add this header
// if the body is below a certain size and a PEM encoded merkle tree
// certificate will likely exceed this threshold. Since we know the length
// we can reliably set it ourselves and not worry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see this comment is a copy of an existing comment with NOTE(@...). IMO, let's remove the handle (we don't generally need to record who wrote a comment), and let's also remove the NOTE; all comments are notes, and NOTE suggests this one is particularly important.

Further, with the benefit of hindsight I think we could shorten or remove both comments. I might say net/http only adds Content-Length automatically "if the total size of all written data is under a few KB".

Really the comment I wish we had is one that explains why it's important that we set Content-Length here. I think we don't actually need it, and #3366 doesn't explain why it's needed. The purpose of Content-Length is so an HTTP client knows when a response is done and reuse the connection for the next request. However, there's an alternative that Go will use automatically in HTTP/1.1: Transfer-Encoding: chunked. That would work just fine here.

Anyhow, it's about six of one half dozen of the other: my inclination is to keep setting Content-Length explicitly in both sites, and shorten both comments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some additional context in #1320: we set Content-Length because otherwise our responses to HEAD will have the wrong Content-Length, per HTTP spec.

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.

wfe: New endpoint for serving standalone MTCs

2 participants