diff --git a/authority/authority.go b/authority/authority.go index 7cd0f2ac..29cbf846 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -62,7 +62,7 @@ type Authority struct { x509Enforcers []provisioner.CertificateEnforcer // SCEP CA - scepService *scep.Service + scepAuthority *scep.Authority // SSH CA sshHostPassword []byte @@ -263,11 +263,12 @@ func (a *Authority) ReloadAdminResources(ctx context.Context) error { a.admins = adminClxn // update the SCEP service with the currently active SCEP - // provisioner names. - // TODO(hs): trigger SCEP authority (re)validation using - // the current set of SCEP provisioners. - if a.scepService != nil { - a.scepService.UpdateProvisioners(a.getSCEPProvisionerNames()) + // provisioner names and revalidate the configuration. + if a.scepAuthority != nil { + a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames()) + if err := a.scepAuthority.Validate(); err != nil { + log.Printf("failed validating SCEP authority: %v\n", err) + } } return nil @@ -696,10 +697,16 @@ func (a *Authority) init() error { // can be validated when the CA is started. options.SCEPProvisionerNames = a.getSCEPProvisionerNames() - a.scepService, err = scep.NewService(ctx, options) + // create a new SCEP authority + a.scepAuthority, err = scep.New(a, options) if err != nil { return err } + + // validate the SCEP authority + if err := a.scepAuthority.Validate(); err != nil { + a.initLogf("failed validating SCEP authority: %v", err) + } } // Load X509 constraints engine. @@ -871,9 +878,9 @@ func (a *Authority) getSCEPProvisionerNames() (names []string) { return } -// GetSCEP returns the configured SCEP Service. -func (a *Authority) GetSCEP() *scep.Service { - return a.scepService +// GetSCEP returns the configured SCEP Authority +func (a *Authority) GetSCEP() *scep.Authority { + return a.scepAuthority } func (a *Authority) startCRLGenerator() error { diff --git a/ca/ca.go b/ca/ca.go index 6b9a2a13..c13496a6 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -250,19 +250,14 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { var scepAuthority *scep.Authority if ca.shouldServeSCEPEndpoints() { - if scepAuthority, err = scep.New(auth, scep.AuthorityOptions{ - Service: auth.GetSCEP(), - }); err != nil { - return nil, errors.Wrap(err, "failed creating SCEP authority") - } - // validate the SCEP authority configuration. Currently this // will not result in a failure to start if one or more SCEP // provisioners are not correctly configured. Only a log will // be emitted. - shouldFail := false + scepAuthority = auth.GetSCEP() if err := scepAuthority.Validate(); err != nil { err = errors.Wrap(err, "failed validating SCEP authority") + shouldFail := false if shouldFail { return nil, err } diff --git a/scep/authority.go b/scep/authority.go index 6f2387c1..55fd2086 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -18,8 +18,13 @@ import ( // Authority is the layer that handles all SCEP interactions. type Authority struct { - service *Service // TODO: refactor, so that this is not required - signAuth SignAuthority + signAuth SignAuthority + roots []*x509.Certificate + intermediates []*x509.Certificate + signerCertificate *x509.Certificate + signer crypto.Signer + defaultDecrypter crypto.Decrypter + scepProvisionerNames []string } type authorityKey struct{} @@ -45,13 +50,6 @@ func MustFromContext(ctx context.Context) *Authority { } } -// AuthorityOptions required to create a new SCEP Authority. -type AuthorityOptions struct { - // Service provides the roots, intermediates, the signer and the (default) - // decrypter to the SCEP Authority. - Service *Service -} - // SignAuthority is the interface for a signing authority type SignAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) @@ -59,10 +57,18 @@ type SignAuthority interface { } // New returns a new Authority that implements the SCEP interface. -func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { +func New(signAuth SignAuthority, opts Options) (*Authority, error) { + if err := opts.Validate(); err != nil { + return nil, err + } authority := &Authority{ - signAuth: signAuth, - service: ops.Service, + signAuth: signAuth, // TODO: provide signAuth through context instead? + roots: opts.Roots, + intermediates: opts.Intermediates, + signerCertificate: opts.SignerCert, + signer: opts.Signer, + defaultDecrypter: opts.Decrypter, + scepProvisionerNames: opts.SCEPProvisionerNames, } return authority, nil } @@ -71,15 +77,15 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { // The validation includes a check if a decrypter is available, either // an authority wide decrypter, or a provisioner specific decrypter. func (a *Authority) Validate() error { - noDefaultDecrypterAvailable := a.service.defaultDecrypter == nil - for _, name := range a.service.scepProvisionerNames { + noDefaultDecrypterAvailable := a.defaultDecrypter == nil + for _, name := range a.scepProvisionerNames { p, err := a.LoadProvisionerByName(name) if err != nil { return fmt.Errorf("failed loading provisioner %q: %w", name, err) } if scepProv, ok := p.(*provisioner.SCEP); ok { cert, decrypter := scepProv.GetDecrypter() - // TODO: return sentinel/typed error, to be able to ignore/log these cases during init? + // TODO(hs): return sentinel/typed error, to be able to ignore/log these cases during init? if cert == nil && noDefaultDecrypterAvailable { return fmt.Errorf("SCEP provisioner %q does not have a decrypter certificate", name) } @@ -92,6 +98,13 @@ func (a *Authority) Validate() error { return nil } +// UpdateProvisioners updates the SCEP Authority with the new, and hopefully +// current SCEP provisioners configured. This allows the Authority to be +// validated with the latest data. +func (a *Authority) UpdateProvisioners(scepProvisionerNames []string) { + a.scepProvisionerNames = scepProvisionerNames +} + var ( // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 defaultCapabilities = []string{ @@ -134,15 +147,14 @@ func (a *Authority) GetCACertificates(ctx context.Context) (certs []*x509.Certif certs = append(certs, decrypterCertificate) } - // TODO: ensure logic, so that signer is first intermediate and that - // there are no doubles certificates. - //certs = append(certs, a.service.signerCertificate) - certs = append(certs, a.service.intermediates...) + // TODO(hs): ensure logic is in place that checks the signer is the first + // intermediate and that there are no double certificates. + certs = append(certs, a.intermediates...) - // the CA roots are added for completeness. Clients are responsible - // to select the right cert(s) to store and use. + // the CA roots are added for completeness when configured to do so. Clients + // are responsible to select the right cert(s) to store and use. if p.ShouldIncludeRootInChain() { - certs = append(certs, a.service.roots...) + certs = append(certs, a.roots...) } return certs, nil @@ -213,8 +225,8 @@ func (a *Authority) selectDecrypter(ctx context.Context) (cert *x509.Certificate } // fallback to the CA wide decrypter - cert = a.service.signerCertificate - pkey = a.service.defaultDecrypter + cert = a.signerCertificate + pkey = a.defaultDecrypter return } @@ -351,8 +363,8 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m // as the first certificate in the array signedData.AddCertificate(cert) - authCert := a.service.signerCertificate - signer := a.service.signer + authCert := a.signerCertificate + signer := a.signer // sign the attributes if err := signedData.AddSigner(authCert, signer, config); err != nil { @@ -423,7 +435,7 @@ func (a *Authority) CreateFailureResponse(_ context.Context, _ *x509.Certificate } // sign the attributes - if err := signedData.AddSigner(a.service.signerCertificate, a.service.signer, config); err != nil { + if err := signedData.AddSigner(a.signerCertificate, a.signer, config); err != nil { return nil, err } diff --git a/scep/common.go b/scep/common.go deleted file mode 100644 index 73b16ed4..00000000 --- a/scep/common.go +++ /dev/null @@ -1,29 +0,0 @@ -package scep - -import ( - "context" - "errors" -) - -// ContextKey is the key type for storing and searching for SCEP request -// essentials in the context of a request. -type ContextKey string - -const ( - // ProvisionerContextKey provisioner key - ProvisionerContextKey = ContextKey("provisioner") -) - -// provisionerFromContext searches the context for a SCEP provisioner. -// Returns the provisioner or an error. -func provisionerFromContext(ctx context.Context) (Provisioner, error) { - val := ctx.Value(ProvisionerContextKey) - if val == nil { - return nil, errors.New("provisioner expected in request context") - } - p, ok := val.(Provisioner) - if !ok || p == nil { - return nil, errors.New("provisioner in context is not a SCEP provisioner") - } - return p, nil -} diff --git a/scep/database.go b/scep/database.go deleted file mode 100644 index f73573fd..00000000 --- a/scep/database.go +++ /dev/null @@ -1,7 +0,0 @@ -package scep - -import "crypto/x509" - -type DB interface { - StoreCertificate(crt *x509.Certificate) error -} diff --git a/scep/provisioner.go b/scep/provisioner.go index cb41ed47..79852e22 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -4,6 +4,7 @@ import ( "context" "crypto" "crypto/x509" + "errors" "time" "github.com/smallstep/certificates/authority/provisioner" @@ -22,3 +23,26 @@ type Provisioner interface { GetContentEncryptionAlgorithm() int ValidateChallenge(ctx context.Context, challenge, transactionID string) error } + +// ContextKey is the key type for storing and searching for SCEP request +// essentials in the context of a request. +type ContextKey string + +const ( + // ProvisionerContextKey provisioner key + ProvisionerContextKey = ContextKey("provisioner") +) + +// provisionerFromContext searches the context for a SCEP provisioner. +// Returns the provisioner or an error. +func provisionerFromContext(ctx context.Context) (Provisioner, error) { + val := ctx.Value(ProvisionerContextKey) + if val == nil { + return nil, errors.New("provisioner expected in request context") + } + p, ok := val.(Provisioner) + if !ok || p == nil { + return nil, errors.New("provisioner in context is not a SCEP provisioner") + } + return p, nil +} diff --git a/scep/service.go b/scep/service.go deleted file mode 100644 index 60d4c8b2..00000000 --- a/scep/service.go +++ /dev/null @@ -1,38 +0,0 @@ -package scep - -import ( - "context" - "crypto" - "crypto/x509" -) - -// Service is a wrapper for a crypto.Decrypter and crypto.Signer for -// decrypting SCEP requests and signing certificates in response to -// SCEP certificate requests. -type Service struct { - roots []*x509.Certificate - intermediates []*x509.Certificate - signerCertificate *x509.Certificate - signer crypto.Signer - defaultDecrypter crypto.Decrypter - scepProvisionerNames []string -} - -// NewService returns a new Service type. -func NewService(_ context.Context, opts Options) (*Service, error) { - if err := opts.Validate(); err != nil { - return nil, err - } - return &Service{ - roots: opts.Roots, - intermediates: opts.Intermediates, - signerCertificate: opts.SignerCert, - signer: opts.Signer, - defaultDecrypter: opts.Decrypter, - scepProvisionerNames: opts.SCEPProvisionerNames, - }, nil -} - -func (s *Service) UpdateProvisioners(scepProvisionerNames []string) { - s.scepProvisionerNames = scepProvisionerNames -}