Repository: go-openapi/runtime
Pull request #166 (2019-11-13 20:40:46)

165 - Allow a caller supplied x509.CertPool for clients
Signed-off-by: Matthew Mussomele <matt@nefeli.io>


Patch:
diff --git a/client/runtime.go b/client/runtime.go
index ab81ef0..a34c976 100644
--- a/client/runtime.go
+++ b/client/runtime.go
@@ -66,6 +66,13 @@ type TLSClientOptions struct {
 	// If this field (and CA) is not set, the system certificate pool is used.
 	LoadedCA *x509.Certificate
 
+	// LoadedCAPool specifies a pool of RootCAs to use when validating the server's TLS certificate.
+	// If set, it will be combined with the the other loaded certificates (see LoadedCA and CA).
+	// If neither LoadedCA or CA is set, the provided pool with override the system
+	// certificate pool.
+	// The caller must not use the supplied pool after calling TLSClientAuth.
+	LoadedCAPool *x509.CertPool
+
 	// ServerName specifies the hostname to use when verifying the server certificate.
 	// If this field is set then InsecureSkipVerify will be ignored and treated as
 	// false.
@@ -149,7 +156,7 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
 	// that way when a request is made to a server known by the system trust store,
 	// the name is still verified
 	if opts.LoadedCA != nil {
-		caCertPool := x509.NewCertPool()
+		caCertPool := basePool(opts.LoadedCAPool)
 		caCertPool.AddCert(opts.LoadedCA)
 		cfg.RootCAs = caCertPool
 	} else if opts.CA != "" {
@@ -158,9 +165,11 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
 		if err != nil {
 			return nil, fmt.Errorf("tls client ca: %v", err)
 		}
-		caCertPool := x509.NewCertPool()
+		caCertPool := basePool(opts.LoadedCAPool)
 		caCertPool.AppendCertsFromPEM(caCert)
 		cfg.RootCAs = caCertPool
+	} else if opts.LoadedCAPool != nil {
+		cfg.RootCAs = opts.LoadedCAPool
 	}
 
 	// apply servername overrride
@@ -174,6 +183,13 @@ func TLSClientAuth(opts TLSClientOptions) (*tls.Config, error) {
 	return cfg, nil
 }
 
+func basePool(pool *x509.CertPool) *x509.CertPool {
+	if pool == nil {
+		return x509.NewCertPool()
+	}
+	return pool
+}
+
 // TLSTransport creates a http client transport suitable for mutual tls auth
 func TLSTransport(opts TLSClientOptions) (http.RoundTripper, error) {
 	cfg, err := TLSClientAuth(opts)


Test patch:
diff --git a/client/runtime_test.go b/client/runtime_test.go
index 765dcb0..7b6e17e 100644
--- a/client/runtime_test.go
+++ b/client/runtime_test.go
@@ -152,6 +152,66 @@ func TestRuntime_TLSAuthConfigWithLoadedCA(t *testing.T) {
 	}
 }
 
+func TestRuntime_TLSAuthConfigWithLoadedCAPool(t *testing.T) {
+
+	certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt")
+	require.NoError(t, err)
+
+	block, _ := pem.Decode(certPem)
+	require.NotNil(t, block)
+
+	cert, err := x509.ParseCertificate(block.Bytes)
+	require.NoError(t, err)
+
+	pool := x509.NewCertPool()
+	pool.AddCert(cert)
+
+	var opts TLSClientOptions
+	opts.LoadedCAPool = pool
+
+	cfg, err := TLSClientAuth(opts)
+	if assert.NoError(t, err) {
+		if assert.NotNil(t, cfg) {
+			require.NotNil(t, cfg.RootCAs)
+
+			// Using require.Len prints the (very large and incomprehensible)
+			// Subjects list on failure, so instead use require.Equal.
+			require.Equal(t, 1, len(cfg.RootCAs.Subjects()))
+		}
+	}
+}
+
+func TestRuntime_TLSAuthConfigWithLoadedCAPoolAndLoadedCA(t *testing.T) {
+
+	certPem, err := ioutil.ReadFile("../fixtures/certs/myCA.crt")
+	require.NoError(t, err)
+
+	block, _ := pem.Decode(certPem)
+	require.NotNil(t, block)
+
+	cert, err := x509.ParseCertificate(block.Bytes)
+	require.NoError(t, err)
+
+	pool, err := x509.SystemCertPool()
+	require.NoError(t, err)
+	startingCertCount := len(pool.Subjects())
+
+	var opts TLSClientOptions
+	opts.LoadedCAPool = pool
+	opts.LoadedCA = cert
+
+	cfg, err := TLSClientAuth(opts)
+	if assert.NoError(t, err) {
+		if assert.NotNil(t, cfg) {
+			require.NotNil(t, cfg.RootCAs)
+
+			// Using require.Len prints the (very large and incomprehensible)
+			// Subjects list on failure, so instead use require.Equal.
+			require.Equal(t, startingCertCount+1, len(cfg.RootCAs.Subjects()))
+		}
+	}
+}
+
 func TestRuntime_TLSAuthConfigWithVerifyPeerCertificate(t *testing.T) {
 	var opts TLSClientOptions
 	opts.InsecureSkipVerify = true