Add x5c provisioner capabilities
parent
2781045524
commit
d368791606
@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5
|
||||
MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr
|
||||
VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW
|
||||
f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN
|
||||
wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z
|
||||
AvUgkUQ2G25NBRmX
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIALytC4LyTTAagMLMv+rzq2vtfhFkhuyBz4kqsnRs6zioAoGCCqGSM49
|
||||
AwEHoUQDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvrVpgS
|
||||
PXYruNRFduPWX564Abz/TDmb276JbKGeQg==
|
||||
-----END EC PRIVATE KEY-----
|
@ -0,0 +1,267 @@
|
||||
package provisioner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
// x5cPayload extends jwt.Claims with step attributes.
|
||||
type x5cPayload struct {
|
||||
jose.Claims
|
||||
SANs []string `json:"sans,omitempty"`
|
||||
Step *stepPayload `json:"step,omitempty"`
|
||||
chains [][]*x509.Certificate
|
||||
}
|
||||
|
||||
// X5C is the default provisioner, an entity that can sign tokens necessary for
|
||||
// signature requests.
|
||||
type X5C struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Roots []byte `json:"roots"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
claimer *Claimer
|
||||
audiences Audiences
|
||||
rootPool *x509.CertPool
|
||||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any X5C provisioner.
|
||||
func (p *X5C) GetID() string {
|
||||
return "x5c/" + p.Name
|
||||
}
|
||||
|
||||
// GetTokenID returns the identifier of the token.
|
||||
func (p *X5C) GetTokenID(ott string) (string, error) {
|
||||
// Validate payload
|
||||
token, err := jose.ParseSigned(ott)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing token")
|
||||
}
|
||||
|
||||
// Get claims w/out verification. We need to look up the provisioner
|
||||
// key in order to verify the claims and we need the issuer from the claims
|
||||
// before we can look up the provisioner.
|
||||
var claims jose.Claims
|
||||
if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||
return "", errors.Wrap(err, "error verifying claims")
|
||||
}
|
||||
return claims.ID, nil
|
||||
}
|
||||
|
||||
// GetName returns the name of the provisioner.
|
||||
func (p *X5C) GetName() string {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
// GetType returns the type of provisioner.
|
||||
func (p *X5C) GetType() Type {
|
||||
return TypeX5C
|
||||
}
|
||||
|
||||
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
|
||||
func (p *X5C) GetEncryptedKey() (string, string, bool) {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// Init initializes and validates the fields of a X5C type.
|
||||
func (p *X5C) Init(config Config) error {
|
||||
switch {
|
||||
case p.Type == "":
|
||||
return errors.New("provisioner type cannot be empty")
|
||||
case p.Name == "":
|
||||
return errors.New("provisioner name cannot be empty")
|
||||
case len(p.Roots) == 0:
|
||||
return errors.New("provisioner root(s) cannot be empty")
|
||||
}
|
||||
|
||||
p.rootPool = x509.NewCertPool()
|
||||
|
||||
var (
|
||||
block *pem.Block
|
||||
rest = p.Roots
|
||||
)
|
||||
for rest != nil {
|
||||
block, rest = pem.Decode(rest)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing x509 certificate from PEM block")
|
||||
}
|
||||
p.rootPool.AddCert(cert)
|
||||
}
|
||||
|
||||
// Verify that at least one root was found.
|
||||
if len(p.rootPool.Subjects()) == 0 {
|
||||
return errors.Errorf("no x509 certificates found in roots attribute for provisioner %s", p.GetName())
|
||||
}
|
||||
|
||||
// Update claims with global ones
|
||||
var err error
|
||||
if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// authorizeToken performs common jwt authorization actions and returns the
|
||||
// claims for case specific downstream parsing.
|
||||
// e.g. a Sign request will auth/validate different fields than a Revoke request.
|
||||
func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, error) {
|
||||
jwt, err := jose.ParseSigned(token)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing token")
|
||||
}
|
||||
|
||||
verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{
|
||||
Roots: p.rootPool,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error verifying x5c certificate chain")
|
||||
}
|
||||
leaf := verifiedChains[0][0]
|
||||
|
||||
if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
|
||||
return nil, errors.New("certificate used to sign x5c token cannot be used for digital signature")
|
||||
}
|
||||
|
||||
// Using the leaf certificates key to validate the claims accomplishes two
|
||||
// things:
|
||||
// 1. Asserts that the private key used to sign the token corresponds
|
||||
// to the public certificate in the `x5c` header of the token.
|
||||
// 2. Asserts that the claims are valid - have not been tampered with.
|
||||
var claims x5cPayload
|
||||
if err = jwt.Claims(leaf.PublicKey, &claims); err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing claims")
|
||||
}
|
||||
|
||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||
// more than a few minutes.
|
||||
if err = claims.ValidateWithLeeway(jose.Expected{
|
||||
Issuer: p.Name,
|
||||
Time: time.Now().UTC(),
|
||||
}, time.Minute); err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid token")
|
||||
}
|
||||
|
||||
// validate audiences with the defaults
|
||||
if !matchesAudience(claims.Audience, audiences) {
|
||||
return nil, errors.New("invalid token: invalid audience claim (aud)")
|
||||
}
|
||||
|
||||
if claims.Subject == "" {
|
||||
return nil, errors.New("token subject cannot be empty")
|
||||
}
|
||||
|
||||
// Save the verified chains on the x5c payload object.
|
||||
claims.chains = verifiedChains
|
||||
return &claims, nil
|
||||
}
|
||||
|
||||
// AuthorizeRevoke returns an error if the provisioner does not have rights to
|
||||
// revoke the certificate with serial number in the `sub` property.
|
||||
func (p *X5C) AuthorizeRevoke(token string) error {
|
||||
_, err := p.authorizeToken(token, p.audiences.Revoke)
|
||||
return err
|
||||
}
|
||||
|
||||
// AuthorizeSign validates the given token.
|
||||
func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
claims, err := p.authorizeToken(token, p.audiences.Sign)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for SSH sign-ing request.
|
||||
if MethodFromContext(ctx) == SignSSHMethod {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return p.authorizeSSHSign(claims)
|
||||
}
|
||||
|
||||
// NOTE: This is for backwards compatibility with older versions of cli
|
||||
// and certificates. Older versions added the token subject as the only SAN
|
||||
// in a CSR by default.
|
||||
if len(claims.SANs) == 0 {
|
||||
claims.SANs = []string{claims.Subject}
|
||||
}
|
||||
|
||||
dnsNames, ips, emails := x509util.SplitSANs(claims.SANs)
|
||||
|
||||
return []SignOption{
|
||||
// modifiers / withOptions
|
||||
newProvisionerExtensionOption(TypeX5C, p.Name, ""),
|
||||
profileLimitDuration{p.claimer.DefaultTLSCertDuration(), claims.chains[0][0].NotAfter},
|
||||
// validators
|
||||
commonNameValidator(claims.Subject),
|
||||
defaultPublicKeyValidator{},
|
||||
dnsNamesValidator(dnsNames),
|
||||
emailAddressesValidator(emails),
|
||||
ipAddressesValidator(ips),
|
||||
newValidityValidator(p.claimer.MinTLSCertDuration(), p.claimer.MaxTLSCertDuration()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AuthorizeRenewal returns an error if the renewal is disabled.
|
||||
func (p *X5C) AuthorizeRenewal(cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errors.Errorf("renew is disabled for provisioner %s", p.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// authorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) {
|
||||
if claims.Step == nil || claims.Step.SSH == nil {
|
||||
return nil, errors.New("authorization token must be an SSH provisioning token")
|
||||
}
|
||||
opts := claims.Step.SSH
|
||||
signOptions := []SignOption{
|
||||
// validates user's SSHOptions with the ones in the token
|
||||
sshCertificateOptionsValidator(*opts),
|
||||
// set the key id to the token subject
|
||||
sshCertificateKeyIDModifier(claims.Subject),
|
||||
}
|
||||
|
||||
// Add modifiers from custom claims
|
||||
if opts.CertType != "" {
|
||||
signOptions = append(signOptions, sshCertificateCertTypeModifier(opts.CertType))
|
||||
}
|
||||
if len(opts.Principals) > 0 {
|
||||
signOptions = append(signOptions, sshCertificatePrincipalsModifier(opts.Principals))
|
||||
}
|
||||
t := now()
|
||||
if !opts.ValidAfter.IsZero() {
|
||||
signOptions = append(signOptions, sshCertificateValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
|
||||
}
|
||||
if !opts.ValidBefore.IsZero() {
|
||||
signOptions = append(signOptions, sshCertificateValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
|
||||
}
|
||||
|
||||
// Default to a user certificate with no principals if not set
|
||||
signOptions = append(signOptions, sshCertificateDefaultsModifier{CertType: SSHUserCert})
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Checks the validity bounds, and set the validity if has not been set.
|
||||
sshLimitValidityModifier(p.claimer, claims.chains[0][0].NotAfter),
|
||||
// Validate public key.
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// Validate the validity period.
|
||||
&sshCertificateValidityValidator{p.claimer},
|
||||
// Require all the fields in the SSH certificate
|
||||
&sshCertificateDefaultValidator{},
|
||||
), nil
|
||||
}
|
@ -0,0 +1,751 @@
|
||||
package provisioner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
)
|
||||
|
||||
func TestX5C_Getters(t *testing.T) {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
id := "x5c/" + p.Name
|
||||
if got := p.GetID(); got != id {
|
||||
t.Errorf("X5C.GetID() = %v, want %v:%v", got, p.Name, id)
|
||||
}
|
||||
if got := p.GetName(); got != p.Name {
|
||||
t.Errorf("X5C.GetName() = %v, want %v", got, p.Name)
|
||||
}
|
||||
if got := p.GetType(); got != TypeX5C {
|
||||
t.Errorf("X5C.GetType() = %v, want %v", got, TypeX5C)
|
||||
}
|
||||
kid, key, ok := p.GetEncryptedKey()
|
||||
if kid != "" || key != "" || ok == true {
|
||||
t.Errorf("X5C.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)",
|
||||
kid, key, ok, "", "", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_Init(t *testing.T) {
|
||||
type ProvisionerValidateTest struct {
|
||||
p *X5C
|
||||
err error
|
||||
extraValid func(*X5C) error
|
||||
}
|
||||
tests := map[string]func(*testing.T) ProvisionerValidateTest{
|
||||
"fail/empty": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{},
|
||||
err: errors.New("provisioner type cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-name": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{
|
||||
Type: "X5C",
|
||||
},
|
||||
err: errors.New("provisioner name cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-type": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo"},
|
||||
err: errors.New("provisioner type cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/empty-key": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo", Type: "bar"},
|
||||
err: errors.New("provisioner root(s) cannot be empty"),
|
||||
}
|
||||
},
|
||||
"fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences},
|
||||
err: errors.Errorf("no x509 certificates found in roots attribute for provisioner foo"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p.Claims = &Claims{DefaultTLSDur: &Duration{0}}
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
}
|
||||
},
|
||||
"ok/root-chain": func(t *testing.T) ProvisionerValidateTest {
|
||||
p, err := generateX5C([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBhTCCASqgAwIBAgIRAMalM7pKi0GCdKjO6u88OyowCgYIKoZIzj0EAwIwFDES
|
||||
MBAGA1UEAxMJcm9vdC10ZXN0MCAXDTE5MTAwMjAyMzk0OFoYDzIxMTkwOTA4MDIz
|
||||
OTQ4WjAUMRIwEAYDVQQDEwlyb290LXRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB
|
||||
BwNCAAS29QTCXUu7cx9sa9wZPpRSFq/zXaw8Ai3EIygayrBsKnX42U2atBUjcBZO
|
||||
BWL6A+PpLzU9ja867U5SYNHERS+Oo1swWTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowFAYD
|
||||
VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH
|
||||
1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY
|
||||
M46l92gdOozT
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
extraValid: func(p *X5C) error {
|
||||
numCerts := len(p.rootPool.Subjects())
|
||||
if numCerts != 2 {
|
||||
return errors.Errorf("unexpected number of certs: want 2, but got %d", numCerts)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
config := Config{
|
||||
Claims: globalProvisionerClaims,
|
||||
Audiences: testAudiences,
|
||||
}
|
||||
for name, get := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := get(t)
|
||||
err := tc.p.Init(config)
|
||||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.Equals(t, tc.err.Error(), err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Equals(t, tc.p.audiences, config.Audiences.WithFragment(tc.p.GetID()))
|
||||
if tc.extraValid != nil {
|
||||
assert.Nil(t, tc.extraValid(tc.p))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_authorizeToken(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/bad-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-cert-chain": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBpTCCAUugAwIBAgIRAOn2LHXjYyTXQ7PNjDTSKiIwCgYIKoZIzj0EAwIwHDEa
|
||||
MBgGA1UEAxMRU21hbGxzdGVwIFJvb3QgQ0EwHhcNMTkwOTE0MDk1NTM2WhcNMjkw
|
||||
OTExMDk1NTM2WjAkMSIwIAYDVQQDExlTbWFsbHN0ZXAgSW50ZXJtZWRpYXRlIENB
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2Cs0TY0dLM4b2s+z8+cc3JJp/W5H
|
||||
zQRvICX/1aJ4MuObNLcvoSguJwJEkYpGB5fhb0KvoL+ebHfEOywGNwrWkaNmMGQw
|
||||
DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNLJ
|
||||
4ZXoX9cI6YkGPxgs2US3ssVzMB8GA1UdIwQYMBaAFGIwpqz85wL29aF47Vj9XSVM
|
||||
P9K7MAoGCCqGSM49BAMCA0gAMEUCIQC5c1ldDcesDb31GlO5cEJvOcRrIrNtkk8m
|
||||
a5wpg+9s6QIgHIW6L60F8klQX+EO3o0SBqLeNcaskA4oSZsKjEdpSGo=
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"),
|
||||
}
|
||||
},
|
||||
"fail/doubled-up-self-signed-cert": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w
|
||||
DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow
|
||||
EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI
|
||||
3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG
|
||||
M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ
|
||||
BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS
|
||||
ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G
|
||||
EXAHTA9L
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBgjCCASigAwIBAgIQIZiE9wpmSj6SMMDfHD17qjAKBggqhkjOPQQDAjAQMQ4w
|
||||
DAYDVQQDEwVsZWFmMjAgFw0xOTEwMDIwMzEzNTlaGA8yMTE5MDkwODAzMTM1OVow
|
||||
EDEOMAwGA1UEAxMFbGVhZjIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuajJI
|
||||
3YgDaj+jorioJzGJc2+V1hUM7XzN9tIHoUeItgny9GW08TrTc23h1cCZteNZvayG
|
||||
M0wGpGeXOnE4IlH9o2IwYDAOBgNVHQ8BAf8EBAMCBSAwHQYDVR0lBBYwFAYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBT99+JChTh3LWOHaqlSwNiwND18/zAQ
|
||||
BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS
|
||||
ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G
|
||||
EXAHTA9L
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error verifying x5c certificate chain: x509: certificate signed by unknown authority"),
|
||||
}
|
||||
},
|
||||
"fail/digital-signature-ext-required": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBuTCCAV+gAwIBAgIQeRJLdDMIdn/T2ORKxYABezAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMjQxMTRaGA8yMTE5
|
||||
MDkwODAyNDExMlowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEDA1nGTOujobkcBWklyvymhWE5gQlvNLarVzhhhvPDw+MK2LX
|
||||
yqkXrYZM10GrwQZuQ7ykHnjz00U/KXpPRQ7+0qOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BSAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQYv0AK
|
||||
3GUOvC+m8ZTfyhn7tKQOazAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIhAPmertx0
|
||||
lchRU3kAu647exvlhEr1xosPOu6P8kVYbtTEAiAA51w9EYIT/Zb26M3eQV817T2g
|
||||
Dnhl0ElPQsA92pkqbA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
tok, err := generateToken("", p.Name, testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("certificate used to sign x5c token cannot be used for digital signature"),
|
||||
}
|
||||
},
|
||||
"fail/signature-does-not-match-x5c-pub-key": func(t *testing.T) test {
|
||||
certs, err := parseCerts([]byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAV+gAwIBAgIQFdu723gqgGaTaqjf6ny88zAKBggqhkjOPQQDAjAcMRow
|
||||
GAYDVQQDExFpbnRlcm1lZGlhdGUtdGVzdDAgFw0xOTEwMDIwMzE4NTNaGA8yMTE5
|
||||
MDkwODAzMTg1MVowFDESMBAGA1UEAxMJbGVhZi10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEaV6807GhWEtMxA39zjuMVHAiN2/Ri5B1R1s+Y/8mlrKIvuvr
|
||||
VpgSPXYruNRFduPWX564Abz/TDmb276JbKGeQqOBiDCBhTAOBgNVHQ8BAf8EBAMC
|
||||
BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBReMkPW
|
||||
f4MNWdg7KN4xI4ZLJd0IJDAfBgNVHSMEGDAWgBSckDGJlzLaJsdy698XH32gPDMp
|
||||
czAUBgNVHREEDTALgglsZWFmLXRlc3QwCgYIKoZIzj0EAwIDRwAwRAIgKYLKXpTN
|
||||
wtvZZaIvDzq1p8MO/SZ8yI42Ot69dNk/QtkCIBSvg5PozYcfbvwkgX5SwsjfYu0Z
|
||||
AvUgkUQ2G25NBRmX
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCAVygAwIBAgIQNr+f4IkABY2n4wx4sLOMrTAKBggqhkjOPQQDAjAUMRIw
|
||||
EAYDVQQDEwlyb290LXRlc3QwIBcNMTkxMDAyMDI0MDM0WhgPMjExOTA5MDgwMjQw
|
||||
MzJaMBwxGjAYBgNVBAMTEWludGVybWVkaWF0ZS10ZXN0MFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAEflfRhPjgJXv4zsPWahXjM2UU61aRFErN0iw88ZPyxea22fxl
|
||||
qN9ezntTXxzsS+mZiWapl8B40ACJgvP+WLQBHKOBhTCBgjAOBgNVHQ8BAf8EBAMC
|
||||
AQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUnJAxiZcy2ibHcuvfFx99
|
||||
oDwzKXMwHwYDVR0jBBgwFoAUpHS7FfaQ5bCrTxUeu6R2ZC3VGOowHAYDVR0RBBUw
|
||||
E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1
|
||||
2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC
|
||||
lgsqsR63is+0YQ==
|
||||
-----END CERTIFICATE-----`))
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", "foobar", testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("error parsing claims: square/go-jose: error in cryptographic primitive"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-issuer": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", "foobar", testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("invalid token: square/go-jose/jwt: validation failed, invalid issuer claim (iss)"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-audience": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.GetName(), "foobar", "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("invalid token: invalid audience claim (aud)"),
|
||||
}
|
||||
},
|
||||
"fail/empty-subject": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
err: errors.New("token subject cannot be empty"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.NotNil(t, claims)
|
||||
assert.NotNil(t, claims.chains)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeSign(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
ctx context.Context
|
||||
err error
|
||||
dns []string
|
||||
emails []string
|
||||
ips []net.IP
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"fail/ssh/disabled": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p.claimer.claims = provisionerClaims()
|
||||
*p.claimer.claims.EnableSSHCA = false
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignSSHMethod),
|
||||
token: tok,
|
||||
err: errors.Errorf("ssh ca is disabled for provisioner x5c/%s", p.GetName()),
|
||||
}
|
||||
},
|
||||
"fail/ssh/invalid-token": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignSSHMethod),
|
||||
token: tok,
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"ok/empty-sans": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
token: tok,
|
||||
dns: []string{"foo"},
|
||||
emails: []string{},
|
||||
ips: []net.IP{},
|
||||
}
|
||||
},
|
||||
"ok/multi-sans": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "",
|
||||
[]string{"127.0.0.1", "foo", "max@smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
ctx: NewContextWithMethod(context.Background(), SignMethod),
|
||||
token: tok,
|
||||
dns: []string{"foo"},
|
||||
emails: []string{"max@smallstep.com"},
|
||||
ips: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if opts, err := tc.p.AuthorizeSign(tc.ctx, tc.token); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
if assert.NotNil(t, opts) {
|
||||
tot := 0
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case *provisionerExtensionOption:
|
||||
assert.Equals(t, v.Type, int(TypeX5C))
|
||||
assert.Equals(t, v.Name, tc.p.GetName())
|
||||
assert.Equals(t, v.CredentialID, "")
|
||||
assert.Len(t, 0, v.KeyValuePairs)
|
||||
case profileLimitDuration:
|
||||
assert.Equals(t, v.def, tc.p.claimer.DefaultTLSCertDuration())
|
||||
|
||||
claims, err := tc.p.authorizeToken(tc.token, tc.p.audiences.Sign)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, v.notAfter, claims.chains[0][0].NotAfter)
|
||||
case commonNameValidator:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case defaultPublicKeyValidator:
|
||||
case dnsNamesValidator:
|
||||
assert.Equals(t, []string(v), tc.dns)
|
||||
case emailAddressesValidator:
|
||||
assert.Equals(t, []string(v), tc.emails)
|
||||
case ipAddressesValidator:
|
||||
assert.Equals(t, []net.IP(v), tc.ips)
|
||||
case *validityValidator:
|
||||
assert.Equals(t, v.min, tc.p.claimer.MinTLSCertDuration())
|
||||
assert.Equals(t, v.max, tc.p.claimer.MaxTLSCertDuration())
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
tot++
|
||||
}
|
||||
assert.Equals(t, tot, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_authorizeSSHSign(t *testing.T) {
|
||||
_, fn := mockNow()
|
||||
defer fn()
|
||||
type test struct {
|
||||
p *X5C
|
||||
claims *x5cPayload
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/no-Step-claim": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: new(x5cPayload),
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"fail/no-SSH-subattribute-in-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{Step: new(stepPayload)},
|
||||
err: errors.New("authorization token must be an SSH provisioning token"),
|
||||
}
|
||||
},
|
||||
"ok/with-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{
|
||||
Step: &stepPayload{SSH: &SSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
Principals: []string{"max", "mariano", "alan"},
|
||||
ValidAfter: TimeDuration{d: 5 * time.Minute},
|
||||
ValidBefore: TimeDuration{d: 10 * time.Minute},
|
||||
}},
|
||||
Claims: jose.Claims{Subject: "foo"},
|
||||
chains: [][]*x509.Certificate{certs},
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/without-claims": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
claims: &x5cPayload{
|
||||
Step: &stepPayload{SSH: &SSHOptions{}},
|
||||
Claims: jose.Claims{Subject: "foo"},
|
||||
chains: [][]*x509.Certificate{certs},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if opts, err := tc.p.authorizeSSHSign(tc.claims); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
if assert.NotNil(t, opts) {
|
||||
tot := 0
|
||||
nw := now()
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case sshCertificateOptionsValidator:
|
||||
tc.claims.Step.SSH.ValidAfter.t = time.Time{}
|
||||
tc.claims.Step.SSH.ValidBefore.t = time.Time{}
|
||||
assert.Equals(t, SSHOptions(v), *tc.claims.Step.SSH)
|
||||
case sshCertificateKeyIDModifier:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case sshCertificateCertTypeModifier:
|
||||
assert.Equals(t, string(v), tc.claims.Step.SSH.CertType)
|
||||
case sshCertificatePrincipalsModifier:
|
||||
assert.Equals(t, []string(v), tc.claims.Step.SSH.Principals)
|
||||
case sshCertificateValidAfterModifier:
|
||||
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix())
|
||||
case sshCertificateValidBeforeModifier:
|
||||
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix())
|
||||
case sshCertificateDefaultsModifier:
|
||||
assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert})
|
||||
case *sshValidityModifier:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
assert.Equals(t, v.validBefore, tc.claims.chains[0][0].NotAfter)
|
||||
case *sshCertificateValidityValidator:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator,
|
||||
*sshCertificateDefaultValidator:
|
||||
default:
|
||||
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
|
||||
}
|
||||
tot++
|
||||
}
|
||||
if len(tc.claims.Step.SSH.CertType) > 0 {
|
||||
assert.Equals(t, tot, 12)
|
||||
} else {
|
||||
assert.Equals(t, tot, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeRevoke(t *testing.T) {
|
||||
type test struct {
|
||||
p *X5C
|
||||
token string
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) test{
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: "foo",
|
||||
err: errors.New("error parsing token"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
tok, err := generateToken("foo", p.GetName(), testAudiences.Revoke[0], "",
|
||||
[]string{"test.smallstep.com"}, time.Now(), jwk,
|
||||
withX5CHdr(certs))
|
||||
assert.FatalError(t, err)
|
||||
return test{
|
||||
p: p,
|
||||
token: tok,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := tt(t)
|
||||
if err := tc.p.AuthorizeRevoke(tc.token); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestX5C_AuthorizeRenewal(t *testing.T) {
|
||||
p1, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
p2, err := generateX5C(nil)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
// disable renewal
|
||||
disable := true
|
||||
p2.Claims = &Claims{DisableRenewal: &disable}
|
||||
p2.claimer, err = NewClaimer(p2.Claims, globalProvisionerClaims)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type args struct {
|
||||
cert *x509.Certificate
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prov *X5C
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", p1, args{nil}, false},
|
||||
{"fail", p2, args{nil}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.prov.AuthorizeRenewal(tt.args.cert); (err != nil) != tt.wantErr {
|
||||
t.Errorf("X5C.AuthorizeRenewal() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue