Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
hdecarne committed Dec 16, 2023
1 parent 4dd6de6 commit ba54103
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 44 deletions.
2 changes: 1 addition & 1 deletion certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type RevocationListFactory interface {
// Name returns the name of this factory.
Name() string
// New creates a new X.509 revocation list.
New() (*x509.RevocationList, error)
New(issuer *x509.Certificate, signer crypto.PrivateKey) (*x509.RevocationList, error)
}

// IsRoot checks whether the given certificate is a root certificate.
Expand Down
12 changes: 4 additions & 8 deletions certs/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("CertificateFactory", localCertificateFactoryName).Logger()
logger := log.RootLogger().With().Str("Factory", localCertificateFactoryName).Logger()
return &localCertificateFactory{
template: template,
keyPairFactory: keyPairFactory,
Expand All @@ -73,18 +73,16 @@ func NewLocalCertificateFactory(template *x509.Certificate, keyPairFactory keys.

type localRevocationListFactory struct {
template *x509.RevocationList
issuer *x509.Certificate
signer crypto.PrivateKey
logger *zerolog.Logger
}

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

func (factory *localRevocationListFactory) New() (*x509.RevocationList, error) {
func (factory *localRevocationListFactory) New(issuer *x509.Certificate, signer crypto.PrivateKey) (*x509.RevocationList, error) {
factory.logger.Info().Msg("creating local X.509 revocation list...")
revocationListBytes, err := x509.CreateRevocationList(rand.Reader, factory.template, factory.issuer, keys.KeyFromPrivate(factory.signer))
revocationListBytes, err := x509.CreateRevocationList(rand.Reader, factory.template, issuer, keys.KeyFromPrivate(signer))
if err != nil {
return nil, fmt.Errorf("failed to create revocation list (cause: %w)", err)
}
Expand All @@ -96,12 +94,10 @@ func (factory *localRevocationListFactory) New() (*x509.RevocationList, error) {
}

// NewLocalRevocationListFactory creates a new revocation list factory for locally issued certificates.
func NewLocalRevocationListFactory(template *x509.RevocationList, issuer *x509.Certificate, signer crypto.PrivateKey) RevocationListFactory {
func NewLocalRevocationListFactory(template *x509.RevocationList) RevocationListFactory {
logger := log.RootLogger().With().Str("Factory", localCertificateFactoryName).Logger()
return &localRevocationListFactory{
template: template,
issuer: issuer,
signer: signer,
logger: &logger,
}
}
4 changes: 2 additions & 2 deletions certs/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ func TestLocalRevocationListFactory(t *testing.T) {
signer, issuer, err := issuerFactory.New()
require.NoError(t, err)
template := newTestRevocationListEmplate(1)
rlf := certs.NewLocalRevocationListFactory(template, issuer, signer)
revocationList, err := rlf.New()
rlf := certs.NewLocalRevocationListFactory(template)
revocationList, err := rlf.New(issuer, signer)
require.NoError(t, err)
require.NotNil(t, revocationList)
}
Expand Down
18 changes: 18 additions & 0 deletions storage/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ func (backend *fsBackend) GetVersion(name string, version Version) ([]byte, erro
return data, nil
}

func (backend *fsBackend) Log(name string, message string) error {
entryPath, err := backend.checkEntryPath(name, true)
if err != nil {
return err
}
logPath := filepath.Join(entryPath, "log")
logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, fsBackendFilePerm)
if err != nil {
return fmt.Errorf("failed to open log file '%s' (cause: %w)", logPath, err)
}
defer logFile.Close()
_, err = logFile.WriteString(message)
if err != nil {
return fmt.Errorf("failed to write log file '%s' (cause: %w)", logPath, err)
}
return nil
}

func (backend *fsBackend) checkEntryPath(name string, create bool) (string, error) {
entryPath := filepath.Join(backend.path, name)
pathInfo, err := os.Stat(entryPath)
Expand Down
5 changes: 5 additions & 0 deletions storage/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ func (backend *memoryBackend) GetVersion(name string, version Version) ([]byte,
return nil, ErrNotExist
}

func (backend *memoryBackend) Log(name string, message string) error {
backend.logger.Info().Msgf("log: %s", message)
return nil
}

func NewMemoryStorage(versionLimit VersionLimit) Backend {
logger := log.RootLogger().With().Str("Backend", memoryBackendURI).Logger()
return &memoryBackend{
Expand Down
1 change: 1 addition & 0 deletions storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Backend interface {
Get(name string) ([]byte, error)
GetVersions(name string) ([]Version, error)
GetVersion(name string, version Version) ([]byte, error)
Log(name string, message string) error
}

var ErrNotExist = errors.New("storage item does not exist")
59 changes: 43 additions & 16 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,14 @@ import (
"encoding/json"
"fmt"
"strings"
"time"

"github.com/hdecarne-github/go-certstore/certs"
"github.com/hdecarne-github/go-certstore/storage"
"github.com/hdecarne-github/go-log"
"github.com/rs/zerolog"
)

type Audit int

const (
AuditCreate Audit = 1
AuditAccess Audit = 2
)

type Registry struct {
settings *storeSettings
backend storage.Backend
Expand All @@ -51,7 +45,11 @@ func (registry *Registry) CreateCertificate(name string, factory certs.Certifica
return "", err
}
data.setCertificate(certificate)
return registry.createEntryData(name, data)
createdName, err := registry.createEntryData(name, data)
if err == nil {
registry.audit(auditCreateCertificate, createdName, user)
}
return createdName, err
}

func (registry *Registry) CreateCertificateRequest(name string, factory certs.CertificateRequestFactory, user string) (string, error) {
Expand All @@ -65,7 +63,11 @@ func (registry *Registry) CreateCertificateRequest(name string, factory certs.Ce
return "", err
}
data.setCertificateRequest(certificateRequest)
return registry.createEntryData(name, data)
createdName, err := registry.createEntryData(name, data)
if err == nil {
registry.audit(auditCreateCertificateRequest, createdName, user)
}
return createdName, err
}

func (registry *Registry) Entries() (*RegistryEntries, error) {
Expand Down Expand Up @@ -164,6 +166,30 @@ func (registry *Registry) unmarshalEntryData(dataBytes []byte) (*registryEntryDa
return data, nil
}

const storeAuditName = ".audit"

func (registry *Registry) audit(pattern auditPattern, name string, user string) {
message := pattern.sprintf(name, user)
registry.logger.Info().Msgf("audit: %s", message)
err := registry.backend.Log(storeAuditName, message)
if err != nil {
registry.logger.Error().Msgf("audit log failed for message '%s' (cause: %s)", message, err)
}
}

type auditPattern string

const (
auditCreateCertificate auditPattern = "%d;Create;Certificate;%s;%s"
auditCreateCertificateRequest auditPattern = "%d;Create;CertificateRequest;%s;%s"
auditCreateRevocationList auditPattern = "%d;Create;RevocationList;%s;%s"
auditAccessKey auditPattern = "%d;Access;Key;%s;%s"
)

func (pattern auditPattern) sprintf(name string, user string) string {
return fmt.Sprintf(string(pattern), time.Now().UnixMilli(), name, user)
}

type RegistryEntries struct {
registry *Registry
names storage.Names
Expand Down Expand Up @@ -211,11 +237,11 @@ func (entry *RegistryEntry) HasKey() bool {
return entry.key != nil
}

func (entry *RegistryEntry) Key(user string) (crypto.PrivateKey, error) {
if entry.key == nil {
return entry.key, nil
func (entry *RegistryEntry) Key(user string) crypto.PrivateKey {
if entry.key != nil {
entry.registry.audit(auditAccessKey, entry.name, user)
}
return entry.key, nil
return entry.key
}

func (entry *RegistryEntry) HasCertificate() bool {
Expand All @@ -235,10 +261,10 @@ func (entry *RegistryEntry) CertificateRequest() *x509.CertificateRequest {
}

func (entry *RegistryEntry) ResetRevocationList(factory certs.RevocationListFactory, user string) (*x509.RevocationList, error) {
if !(entry.IsRoot() && entry.CanIssue()) {
return nil, fmt.Errorf("cannot create revocation list for non-root/non-issueing certificate")
if !entry.CanIssue() {
return nil, fmt.Errorf("cannot create revocation list for a non-issueing certificate")
}
revocationList, err := factory.New()
revocationList, err := factory.New(entry.Certificate(), entry.Key(user))
if err != nil {
return nil, err
}
Expand All @@ -249,6 +275,7 @@ func (entry *RegistryEntry) ResetRevocationList(factory certs.RevocationListFact
data.setRevocationList(revocationList)
entry.registry.updateEntryData(entry.name, data)
entry.revocationList = revocationList
entry.registry.audit(auditCreateRevocationList, entry.name, user)
return revocationList, nil
}

Expand Down
25 changes: 8 additions & 17 deletions store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ func TestCreateCertificate(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, entry)
require.True(t, entry.HasKey())
entryKey, err := entry.Key(user)
require.NoError(t, err)
entryKey := entry.Key(user)
require.NotNil(t, entryKey)
require.True(t, entry.HasCertificate())
entryCertificate := entry.Certificate()
Expand All @@ -68,8 +67,7 @@ func TestCreateCertificateRequest(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, entry)
require.True(t, entry.HasKey())
entryKey, err := entry.Key(user)
require.NoError(t, err)
entryKey := entry.Key(user)
require.NotNil(t, entryKey)
require.True(t, entry.HasCertificateRequest())
entryCertificate := entry.CertificateRequest()
Expand All @@ -87,9 +85,7 @@ func TestResetRevocationList(t *testing.T) {
entry, err := registry.Entry(createdName)
require.NoError(t, err)
require.False(t, entry.HasRevocationList())
key, err := entry.Key(user)
require.NoError(t, err)
revocationListFactory := newTestRevocationListFactory(entry.Certificate(), key)
revocationListFactory := newTestRevocationListFactory()
revocationList1, err := entry.ResetRevocationList(revocationListFactory, user)
require.NoError(t, err)
require.NotNil(t, revocationList1)
Expand Down Expand Up @@ -149,10 +145,7 @@ func createTestRootEntries(t *testing.T, registry *store.Registry, user string,
require.Equal(t, name, createdName)
entry, err := registry.Entry(createdName)
require.NoError(t, err)
entryCert := entry.Certificate()
entryKey, err := entry.Key(user)
require.NoError(t, err)
_, err = entry.ResetRevocationList(newTestRevocationListFactory(entryCert, entryKey), user)
_, err = entry.ResetRevocationList(newTestRevocationListFactory(), user)
require.NoError(t, err)
createTestIntermediateEntries(t, registry, createdName, user, count)
}
Expand All @@ -162,8 +155,7 @@ func createTestIntermediateEntries(t *testing.T, registry *store.Registry, issue
issuerEntry, err := registry.Entry(issuerName)
require.NoError(t, err)
issuerCert := issuerEntry.Certificate()
issuerKey, err := issuerEntry.Key(user)
require.NoError(t, err)
issuerKey := issuerEntry.Key(user)
for i := 0; i < count; i++ {
name := fmt.Sprintf("%s:intermediate%d", issuerName, i+1)
factory := newTestIntermediateCertificateFactory(name, issuerCert, issuerKey)
Expand All @@ -178,8 +170,7 @@ func createTestLeafEntries(t *testing.T, registry *store.Registry, issuerName st
issuerEntry, err := registry.Entry(issuerName)
require.NoError(t, err)
issuerCert := issuerEntry.Certificate()
issuerKey, err := issuerEntry.Key(user)
require.NoError(t, err)
issuerKey := issuerEntry.Key(user)
for i := 0; i < count; i++ {
name := fmt.Sprintf("%s:leaf%d", issuerName, i+1)
factory := newTestLeafCertificateFactory(name, issuerCert, issuerKey)
Expand Down Expand Up @@ -240,12 +231,12 @@ func newTestCertificateRequestFactory(cn string) certs.CertificateRequestFactory
return certs.NewRemoteCertificateRequestFactory(template, testKeyAlg.NewKeyPairFactory())
}

func newTestRevocationListFactory(issuer *x509.Certificate, signer crypto.PrivateKey) certs.RevocationListFactory {
func newTestRevocationListFactory() certs.RevocationListFactory {
now := time.Now()
template := &x509.RevocationList{
Number: big.NewInt(1),
ThisUpdate: now,
NextUpdate: now.AddDate(0, 1, 0),
}
return certs.NewLocalRevocationListFactory(template, issuer, signer)
return certs.NewLocalRevocationListFactory(template)
}

0 comments on commit ba54103

Please sign in to comment.