From 1b86b6ea09229ddbcacb3f68a670d927c409fd41 Mon Sep 17 00:00:00 2001 From: yuhan6665 <1588741+yuhan6665@users.noreply.github.com> Date: Sun, 25 May 2025 15:32:29 -0400 Subject: [PATCH] crypto/tls: add GetEncryptedClientHelloKeys This allows servers to rotate their ECH keys without needing to restart the server. Fixes #71920 Change-Id: I55591ab3303d5fde639038541c50edcf1fafc9aa Reviewed-on: https://go-review.googlesource.com/c/go/+/670655 TryBot-Bypass: Roland Shoemaker Reviewed-by: David Chase Auto-Submit: Roland Shoemaker Reviewed-by: Daniel McCarney --- common.go | 18 ++++++++++++++++++ ech.go | 6 +++--- handshake_server.go | 10 +++++++++- handshake_server_tls13.go | 12 ++++++++++-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/common.go b/common.go index ed987ac..19dbd63 100644 --- a/common.go +++ b/common.go @@ -851,6 +851,20 @@ type Config struct { // when ECH is rejected, even if set, and InsecureSkipVerify is ignored. EncryptedClientHelloRejectionVerify func(ConnectionState) error + // GetEncryptedClientHelloKeys, if not nil, is called when by a server when + // a client attempts ECH. + // + // If GetEncryptedClientHelloKeys is not nil, [EncryptedClientHelloKeys] is + // ignored. + // + // If GetEncryptedClientHelloKeys returns an error, the handshake will be + // aborted and the error will be returned. Otherwise, + // GetEncryptedClientHelloKeys must return a non-nil slice of + // [EncryptedClientHelloKey] that represents the acceptable ECH keys. + // + // For further details, see [EncryptedClientHelloKeys]. + GetEncryptedClientHelloKeys func(*ClientHelloInfo) ([]EncryptedClientHelloKey, error) + // EncryptedClientHelloKeys are the ECH keys to use when a client // attempts ECH. // @@ -861,6 +875,9 @@ type Config struct { // will send a list of configs to retry based on the set of // EncryptedClientHelloKeys which have the SendAsRetry field set. // + // If GetEncryptedClientHelloKeys is non-nil, EncryptedClientHelloKeys is + // ignored. + // // On the client side, this field is ignored. In order to configure ECH for // clients, see the EncryptedClientHelloConfigList field. EncryptedClientHelloKeys []EncryptedClientHelloKey @@ -961,6 +978,7 @@ func (c *Config) Clone() *Config { GetCertificate: c.GetCertificate, GetClientCertificate: c.GetClientCertificate, GetConfigForClient: c.GetConfigForClient, + GetEncryptedClientHelloKeys: c.GetEncryptedClientHelloKeys, VerifyPeerCertificate: c.VerifyPeerCertificate, VerifyConnection: c.VerifyConnection, RootCAs: c.RootCAs, diff --git a/ech.go b/ech.go index 8a559ad..3d9226b 100644 --- a/ech.go +++ b/ech.go @@ -579,7 +579,7 @@ func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([ return builder.Bytes() } -func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) { +func (c *Conn) processECHClientHello(outer *clientHelloMsg, echKeys []EncryptedClientHelloKey) (*clientHelloMsg, *echServerContext, error) { echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello) if err != nil { if errors.Is(err, errInvalidECHExt) { @@ -595,11 +595,11 @@ func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *e return outer, &echServerContext{inner: true}, nil } - if len(c.config.EncryptedClientHelloKeys) == 0 { + if len(echKeys) == 0 { return outer, nil, nil } - for _, echKey := range c.config.EncryptedClientHelloKeys { + for _, echKey := range echKeys { skip, config, err := parseECHConfig(echKey.Config) if err != nil || skip { c.sendAlert(alertInternalError) diff --git a/handshake_server.go b/handshake_server.go index 7f98cb8..bbce606 100644 --- a/handshake_server.go +++ b/handshake_server.go @@ -150,7 +150,15 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer // the contents of the client hello, since we may swap it out completely. var ech *echServerContext if len(clientHello.encryptedClientHello) != 0 { - clientHello, ech, err = c.processECHClientHello(clientHello) + echKeys := c.config.EncryptedClientHelloKeys + if c.config.GetEncryptedClientHelloKeys != nil { + echKeys, err = c.config.GetEncryptedClientHelloKeys(clientHelloInfo(ctx, c, clientHello)) + if err != nil { + c.sendAlert(alertInternalError) + return nil, nil, err + } + } + clientHello, ech, err = c.processECHClientHello(clientHello, echKeys) if err != nil { return nil, nil, err } diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go index bc108a7..b41d6df 100644 --- a/handshake_server_tls13.go +++ b/handshake_server_tls13.go @@ -877,8 +877,16 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { // If client sent ECH extension, but we didn't accept it, // send retry configs, if available. - if len(hs.c.config.EncryptedClientHelloKeys) > 0 && len(hs.clientHello.encryptedClientHello) > 0 && hs.echContext == nil { - encryptedExtensions.echRetryConfigs, err = buildRetryConfigList(hs.c.config.EncryptedClientHelloKeys) + echKeys := hs.c.config.EncryptedClientHelloKeys + if hs.c.config.GetEncryptedClientHelloKeys != nil { + echKeys, err = hs.c.config.GetEncryptedClientHelloKeys(clientHelloInfo(hs.ctx, c, hs.clientHello)) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + } + if len(echKeys) > 0 && len(hs.clientHello.encryptedClientHello) > 0 && hs.echContext == nil { + encryptedExtensions.echRetryConfigs, err = buildRetryConfigList(echKeys) if err != nil { c.sendAlert(alertInternalError) return err