diff --git a/acme/account.go b/acme/account.go index eeac09b9..ea0e7fdc 100644 --- a/acme/account.go +++ b/acme/account.go @@ -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 diff --git a/acme/account_test.go b/acme/account_test.go index ea63550f..0008551a 100644 --- a/acme/account_test.go +++ b/acme/account_test.go @@ -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 ( diff --git a/acme/api/account_test.go b/acme/api/account_test.go index 0e34f980..bdd61c59 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -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 ( diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index e3db69b7..7e19ea75 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -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 { diff --git a/acme/api/middleware.go b/acme/api/middleware.go index f7d7dcf4..3bf5f89a 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -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: diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index 916d84f0..d2a9cdc0 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -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") diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 487b8669..a1c8fef7 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -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) { diff --git a/acme/authority.go b/acme/authority.go index e37835f6..959dc9c4 100644 --- a/acme/authority.go +++ b/acme/authority.go @@ -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. diff --git a/acme/authority_test.go b/acme/authority_test.go index 19b42cb6..d411ca06 100644 --- a/acme/authority_test.go +++ b/acme/authority_test.go @@ -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) { diff --git a/acme/certificate_test.go b/acme/certificate_test.go index e99eb5af..a4b8f91a 100644 --- a/acme/certificate_test.go +++ b/acme/certificate_test.go @@ -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) { diff --git a/acme/challenge.go b/acme/challenge.go index 82fa9327..a032bc00 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -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 diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 39b33e8c..c3d97f9f 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -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{ diff --git a/acme/common.go b/acme/common.go index d2d710cc..fec47b94 100644 --- a/acme/common.go +++ b/acme/common.go @@ -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 -- diff --git a/api/api.go b/api/api.go index 205f6d1b..699092a7 100644 --- a/api/api.go +++ b/api/api.go @@ -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) diff --git a/api/api_test.go b/api/api_test.go index aab47e53..190e5a2a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -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) diff --git a/api/sign.go b/api/sign.go index eb977bcd..69e9a1a5 100644 --- a/api/sign.go +++ b/api/sign.go @@ -5,18 +5,18 @@ import ( "encoding/json" "net/http" + "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" - "github.com/smallstep/cli/crypto/tlsutil" ) // SignRequest is the request body for a certificate signature request. type SignRequest struct { CsrPEM CertificateRequest `json:"csr"` OTT string `json:"ott"` - NotAfter TimeDuration `json:"notAfter"` - NotBefore TimeDuration `json:"notBefore"` - TemplateData json.RawMessage `json:"templateData"` + NotAfter TimeDuration `json:"notAfter,omitempty"` + NotBefore TimeDuration `json:"notBefore,omitempty"` + TemplateData json.RawMessage `json:"templateData,omitempty"` } // Validate checks the fields of the SignRequest and returns nil if they are ok @@ -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 diff --git a/api/ssh.go b/api/ssh.go index 70469fbf..9962ad4f 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -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) diff --git a/api/ssh_test.go b/api/ssh_test.go index 7561709a..1873a96d 100644 --- a/api/ssh_test.go +++ b/api/ssh_test.go @@ -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) diff --git a/authority/authority.go b/authority/authority.go index 78cfa608..a0a80b62 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -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 } diff --git a/authority/authority_test.go b/authority/authority_test.go index f87f1df3..54de0040 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -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"), } }, } diff --git a/authority/authorize.go b/authority/authorize.go index 2bf7223b..0d3b767f 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -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" ) diff --git a/authority/authorize_test.go b/authority/authorize_test.go index c562437c..90eb8e46 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -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, diff --git a/authority/config.go b/authority/config.go index a26d19ad..1d49f9a1 100644 --- a/authority/config.go +++ b/authority/config.go @@ -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{ diff --git a/authority/config_test.go b/authority/config_test.go index d049d47e..87cd3fba 100644 --- a/authority/config_test.go +++ b/authority/config_test.go @@ -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 { diff --git a/authority/options.go b/authority/options.go index 59566822..9457f276 100644 --- a/authority/options.go +++ b/authority/options.go @@ -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 diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 381441b9..db3389c8 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -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 diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 7409532d..97952911 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -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 { diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index ba281133..ea8b08ec 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -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 diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index e919a5cd..f21a5676 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -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) { diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index a1d11740..16716698 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -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. diff --git a/authority/provisioner/collection_test.go b/authority/provisioner/collection_test.go index cd15c18c..a0a79e92 100644 --- a/authority/provisioner/collection_test.go +++ b/authority/provisioner/collection_test.go @@ -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) { diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 5ad00a96..830e7965 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -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 diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index 23e306f4..d6c4054c 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -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) { diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 2716a8d9..d6a97e2b 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -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. diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 2048bc64..9198ff69 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -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{ diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index b830e098..d64c1dfd 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -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 diff --git a/authority/provisioner/k8sSA_test.go b/authority/provisioner/k8sSA_test.go index 16a57aaf..03ae7eff 100644 --- a/authority/provisioner/k8sSA_test.go +++ b/authority/provisioner/k8sSA_test.go @@ -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: diff --git a/authority/provisioner/keystore.go b/authority/provisioner/keystore.go index c672c40c..f775e150 100644 --- a/authority/provisioner/keystore.go +++ b/authority/provisioner/keystore.go @@ -10,7 +10,7 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" ) const ( diff --git a/authority/provisioner/keystore_test.go b/authority/provisioner/keystore_test.go index 63c29a3b..9b0746ac 100644 --- a/authority/provisioner/keystore_test.go +++ b/authority/provisioner/keystore_test.go @@ -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) { diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 295dbb59..64e16052 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -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 diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index 55093a91..b0e2f2f4 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -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) { diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index 88e22523..593a38d9 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -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. diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index 519f1a58..718a20bd 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -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 diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index c413a100..aed1900a 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -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. diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index a4a2935c..a0d3cde0 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -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) }) } diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index e823a7d8..a872513e 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -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 { diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 600fd975..693690f6 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -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) - } - }) - } -} diff --git a/authority/provisioner/ssh_options.go b/authority/provisioner/ssh_options.go new file mode 100644 index 00000000..8ec21942 --- /dev/null +++ b/authority/provisioner/ssh_options.go @@ -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 +} diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index c08ddc3e..c530cd3c 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -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") } } diff --git a/authority/provisioner/sshpop.go b/authority/provisioner/sshpop.go index db1c5a89..223f0b9e 100644 --- a/authority/provisioner/sshpop.go +++ b/authority/provisioner/sshpop.go @@ -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" ) diff --git a/authority/provisioner/sshpop_test.go b/authority/provisioner/sshpop_test.go index 5863b6f9..5d51b90e 100644 --- a/authority/provisioner/sshpop_test.go +++ b/authority/provisioner/sshpop_test.go @@ -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" ) diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index a279cf8a..0f4ceb05 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -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" ) diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 02abf684..2b05f4c8 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -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. diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 73a3ba9f..5d288de5 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -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) } } } diff --git a/authority/root_test.go b/authority/root_test.go index a936b66f..6e5f1932 100644 --- a/authority/root_test.go +++ b/authority/root_test.go @@ -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) { diff --git a/authority/ssh.go b/authority/ssh.go index 05b899e6..bb0ff562 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -13,10 +13,10 @@ import ( "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/templates" - "github.com/smallstep/cli/crypto/randutil" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" + "go.step.sm/crypto/randutil" + "go.step.sm/crypto/sshutil" "golang.org/x/crypto/ssh" ) @@ -51,6 +51,21 @@ type Bastion struct { Flags string `json:"flags,omitempty"` } +// HostTag are tagged with k,v pairs. These tags are how a user is ultimately +// associated with a host. +type HostTag struct { + ID string + Name string + Value string +} + +// Host defines expected attributes for an ssh host. +type Host struct { + HostID string `json:"hid"` + HostTags []HostTag `json:"host_tags"` + Hostname string `json:"hostname"` +} + // Validate checks the fields in SSHConfig. func (c *SSHConfig) Validate() error { if c == nil { @@ -205,100 +220,113 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname str // SignSSH creates a signed SSH certificate with the given public key and options. func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { - var mods []provisioner.SSHCertModifier - var validators []provisioner.SSHCertValidator + var ( + certOptions []sshutil.Option + mods []provisioner.SSHCertModifier + validators []provisioner.SSHCertValidator + ) + + // Validate given options. + if err := opts.Validate(); err != nil { + return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH") + } // Set backdate with the configured value opts.Backdate = a.config.AuthorityConfig.Backdate.Duration for _, op := range signOpts { switch o := op.(type) { + // add options to NewCertificate + case provisioner.SSHCertificateOptions: + certOptions = append(certOptions, o.Options(opts)...) + // modify the ssh.Certificate case provisioner.SSHCertModifier: mods = append(mods, o) - // modify the ssh.Certificate given the SSHOptions - case provisioner.SSHCertOptionModifier: - mods = append(mods, o.Option(opts)) + // validate the ssh.Certificate case provisioner.SSHCertValidator: validators = append(validators, o) + // validate the given SSHOptions case provisioner.SSHCertOptionsValidator: if err := o.Valid(opts); err != nil { - return nil, errs.Wrap(http.StatusForbidden, err, "signSSH") + return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH") } + default: - return nil, errs.InternalServer("signSSH: invalid extra option type %T", o) + return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) } } - nonce, err := randutil.ASCII(32) + // 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 } diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 8e64b108..b5cce1fd 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -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"}, }) diff --git a/authority/tls.go b/authority/tls.go index f5d87d0d..a5474d60 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -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 } diff --git a/authority/tls_options.go b/authority/tls_options.go new file mode 100644 index 00000000..3edde605 --- /dev/null +++ b/authority/tls_options.go @@ -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, + } +} diff --git a/authority/tls_options_test.go b/authority/tls_options_test.go new file mode 100644 index 00000000..96c58c5d --- /dev/null +++ b/authority/tls_options_test.go @@ -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) + } + }) + } +} diff --git a/authority/tls_test.go b/authority/tls_test.go index a27f9c15..d9f1f6d4 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -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) diff --git a/ca/acmeClient.go b/ca/acmeClient.go index 3895381f..deb8a3a2 100644 --- a/ca/acmeClient.go +++ b/ca/acmeClient.go @@ -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. diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index 5163101a..25d74b9d 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -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) { diff --git a/ca/bootstrap.go b/ca/bootstrap.go index 6c532d5c..c9e859bf 100644 --- a/ca/bootstrap.go +++ b/ca/bootstrap.go @@ -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") } diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index 9b78d0ee..d93de892 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -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) } diff --git a/ca/ca_test.go b/ca/ca_test.go index 15e4d42a..6e297733 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -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) diff --git a/ca/client.go b/ca/client.go index 370126d6..1282d6f4 100644 --- a/ca/client.go +++ b/ca/client.go @@ -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 } diff --git a/ca/client_test.go b/ca/client_test.go index f880c876..dbba4d4c 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -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" ) diff --git a/ca/identity/identity.go b/ca/identity/identity.go index d37628f1..c570e46c 100644 --- a/ca/identity/identity.go +++ b/ca/identity/identity.go @@ -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. diff --git a/ca/identity/identity_test.go b/ca/identity/identity_test.go index 139c6917..7064cead 100644 --- a/ca/identity/identity_test.go +++ b/ca/identity/identity_test.go @@ -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) { diff --git a/ca/provisioner.go b/ca/provisioner.go index d5dfd648..80dd600a 100644 --- a/ca/provisioner.go +++ b/ca/provisioner.go @@ -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 diff --git a/ca/provisioner_test.go b/ca/provisioner_test.go index 1d20eff6..ea0ca51e 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -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) } diff --git a/ca/tls_test.go b/ca/tls_test.go index bf29e9a6..5513e06d 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -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) } diff --git a/cmd/step-awskms-init/main.go b/cmd/step-awskms-init/main.go index 2241cdd6..d7421c80 100644 --- a/cmd/step-awskms-init/main.go +++ b/cmd/step-awskms-init/main.go @@ -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" ) diff --git a/cmd/step-cloudkms-init/main.go b/cmd/step-cloudkms-init/main.go index eb23b048..9eab25bc 100644 --- a/cmd/step-cloudkms-init/main.go +++ b/cmd/step-cloudkms-init/main.go @@ -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" ) diff --git a/cmd/step-yubikey-init/main.go b/cmd/step-yubikey-init/main.go index 5a75a9ac..d5e81075 100644 --- a/cmd/step-yubikey-init/main.go +++ b/cmd/step-yubikey-init/main.go @@ -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" diff --git a/commands/onboard.go b/commands/onboard.go index 9f35c993..3494a3f2 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -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 diff --git a/go.mod b/go.mod index dd581e58..bdd788c6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/smallstep/certificates -go 1.13 +go 1.14 require ( cloud.google.com/go v0.51.0 @@ -19,14 +19,15 @@ require ( github.com/smallstep/cli v0.15.0 github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 - go.step.sm/crypto v0.2.0 + go.step.sm/crypto v0.3.0 golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/net v0.0.0-20200202094626-16171245cfb2 google.golang.org/api v0.15.0 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb google.golang.org/grpc v1.26.0 - gopkg.in/square/go-jose.v2 v2.4.0 + gopkg.in/square/go-jose.v2 v2.5.1 ) -//replace github.com/smallstep/cli => ../cli -//replace github.com/smallstep/nosql => ../nosql +// replace github.com/smallstep/cli => ../cli +// replace github.com/smallstep/nosql => ../nosql +// replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index 48cd441c..3fe8c84b 100644 --- a/go.sum +++ b/go.sum @@ -544,8 +544,8 @@ go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 h1:FymMl8TrXGxFf80BWpO0CnkS go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U= go.step.sm/crypto v0.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E= go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= -go.step.sm/crypto v0.2.0 h1:Rx+XqrNO4ZGHWlT9QCXls2L9pvcNiI23zEpAq0fctvY= -go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww= +go.step.sm/crypto v0.3.0 h1:wlbhLw2LvzDYwKcUJS2plBALptMXaKUhI2nbdfsXjTU= +go.step.sm/crypto v0.3.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -748,6 +748,8 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index df75d0e1..5e88eb80 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -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. diff --git a/kms/awskms/awskms_test.go b/kms/awskms/awskms_test.go index f19e1c49..c86645e2 100644 --- a/kms/awskms/awskms_test.go +++ b/kms/awskms/awskms_test.go @@ -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) { diff --git a/kms/awskms/signer.go b/kms/awskms/signer.go index 3d9767d0..0eec10c3 100644 --- a/kms/awskms/signer.go +++ b/kms/awskms/signer.go @@ -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. diff --git a/kms/awskms/signer_test.go b/kms/awskms/signer_test.go index 51915174..9694c62a 100644 --- a/kms/awskms/signer_test.go +++ b/kms/awskms/signer_test.go @@ -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) { diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index 01cbcab2..547bfc62 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -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" ) diff --git a/kms/cloudkms/cloudkms_test.go b/kms/cloudkms/cloudkms_test.go index c5eba318..1038432a 100644 --- a/kms/cloudkms/cloudkms_test.go +++ b/kms/cloudkms/cloudkms_test.go @@ -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" diff --git a/kms/cloudkms/signer.go b/kms/cloudkms/signer.go index b9232ca4..303c2496 100644 --- a/kms/cloudkms/signer.go +++ b/kms/cloudkms/signer.go @@ -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" ) diff --git a/kms/cloudkms/signer_test.go b/kms/cloudkms/signer_test.go index 9a05e131..dec176f4 100644 --- a/kms/cloudkms/signer_test.go +++ b/kms/cloudkms/signer_test.go @@ -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" ) diff --git a/kms/softkms/softkms.go b/kms/softkms/softkms.go index 3db9cbcc..e7873796 100644 --- a/kms/softkms/softkms.go +++ b/kms/softkms/softkms.go @@ -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 { diff --git a/kms/softkms/softkms_test.go b/kms/softkms/softkms_test.go index 44dccaa9..11c0cdd1 100644 --- a/kms/softkms/softkms_test.go +++ b/kms/softkms/softkms_test.go @@ -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) { diff --git a/make/docker.mk b/make/docker.mk index 1c932d74..8ed25219 100644 --- a/make/docker.mk +++ b/make/docker.mk @@ -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 diff --git a/pki/pki.go b/pki/pki.go index ec105e3b..f58cfb38 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/pkg/errors" "github.com/smallstep/certificates/authority" @@ -21,14 +22,13 @@ import ( "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/db" "github.com/smallstep/cli/config" - "github.com/smallstep/cli/crypto/keys" - "github.com/smallstep/cli/crypto/pemutil" - "github.com/smallstep/cli/crypto/tlsutil" - "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/errs" - "github.com/smallstep/cli/jose" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" + "go.step.sm/crypto/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/pemutil" + "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" ) @@ -114,6 +114,18 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) { } } +func generateDefaultKey() (crypto.Signer, error) { + priv, err := keyutil.GenerateDefaultKey() + if err != nil { + return nil, err + } + signer, ok := priv.(crypto.Signer) + if !ok { + return nil, errors.Errorf("type %T is not a cyrpto.Signer", priv) + } + return signer, nil +} + // GetProvisionerKey returns the encrypted provisioner key with the for the // given kid. func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { @@ -255,25 +267,35 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error { // GenerateRootCertificate generates a root certificate with the given name. func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { - rootProfile, err := x509util.NewRootProfile(name) + signer, err := generateDefaultKey() if err != nil { return nil, nil, err } - rootBytes, err := rootProfile.CreateWriteCertificate(p.root, p.rootKey, string(pass)) + cr, err := x509util.CreateCertificateRequest(name, []string{}, signer) if err != nil { return nil, nil, err } - rootCrt, err := x509.ParseCertificate(rootBytes) + data := x509util.CreateTemplateData(name, []string{}) + cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) if err != nil { - return nil, nil, errors.Wrap(err, "error parsing root certificate") + return nil, nil, err } - sum := sha256.Sum256(rootCrt.Raw) - p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:])) + template := cert.GetCertificate() + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.AddDate(10, 0, 0) + rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer) + if err != nil { + return nil, nil, err + } - return rootCrt, rootProfile.SubjectPrivateKey(), nil + if err := p.WriteRootCertificate(rootCrt, signer, pass); err != nil { + return nil, nil, err + } + + return rootCrt, signer, nil } // WriteRootCertificate writes to disk the given certificate and key. @@ -285,7 +307,7 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{ return err } - _, err := pemutil.Serialize(rootKey, pemutil.WithPassword([]byte(pass)), pemutil.ToFile(p.rootKey, 0600)) + _, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600)) if err != nil { return err } @@ -299,12 +321,46 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{ // GenerateIntermediateCertificate generates an intermediate certificate with // the given name. func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { - interProfile, err := x509util.NewIntermediateProfile(name, rootCrt, rootKey) + key, err := generateDefaultKey() if err != nil { return err } - _, err = interProfile.CreateWriteCertificate(p.intermediate, p.intermediateKey, string(pass)) - 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(), } diff --git a/sshutil/types.go b/sshutil/types.go deleted file mode 100644 index 9300771b..00000000 --- a/sshutil/types.go +++ /dev/null @@ -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"` -}