From a4514c42ddda3d101fead04c0783182e2fd49cd0 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sat, 11 Sep 2010 23:41:12 -0400 Subject: [PATCH] http: check https certificate against host name Fixes #1093. R=agl, agl1 CC=golang-dev https://golang.org/cl/2115045 --- src/pkg/crypto/tls/conn.go | 7 +++++++ src/pkg/crypto/x509/x509.go | 28 +++++++++++++++++++++++----- src/pkg/crypto/x509/x509_test.go | 4 ++-- src/pkg/http/client.go | 16 +++++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/pkg/crypto/tls/conn.go b/src/pkg/crypto/tls/conn.go index 85e76a00c02..78566fa8c53 100644 --- a/src/pkg/crypto/tls/conn.go +++ b/src/pkg/crypto/tls/conn.go @@ -670,3 +670,10 @@ func (c *Conn) PeerCertificates() []*x509.Certificate { return c.peerCertificates } + +// VerifyHostname checks that the peer certificate chain is valid for +// connecting to host. If so, it returns nil; if not, it returns an os.Error +// describing the problem. +func (c *Conn) VerifyHostname(host string) os.Error { + return c.PeerCertificates()[0].VerifyHostname(host) +} diff --git a/src/pkg/crypto/x509/x509.go b/src/pkg/crypto/x509/x509.go index e4a05d3ef09..3d940e585b4 100644 --- a/src/pkg/crypto/x509/x509.go +++ b/src/pkg/crypto/x509/x509.go @@ -426,19 +426,37 @@ func matchHostnames(pattern, host string) bool { return true } -// IsValidForHost returns true iff c is a valid certificate for the given host. -func (c *Certificate) IsValidForHost(h string) bool { +type HostnameError struct { + Certificate *Certificate + Host string +} + +func (h *HostnameError) String() string { + var valid string + c := h.Certificate + if len(c.DNSNames) > 0 { + valid = strings.Join(c.DNSNames, ", ") + } else { + valid = c.Subject.CommonName + } + return "certificate is valid for " + valid + ", not " + h.Host +} + +// VerifyHostname returns nil if c is a valid certificate for the named host. +// Otherwise it returns an os.Error describing the mismatch. +func (c *Certificate) VerifyHostname(h string) os.Error { if len(c.DNSNames) > 0 { for _, match := range c.DNSNames { if matchHostnames(match, h) { - return true + return nil } } // If Subject Alt Name is given, we ignore the common name. - return false + } else if matchHostnames(c.Subject.CommonName, h) { + return nil } - return matchHostnames(c.Subject.CommonName, h) + return &HostnameError{c, h} } type UnhandledCriticalExtension struct{} diff --git a/src/pkg/crypto/x509/x509_test.go b/src/pkg/crypto/x509/x509_test.go index fa87fe26abc..12292c1b2e8 100644 --- a/src/pkg/crypto/x509/x509_test.go +++ b/src/pkg/crypto/x509/x509_test.go @@ -96,8 +96,8 @@ func TestCertificateParse(t *testing.T) { t.Error(err) } - if !certs[0].IsValidForHost("mail.google.com") { - t.Errorf("cert not valid for host") + if err := certs[0].VerifyHostname("mail.google.com"); err != nil { + t.Error(err) } } diff --git a/src/pkg/http/client.go b/src/pkg/http/client.go index d77bf0e7594..41e571c2c1b 100644 --- a/src/pkg/http/client.go +++ b/src/pkg/http/client.go @@ -59,11 +59,21 @@ func send(req *Request) (resp *Response, err os.Error) { var conn io.ReadWriteCloser if req.URL.Scheme == "http" { conn, err = net.Dial("tcp", "", addr) + if err != nil { + return nil, err + } } else { // https conn, err = tls.Dial("tcp", "", addr) - } - if err != nil { - return nil, err + if err != nil { + return nil, err + } + h := req.URL.Host + if hasPort(h) { + h = h[0:strings.LastIndex(h, ":")] + } + if err := conn.(*tls.Conn).VerifyHostname(h); err != nil { + return nil, err + } } err = req.Write(conn)