Compare commits

..

No commits in common. "72ffe8456d5c59ffc81818852af6d51d86c2656d" and "99c2ebe0559c41a0e0274fe02af978a9c3135900" have entirely different histories.

9 changed files with 154 additions and 367 deletions

2
.gitignore vendored
View File

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

View File

@ -1,8 +0,0 @@
---- 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,13 +1,6 @@
COVERAGE_DIR=coverage
lint: lint:
golangci-lint run ./... golangci-lint run ./...
goreportcard: goreportcard:
goreportcard-cli -v goreportcard-cli -v
test: test:
go 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,14 +65,15 @@ make goreportcard
``` ```
output: output:
```bash ```bash
➜ go-cert-gen git:(main) goreportcard-cli -v goreportcard-cli -v
Grade .......... A+ 100.0% Grade ........... A+ 94.1%
Files ................. 12 Files .................. 9
Issues ................. 0 Issues ................. 1
gofmt ............... 100% gofmt ............... 100%
go_vet .............. 100% go_vet .............. 100%
gocyclo ............. 100% gocyclo ............. 100%
ineffassign ......... 100% ineffassign ......... 100%
license ............. 100% license ............... 0%
misspell ............ 100% misspell ............ 100%
``` ```

3
go.mod
View File

@ -7,18 +7,15 @@ require (
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // 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/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect

View File

@ -8,104 +8,15 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"log"
"time" "time"
"gitlab.com/urkob/go-cert-gen/pkg/ca" "gitlab.com/urkob/go-cert-gen/pkg/ca"
"gitlab.com/urkob/go-cert-gen/pkg/client" "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. // Creates a new 512bit private key.
func newPrivateKey() (*ecdsa.PrivateKey, error) { func newPrivateKey() (*ecdsa.PrivateKey, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@ -134,16 +45,155 @@ func encodePrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) {
out := &bytes.Buffer{} out := &bytes.Buffer{}
privBytes, err := x509.MarshalPKCS8PrivateKey(priv) privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil { if err != nil {
return nil, fmt.Errorf("x509.MarshalPKCS8PrivateKey: %s", err) return nil, fmt.Errorf("marshal: %s", err)
} }
err = pem.Encode(out, &pem.Block{ err = pem.Encode(out, &pem.Block{
Type: PRIVATE_KEY, Type: "PRIVATE KEY",
Bytes: privBytes, 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 { if err != nil {
return nil, err return nil, nil, fmt.Errorf("newPrivateKey: %s", err)
} }
return out.Bytes(), nil 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
} }

View File

@ -1,80 +0,0 @@
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

@ -1,36 +0,0 @@
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

@ -1,128 +0,0 @@
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)
}