forked from TrueCloudLab/certificates
Merge pull request #329 from smallstep/ssh-cert-templates
SSH cert templates
This commit is contained in:
commit
35bd3ec383
92 changed files with 1643 additions and 1013 deletions
|
@ -6,8 +6,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
// Account is a subset of the internal account type containing only those
|
||||
|
|
|
@ -12,9 +12,9 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -19,8 +19,8 @@ import (
|
|||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
type mockAcmeAuthority struct {
|
||||
|
|
|
@ -14,9 +14,9 @@ import (
|
|||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
)
|
||||
|
||||
type nextHTTP = func(http.ResponseWriter, *http.Request)
|
||||
|
@ -173,10 +173,10 @@ func (h *Handler) validateJWS(next nextHTTP) nextHTTP {
|
|||
if hdr.JSONWebKey != nil {
|
||||
switch k := hdr.JSONWebKey.Key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if k.Size() < keys.MinRSAKeyBytes {
|
||||
if k.Size() < keyutil.MinRSAKeyBytes {
|
||||
api.WriteError(w, acme.MalformedErr(errors.Errorf("rsa "+
|
||||
"keys must be at least %d bits (%d bytes) in size",
|
||||
8*keys.MinRSAKeyBytes, keys.MinRSAKeyBytes)))
|
||||
8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes)))
|
||||
return
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
var testBody = []byte("foo")
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestNewOrderRequestValidate(t *testing.T) {
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
database "github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
// Interface is the acme authority interface.
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestAuthorityGetLink(t *testing.T) {
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/nosql"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func defaultCertOps() (*CertOptions, error) {
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
// Challenge is a subset of the challenge type containing only those attributes
|
||||
|
|
|
@ -28,9 +28,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/nosql"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
var testOps = ChallengeOptions{
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
// Provisioner is an interface that implements a subset of the provisioner.Interface --
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
)
|
||||
|
||||
// Authority is the interface implemented by a CA authority.
|
||||
|
@ -32,7 +31,7 @@ type Authority interface {
|
|||
// context specifies the Authorize[Sign|Revoke|etc.] method.
|
||||
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
|
||||
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
||||
GetTLSOptions() *tlsutil.TLSOptions
|
||||
GetTLSOptions() *authority.TLSOptions
|
||||
Root(shasum string) (*x509.Certificate, error)
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
|
||||
|
|
|
@ -31,10 +31,8 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -548,7 +546,7 @@ type mockAuthority struct {
|
|||
ret1, ret2 interface{}
|
||||
err error
|
||||
authorizeSign func(ott string) ([]provisioner.SignOption, error)
|
||||
getTLSOptions func() *tlsutil.TLSOptions
|
||||
getTLSOptions func() *authority.TLSOptions
|
||||
root func(shasum string) (*x509.Certificate, error)
|
||||
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
|
||||
|
@ -564,7 +562,7 @@ type mockAuthority struct {
|
|||
signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
rekeySSH func(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
||||
getSSHHosts func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)
|
||||
getSSHHosts func(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error)
|
||||
getSSHRoots func(ctx context.Context) (*authority.SSHKeys, error)
|
||||
getSSHFederation func(ctx context.Context) (*authority.SSHKeys, error)
|
||||
getSSHConfig func(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
|
||||
|
@ -585,11 +583,11 @@ func (m *mockAuthority) AuthorizeSign(ott string) ([]provisioner.SignOption, err
|
|||
return m.ret1.([]provisioner.SignOption), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetTLSOptions() *tlsutil.TLSOptions {
|
||||
func (m *mockAuthority) GetTLSOptions() *authority.TLSOptions {
|
||||
if m.getTLSOptions != nil {
|
||||
return m.getTLSOptions()
|
||||
}
|
||||
return m.ret1.(*tlsutil.TLSOptions)
|
||||
return m.ret1.(*authority.TLSOptions)
|
||||
}
|
||||
|
||||
func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
|
||||
|
@ -697,11 +695,11 @@ func (m *mockAuthority) RekeySSH(ctx context.Context, cert *ssh.Certificate, key
|
|||
return m.ret1.(*ssh.Certificate), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
|
||||
func (m *mockAuthority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error) {
|
||||
if m.getSSHHosts != nil {
|
||||
return m.getSSHHosts(ctx, cert)
|
||||
}
|
||||
return m.ret1.([]sshutil.Host), m.err
|
||||
return m.ret1.([]authority.Host), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) GetSSHRoots(ctx context.Context) (*authority.SSHKeys, error) {
|
||||
|
@ -882,7 +880,7 @@ func Test_caHandler_Sign(t *testing.T) {
|
|||
authorizeSign: func(ott string) ([]provisioner.SignOption, error) {
|
||||
return tt.certAttrOpts, tt.autherr
|
||||
},
|
||||
getTLSOptions: func() *tlsutil.TLSOptions {
|
||||
getTLSOptions: func() *authority.TLSOptions {
|
||||
return nil
|
||||
},
|
||||
}).(*caHandler)
|
||||
|
@ -933,7 +931,7 @@ func Test_caHandler_Renew(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := New(&mockAuthority{
|
||||
ret1: tt.cert, ret2: tt.root, err: tt.err,
|
||||
getTLSOptions: func() *tlsutil.TLSOptions {
|
||||
getTLSOptions: func() *authority.TLSOptions {
|
||||
return nil
|
||||
},
|
||||
}).(*caHandler)
|
||||
|
@ -994,7 +992,7 @@ func Test_caHandler_Rekey(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := New(&mockAuthority{
|
||||
ret1: tt.cert, ret2: tt.root, err: tt.err,
|
||||
getTLSOptions: func() *tlsutil.TLSOptions {
|
||||
getTLSOptions: func() *authority.TLSOptions {
|
||||
return nil
|
||||
},
|
||||
}).(*caHandler)
|
||||
|
|
10
api/sign.go
10
api/sign.go
|
@ -5,18 +5,18 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
)
|
||||
|
||||
// SignRequest is the request body for a certificate signature request.
|
||||
type SignRequest struct {
|
||||
CsrPEM CertificateRequest `json:"csr"`
|
||||
OTT string `json:"ott"`
|
||||
NotAfter TimeDuration `json:"notAfter"`
|
||||
NotBefore TimeDuration `json:"notBefore"`
|
||||
TemplateData json.RawMessage `json:"templateData"`
|
||||
NotAfter TimeDuration `json:"notAfter,omitempty"`
|
||||
NotBefore TimeDuration `json:"notBefore,omitempty"`
|
||||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
}
|
||||
|
||||
// Validate checks the fields of the SignRequest and returns nil if they are ok
|
||||
|
@ -40,7 +40,7 @@ type SignResponse struct {
|
|||
ServerPEM Certificate `json:"crt"`
|
||||
CaPEM Certificate `json:"ca"`
|
||||
CertChainPEM []Certificate `json:"certChain"`
|
||||
TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"`
|
||||
TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"`
|
||||
TLS *tls.ConnectionState `json:"-"`
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
@ -27,7 +26,7 @@ type SSHAuthority interface {
|
|||
GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error)
|
||||
GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
|
||||
CheckSSHHost(ctx context.Context, principal string, token string) (bool, error)
|
||||
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)
|
||||
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error)
|
||||
GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error)
|
||||
}
|
||||
|
||||
|
@ -36,12 +35,13 @@ type SSHSignRequest struct {
|
|||
PublicKey []byte `json:"publicKey"` // base64 encoded
|
||||
OTT string `json:"ott"`
|
||||
CertType string `json:"certType,omitempty"`
|
||||
KeyID string `json:"keyID,omitempty"`
|
||||
Principals []string `json:"principals,omitempty"`
|
||||
ValidAfter TimeDuration `json:"validAfter,omitempty"`
|
||||
ValidBefore TimeDuration `json:"validBefore,omitempty"`
|
||||
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
|
||||
KeyID string `json:"keyID"`
|
||||
IdentityCSR CertificateRequest `json:"identityCSR,omitempty"`
|
||||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates the SSHSignRequest.
|
||||
|
@ -86,7 +86,7 @@ type SSHCertificate struct {
|
|||
// SSHGetHostsResponse is the response object that returns the list of valid
|
||||
// hosts for SSH.
|
||||
type SSHGetHostsResponse struct {
|
||||
Hosts []sshutil.Host `json:"hosts"`
|
||||
Hosts []authority.Host `json:"hosts"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
|
||||
|
@ -280,6 +280,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
|
|||
Principals: body.Principals,
|
||||
ValidBefore: body.ValidBefore,
|
||||
ValidAfter: body.ValidAfter,
|
||||
TemplateData: body.TemplateData,
|
||||
}
|
||||
|
||||
ctx := provisioner.NewContextWithMethod(r.Context(), provisioner.SSHSignMethod)
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
@ -569,29 +568,29 @@ func Test_caHandler_SSHCheckHost(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_caHandler_SSHGetHosts(t *testing.T) {
|
||||
hosts := []sshutil.Host{
|
||||
{HostID: "1", HostTags: []sshutil.HostTag{{ID: "1", Name: "group", Value: "1"}}, Hostname: "host1"},
|
||||
{HostID: "2", HostTags: []sshutil.HostTag{{ID: "1", Name: "group", Value: "1"}, {ID: "2", Name: "group", Value: "2"}}, Hostname: "host2"},
|
||||
hosts := []authority.Host{
|
||||
{HostID: "1", HostTags: []authority.HostTag{{ID: "1", Name: "group", Value: "1"}}, Hostname: "host1"},
|
||||
{HostID: "2", HostTags: []authority.HostTag{{ID: "1", Name: "group", Value: "1"}, {ID: "2", Name: "group", Value: "2"}}, Hostname: "host2"},
|
||||
}
|
||||
hostsJSON, err := json.Marshal(hosts)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
hosts []sshutil.Host
|
||||
hosts []authority.Host
|
||||
err error
|
||||
body []byte
|
||||
statusCode int
|
||||
}{
|
||||
{"ok", hosts, nil, []byte(fmt.Sprintf(`{"hosts":%s}`, hostsJSON)), http.StatusOK},
|
||||
{"empty (array)", []sshutil.Host{}, nil, []byte(`{"hosts":[]}`), http.StatusOK},
|
||||
{"empty (array)", []authority.Host{}, nil, []byte(`{"hosts":[]}`), http.StatusOK},
|
||||
{"empty (nil)", nil, nil, []byte(`{"hosts":null}`), http.StatusOK},
|
||||
{"error", nil, fmt.Errorf("an error"), nil, http.StatusInternalServerError},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
h := New(&mockAuthority{
|
||||
getSSHHosts: func(context.Context, *x509.Certificate) ([]sshutil.Host, error) {
|
||||
getSSHHosts: func(context.Context, *x509.Certificate) ([]authority.Host, error) {
|
||||
return tt.hosts, tt.err
|
||||
},
|
||||
}).(*caHandler)
|
||||
|
|
|
@ -15,9 +15,8 @@ import (
|
|||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -55,7 +54,7 @@ type Authority struct {
|
|||
// Custom functions
|
||||
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) ([]sshutil.Host, error)
|
||||
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error)
|
||||
getIdentityFunc provisioner.GetIdentityFunc
|
||||
}
|
||||
|
||||
|
|
|
@ -15,14 +15,14 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
stepJOSE "github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func testAuthority(t *testing.T, opts ...Option) *Authority {
|
||||
maxjwk, err := stepJOSE.ParseKey("testdata/secrets/max_pub.jwk")
|
||||
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
disableRenewal := true
|
||||
enableSSHCA := true
|
||||
|
@ -103,7 +103,7 @@ func TestAuthorityNew(t *testing.T) {
|
|||
c.Root = []string{"foo"}
|
||||
return &newTest{
|
||||
config: c,
|
||||
err: errors.New("open foo failed: no such file or directory"),
|
||||
err: errors.New("error reading foo: no such file or directory"),
|
||||
}
|
||||
},
|
||||
"fail bad password": func(t *testing.T) *newTest {
|
||||
|
@ -121,7 +121,7 @@ func TestAuthorityNew(t *testing.T) {
|
|||
c.IntermediateCert = "wrong"
|
||||
return &newTest{
|
||||
config: c,
|
||||
err: errors.New("open wrong failed: no such file or directory"),
|
||||
err: errors.New("error reading wrong: no such file or directory"),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -17,11 +17,10 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
var testAudiences = provisioner.Audiences{
|
||||
|
@ -84,7 +83,7 @@ func generateToken(sub, iss, aud string, sans []string, iat time.Time, jwk *jose
|
|||
func TestAuthority_authorizeToken(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -112,16 +111,16 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/prehistoric-token": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
IssuedAt: jwt.NewNumericDate(now.Add(-time.Hour)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
IssuedAt: jose.NewNumericDate(now.Add(-time.Hour)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -131,11 +130,11 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/provisioner-not-found": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "44",
|
||||
}
|
||||
|
@ -143,7 +142,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "foo"))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
raw, err := jwt.Signed(_sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(_sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -153,15 +152,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/simpledb": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -170,15 +169,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
},
|
||||
"fail/simpledb/token-already-used": func(t *testing.T) *authorizeTest {
|
||||
_a := testAuthority(t)
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
_, err = _a.authorizeToken(context.Background(), raw)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -197,15 +196,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
|
@ -220,15 +219,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
|
@ -245,15 +244,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: _a,
|
||||
|
@ -288,7 +287,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
|
|||
func TestAuthority_authorizeRevoke(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -316,15 +315,15 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/token/invalid-subject": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -334,15 +333,15 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/token": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "44",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -372,7 +371,7 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
|
|||
func TestAuthority_authorizeSign(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -400,15 +399,15 @@ func TestAuthority_authorizeSign(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -418,15 +417,15 @@ func TestAuthority_authorizeSign(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "44",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -459,7 +458,7 @@ func TestAuthority_authorizeSign(t *testing.T) {
|
|||
func TestAuthority_Authorize(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -496,15 +495,15 @@ func TestAuthority_Authorize(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/sign": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.Sign,
|
||||
ID: "1",
|
||||
}
|
||||
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -522,15 +521,15 @@ func TestAuthority_Authorize(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/revoke": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.Revoke,
|
||||
ID: "2",
|
||||
}
|
||||
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -622,15 +621,15 @@ func TestAuthority_Authorize(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/sshRevoke": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.SSHRevoke,
|
||||
ID: "3",
|
||||
}
|
||||
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -892,7 +891,7 @@ func createSSHCert(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate,
|
|||
func TestAuthority_authorizeSSHSign(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -920,15 +919,15 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAudience,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -961,7 +960,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
|||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
assert.Len(t, 11, got)
|
||||
assert.Len(t, 7, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -971,7 +970,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
|||
func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -999,15 +998,15 @@ func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/sshRenew-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.SSHRenew,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -1073,7 +1072,7 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
|
|||
},
|
||||
})}...)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -1100,15 +1099,15 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.SSHRevoke,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
@ -1164,7 +1163,7 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
|
|||
func TestAuthority_authorizeSSHRekey(t *testing.T) {
|
||||
a := testAuthority(t)
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -1192,15 +1191,15 @@ func TestAuthority_authorizeSSHRekey(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/sshRekey-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
|
||||
cl := jwt.Claims{
|
||||
cl := jose.Claims{
|
||||
Subject: "",
|
||||
Issuer: validIssuer,
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: testAudiences.SSHRekey,
|
||||
ID: "43",
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
return &authorizeTest{
|
||||
auth: a,
|
||||
|
|
|
@ -12,15 +12,13 @@ import (
|
|||
"github.com/smallstep/certificates/db"
|
||||
kms "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTLSOptions represents the default TLS version as well as the cipher
|
||||
// suites used in the TLS certificates.
|
||||
DefaultTLSOptions = tlsutil.TLSOptions{
|
||||
CipherSuites: x509util.CipherSuites{
|
||||
DefaultTLSOptions = TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
|
@ -61,15 +59,27 @@ type Config struct {
|
|||
DB *db.Config `json:"db,omitempty"`
|
||||
Monitoring json.RawMessage `json:"monitoring,omitempty"`
|
||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
|
||||
TLS *TLSOptions `json:"tls,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Templates *templates.Templates `json:"templates,omitempty"`
|
||||
}
|
||||
|
||||
// ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer
|
||||
// x509 Certificate blocks.
|
||||
type ASN1DN struct {
|
||||
Country string `json:"country,omitempty" step:"country"`
|
||||
Organization string `json:"organization,omitempty" step:"organization"`
|
||||
OrganizationalUnit string `json:"organizationalUnit,omitempty" step:"organizationalUnit"`
|
||||
Locality string `json:"locality,omitempty" step:"locality"`
|
||||
Province string `json:"province,omitempty" step:"province"`
|
||||
StreetAddress string `json:"streetAddress,omitempty" step:"streetAddress"`
|
||||
CommonName string `json:"commonName,omitempty" step:"commonName"`
|
||||
}
|
||||
|
||||
// AuthConfig represents the configuration options for the authority.
|
||||
type AuthConfig struct {
|
||||
Provisioners provisioner.List `json:"provisioners"`
|
||||
Template *x509util.ASN1DN `json:"template,omitempty"`
|
||||
Template *ASN1DN `json:"template,omitempty"`
|
||||
Claims *provisioner.Claims `json:"claims,omitempty"`
|
||||
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
|
||||
Backdate *provisioner.Duration `json:"backdate,omitempty"`
|
||||
|
@ -82,7 +92,7 @@ func (c *AuthConfig) init() {
|
|||
c.Provisioners = provisioner.List{}
|
||||
}
|
||||
if c.Template == nil {
|
||||
c.Template = &x509util.ASN1DN{}
|
||||
c.Template = &ASN1DN{}
|
||||
}
|
||||
if c.Backdate == nil {
|
||||
c.Backdate = &provisioner.Duration{
|
||||
|
|
|
@ -7,15 +7,13 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
stepJOSE "github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
maxjwk, err := stepJOSE.ParseKey("testdata/secrets/max_pub.jwk")
|
||||
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
ac := &AuthConfig{
|
||||
Provisioners: provisioner.List{
|
||||
|
@ -35,7 +33,7 @@ func TestConfigValidate(t *testing.T) {
|
|||
type ConfigValidateTest struct {
|
||||
config *Config
|
||||
err error
|
||||
tls tlsutil.TLSOptions
|
||||
tls TLSOptions
|
||||
}
|
||||
tests := map[string]func(*testing.T) ConfigValidateTest{
|
||||
"empty-address": func(t *testing.T) ConfigValidateTest {
|
||||
|
@ -141,7 +139,7 @@ func TestConfigValidate(t *testing.T) {
|
|||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
TLS: &tlsutil.TLSOptions{},
|
||||
TLS: &TLSOptions{},
|
||||
},
|
||||
tls: DefaultTLSOptions,
|
||||
}
|
||||
|
@ -156,8 +154,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
TLS: &tlsutil.TLSOptions{
|
||||
CipherSuites: x509util.CipherSuites{
|
||||
TLS: &TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
},
|
||||
MinVersion: 1.0,
|
||||
|
@ -165,8 +163,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
Renegotiation: true,
|
||||
},
|
||||
},
|
||||
tls: tlsutil.TLSOptions{
|
||||
CipherSuites: x509util.CipherSuites{
|
||||
tls: TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
},
|
||||
MinVersion: 1.0,
|
||||
|
@ -185,8 +183,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
TLS: &tlsutil.TLSOptions{
|
||||
CipherSuites: x509util.CipherSuites{
|
||||
TLS: &TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
},
|
||||
MinVersion: 1.2,
|
||||
|
@ -217,7 +215,7 @@ func TestConfigValidate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthConfigValidate(t *testing.T) {
|
||||
asn1dn := x509util.ASN1DN{
|
||||
asn1dn := ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -226,9 +224,9 @@ func TestAuthConfigValidate(t *testing.T) {
|
|||
CommonName: "test",
|
||||
}
|
||||
|
||||
maxjwk, err := stepJOSE.ParseKey("testdata/secrets/max_pub.jwk")
|
||||
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
p := provisioner.List{
|
||||
&provisioner.JWK{
|
||||
|
@ -245,7 +243,7 @@ func TestAuthConfigValidate(t *testing.T) {
|
|||
|
||||
type AuthConfigValidateTest struct {
|
||||
ac *AuthConfig
|
||||
asn1dn x509util.ASN1DN
|
||||
asn1dn ASN1DN
|
||||
err error
|
||||
}
|
||||
tests := map[string]func(*testing.T) AuthConfigValidateTest{
|
||||
|
@ -258,7 +256,7 @@ func TestAuthConfigValidate(t *testing.T) {
|
|||
"ok-empty-provisioners": func(t *testing.T) AuthConfigValidateTest {
|
||||
return AuthConfigValidateTest{
|
||||
ac: &AuthConfig{},
|
||||
asn1dn: x509util.ASN1DN{},
|
||||
asn1dn: ASN1DN{},
|
||||
}
|
||||
},
|
||||
"ok-empty-asn1dn-template": func(t *testing.T) AuthConfigValidateTest {
|
||||
|
@ -266,7 +264,7 @@ func TestAuthConfigValidate(t *testing.T) {
|
|||
ac: &AuthConfig{
|
||||
Provisioners: p,
|
||||
},
|
||||
asn1dn: x509util.ASN1DN{},
|
||||
asn1dn: ASN1DN{},
|
||||
}
|
||||
},
|
||||
"ok-custom-asn1dn": func(t *testing.T) AuthConfigValidateTest {
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -64,7 +63,7 @@ func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastio
|
|||
|
||||
// WithSSHGetHosts sets a custom function to get the bastion for a
|
||||
// given user-host pair.
|
||||
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)) Option {
|
||||
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]Host, error)) Option {
|
||||
return func(a *Authority) error {
|
||||
a.sshGetHostsFunc = fn
|
||||
return nil
|
||||
|
|
|
@ -17,7 +17,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -579,35 +580,44 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
|||
}
|
||||
|
||||
doc := claims.document
|
||||
signOptions := []SignOption{}
|
||||
|
||||
signOptions := []SignOption{
|
||||
// set the key id to the instance id
|
||||
sshCertKeyIDModifier(doc.InstanceID),
|
||||
// Enforce host certificate.
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
var principals []string
|
||||
if p.DisableCustomSANs {
|
||||
principals = []string{
|
||||
// Validated principals.
|
||||
principals := []string{
|
||||
doc.PrivateIP,
|
||||
fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region),
|
||||
}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
if p.DisableCustomSANs {
|
||||
defaults.Principals = principals
|
||||
} else {
|
||||
// Check that at least one principal is sent in the request.
|
||||
signOptions = append(signOptions, &sshCertOptionsRequireValidator{
|
||||
Principals: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Default to cert type to host
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
Principals: principals,
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(sshutil.HostCert, doc.InstanceID, principals)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
// Validate user options
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(defaults))
|
||||
// Set defaults if not given as user options
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier(defaults))
|
||||
templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions = append(signOptions, templateOptions)
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Validate user SignSSHOptions.
|
||||
sshCertOptionsValidator(defaults),
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{p.claimer},
|
||||
// Validate public key
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestAWS_Getters(t *testing.T) {
|
||||
|
@ -763,6 +763,7 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) {
|
|||
} else if assert.NotNil(t, got) {
|
||||
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||
if (err != nil) != tt.wantSignErr {
|
||||
|
||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||
} else {
|
||||
if tt.wantSignErr {
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -338,30 +339,42 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
|
|||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions := []SignOption{
|
||||
// set the key id to the instance name
|
||||
sshCertKeyIDModifier(name),
|
||||
}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
var principals []string
|
||||
if p.DisableCustomSANs {
|
||||
principals = []string{name}
|
||||
}
|
||||
signOptions := []SignOption{}
|
||||
|
||||
// Default to host + known hostnames
|
||||
// Enforce host certificate.
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
Principals: principals,
|
||||
}
|
||||
// Validate user options
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(defaults))
|
||||
// Set defaults if not given as user options
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier(defaults))
|
||||
|
||||
// Validated principals.
|
||||
principals := []string{name}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
if p.DisableCustomSANs {
|
||||
defaults.Principals = principals
|
||||
} else {
|
||||
// Check that at least one principal is sent in the request.
|
||||
signOptions = append(signOptions, &sshCertOptionsRequireValidator{
|
||||
Principals: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(sshutil.HostCert, name, principals)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions = append(signOptions, templateOptions)
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Validate user SignSSHOptions.
|
||||
sshCertOptionsValidator(defaults),
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{p.claimer},
|
||||
// Validate public key
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestAzure_Getters(t *testing.T) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
// DefaultProvisionersLimit is the default limit for listing provisioners.
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestCollection_Load(t *testing.T) {
|
||||
|
|
|
@ -15,7 +15,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -378,34 +379,44 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
|||
}
|
||||
|
||||
ce := claims.Google.ComputeEngine
|
||||
signOptions := []SignOption{}
|
||||
|
||||
signOptions := []SignOption{
|
||||
// set the key id to the instance name
|
||||
sshCertKeyIDModifier(ce.InstanceName),
|
||||
// Enforce host certificate.
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
var principals []string
|
||||
if p.DisableCustomSANs {
|
||||
principals = []string{
|
||||
// Validated principals.
|
||||
principals := []string{
|
||||
fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID),
|
||||
fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID),
|
||||
}
|
||||
|
||||
// Only enforce known principals if disable custom sans is true.
|
||||
if p.DisableCustomSANs {
|
||||
defaults.Principals = principals
|
||||
} else {
|
||||
// Check that at least one principal is sent in the request.
|
||||
signOptions = append(signOptions, &sshCertOptionsRequireValidator{
|
||||
Principals: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Default to host + known hostnames
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
Principals: principals,
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(sshutil.HostCert, ce.InstanceName, principals)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
// Validate user options
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(defaults))
|
||||
// Set defaults if not given as user options
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier(defaults))
|
||||
|
||||
templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions = append(signOptions, templateOptions)
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Validate user SignSSHOptions.
|
||||
sshCertOptionsValidator(defaults),
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{p.claimer},
|
||||
// Validate public key
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestGCP_Getters(t *testing.T) {
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -203,41 +204,54 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
|||
|
||||
opts := claims.Step.SSH
|
||||
signOptions := []SignOption{
|
||||
// validates user's SSHOptions with the ones in the token
|
||||
// validates user's SignSSHOptions with the ones in the token
|
||||
sshCertOptionsValidator(*opts),
|
||||
// validate users's KeyID is the token subject.
|
||||
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
|
||||
}
|
||||
|
||||
t := now()
|
||||
// Add modifiers from custom claims
|
||||
// FIXME: this is also set in the sign method using SSHOptions.Modify.
|
||||
// Default template attributes.
|
||||
certType := sshutil.UserCert
|
||||
keyID := claims.Subject
|
||||
principals := []string{claims.Subject}
|
||||
|
||||
// Use options in the token.
|
||||
if opts.CertType != "" {
|
||||
signOptions = append(signOptions, sshCertTypeModifier(opts.CertType))
|
||||
if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil {
|
||||
return nil, errs.Wrap(http.StatusBadRequest, err, "jwk.AuthorizeSSHSign")
|
||||
}
|
||||
}
|
||||
if opts.KeyID != "" {
|
||||
keyID = opts.KeyID
|
||||
}
|
||||
if len(opts.Principals) > 0 {
|
||||
signOptions = append(signOptions, sshCertPrincipalsModifier(opts.Principals))
|
||||
principals = opts.Principals
|
||||
}
|
||||
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(certType, keyID, principals)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
templateOptions, err := TemplateSSHOptions(p.Options, data)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
||||
}
|
||||
signOptions = append(signOptions, templateOptions)
|
||||
|
||||
// Add modifiers from custom claims
|
||||
t := now()
|
||||
if !opts.ValidAfter.IsZero() {
|
||||
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
|
||||
}
|
||||
if !opts.ValidBefore.IsZero() {
|
||||
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
|
||||
}
|
||||
if opts.KeyID != "" {
|
||||
signOptions = append(signOptions, sshCertKeyIDModifier(opts.KeyID))
|
||||
} else {
|
||||
signOptions = append(signOptions, sshCertKeyIDModifier(claims.Subject))
|
||||
}
|
||||
|
||||
// Default to a user certificate with no principals if not set
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier{CertType: SSHUserCert})
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{p.claimer},
|
||||
// Validate that the keyID is equivalent to the token subject.
|
||||
sshCertKeyIDValidator(claims.Subject),
|
||||
// Validate public key
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// Validate the validity period.
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestJWK_Getters(t *testing.T) {
|
||||
|
@ -512,10 +512,6 @@ func TestJWK_AuthorizeSign_SSHOptions(t *testing.T) {
|
|||
}{
|
||||
{"ok-user", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, expectedUserOptions, false, false},
|
||||
{"ok-host", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, &SignSSHOptions{}, jwk}, expectedHostOptions, false, false},
|
||||
{"ok-user-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false},
|
||||
{"ok-host-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, jwk}, expectedHostOptions, false, false},
|
||||
{"ok-user-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user"}, &SignSSHOptions{Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false},
|
||||
{"ok-host-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{Principals: []string{"smallstep.com"}}, &SignSSHOptions{CertType: "host"}, jwk}, expectedHostOptions, false, false},
|
||||
{"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{
|
||||
CertType: "user", Principals: []string{"name"},
|
||||
}, &SignSSHOptions{
|
||||
|
|
|
@ -11,8 +11,9 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -249,16 +250,27 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
|
|||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID())
|
||||
}
|
||||
if _, err := p.authorizeToken(token, p.audiences.SSHSign); err != nil {
|
||||
claims, err := p.authorizeToken(token, p.audiences.SSHSign)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign")
|
||||
}
|
||||
|
||||
// Default to a user certificate with no principals if not set
|
||||
signOptions := []SignOption{sshCertDefaultsModifier{CertType: SSHUserCert}}
|
||||
// Certificate templates.
|
||||
// Set some default variables to be used in the templates.
|
||||
data := sshutil.CreateTemplateData(sshutil.HostCert, claims.ServiceAccountName, []string{claims.ServiceAccountName})
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.CertificateRequestTemplate)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions := []SignOption{templateOptions}
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Require type, key-id and principals in the SignSSHOptions.
|
||||
&sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true},
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{p.claimer},
|
||||
// Validate public key
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func TestK8sSA_Getters(t *testing.T) {
|
||||
|
@ -361,9 +361,9 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
|
|||
tot := 0
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case sshCertDefaultsModifier:
|
||||
assert.Equals(t, v.CertType, SSHUserCert)
|
||||
case *sshDefaultExtensionModifier:
|
||||
case sshCertificateOptionsFunc:
|
||||
case *sshCertOptionsRequireValidator:
|
||||
assert.Equals(t, v, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true})
|
||||
case *sshCertValidityValidator:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
case *sshDefaultPublicKeyValidator:
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func Test_newKeyStore(t *testing.T) {
|
||||
|
|
|
@ -13,7 +13,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -369,10 +370,6 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
|
|||
if claims.Email == "" {
|
||||
return nil, errs.Unauthorized("oidc.AuthorizeSSHSign: failed to validate oidc token payload: email not found")
|
||||
}
|
||||
signOptions := []SignOption{
|
||||
// set the key id to the token email
|
||||
sshCertKeyIDModifier(claims.Email),
|
||||
}
|
||||
|
||||
// Get the identity using either the default identityFunc or one injected
|
||||
// externally.
|
||||
|
@ -380,25 +377,52 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
|
|||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign")
|
||||
}
|
||||
defaults := SignSSHOptions{
|
||||
CertType: SSHUserCert,
|
||||
Principals: iden.Usernames,
|
||||
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(sshutil.UserCert, claims.Email, iden.Usernames)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
// Add custom extensions added in the identity function.
|
||||
for k, v := range iden.Permissions.Extensions {
|
||||
data.AddExtension(k, v)
|
||||
}
|
||||
// Add custom critical options added in the identity function.
|
||||
for k, v := range iden.Permissions.CriticalOptions {
|
||||
data.AddCriticalOption(k, v)
|
||||
}
|
||||
|
||||
// Use the default template unless no-templates are configured and email is
|
||||
// an admin, in that case we will use the parameters in the request.
|
||||
isAdmin := o.IsAdmin(claims.Email)
|
||||
defaultTemplate := sshutil.DefaultTemplate
|
||||
if isAdmin && !o.Options.GetSSHOptions().HasTemplate() {
|
||||
defaultTemplate = sshutil.DefaultAdminTemplate
|
||||
}
|
||||
|
||||
templateOptions, err := CustomSSHTemplateOptions(o.Options, data, defaultTemplate)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
|
||||
}
|
||||
signOptions := []SignOption{templateOptions}
|
||||
|
||||
// Admin users can use any principal, and can sign user and host certificates.
|
||||
// Non-admin users can only use principals returned by the identityFunc, and
|
||||
// can only sign user certificates.
|
||||
if !o.IsAdmin(claims.Email) {
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(defaults))
|
||||
if isAdmin {
|
||||
signOptions = append(signOptions, &sshCertOptionsRequireValidator{
|
||||
CertType: true,
|
||||
KeyID: true,
|
||||
Principals: true,
|
||||
})
|
||||
} else {
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{
|
||||
CertType: SSHUserCert,
|
||||
Principals: iden.Usernames,
|
||||
}))
|
||||
}
|
||||
|
||||
// Default to a user certificate with usernames as principals if those options
|
||||
// are not set.
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier(defaults))
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Set the validity bounds if not set.
|
||||
&sshDefaultDuration{o.claimer},
|
||||
// Validate public key
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
func Test_openIDConfiguration_Validate(t *testing.T) {
|
||||
|
@ -565,33 +565,34 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
|
|||
{"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false},
|
||||
{"ok-user", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false},
|
||||
{"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name"},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"mariano"},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"ok-options", p1, args{t1, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name"},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"admin", p3, args{okAdmin, SignSSHOptions{}, pub}, expectedAdminOptions, http.StatusOK, false, false},
|
||||
{"admin-user", p3, args{okAdmin, SignSSHOptions{CertType: "user"}, pub}, expectedAdminOptions, http.StatusOK, false, false},
|
||||
{"admin-principals", p3, args{okAdmin, SignSSHOptions{Principals: []string{"root"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"root"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"admin-options", p3, args{okAdmin, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"admin-host", p3, args{okAdmin, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub},
|
||||
{"admin-user", p3, args{okAdmin, SignSSHOptions{CertType: "user", KeyID: "root@example.com", Principals: []string{"root", "root@example.com"}}, pub},
|
||||
expectedAdminOptions, http.StatusOK, false, false},
|
||||
{"admin-host", p3, args{okAdmin, SignSSHOptions{CertType: "host", KeyID: "smallstep.com", Principals: []string{"smallstep.com"}}, pub},
|
||||
expectedHostOptions, http.StatusOK, false, false},
|
||||
{"admin-options", p3, args{okAdmin, SignSSHOptions{CertType: "user", KeyID: "name", Principals: []string{"name"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name"},
|
||||
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
|
||||
{"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true},
|
||||
{"fail-user-host", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusOK, false, true},
|
||||
{"fail-user-principals", p1, args{t1, SignSSHOptions{Principals: []string{"root"}}, pub}, nil, http.StatusOK, false, true},
|
||||
{"fail-email", p3, args{failEmail, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
|
||||
{"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false},
|
||||
{"fail-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
|
||||
// Missing parametrs
|
||||
{"fail-admin-type", p3, args{okAdmin, SignSSHOptions{KeyID: "root@example.com", Principals: []string{"root@example.com"}}, pub}, nil, http.StatusUnauthorized, false, true},
|
||||
{"fail-admin-key-id", p3, args{okAdmin, SignSSHOptions{CertType: "user", Principals: []string{"root@example.com"}}, pub}, nil, http.StatusUnauthorized, false, true},
|
||||
{"fail-admin-principals", p3, args{okAdmin, SignSSHOptions{CertType: "user", KeyID: "root@example.com"}, pub}, nil, http.StatusUnauthorized, false, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -25,9 +25,10 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option {
|
|||
// each provisioner.
|
||||
type Options struct {
|
||||
X509 *X509Options `json:"x509,omitempty"`
|
||||
SSH *SSHOptions `json:"ssh,omitempty"`
|
||||
}
|
||||
|
||||
// GetX509Options returns the X.509Options
|
||||
// GetX509Options returns the X.509 options.
|
||||
func (o *Options) GetX509Options() *X509Options {
|
||||
if o == nil {
|
||||
return nil
|
||||
|
@ -35,18 +36,26 @@ func (o *Options) GetX509Options() *X509Options {
|
|||
return o.X509
|
||||
}
|
||||
|
||||
// GetSSHOptions returns the SSH options.
|
||||
func (o *Options) GetSSHOptions() *SSHOptions {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return o.SSH
|
||||
}
|
||||
|
||||
// X509Options contains specific options for X.509 certificates.
|
||||
type X509Options struct {
|
||||
// Template contains a X.509 certificate template. It can be a JSON template
|
||||
// escaped in a string or it can be also encoded in base64.
|
||||
Template string `json:"template"`
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateFile points to a file containing a X.509 certificate template.
|
||||
TemplateFile string `json:"templateFile"`
|
||||
TemplateFile string `json:"templateFile,omitempty"`
|
||||
|
||||
// TemplateData is a JSON object with variables that can be used in custom
|
||||
// templates.
|
||||
TemplateData json.RawMessage `json:"templateData"`
|
||||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
}
|
||||
|
||||
// HasTemplate returns true if a template is defined in the provisioner options.
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -46,6 +46,28 @@ func TestOptions_GetX509Options(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOptions_GetSSHOptions(t *testing.T) {
|
||||
type fields struct {
|
||||
o *Options
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want *SSHOptions
|
||||
}{
|
||||
{"ok", fields{&Options{SSH: &SSHOptions{Template: "foo"}}}, &SSHOptions{Template: "foo"}},
|
||||
{"nil", fields{&Options{}}, nil},
|
||||
{"nilOptions", fields{nil}, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.fields.o.GetSSHOptions(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Options.GetSSHOptions() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerX509Options_HasTemplate(t *testing.T) {
|
||||
type fields struct {
|
||||
Template string
|
||||
|
|
|
@ -327,6 +327,13 @@ func (b *base) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certif
|
|||
// by provisioners to populate certificate fields.
|
||||
type Identity struct {
|
||||
Usernames []string `json:"usernames"`
|
||||
Permissions `json:"permissions"`
|
||||
}
|
||||
|
||||
// Permissions defines extra extensions and critical options to grant to an SSH certificate.
|
||||
type Permissions struct {
|
||||
Extensions map[string]string `json:"extensions"`
|
||||
CriticalOptions map[string]string `json:"criticalOptions"`
|
||||
}
|
||||
|
||||
// GetIdentityFunc is a function that returns an identity.
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func Test_emailOnlyIdentity_Valid(t *testing.T) {
|
||||
|
@ -614,7 +614,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
|
|||
t.Run(name, func(t *testing.T) {
|
||||
tt := run()
|
||||
assert.FatalError(t, tt.pdd.Modify(tt.cert, tt.so), "unexpected error")
|
||||
time.Sleep(1 * time.Nanosecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
tt.valid(tt.cert)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,11 +3,12 @@ package provisioner
|
|||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -23,14 +24,7 @@ const (
|
|||
// certificate.
|
||||
type SSHCertModifier interface {
|
||||
SignOption
|
||||
Modify(cert *ssh.Certificate) error
|
||||
}
|
||||
|
||||
// SSHCertOptionModifier is the interface used to add custom options used
|
||||
// to modify the SSH certificate.
|
||||
type SSHCertOptionModifier interface {
|
||||
SignOption
|
||||
Option(o SignSSHOptions) SSHCertModifier
|
||||
Modify(cert *ssh.Certificate, opts SignSSHOptions) error
|
||||
}
|
||||
|
||||
// SSHCertValidator is the interface used to validate an SSH certificate.
|
||||
|
@ -46,14 +40,6 @@ type SSHCertOptionsValidator interface {
|
|||
Valid(got SignSSHOptions) error
|
||||
}
|
||||
|
||||
// sshModifierFunc is an adapter to allow the use of ordinary functions as SSH
|
||||
// certificate modifiers.
|
||||
type sshModifierFunc func(cert *ssh.Certificate) error
|
||||
|
||||
func (f sshModifierFunc) Modify(cert *ssh.Certificate) error {
|
||||
return f(cert)
|
||||
}
|
||||
|
||||
// SignSSHOptions contains the options that can be passed to the SignSSH method.
|
||||
type SignSSHOptions struct {
|
||||
CertType string `json:"certType"`
|
||||
|
@ -61,16 +47,25 @@ type SignSSHOptions struct {
|
|||
Principals []string `json:"principals"`
|
||||
ValidAfter TimeDuration `json:"validAfter,omitempty"`
|
||||
ValidBefore TimeDuration `json:"validBefore,omitempty"`
|
||||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
Backdate time.Duration `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates the given SignSSHOptions.
|
||||
func (o SignSSHOptions) Validate() error {
|
||||
if o.CertType != "" && o.CertType != SSHUserCert && o.CertType != SSHHostCert {
|
||||
return errors.Errorf("unknown certType %s", o.CertType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the uint32 representation of the CertType.
|
||||
func (o SignSSHOptions) Type() uint32 {
|
||||
return sshCertTypeUInt32(o.CertType)
|
||||
}
|
||||
|
||||
// Modify implements SSHCertModifier and sets the SSHOption in the ssh.Certificate.
|
||||
func (o SignSSHOptions) Modify(cert *ssh.Certificate) error {
|
||||
func (o SignSSHOptions) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
switch o.CertType {
|
||||
case "": // ignore
|
||||
case SSHUserCert:
|
||||
|
@ -84,6 +79,12 @@ func (o SignSSHOptions) Modify(cert *ssh.Certificate) error {
|
|||
cert.KeyId = o.KeyID
|
||||
cert.ValidPrincipals = o.Principals
|
||||
|
||||
return o.ModifyValidity(cert)
|
||||
}
|
||||
|
||||
// ModifyValidity modifies only the ValidAfter and ValidBefore on the given
|
||||
// ssh.Certificate.
|
||||
func (o SignSSHOptions) ModifyValidity(cert *ssh.Certificate) error {
|
||||
t := now()
|
||||
if !o.ValidAfter.IsZero() {
|
||||
cert.ValidAfter = uint64(o.ValidAfter.RelativeTime(t).Unix())
|
||||
|
@ -94,7 +95,6 @@ func (o SignSSHOptions) Modify(cert *ssh.Certificate) error {
|
|||
if cert.ValidAfter > 0 && cert.ValidBefore > 0 && cert.ValidAfter > cert.ValidBefore {
|
||||
return errors.New("ssh certificate valid after cannot be greater than valid before")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ func (o SignSSHOptions) match(got SignSSHOptions) error {
|
|||
type sshCertPrincipalsModifier []string
|
||||
|
||||
// Modify the ValidPrincipals value of the cert.
|
||||
func (o sshCertPrincipalsModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (o sshCertPrincipalsModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
cert.ValidPrincipals = []string(o)
|
||||
return nil
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ func (o sshCertPrincipalsModifier) Modify(cert *ssh.Certificate) error {
|
|||
// Key ID in the SSH certificate.
|
||||
type sshCertKeyIDModifier string
|
||||
|
||||
func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
cert.KeyId = string(m)
|
||||
return nil
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate) error {
|
|||
type sshCertTypeModifier string
|
||||
|
||||
// Modify sets the CertType for the ssh certificate.
|
||||
func (m sshCertTypeModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshCertTypeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
cert.CertType = sshCertTypeUInt32(string(m))
|
||||
return nil
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ func (m sshCertTypeModifier) Modify(cert *ssh.Certificate) error {
|
|||
// ValidAfter in the SSH certificate.
|
||||
type sshCertValidAfterModifier uint64
|
||||
|
||||
func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
cert.ValidAfter = uint64(m)
|
||||
return nil
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate) error {
|
|||
// ValidBefore in the SSH certificate.
|
||||
type sshCertValidBeforeModifier uint64
|
||||
|
||||
func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
cert.ValidBefore = uint64(m)
|
||||
return nil
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate) error {
|
|||
type sshCertDefaultsModifier SignSSHOptions
|
||||
|
||||
// Modify implements the SSHCertModifier interface.
|
||||
func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
if cert.CertType == 0 {
|
||||
cert.CertType = sshCertTypeUInt32(m.CertType)
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error {
|
|||
// the default extensions in an SSH certificate.
|
||||
type sshDefaultExtensionModifier struct{}
|
||||
|
||||
func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
|
||||
switch cert.CertType {
|
||||
// Default to no extensions for HostCert.
|
||||
case ssh.HostCert:
|
||||
|
@ -215,8 +215,9 @@ type sshDefaultDuration struct {
|
|||
*Claimer
|
||||
}
|
||||
|
||||
func (m *sshDefaultDuration) Option(o SignSSHOptions) SSHCertModifier {
|
||||
return sshModifierFunc(func(cert *ssh.Certificate) error {
|
||||
// Modify implements SSHCertModifier and sets the validity if it has not been
|
||||
// set, but it always applies the backdate.
|
||||
func (m *sshDefaultDuration) Modify(cert *ssh.Certificate, o SignSSHOptions) error {
|
||||
d, err := m.DefaultSSHCertDuration(cert.CertType)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -235,7 +236,6 @@ func (m *sshDefaultDuration) Option(o SignSSHOptions) SSHCertModifier {
|
|||
cert.ValidAfter -= backdate
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// sshLimitDuration adjusts the duration to min(default, remaining provisioning
|
||||
|
@ -248,13 +248,15 @@ type sshLimitDuration struct {
|
|||
NotAfter time.Time
|
||||
}
|
||||
|
||||
func (m *sshLimitDuration) Option(o SignSSHOptions) SSHCertModifier {
|
||||
// Modify implements SSHCertModifier and modifies the validity of the
|
||||
// certificate to expire before the configured limit.
|
||||
func (m *sshLimitDuration) Modify(cert *ssh.Certificate, o SignSSHOptions) error {
|
||||
if m.NotAfter.IsZero() {
|
||||
defaultDuration := &sshDefaultDuration{m.Claimer}
|
||||
return defaultDuration.Option(o)
|
||||
return defaultDuration.Modify(cert, o)
|
||||
}
|
||||
|
||||
return sshModifierFunc(func(cert *ssh.Certificate) error {
|
||||
// Make sure the duration is within the limits.
|
||||
d, err := m.DefaultSSHCertDuration(cert.CertType)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -292,7 +294,6 @@ func (m *sshLimitDuration) Option(o SignSSHOptions) SSHCertModifier {
|
|||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// sshCertOptionsValidator validates the user SSHOptions with the ones
|
||||
|
@ -306,6 +307,26 @@ func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error {
|
|||
return want.match(got)
|
||||
}
|
||||
|
||||
// sshCertOptionsRequireValidator defines which elements in the SignSSHOptions are required.
|
||||
type sshCertOptionsRequireValidator struct {
|
||||
CertType bool
|
||||
KeyID bool
|
||||
Principals bool
|
||||
}
|
||||
|
||||
func (v *sshCertOptionsRequireValidator) Valid(got SignSSHOptions) error {
|
||||
switch {
|
||||
case v.CertType && got.CertType == "":
|
||||
return errors.New("ssh certificate certType cannot be empty")
|
||||
case v.KeyID && got.KeyID == "":
|
||||
return errors.New("ssh certificate keyID cannot be empty")
|
||||
case v.Principals && len(got.Principals) == 0:
|
||||
return errors.New("ssh certificate principals cannot be empty")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type sshCertValidityValidator struct {
|
||||
*Claimer
|
||||
}
|
||||
|
@ -354,7 +375,9 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
|
|||
// fields in the SSH certificate.
|
||||
type sshCertDefaultValidator struct{}
|
||||
|
||||
// Valid returns an error if the given certificate does not contain the necessary fields.
|
||||
// Valid returns an error if the given certificate does not contain the
|
||||
// necessary fields. We skip ValidPrincipals and Extensions as with custom
|
||||
// templates you can set them empty.
|
||||
func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error {
|
||||
switch {
|
||||
case len(cert.Nonce) == 0:
|
||||
|
@ -367,16 +390,12 @@ func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SignSSHOptions)
|
|||
return errors.Errorf("ssh certificate has an unknown type: %d", cert.CertType)
|
||||
case cert.KeyId == "":
|
||||
return errors.New("ssh certificate key id cannot be empty")
|
||||
case len(cert.ValidPrincipals) == 0:
|
||||
return errors.New("ssh certificate valid principals cannot be empty")
|
||||
case cert.ValidAfter == 0:
|
||||
return errors.New("ssh certificate validAfter cannot be 0")
|
||||
case cert.ValidBefore < uint64(now().Unix()):
|
||||
return errors.New("ssh certificate validBefore cannot be in the past")
|
||||
case cert.ValidBefore < cert.ValidAfter:
|
||||
return errors.New("ssh certificate validBefore cannot be before validAfter")
|
||||
case cert.CertType == ssh.UserCert && len(cert.Extensions) == 0:
|
||||
return errors.New("ssh certificate extensions cannot be empty")
|
||||
case cert.SignatureKey == nil:
|
||||
return errors.New("ssh certificate signature key cannot be nil")
|
||||
case cert.Signature == nil:
|
||||
|
@ -404,9 +423,9 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOpti
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if key.Size() < keys.MinRSAKeyBytes {
|
||||
if key.Size() < keyutil.MinRSAKeyBytes {
|
||||
return errors.Errorf("ssh certificate key must be at least %d bits (%d bytes)",
|
||||
8*keys.MinRSAKeyBytes, keys.MinRSAKeyBytes)
|
||||
8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes)
|
||||
}
|
||||
return nil
|
||||
case ssh.KeyAlgoDSA:
|
||||
|
@ -416,17 +435,6 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOpti
|
|||
}
|
||||
}
|
||||
|
||||
// sshCertKeyIDValidator implements a validator for the KeyId attribute.
|
||||
type sshCertKeyIDValidator string
|
||||
|
||||
// Valid returns an error if the given certificate does not contain the necessary fields.
|
||||
func (v sshCertKeyIDValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error {
|
||||
if string(v) != cert.KeyId {
|
||||
return errors.Errorf("invalid ssh certificate KeyId; want %s, but got %s", string(v), cert.KeyId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sshCertTypeUInt32
|
||||
func sshCertTypeUInt32(ct string) uint32 {
|
||||
switch ct {
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -40,7 +39,7 @@ func TestSSHOptions_Type(t *testing.T) {
|
|||
|
||||
func TestSSHOptions_Modify(t *testing.T) {
|
||||
type test struct {
|
||||
so *SignSSHOptions
|
||||
so SignSSHOptions
|
||||
cert *ssh.Certificate
|
||||
valid func(*ssh.Certificate)
|
||||
err error
|
||||
|
@ -48,21 +47,21 @@ func TestSSHOptions_Modify(t *testing.T) {
|
|||
tests := map[string](func() test){
|
||||
"fail/unexpected-cert-type": func() test {
|
||||
return test{
|
||||
so: &SignSSHOptions{CertType: "foo"},
|
||||
so: SignSSHOptions{CertType: "foo"},
|
||||
cert: new(ssh.Certificate),
|
||||
err: errors.Errorf("ssh certificate has an unknown type - foo"),
|
||||
}
|
||||
},
|
||||
"fail/validAfter-greater-validBefore": func() test {
|
||||
return test{
|
||||
so: &SignSSHOptions{CertType: "user"},
|
||||
so: SignSSHOptions{CertType: "user"},
|
||||
cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)},
|
||||
err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"),
|
||||
}
|
||||
},
|
||||
"ok/user-cert": func() test {
|
||||
return test{
|
||||
so: &SignSSHOptions{CertType: "user"},
|
||||
so: SignSSHOptions{CertType: "user"},
|
||||
cert: new(ssh.Certificate),
|
||||
valid: func(cert *ssh.Certificate) {
|
||||
assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
|
||||
|
@ -71,7 +70,7 @@ func TestSSHOptions_Modify(t *testing.T) {
|
|||
},
|
||||
"ok/host-cert": func() test {
|
||||
return test{
|
||||
so: &SignSSHOptions{CertType: "host"},
|
||||
so: SignSSHOptions{CertType: "host"},
|
||||
cert: new(ssh.Certificate),
|
||||
valid: func(cert *ssh.Certificate) {
|
||||
assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
|
||||
|
@ -81,7 +80,7 @@ func TestSSHOptions_Modify(t *testing.T) {
|
|||
"ok": func() test {
|
||||
va := time.Now().Add(5 * time.Minute)
|
||||
vb := time.Now().Add(1 * time.Hour)
|
||||
so := &SignSSHOptions{CertType: "host", KeyID: "foo", Principals: []string{"foo", "bar"},
|
||||
so := SignSSHOptions{CertType: "host", KeyID: "foo", Principals: []string{"foo", "bar"},
|
||||
ValidAfter: NewTimeDuration(va), ValidBefore: NewTimeDuration(vb)}
|
||||
return test{
|
||||
so: so,
|
||||
|
@ -99,7 +98,7 @@ func TestSSHOptions_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if err := tc.so.Modify(tc.cert); err != nil {
|
||||
if err := tc.so.Modify(tc.cert, tc.so); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
|
@ -222,7 +221,7 @@ func Test_sshCertPrincipalsModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert)) {
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
||||
assert.Equals(t, tc.cert.ValidPrincipals, tc.expected)
|
||||
}
|
||||
})
|
||||
|
@ -248,7 +247,7 @@ func Test_sshCertKeyIDModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert)) {
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
||||
assert.Equals(t, tc.cert.KeyId, tc.expected)
|
||||
}
|
||||
})
|
||||
|
@ -287,7 +286,7 @@ func Test_sshCertTypeModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert)) {
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
||||
assert.Equals(t, tc.cert.CertType, uint32(tc.expected))
|
||||
}
|
||||
})
|
||||
|
@ -312,7 +311,7 @@ func Test_sshCertValidAfterModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert)) {
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
||||
assert.Equals(t, tc.cert.ValidAfter, tc.expected)
|
||||
}
|
||||
})
|
||||
|
@ -375,7 +374,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert)) {
|
||||
if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) {
|
||||
tc.valid(tc.cert)
|
||||
}
|
||||
})
|
||||
|
@ -476,7 +475,7 @@ func Test_sshDefaultExtensionModifier_Modify(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tc := run()
|
||||
if err := tc.modifier.Modify(tc.cert); err != nil {
|
||||
if err := tc.modifier.Modify(tc.cert, SignSSHOptions{}); err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
assert.HasPrefix(t, err.Error(), tc.err.Error())
|
||||
}
|
||||
|
@ -490,7 +489,7 @@ func Test_sshDefaultExtensionModifier_Modify(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_sshCertDefaultValidator_Valid(t *testing.T) {
|
||||
pub, _, err := keys.GenerateDefaultKeyPair()
|
||||
pub, _, err := keyutil.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
sshPub, err := ssh.NewPublicKey(pub)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -526,11 +525,6 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
|
|||
&ssh.Certificate{Nonce: []byte("foo"), Key: sshPub, Serial: 1, CertType: 1},
|
||||
errors.New("ssh certificate key id cannot be empty"),
|
||||
},
|
||||
{
|
||||
"fail/empty-valid-principals",
|
||||
&ssh.Certificate{Nonce: []byte("foo"), Key: sshPub, Serial: 1, CertType: 1, KeyId: "foo"},
|
||||
errors.New("ssh certificate valid principals cannot be empty"),
|
||||
},
|
||||
{
|
||||
"fail/zero-validAfter",
|
||||
&ssh.Certificate{
|
||||
|
@ -572,20 +566,6 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
|
|||
},
|
||||
errors.New("ssh certificate validBefore cannot be before validAfter"),
|
||||
},
|
||||
{
|
||||
"fail/empty-extensions",
|
||||
&ssh.Certificate{
|
||||
Nonce: []byte("foo"),
|
||||
Key: sshPub,
|
||||
Serial: 1,
|
||||
CertType: 1,
|
||||
KeyId: "foo",
|
||||
ValidPrincipals: []string{"foo"},
|
||||
ValidAfter: uint64(time.Now().Unix()),
|
||||
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
||||
},
|
||||
errors.New("ssh certificate extensions cannot be empty"),
|
||||
},
|
||||
{
|
||||
"fail/nil-signature-key",
|
||||
&ssh.Certificate{
|
||||
|
@ -656,6 +636,38 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
|
|||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ok/emptyPrincipals",
|
||||
&ssh.Certificate{
|
||||
Nonce: []byte("foo"),
|
||||
Key: sshPub,
|
||||
Serial: 1,
|
||||
CertType: 1,
|
||||
KeyId: "foo",
|
||||
ValidPrincipals: []string{},
|
||||
ValidAfter: uint64(time.Now().Unix()),
|
||||
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
||||
SignatureKey: sshPub,
|
||||
Signature: &ssh.Signature{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ok/empty-extensions",
|
||||
&ssh.Certificate{
|
||||
Nonce: []byte("foo"),
|
||||
Key: sshPub,
|
||||
Serial: 1,
|
||||
CertType: 1,
|
||||
KeyId: "foo",
|
||||
ValidPrincipals: []string{},
|
||||
ValidAfter: uint64(time.Now().Unix()),
|
||||
ValidBefore: uint64(time.Now().Add(10 * time.Minute).Unix()),
|
||||
SignatureKey: sshPub,
|
||||
Signature: &ssh.Signature{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -908,7 +920,7 @@ func Test_sshValidityModifier(t *testing.T) {
|
|||
for name, run := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tt := run()
|
||||
if err := tt.svm.Option(SignSSHOptions{}).Modify(tt.cert); err != nil {
|
||||
if err := tt.svm.Modify(tt.cert, SignSSHOptions{}); err != nil {
|
||||
if assert.NotNil(t, tt.err) {
|
||||
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
||||
}
|
||||
|
@ -921,28 +933,6 @@ func Test_sshValidityModifier(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_sshModifierFunc_Modify(t *testing.T) {
|
||||
type args struct {
|
||||
cert *ssh.Certificate
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
f sshModifierFunc
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", func(cert *ssh.Certificate) error { return nil }, args{&ssh.Certificate{}}, false},
|
||||
{"fail", func(cert *ssh.Certificate) error { return fmt.Errorf("an error") }, args{&ssh.Certificate{}}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.f.Modify(tt.args.cert); (err != nil) != tt.wantErr {
|
||||
t.Errorf("sshModifierFunc.Modify() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sshDefaultDuration_Option(t *testing.T) {
|
||||
tm, fn := mockNow()
|
||||
defer fn()
|
||||
|
@ -998,8 +988,7 @@ func Test_sshDefaultDuration_Option(t *testing.T) {
|
|||
m := &sshDefaultDuration{
|
||||
Claimer: tt.fields.Claimer,
|
||||
}
|
||||
v := m.Option(tt.args.o)
|
||||
if err := v.Modify(tt.args.cert); (err != nil) != tt.wantErr {
|
||||
if err := m.Modify(tt.args.cert, tt.args.o); (err != nil) != tt.wantErr {
|
||||
t.Errorf("sshDefaultDuration.Option() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.args.cert, tt.want) {
|
||||
|
@ -1008,32 +997,3 @@ func Test_sshDefaultDuration_Option(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sshLimitDuration_Option(t *testing.T) {
|
||||
type fields struct {
|
||||
Claimer *Claimer
|
||||
NotAfter time.Time
|
||||
}
|
||||
type args struct {
|
||||
o SignSSHOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want SSHCertModifier
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &sshLimitDuration{
|
||||
Claimer: tt.fields.Claimer,
|
||||
NotAfter: tt.fields.NotAfter,
|
||||
}
|
||||
if got := m.Option(tt.args.o); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("sshLimitDuration.Option() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
108
authority/provisioner/ssh_options.go
Normal file
108
authority/provisioner/ssh_options.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package provisioner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
)
|
||||
|
||||
// SSHCertificateOptions is an interface that returns a list of options passed when
|
||||
// creating a new certificate.
|
||||
type SSHCertificateOptions interface {
|
||||
Options(SignSSHOptions) []sshutil.Option
|
||||
}
|
||||
|
||||
type sshCertificateOptionsFunc func(SignSSHOptions) []sshutil.Option
|
||||
|
||||
func (fn sshCertificateOptionsFunc) Options(so SignSSHOptions) []sshutil.Option {
|
||||
return fn(so)
|
||||
}
|
||||
|
||||
// SSHOptions are a collection of custom options that can be added to each
|
||||
// provisioner.
|
||||
type SSHOptions struct {
|
||||
// Template contains an SSH certificate template. It can be a JSON template
|
||||
// escaped in a string or it can be also encoded in base64.
|
||||
Template string `json:"template,omitempty"`
|
||||
|
||||
// TemplateFile points to a file containing a SSH certificate template.
|
||||
TemplateFile string `json:"templateFile,omitempty"`
|
||||
|
||||
// TemplateData is a JSON object with variables that can be used in custom
|
||||
// templates.
|
||||
TemplateData json.RawMessage `json:"templateData,omitempty"`
|
||||
}
|
||||
|
||||
// HasTemplate returns true if a template is defined in the provisioner options.
|
||||
func (o *SSHOptions) HasTemplate() bool {
|
||||
return o != nil && (o.Template != "" || o.TemplateFile != "")
|
||||
}
|
||||
|
||||
// SSHTemplateOptions generates a SSHCertificateOptions with the template and
|
||||
// data defined in the ProvisionerOptions, the provisioner generated data, and
|
||||
// the user data provided in the request. If no template has been provided,
|
||||
// x509util.DefaultLeafTemplate will be used.
|
||||
func TemplateSSHOptions(o *Options, data sshutil.TemplateData) (SSHCertificateOptions, error) {
|
||||
return CustomSSHTemplateOptions(o, data, sshutil.DefaultTemplate)
|
||||
}
|
||||
|
||||
// CustomTemplateOptions generates a CertificateOptions with the template, data
|
||||
// defined in the ProvisionerOptions, the provisioner generated data and the
|
||||
// user data provided in the request. If no template has been provided in the
|
||||
// ProvisionerOptions, the given template will be used.
|
||||
func CustomSSHTemplateOptions(o *Options, data sshutil.TemplateData, defaultTemplate string) (SSHCertificateOptions, error) {
|
||||
opts := o.GetSSHOptions()
|
||||
if data == nil {
|
||||
data = sshutil.NewTemplateData()
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
// Add template data if any.
|
||||
if len(opts.TemplateData) > 0 {
|
||||
if err := json.Unmarshal(opts.TemplateData, &data); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling template data")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sshCertificateOptionsFunc(func(so SignSSHOptions) []sshutil.Option {
|
||||
// We're not provided user data without custom templates.
|
||||
if !opts.HasTemplate() {
|
||||
return []sshutil.Option{
|
||||
sshutil.WithTemplate(defaultTemplate, data),
|
||||
}
|
||||
}
|
||||
|
||||
// Add user provided data.
|
||||
if len(so.TemplateData) > 0 {
|
||||
userObject := make(map[string]interface{})
|
||||
if err := json.Unmarshal(so.TemplateData, &userObject); err != nil {
|
||||
data.SetUserData(map[string]interface{}{})
|
||||
} else {
|
||||
data.SetUserData(userObject)
|
||||
}
|
||||
}
|
||||
|
||||
// Load a template from a file if Template is not defined.
|
||||
if opts.Template == "" && opts.TemplateFile != "" {
|
||||
return []sshutil.Option{
|
||||
sshutil.WithTemplateFile(opts.TemplateFile, data),
|
||||
}
|
||||
}
|
||||
|
||||
// Load a template from the Template fields
|
||||
// 1. As a JSON in a string.
|
||||
template := strings.TrimSpace(opts.Template)
|
||||
if strings.HasPrefix(template, "{") {
|
||||
return []sshutil.Option{
|
||||
sshutil.WithTemplate(template, data),
|
||||
}
|
||||
}
|
||||
// 2. As a base64 encoded JSON.
|
||||
return []sshutil.Option{
|
||||
sshutil.WithTemplateBase64(template, data),
|
||||
}
|
||||
}), nil
|
||||
}
|
|
@ -2,11 +2,13 @@ package provisioner
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -46,16 +48,17 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
|
|||
}
|
||||
|
||||
var mods []SSHCertModifier
|
||||
var certOptions []sshutil.Option
|
||||
var validators []SSHCertValidator
|
||||
|
||||
for _, op := range signOpts {
|
||||
switch o := op.(type) {
|
||||
// add options to NewCertificate
|
||||
case SSHCertificateOptions:
|
||||
certOptions = append(certOptions, o.Options(opts)...)
|
||||
// modify the ssh.Certificate
|
||||
case SSHCertModifier:
|
||||
mods = append(mods, o)
|
||||
// modify the ssh.Certificate given the SSHOptions
|
||||
case SSHCertOptionModifier:
|
||||
mods = append(mods, o.Option(opts))
|
||||
// validate the ssh.Certificate
|
||||
case SSHCertValidator:
|
||||
validators = append(validators, o)
|
||||
|
@ -69,22 +72,39 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
|
|||
}
|
||||
}
|
||||
|
||||
// Build base certificate with the key and some random values
|
||||
cert := &ssh.Certificate{
|
||||
Nonce: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
|
||||
// Simulated certificate request with request options.
|
||||
cr := sshutil.CertificateRequest{
|
||||
Type: opts.CertType,
|
||||
KeyID: opts.KeyID,
|
||||
Principals: opts.Principals,
|
||||
Key: pub,
|
||||
Serial: 1234567890,
|
||||
}
|
||||
|
||||
// Use opts to modify the certificate
|
||||
if err := opts.Modify(cert); err != nil {
|
||||
return nil, err
|
||||
// Create certificate from template.
|
||||
certificate, err := sshutil.NewCertificate(cr, certOptions...)
|
||||
if err != nil {
|
||||
if _, ok := err.(*sshutil.TemplateError); ok {
|
||||
return nil, errs.NewErr(http.StatusBadRequest, err,
|
||||
errs.WithMessage(err.Error()),
|
||||
errs.WithKeyVal("signOptions", signOpts),
|
||||
)
|
||||
}
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
// Use provisioner modifiers
|
||||
// Get actual *ssh.Certificate and continue with provisioner modifiers.
|
||||
cert := certificate.GetCertificate()
|
||||
|
||||
// Use SignSSHOptions to modify the certificate validity. It will be later
|
||||
// checked or set if not defined.
|
||||
if err := opts.ModifyValidity(cert); err != nil {
|
||||
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
// Use provisioner modifiers.
|
||||
for _, m := range mods {
|
||||
if err := m.Modify(cert); err != nil {
|
||||
return nil, err
|
||||
if err := m.Modify(cert, opts); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,23 +121,17 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert.SignatureKey = signer.PublicKey()
|
||||
|
||||
// Get bytes for signing trailing the signature length.
|
||||
data := cert.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
// Sign the certificate
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
// Sign certificate.
|
||||
cert, err = sshutil.CreateCertificate(cert, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
|
||||
}
|
||||
cert.Signature = sig
|
||||
|
||||
// User provisioners validators
|
||||
// User provisioners validators.
|
||||
for _, v := range validators {
|
||||
if err := v.Valid(cert, opts); err != nil {
|
||||
return nil, err
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
|
@ -247,16 +248,41 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
|||
signOptions := []SignOption{
|
||||
// validates user's SSHOptions with the ones in the token
|
||||
sshCertOptionsValidator(*opts),
|
||||
// validate users's KeyID is the token subject.
|
||||
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
|
||||
}
|
||||
|
||||
// Add modifiers from custom claims
|
||||
// FIXME: this is also set in the sign method using SSHOptions.Modify.
|
||||
// Default template attributes.
|
||||
certType := sshutil.UserCert
|
||||
keyID := claims.Subject
|
||||
principals := []string{claims.Subject}
|
||||
|
||||
// Use options in the token.
|
||||
if opts.CertType != "" {
|
||||
signOptions = append(signOptions, sshCertTypeModifier(opts.CertType))
|
||||
if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil {
|
||||
return nil, errs.Wrap(http.StatusBadRequest, err, "x5c.AuthorizeSSHSign")
|
||||
}
|
||||
}
|
||||
if opts.KeyID != "" {
|
||||
keyID = opts.KeyID
|
||||
}
|
||||
if len(opts.Principals) > 0 {
|
||||
signOptions = append(signOptions, sshCertPrincipalsModifier(opts.Principals))
|
||||
principals = opts.Principals
|
||||
}
|
||||
|
||||
// Certificate templates.
|
||||
data := sshutil.CreateTemplateData(certType, keyID, principals)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
templateOptions, err := TemplateSSHOptions(p.Options, data)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeSSHSign")
|
||||
}
|
||||
signOptions = append(signOptions, templateOptions)
|
||||
|
||||
// Add modifiers from custom claims
|
||||
t := now()
|
||||
if !opts.ValidAfter.IsZero() {
|
||||
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
|
||||
|
@ -264,21 +290,10 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
|
|||
if !opts.ValidBefore.IsZero() {
|
||||
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
|
||||
}
|
||||
// Make sure to define the the KeyID
|
||||
if opts.KeyID == "" {
|
||||
signOptions = append(signOptions, sshCertKeyIDModifier(claims.Subject))
|
||||
}
|
||||
|
||||
// Default to a user certificate with no principals if not set
|
||||
signOptions = append(signOptions, sshCertDefaultsModifier{CertType: SSHUserCert})
|
||||
|
||||
return append(signOptions,
|
||||
// Set the default extensions.
|
||||
&sshDefaultExtensionModifier{},
|
||||
// Checks the validity bounds, and set the validity if has not been set.
|
||||
&sshLimitDuration{p.claimer, claims.chains[0][0].NotAfter},
|
||||
// set the key id to the token subject
|
||||
sshCertKeyIDValidator(claims.Subject),
|
||||
// Validate public key.
|
||||
&sshDefaultPublicKeyValidator{},
|
||||
// Validate the validity period.
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
func TestX5C_Getters(t *testing.T) {
|
||||
|
@ -154,7 +154,7 @@ M46l92gdOozT
|
|||
func TestX5C_authorizeToken(t *testing.T) {
|
||||
x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
x5cJWK, err := jose.ParseKey("./testdata/secrets/x5c-leaf.key")
|
||||
x5cJWK, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type test struct {
|
||||
|
@ -402,7 +402,7 @@ lgsqsR63is+0YQ==
|
|||
func TestX5C_AuthorizeSign(t *testing.T) {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/secrets/x5c-leaf.key")
|
||||
jwk, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type test struct {
|
||||
|
@ -518,7 +518,7 @@ func TestX5C_AuthorizeRevoke(t *testing.T) {
|
|||
"ok": func(t *testing.T) test {
|
||||
certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
jwk, err := jose.ParseKey("./testdata/secrets/x5c-leaf.key")
|
||||
jwk, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
p, err := generateX5C(nil)
|
||||
|
@ -599,7 +599,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) {
|
|||
func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
||||
x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
|
||||
assert.FatalError(t, err)
|
||||
x5cJWK, err := jose.ParseKey("./testdata/secrets/x5c-leaf.key")
|
||||
x5cJWK, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
_, fn := mockNow()
|
||||
|
@ -698,6 +698,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
|||
},
|
||||
Step: &stepPayload{SSH: &SignSSHOptions{
|
||||
CertType: SSHHostCert,
|
||||
KeyID: "foo",
|
||||
Principals: []string{"max", "mariano", "alan"},
|
||||
ValidAfter: TimeDuration{d: 5 * time.Minute},
|
||||
ValidBefore: TimeDuration{d: 10 * time.Minute},
|
||||
|
@ -753,19 +754,19 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
|||
if assert.Nil(t, tc.err) {
|
||||
if assert.NotNil(t, opts) {
|
||||
tot := 0
|
||||
firstValidator := true
|
||||
nw := now()
|
||||
for _, o := range opts {
|
||||
switch v := o.(type) {
|
||||
case sshCertOptionsValidator:
|
||||
tc.claims.Step.SSH.ValidAfter.t = time.Time{}
|
||||
tc.claims.Step.SSH.ValidBefore.t = time.Time{}
|
||||
if firstValidator {
|
||||
assert.Equals(t, SignSSHOptions(v), *tc.claims.Step.SSH)
|
||||
case sshCertKeyIDModifier:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case sshCertTypeModifier:
|
||||
assert.Equals(t, string(v), tc.claims.Step.SSH.CertType)
|
||||
case sshCertPrincipalsModifier:
|
||||
assert.Equals(t, []string(v), tc.claims.Step.SSH.Principals)
|
||||
} else {
|
||||
assert.Equals(t, SignSSHOptions(v), SignSSHOptions{KeyID: tc.claims.Subject})
|
||||
}
|
||||
firstValidator = false
|
||||
case sshCertValidAfterModifier:
|
||||
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix())
|
||||
case sshCertValidBeforeModifier:
|
||||
|
@ -777,19 +778,16 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
|||
assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter)
|
||||
case *sshCertValidityValidator:
|
||||
assert.Equals(t, v.Claimer, tc.p.claimer)
|
||||
case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator,
|
||||
*sshCertDefaultValidator:
|
||||
case sshCertKeyIDValidator:
|
||||
assert.Equals(t, string(v), "foo")
|
||||
case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc:
|
||||
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, 13)
|
||||
} else {
|
||||
assert.Equals(t, tot, 9)
|
||||
} else {
|
||||
assert.Equals(t, tot, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestRoot(t *testing.T) {
|
||||
|
|
189
authority/ssh.go
189
authority/ssh.go
|
@ -13,10 +13,10 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -51,6 +51,21 @@ type Bastion struct {
|
|||
Flags string `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// HostTag are tagged with k,v pairs. These tags are how a user is ultimately
|
||||
// associated with a host.
|
||||
type HostTag struct {
|
||||
ID string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Host defines expected attributes for an ssh host.
|
||||
type Host struct {
|
||||
HostID string `json:"hid"`
|
||||
HostTags []HostTag `json:"host_tags"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// Validate checks the fields in SSHConfig.
|
||||
func (c *SSHConfig) Validate() error {
|
||||
if c == nil {
|
||||
|
@ -205,100 +220,113 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname str
|
|||
|
||||
// SignSSH creates a signed SSH certificate with the given public key and options.
|
||||
func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
|
||||
var mods []provisioner.SSHCertModifier
|
||||
var validators []provisioner.SSHCertValidator
|
||||
var (
|
||||
certOptions []sshutil.Option
|
||||
mods []provisioner.SSHCertModifier
|
||||
validators []provisioner.SSHCertValidator
|
||||
)
|
||||
|
||||
// Validate given options.
|
||||
if err := opts.Validate(); err != nil {
|
||||
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
// Set backdate with the configured value
|
||||
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
|
||||
|
||||
for _, op := range signOpts {
|
||||
switch o := op.(type) {
|
||||
// add options to NewCertificate
|
||||
case provisioner.SSHCertificateOptions:
|
||||
certOptions = append(certOptions, o.Options(opts)...)
|
||||
|
||||
// modify the ssh.Certificate
|
||||
case provisioner.SSHCertModifier:
|
||||
mods = append(mods, o)
|
||||
// modify the ssh.Certificate given the SSHOptions
|
||||
case provisioner.SSHCertOptionModifier:
|
||||
mods = append(mods, o.Option(opts))
|
||||
|
||||
// validate the ssh.Certificate
|
||||
case provisioner.SSHCertValidator:
|
||||
validators = append(validators, o)
|
||||
|
||||
// validate the given SSHOptions
|
||||
case provisioner.SSHCertOptionsValidator:
|
||||
if err := o.Valid(opts); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errs.InternalServer("signSSH: invalid extra option type %T", o)
|
||||
return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
|
||||
}
|
||||
}
|
||||
|
||||
nonce, err := randutil.ASCII(32)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH")
|
||||
}
|
||||
|
||||
var serial uint64
|
||||
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error reading random number")
|
||||
}
|
||||
|
||||
// Build base certificate with the key and some random values
|
||||
cert := &ssh.Certificate{
|
||||
Nonce: []byte(nonce),
|
||||
// Simulated certificate request with request options.
|
||||
cr := sshutil.CertificateRequest{
|
||||
Type: opts.CertType,
|
||||
KeyID: opts.KeyID,
|
||||
Principals: opts.Principals,
|
||||
Key: key,
|
||||
Serial: serial,
|
||||
}
|
||||
|
||||
// Use opts to modify the certificate
|
||||
if err := opts.Modify(cert); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
|
||||
// Create certificate from template.
|
||||
certificate, err := sshutil.NewCertificate(cr, certOptions...)
|
||||
if err != nil {
|
||||
if _, ok := err.(*sshutil.TemplateError); ok {
|
||||
return nil, errs.NewErr(http.StatusBadRequest, err,
|
||||
errs.WithMessage(err.Error()),
|
||||
errs.WithKeyVal("signOptions", signOpts),
|
||||
)
|
||||
}
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
// Use provisioner modifiers
|
||||
// Get actual *ssh.Certificate and continue with provisioner modifiers.
|
||||
certTpl := certificate.GetCertificate()
|
||||
|
||||
// Use SignSSHOptions to modify the certificate validity. It will be later
|
||||
// checked or set if not defined.
|
||||
if err := opts.ModifyValidity(certTpl); err != nil {
|
||||
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
|
||||
}
|
||||
|
||||
// Use provisioner modifiers.
|
||||
for _, m := range mods {
|
||||
if err := m.Modify(cert); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
|
||||
if err := m.Modify(certTpl, opts); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
|
||||
}
|
||||
}
|
||||
|
||||
// Get signer from authority keys
|
||||
var signer ssh.Signer
|
||||
switch cert.CertType {
|
||||
switch certTpl.CertType {
|
||||
case ssh.UserCert:
|
||||
if a.sshCAUserCertSignKey == nil {
|
||||
return nil, errs.NotImplemented("signSSH: user certificate signing is not enabled")
|
||||
return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
|
||||
}
|
||||
signer = a.sshCAUserCertSignKey
|
||||
case ssh.HostCert:
|
||||
if a.sshCAHostCertSignKey == nil {
|
||||
return nil, errs.NotImplemented("signSSH: host certificate signing is not enabled")
|
||||
return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
|
||||
}
|
||||
signer = a.sshCAHostCertSignKey
|
||||
default:
|
||||
return nil, errs.InternalServer("signSSH: unexpected ssh certificate type: %d", cert.CertType)
|
||||
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
|
||||
}
|
||||
cert.SignatureKey = signer.PublicKey()
|
||||
|
||||
// Get bytes for signing trailing the signature length.
|
||||
data := cert.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
// Sign the certificate
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
// Sign certificate.
|
||||
cert, err := sshutil.CreateCertificate(certTpl, signer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
|
||||
}
|
||||
cert.Signature = sig
|
||||
|
||||
// User provisioners validators
|
||||
// User provisioners validators.
|
||||
for _, v := range validators {
|
||||
if err := v.Valid(cert, opts); err != nil {
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
|
||||
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
|
||||
}
|
||||
}
|
||||
|
||||
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error storing certificate in db")
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
|
@ -306,16 +334,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
|
|||
|
||||
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
|
||||
func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) {
|
||||
nonce, err := randutil.ASCII(32)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH")
|
||||
}
|
||||
|
||||
var serial uint64
|
||||
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error reading random number")
|
||||
}
|
||||
|
||||
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
|
||||
return nil, errs.BadRequest("rewnewSSH: cannot renew certificate without validity period")
|
||||
}
|
||||
|
@ -326,22 +344,22 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
|
|||
va := now.Add(-1 * backdate)
|
||||
vb := now.Add(duration - backdate)
|
||||
|
||||
// Build base certificate with the key and some random values
|
||||
cert := &ssh.Certificate{
|
||||
Nonce: []byte(nonce),
|
||||
// Build base certificate with the old key.
|
||||
// Nonce and serial will be automatically generated on signing.
|
||||
certTpl := &ssh.Certificate{
|
||||
Key: oldCert.Key,
|
||||
Serial: serial,
|
||||
CertType: oldCert.CertType,
|
||||
KeyId: oldCert.KeyId,
|
||||
ValidPrincipals: oldCert.ValidPrincipals,
|
||||
Permissions: oldCert.Permissions,
|
||||
Reserved: oldCert.Reserved,
|
||||
ValidAfter: uint64(va.Unix()),
|
||||
ValidBefore: uint64(vb.Unix()),
|
||||
}
|
||||
|
||||
// Get signer from authority keys
|
||||
var signer ssh.Signer
|
||||
switch cert.CertType {
|
||||
switch certTpl.CertType {
|
||||
case ssh.UserCert:
|
||||
if a.sshCAUserCertSignKey == nil {
|
||||
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
|
||||
|
@ -353,20 +371,14 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
|
|||
}
|
||||
signer = a.sshCAHostCertSignKey
|
||||
default:
|
||||
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", cert.CertType)
|
||||
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
|
||||
}
|
||||
cert.SignatureKey = signer.PublicKey()
|
||||
|
||||
// Get bytes for signing trailing the signature length.
|
||||
data := cert.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
// Sign the certificate
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
// Sign certificate.
|
||||
cert, err := sshutil.CreateCertificate(certTpl, signer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error signing certificate")
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
|
||||
}
|
||||
cert.Signature = sig
|
||||
|
||||
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
|
||||
|
@ -389,16 +401,6 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
|
|||
}
|
||||
}
|
||||
|
||||
nonce, err := randutil.ASCII(32)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH")
|
||||
}
|
||||
|
||||
var serial uint64
|
||||
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error reading random number")
|
||||
}
|
||||
|
||||
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
|
||||
return nil, errs.BadRequest("rekeySSH; cannot rekey certificate without validity period")
|
||||
}
|
||||
|
@ -409,15 +411,15 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
|
|||
va := now.Add(-1 * backdate)
|
||||
vb := now.Add(duration - backdate)
|
||||
|
||||
// Build base certificate with the key and some random values
|
||||
// Build base certificate with the new key.
|
||||
// Nonce and serial will be automatically generated on signing.
|
||||
cert := &ssh.Certificate{
|
||||
Nonce: []byte(nonce),
|
||||
Key: pub,
|
||||
Serial: serial,
|
||||
CertType: oldCert.CertType,
|
||||
KeyId: oldCert.KeyId,
|
||||
ValidPrincipals: oldCert.ValidPrincipals,
|
||||
Permissions: oldCert.Permissions,
|
||||
Reserved: oldCert.Reserved,
|
||||
ValidAfter: uint64(va.Unix()),
|
||||
ValidBefore: uint64(vb.Unix()),
|
||||
}
|
||||
|
@ -438,18 +440,13 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
|
|||
default:
|
||||
return nil, errs.BadRequest("rekeySSH; unexpected ssh certificate type: %d", cert.CertType)
|
||||
}
|
||||
cert.SignatureKey = signer.PublicKey()
|
||||
|
||||
// Get bytes for signing trailing the signature length.
|
||||
data := cert.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
// Sign the certificate.
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
var err error
|
||||
// Sign certificate.
|
||||
cert, err = sshutil.CreateCertificate(cert, signer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error signing certificate")
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
|
||||
}
|
||||
cert.Signature = sig
|
||||
|
||||
// Apply validators from provisioner.
|
||||
for _, v := range validators {
|
||||
|
@ -571,7 +568,7 @@ func (a *Authority) CheckSSHHost(ctx context.Context, principal string, token st
|
|||
}
|
||||
|
||||
// GetSSHHosts returns a list of valid host principals.
|
||||
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
|
||||
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
|
||||
if a.sshGetHostsFunc != nil {
|
||||
hosts, err := a.sshGetHostsFunc(ctx, cert)
|
||||
return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
|
||||
|
@ -581,9 +578,9 @@ func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
|
||||
}
|
||||
|
||||
hosts := make([]sshutil.Host, len(hostnames))
|
||||
hosts := make([]Host, len(hostnames))
|
||||
for i, hn := range hostnames {
|
||||
hosts[i] = sshutil.Host{Hostname: hn}
|
||||
hosts[i] = Host{Hostname: hn}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
|
|
@ -18,15 +18,15 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/sshutil"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type sshTestModifier ssh.Certificate
|
||||
|
||||
func (m sshTestModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshTestModifier) Modify(cert *ssh.Certificate, _ provisioner.SignSSHOptions) error {
|
||||
if m.CertType != 0 {
|
||||
cert.CertType = m.CertType
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func (m sshTestModifier) Modify(cert *ssh.Certificate) error {
|
|||
|
||||
type sshTestCertModifier string
|
||||
|
||||
func (m sshTestCertModifier) Modify(cert *ssh.Certificate) error {
|
||||
func (m sshTestCertModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
|
||||
if m == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -80,8 +80,11 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
|
|||
|
||||
type sshTestOptionsModifier string
|
||||
|
||||
func (m sshTestOptionsModifier) Option(opts provisioner.SignSSHOptions) provisioner.SSHCertModifier {
|
||||
return sshTestCertModifier(string(m))
|
||||
func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
|
||||
if m == "" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(string(m))
|
||||
}
|
||||
|
||||
func TestAuthority_SignSSH(t *testing.T) {
|
||||
|
@ -101,6 +104,29 @@ func TestAuthority_SignSSH(t *testing.T) {
|
|||
CertType: ssh.HostCert,
|
||||
}
|
||||
|
||||
userTemplate, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", nil))
|
||||
assert.FatalError(t, err)
|
||||
hostTemplate, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.HostCert, "key-id", nil))
|
||||
assert.FatalError(t, err)
|
||||
userTemplateWithUser, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
|
||||
assert.FatalError(t, err)
|
||||
hostTemplateWithHosts, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.HostCert, "key-id", []string{"foo.test.com", "bar.test.com"}))
|
||||
assert.FatalError(t, err)
|
||||
userCustomTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{
|
||||
SSH: &provisioner.SSHOptions{Template: `{
|
||||
"type": "{{ .Type }}",
|
||||
"keyId": "{{ .KeyID }}",
|
||||
"principals": {{ append .Principals "admin" | toJson }},
|
||||
"extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }},
|
||||
"criticalOptions": {{ toJson .CriticalOptions }}
|
||||
}`},
|
||||
}, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
|
||||
assert.FatalError(t, err)
|
||||
userFailTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{
|
||||
SSH: &provisioner.SSHOptions{Template: `{{ fail "an error"}}`},
|
||||
}, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
type fields struct {
|
||||
|
@ -125,27 +151,29 @@ func TestAuthority_SignSSH(t *testing.T) {
|
|||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostOptions}}, want{CertType: ssh.HostCert}, false},
|
||||
{"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert}, false},
|
||||
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false},
|
||||
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false},
|
||||
{"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false},
|
||||
{"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false},
|
||||
{"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true},
|
||||
{"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, want{}, true},
|
||||
{"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("an error")}}, want{}, true},
|
||||
{"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("an error")}}, want{}, true},
|
||||
{"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("an error")}}, want{}, true},
|
||||
{"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, want{}, true},
|
||||
{"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{}, true},
|
||||
{"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{}, true},
|
||||
{"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true},
|
||||
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostTemplate, hostOptions}}, want{CertType: ssh.HostCert}, false},
|
||||
{"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert}, false},
|
||||
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false},
|
||||
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{hostTemplateWithHosts}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false},
|
||||
{"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false},
|
||||
{"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false},
|
||||
{"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false},
|
||||
{"ok-custom-template", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userCustomTemplate, userOptions}}, want{CertType: ssh.UserCert, Principals: []string{"user", "admin"}}, false},
|
||||
{"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{userTemplate}}, want{}, true},
|
||||
{"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertValidator("an error")}}, want{}, true},
|
||||
{"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertModifier("an error")}}, want{}, true},
|
||||
{"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("an error")}}, want{}, true},
|
||||
{"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("an error")}}, want{}, true},
|
||||
{"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, "wrong type"}}, want{}, true},
|
||||
{"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{}, true},
|
||||
{"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{}, true},
|
||||
{"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, sshTestModifier{CertType: 100}}}, want{}, true},
|
||||
{"fail-custom-template", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userFailTemplate, userOptions}}, want{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -678,17 +706,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
|
|||
a := testAuthority(t)
|
||||
|
||||
type test struct {
|
||||
getHostsFunc func(context.Context, *x509.Certificate) ([]sshutil.Host, error)
|
||||
getHostsFunc func(context.Context, *x509.Certificate) ([]Host, error)
|
||||
auth *Authority
|
||||
cert *x509.Certificate
|
||||
cmp func(got []sshutil.Host)
|
||||
cmp func(got []Host)
|
||||
err error
|
||||
code int
|
||||
}
|
||||
tests := map[string]func(t *testing.T) *test{
|
||||
"fail/getHostsFunc-fail": func(t *testing.T) *test {
|
||||
return &test{
|
||||
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
|
||||
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
|
||||
return nil, errors.New("force")
|
||||
},
|
||||
cert: &x509.Certificate{},
|
||||
|
@ -697,17 +725,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"ok/getHostsFunc-defined": func(t *testing.T) *test {
|
||||
hosts := []sshutil.Host{
|
||||
hosts := []Host{
|
||||
{HostID: "1", Hostname: "foo"},
|
||||
{HostID: "2", Hostname: "bar"},
|
||||
}
|
||||
|
||||
return &test{
|
||||
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
|
||||
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
|
||||
return hosts, nil
|
||||
},
|
||||
cert: &x509.Certificate{},
|
||||
cmp: func(got []sshutil.Host) {
|
||||
cmp: func(got []Host) {
|
||||
assert.Equals(t, got, hosts)
|
||||
},
|
||||
}
|
||||
|
@ -732,8 +760,8 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
|
|||
},
|
||||
})),
|
||||
cert: &x509.Certificate{},
|
||||
cmp: func(got []sshutil.Host) {
|
||||
assert.Equals(t, got, []sshutil.Host{
|
||||
cmp: func(got []Host) {
|
||||
assert.Equals(t, got, []Host{
|
||||
{Hostname: "foo"},
|
||||
{Hostname: "bar"},
|
||||
})
|
||||
|
|
|
@ -8,31 +8,28 @@ import (
|
|||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
x509legacy "github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
// GetTLSOptions returns the tls options configured.
|
||||
func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions {
|
||||
func (a *Authority) GetTLSOptions() *TLSOptions {
|
||||
return a.config.TLS
|
||||
}
|
||||
|
||||
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
|
||||
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
|
||||
|
||||
func withDefaultASN1DN(def *x509legacy.ASN1DN) provisioner.CertificateModifierFunc {
|
||||
func withDefaultASN1DN(def *ASN1DN) provisioner.CertificateModifierFunc {
|
||||
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
|
||||
if def == nil {
|
||||
return errors.New("default ASN1DN template cannot be nil")
|
||||
|
@ -246,21 +243,10 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
|
|||
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
|
||||
}
|
||||
|
||||
leaf, err := x509legacy.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer)
|
||||
serverCert, err := x509util.CreateCertificate(newCert, a.x509Issuer, newCert.PublicKey, a.x509Signer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...)
|
||||
}
|
||||
crtBytes, err := leaf.CreateCertificate()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.Rekey; error renewing certificate from existing server certificate", opts...)
|
||||
}
|
||||
|
||||
serverCert, err := x509.ParseCertificate(crtBytes)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.Rekey; error parsing new server certificate", opts...)
|
||||
}
|
||||
|
||||
if err = a.db.StoreCertificate(serverCert); err != nil {
|
||||
if err != db.ErrNotImplemented {
|
||||
|
@ -295,7 +281,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
|||
errs.WithKeyVal("reason", revokeOpts.Reason),
|
||||
errs.WithKeyVal("passiveOnly", revokeOpts.PassiveOnly),
|
||||
errs.WithKeyVal("MTLS", revokeOpts.MTLS),
|
||||
errs.WithKeyVal("context", fmt.Sprint(provisioner.MethodFromContext(ctx))),
|
||||
errs.WithKeyVal("context", provisioner.MethodFromContext(ctx).String()),
|
||||
}
|
||||
if revokeOpts.MTLS {
|
||||
opts = append(opts, errs.WithKeyVal("certificate", base64.StdEncoding.EncodeToString(revokeOpts.Crt.Raw)))
|
||||
|
@ -372,48 +358,62 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
|||
|
||||
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
|
||||
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||
profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer,
|
||||
x509legacy.WithHosts(strings.Join(a.config.DNSNames, ",")))
|
||||
if err != nil {
|
||||
fatal := func(err error) (*tls.Certificate, error) {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
||||
}
|
||||
|
||||
crtBytes, err := profile.CreateCertificate()
|
||||
// Generate default key.
|
||||
priv, err := keyutil.GenerateDefaultKey()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
||||
return fatal(err)
|
||||
}
|
||||
signer, ok := priv.(crypto.Signer)
|
||||
if !ok {
|
||||
return fatal(errors.New("private key is not a crypto.Signer"))
|
||||
}
|
||||
|
||||
keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey())
|
||||
// Create initial certificate request.
|
||||
cr, err := x509util.CreateCertificateRequest("Step Online CA", a.config.DNSNames, signer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
||||
return fatal(err)
|
||||
}
|
||||
|
||||
// Generate certificate template directly from the certificate request.
|
||||
template, err := x509util.NewCertificate(cr)
|
||||
if err != nil {
|
||||
return fatal(err)
|
||||
}
|
||||
|
||||
// Get x509 certificate template, set validity and sign it.
|
||||
now := time.Now()
|
||||
certTpl := template.GetCertificate()
|
||||
certTpl.NotBefore = now.Add(-1 * time.Minute)
|
||||
certTpl.NotAfter = now.Add(24 * time.Hour)
|
||||
|
||||
cert, err := x509util.CreateCertificate(certTpl, a.x509Issuer, cr.PublicKey, a.x509Signer)
|
||||
if err != nil {
|
||||
return fatal(err)
|
||||
}
|
||||
|
||||
// Generate PEM blocks to create tls.Certificate
|
||||
crtPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: crtBytes,
|
||||
Bytes: cert.Raw,
|
||||
})
|
||||
|
||||
// Load the x509 key pair (combining server and intermediate blocks)
|
||||
// to a tls.Certificate.
|
||||
intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
|
||||
return fatal(err)
|
||||
}
|
||||
tlsCrt, err := tls.X509KeyPair(append(crtPEM,
|
||||
pem.EncodeToMemory(intermediatePEM)...),
|
||||
pem.EncodeToMemory(keyPEM))
|
||||
keyPEM, err := pemutil.Serialize(priv)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.GetTLSCertificate; error creating tls certificate")
|
||||
return fatal(err)
|
||||
}
|
||||
|
||||
// Get the 'leaf' certificate and set the attribute accordingly.
|
||||
leaf, err := x509.ParseCertificate(tlsCrt.Certificate[0])
|
||||
tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM))
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.GetTLSCertificate; error parsing tls certificate")
|
||||
return fatal(err)
|
||||
}
|
||||
tlsCrt.Leaf = leaf
|
||||
|
||||
// Set leaf certificate
|
||||
tlsCrt.Leaf = cert
|
||||
return &tlsCrt, nil
|
||||
}
|
||||
|
|
157
authority/tls_options.go
Normal file
157
authority/tls_options.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTLSMinVersion default minimum version of TLS.
|
||||
DefaultTLSMinVersion = TLSVersion(1.2)
|
||||
// DefaultTLSMaxVersion default maximum version of TLS.
|
||||
DefaultTLSMaxVersion = TLSVersion(1.3)
|
||||
// DefaultTLSRenegotiation default TLS connection renegotiation policy.
|
||||
DefaultTLSRenegotiation = false // Never regnegotiate.
|
||||
// DefaultTLSCipherSuites specifies default step ciphersuite(s).
|
||||
DefaultTLSCipherSuites = CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
}
|
||||
// ApprovedTLSCipherSuites smallstep approved ciphersuites.
|
||||
ApprovedTLSCipherSuites = CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
||||
}
|
||||
)
|
||||
|
||||
// TLSVersion represents a TLS version number.
|
||||
type TLSVersion float64
|
||||
|
||||
// Validate implements models.Validator and checks that a cipher suite is
|
||||
// valid.
|
||||
func (v TLSVersion) Validate() error {
|
||||
if _, ok := tlsVersions[v]; ok {
|
||||
return nil
|
||||
}
|
||||
return errors.Errorf("%f is not a valid tls version", v)
|
||||
}
|
||||
|
||||
// Value returns the Go constant for the TLSVersion.
|
||||
func (v TLSVersion) Value() uint16 {
|
||||
return tlsVersions[v]
|
||||
}
|
||||
|
||||
// String returns the Go constant for the TLSVersion.
|
||||
func (v TLSVersion) String() string {
|
||||
k := v.Value()
|
||||
switch k {
|
||||
case tls.VersionTLS10:
|
||||
return "1.0"
|
||||
case tls.VersionTLS11:
|
||||
return "1.1"
|
||||
case tls.VersionTLS12:
|
||||
return "1.2"
|
||||
case tls.VersionTLS13:
|
||||
return "1.3"
|
||||
default:
|
||||
return fmt.Sprintf("unexpected value: %f", v)
|
||||
}
|
||||
}
|
||||
|
||||
// tlsVersions has the list of supported tls version.
|
||||
var tlsVersions = map[TLSVersion]uint16{
|
||||
// Defaults to TLS 1.3
|
||||
0: tls.VersionTLS13,
|
||||
// Options
|
||||
1.0: tls.VersionTLS10,
|
||||
1.1: tls.VersionTLS11,
|
||||
1.2: tls.VersionTLS12,
|
||||
1.3: tls.VersionTLS13,
|
||||
}
|
||||
|
||||
// CipherSuites represents an array of string codes representing the cipher
|
||||
// suites.
|
||||
type CipherSuites []string
|
||||
|
||||
// Validate implements models.Validator and checks that a cipher suite is
|
||||
// valid.
|
||||
func (c CipherSuites) Validate() error {
|
||||
for _, s := range c {
|
||||
if _, ok := cipherSuites[s]; !ok {
|
||||
return errors.Errorf("%s is not a valid cipher suite", s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns an []uint16 for the cipher suites.
|
||||
func (c CipherSuites) Value() []uint16 {
|
||||
values := make([]uint16, len(c))
|
||||
for i, s := range c {
|
||||
values[i] = cipherSuites[s]
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// cipherSuites has the list of supported cipher suites.
|
||||
var cipherSuites = map[string]uint16{
|
||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
// TLSOptions represents the TLS options that can be specified on *tls.Config
|
||||
// types to configure HTTPS servers and clients.
|
||||
type TLSOptions struct {
|
||||
CipherSuites CipherSuites `json:"cipherSuites"`
|
||||
MinVersion TLSVersion `json:"minVersion"`
|
||||
MaxVersion TLSVersion `json:"maxVersion"`
|
||||
Renegotiation bool `json:"renegotiation"`
|
||||
}
|
||||
|
||||
// TLSConfig returns the tls.Config equivalent of the TLSOptions.
|
||||
func (t *TLSOptions) TLSConfig() *tls.Config {
|
||||
var rs tls.RenegotiationSupport
|
||||
if t.Renegotiation {
|
||||
rs = tls.RenegotiateFreelyAsClient
|
||||
} else {
|
||||
rs = tls.RenegotiateNever
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
CipherSuites: t.CipherSuites.Value(),
|
||||
MinVersion: t.MinVersion.Value(),
|
||||
MaxVersion: t.MaxVersion.Value(),
|
||||
Renegotiation: rs,
|
||||
}
|
||||
}
|
169
authority/tls_options_test.go
Normal file
169
authority/tls_options_test.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTLSVersion_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v TLSVersion
|
||||
wantErr bool
|
||||
}{
|
||||
{"default", TLSVersion(0), false},
|
||||
{"1.0", TLSVersion(1.0), false},
|
||||
{"1.1", TLSVersion(1.1), false},
|
||||
{"1.2", TLSVersion(1.2), false},
|
||||
{"1.3", TLSVersion(1.3), false},
|
||||
{"0.99", TLSVersion(0.99), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.v.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("TLSVersion.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSVersion_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v TLSVersion
|
||||
want string
|
||||
}{
|
||||
{"default", TLSVersion(0), "1.3"},
|
||||
{"1.0", TLSVersion(1.0), "1.0"},
|
||||
{"1.1", TLSVersion(1.1), "1.1"},
|
||||
{"1.2", TLSVersion(1.2), "1.2"},
|
||||
{"1.3", TLSVersion(1.3), "1.3"},
|
||||
{"0.99", TLSVersion(0.99), "unexpected value: 0.990000"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.v.String(); got != tt.want {
|
||||
t.Errorf("TLSVersion.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCipherSuites_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c CipherSuites
|
||||
wantErr bool
|
||||
}{
|
||||
{"TLS_RSA_WITH_RC4_128_SHA", CipherSuites{"TLS_RSA_WITH_RC4_128_SHA"}, false},
|
||||
{"TLS_RSA_WITH_3DES_EDE_CBC_SHA", CipherSuites{"TLS_RSA_WITH_3DES_EDE_CBC_SHA"}, false},
|
||||
{"TLS_RSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_RSA_WITH_AES_128_CBC_SHA"}, false},
|
||||
{"TLS_RSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_RSA_WITH_AES_256_CBC_SHA"}, false},
|
||||
{"TLS_RSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_RSA_WITH_AES_128_CBC_SHA256"}, false},
|
||||
{"TLS_RSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_RSA_WITH_AES_128_GCM_SHA256"}, false},
|
||||
{"TLS_RSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_RSA_WITH_AES_256_GCM_SHA384"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"}, false},
|
||||
{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"}, false},
|
||||
{"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", CipherSuites{"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"}, false},
|
||||
{"multiple", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, false},
|
||||
{"fail", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_BAD_CIPHERSUITE"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.c.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("CipherSuites.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCipherSuites_Value(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c CipherSuites
|
||||
want []uint16
|
||||
}{
|
||||
{"TLS_RSA_WITH_RC4_128_SHA", CipherSuites{"TLS_RSA_WITH_RC4_128_SHA"}, []uint16{tls.TLS_RSA_WITH_RC4_128_SHA}},
|
||||
{"TLS_RSA_WITH_3DES_EDE_CBC_SHA", CipherSuites{"TLS_RSA_WITH_3DES_EDE_CBC_SHA"}, []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}},
|
||||
{"TLS_RSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_RSA_WITH_AES_128_CBC_SHA"}, []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA}},
|
||||
{"TLS_RSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_RSA_WITH_AES_256_CBC_SHA"}, []uint16{tls.TLS_RSA_WITH_AES_256_CBC_SHA}},
|
||||
{"TLS_RSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_RSA_WITH_AES_128_CBC_SHA256"}, []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA256}},
|
||||
{"TLS_RSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_RSA_WITH_AES_128_GCM_SHA256"}, []uint16{tls.TLS_RSA_WITH_AES_128_GCM_SHA256}},
|
||||
{"TLS_RSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_RSA_WITH_AES_256_GCM_SHA384"}, []uint16{tls.TLS_RSA_WITH_AES_256_GCM_SHA384}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}},
|
||||
{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305}},
|
||||
{"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"}, []uint16{tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"}, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"}, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"}, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256}},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"}, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}},
|
||||
{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", CipherSuites{"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"}, []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}},
|
||||
{"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", CipherSuites{"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"}, []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}},
|
||||
{"multiple", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}},
|
||||
{"fail", CipherSuites{"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_BAD_CIPHERSUITE"}, []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 0}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.c.Value(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CipherSuites.Value() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSOptions_TLSConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
CipherSuites CipherSuites
|
||||
MinVersion TLSVersion
|
||||
MaxVersion TLSVersion
|
||||
Renegotiation bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want *tls.Config
|
||||
}{
|
||||
{"default", fields{DefaultTLSCipherSuites, DefaultTLSMinVersion, DefaultTLSMaxVersion, DefaultTLSRenegotiation}, &tls.Config{
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
Renegotiation: tls.RenegotiateNever,
|
||||
}},
|
||||
{"renegotation", fields{DefaultTLSCipherSuites, DefaultTLSMinVersion, DefaultTLSMaxVersion, true}, &tls.Config{
|
||||
CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
Renegotiation: tls.RenegotiateFreelyAsClient,
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &TLSOptions{
|
||||
CipherSuites: tt.fields.CipherSuites,
|
||||
MinVersion: tt.fields.MinVersion,
|
||||
MaxVersion: tt.fields.MaxVersion,
|
||||
Renegotiation: tt.fields.Renegotiation,
|
||||
}
|
||||
if got := o.TLSConfig(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("TLSOptions.TLSConfig() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package authority
|
|||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
|
@ -20,11 +22,10 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
|
@ -52,10 +53,73 @@ func (m *certificateDurationEnforcer) Enforce(cert *x509.Certificate) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func withProvisionerOID(name, kid string) x509util.WithOption {
|
||||
return func(p x509util.Profile) error {
|
||||
crt := p.Subject()
|
||||
func generateCertificate(t *testing.T, commonName string, sans []string, opts ...interface{}) *x509.Certificate {
|
||||
t.Helper()
|
||||
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cr, err := x509util.CreateCertificateRequest(commonName, sans, priv)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
template, err := x509util.NewCertificate(cr)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cert := template.GetCertificate()
|
||||
for _, m := range opts {
|
||||
switch m := m.(type) {
|
||||
case provisioner.CertificateModifierFunc:
|
||||
err = m.Modify(cert, provisioner.SignOptions{})
|
||||
assert.FatalError(t, err)
|
||||
case signerFunc:
|
||||
cert, err = m(cert, priv.Public())
|
||||
assert.FatalError(t, err)
|
||||
default:
|
||||
t.Fatalf("unknown type %T", m)
|
||||
}
|
||||
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func generateRootCertificate(t *testing.T) (*x509.Certificate, crypto.Signer) {
|
||||
t.Helper()
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cr, err := x509util.CreateCertificateRequest("TestRootCA", nil, priv)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
data := x509util.CreateTemplateData("TestRootCA", nil)
|
||||
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cert := template.GetCertificate()
|
||||
cert, err = x509util.CreateCertificate(cert, cert, priv.Public(), priv)
|
||||
assert.FatalError(t, err)
|
||||
return cert, priv
|
||||
}
|
||||
|
||||
func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) {
|
||||
t.Helper()
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cr, err := x509util.CreateCertificateRequest("TestIntermediateCA", nil, priv)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
data := x509util.CreateTemplateData("TestIntermediateCA", nil)
|
||||
template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
cert := template.GetCertificate()
|
||||
cert, err = x509util.CreateCertificate(cert, issuer, priv.Public(), signer)
|
||||
assert.FatalError(t, err)
|
||||
return cert, priv
|
||||
}
|
||||
|
||||
func withProvisionerOID(name, kid string) provisioner.CertificateModifierFunc {
|
||||
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
|
||||
b, err := asn1.Marshal(stepProvisionerASN1{
|
||||
Type: provisionerTypeJWK,
|
||||
Name: []byte(name),
|
||||
|
@ -69,11 +133,26 @@ func withProvisionerOID(name, kid string) x509util.WithOption {
|
|||
Critical: false,
|
||||
Value: b,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func withNotBeforeNotAfter(notBefore, notAfter time.Time) provisioner.CertificateModifierFunc {
|
||||
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
|
||||
crt.NotBefore = notBefore
|
||||
crt.NotAfter = notAfter
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type signerFunc func(crt *x509.Certificate, pub crypto.PublicKey) (*x509.Certificate, error)
|
||||
|
||||
func withSigner(issuer *x509.Certificate, signer crypto.Signer) signerFunc {
|
||||
return func(crt *x509.Certificate, pub crypto.PublicKey) (*x509.Certificate, error) {
|
||||
return x509util.CreateCertificate(crt, issuer, pub, signer)
|
||||
}
|
||||
}
|
||||
|
||||
func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateRequest)) *x509.CertificateRequest {
|
||||
_csr := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: "smallstep test"},
|
||||
|
@ -117,12 +196,12 @@ type basicConstraints struct {
|
|||
}
|
||||
|
||||
func TestAuthority_Sign(t *testing.T) {
|
||||
pub, priv, err := keys.GenerateDefaultKeyPair()
|
||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
a := testAuthority(t)
|
||||
assert.FatalError(t, err)
|
||||
a.config.AuthorityConfig.Template = &x509util.ASN1DN{
|
||||
a.config.AuthorityConfig.Template = &ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -140,7 +219,7 @@ func TestAuthority_Sign(t *testing.T) {
|
|||
|
||||
// Create a token to get test extra opts.
|
||||
p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK)
|
||||
key, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -517,11 +596,8 @@ ZYtQ9Ot36qc=
|
|||
}
|
||||
|
||||
func TestAuthority_Renew(t *testing.T) {
|
||||
pub, _, err := keys.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
a := testAuthority(t)
|
||||
a.config.AuthorityConfig.Template = &x509util.ASN1DN{
|
||||
a.config.AuthorityConfig.Template = &ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -530,13 +606,6 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
CommonName: "renew",
|
||||
}
|
||||
|
||||
certModToWithOptions := func(m provisioner.CertificateModifierFunc) x509util.WithOption {
|
||||
return func(p x509util.Profile) error {
|
||||
crt := p.Subject()
|
||||
return m.Modify(crt, provisioner.SignOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
nb1 := now.Add(-time.Minute * 7)
|
||||
na1 := now
|
||||
|
@ -545,28 +614,17 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
NotAfter: provisioner.NewTimeDuration(na1),
|
||||
}
|
||||
|
||||
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
|
||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)),
|
||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID))
|
||||
assert.FatalError(t, err)
|
||||
certBytes, err := leaf.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
assert.FatalError(t, err)
|
||||
cert := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
|
||||
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
|
||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID),
|
||||
withSigner(a.x509Issuer, a.x509Signer))
|
||||
|
||||
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
|
||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)),
|
||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||
certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
|
||||
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
|
||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID),
|
||||
)
|
||||
assert.FatalError(t, err)
|
||||
certBytesNoRenew, err := leafNoRenew.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
certNoRenew, err := x509.ParseCertificate(certBytesNoRenew)
|
||||
assert.FatalError(t, err)
|
||||
withSigner(a.x509Issuer, a.x509Signer))
|
||||
|
||||
type renewTest struct {
|
||||
auth *Authority
|
||||
|
@ -581,7 +639,7 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
return &renewTest{
|
||||
auth: _a,
|
||||
cert: cert,
|
||||
err: errors.New("authority.Rekey; error renewing certificate from existing server certificate"),
|
||||
err: errors.New("authority.Rekey: error creating certificate"),
|
||||
code: http.StatusInternalServerError,
|
||||
}, nil
|
||||
},
|
||||
|
@ -599,24 +657,12 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
}, nil
|
||||
},
|
||||
"ok/success-new-intermediate": func() (*renewTest, error) {
|
||||
newRootProfile, err := x509util.NewRootProfile("new-root")
|
||||
assert.FatalError(t, err)
|
||||
newRootBytes, err := newRootProfile.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
newRootCert, err := x509.ParseCertificate(newRootBytes)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
newIntermediateProfile, err := x509util.NewIntermediateProfile("new-intermediate",
|
||||
newRootCert, newRootProfile.SubjectPrivateKey())
|
||||
assert.FatalError(t, err)
|
||||
newIntermediateBytes, err := newIntermediateProfile.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
newIntermediateCert, err := x509.ParseCertificate(newIntermediateBytes)
|
||||
assert.FatalError(t, err)
|
||||
rootCert, rootSigner := generateRootCertificate(t)
|
||||
intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner)
|
||||
|
||||
_a := testAuthority(t)
|
||||
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer)
|
||||
_a.x509Issuer = newIntermediateCert
|
||||
_a.x509Signer = intSigner
|
||||
_a.x509Issuer = intCert
|
||||
return &renewTest{
|
||||
auth: _a,
|
||||
cert: cert,
|
||||
|
@ -678,7 +724,7 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
|
||||
|
||||
subjectKeyID, err := generateSubjectKeyID(pub)
|
||||
subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
|
||||
|
||||
|
@ -742,13 +788,11 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAuthority_Rekey(t *testing.T) {
|
||||
pub, _, err := keys.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
pub1, _, err := keys.GenerateDefaultKeyPair()
|
||||
pub, _, err := keyutil.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
a := testAuthority(t)
|
||||
a.config.AuthorityConfig.Template = &x509util.ASN1DN{
|
||||
a.config.AuthorityConfig.Template = &ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -757,13 +801,6 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
CommonName: "renew",
|
||||
}
|
||||
|
||||
certModToWithOptions := func(m provisioner.CertificateModifierFunc) x509util.WithOption {
|
||||
return func(p x509util.Profile) error {
|
||||
crt := p.Subject()
|
||||
return m.Modify(crt, provisioner.SignOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
nb1 := now.Add(-time.Minute * 7)
|
||||
na1 := now
|
||||
|
@ -772,28 +809,17 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
NotAfter: provisioner.NewTimeDuration(na1),
|
||||
}
|
||||
|
||||
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
|
||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)),
|
||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID))
|
||||
assert.FatalError(t, err)
|
||||
certBytes, err := leaf.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
cert, err := x509.ParseCertificate(certBytes)
|
||||
assert.FatalError(t, err)
|
||||
cert := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
|
||||
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
|
||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID),
|
||||
withSigner(a.x509Issuer, a.x509Signer))
|
||||
|
||||
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
|
||||
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0),
|
||||
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)),
|
||||
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||
certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
|
||||
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
|
||||
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID),
|
||||
)
|
||||
assert.FatalError(t, err)
|
||||
certBytesNoRenew, err := leafNoRenew.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
certNoRenew, err := x509.ParseCertificate(certBytesNoRenew)
|
||||
assert.FatalError(t, err)
|
||||
withSigner(a.x509Issuer, a.x509Signer))
|
||||
|
||||
type renewTest struct {
|
||||
auth *Authority
|
||||
|
@ -809,7 +835,7 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
return &renewTest{
|
||||
auth: _a,
|
||||
cert: cert,
|
||||
err: errors.New("authority.Rekey; error renewing certificate from existing server certificate"),
|
||||
err: errors.New("authority.Rekey: error creating certificate"),
|
||||
code: http.StatusInternalServerError,
|
||||
}, nil
|
||||
},
|
||||
|
@ -830,28 +856,16 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
return &renewTest{
|
||||
auth: a,
|
||||
cert: cert,
|
||||
pk: pub1,
|
||||
pk: pub,
|
||||
}, nil
|
||||
},
|
||||
"ok/renew/success-new-intermediate": func() (*renewTest, error) {
|
||||
newRootProfile, err := x509util.NewRootProfile("new-root")
|
||||
assert.FatalError(t, err)
|
||||
newRootBytes, err := newRootProfile.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
newRootCert, err := x509.ParseCertificate(newRootBytes)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
newIntermediateProfile, err := x509util.NewIntermediateProfile("new-intermediate",
|
||||
newRootCert, newRootProfile.SubjectPrivateKey())
|
||||
assert.FatalError(t, err)
|
||||
newIntermediateBytes, err := newIntermediateProfile.CreateCertificate()
|
||||
assert.FatalError(t, err)
|
||||
newIntermediateCert, err := x509.ParseCertificate(newIntermediateBytes)
|
||||
assert.FatalError(t, err)
|
||||
rootCert, rootSigner := generateRootCertificate(t)
|
||||
intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner)
|
||||
|
||||
_a := testAuthority(t)
|
||||
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer)
|
||||
_a.x509Issuer = newIntermediateCert
|
||||
_a.x509Signer = intSigner
|
||||
_a.x509Issuer = intCert
|
||||
return &renewTest{
|
||||
auth: _a,
|
||||
cert: cert,
|
||||
|
@ -989,7 +1003,7 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
func TestAuthority_GetTLSOptions(t *testing.T) {
|
||||
type renewTest struct {
|
||||
auth *Authority
|
||||
opts *tlsutil.TLSOptions
|
||||
opts *TLSOptions
|
||||
}
|
||||
tests := map[string]func() (*renewTest, error){
|
||||
"default": func() (*renewTest, error) {
|
||||
|
@ -998,8 +1012,8 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
|
|||
},
|
||||
"non-default": func() (*renewTest, error) {
|
||||
a := testAuthority(t)
|
||||
a.config.TLS = &tlsutil.TLSOptions{
|
||||
CipherSuites: x509util.CipherSuites{
|
||||
a.config.TLS = &TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
|
@ -1029,7 +1043,7 @@ func TestAuthority_Revoke(t *testing.T) {
|
|||
validAudience := testAudiences.Revoke
|
||||
now := time.Now().UTC()
|
||||
|
||||
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
|
||||
|
@ -1222,7 +1236,7 @@ func TestAuthority_Revoke(t *testing.T) {
|
|||
assert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode)
|
||||
assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason)
|
||||
assert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS)
|
||||
assert.Equals(t, ctxErr.Details["context"], fmt.Sprint(provisioner.RevokeMethod))
|
||||
assert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String())
|
||||
|
||||
if tc.checkErrDetails != nil {
|
||||
tc.checkErrDetails(ctxErr)
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
acmeAPI "github.com/smallstep/certificates/acme/api"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
// ACMEClient implements an HTTP client to an ACME API.
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
"github.com/smallstep/certificates/acme"
|
||||
acmeAPI "github.com/smallstep/certificates/acme/api"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestNewACMEClient(t *testing.T) {
|
||||
|
|
|
@ -8,8 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
type tokenClaims struct {
|
||||
|
@ -20,7 +19,7 @@ type tokenClaims struct {
|
|||
// Bootstrap is a helper function that initializes a client with the
|
||||
// configuration in the bootstrap token.
|
||||
func Bootstrap(token string) (*Client, error) {
|
||||
tok, err := jwt.ParseSigned(token)
|
||||
tok, err := jose.ParseSigned(token)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error parsing token")
|
||||
}
|
||||
|
|
|
@ -15,10 +15,8 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
stepJOSE "github.com/smallstep/cli/jose"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
func newLocalListener() net.Listener {
|
||||
|
@ -78,7 +76,7 @@ func startCAServer(configFile string) (*CA, string, error) {
|
|||
|
||||
func generateBootstrapToken(ca, subject, sha string) string {
|
||||
now := time.Now()
|
||||
jwk, err := stepJOSE.ParseKey("testdata/secrets/ott_mariano_priv.jwk", stepJOSE.WithPassword([]byte("password")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -93,21 +91,21 @@ func generateBootstrapToken(ca, subject, sha string) string {
|
|||
}
|
||||
cl := struct {
|
||||
SHA string `json:"sha"`
|
||||
jwt.Claims
|
||||
jose.Claims
|
||||
SANS []string `json:"sans"`
|
||||
}{
|
||||
SHA: sha,
|
||||
Claims: jwt.Claims{
|
||||
Claims: jose.Claims{
|
||||
ID: id,
|
||||
Subject: subject,
|
||||
Issuer: "mariano",
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: []string{ca + "/sign"},
|
||||
},
|
||||
SANS: []string{subject},
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -25,13 +25,11 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
stepJOSE "github.com/smallstep/cli/jose"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
type ClosingBuffer struct {
|
||||
|
@ -76,10 +74,10 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
func TestCASign(t *testing.T) {
|
||||
pub, priv, err := keys.GenerateDefaultKeyPair()
|
||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
asn1dn := &x509util.ASN1DN{
|
||||
asn1dn := &authority.ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -93,13 +91,9 @@ func TestCASign(t *testing.T) {
|
|||
config.AuthorityConfig.Template = asn1dn
|
||||
ca, err := New(config)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
intermediateIdentity, err := x509util.LoadIdentityFromDisk("testdata/secrets/intermediate_ca.crt",
|
||||
"testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password")))
|
||||
intermediateCert, err := pemutil.ReadCertificate("testdata/secrets/intermediate_ca.crt")
|
||||
assert.FatalError(t, err)
|
||||
|
||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_priv.jwk",
|
||||
stepJOSE.WithPassword([]byte("pass")))
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||
assert.FatalError(t, err)
|
||||
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: clijwk.Key},
|
||||
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", clijwk.KeyID))
|
||||
|
@ -181,20 +175,20 @@ ZEp7knvU2psWRw==
|
|||
jti, err := randutil.ASCII(32)
|
||||
assert.FatalError(t, err)
|
||||
cl := struct {
|
||||
jwt.Claims
|
||||
jose.Claims
|
||||
SANS []string `json:"sans"`
|
||||
}{
|
||||
Claims: jwt.Claims{
|
||||
Claims: jose.Claims{
|
||||
Subject: "invalid",
|
||||
Issuer: "step-cli",
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAud,
|
||||
ID: jti,
|
||||
},
|
||||
SANS: []string{"invalid"},
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
csr, err := getCSR(priv)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -214,20 +208,20 @@ ZEp7knvU2psWRw==
|
|||
jti, err := randutil.ASCII(32)
|
||||
assert.FatalError(t, err)
|
||||
cl := struct {
|
||||
jwt.Claims
|
||||
jose.Claims
|
||||
SANS []string `json:"sans"`
|
||||
}{
|
||||
Claims: jwt.Claims{
|
||||
Claims: jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: "step-cli",
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAud,
|
||||
ID: jti,
|
||||
},
|
||||
SANS: []string{"test.smallstep.com"},
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
csr, err := getCSR(priv)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -248,19 +242,19 @@ ZEp7knvU2psWRw==
|
|||
jti, err := randutil.ASCII(32)
|
||||
assert.FatalError(t, err)
|
||||
cl := struct {
|
||||
jwt.Claims
|
||||
jose.Claims
|
||||
SANS []string `json:"sans"`
|
||||
}{
|
||||
Claims: jwt.Claims{
|
||||
Claims: jose.Claims{
|
||||
Subject: "test.smallstep.com",
|
||||
Issuer: "step-cli",
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: validAud,
|
||||
ID: jti,
|
||||
},
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
assert.FatalError(t, err)
|
||||
csr, err := getCSR(priv)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -321,9 +315,9 @@ ZEp7knvU2psWRw==
|
|||
assert.FatalError(t, err)
|
||||
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
|
||||
|
||||
assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId)
|
||||
assert.Equals(t, leaf.AuthorityKeyId, intermediateCert.SubjectKeyId)
|
||||
|
||||
realIntermediate, err := x509.ParseCertificate(intermediateIdentity.Crt.Raw)
|
||||
realIntermediate, err := x509.ParseCertificate(intermediateCert.Raw)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, intermediate, realIntermediate)
|
||||
} else {
|
||||
|
@ -555,10 +549,10 @@ func TestCAHealth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCARenew(t *testing.T) {
|
||||
pub, _, err := keys.GenerateDefaultKeyPair()
|
||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
asn1dn := &x509util.ASN1DN{
|
||||
asn1dn := &authority.ASN1DN{
|
||||
Country: "Tazmania",
|
||||
Organization: "Acme Co",
|
||||
Locality: "Landscapes",
|
||||
|
@ -574,8 +568,9 @@ func TestCARenew(t *testing.T) {
|
|||
assert.FatalError(t, err)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
intermediateIdentity, err := x509util.LoadIdentityFromDisk("testdata/secrets/intermediate_ca.crt",
|
||||
"testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password")))
|
||||
intermediateCert, err := pemutil.ReadCertificate("testdata/secrets/intermediate_ca.crt")
|
||||
assert.FatalError(t, err)
|
||||
intermediateKey, err := pemutil.Read("testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password")))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
@ -605,15 +600,15 @@ func TestCARenew(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"success": func(t *testing.T) *renewTest {
|
||||
profile, err := x509util.NewLeafProfile("test", intermediateIdentity.Crt,
|
||||
intermediateIdentity.Key, x509util.WithPublicKey(pub),
|
||||
x509util.WithNotBeforeAfterDuration(now, leafExpiry, 0), x509util.WithHosts("funk"))
|
||||
cr, err := x509util.CreateCertificateRequest("test", []string{"funk"}, priv.(crypto.Signer))
|
||||
assert.FatalError(t, err)
|
||||
crtBytes, err := profile.CreateCertificate()
|
||||
cert, err := x509util.NewCertificate(cr)
|
||||
assert.FatalError(t, err)
|
||||
crt, err := x509.ParseCertificate(crtBytes)
|
||||
crt := cert.GetCertificate()
|
||||
crt.NotBefore = time.Now()
|
||||
crt.NotAfter = leafExpiry
|
||||
crt, err = x509util.CreateCertificate(crt, intermediateCert, pub, intermediateKey.(crypto.Signer))
|
||||
assert.FatalError(t, err)
|
||||
|
||||
return &renewTest{
|
||||
ca: ca,
|
||||
tlsConnState: &tls.ConnectionState{
|
||||
|
@ -661,9 +656,9 @@ func TestCARenew(t *testing.T) {
|
|||
subjectKeyID, err := generateSubjectKeyID(pub)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
|
||||
assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId)
|
||||
assert.Equals(t, leaf.AuthorityKeyId, intermediateCert.SubjectKeyId)
|
||||
|
||||
realIntermediate, err := x509.ParseCertificate(intermediateIdentity.Crt.Raw)
|
||||
realIntermediate, err := x509.ParseCertificate(intermediateCert.Raw)
|
||||
assert.FatalError(t, err)
|
||||
assert.Equals(t, intermediate, realIntermediate)
|
||||
|
||||
|
|
|
@ -28,9 +28,9 @@ import (
|
|||
"github.com/smallstep/certificates/ca/identity"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/config"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"golang.org/x/net/http2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
@ -1102,7 +1102,7 @@ func CreateSignRequest(ott string) (*api.SignRequest, crypto.PrivateKey, error)
|
|||
// CreateCertificateRequest creates a new CSR with the given common name and
|
||||
// SANs. If no san is provided the commonName will set also a SAN.
|
||||
func CreateCertificateRequest(commonName string, sans ...string) (*api.CertificateRequest, crypto.PrivateKey, error) {
|
||||
key, err := keys.GenerateDefaultKey()
|
||||
key, err := keyutil.GenerateDefaultKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/cli/config"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
// Type represents the different types of identity files.
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestLoadDefaultIdentity(t *testing.T) {
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/cli/token"
|
||||
"github.com/smallstep/cli/token/provision"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
const tokenLifetime = 5 * time.Minute
|
||||
|
|
|
@ -7,13 +7,13 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
func getTestProvisioner(t *testing.T, caURL string) *Provisioner {
|
||||
jwk, err := jose.ParseKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -18,15 +18,13 @@ import (
|
|||
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
stepJOSE "github.com/smallstep/cli/jose"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
func generateOTT(subject string) string {
|
||||
now := time.Now()
|
||||
jwk, err := stepJOSE.ParseKey("testdata/secrets/ott_mariano_priv.jwk", stepJOSE.WithPassword([]byte("password")))
|
||||
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -40,20 +38,20 @@ func generateOTT(subject string) string {
|
|||
panic(err)
|
||||
}
|
||||
cl := struct {
|
||||
jwt.Claims
|
||||
jose.Claims
|
||||
SANS []string `json:"sans"`
|
||||
}{
|
||||
Claims: jwt.Claims{
|
||||
Claims: jose.Claims{
|
||||
ID: id,
|
||||
Subject: subject,
|
||||
Issuer: "mariano",
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||
NotBefore: jose.NewNumericDate(now),
|
||||
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
|
||||
Audience: []string{"https://127.0.0.1:0/sign"},
|
||||
},
|
||||
SANS: []string{subject},
|
||||
}
|
||||
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ import (
|
|||
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/awskms"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ import (
|
|||
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/cloudkms"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
|
||||
// Enable yubikey.
|
||||
_ "github.com/smallstep/certificates/kms/yubikey"
|
||||
|
|
|
@ -13,11 +13,11 @@ import (
|
|||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/pki"
|
||||
"github.com/smallstep/cli/command"
|
||||
"github.com/smallstep/cli/crypto/randutil"
|
||||
"github.com/smallstep/cli/errs"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
// defaultOnboardingURL is the production onboarding url, to use a development
|
||||
|
|
11
go.mod
11
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/smallstep/certificates
|
||||
|
||||
go 1.13
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.51.0
|
||||
|
@ -19,14 +19,15 @@ require (
|
|||
github.com/smallstep/cli v0.15.0
|
||||
github.com/smallstep/nosql v0.3.0
|
||||
github.com/urfave/cli v1.22.2
|
||||
go.step.sm/crypto v0.2.0
|
||||
go.step.sm/crypto v0.3.0
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
google.golang.org/api v0.15.0
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb
|
||||
google.golang.org/grpc v1.26.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.0
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
)
|
||||
|
||||
//replace github.com/smallstep/cli => ../cli
|
||||
//replace github.com/smallstep/nosql => ../nosql
|
||||
// replace github.com/smallstep/cli => ../cli
|
||||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
|
|
6
go.sum
6
go.sum
|
@ -544,8 +544,8 @@ go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 h1:FymMl8TrXGxFf80BWpO0CnkS
|
|||
go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U=
|
||||
go.step.sm/crypto v0.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E=
|
||||
go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8=
|
||||
go.step.sm/crypto v0.2.0 h1:Rx+XqrNO4ZGHWlT9QCXls2L9pvcNiI23zEpAq0fctvY=
|
||||
go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww=
|
||||
go.step.sm/crypto v0.3.0 h1:wlbhLw2LvzDYwKcUJS2plBALptMXaKUhI2nbdfsXjTU=
|
||||
go.step.sm/crypto v0.3.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
|
@ -748,6 +748,8 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/uri"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
// KMS implements a KMS using AWS Key Management Service.
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
// Signer implements a crypto.Signer using the AWS KMS.
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestNewSigner(t *testing.T) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
gax "github.com/googleapis/gax-go/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"google.golang.org/api/option"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,8 +10,9 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
type algorithmAttributes struct {
|
||||
|
@ -41,7 +42,7 @@ var generateKey = func(kty, crv string, size int) (interface{}, interface{}, err
|
|||
if kty == "RSA" && size == 0 {
|
||||
size = DefaultRSAKeySize
|
||||
}
|
||||
return keys.GenerateKeyPair(kty, crv, size)
|
||||
return keyutil.GenerateKeyPair(kty, crv, size)
|
||||
}
|
||||
|
||||
// SoftKMS is a key manager that uses keys stored in disk.
|
||||
|
@ -53,6 +54,9 @@ func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) {
|
|||
}
|
||||
|
||||
func init() {
|
||||
pemutil.PromptPassword = func(msg string) ([]byte, error) {
|
||||
return ui.PromptPassword(msg)
|
||||
}
|
||||
apiv1.Register(apiv1.SoftKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
||||
return New(ctx, opts)
|
||||
})
|
||||
|
@ -98,6 +102,8 @@ func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e
|
|||
}
|
||||
}
|
||||
|
||||
// CreateKey generates a new key using Golang crypto and returns both public and
|
||||
// private key.
|
||||
func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
|
||||
if !ok {
|
||||
|
@ -123,6 +129,7 @@ func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespon
|
|||
}, nil
|
||||
}
|
||||
|
||||
// GetPublicKey returns the public key from the file passed in the request name.
|
||||
func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
||||
v, err := pemutil.Read(req.Name)
|
||||
if err != nil {
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
ifeq (, $(shell which docker))
|
||||
DOCKER_CLIENT_OS := linux
|
||||
else
|
||||
DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}'))
|
||||
DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}' 2>/dev/null))
|
||||
endif
|
||||
|
||||
DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64
|
||||
|
|
98
pki/pki.go
98
pki/pki.go
|
@ -14,6 +14,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
|
@ -21,14 +22,13 @@ import (
|
|||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/cli/config"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"github.com/smallstep/cli/crypto/tlsutil"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
"github.com/smallstep/cli/errs"
|
||||
"github.com/smallstep/cli/jose"
|
||||
"github.com/smallstep/cli/ui"
|
||||
"github.com/smallstep/cli/utils"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -114,6 +114,18 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func generateDefaultKey() (crypto.Signer, error) {
|
||||
priv, err := keyutil.GenerateDefaultKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signer, ok := priv.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("type %T is not a cyrpto.Signer", priv)
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// GetProvisionerKey returns the encrypted provisioner key with the for the
|
||||
// given kid.
|
||||
func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
|
||||
|
@ -255,25 +267,35 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
|
|||
|
||||
// GenerateRootCertificate generates a root certificate with the given name.
|
||||
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) {
|
||||
rootProfile, err := x509util.NewRootProfile(name)
|
||||
signer, err := generateDefaultKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass))
|
||||
cr, err := x509util.CreateCertificateRequest(name, []string{}, signer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rootCrt, err := x509.ParseCertificate(rootBytes)
|
||||
data := x509util.CreateTemplateData(name, []string{})
|
||||
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "error parsing root certificate")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(rootCrt.Raw)
|
||||
p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
|
||||
template := cert.GetCertificate()
|
||||
template.NotBefore = time.Now()
|
||||
template.NotAfter = template.NotBefore.AddDate(10, 0, 0)
|
||||
rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return rootCrt, rootProfile.SubjectPrivateKey(), nil
|
||||
if err := p.WriteRootCertificate(rootCrt, signer, pass); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return rootCrt, signer, nil
|
||||
}
|
||||
|
||||
// WriteRootCertificate writes to disk the given certificate and key.
|
||||
|
@ -285,7 +307,7 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
|
|||
return err
|
||||
}
|
||||
|
||||
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword([]byte(pass)), pemutil.ToFile(p.rootKey, 0600))
|
||||
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -299,12 +321,46 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
|
|||
// GenerateIntermediateCertificate generates an intermediate certificate with
|
||||
// the given name.
|
||||
func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
|
||||
interProfile, err := x509util.NewIntermediateProfile(name, rootCrt, rootKey)
|
||||
key, err := generateDefaultKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass))
|
||||
|
||||
cr, err := x509util.CreateCertificateRequest(name, []string{}, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := x509util.CreateTemplateData(name, []string{})
|
||||
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultIntermediateTemplate, data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template := cert.GetCertificate()
|
||||
template.NotBefore = rootCrt.NotBefore
|
||||
template.NotAfter = rootCrt.NotAfter
|
||||
intermediateCrt, err := x509util.CreateCertificate(template, rootCrt, key.Public(), rootKey.(crypto.Signer))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.WriteIntermediateCertificate(intermediateCrt, key, pass)
|
||||
}
|
||||
|
||||
// WriteIntermediateCertificate writes to disk the given certificate and key.
|
||||
func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error {
|
||||
if err := utils.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: crt.Raw,
|
||||
}), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateSSHSigningKeys generates and encrypts a private key used for signing
|
||||
|
@ -313,7 +369,7 @@ func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
|||
var pubNames = []string{p.sshHostPubKey, p.sshUserPubKey}
|
||||
var privNames = []string{p.sshHostKey, p.sshUserKey}
|
||||
for i := 0; i < 2; i++ {
|
||||
pub, priv, err := keys.GenerateDefaultKeyPair()
|
||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -432,11 +488,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
|||
DisableIssuedAtCheck: false,
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
TLS: &tlsutil.TLSOptions{
|
||||
MinVersion: x509util.DefaultTLSMinVersion,
|
||||
MaxVersion: x509util.DefaultTLSMaxVersion,
|
||||
Renegotiation: x509util.DefaultTLSRenegotiation,
|
||||
CipherSuites: x509util.DefaultTLSCipherSuites,
|
||||
TLS: &authority.TLSOptions{
|
||||
MinVersion: authority.DefaultTLSMinVersion,
|
||||
MaxVersion: authority.DefaultTLSMaxVersion,
|
||||
Renegotiation: authority.DefaultTLSRenegotiation,
|
||||
CipherSuites: authority.DefaultTLSCipherSuites,
|
||||
},
|
||||
Templates: p.getTemplates(),
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
// Hosts are tagged with k,v pairs. These tags are how a user is ultimately
|
||||
// associated with a host.
|
||||
type HostTag struct {
|
||||
ID string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Host defines expected attributes for an ssh host.
|
||||
type Host struct {
|
||||
HostID string `json:"hid"`
|
||||
HostTags []HostTag `json:"host_tags"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
Loading…
Reference in a new issue