forked from TrueCloudLab/certificates
Clean up the SCEP authority and provisioner
This commit is contained in:
parent
a1f187e3df
commit
6985b4be62
9 changed files with 74 additions and 54 deletions
10
api/api.go
10
api/api.go
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
15
ca/ca.go
15
ca/ca.go
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -15,6 +15,7 @@ type Service struct {
|
||||||
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.
|
||||||
|
@ -28,5 +29,10 @@ func NewService(_ context.Context, opts Options) (*Service, error) {
|
||||||
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue