Compare commits

..

24 Commits

Author SHA1 Message Date
e77a67a3de feat: load env from file 2023-03-09 13:29:05 +01:00
d815b12719 feat: update README 2023-03-08 12:17:48 +01:00
7660cc57fd feat: writer test full coverage 2023-03-05 15:00:53 +01:00
25526ac993 feat: cmd add some tests 2023-03-05 15:00:37 +01:00
0bf26d2019 feat: fix force error after delete directory 2023-03-05 00:10:46 +01:00
b049c6a779 fix lint 2023-03-05 00:07:48 +01:00
0bf7b63286 feat: test coverage more than 85% 2023-03-05 00:05:00 +01:00
89136cae59 feat: test coverage more than 80% 2023-03-05 00:04:41 +01:00
72ffe8456d feat: update README 2023-03-03 22:46:10 +01:00
8b1d063ffe feat: add license 2023-03-03 22:45:40 +01:00
bb9df2fe8d feat: add more tests to increase coverage 2023-03-03 22:44:57 +01:00
ef2112534c feat: add tests 2023-03-03 22:31:10 +01:00
8d9c1542dd feat: update gitignore 2023-03-03 22:30:56 +01:00
99c2ebe055 fix README 2023-02-20 21:37:22 +01:00
9893e625b5 fix: duration 2023-02-20 21:35:19 +01:00
d37336d578 feat: update READMe add goreportcard 2023-02-15 20:05:21 +01:00
03272b363d feat: fix misspell 2023-02-15 19:49:25 +01:00
6088b91dbd feat: update makefile 2023-02-15 19:49:14 +01:00
9a470510a4 feat: add Makefile 2023-02-15 19:48:05 +01:00
34e22c5338 feat: update readme 2023-02-15 19:47:58 +01:00
868ebbf79d fix lint 2023-02-15 19:47:52 +01:00
e73874986e feat: update README 2023-02-15 19:45:27 +01:00
762ba66de8 feat: add README 2023-02-15 19:41:00 +01:00
b7ae9b2fd9 feat: add .env.example 2023-02-15 19:34:48 +01:00
17 changed files with 691 additions and 176 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
VIPER_CONFIG=your-viper-file-name-without-extension
VIPER_CONFIG_TYPE=yaml
ENV=dev

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

13
Makefile Normal file
View File

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

95
README.md Normal file
View File

@@ -0,0 +1,95 @@
# go-gen-cert
## Preamble
I've decided to create this project based on [this example](https://github.com/yasushi-saito/grpc-ssl-example/blob/master/go/main.go) but with some improvements, which I would like to give thanks.
I had some trouble during TLS communication between both of my gRPC server and client. I've decided to create a tool to generate SSL certificates following a little of this [guide](https://jamielinux.com/docs/openssl-certificate-authority/create-the-intermediate-pair.html).
## TODO:
- [ ] Create intermediate authority to sign certificates on behalf CA to add more security. If intermediate is hacked then you can revoke from CA and generate new intermediates keeping CA isolated from beeing hacked.
- ~~[x] Complete tests~~
## Configuration
If you are on `dev` environment, like I've been doing, you must create `.env` file similar as `.env.example` in this repo:
```bash
VIPER_CONFIG=your-viper-file-name-without-extension
VIPER_CONFIG_TYPE=yaml
ENV=dev
```
Then add viper configuration file, yaml for example, in your root directory:
```yaml
export_dir: "/home"
ca:
serial_number: 12152 # serial number
subject:
organization: "yourdomain.com"
common_name: "*.yourdomain.com"
key_usage: 1
ext_key_usage:
- 1
- 2
duration: "8760h0m0s" #1 year
client:
serial_number: 12151232 # serial number
subject:
organization: "yourdomain.com"
country: "RM"
province: "REML"
locality: ""
street_address: ""
postal_code: ""
subject_key_id:
- 1
- 2
- 3
- 4
- 6
key_usage: 1
ext_key_usage:
- 1
- 2
duration: "8760h0m0s"
```
## Execution
Then you can just run
```bash
go run main.go
```
## tests
Just simply run make command and watch coverage results on `cover.html` within `coverage`
```shell
make test-coverage
rm -rf coverage
mkdir coverage
go test -v -coverprofile coverage/cover.out ./...
=== RUN TestCredentialsFromKeyWithPasswd
--- PASS: TestCredentialsFromKeyWithPasswd (0.37s)
=== RUN TestCredentialsFromKeyWithPasswdError
--- PASS: TestCredentialsFromKeyWithPasswdError (0.46s)
PASS
coverage: 90.9% of statements
ok gitea.urkob.com/urko/go-grpc-certificate/pkg/credentials 0.839s coverage: 90.9% of statements
go tool cover -html coverage/cover.out -o coverage/cover.html
```
## goreportcard
```bash
make goreportcard
```
output:
```bash
➜ 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 ............. 100%
misspell ............ 100%
```

View File

@@ -27,9 +27,9 @@ var envConfig struct {
var writer pkgio.WriterIface
func intEnvConfig(isProd bool) {
if !isProd {
err := godotenv.Load(util.RootDir() + "/.env")
func intEnvConfig(envFilePath string) {
if envFilePath != "" {
err := godotenv.Load(envFilePath)
if err != nil {
log.Fatalf("environment variable ENV is empty and an error occurred while loading the .env file: %s\n", err)
}
@@ -87,32 +87,59 @@ var rootCmd = &cobra.Command{
log.Fatalf("rootCA.WithClientCert: %s", err)
}
exportPem("root-ca.pem", rootCA.PEM())
exportPem("root-key.pem", rootCA.Key())
outputPath, err := exportPem("root-ca.pem", rootCA.PEM())
if err != nil {
log.Fatalf("exportPem: %s\n", err)
}
log.Printf("file created successfully: %s\n", outputPath)
exportPem("client-cert.pem", clientCert.PEM())
exportPem("client-key.pem", clientCert.Key())
outputPath, err = exportPem("root-key.pem", rootCA.Key())
if err != nil {
log.Fatalf("exportPem: %s\n", err)
}
log.Printf("file created successfully: %s\n", outputPath)
outputPath, err = exportPem("client-cert.pem", clientCert.PEM())
if err != nil {
log.Fatalf("exportPem: %s\n", err)
}
log.Printf("file created successfully: %s\n", outputPath)
outputPath, err = exportPem("client-key.pem", clientCert.Key())
if err != nil {
log.Fatalf("exportPem: %s\n", err)
}
log.Printf("file created successfully: %s\n", outputPath)
},
}
func getExtKeyUsage(intKeyUsageSlice []int) []x509.ExtKeyUsage {
extKeyUsage := make([]x509.ExtKeyUsage, len(intKeyUsageSlice))
if intKeyUsageSlice == nil || len(intKeyUsageSlice) <= 0 {
return []x509.ExtKeyUsage{}
}
extKeyUsage := make([]x509.ExtKeyUsage, 0, len(intKeyUsageSlice))
for _, v := range intKeyUsageSlice {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(v))
}
return extKeyUsage
}
func exportPem(filename string, data []byte) {
func exportPem(filename string, data []byte) (string, error) {
outputPath, err := writer.WriteFile(filename, data)
if err != nil {
log.Fatalf("rootCA.WithClientCert: %s", err)
return "", fmt.Errorf("rootCA.WithClientCert: %s", err)
}
log.Printf("file created successfuly: %s\n", outputPath)
return outputPath, nil
}
func init() {
intEnvConfig(false)
envFile := ""
if os.Getenv("ENV") != "prod" {
envFile = "./.env"
}
intEnvConfig(envFile)
cobra.OnInitialize(initConfig)
}

View File

@@ -1,7 +1,62 @@
package cmd
import "testing"
import (
"crypto/x509"
"log"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/urkob/go-cert-gen/internal/io"
)
func TestExecute(t *testing.T) {
Execute()
}
func Test_getExtKeyUsage(t *testing.T) {
intKeyUsageSlice := make([]int, 0, 1)
intKeyUsageSlice = append(intKeyUsageSlice, int(x509.ExtKeyUsageClientAuth))
keyUsage := getExtKeyUsage(intKeyUsageSlice)
assert.Len(t, keyUsage, len(intKeyUsageSlice))
assert.Equal(t, keyUsage[0], x509.ExtKeyUsageClientAuth)
intKeyUsageSlice = make([]int, 0)
keyUsage = getExtKeyUsage(intKeyUsageSlice)
assert.Len(t, keyUsage, 0)
keyUsage = getExtKeyUsage(nil)
assert.Len(t, keyUsage, 0)
}
var testFile = "test-file.txt"
func init() {
wd, err := os.Getwd()
if err != nil {
log.Fatalf("os.Getwd: %s\n", err)
}
writer = io.NewWriter(wd)
}
func Test_exportPem(t *testing.T) {
defer func() {
os.Remove(testFile)
err := os.Remove(testFile)
require.NoError(t, err)
}()
data := []byte("test data")
outputPath, err := exportPem(testFile, data)
require.NoError(t, err)
require.NotEmpty(t, outputPath)
}
func Test_exportPemError(t *testing.T) {
data := []byte("test data")
outputPath, err := exportPem("", data)
require.Error(t, err)
require.Empty(t, outputPath)
}

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,147 +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)
}
pem.Encode(out, &pem.Block{
Type: "PRIVATE KEY",
err = pem.Encode(out, &pem.Block{
Type: PRIVATE_KEY,
Bytes: privBytes,
})
if err != nil {
return nil, err
}
return out.Bytes(), 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{}
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der})
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{}
pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der})
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

@@ -0,0 +1,143 @@
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_newRootCAError(t *testing.T) {
_, _, err := newRootCA(&ca.CaConfig{})
require.Error(t, err)
}
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)
}
func Test_rootCA_WithClientCertEror(t *testing.T) {
rootCert := rootCA{
caPEM: nil,
}
_, err := rootCert.WithClientCert(&clientTestConfig)
require.Error(t, err)
}

View File

@@ -2,6 +2,7 @@ package io
import (
"fmt"
"log"
"os"
"gitlab.com/urkob/go-cert-gen/pkg/io"
@@ -11,22 +12,33 @@ type writer struct {
dirPath string
}
// WriteFile writes file into `w writer` directory.
// Returns outputPath and error
func (w writer) WriteFile(filename string, data []byte) (string, error) {
if filename == "" {
return "", fmt.Errorf("filename cannot be empty")
}
if w.dirPath == "" {
return "", fmt.Errorf("export directory cannot be empty")
}
if err := os.MkdirAll(w.dirPath, 0o755); err != nil {
return "", err
}
log.Println("to write file")
outputPath := w.dirPath + "/" + filename
if err := os.WriteFile(outputPath, data, 0o600); err != nil {
return "", err
}
log.Println("file written")
return outputPath, nil
}
func NewWriter(dirPath string) io.WriterIface {
return newWriter(dirPath)
}
func newWriter(dirPath string) *writer {
return &writer{
dirPath: dirPath,
}

View File

@@ -0,0 +1,94 @@
package io
import (
"io/fs"
"log"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
testWriterPath = "testPath"
testWriterPathError = "test Path, @:ººººº\\/.Ç*⁺´+"
testFileContent = []byte("test data")
testFileName = "test-file.txt"
workingDir = ""
)
func deleteAllDirs() error {
return os.RemoveAll(testWriterPath)
}
func init() {
err := deleteAllDirs()
if err != nil {
log.Fatalln("deleteAllDirs: ", err)
}
workingDir, err = os.Getwd()
if err != nil {
log.Fatalln("os.Getwd: ", err)
}
}
func Test_newWriter(t *testing.T) {
w := newWriter(testWriterPath)
require.NotNil(t, w)
}
func TestNewWriter(t *testing.T) {
w := NewWriter(testWriterPath)
require.NotNil(t, w)
}
func Test_writer_WriteFile(t *testing.T) {
err := deleteAllDirs()
require.NoError(t, err)
defer func(t *testing.T) {
err := deleteAllDirs()
require.NoError(t, err)
}(t)
w := newWriter(testWriterPath)
wgot, err := w.WriteFile(testFileName, testFileContent)
require.NoError(t, err)
btsReaded, err := os.ReadFile(wgot)
require.NoError(t, err)
require.NotEmpty(t, btsReaded)
require.Equal(t, string(btsReaded), string(testFileContent))
}
func Test_writer_WriteFileError(t *testing.T) {
err := deleteAllDirs()
require.NoError(t, err)
defer func(t *testing.T) {
err := deleteAllDirs()
require.NoError(t, err)
}(t)
w := newWriter("")
_, err = w.WriteFile(testFileName, testFileContent)
assert.Error(t, err)
w = newWriter(testWriterPath)
_, err = w.WriteFile("", testFileContent)
require.Error(t, err)
err = os.MkdirAll(testWriterPath, fs.ModeDir)
require.NoError(t, err, "mkdir should not throw error")
err = os.MkdirAll(testWriterPath+"/"+testFileName, fs.ModeDir)
require.NoError(t, err)
_, err = w.WriteFile(testFileName, nil)
assert.Error(t, err)
w = newWriter(testWriterPathError)
_, err = w.WriteFile(testFileName, testFileContent)
require.Error(t, err)
}

View File

@@ -29,26 +29,10 @@ type Subject struct {
PostalCode string
}
/*
var (
subjectKeyId = []byte{1, 2, 3, 4, 6}
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}
keyUsage = x509.KeyUsageDigitalSignature
)
func NewDefaultConfig() *ClientCertConfig {
return &ClientCertConfig{
Serial: big.NewInt(12321),
Subject: Subject{
Organization: "",
Country: "",
Province: "",
Locality: "",
StreetAddress: "",
PostalCode: "",
},
Duration: time.Duration(time.Hour * 24 * 365),
SubjectKeyId: subjectKeyId,
ExtKeyUsage: extKeyUsage,
KeyUsage: keyUsage,
}
}
*/

View File

@@ -1,5 +1,7 @@
package io
type WriterIface interface {
// WriteFile writes file into `w writer` directory.
// Returns outputPath and error
WriteFile(filename string, data []byte) (string, error)
}

View File

@@ -8,7 +8,7 @@ ca:
ext_key_usage:
- 1
- 2
duration: 518400 #1 year
duration: "8760h0m0s" #1 year
client:
serial_number: 12151232 # serial number
subject:
@@ -28,4 +28,4 @@ client:
ext_key_usage:
- 1
- 2
duration: 518400
duration: "8760h0m0s"