indefinitely.
</p>
+<p><!-- CL 246338 -->
+ <a href="/pkg/crypto/tls#Conn.HandshakeContext">(*Conn).HandshakeContext</a> was added to
+ allow the user to control cancellation of an in-progress TLS Handshake.
+ The context provided is propagated into the
+ <a href="/pkg/crypto/tls#ClientHelloInfo">ClientHelloInfo</a>
+ and <a href="/pkg/crypto/tls#CertificateRequestInfo">CertificateRequestInfo</a>
+ structs and accessible through the new
+ <a href="/pkg/crypto/tls#ClientHelloInfo.Context">(*ClientHelloInfo).Context</a>
+ and
+ <a href="/pkg/crypto/tls#CertificateRequestInfo.Context">
+ (*CertificateRequestInfo).Context
+ </a> methods respectively. Canceling the context after the handshake has finished
+ has no effect.
+</p>
+
<h3 id="crypto/x509"><a href="/pkg/crypto/x509">crypto/x509</a></h3>
<p><!-- CL 235078 -->
Cookies set with <code>SameSiteDefaultMode</code> now behave according to the current
spec (no attribute is set) instead of generating a SameSite key without a value.
</p>
+
+ <p><!-- CL 246338 -->
+ The <a href="/pkg/net/http/"><code>net/http</code></a> package now uses the new
+ <a href="/pkg/crypto/tls#Conn.HandshakeContext"><code>(*tls.Conn).HandshakeContext</code></a>
+ with the <a href="/pkg/net/http/#Request"><code>Request</code></a> context
+ when performing TLS handshakes in the client or server.
+ </p>
</dd>
</dl><!-- net/http -->
import (
"bytes"
"container/list"
+ "context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
// config is embedded by the GetCertificate or GetConfigForClient caller,
// for use with SupportsCertificate.
config *Config
+
+ // ctx is the context of the handshake that is in progress.
+ ctx context.Context
+}
+
+// Context returns the context of the handshake that is in progress.
+// This context is a child of the context passed to HandshakeContext,
+// if any, and is canceled when the handshake concludes.
+func (c *ClientHelloInfo) Context() context.Context {
+ return c.ctx
}
// CertificateRequestInfo contains information from a server's
// Version is the TLS version that was negotiated for this connection.
Version uint16
+
+ // ctx is the context of the handshake that is in progress.
+ ctx context.Context
+}
+
+// Context returns the context of the handshake that is in progress.
+// This context is a child of the context passed to HandshakeContext,
+// if any, and is canceled when the handshake concludes.
+func (c *CertificateRequestInfo) Context() context.Context {
+ return c.ctx
}
// RenegotiationSupport enumerates the different levels of support for TLS
import (
"bytes"
+ "context"
"crypto/cipher"
"crypto/subtle"
"crypto/x509"
// constant
conn net.Conn
isClient bool
- handshakeFn func() error // (*Conn).clientHandshake or serverHandshake
+ handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake
// handshakeStatus is 1 if the connection is currently transferring
// application data (i.e. is not currently processing a handshake).
defer c.handshakeMutex.Unlock()
atomic.StoreUint32(&c.handshakeStatus, 0)
- if c.handshakeErr = c.clientHandshake(); c.handshakeErr == nil {
+ if c.handshakeErr = c.clientHandshake(context.Background()); c.handshakeErr == nil {
c.handshakes++
}
return c.handshakeErr
// first Read or Write will call it automatically.
//
// For control over canceling or setting a timeout on a handshake, use
-// the Dialer's DialContext method.
+// HandshakeContext or the Dialer's DialContext method instead.
func (c *Conn) Handshake() error {
+ return c.HandshakeContext(context.Background())
+}
+
+// HandshakeContext runs the client or server handshake
+// protocol if it has not yet been run.
+//
+// The provided Context must be non-nil. If the context is canceled before
+// the handshake is complete, the handshake is interrupted and an error is returned.
+// Once the handshake has completed, cancellation of the context will not affect the
+// connection.
+//
+// Most uses of this package need not call HandshakeContext explicitly: the
+// first Read or Write will call it automatically.
+func (c *Conn) HandshakeContext(ctx context.Context) error {
+ // Delegate to unexported method for named return
+ // without confusing documented signature.
+ return c.handshakeContext(ctx)
+}
+
+func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
+ 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
+ // this cancellation. In the former case, we need to close the connection.
+ defer cancel()
+
+ // Start the "interrupter" goroutine, if this context might be canceled.
+ // (The background context cannot).
+ //
+ // The interrupter goroutine waits for the input context to be done and
+ // closes the connection if this happens before the function returns.
+ if ctx.Done() != nil {
+ done := make(chan struct{})
+ interruptRes := make(chan error, 1)
+ defer func() {
+ close(done)
+ if ctxErr := <-interruptRes; ctxErr != nil {
+ // Return context error to user.
+ ret = ctxErr
+ }
+ }()
+ go func() {
+ select {
+ case <-handshakeCtx.Done():
+ // Close the connection, discarding the error
+ _ = c.conn.Close()
+ interruptRes <- handshakeCtx.Err()
+ case <-done:
+ interruptRes <- nil
+ }
+ }()
+ }
+
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
c.in.Lock()
defer c.in.Unlock()
- c.handshakeErr = c.handshakeFn()
+ c.handshakeErr = c.handshakeFn(handshakeCtx)
if c.handshakeErr == nil {
c.handshakes++
} else {
import (
"bytes"
+ "context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
type clientHandshakeState struct {
c *Conn
+ ctx context.Context
serverHello *serverHelloMsg
hello *clientHelloMsg
suite *cipherSuite
return hello, params, nil
}
-func (c *Conn) clientHandshake() (err error) {
+func (c *Conn) clientHandshake(ctx context.Context) (err error) {
if c.config == nil {
c.config = defaultConfig()
}
if c.vers == VersionTLS13 {
hs := &clientHandshakeStateTLS13{
c: c,
+ ctx: ctx,
serverHello: serverHello,
hello: hello,
ecdheParams: ecdheParams,
hs := &clientHandshakeState{
c: c,
+ ctx: ctx,
serverHello: serverHello,
hello: hello,
session: session,
certRequested = true
hs.finishedHash.Write(certReq.marshal())
- cri := certificateRequestInfoFromMsg(c.vers, certReq)
+ cri := certificateRequestInfoFromMsg(hs.ctx, c.vers, certReq)
if chainToSend, err = c.getClientCertificate(cri); err != nil {
c.sendAlert(alertInternalError)
return err
// certificateRequestInfoFromMsg generates a CertificateRequestInfo from a TLS
// <= 1.2 CertificateRequest, making an effort to fill in missing information.
-func certificateRequestInfoFromMsg(vers uint16, certReq *certificateRequestMsg) *CertificateRequestInfo {
+func certificateRequestInfoFromMsg(ctx context.Context, vers uint16, certReq *certificateRequestMsg) *CertificateRequestInfo {
cri := &CertificateRequestInfo{
AcceptableCAs: certReq.certificateAuthorities,
Version: vers,
+ ctx: ctx,
}
var rsaAvail, ecAvail bool
import (
"bytes"
+ "context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"os/exec"
"path/filepath"
"reflect"
+ "runtime"
"strconv"
"strings"
"testing"
serverConfig.Certificates[0].SignedCertificateTimestamps, ccs.SignedCertificateTimestamps)
}
}
+
+func TestClientHandshakeContextCancellation(t *testing.T) {
+ c, s := localPipe(t)
+ serverConfig := testConfig.Clone()
+ serverErr := make(chan error, 1)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ go func() {
+ defer close(serverErr)
+ defer s.Close()
+ conn := Server(s, serverConfig)
+ _, err := conn.readClientHello(ctx)
+ cancel()
+ serverErr <- err
+ }()
+ cli := Client(c, testConfig)
+ err := cli.HandshakeContext(ctx)
+ if err == nil {
+ t.Fatal("Client handshake did not error when the context was canceled")
+ }
+ if err != context.Canceled {
+ t.Errorf("Unexpected client handshake error: %v", err)
+ }
+ if err := <-serverErr; err != nil {
+ t.Errorf("Unexpected server error: %v", err)
+ }
+ if runtime.GOARCH == "wasm" {
+ t.Skip("conn.Close does not error as expected when called multiple times on WASM")
+ }
+ err = cli.Close()
+ if err == nil {
+ t.Error("Client connection was not closed when the context was canceled")
+ }
+}
import (
"bytes"
+ "context"
"crypto"
"crypto/hmac"
"crypto/rsa"
type clientHandshakeStateTLS13 struct {
c *Conn
+ ctx context.Context
serverHello *serverHelloMsg
hello *clientHelloMsg
ecdheParams ecdheParameters
AcceptableCAs: hs.certReq.certificateAuthorities,
SignatureSchemes: hs.certReq.supportedSignatureAlgorithms,
Version: c.vers,
+ ctx: hs.ctx,
})
if err != nil {
return err
package tls
import (
+ "context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
// It's discarded once the handshake has completed.
type serverHandshakeState struct {
c *Conn
+ ctx context.Context
clientHello *clientHelloMsg
hello *serverHelloMsg
suite *cipherSuite
}
// serverHandshake performs a TLS handshake as a server.
-func (c *Conn) serverHandshake() error {
- clientHello, err := c.readClientHello()
+func (c *Conn) serverHandshake(ctx context.Context) error {
+ clientHello, err := c.readClientHello(ctx)
if err != nil {
return err
}
if c.vers == VersionTLS13 {
hs := serverHandshakeStateTLS13{
c: c,
+ ctx: ctx,
clientHello: clientHello,
}
return hs.handshake()
hs := serverHandshakeState{
c: c,
+ ctx: ctx,
clientHello: clientHello,
}
return hs.handshake()
}
// readClientHello reads a ClientHello message and selects the protocol version.
-func (c *Conn) readClientHello() (*clientHelloMsg, error) {
+func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) {
msg, err := c.readHandshake()
if err != nil {
return nil, err
var configForClient *Config
originalConfig := c.config
if c.config.GetConfigForClient != nil {
- chi := clientHelloInfo(c, clientHello)
+ chi := clientHelloInfo(ctx, c, clientHello)
if configForClient, err = c.config.GetConfigForClient(chi); err != nil {
c.sendAlert(alertInternalError)
return nil, err
}
}
- hs.cert, err = c.config.getCertificate(clientHelloInfo(c, hs.clientHello))
+ hs.cert, err = c.config.getCertificate(clientHelloInfo(hs.ctx, c, hs.clientHello))
if err != nil {
if err == errNoCertificates {
c.sendAlert(alertUnrecognizedName)
return nil
}
-func clientHelloInfo(c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {
+func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) *ClientHelloInfo {
supportedVersions := clientHello.supportedVersions
if len(clientHello.supportedVersions) == 0 {
supportedVersions = supportedVersionsFromMax(clientHello.vers)
SupportedVersions: supportedVersions,
Conn: c.conn,
config: c.config,
+ ctx: ctx,
}
}
import (
"bytes"
+ "context"
"crypto"
"crypto/elliptic"
"crypto/x509"
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strings"
"testing"
"time"
cli.writeRecord(recordTypeHandshake, m.marshal())
c.Close()
}()
+ ctx := context.Background()
conn := Server(s, serverConfig)
- ch, err := conn.readClientHello()
+ ch, err := conn.readClientHello(ctx)
hs := serverHandshakeState{
c: conn,
+ ctx: ctx,
clientHello: ch,
}
if err == nil {
c.Close()
}()
conn := Server(s, serverConfig)
- ch, err := conn.readClientHello()
+ ctx := context.Background()
+ ch, err := conn.readClientHello(ctx)
hs := serverHandshakeState{
c: conn,
+ ctx: ctx,
clientHello: ch,
}
if err == nil {
t.Errorf("expected RSA certificate, got %v", got)
}
}
+
+func TestServerHandshakeContextCancellation(t *testing.T) {
+ c, s := localPipe(t)
+ clientConfig := testConfig.Clone()
+ clientErr := make(chan error, 1)
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ go func() {
+ defer close(clientErr)
+ defer c.Close()
+ clientHello := &clientHelloMsg{
+ vers: VersionTLS10,
+ random: make([]byte, 32),
+ cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+ compressionMethods: []uint8{compressionNone},
+ }
+ cli := Client(c, clientConfig)
+ _, err := cli.writeRecord(recordTypeHandshake, clientHello.marshal())
+ cancel()
+ clientErr <- err
+ }()
+ conn := Server(s, testConfig)
+ err := conn.HandshakeContext(ctx)
+ if err == nil {
+ t.Fatal("Server handshake did not error when the context was canceled")
+ }
+ if err != context.Canceled {
+ t.Errorf("Unexpected server handshake error: %v", err)
+ }
+ if err := <-clientErr; err != nil {
+ t.Errorf("Unexpected client error: %v", err)
+ }
+ if runtime.GOARCH == "wasm" {
+ t.Skip("conn.Close does not error as expected when called multiple times on WASM")
+ }
+ err = conn.Close()
+ if err == nil {
+ t.Error("Server connection was not closed when the context was canceled")
+ }
+}
import (
"bytes"
+ "context"
"crypto"
"crypto/hmac"
"crypto/rsa"
type serverHandshakeStateTLS13 struct {
c *Conn
+ ctx context.Context
clientHello *clientHelloMsg
hello *serverHelloMsg
sentDummyCCS bool
return c.sendAlert(alertMissingExtension)
}
- certificate, err := c.config.getCertificate(clientHelloInfo(c, hs.clientHello))
+ certificate, err := c.config.getCertificate(clientHelloInfo(hs.ctx, c, hs.clientHello))
if err != nil {
if err == errNoCertificates {
c.sendAlert(alertUnrecognizedName)
"io/ioutil"
"net"
"strings"
- "time"
)
// Server returns a new TLS server side connection
}
func dial(ctx context.Context, netDialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
- // We want the Timeout and Deadline values from dialer to cover the
- // whole process: TCP connection and TLS handshake. This means that we
- // also need to start our own timers now.
- timeout := netDialer.Timeout
-
- if !netDialer.Deadline.IsZero() {
- deadlineTimeout := time.Until(netDialer.Deadline)
- if timeout == 0 || deadlineTimeout < timeout {
- timeout = deadlineTimeout
- }
+ if netDialer.Timeout != 0 {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, netDialer.Timeout)
+ defer cancel()
}
- // hsErrCh is non-nil if we might not wait for Handshake to complete.
- var hsErrCh chan error
- if timeout != 0 || ctx.Done() != nil {
- hsErrCh = make(chan error, 2)
- }
- if timeout != 0 {
- timer := time.AfterFunc(timeout, func() {
- hsErrCh <- timeoutError{}
- })
- defer timer.Stop()
+ if !netDialer.Deadline.IsZero() {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithDeadline(ctx, netDialer.Deadline)
+ defer cancel()
}
rawConn, err := netDialer.DialContext(ctx, network, addr)
}
conn := Client(rawConn, config)
-
- if hsErrCh == nil {
- err = conn.Handshake()
- } else {
- go func() {
- hsErrCh <- conn.Handshake()
- }()
-
- select {
- case <-ctx.Done():
- err = ctx.Err()
- case err = <-hsErrCh:
- if err != nil {
- // If the error was due to the context
- // closing, prefer the context's error, rather
- // than some random network teardown error.
- if e := ctx.Err(); e != nil {
- err = e
- }
- }
- }
- }
-
- if err != nil {
+ if err := conn.HandshakeContext(ctx); err != nil {
rawConn.Close()
return nil, err
}
-
return conn, nil
}
if d := c.server.WriteTimeout; d != 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
- if err := tlsConn.Handshake(); err != nil {
+ if err := tlsConn.HandshakeContext(ctx); err != nil {
// If the handshake failed due to the client not speaking
// TLS, assume they're speaking plaintext HTTP and write a
// 400 response on the TLS conn's underlying net.Conn.
// Add TLS to a persistent connection, i.e. negotiate a TLS session. If pconn is already a TLS
// tunnel, this function establishes a nested TLS session inside the encrypted channel.
// The remote endpoint's name may be overridden by TLSClientConfig.ServerName.
-func (pconn *persistConn) addTLS(name string, trace *httptrace.ClientTrace) error {
+func (pconn *persistConn) addTLS(ctx context.Context, name string, trace *httptrace.ClientTrace) error {
// Initiate TLS and check remote host name against certificate.
cfg := cloneTLSConfig(pconn.t.TLSClientConfig)
if cfg.ServerName == "" {
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
- err := tlsConn.Handshake()
+ err := tlsConn.HandshakeContext(ctx)
if timer != nil {
timer.Stop()
}
if trace != nil && trace.TLSHandshakeStart != nil {
trace.TLSHandshakeStart()
}
- if err := tc.Handshake(); err != nil {
+ if err := tc.HandshakeContext(ctx); err != nil {
go pconn.conn.Close()
if trace != nil && trace.TLSHandshakeDone != nil {
trace.TLSHandshakeDone(tls.ConnectionState{}, err)
if firstTLSHost, _, err = net.SplitHostPort(cm.addr()); err != nil {
return nil, wrapErr(err)
}
- if err = pconn.addTLS(firstTLSHost, trace); err != nil {
+ if err = pconn.addTLS(ctx, firstTLSHost, trace); err != nil {
return nil, wrapErr(err)
}
}
}
if cm.proxyURL != nil && cm.targetScheme == "https" {
- if err := pconn.addTLS(cm.tlsHost(), trace); err != nil {
+ if err := pconn.addTLS(ctx, cm.tlsHost(), trace); err != nil {
return nil, err
}
}
if err != nil {
return nil, err
}
- return c, c.Handshake()
+ return c, c.HandshakeContext(ctx)
}
req, err := NewRequest("GET", ts.URL, nil)