ohttp

package module
v0.0.80 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 11, 2025 License: Apache-2.0 Imports: 27 Imported by: 0

README

OHTTP - Oblivious HTTP Client and Gateway written in Go

This package implements OHTTP in Go. It provides both a Transport (Client) and a Gateway.

ohttp is a Go package that provides an Oblivious HTTP (OHTTP) client and Gateway as specified in RFC 9458 and draft-ietf-ohai-chunked-ohttp-03.

Features

  • Easy to use:
    • The Transport implements http.RoundTripper and can be plugged into a regular *http.Client. No need to adapt the rest of your app to send requests over OHTTP.
    • The Gateway can be used as a Middleware to wrap an existing http.Handler. The handler can speak regular HTTP while the middleware translates to OHTTP.
  • Supports both unchunked (known length) and unchunked (unknown length) requests/responses.
  • Defaults to bhttp encoding but accepts custom encodings.
  • Overwrite every aspect of the roundtrip via custom encodings.

On OHTTP relays

An OHTTP Relay sits between an OHTTP Client and a Gateway. The relay keeps the identity of clients from the Gateway. For OHTTP to function as a system, it is fundamental that the relay is ran by a third party.

This package does not include a relay and assumes you have a third party relay in production environments. Cloudflare, Fastly and oblivious.network all offer OHTTP Relays as a service.

If you need a fake relay for local testing we advise you to use a general purpose proxy like httputil.ReverseProxy. This won't provide any anonymity, but your requests will at least follow a similar path.

Real world OHTTP Relays can be more constrained than a general purpose proxy, so be sure to verify the constraints on the relay you're planning to use.

Gateway as a dedicated server

The Gateway can be run in two ways:

  • Combined Gateway and target resource in a single server.
  • Dedicated Gateway and target resource servers.

The Gateway can be combined with the httputil.ReverseProxy to redirect requests to the target resource server. See the example below.

// Create the gateway and wrap the handler in its middleware.
gateway, err := ohttp.NewGateway(keyPair)
if err != nil {
  log.Fatalf("failed to create gateway: %v", err)
}

// Create a proxy that redirects requests to an external URL.
targetURL, err := url.Parse("https://example.com")
if err != nil {
  log.Fatalf("failed to parse url: %v", err)
}

proxy := httputil.NewSingleHostReverseProxy(targetURL)

// Wrap the proxy in the ohttp middleware.
h := ohttp.Middleware(gateway, http.HandlerFunc(proxy))

// h can now be used as the handler in a http.Server.

The above example only redirects to a single host (example.com). If you want the gateway to redirect to multiple hosts, and want the client to provide the hostname, look into the ohttp.NewHostnameAllowlist and ohttp.WithRequestValidator functions.

Example: Client and service over OHTTP

See examples/README.md for a local example that runs both the Client, the Gateway and a fake relay.

Comparison to alternative packages

Below we compare the features of this package to two other Go OHTTP packages.

chris-wood/ohttp-go

A great reference implementation by one of the authors of the OHTTP RFC. It's a library that provides both a client and a gateway.

Feature openpcc/ohttp chris-wood/ohttp-go Notes
Key rotation Keys are sourced on demand via an injectable function Gateway is initialized with a list of static keys
Key distribution - - RFC 9458 explicitly leaves this unspecified
Hardware support Allows for injection of custom HPKE components Hard dependency on cloudflare/circl/hpke
Unchunked messages X X
Chunked messages per draft RFC X -
Custom message encodings X X
Integration http.Transport and http.Handler compatible middleware. Requires glue code to integrate into an app or client.
cloudflare/privacy-gateway-server-go

A command that wraps the chris-wood/ohttp-go to provide a gateway you can run as a dedicated server. Since this is a command, we'll compare it to the recommended way to run the openpcc/ohttp Gateway as a dedicated server.

Feature openpcc/ohttp cloudflare/privacy-gateway-server-go Notes
Key rotation Keys are sourced on demand via an injectable function Gateway is initialized with a list of static keys
Key distribution - Key Configs can be retrieved via endpoint. RFC 9458 explicitly leaves this unspecified.
Hardware support Allows for injection of custom HPKE components Hard dependency on cloudflare/circl/hpke
Unchunked messages X X
Chunked messages per draft RFC X -
Custom message encodings X X
Integration Need to write your own command. Provides a command.

Found a security issue?

Reach out to security@confidentsecurity.com.

Development

Run tests with go test ./...

Run the examples with go run ./examples/client etc.

License

This project is licensed under the Apache License 2.0.

Contributing

For guidelines on contributing to this project, please see CONTRIBUTING.md.

Documentation

Overview

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Constants

View Source
const (
	// RequestMediaType is the media type for an OHTTP Request per RFC 9458.
	RequestMediaType = "message/ohttp-req"
	// ResponseMediaType is the media type for an OHTTP Response per RFC 9458.
	ResponseMediaType = "message/ohttp-res"
	// ChunkedRequestMediaType is the media type for a Chunked OHTTP Request per
	// the Chunked Oblivious HTTP Messages Draft RFC.
	ChunkedRequestMediaType = "message/ohttp-chunked-req"
	// ChunkedResponseMediaType is the media type for a Chunked OHTTP Response per
	// the Chunked Oblivious HTTP Messages Draft RFC.
	ChunkedResponseMediaType = "message/ohttp-chunked-res"
)
View Source
const DefaultAllowedHostname = "ohttp.invalid"

DefaultAllowedHostname is the hostname that will be added to an otherwise empty [HostnameAllowList].

This hostname is guaranteed to be non-routable due to the .invalid TLD. This way you need to pick an explicit Target Resource hostname when using the Gateway together with a proxy.

Variables

View Source
var (
	ErrorKeyNotYetActive = errors.New("key not yet active")
	ErrorKeyExpired      = errors.New("key expired")
)
View Source
var ErrChunkedResponseEncoderMismatch = errors.New("ohttp.Middleware used with an incompatible response encoder")
View Source
var ErrHostnameNotAllowed = errors.New("hostname not allowed")

ErrHostnameNotAllowed indicates a hostname is not allowed.

Functions

func Middleware

func Middleware(g *Gateway, next http.Handler) http.Handler

Middleware uses the gateway as a handler middleware.

The wrapped handler represents a Target Resource. It receives the decapsulated request and writes a decapsulated response. It can either deal with these directly, or proxy them elsewhere.

The Middleware follows the net/http Server logic when deciding whether a response should be chunked or not. Upon detecting a chunked response, it will encapsulate and begin streaming chunks.

Important when using custom response encoders: This middleware is only compatible with response encoders that map responses with chunked Transfer-Encoding, to chunked OHTTP messages. If there is disagreement, this handler will begin returning ErrChunkedResponseEncoderMismatch. It will also call the error handler on the gateway with this error.

The default BHTTP encoding is fully compatible with this middleware.

Types

type ErrorCode

type ErrorCode int

ErrorCode is the error code of a gateway error.

const (
	ErrorCodeRequestIO  ErrorCode = 1
	ErrorCodeResponseIO ErrorCode = 2

	ErrorCodeInvalidRequestHeader ErrorCode = 100
	ErrorCodeKeyNotFound          ErrorCode = 101
	ErrorCodeInvalidKey           ErrorCode = 102
	ErrorCodeInactiveKey          ErrorCode = 103

	ErrorCodeInvalidRequestContentType ErrorCode = 200
	ErrorCodeRequestDecoding           ErrorCode = 201
	ErrorCodeInvalidRequest            ErrorCode = 202

	ErrorCodeResponseEncoding   ErrorCode = 300
	ErrorCodeResponseEncryption ErrorCode = 301
)

Error codes returned by the Gateway.

type ErrorHandler

type ErrorHandler interface {
	// HandleError handles the provided error.
	//
	// headersWritten indicates whether the error handler can still write headers or not.
	//
	// Because the Gateway records the response of the inner handler, there is no guarantee
	// that the inner handler writing a header will also make the Gateway immediately write a header.
	HandleError(w http.ResponseWriter, headersWritten bool, err error)
}

ErrorHandler is used to write or log errors from the Gateway.

type Gateway

type Gateway struct {
	// contains filtered or unexported fields
}

Gateway is an OHTTP Gateway. It decapsulates incoming requests and encapsulates outgoing responses.

The Gateway is designed to be used as a http Middleware but can also be used as a standalone component by calling [DecapRequest] directly.

By default the Gateway only accepts decapsulated requests with a DefaultAllowedHostname hostname. This is to prevent the Gateway being used as a relay to arbitrary Target Resources.

This behaviour can be overwritten by providing a custom HostnameAllowlist or RequestValidator implementation.

func NewGateway

func NewGateway(finder SecretKeyFinder, opts ...GatewayOption) (*Gateway, error)

NewGateway creates a new OHTTP Gateway.

func (*Gateway) Decapsulate

func (g *Gateway) Decapsulate(r *http.Request) (*http.Request, *ResponseEncapsulator, error)

Decapsulate decapsulates an OHTTP request. Decapsulate returns the decapsulated request as well as a response encapsulator that can be used to encapsulate the corresponding response.

type GatewayError

type GatewayError struct {
	Code ErrorCode
	Err  error
}

GatewayError is an error that occurred during the handling of an encapsulated OHTTP request. Gateway error handlers can check for this error and derive more detailed information.

func (GatewayError) Error

func (e GatewayError) Error() string

func (GatewayError) IsGeneralError

func (e GatewayError) IsGeneralError() bool

IsGeneralError indicates whether the error is a general error.

func (GatewayError) IsKeyError

func (e GatewayError) IsKeyError() bool

IsKeyError indicates whether the error has a key-related error code.

func (GatewayError) IsRequestError

func (e GatewayError) IsRequestError() bool

IsRequestError indicates whether the error has a request-related error code.

func (GatewayError) IsResponseError

func (e GatewayError) IsResponseError() bool

IsResponseError indicates whether the error has a response-related error code.

func (GatewayError) Unwrap

func (e GatewayError) Unwrap() error

type GatewayOption

type GatewayOption func(cfg *gatewayCfg) error

GatewayOption configures a gateway.

func WithErrorHandler

func WithErrorHandler(errHandler ErrorHandler) GatewayOption

WithErrorHandler provides a custom error handler for a gateway.

func WithOTELTracer

func WithOTELTracer(tracer trace.Tracer) GatewayOption

WithOTELTracer provides a custom request validator for a gateway. Set the validator to nil to disable request validation. Be careful as clients can send requests with any hostname.

func WithRequestDecoder

func WithRequestDecoder(dec encoding.RequestDecoder) GatewayOption

WithRequestDecoder provides a custom request decoder for a gateway.

func WithRequestValidator

func WithRequestValidator(validator RequestValidator) GatewayOption

WithRequestValidator provides a custom request validator for a gateway. Set the validator to nil to disable request validation. Be careful as clients can send requests with any hostname.

type HostnameAllowlist

type HostnameAllowlist struct {
	// contains filtered or unexported fields
}

HostnameAllowlist is a RequestValidator that checks the hostname on a decapsulated request against the hostnames in the list.

func NewHostnameAllowlist

func NewHostnameAllowlist(hostnames ...string) HostnameAllowlist

NewHostnameAllowlist creates new HostnameAllowList with the given hostnames.

If no hostnames are provided, this function will automatically add DefaultAllowedHostname.

func (HostnameAllowlist) ValidRequest

func (l HostnameAllowlist) ValidRequest(r *http.Request) error

ValidRequest checks if the given request has a hostname that is allowed. If the hostname is on the list, the request will be valid, if it's not on the list the request will be invalid and ErrHostnameNotAllowed will be returned.

type JSONProblemErrorHandler

type JSONProblemErrorHandler struct {
	LogFunc func(err error)
}

JSONProblemErrorHandler writes errors as application/json+problem responses and optionally logs error with the provided LogFunc.

func (*JSONProblemErrorHandler) HandleError

func (h *JSONProblemErrorHandler) HandleError(w http.ResponseWriter, headersWritten bool, err error)

HandleError logs the error and writes a JSON problem response if no headers have been written.

type KeyConfig

type KeyConfig struct {
	KeyID               byte
	KemID               hpke.KEM
	PublicKey           kem.PublicKey
	SymmetricAlgorithms []SymmetricAlgorithm
}

KeyConfig is a key configuration as specified in RFC 9458. Key configs are serialized according to the format specified in section 3.1.

It provides the information required by a client to encapsulate requests for a specific gateway.

The RFC does not specify how the client acquires a key configuration, just its format.

func (KeyConfig) MarshalBinary

func (k KeyConfig) MarshalBinary() ([]byte, error)

MarshalBinary marshals the KeyConfig to the binary representations specified in the RFC.

func (*KeyConfig) UnmarshalBinary

func (k *KeyConfig) UnmarshalBinary(b []byte) error

UnmarshalBinary unmarshals a key config from the binary representation specified in the RFC.

type KeyConfigs

type KeyConfigs []KeyConfig

KeyConfigs is a list key of configurations as specified in RFC 9458.

These key configurations are serialized to the application/ohttp-keys format specified in section 3.2 of the RFC.

func (KeyConfigs) MarshalBinary

func (k KeyConfigs) MarshalBinary() ([]byte, error)

MarshalBinary marshals the key configurations to binary application/ohttp-keys format.

func (*KeyConfigs) UnmarshalBinary

func (k *KeyConfigs) UnmarshalBinary(p []byte) error

UnmarshalBinary unmarshals the key configurations from the application/ohttp-keys binary format.

type KeyPair

type KeyPair struct {
	SecretKey kem.PrivateKey
	KeyConfig KeyConfig
}

Keypair combines a private key with its KeyConfig. Can be provided as a SecretKeyFinder to a Gateway. Suites are constructed as circl/hpke suites.

func (KeyPair) FindSecretKey

func (k KeyPair) FindSecretKey(_ context.Context, header twoway.RequestHeader) (SecretKeyInfo, error)

FindSecretKey checks if the keypair matches the given header and returns appropriate info if it does.

type MessageInfo

type MessageInfo struct {
	// Len indicates the length of an unchunked OHTTP message.
	Length int
	// HeaderLen returns the length of the fixed header portion of this message.
	HeaderLen int
	// MaxCiphertextChunkLen returns the maximum length of a ciphertext chunk. Only applies
	// to chunked OHTTP messages.
	MaxCiphertextChunkLen int
}

MessageInfo returns additional information about an OHTTP message.

type RequestValidator

type RequestValidator interface {
	ValidRequest(r *http.Request) error
}

RequestValidator validates decapsulated requests before the Gateway forwards them the wrapped handler.

The validator operates on the same request as the wrapped handler. Keep that in mind if the request body is read as part of validation.

When using creating a custom request validator for a Gateway that redirects decapsulated requests be sure to validate the hostname to prevent unexpected redirects.

type ResponseDecapsulator

type ResponseDecapsulator struct {
	// contains filtered or unexported fields
}

ResponseDecapsulator decapsulates an OHTTP response.

func (*ResponseDecapsulator) Decapsulate

func (d *ResponseDecapsulator) Decapsulate(ctx context.Context, encapResp *http.Response) (*http.Response, error)

Decapsulate decapsulates the provided OHTTP response and returns the original response.

type ResponseEncapsulator

type ResponseEncapsulator struct {
	// contains filtered or unexported fields
}

ResponseEncapsulator encapsulates a response.

func (*ResponseEncapsulator) Encapsulate

func (e *ResponseEncapsulator) Encapsulate(resp *http.Response) (*http.Response, error)

Encapsulate encapsulates the response as an OHTTP response.

func (*ResponseEncapsulator) EncapsulateWithMessageInfo

func (e *ResponseEncapsulator) EncapsulateWithMessageInfo(resp *http.Response) (*http.Response, MessageInfo, error)

EncapsulateWithMessageInfo does the same [Encapsulate] but returns additional meta data about the body of the encapsulated response.

type ResponseStatusError

type ResponseStatusError struct {
	StatusCode int
	Err        error
}

ResponseStatusError is an error code that was discovered during the transport (decap) of an OHTTP response. This helper type is used for passing error state around should an encapsulated response return a non-200 status code.

func (ResponseStatusError) Error

func (e ResponseStatusError) Error() string

func (ResponseStatusError) IsClientError

func (e ResponseStatusError) IsClientError() bool

func (ResponseStatusError) Unwrap

func (e ResponseStatusError) Unwrap() error

type SecretKeyFinder

type SecretKeyFinder interface {
	FindSecretKey(ctx context.Context, header twoway.RequestHeader) (SecretKeyInfo, error)
}

SecretKeyFinder finds secret key info for a given request header.

This allows the Gateway to find appropriate keys on-demand before a request is decapsulated.

type SecretKeyInfo

type SecretKeyInfo struct {
	KeyID byte
	Suite twoway.HPKESuite
	Key   kem.PrivateKey
}

SecretKeyInfo holds the identity and suite information for a private key.

type SymmetricAlgorithm

type SymmetricAlgorithm struct {
	KDFID  hpke.KDF
	AEADID hpke.AEAD
}

SymmetricAlgorithm is a pair of KDF and AEAD identifiers that a Gateway supports.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport is a http.RoundTripper that transports requests and responses over OHTTP.

func NewTransport

func NewTransport(keyConfig KeyConfig, relayURL string, opts ...TransportOption) (*Transport, error)

NewTransport creates a new transport for the given keyConfig and options.

func NewTransportWithSender

func NewTransportWithSender(sender *twoway.RequestSender, relayURL string, opts ...TransportOption) (*Transport, error)

NewTransportWithSender creates a new transport with the provided sender. This method allows for the creation of Transports with custom HPKE suites.

func (*Transport) Encapsulate

func (c *Transport) Encapsulate(req *http.Request) (*http.Request, *ResponseDecapsulator, error)

Encapsulate encapsulates the request as an OHTTP request. Encapsulate returns the encapsulated request and a response decapsulator that can be used to decapsulate the corresponding response.

func (*Transport) EncapsulateWithMessageInfo

func (c *Transport) EncapsulateWithMessageInfo(req *http.Request) (*http.Request, *ResponseDecapsulator, MessageInfo, error)

EncapsulateWithMessageInfo does the same as Encapsulate, but returns additional meta data about the body of the encapsulated request.

func (*Transport) RoundTrip

func (c *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error)

RoundTrip implements http.RoundTripper.

type TransportOption

type TransportOption func(cfg *transportCfg) error

TransportOption allows for the configuration of Transports.

func WithHTTPClient

func WithHTTPClient(c *http.Client) TransportOption

WithHTTPClient provides a custom http client to the Transport.

func WithOTELTransportTracer

func WithOTELTransportTracer(tracer trace.Tracer) TransportOption

WithOTELTransportTracer provides a custom otel tracer for the transport to use for tracing

func WithRequestEncoder

func WithRequestEncoder(enc encoding.RequestEncoder) TransportOption

WithRequestEncoder provides a custom request encoder to the Transport.

Directories

Path Synopsis
bhttp
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
examples
client command
fakerelay command
gateway command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL