Use standard library types for certificates and revocations

main
Jan Dittberner 2 years ago committed by Jan Dittberner
parent 42c7dc7170
commit 20580cda52

@ -13,58 +13,10 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
)
var OidCRLReason = asn1.ObjectIdentifier{2, 5, 29, 21}
type CRLReason int "git.cacert.org/cacert-gosigner/x509/revoking"
// CRL reason codes as defined in RFC 5280 section 5.3.1
const (
CRLReasonUnspecified CRLReason = 0
CRLReasonKeyCompromise CRLReason = 1
CRLReasonCACompromise CRLReason = 2
CRLReasonAffiliationChanged CRLReason = 3
CRLReasonSuperseded CRLReason = 4
CRLReasonCessationOfOperation CRLReason = 5
CRLReasonCertificateHold CRLReason = 6
CRLReasonRemoveFromCRL CRLReason = 8
CRLReasonPrivilegeWithdrawn CRLReason = 9
CRLReasonAACompromise CRLReason = 10
) )
var crlReasonNames = map[CRLReason]string{
CRLReasonUnspecified: "unspecified",
CRLReasonKeyCompromise: "keyCompromise",
CRLReasonCACompromise: "CACompromise",
CRLReasonAffiliationChanged: "affiliationChanged",
CRLReasonSuperseded: "superseded",
CRLReasonCessationOfOperation: "cessationOfOperation",
CRLReasonCertificateHold: "certificateHold",
CRLReasonRemoveFromCRL: "removeFromCRL",
CRLReasonPrivilegeWithdrawn: "privilegeWithdrawn",
CRLReasonAACompromise: "AACompromise",
}
func (r CRLReason) String() string {
if reason, ok := crlReasonNames[r]; ok {
return reason
}
return crlReasonNames[CRLReasonUnspecified]
}
// ParseReason takes a reason string and performs a case-insensitive match to a reason code
func ParseReason(rs string) CRLReason {
for key, name := range crlReasonNames {
if strings.EqualFold(name, rs) {
return key
}
}
return CRLReasonUnspecified
}
const TimeSpec = "060102030405Z" const TimeSpec = "060102030405Z"
type indexStatus string type indexStatus string
@ -81,7 +33,7 @@ type indexEntry struct {
statusFlag indexStatus statusFlag indexStatus
expiresAt time.Time expiresAt time.Time
revokedAt *time.Time revokedAt *time.Time
revocationReason CRLReason revocationReason revoking.CRLReason
serialNumber *big.Int serialNumber *big.Int
fileName string fileName string
certificateSubjectDN string certificateSubjectDN string
@ -119,7 +71,7 @@ type Repository struct {
entries []indexEntry entries []indexEntry
} }
func (ie *indexEntry) markRevoked(revocationTime time.Time, reason CRLReason) { func (ie *indexEntry) markRevoked(revocationTime time.Time, reason revoking.CRLReason) {
if ie.statusFlag == certificateValid { if ie.statusFlag == certificateValid {
ie.statusFlag = certificateRevoked ie.statusFlag = certificateRevoked
ie.revokedAt = &revocationTime ie.revokedAt = &revocationTime
@ -168,10 +120,10 @@ func (r *Repository) StoreRevocation(revoked *pkix.RevokedCertificate) error {
return CannotRevokeUnknown{Serial: revoked.SerialNumber} return CannotRevokeUnknown{Serial: revoked.SerialNumber}
} }
reason := CRLReasonUnspecified reason := revoking.CRLReasonUnspecified
for _, ext := range revoked.Extensions { for _, ext := range revoked.Extensions {
if ext.Id.Equal(OidCRLReason) { if ext.Id.Equal(revoking.OidCRLReason) {
_, err := asn1.Unmarshal(ext.Value, &reason) _, err := asn1.Unmarshal(ext.Value, &reason)
if err != nil { if err != nil {
return fmt.Errorf("could not unmarshal ") return fmt.Errorf("could not unmarshal ")
@ -225,6 +177,31 @@ func (r *Repository) StoreCertificate(signed *x509.Certificate) error {
return nil return nil
} }
func (r *Repository) RevokedCertificates() ([]pkix.RevokedCertificate, error) {
var err error
r.lock.Lock()
defer r.lock.Unlock()
err = r.loadIndex()
if err != nil {
return nil, err
}
result := make([]pkix.RevokedCertificate, 0)
for _, entry := range r.entries {
if entry.revokedAt != nil {
result = append(result, pkix.RevokedCertificate{
SerialNumber: entry.serialNumber,
RevocationTime: *entry.revokedAt,
Extensions: []pkix.Extension{entry.revocationReason.BuildExtension()},
})
}
}
return result, nil
}
func (r *Repository) loadIndex() error { func (r *Repository) loadIndex() error {
entries := make([]indexEntry, 0, 100) entries := make([]indexEntry, 0, 100)
@ -320,14 +297,14 @@ func (r *Repository) newIndexEntryFromLine(text string) (*indexEntry, error) {
} }
var revocationTimeParsed time.Time var revocationTimeParsed time.Time
var revocationReason CRLReason var revocationReason revoking.CRLReason
if fields[2] != "" { if fields[2] != "" {
var timeString string var timeString string
if strings.Contains(fields[2], ",") { if strings.Contains(fields[2], ",") {
parts := strings.SplitN(fields[2], ",", 2) parts := strings.SplitN(fields[2], ",", 2)
timeString = parts[0] timeString = parts[0]
revocationReason = ParseReason(parts[1]) revocationReason = revoking.ParseReason(parts[1])
} else { } else {
timeString = fields[2] timeString = fields[2]
} }

@ -4,7 +4,6 @@ import (
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1"
"math/big" "math/big"
"os" "os"
"path" "path"
@ -13,6 +12,7 @@ import (
"time" "time"
"git.cacert.org/cacert-gosigner/x509/openssl" "git.cacert.org/cacert-gosigner/x509/openssl"
"git.cacert.org/cacert-gosigner/x509/revoking"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -28,19 +28,12 @@ func TestStoreRevocation(t *testing.T) {
t.Errorf("could not create random serial: %v", err) t.Errorf("could not create random serial: %v", err)
} }
extBytes, err := asn1.Marshal(openssl.CRLReasonKeyCompromise)
if err != nil {
t.Errorf("could not marshal revocation reason: %v", err)
}
notAfter := time.Now().UTC().Add(24 * time.Hour).UTC() notAfter := time.Now().UTC().Add(24 * time.Hour).UTC()
err = fr.StoreRevocation(&pkix.RevokedCertificate{ err = fr.StoreRevocation(&pkix.RevokedCertificate{
SerialNumber: serial, SerialNumber: serial,
RevocationTime: notAfter, RevocationTime: notAfter,
Extensions: []pkix.Extension{ Extensions: []pkix.Extension{revoking.CRLReasonKeyCompromise.BuildExtension()},
{Id: openssl.OidCRLReason, Value: extBytes},
},
}) })
assert.ErrorIs(t, err, openssl.CannotRevokeUnknown{Serial: serial}) assert.ErrorIs(t, err, openssl.CannotRevokeUnknown{Serial: serial})
@ -63,9 +56,7 @@ func TestStoreRevocation(t *testing.T) {
err = fr.StoreRevocation(&pkix.RevokedCertificate{ err = fr.StoreRevocation(&pkix.RevokedCertificate{
SerialNumber: serial, SerialNumber: serial,
RevocationTime: time.Now(), RevocationTime: time.Now(),
Extensions: []pkix.Extension{ Extensions: []pkix.Extension{revoking.CRLReasonKeyCompromise.BuildExtension()},
{Id: openssl.OidCRLReason, Value: extBytes},
},
}) })
assert.NoError(t, err) assert.NoError(t, err)

@ -1,7 +1,12 @@
package revoking package revoking
import (
"crypto/x509/pkix"
)
// A Repository for storing certificate status information // A Repository for storing certificate status information
type Repository interface { type Repository interface {
// StoreRevocation stores information about a revoked certificate. // StoreRevocation stores information about a revoked certificate.
StoreRevocation(*CertificateRevoked) error StoreRevocation(*pkix.RevokedCertificate) error
RevokedCertificates() ([]pkix.RevokedCertificate, error)
} }

@ -1,17 +1,88 @@
package revoking package revoking
import ( import (
"crypto"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"log"
"math/big" "math/big"
"strings"
"time" "time"
) )
var OidCRLReason = asn1.ObjectIdentifier{2, 5, 29, 21}
type CRLReason int
// CRL reason codes as defined in RFC 5280 section 5.3.1
const (
CRLReasonUnspecified CRLReason = 0
CRLReasonKeyCompromise CRLReason = 1
CRLReasonCACompromise CRLReason = 2
CRLReasonAffiliationChanged CRLReason = 3
CRLReasonSuperseded CRLReason = 4
CRLReasonCessationOfOperation CRLReason = 5
CRLReasonCertificateHold CRLReason = 6
CRLReasonRemoveFromCRL CRLReason = 8
CRLReasonPrivilegeWithdrawn CRLReason = 9
CRLReasonAACompromise CRLReason = 10
)
var crlReasonNames = map[CRLReason]string{
CRLReasonUnspecified: "unspecified",
CRLReasonKeyCompromise: "keyCompromise",
CRLReasonCACompromise: "CACompromise",
CRLReasonAffiliationChanged: "affiliationChanged",
CRLReasonSuperseded: "superseded",
CRLReasonCessationOfOperation: "cessationOfOperation",
CRLReasonCertificateHold: "certificateHold",
CRLReasonRemoveFromCRL: "removeFromCRL",
CRLReasonPrivilegeWithdrawn: "privilegeWithdrawn",
CRLReasonAACompromise: "AACompromise",
}
func (r CRLReason) String() string {
if reason, ok := crlReasonNames[r]; ok {
return reason
}
return crlReasonNames[CRLReasonUnspecified]
}
func (r CRLReason) BuildExtension() pkix.Extension {
extBytes, err := asn1.Marshal(r)
if err != nil {
// we can panic here because all values of CRLReason must be ASN.1 marshal-able
log.Panicf("could not marshal revocation reason: %v", err)
}
return pkix.Extension{Id: OidCRLReason, Value: extBytes}
}
// ParseReason takes a reason string and performs a case-insensitive match to a reason code
func ParseReason(rs string) CRLReason {
for key, name := range crlReasonNames {
if strings.EqualFold(name, rs) {
return key
}
}
return CRLReasonUnspecified
}
type X509Revoking struct { type X509Revoking struct {
repository Repository repository Repository
crlAlgorithm x509.SignatureAlgorithm
crlIssuer *x509.Certificate
signer crypto.Signer
} }
type RevokeCertificate struct { type RevokeCertificate struct {
serialNumber *big.Int serialNumber *big.Int
reason string reason CRLReason
} }
type CertificateRevoked struct { type CertificateRevoked struct {
@ -20,13 +91,15 @@ type CertificateRevoked struct {
reason string reason string
} }
type CRLInformation struct{} type CRLInformation struct {
CRL []byte // DER encoded CRL
}
func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*CertificateRevoked, error) { func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*pkix.RevokedCertificate, error) {
revoked := &CertificateRevoked{ revoked := &pkix.RevokedCertificate{
serialNumber: revokeCertificate.serialNumber, SerialNumber: revokeCertificate.serialNumber,
revocationTime: time.Now(), RevocationTime: time.Now(),
reason: revokeCertificate.reason, Extensions: []pkix.Extension{revokeCertificate.reason.BuildExtension()},
} }
if err := r.repository.StoreRevocation(revoked); err != nil { if err := r.repository.StoreRevocation(revoked); err != nil {
@ -37,21 +110,27 @@ func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*Certificat
} }
func (r *X509Revoking) CreateCRL() (*CRLInformation, error) { func (r *X509Revoking) CreateCRL() (*CRLInformation, error) {
return &CRLInformation{}, nil revoked, err := r.repository.RevokedCertificates()
} if err != nil {
return nil, fmt.Errorf("could not get revocation information: %w", err)
func (r *CertificateRevoked) SerialNumber() *big.Int { }
return r.serialNumber
}
func (r *CertificateRevoked) RevocationTime() time.Time { list, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
return r.revocationTime SignatureAlgorithm: r.crlAlgorithm,
} RevokedCertificates: revoked,
}, r.crlIssuer, r.signer)
if err != nil {
return nil, fmt.Errorf("could not sign revocation list: %w", err)
}
func (r *CertificateRevoked) Reason() string { return &CRLInformation{CRL: list}, nil
return r.reason
} }
func NewX509Revoking(repo Repository) *X509Revoking { func NewX509Revoking(
return &X509Revoking{repository: repo} repo Repository,
crlAlgorithm x509.SignatureAlgorithm,
issuer *x509.Certificate,
signer crypto.Signer,
) *X509Revoking {
return &X509Revoking{repository: repo, crlAlgorithm: crlAlgorithm, crlIssuer: issuer, signer: signer}
} }

@ -2,8 +2,13 @@ package revoking
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"math/big" "math/big"
rand2 "math/rand"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -12,25 +17,54 @@ type testRepo struct {
revoked []big.Int revoked []big.Int
} }
func (t *testRepo) StoreRevocation(revoked *CertificateRevoked) error { func (t *testRepo) RevokedCertificates() ([]pkix.RevokedCertificate, error) {
t.revoked = append(t.revoked, *revoked.serialNumber) result := make([]pkix.RevokedCertificate, len(t.revoked))
for i, s := range t.revoked {
result[i] = pkix.RevokedCertificate{
SerialNumber: &s,
RevocationTime: time.Now(),
}
}
return result, nil
}
func (t *testRepo) StoreRevocation(revoked *pkix.RevokedCertificate) error {
t.revoked = append(t.revoked, *revoked.SerialNumber)
return nil return nil
} }
func TestRevoking(t *testing.T) { func TestRevoking(t *testing.T) {
testRepository := testRepo{revoked: make([]big.Int, 0)} testRepository := testRepo{revoked: make([]big.Int, 0)}
r := NewX509Revoking(&testRepository)
caKey, err := rsa.GenerateKey(rand.Reader, 3072)
if err != nil {
t.Fatalf("could not generate key pair: %v", err)
}
caTemplate := &x509.Certificate{Subject: pkix.Name{CommonName: "Test CA"}, SerialNumber: big.NewInt(rand2.Int63())}
certificateBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, caKey.Public(), caKey)
if err != nil {
t.Fatalf("could not self-sign CA certificate: %v", err)
}
caCertificate, err := x509.ParseCertificate(certificateBytes)
if err != nil {
t.Fatalf("could not create test CA certificate: %v", err)
}
r := NewX509Revoking(&testRepository, x509.ECDSAWithSHA256, caCertificate, caKey)
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil { if err != nil {
t.Errorf("could not create random serial: %v", err) t.Errorf("could not create random serial: %v", err)
} }
revoke, err := r.Revoke(&RevokeCertificate{serialNumber: serial, reason: "for testing"}) revoke, err := r.Revoke(&RevokeCertificate{serialNumber: serial, reason: CRLReasonKeyCompromise})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "for testing", revoke.reason) assert.Equal(t, CRLReasonKeyCompromise.BuildExtension(), revoke.Extensions[0])
assert.Equal(t, serial, revoke.serialNumber) assert.Equal(t, serial, revoke.SerialNumber)
assert.Contains(t, testRepository.revoked, *serial) assert.Contains(t, testRepository.revoked, *serial)
} }

@ -1,5 +1,7 @@
package signing package signing
import "crypto/x509"
type Repository interface { type Repository interface {
StoreCertificate(*CertificateSigned) error StoreCertificate(certificate *x509.Certificate) error
} }

@ -7,36 +7,12 @@ import (
) )
type SignerRequest struct { type SignerRequest struct {
csr *x509.CertificateRequest CSR *x509.CertificateRequest
subjectDN pkix.Name SubjectDN pkix.Name
emails []string Emails []string
dnsNames []string DnsNames []string
duration time.Duration Duration time.Duration
signatureAlgorithm x509.SignatureAlgorithm SignatureAlgorithm x509.SignatureAlgorithm
}
func (s *SignerRequest) SignatureAlgorithm() x509.SignatureAlgorithm {
return s.signatureAlgorithm
}
func (s *SignerRequest) Duration() time.Duration {
return s.duration
}
func (s *SignerRequest) DnsNames() []string {
return s.dnsNames
}
func (s *SignerRequest) Emails() []string {
return s.emails
}
func (s *SignerRequest) Csr() *x509.CertificateRequest {
return s.csr
}
func (s *SignerRequest) SubjectDN() pkix.Name {
return s.subjectDN
} }
func NewSignerRequest( func NewSignerRequest(
@ -47,19 +23,19 @@ func NewSignerRequest(
signatureAlgorithm x509.SignatureAlgorithm, signatureAlgorithm x509.SignatureAlgorithm,
) *SignerRequest { ) *SignerRequest {
return &SignerRequest{ return &SignerRequest{
csr: csr, CSR: csr,
subjectDN: subjectDN, SubjectDN: subjectDN,
emails: emails, Emails: emails,
dnsNames: dnsNames, DnsNames: dnsNames,
duration: duration, Duration: duration,
signatureAlgorithm: signatureAlgorithm, SignatureAlgorithm: signatureAlgorithm,
} }
} }
type SignerResponse interface { type SignerResponse struct {
Certificate() *x509.Certificate Certificate *x509.Certificate
} }
type Signer interface { type Signer interface {
SignCertificate(*SignerRequest) (SignerResponse, error) SignCertificate(*SignerRequest) (*SignerResponse, error)
} }

@ -73,15 +73,10 @@ func (x *X509Signing) Sign(signingRequest *RequestSignature) (*CertificateSigned
return nil, err return nil, err
} }
result := NewCertificateSigned(certificateFromSigner) err = x.repo.StoreCertificate(certificateFromSigner.Certificate)
err = x.repo.StoreCertificate(result)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return result, nil return &CertificateSigned{certificate: certificateFromSigner.Certificate}, nil
}
func NewCertificateSigned(signed SignerResponse) *CertificateSigned {
return &CertificateSigned{certificate: signed.Certificate()}
} }

@ -19,9 +19,8 @@ type testRepo struct {
certs map[string]x509.Certificate certs map[string]x509.Certificate
} }
func (r *testRepo) StoreCertificate(c *signing.CertificateSigned) error { func (r *testRepo) StoreCertificate(certificate *x509.Certificate) error {
cert := c.Certificate() r.certs[certificate.SerialNumber.Text(16)] = *certificate
r.certs[cert.SerialNumber.Text(16)] = *cert
return nil return nil
} }
@ -30,32 +29,24 @@ type testSigner struct {
certificate *x509.Certificate certificate *x509.Certificate
} }
type testSignerResponse struct { func newTestSignerResponse(certificate *x509.Certificate) *signing.SignerResponse {
certificate *x509.Certificate return &signing.SignerResponse{Certificate: certificate}
}
func (t testSignerResponse) Certificate() *x509.Certificate {
return t.certificate
}
func newTestSignerResponse(certificate *x509.Certificate) *testSignerResponse {
return &testSignerResponse{certificate: certificate}
} }
func (s *testSigner) SignCertificate(request *signing.SignerRequest) (signing.SignerResponse, error) { func (s *testSigner) SignCertificate(request *signing.SignerRequest) (*signing.SignerResponse, error) {
startDate := time.Now().Add(-1 * time.Minute) startDate := time.Now().Add(-1 * time.Minute)
template := &x509.Certificate{ template := &x509.Certificate{
Subject: request.SubjectDN(), Subject: request.SubjectDN,
SerialNumber: big.NewInt(rand2.Int63()), SerialNumber: big.NewInt(rand2.Int63()),
EmailAddresses: request.Emails(), EmailAddresses: request.Emails,
NotBefore: startDate, NotBefore: startDate,
NotAfter: startDate.Add(request.Duration()), NotAfter: startDate.Add(request.Duration),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection},
SignatureAlgorithm: request.SignatureAlgorithm(), SignatureAlgorithm: request.SignatureAlgorithm,
} }
certBytes, err := x509.CreateCertificate(rand.Reader, template, s.certificate, request.Csr().PublicKey, s.key) certBytes, err := x509.CreateCertificate(rand.Reader, template, s.certificate, request.CSR.PublicKey, s.key)
if err != nil { if err != nil {
return nil, err return nil, err
} }

Loading…
Cancel
Save