From 311c9d767bc72582bfbf5d74e6b5d75a51711fee Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 14:00:47 +0100 Subject: [PATCH] Add AuthorizeSign method to SCEP authority --- authority/provisioner/scep.go | 18 ++++++++++++- scep/authority.go | 48 +++++++++++++++++------------------ scep/provisioner.go | 3 ++- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 6cdfa69f..10414b5e 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -1,6 +1,7 @@ package provisioner import ( + "context" "time" "github.com/pkg/errors" @@ -13,7 +14,7 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - // ForceCN bool `json:"forceCN,omitempty"` + ForceCN bool `json:"forceCN,omitempty"` Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer @@ -75,6 +76,21 @@ func (s *SCEP) Init(config Config) (err error) { return err } +// AuthorizeSign does not do any validation, because all validation is handled +// in the SCEP protocol. This method returns a list of modifiers / constraints +// on the resulting certificate. +func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { + return []SignOption{ + // modifiers / withOptions + newProvisionerExtensionOption(TypeSCEP, s.Name, ""), + newForceCNOption(s.ForceCN), + profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, + newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), + }, nil +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/authority.go b/scep/authority.go index cb191d34..c9a2cdb8 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -6,8 +6,6 @@ import ( "crypto/x509" "errors" "fmt" - "math/big" - "math/rand" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" @@ -53,8 +51,6 @@ type Interface interface { // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) - //GetSigningKey() (*rsa.PrivateKey, error) - DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } @@ -201,12 +197,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("parse CSR from pkiEnvelope") + return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) } // check for challengePassword cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("scep: parse challenge password in pkiEnvelope") + return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err) } msg.CSRReqMessage = µscep.CSRReqMessage{ RawDecrypted: msg.pkiEnvelope, @@ -227,6 +223,11 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate + // to be signed, which can be performed asynchronously / out-of-band. In that case a client can + // poll for the status. It seems to be similar as what can happen in ACME, so might want to model + // the implementation after the one in the ACME authority. Requires storage, etc. + p, err := ProvisionerFromContext(ctx) if err != nil { return nil, err @@ -245,32 +246,32 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) data.SetSANs(csr.DNSNames) + data.SetCertificateRequest(csr) - // TODO: proper options - opts := provisioner.SignOptions{} - signOps := []provisioner.SignOption{} + // Get authorizations from the SCEP provisioner. + ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) + signOps, err := p.AuthorizeSign(ctx, "") + if err != nil { + return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) + } + + opts := provisioner.SignOptions{ + // NotBefore: provisioner.NewTimeDuration(o.NotBefore), + // NotAfter: provisioner.NewTimeDuration(o.NotAfter), + } templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) if err != nil { - return nil, fmt.Errorf("error creating template options from SCEP provisioner") + return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err) } signOps = append(signOps, templateOptions) - // // Create and store a new certificate. - // certChain, err := auth.Sign(csr, provisioner.SignOptions{ - // NotBefore: provisioner.NewTimeDuration(o.NotBefore), - // NotAfter: provisioner.NewTimeDuration(o.NotAfter), - // }, signOps...) - // if err != nil { - // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) - // } - - certs, err := a.signAuth.Sign(csr, opts, signOps...) + certChain, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { - return nil, err + return nil, fmt.Errorf("error generating certificate for order %w", err) } - cert := certs[0] + cert := certChain[0] // fmt.Println("CERT") // fmt.Println(cert) @@ -278,9 +279,6 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 // fmt.Println(cert.Issuer) // fmt.Println(cert.Subject) - serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - cert.SerialNumber = serial - // create a degenerate cert structure deg, err := DegenerateCertificates([]*x509.Certificate{cert}) if err != nil { diff --git a/scep/provisioner.go b/scep/provisioner.go index d543d453..64a787d4 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -1,6 +1,7 @@ package scep import ( + "context" "time" "github.com/smallstep/certificates/authority/provisioner" @@ -9,7 +10,7 @@ import ( // Provisioner is an interface that implements a subset of the provisioner.Interface -- // only those methods required by the SCEP api/authority. type Provisioner interface { - // AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) + AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options