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.
This commit is contained in:
Mariano Cano 2020-07-29 14:29:12 -07:00
parent ad28f0f59a
commit df1f7e5a2e
7 changed files with 118 additions and 72 deletions

View file

@ -29,8 +29,8 @@ type Certificate struct {
// NewCertificate creates a new certificate with the given key after parsing a // NewCertificate creates a new certificate with the given key after parsing a
// template given in the options. // template given in the options.
func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) { func NewCertificate(cr CertificateRequest, opts ...Option) (*Certificate, error) {
o, err := new(Options).apply(key, opts) o, err := new(Options).apply(cr, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -46,7 +46,7 @@ func NewCertificate(key ssh.PublicKey, opts ...Option) (*Certificate, error) {
} }
// Complete with public key // Complete with public key
cert.Key = key cert.Key = cr.Key
return &cert, nil return &cert, nil
} }

View file

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

View file

@ -36,9 +36,12 @@ func mustGeneratePublicKey(t *testing.T) ssh.PublicKey {
func TestNewCertificate(t *testing.T) { func TestNewCertificate(t *testing.T) {
key := mustGeneratePublicKey(t) key := mustGeneratePublicKey(t)
cr := CertificateRequest{
Key: key,
}
type args struct { type args struct {
key ssh.PublicKey cr CertificateRequest
opts []Option opts []Option
} }
tests := []struct { tests := []struct {
@ -47,7 +50,7 @@ func TestNewCertificate(t *testing.T) {
want *Certificate want *Certificate
wantErr bool 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, Nonce: nil,
Key: key, Key: key,
Serial: 0, Serial: 0,
@ -68,7 +71,7 @@ func TestNewCertificate(t *testing.T) {
SignatureKey: nil, SignatureKey: nil,
Signature: nil, Signature: nil,
}, false}, }, 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, Nonce: nil,
Key: key, Key: key,
Serial: 0, Serial: 0,
@ -83,12 +86,12 @@ func TestNewCertificate(t *testing.T) {
SignatureKey: nil, SignatureKey: nil,
Signature: nil, Signature: nil,
}, false}, }, false},
{"file", args{key, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{ {"file", args{cr, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{
TypeKey: UserCert, TypeKey: UserCert,
KeyIDKey: "john@doe.com", KeyIDKey: "john@doe.com",
PrincipalsKey: []string{"john", "john@doe.com"}, PrincipalsKey: []string{"john", "john@doe.com"},
ExtensionsKey: DefaultExtensions(UserCert), ExtensionsKey: DefaultExtensions(UserCert),
InsecureKey: map[string]interface{}{ InsecureKey: TemplateData{
"User": map[string]interface{}{"username": "john"}, "User": map[string]interface{}{"username": "john"},
}, },
})}}, &Certificate{ })}}, &Certificate{
@ -102,18 +105,18 @@ func TestNewCertificate(t *testing.T) {
ValidBefore: 0, ValidBefore: 0,
CriticalOptions: nil, CriticalOptions: nil,
Extensions: map[string]string{ Extensions: map[string]string{
"login@github.com": "john",
"permit-X11-forwarding": "", "permit-X11-forwarding": "",
"permit-agent-forwarding": "", "permit-agent-forwarding": "",
"permit-port-forwarding": "", "permit-port-forwarding": "",
"permit-pty": "", "permit-pty": "",
"permit-user-rc": "", "permit-user-rc": "",
"login@github.com": "john",
}, },
Reserved: nil, Reserved: nil,
SignatureKey: nil, SignatureKey: nil,
Signature: nil, Signature: nil,
}, false}, }, 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, Nonce: nil,
Key: key, Key: key,
Serial: 0, Serial: 0,
@ -128,22 +131,22 @@ func TestNewCertificate(t *testing.T) {
SignatureKey: nil, SignatureKey: nil,
Signature: nil, Signature: nil,
}, false}, }, false},
{"failNilOptions", args{key, nil}, nil, true}, {"failNilOptions", args{cr, nil}, nil, true},
{"failEmptyOptions", args{key, nil}, nil, true}, {"failEmptyOptions", args{cr, nil}, nil, true},
{"badBase64Template", args{key, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true}, {"badBase64Template", args{cr, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true},
{"badFileTemplate", args{key, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true}, {"badFileTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true},
{"badJsonTemplate", args{key, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true}, {"badJsonTemplate", args{cr, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true},
{"failTemplate", args{key, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true}, {"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := NewCertificate(tt.args.key, tt.args.opts...) got, err := NewCertificate(tt.args.cr, tt.args.opts...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if !reflect.DeepEqual(got, tt.want) { 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)
} }
}) })
} }

View file

@ -9,7 +9,6 @@ import (
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/config" "github.com/smallstep/cli/config"
"golang.org/x/crypto/ssh"
) )
func getFuncMap(failMessage *string) template.FuncMap { func getFuncMap(failMessage *string) template.FuncMap {
@ -26,9 +25,9 @@ type Options struct {
CertBuffer *bytes.Buffer 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 { for _, fn := range opts {
if err := fn(key, o); err != nil { if err := fn(cr, o); err != nil {
return o, err 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. // 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 // WithTemplate is an options that executes the given template text with the
// given data. // given data.
func WithTemplate(text string, data TemplateData) Option { 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) terr := new(TemplateError)
funcMap := getFuncMap(&terr.Message) funcMap := getFuncMap(&terr.Message)
@ -51,7 +50,7 @@ func WithTemplate(text string, data TemplateData) Option {
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
data.SetPublicKey(key) data.SetCertificateRequest(cr)
if err := tmpl.Execute(buf, data); err != nil { if err := tmpl.Execute(buf, data); err != nil {
if terr.Message != "" { if terr.Message != "" {
return terr return terr
@ -66,26 +65,26 @@ func WithTemplate(text string, data TemplateData) Option {
// WithTemplateBase64 is an options that executes the given template base64 // WithTemplateBase64 is an options that executes the given template base64
// string with the given data. // string with the given data.
func WithTemplateBase64(s string, data TemplateData) Option { 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) b, err := base64.StdEncoding.DecodeString(s)
if err != nil { if err != nil {
return errors.Wrap(err, "error decoding template") return errors.Wrap(err, "error decoding template")
} }
fn := WithTemplate(string(b), data) 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 // WithTemplateFile is an options that reads the template file and executes it
// with the given data. // with the given data.
func WithTemplateFile(path string, data TemplateData) Option { 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) filename := config.StepAbs(path)
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return errors.Wrapf(err, "error reading %s", path) return errors.Wrapf(err, "error reading %s", path)
} }
fn := WithTemplate(string(b), data) fn := WithTemplate(string(b), data)
return fn(key, o) return fn(cr, o)
} }
} }

View file

@ -7,7 +7,6 @@ import (
"testing" "testing"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/ssh"
) )
func Test_getFuncMap_fail(t *testing.T) { func Test_getFuncMap_fail(t *testing.T) {
@ -28,11 +27,14 @@ func Test_getFuncMap_fail(t *testing.T) {
func TestWithTemplate(t *testing.T) { func TestWithTemplate(t *testing.T) {
key := mustGeneratePublicKey(t) key := mustGeneratePublicKey(t)
cr := CertificateRequest{
Key: key,
}
type args struct { type args struct {
text string text string
data TemplateData data TemplateData
key ssh.PublicKey cr CertificateRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -45,7 +47,7 @@ func TestWithTemplate(t *testing.T) {
KeyIDKey: "jane@doe.com", KeyIDKey: "jane@doe.com",
PrincipalsKey: []string{"jane", "jane@doe.com"}, PrincipalsKey: []string{"jane", "jane@doe.com"},
ExtensionsKey: DefaultExtensions(UserCert), ExtensionsKey: DefaultExtensions(UserCert),
}, key}, Options{ }, cr}, Options{
CertBuffer: bytes.NewBufferString(`{ CertBuffer: bytes.NewBufferString(`{
"type": "user", "type": "user",
"keyId": "jane@doe.com", "keyId": "jane@doe.com",
@ -56,24 +58,24 @@ func TestWithTemplate(t *testing.T) {
TypeKey: "host", TypeKey: "host",
KeyIDKey: "foo", KeyIDKey: "foo",
PrincipalsKey: []string{"foo.internal"}, PrincipalsKey: []string{"foo.internal"},
}, key}, Options{ }, cr}, Options{
CertBuffer: bytes.NewBufferString(`{ CertBuffer: bytes.NewBufferString(`{
"type": "host", "type": "host",
"keyId": "foo", "keyId": "foo",
"principals": ["foo.internal"], "principals": ["foo.internal"],
"extensions": null "extensions": null
}`)}, false}, }`)}, false},
{"fail", args{`{{ fail "a message" }}`, TemplateData{}, key}, Options{}, true}, {"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true},
{"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, key}, Options{}, true}, {"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, cr}, Options{}, true},
{"error", args{`{{ mustHas 3 .Data }}`, TemplateData{ {"error", args{`{{ mustHas 3 .Data }}`, TemplateData{
"Data": 3, "Data": 3,
}, key}, Options{}, true}, }, cr}, Options{}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var got Options var got Options
fn := WithTemplate(tt.args.text, tt.args.data) 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) t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr)
} }
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
@ -85,11 +87,14 @@ func TestWithTemplate(t *testing.T) {
func TestWithTemplateBase64(t *testing.T) { func TestWithTemplateBase64(t *testing.T) {
key := mustGeneratePublicKey(t) key := mustGeneratePublicKey(t)
cr := CertificateRequest{
Key: key,
}
type args struct { type args struct {
s string s string
data TemplateData data TemplateData
key ssh.PublicKey cr CertificateRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -102,20 +107,20 @@ func TestWithTemplateBase64(t *testing.T) {
KeyIDKey: "foo.internal", KeyIDKey: "foo.internal",
PrincipalsKey: []string{"foo.internal", "bar.internal"}, PrincipalsKey: []string{"foo.internal", "bar.internal"},
ExtensionsKey: map[string]interface{}{"foo": "bar"}, ExtensionsKey: map[string]interface{}{"foo": "bar"},
}, key}, Options{ }, cr}, Options{
CertBuffer: bytes.NewBufferString(`{ CertBuffer: bytes.NewBufferString(`{
"type": "host", "type": "host",
"keyId": "foo.internal", "keyId": "foo.internal",
"principals": ["foo.internal","bar.internal"], "principals": ["foo.internal","bar.internal"],
"extensions": {"foo":"bar"} "extensions": {"foo":"bar"}
}`)}, false}, }`)}, false},
{"badBase64", args{"foobar", TemplateData{}, key}, Options{}, true}, {"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var got Options var got Options
fn := WithTemplateBase64(tt.args.s, tt.args.data) 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) t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr)
} }
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {
@ -127,13 +132,16 @@ func TestWithTemplateBase64(t *testing.T) {
func TestWithTemplateFile(t *testing.T) { func TestWithTemplateFile(t *testing.T) {
key := mustGeneratePublicKey(t) key := mustGeneratePublicKey(t)
cr := CertificateRequest{
Key: key,
}
data := TemplateData{ data := TemplateData{
TypeKey: "user", TypeKey: "user",
KeyIDKey: "jane@doe.com", KeyIDKey: "jane@doe.com",
PrincipalsKey: []string{"jane", "jane@doe.com"}, PrincipalsKey: []string{"jane", "jane@doe.com"},
ExtensionsKey: DefaultExtensions(UserCert), ExtensionsKey: DefaultExtensions(UserCert),
InsecureKey: map[string]interface{}{ InsecureKey: TemplateData{
UserKey: map[string]interface{}{ UserKey: map[string]interface{}{
"username": "jane", "username": "jane",
}, },
@ -143,7 +151,7 @@ func TestWithTemplateFile(t *testing.T) {
type args struct { type args struct {
path string path string
data TemplateData data TemplateData
key ssh.PublicKey cr CertificateRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -151,7 +159,7 @@ func TestWithTemplateFile(t *testing.T) {
want Options want Options
wantErr bool wantErr bool
}{ }{
{"github.com", args{"./testdata/github.tpl", data, key}, Options{ {"github.com", args{"./testdata/github.tpl", data, cr}, Options{
CertBuffer: bytes.NewBufferString(`{ CertBuffer: bytes.NewBufferString(`{
"type": "user", "type": "user",
"keyId": "jane@doe.com", "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":""} "extensions": {"login@github.com":"jane","permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""}
}`), }`),
}, false}, }, false},
{"missing", args{"./testdata/missing.tpl", data, key}, Options{}, true}, {"missing", args{"./testdata/missing.tpl", data, cr}, Options{}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var got Options var got Options
fn := WithTemplateFile(tt.args.path, tt.args.data) 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) t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr)
} }
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {

View file

@ -1,7 +1,5 @@
package sshutil package sshutil
import "golang.org/x/crypto/ssh"
const ( const (
TypeKey = "Type" TypeKey = "Type"
KeyIDKey = "KeyID" KeyIDKey = "KeyID"
@ -10,7 +8,7 @@ const (
TokenKey = "Token" TokenKey = "Token"
InsecureKey = "Insecure" InsecureKey = "Insecure"
UserKey = "User" UserKey = "User"
PublicKey = "PublicKey" CertificateRequestKey = "CR"
) )
// TemplateError represents an error in a template produced by the fail // 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) t.SetInsecure(UserKey, v)
} }
// SetUserData sets the given user provided object in the insecure template // SetCertificateRequest sets the simulated ssh certificate request the insecure
// data. // template data.
func (t TemplateData) SetPublicKey(v ssh.PublicKey) { func (t TemplateData) SetCertificateRequest(cr CertificateRequest) {
t.Set(PublicKey, v) t.SetInsecure(CertificateRequestKey, cr)
} }
// DefaultCertificate is the default template for an SSH certificate. // DefaultCertificate is the default template for an SSH certificate.
@ -130,3 +128,18 @@ const DefaultCertificate = `{
"principals": {{ toJson .Principals }}, "principals": {{ toJson .Principals }},
"extensions": {{ toJson .Extensions }} "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 }}
}`

View file

@ -3,8 +3,6 @@ package sshutil
import ( import (
"reflect" "reflect"
"testing" "testing"
"golang.org/x/crypto/ssh"
) )
func TestTemplateError_Error(t *testing.T) { func TestTemplateError_Error(t *testing.T) {
@ -374,11 +372,11 @@ func TestTemplateData_SetUserData(t *testing.T) {
} }
} }
func TestTemplateData_SetPublicKey(t *testing.T) { func TestTemplateData_SetCertificateRequest(t *testing.T) {
k1 := mustGeneratePublicKey(t) cr1 := CertificateRequest{Key: mustGeneratePublicKey(t)}
k2 := mustGeneratePublicKey(t) cr2 := CertificateRequest{Key: mustGeneratePublicKey(t)}
type args struct { type args struct {
v ssh.PublicKey cr CertificateRequest
} }
tests := []struct { tests := []struct {
name string name string
@ -386,20 +384,28 @@ func TestTemplateData_SetPublicKey(t *testing.T) {
args args args args
want TemplateData want TemplateData
}{ }{
{"ok", TemplateData{}, args{k1}, TemplateData{ {"ok", TemplateData{}, args{cr1}, TemplateData{
PublicKey: k1, InsecureKey: TemplateData{
CertificateRequestKey: cr1,
},
}}, }},
{"overwrite", TemplateData{ {"overwrite", TemplateData{
PublicKey: k1, InsecureKey: TemplateData{
}, args{k2}, TemplateData{ UserKey: "data",
PublicKey: k2, CertificateRequestKey: cr1,
},
}, args{cr2}, TemplateData{
InsecureKey: TemplateData{
UserKey: "data",
CertificateRequestKey: cr2,
},
}}, }},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) { 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)
} }
}) })
} }