0
0
mirror of https://github.com/XTLS/REALITY.git synced 2025-08-22 06:28:35 +00:00

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 <roland@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
This commit is contained in:
yuhan6665 2025-05-25 15:32:29 -04:00
parent 176e7bdccb
commit 8ef3e8ca6d
4 changed files with 40 additions and 6 deletions

View File

@ -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,

6
ech.go
View File

@ -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)

View File

@ -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
}

View File

@ -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