Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
hdecarne committed Dec 17, 2023
1 parent 6f0f1ff commit 3c330ef
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 55 deletions.
4 changes: 2 additions & 2 deletions certs/acme.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/rs/zerolog"
)

const acmeCertficateFactoryNamePattern = "ACME[%s]"
const acmeFactoryNamePattern = "ACME[%s]"

type acmeCertificateFactory struct {
name string
Expand Down Expand Up @@ -67,7 +67,7 @@ func (factory *acmeCertificateFactory) New() (crypto.PrivateKey, *x509.Certifica

// NewACMECertificateFactory creates a new certificate factory for ACME based certificates.
func NewACMECertificateFactory(certificateRequest *acme.CertificateRequest, keyPairFactory keys.KeyPairFactory) CertificateFactory {
name := fmt.Sprintf(acmeCertficateFactoryNamePattern, certificateRequest.Provider.Name)
name := fmt.Sprintf(acmeFactoryNamePattern, certificateRequest.Provider.Name)
logger := log.RootLogger().With().Str("Factory", name).Logger()
return &acmeCertificateFactory{
name: name,
Expand Down
12 changes: 6 additions & 6 deletions certs/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/rs/zerolog"
)

const localCertificateFactoryName = "Local"
const localFactoryName = "Local"

type localCertificateFactory struct {
template *x509.Certificate
Expand All @@ -28,7 +28,7 @@ type localCertificateFactory struct {
}

func (factory *localCertificateFactory) Name() string {
return localCertificateFactoryName
return localFactoryName
}

func (factory *localCertificateFactory) New() (crypto.PrivateKey, *x509.Certificate, error) {
Expand All @@ -42,7 +42,7 @@ func (factory *localCertificateFactory) New() (crypto.PrivateKey, *x509.Certific
// parent signed
factory.logger.Info().Msg("creating signed local X.509 certificate...")
createTemplate.SerialNumber = nextSerialNumber()
certificateBytes, err = x509.CreateCertificate(rand.Reader, createTemplate, createTemplate, keyPair.Public(), factory.signer)
certificateBytes, err = x509.CreateCertificate(rand.Reader, createTemplate, factory.parent, keyPair.Public(), factory.signer)
} else {
// self-signed
factory.logger.Info().Msg("creating self-signed local X.509 certificate...")
Expand All @@ -61,7 +61,7 @@ func (factory *localCertificateFactory) New() (crypto.PrivateKey, *x509.Certific

// NewLocalCertificateFactory creates a new certificate factory for locally issued certificates.
func NewLocalCertificateFactory(template *x509.Certificate, keyPairFactory keys.KeyPairFactory, parent *x509.Certificate, signer crypto.PrivateKey) CertificateFactory {
logger := log.RootLogger().With().Str("Factory", localCertificateFactoryName).Logger()
logger := log.RootLogger().With().Str("Factory", localFactoryName).Logger()
return &localCertificateFactory{
template: template,
keyPairFactory: keyPairFactory,
Expand All @@ -77,7 +77,7 @@ type localRevocationListFactory struct {
}

func (factory *localRevocationListFactory) Name() string {
return localCertificateFactoryName
return localFactoryName
}

func (factory *localRevocationListFactory) New(issuer *x509.Certificate, signer crypto.PrivateKey) (*x509.RevocationList, error) {
Expand All @@ -95,7 +95,7 @@ func (factory *localRevocationListFactory) New(issuer *x509.Certificate, signer

// NewLocalRevocationListFactory creates a new revocation list factory for locally issued certificates.
func NewLocalRevocationListFactory(template *x509.RevocationList) RevocationListFactory {
logger := log.RootLogger().With().Str("Factory", localCertificateFactoryName).Logger()
logger := log.RootLogger().With().Str("Factory", localFactoryName).Logger()
return &localRevocationListFactory{
template: template,
logger: &logger,
Expand Down
12 changes: 6 additions & 6 deletions certs/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (

func TestLocalCertificateFactory(t *testing.T) {
// self-signed
template1 := newTestCertificateTemplate("Test1")
template1 := newLocalTestCertificateTemplate("Test1")
template1.IsCA = true
template1.KeyUsage = template1.KeyUsage | x509.KeyUsageCertSign
cf1 := certs.NewLocalCertificateFactory(template1, keys.ECDSA224.NewKeyPairFactory(), nil, nil)
Expand All @@ -32,7 +32,7 @@ func TestLocalCertificateFactory(t *testing.T) {
require.Equal(t, big.NewInt(1), cert1.SerialNumber)
require.Equal(t, template1.Subject.Organization, cert1.Subject.Organization)
// signed
template2 := newTestCertificateTemplate("Test2")
template2 := newLocalTestCertificateTemplate("Test2")
cf2 := certs.NewLocalCertificateFactory(template2, keys.ECDSA224.NewKeyPairFactory(), cert1, privateKey1)
require.NotNil(t, cf2)
privateKey2, cert2, err := cf2.New()
Expand All @@ -42,20 +42,20 @@ func TestLocalCertificateFactory(t *testing.T) {
require.Equal(t, template2.Subject.Organization, cert2.Subject.Organization)
}
func TestLocalRevocationListFactory(t *testing.T) {
issuerTemplate := newTestCertificateTemplate("Issuer")
issuerTemplate := newLocalTestCertificateTemplate("Issuer")
issuerTemplate.IsCA = true
issuerTemplate.KeyUsage = issuerTemplate.KeyUsage | x509.KeyUsageCRLSign
issuerFactory := certs.NewLocalCertificateFactory(issuerTemplate, keys.ECDSA224.NewKeyPairFactory(), nil, nil)
signer, issuer, err := issuerFactory.New()
require.NoError(t, err)
template := newTestRevocationListEmplate(1)
template := newLocalTestRevocationListEmplate(1)
rlf := certs.NewLocalRevocationListFactory(template)
revocationList, err := rlf.New(issuer, signer)
require.NoError(t, err)
require.NotNil(t, revocationList)
}

func newTestCertificateTemplate(cn string) *x509.Certificate {
func newLocalTestCertificateTemplate(cn string) *x509.Certificate {
now := time.Now()
return &x509.Certificate{
Subject: pkix.Name{CommonName: cn},
Expand All @@ -64,7 +64,7 @@ func newTestCertificateTemplate(cn string) *x509.Certificate {
}
}

func newTestRevocationListEmplate(number int64) *x509.RevocationList {
func newLocalTestRevocationListEmplate(number int64) *x509.RevocationList {
now := time.Now()
return &x509.RevocationList{
Number: big.NewInt(number),
Expand Down
45 changes: 42 additions & 3 deletions certs/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,46 @@ import (
"github.com/rs/zerolog"
)

const remoteCertificateRequestFactoryName = "Remote"
const remoteFactoryName = "Remote"

type remoteCertificateFactory struct {
template *x509.Certificate
request *x509.CertificateRequest
parent *x509.Certificate
signer crypto.PrivateKey
logger *zerolog.Logger
}

func (factory *remoteCertificateFactory) Name() string {
return remoteFactoryName
}

func (factory *remoteCertificateFactory) New() (crypto.PrivateKey, *x509.Certificate, error) {
createTemplate := factory.template
factory.logger.Info().Msg("creating X.509 certificate from remote request...")
createTemplate.SerialNumber = nextSerialNumber()
certificateBytes, err := x509.CreateCertificate(rand.Reader, createTemplate, factory.parent, factory.request.PublicKey, factory.signer)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate (cause: %w)", err)
}
certificate, err := x509.ParseCertificate(certificateBytes)
if err != nil {
return nil, nil, fmt.Errorf("failed parse certificate bytes (cause: %w)", err)
}
return nil, certificate, nil
}

// NewRemoteCertificateFactory creates a new certificate factory for request based certificates.
func NewRemoteCertificateFactory(template *x509.Certificate, request *x509.CertificateRequest, parent *x509.Certificate, signer crypto.PrivateKey) CertificateFactory {
logger := log.RootLogger().With().Str("Factory", remoteFactoryName).Logger()
return &remoteCertificateFactory{
template: template,
request: request,
parent: parent,
signer: signer,
logger: &logger,
}
}

type remoteCertificateRequestFactory struct {
template *x509.CertificateRequest
Expand All @@ -25,7 +64,7 @@ type remoteCertificateRequestFactory struct {
}

func (factory *remoteCertificateRequestFactory) Name() string {
return remoteCertificateRequestFactoryName
return remoteFactoryName
}

func (factory *remoteCertificateRequestFactory) New() (crypto.PrivateKey, *x509.CertificateRequest, error) {
Expand All @@ -47,7 +86,7 @@ func (factory *remoteCertificateRequestFactory) New() (crypto.PrivateKey, *x509.

// NewRemoteCertificateRequestFactory creates a new certificate request factory for remotely signed certificates.
func NewRemoteCertificateRequestFactory(template *x509.CertificateRequest, keyPairFactory keys.KeyPairFactory) CertificateRequestFactory {
logger := log.RootLogger().With().Str("Factory", remoteCertificateRequestFactoryName).Logger()
logger := log.RootLogger().With().Str("Factory", remoteFactoryName).Logger()
return &remoteCertificateRequestFactory{
template: template,
keyPairFactory: keyPairFactory,
Expand Down
59 changes: 53 additions & 6 deletions certs/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,71 @@ package certs_test
import (
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"testing"
"time"

"github.com/hdecarne-github/go-certstore/certs"
"github.com/hdecarne-github/go-certstore/keys"
"github.com/stretchr/testify/require"
)

func TestRemoteCertificateFactory(t *testing.T) {
kpf := keys.ECDSA224.NewKeyPairFactory()
requestTemplate := newRemoteTestCertificateRequestTemplate("TestRemoteCertificateFactory")
crf := certs.NewRemoteCertificateRequestFactory(requestTemplate, kpf)
_, request, err := crf.New()
require.NoError(t, err)
rootTemplate := newRemoteTestRootCertificateTemplate(request.Subject.CommonName)
rootCF := certs.NewLocalCertificateFactory(rootTemplate, kpf, nil, nil)
rootPrivateKey, root, err := rootCF.New()
require.NoError(t, err)
template := newRemoteTestCertificateTemplate(request.Subject.CommonName)
cf := certs.NewRemoteCertificateFactory(template, request, root, rootPrivateKey)
require.NotNil(t, cf)
require.Equal(t, "Remote", cf.Name())
privateKey, certificate, err := cf.New()
require.NoError(t, err)
require.Nil(t, privateKey)
require.NotNil(t, certificate)
}

func TestRemoteCertificateRequestFactory(t *testing.T) {
template := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"TestLocalCertificateFactory"},
},
}
template := newRemoteTestCertificateRequestTemplate("TestRemoteCertificateRequestFactory")
crf := certs.NewRemoteCertificateRequestFactory(template, keys.ECDSA224.NewKeyPairFactory())
require.NotNil(t, crf)
require.Equal(t, "Remote", crf.Name())
privateKey, request, err := crf.New()
require.NoError(t, err)
require.NotNil(t, privateKey)
require.NotNil(t, request)
require.NoError(t, err)
}

func newRemoteTestCertificateTemplate(cn string) *x509.Certificate {
now := time.Now()
return &x509.Certificate{
Subject: pkix.Name{CommonName: cn},
NotBefore: now,
NotAfter: now.AddDate(0, 0, 1),
}
}

func newRemoteTestCertificateRequestTemplate(cn string) *x509.CertificateRequest {
return &x509.CertificateRequest{
Subject: pkix.Name{CommonName: cn},
}
}

func newRemoteTestRootCertificateTemplate(cn string) *x509.Certificate {
now := time.Now()
return &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: cn},
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 2,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
NotBefore: now,
NotAfter: now.AddDate(0, 0, 1),
}
}
8 changes: 5 additions & 3 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ func (registry *Registry) CreateCertificate(name string, factory certs.Certifica
return "", err
}
data := &registryEntryData{}
err = data.setKey(key, registry.settings.Secret)
if err != nil {
return "", err
if key != nil {
err = data.setKey(key, registry.settings.Secret)
if err != nil {
return "", err
}
}
data.setCertificate(certificate)
createdName, err := registry.createEntryData(name, data)
Expand Down
79 changes: 50 additions & 29 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,32 @@ func TestResetRevocationList(t *testing.T) {
require.Equal(t, revocationList1, revocationList2)
}

func TestMerge(t *testing.T) {
path, err := os.MkdirTemp("", "TestMerge*")
require.NoError(t, err)
defer os.RemoveAll(path)
backend, err := storage.NewFSStorage(testVersionLimit, path)
require.NoError(t, err)
registry, err := store.NewStore(backend)
require.NoError(t, err)
otherRegistry, err := store.NewStore(storage.NewMemoryStorage(testVersionLimit))
require.NoError(t, err)
user := "TestMergeUser"
populateTestStore(t, otherRegistry, user, 5)
start := time.Now()
err = registry.Merge(otherRegistry, user)
require.NoError(t, err)
elapsed := time.Since(start)
fmt.Printf("%s merged once (took: %s)\n", registry.Name(), elapsed)
checkStoreEntries(t, registry, 160, 5)
start = time.Now()
err = registry.Merge(otherRegistry, user)
require.NoError(t, err)
elapsed = time.Since(start)
fmt.Printf("%s merged twice (took: %s)\n", registry.Name(), elapsed)
checkStoreEntries(t, registry, 160, 5)
}

func TestEntries(t *testing.T) {
path, err := os.MkdirTemp("", "TestEntries*")
require.NoError(t, err)
Expand All @@ -106,15 +132,16 @@ func TestEntries(t *testing.T) {
registry, err := store.NewStore(backend)
require.NoError(t, err)
user := "TestEntriesUser"
start := time.Now()
populateTestStore(t, registry, user, 10)
elapsed := time.Since(start)
fmt.Printf("Store populated (took: %s)\n", elapsed)
checkStoreEntries(t, registry, 1120, 10)
}

func checkStoreEntries(t *testing.T, registry *store.Registry, total int, roots int) {
entries, err := registry.Entries()
require.NoError(t, err)
totalCount := 0
rootCount := 0
start = time.Now()
start := time.Now()
for {
nextEntry, err := entries.Next()
require.NoError(t, err)
Expand All @@ -126,36 +153,18 @@ func TestEntries(t *testing.T) {
rootCount++
}
}
elapsed = time.Since(start)
fmt.Printf("Store entries listed (took: %s)\n", elapsed)
require.Equal(t, 1110, totalCount)
require.Equal(t, 10, rootCount)
}

func TestMerge(t *testing.T) {
path, err := os.MkdirTemp("", "TestMerge*")
require.NoError(t, err)
defer os.RemoveAll(path)
backend, err := storage.NewFSStorage(testVersionLimit, path)
require.NoError(t, err)
registry, err := store.NewStore(backend)
require.NoError(t, err)
otherRegistry, err := store.NewStore(storage.NewMemoryStorage(testVersionLimit))
require.NoError(t, err)
user := "TestMergeUser"
start := time.Now()
populateTestStore(t, otherRegistry, user, 5)
elapsed := time.Since(start)
fmt.Printf("Store populated (took: %s)\n", elapsed)
start = time.Now()
err = registry.Merge(otherRegistry, user)
require.NoError(t, err)
elapsed = time.Since(start)
fmt.Printf("Store merged (took: %s)\n", elapsed)
fmt.Printf("%s entries listed (took: %s)\n", registry.Name(), elapsed)
require.Equal(t, total, totalCount)
require.Equal(t, roots, rootCount)
}

func populateTestStore(t *testing.T, registry *store.Registry, user string, count int) {
start := time.Now()
createTestRootEntries(t, registry, user, count)
createTestRequestEntries(t, registry, user, count)
elapsed := time.Since(start)
fmt.Printf("%s populated (took: %s)\n", registry.Name(), elapsed)
}

func createTestRootEntries(t *testing.T, registry *store.Registry, user string, count int) {
Expand Down Expand Up @@ -202,6 +211,18 @@ func createTestLeafEntries(t *testing.T, registry *store.Registry, issuerName st
}
}

func createTestRequestEntries(t *testing.T, registry *store.Registry, user string, count int) {
for i := 0; i < count; i++ {
name := fmt.Sprintf("request%d", i+1)
factory := newTestCertificateRequestFactory(name)
createdName, err := registry.CreateCertificateRequest(name, factory, user)
require.NoError(t, err)
require.Equal(t, name, createdName)
_, err = registry.Entry(createdName)
require.NoError(t, err)
}
}

func newTestRootCertificateFactory(cn string) certs.CertificateFactory {
now := time.Now()
template := &x509.Certificate{
Expand Down

0 comments on commit 3c330ef

Please sign in to comment.