9c9b97f554
Strengthening VerifyHostname exposed the fact that for resumed connections, ConnectionState().VerifiedChains was not being saved and restored during the ClientSessionCache operations. Do that. This change just saves the verified chains in the client's session cache. It does not re-verify the certificates when resuming a connection. There are arguments both ways about this: we want fast, light-weight resumption connections (thus suggesting that we shouldn't verify) but it could also be a little surprising that, if the verification config is changed, that would be ignored if the same session cache is used. On the server side we do re-verify client-auth certificates, but the situation is a little different there. The client session cache is an object in memory that's reset each time the process restarts. But the server's session cache is a conceptual object, held by the clients, so can persist across server restarts. Thus the chance of a change in verification config being surprisingly ignored is much higher in the server case. Fixes #12024. Change-Id: I3081029623322ce3d9f4f3819659fdd9a381db16 Reviewed-on: https://go-review.googlesource.com/13164 Reviewed-by: Russ Cox <rsc@golang.org> Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Adam Langley <agl@golang.org>
335 lines
9.6 KiB
Go
335 lines
9.6 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package tls
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var rsaCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
|
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
|
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
|
|
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
|
|
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
|
|
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
|
|
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
|
|
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
|
|
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
|
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
|
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
|
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
|
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
|
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
|
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
|
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
|
-----END RSA PRIVATE KEY-----
|
|
`
|
|
|
|
// keyPEM is the same as rsaKeyPEM, but declares itself as just
|
|
// "PRIVATE KEY", not "RSA PRIVATE KEY". https://golang.org/issue/4477
|
|
var keyPEM = `-----BEGIN PRIVATE KEY-----
|
|
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
|
|
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
|
|
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
|
|
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
|
|
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
|
|
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
|
|
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
|
|
-----END PRIVATE KEY-----
|
|
`
|
|
|
|
var ecdsaCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIB/jCCAWICCQDscdUxw16XFDAJBgcqhkjOPQQBMEUxCzAJBgNVBAYTAkFVMRMw
|
|
EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
|
|
eSBMdGQwHhcNMTIxMTE0MTI0MDQ4WhcNMTUxMTE0MTI0MDQ4WjBFMQswCQYDVQQG
|
|
EwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lk
|
|
Z2l0cyBQdHkgTHRkMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBY9+my9OoeSUR
|
|
lDQdV/x8LsOuLilthhiS1Tz4aGDHIPwC1mlvnf7fg5lecYpMCrLLhauAc1UJXcgl
|
|
01xoLuzgtAEAgv2P/jgytzRSpUYvgLBt1UA0leLYBy6mQQbrNEuqT3INapKIcUv8
|
|
XxYP0xMEUksLPq6Ca+CRSqTtrd/23uTnapkwCQYHKoZIzj0EAQOBigAwgYYCQXJo
|
|
A7Sl2nLVf+4Iu/tAX/IF4MavARKC4PPHK3zfuGfPR3oCCcsAoz3kAzOeijvd0iXb
|
|
H5jBImIxPL4WxQNiBTexAkF8D1EtpYuWdlVQ80/h/f4pBcGiXPqX5h2PQSQY7hP1
|
|
+jwM1FGS4fREIOvlBYr/SzzQRtwrvrzGYxDEDbsC0ZGRnA==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
var ecdsaKeyPEM = `-----BEGIN EC PARAMETERS-----
|
|
BgUrgQQAIw==
|
|
-----END EC PARAMETERS-----
|
|
-----BEGIN EC PRIVATE KEY-----
|
|
MIHcAgEBBEIBrsoKp0oqcv6/JovJJDoDVSGWdirrkgCWxrprGlzB9o0X8fV675X0
|
|
NwuBenXFfeZvVcwluO7/Q9wkYoPd/t3jGImgBwYFK4EEACOhgYkDgYYABAFj36bL
|
|
06h5JRGUNB1X/Hwuw64uKW2GGJLVPPhoYMcg/ALWaW+d/t+DmV5xikwKssuFq4Bz
|
|
VQldyCXTXGgu7OC0AQCC/Y/+ODK3NFKlRi+AsG3VQDSV4tgHLqZBBus0S6pPcg1q
|
|
kohxS/xfFg/TEwRSSws+roJr4JFKpO2t3/be5OdqmQ==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
|
|
var keyPairTests = []struct {
|
|
algo string
|
|
cert string
|
|
key string
|
|
}{
|
|
{"ECDSA", ecdsaCertPEM, ecdsaKeyPEM},
|
|
{"RSA", rsaCertPEM, rsaKeyPEM},
|
|
{"RSA-untyped", rsaCertPEM, keyPEM}, // golang.org/issue/4477
|
|
}
|
|
|
|
func TestX509KeyPair(t *testing.T) {
|
|
var pem []byte
|
|
for _, test := range keyPairTests {
|
|
pem = []byte(test.cert + test.key)
|
|
if _, err := X509KeyPair(pem, pem); err != nil {
|
|
t.Errorf("Failed to load %s cert followed by %s key: %s", test.algo, test.algo, err)
|
|
}
|
|
pem = []byte(test.key + test.cert)
|
|
if _, err := X509KeyPair(pem, pem); err != nil {
|
|
t.Errorf("Failed to load %s key followed by %s cert: %s", test.algo, test.algo, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestX509MixedKeyPair(t *testing.T) {
|
|
if _, err := X509KeyPair([]byte(rsaCertPEM), []byte(ecdsaKeyPEM)); err == nil {
|
|
t.Error("Load of RSA certificate succeeded with ECDSA private key")
|
|
}
|
|
if _, err := X509KeyPair([]byte(ecdsaCertPEM), []byte(rsaKeyPEM)); err == nil {
|
|
t.Error("Load of ECDSA certificate succeeded with RSA private key")
|
|
}
|
|
}
|
|
|
|
func newLocalListener(t *testing.T) net.Listener {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
ln, err = net.Listen("tcp6", "[::1]:0")
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ln
|
|
}
|
|
|
|
func TestDialTimeout(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
listener := newLocalListener(t)
|
|
|
|
addr := listener.Addr().String()
|
|
defer listener.Close()
|
|
|
|
complete := make(chan bool)
|
|
defer close(complete)
|
|
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
<-complete
|
|
conn.Close()
|
|
}()
|
|
|
|
dialer := &net.Dialer{
|
|
Timeout: 10 * time.Millisecond,
|
|
}
|
|
|
|
var err error
|
|
if _, err = DialWithDialer(dialer, "tcp", addr, nil); err == nil {
|
|
t.Fatal("DialWithTimeout completed successfully")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "timed out") {
|
|
t.Errorf("resulting error not a timeout: %s", err)
|
|
}
|
|
}
|
|
|
|
// tests that Conn.Read returns (non-zero, io.EOF) instead of
|
|
// (non-zero, nil) when a Close (alertCloseNotify) is sitting right
|
|
// behind the application data in the buffer.
|
|
func TestConnReadNonzeroAndEOF(t *testing.T) {
|
|
// This test is racy: it assumes that after a write to a
|
|
// localhost TCP connection, the peer TCP connection can
|
|
// immediately read it. Because it's racy, we skip this test
|
|
// in short mode, and then retry it several times with an
|
|
// increasing sleep in between our final write (via srv.Close
|
|
// below) and the following read.
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
var err error
|
|
for delay := time.Millisecond; delay <= 64*time.Millisecond; delay *= 2 {
|
|
if err = testConnReadNonzeroAndEOF(t, delay); err == nil {
|
|
return
|
|
}
|
|
}
|
|
t.Error(err)
|
|
}
|
|
|
|
func testConnReadNonzeroAndEOF(t *testing.T, delay time.Duration) error {
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
|
|
srvCh := make(chan *Conn, 1)
|
|
var serr error
|
|
go func() {
|
|
sconn, err := ln.Accept()
|
|
if err != nil {
|
|
serr = err
|
|
srvCh <- nil
|
|
return
|
|
}
|
|
serverConfig := *testConfig
|
|
srv := Server(sconn, &serverConfig)
|
|
if err := srv.Handshake(); err != nil {
|
|
serr = fmt.Errorf("handshake: %v", err)
|
|
srvCh <- nil
|
|
return
|
|
}
|
|
srvCh <- srv
|
|
}()
|
|
|
|
clientConfig := *testConfig
|
|
conn, err := Dial("tcp", ln.Addr().String(), &clientConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
srv := <-srvCh
|
|
if srv == nil {
|
|
return serr
|
|
}
|
|
|
|
buf := make([]byte, 6)
|
|
|
|
srv.Write([]byte("foobar"))
|
|
n, err := conn.Read(buf)
|
|
if n != 6 || err != nil || string(buf) != "foobar" {
|
|
return fmt.Errorf("Read = %d, %v, data %q; want 6, nil, foobar", n, err, buf)
|
|
}
|
|
|
|
srv.Write([]byte("abcdef"))
|
|
srv.Close()
|
|
time.Sleep(delay)
|
|
n, err = conn.Read(buf)
|
|
if n != 6 || string(buf) != "abcdef" {
|
|
return fmt.Errorf("Read = %d, buf= %q; want 6, abcdef", n, buf)
|
|
}
|
|
if err != io.EOF {
|
|
return fmt.Errorf("Second Read error = %v; want io.EOF", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestTLSUniqueMatches(t *testing.T) {
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
|
|
serverTLSUniques := make(chan []byte)
|
|
go func() {
|
|
for i := 0; i < 2; i++ {
|
|
sconn, err := ln.Accept()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serverConfig := *testConfig
|
|
srv := Server(sconn, &serverConfig)
|
|
if err := srv.Handshake(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
serverTLSUniques <- srv.ConnectionState().TLSUnique
|
|
}
|
|
}()
|
|
|
|
clientConfig := *testConfig
|
|
clientConfig.ClientSessionCache = NewLRUClientSessionCache(1)
|
|
conn, err := Dial("tcp", ln.Addr().String(), &clientConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(conn.ConnectionState().TLSUnique, <-serverTLSUniques) {
|
|
t.Error("client and server channel bindings differ")
|
|
}
|
|
conn.Close()
|
|
|
|
conn, err = Dial("tcp", ln.Addr().String(), &clientConfig)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
if !conn.ConnectionState().DidResume {
|
|
t.Error("second session did not use resumption")
|
|
}
|
|
if !bytes.Equal(conn.ConnectionState().TLSUnique, <-serverTLSUniques) {
|
|
t.Error("client and server channel bindings differ when session resumption is used")
|
|
}
|
|
}
|
|
|
|
func TestVerifyHostname(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
c, err := Dial("tcp", "www.google.com:https", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.VerifyHostname("www.google.com"); err != nil {
|
|
t.Fatalf("verify www.google.com: %v", err)
|
|
}
|
|
if err := c.VerifyHostname("www.yahoo.com"); err == nil {
|
|
t.Fatalf("verify www.yahoo.com succeeded")
|
|
}
|
|
|
|
c, err = Dial("tcp", "www.google.com:https", &Config{InsecureSkipVerify: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.VerifyHostname("www.google.com"); err == nil {
|
|
t.Fatalf("verify www.google.com succeeded with InsecureSkipVerify=true")
|
|
}
|
|
if err := c.VerifyHostname("www.yahoo.com"); err == nil {
|
|
t.Fatalf("verify www.google.com succeeded with InsecureSkipVerify=true")
|
|
}
|
|
}
|
|
|
|
func TestVerifyHostnameResumed(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
config := &Config{
|
|
ClientSessionCache: NewLRUClientSessionCache(32),
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
c, err := Dial("tcp", "www.google.com:https", config)
|
|
if err != nil {
|
|
t.Fatalf("Dial #%d: %v", i, err)
|
|
}
|
|
cs := c.ConnectionState()
|
|
if i > 0 && !cs.DidResume {
|
|
t.Fatalf("Subsequent connection unexpectedly didn't resume")
|
|
}
|
|
if cs.VerifiedChains == nil {
|
|
t.Fatalf("Dial #%d: cs.VerifiedChains == nil", i)
|
|
}
|
|
if err := c.VerifyHostname("www.google.com"); err != nil {
|
|
t.Fatalf("verify www.google.com #%d: %v", i, err)
|
|
}
|
|
c.Close()
|
|
}
|
|
}
|