New Upstream Release - golang-github-marten-seemann-qtls-go1-18

Ready changes

Summary

Merged new upstream version: 0.2.0 (was: 0.1.3).

Resulting package

Built on 2023-04-22T07:20 (took 5m15s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-marten-seemann-qtls-go1-18-dev

Lintian Result

Diff

diff --git a/README.md b/README.md
index 3e90221..c592c4c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # qtls
 
-[![Go Reference](https://pkg.go.dev/badge/github.com/marten-seemann/qtls-go1-17.svg)](https://pkg.go.dev/github.com/marten-seemann/qtls-go1-17)
-[![.github/workflows/go-test.yml](https://github.com/marten-seemann/qtls-go1-17/actions/workflows/go-test.yml/badge.svg)](https://github.com/marten-seemann/qtls-go1-17/actions/workflows/go-test.yml)
+[![Go Reference](https://pkg.go.dev/badge/github.com/quic-go/qtls-go1-18.svg)](https://pkg.go.dev/github.com/quic-go/qtls-go1-18)
+[![.github/workflows/go-test.yml](https://github.com/quic-go/qtls-go1-18/actions/workflows/go-test.yml/badge.svg)](https://github.com/quic-go/qtls-go1-18/actions/workflows/go-test.yml)
 
 This repository contains a modified version of the standard library's TLS implementation, modified for the QUIC protocol. It is used by [quic-go](https://github.com/lucas-clemente/quic-go).
diff --git a/common.go b/common.go
index 4c9aeeb..3e4ced7 100644
--- a/common.go
+++ b/common.go
@@ -345,7 +345,8 @@ type clientSessionState struct {
 // goroutines. Up to TLS 1.2, only ticket-based resumption is supported, not
 // SessionID-based resumption. In TLS 1.3 they were merged into PSK modes, which
 // are supported via this interface.
-//go:generate sh -c "mockgen -package qtls -destination mock_client_session_cache_test.go github.com/marten-seemann/qtls-go1-17 ClientSessionCache"
+//
+//go:generate sh -c "mockgen -package qtls -destination mock_client_session_cache_test.go github.com/quic-go/qtls-go1-18 ClientSessionCache"
 type ClientSessionCache = tls.ClientSessionCache
 
 // SignatureScheme is a tls.SignatureScheme
diff --git a/conn.go b/conn.go
index 31a87b5..2b8c730 100644
--- a/conn.go
+++ b/conn.go
@@ -32,6 +32,7 @@ type Conn struct {
 
 	// handshakeStatus is 1 if the connection is currently transferring
 	// application data (i.e. is not currently processing a handshake).
+	// handshakeStatus == 1 implies handshakeErr == nil.
 	// This field is only to be accessed with sync/atomic.
 	handshakeStatus uint32
 	// constant after handshake; protected by handshakeMutex
@@ -124,6 +125,9 @@ type Conn struct {
 	used0RTT bool
 
 	tmp [16]byte
+
+	connStateMutex sync.Mutex
+	connState      ConnectionStateWith0RTT
 }
 
 // Access to net.Conn methods.
@@ -1458,6 +1462,13 @@ func (c *Conn) HandshakeContext(ctx context.Context) error {
 }
 
 func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
+	// Fast sync/atomic-based exit if there is no handshake in flight and the
+	// last one succeeded without an error. Avoids the expensive context setup
+	// and mutex for most Read and Write calls.
+	if c.handshakeComplete() {
+		return nil
+	}
+
 	handshakeCtx, cancel := context.WithCancel(ctx)
 	// Note: defer this before starting the "interrupter" goroutine
 	// so that we can tell the difference between the input being canceled and
@@ -1516,25 +1527,25 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
 	if c.handshakeErr == nil && !c.handshakeComplete() {
 		c.handshakeErr = errors.New("tls: internal error: handshake should have had a result")
 	}
+	if c.handshakeErr != nil && c.handshakeComplete() {
+		panic("tls: internal error: handshake returned an error but is marked successful")
+	}
 
 	return c.handshakeErr
 }
 
 // ConnectionState returns basic TLS details about the connection.
 func (c *Conn) ConnectionState() ConnectionState {
-	c.handshakeMutex.Lock()
-	defer c.handshakeMutex.Unlock()
-	return c.connectionStateLocked()
+	c.connStateMutex.Lock()
+	defer c.connStateMutex.Unlock()
+	return c.connState.ConnectionState
 }
 
 // ConnectionStateWith0RTT returns basic TLS details (incl. 0-RTT status) about the connection.
 func (c *Conn) ConnectionStateWith0RTT() ConnectionStateWith0RTT {
-	c.handshakeMutex.Lock()
-	defer c.handshakeMutex.Unlock()
-	return ConnectionStateWith0RTT{
-		ConnectionState: c.connectionStateLocked(),
-		Used0RTT:        c.used0RTT,
-	}
+	c.connStateMutex.Lock()
+	defer c.connStateMutex.Unlock()
+	return c.connState
 }
 
 func (c *Conn) connectionStateLocked() ConnectionState {
@@ -1565,6 +1576,15 @@ func (c *Conn) connectionStateLocked() ConnectionState {
 	return toConnectionState(state)
 }
 
+func (c *Conn) updateConnectionState() {
+	c.connStateMutex.Lock()
+	defer c.connStateMutex.Unlock()
+	c.connState = ConnectionStateWith0RTT{
+		Used0RTT:        c.used0RTT,
+		ConnectionState: c.connectionStateLocked(),
+	}
+}
+
 // OCSPResponse returns the stapled OCSP response from the TLS server, if
 // any. (Only valid for client connections.)
 func (c *Conn) OCSPResponse() []byte {
diff --git a/debian/changelog b/debian/changelog
index c1abc02..fc83be0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-marten-seemann-qtls-go1-18 (0.2.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 22 Apr 2023 07:15:36 -0000
+
 golang-github-marten-seemann-qtls-go1-18 (0.1.1-2) unstable; urgency=medium
 
   * Source-only upload for testing migration.
diff --git a/go.mod b/go.mod
index df3cdbb..705ce2b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/marten-seemann/qtls-go1-18
+module github.com/quic-go/qtls-go1-18
 
 go 1.18
 
diff --git a/handshake_client.go b/handshake_client.go
index ab691d5..a2a0eae 100644
--- a/handshake_client.go
+++ b/handshake_client.go
@@ -290,6 +290,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
 		c.config.ClientSessionCache.Put(cacheKey, toClientSessionState(hs.session))
 	}
 
+	c.updateConnectionState()
 	return nil
 }
 
diff --git a/handshake_client_tls13.go b/handshake_client_tls13.go
index 0de59fc..09d602d 100644
--- a/handshake_client_tls13.go
+++ b/handshake_client_tls13.go
@@ -78,6 +78,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
 	if err := hs.processServerHello(); err != nil {
 		return err
 	}
+	c.updateConnectionState()
 	if err := hs.sendDummyChangeCipherSpec(); err != nil {
 		return err
 	}
@@ -90,6 +91,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
 	if err := hs.readServerCertificate(); err != nil {
 		return err
 	}
+	c.updateConnectionState()
 	if err := hs.readServerFinished(); err != nil {
 		return err
 	}
@@ -104,7 +106,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
 	}
 
 	atomic.StoreUint32(&c.handshakeStatus, 1)
-
+	c.updateConnectionState()
 	return nil
 }
 
diff --git a/handshake_server.go b/handshake_server.go
index 2fe8284..a6519d7 100644
--- a/handshake_server.go
+++ b/handshake_server.go
@@ -132,6 +132,7 @@ func (hs *serverHandshakeState) handshake() error {
 	c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.clientHello.random, hs.hello.random)
 	atomic.StoreUint32(&c.handshakeStatus, 1)
 
+	c.updateConnectionState()
 	return nil
 }
 
@@ -270,7 +271,7 @@ func (hs *serverHandshakeState) processClientHello() error {
 
 	hs.ecdheOk = supportsECDHE(c.config, hs.clientHello.supportedCurves, hs.clientHello.supportedPoints)
 
-	if hs.ecdheOk {
+	if hs.ecdheOk && len(hs.clientHello.supportedPoints) > 0 {
 		// Although omitting the ec_point_formats extension is permitted, some
 		// old OpenSSL version will refuse to handshake if not present.
 		//
@@ -351,6 +352,13 @@ func supportsECDHE(c *config, supportedCurves []CurveID, supportedPoints []uint8
 			break
 		}
 	}
+	// Per RFC 8422, Section 5.1.2, if the Supported Point Formats extension is
+	// missing, uncompressed points are supported. If supportedPoints is empty,
+	// the extension must be missing, as an empty extension body is rejected by
+	// the parser. See https://go.dev/issue/49126.
+	if len(supportedPoints) == 0 {
+		supportsPointFormat = true
+	}
 
 	return supportsCurve && supportsPointFormat
 }
diff --git a/handshake_server_test.go b/handshake_server_test.go
index 5a28c6a..5ab1fdd 100644
--- a/handshake_server_test.go
+++ b/handshake_server_test.go
@@ -281,7 +281,7 @@ func TestTLS12OnlyCipherSuites(t *testing.T) {
 
 func TestTLSPointFormats(t *testing.T) {
 	// Test that a Server returns the ec_point_format extension when ECC is
-	// negotiated, and not returned on RSA handshake.
+	// negotiated, and not on a RSA handshake or if ec_point_format is missing.
 	tests := []struct {
 		name                string
 		cipherSuites        []uint16
@@ -289,8 +289,11 @@ func TestTLSPointFormats(t *testing.T) {
 		supportedPoints     []uint8
 		wantSupportedPoints bool
 	}{
-		{"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{compressionNone}, true},
+		{"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{pointFormatUncompressed}, true},
+		{"ECC without ec_point_format", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, nil, false},
+		{"ECC with extra values", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{13, 37, pointFormatUncompressed, 42}, true},
 		{"RSA", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, nil, false},
+		{"RSA with ec_point_format", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, []uint8{pointFormatUncompressed}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -330,18 +333,8 @@ func TestTLSPointFormats(t *testing.T) {
 				t.Fatalf("didn't get ServerHello message in reply. Got %v\n", reply)
 			}
 			if tt.wantSupportedPoints {
-				if len(serverHello.supportedPoints) < 1 {
-					t.Fatal("missing ec_point_format extension from server")
-				}
-				found := false
-				for _, p := range serverHello.supportedPoints {
-					if p == pointFormatUncompressed {
-						found = true
-						break
-					}
-				}
-				if !found {
-					t.Fatal("missing uncompressed format in ec_point_format extension from server")
+				if !bytes.Equal(serverHello.supportedPoints, []uint8{pointFormatUncompressed}) {
+					t.Fatal("incorrect ec_point_format extension from server")
 				}
 			} else {
 				if len(serverHello.supportedPoints) != 0 {
@@ -1579,6 +1572,7 @@ func TestSNIGivenOnFailure(t *testing.T) {
 		t.Error("No error reported from server")
 	}
 
+	hs.c.updateConnectionState()
 	cs := hs.c.ConnectionState()
 	if cs.HandshakeComplete {
 		t.Error("Handshake registered as complete")
diff --git a/handshake_server_tls13.go b/handshake_server_tls13.go
index d26deb8..7ce09c3 100644
--- a/handshake_server_tls13.go
+++ b/handshake_server_tls13.go
@@ -53,6 +53,7 @@ func (hs *serverHandshakeStateTLS13) handshake() error {
 	if err := hs.checkForResumption(); err != nil {
 		return err
 	}
+	c.updateConnectionState()
 	if err := hs.pickCertificate(); err != nil {
 		return err
 	}
@@ -75,12 +76,13 @@ func (hs *serverHandshakeStateTLS13) handshake() error {
 	if err := hs.readClientCertificate(); err != nil {
 		return err
 	}
+	c.updateConnectionState()
 	if err := hs.readClientFinished(); err != nil {
 		return err
 	}
 
 	atomic.StoreUint32(&c.handshakeStatus, 1)
-
+	c.updateConnectionState()
 	return nil
 }
 
@@ -777,6 +779,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
 	if err != nil {
 		return err
 	}
+
 	if _, err := c.writeRecord(recordTypeHandshake, m.marshal()); err != nil {
 		return err
 	}
diff --git a/mock_client_session_cache_test.go b/mock_client_session_cache_test.go
index 38d62e6..6988cb8 100644
--- a/mock_client_session_cache_test.go
+++ b/mock_client_session_cache_test.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/marten-seemann/qtls-go1-17 (interfaces: ClientSessionCache)
+// Source: github.com/quic-go/qtls-go1-18 (interfaces: ClientSessionCache)
 
 // Package qtls is a generated GoMock package.
 package qtls
diff --git a/record_layer_test.go b/record_layer_test.go
index 9bd0664..2b3877a 100644
--- a/record_layer_test.go
+++ b/record_layer_test.go
@@ -74,149 +74,184 @@ func TestAlternativeRecordLayer(t *testing.T) {
 	cOut := make(chan interface{}, 10)
 	defer close(cOut)
 
-	serverEvents := make(chan interface{}, 100)
+	testConfig := testConfig.Clone()
+	testConfig.NextProtos = []string{"alpn"}
+
+	// server side
+	errChan := make(chan error)
+	serverConn := Server(
+		&unusedConn{},
+		testConfig,
+		&ExtraConfig{AlternativeRecordLayer: &recordLayerWithKeys{in: sIn, out: sOut}},
+	)
+	go func() {
+		defer serverConn.Close()
+		err := serverConn.Handshake()
+		connState := serverConn.ConnectionState()
+		if !connState.HandshakeComplete {
+			t.Fatal("expected the handshake to have completed")
+		}
+		errChan <- err
+	}()
+	serverKeyChan := make(chan *exportedKey, 4) // see server loop for the order in which keys are provided
 	go func() {
+		var counter int
 		for {
 			c, ok := <-sOut
 			if !ok {
 				return
 			}
-			serverEvents <- c
+			switch counter {
+			case 0:
+				if c.([]byte)[0] != typeServerHello {
+					t.Errorf("expected ServerHello")
+				}
+				connState := serverConn.ConnectionState()
+				if connState.HandshakeComplete {
+					t.Error("didn't expect the handshake to be complete yet")
+				}
+				if connState.Version != VersionTLS13 {
+					t.Errorf("expected TLS 1.3, got %x", connState.Version)
+				}
+				if connState.NegotiatedProtocol == "" {
+					t.Error("expected ALPN to be negotiated")
+				}
+			case 1:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "read" || keyEv.encLevel != EncryptionHandshake {
+					t.Errorf("expected the handshake read key")
+				}
+				serverKeyChan <- keyEv
+			case 2:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "write" || keyEv.encLevel != EncryptionHandshake {
+					t.Errorf("expected the handshake write key")
+				}
+				serverKeyChan <- keyEv
+			case 3:
+				if c.([]byte)[0] != typeEncryptedExtensions {
+					t.Errorf("expected EncryptedExtensions")
+				}
+			case 4:
+				if c.([]byte)[0] != typeCertificate {
+					t.Errorf("expected Certificate")
+				}
+			case 5:
+				if c.([]byte)[0] != typeCertificateVerify {
+					t.Errorf("expected CertificateVerify")
+				}
+			case 6:
+				if c.([]byte)[0] != typeFinished {
+					t.Errorf("expected Finished")
+				}
+			case 7:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "write" || keyEv.encLevel != EncryptionApplication {
+					t.Errorf("expected the application write key")
+				}
+				serverKeyChan <- keyEv
+			case 8:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "read" || keyEv.encLevel != EncryptionApplication {
+					t.Errorf("expected the application read key")
+				}
+				serverKeyChan <- keyEv
+			default:
+				t.Error("didn't expect any more events")
+			}
+			counter++
 			if b, ok := c.([]byte); ok {
 				cIn <- b
 			}
 		}
 	}()
 
-	clientEvents := make(chan interface{}, 100)
+	// client side
+	clientConn := Client(
+		&unusedConn{},
+		testConfig,
+		&ExtraConfig{AlternativeRecordLayer: &recordLayerWithKeys{in: cIn, out: cOut}},
+	)
+	defer clientConn.Close()
 	go func() {
+		var counter int
 		for {
 			c, ok := <-cOut
 			if !ok {
 				return
 			}
-			clientEvents <- c
+			switch counter {
+			case 0:
+				if c.([]byte)[0] != typeClientHello {
+					t.Errorf("expected ClientHello")
+				}
+				connState := clientConn.ConnectionState()
+				if connState.HandshakeComplete {
+					t.Error("didn't expect the handshake to be complete yet")
+				}
+				if len(connState.PeerCertificates) != 0 {
+					t.Error("didn't expect a certificate yet")
+				}
+			case 1:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "write" || keyEv.encLevel != EncryptionHandshake {
+					t.Errorf("expected the handshake write key")
+				}
+				compareExportedKeys(t, <-serverKeyChan, keyEv)
+			case 2:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "read" || keyEv.encLevel != EncryptionHandshake {
+					t.Errorf("expected the handshake read key")
+				}
+				compareExportedKeys(t, <-serverKeyChan, keyEv)
+			case 3:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "read" || keyEv.encLevel != EncryptionApplication {
+					t.Errorf("expected the application read key")
+				}
+				compareExportedKeys(t, <-serverKeyChan, keyEv)
+			case 4:
+				if c.([]byte)[0] != typeFinished {
+					t.Errorf("expected Finished")
+				}
+			case 5:
+				keyEv := c.(*exportedKey)
+				if keyEv.typ != "write" || keyEv.encLevel != EncryptionApplication {
+					t.Errorf("expected the application write key")
+				}
+				compareExportedKeys(t, <-serverKeyChan, keyEv)
+			default:
+				t.Error("didn't expect any more events")
+			}
+			counter++
 			if b, ok := c.([]byte); ok {
 				sIn <- b
 			}
 		}
 	}()
 
-	errChan := make(chan error)
-	go func() {
-		extraConf := &ExtraConfig{
-			AlternativeRecordLayer: &recordLayerWithKeys{in: sIn, out: sOut},
-		}
-		tlsConn := Server(&unusedConn{}, testConfig, extraConf)
-		defer tlsConn.Close()
-		errChan <- tlsConn.Handshake()
-	}()
-
-	extraConf := &ExtraConfig{
-		AlternativeRecordLayer: &recordLayerWithKeys{in: cIn, out: cOut},
-	}
-	tlsConn := Client(&unusedConn{}, testConfig, extraConf)
-	defer tlsConn.Close()
-	if err := tlsConn.Handshake(); err != nil {
+	if err := clientConn.Handshake(); err != nil {
 		t.Fatalf("Handshake failed: %s", err)
 	}
-
-	// Handshakes completed. Now check that events were received in the correct order.
-	var clientHandshakeReadKey, clientHandshakeWriteKey *exportedKey
-	var clientApplicationReadKey, clientApplicationWriteKey *exportedKey
-	for i := 0; i <= 5; i++ {
-		ev := <-clientEvents
-		switch i {
-		case 0:
-			if ev.([]byte)[0] != typeClientHello {
-				t.Fatalf("expected ClientHello")
-			}
-		case 1:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "write" || keyEv.encLevel != EncryptionHandshake {
-				t.Fatalf("expected the handshake write key")
-			}
-			clientHandshakeWriteKey = keyEv
-		case 2:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "read" || keyEv.encLevel != EncryptionHandshake {
-				t.Fatalf("expected the handshake read key")
-			}
-			clientHandshakeReadKey = keyEv
-		case 3:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "read" || keyEv.encLevel != EncryptionApplication {
-				t.Fatalf("expected the application read key")
-			}
-			clientApplicationReadKey = keyEv
-		case 4:
-			if ev.([]byte)[0] != typeFinished {
-				t.Fatalf("expected Finished")
-			}
-		case 5:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "write" || keyEv.encLevel != EncryptionApplication {
-				t.Fatalf("expected the application write key")
-			}
-			clientApplicationWriteKey = keyEv
-		}
+	connState := clientConn.ConnectionState()
+	if !connState.HandshakeComplete {
+		t.Fatal("expected the handshake to have completed")
 	}
-	if len(clientEvents) > 0 {
-		t.Fatal("didn't expect any more client events")
+	if connState.Version != VersionTLS13 {
+		t.Errorf("expected TLS 1.3, got %x", connState.Version)
+	}
+	if len(connState.PeerCertificates) == 0 {
+		t.Fatal("expected the certificate to be set")
 	}
 
-	for i := 0; i <= 8; i++ {
-		ev := <-serverEvents
-		switch i {
-		case 0:
-			if ev.([]byte)[0] != typeServerHello {
-				t.Fatalf("expected ServerHello")
-			}
-		case 1:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "read" || keyEv.encLevel != EncryptionHandshake {
-				t.Fatalf("expected the handshake read key")
-			}
-			compareExportedKeys(t, clientHandshakeWriteKey, keyEv)
-		case 2:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "write" || keyEv.encLevel != EncryptionHandshake {
-				t.Fatalf("expected the handshake write key")
-			}
-			compareExportedKeys(t, clientHandshakeReadKey, keyEv)
-		case 3:
-			if ev.([]byte)[0] != typeEncryptedExtensions {
-				t.Fatalf("expected EncryptedExtensions")
-			}
-		case 4:
-			if ev.([]byte)[0] != typeCertificate {
-				t.Fatalf("expected Certificate")
-			}
-		case 5:
-			if ev.([]byte)[0] != typeCertificateVerify {
-				t.Fatalf("expected CertificateVerify")
-			}
-		case 6:
-			if ev.([]byte)[0] != typeFinished {
-				t.Fatalf("expected Finished")
-			}
-		case 7:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "write" || keyEv.encLevel != EncryptionApplication {
-				t.Fatalf("expected the application write key")
-			}
-			compareExportedKeys(t, clientApplicationReadKey, keyEv)
-		case 8:
-			keyEv := ev.(*exportedKey)
-			if keyEv.typ != "read" || keyEv.encLevel != EncryptionApplication {
-				t.Fatalf("expected the application read key")
-			}
-			compareExportedKeys(t, clientApplicationWriteKey, keyEv)
+	select {
+	case <-time.After(500 * time.Millisecond):
+		t.Fatal("server timed out")
+	case err := <-errChan:
+		if err != nil {
+			t.Fatalf("server handshake failed: %s", err)
 		}
 	}
-	if len(serverEvents) > 0 {
-		t.Fatal("didn't expect any more server events")
-	}
 }
 
 func TestErrorOnOldTLSVersions(t *testing.T) {
diff --git a/ticket.go b/ticket.go
index 006b8c1..81e8a52 100644
--- a/ticket.go
+++ b/ticket.go
@@ -11,6 +11,7 @@ import (
 	"crypto/hmac"
 	"crypto/sha256"
 	"crypto/subtle"
+	"encoding/binary"
 	"errors"
 	"io"
 	"time"
@@ -232,6 +233,20 @@ func (c *Conn) getSessionTicketMsg(appData []byte) (*newSessionTicketMsgTLS13, e
 		return nil, err
 	}
 	m.lifetime = uint32(maxSessionTicketLifetime / time.Second)
+
+	// ticket_age_add is a random 32-bit value. See RFC 8446, section 4.6.1
+	// The value is not stored anywhere; we never need to check the ticket age
+	// because 0-RTT is not supported.
+	ageAdd := make([]byte, 4)
+	_, err = c.config.rand().Read(ageAdd)
+	if err != nil {
+		return nil, err
+	}
+	m.ageAdd = binary.LittleEndian.Uint32(ageAdd)
+
+	// ticket_nonce, which must be unique per connection, is always left at
+	// zero because we only ever send one ticket per connection.
+
 	if c.extraConfig != nil {
 		m.maxEarlyData = c.extraConfig.MaxEarlyData
 	}

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details