392 lines
11 KiB
Go
392 lines
11 KiB
Go
|
package tlsconfig
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto"
|
||
|
"crypto/ecdsa"
|
||
|
"crypto/elliptic"
|
||
|
"crypto/rand"
|
||
|
"crypto/rsa"
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"crypto/x509/pkix"
|
||
|
"encoding/pem"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"math/big"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
var certTemplate = x509.Certificate{
|
||
|
SerialNumber: big.NewInt(199999),
|
||
|
Subject: pkix.Name{
|
||
|
CommonName: "test",
|
||
|
},
|
||
|
NotBefore: time.Now().AddDate(-1, 1, 1),
|
||
|
NotAfter: time.Now().AddDate(1, 1, 1),
|
||
|
|
||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
|
||
|
|
||
|
BasicConstraintsValid: true,
|
||
|
}
|
||
|
|
||
|
func generateCertificate(t *testing.T, signer crypto.Signer, out io.Writer) {
|
||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, signer.Public(), signer)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to generate a certificate", err.Error())
|
||
|
}
|
||
|
|
||
|
if err = pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||
|
t.Fatal("Unable to write cert to file", err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// generates a multiple-certificate file with both RSA and ECDSA certs and
|
||
|
// returns the filename so that cleanup can be deferred.
|
||
|
func generateMultiCert(t *testing.T, tempDir string) string {
|
||
|
certOut, err := os.Create(filepath.Join(tempDir, "multi"))
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to create file to write multi-cert to", err.Error())
|
||
|
}
|
||
|
defer certOut.Close()
|
||
|
|
||
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to generate RSA key for multi-cert", err.Error())
|
||
|
}
|
||
|
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to generate ECDSA key for multi-cert", err.Error())
|
||
|
}
|
||
|
|
||
|
for _, signer := range []crypto.Signer{rsaKey, ecKey} {
|
||
|
generateCertificate(t, signer, certOut)
|
||
|
}
|
||
|
|
||
|
return certOut.Name()
|
||
|
}
|
||
|
|
||
|
func generateCertAndKey(t *testing.T, tempDir string) (string, string) {
|
||
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to generate RSA key", err.Error())
|
||
|
|
||
|
}
|
||
|
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
|
||
|
|
||
|
keyOut, err := os.Create(filepath.Join(tempDir, "key"))
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to create file to write key to", err.Error())
|
||
|
|
||
|
}
|
||
|
defer keyOut.Close()
|
||
|
|
||
|
if err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}); err != nil {
|
||
|
t.Fatal("Unable to write key to file", err.Error())
|
||
|
}
|
||
|
|
||
|
certOut, err := os.Create(filepath.Join(tempDir, "cert"))
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to create file to write cert to", err.Error())
|
||
|
}
|
||
|
defer certOut.Close()
|
||
|
|
||
|
generateCertificate(t, rsaKey, certOut)
|
||
|
|
||
|
return keyOut.Name(), certOut.Name()
|
||
|
}
|
||
|
|
||
|
func makeTempDir(t *testing.T) string {
|
||
|
tempDir, err := ioutil.TempDir("", "tlsconfig-test")
|
||
|
if err != nil {
|
||
|
t.Fatal("Could not make a temporary directory", err.Error())
|
||
|
}
|
||
|
return tempDir
|
||
|
}
|
||
|
|
||
|
// If the cert files and directory are provided but are invalid, an error is
|
||
|
// returned.
|
||
|
func TestConfigServerTLSFailsIfUnableToLoadCerts(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
ca := generateMultiCert(t, tempDir)
|
||
|
|
||
|
tempFile, err := ioutil.TempFile("", "cert-test")
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to create temporary empty file")
|
||
|
}
|
||
|
defer os.RemoveAll(tempFile.Name())
|
||
|
tempFile.Close()
|
||
|
|
||
|
for _, badFile := range []string{"not-a-file", tempFile.Name()} {
|
||
|
for i := 0; i < 3; i++ {
|
||
|
files := []string{cert, key, ca}
|
||
|
files[i] = badFile
|
||
|
|
||
|
result, err := Server(Options{
|
||
|
CertFile: files[0],
|
||
|
KeyFile: files[1],
|
||
|
CAFile: files[2],
|
||
|
ClientAuth: tls.VerifyClientCertIfGiven,
|
||
|
})
|
||
|
if err == nil || result != nil {
|
||
|
t.Fatal("Expected a non-real file to error and return a nil TLS config")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If server cert and key are provided and client auth and client CA are not
|
||
|
// set, a tls config with only the server certs will be returned.
|
||
|
func TestConfigServerTLSServerCertsOnly(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
|
||
|
keypair, err := tls.LoadX509KeyPair(cert, key)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to load the generated cert and key")
|
||
|
}
|
||
|
|
||
|
tlsConfig, err := Server(Options{
|
||
|
CertFile: cert,
|
||
|
KeyFile: key,
|
||
|
})
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure server TLS", err)
|
||
|
}
|
||
|
|
||
|
if len(tlsConfig.Certificates) != 1 {
|
||
|
t.Fatal("Unexpected server certificates")
|
||
|
}
|
||
|
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
|
||
|
t.Fatal("Unexpected server certificates")
|
||
|
}
|
||
|
for i, cert := range tlsConfig.Certificates[0].Certificate {
|
||
|
if !bytes.Equal(cert, keypair.Certificate[i]) {
|
||
|
t.Fatal("Unexpected server certificates")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(tlsConfig.CipherSuites, DefaultServerAcceptedCiphers) {
|
||
|
t.Fatal("Unexpected server cipher suites")
|
||
|
}
|
||
|
if !tlsConfig.PreferServerCipherSuites {
|
||
|
t.Fatal("Expected server to prefer cipher suites")
|
||
|
}
|
||
|
if tlsConfig.MinVersion != tls.VersionTLS10 {
|
||
|
t.Fatal("Unexpected server TLS version")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If client CA is provided, it will only be used if the client auth is >=
|
||
|
// VerifyClientCertIfGiven
|
||
|
func TestConfigServerTLSClientCANotSetIfClientAuthTooLow(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
ca := generateMultiCert(t, tempDir)
|
||
|
|
||
|
tlsConfig, err := Server(Options{
|
||
|
CertFile: cert,
|
||
|
KeyFile: key,
|
||
|
ClientAuth: tls.RequestClientCert,
|
||
|
CAFile: ca,
|
||
|
})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure server TLS", err)
|
||
|
}
|
||
|
|
||
|
if len(tlsConfig.Certificates) != 1 {
|
||
|
t.Fatal("Unexpected server certificates")
|
||
|
}
|
||
|
if tlsConfig.ClientAuth != tls.RequestClientCert {
|
||
|
t.Fatal("ClientAuth was not set to what was in the options")
|
||
|
}
|
||
|
if tlsConfig.ClientCAs != nil {
|
||
|
t.Fatalf("Client CAs should never have been set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If client CA is provided, it will only be used if the client auth is >=
|
||
|
// VerifyClientCertIfGiven
|
||
|
func TestConfigServerTLSClientCASet(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
ca := generateMultiCert(t, tempDir)
|
||
|
|
||
|
tlsConfig, err := Server(Options{
|
||
|
CertFile: cert,
|
||
|
KeyFile: key,
|
||
|
ClientAuth: tls.VerifyClientCertIfGiven,
|
||
|
CAFile: ca,
|
||
|
})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure server TLS", err)
|
||
|
}
|
||
|
|
||
|
if len(tlsConfig.Certificates) != 1 {
|
||
|
t.Fatal("Unexpected server certificates")
|
||
|
}
|
||
|
if tlsConfig.ClientAuth != tls.VerifyClientCertIfGiven {
|
||
|
t.Fatal("ClientAuth was not set to what was in the options")
|
||
|
}
|
||
|
if tlsConfig.ClientCAs == nil || len(tlsConfig.ClientCAs.Subjects()) != 2 {
|
||
|
t.Fatalf("Client CAs were never set correctly")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The root CA is never set if InsecureSkipBoolean is set to true, but the
|
||
|
// default client options are set
|
||
|
func TestConfigClientTLSNoVerify(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
ca := generateMultiCert(t, tempDir)
|
||
|
|
||
|
tlsConfig, err := Client(Options{CAFile: ca, InsecureSkipVerify: true})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure client TLS", err)
|
||
|
}
|
||
|
|
||
|
if tlsConfig.RootCAs != nil {
|
||
|
t.Fatal("Should not have set Root CAs", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
|
||
|
t.Fatal("Unexpected client cipher suites")
|
||
|
}
|
||
|
if tlsConfig.MinVersion != tls.VersionTLS12 {
|
||
|
t.Fatal("Unexpected client TLS version")
|
||
|
}
|
||
|
|
||
|
if tlsConfig.Certificates != nil {
|
||
|
t.Fatal("Somehow client certificates were set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The root CA is never set if InsecureSkipBoolean is set to false and root CA
|
||
|
// is not provided.
|
||
|
func TestConfigClientTLSNoRoot(t *testing.T) {
|
||
|
tlsConfig, err := Client(Options{})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure client TLS", err)
|
||
|
}
|
||
|
|
||
|
if tlsConfig.RootCAs != nil {
|
||
|
t.Fatal("Should not have set Root CAs", err)
|
||
|
}
|
||
|
|
||
|
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
|
||
|
t.Fatal("Unexpected client cipher suites")
|
||
|
}
|
||
|
if tlsConfig.MinVersion != tls.VersionTLS12 {
|
||
|
t.Fatal("Unexpected client TLS version")
|
||
|
}
|
||
|
|
||
|
if tlsConfig.Certificates != nil {
|
||
|
t.Fatal("Somehow client certificates were set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The RootCA is set if the file is provided and InsecureSkipVerify is false
|
||
|
func TestConfigClientTLSRootCAFileWithOneCert(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
ca := generateMultiCert(t, tempDir)
|
||
|
|
||
|
tlsConfig, err := Client(Options{CAFile: ca})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure client TLS", err)
|
||
|
}
|
||
|
|
||
|
if tlsConfig.RootCAs == nil || len(tlsConfig.RootCAs.Subjects()) != 2 {
|
||
|
t.Fatal("Root CAs not set properly", err)
|
||
|
}
|
||
|
if tlsConfig.Certificates != nil {
|
||
|
t.Fatal("Somehow client certificates were set")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// An error is returned if a root CA is provided but the file doesn't exist.
|
||
|
func TestConfigClientTLSNonexistentRootCAFile(t *testing.T) {
|
||
|
tlsConfig, err := Client(Options{CAFile: "nonexistent"})
|
||
|
|
||
|
if err == nil || tlsConfig != nil {
|
||
|
t.Fatal("Should not have been able to configure client TLS", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// An error is returned if either the client cert or the key are provided
|
||
|
// but invalid or blank.
|
||
|
func TestConfigClientTLSClientCertOrKeyInvalid(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
|
||
|
tempFile, err := ioutil.TempFile("", "cert-test")
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to create temporary empty file")
|
||
|
}
|
||
|
defer os.Remove(tempFile.Name())
|
||
|
tempFile.Close()
|
||
|
|
||
|
for i := 0; i < 2; i++ {
|
||
|
for _, invalid := range []string{"not-a-file", "", tempFile.Name()} {
|
||
|
files := []string{cert, key}
|
||
|
files[i] = invalid
|
||
|
|
||
|
tlsConfig, err := Client(Options{CertFile: files[0], KeyFile: files[1]})
|
||
|
if err == nil || tlsConfig != nil {
|
||
|
t.Fatal("Should not have been able to configure client TLS", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The certificate is set if the client cert and client key are provided and
|
||
|
// valid.
|
||
|
func TestConfigClientTLSValidClientCertAndKey(t *testing.T) {
|
||
|
tempDir := makeTempDir(t)
|
||
|
defer os.RemoveAll(tempDir)
|
||
|
key, cert := generateCertAndKey(t, tempDir)
|
||
|
|
||
|
keypair, err := tls.LoadX509KeyPair(cert, key)
|
||
|
if err != nil {
|
||
|
t.Fatal("Unable to load the generated cert and key")
|
||
|
}
|
||
|
|
||
|
tlsConfig, err := Client(Options{CertFile: cert, KeyFile: key})
|
||
|
|
||
|
if err != nil || tlsConfig == nil {
|
||
|
t.Fatal("Unable to configure client TLS", err)
|
||
|
}
|
||
|
|
||
|
if len(tlsConfig.Certificates) != 1 {
|
||
|
t.Fatal("Unexpected client certificates")
|
||
|
}
|
||
|
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
|
||
|
t.Fatal("Unexpected client certificates")
|
||
|
}
|
||
|
for i, cert := range tlsConfig.Certificates[0].Certificate {
|
||
|
if !bytes.Equal(cert, keypair.Certificate[i]) {
|
||
|
t.Fatal("Unexpected client certificates")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if tlsConfig.RootCAs != nil {
|
||
|
t.Fatal("Root CAs should not have been set", err)
|
||
|
}
|
||
|
}
|