Compare commits

..

5 Commits

Author SHA1 Message Date
Urko 72ffe8456d feat: update README 2023-03-03 22:46:10 +01:00
Urko 8b1d063ffe feat: add license 2023-03-03 22:45:40 +01:00
Urko bb9df2fe8d feat: add more tests to increase coverage 2023-03-03 22:44:57 +01:00
Urko ef2112534c feat: add tests 2023-03-03 22:31:10 +01:00
Urko 8d9c1542dd feat: update gitignore 2023-03-03 22:30:56 +01:00
9 changed files with 370 additions and 157 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
.env
viper.default.yaml
.vscode
certs
coverage

8
LICENSE Normal file
View File

@ -0,0 +1,8 @@
---- Definitions ----
license means right to use
Everybody is invited to contribute to improve this project and the main idea.
This idea which is to help the community to develop more secure code.
By the grace of YAHWEH

View File

@ -1,6 +1,13 @@
COVERAGE_DIR=coverage
lint:
golangci-lint run ./...
goreportcard:
goreportcard-cli -v
test:
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

View File

@ -65,15 +65,14 @@ make goreportcard
```
output:
```bash
goreportcard-cli -v
Grade ........... A+ 94.1%
Files .................. 9
Issues ................. 1
➜ go-cert-gen git:(main) goreportcard-cli -v
Grade .......... A+ 100.0%
Files ................. 12
Issues ................. 0
gofmt ............... 100%
go_vet .............. 100%
gocyclo ............. 100%
ineffassign ......... 100%
license ............... 0%
license ............. 100%
misspell ............ 100%
```

3
go.mod
View File

@ -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

View File

@ -0,0 +1,80 @@
package cert
import (
"bytes"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"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)
if block == nil {
return nil, nil, errors.New("pem.Decode")
}
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
}

View File

@ -0,0 +1,36 @@
package cert
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_newClientCert(t *testing.T) {
ca, err := NewRootCA(&rootTestConfig)
require.NoError(t, err)
require.NotNil(t, ca)
require.NotNil(t, ca.Key())
require.Greater(t, len(ca.Key()), 0)
require.NotNil(t, ca.PEM())
require.Greater(t, len(ca.PEM()), 0)
x509RootCA, err := parseCertificate(ca.PEM())
require.NoError(t, err)
pem, key, err := newClientCert(&clientTestConfig, x509RootCA, ca.Key())
require.NoError(t, err)
require.NotNil(t, pem)
require.Greater(t, len(pem), 0)
require.NotNil(t, key)
require.Greater(t, len(key), 0)
}
func Test_newClientCertErrr(t *testing.T) {
_, _, err := newClientCert(&clientTestConfig, nil, []byte{})
require.Error(t, err)
}

View File

@ -8,15 +8,104 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"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) {
if config == nil {
return nil, nil, errors.New("ca.CaConfig config is nil")
}
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 +134,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)
return nil, 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
return out.Bytes(), nil
}

View File

@ -0,0 +1,128 @@
package cert
import (
"crypto/ecdsa"
"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().Params().Name, 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_encodePrivateKeyError(t *testing.T) {
key := ecdsa.PrivateKey{}
_, err := encodePrivateKey(&key)
require.Error(t, err)
}
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 Test_parseCertificateError(t *testing.T) {
_, err := parseCertificate([]byte{})
require.Error(t, err)
}
func TestNewRootCA(t *testing.T) {
rootCert, err := NewRootCA(&rootTestConfig)
require.NoError(t, err)
require.NotNil(t, rootCert)
}
func TestNewRootCAERror(t *testing.T) {
_, err := NewRootCA(nil)
require.Error(t, err)
}
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)
}