First DDD based signer implementation parts

This commit is contained in:
Jan Dittberner 2021-08-23 20:53:43 +02:00 committed by Jan Dittberner
commit 3affc704d8
13 changed files with 387 additions and 0 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.go text

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea/

11
go.mod Normal file
View file

@ -0,0 +1,11 @@
module git.cacert.org/cacert-gosigner
go 1.17
require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.1.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

11
go.sum Normal file
View file

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,5 @@
package signing
type Repository interface {
StoreSignature(key *SignedPublicKey) error
}

View file

@ -0,0 +1,11 @@
package signing
type OpenPGPSigning struct{}
type RequestInformation struct{}
type SignedPublicKey struct{}
func (o *OpenPGPSigning) Sign(signingRequest *RequestInformation) (*SignedPublicKey, error) {
return &SignedPublicKey{}, nil
}

199
x509/openssl/repository.go Normal file
View file

@ -0,0 +1,199 @@
package openssl
import (
"bufio"
"errors"
"fmt"
"math/big"
"os"
"path"
"strings"
"sync"
"time"
"git.cacert.org/cacert-gosigner/x509/revoking"
"git.cacert.org/cacert-gosigner/x509/signing"
)
// The FileRepository stores information about signed and revoked certificates
// in an openssl index.txt compatible file.
//
// A reference for the file format can be found at
// https://pki-tutorial.readthedocs.io/en/latest/cadb.html.
type FileRepository struct {
indexFileName string
lock sync.Locker
}
type indexStatus string
const (
CertificateValid indexStatus = "V"
CertificateRevoked = "R"
CertificateExpired = "E"
)
const opensslTimeSpec = "060102030405Z"
type indexEntry struct {
statusFlag indexStatus
expiresAt time.Time
revokedAt time.Time
revocationReason string
serialNumber *big.Int
fileName string
certificateSubjectDN string
}
func (e *indexEntry) markRevoked(revocationTime time.Time, reason string) {
if e.statusFlag == CertificateValid {
e.statusFlag = CertificateRevoked
e.revokedAt = revocationTime
e.revocationReason = reason
}
}
type IndexFile struct {
entries []*indexEntry
}
func (f *IndexFile) findEntry(number *big.Int) (*indexEntry, error) {
for _, entry := range f.entries {
if entry.serialNumber == number {
return entry, nil
}
}
return nil, fmt.Errorf("no entry for serial number %s found", number)
}
// StoreRevocation records information about a revoked certificate.
func (r *FileRepository) StoreRevocation(revoked *revoking.CertificateRevoked) error {
r.lock.Lock()
defer r.lock.Unlock()
index, err := r.loadIndex()
if err != nil {
return err
}
entry, err := index.findEntry(revoked.SerialNumber())
if err != nil {
return err
}
entry.markRevoked(revoked.RevocationTime(), revoked.Reason())
err = r.writeIndex(index)
return err
}
// StoreCertificate records information about a signed certificate.
func (r *FileRepository) StoreCertificate(signed *signing.CertificateSigned) error {
r.lock.Lock()
defer r.lock.Unlock()
return nil
}
func (r *FileRepository) loadIndex() (*IndexFile, error) {
f, err := os.Open(r.indexFileName)
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
entries := make([]*indexEntry, 0)
indexScanner := bufio.NewScanner(f)
for indexScanner.Scan() {
indexEntry, err := r.newIndexEntry(indexScanner.Text())
if err != nil {
return nil, err
}
entries = append(entries, indexEntry)
}
if err := indexScanner.Err(); err != nil {
return nil, err
}
return &IndexFile{entries: entries}, nil
}
func (r *FileRepository) writeIndex(index *IndexFile) error {
return errors.New("not implemented")
}
func (r *FileRepository) newIndexEntry(text string) (*indexEntry, error) {
fields := strings.Split(text, "\t")
const expectedFieldNumber = 6
if len(fields) != expectedFieldNumber {
return nil, fmt.Errorf(
"unexpected number of fields %d instead of %d",
len(fields),
expectedFieldNumber,
)
}
expirationParsed, err := time.Parse(opensslTimeSpec, fields[1])
if err != nil {
return nil, err
}
var revocationTimeParsed time.Time
var revocationReason string
if fields[2] != "" {
var timeString string
if strings.Contains(fields[2], ",") {
parts := strings.SplitN(fields[2], ",", 2)
timeString = parts[0]
revocationReason = parts[1]
} else {
timeString = fields[2]
}
revocationTimeParsed, err = time.Parse(opensslTimeSpec, timeString)
if err != nil {
return nil, err
}
}
serialParsed := new(big.Int)
if _, ok := serialParsed.SetString(fields[3], 16); !ok {
return nil, fmt.Errorf("could not parse serial number %s", fields[3])
}
fileNameParsed := "unknown"
if fields[4] != "" {
_, err = os.Stat(path.Join(path.Dir(r.indexFileName), fields[4]))
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
fileNameParsed = fields[4]
}
}
subjectDNParsed := fields[5]
return &indexEntry{
statusFlag: indexStatus(fields[0]),
expiresAt: expirationParsed,
revokedAt: revocationTimeParsed,
revocationReason: revocationReason,
serialNumber: serialParsed,
fileName: fileNameParsed,
certificateSubjectDN: subjectDNParsed,
}, nil
}
func NewFileRepository(baseDirectory string) (*FileRepository, error) {
err := os.Chdir(baseDirectory)
if err != nil {
return nil, err
}
return &FileRepository{
indexFileName: path.Join(baseDirectory, "index.txt"),
lock: &sync.Mutex{},
}, nil
}

View file

@ -0,0 +1,32 @@
package openssl_test
import (
"path"
"testing"
"git.cacert.org/cacert-gosigner/x509/openssl"
"git.cacert.org/cacert-gosigner/x509/revoking"
"git.cacert.org/cacert-gosigner/x509/signing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStoreRevocation(t *testing.T) {
fr, err := openssl.NewFileRepository(t.TempDir())
require.NoError(t, err)
err = fr.StoreRevocation(&revoking.CertificateRevoked{})
assert.NoError(t, err)
assert.FileExists(t, path.Join(t.TempDir(), "index.txt"))
}
func TestStoreCertificate(t *testing.T) {
fr, err := openssl.NewFileRepository(t.TempDir())
require.NoError(t, err)
err = fr.StoreCertificate(&signing.CertificateSigned{})
assert.NoError(t, err)
assert.FileExists(t, path.Join(t.TempDir(), "index.txt"))
}

View file

@ -0,0 +1,7 @@
package revoking
// A Repository for storing certificate status information
type Repository interface {
// StoreRevocation stores information about a revoked certificate.
StoreRevocation(*CertificateRevoked) error
}

57
x509/revoking/revoking.go Normal file
View file

@ -0,0 +1,57 @@
package revoking
import (
"math/big"
"time"
)
type X509Revoking struct {
repository Repository
}
type RevokeCertificate struct {
serialNumber *big.Int
reason string
}
type CertificateRevoked struct {
serialNumber *big.Int
revocationTime time.Time
reason string
}
type CRLInformation struct{}
func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*CertificateRevoked, error) {
revoked := &CertificateRevoked{
serialNumber: revokeCertificate.serialNumber,
revocationTime: time.Now(),
reason: revokeCertificate.reason,
}
if err := r.repository.StoreRevocation(revoked); err != nil {
return nil, err
}
return revoked, nil
}
func (r *X509Revoking) CreateCRL() (*CRLInformation, error) {
return &CRLInformation{}, nil
}
func (r *CertificateRevoked) SerialNumber() *big.Int {
return r.serialNumber
}
func (r *CertificateRevoked) RevocationTime() time.Time {
return r.revocationTime
}
func (r *CertificateRevoked) Reason() string {
return r.reason
}
func NewX509Revoking(repo Repository) *X509Revoking {
return &X509Revoking{repository: repo}
}

View file

@ -0,0 +1,36 @@
package revoking
import (
"math/big"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type testRepo struct {
revoked []big.Int
}
func (t *testRepo) StoreRevocation(revoked *CertificateRevoked) error {
t.revoked = append(t.revoked, *revoked.serialNumber)
return nil
}
func TestRevoking(t *testing.T) {
testRepository := testRepo{revoked: make([]big.Int, 0)}
r := NewX509Revoking(&testRepository)
rand.Seed(time.Now().Unix())
serial := big.NewInt(rand.Int63())
revoke, err := r.Revoke(&RevokeCertificate{serialNumber: serial, reason: "for testing"})
assert.NoError(t, err)
assert.Equal(t, "for testing", revoke.reason)
assert.Equal(t, serial, revoke.serialNumber)
assert.Contains(t, testRepository.revoked, *serial)
}

View file

@ -0,0 +1,5 @@
package signing
type Repository interface {
StoreCertificate(*CertificateSigned) error
}

11
x509/signing/signing.go Normal file
View file

@ -0,0 +1,11 @@
package signing
type X509Signing struct{}
type RequestInformation struct{}
type CertificateSigned struct{}
func (x *X509Signing) Sign(signingRequest *RequestInformation) (*CertificateSigned, error) {
return &CertificateSigned{}, nil
}