forked from TrueCloudLab/certificates
Refactor SCEP provisioner and decrypter
This commit is contained in:
parent
0377fe559b
commit
180162bd6a
11 changed files with 234 additions and 250 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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() {
|
||||
|
|
27
ca/ca.go
27
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
|
||||
|
|
20
go.mod
20
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
|
||||
|
|
39
go.sum
39
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=
|
||||
|
|
|
@ -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
|
||||
// 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
|
||||
//
|
||||
// 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.
|
||||
// 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. 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.
|
||||
|
||||
// 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
|
||||
return
|
||||
}
|
||||
|
||||
if len(a.service.certificateChain) == 0 {
|
||||
return nil, errors.New("no intermediate certificate available in SCEP authority")
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ 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
|
||||
roots []*x509.Certificate
|
||||
intermediates []*x509.Certificate
|
||||
signerCertificate *x509.Certificate
|
||||
signer crypto.Signer
|
||||
defaultDecrypterCertificate *x509.Certificate
|
||||
defaultDecrypter crypto.Decrypter
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
roots: opts.Roots,
|
||||
intermediates: opts.Intermediates,
|
||||
signerCertificate: opts.SignerCert,
|
||||
signer: opts.Signer,
|
||||
defaultDecrypterCertificate: opts.DecrypterCert,
|
||||
defaultDecrypter: opts.Decrypter,
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue