From af3eeb870e5ead176e83254119e2b282ec9dbedd Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 24 Jul 2020 17:08:32 -0700 Subject: [PATCH 01/47] Add package to generate ssh certificate for templates. --- sshutil/certificate.go | 104 +++++++++ sshutil/certificate_test.go | 347 ++++++++++++++++++++++++++++++ sshutil/options.go | 91 ++++++++ sshutil/options_test.go | 176 ++++++++++++++++ sshutil/templates.go | 132 ++++++++++++ sshutil/templates_test.go | 406 ++++++++++++++++++++++++++++++++++++ sshutil/testdata/github.tpl | 10 + sshutil/types.go | 65 ++++++ sshutil/types_test.go | 82 ++++++++ 9 files changed, 1413 insertions(+) create mode 100644 sshutil/certificate.go create mode 100644 sshutil/certificate_test.go create mode 100644 sshutil/options.go create mode 100644 sshutil/options_test.go create mode 100644 sshutil/templates.go create mode 100644 sshutil/templates_test.go create mode 100644 sshutil/testdata/github.tpl create mode 100644 sshutil/types_test.go diff --git a/sshutil/certificate.go b/sshutil/certificate.go new file mode 100644 index 00000000..473344a4 --- /dev/null +++ b/sshutil/certificate.go @@ -0,0 +1,104 @@ +package sshutil + +import ( + "crypto/rand" + "encoding/binary" + "encoding/json" + + "github.com/pkg/errors" + "github.com/smallstep/cli/crypto/randutil" + "golang.org/x/crypto/ssh" +) + +// Certificate is the json representation of ssh.Certificate. +type Certificate struct { + Nonce []byte `json:"nonce"` + Key ssh.PublicKey `json:"-"` + Serial uint64 `json:"serial"` + Type CertType `json:"type"` + KeyID string `json:"keyId"` + Principals []string `json:"principals"` + ValidAfter uint64 `json:"-"` + ValidBefore uint64 `json:"-"` + CriticalOptions map[string]string `json:"criticalOptions"` + Extensions map[string]string `json:"extensions"` + Reserved []byte `json:"reserved"` + SignatureKey ssh.PublicKey `json:"-"` + Signature *ssh.Signature `json:"-"` +} + +// NewCertificate creates a new certificate with the given key after parsing a +// template given in the options. +func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) { + o, err := new(Options).apply(key, opts) + if err != nil { + return nil, err + } + + if o.CertBuffer == nil { + return nil, errors.New("certificate template cannot be empty") + } + + // With templates + var cert Certificate + if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil { + return nil, errors.Wrap(err, "error unmarshaling certificate") + } + + // Complete with public key + cert.Key = key + + return &cert, nil +} + +func (c *Certificate) GetCertificate() *ssh.Certificate { + return &ssh.Certificate{ + Nonce: c.Nonce, + Key: c.Key, + Serial: c.Serial, + CertType: uint32(c.Type), + KeyId: c.KeyID, + ValidPrincipals: c.Principals, + ValidAfter: c.ValidAfter, + ValidBefore: c.ValidBefore, + Permissions: ssh.Permissions{ + CriticalOptions: c.CriticalOptions, + Extensions: c.Extensions, + }, + Reserved: c.Reserved, + } +} + +// CreateCertificate signs the given certificate with the given signer. If the +// certificate does not have a nonce or a serial, it will create random ones. +func CreateCertificate(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate, error) { + if len(cert.Nonce) == 0 { + nonce, err := randutil.ASCII(32) + if err != nil { + return nil, err + } + cert.Nonce = []byte(nonce) + } + + if cert.Serial == 0 { + if err := binary.Read(rand.Reader, binary.BigEndian, &cert.Serial); err != nil { + return nil, errors.Wrap(err, "error reading random number") + } + } + + // Set signer public key. + 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) + if err != nil { + return nil, errors.Wrap(err, "error signing certificate") + } + cert.Signature = sig + + return cert, nil +} diff --git a/sshutil/certificate_test.go b/sshutil/certificate_test.go new file mode 100644 index 00000000..ed8c5f9a --- /dev/null +++ b/sshutil/certificate_test.go @@ -0,0 +1,347 @@ +package sshutil + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "reflect" + "testing" + + "golang.org/x/crypto/ssh" +) + +func mustGenerateKey(t *testing.T) (ssh.PublicKey, ssh.Signer) { + t.Helper() + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + key, err := ssh.NewPublicKey(pub) + if err != nil { + t.Fatal(err) + } + signer, err := ssh.NewSignerFromKey(priv) + if err != nil { + t.Fatal(err) + } + return key, signer +} + +func mustGeneratePublicKey(t *testing.T) ssh.PublicKey { + t.Helper() + key, _ := mustGenerateKey(t) + return key +} + +func TestNewCertificate(t *testing.T) { + key := mustGeneratePublicKey(t) + + type args struct { + key ssh.PublicKey + opts []Option + } + tests := []struct { + name string + args args + want *Certificate + wantErr bool + }{ + {"user", args{key, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(UserCert, "jane@doe.com", []string{"jane"}))}}, &Certificate{ + Nonce: nil, + Key: key, + Serial: 0, + Type: UserCert, + KeyID: "jane@doe.com", + Principals: []string{"jane"}, + ValidAfter: 0, + ValidBefore: 0, + CriticalOptions: nil, + Extensions: map[string]string{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + }, + Reserved: nil, + SignatureKey: nil, + Signature: nil, + }, false}, + {"host", args{key, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(HostCert, "foobar", []string{"foo.internal", "bar.internal"}))}}, &Certificate{ + Nonce: nil, + Key: key, + Serial: 0, + Type: HostCert, + KeyID: "foobar", + Principals: []string{"foo.internal", "bar.internal"}, + ValidAfter: 0, + ValidBefore: 0, + CriticalOptions: nil, + Extensions: nil, + Reserved: nil, + SignatureKey: nil, + Signature: nil, + }, false}, + {"file", args{key, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{ + TypeKey: UserCert, + KeyIDKey: "john@doe.com", + PrincipalsKey: []string{"john", "john@doe.com"}, + ExtensionsKey: DefaultExtensions(UserCert), + InsecureKey: map[string]interface{}{ + "User": map[string]interface{}{"username": "john"}, + }, + })}}, &Certificate{ + Nonce: nil, + Key: key, + Serial: 0, + Type: UserCert, + KeyID: "john@doe.com", + Principals: []string{"john", "john@doe.com"}, + ValidAfter: 0, + ValidBefore: 0, + CriticalOptions: nil, + Extensions: map[string]string{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + "login@github.com": "john", + }, + Reserved: nil, + SignatureKey: nil, + Signature: nil, + }, false}, + {"base64", args{key, []Option{WithTemplateBase64(base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), CreateTemplateData(HostCert, "foo.internal", nil))}}, &Certificate{ + Nonce: nil, + Key: key, + Serial: 0, + Type: HostCert, + KeyID: "foo.internal", + Principals: nil, + ValidAfter: 0, + ValidBefore: 0, + CriticalOptions: nil, + Extensions: nil, + Reserved: nil, + SignatureKey: nil, + Signature: nil, + }, false}, + {"failNilOptions", args{key, nil}, nil, true}, + {"failEmptyOptions", args{key, nil}, nil, true}, + {"badBase64Template", args{key, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true}, + {"badFileTemplate", args{key, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true}, + {"badJsonTemplate", args{key, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true}, + {"failTemplate", args{key, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewCertificate(tt.args.key, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCertificate_GetCertificate(t *testing.T) { + key := mustGeneratePublicKey(t) + + type fields struct { + Nonce []byte + Key ssh.PublicKey + Serial uint64 + Type CertType + KeyID string + Principals []string + ValidAfter uint64 + ValidBefore uint64 + CriticalOptions map[string]string + Extensions map[string]string + Reserved []byte + SignatureKey ssh.PublicKey + Signature *ssh.Signature + } + tests := []struct { + name string + fields fields + want *ssh.Certificate + }{ + {"user", fields{ + Nonce: []byte("0123456789"), + Key: key, + Serial: 123, + Type: UserCert, + KeyID: "key-id", + Principals: []string{"john"}, + ValidAfter: 1111, + ValidBefore: 2222, + CriticalOptions: map[string]string{"foo": "bar"}, + Extensions: map[string]string{"login@github.com": "john"}, + Reserved: []byte("reserved"), + SignatureKey: key, + Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")}, + }, &ssh.Certificate{ + Nonce: []byte("0123456789"), + Key: key, + Serial: 123, + CertType: ssh.UserCert, + KeyId: "key-id", + ValidPrincipals: []string{"john"}, + ValidAfter: 1111, + ValidBefore: 2222, + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{"foo": "bar"}, + Extensions: map[string]string{"login@github.com": "john"}, + }, + Reserved: []byte("reserved"), + }}, + {"host", fields{ + Nonce: []byte("0123456789"), + Key: key, + Serial: 123, + Type: HostCert, + KeyID: "key-id", + Principals: []string{"foo.internal", "bar.internal"}, + ValidAfter: 1111, + ValidBefore: 2222, + CriticalOptions: map[string]string{"foo": "bar"}, + Extensions: nil, + Reserved: []byte("reserved"), + SignatureKey: key, + Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")}, + }, &ssh.Certificate{ + Nonce: []byte("0123456789"), + Key: key, + Serial: 123, + CertType: ssh.HostCert, + KeyId: "key-id", + ValidPrincipals: []string{"foo.internal", "bar.internal"}, + ValidAfter: 1111, + ValidBefore: 2222, + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{"foo": "bar"}, + Extensions: nil, + }, + Reserved: []byte("reserved"), + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Certificate{ + Nonce: tt.fields.Nonce, + Key: tt.fields.Key, + Serial: tt.fields.Serial, + Type: tt.fields.Type, + KeyID: tt.fields.KeyID, + Principals: tt.fields.Principals, + ValidAfter: tt.fields.ValidAfter, + ValidBefore: tt.fields.ValidBefore, + CriticalOptions: tt.fields.CriticalOptions, + Extensions: tt.fields.Extensions, + Reserved: tt.fields.Reserved, + SignatureKey: tt.fields.SignatureKey, + Signature: tt.fields.Signature, + } + if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Certificate.GetCertificate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCreateCertificate(t *testing.T) { + key, signer := mustGenerateKey(t) + type args struct { + cert *ssh.Certificate + signer ssh.Signer + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"ok", args{&ssh.Certificate{ + Nonce: []byte("0123456789"), + Key: key, + Serial: 123, + CertType: ssh.HostCert, + KeyId: "foo", + ValidPrincipals: []string{"foo.internal"}, + ValidAfter: 1111, + ValidBefore: 2222, + Permissions: ssh.Permissions{}, + Reserved: []byte("reserved"), + }, signer}, false}, + {"emptyNonce", args{&ssh.Certificate{ + Key: key, + Serial: 123, + CertType: ssh.UserCert, + KeyId: "jane@doe.com", + ValidPrincipals: []string{"jane"}, + ValidAfter: 1111, + ValidBefore: 2222, + Permissions: ssh.Permissions{}, + Reserved: []byte("reserved"), + }, signer}, false}, + {"emptySerial", args{&ssh.Certificate{ + Nonce: []byte("0123456789"), + Key: key, + CertType: ssh.UserCert, + KeyId: "jane@doe.com", + ValidPrincipals: []string{"jane"}, + ValidAfter: 1111, + ValidBefore: 2222, + Permissions: ssh.Permissions{}, + Reserved: []byte("reserved"), + }, signer}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := CreateCertificate(tt.args.cert, tt.args.signer) + if (err != nil) != tt.wantErr { + t.Errorf("CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil { + switch { + case len(got.Nonce) == 0: + t.Errorf("CreateCertificate() nonce should not be empty") + case got.Serial == 0: + t.Errorf("CreateCertificate() serial should not be 0") + case got.Signature == nil: + t.Errorf("CreateCertificate() signature should not be nil") + case !bytes.Equal(got.SignatureKey.Marshal(), tt.args.signer.PublicKey().Marshal()): + t.Errorf("CreateCertificate() signature key is not the expected one") + } + + signature := got.Signature + got.Signature = nil + + data := got.Marshal() + data = data[:len(data)-4] + + sig, err := signer.Sign(rand.Reader, data) + if err != nil { + t.Errorf("signer.Sign() error = %v", err) + } + + // Verify signature + got.Signature = signature + if err := signer.PublicKey().Verify(data, got.Signature); err != nil { + t.Errorf("CreateCertificate() signature verify error = %v", err) + } + // Verify data with public key in cert + if err := got.Verify(data, sig); err != nil { + t.Errorf("CreateCertificate() certificate verify error = %v", err) + } + + } + }) + } +} diff --git a/sshutil/options.go b/sshutil/options.go new file mode 100644 index 00000000..58562ba9 --- /dev/null +++ b/sshutil/options.go @@ -0,0 +1,91 @@ +package sshutil + +import ( + "bytes" + "encoding/base64" + "io/ioutil" + "text/template" + + "github.com/Masterminds/sprig/v3" + "github.com/pkg/errors" + "github.com/smallstep/cli/config" + "golang.org/x/crypto/ssh" +) + +func getFuncMap(failMessage *string) template.FuncMap { + m := sprig.TxtFuncMap() + m["fail"] = func(msg string) (string, error) { + *failMessage = msg + return "", errors.New(msg) + } + return m +} + +// Options are the options that can be passed to NewCertificate. +type Options struct { + CertBuffer *bytes.Buffer +} + +func (o *Options) apply(key ssh.PublicKey, opts []Option) (*Options, error) { + for _, fn := range opts { + if err := fn(key, o); err != nil { + return o, err + } + } + return o, nil +} + +// Option is the type used as a variadic argument in NewCertificate. +type Option func(key ssh.PublicKey, o *Options) error + +// WithTemplate is an options that executes the given template text with the +// given data. +func WithTemplate(text string, data TemplateData) Option { + return func(key ssh.PublicKey, o *Options) error { + terr := new(TemplateError) + funcMap := getFuncMap(&terr.Message) + + tmpl, err := template.New("template").Funcs(funcMap).Parse(text) + if err != nil { + return errors.Wrapf(err, "error parsing template") + } + + buf := new(bytes.Buffer) + data.SetPublicKey(key) + if err := tmpl.Execute(buf, data); err != nil { + if terr.Message != "" { + return terr + } + return errors.Wrapf(err, "error executing template") + } + o.CertBuffer = buf + return nil + } +} + +// WithTemplateBase64 is an options that executes the given template base64 +// string with the given data. +func WithTemplateBase64(s string, data TemplateData) Option { + return func(key ssh.PublicKey, o *Options) error { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return errors.Wrap(err, "error decoding template") + } + fn := WithTemplate(string(b), data) + return fn(key, o) + } +} + +// WithTemplateFile is an options that reads the template file and executes it +// with the given data. +func WithTemplateFile(path string, data TemplateData) Option { + return func(key ssh.PublicKey, o *Options) error { + filename := config.StepAbs(path) + b, err := ioutil.ReadFile(filename) + if err != nil { + return errors.Wrapf(err, "error reading %s", path) + } + fn := WithTemplate(string(b), data) + return fn(key, o) + } +} diff --git a/sshutil/options_test.go b/sshutil/options_test.go new file mode 100644 index 00000000..ac4da67d --- /dev/null +++ b/sshutil/options_test.go @@ -0,0 +1,176 @@ +package sshutil + +import ( + "bytes" + "encoding/base64" + "reflect" + "testing" + + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" +) + +func Test_getFuncMap_fail(t *testing.T) { + var failMesage string + fns := getFuncMap(&failMesage) + fail := fns["fail"].(func(s string) (string, error)) + s, err := fail("the fail message") + if err == nil { + t.Errorf("fail() error = %v, wantErr %v", err, errors.New("the fail message")) + } + if s != "" { + t.Errorf("fail() = \"%s\", want \"the fail message\"", s) + } + if failMesage != "the fail message" { + t.Errorf("fail() message = \"%s\", want \"the fail message\"", failMesage) + } +} + +func TestWithTemplate(t *testing.T) { + key := mustGeneratePublicKey(t) + + type args struct { + text string + data TemplateData + key ssh.PublicKey + } + tests := []struct { + name string + args args + want Options + wantErr bool + }{ + {"user", args{DefaultCertificate, TemplateData{ + TypeKey: "user", + KeyIDKey: "jane@doe.com", + PrincipalsKey: []string{"jane", "jane@doe.com"}, + ExtensionsKey: DefaultExtensions(UserCert), + }, key}, Options{ + CertBuffer: bytes.NewBufferString(`{ + "type": "user", + "keyId": "jane@doe.com", + "principals": ["jane","jane@doe.com"], + "extensions": {"permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""} +}`)}, false}, + {"host", args{DefaultCertificate, TemplateData{ + TypeKey: "host", + KeyIDKey: "foo", + PrincipalsKey: []string{"foo.internal"}, + }, key}, Options{ + CertBuffer: bytes.NewBufferString(`{ + "type": "host", + "keyId": "foo", + "principals": ["foo.internal"], + "extensions": null +}`)}, false}, + {"fail", args{`{{ fail "a message" }}`, TemplateData{}, key}, Options{}, true}, + {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, key}, Options{}, true}, + {"error", args{`{{ mustHas 3 .Data }}`, TemplateData{ + "Data": 3, + }, key}, Options{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Options + fn := WithTemplate(tt.args.text, tt.args.data) + if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("WithTemplate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestWithTemplateBase64(t *testing.T) { + key := mustGeneratePublicKey(t) + + type args struct { + s string + data TemplateData + key ssh.PublicKey + } + tests := []struct { + name string + args args + want Options + wantErr bool + }{ + {"host", args{base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), TemplateData{ + TypeKey: "host", + KeyIDKey: "foo.internal", + PrincipalsKey: []string{"foo.internal", "bar.internal"}, + ExtensionsKey: map[string]interface{}{"foo": "bar"}, + }, key}, Options{ + CertBuffer: bytes.NewBufferString(`{ + "type": "host", + "keyId": "foo.internal", + "principals": ["foo.internal","bar.internal"], + "extensions": {"foo":"bar"} +}`)}, false}, + {"badBase64", args{"foobar", TemplateData{}, key}, Options{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Options + fn := WithTemplateBase64(tt.args.s, tt.args.data) + if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("WithTemplateBase64() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestWithTemplateFile(t *testing.T) { + key := mustGeneratePublicKey(t) + + data := TemplateData{ + TypeKey: "user", + KeyIDKey: "jane@doe.com", + PrincipalsKey: []string{"jane", "jane@doe.com"}, + ExtensionsKey: DefaultExtensions(UserCert), + InsecureKey: map[string]interface{}{ + UserKey: map[string]interface{}{ + "username": "jane", + }, + }, + } + + type args struct { + path string + data TemplateData + key ssh.PublicKey + } + tests := []struct { + name string + args args + want Options + wantErr bool + }{ + {"github.com", args{"./testdata/github.tpl", data, key}, Options{ + CertBuffer: bytes.NewBufferString(`{ + "type": "user", + "keyId": "jane@doe.com", + "principals": ["jane","jane@doe.com"], + "extensions": {"login@github.com":"jane","permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""} +}`), + }, false}, + {"missing", args{"./testdata/missing.tpl", data, key}, Options{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Options + fn := WithTemplateFile(tt.args.path, tt.args.data) + if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("WithTemplateFile() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/sshutil/templates.go b/sshutil/templates.go new file mode 100644 index 00000000..c925da3a --- /dev/null +++ b/sshutil/templates.go @@ -0,0 +1,132 @@ +package sshutil + +import "golang.org/x/crypto/ssh" + +const ( + TypeKey = "Type" + KeyIDKey = "KeyID" + PrincipalsKey = "Principals" + ExtensionsKey = "Extensions" + TokenKey = "Token" + InsecureKey = "Insecure" + UserKey = "User" + PublicKey = "PublicKey" +) + +// TemplateError represents an error in a template produced by the fail +// function. +type TemplateError struct { + Message string +} + +// Error implements the error interface and returns the error string when a +// template executes the `fail "message"` function. +func (e *TemplateError) Error() string { + return e.Message +} + +// TemplateData is an alias for map[string]interface{}. It represents the data +// passed to the templates. +type TemplateData map[string]interface{} + +// CreateTemplateData returns a TemplateData with the given certificate type, +// key id, principals, and the default extensions. +func CreateTemplateData(ct CertType, keyID string, principals []string) TemplateData { + return TemplateData{ + TypeKey: ct.String(), + KeyIDKey: keyID, + PrincipalsKey: principals, + ExtensionsKey: DefaultExtensions(ct), + } +} + +// DefaultExtensions returns the default extensions set in an SSH certificate. +func DefaultExtensions(ct CertType) map[string]interface{} { + switch ct { + case UserCert: + return map[string]interface{}{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + } + default: + return nil + } +} + +// NewTemplateData creates a new map for templates data. +func NewTemplateData() TemplateData { + return TemplateData{} +} + +// AddExtension adds one extension to the templates data. +func (t TemplateData) AddExtension(key, value string) { + if m, ok := t[ExtensionsKey].(map[string]interface{}); ok { + m[key] = value + } else { + t[ExtensionsKey] = map[string]interface{}{ + key: value, + } + } +} + +// Set sets a key-value pair in the template data. +func (t TemplateData) Set(key string, v interface{}) { + t[key] = v +} + +// SetInsecure sets a key-value pair in the insecure template data. +func (t TemplateData) SetInsecure(key string, v interface{}) { + if m, ok := t[InsecureKey].(TemplateData); ok { + m[key] = v + } else { + t[InsecureKey] = TemplateData{key: v} + } +} + +// SetType sets the certificate type in the template data. +func (t TemplateData) SetType(typ CertType) { + t.Set(TypeKey, typ.String()) +} + +// SetType sets the certificate key id in the template data. +func (t TemplateData) SetKeyID(id string) { + t.Set(KeyIDKey, id) +} + +// SetPrincipals sets the certificate principals in the template data. +func (t TemplateData) SetPrincipals(p []string) { + t.Set(PrincipalsKey, p) +} + +// SetExtensions sets the certificate extensions in the template data. +func (t TemplateData) SetExtensions(e map[string]interface{}) { + t.Set(ExtensionsKey, e) +} + +// SetToken sets the given token in the template data. +func (t TemplateData) SetToken(v interface{}) { + t.Set(TokenKey, v) +} + +// SetUserData sets the given user provided object in the insecure template +// data. +func (t TemplateData) SetUserData(v interface{}) { + t.SetInsecure(UserKey, v) +} + +// SetUserData sets the given user provided object in the insecure template +// data. +func (t TemplateData) SetPublicKey(v ssh.PublicKey) { + t.Set(PublicKey, v) +} + +// DefaultCertificate is the default template for an SSH certificate. +const DefaultCertificate = `{ + "type": "{{ .Type }}", + "keyId": "{{ .KeyID }}", + "principals": {{ toJson .Principals }}, + "extensions": {{ toJson .Extensions }} +}` diff --git a/sshutil/templates_test.go b/sshutil/templates_test.go new file mode 100644 index 00000000..ad550ccb --- /dev/null +++ b/sshutil/templates_test.go @@ -0,0 +1,406 @@ +package sshutil + +import ( + "reflect" + "testing" + + "golang.org/x/crypto/ssh" +) + +func TestTemplateError_Error(t *testing.T) { + type fields struct { + Message string + } + tests := []struct { + name string + fields fields + want string + }{ + {"ok", fields{"message"}, "message"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &TemplateError{ + Message: tt.fields.Message, + } + if got := e.Error(); got != tt.want { + t.Errorf("TemplateError.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCreateTemplateData(t *testing.T) { + type args struct { + ct CertType + keyID string + principals []string + } + tests := []struct { + name string + args args + want TemplateData + }{ + {"user", args{UserCert, "john@doe.com", []string{"john", "john@doe.com"}}, TemplateData{ + TypeKey: "user", + KeyIDKey: "john@doe.com", + PrincipalsKey: []string{"john", "john@doe.com"}, + ExtensionsKey: map[string]interface{}{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + }, + }}, + {"host", args{HostCert, "foo", []string{"foo.internal"}}, TemplateData{ + TypeKey: "host", + KeyIDKey: "foo", + PrincipalsKey: []string{"foo.internal"}, + ExtensionsKey: map[string]interface{}(nil), + }}, + {"other", args{100, "foo", []string{"foo.internal"}}, TemplateData{ + TypeKey: "", + KeyIDKey: "foo", + PrincipalsKey: []string{"foo.internal"}, + ExtensionsKey: map[string]interface{}(nil), + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CreateTemplateData(tt.args.ct, tt.args.keyID, tt.args.principals); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CreateTemplateData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDefaultExtensions(t *testing.T) { + type args struct { + ct CertType + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + {"user", args{UserCert}, map[string]interface{}{ + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "", + }}, + {"host", args{HostCert}, nil}, + {"other", args{100}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := DefaultExtensions(tt.args.ct); !reflect.DeepEqual(got, tt.want) { + t.Errorf("DefaultExtensions() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewTemplateData(t *testing.T) { + tests := []struct { + name string + want TemplateData + }{ + {"ok", TemplateData{}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewTemplateData(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewTemplateData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTemplateData_AddExtension(t *testing.T) { + type args struct { + key string + value string + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"empty", TemplateData{}, args{"key", "value"}, TemplateData{ + ExtensionsKey: map[string]interface{}{"key": "value"}, + }}, + {"overwrite", TemplateData{ + ExtensionsKey: map[string]interface{}{"key": "value"}, + }, args{"key", "value"}, TemplateData{ + ExtensionsKey: map[string]interface{}{ + "key": "value", + }, + }}, + {"add", TemplateData{ + ExtensionsKey: map[string]interface{}{"foo": "bar"}, + }, args{"key", "value"}, TemplateData{ + ExtensionsKey: map[string]interface{}{ + "key": "value", + "foo": "bar", + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.AddExtension(tt.args.key, tt.args.value) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("AddExtension() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_Set(t *testing.T) { + type args struct { + key string + v interface{} + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"foo", "bar"}, TemplateData{ + "foo": "bar", + }}, + {"overwrite", TemplateData{}, args{"foo", "bar"}, TemplateData{ + "foo": "bar", + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.Set(tt.args.key, tt.args.v) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("Set() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_SetInsecure(t *testing.T) { + type args struct { + key string + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{InsecureKey: TemplateData{"foo": "bar"}}}, + {"overwrite", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{InsecureKey: TemplateData{"foo": "zar"}}}, + {"add", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", "bar": "foo"}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetInsecure(tt.args.key, tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetInsecure() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetType(t *testing.T) { + type args struct { + typ CertType + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"user", TemplateData{}, args{UserCert}, TemplateData{ + TypeKey: "user", + }}, + {"host", TemplateData{}, args{HostCert}, TemplateData{ + TypeKey: "host", + }}, + {"overwrite", TemplateData{ + TypeKey: "host", + }, args{UserCert}, TemplateData{ + TypeKey: "user", + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetType(tt.args.typ) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetType() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_SetKeyID(t *testing.T) { + type args struct { + id string + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"key-id"}, TemplateData{ + KeyIDKey: "key-id", + }}, + {"overwrite", TemplateData{}, args{"key-id-2"}, TemplateData{ + KeyIDKey: "key-id-2", + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetKeyID(tt.args.id) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetKeyID() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_SetPrincipals(t *testing.T) { + type args struct { + p []string + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{[]string{"jane"}}, TemplateData{ + PrincipalsKey: []string{"jane"}, + }}, + {"overwrite", TemplateData{}, args{[]string{"john", "john@doe.com"}}, TemplateData{ + PrincipalsKey: []string{"john", "john@doe.com"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetPrincipals(tt.args.p) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetPrincipals() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_SetExtensions(t *testing.T) { + type args struct { + e map[string]interface{} + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{ + ExtensionsKey: map[string]interface{}{"foo": "bar"}, + }}, + {"overwrite", TemplateData{ + ExtensionsKey: map[string]interface{}{"foo": "bar"}, + }, args{map[string]interface{}{"key": "value"}}, TemplateData{ + ExtensionsKey: map[string]interface{}{"key": "value"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetExtensions(tt.args.e) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetExtensions() = %v, want %v", tt.t, tt.want) + } + }) + } +} + +func TestTemplateData_SetToken(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"token"}, TemplateData{TokenKey: "token"}}, + {"overwrite", TemplateData{TokenKey: "foo"}, args{"token"}, TemplateData{TokenKey: "token"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetToken(tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetToken() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetUserData(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + td TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, + {"overwrite", TemplateData{InsecureKey: TemplateData{UserKey: "foo"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, + {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", UserKey: "userData"}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.td.SetUserData(tt.args.v) + if !reflect.DeepEqual(tt.td, tt.want) { + t.Errorf("TemplateData.SetUserData() = %v, want %v", tt.td, tt.want) + } + }) + } +} + +func TestTemplateData_SetPublicKey(t *testing.T) { + k1 := mustGeneratePublicKey(t) + k2 := mustGeneratePublicKey(t) + type args struct { + v ssh.PublicKey + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{k1}, TemplateData{ + PublicKey: k1, + }}, + {"overwrite", TemplateData{ + PublicKey: k1, + }, args{k2}, TemplateData{ + PublicKey: k2, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetPublicKey(tt.args.v) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("TemplateData.SetPublicKey() = %v, want %v", tt.t, tt.want) + } + }) + } +} diff --git a/sshutil/testdata/github.tpl b/sshutil/testdata/github.tpl new file mode 100644 index 00000000..2340522f --- /dev/null +++ b/sshutil/testdata/github.tpl @@ -0,0 +1,10 @@ +{ + "type": "{{ .Type }}", + "keyId": "{{ .KeyID }}", + "principals": {{ toJson .Principals }}, +{{- if .Insecure.User.username }} + "extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }} +{{- else }} + "extensions": {{ toJson .Extensions }} +{{- end }} +} \ No newline at end of file diff --git a/sshutil/types.go b/sshutil/types.go index 9300771b..bf62933b 100644 --- a/sshutil/types.go +++ b/sshutil/types.go @@ -1,5 +1,13 @@ package sshutil +import ( + "encoding/json" + "strings" + + "github.com/pkg/errors" + "golang.org/x/crypto/ssh" +) + // Hosts are tagged with k,v pairs. These tags are how a user is ultimately // associated with a host. type HostTag struct { @@ -14,3 +22,60 @@ type Host struct { HostTags []HostTag `json:"host_tags"` Hostname string `json:"hostname"` } + +// CertType defines the certificate type, it can be a user or a host +// certificate. +type CertType uint32 + +const ( + // UserCert defines a user certificate. + UserCert CertType = ssh.UserCert + + // HostCert defines a host certificate. + HostCert CertType = ssh.HostCert +) + +const ( + userString = "user" + hostString = "host" +) + +// String returns "user" for user certificates and "host" for host certificates. +// It will return the empty string for any other value. +func (c CertType) String() string { + switch c { + case UserCert: + return userString + case HostCert: + return hostString + default: + return "" + } +} + +// MarshalJSON implements the json.Marshaler interface for CertType. UserCert +// will be marshaled as the string "user" and HostCert as "host". +func (c CertType) MarshalJSON() ([]byte, error) { + if s := c.String(); s != "" { + return []byte(`"` + s + `"`), nil + } + return nil, errors.Errorf("unknown certificate type %d", c) +} + +// UnmarshalJSON implements the json.Unmarshaler interface for CertType. +func (c *CertType) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return errors.Wrap(err, "error unmarshaling certificate type") + } + switch strings.ToLower(s) { + case userString: + *c = UserCert + return nil + case hostString: + *c = HostCert + return nil + default: + return errors.Errorf("error unmarshaling '%s' as a certificate type", s) + } +} diff --git a/sshutil/types_test.go b/sshutil/types_test.go new file mode 100644 index 00000000..15306554 --- /dev/null +++ b/sshutil/types_test.go @@ -0,0 +1,82 @@ +package sshutil + +import ( + "reflect" + "testing" +) + +func TestCertType_String(t *testing.T) { + tests := []struct { + name string + c CertType + want string + }{ + {"user", UserCert, "user"}, + {"host", HostCert, "host"}, + {"empty", 100, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.c.String(); got != tt.want { + t.Errorf("CertType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCertType_MarshalJSON(t *testing.T) { + tests := []struct { + name string + c CertType + want []byte + wantErr bool + }{ + {"user", UserCert, []byte(`"user"`), false}, + {"host", HostCert, []byte(`"host"`), false}, + {"error", 100, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.c.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("CertType.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("CertType.MarshalJSON() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCertType_UnmarshalJSON(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + args args + want CertType + wantErr bool + }{ + {"user", args{[]byte(`"user"`)}, UserCert, false}, + {"USER", args{[]byte(`"USER"`)}, UserCert, false}, + {"host", args{[]byte(`"host"`)}, HostCert, false}, + {"HosT", args{[]byte(`"HosT"`)}, HostCert, false}, + {" user ", args{[]byte(`" user "`)}, 0, true}, + {"number", args{[]byte(`1`)}, 0, true}, + {"object", args{[]byte(`{}`)}, 0, true}, + {"badJSON", args{[]byte(`"user`)}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ct CertType + if err := ct.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("CertType.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(ct, tt.want) { + t.Errorf("CertType.UnmarshalJSON() = %v, want %v", ct, tt.want) + } + }) + } +} From c6746425a3bc7ae390a87a8b717ac0fc4bd914b5 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 24 Jul 2020 17:09:29 -0700 Subject: [PATCH 02/47] Add methods to initialize ssh templates in provisioners. --- authority/provisioner/ssh_options.go | 107 +++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 authority/provisioner/ssh_options.go diff --git a/authority/provisioner/ssh_options.go b/authority/provisioner/ssh_options.go new file mode 100644 index 00000000..784b0958 --- /dev/null +++ b/authority/provisioner/ssh_options.go @@ -0,0 +1,107 @@ +package provisioner + +import ( + "encoding/json" + "strings" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/sshutil" +) + +// CertificateOptions 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 CertificateOptions 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 *SSHOptions, data sshutil.TemplateData) (SSHCertificateOptions, error) { + return CustomSSHTemplateOptions(o, data, sshutil.DefaultCertificate) +} + +// 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 *SSHOptions, data sshutil.TemplateData, defaultTemplate string) (SSHCertificateOptions, error) { + if o != nil { + if data == nil { + data = sshutil.NewTemplateData() + } + + // Add template data if any. + if len(o.TemplateData) > 0 { + if err := json.Unmarshal(o.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 !o.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 o.Template == "" && o.TemplateFile != "" { + return []sshutil.Option{ + sshutil.WithTemplateFile(o.TemplateFile, data), + } + } + + // Load a template from the Template fields + // 1. As a JSON in a string. + template := strings.TrimSpace(o.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 +} From fdd0eb67739a73e47fa601ea9cc65936234cf494 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 11:06:51 -0700 Subject: [PATCH 03/47] Create method CertTypeFromString(s string). --- sshutil/types.go | 24 ++++++++++++++++-------- sshutil/types_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/sshutil/types.go b/sshutil/types.go index bf62933b..d1c45564 100644 --- a/sshutil/types.go +++ b/sshutil/types.go @@ -40,6 +40,18 @@ const ( hostString = "host" ) +// CertTypeFromString returns the CertType for the string "user" and "host". +func CertTypeFromString(s string) (CertType, error) { + switch strings.ToLower(s) { + case userString: + return UserCert, nil + case hostString: + return HostCert, nil + default: + return 0, errors.Errorf("unknown certificate type '%s'", s) + } +} + // String returns "user" for user certificates and "host" for host certificates. // It will return the empty string for any other value. func (c CertType) String() string { @@ -68,14 +80,10 @@ func (c *CertType) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &s); err != nil { return errors.Wrap(err, "error unmarshaling certificate type") } - switch strings.ToLower(s) { - case userString: - *c = UserCert - return nil - case hostString: - *c = HostCert - return nil - default: + certType, err := CertTypeFromString(s) + if err != nil { return errors.Errorf("error unmarshaling '%s' as a certificate type", s) } + *c = certType + return nil } diff --git a/sshutil/types_test.go b/sshutil/types_test.go index 15306554..556b461c 100644 --- a/sshutil/types_test.go +++ b/sshutil/types_test.go @@ -5,6 +5,37 @@ import ( "testing" ) +func TestCertTypeFromString(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want CertType + wantErr bool + }{ + {"user", args{"user"}, UserCert, false}, + {"USER", args{"USER"}, UserCert, false}, + {"host", args{"host"}, HostCert, false}, + {"Host", args{"Host"}, HostCert, false}, + {" user ", args{" user "}, 0, true}, + {"invalid", args{"invalid"}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := CertTypeFromString(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("CertTypeFromString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("CertTypeFromString() = %v, want %v", got, tt.want) + } + }) + } +} + func TestCertType_String(t *testing.T) { tests := []struct { name string From 631f1612a16a49e2443596f194ab3bd5e6710e0e Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:41:53 -0700 Subject: [PATCH 04/47] Add TemplateData to SignSSHOptions. --- authority/provisioner/sign_ssh_options.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index e823a7d8..d08cdab9 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -3,6 +3,7 @@ package provisioner import ( "crypto/rsa" "encoding/binary" + "encoding/json" "math/big" "time" @@ -56,12 +57,13 @@ func (f sshModifierFunc) Modify(cert *ssh.Certificate) error { // 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"` + Backdate time.Duration `json:"-"` } // Type returns the uint32 representation of the CertType. From 570ede45e79091a017eceba3218f77c00bc1fea9 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:42:52 -0700 Subject: [PATCH 05/47] Do not enforce number of principals or extensions. --- authority/provisioner/sign_ssh_options.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index d08cdab9..6352204f 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -356,7 +356,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: @@ -369,16 +371,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: From b66d1235726d183c0028b8cae982f420b0a180c4 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:43:41 -0700 Subject: [PATCH 06/47] Use sshutil for SSH certificate signing. --- authority/ssh.go | 56 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/authority/ssh.go b/authority/ssh.go index 05b899e6..87e4bc8e 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -205,56 +205,66 @@ 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 + ) // 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") } + default: return nil, errs.InternalServer("signSSH: invalid extra option type %T", o) } } - nonce, err := randutil.ASCII(32) + // Create certificate from template. + certificate, err := sshutil.NewCertificate(key, 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 user and provisioner + // modifiers. + cert := certificate.GetCertificate() - // 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 + // Use SignSSHOptions 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") @@ -277,20 +287,14 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi default: return nil, errs.InternalServer("signSSH: 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) + // Sign certificate. + cert, err = sshutil.CreateCertificate(cert, signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "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") From d7e590908e0b23a6c02a854206357c08f729a3fc Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:54:51 -0700 Subject: [PATCH 07/47] Use sshutil for ssh renewing and rekeying. --- authority/ssh.go | 58 ++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 44 deletions(-) diff --git a/authority/ssh.go b/authority/ssh.go index 87e4bc8e..61050029 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -310,16 +310,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") } @@ -330,15 +320,15 @@ 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 + // Build base certificate with the old key. + // Nonce and serial will be automatically generated on signing. cert := &ssh.Certificate{ - Nonce: []byte(nonce), 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()), } @@ -359,18 +349,13 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss default: return nil, errs.InternalServer("renewSSH: 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, "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") @@ -393,16 +378,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") } @@ -413,15 +388,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()), } @@ -442,18 +417,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 { From f75a12e10ae8990751b6c16869ffbe4519291884 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:57:29 -0700 Subject: [PATCH 08/47] Add omitempty tag option. --- authority/provisioner/options.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index c56b6064..713d48ec 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -39,14 +39,14 @@ func (o *Options) GetX509Options() *X509Options { 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. From 380a0d6daf951e7542fcb82dfd8a18de8025a090 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 15:58:05 -0700 Subject: [PATCH 09/47] Add ssh certificate templates to JWK provisioner. --- authority/provisioner/jwk.go | 45 ++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 2133727b..1769860a 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -33,6 +34,7 @@ type JWK struct { EncryptedKey string `json:"encryptedKey,omitempty"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` claimer *Claimer audiences Audiences } @@ -207,33 +209,46 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, sshCertOptionsValidator(*opts), } - 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.SSHOptions, 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. From c2dc76550ce519b53ddba7d8b340d6732d52abdb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 16:03:16 -0700 Subject: [PATCH 10/47] Add ssh certificate template to X5C provisioner. --- authority/provisioner/x5c.go | 58 +++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index ff01b151..ee05078e 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -25,14 +26,15 @@ type x5cPayload struct { // signature requests. type X5C struct { *base - Type string `json:"type"` - Name string `json:"name"` - Roots []byte `json:"roots"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - claimer *Claimer - audiences Audiences - rootPool *x509.CertPool + Type string `json:"type"` + Name string `json:"name"` + Roots []byte `json:"roots"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + claimer *Claimer + audiences Audiences + rootPool *x509.CertPool } // GetID returns the provisioner unique identifier. The name and credential id @@ -249,14 +251,37 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, sshCertOptionsValidator(*opts), } - // 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.SSHOptions, 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())) @@ -264,17 +289,8 @@ 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 From 715eb4eacc1ea50bc209853064eb170dd73982cf Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 27 Jul 2020 17:47:52 -0700 Subject: [PATCH 11/47] Add initial support for ssh templates on OIDC. --- authority/provisioner/oidc.go | 48 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 8cbe849d..3939cc74 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -53,18 +54,19 @@ type openIDPayload struct { // ClientSecret is mandatory, but it can be an empty string. type OIDC struct { *base - Type string `json:"type"` - Name string `json:"name"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - ConfigurationEndpoint string `json:"configurationEndpoint"` - TenantID string `json:"tenantID,omitempty"` - Admins []string `json:"admins,omitempty"` - Domains []string `json:"domains,omitempty"` - Groups []string `json:"groups,omitempty"` - ListenAddress string `json:"listenAddress,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + ConfigurationEndpoint string `json:"configurationEndpoint"` + TenantID string `json:"tenantID,omitempty"` + Admins []string `json:"admins,omitempty"` + Domains []string `json:"domains,omitempty"` + Groups []string `json:"groups,omitempty"` + ListenAddress string `json:"listenAddress,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` configuration openIDConfiguration keyStore *keyStore claimer *Claimer @@ -369,10 +371,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. @@ -385,6 +383,18 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption Principals: iden.Usernames, } + // Certificate templates. + data := sshutil.CreateTemplateData(sshutil.UserCert, claims.Email, iden.Usernames) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := TemplateSSHOptions(o.SSHOptions, data) + 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. @@ -392,13 +402,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption signOptions = append(signOptions, sshCertOptionsValidator(defaults)) } - // 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 From ad28f0f59add73252e96e6727cb0ed4e14f3f969 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 28 Jul 2020 17:58:25 -0700 Subject: [PATCH 12/47] Move variable where it is used. --- authority/provisioner/oidc.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 3939cc74..bb70444c 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -378,10 +378,6 @@ 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) @@ -399,7 +395,10 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption // 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)) + signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{ + CertType: SSHUserCert, + Principals: iden.Usernames, + })) } return append(signOptions, From df1f7e5a2e98ca442e2b74b58977445a4382fda9 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 14:29:12 -0700 Subject: [PATCH 13/47] Use CertificateRequest type as input for ssh NewCertificate. SSH does not have a real concept of ssh certificate request, but we are using the type to encapsulate the parameters coming in the request. --- sshutil/certificate.go | 6 ++--- sshutil/certificate_request.go | 17 ++++++++++++++ sshutil/certificate_test.go | 33 ++++++++++++++------------ sshutil/options.go | 19 ++++++++------- sshutil/options_test.go | 42 ++++++++++++++++++++-------------- sshutil/templates.go | 41 +++++++++++++++++++++------------ sshutil/templates_test.go | 32 +++++++++++++++----------- 7 files changed, 118 insertions(+), 72 deletions(-) create mode 100644 sshutil/certificate_request.go diff --git a/sshutil/certificate.go b/sshutil/certificate.go index 473344a4..1b58882c 100644 --- a/sshutil/certificate.go +++ b/sshutil/certificate.go @@ -29,8 +29,8 @@ type Certificate struct { // NewCertificate creates a new certificate with the given key after parsing a // template given in the options. -func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) { - o, err := new(Options).apply(key, opts) +func NewCertificate(cr CertificateRequest, opts ...Option) (*Certificate, error) { + o, err := new(Options).apply(cr, opts) if err != nil { return nil, err } @@ -46,7 +46,7 @@ func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) { } // Complete with public key - cert.Key = key + cert.Key = cr.Key return &cert, nil } diff --git a/sshutil/certificate_request.go b/sshutil/certificate_request.go new file mode 100644 index 00000000..d4081dc3 --- /dev/null +++ b/sshutil/certificate_request.go @@ -0,0 +1,17 @@ +package sshutil + +import "golang.org/x/crypto/ssh" + +// CertificateRequests simulates a certificate request for SSH. SSH does not +// have a concept of certificate requests, but the CA accepts the key and some +// other parameters in the requests that are part of the certificate. This +// struct will hold these parameters. +// +// CertificateRequests object will be used in the templates to set parameters +// passed with the API instead of the validated ones. +type CertificateRequest struct { + Key ssh.PublicKey + Type CertType + KeyID string + Principals []string +} diff --git a/sshutil/certificate_test.go b/sshutil/certificate_test.go index ed8c5f9a..8042cc6b 100644 --- a/sshutil/certificate_test.go +++ b/sshutil/certificate_test.go @@ -36,9 +36,12 @@ func mustGeneratePublicKey(t *testing.T) ssh.PublicKey { func TestNewCertificate(t *testing.T) { key := mustGeneratePublicKey(t) + cr := CertificateRequest{ + Key: key, + } type args struct { - key ssh.PublicKey + cr CertificateRequest opts []Option } tests := []struct { @@ -47,7 +50,7 @@ func TestNewCertificate(t *testing.T) { want *Certificate wantErr bool }{ - {"user", args{key, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(UserCert, "jane@doe.com", []string{"jane"}))}}, &Certificate{ + {"user", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(UserCert, "jane@doe.com", []string{"jane"}))}}, &Certificate{ Nonce: nil, Key: key, Serial: 0, @@ -68,7 +71,7 @@ func TestNewCertificate(t *testing.T) { SignatureKey: nil, Signature: nil, }, false}, - {"host", args{key, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(HostCert, "foobar", []string{"foo.internal", "bar.internal"}))}}, &Certificate{ + {"host", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(HostCert, "foobar", []string{"foo.internal", "bar.internal"}))}}, &Certificate{ Nonce: nil, Key: key, Serial: 0, @@ -83,12 +86,12 @@ func TestNewCertificate(t *testing.T) { SignatureKey: nil, Signature: nil, }, false}, - {"file", args{key, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{ + {"file", args{cr, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{ TypeKey: UserCert, KeyIDKey: "john@doe.com", PrincipalsKey: []string{"john", "john@doe.com"}, ExtensionsKey: DefaultExtensions(UserCert), - InsecureKey: map[string]interface{}{ + InsecureKey: TemplateData{ "User": map[string]interface{}{"username": "john"}, }, })}}, &Certificate{ @@ -102,18 +105,18 @@ func TestNewCertificate(t *testing.T) { ValidBefore: 0, CriticalOptions: nil, Extensions: map[string]string{ + "login@github.com": "john", "permit-X11-forwarding": "", "permit-agent-forwarding": "", "permit-port-forwarding": "", "permit-pty": "", "permit-user-rc": "", - "login@github.com": "john", }, Reserved: nil, SignatureKey: nil, Signature: nil, }, false}, - {"base64", args{key, []Option{WithTemplateBase64(base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), CreateTemplateData(HostCert, "foo.internal", nil))}}, &Certificate{ + {"base64", args{cr, []Option{WithTemplateBase64(base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), CreateTemplateData(HostCert, "foo.internal", nil))}}, &Certificate{ Nonce: nil, Key: key, Serial: 0, @@ -128,22 +131,22 @@ func TestNewCertificate(t *testing.T) { SignatureKey: nil, Signature: nil, }, false}, - {"failNilOptions", args{key, nil}, nil, true}, - {"failEmptyOptions", args{key, nil}, nil, true}, - {"badBase64Template", args{key, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true}, - {"badFileTemplate", args{key, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true}, - {"badJsonTemplate", args{key, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true}, - {"failTemplate", args{key, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true}, + {"failNilOptions", args{cr, nil}, nil, true}, + {"failEmptyOptions", args{cr, nil}, nil, true}, + {"badBase64Template", args{cr, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true}, + {"badFileTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true}, + {"badJsonTemplate", args{cr, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true}, + {"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewCertificate(tt.args.key, tt.args.opts...) + got, err := NewCertificate(tt.args.cr, tt.args.opts...) if (err != nil) != tt.wantErr { t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewCertificate() = %v, want %v", got, tt.want) + t.Errorf("NewCertificate() = \n%+v, want \n%+v", got, tt.want) } }) } diff --git a/sshutil/options.go b/sshutil/options.go index 58562ba9..e6f094e0 100644 --- a/sshutil/options.go +++ b/sshutil/options.go @@ -9,7 +9,6 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/pkg/errors" "github.com/smallstep/cli/config" - "golang.org/x/crypto/ssh" ) func getFuncMap(failMessage *string) template.FuncMap { @@ -26,9 +25,9 @@ type Options struct { CertBuffer *bytes.Buffer } -func (o *Options) apply(key ssh.PublicKey, opts []Option) (*Options, error) { +func (o *Options) apply(cr CertificateRequest, opts []Option) (*Options, error) { for _, fn := range opts { - if err := fn(key, o); err != nil { + if err := fn(cr, o); err != nil { return o, err } } @@ -36,12 +35,12 @@ func (o *Options) apply(key ssh.PublicKey, opts []Option) (*Options, error) { } // Option is the type used as a variadic argument in NewCertificate. -type Option func(key ssh.PublicKey, o *Options) error +type Option func(cr CertificateRequest, o *Options) error // WithTemplate is an options that executes the given template text with the // given data. func WithTemplate(text string, data TemplateData) Option { - return func(key ssh.PublicKey, o *Options) error { + return func(cr CertificateRequest, o *Options) error { terr := new(TemplateError) funcMap := getFuncMap(&terr.Message) @@ -51,7 +50,7 @@ func WithTemplate(text string, data TemplateData) Option { } buf := new(bytes.Buffer) - data.SetPublicKey(key) + data.SetCertificateRequest(cr) if err := tmpl.Execute(buf, data); err != nil { if terr.Message != "" { return terr @@ -66,26 +65,26 @@ func WithTemplate(text string, data TemplateData) Option { // WithTemplateBase64 is an options that executes the given template base64 // string with the given data. func WithTemplateBase64(s string, data TemplateData) Option { - return func(key ssh.PublicKey, o *Options) error { + return func(cr CertificateRequest, o *Options) error { b, err := base64.StdEncoding.DecodeString(s) if err != nil { return errors.Wrap(err, "error decoding template") } fn := WithTemplate(string(b), data) - return fn(key, o) + return fn(cr, o) } } // WithTemplateFile is an options that reads the template file and executes it // with the given data. func WithTemplateFile(path string, data TemplateData) Option { - return func(key ssh.PublicKey, o *Options) error { + return func(cr CertificateRequest, o *Options) error { filename := config.StepAbs(path) b, err := ioutil.ReadFile(filename) if err != nil { return errors.Wrapf(err, "error reading %s", path) } fn := WithTemplate(string(b), data) - return fn(key, o) + return fn(cr, o) } } diff --git a/sshutil/options_test.go b/sshutil/options_test.go index ac4da67d..1c9e4aa4 100644 --- a/sshutil/options_test.go +++ b/sshutil/options_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/pkg/errors" - "golang.org/x/crypto/ssh" ) func Test_getFuncMap_fail(t *testing.T) { @@ -28,11 +27,14 @@ func Test_getFuncMap_fail(t *testing.T) { func TestWithTemplate(t *testing.T) { key := mustGeneratePublicKey(t) + cr := CertificateRequest{ + Key: key, + } type args struct { text string data TemplateData - key ssh.PublicKey + cr CertificateRequest } tests := []struct { name string @@ -45,7 +47,7 @@ func TestWithTemplate(t *testing.T) { KeyIDKey: "jane@doe.com", PrincipalsKey: []string{"jane", "jane@doe.com"}, ExtensionsKey: DefaultExtensions(UserCert), - }, key}, Options{ + }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "user", "keyId": "jane@doe.com", @@ -56,24 +58,24 @@ func TestWithTemplate(t *testing.T) { TypeKey: "host", KeyIDKey: "foo", PrincipalsKey: []string{"foo.internal"}, - }, key}, Options{ + }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "host", "keyId": "foo", "principals": ["foo.internal"], "extensions": null }`)}, false}, - {"fail", args{`{{ fail "a message" }}`, TemplateData{}, key}, Options{}, true}, - {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, key}, Options{}, true}, + {"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true}, + {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, cr}, Options{}, true}, {"error", args{`{{ mustHas 3 .Data }}`, TemplateData{ "Data": 3, - }, key}, Options{}, true}, + }, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplate(tt.args.text, tt.args.data) - if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { @@ -85,11 +87,14 @@ func TestWithTemplate(t *testing.T) { func TestWithTemplateBase64(t *testing.T) { key := mustGeneratePublicKey(t) + cr := CertificateRequest{ + Key: key, + } type args struct { s string data TemplateData - key ssh.PublicKey + cr CertificateRequest } tests := []struct { name string @@ -102,20 +107,20 @@ func TestWithTemplateBase64(t *testing.T) { KeyIDKey: "foo.internal", PrincipalsKey: []string{"foo.internal", "bar.internal"}, ExtensionsKey: map[string]interface{}{"foo": "bar"}, - }, key}, Options{ + }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "host", "keyId": "foo.internal", "principals": ["foo.internal","bar.internal"], "extensions": {"foo":"bar"} }`)}, false}, - {"badBase64", args{"foobar", TemplateData{}, key}, Options{}, true}, + {"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplateBase64(tt.args.s, tt.args.data) - if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { @@ -127,13 +132,16 @@ func TestWithTemplateBase64(t *testing.T) { func TestWithTemplateFile(t *testing.T) { key := mustGeneratePublicKey(t) + cr := CertificateRequest{ + Key: key, + } data := TemplateData{ TypeKey: "user", KeyIDKey: "jane@doe.com", PrincipalsKey: []string{"jane", "jane@doe.com"}, ExtensionsKey: DefaultExtensions(UserCert), - InsecureKey: map[string]interface{}{ + InsecureKey: TemplateData{ UserKey: map[string]interface{}{ "username": "jane", }, @@ -143,7 +151,7 @@ func TestWithTemplateFile(t *testing.T) { type args struct { path string data TemplateData - key ssh.PublicKey + cr CertificateRequest } tests := []struct { name string @@ -151,7 +159,7 @@ func TestWithTemplateFile(t *testing.T) { want Options wantErr bool }{ - {"github.com", args{"./testdata/github.tpl", data, key}, Options{ + {"github.com", args{"./testdata/github.tpl", data, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "user", "keyId": "jane@doe.com", @@ -159,13 +167,13 @@ func TestWithTemplateFile(t *testing.T) { "extensions": {"login@github.com":"jane","permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""} }`), }, false}, - {"missing", args{"./testdata/missing.tpl", data, key}, Options{}, true}, + {"missing", args{"./testdata/missing.tpl", data, cr}, Options{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got Options fn := WithTemplateFile(tt.args.path, tt.args.data) - if err := fn(tt.args.key, &got); (err != nil) != tt.wantErr { + if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { diff --git a/sshutil/templates.go b/sshutil/templates.go index c925da3a..8f0265ab 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -1,16 +1,14 @@ package sshutil -import "golang.org/x/crypto/ssh" - const ( - TypeKey = "Type" - KeyIDKey = "KeyID" - PrincipalsKey = "Principals" - ExtensionsKey = "Extensions" - TokenKey = "Token" - InsecureKey = "Insecure" - UserKey = "User" - PublicKey = "PublicKey" + TypeKey = "Type" + KeyIDKey = "KeyID" + PrincipalsKey = "Principals" + ExtensionsKey = "Extensions" + TokenKey = "Token" + InsecureKey = "Insecure" + UserKey = "User" + CertificateRequestKey = "CR" ) // TemplateError represents an error in a template produced by the fail @@ -117,10 +115,10 @@ func (t TemplateData) SetUserData(v interface{}) { t.SetInsecure(UserKey, v) } -// SetUserData sets the given user provided object in the insecure template -// data. -func (t TemplateData) SetPublicKey(v ssh.PublicKey) { - t.Set(PublicKey, v) +// SetCertificateRequest sets the simulated ssh certificate request the insecure +// template data. +func (t TemplateData) SetCertificateRequest(cr CertificateRequest) { + t.SetInsecure(CertificateRequestKey, cr) } // DefaultCertificate is the default template for an SSH certificate. @@ -130,3 +128,18 @@ const DefaultCertificate = `{ "principals": {{ toJson .Principals }}, "extensions": {{ toJson .Extensions }} }` + +const DefaultIIDCertificate = `{ + "type": "{{ .Type }}", +{{- if .Insecure.CR.KeyID }} + "keyId": "{{ .Insecure.CR.KeyID }}", +{{- else }} + "keyId": "{{ .KeyID }}", +{{- end}} +{{- if .Insecure.CR.Principals }} + "principals": {{ toJson .Insecure.CR.Principals }}, +{{- else }} + "principals": {{ toJson .Principals }}, +{{- end }} + "extensions": {{ toJson .Extensions }} +}` diff --git a/sshutil/templates_test.go b/sshutil/templates_test.go index ad550ccb..0d411c32 100644 --- a/sshutil/templates_test.go +++ b/sshutil/templates_test.go @@ -3,8 +3,6 @@ package sshutil import ( "reflect" "testing" - - "golang.org/x/crypto/ssh" ) func TestTemplateError_Error(t *testing.T) { @@ -374,11 +372,11 @@ func TestTemplateData_SetUserData(t *testing.T) { } } -func TestTemplateData_SetPublicKey(t *testing.T) { - k1 := mustGeneratePublicKey(t) - k2 := mustGeneratePublicKey(t) +func TestTemplateData_SetCertificateRequest(t *testing.T) { + cr1 := CertificateRequest{Key: mustGeneratePublicKey(t)} + cr2 := CertificateRequest{Key: mustGeneratePublicKey(t)} type args struct { - v ssh.PublicKey + cr CertificateRequest } tests := []struct { name string @@ -386,20 +384,28 @@ func TestTemplateData_SetPublicKey(t *testing.T) { args args want TemplateData }{ - {"ok", TemplateData{}, args{k1}, TemplateData{ - PublicKey: k1, + {"ok", TemplateData{}, args{cr1}, TemplateData{ + InsecureKey: TemplateData{ + CertificateRequestKey: cr1, + }, }}, {"overwrite", TemplateData{ - PublicKey: k1, - }, args{k2}, TemplateData{ - PublicKey: k2, + InsecureKey: TemplateData{ + UserKey: "data", + CertificateRequestKey: cr1, + }, + }, args{cr2}, TemplateData{ + InsecureKey: TemplateData{ + UserKey: "data", + CertificateRequestKey: cr2, + }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.t.SetPublicKey(tt.args.v) + tt.t.SetCertificateRequest(tt.args.cr) if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("TemplateData.SetPublicKey() = %v, want %v", tt.t, tt.want) + t.Errorf("TemplateData.SetCertificateRequest() = %v, want %v", tt.t, tt.want) } }) } From c1fc45c87234adf19b4656318aec4cb463347757 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 16:06:39 -0700 Subject: [PATCH 14/47] Simplify SSH modifiers with options. It also changes the behavior of the request options to modify only the validity of the certificate. --- authority/provisioner/sign_ssh_options.go | 147 ++++++++++------------ authority/ssh.go | 54 +++++--- 2 files changed, 103 insertions(+), 98 deletions(-) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 6352204f..a9638d24 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -24,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. @@ -47,14 +40,6 @@ type SSHCertOptionsValidator interface { Valid(got SignSSHOptions) error } -// sshModifierFunc is an adapter to allow the use of ordinary functions as SSH -// certificate modifiers. -type sshModifierFunc func(cert *ssh.Certificate) error - -func (f sshModifierFunc) Modify(cert *ssh.Certificate) error { - return f(cert) -} - // SignSSHOptions contains the options that can be passed to the SignSSH method. type SignSSHOptions struct { CertType string `json:"certType"` @@ -72,7 +57,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: @@ -86,6 +71,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()) @@ -96,7 +87,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 } @@ -123,7 +113,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 } @@ -132,7 +122,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 } @@ -142,7 +132,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 } @@ -151,7 +141,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 } @@ -160,7 +150,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 } @@ -217,27 +207,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 @@ -250,51 +240,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 diff --git a/authority/ssh.go b/authority/ssh.go index 61050029..1d449b80 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -206,6 +206,8 @@ 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 ( + err error + certType sshutil.CertType certOptions []sshutil.Option mods []provisioner.SSHCertModifier validators []provisioner.SSHCertValidator @@ -214,6 +216,14 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Set backdate with the configured value opts.Backdate = a.config.AuthorityConfig.Backdate.Duration + // Validate certificate type. + if opts.CertType != "" { + certType, err = sshutil.CertTypeFromString(opts.CertType) + if err != nil { + return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH") + } + } + for _, op := range signOpts { switch o := op.(type) { // add options to NewCertificate @@ -224,10 +234,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi 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) @@ -235,16 +241,24 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // 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) } } + // Simulated certificate request with request options. + cr := sshutil.CertificateRequest{ + Type: certType, + KeyID: opts.KeyID, + Principals: opts.Principals, + Key: key, + } + // Create certificate from template. - certificate, err := sshutil.NewCertificate(key, certOptions...) + certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { if _, ok := err.(*sshutil.TemplateError); ok { return nil, errs.NewErr(http.StatusBadRequest, err, @@ -255,19 +269,19 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH") } - // Get actual *ssh.Certificate and continue with user and provisioner - // modifiers. + // Get actual *ssh.Certificate and continue with provisioner modifiers. cert := certificate.GetCertificate() - // Use SignSSHOptions to modify the certificate. - if err := opts.Modify(cert); err != nil { - return nil, errs.Wrap(http.StatusForbidden, err, "signSSH") + // 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, errs.Wrap(http.StatusForbidden, err, "signSSH") + if err := m.Modify(cert, opts); err != nil { + return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH") } } @@ -276,33 +290,33 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi switch cert.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", cert.CertType) } // Sign certificate. cert, err = sshutil.CreateCertificate(cert, 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") } // 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 From e0dce54338d0310330df53898fc6d981abeabf98 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 18:05:02 -0700 Subject: [PATCH 15/47] Add missing argument. --- authority/provisioner/sign_ssh_options.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index a9638d24..b9bf63b5 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -160,7 +160,7 @@ func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate, _ SignSSHOptio 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) } @@ -180,7 +180,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: From 8e7bf96769dd5a0aed047e6975285b4d85e6601f Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 18:05:22 -0700 Subject: [PATCH 16/47] Fix error prefix. --- authority/provisioner/x5c.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index ee05078e..69d5ba59 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -259,7 +259,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, // Use options in the token. if opts.CertType != "" { if certType, err = sshutil.CertTypeFromString(opts.CertType); err != nil { - return nil, errs.Wrap(http.StatusBadRequest, err, "jwk.AuthorizeSSHSign") + return nil, errs.Wrap(http.StatusBadRequest, err, "x5c.AuthorizeSSHSign") } } if opts.KeyID != "" { @@ -277,7 +277,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, templateOptions, err := TemplateSSHOptions(p.SSHOptions, data) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") + return nil, errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeSSHSign") } signOptions = append(signOptions, templateOptions) From 6c36ceb158f42409b79f9360a9ad386590ec78b7 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 18:05:35 -0700 Subject: [PATCH 17/47] Add initial template support for iid provisisioners. --- authority/provisioner/aws.go | 64 +++++++++++++++++++-------------- authority/provisioner/azure.go | 60 ++++++++++++++++++------------- authority/provisioner/gcp.go | 65 ++++++++++++++++++++-------------- 3 files changed, 111 insertions(+), 78 deletions(-) diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 9f3764c0..cdfe49c3 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -126,14 +127,15 @@ type awsInstanceIdentityDocument struct { // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html type AWS struct { *base - Type string `json:"type"` - Name string `json:"name"` - Accounts []string `json:"accounts"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge Duration `json:"instanceAge,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + Accounts []string `json:"accounts"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge Duration `json:"instanceAge,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` claimer *Claimer config *awsConfig audiences Audiences @@ -468,34 +470,42 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, doc := claims.document - signOptions := []SignOption{ - // set the key id to the instance id - sshCertKeyIDModifier(doc.InstanceID), - } - - // 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), - } + // Validated principals. + principals := []string{ + doc.PrivateIP, + fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region), } // Default to cert type to host defaults := SignSSHOptions{ - CertType: SSHHostCert, - Principals: principals, + CertType: SSHHostCert, + } + defaultTemplate := sshutil.DefaultIIDCertificate + + // Only enforce known principals if disable custom sans is true. + if p.DisableCustomSANs { + defaults.Principals = principals + defaultTemplate = sshutil.DefaultCertificate } // Validate user options - signOptions = append(signOptions, sshCertOptionsValidator(defaults)) - // Set defaults if not given as user options - signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) + signOptions := []SignOption{ + sshCertOptionsValidator(defaults), + } + + // Certificate templates. + data := sshutil.CreateTemplateData(sshutil.HostCert, doc.InstanceID, principals) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign") + } + signOptions = append(signOptions, templateOptions) return append(signOptions, - // Set the default extensions. - &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index a677f9a9..27835b3f 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -83,15 +84,16 @@ type azurePayload struct { // and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service type Azure struct { *base - Type string `json:"type"` - Name string `json:"name"` - TenantID string `json:"tenantID"` - ResourceGroups []string `json:"resourceGroups"` - Audience string `json:"audience,omitempty"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + TenantID string `json:"tenantID"` + ResourceGroups []string `json:"resourceGroups"` + Audience string `json:"audience,omitempty"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` claimer *Claimer config *azureConfig oidcConfig openIDConfiguration @@ -338,30 +340,40 @@ 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), + + // Validated principals + principals := []string{name} + + // Default options and template + defaults := SignSSHOptions{ + CertType: SSHHostCert, } + defaultTemplate := sshutil.DefaultIIDCertificate // Only enforce known principals if disable custom sans is true. - var principals []string if p.DisableCustomSANs { - principals = []string{name} + defaults.Principals = principals + defaultTemplate = sshutil.DefaultCertificate } - // Default to host + known hostnames - defaults := SignSSHOptions{ - CertType: SSHHostCert, - Principals: principals, - } // Validate user options - signOptions = append(signOptions, sshCertOptionsValidator(defaults)) - // Set defaults if not given as user options - signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) + signOptions := []SignOption{ + sshCertOptionsValidator(defaults), + } + + // Certificate templates. + data := sshutil.CreateTemplateData(sshutil.HostCert, name, principals) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") + } + signOptions = append(signOptions, templateOptions) return append(signOptions, - // Set the default extensions. - &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 69a42ec2..8f53553c 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -15,6 +15,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/jose" ) @@ -77,15 +78,16 @@ func newGCPConfig() *gcpConfig { // https://cloud.google.com/compute/docs/instances/verifying-instance-identity type GCP struct { *base - Type string `json:"type"` - Name string `json:"name"` - ServiceAccounts []string `json:"serviceAccounts"` - ProjectIDs []string `json:"projectIDs"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge Duration `json:"instanceAge,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + ServiceAccounts []string `json:"serviceAccounts"` + ProjectIDs []string `json:"projectIDs"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge Duration `json:"instanceAge,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` claimer *Claimer config *gcpConfig keyStore *keyStore @@ -379,33 +381,42 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, ce := claims.Google.ComputeEngine - signOptions := []SignOption{ - // set the key id to the instance name - sshCertKeyIDModifier(ce.InstanceName), + // 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), } + // Default options and template + defaults := SignSSHOptions{ + CertType: SSHHostCert, + } + defaultTemplate := sshutil.DefaultIIDCertificate + // 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 + defaultTemplate = sshutil.DefaultCertificate } - // Default to host + known hostnames - defaults := SignSSHOptions{ - CertType: SSHHostCert, - Principals: principals, - } // Validate user options - signOptions = append(signOptions, sshCertOptionsValidator(defaults)) - // Set defaults if not given as user options - signOptions = append(signOptions, sshCertDefaultsModifier(defaults)) + signOptions := []SignOption{ + sshCertOptionsValidator(defaults), + } + + // Certificate templates. + data := sshutil.CreateTemplateData(sshutil.HostCert, ce.InstanceName, principals) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") + } + signOptions = append(signOptions, templateOptions) return append(signOptions, - // Set the default extensions - &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key From a78f7e8913d0154b480e8b86aca33d4da10dd174 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 29 Jul 2020 19:26:46 -0700 Subject: [PATCH 18/47] Add template support on k8ssa provisioner. --- authority/provisioner/k8sSA.go | 37 +++++++++++++++-------- authority/provisioner/sign_ssh_options.go | 20 ++++++++++++ authority/ssh.go | 12 +------- sshutil/certificate_request.go | 2 +- sshutil/templates.go | 15 +++++++++ 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 3c37cae7..dc11e3eb 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" @@ -41,13 +42,14 @@ type k8sSAPayload struct { // entity trusted to make signature requests. type K8sSA struct { *base - Type string `json:"type"` - Name string `json:"name"` - PubKeys []byte `json:"publicKeys,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - claimer *Claimer - audiences Audiences + Type string `json:"type"` + Name string `json:"name"` + PubKeys []byte `json:"publicKeys,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + claimer *Claimer + audiences Audiences //kauthn kauthn.AuthenticationV1Interface pubKeys []interface{} } @@ -249,16 +251,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.SSHOptions, 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/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index b9bf63b5..8c6000f0 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -299,6 +299,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 } diff --git a/authority/ssh.go b/authority/ssh.go index 1d449b80..41f37579 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -206,8 +206,6 @@ 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 ( - err error - certType sshutil.CertType certOptions []sshutil.Option mods []provisioner.SSHCertModifier validators []provisioner.SSHCertValidator @@ -216,14 +214,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Set backdate with the configured value opts.Backdate = a.config.AuthorityConfig.Backdate.Duration - // Validate certificate type. - if opts.CertType != "" { - certType, err = sshutil.CertTypeFromString(opts.CertType) - if err != nil { - return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH") - } - } - for _, op := range signOpts { switch o := op.(type) { // add options to NewCertificate @@ -251,7 +241,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Simulated certificate request with request options. cr := sshutil.CertificateRequest{ - Type: certType, + Type: opts.CertType, KeyID: opts.KeyID, Principals: opts.Principals, Key: key, diff --git a/sshutil/certificate_request.go b/sshutil/certificate_request.go index d4081dc3..85a69502 100644 --- a/sshutil/certificate_request.go +++ b/sshutil/certificate_request.go @@ -11,7 +11,7 @@ import "golang.org/x/crypto/ssh" // passed with the API instead of the validated ones. type CertificateRequest struct { Key ssh.PublicKey - Type CertType + Type string KeyID string Principals []string } diff --git a/sshutil/templates.go b/sshutil/templates.go index 8f0265ab..8710f8e2 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -143,3 +143,18 @@ const DefaultIIDCertificate = `{ {{- end }} "extensions": {{ toJson .Extensions }} }` + +const CertificateRequestTemplate = `{ + "type": "{{ .Insecure.CR.Type }}", + "keyId": "{{ .Insecure.CR.KeyID }}", + "principals": {{ toJson .Insecure.CR.Principals }} +{{- if eq .Insecure.CR.Type "user" }} + , "extensions": { + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "" + } +{{- end }} +}` From 3b19bb9796028ec8ab8c74d3a08fe57cb4a559e6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 14:59:17 -0700 Subject: [PATCH 19/47] Add TemplateData to SSHSignRequest. Add some omitempty tags. --- api/sign.go | 6 +++--- api/ssh.go | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/api/sign.go b/api/sign.go index f24f4d02..90a9aa99 100644 --- a/api/sign.go +++ b/api/sign.go @@ -14,9 +14,9 @@ import ( 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 diff --git a/api/ssh.go b/api/ssh.go index 70469fbf..7e3cb3db 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -36,12 +36,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. @@ -275,11 +276,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) From 8ff8d90f8ccfdb17f93956f9eee9a6276478473c Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 14:59:54 -0700 Subject: [PATCH 20/47] On JWK and X5C validate the key id on the request. --- authority/provisioner/jwk.go | 4 ++-- authority/provisioner/x5c.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 1769860a..e8caa25d 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -207,6 +207,8 @@ func (p *JWK) 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}), } // Default template attributes. @@ -251,8 +253,6 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, return append(signOptions, // 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/x5c.go b/authority/provisioner/x5c.go index 69d5ba59..281ab625 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -249,6 +249,8 @@ 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}), } // Default template attributes. @@ -293,8 +295,6 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, return append(signOptions, // 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. From 02379d494bb01a9dba6c8cab739e48198b3121bb Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 17:24:05 -0700 Subject: [PATCH 21/47] Add support for extensions and critical options on the identity function. --- authority/provisioner/oidc.go | 8 ++++ authority/provisioner/provisioner.go | 9 +++- sshutil/templates.go | 21 ++++++++- sshutil/templates_test.go | 69 ++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index bb70444c..870541cd 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -384,6 +384,14 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption 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) + } templateOptions, err := TemplateSSHOptions(o.SSHOptions, data) if err != nil { 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/sshutil/templates.go b/sshutil/templates.go index 8710f8e2..f51b46f6 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -5,6 +5,7 @@ const ( KeyIDKey = "KeyID" PrincipalsKey = "Principals" ExtensionsKey = "Extensions" + CriticalOptionsKey = "CriticalOptions" TokenKey = "Token" InsecureKey = "Insecure" UserKey = "User" @@ -70,6 +71,17 @@ func (t TemplateData) AddExtension(key, value string) { } } +// AddCriticalOption adds one critical option to the templates data. +func (t TemplateData) AddCriticalOption(key, value string) { + if m, ok := t[CriticalOptionsKey].(map[string]interface{}); ok { + m[key] = value + } else { + t[CriticalOptionsKey] = map[string]interface{}{ + key: value, + } + } +} + // Set sets a key-value pair in the template data. func (t TemplateData) Set(key string, v interface{}) { t[key] = v @@ -104,6 +116,12 @@ func (t TemplateData) SetExtensions(e map[string]interface{}) { t.Set(ExtensionsKey, e) } +// SetCriticalOptions sets the certificate critical options in the template +// data. +func (t TemplateData) SetCriticalOptions(o map[string]interface{}) { + t.Set(CriticalOptionsKey, o) +} + // SetToken sets the given token in the template data. func (t TemplateData) SetToken(v interface{}) { t.Set(TokenKey, v) @@ -126,7 +144,8 @@ const DefaultCertificate = `{ "type": "{{ .Type }}", "keyId": "{{ .KeyID }}", "principals": {{ toJson .Principals }}, - "extensions": {{ toJson .Extensions }} + "extensions": {{ toJson .Extensions }}, + "criticalOptions": {{ toJson .CriticalOptions }} }` const DefaultIIDCertificate = `{ diff --git a/sshutil/templates_test.go b/sshutil/templates_test.go index 0d411c32..ac4152b5 100644 --- a/sshutil/templates_test.go +++ b/sshutil/templates_test.go @@ -157,6 +157,46 @@ func TestTemplateData_AddExtension(t *testing.T) { } } +func TestTemplateData_AddCriticalOption(t *testing.T) { + type args struct { + key string + value string + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"empty", TemplateData{}, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }}, + {"overwrite", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{ + "key": "value", + }, + }}, + {"add", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }, args{"key", "value"}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{ + "key": "value", + "foo": "bar", + }, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.AddCriticalOption(tt.args.key, tt.args.value) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("AddCriticalOption() = %v, want %v", tt.t, tt.want) + } + }) + } +} + func TestTemplateData_Set(t *testing.T) { type args struct { key string @@ -325,6 +365,35 @@ func TestTemplateData_SetExtensions(t *testing.T) { } } +func TestTemplateData_SetCriticalOptions(t *testing.T) { + type args struct { + e map[string]interface{} + } + tests := []struct { + name string + t TemplateData + args args + want TemplateData + }{ + {"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }}, + {"overwrite", TemplateData{ + CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, + }, args{map[string]interface{}{"key": "value"}}, TemplateData{ + CriticalOptionsKey: map[string]interface{}{"key": "value"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.t.SetCriticalOptions(tt.args.e) + if !reflect.DeepEqual(tt.t, tt.want) { + t.Errorf("SetCriticalOptions() = %v, want %v", tt.t, tt.want) + } + }) + } +} + func TestTemplateData_SetToken(t *testing.T) { type args struct { v interface{} From d82bdc1a00208280787ac08021462110bb85fd53 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 18:04:39 -0700 Subject: [PATCH 22/47] Fix tests with criticalOptions. --- sshutil/options_test.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sshutil/options_test.go b/sshutil/options_test.go index 1c9e4aa4..a5adb7ff 100644 --- a/sshutil/options_test.go +++ b/sshutil/options_test.go @@ -52,18 +52,21 @@ func TestWithTemplate(t *testing.T) { "type": "user", "keyId": "jane@doe.com", "principals": ["jane","jane@doe.com"], - "extensions": {"permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""} + "extensions": {"permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""}, + "criticalOptions": null }`)}, false}, {"host", args{DefaultCertificate, TemplateData{ - TypeKey: "host", - KeyIDKey: "foo", - PrincipalsKey: []string{"foo.internal"}, + TypeKey: "host", + KeyIDKey: "foo", + PrincipalsKey: []string{"foo.internal"}, + CriticalOptionsKey: map[string]string{"foo": "bar"}, }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "host", "keyId": "foo", "principals": ["foo.internal"], - "extensions": null + "extensions": null, + "criticalOptions": {"foo":"bar"} }`)}, false}, {"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true}, {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, cr}, Options{}, true}, @@ -103,16 +106,18 @@ func TestWithTemplateBase64(t *testing.T) { wantErr bool }{ {"host", args{base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), TemplateData{ - TypeKey: "host", - KeyIDKey: "foo.internal", - PrincipalsKey: []string{"foo.internal", "bar.internal"}, - ExtensionsKey: map[string]interface{}{"foo": "bar"}, + TypeKey: "host", + KeyIDKey: "foo.internal", + PrincipalsKey: []string{"foo.internal", "bar.internal"}, + ExtensionsKey: map[string]interface{}{"foo": "bar"}, + CriticalOptionsKey: map[string]interface{}{"bar": "foo"}, }, cr}, Options{ CertBuffer: bytes.NewBufferString(`{ "type": "host", "keyId": "foo.internal", "principals": ["foo.internal","bar.internal"], - "extensions": {"foo":"bar"} + "extensions": {"foo":"bar"}, + "criticalOptions": {"bar":"foo"} }`)}, false}, {"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true}, } From aa657cdb4b578478d48830772dc59542187974f9 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 30 Jul 2020 18:44:52 -0700 Subject: [PATCH 23/47] Use SSHOptions inside provisioner options. --- authority/provisioner/aws.go | 19 ++--- authority/provisioner/azure.go | 21 +++-- authority/provisioner/gcp.go | 21 +++-- authority/provisioner/jwk.go | 5 +- authority/provisioner/k8sSA.go | 17 ++-- authority/provisioner/oidc.go | 27 +++--- authority/provisioner/options.go | 11 ++- authority/provisioner/options_test.go | 22 +++++ .../provisioner/sign_ssh_options_test.go | 83 ++++--------------- authority/provisioner/ssh_options.go | 25 +++--- authority/provisioner/ssh_test.go | 7 +- authority/provisioner/x5c.go | 19 ++--- 12 files changed, 123 insertions(+), 154 deletions(-) diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index cdfe49c3..8d6cefe8 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -127,15 +127,14 @@ type awsInstanceIdentityDocument struct { // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html type AWS struct { *base - Type string `json:"type"` - Name string `json:"name"` - Accounts []string `json:"accounts"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge Duration `json:"instanceAge,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + Accounts []string `json:"accounts"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge Duration `json:"instanceAge,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *awsConfig audiences Audiences @@ -499,7 +498,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign") } diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 27835b3f..7d1ef226 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -84,16 +84,15 @@ type azurePayload struct { // and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service type Azure struct { *base - Type string `json:"type"` - Name string `json:"name"` - TenantID string `json:"tenantID"` - ResourceGroups []string `json:"resourceGroups"` - Audience string `json:"audience,omitempty"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + TenantID string `json:"tenantID"` + ResourceGroups []string `json:"resourceGroups"` + Audience string `json:"audience,omitempty"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *azureConfig oidcConfig openIDConfiguration @@ -367,7 +366,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") } diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 8f53553c..e2c783ba 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -78,16 +78,15 @@ func newGCPConfig() *gcpConfig { // https://cloud.google.com/compute/docs/instances/verifying-instance-identity type GCP struct { *base - Type string `json:"type"` - Name string `json:"name"` - ServiceAccounts []string `json:"serviceAccounts"` - ProjectIDs []string `json:"projectIDs"` - DisableCustomSANs bool `json:"disableCustomSANs"` - DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` - InstanceAge Duration `json:"instanceAge,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + ServiceAccounts []string `json:"serviceAccounts"` + ProjectIDs []string `json:"projectIDs"` + DisableCustomSANs bool `json:"disableCustomSANs"` + DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + InstanceAge Duration `json:"instanceAge,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *gcpConfig keyStore *keyStore @@ -410,7 +409,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index e8caa25d..9d33a519 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -34,7 +34,6 @@ type JWK struct { EncryptedKey string `json:"encryptedKey,omitempty"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` claimer *Claimer audiences Audiences } @@ -205,7 +204,7 @@ 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}), @@ -235,7 +234,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := TemplateSSHOptions(p.SSHOptions, data) + templateOptions, err := TemplateSSHOptions(p.Options, data) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index dc11e3eb..b28373d7 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -42,14 +42,13 @@ type k8sSAPayload struct { // entity trusted to make signature requests. type K8sSA struct { *base - Type string `json:"type"` - Name string `json:"name"` - PubKeys []byte `json:"publicKeys,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` - claimer *Claimer - audiences Audiences + Type string `json:"type"` + Name string `json:"name"` + PubKeys []byte `json:"publicKeys,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + claimer *Claimer + audiences Audiences //kauthn kauthn.AuthenticationV1Interface pubKeys []interface{} } @@ -263,7 +262,7 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, sshutil.CertificateRequestTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.CertificateRequestTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign") } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 870541cd..70675ba7 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -54,19 +54,18 @@ type openIDPayload struct { // ClientSecret is mandatory, but it can be an empty string. type OIDC struct { *base - Type string `json:"type"` - Name string `json:"name"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - ConfigurationEndpoint string `json:"configurationEndpoint"` - TenantID string `json:"tenantID,omitempty"` - Admins []string `json:"admins,omitempty"` - Domains []string `json:"domains,omitempty"` - Groups []string `json:"groups,omitempty"` - ListenAddress string `json:"listenAddress,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + ConfigurationEndpoint string `json:"configurationEndpoint"` + TenantID string `json:"tenantID,omitempty"` + Admins []string `json:"admins,omitempty"` + Domains []string `json:"domains,omitempty"` + Groups []string `json:"groups,omitempty"` + ListenAddress string `json:"listenAddress,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` configuration openIDConfiguration keyStore *keyStore claimer *Claimer @@ -393,7 +392,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption data.AddCriticalOption(k, v) } - templateOptions, err := TemplateSSHOptions(o.SSHOptions, data) + templateOptions, err := TemplateSSHOptions(o.Options, data) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") } diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index 713d48ec..eaa19590 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -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,6 +36,14 @@ 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 diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index 54b07bf3..ed4466e7 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -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/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 600fd975..3c0d9bb5 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -1,7 +1,6 @@ package provisioner import ( - "fmt" "reflect" "testing" "time" @@ -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()) } @@ -908,7 +907,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 +920,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 +975,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 +984,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 index 784b0958..0a9c31dc 100644 --- a/authority/provisioner/ssh_options.go +++ b/authority/provisioner/ssh_options.go @@ -44,7 +44,7 @@ func (o *SSHOptions) HasTemplate() bool { // 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 *SSHOptions, data sshutil.TemplateData) (SSHCertificateOptions, error) { +func TemplateSSHOptions(o *Options, data sshutil.TemplateData) (SSHCertificateOptions, error) { return CustomSSHTemplateOptions(o, data, sshutil.DefaultCertificate) } @@ -52,15 +52,16 @@ func TemplateSSHOptions(o *SSHOptions, data sshutil.TemplateData) (SSHCertificat // 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 *SSHOptions, data sshutil.TemplateData, defaultTemplate string) (SSHCertificateOptions, error) { - if o != nil { - if data == nil { - data = sshutil.NewTemplateData() - } +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(o.TemplateData) > 0 { - if err := json.Unmarshal(o.TemplateData, &data); err != nil { + if len(opts.TemplateData) > 0 { + if err := json.Unmarshal(opts.TemplateData, &data); err != nil { return nil, errors.Wrap(err, "error unmarshaling template data") } } @@ -68,7 +69,7 @@ func CustomSSHTemplateOptions(o *SSHOptions, data sshutil.TemplateData, defaultT return sshCertificateOptionsFunc(func(so SignSSHOptions) []sshutil.Option { // We're not provided user data without custom templates. - if !o.HasTemplate() { + if !opts.HasTemplate() { return []sshutil.Option{ sshutil.WithTemplate(defaultTemplate, data), } @@ -85,15 +86,15 @@ func CustomSSHTemplateOptions(o *SSHOptions, data sshutil.TemplateData, defaultT } // Load a template from a file if Template is not defined. - if o.Template == "" && o.TemplateFile != "" { + if opts.Template == "" && opts.TemplateFile != "" { return []sshutil.Option{ - sshutil.WithTemplateFile(o.TemplateFile, data), + sshutil.WithTemplateFile(opts.TemplateFile, data), } } // Load a template from the Template fields // 1. As a JSON in a string. - template := strings.TrimSpace(o.Template) + template := strings.TrimSpace(opts.Template) if strings.HasPrefix(template, "{") { return []sshutil.Option{ sshutil.WithTemplate(template, data), diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index c08ddc3e..5511e6cd 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -53,9 +53,6 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si // 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) @@ -77,13 +74,13 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si } // Use opts to modify the certificate - if err := opts.Modify(cert); err != nil { + if err := opts.Modify(cert, opts); err != nil { return nil, err } // Use provisioner modifiers for _, m := range mods { - if err := m.Modify(cert); err != nil { + if err := m.Modify(cert, opts); err != nil { return nil, err } } diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 281ab625..27c34d79 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -26,15 +26,14 @@ type x5cPayload struct { // signature requests. type X5C struct { *base - Type string `json:"type"` - Name string `json:"name"` - Roots []byte `json:"roots"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - SSHOptions *SSHOptions `json:"sshOptions,omitempty"` - claimer *Claimer - audiences Audiences - rootPool *x509.CertPool + Type string `json:"type"` + Name string `json:"name"` + Roots []byte `json:"roots"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + claimer *Claimer + audiences Audiences + rootPool *x509.CertPool } // GetID returns the provisioner unique identifier. The name and credential id @@ -277,7 +276,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := TemplateSSHOptions(p.SSHOptions, data) + templateOptions, err := TemplateSSHOptions(p.Options, data) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "x5c.AuthorizeSSHSign") } From 9822305bb655287c8ddc7005eab6b2a337077edf Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 15:11:42 -0700 Subject: [PATCH 24/47] Use only the IID template on IID provisioners. Use always sshutil.DefaultIIDCertificate and require at least one principal on IID provisioners. --- authority/provisioner/aws.go | 27 +++++++++++---------- authority/provisioner/azure.go | 24 ++++++++++--------- authority/provisioner/gcp.go | 29 ++++++++++++----------- authority/provisioner/sign_ssh_options.go | 2 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 8d6cefe8..6d2f9f9c 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -468,6 +468,12 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, } doc := claims.document + signOptions := []SignOption{} + + // Enforce host certificate. + defaults := SignSSHOptions{ + CertType: SSHHostCert, + } // Validated principals. principals := []string{ @@ -475,21 +481,14 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region), } - // Default to cert type to host - defaults := SignSSHOptions{ - CertType: SSHHostCert, - } - defaultTemplate := sshutil.DefaultIIDCertificate - // Only enforce known principals if disable custom sans is true. if p.DisableCustomSANs { defaults.Principals = principals - defaultTemplate = sshutil.DefaultCertificate - } - - // Validate user options - signOptions := []SignOption{ - sshCertOptionsValidator(defaults), + } else { + // Check that at least one principal is sent in the request. + signOptions = append(signOptions, &sshCertOptionsRequireValidator{ + Principals: true, + }) } // Certificate templates. @@ -498,13 +497,15 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign") } signOptions = append(signOptions, templateOptions) return append(signOptions, + // Validate user SignSSHOptions. + sshCertOptionsValidator(defaults), // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 7d1ef226..2121c86b 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -340,24 +340,24 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") } - // Validated principals - principals := []string{name} + signOptions := []SignOption{} - // Default options and template + // Enforce host certificate. defaults := SignSSHOptions{ CertType: SSHHostCert, } - defaultTemplate := sshutil.DefaultIIDCertificate + + // Validated principals. + principals := []string{name} // Only enforce known principals if disable custom sans is true. if p.DisableCustomSANs { defaults.Principals = principals - defaultTemplate = sshutil.DefaultCertificate - } - - // Validate user options - signOptions := []SignOption{ - sshCertOptionsValidator(defaults), + } else { + // Check that at least one principal is sent in the request. + signOptions = append(signOptions, &sshCertOptionsRequireValidator{ + Principals: true, + }) } // Certificate templates. @@ -366,13 +366,15 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") } signOptions = append(signOptions, templateOptions) return append(signOptions, + // Validate user SignSSHOptions. + sshCertOptionsValidator(defaults), // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index e2c783ba..e9a854ae 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -379,28 +379,27 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, } ce := claims.Google.ComputeEngine + signOptions := []SignOption{} - // Validated principals + // 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), } - // Default options and template - defaults := SignSSHOptions{ - CertType: SSHHostCert, - } - defaultTemplate := sshutil.DefaultIIDCertificate - // Only enforce known principals if disable custom sans is true. if p.DisableCustomSANs { defaults.Principals = principals - defaultTemplate = sshutil.DefaultCertificate - } - - // Validate user options - signOptions := []SignOption{ - sshCertOptionsValidator(defaults), + } else { + // Check that at least one principal is sent in the request. + signOptions = append(signOptions, &sshCertOptionsRequireValidator{ + Principals: true, + }) } // Certificate templates. @@ -409,13 +408,15 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, defaultTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") } signOptions = append(signOptions, templateOptions) return append(signOptions, + // Validate user SignSSHOptions. + sshCertOptionsValidator(defaults), // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 8c6000f0..279c2ae1 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -306,7 +306,7 @@ type sshCertOptionsRequireValidator struct { Principals bool } -func (v sshCertOptionsRequireValidator) Valid(got SignSSHOptions) error { +func (v *sshCertOptionsRequireValidator) Valid(got SignSSHOptions) error { switch { case v.CertType && got.CertType == "": return errors.New("ssh certificate certType cannot be empty") From b66bdfabcd63ca3048b13ea9d2fb31331622ff0f Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 15:28:48 -0700 Subject: [PATCH 25/47] Enforce an OIDC users to send all template variables. --- authority/provisioner/oidc.go | 18 ++++++++++++++++-- sshutil/templates.go | 26 +++++++++++++++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 70675ba7..22c3afab 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -392,7 +392,15 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption data.AddCriticalOption(k, v) } - templateOptions, err := TemplateSSHOptions(o.Options, data) + // 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.DefaultCertificate + if isAdmin && !o.Options.GetSSHOptions().HasTemplate() { + defaultTemplate = sshutil.DefaultAdminCertificate + } + + templateOptions, err := CustomSSHTemplateOptions(o.Options, data, defaultTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") } @@ -401,7 +409,13 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption // 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) { + if isAdmin { + signOptions = append(signOptions, &sshCertOptionsRequireValidator{ + CertType: true, + KeyID: true, + Principals: true, + }) + } else { signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{ CertType: SSHUserCert, Principals: iden.Usernames, diff --git a/sshutil/templates.go b/sshutil/templates.go index f51b46f6..2565c4fd 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -1,5 +1,6 @@ package sshutil +// Variables used to hold template data. const ( TypeKey = "Type" KeyIDKey = "KeyID" @@ -101,7 +102,7 @@ func (t TemplateData) SetType(typ CertType) { t.Set(TypeKey, typ.String()) } -// SetType sets the certificate key id in the template data. +// SetKeyID sets the certificate key id in the template data. func (t TemplateData) SetKeyID(id string) { t.Set(KeyIDKey, id) } @@ -148,13 +149,25 @@ const DefaultCertificate = `{ "criticalOptions": {{ toJson .CriticalOptions }} }` +// DefaultAdminCertificate is the template used by an admin user in a OIDC +// provisioner. +const DefaultAdminCertificate = `{ + "type": "{{ .Insecure.CR.Type }}", + "keyId": "{{ .Insecure.CR.KeyID }}", + "principals": {{ toJson .Insecure.CR.Principals }} +{{- if eq .Insecure.CR.Type "user" }} + , "extensions": {{ toJson .Extensions }}, + "criticalOptions": {{ toJson .CriticalOptions }} +{{- end }} +}` + +// DefaultIIDCertificate is the default template for IID provisioners. By +// default certificate type will be set always to host, key id to the instance +// id. Principals will be only enforced by the provisioner if disableCustomSANs +// is set to true. const DefaultIIDCertificate = `{ "type": "{{ .Type }}", -{{- if .Insecure.CR.KeyID }} - "keyId": "{{ .Insecure.CR.KeyID }}", -{{- else }} "keyId": "{{ .KeyID }}", -{{- end}} {{- if .Insecure.CR.Principals }} "principals": {{ toJson .Insecure.CR.Principals }}, {{- else }} @@ -163,6 +176,9 @@ const DefaultIIDCertificate = `{ "extensions": {{ toJson .Extensions }} }` +// CertificateRequestTemplate is the template used for provisioners that accepts +// any certificate request. The provisioner must validate that type, keyId and +// principals are passed in the request. const CertificateRequestTemplate = `{ "type": "{{ .Insecure.CR.Type }}", "keyId": "{{ .Insecure.CR.KeyID }}", From 413af88aad98cdf6c1eaff0a177782519a0af62c Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 18:10:29 -0700 Subject: [PATCH 26/47] Fix provisioning tests. --- authority/provisioner/aws_test.go | 1 + authority/provisioner/jwk_test.go | 4 -- authority/provisioner/k8sSA_test.go | 6 +- authority/provisioner/oidc_test.go | 25 ++++---- authority/provisioner/sign_ssh_options.go | 2 +- .../provisioner/sign_ssh_options_test.go | 51 ++++++++++------ authority/provisioner/ssh_test.go | 61 ++++++++++++------- authority/provisioner/x5c_test.go | 24 ++++---- 8 files changed, 100 insertions(+), 74 deletions(-) diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 4c4dbae4..94365982 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -708,6 +708,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/jwk_test.go b/authority/provisioner/jwk_test.go index 2048bc64..61f66953 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -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_test.go b/authority/provisioner/k8sSA_test.go index 16a57aaf..9c731ae4 100644 --- a/authority/provisioner/k8sSA_test.go +++ b/authority/provisioner/k8sSA_test.go @@ -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/oidc_test.go b/authority/provisioner/oidc_test.go index 55093a91..cb830246 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -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/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 279c2ae1..e9d5c727 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -47,7 +47,7 @@ type SignSSHOptions struct { Principals []string `json:"principals"` ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"` - TemplateData json.RawMessage `json:"templateData"` + TemplateData json.RawMessage `json:"templateData,omitempty"` Backdate time.Duration `json:"-"` } diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 3c0d9bb5..9ab72a51 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -525,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{ @@ -571,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{ @@ -655,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) { diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index 5511e6cd..3c8f7118 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" + "github.com/smallstep/certificates/sshutil" "golang.org/x/crypto/ssh" ) @@ -46,10 +48,14 @@ 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) @@ -66,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, opts); 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, opts); err != nil { - return nil, err + return nil, errs.Wrap(http.StatusForbidden, err, "authority.SignSSH") } } @@ -98,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/x5c_test.go b/authority/provisioner/x5c_test.go index 73a3ba9f..fac8e60e 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -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) } } } From c4bbc81d9fd7d91b5c034dc5c8041e0eac27e0da Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 18:36:05 -0700 Subject: [PATCH 27/47] Fix authority tests. --- authority/authorize_test.go | 2 +- authority/provisioner/sign_ssh_options.go | 8 +++ authority/ssh.go | 5 ++ authority/ssh_test.go | 62 ++++++++++++++--------- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/authority/authorize_test.go b/authority/authorize_test.go index c562437c..a251dab9 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -961,7 +961,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - assert.Len(t, 11, got) + assert.Len(t, 7, got) } } }) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index e9d5c727..b2ec04d4 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -51,6 +51,14 @@ type SignSSHOptions struct { Backdate time.Duration `json:"-"` } +// Validate validates the given SignSSHOptions. +func (o SignSSHOptions) Validate() error { + if o.CertType != "" && o.CertType != SSHUserCert && o.CertType != SSHHostCert { + return errors.Errorf("unknown certType %s", o.CertType) + } + return nil +} + // Type returns the uint32 representation of the CertType. func (o SignSSHOptions) Type() uint32 { return sshCertTypeUInt32(o.CertType) diff --git a/authority/ssh.go b/authority/ssh.go index 41f37579..6534b772 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -211,6 +211,11 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi 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 diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 8e64b108..42481684 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -26,7 +26,7 @@ import ( 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,15 @@ 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) + now := time.Now() type fields struct { @@ -125,27 +137,27 @@ 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}, + {"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}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 8d89bbd62ff8d62bcb76c968d0b0a722ccfbc764 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 18:39:02 -0700 Subject: [PATCH 28/47] Remove unused code. --- authority/provisioner/sign_ssh_options.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index b2ec04d4..d948ddac 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -435,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 { From 342cb713eeaab95fb7af6c8de5520cf42ad56b31 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 18:51:47 -0700 Subject: [PATCH 29/47] Add test with custom templates. --- authority/ssh_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 42481684..1a23acad 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -112,6 +112,20 @@ func TestAuthority_SignSSH(t *testing.T) { 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() @@ -149,6 +163,7 @@ func TestAuthority_SignSSH(t *testing.T) { {"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}, @@ -158,6 +173,7 @@ func TestAuthority_SignSSH(t *testing.T) { {"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) { From 37f84e9bb33f84106384b3990d6fb260e2200d44 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 3 Aug 2020 19:01:15 -0700 Subject: [PATCH 30/47] Add delay in test. --- authority/provisioner/sign_options_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index 459455bc..28b0dc82 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -659,7 +659,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) }) } From ce1eb0a01b68420351dc0d6d53d00ec23ffc6c3d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 5 Aug 2020 19:09:06 -0700 Subject: [PATCH 31/47] Use new x509util for renew/rekey. --- authority/tls.go | 13 +------------ authority/tls_test.go | 4 ++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/authority/tls.go b/authority/tls.go index 76edf360..baaac270 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -245,21 +245,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 { diff --git a/authority/tls_test.go b/authority/tls_test.go index 90dc7075..41347f17 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -538,7 +538,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 }, @@ -766,7 +766,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 }, From e83e47a91ebebe1b9426ac08f66b4c50e21a36bf Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 11:26:51 -0700 Subject: [PATCH 32/47] Use sshutil and randutil from go.step.sm/crypto. --- acme/common.go | 2 +- api/api_test.go | 7 +++---- api/ssh.go | 5 ++--- api/ssh_test.go | 13 ++++++------- authority/authority.go | 3 +-- authority/authorize_test.go | 2 +- authority/options.go | 3 +-- authority/provisioner/aws.go | 4 ++-- authority/provisioner/azure.go | 4 ++-- authority/provisioner/gcp.go | 4 ++-- authority/provisioner/jwk.go | 2 +- authority/provisioner/k8sSA.go | 2 +- authority/provisioner/oidc.go | 6 +++--- authority/provisioner/ssh_options.go | 6 +++--- authority/provisioner/ssh_test.go | 2 +- authority/provisioner/utils_test.go | 2 +- authority/provisioner/x5c.go | 2 +- authority/provisioner/x5c_test.go | 2 +- authority/ssh.go | 25 ++++++++++++++++++++----- authority/ssh_test.go | 18 +++++++++--------- ca/bootstrap_test.go | 2 +- ca/ca_test.go | 2 +- ca/provisioner.go | 2 +- ca/tls_test.go | 2 +- commands/onboard.go | 2 +- go.mod | 2 +- go.sum | 3 +++ sshutil/certificate.go | 2 +- 28 files changed, 72 insertions(+), 59 deletions(-) diff --git a/acme/common.go b/acme/common.go index d2d710cc..45b2e476 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/randutil" ) // Provisioner is an interface that implements a subset of the provisioner.Interface -- diff --git a/api/api_test.go b/api/api_test.go index aab47e53..7df021cc 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -31,7 +31,6 @@ 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" @@ -564,7 +563,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) @@ -697,11 +696,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) { diff --git a/api/ssh.go b/api/ssh.go index 7e3cb3db..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) } @@ -87,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, 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..36bfe334 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -15,7 +15,6 @@ 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" "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/authorize_test.go b/authority/authorize_test.go index a251dab9..167f11d0 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -18,8 +18,8 @@ import ( "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/randutil" "golang.org/x/crypto/ssh" "gopkg.in/square/go-jose.v2/jwt" ) 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 4a05a2bf..d25b5743 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -17,8 +17,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) @@ -497,7 +497,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSSHSign") } diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index cccb4ceb..9934f56b 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -14,8 +14,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) @@ -366,7 +366,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "azure.AuthorizeSSHSign") } diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 4cacca12..42585124 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -15,8 +15,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) @@ -408,7 +408,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDCertificate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index df1a9e67..a42cc1ce 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -8,8 +8,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index a69b91e0..ee48d283 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -11,9 +11,9 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index d2ec3290..5fb4f449 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -13,8 +13,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) @@ -395,9 +395,9 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption // 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.DefaultCertificate + defaultTemplate := sshutil.DefaultTemplate if isAdmin && !o.Options.GetSSHOptions().HasTemplate() { - defaultTemplate = sshutil.DefaultAdminCertificate + defaultTemplate = sshutil.DefaultAdminTemplate } templateOptions, err := CustomSSHTemplateOptions(o.Options, data, defaultTemplate) diff --git a/authority/provisioner/ssh_options.go b/authority/provisioner/ssh_options.go index 0a9c31dc..81c4371b 100644 --- a/authority/provisioner/ssh_options.go +++ b/authority/provisioner/ssh_options.go @@ -5,10 +5,10 @@ import ( "strings" "github.com/pkg/errors" - "github.com/smallstep/certificates/sshutil" + "go.step.sm/crypto/sshutil" ) -// CertificateOptions is an interface that returns a list of options passed when +// SSHCertificateOptions is an interface that returns a list of options passed when // creating a new certificate. type SSHCertificateOptions interface { Options(SignSSHOptions) []sshutil.Option @@ -45,7 +45,7 @@ func (o *SSHOptions) HasTemplate() bool { // 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.DefaultCertificate) + return CustomSSHTemplateOptions(o, data, sshutil.DefaultTemplate) } // CustomTemplateOptions generates a CertificateOptions with the template, data diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index 3c8f7118..c530cd3c 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" + "go.step.sm/crypto/sshutil" "golang.org/x/crypto/ssh" ) diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index ec02a5dc..19c6436d 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -17,8 +17,8 @@ import ( "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/randutil" "golang.org/x/crypto/ssh" ) diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 649ad178..1f6b0891 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -9,8 +9,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/sshutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index fac8e60e..58130413 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -10,8 +10,8 @@ import ( "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/randutil" ) func TestX5C_Getters(t *testing.T) { diff --git a/authority/ssh.go b/authority/ssh.go index 6534b772..c7d144b2 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/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 { @@ -554,7 +569,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") @@ -564,9 +579,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 1a23acad..3b21a85f 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -18,9 +18,9 @@ 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/sshutil" "golang.org/x/crypto/ssh" ) @@ -706,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{}, @@ -725,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) }, } @@ -760,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/ca/bootstrap_test.go b/ca/bootstrap_test.go index 9b78d0ee..49c20dc0 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -15,8 +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" + "go.step.sm/crypto/randutil" jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" ) diff --git a/ca/ca_test.go b/ca/ca_test.go index 15e4d42a..08e40728 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -27,9 +27,9 @@ import ( "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" + "go.step.sm/crypto/randutil" jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" ) diff --git a/ca/provisioner.go b/ca/provisioner.go index d5dfd648..28975fa4 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/randutil" ) const tokenLifetime = 5 * time.Minute diff --git a/ca/tls_test.go b/ca/tls_test.go index bf29e9a6..8dee0a6f 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -18,8 +18,8 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" - "github.com/smallstep/cli/crypto/randutil" stepJOSE "github.com/smallstep/cli/jose" + "go.step.sm/crypto/randutil" jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" ) 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 d30e78d0..c163eaa3 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 - go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 + go.step.sm/crypto v0.1.0 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 google.golang.org/api v0.15.0 diff --git a/go.sum b/go.sum index d2907d00..520d0577 100644 --- a/go.sum +++ b/go.sum @@ -479,6 +479,7 @@ github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9P github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8= github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8= +github.com/smallstep/cli v0.14.6 h1:xc9rawDKB70Vgvg10gfQAh9EpDWS3k1O002J5bApqUk= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 h1:TcCYqEqh6EIEiFabRdtG0IGyFK01kRLTjx6TIKqjxX8= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95/go.mod h1:7aWHk7WwJMpEP4PYyav86FMpaI9vuA0uJRliUAqCwxg= github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= @@ -579,6 +580,8 @@ go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 h1:FymMl8TrXGxFf80BWpO0CnkSfLnw0BkDdRrhbMGf5zE= go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U= +go.step.sm/crypto v0.1.0 h1:SLo25kNU3C6u8Ne5BnavI9bhtA+PBrMnnNZKYIWhKFU= +go.step.sm/crypto v0.1.0/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= 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 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= diff --git a/sshutil/certificate.go b/sshutil/certificate.go index 1b58882c..f1f68292 100644 --- a/sshutil/certificate.go +++ b/sshutil/certificate.go @@ -6,7 +6,7 @@ import ( "encoding/json" "github.com/pkg/errors" - "github.com/smallstep/cli/crypto/randutil" + "go.step.sm/crypto/randutil" "golang.org/x/crypto/ssh" ) From 77624c6b1c89c72f1b282fa811249f2b91712b59 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 11:29:04 -0700 Subject: [PATCH 33/47] Remove now migrated sshutil. --- sshutil/certificate.go | 104 ------- sshutil/certificate_request.go | 17 -- sshutil/certificate_test.go | 350 ------------------------ sshutil/options.go | 90 ------ sshutil/options_test.go | 189 ------------- sshutil/templates.go | 195 ------------- sshutil/templates_test.go | 481 --------------------------------- sshutil/testdata/github.tpl | 10 - sshutil/types.go | 89 ------ sshutil/types_test.go | 113 -------- 10 files changed, 1638 deletions(-) delete mode 100644 sshutil/certificate.go delete mode 100644 sshutil/certificate_request.go delete mode 100644 sshutil/certificate_test.go delete mode 100644 sshutil/options.go delete mode 100644 sshutil/options_test.go delete mode 100644 sshutil/templates.go delete mode 100644 sshutil/templates_test.go delete mode 100644 sshutil/testdata/github.tpl delete mode 100644 sshutil/types.go delete mode 100644 sshutil/types_test.go diff --git a/sshutil/certificate.go b/sshutil/certificate.go deleted file mode 100644 index f1f68292..00000000 --- a/sshutil/certificate.go +++ /dev/null @@ -1,104 +0,0 @@ -package sshutil - -import ( - "crypto/rand" - "encoding/binary" - "encoding/json" - - "github.com/pkg/errors" - "go.step.sm/crypto/randutil" - "golang.org/x/crypto/ssh" -) - -// Certificate is the json representation of ssh.Certificate. -type Certificate struct { - Nonce []byte `json:"nonce"` - Key ssh.PublicKey `json:"-"` - Serial uint64 `json:"serial"` - Type CertType `json:"type"` - KeyID string `json:"keyId"` - Principals []string `json:"principals"` - ValidAfter uint64 `json:"-"` - ValidBefore uint64 `json:"-"` - CriticalOptions map[string]string `json:"criticalOptions"` - Extensions map[string]string `json:"extensions"` - Reserved []byte `json:"reserved"` - SignatureKey ssh.PublicKey `json:"-"` - Signature *ssh.Signature `json:"-"` -} - -// NewCertificate creates a new certificate with the given key after parsing a -// template given in the options. -func NewCertificate(cr CertificateRequest, opts ...Option) (*Certificate, error) { - o, err := new(Options).apply(cr, opts) - if err != nil { - return nil, err - } - - if o.CertBuffer == nil { - return nil, errors.New("certificate template cannot be empty") - } - - // With templates - var cert Certificate - if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil { - return nil, errors.Wrap(err, "error unmarshaling certificate") - } - - // Complete with public key - cert.Key = cr.Key - - return &cert, nil -} - -func (c *Certificate) GetCertificate() *ssh.Certificate { - return &ssh.Certificate{ - Nonce: c.Nonce, - Key: c.Key, - Serial: c.Serial, - CertType: uint32(c.Type), - KeyId: c.KeyID, - ValidPrincipals: c.Principals, - ValidAfter: c.ValidAfter, - ValidBefore: c.ValidBefore, - Permissions: ssh.Permissions{ - CriticalOptions: c.CriticalOptions, - Extensions: c.Extensions, - }, - Reserved: c.Reserved, - } -} - -// CreateCertificate signs the given certificate with the given signer. If the -// certificate does not have a nonce or a serial, it will create random ones. -func CreateCertificate(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate, error) { - if len(cert.Nonce) == 0 { - nonce, err := randutil.ASCII(32) - if err != nil { - return nil, err - } - cert.Nonce = []byte(nonce) - } - - if cert.Serial == 0 { - if err := binary.Read(rand.Reader, binary.BigEndian, &cert.Serial); err != nil { - return nil, errors.Wrap(err, "error reading random number") - } - } - - // Set signer public key. - 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) - if err != nil { - return nil, errors.Wrap(err, "error signing certificate") - } - cert.Signature = sig - - return cert, nil -} diff --git a/sshutil/certificate_request.go b/sshutil/certificate_request.go deleted file mode 100644 index 85a69502..00000000 --- a/sshutil/certificate_request.go +++ /dev/null @@ -1,17 +0,0 @@ -package sshutil - -import "golang.org/x/crypto/ssh" - -// CertificateRequests simulates a certificate request for SSH. SSH does not -// have a concept of certificate requests, but the CA accepts the key and some -// other parameters in the requests that are part of the certificate. This -// struct will hold these parameters. -// -// CertificateRequests object will be used in the templates to set parameters -// passed with the API instead of the validated ones. -type CertificateRequest struct { - Key ssh.PublicKey - Type string - KeyID string - Principals []string -} diff --git a/sshutil/certificate_test.go b/sshutil/certificate_test.go deleted file mode 100644 index 8042cc6b..00000000 --- a/sshutil/certificate_test.go +++ /dev/null @@ -1,350 +0,0 @@ -package sshutil - -import ( - "bytes" - "crypto/ed25519" - "crypto/rand" - "encoding/base64" - "reflect" - "testing" - - "golang.org/x/crypto/ssh" -) - -func mustGenerateKey(t *testing.T) (ssh.PublicKey, ssh.Signer) { - t.Helper() - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatal(err) - } - key, err := ssh.NewPublicKey(pub) - if err != nil { - t.Fatal(err) - } - signer, err := ssh.NewSignerFromKey(priv) - if err != nil { - t.Fatal(err) - } - return key, signer -} - -func mustGeneratePublicKey(t *testing.T) ssh.PublicKey { - t.Helper() - key, _ := mustGenerateKey(t) - return key -} - -func TestNewCertificate(t *testing.T) { - key := mustGeneratePublicKey(t) - cr := CertificateRequest{ - Key: key, - } - - type args struct { - cr CertificateRequest - opts []Option - } - tests := []struct { - name string - args args - want *Certificate - wantErr bool - }{ - {"user", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(UserCert, "jane@doe.com", []string{"jane"}))}}, &Certificate{ - Nonce: nil, - Key: key, - Serial: 0, - Type: UserCert, - KeyID: "jane@doe.com", - Principals: []string{"jane"}, - ValidAfter: 0, - ValidBefore: 0, - CriticalOptions: nil, - Extensions: map[string]string{ - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "", - }, - Reserved: nil, - SignatureKey: nil, - Signature: nil, - }, false}, - {"host", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(HostCert, "foobar", []string{"foo.internal", "bar.internal"}))}}, &Certificate{ - Nonce: nil, - Key: key, - Serial: 0, - Type: HostCert, - KeyID: "foobar", - Principals: []string{"foo.internal", "bar.internal"}, - ValidAfter: 0, - ValidBefore: 0, - CriticalOptions: nil, - Extensions: nil, - Reserved: nil, - SignatureKey: nil, - Signature: nil, - }, false}, - {"file", args{cr, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{ - TypeKey: UserCert, - KeyIDKey: "john@doe.com", - PrincipalsKey: []string{"john", "john@doe.com"}, - ExtensionsKey: DefaultExtensions(UserCert), - InsecureKey: TemplateData{ - "User": map[string]interface{}{"username": "john"}, - }, - })}}, &Certificate{ - Nonce: nil, - Key: key, - Serial: 0, - Type: UserCert, - KeyID: "john@doe.com", - Principals: []string{"john", "john@doe.com"}, - ValidAfter: 0, - ValidBefore: 0, - CriticalOptions: nil, - Extensions: map[string]string{ - "login@github.com": "john", - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "", - }, - Reserved: nil, - SignatureKey: nil, - Signature: nil, - }, false}, - {"base64", args{cr, []Option{WithTemplateBase64(base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), CreateTemplateData(HostCert, "foo.internal", nil))}}, &Certificate{ - Nonce: nil, - Key: key, - Serial: 0, - Type: HostCert, - KeyID: "foo.internal", - Principals: nil, - ValidAfter: 0, - ValidBefore: 0, - CriticalOptions: nil, - Extensions: nil, - Reserved: nil, - SignatureKey: nil, - Signature: nil, - }, false}, - {"failNilOptions", args{cr, nil}, nil, true}, - {"failEmptyOptions", args{cr, nil}, nil, true}, - {"badBase64Template", args{cr, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true}, - {"badFileTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true}, - {"badJsonTemplate", args{cr, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true}, - {"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NewCertificate(tt.args.cr, tt.args.opts...) - if (err != nil) != tt.wantErr { - t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewCertificate() = \n%+v, want \n%+v", got, tt.want) - } - }) - } -} - -func TestCertificate_GetCertificate(t *testing.T) { - key := mustGeneratePublicKey(t) - - type fields struct { - Nonce []byte - Key ssh.PublicKey - Serial uint64 - Type CertType - KeyID string - Principals []string - ValidAfter uint64 - ValidBefore uint64 - CriticalOptions map[string]string - Extensions map[string]string - Reserved []byte - SignatureKey ssh.PublicKey - Signature *ssh.Signature - } - tests := []struct { - name string - fields fields - want *ssh.Certificate - }{ - {"user", fields{ - Nonce: []byte("0123456789"), - Key: key, - Serial: 123, - Type: UserCert, - KeyID: "key-id", - Principals: []string{"john"}, - ValidAfter: 1111, - ValidBefore: 2222, - CriticalOptions: map[string]string{"foo": "bar"}, - Extensions: map[string]string{"login@github.com": "john"}, - Reserved: []byte("reserved"), - SignatureKey: key, - Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")}, - }, &ssh.Certificate{ - Nonce: []byte("0123456789"), - Key: key, - Serial: 123, - CertType: ssh.UserCert, - KeyId: "key-id", - ValidPrincipals: []string{"john"}, - ValidAfter: 1111, - ValidBefore: 2222, - Permissions: ssh.Permissions{ - CriticalOptions: map[string]string{"foo": "bar"}, - Extensions: map[string]string{"login@github.com": "john"}, - }, - Reserved: []byte("reserved"), - }}, - {"host", fields{ - Nonce: []byte("0123456789"), - Key: key, - Serial: 123, - Type: HostCert, - KeyID: "key-id", - Principals: []string{"foo.internal", "bar.internal"}, - ValidAfter: 1111, - ValidBefore: 2222, - CriticalOptions: map[string]string{"foo": "bar"}, - Extensions: nil, - Reserved: []byte("reserved"), - SignatureKey: key, - Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")}, - }, &ssh.Certificate{ - Nonce: []byte("0123456789"), - Key: key, - Serial: 123, - CertType: ssh.HostCert, - KeyId: "key-id", - ValidPrincipals: []string{"foo.internal", "bar.internal"}, - ValidAfter: 1111, - ValidBefore: 2222, - Permissions: ssh.Permissions{ - CriticalOptions: map[string]string{"foo": "bar"}, - Extensions: nil, - }, - Reserved: []byte("reserved"), - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &Certificate{ - Nonce: tt.fields.Nonce, - Key: tt.fields.Key, - Serial: tt.fields.Serial, - Type: tt.fields.Type, - KeyID: tt.fields.KeyID, - Principals: tt.fields.Principals, - ValidAfter: tt.fields.ValidAfter, - ValidBefore: tt.fields.ValidBefore, - CriticalOptions: tt.fields.CriticalOptions, - Extensions: tt.fields.Extensions, - Reserved: tt.fields.Reserved, - SignatureKey: tt.fields.SignatureKey, - Signature: tt.fields.Signature, - } - if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Certificate.GetCertificate() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCreateCertificate(t *testing.T) { - key, signer := mustGenerateKey(t) - type args struct { - cert *ssh.Certificate - signer ssh.Signer - } - tests := []struct { - name string - args args - wantErr bool - }{ - {"ok", args{&ssh.Certificate{ - Nonce: []byte("0123456789"), - Key: key, - Serial: 123, - CertType: ssh.HostCert, - KeyId: "foo", - ValidPrincipals: []string{"foo.internal"}, - ValidAfter: 1111, - ValidBefore: 2222, - Permissions: ssh.Permissions{}, - Reserved: []byte("reserved"), - }, signer}, false}, - {"emptyNonce", args{&ssh.Certificate{ - Key: key, - Serial: 123, - CertType: ssh.UserCert, - KeyId: "jane@doe.com", - ValidPrincipals: []string{"jane"}, - ValidAfter: 1111, - ValidBefore: 2222, - Permissions: ssh.Permissions{}, - Reserved: []byte("reserved"), - }, signer}, false}, - {"emptySerial", args{&ssh.Certificate{ - Nonce: []byte("0123456789"), - Key: key, - CertType: ssh.UserCert, - KeyId: "jane@doe.com", - ValidPrincipals: []string{"jane"}, - ValidAfter: 1111, - ValidBefore: 2222, - Permissions: ssh.Permissions{}, - Reserved: []byte("reserved"), - }, signer}, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CreateCertificate(tt.args.cert, tt.args.signer) - if (err != nil) != tt.wantErr { - t.Errorf("CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != nil { - switch { - case len(got.Nonce) == 0: - t.Errorf("CreateCertificate() nonce should not be empty") - case got.Serial == 0: - t.Errorf("CreateCertificate() serial should not be 0") - case got.Signature == nil: - t.Errorf("CreateCertificate() signature should not be nil") - case !bytes.Equal(got.SignatureKey.Marshal(), tt.args.signer.PublicKey().Marshal()): - t.Errorf("CreateCertificate() signature key is not the expected one") - } - - signature := got.Signature - got.Signature = nil - - data := got.Marshal() - data = data[:len(data)-4] - - sig, err := signer.Sign(rand.Reader, data) - if err != nil { - t.Errorf("signer.Sign() error = %v", err) - } - - // Verify signature - got.Signature = signature - if err := signer.PublicKey().Verify(data, got.Signature); err != nil { - t.Errorf("CreateCertificate() signature verify error = %v", err) - } - // Verify data with public key in cert - if err := got.Verify(data, sig); err != nil { - t.Errorf("CreateCertificate() certificate verify error = %v", err) - } - - } - }) - } -} diff --git a/sshutil/options.go b/sshutil/options.go deleted file mode 100644 index e6f094e0..00000000 --- a/sshutil/options.go +++ /dev/null @@ -1,90 +0,0 @@ -package sshutil - -import ( - "bytes" - "encoding/base64" - "io/ioutil" - "text/template" - - "github.com/Masterminds/sprig/v3" - "github.com/pkg/errors" - "github.com/smallstep/cli/config" -) - -func getFuncMap(failMessage *string) template.FuncMap { - m := sprig.TxtFuncMap() - m["fail"] = func(msg string) (string, error) { - *failMessage = msg - return "", errors.New(msg) - } - return m -} - -// Options are the options that can be passed to NewCertificate. -type Options struct { - CertBuffer *bytes.Buffer -} - -func (o *Options) apply(cr CertificateRequest, opts []Option) (*Options, error) { - for _, fn := range opts { - if err := fn(cr, o); err != nil { - return o, err - } - } - return o, nil -} - -// Option is the type used as a variadic argument in NewCertificate. -type Option func(cr CertificateRequest, o *Options) error - -// WithTemplate is an options that executes the given template text with the -// given data. -func WithTemplate(text string, data TemplateData) Option { - return func(cr CertificateRequest, o *Options) error { - terr := new(TemplateError) - funcMap := getFuncMap(&terr.Message) - - tmpl, err := template.New("template").Funcs(funcMap).Parse(text) - if err != nil { - return errors.Wrapf(err, "error parsing template") - } - - buf := new(bytes.Buffer) - data.SetCertificateRequest(cr) - if err := tmpl.Execute(buf, data); err != nil { - if terr.Message != "" { - return terr - } - return errors.Wrapf(err, "error executing template") - } - o.CertBuffer = buf - return nil - } -} - -// WithTemplateBase64 is an options that executes the given template base64 -// string with the given data. -func WithTemplateBase64(s string, data TemplateData) Option { - return func(cr CertificateRequest, o *Options) error { - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return errors.Wrap(err, "error decoding template") - } - fn := WithTemplate(string(b), data) - return fn(cr, o) - } -} - -// WithTemplateFile is an options that reads the template file and executes it -// with the given data. -func WithTemplateFile(path string, data TemplateData) Option { - return func(cr CertificateRequest, o *Options) error { - filename := config.StepAbs(path) - b, err := ioutil.ReadFile(filename) - if err != nil { - return errors.Wrapf(err, "error reading %s", path) - } - fn := WithTemplate(string(b), data) - return fn(cr, o) - } -} diff --git a/sshutil/options_test.go b/sshutil/options_test.go deleted file mode 100644 index a5adb7ff..00000000 --- a/sshutil/options_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package sshutil - -import ( - "bytes" - "encoding/base64" - "reflect" - "testing" - - "github.com/pkg/errors" -) - -func Test_getFuncMap_fail(t *testing.T) { - var failMesage string - fns := getFuncMap(&failMesage) - fail := fns["fail"].(func(s string) (string, error)) - s, err := fail("the fail message") - if err == nil { - t.Errorf("fail() error = %v, wantErr %v", err, errors.New("the fail message")) - } - if s != "" { - t.Errorf("fail() = \"%s\", want \"the fail message\"", s) - } - if failMesage != "the fail message" { - t.Errorf("fail() message = \"%s\", want \"the fail message\"", failMesage) - } -} - -func TestWithTemplate(t *testing.T) { - key := mustGeneratePublicKey(t) - cr := CertificateRequest{ - Key: key, - } - - type args struct { - text string - data TemplateData - cr CertificateRequest - } - tests := []struct { - name string - args args - want Options - wantErr bool - }{ - {"user", args{DefaultCertificate, TemplateData{ - TypeKey: "user", - KeyIDKey: "jane@doe.com", - PrincipalsKey: []string{"jane", "jane@doe.com"}, - ExtensionsKey: DefaultExtensions(UserCert), - }, cr}, Options{ - CertBuffer: bytes.NewBufferString(`{ - "type": "user", - "keyId": "jane@doe.com", - "principals": ["jane","jane@doe.com"], - "extensions": {"permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""}, - "criticalOptions": null -}`)}, false}, - {"host", args{DefaultCertificate, TemplateData{ - TypeKey: "host", - KeyIDKey: "foo", - PrincipalsKey: []string{"foo.internal"}, - CriticalOptionsKey: map[string]string{"foo": "bar"}, - }, cr}, Options{ - CertBuffer: bytes.NewBufferString(`{ - "type": "host", - "keyId": "foo", - "principals": ["foo.internal"], - "extensions": null, - "criticalOptions": {"foo":"bar"} -}`)}, false}, - {"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true}, - {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, cr}, Options{}, true}, - {"error", args{`{{ mustHas 3 .Data }}`, TemplateData{ - "Data": 3, - }, cr}, Options{}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got Options - fn := WithTemplate(tt.args.text, tt.args.data) - if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { - t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("WithTemplate() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestWithTemplateBase64(t *testing.T) { - key := mustGeneratePublicKey(t) - cr := CertificateRequest{ - Key: key, - } - - type args struct { - s string - data TemplateData - cr CertificateRequest - } - tests := []struct { - name string - args args - want Options - wantErr bool - }{ - {"host", args{base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), TemplateData{ - TypeKey: "host", - KeyIDKey: "foo.internal", - PrincipalsKey: []string{"foo.internal", "bar.internal"}, - ExtensionsKey: map[string]interface{}{"foo": "bar"}, - CriticalOptionsKey: map[string]interface{}{"bar": "foo"}, - }, cr}, Options{ - CertBuffer: bytes.NewBufferString(`{ - "type": "host", - "keyId": "foo.internal", - "principals": ["foo.internal","bar.internal"], - "extensions": {"foo":"bar"}, - "criticalOptions": {"bar":"foo"} -}`)}, false}, - {"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got Options - fn := WithTemplateBase64(tt.args.s, tt.args.data) - if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { - t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("WithTemplateBase64() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestWithTemplateFile(t *testing.T) { - key := mustGeneratePublicKey(t) - cr := CertificateRequest{ - Key: key, - } - - data := TemplateData{ - TypeKey: "user", - KeyIDKey: "jane@doe.com", - PrincipalsKey: []string{"jane", "jane@doe.com"}, - ExtensionsKey: DefaultExtensions(UserCert), - InsecureKey: TemplateData{ - UserKey: map[string]interface{}{ - "username": "jane", - }, - }, - } - - type args struct { - path string - data TemplateData - cr CertificateRequest - } - tests := []struct { - name string - args args - want Options - wantErr bool - }{ - {"github.com", args{"./testdata/github.tpl", data, cr}, Options{ - CertBuffer: bytes.NewBufferString(`{ - "type": "user", - "keyId": "jane@doe.com", - "principals": ["jane","jane@doe.com"], - "extensions": {"login@github.com":"jane","permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""} -}`), - }, false}, - {"missing", args{"./testdata/missing.tpl", data, cr}, Options{}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got Options - fn := WithTemplateFile(tt.args.path, tt.args.data) - if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr { - t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("WithTemplateFile() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/sshutil/templates.go b/sshutil/templates.go deleted file mode 100644 index 2565c4fd..00000000 --- a/sshutil/templates.go +++ /dev/null @@ -1,195 +0,0 @@ -package sshutil - -// Variables used to hold template data. -const ( - TypeKey = "Type" - KeyIDKey = "KeyID" - PrincipalsKey = "Principals" - ExtensionsKey = "Extensions" - CriticalOptionsKey = "CriticalOptions" - TokenKey = "Token" - InsecureKey = "Insecure" - UserKey = "User" - CertificateRequestKey = "CR" -) - -// TemplateError represents an error in a template produced by the fail -// function. -type TemplateError struct { - Message string -} - -// Error implements the error interface and returns the error string when a -// template executes the `fail "message"` function. -func (e *TemplateError) Error() string { - return e.Message -} - -// TemplateData is an alias for map[string]interface{}. It represents the data -// passed to the templates. -type TemplateData map[string]interface{} - -// CreateTemplateData returns a TemplateData with the given certificate type, -// key id, principals, and the default extensions. -func CreateTemplateData(ct CertType, keyID string, principals []string) TemplateData { - return TemplateData{ - TypeKey: ct.String(), - KeyIDKey: keyID, - PrincipalsKey: principals, - ExtensionsKey: DefaultExtensions(ct), - } -} - -// DefaultExtensions returns the default extensions set in an SSH certificate. -func DefaultExtensions(ct CertType) map[string]interface{} { - switch ct { - case UserCert: - return map[string]interface{}{ - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "", - } - default: - return nil - } -} - -// NewTemplateData creates a new map for templates data. -func NewTemplateData() TemplateData { - return TemplateData{} -} - -// AddExtension adds one extension to the templates data. -func (t TemplateData) AddExtension(key, value string) { - if m, ok := t[ExtensionsKey].(map[string]interface{}); ok { - m[key] = value - } else { - t[ExtensionsKey] = map[string]interface{}{ - key: value, - } - } -} - -// AddCriticalOption adds one critical option to the templates data. -func (t TemplateData) AddCriticalOption(key, value string) { - if m, ok := t[CriticalOptionsKey].(map[string]interface{}); ok { - m[key] = value - } else { - t[CriticalOptionsKey] = map[string]interface{}{ - key: value, - } - } -} - -// Set sets a key-value pair in the template data. -func (t TemplateData) Set(key string, v interface{}) { - t[key] = v -} - -// SetInsecure sets a key-value pair in the insecure template data. -func (t TemplateData) SetInsecure(key string, v interface{}) { - if m, ok := t[InsecureKey].(TemplateData); ok { - m[key] = v - } else { - t[InsecureKey] = TemplateData{key: v} - } -} - -// SetType sets the certificate type in the template data. -func (t TemplateData) SetType(typ CertType) { - t.Set(TypeKey, typ.String()) -} - -// SetKeyID sets the certificate key id in the template data. -func (t TemplateData) SetKeyID(id string) { - t.Set(KeyIDKey, id) -} - -// SetPrincipals sets the certificate principals in the template data. -func (t TemplateData) SetPrincipals(p []string) { - t.Set(PrincipalsKey, p) -} - -// SetExtensions sets the certificate extensions in the template data. -func (t TemplateData) SetExtensions(e map[string]interface{}) { - t.Set(ExtensionsKey, e) -} - -// SetCriticalOptions sets the certificate critical options in the template -// data. -func (t TemplateData) SetCriticalOptions(o map[string]interface{}) { - t.Set(CriticalOptionsKey, o) -} - -// SetToken sets the given token in the template data. -func (t TemplateData) SetToken(v interface{}) { - t.Set(TokenKey, v) -} - -// SetUserData sets the given user provided object in the insecure template -// data. -func (t TemplateData) SetUserData(v interface{}) { - t.SetInsecure(UserKey, v) -} - -// SetCertificateRequest sets the simulated ssh certificate request the insecure -// template data. -func (t TemplateData) SetCertificateRequest(cr CertificateRequest) { - t.SetInsecure(CertificateRequestKey, cr) -} - -// DefaultCertificate is the default template for an SSH certificate. -const DefaultCertificate = `{ - "type": "{{ .Type }}", - "keyId": "{{ .KeyID }}", - "principals": {{ toJson .Principals }}, - "extensions": {{ toJson .Extensions }}, - "criticalOptions": {{ toJson .CriticalOptions }} -}` - -// DefaultAdminCertificate is the template used by an admin user in a OIDC -// provisioner. -const DefaultAdminCertificate = `{ - "type": "{{ .Insecure.CR.Type }}", - "keyId": "{{ .Insecure.CR.KeyID }}", - "principals": {{ toJson .Insecure.CR.Principals }} -{{- if eq .Insecure.CR.Type "user" }} - , "extensions": {{ toJson .Extensions }}, - "criticalOptions": {{ toJson .CriticalOptions }} -{{- end }} -}` - -// DefaultIIDCertificate is the default template for IID provisioners. By -// default certificate type will be set always to host, key id to the instance -// id. Principals will be only enforced by the provisioner if disableCustomSANs -// is set to true. -const DefaultIIDCertificate = `{ - "type": "{{ .Type }}", - "keyId": "{{ .KeyID }}", -{{- if .Insecure.CR.Principals }} - "principals": {{ toJson .Insecure.CR.Principals }}, -{{- else }} - "principals": {{ toJson .Principals }}, -{{- end }} - "extensions": {{ toJson .Extensions }} -}` - -// CertificateRequestTemplate is the template used for provisioners that accepts -// any certificate request. The provisioner must validate that type, keyId and -// principals are passed in the request. -const CertificateRequestTemplate = `{ - "type": "{{ .Insecure.CR.Type }}", - "keyId": "{{ .Insecure.CR.KeyID }}", - "principals": {{ toJson .Insecure.CR.Principals }} -{{- if eq .Insecure.CR.Type "user" }} - , "extensions": { - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "" - } -{{- end }} -}` diff --git a/sshutil/templates_test.go b/sshutil/templates_test.go deleted file mode 100644 index ac4152b5..00000000 --- a/sshutil/templates_test.go +++ /dev/null @@ -1,481 +0,0 @@ -package sshutil - -import ( - "reflect" - "testing" -) - -func TestTemplateError_Error(t *testing.T) { - type fields struct { - Message string - } - tests := []struct { - name string - fields fields - want string - }{ - {"ok", fields{"message"}, "message"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &TemplateError{ - Message: tt.fields.Message, - } - if got := e.Error(); got != tt.want { - t.Errorf("TemplateError.Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCreateTemplateData(t *testing.T) { - type args struct { - ct CertType - keyID string - principals []string - } - tests := []struct { - name string - args args - want TemplateData - }{ - {"user", args{UserCert, "john@doe.com", []string{"john", "john@doe.com"}}, TemplateData{ - TypeKey: "user", - KeyIDKey: "john@doe.com", - PrincipalsKey: []string{"john", "john@doe.com"}, - ExtensionsKey: map[string]interface{}{ - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "", - }, - }}, - {"host", args{HostCert, "foo", []string{"foo.internal"}}, TemplateData{ - TypeKey: "host", - KeyIDKey: "foo", - PrincipalsKey: []string{"foo.internal"}, - ExtensionsKey: map[string]interface{}(nil), - }}, - {"other", args{100, "foo", []string{"foo.internal"}}, TemplateData{ - TypeKey: "", - KeyIDKey: "foo", - PrincipalsKey: []string{"foo.internal"}, - ExtensionsKey: map[string]interface{}(nil), - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := CreateTemplateData(tt.args.ct, tt.args.keyID, tt.args.principals); !reflect.DeepEqual(got, tt.want) { - t.Errorf("CreateTemplateData() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestDefaultExtensions(t *testing.T) { - type args struct { - ct CertType - } - tests := []struct { - name string - args args - want map[string]interface{} - }{ - {"user", args{UserCert}, map[string]interface{}{ - "permit-X11-forwarding": "", - "permit-agent-forwarding": "", - "permit-port-forwarding": "", - "permit-pty": "", - "permit-user-rc": "", - }}, - {"host", args{HostCert}, nil}, - {"other", args{100}, nil}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := DefaultExtensions(tt.args.ct); !reflect.DeepEqual(got, tt.want) { - t.Errorf("DefaultExtensions() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestNewTemplateData(t *testing.T) { - tests := []struct { - name string - want TemplateData - }{ - {"ok", TemplateData{}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NewTemplateData(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewTemplateData() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestTemplateData_AddExtension(t *testing.T) { - type args struct { - key string - value string - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"empty", TemplateData{}, args{"key", "value"}, TemplateData{ - ExtensionsKey: map[string]interface{}{"key": "value"}, - }}, - {"overwrite", TemplateData{ - ExtensionsKey: map[string]interface{}{"key": "value"}, - }, args{"key", "value"}, TemplateData{ - ExtensionsKey: map[string]interface{}{ - "key": "value", - }, - }}, - {"add", TemplateData{ - ExtensionsKey: map[string]interface{}{"foo": "bar"}, - }, args{"key", "value"}, TemplateData{ - ExtensionsKey: map[string]interface{}{ - "key": "value", - "foo": "bar", - }, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.AddExtension(tt.args.key, tt.args.value) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("AddExtension() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_AddCriticalOption(t *testing.T) { - type args struct { - key string - value string - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"empty", TemplateData{}, args{"key", "value"}, TemplateData{ - CriticalOptionsKey: map[string]interface{}{"key": "value"}, - }}, - {"overwrite", TemplateData{ - CriticalOptionsKey: map[string]interface{}{"key": "value"}, - }, args{"key", "value"}, TemplateData{ - CriticalOptionsKey: map[string]interface{}{ - "key": "value", - }, - }}, - {"add", TemplateData{ - CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, - }, args{"key", "value"}, TemplateData{ - CriticalOptionsKey: map[string]interface{}{ - "key": "value", - "foo": "bar", - }, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.AddCriticalOption(tt.args.key, tt.args.value) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("AddCriticalOption() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_Set(t *testing.T) { - type args struct { - key string - v interface{} - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{"foo", "bar"}, TemplateData{ - "foo": "bar", - }}, - {"overwrite", TemplateData{}, args{"foo", "bar"}, TemplateData{ - "foo": "bar", - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.Set(tt.args.key, tt.args.v) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("Set() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetInsecure(t *testing.T) { - type args struct { - key string - v interface{} - } - tests := []struct { - name string - td TemplateData - args args - want TemplateData - }{ - {"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{InsecureKey: TemplateData{"foo": "bar"}}}, - {"overwrite", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{InsecureKey: TemplateData{"foo": "zar"}}}, - {"add", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", "bar": "foo"}}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.td.SetInsecure(tt.args.key, tt.args.v) - if !reflect.DeepEqual(tt.td, tt.want) { - t.Errorf("TemplateData.SetInsecure() = %v, want %v", tt.td, tt.want) - } - }) - } -} - -func TestTemplateData_SetType(t *testing.T) { - type args struct { - typ CertType - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"user", TemplateData{}, args{UserCert}, TemplateData{ - TypeKey: "user", - }}, - {"host", TemplateData{}, args{HostCert}, TemplateData{ - TypeKey: "host", - }}, - {"overwrite", TemplateData{ - TypeKey: "host", - }, args{UserCert}, TemplateData{ - TypeKey: "user", - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetType(tt.args.typ) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("SetType() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetKeyID(t *testing.T) { - type args struct { - id string - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{"key-id"}, TemplateData{ - KeyIDKey: "key-id", - }}, - {"overwrite", TemplateData{}, args{"key-id-2"}, TemplateData{ - KeyIDKey: "key-id-2", - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetKeyID(tt.args.id) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("SetKeyID() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetPrincipals(t *testing.T) { - type args struct { - p []string - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{[]string{"jane"}}, TemplateData{ - PrincipalsKey: []string{"jane"}, - }}, - {"overwrite", TemplateData{}, args{[]string{"john", "john@doe.com"}}, TemplateData{ - PrincipalsKey: []string{"john", "john@doe.com"}, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetPrincipals(tt.args.p) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("SetPrincipals() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetExtensions(t *testing.T) { - type args struct { - e map[string]interface{} - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{ - ExtensionsKey: map[string]interface{}{"foo": "bar"}, - }}, - {"overwrite", TemplateData{ - ExtensionsKey: map[string]interface{}{"foo": "bar"}, - }, args{map[string]interface{}{"key": "value"}}, TemplateData{ - ExtensionsKey: map[string]interface{}{"key": "value"}, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetExtensions(tt.args.e) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("SetExtensions() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetCriticalOptions(t *testing.T) { - type args struct { - e map[string]interface{} - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{ - CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, - }}, - {"overwrite", TemplateData{ - CriticalOptionsKey: map[string]interface{}{"foo": "bar"}, - }, args{map[string]interface{}{"key": "value"}}, TemplateData{ - CriticalOptionsKey: map[string]interface{}{"key": "value"}, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetCriticalOptions(tt.args.e) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("SetCriticalOptions() = %v, want %v", tt.t, tt.want) - } - }) - } -} - -func TestTemplateData_SetToken(t *testing.T) { - type args struct { - v interface{} - } - tests := []struct { - name string - td TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{"token"}, TemplateData{TokenKey: "token"}}, - {"overwrite", TemplateData{TokenKey: "foo"}, args{"token"}, TemplateData{TokenKey: "token"}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.td.SetToken(tt.args.v) - if !reflect.DeepEqual(tt.td, tt.want) { - t.Errorf("TemplateData.SetToken() = %v, want %v", tt.td, tt.want) - } - }) - } -} - -func TestTemplateData_SetUserData(t *testing.T) { - type args struct { - v interface{} - } - tests := []struct { - name string - td TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, - {"overwrite", TemplateData{InsecureKey: TemplateData{UserKey: "foo"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}}, - {"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", UserKey: "userData"}}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.td.SetUserData(tt.args.v) - if !reflect.DeepEqual(tt.td, tt.want) { - t.Errorf("TemplateData.SetUserData() = %v, want %v", tt.td, tt.want) - } - }) - } -} - -func TestTemplateData_SetCertificateRequest(t *testing.T) { - cr1 := CertificateRequest{Key: mustGeneratePublicKey(t)} - cr2 := CertificateRequest{Key: mustGeneratePublicKey(t)} - type args struct { - cr CertificateRequest - } - tests := []struct { - name string - t TemplateData - args args - want TemplateData - }{ - {"ok", TemplateData{}, args{cr1}, TemplateData{ - InsecureKey: TemplateData{ - CertificateRequestKey: cr1, - }, - }}, - {"overwrite", TemplateData{ - InsecureKey: TemplateData{ - UserKey: "data", - CertificateRequestKey: cr1, - }, - }, args{cr2}, TemplateData{ - InsecureKey: TemplateData{ - UserKey: "data", - CertificateRequestKey: cr2, - }, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.t.SetCertificateRequest(tt.args.cr) - if !reflect.DeepEqual(tt.t, tt.want) { - t.Errorf("TemplateData.SetCertificateRequest() = %v, want %v", tt.t, tt.want) - } - }) - } -} diff --git a/sshutil/testdata/github.tpl b/sshutil/testdata/github.tpl deleted file mode 100644 index 2340522f..00000000 --- a/sshutil/testdata/github.tpl +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "{{ .Type }}", - "keyId": "{{ .KeyID }}", - "principals": {{ toJson .Principals }}, -{{- if .Insecure.User.username }} - "extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }} -{{- else }} - "extensions": {{ toJson .Extensions }} -{{- end }} -} \ No newline at end of file diff --git a/sshutil/types.go b/sshutil/types.go deleted file mode 100644 index d1c45564..00000000 --- a/sshutil/types.go +++ /dev/null @@ -1,89 +0,0 @@ -package sshutil - -import ( - "encoding/json" - "strings" - - "github.com/pkg/errors" - "golang.org/x/crypto/ssh" -) - -// 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"` -} - -// CertType defines the certificate type, it can be a user or a host -// certificate. -type CertType uint32 - -const ( - // UserCert defines a user certificate. - UserCert CertType = ssh.UserCert - - // HostCert defines a host certificate. - HostCert CertType = ssh.HostCert -) - -const ( - userString = "user" - hostString = "host" -) - -// CertTypeFromString returns the CertType for the string "user" and "host". -func CertTypeFromString(s string) (CertType, error) { - switch strings.ToLower(s) { - case userString: - return UserCert, nil - case hostString: - return HostCert, nil - default: - return 0, errors.Errorf("unknown certificate type '%s'", s) - } -} - -// String returns "user" for user certificates and "host" for host certificates. -// It will return the empty string for any other value. -func (c CertType) String() string { - switch c { - case UserCert: - return userString - case HostCert: - return hostString - default: - return "" - } -} - -// MarshalJSON implements the json.Marshaler interface for CertType. UserCert -// will be marshaled as the string "user" and HostCert as "host". -func (c CertType) MarshalJSON() ([]byte, error) { - if s := c.String(); s != "" { - return []byte(`"` + s + `"`), nil - } - return nil, errors.Errorf("unknown certificate type %d", c) -} - -// UnmarshalJSON implements the json.Unmarshaler interface for CertType. -func (c *CertType) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return errors.Wrap(err, "error unmarshaling certificate type") - } - certType, err := CertTypeFromString(s) - if err != nil { - return errors.Errorf("error unmarshaling '%s' as a certificate type", s) - } - *c = certType - return nil -} diff --git a/sshutil/types_test.go b/sshutil/types_test.go deleted file mode 100644 index 556b461c..00000000 --- a/sshutil/types_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package sshutil - -import ( - "reflect" - "testing" -) - -func TestCertTypeFromString(t *testing.T) { - type args struct { - s string - } - tests := []struct { - name string - args args - want CertType - wantErr bool - }{ - {"user", args{"user"}, UserCert, false}, - {"USER", args{"USER"}, UserCert, false}, - {"host", args{"host"}, HostCert, false}, - {"Host", args{"Host"}, HostCert, false}, - {" user ", args{" user "}, 0, true}, - {"invalid", args{"invalid"}, 0, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := CertTypeFromString(tt.args.s) - if (err != nil) != tt.wantErr { - t.Errorf("CertTypeFromString() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("CertTypeFromString() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCertType_String(t *testing.T) { - tests := []struct { - name string - c CertType - want string - }{ - {"user", UserCert, "user"}, - {"host", HostCert, "host"}, - {"empty", 100, ""}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.c.String(); got != tt.want { - t.Errorf("CertType.String() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCertType_MarshalJSON(t *testing.T) { - tests := []struct { - name string - c CertType - want []byte - wantErr bool - }{ - {"user", UserCert, []byte(`"user"`), false}, - {"host", HostCert, []byte(`"host"`), false}, - {"error", 100, nil, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tt.c.MarshalJSON() - if (err != nil) != tt.wantErr { - t.Errorf("CertType.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("CertType.MarshalJSON() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestCertType_UnmarshalJSON(t *testing.T) { - type args struct { - data []byte - } - tests := []struct { - name string - args args - want CertType - wantErr bool - }{ - {"user", args{[]byte(`"user"`)}, UserCert, false}, - {"USER", args{[]byte(`"USER"`)}, UserCert, false}, - {"host", args{[]byte(`"host"`)}, HostCert, false}, - {"HosT", args{[]byte(`"HosT"`)}, HostCert, false}, - {" user ", args{[]byte(`" user "`)}, 0, true}, - {"number", args{[]byte(`1`)}, 0, true}, - {"object", args{[]byte(`{}`)}, 0, true}, - {"badJSON", args{[]byte(`"user`)}, 0, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var ct CertType - if err := ct.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { - t.Errorf("CertType.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - } - if !reflect.DeepEqual(ct, tt.want) { - t.Errorf("CertType.UnmarshalJSON() = %v, want %v", ct, tt.want) - } - }) - } -} From 4943ae58d8790345daf0883da7ceea9fb3108c30 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 15:29:18 -0700 Subject: [PATCH 34/47] Move TLSOption, TLSVersion, CipherSuites and ASN1DN to certificates. --- api/api.go | 3 +- api/api_test.go | 13 ++- api/sign.go | 12 +-- authority/config.go | 24 +++-- authority/config_test.go | 26 +++--- authority/tls.go | 5 +- authority/tls_options.go | 157 +++++++++++++++++++++++++++++++ authority/tls_options_test.go | 169 ++++++++++++++++++++++++++++++++++ authority/tls_test.go | 13 ++- ca/ca_test.go | 4 +- go.mod | 2 +- pki/pki.go | 11 +-- 12 files changed, 384 insertions(+), 55 deletions(-) create mode 100644 authority/tls_options.go create mode 100644 authority/tls_options_test.go diff --git a/api/api.go b/api/api.go index be735d69..0cabb416 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 7df021cc..31d45f5d 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -32,7 +32,6 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/templates" - "github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/jose" "golang.org/x/crypto/ssh" ) @@ -547,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) @@ -584,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) { @@ -881,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) @@ -932,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) @@ -993,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 90a9aa99..a834b520 100644 --- a/api/sign.go +++ b/api/sign.go @@ -5,9 +5,9 @@ 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. @@ -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/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..22bfd6c8 100644 --- a/authority/config_test.go +++ b/authority/config_test.go @@ -7,8 +7,6 @@ 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" ) @@ -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", @@ -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/tls.go b/authority/tls.go index baaac270..bff60649 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -17,21 +17,20 @@ import ( "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/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") 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 41347f17..47bc964d 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -22,7 +22,6 @@ import ( "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" "gopkg.in/square/go-jose.v2/jwt" @@ -122,7 +121,7 @@ func TestAuthority_Sign(t *testing.T) { 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", @@ -478,7 +477,7 @@ func TestAuthority_Renew(t *testing.T) { 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", @@ -705,7 +704,7 @@ func TestAuthority_Rekey(t *testing.T) { 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", @@ -946,7 +945,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) { @@ -955,8 +954,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", }, diff --git a/ca/ca_test.go b/ca/ca_test.go index 08e40728..43733a82 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -79,7 +79,7 @@ func TestCASign(t *testing.T) { pub, priv, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) - asn1dn := &x509util.ASN1DN{ + asn1dn := &authority.ASN1DN{ Country: "Tazmania", Organization: "Acme Co", Locality: "Landscapes", @@ -558,7 +558,7 @@ func TestCARenew(t *testing.T) { pub, _, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) - asn1dn := &x509util.ASN1DN{ + asn1dn := &authority.ASN1DN{ Country: "Tazmania", Organization: "Acme Co", Locality: "Landscapes", diff --git a/go.mod b/go.mod index c163eaa3..757cc8fc 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 diff --git a/pki/pki.go b/pki/pki.go index d52f247e..db0ef1ac 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -23,7 +23,6 @@ import ( "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" @@ -432,11 +431,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(), } From 0a59efd853c259d232f657ac1a3fcb3f388c86c6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 16:09:22 -0700 Subject: [PATCH 35/47] Use new x509util to generate the CA certificate. --- authority/tls.go | 63 +++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/authority/tls.go b/authority/tls.go index bff60649..4dd7fecf 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -9,15 +9,14 @@ import ( "encoding/base64" "encoding/pem" "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/keys" "github.com/smallstep/cli/crypto/pemutil" - x509legacy "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" "go.step.sm/crypto/x509util" ) @@ -359,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 := keys.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 directly from the certificate request. + certificate, err := x509util.NewCertificate(cr) + if err != nil { + return fatal(err) + } + + // Get certificate template, set validity and sign it. + now := time.Now() + template := certificate.GetCertificate() + template.NotBefore = now.Add(-1 * time.Minute) + template.NotAfter = now.Add(24 * time.Hour) + + cert, err := x509util.CreateCertificate(template, 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 } From 3577d696c757de9b3916209406af6dfdc2825069 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 18:14:32 -0700 Subject: [PATCH 36/47] Use new x509util in tls_test.go --- authority/tls_test.go | 211 ++++++++++++++++++++++-------------------- 1 file changed, 113 insertions(+), 98 deletions(-) diff --git a/authority/tls_test.go b/authority/tls_test.go index 47bc964d..dbbabd03 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" @@ -22,8 +24,8 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/crypto/pemutil" - "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" "gopkg.in/square/go-jose.v2/jwt" ) @@ -51,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), @@ -68,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"}, @@ -473,9 +553,6 @@ ZYtQ9Ot36qc= } func TestAuthority_Renew(t *testing.T) { - pub, _, err := keys.GenerateDefaultKeyPair() - assert.FatalError(t, err) - a := testAuthority(t) a.config.AuthorityConfig.Template = &ASN1DN{ Country: "Tazmania", @@ -486,13 +563,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 @@ -501,28 +571,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 @@ -555,24 +614,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, @@ -634,7 +681,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) @@ -700,8 +747,6 @@ func TestAuthority_Renew(t *testing.T) { func TestAuthority_Rekey(t *testing.T) { pub, _, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) - pub1, _, err := keys.GenerateDefaultKeyPair() - assert.FatalError(t, err) a := testAuthority(t) a.config.AuthorityConfig.Template = &ASN1DN{ @@ -713,13 +758,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 @@ -728,28 +766,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 @@ -786,28 +813,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, From 8c2d5425e7c5eb4d49a21b34dbd8265e5d7535c6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 10 Aug 2020 19:05:27 -0700 Subject: [PATCH 37/47] Use new x509util on pki package. --- pki/pki.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/pki/pki.go b/pki/pki.go index db0ef1ac..12595917 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" @@ -23,11 +24,11 @@ import ( "github.com/smallstep/cli/config" "github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/crypto/pemutil" - "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/x509util" "golang.org/x/crypto/ssh" ) @@ -113,6 +114,18 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) { } } +func generateDefaultKey() (crypto.Signer, error) { + priv, err := keys.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) { @@ -254,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. @@ -284,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 } @@ -298,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 From 533ad0ca20f5f42eab33135f7ff1b708de1504e1 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 11 Aug 2020 17:59:33 -0700 Subject: [PATCH 38/47] Use always go.step.sm/crypto/x509util --- ca/ca_test.go | 37 +++++++++++++++++-------------------- ca/client.go | 2 +- ca/client_test.go | 2 +- ca/provisioner_test.go | 2 +- go.mod | 7 ++++--- go.sum | 7 ++----- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/ca/ca_test.go b/ca/ca_test.go index 43733a82..197e4cfe 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -27,9 +27,9 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/crypto/pemutil" - "github.com/smallstep/cli/crypto/x509util" stepJOSE "github.com/smallstep/cli/jose" "go.step.sm/crypto/randutil" + "go.step.sm/crypto/x509util" jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2/jwt" ) @@ -93,13 +93,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 := stepJOSE.ParseKey("testdata/secrets/step_cli_key_priv.jwk", stepJOSE.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)) @@ -321,9 +317,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,7 +551,7 @@ func TestCAHealth(t *testing.T) { } func TestCARenew(t *testing.T) { - pub, _, err := keys.GenerateDefaultKeyPair() + pub, priv, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) asn1dn := &authority.ASN1DN{ @@ -574,8 +570,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 +602,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 +658,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..7edc1dc6 100644 --- a/ca/client.go +++ b/ca/client.go @@ -30,7 +30,7 @@ import ( "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/x509util" "golang.org/x/net/http2" "gopkg.in/square/go-jose.v2/jwt" ) 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/provisioner_test.go b/ca/provisioner_test.go index 1d20eff6..b3fe1346 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/smallstep/cli/crypto/pemutil" - "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) func getTestProvisioner(t *testing.T, caURL string) *Provisioner { diff --git a/go.mod b/go.mod index 757cc8fc..6d602db1 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 - go.step.sm/crypto v0.1.0 + go.step.sm/crypto v0.1.1 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 google.golang.org/api v0.15.0 @@ -26,5 +26,6 @@ require ( gopkg.in/square/go-jose.v2 v2.4.0 ) -//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 520d0577..917e90c0 100644 --- a/go.sum +++ b/go.sum @@ -479,7 +479,6 @@ github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9P github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8= github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8= -github.com/smallstep/cli v0.14.6 h1:xc9rawDKB70Vgvg10gfQAh9EpDWS3k1O002J5bApqUk= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 h1:TcCYqEqh6EIEiFabRdtG0IGyFK01kRLTjx6TIKqjxX8= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95/go.mod h1:7aWHk7WwJMpEP4PYyav86FMpaI9vuA0uJRliUAqCwxg= github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= @@ -578,10 +577,8 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 h1:FymMl8TrXGxFf80BWpO0CnkSfLnw0BkDdRrhbMGf5zE= -go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U= -go.step.sm/crypto v0.1.0 h1:SLo25kNU3C6u8Ne5BnavI9bhtA+PBrMnnNZKYIWhKFU= -go.step.sm/crypto v0.1.0/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= +go.step.sm/crypto v0.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E= +go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= 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 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= From d30a95236d7998bd20753abc9dab434dfd416ae4 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 14 Aug 2020 15:33:50 -0700 Subject: [PATCH 39/47] Use always go.step.sm/crypto --- acme/api/handler_test.go | 2 +- acme/api/middleware.go | 6 +++--- acme/api/order_test.go | 2 +- acme/certificate_test.go | 2 +- authority/authority.go | 2 +- authority/authority_test.go | 2 +- authority/authorize_test.go | 2 +- authority/provisioner/k8sSA.go | 2 +- authority/provisioner/options_test.go | 2 +- authority/provisioner/sign_options_test.go | 2 +- authority/provisioner/sign_ssh_options.go | 6 +++--- authority/provisioner/sign_ssh_options_test.go | 4 ++-- authority/provisioner/sshpop_test.go | 2 +- authority/provisioner/utils_test.go | 2 +- authority/provisioner/x5c_test.go | 2 +- authority/root_test.go | 2 +- authority/tls.go | 6 +++--- authority/tls_test.go | 8 ++++---- ca/acmeClient_test.go | 2 +- ca/ca_test.go | 8 ++++---- ca/client.go | 6 +++--- ca/identity/identity.go | 2 +- ca/identity/identity_test.go | 2 +- ca/provisioner_test.go | 2 +- cmd/step-awskms-init/main.go | 2 +- cmd/step-cloudkms-init/main.go | 2 +- cmd/step-yubikey-init/main.go | 2 +- go.mod | 4 ++-- go.sum | 3 +++ kms/awskms/awskms.go | 2 +- kms/awskms/awskms_test.go | 2 +- kms/awskms/signer.go | 2 +- kms/awskms/signer_test.go | 2 +- kms/cloudkms/cloudkms.go | 2 +- kms/cloudkms/cloudkms_test.go | 2 +- kms/cloudkms/signer.go | 2 +- kms/cloudkms/signer_test.go | 2 +- kms/softkms/softkms.go | 13 ++++++++++--- kms/softkms/softkms_test.go | 2 +- pki/pki.go | 8 ++++---- 40 files changed, 70 insertions(+), 60 deletions(-) diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index f8bac96c..34493357 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/pemutil" ) type mockAcmeAuthority struct { diff --git a/acme/api/middleware.go b/acme/api/middleware.go index f7d7dcf4..a847db64 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/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/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/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/authority/authority.go b/authority/authority.go index 36bfe334..a0a80b62 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -16,7 +16,7 @@ import ( "github.com/smallstep/certificates/kms" kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/templates" - "github.com/smallstep/cli/crypto/pemutil" + "go.step.sm/crypto/pemutil" "golang.org/x/crypto/ssh" ) diff --git a/authority/authority_test.go b/authority/authority_test.go index f87f1df3..0f294a23 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -15,8 +15,8 @@ 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/pemutil" ) func testAuthority(t *testing.T, opts ...Option) *Authority { diff --git a/authority/authorize_test.go b/authority/authorize_test.go index 167f11d0..f0c359c0 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -17,8 +17,8 @@ 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/jose" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" "golang.org/x/crypto/ssh" "gopkg.in/square/go-jose.v2/jwt" diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index ee48d283..10309ced 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -11,8 +11,8 @@ 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/pemutil" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" ) diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index 38d50e3e..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" ) diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index 28b0dc82..5d3a5d3a 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) { diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index d948ddac..a872513e 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -8,7 +8,7 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/cli/crypto/keys" + "go.step.sm/crypto/keyutil" "golang.org/x/crypto/ssh" ) @@ -423,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: diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 9ab72a51..693690f6 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" - "github.com/smallstep/cli/crypto/keys" + "go.step.sm/crypto/keyutil" "golang.org/x/crypto/ssh" ) @@ -489,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) diff --git a/authority/provisioner/sshpop_test.go b/authority/provisioner/sshpop_test.go index 5863b6f9..b35601d4 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/pemutil" "golang.org/x/crypto/ssh" ) diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 19c6436d..62efe8e2 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -16,8 +16,8 @@ import ( "time" "github.com/pkg/errors" - "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" "golang.org/x/crypto/ssh" ) diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 58130413..c1f9bf66 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -9,8 +9,8 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/certificates/errs" - "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" ) 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/tls.go b/authority/tls.go index 4dd7fecf..08741972 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -15,9 +15,9 @@ 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/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" ) @@ -363,7 +363,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { } // Generate default key. - priv, err := keys.GenerateDefaultKey() + priv, err := keyutil.GenerateDefaultKey() if err != nil { return fatal(err) } diff --git a/authority/tls_test.go b/authority/tls_test.go index dbbabd03..e749e51e 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -22,9 +22,9 @@ 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/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "gopkg.in/square/go-jose.v2/jwt" ) @@ -196,7 +196,7 @@ 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) @@ -745,7 +745,7 @@ func TestAuthority_Renew(t *testing.T) { } func TestAuthority_Rekey(t *testing.T) { - pub, _, err := keys.GenerateDefaultKeyPair() + pub, _, err := keyutil.GenerateDefaultKeyPair() assert.FatalError(t, err) a := testAuthority(t) diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index 5163101a..68990203 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/pemutil" ) func TestNewACMEClient(t *testing.T) { diff --git a/ca/ca_test.go b/ca/ca_test.go index 197e4cfe..aae5b729 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -25,9 +25,9 @@ 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" stepJOSE "github.com/smallstep/cli/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" "go.step.sm/crypto/x509util" jose "gopkg.in/square/go-jose.v2" @@ -76,7 +76,7 @@ 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 := &authority.ASN1DN{ @@ -551,7 +551,7 @@ func TestCAHealth(t *testing.T) { } func TestCARenew(t *testing.T) { - pub, priv, err := keys.GenerateDefaultKeyPair() + pub, priv, err := keyutil.GenerateDefaultKeyPair() assert.FatalError(t, err) asn1dn := &authority.ASN1DN{ diff --git a/ca/client.go b/ca/client.go index 7edc1dc6..1282d6f4 100644 --- a/ca/client.go +++ b/ca/client.go @@ -28,8 +28,8 @@ 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" + "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/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_test.go b/ca/provisioner_test.go index b3fe1346..c9910a04 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" ) 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/go.mod b/go.mod index 6d602db1..769900e4 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 go.step.sm/crypto v0.1.1 - golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 + 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 @@ -28,4 +28,4 @@ require ( // replace github.com/smallstep/cli => ../cli // replace github.com/smallstep/nosql => ../nosql -// replace go.step.sm/crypto => ../crypto +replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index 917e90c0..aabf4f1c 100644 --- a/go.sum +++ b/go.sum @@ -479,6 +479,7 @@ github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9P github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8= github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8= +github.com/smallstep/cli v0.14.6 h1:xc9rawDKB70Vgvg10gfQAh9EpDWS3k1O002J5bApqUk= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 h1:TcCYqEqh6EIEiFabRdtG0IGyFK01kRLTjx6TIKqjxX8= github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95/go.mod h1:7aWHk7WwJMpEP4PYyav86FMpaI9vuA0uJRliUAqCwxg= github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= @@ -609,6 +610,8 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+v golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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/pki/pki.go b/pki/pki.go index 12595917..6ee5a110 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -22,12 +22,12 @@ 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/errs" "github.com/smallstep/cli/jose" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" ) @@ -115,7 +115,7 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) { } func generateDefaultKey() (crypto.Signer, error) { - priv, err := keys.GenerateDefaultKey() + priv, err := keyutil.GenerateDefaultKey() if err != nil { return nil, err } @@ -369,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 } From b900a7a2fca6a9fad4cf58ae72a6cf4adf89a850 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 14 Aug 2020 15:38:54 -0700 Subject: [PATCH 40/47] Fix error message in tests. --- authority/authority_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authority/authority_test.go b/authority/authority_test.go index 0f294a23..fe451245 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -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"), } }, } From 32ba80f44631c0a575da6526553085d7ab9a23d8 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 14 Aug 2020 15:44:18 -0700 Subject: [PATCH 41/47] Use pemutil branch. --- go.mod | 4 ++-- go.sum | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 769900e4..b964b368 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 - go.step.sm/crypto v0.1.1 + go.step.sm/crypto v0.1.2-0.20200814221812-7a041a9f5319 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 @@ -28,4 +28,4 @@ require ( // replace github.com/smallstep/cli => ../cli // replace github.com/smallstep/nosql => ../nosql -replace go.step.sm/crypto => ../crypto +// replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index aabf4f1c..737899ce 100644 --- a/go.sum +++ b/go.sum @@ -580,6 +580,8 @@ go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.step.sm/crypto v0.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E= go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8= +go.step.sm/crypto v0.1.2-0.20200814221812-7a041a9f5319 h1:3y6E6bAbYMRT02hHW/jWJfevUUEx2yYJ2Zelex2sR3E= +go.step.sm/crypto v0.1.2-0.20200814221812-7a041a9f5319/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww= 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 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= From ba918100d037a691367e14df853a450ade96df13 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 24 Aug 2020 14:44:11 -0700 Subject: [PATCH 42/47] Use go.step.sm/crypto/jose Replace use of github.com/smallstep/cli/crypto with the new package go.step.sm/crypto/jose. --- acme/account.go | 2 +- acme/account_test.go | 2 +- acme/api/account_test.go | 2 +- acme/api/handler_test.go | 2 +- acme/api/middleware.go | 2 +- acme/api/middleware_test.go | 2 +- acme/authority.go | 2 +- acme/authority_test.go | 2 +- acme/challenge.go | 2 +- acme/challenge_test.go | 2 +- acme/common.go | 2 +- api/api_test.go | 2 +- authority/authority_test.go | 6 +- authority/authorize.go | 2 +- authority/authorize_test.go | 165 +++++++++++------------ authority/config_test.go | 10 +- authority/provisioner/aws.go | 2 +- authority/provisioner/aws_test.go | 2 +- authority/provisioner/azure.go | 2 +- authority/provisioner/azure_test.go | 2 +- authority/provisioner/collection.go | 2 +- authority/provisioner/collection_test.go | 2 +- authority/provisioner/gcp.go | 2 +- authority/provisioner/gcp_test.go | 2 +- authority/provisioner/jwk.go | 2 +- authority/provisioner/jwk_test.go | 2 +- authority/provisioner/k8sSA.go | 2 +- authority/provisioner/k8sSA_test.go | 2 +- authority/provisioner/keystore.go | 2 +- authority/provisioner/keystore_test.go | 2 +- authority/provisioner/oidc.go | 2 +- authority/provisioner/oidc_test.go | 2 +- authority/provisioner/options.go | 2 +- authority/provisioner/sshpop.go | 2 +- authority/provisioner/sshpop_test.go | 2 +- authority/provisioner/utils_test.go | 2 +- authority/provisioner/x5c.go | 2 +- authority/provisioner/x5c_test.go | 10 +- authority/ssh.go | 2 +- authority/ssh_test.go | 2 +- authority/tls.go | 4 +- authority/tls_test.go | 8 +- ca/acmeClient.go | 2 +- ca/acmeClient_test.go | 2 +- ca/bootstrap.go | 5 +- ca/bootstrap_test.go | 16 +-- ca/ca_test.go | 36 +++-- ca/provisioner.go | 2 +- ca/provisioner_test.go | 4 +- ca/tls_test.go | 16 +-- go.mod | 6 +- go.sum | 4 + pki/pki.go | 2 +- 53 files changed, 183 insertions(+), 187 deletions(-) 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 34493357..ee602da6 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -19,7 +19,7 @@ import ( "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "go.step.sm/crypto/pemutil" ) diff --git a/acme/api/middleware.go b/acme/api/middleware.go index a847db64..3bf5f89a 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -14,8 +14,8 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/logging" - "github.com/smallstep/cli/jose" "github.com/smallstep/nosql" + "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" ) 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/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/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 45b2e476..fec47b94 100644 --- a/acme/common.go +++ b/acme/common.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "go.step.sm/crypto/randutil" ) diff --git a/api/api_test.go b/api/api_test.go index 31d45f5d..190e5a2a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -32,7 +32,7 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/templates" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "golang.org/x/crypto/ssh" ) diff --git a/authority/authority_test.go b/authority/authority_test.go index fe451245..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" - 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 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 f0c359c0..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/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, @@ -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_test.go b/authority/config_test.go index 22bfd6c8..87cd3fba 100644 --- a/authority/config_test.go +++ b/authority/config_test.go @@ -7,13 +7,13 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/certificates/authority/provisioner" - 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{ @@ -224,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{ diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index d25b5743..31cf340b 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -17,7 +17,7 @@ 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" ) diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 94365982..3da945c1 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) { diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 9934f56b..ea8b08ec 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -14,7 +14,7 @@ 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" ) 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 42585124..830e7965 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -15,7 +15,7 @@ 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" ) 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 a42cc1ce..d6a97e2b 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -8,7 +8,7 @@ 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" ) diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 61f66953..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) { diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 10309ced..d64c1dfd 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" - "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" diff --git a/authority/provisioner/k8sSA_test.go b/authority/provisioner/k8sSA_test.go index 9c731ae4..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) { 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 5fb4f449..64e16052 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -13,7 +13,7 @@ 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" ) diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index cb830246..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) { diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index 189cdfbf..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" ) 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 b35601d4..5d51b90e 100644 --- a/authority/provisioner/sshpop_test.go +++ b/authority/provisioner/sshpop_test.go @@ -13,7 +13,7 @@ import ( "github.com/smallstep/assert" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - "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 62efe8e2..9a4f4a09 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -16,7 +16,7 @@ import ( "time" "github.com/pkg/errors" - "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 1f6b0891..2b05f4c8 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -9,7 +9,7 @@ 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" ) diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index c1f9bf66..5d288de5 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -9,7 +9,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" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/randutil" ) @@ -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() diff --git a/authority/ssh.go b/authority/ssh.go index c7d144b2..14798dc2 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -14,7 +14,7 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/templates" - "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" diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 3b21a85f..b5cce1fd 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -19,7 +19,7 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" "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" ) diff --git a/authority/tls.go b/authority/tls.go index 08741972..efd02887 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -15,7 +15,7 @@ import ( "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - "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" @@ -281,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", string(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))) diff --git a/authority/tls_test.go b/authority/tls_test.go index e749e51e..234caaf8 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -22,7 +22,7 @@ import ( "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - "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" @@ -219,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) @@ -1000,7 +1000,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}, @@ -1193,7 +1193,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"], string(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 68990203..25d74b9d 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -16,7 +16,7 @@ import ( "github.com/smallstep/certificates/acme" acmeAPI "github.com/smallstep/certificates/acme/api" "github.com/smallstep/certificates/api" - "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "go.step.sm/crypto/pemutil" ) 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 49c20dc0..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" - stepJOSE "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "go.step.sm/crypto/randutil" - jose "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" ) 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 aae5b729..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" - stepJOSE "github.com/smallstep/cli/jose" + "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" - jose "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" ) type ClosingBuffer struct { @@ -95,7 +93,7 @@ func TestCASign(t *testing.T) { assert.FatalError(t, err) 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)) @@ -177,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) @@ -210,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) @@ -244,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) diff --git a/ca/provisioner.go b/ca/provisioner.go index 28975fa4..80dd600a 100644 --- a/ca/provisioner.go +++ b/ca/provisioner.go @@ -7,9 +7,9 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" - "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" ) diff --git a/ca/provisioner_test.go b/ca/provisioner_test.go index c9910a04..ea0ca51e 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -7,13 +7,13 @@ import ( "testing" "time" - "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 8dee0a6f..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" - stepJOSE "github.com/smallstep/cli/jose" + "go.step.sm/crypto/jose" "go.step.sm/crypto/randutil" - jose "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" ) func generateOTT(subject string) string { 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/go.mod b/go.mod index b964b368..086ba27a 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/xid v1.2.1 github.com/sirupsen/logrus v1.4.2 - github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15 + github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/cli v0.14.7-rc.1.0.20200721180458-731b7c4c8c95 github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 @@ -23,9 +23,9 @@ require ( 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 go.step.sm/crypto => ../crypto +replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index 737899ce..b6858bd4 100644 --- a/go.sum +++ b/go.sum @@ -476,6 +476,8 @@ github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3q github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15 h1:kSImCuenAkXtCaBeQ1UhmzzJGRhSm8sVH7I3sHE2Qdg= github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= +github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8= github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8= @@ -804,6 +806,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/pki/pki.go b/pki/pki.go index 6ee5a110..e6df2c69 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -23,9 +23,9 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/cli/config" "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" From ef86bedb2c8c9360ac700ff168b040972fa1dcb8 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 25 Aug 2020 11:46:04 -0700 Subject: [PATCH 43/47] Upgrade go.step.sm dependency to v0.3.0 --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 440a68b0..bdd788c6 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ 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 @@ -30,4 +30,4 @@ require ( // replace github.com/smallstep/cli => ../cli // replace github.com/smallstep/nosql => ../nosql -replace go.step.sm/crypto => ../crypto +// replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index b64020d6..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= From 193d18ee21bd9a476222d55337569fe17ab4988d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 25 Aug 2020 18:14:36 -0700 Subject: [PATCH 44/47] Hide unnecessary error. --- make/docker.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b7269b65796c0a43fb87b4944927d77c5b44b12b Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 28 Aug 2020 14:22:13 -0700 Subject: [PATCH 45/47] Fix comment. --- authority/provisioner/ssh_options.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/authority/provisioner/ssh_options.go b/authority/provisioner/ssh_options.go index 81c4371b..8ec21942 100644 --- a/authority/provisioner/ssh_options.go +++ b/authority/provisioner/ssh_options.go @@ -40,9 +40,9 @@ func (o *SSHOptions) HasTemplate() bool { return o != nil && (o.Template != "" || o.TemplateFile != "") } -// SSHTemplateOptions generates a CertificateOptions 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, +// 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) From 4d375a06f569bf8be17b05a9ebf00fd4cfe4ac79 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 28 Aug 2020 14:29:18 -0700 Subject: [PATCH 46/47] Make clearer what's an unsigned cert. --- authority/ssh.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/authority/ssh.go b/authority/ssh.go index 14798dc2..bb0ff562 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -280,24 +280,24 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi } // Get actual *ssh.Certificate and continue with provisioner modifiers. - cert := certificate.GetCertificate() + certTpl := 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 { + if err := opts.ModifyValidity(certTpl); err != nil { return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH") } // Use provisioner modifiers. for _, m := range mods { - if err := m.Modify(cert, opts); err != nil { + 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("authority.SignSSH: user certificate signing is not enabled") @@ -309,11 +309,11 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi } signer = a.sshCAHostCertSignKey default: - return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", cert.CertType) + return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) } // Sign certificate. - cert, err = sshutil.CreateCertificate(cert, signer) + cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") } @@ -346,7 +346,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss // Build base certificate with the old key. // Nonce and serial will be automatically generated on signing. - cert := &ssh.Certificate{ + certTpl := &ssh.Certificate{ Key: oldCert.Key, CertType: oldCert.CertType, KeyId: oldCert.KeyId, @@ -359,7 +359,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss // 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") @@ -371,12 +371,11 @@ 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) } - var err error // Sign certificate. - cert, err = sshutil.CreateCertificate(cert, signer) + cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } From cef0475e71c2b331dd9c98189a959c4953c0f700 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 28 Aug 2020 14:33:26 -0700 Subject: [PATCH 47/47] Make clear what's a template/unsigned certificate. --- authority/tls.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/authority/tls.go b/authority/tls.go index 30434c97..a5474d60 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -378,19 +378,19 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { return fatal(err) } - // Generate certificate directly from the certificate request. - certificate, err := x509util.NewCertificate(cr) + // Generate certificate template directly from the certificate request. + template, err := x509util.NewCertificate(cr) if err != nil { return fatal(err) } - // Get certificate template, set validity and sign it. + // Get x509 certificate template, set validity and sign it. now := time.Now() - template := certificate.GetCertificate() - template.NotBefore = now.Add(-1 * time.Minute) - template.NotAfter = now.Add(24 * time.Hour) + certTpl := template.GetCertificate() + certTpl.NotBefore = now.Add(-1 * time.Minute) + certTpl.NotAfter = now.Add(24 * time.Hour) - cert, err := x509util.CreateCertificate(template, a.x509Issuer, cr.PublicKey, a.x509Signer) + cert, err := x509util.CreateCertificate(certTpl, a.x509Issuer, cr.PublicKey, a.x509Signer) if err != nil { return fatal(err) }