crypto/tls: don't send IPv6 literals and absolute FQDNs as SNI values

This is a followup change to #13111 for filtering out IPv6 literals and
absolute FQDNs from being as the SNI values.

Updates #13111.
Fixes #14404.

Change-Id: I09ab8d2a9153d9a92147e57ca141f2e97ddcef6e
Reviewed-on: https://go-review.googlesource.com/19704
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Mikio Hara 2016-02-19 16:25:52 +09:00
parent 6ae0475759
commit 75d204850c
2 changed files with 61 additions and 16 deletions

View File

@ -16,6 +16,7 @@ import (
"io" "io"
"net" "net"
"strconv" "strconv"
"strings"
) )
type clientHandshakeState struct { type clientHandshakeState struct {
@ -49,20 +50,13 @@ func (c *Conn) clientHandshake() error {
return errors.New("tls: NextProtos values too large") return errors.New("tls: NextProtos values too large")
} }
sni := c.config.ServerName
// IP address literals are not permitted as SNI values. See
// https://tools.ietf.org/html/rfc6066#section-3.
if net.ParseIP(sni) != nil {
sni = ""
}
hello := &clientHelloMsg{ hello := &clientHelloMsg{
vers: c.config.maxVersion(), vers: c.config.maxVersion(),
compressionMethods: []uint8{compressionNone}, compressionMethods: []uint8{compressionNone},
random: make([]byte, 32), random: make([]byte, 32),
ocspStapling: true, ocspStapling: true,
scts: true, scts: true,
serverName: sni, serverName: hostnameInSNI(c.config.ServerName),
supportedCurves: c.config.curvePreferences(), supportedCurves: c.config.curvePreferences(),
supportedPoints: []uint8{pointFormatUncompressed}, supportedPoints: []uint8{pointFormatUncompressed},
nextProtoNeg: len(c.config.NextProtos) > 0, nextProtoNeg: len(c.config.NextProtos) > 0,
@ -665,3 +659,23 @@ func mutualProtocol(protos, preferenceProtos []string) (string, bool) {
return protos[0], true return protos[0], true
} }
// hostnameInSNI converts name into an approriate hostname for SNI.
// Literal IP addresses and absolute FQDNs are not permitted as SNI values.
// See https://tools.ietf.org/html/rfc6066#section-3.
func hostnameInSNI(name string) string {
host := name
if len(host) > 0 && host[0] == '[' && host[len(host)-1] == ']' {
host = host[1 : len(host)-1]
}
if i := strings.LastIndex(host, "%"); i > 0 {
host = host[:i]
}
if net.ParseIP(host) != nil {
return ""
}
if len(name) > 0 && name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
return name
}

View File

@ -618,14 +618,35 @@ func TestHandshakClientSCTs(t *testing.T) {
runClientTestTLS12(t, test) runClientTestTLS12(t, test)
} }
func TestNoIPAddressesInSNI(t *testing.T) { var hostnameInSNITests = []struct {
for _, ipLiteral := range []string{"1.2.3.4", "::1"} { in, out string
}{
// Opaque string
{"", ""},
{"localhost", "localhost"},
{"foo, bar, baz and qux", "foo, bar, baz and qux"},
// DNS hostname
{"golang.org", "golang.org"},
{"golang.org.", "golang.org"},
// Literal IPv4 address
{"1.2.3.4", ""},
// Literal IPv6 address
{"::1", ""},
{"::1%lo0", ""}, // with zone identifier
{"[::1]", ""}, // as per RFC 5952 we allow the [] style as IPv6 literal
{"[::1%lo0]", ""},
}
func TestHostnameInSNI(t *testing.T) {
for _, tt := range hostnameInSNITests {
c, s := net.Pipe() c, s := net.Pipe()
go func() { go func(host string) {
client := Client(c, &Config{ServerName: ipLiteral}) Client(c, &Config{ServerName: host, InsecureSkipVerify: true}).Handshake()
client.Handshake() }(tt.in)
}()
var header [5]byte var header [5]byte
if _, err := io.ReadFull(s, header[:]); err != nil { if _, err := io.ReadFull(s, header[:]); err != nil {
@ -637,10 +658,20 @@ func TestNoIPAddressesInSNI(t *testing.T) {
if _, err := io.ReadFull(s, record[:]); err != nil { if _, err := io.ReadFull(s, record[:]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
c.Close()
s.Close() s.Close()
if bytes.Index(record, []byte(ipLiteral)) != -1 { var m clientHelloMsg
t.Errorf("IP literal %q found in ClientHello: %x", ipLiteral, record) if !m.unmarshal(record) {
t.Errorf("unmarshaling ClientHello for %q failed", tt.in)
continue
}
if tt.in != tt.out && m.serverName == tt.in {
t.Errorf("prohibited %q found in ClientHello: %x", tt.in, record)
}
if m.serverName != tt.out {
t.Errorf("expected %q not found in ClientHello: %x", tt.out, record)
} }
} }
} }