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