From 180162bd6a82b0ad1b34daa4d1f5f39cca37d591 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 1 Jun 2023 12:10:54 +0200 Subject: [PATCH] Refactor SCEP provisioner and decrypter --- api/api.go | 9 ++- authority/authority.go | 81 +++++++-------------- authority/authority_test.go | 4 +- authority/provisioner/scep.go | 26 +++++-- authority/provisioners.go | 20 ++++- ca/ca.go | 27 ++++--- go.mod | 20 ++--- go.sum | 39 +++++----- scep/authority.go | 133 +++++++++++++--------------------- scep/options.go | 99 +++++++++++++------------ scep/service.go | 26 +++---- 11 files changed, 234 insertions(+), 250 deletions(-) diff --git a/api/api.go b/api/api.go index 0474471a..849fd24f 100644 --- a/api/api.go +++ b/api/api.go @@ -244,13 +244,16 @@ func (p ProvisionersResponse) MarshalJSON() ([]byte, error) { continue } + fmt.Println(scepProv.KMS) + fmt.Println(fmt.Sprintf("%#+v", scepProv)) + type old struct { challengePassword string - decrypterCertificate string + decrypterCertificate []byte decrypterKey string decrypterKeyPassword string } - o := old{scepProv.ChallengePassword, scepProv.DecrypterCert, scepProv.DecrypterKey, scepProv.DecrypterKeyPassword} + o := old{scepProv.ChallengePassword, scepProv.DecrypterCertificate, scepProv.DecrypterKey, scepProv.DecrypterKeyPassword} scepProv.ChallengePassword = "*** REDACTED ***" // TODO: remove the details in the API response // scepProv.DecrypterCert = "" @@ -259,7 +262,7 @@ func (p ProvisionersResponse) MarshalJSON() ([]byte, error) { defer func(o old) { //nolint:gocritic // defer in loop required to restore initial state of provisioners scepProv.ChallengePassword = o.challengePassword - scepProv.DecrypterCert = o.decrypterCertificate + scepProv.DecrypterCertificate = o.decrypterCertificate scepProv.DecrypterKey = o.decrypterKey scepProv.DecrypterKeyPassword = o.decrypterKeyPassword }(o) diff --git a/authority/authority.go b/authority/authority.go index ef51b61d..8b93a634 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -641,56 +641,45 @@ func (a *Authority) init() error { return err } - // Check if a KMS with decryption capability is required and available - if a.requiresDecrypter() { - if _, ok := a.keyManager.(kmsapi.Decrypter); !ok { - return errors.New("keymanager doesn't provide crypto.Decrypter") - } - } - - // TODO: decide if this is a good approach for providing the SCEP functionality - // It currently mirrors the logic for the x509CAService - if a.requiresSCEPService() && a.scepService == nil { + // The SCEP functionality is provided through an instance of + // scep.Service. It is initialized once when the CA is started. + // TODO: should the SCEP service support reloading? For example, + // when the admin resources are reloaded, specifically the provisioners, + // 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 + // to be revalidated, because not all SCEP provisioner may have a + // valid decrypter available. + if a.requiresSCEP() && a.GetSCEP() == nil { var options scep.Options - - // Read intermediate and create X509 signer and decrypter for default CAS. - options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + options.Roots = a.rootX509Certs + options.Intermediates, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) if err != nil { return err } - options.CertificateChain = append(options.CertificateChain, a.rootX509Certs...) - options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + options.SignerCert = options.Intermediates[0] + if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.IntermediateKey, Password: a.password, - }) - if err != nil { + }); err != nil { return err } - options.SignerCert = options.CertificateChain[0] - options.DecrypterCert = options.CertificateChain[0] - // TODO: instead of creating the decrypter here, pass the // intermediate key + chain down to the SCEP service / authority, - // and only instantiate it when required there. + // and only instantiate it when required there. Is that possible? + // Also with entering passwords? // TODO: if moving the logic, try improving the logic for the - // decrypter password too? - if km, ok := a.keyManager.(kmsapi.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + // decrypter password too? Right now it needs to be entered multiple + // times; I've observed it to be three times maximum, every time + // the intermediate key is read. + _, isRSA := options.Signer.Public().(*rsa.PublicKey) + if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA { + if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ DecryptionKey: a.config.IntermediateKey, Password: a.password, - }) - if err == nil { - // when creating the decrypter fails, ignore the error - // TODO(hs): decide if this is OK. It could fail at startup, but it - // could be up later. Right now decryption would always fail. - key, ok := options.Decrypter.Public().(*rsa.PublicKey) - if !ok { - return errors.New("only RSA keys are currently supported as decrypters") - } - if !key.Equal(options.DecrypterCert.PublicKey) { - return errors.New("mismatch between decryption certificate and decrypter public keys") - } + }); err == nil { + // only pass the decrypter down when it was successfully created + options.Decrypter = decrypter } } @@ -698,8 +687,6 @@ func (a *Authority) init() error { if err != nil { return err } - - // TODO: mimick the x509CAService GetCertificateAuthority here too? } // Load X509 constraints engine. @@ -851,17 +838,9 @@ func (a *Authority) IsRevoked(sn string) (bool, error) { return a.db.IsRevoked(sn) } -// requiresDecrypter returns whether the Authority -// requires a KMS that provides a crypto.Decrypter -// Currently this is only required when SCEP is -// enabled. -func (a *Authority) requiresDecrypter() bool { - return a.requiresSCEPService() -} - // requiresSCEPService iterates over the configured provisioners // and determines if one of them is a SCEP provisioner. -func (a *Authority) requiresSCEPService() bool { +func (a *Authority) requiresSCEP() bool { for _, p := range a.config.AuthorityConfig.Provisioners { if p.GetType() == provisioner.TypeSCEP { return true @@ -870,12 +849,8 @@ func (a *Authority) requiresSCEPService() bool { return false } -// GetSCEPService returns the configured SCEP Service. -// -// TODO: this function is intended to exist temporarily in order to make SCEP -// work more easily. It can be made more correct by using the right -// interfaces/abstractions after it works as expected. -func (a *Authority) GetSCEPService() *scep.Service { +// GetSCEP returns the configured SCEP Service. +func (a *Authority) GetSCEP() *scep.Service { return a.scepService } diff --git a/authority/authority_test.go b/authority/authority_test.go index 82a05a3e..45c7cd86 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -478,7 +478,7 @@ func testScepAuthority(t *testing.T, opts ...Option) *Authority { return a } -func TestAuthority_GetSCEPService(t *testing.T) { +func TestAuthority_GetSCEP(t *testing.T) { _ = testScepAuthority(t) p := provisioner.List{ &provisioner.SCEP{ @@ -542,7 +542,7 @@ func TestAuthority_GetSCEPService(t *testing.T) { return } if tt.wantService { - if got := a.GetSCEPService(); (got != nil) != tt.wantService { + if got := a.GetSCEP(); (got != nil) != tt.wantService { t.Errorf("Authority.GetSCEPService() = %v, wantService %v", got, tt.wantService) } } diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 77c02e8f..192c75b1 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "crypto/subtle" "crypto/x509" + "encoding/pem" "fmt" "net/http" "time" @@ -14,7 +15,6 @@ import ( "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" - "go.step.sm/crypto/pemutil" "go.step.sm/linkedca" "github.com/smallstep/certificates/webhook" @@ -40,7 +40,7 @@ type SCEP struct { // TODO KMS *kms.Options `json:"kms,omitempty"` - DecrypterCert string `json:"decrypterCert"` + DecrypterCertificate []byte `json:"decrypterCertificate"` DecrypterKey string `json:"decrypterKey"` DecrypterKeyPassword string `json:"decrypterKeyPassword"` @@ -198,18 +198,32 @@ func (s *SCEP) Init(config Config) (err error) { } km, ok := s.keyManager.(kmsapi.Decrypter) if !ok { - return fmt.Errorf(`%q is not a kmsapi.Decrypter`, s.KMS.Type) + return fmt.Errorf("%q is not a kmsapi.Decrypter", s.KMS.Type) } - if s.DecrypterKey != "" || s.DecrypterCert != "" { + if s.DecrypterKey != "" || len(s.DecrypterCertificate) > 0 { if s.decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ DecryptionKey: s.DecrypterKey, Password: []byte(s.DecrypterKeyPassword), }); err != nil { return fmt.Errorf("failed creating decrypter: %w", err) } - if s.decrypterCertificate, err = pemutil.ReadCertificate(s.DecrypterCert); err != nil { - return fmt.Errorf("failed reading certificate: %w", err) + + // Parse decrypter certificate + block, rest := pem.Decode(s.DecrypterCertificate) + if len(rest) > 0 { + fmt.Println(string(rest)) + return errors.New("failed parsing decrypter certificate: trailing data") + } + if block == nil { + return errors.New("failed parsing decrypter certificate: no PEM block found") } + if s.decrypterCertificate, err = x509.ParseCertificate(block.Bytes); err != nil { + return fmt.Errorf("failed parsing decrypter certificate: %w", err) + } + + // if s.decrypterCertificate, err = pemutil.ReadCertificate(s.DecrypterCertFile); err != nil { + // return fmt.Errorf("failed reading certificate: %w", err) + // } decrypterPublicKey, ok := s.decrypter.Public().(*rsa.PublicKey) if !ok { return fmt.Errorf("only RSA keys are supported") diff --git a/authority/provisioners.go b/authority/provisioners.go index 5d594536..35030933 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -8,6 +8,7 @@ import ( "encoding/pem" "fmt" "os" + "strings" "github.com/pkg/errors" "gopkg.in/square/go-jose.v2/jwt" @@ -15,6 +16,7 @@ import ( "go.step.sm/cli-utils/step" "go.step.sm/cli-utils/ui" "go.step.sm/crypto/jose" + "go.step.sm/crypto/kms" "go.step.sm/linkedca" "github.com/smallstep/certificates/authority/admin" @@ -235,7 +237,7 @@ func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisi } if err := certProv.Init(provisionerConfig); err != nil { - return admin.WrapError(admin.ErrorBadRequestType, err, "error validating configuration for provisioner %s", prov.Name) + return admin.WrapError(admin.ErrorBadRequestType, err, "error validating configuration for provisioner %q", prov.Name) } // Store to database -- this will set the ID. @@ -960,7 +962,7 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, }, nil case *linkedca.ProvisionerDetails_SCEP: cfg := d.SCEP - return &provisioner.SCEP{ + s := &provisioner.SCEP{ ID: p.Id, Type: p.Type.String(), Name: p.Name, @@ -972,7 +974,19 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, EncryptionAlgorithmIdentifier: int(cfg.EncryptionAlgorithmIdentifier), Claims: claims, Options: options, - }, nil + } + if decrypter := cfg.GetDecrypter(); decrypter != nil { + if dkms := decrypter.GetKms(); dkms != nil { + s.KMS = &kms.Options{ + Type: kms.Type(strings.ToLower(linkedca.KMS_Type_name[int32(dkms.Type)])), + CredentialsFile: dkms.CredentialsFile, + } + } + s.DecrypterCertificate = decrypter.DecrypterCertificate + s.DecrypterKey = decrypter.DecrypterKey + s.DecrypterKeyPassword = decrypter.DecrypterKeyPassword + } + return s, nil case *linkedca.ProvisionerDetails_Nebula: var roots []byte for i, root := range d.Nebula.GetRoots() { diff --git a/ca/ca.go b/ca/ca.go index b8f65332..a3f261e7 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -250,19 +250,24 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { var scepAuthority *scep.Authority if ca.shouldServeSCEPEndpoints() { - scepPrefix := "scep" - scepAuthority, err = scep.New(auth, scep.AuthorityOptions{ - Service: auth.GetSCEPService(), - DNS: dns, - Prefix: scepPrefix, - }) - if err != nil { - return nil, errors.Wrap(err, "error creating SCEP authority") + if scepAuthority, err = scep.New(auth, scep.AuthorityOptions{ + Service: auth.GetSCEP(), + }); err != nil { + return nil, errors.Wrap(err, "failed creating SCEP authority") + } + + // TODO: validate that the scepAuthority is fully valid? E.g. initialization + // may have configured the default decrypter, but if that's not set or if it's + // somehow not usable, all SCEP provisioners should have a valid decrypter + // configured by now. + if err := scepAuthority.Validate(); err != nil { + return nil, errors.Wrap(err, "failed validating SCEP authority") } // According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10), // SCEP operations are performed using HTTP, so that's why the API is mounted // to the insecure mux. + scepPrefix := "scep" insecureMux.Route("/"+scepPrefix, func(r chi.Router) { scepAPI.Route(r) }) @@ -584,10 +589,10 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, *tls.Config, // shouldServeSCEPEndpoints returns if the CA should be // configured with endpoints for SCEP. This is assumed to be -// true if a SCEPService exists, which is true in case a -// SCEP provisioner was configured. +// true if a SCEPService exists, which is true in case at +// least one SCEP provisioner was configured. func (ca *CA) shouldServeSCEPEndpoints() bool { - return ca.auth.GetSCEPService() != nil + return ca.auth.GetSCEP() != nil } //nolint:unused // useful for debugging diff --git a/go.mod b/go.mod index 220ae3f3..b35d3d61 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.3.3 github.com/google/uuid v1.3.0 - github.com/googleapis/gax-go/v2 v2.8.0 + github.com/googleapis/gax-go/v2 v2.9.0 github.com/hashicorp/vault/api v1.9.1 github.com/hashicorp/vault/api/auth/approle v0.4.0 github.com/hashicorp/vault/api/auth/kubernetes v0.4.0 @@ -25,16 +25,16 @@ require ( github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738 github.com/smallstep/nosql v0.6.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/urfave/cli v1.22.13 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.step.sm/cli-utils v0.7.6 - go.step.sm/crypto v0.30.0 + go.step.sm/crypto v0.31.0 go.step.sm/linkedca v0.19.1 - golang.org/x/crypto v0.8.0 + golang.org/x/crypto v0.9.0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 - golang.org/x/net v0.9.0 - google.golang.org/api v0.121.0 + golang.org/x/net v0.10.0 + google.golang.org/api v0.123.0 google.golang.org/grpc v1.55.0 google.golang.org/protobuf v1.30.0 gopkg.in/square/go-jose.v2 v2.6.0 @@ -57,7 +57,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go v1.44.259 // indirect + github.com/aws/aws-sdk-go v1.44.267 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -80,7 +80,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/certificate-transparency-go v1.1.4 // indirect - github.com/google/go-tpm-tools v0.3.11 // indirect + github.com/google/go-tpm-tools v0.3.12 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.3 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect @@ -129,7 +129,7 @@ require ( go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.1.0 // indirect google.golang.org/appengine v1.6.7 // indirect @@ -145,3 +145,5 @@ require ( // use github.com/smallstep/pkcs7 fork with patches applied replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20230302202335-4c094085c948 + +replace go.step.sm/linkedca => ./../linkedca diff --git a/go.sum b/go.sum index 2065094e..e19265ad 100644 --- a/go.sum +++ b/go.sum @@ -165,8 +165,8 @@ github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.259 h1:7yDn1dcv4DZFMKpu+2exIH5O6ipNj9qXrKfdMUaIJwY= -github.com/aws/aws-sdk-go v1.44.259/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.267 h1:Asrp6EMqqRxZvjK0NjzkWcrOk15RnWtupuUrUuZMabk= +github.com/aws/aws-sdk-go v1.44.267/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -430,7 +430,7 @@ github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOm github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= -github.com/google/go-sev-guest v0.5.2 h1:dlCehnxU9aJWEIcTb0j7oZ/yM4qeno7AO6zWokb4mu0= +github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= github.com/google/go-tpm v0.3.2/go.mod h1:j71sMBTfp3X5jPHz852ZOfQMUOf65Gb/Th8pRmp7fvg= @@ -441,8 +441,8 @@ github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N github.com/google/go-tpm-tools v0.2.1/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= github.com/google/go-tpm-tools v0.3.1/go.mod h1:PSg+r5hSZI5tP3X7LBQx2sW1VSZUqZHBSrKyDqrB21U= github.com/google/go-tpm-tools v0.3.9/go.mod h1:22JvWmHcD5w55cs+nMeqDGDxgNS15/2pDq2cLqnc3rc= -github.com/google/go-tpm-tools v0.3.11 h1:imObhmECgDS+ua4aAVPkMfCzE9LTZjS/MmVMCrAG4VY= -github.com/google/go-tpm-tools v0.3.11/go.mod h1:5UcOsOyG5B2hWhKsqNI3TtOjTcZs5sh+3913uMN29Y8= +github.com/google/go-tpm-tools v0.3.12 h1:hpWglH4RaZnGVbgOK3IThI5K++jnFvjQ94EIN34xrUU= +github.com/google/go-tpm-tools v0.3.12/go.mod h1:2OtmyPGPuaWWIOjr+IDhNQb6t5njjbSmZtzc350Q6Ro= github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= @@ -491,8 +491,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5 github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.9.0 h1:ie5/2yPjucjZW6fEGjLhS5+PhEg6owWMrFB5d7TFFhw= +github.com/googleapis/gax-go/v2 v2.9.0/go.mod h1:qf/E3rjAvrwLsAnQW+IClIu+z03yUf4KOoO82NfZ+QY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/goreleaser/goreleaser v0.134.0/go.mod h1:ZT6Y2rSYa6NxQzIsdfWWNWAlYGXGbreo66NmE+3X3WQ= @@ -968,8 +968,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= @@ -1062,8 +1063,8 @@ go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16g go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.step.sm/cli-utils v0.7.6 h1:YkpLVrepmy2c5+eaz/wduiGxlgrRx3YdAStE37if25g= go.step.sm/cli-utils v0.7.6/go.mod h1:j+FxFZ2gbWkAJl0eded/rksuxmNqWpmyxbkXcukGJaY= -go.step.sm/crypto v0.30.0 h1:EzqPTvW1g6kxEnfIf/exDW+MhHGeEhtoNMhQX7P/UwI= -go.step.sm/crypto v0.30.0/go.mod h1:6jFFgUoafyHvb6rNq3NJrBByof4SCzj1n8ThyXuMVAM= +go.step.sm/crypto v0.31.0 h1:8ZG/BxC+0+LzPpk/764h5yubpG3GfxcRVR4E+Aye72g= +go.step.sm/crypto v0.31.0/go.mod h1:Dv4lpkijKiZVkoc6zp+Xaw1xmy+voia1mykvbpQIvuc= go.step.sm/linkedca v0.19.1 h1:uY0ByT/uB3FCQ8zIo9mU7MWG7HKf5sDXNEBeN94MuP8= go.step.sm/linkedca v0.19.1/go.mod h1:vPV2ad3LFQJmV7XWt87VlnJSs6UOqgsbVGVWe3veEmI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1111,8 +1112,8 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1211,8 +1212,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1340,8 +1341,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1349,7 +1350,7 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1490,8 +1491,8 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.121.0 h1:8Oopoo8Vavxx6gt+sgs8s8/X60WBAtKQq6JqnkF+xow= -google.golang.org/api v0.121.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.123.0 h1:yHVU//vA+qkOhm4reEC9LtzHVUCN/IqqNRl1iQ9xE20= +google.golang.org/api v0.123.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/scep/authority.go b/scep/authority.go index af9bdf42..14e5cd1c 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "errors" "fmt" - "net/url" microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util" microscep "github.com/micromdm/scep/v2/scep" @@ -19,9 +18,7 @@ import ( // Authority is the layer that handles all SCEP interactions. type Authority struct { - prefix string - dns string - service *Service + service *Service // TODO: refactor, so that this is not required signAuth SignAuthority } @@ -50,15 +47,9 @@ func MustFromContext(ctx context.Context) *Authority { // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - // Service provides the certificate chain, the signer and the decrypter to the Authority + // Service provides the roots, intermediates, the signer and the (default) + // decrypter to the SCEP Authority. Service *Service - // DNS is the host used to generate accurate SCEP links. By default the authority - // will use the Host from the request, so this value will only be used if - // request.Host is empty. - DNS string - // Prefix is a URL path prefix under which the SCEP api is served. This - // prefix is required to generate accurate SCEP links. - Prefix string } // SignAuthority is the interface for a signing authority @@ -70,14 +61,35 @@ type SignAuthority interface { // New returns a new Authority that implements the SCEP interface. func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { authority := &Authority{ - prefix: ops.Prefix, - dns: ops.DNS, signAuth: signAuth, service: ops.Service, } return authority, nil } +func (a *Authority) Validate() error { + // if a default decrypter is set, the Authority is able + // to decrypt SCEP requests. No need to verify the provisioners. + if a.service.defaultDecrypter != nil { + return nil + } + + for _, name := range []string{"scepca"} { // TODO: correct names; provided through options + p, err := a.LoadProvisionerByName(name) + if err != nil { + fmt.Println("prov load fail: %w", err) + } + if scepProv, ok := p.(*provisioner.SCEP); ok { + if cert, decrypter := scepProv.GetDecrypter(); cert == nil || decrypter == nil { + fmt.Println(fmt.Sprintf("SCEP provisioner %q doesn't have valid decrypter", scepProv.GetName())) + // TODO: return error + } + } + } + + return nil +} + var ( // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 defaultCapabilities = []string{ @@ -97,79 +109,38 @@ func (a *Authority) LoadProvisionerByName(name string) (provisioner.Interface, e return a.signAuth.LoadProvisionerByName(name) } -// GetLinkExplicit returns the requested link from the directory. -func (a *Authority) GetLinkExplicit(provName string, abs bool, baseURL *url.URL, inputs ...string) string { - return a.getLinkExplicit(provName, abs, baseURL, inputs...) -} - -// getLinkExplicit returns an absolute or partial path to the given resource and a base -// URL dynamically obtained from the request for which the link is being calculated. -func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *url.URL, _ ...string) string { - link := "/" + provisionerName - if abs { - // Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351 - u := url.URL{} - if baseURL != nil { - u = *baseURL - } - - // If no Scheme is set, then default to http (in case of SCEP) - if u.Scheme == "" { - u.Scheme = "http" - } - - // If no Host is set, then use the default (first DNS attr in the ca.json). - if u.Host == "" { - u.Host = a.dns - } - - u.Path = a.prefix + link - return u.String() - } - - return link -} - -// GetCACertificates returns the certificate (chain) for the CA -func (a *Authority) GetCACertificates(ctx context.Context) ([]*x509.Certificate, error) { - // TODO: this should return: the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root - // Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73 - // - // This means we might need to think about if we should use the current intermediate CA - // certificate as the "SCEP Server (RA)" certificate. It might be better to have a distinct - // RA certificate, with a corresponding rsa.PrivateKey, just for SCEP usage, which is signed by - // the intermediate CA. Will need to look how we can provide this nicely within step-ca. - // - // This might also mean that we might want to use a distinct instance of KMS for doing the key operations, - // so that we can use RSA just for SCEP. - // - // Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in - // https://tools.ietf.org/id/draft-nourse-scep-21.html. Will continue using the CA directly for now. - // - // The certificate to use should probably depend on the (configured) provisioner and may - // use a distinct certificate, apart from the intermediate. - +// GetCACertificates returns the certificate (chain) for the CA. +// +// This methods returns the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root. +// Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73 +// +// In case a provisioner specific decrypter is available, this is used as the "SCEP Server (RA)" certificate +// instead of the CA intermediate directly. This uses a distinct instance of a KMS for doing the SCEp key +// operations, so that RSA can be used for just SCEP. +// +// Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in +// https://tools.ietf.org/id/draft-nourse-scep-21.html. +func (a *Authority) GetCACertificates(ctx context.Context) (certs []*x509.Certificate, err error) { p, err := provisionerFromContext(ctx) if err != nil { - return nil, err - } - - if len(a.service.certificateChain) == 0 { - return nil, errors.New("no intermediate certificate available in SCEP authority") + return } - certs := []*x509.Certificate{} + // if a provisioner specific RSA decrypter is available, it is returned as + // the first certificate. if decrypterCertificate, _ := p.GetDecrypter(); decrypterCertificate != nil { certs = append(certs, decrypterCertificate) - certs = append(certs, a.service.signerCertificate) - } else { - certs = append(certs, a.service.defaultDecrypterCertificate) } - // NOTE: we're adding the CA roots here, but they are (highly likely) different than what the RFC means. - // Clients are responsible to select the right cert(s) to use, though. - if p.ShouldIncludeRootInChain() && len(a.service.certificateChain) > 1 { - certs = append(certs, a.service.certificateChain[1]) + // 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...) + + // the CA roots are added for completeness. Clients are responsible + // to select the right cert(s) to store and use. + if p.ShouldIncludeRootInChain() { + certs = append(certs, a.service.roots...) } return certs, nil @@ -182,7 +153,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return fmt.Errorf("error parsing pkcs7 content: %w", err) } - fmt.Println(fmt.Sprintf("%#+v", a.service.defaultDecrypterCertificate)) + fmt.Println(fmt.Sprintf("%#+v", a.service.signerCertificate)) fmt.Println(fmt.Sprintf("%#+v", a.service.defaultDecrypter)) cert, pkey, err := a.selectDecrypter(ctx) @@ -246,7 +217,7 @@ func (a *Authority) selectDecrypter(ctx context.Context) (cert *x509.Certificate } // fallback to the CA wide decrypter - cert = a.service.defaultDecrypterCertificate + cert = a.service.signerCertificate pkey = a.service.defaultDecrypter return diff --git a/scep/options.go b/scep/options.go index 00662ae9..43f41fba 100644 --- a/scep/options.go +++ b/scep/options.go @@ -2,77 +2,76 @@ package scep import ( "crypto" + "crypto/rsa" "crypto/x509" - - "github.com/pkg/errors" + "errors" ) type Options struct { - // CertificateChain is the issuer certificate, along with any other bundled certificates - // to be returned in the chain for consumers. Configured in the ca.json crt property. - CertificateChain []*x509.Certificate - SignerCert *x509.Certificate - DecrypterCert *x509.Certificate + // Roots contains the (federated) CA roots certificate(s) + Roots []*x509.Certificate `json:"-"` + // Intermediates points issuer certificate, along with any other bundled certificates + // to be returned in the chain for consumers. + Intermediates []*x509.Certificate `json:"-"` + // SignerCert points to the certificate of the CA signer. It usually is the same as the + // first certificate in the CertificateChain. + SignerCert *x509.Certificate `json:"-"` // Signer signs CSRs in SCEP. Configured in the ca.json key property. Signer crypto.Signer `json:"-"` // Decrypter decrypts encrypted SCEP messages. Configured in the ca.json key property. Decrypter crypto.Decrypter `json:"-"` } +type comparablePublicKey interface { + Equal(crypto.PublicKey) bool +} + // Validate checks the fields in Options. func (o *Options) Validate() error { - if o.CertificateChain == nil { - return errors.New("certificate chain not configured correctly") + switch { + case len(o.Intermediates) == 0: + return errors.New("no intermediate certificate available for SCEP authority") + case o.Signer == nil: + return errors.New("no signer available for SCEP authority") + case o.SignerCert == nil: + return errors.New("no signer certificate available for SCEP authority") } - if len(o.CertificateChain) < 1 { - return errors.New("certificate chain should at least have one certificate") + // check if the signer (intermediate CA) certificate has the same public key as + // the signer. According to the RFC it seems valid to have different keys for + // the intermediate and the CA signing new certificates, so this might change + // in the future. + signerPublicKey := o.Signer.Public().(comparablePublicKey) + if !signerPublicKey.Equal(o.SignerCert.PublicKey) { + return errors.New("mismatch between signer certificate and public key") } - // According to the RFC: https://tools.ietf.org/html/rfc8894#section-3.1, SCEP - // can be used with something different than RSA, but requires the encryption - // to be performed using the challenge password. An older version of specification - // states that only RSA is supported: https://tools.ietf.org/html/draft-nourse-scep-23#section-2.1.1 - // Other algorithms than RSA do not seem to be supported in certnanny/sscep, but it might work + // decrypter can be nil in case a signing only key is used; validation complete. + if o.Decrypter == nil { + return nil + } + + // If a decrypter is available, check that it's backed by an RSA key. According to the + // RFC: https://tools.ietf.org/html/rfc8894#section-3.1, SCEP can be used with something + // different than RSA, but requires the encryption to be performed using the challenge + // password in that case. An older version of specification states that only RSA is + // supported: https://tools.ietf.org/html/draft-nourse-scep-23#section-2.1.1. Other + // algorithms do not seem to be supported in certnanny/sscep, but it might work // in micromdm/scep. Currently only RSA is allowed, but it might be an option // to try other algorithms in the future. - //intermediate := o.CertificateChain[0] - //intermediate := o.SignerCert - // if intermediate.PublicKeyAlgorithm != x509.RSA { - // return errors.New("only the RSA algorithm is (currently) supported") - // } - - // TODO: add checks for key usage? - - //signerPublicKey, ok := o.Signer.Public().(*rsa.PublicKey) - // if !ok { - // return errors.New("only RSA public keys are (currently) supported as signers") - // } - - // check if the intermediate ca certificate has the same public key as the signer. - // According to the RFC it seems valid to have different keys for the intermediate - // and the CA signing new certificates, so this might change in the future. - // if !signerPublicKey.Equal(intermediate.PublicKey) { - // return errors.New("mismatch between certificate chain and signer public keys") - // } - - // TODO: this could be a different decrypter, based on the value - // in the provisioner. - // decrypterPublicKey, ok := o.Decrypter.Public().(*rsa.PublicKey) - // if !ok { - // return errors.New("only RSA public keys are (currently) supported as decrypters") - // } + decrypterPublicKey, ok := o.Decrypter.Public().(*rsa.PublicKey) + if !ok { + return errors.New("only RSA keys are (currently) supported as decrypters") + } // check if intermediate public key is the same as the decrypter public key. // In certnanny/sscep it's mentioned that the signing key can be different - // from the decrypting (and encrypting) key. Currently that's not supported. - // if !decrypterPublicKey.Equal(intermediate.PublicKey) { - // return errors.New("mismatch between certificate chain and decrypter public keys") - // } - - // if !decrypterPublicKey.Equal(o.DecrypterCert.PublicKey) { - // return errors.New("mismatch between certificate chain and decrypter public keys") - // } + // from the decrypting (and encrypting) key. These options are only used and + // validated when the intermediate CA is also used as the decrypter, though, + // so they should match. + if !decrypterPublicKey.Equal(o.SignerCert.PublicKey) { + return errors.New("mismatch between certificate chain and decrypter public keys") + } return nil } diff --git a/scep/service.go b/scep/service.go index ffb4166a..f3a6d097 100644 --- a/scep/service.go +++ b/scep/service.go @@ -6,13 +6,15 @@ import ( "crypto/x509" ) -// Service is a wrapper for crypto.Signer and crypto.Decrypter +// 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 { - certificateChain []*x509.Certificate - signerCertificate *x509.Certificate - signer crypto.Signer - defaultDecrypterCertificate *x509.Certificate - defaultDecrypter crypto.Decrypter + roots []*x509.Certificate + intermediates []*x509.Certificate + signerCertificate *x509.Certificate + signer crypto.Signer + defaultDecrypter crypto.Decrypter } // NewService returns a new Service type. @@ -20,13 +22,11 @@ func NewService(_ context.Context, opts Options) (*Service, error) { if err := opts.Validate(); err != nil { return nil, err } - - // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ - certificateChain: opts.CertificateChain, - signerCertificate: opts.SignerCert, - signer: opts.Signer, - defaultDecrypterCertificate: opts.DecrypterCert, - defaultDecrypter: opts.Decrypter, + roots: opts.Roots, + intermediates: opts.Intermediates, + signerCertificate: opts.SignerCert, + signer: opts.Signer, + defaultDecrypter: opts.Decrypter, }, nil }