First DDD based signer implementation parts
This commit is contained in:
commit
3affc704d8
13 changed files with 387 additions and 0 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.go text
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea/
|
11
go.mod
Normal file
11
go.mod
Normal 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
11
go.sum
Normal 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=
|
5
openpgp/signing/repository.go
Normal file
5
openpgp/signing/repository.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package signing
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
StoreSignature(key *SignedPublicKey) error
|
||||||
|
}
|
11
openpgp/signing/signing.go
Normal file
11
openpgp/signing/signing.go
Normal 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
199
x509/openssl/repository.go
Normal 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
|
||||||
|
}
|
32
x509/openssl/repository_test.go
Normal file
32
x509/openssl/repository_test.go
Normal 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"))
|
||||||
|
}
|
7
x509/revoking/repository.go
Normal file
7
x509/revoking/repository.go
Normal 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
57
x509/revoking/revoking.go
Normal 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}
|
||||||
|
}
|
36
x509/revoking/revoking_test.go
Normal file
36
x509/revoking/revoking_test.go
Normal 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)
|
||||||
|
}
|
5
x509/signing/repository.go
Normal file
5
x509/signing/repository.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package signing
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
StoreCertificate(*CertificateSigned) error
|
||||||
|
}
|
11
x509/signing/signing.go
Normal file
11
x509/signing/signing.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue