Skip to content

Commit f697114

Browse files
rolandshoemakergopherbot
authored andcommitted
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Daniel McCarney <[email protected]> Auto-Submit: Roland Shoemaker <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 50087aa commit f697114

14 files changed

+777
-95
lines changed

api/next/68500.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pkg crypto/tls, type Config struct, EncryptedClientHelloKeys []EncryptedClientHelloKey #68500
2+
pkg crypto/tls, type EncryptedClientHelloKey struct #68500
3+
pkg crypto/tls, type EncryptedClientHelloKey struct, Config []uint8 #68500
4+
pkg crypto/tls, type EncryptedClientHelloKey struct, PrivateKey []uint8 #68500
5+
pkg crypto/tls, type EncryptedClientHelloKey struct, SendAsRetry bool #68500
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The TLS server now supports Encrypted Client Hello (ECH). This feature can be
2+
enabled by populating the [Config.EncryptedClientHelloKeys] field.

src/crypto/tls/bogo_config.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
"TLS-ECH-Client-Reject-ResumeInnerSession-TLS12": "We won't attempt to negotiate 1.2 if ECH is enabled (we could possibly test this if we had the ability to indicate not to send ECH on resumption?)",
1414

15-
"TLS-ECH-Client-Reject-EarlyDataRejected": "We don't support switiching out ECH configs with this level of granularity",
15+
"TLS-ECH-Client-Reject-EarlyDataRejected": "Go does not support early (0-RTT) data",
1616

1717
"TLS-ECH-Client-NoNPN": "We don't support NPN",
1818

@@ -30,8 +30,12 @@
3030
"TLS-ECH-Client-NoSupportedConfigs": "We don't support fallback to cleartext when there are no valid ECH configs",
3131
"TLS-ECH-Client-SkipInvalidPublicName": "We don't support fallback to cleartext when there are no valid ECH configs",
3232

33+
"TLS-ECH-Server-EarlyData": "Go does not support early (0-RTT) data",
34+
"TLS-ECH-Server-EarlyDataRejected": "Go does not support early (0-RTT) data",
35+
36+
"CurveTest-Client-Kyber-TLS13": "Temporarily disabled since the curve ID is not exposed and it cannot be correctly configured",
37+
"CurveTest-Server-Kyber-TLS13": "Temporarily disabled since the curve ID is not exposed and it cannot be correctly configured",
3338

34-
"*ECH-Server*": "no ECH server support",
3539
"SendV2ClientHello*": "We don't support SSLv2",
3640
"*QUIC*": "No QUIC support",
3741
"Compliance-fips*": "No FIPS",
@@ -229,5 +233,11 @@
229233
"EarlyData-UnexpectedHandshake-Server-TLS13": "TODO: first pass, this should be fixed",
230234
"EarlyData-CipherMismatch-Client-TLS13": "TODO: first pass, this should be fixed",
231235
"Resume-Server-UnofferedCipher-TLS13": "TODO: first pass, this should be fixed"
232-
}
236+
},
237+
"AllCurves": [
238+
23,
239+
24,
240+
25,
241+
29
242+
]
233243
}

src/crypto/tls/bogo_shim_test.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ var (
7676
onResumeExpectECHAccepted = flag.Bool("on-resume-expect-ech-accept", false, "")
7777
_ = flag.Bool("on-resume-expect-no-ech-name-override", false, "")
7878
expectedServerName = flag.String("expect-server-name", "", "")
79+
echServerConfig = flagStringSlice("ech-server-config", "")
80+
echServerKey = flagStringSlice("ech-server-key", "")
81+
echServerRetryConfig = flagStringSlice("ech-is-retry-config", "")
7982

8083
expectSessionMiss = flag.Bool("expect-session-miss", false, "")
8184

@@ -105,12 +108,12 @@ func flagStringSlice(name, usage string) *stringSlice {
105108
return f
106109
}
107110

108-
func (saf stringSlice) String() string {
109-
return strings.Join(saf, ",")
111+
func (saf *stringSlice) String() string {
112+
return strings.Join(*saf, ",")
110113
}
111114

112-
func (saf stringSlice) Set(s string) error {
113-
saf = append(saf, s)
115+
func (saf *stringSlice) Set(s string) error {
116+
*saf = append(*saf, s)
114117
return nil
115118
}
116119

@@ -248,6 +251,29 @@ func bogoShim() {
248251
}
249252
}
250253

254+
if len(*echServerConfig) != 0 {
255+
if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) {
256+
log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch")
257+
}
258+
259+
for i, c := range *echServerConfig {
260+
configBytes, err := base64.StdEncoding.DecodeString(c)
261+
if err != nil {
262+
log.Fatalf("parse ech-server-config err: %s", err)
263+
}
264+
privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i])
265+
if err != nil {
266+
log.Fatalf("parse ech-server-key err: %s", err)
267+
}
268+
269+
cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{
270+
Config: configBytes,
271+
PrivateKey: privBytes,
272+
SendAsRetry: (*echServerRetryConfig)[i] == "1",
273+
})
274+
}
275+
}
276+
251277
for i := 0; i < *resumeCount+1; i++ {
252278
if i > 0 && (*onResumeECHConfigListB64 != "") {
253279
echConfigList, err := base64.StdEncoding.DecodeString(*onResumeECHConfigListB64)
@@ -446,8 +472,11 @@ func TestBogoSuite(t *testing.T) {
446472
// are present in the output. They are only checked if -bogo-filter
447473
// was not passed.
448474
assertResults := map[string]string{
449-
"CurveTest-Client-Kyber-TLS13": "PASS",
450-
"CurveTest-Server-Kyber-TLS13": "PASS",
475+
// TODO: these tests are temporarily disabled, since we don't expose the
476+
// necessary curve ID, and it's currently not possible to correctly
477+
// configure it.
478+
// "CurveTest-Client-Kyber-TLS13": "PASS",
479+
// "CurveTest-Server-Kyber-TLS13": "PASS",
451480
}
452481

453482
for name, result := range results.Tests {

src/crypto/tls/common.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -791,8 +791,10 @@ type Config struct {
791791

792792
// EncryptedClientHelloConfigList is a serialized ECHConfigList. If
793793
// provided, clients will attempt to connect to servers using Encrypted
794-
// Client Hello (ECH) using one of the provided ECHConfigs. Servers
795-
// currently ignore this field.
794+
// Client Hello (ECH) using one of the provided ECHConfigs.
795+
//
796+
// Servers do not use this field. In order to configure ECH for servers, see
797+
// the EncryptedClientHelloKeys field.
796798
//
797799
// If the list contains no valid ECH configs, the handshake will fail
798800
// and return an error.
@@ -810,9 +812,11 @@ type Config struct {
810812
EncryptedClientHelloConfigList []byte
811813

812814
// EncryptedClientHelloRejectionVerify, if not nil, is called when ECH is
813-
// rejected, in order to verify the ECH provider certificate in the outer
814-
// Client Hello. If it returns a non-nil error, the handshake is aborted and
815-
// that error results.
815+
// rejected by the remote server, in order to verify the ECH provider
816+
// certificate in the outer Client Hello. If it returns a non-nil error, the
817+
// handshake is aborted and that error results.
818+
//
819+
// On the server side this field is not used.
816820
//
817821
// Unlike VerifyPeerCertificate and VerifyConnection, normal certificate
818822
// verification will not be performed before calling
@@ -824,6 +828,20 @@ type Config struct {
824828
// when ECH is rejected, even if set, and InsecureSkipVerify is ignored.
825829
EncryptedClientHelloRejectionVerify func(ConnectionState) error
826830

831+
// EncryptedClientHelloKeys are the ECH keys to use when a client
832+
// attempts ECH.
833+
//
834+
// If EncryptedClientHelloKeys is set, MinVersion, if set, must be
835+
// VersionTLS13.
836+
//
837+
// If a client attempts ECH, but it is rejected by the server, the server
838+
// will send a list of configs to retry based on the set of
839+
// EncryptedClientHelloKeys which have the SendAsRetry field set.
840+
//
841+
// On the client side, this field is ignored. In order to configure ECH for
842+
// clients, see the EncryptedClientHelloConfigList field.
843+
EncryptedClientHelloKeys []EncryptedClientHelloKey
844+
827845
// mutex protects sessionTicketKeys and autoSessionTicketKeys.
828846
mutex sync.RWMutex
829847
// sessionTicketKeys contains zero or more ticket keys. If set, it means
@@ -837,6 +855,24 @@ type Config struct {
837855
autoSessionTicketKeys []ticketKey
838856
}
839857

858+
// EncryptedClientHelloKey holds a private key that is associated
859+
// with a specific ECH config known to a client.
860+
type EncryptedClientHelloKey struct {
861+
// Config should be a marshalled ECHConfig associated with PrivateKey. This
862+
// must match the config provided to clients byte-for-byte. The config
863+
// should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the
864+
// HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs:
865+
// AES-128-GCM (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002).
866+
Config []byte
867+
// PrivateKey should be a marshalled private key. Currently, we expect
868+
// this to be the output of [ecdh.PrivateKey.Bytes].
869+
PrivateKey []byte
870+
// SendAsRetry indicates if Config should be sent as part of the list of
871+
// retry configs when ECH is requested by the client but rejected by the
872+
// server.
873+
SendAsRetry bool
874+
}
875+
840876
const (
841877
// ticketKeyLifetime is how long a ticket key remains valid and can be used to
842878
// resume a client connection.
@@ -913,6 +949,7 @@ func (c *Config) Clone() *Config {
913949
KeyLogWriter: c.KeyLogWriter,
914950
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
915951
EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify,
952+
EncryptedClientHelloKeys: c.EncryptedClientHelloKeys,
916953
sessionTicketKeys: c.sessionTicketKeys,
917954
autoSessionTicketKeys: c.autoSessionTicketKeys,
918955
}

0 commit comments

Comments
 (0)