Merge pull request #329 from smallstep/ssh-cert-templates

SSH cert templates
This commit is contained in:
Mariano Cano 2020-08-28 14:42:58 -07:00 committed by GitHub
commit 35bd3ec383
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 1643 additions and 1013 deletions

View file

@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/jose"
) )
// Account is a subset of the internal account type containing only those // Account is a subset of the internal account type containing only those

View file

@ -12,9 +12,9 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
) )
var ( var (

View file

@ -16,7 +16,7 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
var ( var (

View file

@ -19,8 +19,8 @@ import (
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/pemutil"
) )
type mockAcmeAuthority struct { type mockAcmeAuthority struct {

View file

@ -14,9 +14,9 @@ import (
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
) )
type nextHTTP = func(http.ResponseWriter, *http.Request) type nextHTTP = func(http.ResponseWriter, *http.Request)
@ -173,10 +173,10 @@ func (h *Handler) validateJWS(next nextHTTP) nextHTTP {
if hdr.JSONWebKey != nil { if hdr.JSONWebKey != nil {
switch k := hdr.JSONWebKey.Key.(type) { switch k := hdr.JSONWebKey.Key.(type) {
case *rsa.PublicKey: case *rsa.PublicKey:
if k.Size() < keys.MinRSAKeyBytes { if k.Size() < keyutil.MinRSAKeyBytes {
api.WriteError(w, acme.MalformedErr(errors.Errorf("rsa "+ api.WriteError(w, acme.MalformedErr(errors.Errorf("rsa "+
"keys must be at least %d bits (%d bytes) in size", "keys must be at least %d bits (%d bytes) in size",
8*keys.MinRSAKeyBytes, keys.MinRSAKeyBytes))) 8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes)))
return return
} }
default: default:

View file

@ -18,8 +18,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
) )
var testBody = []byte("foo") var testBody = []byte("foo")

View file

@ -17,7 +17,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestNewOrderRequestValidate(t *testing.T) { func TestNewOrderRequestValidate(t *testing.T) {

View file

@ -14,8 +14,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
database "github.com/smallstep/certificates/db" database "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/jose"
) )
// Interface is the acme authority interface. // Interface is the acme authority interface.

View file

@ -11,8 +11,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
) )
func TestAuthorityGetLink(t *testing.T) { func TestAuthorityGetLink(t *testing.T) {

View file

@ -10,9 +10,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"go.step.sm/crypto/pemutil"
) )
func defaultCertOps() (*CertOptions, error) { func defaultCertOps() (*CertOptions, error) {

View file

@ -18,8 +18,8 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/jose"
) )
// Challenge is a subset of the challenge type containing only those attributes // Challenge is a subset of the challenge type containing only those attributes

View file

@ -28,9 +28,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"github.com/smallstep/nosql/database" "github.com/smallstep/nosql/database"
"go.step.sm/crypto/jose"
) )
var testOps = ChallengeOptions{ var testOps = ChallengeOptions{

View file

@ -8,8 +8,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
) )
// Provisioner is an interface that implements a subset of the provisioner.Interface -- // Provisioner is an interface that implements a subset of the provisioner.Interface --

View file

@ -23,7 +23,6 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/cli/crypto/tlsutil"
) )
// Authority is the interface implemented by a CA authority. // Authority is the interface implemented by a CA authority.
@ -32,7 +31,7 @@ type Authority interface {
// context specifies the Authorize[Sign|Revoke|etc.] method. // context specifies the Authorize[Sign|Revoke|etc.] method.
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
AuthorizeSign(ott string) ([]provisioner.SignOption, error) AuthorizeSign(ott string) ([]provisioner.SignOption, error)
GetTLSOptions() *tlsutil.TLSOptions GetTLSOptions() *authority.TLSOptions
Root(shasum string) (*x509.Certificate, error) Root(shasum string) (*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Renew(peer *x509.Certificate) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error)

View file

@ -31,10 +31,8 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/tlsutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -548,7 +546,7 @@ type mockAuthority struct {
ret1, ret2 interface{} ret1, ret2 interface{}
err error err error
authorizeSign func(ott string) ([]provisioner.SignOption, error) authorizeSign func(ott string) ([]provisioner.SignOption, error)
getTLSOptions func() *tlsutil.TLSOptions getTLSOptions func() *authority.TLSOptions
root func(shasum string) (*x509.Certificate, error) root func(shasum string) (*x509.Certificate, error)
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*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) 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) 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) 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) 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) getSSHRoots func(ctx context.Context) (*authority.SSHKeys, error)
getSSHFederation 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) 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 return m.ret1.([]provisioner.SignOption), m.err
} }
func (m *mockAuthority) GetTLSOptions() *tlsutil.TLSOptions { func (m *mockAuthority) GetTLSOptions() *authority.TLSOptions {
if m.getTLSOptions != nil { if m.getTLSOptions != nil {
return m.getTLSOptions() return m.getTLSOptions()
} }
return m.ret1.(*tlsutil.TLSOptions) return m.ret1.(*authority.TLSOptions)
} }
func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) { 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 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 { if m.getSSHHosts != nil {
return m.getSSHHosts(ctx, cert) 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) { 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) { authorizeSign: func(ott string) ([]provisioner.SignOption, error) {
return tt.certAttrOpts, tt.autherr return tt.certAttrOpts, tt.autherr
}, },
getTLSOptions: func() *tlsutil.TLSOptions { getTLSOptions: func() *authority.TLSOptions {
return nil return nil
}, },
}).(*caHandler) }).(*caHandler)
@ -933,7 +931,7 @@ func Test_caHandler_Renew(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{ h := New(&mockAuthority{
ret1: tt.cert, ret2: tt.root, err: tt.err, ret1: tt.cert, ret2: tt.root, err: tt.err,
getTLSOptions: func() *tlsutil.TLSOptions { getTLSOptions: func() *authority.TLSOptions {
return nil return nil
}, },
}).(*caHandler) }).(*caHandler)
@ -994,7 +992,7 @@ func Test_caHandler_Rekey(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{ h := New(&mockAuthority{
ret1: tt.cert, ret2: tt.root, err: tt.err, ret1: tt.cert, ret2: tt.root, err: tt.err,
getTLSOptions: func() *tlsutil.TLSOptions { getTLSOptions: func() *authority.TLSOptions {
return nil return nil
}, },
}).(*caHandler) }).(*caHandler)

View file

@ -5,18 +5,18 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/tlsutil"
) )
// SignRequest is the request body for a certificate signature request. // SignRequest is the request body for a certificate signature request.
type SignRequest struct { type SignRequest struct {
CsrPEM CertificateRequest `json:"csr"` CsrPEM CertificateRequest `json:"csr"`
OTT string `json:"ott"` OTT string `json:"ott"`
NotAfter TimeDuration `json:"notAfter"` NotAfter TimeDuration `json:"notAfter,omitempty"`
NotBefore TimeDuration `json:"notBefore"` NotBefore TimeDuration `json:"notBefore,omitempty"`
TemplateData json.RawMessage `json:"templateData"` TemplateData json.RawMessage `json:"templateData,omitempty"`
} }
// Validate checks the fields of the SignRequest and returns nil if they are ok // Validate checks the fields of the SignRequest and returns nil if they are ok
@ -37,11 +37,11 @@ func (s *SignRequest) Validate() error {
// SignResponse is the response object of the certificate signature request. // SignResponse is the response object of the certificate signature request.
type SignResponse struct { type SignResponse struct {
ServerPEM Certificate `json:"crt"` ServerPEM Certificate `json:"crt"`
CaPEM Certificate `json:"ca"` CaPEM Certificate `json:"ca"`
CertChainPEM []Certificate `json:"certChain"` CertChainPEM []Certificate `json:"certChain"`
TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"` TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"`
TLS *tls.ConnectionState `json:"-"` TLS *tls.ConnectionState `json:"-"`
} }
// Sign is an HTTP handler that reads a certificate request and an // Sign is an HTTP handler that reads a certificate request and an

View file

@ -12,7 +12,6 @@ import (
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -27,7 +26,7 @@ type SSHAuthority interface {
GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error) GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error)
GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error) GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
CheckSSHHost(ctx context.Context, principal string, token string) (bool, 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) GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error)
} }
@ -36,12 +35,13 @@ type SSHSignRequest struct {
PublicKey []byte `json:"publicKey"` // base64 encoded PublicKey []byte `json:"publicKey"` // base64 encoded
OTT string `json:"ott"` OTT string `json:"ott"`
CertType string `json:"certType,omitempty"` CertType string `json:"certType,omitempty"`
KeyID string `json:"keyID,omitempty"`
Principals []string `json:"principals,omitempty"` Principals []string `json:"principals,omitempty"`
ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidAfter TimeDuration `json:"validAfter,omitempty"`
ValidBefore TimeDuration `json:"validBefore,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"`
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"` AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
KeyID string `json:"keyID"`
IdentityCSR CertificateRequest `json:"identityCSR,omitempty"` IdentityCSR CertificateRequest `json:"identityCSR,omitempty"`
TemplateData json.RawMessage `json:"templateData,omitempty"`
} }
// Validate validates the SSHSignRequest. // Validate validates the SSHSignRequest.
@ -86,7 +86,7 @@ type SSHCertificate struct {
// SSHGetHostsResponse is the response object that returns the list of valid // SSHGetHostsResponse is the response object that returns the list of valid
// hosts for SSH. // hosts for SSH.
type SSHGetHostsResponse struct { type SSHGetHostsResponse struct {
Hosts []sshutil.Host `json:"hosts"` Hosts []authority.Host `json:"hosts"`
} }
// MarshalJSON implements the json.Marshaler interface. Returns a quoted, // MarshalJSON implements the json.Marshaler interface. Returns a quoted,
@ -275,11 +275,12 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
} }
opts := provisioner.SignSSHOptions{ opts := provisioner.SignSSHOptions{
CertType: body.CertType, CertType: body.CertType,
KeyID: body.KeyID, KeyID: body.KeyID,
Principals: body.Principals, Principals: body.Principals,
ValidBefore: body.ValidBefore, ValidBefore: body.ValidBefore,
ValidAfter: body.ValidAfter, ValidAfter: body.ValidAfter,
TemplateData: body.TemplateData,
} }
ctx := provisioner.NewContextWithMethod(r.Context(), provisioner.SSHSignMethod) ctx := provisioner.NewContextWithMethod(r.Context(), provisioner.SSHSignMethod)

View file

@ -22,7 +22,6 @@ import (
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -569,29 +568,29 @@ func Test_caHandler_SSHCheckHost(t *testing.T) {
} }
func Test_caHandler_SSHGetHosts(t *testing.T) { func Test_caHandler_SSHGetHosts(t *testing.T) {
hosts := []sshutil.Host{ hosts := []authority.Host{
{HostID: "1", HostTags: []sshutil.HostTag{{ID: "1", Name: "group", Value: "1"}}, Hostname: "host1"}, {HostID: "1", HostTags: []authority.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"}, {HostID: "2", HostTags: []authority.HostTag{{ID: "1", Name: "group", Value: "1"}, {ID: "2", Name: "group", Value: "2"}}, Hostname: "host2"},
} }
hostsJSON, err := json.Marshal(hosts) hostsJSON, err := json.Marshal(hosts)
assert.FatalError(t, err) assert.FatalError(t, err)
tests := []struct { tests := []struct {
name string name string
hosts []sshutil.Host hosts []authority.Host
err error err error
body []byte body []byte
statusCode int statusCode int
}{ }{
{"ok", hosts, nil, []byte(fmt.Sprintf(`{"hosts":%s}`, hostsJSON)), http.StatusOK}, {"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}, {"empty (nil)", nil, nil, []byte(`{"hosts":null}`), http.StatusOK},
{"error", nil, fmt.Errorf("an error"), nil, http.StatusInternalServerError}, {"error", nil, fmt.Errorf("an error"), nil, http.StatusInternalServerError},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{ 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 return tt.hosts, tt.err
}, },
}).(*caHandler) }).(*caHandler)

View file

@ -15,9 +15,8 @@ import (
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -55,7 +54,7 @@ type Authority struct {
// Custom functions // Custom functions
sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error) sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error)
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, 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 getIdentityFunc provisioner.GetIdentityFunc
} }

View file

@ -15,14 +15,14 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
stepJOSE "github.com/smallstep/cli/jose" "go.step.sm/crypto/pemutil"
) )
func testAuthority(t *testing.T, opts ...Option) *Authority { 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) 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) assert.FatalError(t, err)
disableRenewal := true disableRenewal := true
enableSSHCA := true enableSSHCA := true
@ -103,7 +103,7 @@ func TestAuthorityNew(t *testing.T) {
c.Root = []string{"foo"} c.Root = []string{"foo"}
return &newTest{ return &newTest{
config: c, 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 { "fail bad password": func(t *testing.T) *newTest {
@ -121,7 +121,7 @@ func TestAuthorityNew(t *testing.T) {
c.IntermediateCert = "wrong" c.IntermediateCert = "wrong"
return &newTest{ return &newTest{
config: c, config: c,
err: errors.New("open wrong failed: no such file or directory"), err: errors.New("error reading wrong: no such file or directory"),
} }
}, },
} }

View file

@ -8,7 +8,7 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -17,11 +17,10 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"gopkg.in/square/go-jose.v2/jwt"
) )
var testAudiences = provisioner.Audiences{ 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) { func TestAuthority_authorizeToken(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/prehistoric-token": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
IssuedAt: jwt.NewNumericDate(now.Add(-time.Hour)), IssuedAt: jose.NewNumericDate(now.Add(-time.Hour)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -131,11 +130,11 @@ func TestAuthority_authorizeToken(t *testing.T) {
} }
}, },
"fail/provisioner-not-found": func(t *testing.T) *authorizeTest { "fail/provisioner-not-found": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "44", ID: "44",
} }
@ -143,7 +142,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "foo")) (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "foo"))
assert.FatalError(t, err) assert.FatalError(t, err)
raw, err := jwt.Signed(_sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(_sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -153,15 +152,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
} }
}, },
"ok/simpledb": func(t *testing.T) *authorizeTest { "ok/simpledb": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -170,15 +169,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
}, },
"fail/simpledb/token-already-used": func(t *testing.T) *authorizeTest { "fail/simpledb/token-already-used": func(t *testing.T) *authorizeTest {
_a := testAuthority(t) _a := testAuthority(t)
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
_, err = _a.authorizeToken(context.Background(), raw) _, err = _a.authorizeToken(context.Background(), raw)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -197,15 +196,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
}, },
} }
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: _a, auth: _a,
@ -220,15 +219,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
}, },
} }
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: _a, auth: _a,
@ -245,15 +244,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
}, },
} }
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: _a, auth: _a,
@ -288,7 +287,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
func TestAuthority_authorizeRevoke(t *testing.T) { func TestAuthority_authorizeRevoke(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/token/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -334,15 +333,15 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
} }
}, },
"ok/token": func(t *testing.T) *authorizeTest { "ok/token": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "44", ID: "44",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -372,7 +371,7 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
func TestAuthority_authorizeSign(t *testing.T) { func TestAuthority_authorizeSign(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -418,15 +417,15 @@ func TestAuthority_authorizeSign(t *testing.T) {
} }
}, },
"ok": func(t *testing.T) *authorizeTest { "ok": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "44", ID: "44",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -459,7 +458,7 @@ func TestAuthority_authorizeSign(t *testing.T) {
func TestAuthority_Authorize(t *testing.T) { func TestAuthority_Authorize(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "ok/sign": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.Sign, Audience: testAudiences.Sign,
ID: "1", ID: "1",
} }
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize() token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -522,15 +521,15 @@ func TestAuthority_Authorize(t *testing.T) {
} }
}, },
"ok/revoke": func(t *testing.T) *authorizeTest { "ok/revoke": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.Revoke, Audience: testAudiences.Revoke,
ID: "2", ID: "2",
} }
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize() token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -622,15 +621,15 @@ func TestAuthority_Authorize(t *testing.T) {
} }
}, },
"ok/sshRevoke": func(t *testing.T) *authorizeTest { "ok/sshRevoke": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRevoke, Audience: testAudiences.SSHRevoke,
ID: "3", ID: "3",
} }
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize() token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -892,7 +891,7 @@ func createSSHCert(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate,
func TestAuthority_authorizeSSHSign(t *testing.T) { func TestAuthority_authorizeSSHSign(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience, Audience: validAudience,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -961,7 +960,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
} }
} else { } else {
if assert.Nil(t, tc.err) { 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) { func TestAuthority_authorizeSSHRenew(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/sshRenew-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRenew, Audience: testAudiences.SSHRenew,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, 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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRevoke, Audience: testAudiences.SSHRevoke,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
@ -1164,7 +1163,7 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
func TestAuthority_authorizeSSHRekey(t *testing.T) { func TestAuthority_authorizeSSHRekey(t *testing.T) {
a := testAuthority(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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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 { "fail/sshRekey-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{ cl := jose.Claims{
Subject: "", Subject: "",
Issuer: validIssuer, Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRekey, Audience: testAudiences.SSHRekey,
ID: "43", ID: "43",
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,

View file

@ -12,15 +12,13 @@ import (
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
kms "github.com/smallstep/certificates/kms/apiv1" kms "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util"
) )
var ( var (
// DefaultTLSOptions represents the default TLS version as well as the cipher // DefaultTLSOptions represents the default TLS version as well as the cipher
// suites used in the TLS certificates. // suites used in the TLS certificates.
DefaultTLSOptions = tlsutil.TLSOptions{ DefaultTLSOptions = TLSOptions{
CipherSuites: x509util.CipherSuites{ CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
@ -61,15 +59,27 @@ type Config struct {
DB *db.Config `json:"db,omitempty"` DB *db.Config `json:"db,omitempty"`
Monitoring json.RawMessage `json:"monitoring,omitempty"` Monitoring json.RawMessage `json:"monitoring,omitempty"`
AuthorityConfig *AuthConfig `json:"authority,omitempty"` AuthorityConfig *AuthConfig `json:"authority,omitempty"`
TLS *tlsutil.TLSOptions `json:"tls,omitempty"` TLS *TLSOptions `json:"tls,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
Templates *templates.Templates `json:"templates,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. // AuthConfig represents the configuration options for the authority.
type AuthConfig struct { type AuthConfig struct {
Provisioners provisioner.List `json:"provisioners"` Provisioners provisioner.List `json:"provisioners"`
Template *x509util.ASN1DN `json:"template,omitempty"` Template *ASN1DN `json:"template,omitempty"`
Claims *provisioner.Claims `json:"claims,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"`
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
Backdate *provisioner.Duration `json:"backdate,omitempty"` Backdate *provisioner.Duration `json:"backdate,omitempty"`
@ -82,7 +92,7 @@ func (c *AuthConfig) init() {
c.Provisioners = provisioner.List{} c.Provisioners = provisioner.List{}
} }
if c.Template == nil { if c.Template == nil {
c.Template = &x509util.ASN1DN{} c.Template = &ASN1DN{}
} }
if c.Backdate == nil { if c.Backdate == nil {
c.Backdate = &provisioner.Duration{ c.Backdate = &provisioner.Duration{

View file

@ -7,15 +7,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/cli/crypto/tlsutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/x509util"
stepJOSE "github.com/smallstep/cli/jose"
) )
func TestConfigValidate(t *testing.T) { 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) 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) assert.FatalError(t, err)
ac := &AuthConfig{ ac := &AuthConfig{
Provisioners: provisioner.List{ Provisioners: provisioner.List{
@ -35,7 +33,7 @@ func TestConfigValidate(t *testing.T) {
type ConfigValidateTest struct { type ConfigValidateTest struct {
config *Config config *Config
err error err error
tls tlsutil.TLSOptions tls TLSOptions
} }
tests := map[string]func(*testing.T) ConfigValidateTest{ tests := map[string]func(*testing.T) ConfigValidateTest{
"empty-address": func(t *testing.T) ConfigValidateTest { "empty-address": func(t *testing.T) ConfigValidateTest {
@ -141,7 +139,7 @@ func TestConfigValidate(t *testing.T) {
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
TLS: &tlsutil.TLSOptions{}, TLS: &TLSOptions{},
}, },
tls: DefaultTLSOptions, tls: DefaultTLSOptions,
} }
@ -156,8 +154,8 @@ func TestConfigValidate(t *testing.T) {
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
TLS: &tlsutil.TLSOptions{ TLS: &TLSOptions{
CipherSuites: x509util.CipherSuites{ CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}, },
MinVersion: 1.0, MinVersion: 1.0,
@ -165,8 +163,8 @@ func TestConfigValidate(t *testing.T) {
Renegotiation: true, Renegotiation: true,
}, },
}, },
tls: tlsutil.TLSOptions{ tls: TLSOptions{
CipherSuites: x509util.CipherSuites{ CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}, },
MinVersion: 1.0, MinVersion: 1.0,
@ -185,8 +183,8 @@ func TestConfigValidate(t *testing.T) {
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
TLS: &tlsutil.TLSOptions{ TLS: &TLSOptions{
CipherSuites: x509util.CipherSuites{ CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}, },
MinVersion: 1.2, MinVersion: 1.2,
@ -217,7 +215,7 @@ func TestConfigValidate(t *testing.T) {
} }
func TestAuthConfigValidate(t *testing.T) { func TestAuthConfigValidate(t *testing.T) {
asn1dn := x509util.ASN1DN{ asn1dn := ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -226,9 +224,9 @@ func TestAuthConfigValidate(t *testing.T) {
CommonName: "test", CommonName: "test",
} }
maxjwk, err := stepJOSE.ParseKey("testdata/secrets/max_pub.jwk") maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
p := provisioner.List{ p := provisioner.List{
&provisioner.JWK{ &provisioner.JWK{
@ -245,7 +243,7 @@ func TestAuthConfigValidate(t *testing.T) {
type AuthConfigValidateTest struct { type AuthConfigValidateTest struct {
ac *AuthConfig ac *AuthConfig
asn1dn x509util.ASN1DN asn1dn ASN1DN
err error err error
} }
tests := map[string]func(*testing.T) AuthConfigValidateTest{ tests := map[string]func(*testing.T) AuthConfigValidateTest{
@ -258,7 +256,7 @@ func TestAuthConfigValidate(t *testing.T) {
"ok-empty-provisioners": func(t *testing.T) AuthConfigValidateTest { "ok-empty-provisioners": func(t *testing.T) AuthConfigValidateTest {
return AuthConfigValidateTest{ return AuthConfigValidateTest{
ac: &AuthConfig{}, ac: &AuthConfig{},
asn1dn: x509util.ASN1DN{}, asn1dn: ASN1DN{},
} }
}, },
"ok-empty-asn1dn-template": func(t *testing.T) AuthConfigValidateTest { "ok-empty-asn1dn-template": func(t *testing.T) AuthConfigValidateTest {
@ -266,7 +264,7 @@ func TestAuthConfigValidate(t *testing.T) {
ac: &AuthConfig{ ac: &AuthConfig{
Provisioners: p, Provisioners: p,
}, },
asn1dn: x509util.ASN1DN{}, asn1dn: ASN1DN{},
} }
}, },
"ok-custom-asn1dn": func(t *testing.T) AuthConfigValidateTest { "ok-custom-asn1dn": func(t *testing.T) AuthConfigValidateTest {

View file

@ -10,7 +10,6 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/sshutil"
"golang.org/x/crypto/ssh" "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 // WithSSHGetHosts sets a custom function to get the bastion for a
// given user-host pair. // 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 { return func(a *Authority) error {
a.sshGetHostsFunc = fn a.sshGetHostsFunc = fn
return nil return nil

View file

@ -17,7 +17,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -579,35 +580,44 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
} }
doc := claims.document doc := claims.document
signOptions := []SignOption{}
signOptions := []SignOption{ // Enforce host certificate.
// set the key id to the instance id defaults := SignSSHOptions{
sshCertKeyIDModifier(doc.InstanceID), CertType: SSHHostCert,
}
// 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. // Only enforce known principals if disable custom sans is true.
var principals []string
if p.DisableCustomSANs { if p.DisableCustomSANs {
principals = []string{ defaults.Principals = principals
doc.PrivateIP, } else {
fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region), // Check that at least one principal is sent in the request.
} signOptions = append(signOptions, &sshCertOptionsRequireValidator{
Principals: true,
})
} }
// Default to cert type to host // Certificate templates.
defaults := SignSSHOptions{ data := sshutil.CreateTemplateData(sshutil.HostCert, doc.InstanceID, principals)
CertType: SSHHostCert, if v, err := unsafeParseSigned(token); err == nil {
Principals: principals, data.SetToken(v)
} }
// Validate user options templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
signOptions = append(signOptions, sshCertOptionsValidator(defaults)) if err != nil {
// Set defaults if not given as user options return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign")
signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) }
signOptions = append(signOptions, templateOptions)
return append(signOptions, return append(signOptions,
// Set the default extensions. // Validate user SignSSHOptions.
&sshDefaultExtensionModifier{}, sshCertOptionsValidator(defaults),
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key

View file

@ -20,7 +20,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestAWS_Getters(t *testing.T) { func TestAWS_Getters(t *testing.T) {
@ -763,6 +763,7 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) {
} else if assert.NotNil(t, got) { } else if assert.NotNil(t, got) {
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer)) cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
if (err != nil) != tt.wantSignErr { if (err != nil) != tt.wantSignErr {
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr) t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
} else { } else {
if tt.wantSignErr { if tt.wantSignErr {

View file

@ -14,7 +14,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -338,30 +339,42 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
} }
signOptions := []SignOption{
// set the key id to the instance name signOptions := []SignOption{}
sshCertKeyIDModifier(name),
// Enforce host certificate.
defaults := SignSSHOptions{
CertType: SSHHostCert,
} }
// Validated principals.
principals := []string{name}
// Only enforce known principals if disable custom sans is true. // Only enforce known principals if disable custom sans is true.
var principals []string
if p.DisableCustomSANs { if p.DisableCustomSANs {
principals = []string{name} 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 // Certificate templates.
defaults := SignSSHOptions{ data := sshutil.CreateTemplateData(sshutil.HostCert, name, principals)
CertType: SSHHostCert, if v, err := unsafeParseSigned(token); err == nil {
Principals: principals, data.SetToken(v)
} }
// Validate user options
signOptions = append(signOptions, sshCertOptionsValidator(defaults)) templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
// Set defaults if not given as user options if err != nil {
signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign")
}
signOptions = append(signOptions, templateOptions)
return append(signOptions, return append(signOptions,
// Set the default extensions. // Validate user SignSSHOptions.
&sshDefaultExtensionModifier{}, sshCertOptionsValidator(defaults),
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key

View file

@ -18,7 +18,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestAzure_Getters(t *testing.T) { func TestAzure_Getters(t *testing.T) {

View file

@ -13,7 +13,7 @@ import (
"sync" "sync"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
// DefaultProvisionersLimit is the default limit for listing provisioners. // DefaultProvisionersLimit is the default limit for listing provisioners.

View file

@ -9,7 +9,7 @@ import (
"testing" "testing"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestCollection_Load(t *testing.T) { func TestCollection_Load(t *testing.T) {

View file

@ -15,7 +15,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -378,34 +379,44 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
} }
ce := claims.Google.ComputeEngine ce := claims.Google.ComputeEngine
signOptions := []SignOption{}
signOptions := []SignOption{ // Enforce host certificate.
// set the key id to the instance name defaults := SignSSHOptions{
sshCertKeyIDModifier(ce.InstanceName), CertType: SSHHostCert,
}
// 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. // Only enforce known principals if disable custom sans is true.
var principals []string
if p.DisableCustomSANs { if p.DisableCustomSANs {
principals = []string{ defaults.Principals = principals
fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID), } else {
fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID), // Check that at least one principal is sent in the request.
} signOptions = append(signOptions, &sshCertOptionsRequireValidator{
Principals: true,
})
} }
// Default to host + known hostnames // Certificate templates.
defaults := SignSSHOptions{ data := sshutil.CreateTemplateData(sshutil.HostCert, ce.InstanceName, principals)
CertType: SSHHostCert, if v, err := unsafeParseSigned(token); err == nil {
Principals: principals, data.SetToken(v)
} }
// Validate user options
signOptions = append(signOptions, sshCertOptionsValidator(defaults)) templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate)
// Set defaults if not given as user options if err != nil {
signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign")
}
signOptions = append(signOptions, templateOptions)
return append(signOptions, return append(signOptions,
// Set the default extensions // Validate user SignSSHOptions.
&sshDefaultExtensionModifier{}, sshCertOptionsValidator(defaults),
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key

View file

@ -19,7 +19,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestGCP_Getters(t *testing.T) { func TestGCP_Getters(t *testing.T) {

View file

@ -8,7 +8,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -203,41 +204,54 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
opts := claims.Step.SSH opts := claims.Step.SSH
signOptions := []SignOption{ signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token // validates user's SignSSHOptions with the ones in the token
sshCertOptionsValidator(*opts), sshCertOptionsValidator(*opts),
// validate users's KeyID is the token subject.
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
} }
t := now() // Default template attributes.
// Add modifiers from custom claims certType := sshutil.UserCert
// FIXME: this is also set in the sign method using SSHOptions.Modify. keyID := claims.Subject
principals := []string{claims.Subject}
// Use options in the token.
if opts.CertType != "" { 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 { 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() { if !opts.ValidAfter.IsZero() {
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix())) signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
} }
if !opts.ValidBefore.IsZero() { if !opts.ValidBefore.IsZero() {
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix())) 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, return append(signOptions,
// Set the default extensions.
&sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate that the keyID is equivalent to the token subject.
sshCertKeyIDValidator(claims.Subject),
// Validate public key // Validate public key
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestJWK_Getters(t *testing.T) { 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-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-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{ {"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{
CertType: "user", Principals: []string{"name"}, CertType: "user", Principals: []string{"name"},
}, &SignSSHOptions{ }, &SignSSHOptions{

View file

@ -11,8 +11,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -249,16 +250,27 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
if !p.claimer.IsSSHCAEnabled() { if !p.claimer.IsSSHCAEnabled() {
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()) 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") return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign")
} }
// Default to a user certificate with no principals if not set // Certificate templates.
signOptions := []SignOption{sshCertDefaultsModifier{CertType: SSHUserCert}} // 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, return append(signOptions,
// Set the default extensions. // Require type, key-id and principals in the SignSSHOptions.
&sshDefaultExtensionModifier{}, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true},
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{p.claimer}, &sshDefaultDuration{p.claimer},
// Validate public key // Validate public key

View file

@ -10,7 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func TestK8sSA_Getters(t *testing.T) { func TestK8sSA_Getters(t *testing.T) {
@ -361,9 +361,9 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
tot := 0 tot := 0
for _, o := range opts { for _, o := range opts {
switch v := o.(type) { switch v := o.(type) {
case sshCertDefaultsModifier: case sshCertificateOptionsFunc:
assert.Equals(t, v.CertType, SSHUserCert) case *sshCertOptionsRequireValidator:
case *sshDefaultExtensionModifier: assert.Equals(t, v, &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true})
case *sshCertValidityValidator: case *sshCertValidityValidator:
assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.Claimer, tc.p.claimer)
case *sshDefaultPublicKeyValidator: case *sshDefaultPublicKeyValidator:

View file

@ -10,7 +10,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
const ( const (

View file

@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func Test_newKeyStore(t *testing.T) { func Test_newKeyStore(t *testing.T) {

View file

@ -13,7 +13,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -369,10 +370,6 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
if claims.Email == "" { if claims.Email == "" {
return nil, errs.Unauthorized("oidc.AuthorizeSSHSign: failed to validate oidc token payload: email not found") 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 // Get the identity using either the default identityFunc or one injected
// externally. // externally.
@ -380,25 +377,52 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign")
} }
defaults := SignSSHOptions{
CertType: SSHUserCert, // Certificate templates.
Principals: iden.Usernames, 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. // 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 // Non-admin users can only use principals returned by the identityFunc, and
// can only sign user certificates. // can only sign user certificates.
if !o.IsAdmin(claims.Email) { if isAdmin {
signOptions = append(signOptions, sshCertOptionsValidator(defaults)) 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, return append(signOptions,
// Set the default extensions
&sshDefaultExtensionModifier{},
// Set the validity bounds if not set. // Set the validity bounds if not set.
&sshDefaultDuration{o.claimer}, &sshDefaultDuration{o.claimer},
// Validate public key // Validate public key

View file

@ -15,7 +15,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
) )
func Test_openIDConfiguration_Validate(t *testing.T) { 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-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-user", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false},
{"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub}, {"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}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub}, {"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}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{}, pub}, {"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{}, pub},
&SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"}, &SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-options", p1, args{t1, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, {"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}, 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", KeyID: "root@example.com", Principals: []string{"root", "root@example.com"}}, pub},
{"admin-user", p3, args{okAdmin, SignSSHOptions{CertType: "user"}, pub}, expectedAdminOptions, http.StatusOK, false, false}, expectedAdminOptions, http.StatusOK, false, false},
{"admin-principals", p3, args{okAdmin, SignSSHOptions{Principals: []string{"root"}}, pub}, {"admin-host", p3, args{okAdmin, SignSSHOptions{CertType: "host", KeyID: "smallstep.com", Principals: []string{"smallstep.com"}}, 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},
expectedHostOptions, http.StatusOK, false, false}, 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-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-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-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-email", p3, args{failEmail, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
{"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, 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}, {"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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -25,9 +25,10 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option {
// each provisioner. // each provisioner.
type Options struct { type Options struct {
X509 *X509Options `json:"x509,omitempty"` 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 { func (o *Options) GetX509Options() *X509Options {
if o == nil { if o == nil {
return nil return nil
@ -35,18 +36,26 @@ func (o *Options) GetX509Options() *X509Options {
return o.X509 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. // X509Options contains specific options for X.509 certificates.
type X509Options struct { type X509Options struct {
// Template contains a X.509 certificate template. It can be a JSON template // 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. // 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 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 // TemplateData is a JSON object with variables that can be used in custom
// templates. // templates.
TemplateData json.RawMessage `json:"templateData"` TemplateData json.RawMessage `json:"templateData,omitempty"`
} }
// HasTemplate returns true if a template is defined in the provisioner options. // HasTemplate returns true if a template is defined in the provisioner options.

View file

@ -7,7 +7,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "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) { func TestProvisionerX509Options_HasTemplate(t *testing.T) {
type fields struct { type fields struct {
Template string Template string

View file

@ -326,7 +326,14 @@ func (b *base) AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certif
// Identity is the type representing an externally supplied identity that is used // Identity is the type representing an externally supplied identity that is used
// by provisioners to populate certificate fields. // by provisioners to populate certificate fields.
type Identity struct { type Identity struct {
Usernames []string `json:"usernames"` 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. // GetIdentityFunc is a function that returns an identity.

View file

@ -12,7 +12,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func Test_emailOnlyIdentity_Valid(t *testing.T) { 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) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
assert.FatalError(t, tt.pdd.Modify(tt.cert, tt.so), "unexpected error") 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) tt.valid(tt.cert)
}) })
} }

View file

@ -3,11 +3,12 @@ package provisioner
import ( import (
"crypto/rsa" "crypto/rsa"
"encoding/binary" "encoding/binary"
"encoding/json"
"math/big" "math/big"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/keys" "go.step.sm/crypto/keyutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -23,14 +24,7 @@ const (
// certificate. // certificate.
type SSHCertModifier interface { type SSHCertModifier interface {
SignOption SignOption
Modify(cert *ssh.Certificate) error Modify(cert *ssh.Certificate, opts SignSSHOptions) error
}
// SSHCertOptionModifier is the interface used to add custom options used
// to modify the SSH certificate.
type SSHCertOptionModifier interface {
SignOption
Option(o SignSSHOptions) SSHCertModifier
} }
// SSHCertValidator is the interface used to validate an SSH certificate. // SSHCertValidator is the interface used to validate an SSH certificate.
@ -46,22 +40,23 @@ type SSHCertOptionsValidator interface {
Valid(got SignSSHOptions) error 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. // SignSSHOptions contains the options that can be passed to the SignSSH method.
type SignSSHOptions struct { type SignSSHOptions struct {
CertType string `json:"certType"` CertType string `json:"certType"`
KeyID string `json:"keyID"` KeyID string `json:"keyID"`
Principals []string `json:"principals"` Principals []string `json:"principals"`
ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidAfter TimeDuration `json:"validAfter,omitempty"`
ValidBefore TimeDuration `json:"validBefore,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"`
Backdate time.Duration `json:"-"` 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. // Type returns the uint32 representation of the CertType.
@ -70,7 +65,7 @@ func (o SignSSHOptions) Type() uint32 {
} }
// Modify implements SSHCertModifier and sets the SSHOption in the ssh.Certificate. // 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 { switch o.CertType {
case "": // ignore case "": // ignore
case SSHUserCert: case SSHUserCert:
@ -84,6 +79,12 @@ func (o SignSSHOptions) Modify(cert *ssh.Certificate) error {
cert.KeyId = o.KeyID cert.KeyId = o.KeyID
cert.ValidPrincipals = o.Principals 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() t := now()
if !o.ValidAfter.IsZero() { if !o.ValidAfter.IsZero() {
cert.ValidAfter = uint64(o.ValidAfter.RelativeTime(t).Unix()) 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 { 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 errors.New("ssh certificate valid after cannot be greater than valid before")
} }
return nil return nil
} }
@ -121,7 +121,7 @@ func (o SignSSHOptions) match(got SignSSHOptions) error {
type sshCertPrincipalsModifier []string type sshCertPrincipalsModifier []string
// Modify the ValidPrincipals value of the cert. // 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) cert.ValidPrincipals = []string(o)
return nil return nil
} }
@ -130,7 +130,7 @@ func (o sshCertPrincipalsModifier) Modify(cert *ssh.Certificate) error {
// Key ID in the SSH certificate. // Key ID in the SSH certificate.
type sshCertKeyIDModifier string type sshCertKeyIDModifier string
func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate) error { func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.KeyId = string(m) cert.KeyId = string(m)
return nil return nil
} }
@ -140,7 +140,7 @@ func (m sshCertKeyIDModifier) Modify(cert *ssh.Certificate) error {
type sshCertTypeModifier string type sshCertTypeModifier string
// Modify sets the CertType for the ssh certificate. // 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)) cert.CertType = sshCertTypeUInt32(string(m))
return nil return nil
} }
@ -149,7 +149,7 @@ func (m sshCertTypeModifier) Modify(cert *ssh.Certificate) error {
// ValidAfter in the SSH certificate. // ValidAfter in the SSH certificate.
type sshCertValidAfterModifier uint64 type sshCertValidAfterModifier uint64
func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate) error { func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.ValidAfter = uint64(m) cert.ValidAfter = uint64(m)
return nil return nil
} }
@ -158,7 +158,7 @@ func (m sshCertValidAfterModifier) Modify(cert *ssh.Certificate) error {
// ValidBefore in the SSH certificate. // ValidBefore in the SSH certificate.
type sshCertValidBeforeModifier uint64 type sshCertValidBeforeModifier uint64
func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate) error { func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
cert.ValidBefore = uint64(m) cert.ValidBefore = uint64(m)
return nil return nil
} }
@ -168,7 +168,7 @@ func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate) error {
type sshCertDefaultsModifier SignSSHOptions type sshCertDefaultsModifier SignSSHOptions
// Modify implements the SSHCertModifier interface. // 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 { if cert.CertType == 0 {
cert.CertType = sshCertTypeUInt32(m.CertType) cert.CertType = sshCertTypeUInt32(m.CertType)
} }
@ -188,7 +188,7 @@ func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error {
// the default extensions in an SSH certificate. // the default extensions in an SSH certificate.
type sshDefaultExtensionModifier struct{} type sshDefaultExtensionModifier struct{}
func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate) error { func (m *sshDefaultExtensionModifier) Modify(cert *ssh.Certificate, _ SignSSHOptions) error {
switch cert.CertType { switch cert.CertType {
// Default to no extensions for HostCert. // Default to no extensions for HostCert.
case ssh.HostCert: case ssh.HostCert:
@ -215,27 +215,27 @@ type sshDefaultDuration struct {
*Claimer *Claimer
} }
func (m *sshDefaultDuration) Option(o SignSSHOptions) SSHCertModifier { // Modify implements SSHCertModifier and sets the validity if it has not been
return sshModifierFunc(func(cert *ssh.Certificate) error { // set, but it always applies the backdate.
d, err := m.DefaultSSHCertDuration(cert.CertType) func (m *sshDefaultDuration) Modify(cert *ssh.Certificate, o SignSSHOptions) error {
if err != nil { d, err := m.DefaultSSHCertDuration(cert.CertType)
return err if err != nil {
} return err
}
var backdate uint64 var backdate uint64
if cert.ValidAfter == 0 { if cert.ValidAfter == 0 {
backdate = uint64(o.Backdate / time.Second) backdate = uint64(o.Backdate / time.Second)
cert.ValidAfter = uint64(now().Truncate(time.Second).Unix()) cert.ValidAfter = uint64(now().Truncate(time.Second).Unix())
} }
if cert.ValidBefore == 0 { if cert.ValidBefore == 0 {
cert.ValidBefore = cert.ValidAfter + uint64(d/time.Second) cert.ValidBefore = cert.ValidAfter + uint64(d/time.Second)
} }
// Apply backdate safely // Apply backdate safely
if cert.ValidAfter > backdate { if cert.ValidAfter > backdate {
cert.ValidAfter -= backdate cert.ValidAfter -= backdate
} }
return nil return nil
})
} }
// sshLimitDuration adjusts the duration to min(default, remaining provisioning // sshLimitDuration adjusts the duration to min(default, remaining provisioning
@ -248,51 +248,52 @@ type sshLimitDuration struct {
NotAfter time.Time 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() { if m.NotAfter.IsZero() {
defaultDuration := &sshDefaultDuration{m.Claimer} 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) d, err := m.DefaultSSHCertDuration(cert.CertType)
if err != nil { if err != nil {
return err return err
} }
var backdate uint64 var backdate uint64
if cert.ValidAfter == 0 { if cert.ValidAfter == 0 {
backdate = uint64(o.Backdate / time.Second) backdate = uint64(o.Backdate / time.Second)
cert.ValidAfter = uint64(now().Truncate(time.Second).Unix()) cert.ValidAfter = uint64(now().Truncate(time.Second).Unix())
} }
certValidAfter := time.Unix(int64(cert.ValidAfter), 0) certValidAfter := time.Unix(int64(cert.ValidAfter), 0)
if certValidAfter.After(m.NotAfter) { if certValidAfter.After(m.NotAfter) {
return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validAfter (%s)", return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validAfter (%s)",
m.NotAfter, certValidAfter) m.NotAfter, certValidAfter)
} }
if cert.ValidBefore == 0 { if cert.ValidBefore == 0 {
certValidBefore := certValidAfter.Add(d) certValidBefore := certValidAfter.Add(d)
if m.NotAfter.Before(certValidBefore) { if m.NotAfter.Before(certValidBefore) {
certValidBefore = m.NotAfter certValidBefore = m.NotAfter
}
cert.ValidBefore = uint64(certValidBefore.Unix())
} else {
certValidBefore := time.Unix(int64(cert.ValidBefore), 0)
if m.NotAfter.Before(certValidBefore) {
return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validBefore (%s)",
m.NotAfter, certValidBefore)
}
} }
cert.ValidBefore = uint64(certValidBefore.Unix())
// Apply backdate safely } else {
if cert.ValidAfter > backdate { certValidBefore := time.Unix(int64(cert.ValidBefore), 0)
cert.ValidAfter -= backdate if m.NotAfter.Before(certValidBefore) {
return errors.Errorf("provisioning credential expiration (%s) is before requested certificate validBefore (%s)",
m.NotAfter, certValidBefore)
} }
}
return nil // Apply backdate safely
}) if cert.ValidAfter > backdate {
cert.ValidAfter -= backdate
}
return nil
} }
// sshCertOptionsValidator validates the user SSHOptions with the ones // sshCertOptionsValidator validates the user SSHOptions with the ones
@ -306,6 +307,26 @@ func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error {
return want.match(got) 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 { type sshCertValidityValidator struct {
*Claimer *Claimer
} }
@ -354,7 +375,9 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti
// fields in the SSH certificate. // fields in the SSH certificate.
type sshCertDefaultValidator struct{} 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 { func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error {
switch { switch {
case len(cert.Nonce) == 0: 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) return errors.Errorf("ssh certificate has an unknown type: %d", cert.CertType)
case cert.KeyId == "": case cert.KeyId == "":
return errors.New("ssh certificate key id cannot be empty") 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: case cert.ValidAfter == 0:
return errors.New("ssh certificate validAfter cannot be 0") return errors.New("ssh certificate validAfter cannot be 0")
case cert.ValidBefore < uint64(now().Unix()): case cert.ValidBefore < uint64(now().Unix()):
return errors.New("ssh certificate validBefore cannot be in the past") return errors.New("ssh certificate validBefore cannot be in the past")
case cert.ValidBefore < cert.ValidAfter: case cert.ValidBefore < cert.ValidAfter:
return errors.New("ssh certificate validBefore cannot be before 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: case cert.SignatureKey == nil:
return errors.New("ssh certificate signature key cannot be nil") return errors.New("ssh certificate signature key cannot be nil")
case cert.Signature == nil: case cert.Signature == nil:
@ -404,9 +423,9 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOpti
if err != nil { if err != nil {
return err 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)", 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 return nil
case ssh.KeyAlgoDSA: 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 // sshCertTypeUInt32
func sshCertTypeUInt32(ct string) uint32 { func sshCertTypeUInt32(ct string) uint32 {
switch ct { switch ct {

View file

@ -1,14 +1,13 @@
package provisioner package provisioner
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/cli/crypto/keys" "go.step.sm/crypto/keyutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -40,7 +39,7 @@ func TestSSHOptions_Type(t *testing.T) {
func TestSSHOptions_Modify(t *testing.T) { func TestSSHOptions_Modify(t *testing.T) {
type test struct { type test struct {
so *SignSSHOptions so SignSSHOptions
cert *ssh.Certificate cert *ssh.Certificate
valid func(*ssh.Certificate) valid func(*ssh.Certificate)
err error err error
@ -48,21 +47,21 @@ func TestSSHOptions_Modify(t *testing.T) {
tests := map[string](func() test){ tests := map[string](func() test){
"fail/unexpected-cert-type": func() test { "fail/unexpected-cert-type": func() test {
return test{ return test{
so: &SignSSHOptions{CertType: "foo"}, so: SignSSHOptions{CertType: "foo"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
err: errors.Errorf("ssh certificate has an unknown type - foo"), err: errors.Errorf("ssh certificate has an unknown type - foo"),
} }
}, },
"fail/validAfter-greater-validBefore": func() test { "fail/validAfter-greater-validBefore": func() test {
return test{ return test{
so: &SignSSHOptions{CertType: "user"}, so: SignSSHOptions{CertType: "user"},
cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)}, cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)},
err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"), err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"),
} }
}, },
"ok/user-cert": func() test { "ok/user-cert": func() test {
return test{ return test{
so: &SignSSHOptions{CertType: "user"}, so: SignSSHOptions{CertType: "user"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
valid: func(cert *ssh.Certificate) { valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.CertType, uint32(ssh.UserCert)) assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
@ -71,7 +70,7 @@ func TestSSHOptions_Modify(t *testing.T) {
}, },
"ok/host-cert": func() test { "ok/host-cert": func() test {
return test{ return test{
so: &SignSSHOptions{CertType: "host"}, so: SignSSHOptions{CertType: "host"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
valid: func(cert *ssh.Certificate) { valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.CertType, uint32(ssh.HostCert)) assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
@ -81,7 +80,7 @@ func TestSSHOptions_Modify(t *testing.T) {
"ok": func() test { "ok": func() test {
va := time.Now().Add(5 * time.Minute) va := time.Now().Add(5 * time.Minute)
vb := time.Now().Add(1 * time.Hour) 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)} ValidAfter: NewTimeDuration(va), ValidBefore: NewTimeDuration(vb)}
return test{ return test{
so: so, so: so,
@ -99,7 +98,7 @@ func TestSSHOptions_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) { if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error()) assert.HasPrefix(t, err.Error(), tc.err.Error())
} }
@ -222,7 +221,7 @@ func Test_sshCertPrincipalsModifier_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) assert.Equals(t, tc.cert.ValidPrincipals, tc.expected)
} }
}) })
@ -248,7 +247,7 @@ func Test_sshCertKeyIDModifier_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) assert.Equals(t, tc.cert.KeyId, tc.expected)
} }
}) })
@ -287,7 +286,7 @@ func Test_sshCertTypeModifier_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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)) 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 { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) assert.Equals(t, tc.cert.ValidAfter, tc.expected)
} }
}) })
@ -375,7 +374,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) tc.valid(tc.cert)
} }
}) })
@ -476,7 +475,7 @@ func Test_sshDefaultExtensionModifier_Modify(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run() 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) { if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error()) 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) { func Test_sshCertDefaultValidator_Valid(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair() pub, _, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
sshPub, err := ssh.NewPublicKey(pub) sshPub, err := ssh.NewPublicKey(pub)
assert.FatalError(t, err) 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}, &ssh.Certificate{Nonce: []byte("foo"), Key: sshPub, Serial: 1, CertType: 1},
errors.New("ssh certificate key id cannot be empty"), 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", "fail/zero-validAfter",
&ssh.Certificate{ &ssh.Certificate{
@ -572,20 +566,6 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
}, },
errors.New("ssh certificate validBefore cannot be before validAfter"), 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", "fail/nil-signature-key",
&ssh.Certificate{ &ssh.Certificate{
@ -656,6 +636,38 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
}, },
nil, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -908,7 +920,7 @@ func Test_sshValidityModifier(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() 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) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) 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) { func Test_sshDefaultDuration_Option(t *testing.T) {
tm, fn := mockNow() tm, fn := mockNow()
defer fn() defer fn()
@ -998,8 +988,7 @@ func Test_sshDefaultDuration_Option(t *testing.T) {
m := &sshDefaultDuration{ m := &sshDefaultDuration{
Claimer: tt.fields.Claimer, Claimer: tt.fields.Claimer,
} }
v := m.Option(tt.args.o) if err := m.Modify(tt.args.cert, tt.args.o); (err != nil) != tt.wantErr {
if err := v.Modify(tt.args.cert); (err != nil) != tt.wantErr {
t.Errorf("sshDefaultDuration.Option() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("sshDefaultDuration.Option() error = %v, wantErr %v", err, tt.wantErr)
} }
if !reflect.DeepEqual(tt.args.cert, tt.want) { 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)
}
})
}
}

View 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
}

View file

@ -2,11 +2,13 @@ package provisioner
import ( import (
"crypto" "crypto"
"crypto/rand"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"time" "time"
"github.com/smallstep/certificates/errs"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -46,16 +48,17 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
} }
var mods []SSHCertModifier var mods []SSHCertModifier
var certOptions []sshutil.Option
var validators []SSHCertValidator var validators []SSHCertValidator
for _, op := range signOpts { for _, op := range signOpts {
switch o := op.(type) { switch o := op.(type) {
// add options to NewCertificate
case SSHCertificateOptions:
certOptions = append(certOptions, o.Options(opts)...)
// modify the ssh.Certificate // modify the ssh.Certificate
case SSHCertModifier: case SSHCertModifier:
mods = append(mods, o) mods = append(mods, o)
// modify the ssh.Certificate given the SSHOptions
case SSHCertOptionModifier:
mods = append(mods, o.Option(opts))
// validate the ssh.Certificate // validate the ssh.Certificate
case SSHCertValidator: case SSHCertValidator:
validators = append(validators, o) 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 // Simulated certificate request with request options.
cert := &ssh.Certificate{ cr := sshutil.CertificateRequest{
Nonce: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, Type: opts.CertType,
Key: pub, KeyID: opts.KeyID,
Serial: 1234567890, Principals: opts.Principals,
Key: pub,
} }
// Use opts to modify the certificate // Create certificate from template.
if err := opts.Modify(cert); err != nil { certificate, err := sshutil.NewCertificate(cr, certOptions...)
return nil, err 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 { for _, m := range mods {
if err := m.Modify(cert); err != nil { if err := m.Modify(cert, opts); err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length. // Sign certificate.
data := cert.Marshal() cert, err = sshutil.CreateCertificate(cert, signer)
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
if err != nil { 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 { for _, v := range validators {
if err := v.Valid(cert, opts); err != nil { if err := v.Valid(cert, opts); err != nil {
return nil, err return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
} }
} }

View file

@ -10,7 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -13,8 +13,8 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -16,9 +16,9 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -9,7 +9,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "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" "go.step.sm/crypto/x509util"
) )
@ -247,16 +248,41 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
signOptions := []SignOption{ signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token // validates user's SSHOptions with the ones in the token
sshCertOptionsValidator(*opts), sshCertOptionsValidator(*opts),
// validate users's KeyID is the token subject.
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
} }
// Add modifiers from custom claims // Default template attributes.
// FIXME: this is also set in the sign method using SSHOptions.Modify. certType := sshutil.UserCert
keyID := claims.Subject
principals := []string{claims.Subject}
// Use options in the token.
if opts.CertType != "" { 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 { 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() t := now()
if !opts.ValidAfter.IsZero() { if !opts.ValidAfter.IsZero() {
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix())) 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() { if !opts.ValidBefore.IsZero() {
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix())) 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, return append(signOptions,
// Set the default extensions.
&sshDefaultExtensionModifier{},
// Checks the validity bounds, and set the validity if has not been set. // Checks the validity bounds, and set the validity if has not been set.
&sshLimitDuration{p.claimer, claims.chains[0][0].NotAfter}, &sshLimitDuration{p.claimer, claims.chains[0][0].NotAfter},
// set the key id to the token subject
sshCertKeyIDValidator(claims.Subject),
// Validate public key. // Validate public key.
&sshDefaultPublicKeyValidator{}, &sshDefaultPublicKeyValidator{},
// Validate the validity period. // Validate the validity period.

View file

@ -9,9 +9,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
) )
func TestX5C_Getters(t *testing.T) { func TestX5C_Getters(t *testing.T) {
@ -154,7 +154,7 @@ M46l92gdOozT
func TestX5C_authorizeToken(t *testing.T) { func TestX5C_authorizeToken(t *testing.T) {
x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
type test struct { type test struct {
@ -402,7 +402,7 @@ lgsqsR63is+0YQ==
func TestX5C_AuthorizeSign(t *testing.T) { func TestX5C_AuthorizeSign(t *testing.T) {
certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
type test struct { type test struct {
@ -518,7 +518,7 @@ func TestX5C_AuthorizeRevoke(t *testing.T) {
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
p, err := generateX5C(nil) p, err := generateX5C(nil)
@ -599,7 +599,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) {
func TestX5C_AuthorizeSSHSign(t *testing.T) { func TestX5C_AuthorizeSSHSign(t *testing.T) {
x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
_, fn := mockNow() _, fn := mockNow()
@ -698,6 +698,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
}, },
Step: &stepPayload{SSH: &SignSSHOptions{ Step: &stepPayload{SSH: &SignSSHOptions{
CertType: SSHHostCert, CertType: SSHHostCert,
KeyID: "foo",
Principals: []string{"max", "mariano", "alan"}, Principals: []string{"max", "mariano", "alan"},
ValidAfter: TimeDuration{d: 5 * time.Minute}, ValidAfter: TimeDuration{d: 5 * time.Minute},
ValidBefore: TimeDuration{d: 10 * 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.Nil(t, tc.err) {
if assert.NotNil(t, opts) { if assert.NotNil(t, opts) {
tot := 0 tot := 0
firstValidator := true
nw := now() nw := now()
for _, o := range opts { for _, o := range opts {
switch v := o.(type) { switch v := o.(type) {
case sshCertOptionsValidator: case sshCertOptionsValidator:
tc.claims.Step.SSH.ValidAfter.t = time.Time{} tc.claims.Step.SSH.ValidAfter.t = time.Time{}
tc.claims.Step.SSH.ValidBefore.t = time.Time{} tc.claims.Step.SSH.ValidBefore.t = time.Time{}
assert.Equals(t, SignSSHOptions(v), *tc.claims.Step.SSH) if firstValidator {
case sshCertKeyIDModifier: assert.Equals(t, SignSSHOptions(v), *tc.claims.Step.SSH)
assert.Equals(t, string(v), "foo") } else {
case sshCertTypeModifier: assert.Equals(t, SignSSHOptions(v), SignSSHOptions{KeyID: tc.claims.Subject})
assert.Equals(t, string(v), tc.claims.Step.SSH.CertType) }
case sshCertPrincipalsModifier: firstValidator = false
assert.Equals(t, []string(v), tc.claims.Step.SSH.Principals)
case sshCertValidAfterModifier: case sshCertValidAfterModifier:
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix()) assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix())
case sshCertValidBeforeModifier: case sshCertValidBeforeModifier:
@ -777,19 +778,16 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter) assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter)
case *sshCertValidityValidator: case *sshCertValidityValidator:
assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.Claimer, tc.p.claimer)
case *sshDefaultExtensionModifier, *sshDefaultPublicKeyValidator, case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc:
*sshCertDefaultValidator:
case sshCertKeyIDValidator:
assert.Equals(t, string(v), "foo")
default: default:
assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v)) assert.FatalError(t, errors.Errorf("unexpected sign option of type %T", v))
} }
tot++ tot++
} }
if len(tc.claims.Step.SSH.CertType) > 0 { if len(tc.claims.Step.SSH.CertType) > 0 {
assert.Equals(t, tot, 13)
} else {
assert.Equals(t, tot, 9) assert.Equals(t, tot, 9)
} else {
assert.Equals(t, tot, 7)
} }
} }
} }

View file

@ -9,7 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestRoot(t *testing.T) { func TestRoot(t *testing.T) {

View file

@ -13,10 +13,10 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -51,6 +51,21 @@ type Bastion struct {
Flags string `json:"flags,omitempty"` 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. // Validate checks the fields in SSHConfig.
func (c *SSHConfig) Validate() error { func (c *SSHConfig) Validate() error {
if c == nil { 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. // 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) { func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var mods []provisioner.SSHCertModifier var (
var validators []provisioner.SSHCertValidator 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 // Set backdate with the configured value
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
for _, op := range signOpts { for _, op := range signOpts {
switch o := op.(type) { switch o := op.(type) {
// add options to NewCertificate
case provisioner.SSHCertificateOptions:
certOptions = append(certOptions, o.Options(opts)...)
// modify the ssh.Certificate // modify the ssh.Certificate
case provisioner.SSHCertModifier: case provisioner.SSHCertModifier:
mods = append(mods, o) mods = append(mods, o)
// modify the ssh.Certificate given the SSHOptions
case provisioner.SSHCertOptionModifier:
mods = append(mods, o.Option(opts))
// validate the ssh.Certificate // validate the ssh.Certificate
case provisioner.SSHCertValidator: case provisioner.SSHCertValidator:
validators = append(validators, o) validators = append(validators, o)
// validate the given SSHOptions // validate the given SSHOptions
case provisioner.SSHCertOptionsValidator: case provisioner.SSHCertOptionsValidator:
if err := o.Valid(opts); err != nil { 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: 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) // Simulated certificate request with request options.
cr := sshutil.CertificateRequest{
Type: opts.CertType,
KeyID: opts.KeyID,
Principals: opts.Principals,
Key: key,
}
// Create certificate from template.
certificate, err := sshutil.NewCertificate(cr, certOptions...)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH") 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")
} }
var serial uint64 // Get actual *ssh.Certificate and continue with provisioner modifiers.
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil { certTpl := certificate.GetCertificate()
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error reading random number")
// 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")
} }
// Build base certificate with the key and some random values // Use provisioner modifiers.
cert := &ssh.Certificate{
Nonce: []byte(nonce),
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")
}
// Use provisioner modifiers
for _, m := range mods { for _, m := range mods {
if err := m.Modify(cert); err != nil { if err := m.Modify(certTpl, opts); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH") return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
} }
} }
// Get signer from authority keys // Get signer from authority keys
var signer ssh.Signer var signer ssh.Signer
switch cert.CertType { switch certTpl.CertType {
case ssh.UserCert: case ssh.UserCert:
if a.sshCAUserCertSignKey == nil { 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 signer = a.sshCAUserCertSignKey
case ssh.HostCert: case ssh.HostCert:
if a.sshCAHostCertSignKey == nil { 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 signer = a.sshCAHostCertSignKey
default: 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. // Sign certificate.
data := cert.Marshal() cert, err := sshutil.CreateCertificate(certTpl, signer)
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
if err != nil { 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 { for _, v := range validators {
if err := v.Valid(cert, opts); err != nil { 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 { 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 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. // 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) { 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 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("rewnewSSH: cannot renew certificate without validity period") 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) va := now.Add(-1 * backdate)
vb := now.Add(duration - backdate) vb := now.Add(duration - backdate)
// Build base certificate with the key and some random values // Build base certificate with the old key.
cert := &ssh.Certificate{ // Nonce and serial will be automatically generated on signing.
Nonce: []byte(nonce), certTpl := &ssh.Certificate{
Key: oldCert.Key, Key: oldCert.Key,
Serial: serial,
CertType: oldCert.CertType, CertType: oldCert.CertType,
KeyId: oldCert.KeyId, KeyId: oldCert.KeyId,
ValidPrincipals: oldCert.ValidPrincipals, ValidPrincipals: oldCert.ValidPrincipals,
Permissions: oldCert.Permissions, Permissions: oldCert.Permissions,
Reserved: oldCert.Reserved,
ValidAfter: uint64(va.Unix()), ValidAfter: uint64(va.Unix()),
ValidBefore: uint64(vb.Unix()), ValidBefore: uint64(vb.Unix()),
} }
// Get signer from authority keys // Get signer from authority keys
var signer ssh.Signer var signer ssh.Signer
switch cert.CertType { switch certTpl.CertType {
case ssh.UserCert: case ssh.UserCert:
if a.sshCAUserCertSignKey == nil { if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled") 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 signer = a.sshCAHostCertSignKey
default: 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. // Sign certificate.
data := cert.Marshal() cert, err := sshutil.CreateCertificate(certTpl, signer)
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
if err != nil { 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 { if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") 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 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("rekeySSH; cannot rekey certificate without validity period") 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) va := now.Add(-1 * backdate)
vb := now.Add(duration - 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{ cert := &ssh.Certificate{
Nonce: []byte(nonce),
Key: pub, Key: pub,
Serial: serial,
CertType: oldCert.CertType, CertType: oldCert.CertType,
KeyId: oldCert.KeyId, KeyId: oldCert.KeyId,
ValidPrincipals: oldCert.ValidPrincipals, ValidPrincipals: oldCert.ValidPrincipals,
Permissions: oldCert.Permissions, Permissions: oldCert.Permissions,
Reserved: oldCert.Reserved,
ValidAfter: uint64(va.Unix()), ValidAfter: uint64(va.Unix()),
ValidBefore: uint64(vb.Unix()), ValidBefore: uint64(vb.Unix()),
} }
@ -438,18 +440,13 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
default: default:
return nil, errs.BadRequest("rekeySSH; unexpected ssh certificate type: %d", cert.CertType) return nil, errs.BadRequest("rekeySSH; unexpected ssh certificate type: %d", cert.CertType)
} }
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length. var err error
data := cert.Marshal() // Sign certificate.
data = data[:len(data)-4] cert, err = sshutil.CreateCertificate(cert, signer)
// Sign the certificate.
sig, err := signer.Sign(rand.Reader, data)
if err != nil { 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. // Apply validators from provisioner.
for _, v := range validators { 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. // 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 { if a.sshGetHostsFunc != nil {
hosts, err := a.sshGetHostsFunc(ctx, cert) hosts, err := a.sshGetHostsFunc(ctx, cert)
return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts") 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") return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
} }
hosts := make([]sshutil.Host, len(hostnames)) hosts := make([]Host, len(hostnames))
for i, hn := range hostnames { for i, hn := range hostnames {
hosts[i] = sshutil.Host{Hostname: hn} hosts[i] = Host{Hostname: hn}
} }
return hosts, nil return hosts, nil
} }

View file

@ -18,15 +18,15 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "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" "golang.org/x/crypto/ssh"
) )
type sshTestModifier ssh.Certificate 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 { if m.CertType != 0 {
cert.CertType = m.CertType cert.CertType = m.CertType
} }
@ -53,7 +53,7 @@ func (m sshTestModifier) Modify(cert *ssh.Certificate) error {
type sshTestCertModifier string type sshTestCertModifier string
func (m sshTestCertModifier) Modify(cert *ssh.Certificate) error { func (m sshTestCertModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
if m == "" { if m == "" {
return nil return nil
} }
@ -80,8 +80,11 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
type sshTestOptionsModifier string type sshTestOptionsModifier string
func (m sshTestOptionsModifier) Option(opts provisioner.SignSSHOptions) provisioner.SSHCertModifier { func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
return sshTestCertModifier(string(m)) if m == "" {
return nil
}
return fmt.Errorf(string(m))
} }
func TestAuthority_SignSSH(t *testing.T) { func TestAuthority_SignSSH(t *testing.T) {
@ -101,6 +104,29 @@ func TestAuthority_SignSSH(t *testing.T) {
CertType: ssh.HostCert, 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() now := time.Now()
type fields struct { type fields struct {
@ -125,27 +151,29 @@ func TestAuthority_SignSSH(t *testing.T) {
want want want want
wantErr bool wantErr bool
}{ }{
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false}, {"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{hostOptions}}, want{CertType: ssh.HostCert}, 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{}}, want{CertType: ssh.UserCert}, 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{}}, want{CertType: ssh.HostCert}, 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{}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, 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{}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, 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{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, 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{}}, want{CertType: ssh.HostCert, ValidBefore: 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{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, 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{userOptions, sshTestCertModifier("")}}, 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{userOptions, sshTestOptionsValidator("")}}, 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{userOptions, sshTestOptionsModifier("")}}, 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},
{"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true}, {"ok-custom-template", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userCustomTemplate, userOptions}}, want{CertType: ssh.UserCert, Principals: []string{"user", "admin"}}, false},
{"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, want{}, true}, {"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{userTemplate}}, want{}, true},
{"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("an error")}}, want{}, true}, {"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertValidator("an error")}}, want{}, true},
{"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("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-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("an error")}}, want{}, true}, {"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("an error")}}, want{}, true},
{"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, want{}, true}, {"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("an error")}}, want{}, true},
{"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{}, true}, {"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, "wrong type"}}, want{}, true},
{"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{}, true}, {"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{}, true},
{"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -678,17 +706,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
a := testAuthority(t) a := testAuthority(t)
type test struct { type test struct {
getHostsFunc func(context.Context, *x509.Certificate) ([]sshutil.Host, error) getHostsFunc func(context.Context, *x509.Certificate) ([]Host, error)
auth *Authority auth *Authority
cert *x509.Certificate cert *x509.Certificate
cmp func(got []sshutil.Host) cmp func(got []Host)
err error err error
code int code int
} }
tests := map[string]func(t *testing.T) *test{ tests := map[string]func(t *testing.T) *test{
"fail/getHostsFunc-fail": func(t *testing.T) *test { "fail/getHostsFunc-fail": func(t *testing.T) *test {
return &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") return nil, errors.New("force")
}, },
cert: &x509.Certificate{}, cert: &x509.Certificate{},
@ -697,17 +725,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
} }
}, },
"ok/getHostsFunc-defined": func(t *testing.T) *test { "ok/getHostsFunc-defined": func(t *testing.T) *test {
hosts := []sshutil.Host{ hosts := []Host{
{HostID: "1", Hostname: "foo"}, {HostID: "1", Hostname: "foo"},
{HostID: "2", Hostname: "bar"}, {HostID: "2", Hostname: "bar"},
} }
return &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 hosts, nil return hosts, nil
}, },
cert: &x509.Certificate{}, cert: &x509.Certificate{},
cmp: func(got []sshutil.Host) { cmp: func(got []Host) {
assert.Equals(t, got, hosts) assert.Equals(t, got, hosts)
}, },
} }
@ -732,8 +760,8 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
}, },
})), })),
cert: &x509.Certificate{}, cert: &x509.Certificate{},
cmp: func(got []sshutil.Host) { cmp: func(got []Host) {
assert.Equals(t, got, []sshutil.Host{ assert.Equals(t, got, []Host{
{Hostname: "foo"}, {Hostname: "foo"},
{Hostname: "bar"}, {Hostname: "bar"},
}) })

View file

@ -8,31 +8,28 @@ import (
"encoding/asn1" "encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"fmt"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/tlsutil" "go.step.sm/crypto/keyutil"
x509legacy "github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
// GetTLSOptions returns the tls options configured. // GetTLSOptions returns the tls options configured.
func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions { func (a *Authority) GetTLSOptions() *TLSOptions {
return a.config.TLS return a.config.TLS
} }
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} 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 { return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
if def == nil { if def == nil {
return errors.New("default ASN1DN template cannot be 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) 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 { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) 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 = a.db.StoreCertificate(serverCert); err != nil {
if err != db.ErrNotImplemented { 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("reason", revokeOpts.Reason),
errs.WithKeyVal("passiveOnly", revokeOpts.PassiveOnly), errs.WithKeyVal("passiveOnly", revokeOpts.PassiveOnly),
errs.WithKeyVal("MTLS", revokeOpts.MTLS), errs.WithKeyVal("MTLS", revokeOpts.MTLS),
errs.WithKeyVal("context", fmt.Sprint(provisioner.MethodFromContext(ctx))), errs.WithKeyVal("context", provisioner.MethodFromContext(ctx).String()),
} }
if revokeOpts.MTLS { if revokeOpts.MTLS {
opts = append(opts, errs.WithKeyVal("certificate", base64.StdEncoding.EncodeToString(revokeOpts.Crt.Raw))) 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. // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, fatal := func(err error) (*tls.Certificate, error) {
x509legacy.WithHosts(strings.Join(a.config.DNSNames, ",")))
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
} }
crtBytes, err := profile.CreateCertificate() // Generate default key.
priv, err := keyutil.GenerateDefaultKey()
if err != nil { 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 { 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{ crtPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", 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) intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") return fatal(err)
} }
tlsCrt, err := tls.X509KeyPair(append(crtPEM, keyPEM, err := pemutil.Serialize(priv)
pem.EncodeToMemory(intermediatePEM)...),
pem.EncodeToMemory(keyPEM))
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, return fatal(err)
"authority.GetTLSCertificate; error creating tls certificate")
} }
// Get the 'leaf' certificate and set the attribute accordingly. tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM))
leaf, err := x509.ParseCertificate(tlsCrt.Certificate[0])
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, return fatal(err)
"authority.GetTLSCertificate; error parsing tls certificate")
} }
tlsCrt.Leaf = leaf // Set leaf certificate
tlsCrt.Leaf = cert
return &tlsCrt, nil return &tlsCrt, nil
} }

157
authority/tls_options.go Normal file
View 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,
}
}

View 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)
}
})
}
}

View file

@ -3,6 +3,8 @@ package authority
import ( import (
"context" "context"
"crypto" "crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"crypto/x509" "crypto/x509"
@ -20,11 +22,10 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/keys" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/keyutil"
"github.com/smallstep/cli/crypto/tlsutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/x509util"
"github.com/smallstep/cli/jose"
"gopkg.in/square/go-jose.v2/jwt" "gopkg.in/square/go-jose.v2/jwt"
) )
@ -52,10 +53,73 @@ func (m *certificateDurationEnforcer) Enforce(cert *x509.Certificate) error {
return nil return nil
} }
func withProvisionerOID(name, kid string) x509util.WithOption { func generateCertificate(t *testing.T, commonName string, sans []string, opts ...interface{}) *x509.Certificate {
return func(p x509util.Profile) error { t.Helper()
crt := p.Subject()
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{ b, err := asn1.Marshal(stepProvisionerASN1{
Type: provisionerTypeJWK, Type: provisionerTypeJWK,
Name: []byte(name), Name: []byte(name),
@ -69,11 +133,26 @@ func withProvisionerOID(name, kid string) x509util.WithOption {
Critical: false, Critical: false,
Value: b, Value: b,
}) })
return nil 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 { func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateRequest)) *x509.CertificateRequest {
_csr := &x509.CertificateRequest{ _csr := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: "smallstep test"}, Subject: pkix.Name{CommonName: "smallstep test"},
@ -117,12 +196,12 @@ type basicConstraints struct {
} }
func TestAuthority_Sign(t *testing.T) { func TestAuthority_Sign(t *testing.T) {
pub, priv, err := keys.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
a := testAuthority(t) a := testAuthority(t)
assert.FatalError(t, err) assert.FatalError(t, err)
a.config.AuthorityConfig.Template = &x509util.ASN1DN{ a.config.AuthorityConfig.Template = &ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -140,7 +219,7 @@ func TestAuthority_Sign(t *testing.T) {
// Create a token to get test extra opts. // Create a token to get test extra opts.
p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK) 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) assert.FatalError(t, err)
token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key) token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -517,11 +596,8 @@ ZYtQ9Ot36qc=
} }
func TestAuthority_Renew(t *testing.T) { func TestAuthority_Renew(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err)
a := testAuthority(t) a := testAuthority(t)
a.config.AuthorityConfig.Template = &x509util.ASN1DN{ a.config.AuthorityConfig.Template = &ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -530,13 +606,6 @@ func TestAuthority_Renew(t *testing.T) {
CommonName: "renew", 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() now := time.Now().UTC()
nb1 := now.Add(-time.Minute * 7) nb1 := now.Add(-time.Minute * 7)
na1 := now na1 := now
@ -545,28 +614,17 @@ func TestAuthority_Renew(t *testing.T) {
NotAfter: provisioner.NewTimeDuration(na1), NotAfter: provisioner.NewTimeDuration(na1),
} }
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, cert := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), 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),
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) withSigner(a.x509Issuer, a.x509Signer))
assert.FatalError(t, err)
certBytes, err := leaf.CreateCertificate()
assert.FatalError(t, err)
cert, err := x509.ParseCertificate(certBytes)
assert.FatalError(t, err)
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), withDefaultASN1DN(a.config.AuthorityConfig.Template),
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID),
) withSigner(a.x509Issuer, a.x509Signer))
assert.FatalError(t, err)
certBytesNoRenew, err := leafNoRenew.CreateCertificate()
assert.FatalError(t, err)
certNoRenew, err := x509.ParseCertificate(certBytesNoRenew)
assert.FatalError(t, err)
type renewTest struct { type renewTest struct {
auth *Authority auth *Authority
@ -581,7 +639,7 @@ func TestAuthority_Renew(t *testing.T) {
return &renewTest{ return &renewTest{
auth: _a, auth: _a,
cert: cert, 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, code: http.StatusInternalServerError,
}, nil }, nil
}, },
@ -599,24 +657,12 @@ func TestAuthority_Renew(t *testing.T) {
}, nil }, nil
}, },
"ok/success-new-intermediate": func() (*renewTest, error) { "ok/success-new-intermediate": func() (*renewTest, error) {
newRootProfile, err := x509util.NewRootProfile("new-root") rootCert, rootSigner := generateRootCertificate(t)
assert.FatalError(t, err) intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner)
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)
_a := testAuthority(t) _a := testAuthority(t)
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer) _a.x509Signer = intSigner
_a.x509Issuer = newIntermediateCert _a.x509Issuer = intCert
return &renewTest{ return &renewTest{
auth: _a, auth: _a,
cert: cert, cert: cert,
@ -678,7 +724,7 @@ func TestAuthority_Renew(t *testing.T) {
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
subjectKeyID, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
@ -742,13 +788,11 @@ func TestAuthority_Renew(t *testing.T) {
} }
func TestAuthority_Rekey(t *testing.T) { func TestAuthority_Rekey(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair() pub, _, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err)
pub1, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
a := testAuthority(t) a := testAuthority(t)
a.config.AuthorityConfig.Template = &x509util.ASN1DN{ a.config.AuthorityConfig.Template = &ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -757,13 +801,6 @@ func TestAuthority_Rekey(t *testing.T) {
CommonName: "renew", 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() now := time.Now().UTC()
nb1 := now.Add(-time.Minute * 7) nb1 := now.Add(-time.Minute * 7)
na1 := now na1 := now
@ -772,28 +809,17 @@ func TestAuthority_Rekey(t *testing.T) {
NotAfter: provisioner.NewTimeDuration(na1), NotAfter: provisioner.NewTimeDuration(na1),
} }
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, cert := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), 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),
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) withSigner(a.x509Issuer, a.x509Signer))
assert.FatalError(t, err)
certBytes, err := leaf.CreateCertificate()
assert.FatalError(t, err)
cert, err := x509.ParseCertificate(certBytes)
assert.FatalError(t, err)
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), withDefaultASN1DN(a.config.AuthorityConfig.Template),
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID),
) withSigner(a.x509Issuer, a.x509Signer))
assert.FatalError(t, err)
certBytesNoRenew, err := leafNoRenew.CreateCertificate()
assert.FatalError(t, err)
certNoRenew, err := x509.ParseCertificate(certBytesNoRenew)
assert.FatalError(t, err)
type renewTest struct { type renewTest struct {
auth *Authority auth *Authority
@ -809,7 +835,7 @@ func TestAuthority_Rekey(t *testing.T) {
return &renewTest{ return &renewTest{
auth: _a, auth: _a,
cert: cert, 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, code: http.StatusInternalServerError,
}, nil }, nil
}, },
@ -830,28 +856,16 @@ func TestAuthority_Rekey(t *testing.T) {
return &renewTest{ return &renewTest{
auth: a, auth: a,
cert: cert, cert: cert,
pk: pub1, pk: pub,
}, nil }, nil
}, },
"ok/renew/success-new-intermediate": func() (*renewTest, error) { "ok/renew/success-new-intermediate": func() (*renewTest, error) {
newRootProfile, err := x509util.NewRootProfile("new-root") rootCert, rootSigner := generateRootCertificate(t)
assert.FatalError(t, err) intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner)
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)
_a := testAuthority(t) _a := testAuthority(t)
_a.x509Signer = newIntermediateProfile.SubjectPrivateKey().(crypto.Signer) _a.x509Signer = intSigner
_a.x509Issuer = newIntermediateCert _a.x509Issuer = intCert
return &renewTest{ return &renewTest{
auth: _a, auth: _a,
cert: cert, cert: cert,
@ -989,7 +1003,7 @@ func TestAuthority_Rekey(t *testing.T) {
func TestAuthority_GetTLSOptions(t *testing.T) { func TestAuthority_GetTLSOptions(t *testing.T) {
type renewTest struct { type renewTest struct {
auth *Authority auth *Authority
opts *tlsutil.TLSOptions opts *TLSOptions
} }
tests := map[string]func() (*renewTest, error){ tests := map[string]func() (*renewTest, error){
"default": func() (*renewTest, error) { "default": func() (*renewTest, error) {
@ -998,8 +1012,8 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
}, },
"non-default": func() (*renewTest, error) { "non-default": func() (*renewTest, error) {
a := testAuthority(t) a := testAuthority(t)
a.config.TLS = &tlsutil.TLSOptions{ a.config.TLS = &TLSOptions{
CipherSuites: x509util.CipherSuites{ CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}, },
@ -1029,7 +1043,7 @@ func TestAuthority_Revoke(t *testing.T) {
validAudience := testAudiences.Revoke validAudience := testAudiences.Revoke
now := time.Now().UTC() 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) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, 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["reasonCode"], tc.opts.ReasonCode)
assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason) assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason)
assert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS) 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 { if tc.checkErrDetails != nil {
tc.checkErrDetails(ctxErr) tc.checkErrDetails(ctxErr)

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
acmeAPI "github.com/smallstep/certificates/acme/api" 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. // ACMEClient implements an HTTP client to an ACME API.

View file

@ -16,8 +16,8 @@ import (
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
acmeAPI "github.com/smallstep/certificates/acme/api" acmeAPI "github.com/smallstep/certificates/acme/api"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/pemutil"
) )
func TestNewACMEClient(t *testing.T) { func TestNewACMEClient(t *testing.T) {

View file

@ -8,8 +8,7 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/jose"
"gopkg.in/square/go-jose.v2/jwt"
) )
type tokenClaims struct { type tokenClaims struct {
@ -20,7 +19,7 @@ type tokenClaims struct {
// Bootstrap is a helper function that initializes a client with the // Bootstrap is a helper function that initializes a client with the
// configuration in the bootstrap token. // configuration in the bootstrap token.
func Bootstrap(token string) (*Client, error) { func Bootstrap(token string) (*Client, error) {
tok, err := jwt.ParseSigned(token) tok, err := jose.ParseSigned(token)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error parsing token") return nil, errors.Wrap(err, "error parsing token")
} }

View file

@ -15,10 +15,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/jose"
stepJOSE "github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
) )
func newLocalListener() net.Listener { func newLocalListener() net.Listener {
@ -78,7 +76,7 @@ func startCAServer(configFile string) (*CA, string, error) {
func generateBootstrapToken(ca, subject, sha string) string { func generateBootstrapToken(ca, subject, sha string) string {
now := time.Now() 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 { if err != nil {
panic(err) panic(err)
} }
@ -93,21 +91,21 @@ func generateBootstrapToken(ca, subject, sha string) string {
} }
cl := struct { cl := struct {
SHA string `json:"sha"` SHA string `json:"sha"`
jwt.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
}{ }{
SHA: sha, SHA: sha,
Claims: jwt.Claims{ Claims: jose.Claims{
ID: id, ID: id,
Subject: subject, Subject: subject,
Issuer: "mariano", Issuer: "mariano",
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: []string{ca + "/sign"}, Audience: []string{ca + "/sign"},
}, },
SANS: []string{subject}, SANS: []string{subject},
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -25,13 +25,11 @@ import (
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/keys" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/keyutil"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/randutil"
stepJOSE "github.com/smallstep/cli/jose" "go.step.sm/crypto/x509util"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
) )
type ClosingBuffer struct { type ClosingBuffer struct {
@ -76,10 +74,10 @@ func TestMain(m *testing.M) {
} }
func TestCASign(t *testing.T) { func TestCASign(t *testing.T) {
pub, priv, err := keys.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
asn1dn := &x509util.ASN1DN{ asn1dn := &authority.ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -93,13 +91,9 @@ func TestCASign(t *testing.T) {
config.AuthorityConfig.Template = asn1dn config.AuthorityConfig.Template = asn1dn
ca, err := New(config) ca, err := New(config)
assert.FatalError(t, err) assert.FatalError(t, err)
intermediateCert, err := pemutil.ReadCertificate("testdata/secrets/intermediate_ca.crt")
intermediateIdentity, err := x509util.LoadIdentityFromDisk("testdata/secrets/intermediate_ca.crt",
"testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password")))
assert.FatalError(t, err) assert.FatalError(t, err)
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_priv.jwk",
stepJOSE.WithPassword([]byte("pass")))
assert.FatalError(t, err) assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: clijwk.Key}, sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: clijwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", clijwk.KeyID)) (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", clijwk.KeyID))
@ -181,20 +175,20 @@ ZEp7knvU2psWRw==
jti, err := randutil.ASCII(32) jti, err := randutil.ASCII(32)
assert.FatalError(t, err) assert.FatalError(t, err)
cl := struct { cl := struct {
jwt.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
}{ }{
Claims: jwt.Claims{ Claims: jose.Claims{
Subject: "invalid", Subject: "invalid",
Issuer: "step-cli", Issuer: "step-cli",
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAud, Audience: validAud,
ID: jti, ID: jti,
}, },
SANS: []string{"invalid"}, SANS: []string{"invalid"},
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
csr, err := getCSR(priv) csr, err := getCSR(priv)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -214,20 +208,20 @@ ZEp7knvU2psWRw==
jti, err := randutil.ASCII(32) jti, err := randutil.ASCII(32)
assert.FatalError(t, err) assert.FatalError(t, err)
cl := struct { cl := struct {
jwt.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
}{ }{
Claims: jwt.Claims{ Claims: jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: "step-cli", Issuer: "step-cli",
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAud, Audience: validAud,
ID: jti, ID: jti,
}, },
SANS: []string{"test.smallstep.com"}, 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) assert.FatalError(t, err)
csr, err := getCSR(priv) csr, err := getCSR(priv)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -248,19 +242,19 @@ ZEp7knvU2psWRw==
jti, err := randutil.ASCII(32) jti, err := randutil.ASCII(32)
assert.FatalError(t, err) assert.FatalError(t, err)
cl := struct { cl := struct {
jwt.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
}{ }{
Claims: jwt.Claims{ Claims: jose.Claims{
Subject: "test.smallstep.com", Subject: "test.smallstep.com",
Issuer: "step-cli", Issuer: "step-cli",
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAud, Audience: validAud,
ID: jti, ID: jti,
}, },
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err) assert.FatalError(t, err)
csr, err := getCSR(priv) csr, err := getCSR(priv)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -321,9 +315,9 @@ ZEp7knvU2psWRw==
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) 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.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equals(t, intermediate, realIntermediate)
} else { } else {
@ -555,10 +549,10 @@ func TestCAHealth(t *testing.T) {
} }
func TestCARenew(t *testing.T) { func TestCARenew(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
asn1dn := &x509util.ASN1DN{ asn1dn := &authority.ASN1DN{
Country: "Tazmania", Country: "Tazmania",
Organization: "Acme Co", Organization: "Acme Co",
Locality: "Landscapes", Locality: "Landscapes",
@ -574,8 +568,9 @@ func TestCARenew(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
assert.FatalError(t, err) assert.FatalError(t, err)
intermediateIdentity, err := x509util.LoadIdentityFromDisk("testdata/secrets/intermediate_ca.crt", intermediateCert, err := pemutil.ReadCertificate("testdata/secrets/intermediate_ca.crt")
"testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password"))) assert.FatalError(t, err)
intermediateKey, err := pemutil.Read("testdata/secrets/intermediate_ca_key", pemutil.WithPassword([]byte("password")))
assert.FatalError(t, err) assert.FatalError(t, err)
now := time.Now().UTC() now := time.Now().UTC()
@ -605,15 +600,15 @@ func TestCARenew(t *testing.T) {
} }
}, },
"success": func(t *testing.T) *renewTest { "success": func(t *testing.T) *renewTest {
profile, err := x509util.NewLeafProfile("test", intermediateIdentity.Crt, cr, err := x509util.CreateCertificateRequest("test", []string{"funk"}, priv.(crypto.Signer))
intermediateIdentity.Key, x509util.WithPublicKey(pub),
x509util.WithNotBeforeAfterDuration(now, leafExpiry, 0), x509util.WithHosts("funk"))
assert.FatalError(t, err) assert.FatalError(t, err)
crtBytes, err := profile.CreateCertificate() cert, err := x509util.NewCertificate(cr)
assert.FatalError(t, err) 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) assert.FatalError(t, err)
return &renewTest{ return &renewTest{
ca: ca, ca: ca,
tlsConnState: &tls.ConnectionState{ tlsConnState: &tls.ConnectionState{
@ -661,9 +656,9 @@ func TestCARenew(t *testing.T) {
subjectKeyID, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(pub)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) 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.FatalError(t, err)
assert.Equals(t, intermediate, realIntermediate) assert.Equals(t, intermediate, realIntermediate)

View file

@ -28,9 +28,9 @@ import (
"github.com/smallstep/certificates/ca/identity" "github.com/smallstep/certificates/ca/identity"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/config" "github.com/smallstep/cli/config"
"github.com/smallstep/cli/crypto/keys" "go.step.sm/crypto/keyutil"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"gopkg.in/square/go-jose.v2/jwt" "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 // CreateCertificateRequest creates a new CSR with the given common name and
// SANs. If no san is provided the commonName will set also a SAN. // SANs. If no san is provided the commonName will set also a SAN.
func CreateCertificateRequest(commonName string, sans ...string) (*api.CertificateRequest, crypto.PrivateKey, error) { func CreateCertificateRequest(commonName string, sans ...string) (*api.CertificateRequest, crypto.PrivateKey, error) {
key, err := keys.GenerateDefaultKey() key, err := keyutil.GenerateDefaultKey()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -22,7 +22,7 @@ import (
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -17,7 +17,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/cli/config" "github.com/smallstep/cli/config"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
// Type represents the different types of identity files. // Type represents the different types of identity files.

View file

@ -13,7 +13,7 @@ import (
"testing" "testing"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestLoadDefaultIdentity(t *testing.T) { func TestLoadDefaultIdentity(t *testing.T) {

View file

@ -7,10 +7,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "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"
"github.com/smallstep/cli/token/provision" "github.com/smallstep/cli/token/provision"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil"
) )
const tokenLifetime = 5 * time.Minute const tokenLifetime = 5 * time.Minute

View file

@ -7,13 +7,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/jose"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/pemutil"
"github.com/smallstep/cli/jose" "go.step.sm/crypto/x509util"
) )
func getTestProvisioner(t *testing.T, caURL string) *Provisioner { 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -18,15 +18,13 @@ import (
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/cli/crypto/randutil" "go.step.sm/crypto/jose"
stepJOSE "github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
) )
func generateOTT(subject string) string { func generateOTT(subject string) string {
now := time.Now() 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 { if err != nil {
panic(err) panic(err)
} }
@ -40,20 +38,20 @@ func generateOTT(subject string) string {
panic(err) panic(err)
} }
cl := struct { cl := struct {
jwt.Claims jose.Claims
SANS []string `json:"sans"` SANS []string `json:"sans"`
}{ }{
Claims: jwt.Claims{ Claims: jose.Claims{
ID: id, ID: id,
Subject: subject, Subject: subject,
Issuer: "mariano", Issuer: "mariano",
NotBefore: jwt.NewNumericDate(now), NotBefore: jose.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)), Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: []string{"https://127.0.0.1:0/sign"}, Audience: []string{"https://127.0.0.1:0/sign"},
}, },
SANS: []string{subject}, SANS: []string{subject},
} }
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -16,9 +16,9 @@ import (
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/awskms" "github.com/smallstep/certificates/kms/awskms"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/ui" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils"
"go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -17,9 +17,9 @@ import (
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/cloudkms" "github.com/smallstep/certificates/kms/cloudkms"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/ui" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils"
"go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )

View file

@ -19,9 +19,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/ui" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils"
"go.step.sm/crypto/pemutil"
// Enable yubikey. // Enable yubikey.
_ "github.com/smallstep/certificates/kms/yubikey" _ "github.com/smallstep/certificates/kms/yubikey"

View file

@ -13,11 +13,11 @@ import (
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/pki" "github.com/smallstep/certificates/pki"
"github.com/smallstep/cli/command" "github.com/smallstep/cli/command"
"github.com/smallstep/cli/crypto/randutil"
"github.com/smallstep/cli/errs" "github.com/smallstep/cli/errs"
"github.com/smallstep/cli/ui" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/crypto/randutil"
) )
// defaultOnboardingURL is the production onboarding url, to use a development // defaultOnboardingURL is the production onboarding url, to use a development

11
go.mod
View file

@ -1,6 +1,6 @@
module github.com/smallstep/certificates module github.com/smallstep/certificates
go 1.13 go 1.14
require ( require (
cloud.google.com/go v0.51.0 cloud.google.com/go v0.51.0
@ -19,14 +19,15 @@ require (
github.com/smallstep/cli v0.15.0 github.com/smallstep/cli v0.15.0
github.com/smallstep/nosql v0.3.0 github.com/smallstep/nosql v0.3.0
github.com/urfave/cli v1.22.2 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/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/net v0.0.0-20200202094626-16171245cfb2
google.golang.org/api v0.15.0 google.golang.org/api v0.15.0
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb
google.golang.org/grpc v1.26.0 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/cli => ../cli
//replace github.com/smallstep/nosql => ../nosql // replace github.com/smallstep/nosql => ../nosql
// replace go.step.sm/crypto => ../crypto

6
go.sum
View file

@ -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.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 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E=
go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= 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.3.0 h1:wlbhLw2LvzDYwKcUJS2plBALptMXaKUhI2nbdfsXjTU=
go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww= 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.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.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.5.1/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/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 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.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/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.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/uri" "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. // KMS implements a KMS using AWS Key Management Service.

View file

@ -14,7 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/kms"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {

View file

@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/kms"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
// Signer implements a crypto.Signer using the AWS KMS. // Signer implements a crypto.Signer using the AWS KMS.

View file

@ -13,7 +13,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/kms"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestNewSigner(t *testing.T) { func TestNewSigner(t *testing.T) {

View file

@ -14,7 +14,7 @@ import (
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
"google.golang.org/api/option" "google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
) )

View file

@ -11,7 +11,7 @@ import (
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/certificates/kms/apiv1" "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" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"

View file

@ -5,7 +5,7 @@ import (
"io" "io"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
) )

View file

@ -11,7 +11,7 @@ import (
"testing" "testing"
gax "github.com/googleapis/gax-go/v2" 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" kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
) )

View file

@ -10,8 +10,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
) )
type algorithmAttributes struct { type algorithmAttributes struct {
@ -41,7 +42,7 @@ var generateKey = func(kty, crv string, size int) (interface{}, interface{}, err
if kty == "RSA" && size == 0 { if kty == "RSA" && size == 0 {
size = DefaultRSAKeySize 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. // 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() { 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) { apiv1.Register(apiv1.SoftKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
return New(ctx, opts) 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) { func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm] v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
if !ok { if !ok {
@ -123,6 +129,7 @@ func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespon
}, nil }, nil
} }
// GetPublicKey returns the public key from the file passed in the request name.
func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
v, err := pemutil.Read(req.Name) v, err := pemutil.Read(req.Name)
if err != nil { if err != nil {

View file

@ -16,7 +16,7 @@ import (
"testing" "testing"
"github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {

View file

@ -9,7 +9,7 @@
ifeq (, $(shell which docker)) ifeq (, $(shell which docker))
DOCKER_CLIENT_OS := linux DOCKER_CLIENT_OS := linux
else 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 endif
DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64 DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64

View file

@ -14,6 +14,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
@ -21,14 +22,13 @@ import (
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/config" "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/errs"
"github.com/smallstep/cli/jose"
"github.com/smallstep/cli/ui" "github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils" "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" "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 // GetProvisionerKey returns the encrypted provisioner key with the for the
// given kid. // given kid.
func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { 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. // GenerateRootCertificate generates a root certificate with the given name.
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) {
rootProfile, err := x509util.NewRootProfile(name) signer, err := generateDefaultKey()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass)) cr, err := x509util.CreateCertificateRequest(name, []string{}, signer)
if err != nil { if err != nil {
return nil, nil, err 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 { if err != nil {
return nil, nil, errors.Wrap(err, "error parsing root certificate") return nil, nil, err
} }
sum := sha256.Sum256(rootCrt.Raw) template := cert.GetCertificate()
p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:])) 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. // WriteRootCertificate writes to disk the given certificate and key.
@ -285,7 +307,7 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
return err 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 { if err != nil {
return err return err
} }
@ -299,12 +321,46 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
// GenerateIntermediateCertificate generates an intermediate certificate with // GenerateIntermediateCertificate generates an intermediate certificate with
// the given name. // the given name.
func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { 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 { if err != nil {
return err return err
} }
_, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass))
return err 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 // 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 pubNames = []string{p.sshHostPubKey, p.sshUserPubKey}
var privNames = []string{p.sshHostKey, p.sshUserKey} var privNames = []string{p.sshHostKey, p.sshUserKey}
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
pub, priv, err := keys.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
if err != nil { if err != nil {
return err return err
} }
@ -432,11 +488,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
DisableIssuedAtCheck: false, DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov}, Provisioners: provisioner.List{prov},
}, },
TLS: &tlsutil.TLSOptions{ TLS: &authority.TLSOptions{
MinVersion: x509util.DefaultTLSMinVersion, MinVersion: authority.DefaultTLSMinVersion,
MaxVersion: x509util.DefaultTLSMaxVersion, MaxVersion: authority.DefaultTLSMaxVersion,
Renegotiation: x509util.DefaultTLSRenegotiation, Renegotiation: authority.DefaultTLSRenegotiation,
CipherSuites: x509util.DefaultTLSCipherSuites, CipherSuites: authority.DefaultTLSCipherSuites,
}, },
Templates: p.getTemplates(), Templates: p.getTemplates(),
} }

View file

@ -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"`
}