Clean up the SCEP authority and provisioner

This commit is contained in:
Herman Slatman 2023-06-01 14:43:32 +02:00
parent a1f187e3df
commit 6985b4be62
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
9 changed files with 74 additions and 54 deletions

View file

@ -244,9 +244,6 @@ func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
continue continue
} }
fmt.Println(scepProv.KMS)
fmt.Println(fmt.Sprintf("%#+v", scepProv))
type old struct { type old struct {
challengePassword string challengePassword string
decrypterCertificate []byte decrypterCertificate []byte
@ -255,10 +252,9 @@ func (p ProvisionersResponse) MarshalJSON() ([]byte, error) {
} }
o := old{scepProv.ChallengePassword, scepProv.DecrypterCertificate, scepProv.DecrypterKey, scepProv.DecrypterKeyPassword} o := old{scepProv.ChallengePassword, scepProv.DecrypterCertificate, scepProv.DecrypterKey, scepProv.DecrypterKeyPassword}
scepProv.ChallengePassword = "*** REDACTED ***" scepProv.ChallengePassword = "*** REDACTED ***"
// TODO: remove the details in the API response scepProv.DecrypterCertificate = []byte("*** REDACTED ***")
// scepProv.DecrypterCert = "" scepProv.DecrypterKey = "*** REDACTED ***"
// scepProv.DecrypterKey = "" scepProv.DecrypterKeyPassword = "*** REDACTED ***"
// scepProv.DecrtyperKeyPassword = ""
defer func(o old) { //nolint:gocritic // defer in loop required to restore initial state of provisioners defer func(o old) { //nolint:gocritic // defer in loop required to restore initial state of provisioners
scepProv.ChallengePassword = o.challengePassword scepProv.ChallengePassword = o.challengePassword

View file

@ -262,6 +262,14 @@ func (a *Authority) ReloadAdminResources(ctx context.Context) error {
a.config.AuthorityConfig.Admins = adminList a.config.AuthorityConfig.Admins = adminList
a.admins = adminClxn 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())
}
return nil return nil
} }
@ -643,7 +651,7 @@ func (a *Authority) init() error {
// The SCEP functionality is provided through an instance of // The SCEP functionality is provided through an instance of
// scep.Service. It is initialized once when the CA is started. // scep.Service. It is initialized once when the CA is started.
// TODO: should the SCEP service support reloading? For example, // TODO(hs): should the SCEP service support reloading? For example,
// when the admin resources are reloaded, specifically the provisioners, // when the admin resources are reloaded, specifically the provisioners,
// it can happen that the SCEP service is no longer required and can // it can happen that the SCEP service is no longer required and can
// be destroyed, or that it needs to be instantiated. It may also need // be destroyed, or that it needs to be instantiated. It may also need
@ -664,11 +672,11 @@ func (a *Authority) init() error {
return err return err
} }
// TODO: instead of creating the decrypter here, pass the // TODO(hs): instead of creating the decrypter here, pass the
// intermediate key + chain down to the SCEP service / authority, // intermediate key + chain down to the SCEP service / authority,
// and only instantiate it when required there. Is that possible? // and only instantiate it when required there. Is that possible?
// Also with entering passwords? // Also with entering passwords?
// TODO: if moving the logic, try improving the logic for the // TODO(hs): if moving the logic, try improving the logic for the
// decrypter password too? Right now it needs to be entered multiple // decrypter password too? Right now it needs to be entered multiple
// times; I've observed it to be three times maximum, every time // times; I've observed it to be three times maximum, every time
// the intermediate key is read. // the intermediate key is read.
@ -678,11 +686,16 @@ func (a *Authority) init() error {
DecryptionKey: a.config.IntermediateKey, DecryptionKey: a.config.IntermediateKey,
Password: a.password, Password: a.password,
}); err == nil { }); err == nil {
// only pass the decrypter down when it was successfully created // only pass the decrypter down when it was successfully created,
// meaning it's an RSA key, and `CreateDecrypter` did not fail.
options.Decrypter = decrypter options.Decrypter = decrypter
} }
} }
// provide the current SCEP provisioner names, so that the provisioners
// can be validated when the CA is started.
options.SCEPProvisionerNames = a.getSCEPProvisionerNames()
a.scepService, err = scep.NewService(ctx, options) a.scepService, err = scep.NewService(ctx, options)
if err != nil { if err != nil {
return err return err
@ -849,6 +862,15 @@ func (a *Authority) requiresSCEP() bool {
return false return false
} }
func (a *Authority) getSCEPProvisionerNames() (names []string) {
for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeSCEP {
names = append(names, p.GetName())
}
}
return
}
// GetSCEP returns the configured SCEP Service. // GetSCEP returns the configured SCEP Service.
func (a *Authority) GetSCEP() *scep.Service { func (a *Authority) GetSCEP() *scep.Service {
return a.scepService return a.scepService

View file

@ -208,10 +208,9 @@ func (s *SCEP) Init(config Config) (err error) {
return fmt.Errorf("failed creating decrypter: %w", err) return fmt.Errorf("failed creating decrypter: %w", err)
} }
// Parse decrypter certificate // parse the decrypter certificate
block, rest := pem.Decode(s.DecrypterCertificate) block, rest := pem.Decode(s.DecrypterCertificate)
if len(rest) > 0 { if len(rest) > 0 {
fmt.Println(string(rest))
return errors.New("failed parsing decrypter certificate: trailing data") return errors.New("failed parsing decrypter certificate: trailing data")
} }
if block == nil { if block == nil {
@ -221,9 +220,7 @@ func (s *SCEP) Init(config Config) (err error) {
return fmt.Errorf("failed parsing decrypter certificate: %w", err) return fmt.Errorf("failed parsing decrypter certificate: %w", err)
} }
// if s.decrypterCertificate, err = pemutil.ReadCertificate(s.DecrypterCertFile); err != nil { // validate the decrypter key
// return fmt.Errorf("failed reading certificate: %w", err)
// }
decrypterPublicKey, ok := s.decrypter.Public().(*rsa.PublicKey) decrypterPublicKey, ok := s.decrypter.Public().(*rsa.PublicKey)
if !ok { if !ok {
return fmt.Errorf("only RSA keys are supported") return fmt.Errorf("only RSA keys are supported")

View file

@ -152,8 +152,6 @@ retry:
return nil, err return nil, err
} }
fmt.Println(req)
secret, err := base64.StdEncoding.DecodeString(w.Secret) secret, err := base64.StdEncoding.DecodeString(w.Secret)
if err != nil { if err != nil {
return nil, err return nil, err
@ -203,7 +201,6 @@ retry:
time.Sleep(time.Second) time.Sleep(time.Second)
goto retry goto retry
} }
fmt.Println(fmt.Sprintf("%#+v", resp))
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
return nil, fmt.Errorf("Webhook server responded with %d", resp.StatusCode) return nil, fmt.Errorf("Webhook server responded with %d", resp.StatusCode)
} }

View file

@ -256,12 +256,17 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
return nil, errors.Wrap(err, "failed creating SCEP authority") return nil, errors.Wrap(err, "failed creating SCEP authority")
} }
// TODO: validate that the scepAuthority is fully valid? E.g. initialization // validate the SCEP authority configuration. Currently this
// may have configured the default decrypter, but if that's not set or if it's // will not result in a failure to start if one or more SCEP
// somehow not usable, all SCEP provisioners should have a valid decrypter // provisioners are not correctly configured. Only a log will
// configured by now. // be emitted.
shouldFail := false
if err := scepAuthority.Validate(); err != nil { if err := scepAuthority.Validate(); err != nil {
return nil, errors.Wrap(err, "failed validating SCEP authority") err = errors.Wrap(err, "failed validating SCEP authority")
if shouldFail {
return nil, err
}
log.Println(err)
} }
// According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10), // According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10),

View file

@ -308,8 +308,6 @@ func PKIOperation(ctx context.Context, req request) (Response, error) {
transactionID := string(msg.TransactionID) transactionID := string(msg.TransactionID)
challengePassword := msg.CSRReqMessage.ChallengePassword challengePassword := msg.CSRReqMessage.ChallengePassword
fmt.Println("challenge password: ", challengePassword)
// NOTE: we're blocking the RenewalReq if the challenge does not match, because otherwise we don't have any authentication. // NOTE: we're blocking the RenewalReq if the challenge does not match, because otherwise we don't have any authentication.
// The macOS SCEP client performs renewals using PKCSreq. The CertNanny SCEP client will use PKCSreq with challenge too, it seems, // The macOS SCEP client performs renewals using PKCSreq. The CertNanny SCEP client will use PKCSreq with challenge too, it seems,
// even if using the renewal flow as described in the README.md. MicroMDM SCEP client also only does PKCSreq by default, unless // even if using the renewal flow as described in the README.md. MicroMDM SCEP client also only does PKCSreq by default, unless
@ -317,7 +315,6 @@ func PKIOperation(ctx context.Context, req request) (Response, error) {
// We'll have to see how it works out. // We'll have to see how it works out.
if msg.MessageType == microscep.PKCSReq || msg.MessageType == microscep.RenewalReq { if msg.MessageType == microscep.PKCSReq || msg.MessageType == microscep.RenewalReq {
if err := auth.ValidateChallenge(ctx, challengePassword, transactionID); err != nil { if err := auth.ValidateChallenge(ctx, challengePassword, transactionID); err != nil {
fmt.Println(err)
if errors.Is(err, provisioner.ErrSCEPChallengeInvalid) { if errors.Is(err, provisioner.ErrSCEPChallengeInvalid) {
return createFailureResponse(ctx, csr, msg, microscep.BadRequest, err) return createFailureResponse(ctx, csr, msg, microscep.BadRequest, err)
} }

View file

@ -67,22 +67,24 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
return authority, nil return authority, nil
} }
// Validate validates if the SCEP Authority has a valid configuration.
// 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 { func (a *Authority) Validate() error {
// if a default decrypter is set, the Authority is able noDefaultDecrypterAvailable := a.service.defaultDecrypter == nil
// to decrypt SCEP requests. No need to verify the provisioners. for _, name := range a.service.scepProvisionerNames {
if a.service.defaultDecrypter != nil {
return nil
}
for _, name := range []string{"scepca"} { // TODO: correct names; provided through options
p, err := a.LoadProvisionerByName(name) p, err := a.LoadProvisionerByName(name)
if err != nil { if err != nil {
fmt.Println("prov load fail: %w", err) return fmt.Errorf("failed loading provisioner %q: %w", name, err)
} }
if scepProv, ok := p.(*provisioner.SCEP); ok { if scepProv, ok := p.(*provisioner.SCEP); ok {
if cert, decrypter := scepProv.GetDecrypter(); cert == nil || decrypter == nil { cert, decrypter := scepProv.GetDecrypter()
fmt.Println(fmt.Sprintf("SCEP provisioner %q doesn't have valid decrypter", scepProv.GetName())) // TODO: return sentinel/typed error, to be able to ignore/log these cases during init?
// TODO: return error if cert == nil && noDefaultDecrypterAvailable {
return fmt.Errorf("SCEP provisioner %q does not have a decrypter certificate", name)
}
if decrypter == nil && noDefaultDecrypterAvailable {
return fmt.Errorf("SCEP provisioner %q does not have decrypter", name)
} }
} }
} }
@ -153,17 +155,11 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
return fmt.Errorf("error parsing pkcs7 content: %w", err) return fmt.Errorf("error parsing pkcs7 content: %w", err)
} }
fmt.Println(fmt.Sprintf("%#+v", a.service.signerCertificate))
fmt.Println(fmt.Sprintf("%#+v", a.service.defaultDecrypter))
cert, pkey, err := a.selectDecrypter(ctx) cert, pkey, err := a.selectDecrypter(ctx)
if err != nil { if err != nil {
return fmt.Errorf("failed selecting decrypter: %w", err) return fmt.Errorf("failed selecting decrypter: %w", err)
} }
fmt.Println(fmt.Sprintf("%#+v", cert))
fmt.Println(fmt.Sprintf("%#+v", pkey))
envelope, err := p7c.Decrypt(cert, pkey) envelope, err := p7c.Decrypt(cert, pkey)
if err != nil { if err != nil {
return fmt.Errorf("error decrypting encrypted pkcs7 content: %w", err) return fmt.Errorf("error decrypting encrypted pkcs7 content: %w", err)

View file

@ -20,6 +20,10 @@ type Options struct {
Signer crypto.Signer `json:"-"` Signer crypto.Signer `json:"-"`
// Decrypter decrypts encrypted SCEP messages. Configured in the ca.json key property. // Decrypter decrypts encrypted SCEP messages. Configured in the ca.json key property.
Decrypter crypto.Decrypter `json:"-"` Decrypter crypto.Decrypter `json:"-"`
// SCEPProvisionerNames contains the currently configured SCEP provioner names. These
// are used to be able to load the provisioners when the SCEP authority is being
// validated.
SCEPProvisionerNames []string
} }
type comparablePublicKey interface { type comparablePublicKey interface {

View file

@ -10,11 +10,12 @@ import (
// decrypting SCEP requests and signing certificates in response to // decrypting SCEP requests and signing certificates in response to
// SCEP certificate requests. // SCEP certificate requests.
type Service struct { type Service struct {
roots []*x509.Certificate roots []*x509.Certificate
intermediates []*x509.Certificate intermediates []*x509.Certificate
signerCertificate *x509.Certificate signerCertificate *x509.Certificate
signer crypto.Signer signer crypto.Signer
defaultDecrypter crypto.Decrypter defaultDecrypter crypto.Decrypter
scepProvisionerNames []string
} }
// NewService returns a new Service type. // NewService returns a new Service type.
@ -23,10 +24,15 @@ func NewService(_ context.Context, opts Options) (*Service, error) {
return nil, err return nil, err
} }
return &Service{ return &Service{
roots: opts.Roots, roots: opts.Roots,
intermediates: opts.Intermediates, intermediates: opts.Intermediates,
signerCertificate: opts.SignerCert, signerCertificate: opts.SignerCert,
signer: opts.Signer, signer: opts.Signer,
defaultDecrypter: opts.Decrypter, defaultDecrypter: opts.Decrypter,
scepProvisionerNames: opts.SCEPProvisionerNames,
}, nil }, nil
} }
func (s *Service) UpdateProvisioners(scepProvisionerNames []string) {
s.scepProvisionerNames = scepProvisionerNames
}