certificates/sshutil/certificate_test.go
Mariano Cano df1f7e5a2e 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.
2020-07-30 17:45:03 -07:00

350 lines
10 KiB
Go

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