From ef2112534c0711809bcd505938bbbb1d69ff6b2d Mon Sep 17 00:00:00 2001 From: Urko Date: Fri, 3 Mar 2023 22:31:10 +0100 Subject: [PATCH] feat: add tests --- Makefile | 9 +- go.mod | 3 + internal/cert/client_cert.go | 75 ++++++++ internal/cert/client_cert_test.go | 18 ++ internal/cert/{cert.go => root_ca.go} | 244 ++++++++++---------------- internal/cert/root_ca_test.go | 111 ++++++++++++ 6 files changed, 310 insertions(+), 150 deletions(-) create mode 100644 internal/cert/client_cert.go create mode 100644 internal/cert/client_cert_test.go rename internal/cert/{cert.go => root_ca.go} (62%) create mode 100644 internal/cert/root_ca_test.go diff --git a/Makefile b/Makefile index 99f8223..41a4155 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,13 @@ +COVERAGE_DIR=coverage + lint: golangci-lint run ./... goreportcard: goreportcard-cli -v test: - go test ./... \ No newline at end of file + go test ./... +test-coverage: + rm -rf ${COVERAGE_DIR} + mkdir ${COVERAGE_DIR} + go test -v -coverprofile ${COVERAGE_DIR}/cover.out ./... + go tool cover -html ${COVERAGE_DIR}/cover.out -o ${COVERAGE_DIR}/cover.html \ No newline at end of file diff --git a/go.mod b/go.mod index 44dc8dc..3206a9a 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,18 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.15.0 + github.com/stretchr/testify v1.8.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/internal/cert/client_cert.go b/internal/cert/client_cert.go new file mode 100644 index 0000000..9bb58af --- /dev/null +++ b/internal/cert/client_cert.go @@ -0,0 +1,75 @@ +package cert + +import ( + "bytes" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "time" + + "gitlab.com/urkob/go-cert-gen/pkg/client" +) + +type clientCert struct { + certPEM []byte + keyPEM []byte +} + +func (c *clientCert) Key() []byte { + return c.keyPEM +} + +func (c *clientCert) PEM() []byte { + return c.certPEM +} + +func newClientCert(config *client.ClientCertConfig, rootCA *x509.Certificate, rootKeyPEM []byte) ([]byte, []byte, error) { + template := &x509.Certificate{ + SerialNumber: config.Serial, + Subject: pkix.Name{ + Organization: []string{config.Subject.Organization}, + Country: []string{config.Subject.Country}, + Province: []string{config.Subject.Province}, + Locality: []string{config.Subject.Locality}, + StreetAddress: []string{config.Subject.StreetAddress}, + PostalCode: []string{config.Subject.PostalCode}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(config.Duration), + SubjectKeyId: config.SubjectKeyId, + ExtKeyUsage: config.ExtKeyUsage, + KeyUsage: config.KeyUsage, + } + + block, _ := pem.Decode(rootKeyPEM) + caPrivKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, nil, fmt.Errorf("x509.ParsePKCS8PrivateKey: %s", err) + } + + priv, err := newPrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("newPrivateKey: %s", err) + } + + der, err := x509.CreateCertificate(rand.Reader, template, rootCA, &priv.PublicKey, caPrivKey) + if err != nil { + return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) + } + + out := &bytes.Buffer{} + err = pem.Encode(out, &pem.Block{Type: CERTIFICATE, Bytes: der}) + if err != nil { + return nil, nil, fmt.Errorf("pem.Encode: %s", err) + } + + certPEM := out.Bytes() + keyPEM, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) + } + + return certPEM, keyPEM, nil +} diff --git a/internal/cert/client_cert_test.go b/internal/cert/client_cert_test.go new file mode 100644 index 0000000..208a479 --- /dev/null +++ b/internal/cert/client_cert_test.go @@ -0,0 +1,18 @@ +package cert + +import ( + "crypto/x509" + "testing" + + "github.com/stretchr/testify/require" + "gitlab.com/urkob/go-cert-gen/pkg/client" +) + +func Test_newClientCert(t *testing.T) { + var config *client.ClientCertConfig + var rootCA *x509.Certificate + var rootKeyPEM []byte + + _, _, err := newClientCert(config, rootCA, rootKeyPEM) + require.NoError(t, err) +} diff --git a/internal/cert/cert.go b/internal/cert/root_ca.go similarity index 62% rename from internal/cert/cert.go rename to internal/cert/root_ca.go index bf32838..55b02f9 100644 --- a/internal/cert/cert.go +++ b/internal/cert/root_ca.go @@ -9,14 +9,99 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" - "log" "time" "gitlab.com/urkob/go-cert-gen/pkg/ca" - "gitlab.com/urkob/go-cert-gen/pkg/client" ) +const ( + CERTIFICATE = "CERTIFICATE" + PRIVATE_KEY = "PRIVATE KEY" +) + +type rootCA struct { + caPEM []byte + keyPEM []byte +} + +func (r *rootCA) Key() []byte { + return r.keyPEM +} + +func (r *rootCA) PEM() []byte { + return r.caPEM +} + +func (r *rootCA) WithClientCert(config *client.ClientCertConfig) (client.ClientCertIface, error) { + x509RootCA, err := parseCertificate(r.caPEM) + if err != nil { + return nil, fmt.Errorf("parseCertificate: %s", err) + } + + clientCertPEM, clientKeyPEM, err := newClientCert(config, x509RootCA, r.keyPEM) + if err != nil { + return nil, fmt.Errorf("newClientCert: %s", err) + } + + return &clientCert{ + certPEM: clientCertPEM, + keyPEM: clientKeyPEM, + }, nil +} + +// Create a self-signed certificate. +func newRootCA(config *ca.CaConfig) ([]byte, []byte, error) { + priv, err := newPrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("newPrivateKey: %s", err) + } + + template := x509.Certificate{ + SerialNumber: config.SerialNumber, + Subject: pkix.Name{ + Organization: []string{config.Subject.Organization}, + CommonName: config.Subject.CommonName, + }, + NotBefore: time.Now().Add(-time.Minute), + NotAfter: time.Now().Add(config.Duration), + IsCA: true, + KeyUsage: config.KeyUsage, + ExtKeyUsage: config.ExtKeyUsage, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) + } + + out := &bytes.Buffer{} + err = pem.Encode(out, &pem.Block{Type: CERTIFICATE, Bytes: der}) + if err != nil { + return nil, nil, fmt.Errorf("pem.Encode: %s", err) + } + + caPEM := out.Bytes() + keyPEM, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) + } + + return caPEM, keyPEM, nil +} + +func NewRootCA(config *ca.CaConfig) (ca.RootCACertificateIface, error) { + caPEM, keyPEM, err := newRootCA(config) + if err != nil { + return nil, fmt.Errorf("newRootCA: %s", err) + } + + return &rootCA{ + caPEM: caPEM, + keyPEM: keyPEM, + }, nil +} + // Creates a new 512bit private key. func newPrivateKey() (*ecdsa.PrivateKey, error) { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -45,155 +130,16 @@ func encodePrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) { out := &bytes.Buffer{} privBytes, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { - return nil, fmt.Errorf("marshal: %s", err) + return nil, fmt.Errorf("x509.MarshalPKCS8PrivateKey: %s", err) } + err = pem.Encode(out, &pem.Block{ - Type: "PRIVATE KEY", + Type: PRIVATE_KEY, Bytes: privBytes, }) - return out.Bytes(), err -} - -// Create a self-signed certificate. -func newRootCA(config *ca.CaConfig) ([]byte, []byte, error) { - priv, err := newPrivateKey() - if err != nil { - return nil, nil, fmt.Errorf("newPrivateKey: %s", err) - } - - template := x509.Certificate{ - SerialNumber: config.SerialNumber, - Subject: pkix.Name{ - Organization: []string{config.Subject.Organization}, - CommonName: config.Subject.CommonName, - }, - NotBefore: time.Now().Add(-time.Minute), - NotAfter: time.Now().Add(config.Duration), - IsCA: true, - KeyUsage: config.KeyUsage, - ExtKeyUsage: config.ExtKeyUsage, - BasicConstraintsValid: true, - } - der, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) - } - - out := &bytes.Buffer{} - err = pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}) - if err != nil { - return nil, nil, fmt.Errorf("pem.Encode: %s", err) - } - - caPEM := out.Bytes() - keyPEM, err := encodePrivateKey(priv) - if err != nil { - return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) - } - - return caPEM, keyPEM, nil -} - -func newClientCert(config *client.ClientCertConfig, rootCA *x509.Certificate, rootKeyPEM []byte) ([]byte, []byte, error) { - template := &x509.Certificate{ - SerialNumber: config.Serial, - Subject: pkix.Name{ - Organization: []string{config.Subject.Organization}, - Country: []string{config.Subject.Country}, - Province: []string{config.Subject.Province}, - Locality: []string{config.Subject.Locality}, - StreetAddress: []string{config.Subject.StreetAddress}, - PostalCode: []string{config.Subject.PostalCode}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(config.Duration), - SubjectKeyId: config.SubjectKeyId, - ExtKeyUsage: config.ExtKeyUsage, - KeyUsage: config.KeyUsage, - } - - block, _ := pem.Decode(rootKeyPEM) - caPrivKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - log.Fatalf("x509.ParsePKCS8PrivateKey: %s", err) - } - - priv, err := newPrivateKey() - if err != nil { - return nil, nil, fmt.Errorf("newPrivateKey: %s", err) - } - - der, err := x509.CreateCertificate(rand.Reader, template, rootCA, &priv.PublicKey, caPrivKey) - if err != nil { - return nil, nil, fmt.Errorf("x509.CreateCertificate: %s", err) - } - - out := &bytes.Buffer{} - err = pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}) - if err != nil { - return nil, nil, fmt.Errorf("pem.Encode: %s", err) - } - - certPEM := out.Bytes() - keyPEM, err := encodePrivateKey(priv) - if err != nil { - return nil, nil, fmt.Errorf("encodePrivateKey: %s", err) - } - - return certPEM, keyPEM, nil -} - -type rootCA struct { - caPEM []byte - keyPEM []byte -} - -type clientCert struct { - certPEM []byte - keyPEM []byte -} - -func (c *clientCert) Key() []byte { - return c.keyPEM -} - -func (c *clientCert) PEM() []byte { - return c.certPEM -} - -func (r *rootCA) WithClientCert(config *client.ClientCertConfig) (client.ClientCertIface, error) { - x509RootCA, err := parseCertificate(r.caPEM) - if err != nil { - return nil, fmt.Errorf("parseCertificate: %s", err) - } - - clientCertPEM, clientKeyPEM, err := newClientCert(config, x509RootCA, r.keyPEM) - if err != nil { - return nil, fmt.Errorf("newClientCert: %s", err) - } - - return &clientCert{ - certPEM: clientCertPEM, - keyPEM: clientKeyPEM, - }, nil -} - -func (r *rootCA) Key() []byte { - return r.keyPEM -} - -func (r *rootCA) PEM() []byte { - return r.caPEM -} - -func NewRootCA(config *ca.CaConfig) (ca.RootCACertificateIface, error) { - caPEM, keyPEM, err := newRootCA(config) - if err != nil { - return nil, fmt.Errorf("newRootCA: %s", err) - } - - return &rootCA{ - caPEM: caPEM, - keyPEM: keyPEM, - }, nil + if err != nil { + return nil, err + } + + return out.Bytes(), nil } diff --git a/internal/cert/root_ca_test.go b/internal/cert/root_ca_test.go new file mode 100644 index 0000000..3f2612a --- /dev/null +++ b/internal/cert/root_ca_test.go @@ -0,0 +1,111 @@ +package cert + +import ( + "crypto/elliptic" + "crypto/x509" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + "gitlab.com/urkob/go-cert-gen/pkg/ca" + "gitlab.com/urkob/go-cert-gen/pkg/client" +) + +const year = time.Hour * 24 * 365 + +var rootTestConfig = ca.CaConfig{ + SerialNumber: big.NewInt(12321), + Subject: ca.CaSubject{ + Organization: "test-organization", + CommonName: "test-organization", + }, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + Duration: year, +} + +var clientTestConfig = client.ClientCertConfig{ + Serial: big.NewInt(12321), + Subject: client.Subject{ + Organization: rootTestConfig.Subject.Organization, + Country: "REML", + Province: "REML", + Locality: "REML", + StreetAddress: "c/o Sovereign 7 rural free delivery", + PostalCode: "[Near 777]", + }, + Duration: year, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + KeyUsage: x509.KeyUsageDigitalSignature, +} + +func Test_newPrivateKey(t *testing.T) { + privKey, err := newPrivateKey() + require.NoError(t, err) + + require.NotEmpty(t, privKey.PublicKey.Params().Name) + require.Equal(t, elliptic.P256(), privKey.PublicKey.Params().Name) +} + +func Test_encodePrivateKey(t *testing.T) { + privKey, err := newPrivateKey() + require.NoError(t, err) + + bytes, err := encodePrivateKey(privKey) + require.NoError(t, err) + + require.NotNil(t, bytes) + require.Greater(t, len(bytes), 0) +} + +func Test_newRootCA(t *testing.T) { + caPEM, keyPEM, err := newRootCA(&rootTestConfig) + + require.NoError(t, err) + require.NotNil(t, caPEM) + require.Greater(t, len(caPEM), 0) + require.NotNil(t, keyPEM) + require.Greater(t, len(keyPEM), 0) +} + +func Test_parseCertificate(t *testing.T) { + caPEM, _, err := newRootCA(&rootTestConfig) + require.NoError(t, err) + + rootCert, err := parseCertificate(caPEM) + require.NoError(t, err) + require.NotNil(t, rootCert) + require.Equal(t, rootCert.SignatureAlgorithm, x509.ECDSAWithSHA256) + require.Equal(t, rootCert.Issuer.Organization, []string{rootTestConfig.Subject.Organization}) + require.Equal(t, rootCert.Issuer.CommonName, rootTestConfig.Subject.CommonName) +} + +func TestNewRootCA(t *testing.T) { + rootCert, err := NewRootCA(&rootTestConfig) + require.NoError(t, err) + require.NotNil(t, rootCert) +} + +func Test_rootCA_WithClientCert(t *testing.T) { + rootCert, err := NewRootCA(&rootTestConfig) + require.NoError(t, err) + require.NotNil(t, rootCert) + + clientSrv, err := rootCert.WithClientCert(&clientTestConfig) + require.NoError(t, err) + require.NotNil(t, clientSrv) + + require.NotNil(t, clientSrv.Key()) + require.Greater(t, len(clientSrv.Key()), 0) + + require.NotNil(t, clientSrv.PEM()) + require.Greater(t, len(clientSrv.PEM()), 0) +}