Skip to content

Commit

Permalink
🦕 api, httpproxy, service: add server ECH support
Browse files Browse the repository at this point in the history
  • Loading branch information
database64128 committed Feb 14, 2025
1 parent 802e816 commit 8392d25
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 3 deletions.
32 changes: 32 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ type ListenerConfig struct {
// used as the root CA set for verifying client certificates.
ClientCAs string `json:"clientCAs"`

// EncryptedClientHelloKeys are the ECH keys to use when a client attempts ECH.
EncryptedClientHelloKeys []EncryptedClientHelloKey `json:"encryptedClientHelloKeys"`

// EnableTLS controls whether to enable TLS.
EnableTLS bool `json:"enableTLS"`

Expand Down Expand Up @@ -127,6 +130,26 @@ type ListenerConfig struct {
Multipath bool `json:"multipath"`
}

// EncryptedClientHelloKey holds a private key that is associated
// with a specific ECH config known to a client.
type EncryptedClientHelloKey struct {
// Config should be a marshalled ECHConfig associated with PrivateKey. This
// must match the config provided to clients byte-for-byte. The config
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
// AES-128-GCM (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002).
Config []byte `json:"config"`

// PrivateKey should be a marshalled private key. Currently, we expect
// this to be the output of [ecdh.PrivateKey.Bytes].
PrivateKey []byte `json:"privateKey"`

// SendAsRetry indicates if Config should be sent as part of the list of
// retry configs when ECH is requested by the client but rejected by the
// server.
SendAsRetry bool `json:"sendAsRetry"`
}

// NewServer returns a new API server from the config.
func (c *Config) NewServer(logger *zap.Logger, listenConfigCache conn.ListenConfigCache, tlsCertStore *tlscerts.Store) (*Server, *ssm.ServerManager, error) {
if len(c.Listeners) == 0 {
Expand Down Expand Up @@ -172,6 +195,15 @@ func (c *Config) NewServer(logger *zap.Logger, listenConfigCache conn.ListenConf
tlsConfig.ClientCAs = pool
}

tlsConfig.EncryptedClientHelloKeys = make([]tls.EncryptedClientHelloKey, len(lnc.EncryptedClientHelloKeys))
for j, key := range lnc.EncryptedClientHelloKeys {
tlsConfig.EncryptedClientHelloKeys[j] = tls.EncryptedClientHelloKey{
Config: key.Config,
PrivateKey: key.PrivateKey,
SendAsRetry: key.SendAsRetry,
}
}

if lnc.RequireAndVerifyClientCert {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
Expand Down
14 changes: 14 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@
],
"certList": "example.com",
"clientCAs": "my-root-ca",
"encryptedClientHelloKeys": [
{
"config": "",
"privateKey": "",
"sendAsRetry": false
}
],
"enableBasicAuth": true,
"enableTLS": false,
"requireAndVerifyClientCert": false
Expand Down Expand Up @@ -542,6 +549,13 @@
"userTimeoutMsecs": 0,
"certList": "example.com",
"clientCAs": "my-root-ca",
"encryptedClientHelloKeys": [
{
"config": "",
"privateKey": "",
"sendAsRetry": false
}
],
"enableTLS": false,
"requireAndVerifyClientCert": false
}
Expand Down
39 changes: 36 additions & 3 deletions httpproxy/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ type ServerConfig struct {
// See [tls.Config.ClientCAs].
ClientCAs *x509.CertPool

// EncryptedClientHelloKeys are the ECH keys to use when a client attempts ECH.
// See [tls.Config.EncryptedClientHelloKeys].
EncryptedClientHelloKeys []EncryptedClientHelloKey

// EnableBasicAuth controls whether to enable HTTP Basic Authentication.
EnableBasicAuth bool

Expand All @@ -197,6 +201,26 @@ type ServerUserCredentials struct {
Password string `json:"password"`
}

// EncryptedClientHelloKey holds a private key that is associated
// with a specific ECH config known to a client.
type EncryptedClientHelloKey struct {
// Config should be a marshalled ECHConfig associated with PrivateKey. This
// must match the config provided to clients byte-for-byte. The config
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
// AES-128-GCM (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002).
Config []byte `json:"config"`

// PrivateKey should be a marshalled private key. Currently, we expect
// this to be the output of [ecdh.PrivateKey.Bytes].
PrivateKey []byte `json:"privateKey"`

// SendAsRetry indicates if Config should be sent as part of the list of
// retry configs when ECH is requested by the client but rejected by the
// server.
SendAsRetry bool `json:"sendAsRetry"`
}

// ProxyServer is an HTTP proxy server.
//
// ProxyServer implements [zerocopy.TCPServer].
Expand Down Expand Up @@ -230,12 +254,21 @@ func (c *ServerConfig) NewProxyServer() (zerocopy.TCPServer, error) {
}

if c.EnableTLS {
echKeys := make([]tls.EncryptedClientHelloKey, len(c.EncryptedClientHelloKeys))
for i, key := range c.EncryptedClientHelloKeys {
echKeys[i] = tls.EncryptedClientHelloKey{
Config: key.Config,
PrivateKey: key.PrivateKey,
SendAsRetry: key.SendAsRetry,
}
}
tlsServer := TLSProxyServer{
plainServer: server,
tlsConfig: &tls.Config{
Certificates: c.Certificates,
GetCertificate: c.GetCertificate,
ClientCAs: c.ClientCAs,
Certificates: c.Certificates,
GetCertificate: c.GetCertificate,
ClientCAs: c.ClientCAs,
EncryptedClientHelloKeys: echKeys,
},
}
if c.RequireAndVerifyClientCert {
Expand Down
4 changes: 4 additions & 0 deletions service/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ func (sc *ServerConfig) TCPRelay() (*TCPRelay, error) {
hpsc := httpproxy.ServerConfig{
Logger: sc.logger,
Users: sc.HTTP.Users,
EncryptedClientHelloKeys: sc.HTTP.EncryptedClientHelloKeys,
EnableBasicAuth: sc.HTTP.EnableBasicAuth,
EnableTLS: sc.HTTP.EnableTLS,
RequireAndVerifyClientCert: sc.HTTP.RequireAndVerifyClientCert,
Expand Down Expand Up @@ -646,6 +647,9 @@ type HTTPProxyServerConfig struct {
// used as the root CA set for verifying client certificates.
ClientCAs string `json:"clientCAs"`

// EncryptedClientHelloKeys are the ECH keys to use when a client attempts ECH.
EncryptedClientHelloKeys []httpproxy.EncryptedClientHelloKey `json:"encryptedClientHelloKeys"`

// EnableBasicAuth controls whether to enable HTTP Basic Authentication.
EnableBasicAuth bool `json:"enableBasicAuth"`

Expand Down

0 comments on commit 8392d25

Please sign in to comment.