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"
"github.com/pkg/errors"
"github.com/smallstep/cli/jose"
"github.com/smallstep/nosql"
"go.step.sm/crypto/jose"
)
// Account is a subset of the internal account type containing only those

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,10 +31,8 @@ import (
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"golang.org/x/crypto/ssh"
)
@ -548,7 +546,7 @@ type mockAuthority struct {
ret1, ret2 interface{}
err error
authorizeSign func(ott string) ([]provisioner.SignOption, error)
getTLSOptions func() *tlsutil.TLSOptions
getTLSOptions func() *authority.TLSOptions
root func(shasum string) (*x509.Certificate, error)
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
@ -564,7 +562,7 @@ type mockAuthority struct {
signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
rekeySSH func(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
getSSHHosts func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)
getSSHHosts func(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error)
getSSHRoots func(ctx context.Context) (*authority.SSHKeys, error)
getSSHFederation func(ctx context.Context) (*authority.SSHKeys, error)
getSSHConfig func(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
@ -585,11 +583,11 @@ func (m *mockAuthority) AuthorizeSign(ott string) ([]provisioner.SignOption, err
return m.ret1.([]provisioner.SignOption), m.err
}
func (m *mockAuthority) GetTLSOptions() *tlsutil.TLSOptions {
func (m *mockAuthority) GetTLSOptions() *authority.TLSOptions {
if m.getTLSOptions != nil {
return m.getTLSOptions()
}
return m.ret1.(*tlsutil.TLSOptions)
return m.ret1.(*authority.TLSOptions)
}
func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) {
@ -697,11 +695,11 @@ func (m *mockAuthority) RekeySSH(ctx context.Context, cert *ssh.Certificate, key
return m.ret1.(*ssh.Certificate), m.err
}
func (m *mockAuthority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
func (m *mockAuthority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error) {
if m.getSSHHosts != nil {
return m.getSSHHosts(ctx, cert)
}
return m.ret1.([]sshutil.Host), m.err
return m.ret1.([]authority.Host), m.err
}
func (m *mockAuthority) GetSSHRoots(ctx context.Context) (*authority.SSHKeys, error) {
@ -882,7 +880,7 @@ func Test_caHandler_Sign(t *testing.T) {
authorizeSign: func(ott string) ([]provisioner.SignOption, error) {
return tt.certAttrOpts, tt.autherr
},
getTLSOptions: func() *tlsutil.TLSOptions {
getTLSOptions: func() *authority.TLSOptions {
return nil
},
}).(*caHandler)
@ -933,7 +931,7 @@ func Test_caHandler_Renew(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{
ret1: tt.cert, ret2: tt.root, err: tt.err,
getTLSOptions: func() *tlsutil.TLSOptions {
getTLSOptions: func() *authority.TLSOptions {
return nil
},
}).(*caHandler)
@ -994,7 +992,7 @@ func Test_caHandler_Rekey(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{
ret1: tt.cert, ret2: tt.root, err: tt.err,
getTLSOptions: func() *tlsutil.TLSOptions {
getTLSOptions: func() *authority.TLSOptions {
return nil
},
}).(*caHandler)

View file

@ -5,18 +5,18 @@ import (
"encoding/json"
"net/http"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/tlsutil"
)
// SignRequest is the request body for a certificate signature request.
type SignRequest struct {
CsrPEM CertificateRequest `json:"csr"`
OTT string `json:"ott"`
NotAfter TimeDuration `json:"notAfter"`
NotBefore TimeDuration `json:"notBefore"`
TemplateData json.RawMessage `json:"templateData"`
NotAfter TimeDuration `json:"notAfter,omitempty"`
NotBefore TimeDuration `json:"notBefore,omitempty"`
TemplateData json.RawMessage `json:"templateData,omitempty"`
}
// Validate checks the fields of the SignRequest and returns nil if they are ok
@ -37,11 +37,11 @@ func (s *SignRequest) Validate() error {
// SignResponse is the response object of the certificate signature request.
type SignResponse struct {
ServerPEM Certificate `json:"crt"`
CaPEM Certificate `json:"ca"`
CertChainPEM []Certificate `json:"certChain"`
TLSOptions *tlsutil.TLSOptions `json:"tlsOptions,omitempty"`
TLS *tls.ConnectionState `json:"-"`
ServerPEM Certificate `json:"crt"`
CaPEM Certificate `json:"ca"`
CertChainPEM []Certificate `json:"certChain"`
TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"`
TLS *tls.ConnectionState `json:"-"`
}
// 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/provisioner"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates"
"golang.org/x/crypto/ssh"
)
@ -27,7 +26,7 @@ type SSHAuthority interface {
GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error)
GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
CheckSSHHost(ctx context.Context, principal string, token string) (bool, error)
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error)
GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error)
}
@ -36,12 +35,13 @@ type SSHSignRequest struct {
PublicKey []byte `json:"publicKey"` // base64 encoded
OTT string `json:"ott"`
CertType string `json:"certType,omitempty"`
KeyID string `json:"keyID,omitempty"`
Principals []string `json:"principals,omitempty"`
ValidAfter TimeDuration `json:"validAfter,omitempty"`
ValidBefore TimeDuration `json:"validBefore,omitempty"`
AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"`
KeyID string `json:"keyID"`
IdentityCSR CertificateRequest `json:"identityCSR,omitempty"`
TemplateData json.RawMessage `json:"templateData,omitempty"`
}
// Validate validates the SSHSignRequest.
@ -86,7 +86,7 @@ type SSHCertificate struct {
// SSHGetHostsResponse is the response object that returns the list of valid
// hosts for SSH.
type SSHGetHostsResponse struct {
Hosts []sshutil.Host `json:"hosts"`
Hosts []authority.Host `json:"hosts"`
}
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
@ -275,11 +275,12 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
}
opts := provisioner.SignSSHOptions{
CertType: body.CertType,
KeyID: body.KeyID,
Principals: body.Principals,
ValidBefore: body.ValidBefore,
ValidAfter: body.ValidAfter,
CertType: body.CertType,
KeyID: body.KeyID,
Principals: body.Principals,
ValidBefore: body.ValidBefore,
ValidAfter: body.ValidAfter,
TemplateData: body.TemplateData,
}
ctx := provisioner.NewContextWithMethod(r.Context(), provisioner.SSHSignMethod)

View file

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

View file

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

View file

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

View file

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

View file

@ -17,11 +17,10 @@ import (
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/randutil"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/randutil"
"golang.org/x/crypto/ssh"
"gopkg.in/square/go-jose.v2/jwt"
)
var testAudiences = provisioner.Audiences{
@ -84,7 +83,7 @@ func generateToken(sub, iss, aud string, sans []string, iat time.Time, jwk *jose
func TestAuthority_authorizeToken(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -112,16 +111,16 @@ func TestAuthority_authorizeToken(t *testing.T) {
}
},
"fail/prehistoric-token": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
IssuedAt: jwt.NewNumericDate(now.Add(-time.Hour)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
IssuedAt: jose.NewNumericDate(now.Add(-time.Hour)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -131,11 +130,11 @@ func TestAuthority_authorizeToken(t *testing.T) {
}
},
"fail/provisioner-not-found": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "44",
}
@ -143,7 +142,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", "foo"))
assert.FatalError(t, err)
raw, err := jwt.Signed(_sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(_sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -153,15 +152,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
}
},
"ok/simpledb": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -170,15 +169,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
},
"fail/simpledb/token-already-used": func(t *testing.T) *authorizeTest {
_a := testAuthority(t)
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
_, err = _a.authorizeToken(context.Background(), raw)
assert.FatalError(t, err)
@ -197,15 +196,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
},
}
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: _a,
@ -220,15 +219,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
},
}
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: _a,
@ -245,15 +244,15 @@ func TestAuthority_authorizeToken(t *testing.T) {
},
}
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: _a,
@ -288,7 +287,7 @@ func TestAuthority_authorizeToken(t *testing.T) {
func TestAuthority_authorizeRevoke(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -316,15 +315,15 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
}
},
"fail/token/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -334,15 +333,15 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
}
},
"ok/token": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "44",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -372,7 +371,7 @@ func TestAuthority_authorizeRevoke(t *testing.T) {
func TestAuthority_authorizeSign(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -400,15 +399,15 @@ func TestAuthority_authorizeSign(t *testing.T) {
}
},
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -418,15 +417,15 @@ func TestAuthority_authorizeSign(t *testing.T) {
}
},
"ok": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "44",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -459,7 +458,7 @@ func TestAuthority_authorizeSign(t *testing.T) {
func TestAuthority_Authorize(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -496,15 +495,15 @@ func TestAuthority_Authorize(t *testing.T) {
}
},
"ok/sign": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.Sign,
ID: "1",
}
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -522,15 +521,15 @@ func TestAuthority_Authorize(t *testing.T) {
}
},
"ok/revoke": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.Revoke,
ID: "2",
}
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -622,15 +621,15 @@ func TestAuthority_Authorize(t *testing.T) {
}
},
"ok/sshRevoke": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "test.smallstep.com",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRevoke,
ID: "3",
}
token, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
token, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -892,7 +891,7 @@ func createSSHCert(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate,
func TestAuthority_authorizeSSHSign(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -920,15 +919,15 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
}
},
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -961,7 +960,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
}
} else {
if assert.Nil(t, tc.err) {
assert.Len(t, 11, got)
assert.Len(t, 7, got)
}
}
})
@ -971,7 +970,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
func TestAuthority_authorizeSSHRenew(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -999,15 +998,15 @@ func TestAuthority_authorizeSSHRenew(t *testing.T) {
}
},
"fail/sshRenew-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRenew,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -1073,7 +1072,7 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
},
})}...)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -1100,15 +1099,15 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
}
},
"fail/invalid-subject": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRevoke,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,
@ -1164,7 +1163,7 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) {
func TestAuthority_authorizeSSHRekey(t *testing.T) {
a := testAuthority(t)
jwk, err := jose.ParseKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
@ -1192,15 +1191,15 @@ func TestAuthority_authorizeSSHRekey(t *testing.T) {
}
},
"fail/sshRekey-unimplemented-jwk-provisioner": func(t *testing.T) *authorizeTest {
cl := jwt.Claims{
cl := jose.Claims{
Subject: "",
Issuer: validIssuer,
NotBefore: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: testAudiences.SSHRekey,
ID: "43",
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
return &authorizeTest{
auth: a,

View file

@ -12,15 +12,13 @@ import (
"github.com/smallstep/certificates/db"
kms "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util"
)
var (
// DefaultTLSOptions represents the default TLS version as well as the cipher
// suites used in the TLS certificates.
DefaultTLSOptions = tlsutil.TLSOptions{
CipherSuites: x509util.CipherSuites{
DefaultTLSOptions = TLSOptions{
CipherSuites: CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
@ -61,15 +59,27 @@ type Config struct {
DB *db.Config `json:"db,omitempty"`
Monitoring json.RawMessage `json:"monitoring,omitempty"`
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
TLS *tlsutil.TLSOptions `json:"tls,omitempty"`
TLS *TLSOptions `json:"tls,omitempty"`
Password string `json:"password,omitempty"`
Templates *templates.Templates `json:"templates,omitempty"`
}
// ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer
// x509 Certificate blocks.
type ASN1DN struct {
Country string `json:"country,omitempty" step:"country"`
Organization string `json:"organization,omitempty" step:"organization"`
OrganizationalUnit string `json:"organizationalUnit,omitempty" step:"organizationalUnit"`
Locality string `json:"locality,omitempty" step:"locality"`
Province string `json:"province,omitempty" step:"province"`
StreetAddress string `json:"streetAddress,omitempty" step:"streetAddress"`
CommonName string `json:"commonName,omitempty" step:"commonName"`
}
// AuthConfig represents the configuration options for the authority.
type AuthConfig struct {
Provisioners provisioner.List `json:"provisioners"`
Template *x509util.ASN1DN `json:"template,omitempty"`
Template *ASN1DN `json:"template,omitempty"`
Claims *provisioner.Claims `json:"claims,omitempty"`
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
Backdate *provisioner.Duration `json:"backdate,omitempty"`
@ -82,7 +92,7 @@ func (c *AuthConfig) init() {
c.Provisioners = provisioner.List{}
}
if c.Template == nil {
c.Template = &x509util.ASN1DN{}
c.Template = &ASN1DN{}
}
if c.Backdate == nil {
c.Backdate = &provisioner.Duration{

View file

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

View file

@ -10,7 +10,6 @@ import (
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/sshutil"
"golang.org/x/crypto/ssh"
)
@ -64,7 +63,7 @@ func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastio
// WithSSHGetHosts sets a custom function to get the bastion for a
// given user-host pair.
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error)) Option {
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]Host, error)) Option {
return func(a *Authority) error {
a.sshGetHostsFunc = fn
return nil

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,8 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util"
)
@ -203,41 +204,54 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
opts := claims.Step.SSH
signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token
// validates user's SignSSHOptions with the ones in the token
sshCertOptionsValidator(*opts),
// validate users's KeyID is the token subject.
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
}
t := now()
// Add modifiers from custom claims
// FIXME: this is also set in the sign method using SSHOptions.Modify.
// Default template attributes.
certType := sshutil.UserCert
keyID := claims.Subject
principals := []string{claims.Subject}
// Use options in the token.
if opts.CertType != "" {
signOptions = append(signOptions, sshCertTypeModifier(opts.CertType))
if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "jwk.AuthorizeSSHSign")
}
}
if opts.KeyID != "" {
keyID = opts.KeyID
}
if len(opts.Principals) > 0 {
signOptions = append(signOptions, sshCertPrincipalsModifier(opts.Principals))
principals = opts.Principals
}
// Certificate templates.
data := sshutil.CreateTemplateData(certType, keyID, principals)
if v, err := unsafeParseSigned(token); err == nil {
data.SetToken(v)
}
templateOptions, err := TemplateSSHOptions(p.Options, data)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
}
signOptions = append(signOptions, templateOptions)
// Add modifiers from custom claims
t := now()
if !opts.ValidAfter.IsZero() {
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
}
if !opts.ValidBefore.IsZero() {
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
}
if opts.KeyID != "" {
signOptions = append(signOptions, sshCertKeyIDModifier(opts.KeyID))
} else {
signOptions = append(signOptions, sshCertKeyIDModifier(claims.Subject))
}
// Default to a user certificate with no principals if not set
signOptions = append(signOptions, sshCertDefaultsModifier{CertType: SSHUserCert})
return append(signOptions,
// Set the default extensions.
&sshDefaultExtensionModifier{},
// Set the validity bounds if not set.
&sshDefaultDuration{p.claimer},
// Validate that the keyID is equivalent to the token subject.
sshCertKeyIDValidator(claims.Subject),
// Validate public key
&sshDefaultPublicKeyValidator{},
// Validate the validity period.

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
)
func TestJWK_Getters(t *testing.T) {
@ -512,10 +512,6 @@ func TestJWK_AuthorizeSign_SSHOptions(t *testing.T) {
}{
{"ok-user", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, expectedUserOptions, false, false},
{"ok-host", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, &SignSSHOptions{}, jwk}, expectedHostOptions, false, false},
{"ok-user-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false},
{"ok-host-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, jwk}, expectedHostOptions, false, false},
{"ok-user-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user"}, &SignSSHOptions{Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false},
{"ok-host-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{Principals: []string{"smallstep.com"}}, &SignSSHOptions{CertType: "host"}, jwk}, expectedHostOptions, false, false},
{"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{
CertType: "user", Principals: []string{"name"},
}, &SignSSHOptions{

View file

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

View file

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

View file

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

View file

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

View file

@ -13,7 +13,8 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util"
)
@ -369,10 +370,6 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
if claims.Email == "" {
return nil, errs.Unauthorized("oidc.AuthorizeSSHSign: failed to validate oidc token payload: email not found")
}
signOptions := []SignOption{
// set the key id to the token email
sshCertKeyIDModifier(claims.Email),
}
// Get the identity using either the default identityFunc or one injected
// externally.
@ -380,25 +377,52 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign")
}
defaults := SignSSHOptions{
CertType: SSHUserCert,
Principals: iden.Usernames,
// Certificate templates.
data := sshutil.CreateTemplateData(sshutil.UserCert, claims.Email, iden.Usernames)
if v, err := unsafeParseSigned(token); err == nil {
data.SetToken(v)
}
// Add custom extensions added in the identity function.
for k, v := range iden.Permissions.Extensions {
data.AddExtension(k, v)
}
// Add custom critical options added in the identity function.
for k, v := range iden.Permissions.CriticalOptions {
data.AddCriticalOption(k, v)
}
// Use the default template unless no-templates are configured and email is
// an admin, in that case we will use the parameters in the request.
isAdmin := o.IsAdmin(claims.Email)
defaultTemplate := sshutil.DefaultTemplate
if isAdmin && !o.Options.GetSSHOptions().HasTemplate() {
defaultTemplate = sshutil.DefaultAdminTemplate
}
templateOptions, err := CustomSSHTemplateOptions(o.Options, data, defaultTemplate)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign")
}
signOptions := []SignOption{templateOptions}
// Admin users can use any principal, and can sign user and host certificates.
// Non-admin users can only use principals returned by the identityFunc, and
// can only sign user certificates.
if !o.IsAdmin(claims.Email) {
signOptions = append(signOptions, sshCertOptionsValidator(defaults))
if isAdmin {
signOptions = append(signOptions, &sshCertOptionsRequireValidator{
CertType: true,
KeyID: true,
Principals: true,
})
} else {
signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{
CertType: SSHUserCert,
Principals: iden.Usernames,
}))
}
// Default to a user certificate with usernames as principals if those options
// are not set.
signOptions = append(signOptions, sshCertDefaultsModifier(defaults))
return append(signOptions,
// Set the default extensions
&sshDefaultExtensionModifier{},
// Set the validity bounds if not set.
&sshDefaultDuration{o.claimer},
// Validate public key

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import (
"reflect"
"testing"
"github.com/smallstep/cli/crypto/pemutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
@ -46,6 +46,28 @@ func TestOptions_GetX509Options(t *testing.T) {
}
}
func TestOptions_GetSSHOptions(t *testing.T) {
type fields struct {
o *Options
}
tests := []struct {
name string
fields fields
want *SSHOptions
}{
{"ok", fields{&Options{SSH: &SSHOptions{Template: "foo"}}}, &SSHOptions{Template: "foo"}},
{"nil", fields{&Options{}}, nil},
{"nilOptions", fields{nil}, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.fields.o.GetSSHOptions(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Options.GetSSHOptions() = %v, want %v", got, tt.want)
}
})
}
}
func TestProvisionerX509Options_HasTemplate(t *testing.T) {
type fields struct {
Template string

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
// by provisioners to populate certificate fields.
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.

View file

@ -12,7 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/assert"
"github.com/smallstep/cli/crypto/pemutil"
"go.step.sm/crypto/pemutil"
)
func Test_emailOnlyIdentity_Valid(t *testing.T) {
@ -614,7 +614,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
t.Run(name, func(t *testing.T) {
tt := run()
assert.FatalError(t, tt.pdd.Modify(tt.cert, tt.so), "unexpected error")
time.Sleep(1 * time.Nanosecond)
time.Sleep(100 * time.Millisecond)
tt.valid(tt.cert)
})
}

View file

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

View file

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

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 (
"crypto"
"crypto/rand"
"fmt"
"net/http"
"reflect"
"time"
"github.com/smallstep/certificates/errs"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh"
)
@ -46,16 +48,17 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
}
var mods []SSHCertModifier
var certOptions []sshutil.Option
var validators []SSHCertValidator
for _, op := range signOpts {
switch o := op.(type) {
// add options to NewCertificate
case SSHCertificateOptions:
certOptions = append(certOptions, o.Options(opts)...)
// modify the ssh.Certificate
case SSHCertModifier:
mods = append(mods, o)
// modify the ssh.Certificate given the SSHOptions
case SSHCertOptionModifier:
mods = append(mods, o.Option(opts))
// validate the ssh.Certificate
case SSHCertValidator:
validators = append(validators, o)
@ -69,22 +72,39 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
}
}
// Build base certificate with the key and some random values
cert := &ssh.Certificate{
Nonce: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0},
Key: pub,
Serial: 1234567890,
// Simulated certificate request with request options.
cr := sshutil.CertificateRequest{
Type: opts.CertType,
KeyID: opts.KeyID,
Principals: opts.Principals,
Key: pub,
}
// Use opts to modify the certificate
if err := opts.Modify(cert); err != nil {
return nil, err
// Create certificate from template.
certificate, err := sshutil.NewCertificate(cr, certOptions...)
if err != nil {
if _, ok := err.(*sshutil.TemplateError); ok {
return nil, errs.NewErr(http.StatusBadRequest, err,
errs.WithMessage(err.Error()),
errs.WithKeyVal("signOptions", signOpts),
)
}
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH")
}
// Use provisioner modifiers
// Get actual *ssh.Certificate and continue with provisioner modifiers.
cert := certificate.GetCertificate()
// Use SignSSHOptions to modify the certificate validity. It will be later
// checked or set if not defined.
if err := opts.ModifyValidity(cert); err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
}
// Use provisioner modifiers.
for _, m := range mods {
if err := m.Modify(cert); err != nil {
return nil, err
if err := m.Modify(cert, opts); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
}
}
@ -101,23 +121,17 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si
if err != nil {
return nil, err
}
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length.
data := cert.Marshal()
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
// Sign certificate.
cert, err = sshutil.CreateCertificate(cert, signer)
if err != nil {
return nil, err
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
}
cert.Signature = sig
// User provisioners validators
// User provisioners validators.
for _, v := range validators {
if err := v.Valid(cert, opts); err != nil {
return nil, err
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
}
}

View file

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

View file

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

View file

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

View file

@ -9,7 +9,8 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/sshutil"
"go.step.sm/crypto/x509util"
)
@ -247,16 +248,41 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
signOptions := []SignOption{
// validates user's SSHOptions with the ones in the token
sshCertOptionsValidator(*opts),
// validate users's KeyID is the token subject.
sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}),
}
// Add modifiers from custom claims
// FIXME: this is also set in the sign method using SSHOptions.Modify.
// Default template attributes.
certType := sshutil.UserCert
keyID := claims.Subject
principals := []string{claims.Subject}
// Use options in the token.
if opts.CertType != "" {
signOptions = append(signOptions, sshCertTypeModifier(opts.CertType))
if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "x5c.AuthorizeSSHSign")
}
}
if opts.KeyID != "" {
keyID = opts.KeyID
}
if len(opts.Principals) > 0 {
signOptions = append(signOptions, sshCertPrincipalsModifier(opts.Principals))
principals = opts.Principals
}
// Certificate templates.
data := sshutil.CreateTemplateData(certType, keyID, principals)
if v, err := unsafeParseSigned(token); err == nil {
data.SetToken(v)
}
templateOptions, err := TemplateSSHOptions(p.Options, data)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeSSHSign")
}
signOptions = append(signOptions, templateOptions)
// Add modifiers from custom claims
t := now()
if !opts.ValidAfter.IsZero() {
signOptions = append(signOptions, sshCertValidAfterModifier(opts.ValidAfter.RelativeTime(t).Unix()))
@ -264,21 +290,10 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
if !opts.ValidBefore.IsZero() {
signOptions = append(signOptions, sshCertValidBeforeModifier(opts.ValidBefore.RelativeTime(t).Unix()))
}
// Make sure to define the the KeyID
if opts.KeyID == "" {
signOptions = append(signOptions, sshCertKeyIDModifier(claims.Subject))
}
// Default to a user certificate with no principals if not set
signOptions = append(signOptions, sshCertDefaultsModifier{CertType: SSHUserCert})
return append(signOptions,
// Set the default extensions.
&sshDefaultExtensionModifier{},
// Checks the validity bounds, and set the validity if has not been set.
&sshLimitDuration{p.claimer, claims.chains[0][0].NotAfter},
// set the key id to the token subject
sshCertKeyIDValidator(claims.Subject),
// Validate public key.
&sshDefaultPublicKeyValidator{},
// Validate the validity period.

View file

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

View file

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

View file

@ -13,10 +13,10 @@ import (
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/randutil"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh"
)
@ -51,6 +51,21 @@ type Bastion struct {
Flags string `json:"flags,omitempty"`
}
// HostTag are tagged with k,v pairs. These tags are how a user is ultimately
// associated with a host.
type HostTag struct {
ID string
Name string
Value string
}
// Host defines expected attributes for an ssh host.
type Host struct {
HostID string `json:"hid"`
HostTags []HostTag `json:"host_tags"`
Hostname string `json:"hostname"`
}
// Validate checks the fields in SSHConfig.
func (c *SSHConfig) Validate() error {
if c == nil {
@ -205,100 +220,113 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname str
// SignSSH creates a signed SSH certificate with the given public key and options.
func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var mods []provisioner.SSHCertModifier
var validators []provisioner.SSHCertValidator
var (
certOptions []sshutil.Option
mods []provisioner.SSHCertModifier
validators []provisioner.SSHCertValidator
)
// Validate given options.
if err := opts.Validate(); err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
}
// Set backdate with the configured value
opts.Backdate = a.config.AuthorityConfig.Backdate.Duration
for _, op := range signOpts {
switch o := op.(type) {
// add options to NewCertificate
case provisioner.SSHCertificateOptions:
certOptions = append(certOptions, o.Options(opts)...)
// modify the ssh.Certificate
case provisioner.SSHCertModifier:
mods = append(mods, o)
// modify the ssh.Certificate given the SSHOptions
case provisioner.SSHCertOptionModifier:
mods = append(mods, o.Option(opts))
// validate the ssh.Certificate
case provisioner.SSHCertValidator:
validators = append(validators, o)
// validate the given SSHOptions
case provisioner.SSHCertOptionsValidator:
if err := o.Valid(opts); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
}
default:
return nil, errs.InternalServer("signSSH: invalid extra option type %T", o)
return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o)
}
}
nonce, err := randutil.ASCII(32)
// 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 {
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
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error reading random number")
// Get actual *ssh.Certificate and continue with provisioner modifiers.
certTpl := certificate.GetCertificate()
// Use SignSSHOptions to modify the certificate validity. It will be later
// checked or set if not defined.
if err := opts.ModifyValidity(certTpl); err != nil {
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH")
}
// Build base certificate with the key and some random values
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
// Use provisioner modifiers.
for _, m := range mods {
if err := m.Modify(cert); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
if err := m.Modify(certTpl, opts); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
}
}
// Get signer from authority keys
var signer ssh.Signer
switch cert.CertType {
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("signSSH: user certificate signing is not enabled")
return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled")
}
signer = a.sshCAUserCertSignKey
case ssh.HostCert:
if a.sshCAHostCertSignKey == nil {
return nil, errs.NotImplemented("signSSH: host certificate signing is not enabled")
return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled")
}
signer = a.sshCAHostCertSignKey
default:
return nil, errs.InternalServer("signSSH: unexpected ssh certificate type: %d", cert.CertType)
return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType)
}
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length.
data := cert.Marshal()
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate")
}
cert.Signature = sig
// User provisioners validators
// User provisioners validators.
for _, v := range validators {
if err := v.Valid(cert, opts); err != nil {
return nil, errs.Wrap(http.StatusForbidden, err, "signSSH")
return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH")
}
}
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error storing certificate in db")
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
}
return cert, nil
@ -306,16 +334,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) {
nonce, err := randutil.ASCII(32)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH")
}
var serial uint64
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error reading random number")
}
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("rewnewSSH: cannot renew certificate without validity period")
}
@ -326,22 +344,22 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
va := now.Add(-1 * backdate)
vb := now.Add(duration - backdate)
// Build base certificate with the key and some random values
cert := &ssh.Certificate{
Nonce: []byte(nonce),
// Build base certificate with the old key.
// Nonce and serial will be automatically generated on signing.
certTpl := &ssh.Certificate{
Key: oldCert.Key,
Serial: serial,
CertType: oldCert.CertType,
KeyId: oldCert.KeyId,
ValidPrincipals: oldCert.ValidPrincipals,
Permissions: oldCert.Permissions,
Reserved: oldCert.Reserved,
ValidAfter: uint64(va.Unix()),
ValidBefore: uint64(vb.Unix()),
}
// Get signer from authority keys
var signer ssh.Signer
switch cert.CertType {
switch certTpl.CertType {
case ssh.UserCert:
if a.sshCAUserCertSignKey == nil {
return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled")
@ -353,20 +371,14 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
}
signer = a.sshCAHostCertSignKey
default:
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", cert.CertType)
return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType)
}
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length.
data := cert.Marshal()
data = data[:len(data)-4]
// Sign the certificate
sig, err := signer.Sign(rand.Reader, data)
// Sign certificate.
cert, err := sshutil.CreateCertificate(certTpl, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error signing certificate")
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
}
cert.Signature = sig
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
@ -389,16 +401,6 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
}
}
nonce, err := randutil.ASCII(32)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH")
}
var serial uint64
if err := binary.Read(rand.Reader, binary.BigEndian, &serial); err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error reading random number")
}
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("rekeySSH; cannot rekey certificate without validity period")
}
@ -409,15 +411,15 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
va := now.Add(-1 * backdate)
vb := now.Add(duration - backdate)
// Build base certificate with the key and some random values
// Build base certificate with the new key.
// Nonce and serial will be automatically generated on signing.
cert := &ssh.Certificate{
Nonce: []byte(nonce),
Key: pub,
Serial: serial,
CertType: oldCert.CertType,
KeyId: oldCert.KeyId,
ValidPrincipals: oldCert.ValidPrincipals,
Permissions: oldCert.Permissions,
Reserved: oldCert.Reserved,
ValidAfter: uint64(va.Unix()),
ValidBefore: uint64(vb.Unix()),
}
@ -438,18 +440,13 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
default:
return nil, errs.BadRequest("rekeySSH; unexpected ssh certificate type: %d", cert.CertType)
}
cert.SignatureKey = signer.PublicKey()
// Get bytes for signing trailing the signature length.
data := cert.Marshal()
data = data[:len(data)-4]
// Sign the certificate.
sig, err := signer.Sign(rand.Reader, data)
var err error
// Sign certificate.
cert, err = sshutil.CreateCertificate(cert, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error signing certificate")
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
}
cert.Signature = sig
// Apply validators from provisioner.
for _, v := range validators {
@ -571,7 +568,7 @@ func (a *Authority) CheckSSHHost(ctx context.Context, principal string, token st
}
// GetSSHHosts returns a list of valid host principals.
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
if a.sshGetHostsFunc != nil {
hosts, err := a.sshGetHostsFunc(ctx, cert)
return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
@ -581,9 +578,9 @@ func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]
return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
}
hosts := make([]sshutil.Host, len(hostnames))
hosts := make([]Host, len(hostnames))
for i, hn := range hostnames {
hosts[i] = sshutil.Host{Hostname: hn}
hosts[i] = Host{Hostname: hn}
}
return hosts, nil
}

View file

@ -18,15 +18,15 @@ import (
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh"
)
type sshTestModifier ssh.Certificate
func (m sshTestModifier) Modify(cert *ssh.Certificate) error {
func (m sshTestModifier) Modify(cert *ssh.Certificate, _ provisioner.SignSSHOptions) error {
if m.CertType != 0 {
cert.CertType = m.CertType
}
@ -53,7 +53,7 @@ func (m sshTestModifier) Modify(cert *ssh.Certificate) error {
type sshTestCertModifier string
func (m sshTestCertModifier) Modify(cert *ssh.Certificate) error {
func (m sshTestCertModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
if m == "" {
return nil
}
@ -80,8 +80,11 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
type sshTestOptionsModifier string
func (m sshTestOptionsModifier) Option(opts provisioner.SignSSHOptions) provisioner.SSHCertModifier {
return sshTestCertModifier(string(m))
func (m sshTestOptionsModifier) Modify(cert *ssh.Certificate, opts provisioner.SignSSHOptions) error {
if m == "" {
return nil
}
return fmt.Errorf(string(m))
}
func TestAuthority_SignSSH(t *testing.T) {
@ -101,6 +104,29 @@ func TestAuthority_SignSSH(t *testing.T) {
CertType: ssh.HostCert,
}
userTemplate, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", nil))
assert.FatalError(t, err)
hostTemplate, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.HostCert, "key-id", nil))
assert.FatalError(t, err)
userTemplateWithUser, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
assert.FatalError(t, err)
hostTemplateWithHosts, err := provisioner.TemplateSSHOptions(nil, sshutil.CreateTemplateData(sshutil.HostCert, "key-id", []string{"foo.test.com", "bar.test.com"}))
assert.FatalError(t, err)
userCustomTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{
SSH: &provisioner.SSHOptions{Template: `{
"type": "{{ .Type }}",
"keyId": "{{ .KeyID }}",
"principals": {{ append .Principals "admin" | toJson }},
"extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }},
"criticalOptions": {{ toJson .CriticalOptions }}
}`},
}, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
assert.FatalError(t, err)
userFailTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{
SSH: &provisioner.SSHOptions{Template: `{{ fail "an error"}}`},
}, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}))
assert.FatalError(t, err)
now := time.Now()
type fields struct {
@ -125,27 +151,29 @@ func TestAuthority_SignSSH(t *testing.T) {
want want
wantErr bool
}{
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false},
{"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostOptions}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false},
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false},
{"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false},
{"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false},
{"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false},
{"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false},
{"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true},
{"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, want{}, true},
{"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("an error")}}, want{}, true},
{"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("an error")}}, want{}, true},
{"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("an error")}}, want{}, true},
{"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, want{}, true},
{"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{}, true},
{"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{}, true},
{"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true},
{"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions}}, want{CertType: ssh.UserCert}, false},
{"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostTemplate, hostOptions}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert}, false},
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false},
{"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{hostTemplateWithHosts}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false},
{"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{userTemplate}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false},
{"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{hostTemplate}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false},
{"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false},
{"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false},
{"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false},
{"ok-custom-template", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userCustomTemplate, userOptions}}, want{CertType: ssh.UserCert, Principals: []string{"user", "admin"}}, false},
{"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{userTemplate}}, want{}, true},
{"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertValidator("an error")}}, want{}, true},
{"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestCertModifier("an error")}}, want{}, true},
{"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("an error")}}, want{}, true},
{"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("an error")}}, want{}, true},
{"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, "wrong type"}}, want{}, true},
{"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{userTemplate}}, want{}, true},
{"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{hostTemplate}}, want{}, true},
{"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, sshTestModifier{CertType: 100}}}, want{}, true},
{"fail-custom-template", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userFailTemplate, userOptions}}, want{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -678,17 +706,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
a := testAuthority(t)
type test struct {
getHostsFunc func(context.Context, *x509.Certificate) ([]sshutil.Host, error)
getHostsFunc func(context.Context, *x509.Certificate) ([]Host, error)
auth *Authority
cert *x509.Certificate
cmp func(got []sshutil.Host)
cmp func(got []Host)
err error
code int
}
tests := map[string]func(t *testing.T) *test{
"fail/getHostsFunc-fail": func(t *testing.T) *test {
return &test{
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
return nil, errors.New("force")
},
cert: &x509.Certificate{},
@ -697,17 +725,17 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
}
},
"ok/getHostsFunc-defined": func(t *testing.T) *test {
hosts := []sshutil.Host{
hosts := []Host{
{HostID: "1", Hostname: "foo"},
{HostID: "2", Hostname: "bar"},
}
return &test{
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]sshutil.Host, error) {
getHostsFunc: func(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
return hosts, nil
},
cert: &x509.Certificate{},
cmp: func(got []sshutil.Host) {
cmp: func(got []Host) {
assert.Equals(t, got, hosts)
},
}
@ -732,8 +760,8 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
},
})),
cert: &x509.Certificate{},
cmp: func(got []sshutil.Host) {
assert.Equals(t, got, []sshutil.Host{
cmp: func(got []Host) {
assert.Equals(t, got, []Host{
{Hostname: "foo"},
{Hostname: "bar"},
})

View file

@ -8,31 +8,28 @@ import (
"encoding/asn1"
"encoding/base64"
"encoding/pem"
"fmt"
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/tlsutil"
x509legacy "github.com/smallstep/cli/crypto/x509util"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
// GetTLSOptions returns the tls options configured.
func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions {
func (a *Authority) GetTLSOptions() *TLSOptions {
return a.config.TLS
}
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
func withDefaultASN1DN(def *x509legacy.ASN1DN) provisioner.CertificateModifierFunc {
func withDefaultASN1DN(def *ASN1DN) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
if def == nil {
return errors.New("default ASN1DN template cannot be nil")
@ -246,21 +243,10 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
}
leaf, err := x509legacy.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer)
serverCert, err := x509util.CreateCertificate(newCert, a.x509Issuer, newCert.PublicKey, a.x509Signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...)
}
crtBytes, err := leaf.CreateCertificate()
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Rekey; error renewing certificate from existing server certificate", opts...)
}
serverCert, err := x509.ParseCertificate(crtBytes)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Rekey; error parsing new server certificate", opts...)
}
if err = a.db.StoreCertificate(serverCert); err != nil {
if err != db.ErrNotImplemented {
@ -295,7 +281,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
errs.WithKeyVal("reason", revokeOpts.Reason),
errs.WithKeyVal("passiveOnly", revokeOpts.PassiveOnly),
errs.WithKeyVal("MTLS", revokeOpts.MTLS),
errs.WithKeyVal("context", fmt.Sprint(provisioner.MethodFromContext(ctx))),
errs.WithKeyVal("context", provisioner.MethodFromContext(ctx).String()),
}
if revokeOpts.MTLS {
opts = append(opts, errs.WithKeyVal("certificate", base64.StdEncoding.EncodeToString(revokeOpts.Crt.Raw)))
@ -372,48 +358,62 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer,
x509legacy.WithHosts(strings.Join(a.config.DNSNames, ",")))
if err != nil {
fatal := func(err error) (*tls.Certificate, error) {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
}
crtBytes, err := profile.CreateCertificate()
// Generate default key.
priv, err := keyutil.GenerateDefaultKey()
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
return fatal(err)
}
signer, ok := priv.(crypto.Signer)
if !ok {
return fatal(errors.New("private key is not a crypto.Signer"))
}
keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey())
// Create initial certificate request.
cr, err := x509util.CreateCertificateRequest("Step Online CA", a.config.DNSNames, signer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
return fatal(err)
}
// Generate certificate template directly from the certificate request.
template, err := x509util.NewCertificate(cr)
if err != nil {
return fatal(err)
}
// Get x509 certificate template, set validity and sign it.
now := time.Now()
certTpl := template.GetCertificate()
certTpl.NotBefore = now.Add(-1 * time.Minute)
certTpl.NotAfter = now.Add(24 * time.Hour)
cert, err := x509util.CreateCertificate(certTpl, a.x509Issuer, cr.PublicKey, a.x509Signer)
if err != nil {
return fatal(err)
}
// Generate PEM blocks to create tls.Certificate
crtPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crtBytes,
Bytes: cert.Raw,
})
// Load the x509 key pair (combining server and intermediate blocks)
// to a tls.Certificate.
intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
return fatal(err)
}
tlsCrt, err := tls.X509KeyPair(append(crtPEM,
pem.EncodeToMemory(intermediatePEM)...),
pem.EncodeToMemory(keyPEM))
keyPEM, err := pemutil.Serialize(priv)
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.GetTLSCertificate; error creating tls certificate")
return fatal(err)
}
// Get the 'leaf' certificate and set the attribute accordingly.
leaf, err := x509.ParseCertificate(tlsCrt.Certificate[0])
tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM))
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.GetTLSCertificate; error parsing tls certificate")
return fatal(err)
}
tlsCrt.Leaf = leaf
// Set leaf certificate
tlsCrt.Leaf = cert
return &tlsCrt, nil
}

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

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme"
acmeAPI "github.com/smallstep/certificates/acme/api"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
)
// ACMEClient implements an HTTP client to an ACME API.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,13 +7,13 @@ import (
"testing"
"time"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
"github.com/smallstep/cli/jose"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
func getTestProvisioner(t *testing.T, caURL string) *Provisioner {
jwk, err := jose.ParseKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password")))
if err != nil {
t.Fatal(err)
}

View file

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

View file

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

View file

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

View file

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

View file

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

11
go.mod
View file

@ -1,6 +1,6 @@
module github.com/smallstep/certificates
go 1.13
go 1.14
require (
cloud.google.com/go v0.51.0
@ -19,14 +19,15 @@ require (
github.com/smallstep/cli v0.15.0
github.com/smallstep/nosql v0.3.0
github.com/urfave/cli v1.22.2
go.step.sm/crypto v0.2.0
go.step.sm/crypto v0.3.0
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
google.golang.org/api v0.15.0
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb
google.golang.org/grpc v1.26.0
gopkg.in/square/go-jose.v2 v2.4.0
gopkg.in/square/go-jose.v2 v2.5.1
)
//replace github.com/smallstep/cli => ../cli
//replace github.com/smallstep/nosql => ../nosql
// replace github.com/smallstep/cli => ../cli
// replace github.com/smallstep/nosql => ../nosql
// replace go.step.sm/crypto => ../crypto

6
go.sum
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.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E=
go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8=
go.step.sm/crypto v0.2.0 h1:Rx+XqrNO4ZGHWlT9QCXls2L9pvcNiI23zEpAq0fctvY=
go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww=
go.step.sm/crypto v0.3.0 h1:wlbhLw2LvzDYwKcUJS2plBALptMXaKUhI2nbdfsXjTU=
go.step.sm/crypto v0.3.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -748,6 +748,8 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/uri"
"github.com/smallstep/cli/crypto/pemutil"
"go.step.sm/crypto/pemutil"
)
// KMS implements a KMS using AWS Key Management Service.

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ import (
gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/pemutil"
"go.step.sm/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

View file

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

View file

@ -11,7 +11,7 @@ import (
"testing"
gax "github.com/googleapis/gax-go/v2"
"github.com/smallstep/cli/crypto/pemutil"
"go.step.sm/crypto/pemutil"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

View file

@ -10,8 +10,9 @@ import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/ui"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
)
type algorithmAttributes struct {
@ -41,7 +42,7 @@ var generateKey = func(kty, crv string, size int) (interface{}, interface{}, err
if kty == "RSA" && size == 0 {
size = DefaultRSAKeySize
}
return keys.GenerateKeyPair(kty, crv, size)
return keyutil.GenerateKeyPair(kty, crv, size)
}
// SoftKMS is a key manager that uses keys stored in disk.
@ -53,6 +54,9 @@ func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) {
}
func init() {
pemutil.PromptPassword = func(msg string) ([]byte, error) {
return ui.PromptPassword(msg)
}
apiv1.Register(apiv1.SoftKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
return New(ctx, opts)
})
@ -98,6 +102,8 @@ func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e
}
}
// CreateKey generates a new key using Golang crypto and returns both public and
// private key.
func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
if !ok {
@ -123,6 +129,7 @@ func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespon
}, nil
}
// GetPublicKey returns the public key from the file passed in the request name.
func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
v, err := pemutil.Read(req.Name)
if err != nil {

View file

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

View file

@ -9,7 +9,7 @@
ifeq (, $(shell which docker))
DOCKER_CLIENT_OS := linux
else
DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}'))
DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}' 2>/dev/null))
endif
DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64

View file

@ -14,6 +14,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
@ -21,14 +22,13 @@ import (
"github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/db"
"github.com/smallstep/cli/config"
"github.com/smallstep/cli/crypto/keys"
"github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/jose"
"github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
)
@ -114,6 +114,18 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) {
}
}
func generateDefaultKey() (crypto.Signer, error) {
priv, err := keyutil.GenerateDefaultKey()
if err != nil {
return nil, err
}
signer, ok := priv.(crypto.Signer)
if !ok {
return nil, errors.Errorf("type %T is not a cyrpto.Signer", priv)
}
return signer, nil
}
// GetProvisionerKey returns the encrypted provisioner key with the for the
// given kid.
func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
@ -255,25 +267,35 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
// GenerateRootCertificate generates a root certificate with the given name.
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) {
rootProfile, err := x509util.NewRootProfile(name)
signer, err := generateDefaultKey()
if err != nil {
return nil, nil, err
}
rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass))
cr, err := x509util.CreateCertificateRequest(name, []string{}, signer)
if err != nil {
return nil, nil, err
}
rootCrt, err := x509.ParseCertificate(rootBytes)
data := x509util.CreateTemplateData(name, []string{})
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data))
if err != nil {
return nil, nil, errors.Wrap(err, "error parsing root certificate")
return nil, nil, err
}
sum := sha256.Sum256(rootCrt.Raw)
p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
template := cert.GetCertificate()
template.NotBefore = time.Now()
template.NotAfter = template.NotBefore.AddDate(10, 0, 0)
rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer)
if err != nil {
return nil, nil, err
}
return rootCrt, rootProfile.SubjectPrivateKey(), nil
if err := p.WriteRootCertificate(rootCrt, signer, pass); err != nil {
return nil, nil, err
}
return rootCrt, signer, nil
}
// WriteRootCertificate writes to disk the given certificate and key.
@ -285,7 +307,7 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
return err
}
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword([]byte(pass)), pemutil.ToFile(p.rootKey, 0600))
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600))
if err != nil {
return err
}
@ -299,12 +321,46 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
// GenerateIntermediateCertificate generates an intermediate certificate with
// the given name.
func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
interProfile, err := x509util.NewIntermediateProfile(name, rootCrt, rootKey)
key, err := generateDefaultKey()
if err != nil {
return err
}
_, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass))
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
@ -313,7 +369,7 @@ func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
var pubNames = []string{p.sshHostPubKey, p.sshUserPubKey}
var privNames = []string{p.sshHostKey, p.sshUserKey}
for i := 0; i < 2; i++ {
pub, priv, err := keys.GenerateDefaultKeyPair()
pub, priv, err := keyutil.GenerateDefaultKeyPair()
if err != nil {
return err
}
@ -432,11 +488,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov},
},
TLS: &tlsutil.TLSOptions{
MinVersion: x509util.DefaultTLSMinVersion,
MaxVersion: x509util.DefaultTLSMaxVersion,
Renegotiation: x509util.DefaultTLSRenegotiation,
CipherSuites: x509util.DefaultTLSCipherSuites,
TLS: &authority.TLSOptions{
MinVersion: authority.DefaultTLSMinVersion,
MaxVersion: authority.DefaultTLSMaxVersion,
Renegotiation: authority.DefaultTLSRenegotiation,
CipherSuites: authority.DefaultTLSCipherSuites,
},
Templates: p.getTemplates(),
}

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