crypto/tls: add server-side ECH

Adds support for server-side ECH.

We make a couple of implementation decisions that are not completely
in-line with the spec. In particular, we don't enforce that the SNI
matches the ECHConfig public_name, and we implement a hybrid
shared/backend mode (rather than shared or split mode, as described in
Section 7). Both of these match the behavior of BoringSSL.

The hybrid server mode will either act as a shared mode server, where-in
the server accepts "outer" client hellos and unwraps them before
processing the "inner" hello, or accepts bare "inner" hellos initially.
This lets the server operate either transparently as a shared mode
server, or a backend server, in Section 7 terminology. This seems like
the best implementation choice for a TLS library.

Fixes #68500

Change-Id: Ife69db7c1886610742e95e76b0ca92587e6d7ed4
Reviewed-on: https://go-review.googlesource.com/c/go/+/623576
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
This commit is contained in:
yuhan6665
2025-05-10 14:31:47 -04:00
parent 32d3673ce6
commit cfe61417e9
8 changed files with 597 additions and 84 deletions
+30 -7
View File
@@ -40,7 +40,7 @@ type serverHandshakeState struct {
// serverHandshake performs a TLS handshake as a server.
func (c *Conn) serverHandshake(ctx context.Context) error {
clientHello, err := c.readClientHello(ctx)
clientHello, ech, err := c.readClientHello(ctx)
if err != nil {
return err
}
@@ -50,6 +50,7 @@ func (c *Conn) serverHandshake(ctx context.Context) error {
c: c,
ctx: ctx,
clientHello: clientHello,
echContext: ech,
}
return hs.handshake()
}
@@ -130,17 +131,27 @@ func (hs *serverHandshakeState) handshake() error {
}
// readClientHello reads a ClientHello message and selects the protocol version.
func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServerContext, error) {
// clientHelloMsg is included in the transcript, but we haven't initialized
// it yet. The respective handshake functions will record it themselves.
msg, err := c.readHandshake(nil)
if err != nil {
return nil, err
return nil, nil, err
}
clientHello, ok := msg.(*clientHelloMsg)
if !ok {
c.sendAlert(alertUnexpectedMessage)
return nil, unexpectedMessageError(clientHello, msg)
return nil, nil, unexpectedMessageError(clientHello, msg)
}
// ECH processing has to be done before we do any other negotiation based on
// 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)
if err != nil {
return nil, nil, err
}
}
var configForClient *Config
@@ -149,7 +160,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
chi := clientHelloInfo(ctx, c, clientHello)
if configForClient, err = c.config.GetConfigForClient(chi); err != nil {
c.sendAlert(alertInternalError)
return nil, err
return nil, nil, err
} else if configForClient != nil {
c.config = configForClient
}
@@ -163,13 +174,25 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
c.vers, ok = c.config.mutualVersion(roleServer, clientVersions)
if !ok {
c.sendAlert(alertProtocolVersion)
return nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions)
}
c.haveVers = true
c.in.version = c.vers
c.out.version = c.vers
return clientHello, nil
// This check reflects some odd specification implied behavior. Client-facing servers
// are supposed to reject hellos with outer ECH and inner ECH that offers 1.2, but
// backend servers are allowed to accept hellos with inner ECH that offer 1.2, since
// they cannot expect client-facing servers to behave properly. Since we act as both
// a client-facing and backend server, we only enforce 1.3 being negotiated if we
// saw a hello with outer ECH first. The spec probably should've made this an error,
// but it didn't, and this matches the boringssl behavior.
if c.vers != VersionTLS13 && (ech != nil && !ech.inner) {
c.sendAlert(alertIllegalParameter)
return nil, nil, errors.New("tls: Encrypted Client Hello cannot be used pre-TLS 1.3")
}
return clientHello, ech, nil
}
func (hs *serverHandshakeState) processClientHello() error {