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/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..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 @@ -48,9 +49,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 +65,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -163,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 } @@ -232,6 +232,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 +303,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. // @@ -381,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) @@ -392,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 d0dd6ad..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) { @@ -338,10 +336,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") @@ -369,39 +364,121 @@ func testUsingCerts(t *testing.T, rootCAs func(ca *CertificateAuthority, certs * } }() + // Add handler + 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 <-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") + 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 + rsp, err := client.Get("https://localhost:8443") + if err != nil { + t.Errorf("Client returned error - %s", err) + } + + // Check the response + if rsp.StatusCode != http.StatusOK { + 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()) + + // 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 != http.StatusOK { + t.Errorf("Unexpected response code - %d", rsp.StatusCode) + } + }) +} + +func ExampleNewCA() { + // 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, _ *http.Request) { + _, err := w.Write([]byte("Hello, World!")) + if err != nil { + fmt.Printf("Error writing response - %s", err) + } + }) + + // Wait for Listener to start + <-time.After(3 * time.Second) + + // Setup HTTP Client with Cert Pool client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: certpool, - }, + TLSClientConfig: certs.ConfigureTLSConfig(ca.GenerateTLSConfig()), }, } // Make an HTTPS request - _, err = client.Get("https://localhost:8443") + rsp, err := client.Get("https://localhost:8443") if err != nil { - t.Errorf("Client returned error - %s", err) + fmt.Printf("Client returned error - %s", err) } -} -func TestUsingSelfSignedCerts(t *testing.T) { - testUsingCerts(t, func(_ *CertificateAuthority, certs *KeyPair) *x509.CertPool { - pool := x509.NewCertPool() - pool.AppendCertsFromPEM(certs.PublicKey()) - return pool - }) -} + // Print the response + fmt.Println(rsp.Status) -func TestUsingCertsWithCA(t *testing.T) { - testUsingCerts(t, func(ca *CertificateAuthority, _ *KeyPair) *x509.CertPool { - return ca.certPool - }) + // Output: + // 200 OK }