diff --git a/Makefile b/Makefile index 000314b..063c2a2 100644 --- a/Makefile +++ b/Makefile @@ -17,4 +17,22 @@ GOFILES=\ prf.go\ tls.go\ +ifeq ($(CGO_ENABLED),1) +CGOFILES_darwin=\ + root_darwin.go +else +GOFILES_darwin+=root_stub.go +endif + +GOFILES_freebsd+=root_unix.go +GOFILES_linux+=root_unix.go +GOFILES_openbsd+=root_unix.go +GOFILES_plan9+=root_stub.go +GOFILES_windows+=root_stub.go + +GOFILES+=$(GOFILES_$(GOOS)) +ifneq ($(CGOFILES_$(GOOS)),) +CGOFILES+=$(CGOFILES_$(GOOS)) +endif + include ../../../Make.pkg diff --git a/common.go b/common.go index 8b4dafb..ea52085 100644 --- a/common.go +++ b/common.go @@ -9,7 +9,6 @@ import ( "crypto/rsa" "crypto/x509" "io" - "io/ioutil" "strings" "sync" "time" @@ -155,6 +154,14 @@ type Config struct { // anything more than self-signed. AuthenticateClient bool + // InsecureSkipVerify controls whether a client verifies the + // server's certificate chain and host name. + // If InsecureSkipVerify is true, TLS accepts any certificate + // presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to man-in-the-middle attacks. + // This should be used only for testing. + InsecureSkipVerify bool + // CipherSuites is a list of supported cipher suites. If CipherSuites // is nil, TLS uses a list of suites supported by the implementation. CipherSuites []uint16 @@ -284,15 +291,6 @@ func defaultConfig() *Config { return &emptyConfig } -// Possible certificate files; stop after finding one. -// On OS X we should really be using the Directory Services keychain -// but that requires a lot of Mach goo to get at. Instead we use -// the same root set that curl uses. -var certFiles = []string{ - "/etc/ssl/certs/ca-certificates.crt", // Linux etc - "/usr/share/curl/curl-ca-bundle.crt", // OS X -} - var once sync.Once func defaultRoots() *x509.CertPool { @@ -310,21 +308,10 @@ func initDefaults() { initDefaultCipherSuites() } -var varDefaultRoots *x509.CertPool - -func initDefaultRoots() { - roots := x509.NewCertPool() - for _, file := range certFiles { - data, err := ioutil.ReadFile(file) - if err == nil { - roots.AppendCertsFromPEM(data) - break - } - } - varDefaultRoots = roots -} - -var varDefaultCipherSuites []uint16 +var ( + varDefaultRoots *x509.CertPool + varDefaultCipherSuites []uint16 +) func initDefaultCipherSuites() { varDefaultCipherSuites = make([]uint16, len(cipherSuites)) diff --git a/handshake_client.go b/handshake_client.go index 0badc39..575a121 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -97,11 +97,9 @@ func (c *Conn) clientHandshake() os.Error { certs[i] = cert } - // If we don't have a root CA set configured then anything is accepted. - // TODO(rsc): Find certificates for OS X 10.6. - if c.config.RootCAs != nil { + if !c.config.InsecureSkipVerify { opts := x509.VerifyOptions{ - Roots: c.config.RootCAs, + Roots: c.config.rootCAs(), CurrentTime: c.config.time(), DNSName: c.config.ServerName, Intermediates: x509.NewCertPool(), diff --git a/handshake_server_test.go b/handshake_server_test.go index 9873eb3..1939f3d 100644 --- a/handshake_server_test.go +++ b/handshake_server_test.go @@ -38,6 +38,7 @@ func init() { testConfig.Certificates[0].Certificate = [][]byte{testCertificate} testConfig.Certificates[0].PrivateKey = testPrivateKey testConfig.CipherSuites = []uint16{TLS_RSA_WITH_RC4_128_SHA} + testConfig.InsecureSkipVerify = true } func testClientHelloFailure(t *testing.T, m handshakeMessage, expected os.Error) { diff --git a/root_darwin.go b/root_darwin.go new file mode 100644 index 0000000..1512241 --- /dev/null +++ b/root_darwin.go @@ -0,0 +1,95 @@ +// Copyright 2011 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 + +/* +// Note: We disable -Werror here because the code in this file uses a deprecated API to stay +// compatible with both Mac OS X 10.6 and 10.7. Using a deprecated function on Darwin generates +// a warning. +#cgo CFLAGS: -Wno-error +#cgo LDFLAGS: -framework CoreFoundation -framework Security +#include +#include + +// FetchPEMRoots fetches the system's list of trusted X.509 root certificates. +// +// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root +// certificates of the system. On failure, the function returns -1. +// +// Note: The CFDataRef returned in pemRoots must be released (using CFRelease) after +// we've consumed its content. +int FetchPEMRoots(CFDataRef *pemRoots) { + if (pemRoots == NULL) { + return -1; + } + + CFArrayRef certs = NULL; + OSStatus err = SecTrustCopyAnchorCertificates(&certs); + if (err != noErr) { + return -1; + } + + CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0); + int i, ncerts = CFArrayGetCount(certs); + for (i = 0; i < ncerts; i++) { + CFDataRef data = NULL; + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i); + if (cert == NULL) { + continue; + } + + // SecKeychainImportExport is deprecated in >= OS X 10.7, and has been replaced by + // SecItemExport. If we're built on a host with a Lion SDK, this code gets conditionally + // included in the output, also for binaries meant for 10.6. + // + // To make sure that we run on both Mac OS X 10.6 and 10.7 we use weak linking + // and check whether SecItemExport is available before we attempt to call it. On + // 10.6, this won't be the case, and we'll fall back to calling SecKeychainItemExport. +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + if (SecItemExport) { + err = SecItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); + if (err != noErr) { + continue; + } + } else +#endif + if (data == NULL) { + err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data); + if (err != noErr) { + continue; + } + } + + if (data != NULL) { + CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data)); + CFRelease(data); + } + } + + CFRelease(certs); + + *pemRoots = combinedData; + return 0; +} +*/ +import "C" +import ( + "crypto/x509" + "unsafe" +) + +func initDefaultRoots() { + roots := x509.NewCertPool() + + var data C.CFDataRef = nil + err := C.FetchPEMRoots(&data) + if err != -1 { + defer C.CFRelease(C.CFTypeRef(data)) + buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data))) + roots.AppendCertsFromPEM(buf) + } + + varDefaultRoots = roots +} diff --git a/root_stub.go b/root_stub.go new file mode 100644 index 0000000..1903eed --- /dev/null +++ b/root_stub.go @@ -0,0 +1,8 @@ +// Copyright 2011 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 + +func initDefaultRoots() { +} diff --git a/root_test.go b/root_test.go new file mode 100644 index 0000000..95a89d8 --- /dev/null +++ b/root_test.go @@ -0,0 +1,36 @@ +// Copyright 2011 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 ( + "testing" +) + +var tlsServers = []string{ + "google.com:443", + "github.com:443", + "twitter.com:443", +} + +func TestOSCertBundles(t *testing.T) { + defaultRoots() + + if testing.Short() { + t.Logf("skipping certificate tests in short mode") + return + } + + for _, addr := range tlsServers { + conn, err := Dial("tcp", addr, nil) + if err != nil { + t.Errorf("unable to verify %v: %v", addr, err) + continue + } + err = conn.Close() + if err != nil { + t.Error(err) + } + } +} diff --git a/root_unix.go b/root_unix.go new file mode 100644 index 0000000..57af92a --- /dev/null +++ b/root_unix.go @@ -0,0 +1,27 @@ +// Copyright 2011 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 ( + "crypto/x509" + "io/ioutil" +) + +// Possible certificate files; stop after finding one. +var certFiles = []string{ + "/etc/ssl/certs/ca-certificates.crt", // Linux etc +} + +func initDefaultRoots() { + roots := x509.NewCertPool() + for _, file := range certFiles { + data, err := ioutil.ReadFile(file) + if err == nil { + roots.AppendCertsFromPEM(data) + break + } + } + varDefaultRoots = roots +}