forked from TrueCloudLab/certificates
Merge pull request #797 from smallstep/herman/scep-macos-renewal-fixes
Fix macOS SCEP client issues
This commit is contained in:
commit
4a0cfd24e5
12 changed files with 160 additions and 126 deletions
|
@ -437,13 +437,6 @@ func (a *Authority) init() error {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -454,6 +447,7 @@ func (a *Authority) init() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.CertificateChain = append(options.CertificateChain, a.rootX509Certs...)
|
||||
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
|
||||
SigningKey: a.config.IntermediateKey,
|
||||
Password: []byte(a.password),
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
|
@ -328,7 +327,6 @@ func TestAuthority_CloseForReload(t *testing.T) {
|
|||
}
|
||||
|
||||
func testScepAuthority(t *testing.T, opts ...Option) *Authority {
|
||||
|
||||
p := provisioner.List{
|
||||
&provisioner.SCEP{
|
||||
Name: "scep1",
|
||||
|
@ -353,39 +351,15 @@ func testScepAuthority(t *testing.T, opts ...Option) *Authority {
|
|||
}
|
||||
|
||||
func TestAuthority_GetSCEPService(t *testing.T) {
|
||||
auth := testScepAuthority(t)
|
||||
fmt.Println(auth)
|
||||
|
||||
_ = testScepAuthority(t)
|
||||
p := provisioner.List{
|
||||
&provisioner.SCEP{
|
||||
Name: "scep1",
|
||||
Type: "SCEP",
|
||||
},
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
config *Config
|
||||
// keyManager kms.KeyManager
|
||||
// provisioners *provisioner.Collection
|
||||
// db db.AuthDB
|
||||
// templates *templates.Templates
|
||||
// x509CAService cas.CertificateAuthorityService
|
||||
// rootX509Certs []*x509.Certificate
|
||||
// federatedX509Certs []*x509.Certificate
|
||||
// certificates *sync.Map
|
||||
// scepService *scep.Service
|
||||
// sshCAUserCertSignKey ssh.Signer
|
||||
// sshCAHostCertSignKey ssh.Signer
|
||||
// sshCAUserCerts []ssh.PublicKey
|
||||
// sshCAHostCerts []ssh.PublicKey
|
||||
// sshCAUserFederatedCerts []ssh.PublicKey
|
||||
// sshCAHostFederatedCerts []ssh.PublicKey
|
||||
// initOnce bool
|
||||
// startTime time.Time
|
||||
// sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error)
|
||||
// sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
|
||||
// sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error)
|
||||
// getIdentityFunc provisioner.GetIdentityFunc
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -434,30 +408,6 @@ func TestAuthority_GetSCEPService(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// a := &Authority{
|
||||
// config: tt.fields.config,
|
||||
// keyManager: tt.fields.keyManager,
|
||||
// provisioners: tt.fields.provisioners,
|
||||
// db: tt.fields.db,
|
||||
// templates: tt.fields.templates,
|
||||
// x509CAService: tt.fields.x509CAService,
|
||||
// rootX509Certs: tt.fields.rootX509Certs,
|
||||
// federatedX509Certs: tt.fields.federatedX509Certs,
|
||||
// certificates: tt.fields.certificates,
|
||||
// scepService: tt.fields.scepService,
|
||||
// sshCAUserCertSignKey: tt.fields.sshCAUserCertSignKey,
|
||||
// sshCAHostCertSignKey: tt.fields.sshCAHostCertSignKey,
|
||||
// sshCAUserCerts: tt.fields.sshCAUserCerts,
|
||||
// sshCAHostCerts: tt.fields.sshCAHostCerts,
|
||||
// sshCAUserFederatedCerts: tt.fields.sshCAUserFederatedCerts,
|
||||
// sshCAHostFederatedCerts: tt.fields.sshCAHostFederatedCerts,
|
||||
// initOnce: tt.fields.initOnce,
|
||||
// startTime: tt.fields.startTime,
|
||||
// sshBastionFunc: tt.fields.sshBastionFunc,
|
||||
// sshCheckHostFunc: tt.fields.sshCheckHostFunc,
|
||||
// sshGetHostsFunc: tt.fields.sshGetHostsFunc,
|
||||
// getIdentityFunc: tt.fields.getIdentityFunc,
|
||||
// }
|
||||
a, err := New(tt.fields.config)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
|
|
@ -18,13 +18,21 @@ type SCEP struct {
|
|||
ForceCN bool `json:"forceCN,omitempty"`
|
||||
ChallengePassword string `json:"challenge,omitempty"`
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
// IncludeRoot makes the provisioner return the CA root in addition to the
|
||||
// intermediate in the GetCACerts response
|
||||
IncludeRoot bool `json:"includeRoot,omitempty"`
|
||||
// MinimumPublicKeyLength is the minimum length for public keys in CSRs
|
||||
MinimumPublicKeyLength int `json:"minimumPublicKeyLength,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
claimer *Claimer
|
||||
MinimumPublicKeyLength int `json:"minimumPublicKeyLength,omitempty"`
|
||||
// Numerical identifier for the ContentEncryptionAlgorithm as defined in github.com/mozilla-services/pkcs7
|
||||
// at https://github.com/mozilla-services/pkcs7/blob/33d05740a3526e382af6395d3513e73d4e66d1cb/encrypt.go#L63
|
||||
// Defaults to 0, being DES-CBC
|
||||
EncryptionAlgorithmIdentifier int `json:"encryptionAlgorithmIdentifier,omitempty"`
|
||||
Options *Options `json:"options,omitempty"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
claimer *Claimer
|
||||
|
||||
secretChallengePassword string
|
||||
encryptionAlgorithm int
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
|
@ -97,7 +105,12 @@ func (s *SCEP) Init(config Config) (err error) {
|
|||
}
|
||||
|
||||
if s.MinimumPublicKeyLength%8 != 0 {
|
||||
return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisible by 8", s.MinimumPublicKeyLength)
|
||||
return errors.Errorf("%d bits is not exactly divisible by 8", s.MinimumPublicKeyLength)
|
||||
}
|
||||
|
||||
s.encryptionAlgorithm = s.EncryptionAlgorithmIdentifier // TODO(hs): we might want to upgrade the default security to AES-CBC?
|
||||
if s.encryptionAlgorithm < 0 || s.encryptionAlgorithm > 4 {
|
||||
return errors.New("only encryption algorithm identifiers from 0 to 4 are valid")
|
||||
}
|
||||
|
||||
// TODO: add other, SCEP specific, options?
|
||||
|
@ -129,3 +142,17 @@ func (s *SCEP) GetChallengePassword() string {
|
|||
func (s *SCEP) GetCapabilities() []string {
|
||||
return s.Capabilities
|
||||
}
|
||||
|
||||
// ShouldIncludeRootInChain indicates if the CA should
|
||||
// return its intermediate, which is currently used for
|
||||
// both signing and decryption, as well as the root in
|
||||
// its chain.
|
||||
func (s *SCEP) ShouldIncludeRootInChain() bool {
|
||||
return s.IncludeRoot
|
||||
}
|
||||
|
||||
// GetContentEncryptionAlgorithm returns the numeric identifier
|
||||
// for the pkcs7 package encryption algorithm to use.
|
||||
func (s *SCEP) GetContentEncryptionAlgorithm() int {
|
||||
return s.encryptionAlgorithm
|
||||
}
|
||||
|
|
|
@ -712,6 +712,21 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
|
|||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_SCEP:
|
||||
cfg := d.SCEP
|
||||
return &provisioner.SCEP{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
ForceCN: cfg.ForceCn,
|
||||
ChallengePassword: cfg.Challenge,
|
||||
Capabilities: cfg.Capabilities,
|
||||
IncludeRoot: cfg.IncludeRoot,
|
||||
MinimumPublicKeyLength: int(cfg.MinimumPublicKeyLength),
|
||||
EncryptionAlgorithmIdentifier: int(cfg.EncryptionAlgorithmIdentifier),
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_Nebula:
|
||||
var roots []byte
|
||||
for i, root := range d.Nebula.GetRoots() {
|
||||
|
@ -944,10 +959,12 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
|
|||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_SCEP{
|
||||
SCEP: &linkedca.SCEPProvisioner{
|
||||
ForceCn: p.ForceCN,
|
||||
Challenge: p.GetChallengePassword(),
|
||||
Capabilities: p.Capabilities,
|
||||
MinimumPublicKeyLength: int32(p.MinimumPublicKeyLength),
|
||||
ForceCn: p.ForceCN,
|
||||
Challenge: p.GetChallengePassword(),
|
||||
Capabilities: p.Capabilities,
|
||||
MinimumPublicKeyLength: int32(p.MinimumPublicKeyLength),
|
||||
IncludeRoot: p.IncludeRoot,
|
||||
EncryptionAlgorithmIdentifier: int32(p.EncryptionAlgorithmIdentifier),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
23
ca/ca.go
23
ca/ca.go
|
@ -417,11 +417,6 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
for _, crt := range auth.GetRootCertificates() {
|
||||
certPool.AddCert(crt)
|
||||
}
|
||||
|
||||
// GetCertificate will only be called if the client supplies SNI
|
||||
// information or if tlsConfig.Certificates is empty.
|
||||
// When client requests are made using an IP address (as opposed to a domain
|
||||
|
@ -432,6 +427,24 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) {
|
|||
tlsConfig.Certificates = []tls.Certificate{}
|
||||
tlsConfig.GetCertificate = ca.renewer.GetCertificateForCA
|
||||
|
||||
// initialize a certificate pool with root CA certificates to trust when doing mTLS.
|
||||
certPool := x509.NewCertPool()
|
||||
for _, crt := range auth.GetRootCertificates() {
|
||||
certPool.AddCert(crt)
|
||||
}
|
||||
|
||||
// adding the intermediate CA certificates to the pool will allow clients that
|
||||
// do mTLS but don't send an intermediate to successfully connect. The intermediates
|
||||
// added here are used when building a certificate chain.
|
||||
intermediates := tlsCrt.Certificate[1:]
|
||||
for _, certBytes := range intermediates {
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certPool.AddCert(cert)
|
||||
}
|
||||
|
||||
// Add support for mutual tls to renew certificates
|
||||
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
|
||||
tlsConfig.ClientCAs = certPool
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
@ -852,7 +853,7 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) {
|
|||
defer srv.Stop()
|
||||
|
||||
// Create fake privateca client
|
||||
conn, err := grpc.DialContext(context.Background(), "", grpc.WithInsecure(),
|
||||
conn, err := grpc.DialContext(context.Background(), "", grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return lis.Dial()
|
||||
}))
|
||||
|
|
9
go.mod
9
go.mod
|
@ -35,12 +35,13 @@ require (
|
|||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||
go.step.sm/cli-utils v0.7.0
|
||||
go.step.sm/crypto v0.15.0
|
||||
go.step.sm/linkedca v0.9.0
|
||||
go.step.sm/linkedca v0.9.2
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
google.golang.org/api v0.47.0
|
||||
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492
|
||||
google.golang.org/grpc v1.39.0
|
||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5
|
||||
google.golang.org/grpc v1.43.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
)
|
||||
|
|
25
go.sum
25
go.sum
|
@ -148,7 +148,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
|
@ -196,6 +200,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
|
|||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||
|
@ -607,8 +612,8 @@ go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/
|
|||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.15.0 h1:VioBln+x3+RoejgeBhvxkLGVYdWRy6PFiAaUUN29/E0=
|
||||
go.step.sm/crypto v0.15.0/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/linkedca v0.9.0 h1:xKXZoRXy4B7LeGBZozq62IQ0p3v8dT33O9UOMpVtRtI=
|
||||
go.step.sm/linkedca v0.9.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.step.sm/linkedca v0.9.2 h1:CpAkd174sLXFfrOZrbPEiTzik91QRj3+L0omsiwsiok=
|
||||
go.step.sm/linkedca v0.9.2/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
|
@ -717,8 +722,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
|
||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -813,8 +819,9 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -889,7 +896,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -974,8 +980,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492 h1:7yQQsvnwjfEahbNNEKcBHv3mR+HnB1ctGY/z1JXzx8M=
|
||||
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
|
||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
|
@ -1003,8 +1009,9 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
|||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
|
|
@ -66,8 +66,8 @@ func New(scepAuth scep.Interface) api.RouterHandler {
|
|||
// Route traffic and implement the Router interface.
|
||||
func (h *Handler) Route(r api.Router) {
|
||||
getLink := h.Auth.GetLinkExplicit
|
||||
r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get))
|
||||
r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post))
|
||||
r.MethodFunc(http.MethodGet, getLink("{provisionerName}", false, nil), h.lookupProvisioner(h.Get))
|
||||
r.MethodFunc(http.MethodPost, getLink("{provisionerName}", false, nil), h.lookupProvisioner(h.Post))
|
||||
}
|
||||
|
||||
// Get handles all SCEP GET requests
|
||||
|
@ -184,14 +184,14 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) {
|
|||
func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
name := chi.URLParam(r, "provisionerID")
|
||||
provisionerID, err := url.PathUnescape(name)
|
||||
name := chi.URLParam(r, "provisionerName")
|
||||
provisionerName, err := url.PathUnescape(name)
|
||||
if err != nil {
|
||||
api.WriteError(w, errors.Errorf("error url unescaping provisioner id '%s'", name))
|
||||
api.WriteError(w, errors.Errorf("error url unescaping provisioner name '%s'", name))
|
||||
return
|
||||
}
|
||||
|
||||
p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID)
|
||||
p, err := h.Auth.LoadProvisionerByName(provisionerName)
|
||||
if err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
|
@ -212,7 +212,7 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
|
|||
// GetCACert returns the CA certificates in a SCEP response
|
||||
func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) {
|
||||
|
||||
certs, err := h.Auth.GetCACertificates()
|
||||
certs, err := h.Auth.GetCACertificates(ctx)
|
||||
if err != nil {
|
||||
return SCEPResponse{}, err
|
||||
}
|
||||
|
@ -289,20 +289,29 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe
|
|||
// NOTE: at this point we have sufficient information for returning nicely signed CertReps
|
||||
csr := msg.CSRReqMessage.CSR
|
||||
|
||||
if msg.MessageType == microscep.PKCSReq {
|
||||
|
||||
// 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,
|
||||
// even if using the renewal flow as described in the README.md. MicroMDM SCEP client also only does PKCSreq by default, unless
|
||||
// a certificate exists; then it will use RenewalReq. Adding the challenge check here may be a small breaking change for clients.
|
||||
// We'll have to see how it works out.
|
||||
if msg.MessageType == microscep.PKCSReq || msg.MessageType == microscep.RenewalReq {
|
||||
challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword)
|
||||
if err != nil {
|
||||
return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("error when checking password"))
|
||||
}
|
||||
|
||||
if !challengeMatches {
|
||||
// TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too.
|
||||
return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("wrong password provided"))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not
|
||||
// TODO: authorize renewal: we can authorize renewals with the challenge password (if reusable secrets are used).
|
||||
// Renewals OPTIONALLY include the challenge if the existing cert is used as authentication, but client SHOULD omit the challenge.
|
||||
// This means that for renewal requests we should check the certificate provided to be signed before by the CA. We could
|
||||
// enforce use of the challenge if we want too. That way we could be more flexible in terms of authentication scheme (i.e. reusing
|
||||
// tokens from other provisioners, calling a webhook, storing multiple secrets, allowing them to be multi-use, etc).
|
||||
// Authentication by the (self-signed) certificate with an optional challenge is required; supporting renewals incl. verification
|
||||
// of the client cert is not.
|
||||
|
||||
certRep, err := h.Auth.SignCSR(ctx, csr, msg)
|
||||
if err != nil {
|
||||
|
|
|
@ -20,10 +20,10 @@ import (
|
|||
|
||||
// Interface is the SCEP authority interface.
|
||||
type Interface interface {
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
LoadProvisionerByName(string) (provisioner.Interface, error)
|
||||
GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
|
||||
|
||||
GetCACertificates() ([]*x509.Certificate, error)
|
||||
GetCACertificates(ctx context.Context) ([]*x509.Certificate, error)
|
||||
DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error
|
||||
SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error)
|
||||
CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error)
|
||||
|
@ -36,6 +36,7 @@ type Authority struct {
|
|||
prefix string
|
||||
dns string
|
||||
intermediateCertificate *x509.Certificate
|
||||
caCerts []*x509.Certificate // TODO(hs): change to use these instead of root and intermediate
|
||||
service *Service
|
||||
signAuth SignAuthority
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ type AuthorityOptions struct {
|
|||
// SignAuthority is the interface for a signing authority
|
||||
type SignAuthority interface {
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
LoadProvisionerByName(string) (provisioner.Interface, error)
|
||||
}
|
||||
|
||||
// New returns a new Authority that implements the SCEP interface.
|
||||
|
@ -72,6 +73,8 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
|
|||
// in its entirety to make this more interoperable with the rest of
|
||||
// step-ca, I think.
|
||||
if ops.Service != nil {
|
||||
authority.caCerts = ops.Service.certificateChain
|
||||
// TODO(hs): look into refactoring SCEP into using just caCerts everywhere, if it makes sense for more elaborate SCEP configuration. Keeping it like this for clarity (for now).
|
||||
authority.intermediateCertificate = ops.Service.certificateChain[0]
|
||||
authority.service = ops.Service
|
||||
}
|
||||
|
@ -82,7 +85,7 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
|
|||
var (
|
||||
// TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2
|
||||
defaultCapabilities = []string{
|
||||
"Renewal",
|
||||
"Renewal", // NOTE: removing this will result in macOS SCEP client stating the server doesn't support renewal, but it uses PKCSreq to do so.
|
||||
"SHA-1",
|
||||
"SHA-256",
|
||||
"AES",
|
||||
|
@ -92,26 +95,21 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// LoadProvisionerByID calls out to the SignAuthority interface to load a
|
||||
// provisioner by ID.
|
||||
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
||||
return a.signAuth.LoadProvisionerByID(id)
|
||||
// LoadProvisionerByName calls out to the SignAuthority interface to load a
|
||||
// provisioner by name.
|
||||
func (a *Authority) LoadProvisionerByName(name string) (provisioner.Interface, error) {
|
||||
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 {
|
||||
// TODO: taken from ACME; move it to directory (if we need a directory in SCEP)?
|
||||
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, inputs ...string) string {
|
||||
|
||||
// TODO: do we need to provide a way to provide a different suffix?
|
||||
// Like "/cgi-bin/pkiclient.exe"? Or would it be enough to have that as the name?
|
||||
link := "/" + provisionerName
|
||||
|
||||
if abs {
|
||||
// Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351
|
||||
u := url.URL{}
|
||||
|
@ -137,7 +135,7 @@ func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *u
|
|||
}
|
||||
|
||||
// GetCACertificates returns the certificate (chain) for the CA
|
||||
func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) {
|
||||
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
|
||||
|
@ -153,14 +151,28 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) {
|
|||
// 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
|
||||
// The certificate to use should probably depend on the (configured) provisioner and may
|
||||
// use a distinct certificate, apart from the intermediate.
|
||||
|
||||
if a.intermediateCertificate == nil {
|
||||
p, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(a.caCerts) == 0 {
|
||||
return nil, errors.New("no intermediate certificate available in SCEP authority")
|
||||
}
|
||||
|
||||
return []*x509.Certificate{a.intermediateCertificate}, nil
|
||||
certs := []*x509.Certificate{}
|
||||
certs = append(certs, a.caCerts[0])
|
||||
|
||||
// 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.caCerts) > 1 {
|
||||
certs = append(certs, a.caCerts[1])
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// DecryptPKIEnvelope decrypts an enveloped message
|
||||
|
@ -211,8 +223,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
|
|||
|
||||
// SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials
|
||||
// returns a new PKIMessage with CertRep data
|
||||
//func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) {
|
||||
//func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) {
|
||||
func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) {
|
||||
|
||||
// TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate
|
||||
|
@ -220,7 +230,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
|
|||
// poll for the status. It seems to be similar as what can happen in ACME, so might want to model
|
||||
// the implementation after the one in the ACME authority. Requires storage, etc.
|
||||
|
||||
p, err := ProvisionerFromContext(ctx)
|
||||
p, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -267,11 +277,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
|
|||
return nil, errors.Wrap(err, "error retrieving authorization options from SCEP provisioner")
|
||||
}
|
||||
|
||||
opts := provisioner.SignOptions{
|
||||
// NotBefore: provisioner.NewTimeDuration(o.NotBefore),
|
||||
// NotAfter: provisioner.NewTimeDuration(o.NotAfter),
|
||||
}
|
||||
|
||||
opts := provisioner.SignOptions{}
|
||||
templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating template options from SCEP provisioner")
|
||||
|
@ -292,10 +298,17 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// apparently the pkcs7 library uses a global default setting for the content encryption
|
||||
// algorithm to use when en- or decrypting data. We need to restore the current setting after
|
||||
// the cryptographic operation, so that other usages of the library are not influenced by
|
||||
// this call to Encrypt(). We are not required to use the same algorithm the SCEP client uses.
|
||||
encryptionAlgorithmToRestore := pkcs7.ContentEncryptionAlgorithm
|
||||
pkcs7.ContentEncryptionAlgorithm = p.GetContentEncryptionAlgorithm()
|
||||
e7, err := pkcs7.Encrypt(deg, msg.P7.Certificates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkcs7.ContentEncryptionAlgorithm = encryptionAlgorithmToRestore
|
||||
|
||||
// PKIMessageAttributes to be signed
|
||||
config := pkcs7.SignerInfoConfig{
|
||||
|
@ -434,7 +447,7 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi
|
|||
// MatchChallengePassword verifies a SCEP challenge password
|
||||
func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) {
|
||||
|
||||
p, err := ProvisionerFromContext(ctx)
|
||||
p, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -453,7 +466,7 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string)
|
|||
// GetCACaps returns the CA capabilities
|
||||
func (a *Authority) GetCACaps(ctx context.Context) []string {
|
||||
|
||||
p, err := ProvisionerFromContext(ctx)
|
||||
p, err := provisionerFromContext(ctx)
|
||||
if err != nil {
|
||||
return defaultCapabilities
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ const (
|
|||
ProvisionerContextKey = ContextKey("provisioner")
|
||||
)
|
||||
|
||||
// ProvisionerFromContext searches the context for a SCEP provisioner.
|
||||
// provisionerFromContext searches the context for a SCEP provisioner.
|
||||
// Returns the provisioner or an error.
|
||||
func ProvisionerFromContext(ctx context.Context) (Provisioner, error) {
|
||||
func provisionerFromContext(ctx context.Context) (Provisioner, error) {
|
||||
val := ctx.Value(ProvisionerContextKey)
|
||||
if val == nil {
|
||||
return nil, errors.New("provisioner expected in request context")
|
||||
|
|
|
@ -16,4 +16,6 @@ type Provisioner interface {
|
|||
GetOptions() *provisioner.Options
|
||||
GetChallengePassword() string
|
||||
GetCapabilities() []string
|
||||
ShouldIncludeRootInChain() bool
|
||||
GetContentEncryptionAlgorithm() int
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue