diff --git a/cmd/scepserver/scepserver.go b/cmd/scepserver/scepserver.go index f74d649..0f01fe9 100644 --- a/cmd/scepserver/scepserver.go +++ b/cmd/scepserver/scepserver.go @@ -130,7 +130,12 @@ func main() { lginfo.Log("err", "missing CA certificate") os.Exit(1) } - signer := scepdepot.CSRSigner(depot, allowRenewal, clientValidity, *flCAPass) + var signer scepserver.CSRSigner = scepdepot.NewSigner( + depot, + scepdepot.WithAllowRenewalDays(allowRenewal), + scepdepot.WithValidityDays(clientValidity), + scepdepot.WithCAPass(*flCAPass), + ) if *flChallengePassword != "" { signer = scepserver.ChallengeMiddleware(*flChallengePassword, signer) } diff --git a/depot/signer.go b/depot/signer.go index eba431b..635c63b 100644 --- a/depot/signer.go +++ b/depot/signer.go @@ -6,72 +6,114 @@ import ( "time" "github.com/micromdm/scep/cryptoutil" - scepserver "github.com/micromdm/scep/server" - "github.com/micromdm/scep/scep" ) -// CSRSigner returns a CSRSignerFunc for use in new scepserver service -func CSRSigner(depot Depot, allowRenewal, clientValidity int, caPass string) scepserver.CSRSignerFunc { - return func(m *scep.CSRReqMessage) (*x509.Certificate, error) { - csr := m.CSR - id, err := cryptoutil.GenerateSubjectKeyID(csr.PublicKey) - if err != nil { - return nil, err - } - - serial, err := depot.Serial() - if err != nil { - return nil, err - } - - // create cert template - tmpl := &x509.Certificate{ - SerialNumber: serial, - Subject: csr.Subject, - NotBefore: time.Now().Add(-600).UTC(), - NotAfter: time.Now().AddDate(0, 0, clientValidity).UTC(), - SubjectKeyId: id, - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - }, - SignatureAlgorithm: csr.SignatureAlgorithm, - DNSNames: csr.DNSNames, - EmailAddresses: csr.EmailAddresses, - IPAddresses: csr.IPAddresses, - URIs: csr.URIs, - } - - crts, key, err := depot.CA([]byte(caPass)) - ca := crts[0] - // sign the CSR creating a DER encoded cert - crtBytes, err := x509.CreateCertificate(rand.Reader, tmpl, ca, m.CSR.PublicKey, key) - if err != nil { - return nil, err - } - // parse the certificate - crt, err := x509.ParseCertificate(crtBytes) - if err != nil { - return nil, err - } - - name := certName(crt) - - // Test if this certificate is already in the CADB, revoke if needed - // revocation is done if the validity of the existing certificate is - // less than allowRenewal (14 days by default) - _, err = depot.HasCN(name, allowRenewal, crt, false) - if err != nil { - return nil, err - } - - if err := depot.Put(name, crt); err != nil { - return nil, err - } - - return crt, nil +// Signer signs x509 certificates and stores them in a Depot +type Signer struct { + depot Depot + caPass string + allowRenewalDays int + validityDays int +} + +// Option customizes Signer +type Option func(*Signer) + +// NewSigner creates a new Signer +func NewSigner(depot Depot, opts ...Option) *Signer { + s := &Signer{ + depot: depot, + allowRenewalDays: 14, + validityDays: 365, + } + for _, opt := range opts { + opt(s) + } + return s +} + +// WithCAPass specifies the password to use with an encrypted CA key +func WithCAPass(pass string) Option { + return func(s *Signer) { + s.caPass = pass + } +} + +// WithAllowRenewalDays sets the allowable renewal time for existing certs +func WithAllowRenewalDays(r int) Option { + return func(s *Signer) { + s.allowRenewalDays = r + } +} + +// WithValidityDays sets the validity period new certs will use +func WithValidityDays(v int) Option { + return func(s *Signer) { + s.validityDays = v + } +} + +// SignCSR signs a certificate using Signer's Depot CA +func (s *Signer) SignCSR(m *scep.CSRReqMessage) (*x509.Certificate, error) { + id, err := cryptoutil.GenerateSubjectKeyID(m.CSR.PublicKey) + if err != nil { + return nil, err } + + serial, err := s.depot.Serial() + if err != nil { + return nil, err + } + + // create cert template + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: m.CSR.Subject, + NotBefore: time.Now().Add(-600).UTC(), + NotAfter: time.Now().AddDate(0, 0, s.validityDays).UTC(), + SubjectKeyId: id, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + }, + SignatureAlgorithm: m.CSR.SignatureAlgorithm, + DNSNames: m.CSR.DNSNames, + EmailAddresses: m.CSR.EmailAddresses, + IPAddresses: m.CSR.IPAddresses, + URIs: m.CSR.URIs, + } + + caCerts, caKey, err := s.depot.CA([]byte(s.caPass)) + if err != nil { + return nil, err + } + + crtBytes, err := x509.CreateCertificate(rand.Reader, tmpl, caCerts[0], m.CSR.PublicKey, caKey) + if err != nil { + return nil, err + } + + crt, err := x509.ParseCertificate(crtBytes) + if err != nil { + return nil, err + } + + name := certName(crt) + + // Test if this certificate is already in the CADB, revoke if needed + // revocation is done if the validity of the existing certificate is + // less than allowRenewalDays + _, err = s.depot.HasCN(name, s.allowRenewalDays, crt, false) + if err != nil { + return nil, err + } + + if err := s.depot.Put(name, crt); err != nil { + return nil, err + } + + return crt, nil } func certName(crt *x509.Certificate) string { diff --git a/server/service_bolt_test.go b/server/service_bolt_test.go index 8166a90..866d1da 100644 --- a/server/service_bolt_test.go +++ b/server/service_bolt_test.go @@ -45,7 +45,7 @@ func TestCaCert(t *testing.T) { caCert := certs[0] // SCEP service - svc, err := scepserver.NewService(caCert, key, scepdepot.CSRSigner(depot, 14, 365, "")) + svc, err := scepserver.NewService(caCert, key, scepdepot.NewSigner(depot)) if err != nil { t.Fatal(err) }