August 21, 2024·4 min read·engineeringswifthttpprivacy

SwiftNIO Oblivious HTTP separates request contents from client identity

Binary HTTP and encrypted encapsulation provide separate building blocks for OHTTP. The privacy model also depends on the relay and on what the application puts inside its requests.

Encrypting a connection does not prevent its destination from seeing the client's network address. Reusing that connection can also link several requests to the same source. Oblivious HTTP addresses this by routing encrypted messages through a relay, with different participants able to see different parts of the exchange.

The August 21 Swift announcement introduces provisional support through SwiftNIO Oblivious HTTP. The package includes both a binary HTTP representation and the encryption scheme used for encapsulation. Those pieces can be used together for OHTTP or separately when another protocol needs their capabilities.

I find the separation useful because it identifies what each component contributes to privacy. A serializer cannot conceal an IP address, and encrypting a payload cannot establish the relay's operating policy. Understanding the full exchange requires looking beyond the library call that produces ciphertext.

Relay and gateway have different knowledge

RFC 9458, published in January, describes the protocol. A client constructs a request for a target resource and serializes it as binary HTTP. It encrypts that message using the gateway's public-key configuration and sends the encapsulated request to a relay. The relay forwards the protected content to the gateway.

The relay sees the client's connection but cannot read the inner HTTP message. The gateway removes the protection and can handle the request, but its incoming connection comes from the relay. This separates the plaintext request from the client's transport identity.

The response follows the reverse route under encrypted encapsulation. Gateway protection remains intact as the relay forwards it. The client removes the protection to recover the HTTP response. Relaying does not require exposing either inner message to the intermediary.

The RFC treats the gateway and target as logically under the gateway's control, even when the target is a separate HTTP resource. This is important when drawing trust boundaries. The gateway obtains the request plaintext and can determine the response delivered to the client. The protocol does not provide a separate end-to-end confidentiality claim against that gateway for the target interaction.

Application identifiers can undo transport privacy

The RFC's applicability section limits the useful scope of OHTTP. Removing transport linkage helps applications whose requests do not carry their own identifying state. Cookies or authentication can associate requests regardless of whether the originating network address is hidden.

An application therefore needs an appropriate request format, not merely a relay URL. Sending a persistent user identifier inside the protected message hides it from the relay while revealing it to the gateway. That may preserve content confidentiality in transit, but it does not deliver unlinkability from the service receiving that identifier.

Both the relay and gateway must explicitly support the protocol. OHTTP is consequently not a universal switch that makes arbitrary existing websites oblivious to their users. The RFC gives examples such as DNS queries and some telemetry submissions, where separating a request from its source can have practical privacy value.

The additional work has costs. The RFC describes extra HTTP requests, message expansion and cryptographic computation. Connection reuse can amortize some overhead, but the protocol remains more expensive than a direct request. The Swift announcement supplies no benchmark that would justify assigning a universal latency cost to this implementation.

Binary HTTP represents a message independently

The package implements RFC 9292 for binary HTTP. This represents an abstract HTTP message without depending on the framing of HTTP/1.1 or HTTP/2. That matters because the message needs to become a payload carried by another HTTP request.

The announcement demonstrates BHTTPSerializer writing request components into a SwiftNIO ByteBuffer. A corresponding BHTTPParser consumes the buffer and emits message components. Headers and bodies retain their meaning as an HTTP message while the serialized representation becomes suitable for encapsulation.

This component has a use independent of encryption. An application that needs binary HTTP encoding can use it without adopting OHTTP's relay arrangement. Conversely, another application can use the encapsulation APIs with a different message encoding. The package's organization makes those responsibilities explicit.

The initial integration uses SwiftNIO types. The roadmap includes a version based on swift-http-types, potentially allowing users to avoid depending on SwiftNIO. As of the announcement, that is planned work. It should not be treated as an already available way to replace every NIO type in the published examples.

Encapsulation is a separate library

The ObliviousX library supplies the encapsulation operations built on Hybrid Public Key Encryption. Its single-shot functions implement the OHTTP scheme, while streaming functions follow the draft chunked OHTTP specification. The latter remains draft work at this date.

The example passes a key identifier, a server public key and a cryptographic suite into request encapsulation. Decapsulation parses the request header and uses the server's private key to recover the content. Those inputs explain why the client needs a gateway key configuration before sending a protected message.

The RFC requires authentication and integrity protection for obtaining that configuration, while leaving its acquisition mechanism outside the protocol's definition. Encrypting to an untrusted replacement key would not supply the intended relationship to the gateway. Key distribution is therefore part of the application design that surrounds the encapsulation API.

I prefer a library that exposes these distinct operations to an API that implies privacy follows from one call. The initial release offers useful protocol components while identifying the integration work still in progress. That makes its current responsibilities easier to assess.

The release leaves room for integration changes

The announcement calls the package an early release intended to gather feedback. Better ChannelHandler integration with SwiftNIO pipelines remains on the roadmap. The authors also plan final chunked OHTTP support once the IETF draft is finalized, alongside broader API refinements.

That status affects the audience. Protocol implementers can use the binary representation and encapsulation to experiment with clients and services. They should expect the surrounding integration interfaces to evolve. A server implementing the gateway still needs the complete exchange and an application whose request contents preserve the privacy property it wants, including the absence of identifiers that independently link those requests.