Skip to content

Commit

Permalink
feat: support loading client certificate CAs from directories
Browse files Browse the repository at this point in the history
  • Loading branch information
joshiste committed Jul 28, 2023
1 parent 65878fa commit a4f3d26
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 80 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.8.4

- Support loading client certificates from directories

## 1.8.3

- reload tls server key pair if the file changes
Expand Down
18 changes: 13 additions & 5 deletions exthttp/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,19 @@ func prepareHttpsServer(port int, spec ListenSpecification) (*http.Server, func(
func loadCertPool(filePaths []string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
for _, filePath := range filePaths {
caCert, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
pool.AppendCertsFromPEM(caCert)
_ = filepath.Walk(filePath, func(path string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
caCert, err := os.ReadFile(path)
if err == nil {
log.Debug().Msgf("Loading CA certificate from %s", path)
pool.AppendCertsFromPEM(caCert)
} else {
log.Error().Err(err).Msgf("Failed to read CA certificate from %s", path)
}
return nil
})
}
return pool, nil
}
88 changes: 65 additions & 23 deletions exthttp/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/madflojo/testcerts"
"github.com/phayes/freeport"
"github.com/stretchr/testify/require"
"net"
Expand Down Expand Up @@ -76,90 +77,131 @@ func TestStartHttpServer(t *testing.T) {
}

func TestStartHttpsServer(t *testing.T) {
certs, err := testcerts.NewCA().NewKeyPair("localhost")
require.NoError(t, err)

cert, key, err := certs.ToTempFile(t.TempDir())
require.NoError(t, err)

port, err := freeport.GetFreePort()
require.NoError(t, err)

server, start, err := prepareHttpsServer(port, ListenSpecification{
TlsServerCert: "testdata/cert.pem",
TlsServerKey: "testdata/key.pem",
TlsServerCert: cert.Name(),
TlsServerKey: key.Name(),
})
require.NoError(t, err)

go start()
defer server.Close()

_, err = http.Get(fmt.Sprintf("https://localhost:%d", port))
require.ErrorContains(t, err, "certificate")
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(certs.PublicKey())

client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
},
}
_, err = client.Get(fmt.Sprintf("https://localhost:%d", port))
require.NoError(t, err)
}

func TestStartHttpsServerMustFailWhenCertificateCannotBeFound(t *testing.T) {
_, key, err := testcerts.GenerateCertsToTempFile(t.TempDir())
require.NoError(t, err)

port, err := freeport.GetFreePort()
require.NoError(t, err)

_, _, err = prepareHttpsServer(port, ListenSpecification{
TlsServerCert: "testdata/unknown.pem",
TlsServerKey: "testdata/key.pem",
TlsServerCert: filepath.Join(t.TempDir(), "unknown.pem"),
TlsServerKey: key,
})
require.ErrorContains(t, err, "no such file or directory")
}

func TestStartHttpsServerMustFailWhenKeyCannotBeFound(t *testing.T) {
_, key, err := testcerts.GenerateCertsToTempFile(t.TempDir())
require.NoError(t, err)

port, err := freeport.GetFreePort()
require.NoError(t, err)

_, _, err = prepareHttpsServer(port, ListenSpecification{
TlsServerCert: "testdata/cert.pem",
TlsServerKey: "testdata/unknown.pem",
TlsServerCert: key,
TlsServerKey: filepath.Join(t.TempDir(), "unknown.pem"),
})
require.ErrorContains(t, err, "no such file or directory")
}

func TestStartHttpsServerWithMutualTlsMustRefuseConnectionsWithoutMutualTls(t *testing.T) {
ca := testcerts.NewCA()
caCerts, _, err := ca.ToTempFile(t.TempDir())
require.NoError(t, err)

serverPair, err := ca.NewKeyPair("localhost")
require.NoError(t, err)
serverCert, serverKey, err := serverPair.ToTempFile(t.TempDir())
require.NoError(t, err)

port, err := freeport.GetFreePort()
require.NoError(t, err)

server, start, err := prepareHttpsServer(port, ListenSpecification{
TlsServerCert: "testdata/cert.pem",
TlsServerKey: "testdata/key.pem",
TlsClientCas: []string{"testdata/cert.pem"},
TlsServerCert: serverCert.Name(),
TlsServerKey: serverKey.Name(),
TlsClientCas: []string{caCerts.Name()},
})
require.NoError(t, err)

go start()
defer server.Close()

_, err = http.Get(fmt.Sprintf("https://localhost:%d", port))

require.ErrorContains(t, err, "failed to verify certificate")
}

func TestStartHttpsServerWithMutualTlsMustSuccessfullyAllowMutualTlsConnections(t *testing.T) {
func TestStartHttpsServerEnforcingMutualTls(t *testing.T) {
ca := testcerts.NewCA()

clientPair, err := ca.NewKeyPair()
require.NoError(t, err)
clientCertDir := t.TempDir()
err = os.WriteFile(filepath.Join(clientCertDir, "client.crt"), clientPair.PublicKey(), 0644)
require.NoError(t, err)

serverPair, err := ca.NewKeyPair("localhost")
require.NoError(t, err)
serverCert, serverKey, err := serverPair.ToTempFile(t.TempDir())
require.NoError(t, err)

port, err := freeport.GetFreePort()
require.NoError(t, err)

server, start, err := prepareHttpsServer(port, ListenSpecification{
TlsServerCert: "testdata/cert.pem",
TlsServerKey: "testdata/key.pem",
TlsClientCas: []string{"testdata/cert.pem"},
TlsServerCert: serverCert.Name(),
TlsServerKey: serverKey.Name(),
TlsClientCas: []string{clientCertDir},
})
require.NoError(t, err)

go start()
defer server.Close()

cert, err := tls.LoadX509KeyPair("testdata/cert.pem", "testdata/key.pem")
clientCertificate, err := tls.X509KeyPair(clientPair.PublicKey(), clientPair.PrivateKey())
require.NoError(t, err)

caCert, err := os.ReadFile("testdata/cert.pem")
require.NoError(t, err)
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
clientPool := x509.NewCertPool()
clientPool.AppendCertsFromPEM(serverPair.PublicKey())

client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
RootCAs: clientPool,
Certificates: []tls.Certificate{clientCertificate},
},
},
}
Expand Down
24 changes: 0 additions & 24 deletions exthttp/testdata/cert.pem

This file was deleted.

28 changes: 0 additions & 28 deletions exthttp/testdata/key.pem

This file was deleted.

0 comments on commit a4f3d26

Please sign in to comment.