-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
Summary
Ladybird has IPC infrastructure for client certificate authentication (certificate_requested / set_certificate in RequestServer.ipc and RequestClient.ipc), with callbacks wired through LibRequests::Request and ResourceLoader. However, the server-side implementation is non-functional: ConnectionFromClient::set_certificate() unconditionally calls TODO(), and the browser-side callback in ResourceLoader.cpp returns an empty CertificateAndKey.
It seems, that this infrastructure was originally built for the internal LibTLS implementation and has not been adapted for the cURL backend. I would like to make it functional and am opening this issue to discuss the design before writing any code.
Problem: timing mismatch between LibTLS and cURL
The existing IPC flow was designed for LibTLS, which could pause a TLS handshake mid-flight:
- LibTLS encounters the server's
CertificateRequestmessage and pauses. - RequestServer sends
certificate_requested(request_id)to the browser. - The browser responds with
set_certificate(request_id, cert, key). - LibTLS resumes the handshake with the certificate.
cURL does not support pausing the TLS handshake. CURLOPT_SSLCERT and CURLOPT_SSLKEY must be configured on the easy handle before it is added to the multi handle. In the current code, Request::handle_fetch_state() sets all cURL options and calls curl_multi_add_handle() synchronously. By the time a set_certificate IPC response could arrive from the browser, the handshake is already underway or complete.
Proposed approach
Add a RequestClientCertificate state to the Request state machine, placed between RetrieveCookie and Fetch. This follows the same async IPC round-trip pattern that cookie retrieval already uses:
Init -> DNSLookup -> RetrieveCookie -> RequestClientCertificate -> Fetch
The flow would be:
handle_request_client_certificate_state()sendscertificate_requested(request_id)to the browser via the primary connection.- The browser invokes its
on_certificate_requestedcallback, checks for a matching certificate, and responds withset_certificate(). ConnectionFromClient::set_certificate()stores the certificate and key on theRequestobject and transitions toFetch.handle_fetch_state()appliesCURLOPT_SSLCERT_BLOBandCURLOPT_SSLKEY_BLOB(available since cURL 7.71.0; Ladybird ships 8.18.0) if a certificate was provided.
This reuses the existing IPC messages without protocol changes.
Performance
This adds one IPC round-trip per network request. Possible mitigations:
- Only enter the state for HTTPS URLs.
- If the browser has no certificates configured at all, respond immediately without blocking.
- Alternatively, a pre-registration model (browser registers certificates per host via a separate IPC) could avoid per-request queries entirely.
Scope
I am thinking of this in two parts:
-
cURL wiring: the new state, the
set_certificate()implementation,CURLOPT_SSLCERT_BLOB/CURLOPT_SSLKEY_BLOBinhandle_fetch_state(). The browser side would return an empty certificate (no certificate store yet). This makes the existing infrastructure functional end-to-end. -
Browser-side certificate management: replace the empty lambda in
ResourceLoader.cppwith an actual certificate lookup. Certificate import, selection UI, and persistent storage would be separate work.
Questions for maintainers
- Is the
RequestClientCertificatestate approach acceptable, or is a different design preferred (for example, pre-registration orCURLOPT_SSL_CTX_FUNCTIONwith a backend-specific callback)? - Are there concerns about the additional IPC round-trip per HTTPS request?
- Should the WebSocket path (
WebSocketImplCurl) be included in the first change or handled separately?
Relevant code
| Location | Current state |
|---|---|
Services/RequestServer/ConnectionFromClient.cpp:325 |
set_certificate() calls TODO() |
Services/RequestServer/ConnectionFromClient.cpp:425 |
WebSocket set_certificate is a no-op |
Services/RequestServer/Request.cpp:499 |
handle_fetch_state() — cURL TLS setup, no SSLCERT/SSLKEY |
Libraries/LibWeb/Loader/ResourceLoader.cpp:480 |
on_certificate_requested returns empty |
Services/RequestServer/RequestServer.ipc |
set_certificate(request_id, certificate, key) => (bool success) |
Services/RequestServer/RequestClient.ipc |
certificate_requested(request_id) =| |