From e146af5408e37b390315ccb1f27c8745693607ac Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 07:43:51 -0700 Subject: [PATCH 1/6] Functions to generate and configure TLS --- README.md | 4 +--- testcerts.go | 24 +++++++++++++++++++++--- testcerts_test.go | 9 +-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c6469a6..bcb20ed 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,7 @@ func TestFunc(t *testing.T) { // Create a client with the self-signed CA client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: ca.CertPool(), - }, + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), }, } diff --git a/testcerts.go b/testcerts.go index 21b771f..80c7ae7 100644 --- a/testcerts.go +++ b/testcerts.go @@ -48,9 +48,7 @@ For more complex tests, you can also use this package to create a Certificate Au // Create a client with the self-signed CA client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: ca.CertPool(), - }, + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), }, } @@ -66,6 +64,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -232,6 +231,13 @@ func (ca *CertificateAuthority) ToTempFile(dir string) (cfh *os.File, kfh *os.Fi return cfh, kfh, nil } +// GenerateTLSConfig returns a tls.Config with the CertificateAuthority as the RootCA. +func (ca *CertificateAuthority) GenerateTLSConfig() *tls.Config { + return &tls.Config{ + RootCAs: ca.CertPool(), + } +} + // PrivateKey returns the private key of the KeyPair. func (kp *KeyPair) PrivateKey() []byte { return pem.EncodeToMemory(kp.privateKey) @@ -296,6 +302,18 @@ func (kp *KeyPair) ToTempFile(dir string) (cfh *os.File, kfh *os.File, err error return cfh, kfh, nil } +// ConfigureTLSConfig will configure the tls.Config with the KeyPair certificate and private key. +// The returned tls.Config can be used for a server or client. +func (kp *KeyPair) ConfigureTLSConfig(tlsConfig *tls.Config) *tls.Config { + tlsConfig.Certificates = []tls.Certificate{ + { + Certificate: [][]byte{kp.PublicKey()}, + PrivateKey: kp.PrivateKey(), + }, + } + return tlsConfig +} + // GenerateCerts generates a x509 certificate and key. // It returns the certificate and key as byte slices, and any error that occurred. // diff --git a/testcerts_test.go b/testcerts_test.go index d0dd6ad..ad3b263 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -1,7 +1,6 @@ package testcerts import ( - "crypto/tls" "crypto/x509" "fmt" "net/http" @@ -373,15 +372,9 @@ func testUsingCerts(t *testing.T, rootCAs func(ca *CertificateAuthority, certs * <-time.After(3 * time.Second) // Setup HTTP Client with Cert Pool - certpool := rootCAs(ca, certs) - if certpool == nil { - t.Fatalf("Test configuration error: rootCAs arg function returned nil instead of a x509.CertPool") - } client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: certpool, - }, + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), }, } From 95ca84501ecb3b8b3a4067c8c70e11234da41c0c Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 07:51:28 -0700 Subject: [PATCH 2/6] Adding example and makefile --- Makefile | 5 ++++ testcerts_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf60074 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +tests: + go test --race -v -covermode=atomic -coverprofile=coverage.out ./... + +benchmarks: + go test -bench=. -benchmem ./... diff --git a/testcerts_test.go b/testcerts_test.go index ad3b263..a9fb321 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -398,3 +398,61 @@ func TestUsingCertsWithCA(t *testing.T) { return ca.certPool }) } + +func ExampleUsage() { + // Generate a new Certificate Authority + ca := NewCA() + + // Create a new KeyPair with a list of domains + certs, err := ca.NewKeyPair("localhost") + if err != nil { + fmt.Printf("Error generating keypair - %s", err) + } + + // Write the certificates to a file + cert, key, err := certs.ToTempFile("") + if err != nil { + fmt.Printf("Error writing certs to temp files - %s", err) + } + + // Create an HTTP Server + server := &http.Server{ + Addr: "0.0.0.0:8443", + } + defer server.Close() + + go func() { + // Start HTTP Listener + err = server.ListenAndServeTLS(cert.Name(), key.Name()) + if err != nil && err != http.ErrServerClosed { + fmt.Printf("Listener returned error - %s", err) + } + }() + + // Add handler + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, World!")) + }) + + // Wait for Listener to start + <-time.After(3 * time.Second) + + // Setup HTTP Client with Cert Pool + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), + }, + } + + // Make an HTTPS request + rsp, err := client.Get("https://localhost:8443") + if err != nil { + fmt.Printf("Client returned error - %s", err) + } + + // Print the response + fmt.Println(rsp.Status) + + // Output: + // 200 OK +} From 2fe5749d66ad91fb8ef1cf50eff4219045b368dc Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 07:55:40 -0700 Subject: [PATCH 3/6] updating example name --- testcerts_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcerts_test.go b/testcerts_test.go index a9fb321..6bc066f 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -399,7 +399,7 @@ func TestUsingCertsWithCA(t *testing.T) { }) } -func ExampleUsage() { +func ExampleNewCA() { // Generate a new Certificate Authority ca := NewCA() From f9670d8d1e71ecc64915206dd183ad63b9aca497 Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 08:07:31 -0700 Subject: [PATCH 4/6] updating tests for new flow --- testcerts_test.go | 70 +++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/testcerts_test.go b/testcerts_test.go index 6bc066f..f787a3e 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -1,6 +1,7 @@ package testcerts import ( + "crypto/tls" "crypto/x509" "fmt" "net/http" @@ -337,10 +338,7 @@ func TestGenerateCertsToTempFile(t *testing.T) { }) } -// testUsingCerts is called by the two tests below. Both test setting up certificates and subsequently -// configuring the transport of the http.Client to use the generated certificate. -// One uses the own public key as part of the pool (self signed cert) and one uses the cert from the CA. -func testUsingCerts(t *testing.T, rootCAs func(ca *CertificateAuthority, certs *KeyPair) *x509.CertPool) { +func TestFullFlow(t *testing.T) { // Create a signed Certificate and Key for "localhost" ca := NewCA() certs, err := ca.NewKeyPair("localhost") @@ -368,34 +366,58 @@ func testUsingCerts(t *testing.T, rootCAs func(ca *CertificateAuthority, certs * } }() + // Add handler + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, World!")) + }) + // Wait for Listener to start <-time.After(3 * time.Second) - // Setup HTTP Client with Cert Pool - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), - }, - } + t.Run("TestUsingCA", func(t *testing.T) { + // Setup HTTP Client with Cert Pool + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), + }, + } - // Make an HTTPS request - _, err = client.Get("https://localhost:8443") - if err != nil { - t.Errorf("Client returned error - %s", err) - } -} + // Make an HTTPS request + rsp, err := client.Get("https://localhost:8443") + if err != nil { + t.Errorf("Client returned error - %s", err) + } -func TestUsingSelfSignedCerts(t *testing.T) { - testUsingCerts(t, func(_ *CertificateAuthority, certs *KeyPair) *x509.CertPool { + // Check the response + if rsp.StatusCode != 200 { + t.Errorf("Unexpected response code - %d", rsp.StatusCode) + } + }) + + t.Run("TestUsingSelfSigned", func(t *testing.T) { + // Create new CertPool pool := x509.NewCertPool() pool.AppendCertsFromPEM(certs.PublicKey()) - return pool - }) -} -func TestUsingCertsWithCA(t *testing.T) { - testUsingCerts(t, func(ca *CertificateAuthority, _ *KeyPair) *x509.CertPool { - return ca.certPool + // Setup HTTP Client with Cert Pool + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: pool, + }, + }, + } + + // Make an HTTPS request + rsp, err := client.Get("https://localhost:8443") + if err != nil { + t.Errorf("Client returned error - %s", err) + } + + // Check the response + if rsp.StatusCode != 200 { + t.Errorf("Unexpected response code - %d", rsp.StatusCode) + } }) } From 94d4421bea0d85dc25efeee623951f550361ca34 Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 08:22:47 -0700 Subject: [PATCH 5/6] fixing linter errors --- testcerts_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/testcerts_test.go b/testcerts_test.go index f787a3e..7c7cfb0 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -367,8 +367,11 @@ func TestFullFlow(t *testing.T) { }() // Add handler - server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, World!")) + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte("Hello, World!")) + if err != nil { + t.Errorf("Error writing response - %s", err) + } }) // Wait for Listener to start @@ -452,8 +455,11 @@ func ExampleNewCA() { }() // Add handler - server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, World!")) + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte("Hello, World!")) + if err != nil { + fmt.Printf("Error writing response - %s", err) + } }) // Wait for Listener to start From 5be6d6347bd4871909c5c4f3a5cef8a52bf4a897 Mon Sep 17 00:00:00 2001 From: Benjamin Cane Date: Sat, 18 May 2024 08:52:01 -0700 Subject: [PATCH 6/6] fixing minor linting items --- testcerts.go | 9 +++++---- testcerts_test.go | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/testcerts.go b/testcerts.go index 80c7ae7..d267694 100644 --- a/testcerts.go +++ b/testcerts.go @@ -19,7 +19,8 @@ Stop saving test certificates in your code repos. Start generating them in your } } -For more complex tests, you can also use this package to create a Certificate Authority and a key pair signed by that Certificate Authority for any test domain you want. +For more complex tests, you can also use this package to create a Certificate Authority and a key pair signed by +that Certificate Authority for any test domain you want. func TestFunc(t *testing.T) { // Generate Certificate Authority @@ -162,7 +163,7 @@ func (ca *CertificateAuthority) NewKeyPair(domains ...string) (*KeyPair, error) return kp, nil } -// CertPool returns a Certificate Pool of the CertificateAuthority Certificate +// CertPool returns a Certificate Pool of the CertificateAuthority Certificate. func (ca *CertificateAuthority) CertPool() *x509.CertPool { return ca.certPool } @@ -399,7 +400,7 @@ func genKeyPair(ca *x509.Certificate, caKey *ecdsa.PrivateKey, cert *x509.Certif return certToPemBlock(signedCert), key, nil } -// keyToPemBlock converts the key to a private pem.Block +// keyToPemBlock converts the key to a private pem.Block. func keyToPemBlock(key *ecdsa.PrivateKey) (*pem.Block, error) { // Convert key into pem.Block kb, err := x509.MarshalPKCS8PrivateKey(key) @@ -410,7 +411,7 @@ func keyToPemBlock(key *ecdsa.PrivateKey) (*pem.Block, error) { return k, nil } -// certToPemBlock converts the certificate to a public pem.Block +// certToPemBlock converts the certificate to a public pem.Block. func certToPemBlock(cert []byte) *pem.Block { return &pem.Block{Type: "CERTIFICATE", Bytes: cert} } diff --git a/testcerts_test.go b/testcerts_test.go index 7c7cfb0..bd50f76 100644 --- a/testcerts_test.go +++ b/testcerts_test.go @@ -192,10 +192,8 @@ func TestCertsUsage(t *testing.T) { t.Errorf("Unexpected success with invalid tempfile directory") } }) - }) } - } func TestGeneratingCerts(t *testing.T) { @@ -392,7 +390,7 @@ func TestFullFlow(t *testing.T) { } // Check the response - if rsp.StatusCode != 200 { + if rsp.StatusCode != http.StatusOK { t.Errorf("Unexpected response code - %d", rsp.StatusCode) } }) @@ -418,7 +416,7 @@ func TestFullFlow(t *testing.T) { } // Check the response - if rsp.StatusCode != 200 { + if rsp.StatusCode != http.StatusOK { t.Errorf("Unexpected response code - %d", rsp.StatusCode) } })