Merge pull request #102 from smallstep/ssh-rsa-size
Implement validator for ssh keys
This commit is contained in:
commit
e02dd1a5d0
11 changed files with 184 additions and 51 deletions
|
@ -470,6 +470,8 @@ func (p *AWS) authorizeSSHSign(claims *awsPayload) ([]SignOption, error) {
|
||||||
&sshDefaultExtensionModifier{},
|
&sshDefaultExtensionModifier{},
|
||||||
// checks the validity bounds, and set the validity if has not been set
|
// checks the validity bounds, and set the validity if has not been set
|
||||||
&sshCertificateValidityModifier{p.claimer},
|
&sshCertificateValidityModifier{p.claimer},
|
||||||
|
// validate public key
|
||||||
|
&sshDefaultPublicKeyValidator{},
|
||||||
// require all the fields in the SSH certificate
|
// require all the fields in the SSH certificate
|
||||||
&sshCertificateDefaultValidator{},
|
&sshCertificateDefaultValidator{},
|
||||||
), nil
|
), nil
|
||||||
|
|
|
@ -377,6 +377,12 @@ func TestAWS_AuthorizeSign_SSH(t *testing.T) {
|
||||||
signer, err := generateJSONWebKey()
|
signer, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
pub := key.Public().Key
|
||||||
|
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
||||||
expectedHostOptions := &SSHOptions{
|
expectedHostOptions := &SSHOptions{
|
||||||
CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"},
|
CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"},
|
||||||
|
@ -394,6 +400,7 @@ func TestAWS_AuthorizeSign_SSH(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
token string
|
token string
|
||||||
sshOpts SSHOptions
|
sshOpts SSHOptions
|
||||||
|
key interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -403,15 +410,17 @@ func TestAWS_AuthorizeSign_SSH(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantSignErr bool
|
wantSignErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", p1, args{t1, SSHOptions{}}, expectedHostOptions, false, false},
|
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}}, expectedHostOptions, false, false},
|
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, false, false},
|
||||||
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}}, expectedHostOptions, false, false},
|
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-principal-ip", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1"}}}, expectedHostOptionsIP, false, false},
|
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-principal-hostname", p1, args{t1, SSHOptions{Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}}}, expectedHostOptionsHostname, false, false},
|
{"ok-principal-ip", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1"}}, pub}, expectedHostOptionsIP, false, false},
|
||||||
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}}, expectedHostOptions, false, false},
|
{"ok-principal-hostname", p1, args{t1, SSHOptions{Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptionsHostname, false, false},
|
||||||
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}}, nil, false, true},
|
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}}, nil, false, true},
|
{"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, false, true},
|
||||||
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal", "smallstep.com"}}}, nil, false, true},
|
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, false, true},
|
||||||
|
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, false, true},
|
||||||
|
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal", "smallstep.com"}}, pub}, nil, false, 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) {
|
||||||
|
@ -424,7 +433,7 @@ func TestAWS_AuthorizeSign_SSH(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Nil(t, got)
|
assert.Nil(t, got)
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(key.Public().Key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -327,6 +327,8 @@ func (p *Azure) authorizeSSHSign(claims azurePayload, name string) ([]SignOption
|
||||||
&sshDefaultExtensionModifier{},
|
&sshDefaultExtensionModifier{},
|
||||||
// checks the validity bounds, and set the validity if has not been set
|
// checks the validity bounds, and set the validity if has not been set
|
||||||
&sshCertificateValidityModifier{p.claimer},
|
&sshCertificateValidityModifier{p.claimer},
|
||||||
|
// validate public key
|
||||||
|
&sshDefaultPublicKeyValidator{},
|
||||||
// require all the fields in the SSH certificate
|
// require all the fields in the SSH certificate
|
||||||
&sshCertificateDefaultValidator{},
|
&sshCertificateDefaultValidator{},
|
||||||
), nil
|
), nil
|
||||||
|
|
|
@ -3,6 +3,8 @@ package provisioner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -325,6 +327,12 @@ func TestAzure_AuthorizeSign_SSH(t *testing.T) {
|
||||||
signer, err := generateJSONWebKey()
|
signer, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
pub := key.Public().Key
|
||||||
|
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
||||||
expectedHostOptions := &SSHOptions{
|
expectedHostOptions := &SSHOptions{
|
||||||
CertType: "host", Principals: []string{"virtualMachine"},
|
CertType: "host", Principals: []string{"virtualMachine"},
|
||||||
|
@ -334,6 +342,7 @@ func TestAzure_AuthorizeSign_SSH(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
token string
|
token string
|
||||||
sshOpts SSHOptions
|
sshOpts SSHOptions
|
||||||
|
key interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -343,13 +352,15 @@ func TestAzure_AuthorizeSign_SSH(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantSignErr bool
|
wantSignErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", p1, args{t1, SSHOptions{}}, expectedHostOptions, false, false},
|
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}}, expectedHostOptions, false, false},
|
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, false, false},
|
||||||
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine"}}}, expectedHostOptions, false, false},
|
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"virtualMachine"}}}, expectedHostOptions, false, false},
|
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}}, nil, false, true},
|
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}}, nil, false, true},
|
{"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, false, true},
|
||||||
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine", "smallstep.com"}}}, nil, false, true},
|
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, false, true},
|
||||||
|
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, false, true},
|
||||||
|
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine", "smallstep.com"}}, pub}, nil, false, 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) {
|
||||||
|
@ -362,7 +373,7 @@ func TestAzure_AuthorizeSign_SSH(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Nil(t, got)
|
assert.Nil(t, got)
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(key.Public().Key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -382,6 +382,8 @@ func (p *GCP) authorizeSSHSign(claims *gcpPayload) ([]SignOption, error) {
|
||||||
&sshDefaultExtensionModifier{},
|
&sshDefaultExtensionModifier{},
|
||||||
// checks the validity bounds, and set the validity if has not been set
|
// checks the validity bounds, and set the validity if has not been set
|
||||||
&sshCertificateValidityModifier{p.claimer},
|
&sshCertificateValidityModifier{p.claimer},
|
||||||
|
// validate public key
|
||||||
|
&sshDefaultPublicKeyValidator{},
|
||||||
// require all the fields in the SSH certificate
|
// require all the fields in the SSH certificate
|
||||||
&sshCertificateDefaultValidator{},
|
&sshCertificateDefaultValidator{},
|
||||||
), nil
|
), nil
|
||||||
|
|
|
@ -3,6 +3,8 @@ package provisioner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
@ -362,6 +364,12 @@ func TestGCP_AuthorizeSign_SSH(t *testing.T) {
|
||||||
signer, err := generateJSONWebKey()
|
signer, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
pub := key.Public().Key
|
||||||
|
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
||||||
expectedHostOptions := &SSHOptions{
|
expectedHostOptions := &SSHOptions{
|
||||||
CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"},
|
CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"},
|
||||||
|
@ -379,6 +387,7 @@ func TestGCP_AuthorizeSign_SSH(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
token string
|
token string
|
||||||
sshOpts SSHOptions
|
sshOpts SSHOptions
|
||||||
|
key interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -388,15 +397,17 @@ func TestGCP_AuthorizeSign_SSH(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantSignErr bool
|
wantSignErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", p1, args{t1, SSHOptions{}}, expectedHostOptions, false, false},
|
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}}, expectedHostOptions, false, false},
|
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, false, false},
|
||||||
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}}, expectedHostOptions, false, false},
|
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-principal1", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}}, expectedHostOptionsPrincipal1, false, false},
|
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"ok-principal2", p1, args{t1, SSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}}, expectedHostOptionsPrincipal2, false, false},
|
{"ok-principal1", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal1, false, false},
|
||||||
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}}, expectedHostOptions, false, false},
|
{"ok-principal2", p1, args{t1, SSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal2, false, false},
|
||||||
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}}, nil, false, true},
|
{"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}}, nil, false, true},
|
{"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, false, true},
|
||||||
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal", "smallstep.com"}}}, nil, false, true},
|
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, false, true},
|
||||||
|
{"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, false, true},
|
||||||
|
{"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal", "smallstep.com"}}, pub}, nil, false, 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) {
|
||||||
|
@ -409,7 +420,7 @@ func TestGCP_AuthorizeSign_SSH(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Nil(t, got)
|
assert.Nil(t, got)
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(key.Public().Key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -210,6 +210,8 @@ func (p *JWK) authorizeSSHSign(claims *jwtPayload) ([]SignOption, error) {
|
||||||
&sshDefaultExtensionModifier{},
|
&sshDefaultExtensionModifier{},
|
||||||
// checks the validity bounds, and set the validity if has not been set
|
// checks the validity bounds, and set the validity if has not been set
|
||||||
&sshCertificateValidityModifier{p.claimer},
|
&sshCertificateValidityModifier{p.claimer},
|
||||||
|
// validate public key
|
||||||
|
&sshDefaultPublicKeyValidator{},
|
||||||
// require all the fields in the SSH certificate
|
// require all the fields in the SSH certificate
|
||||||
&sshCertificateDefaultValidator{},
|
&sshCertificateDefaultValidator{},
|
||||||
), nil
|
), nil
|
||||||
|
|
|
@ -3,6 +3,8 @@ package provisioner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -356,6 +358,12 @@ func TestJWK_AuthorizeSign_SSH(t *testing.T) {
|
||||||
signer, err := generateJSONWebKey()
|
signer, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
pub := key.Public().Key
|
||||||
|
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
userDuration := p1.claimer.DefaultUserSSHCertDuration()
|
userDuration := p1.claimer.DefaultUserSSHCertDuration()
|
||||||
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
||||||
expectedUserOptions := &SSHOptions{
|
expectedUserOptions := &SSHOptions{
|
||||||
|
@ -370,6 +378,7 @@ func TestJWK_AuthorizeSign_SSH(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
token string
|
token string
|
||||||
sshOpts SSHOptions
|
sshOpts SSHOptions
|
||||||
|
key interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -379,15 +388,17 @@ func TestJWK_AuthorizeSign_SSH(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantSignErr bool
|
wantSignErr bool
|
||||||
}{
|
}{
|
||||||
{"user", p1, args{t1, SSHOptions{}}, expectedUserOptions, false, false},
|
{"user", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, false, false},
|
||||||
{"user-type", p1, args{t1, SSHOptions{CertType: "user"}}, expectedUserOptions, false, false},
|
{"user-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, false, false},
|
||||||
{"user-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}}, expectedUserOptions, false, false},
|
{"user-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, false, false},
|
||||||
{"user-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}}, expectedUserOptions, false, false},
|
{"user-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, pub}, expectedUserOptions, false, false},
|
||||||
{"host", p1, args{t2, SSHOptions{}}, expectedHostOptions, false, false},
|
{"user-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, expectedUserOptions, false, false},
|
||||||
{"host-type", p1, args{t2, SSHOptions{CertType: "host"}}, expectedHostOptions, false, false},
|
{"host", p1, args{t2, SSHOptions{}, pub}, expectedHostOptions, false, false},
|
||||||
{"host-principals", p1, args{t2, SSHOptions{Principals: []string{"smallstep.com"}}}, expectedHostOptions, false, false},
|
{"host-type", p1, args{t2, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, false, false},
|
||||||
{"host-options", p1, args{t2, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}}, expectedHostOptions, false, false},
|
{"host-principals", p1, args{t2, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-signature", p1, args{failSig, SSHOptions{}}, nil, true, false},
|
{"host-options", p1, args{t2, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, false, false},
|
||||||
|
{"fail-signature", p1, args{failSig, SSHOptions{}, pub}, nil, true, false},
|
||||||
|
{"rail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, false, 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) {
|
||||||
|
@ -400,7 +411,7 @@ func TestJWK_AuthorizeSign_SSH(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Nil(t, got)
|
assert.Nil(t, got)
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(key.Public().Key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -336,6 +336,8 @@ func (o *OIDC) authorizeSSHSign(claims *openIDPayload) ([]SignOption, error) {
|
||||||
&sshDefaultExtensionModifier{},
|
&sshDefaultExtensionModifier{},
|
||||||
// checks the validity bounds, and set the validity if has not been set
|
// checks the validity bounds, and set the validity if has not been set
|
||||||
&sshCertificateValidityModifier{o.claimer},
|
&sshCertificateValidityModifier{o.claimer},
|
||||||
|
// validate public key
|
||||||
|
&sshDefaultPublicKeyValidator{},
|
||||||
// require all the fields in the SSH certificate
|
// require all the fields in the SSH certificate
|
||||||
&sshCertificateDefaultValidator{},
|
&sshCertificateDefaultValidator{},
|
||||||
), nil
|
), nil
|
||||||
|
|
|
@ -3,6 +3,8 @@ package provisioner
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -343,6 +345,12 @@ func TestOIDC_AuthorizeSign_SSH(t *testing.T) {
|
||||||
signer, err := generateJSONWebKey()
|
signer, err := generateJSONWebKey()
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
pub := key.Public().Key
|
||||||
|
rsa2048, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
rsa1024, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
userDuration := p1.claimer.DefaultUserSSHCertDuration()
|
userDuration := p1.claimer.DefaultUserSSHCertDuration()
|
||||||
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
hostDuration := p1.claimer.DefaultHostSSHCertDuration()
|
||||||
expectedUserOptions := &SSHOptions{
|
expectedUserOptions := &SSHOptions{
|
||||||
|
@ -361,6 +369,7 @@ func TestOIDC_AuthorizeSign_SSH(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
token string
|
token string
|
||||||
sshOpts SSHOptions
|
sshOpts SSHOptions
|
||||||
|
key interface{}
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -370,18 +379,20 @@ func TestOIDC_AuthorizeSign_SSH(t *testing.T) {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
wantSignErr bool
|
wantSignErr bool
|
||||||
}{
|
}{
|
||||||
{"ok", p1, args{t1, SSHOptions{}}, expectedUserOptions, false, false},
|
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, false, false},
|
||||||
{"ok-user", p1, args{t1, SSHOptions{CertType: "user"}}, expectedUserOptions, false, false},
|
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, false, false},
|
||||||
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}}, expectedUserOptions, false, false},
|
{"ok-user", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, false, false},
|
||||||
{"ok-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}}, expectedUserOptions, false, false},
|
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, pub}, expectedUserOptions, false, false},
|
||||||
{"admin", p3, args{okAdmin, SSHOptions{}}, expectedAdminOptions, false, false},
|
{"ok-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, expectedUserOptions, false, false},
|
||||||
{"admin-user", p3, args{okAdmin, SSHOptions{CertType: "user"}}, expectedAdminOptions, false, false},
|
{"admin", p3, args{okAdmin, SSHOptions{}, pub}, expectedAdminOptions, false, false},
|
||||||
{"admin-principals", p3, args{okAdmin, SSHOptions{Principals: []string{"root"}}}, expectedAdminOptions, false, false},
|
{"admin-user", p3, args{okAdmin, SSHOptions{CertType: "user"}, pub}, expectedAdminOptions, false, false},
|
||||||
{"admin-options", p3, args{okAdmin, SSHOptions{CertType: "user", Principals: []string{"name"}}}, expectedUserOptions, false, false},
|
{"admin-principals", p3, args{okAdmin, SSHOptions{Principals: []string{"root"}}, pub}, expectedAdminOptions, false, false},
|
||||||
{"admin-host", p3, args{okAdmin, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}}, expectedHostOptions, false, false},
|
{"admin-options", p3, args{okAdmin, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, expectedUserOptions, false, false},
|
||||||
{"fail-user-host", p1, args{t1, SSHOptions{CertType: "host"}}, nil, false, true},
|
{"admin-host", p3, args{okAdmin, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, false, false},
|
||||||
{"fail-user-principals", p1, args{t1, SSHOptions{Principals: []string{"root"}}}, nil, false, true},
|
{"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, false, true},
|
||||||
{"fail-email", p3, args{failEmail, SSHOptions{}}, nil, true, false},
|
{"fail-user-host", p1, args{t1, SSHOptions{CertType: "host"}, pub}, nil, false, true},
|
||||||
|
{"fail-user-principals", p1, args{t1, SSHOptions{Principals: []string{"root"}}, pub}, nil, false, true},
|
||||||
|
{"fail-email", p3, args{failEmail, SSHOptions{}, pub}, nil, true, false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -394,7 +405,7 @@ func TestOIDC_AuthorizeSign_SSH(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
assert.Nil(t, got)
|
assert.Nil(t, got)
|
||||||
} else if assert.NotNil(t, got) {
|
} else if assert.NotNil(t, got) {
|
||||||
cert, err := signSSHCertificate(key.Public().Key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
cert, err := signSSHCertificate(tt.args.key, tt.args.sshOpts, got, signer.Key.(crypto.Signer))
|
||||||
if (err != nil) != tt.wantSignErr {
|
if (err != nil) != tt.wantSignErr {
|
||||||
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
t.Errorf("SignSSH error = %v, wantSignErr %v", err, tt.wantSignErr)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package provisioner
|
package provisioner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -275,6 +278,35 @@ func (v *sshCertificateDefaultValidator) Valid(cert *ssh.Certificate) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sshDefaultPublicKeyValidator implements a validator for the certificate key.
|
||||||
|
type sshDefaultPublicKeyValidator struct{}
|
||||||
|
|
||||||
|
// Valid checks that certificate request common name matches the one configured.
|
||||||
|
func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate) error {
|
||||||
|
if cert.Key == nil {
|
||||||
|
return errors.New("ssh certificate key cannot be nil")
|
||||||
|
}
|
||||||
|
switch cert.Key.Type() {
|
||||||
|
case ssh.KeyAlgoRSA:
|
||||||
|
_, in, ok := sshParseString(cert.Key.Marshal())
|
||||||
|
if !ok {
|
||||||
|
return errors.New("ssh certificate key is invalid")
|
||||||
|
}
|
||||||
|
key, err := sshParseRSAPublicKey(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if key.Size() < 256 {
|
||||||
|
return errors.New("ssh certificate key must be at least 2048 bits (256 bytes)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case ssh.KeyAlgoDSA:
|
||||||
|
return errors.New("ssh certificate key algorithm (DSA) is not supported")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sshCertTypeUInt32
|
// sshCertTypeUInt32
|
||||||
func sshCertTypeUInt32(ct string) uint32 {
|
func sshCertTypeUInt32(ct string) uint32 {
|
||||||
switch ct {
|
switch ct {
|
||||||
|
@ -304,3 +336,41 @@ func containsAllMembers(group, subgroup []string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshParseString(in []byte) (out, rest []byte, ok bool) {
|
||||||
|
if len(in) < 4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint32(in)
|
||||||
|
in = in[4:]
|
||||||
|
if uint32(len(in)) < length {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out = in[:length]
|
||||||
|
rest = in[length:]
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sshParseRSAPublicKey(in []byte) (*rsa.PublicKey, error) {
|
||||||
|
var w struct {
|
||||||
|
E *big.Int
|
||||||
|
N *big.Int
|
||||||
|
Rest []byte `ssh:"rest"`
|
||||||
|
}
|
||||||
|
if err := ssh.Unmarshal(in, &w); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error unmarshalling public key")
|
||||||
|
}
|
||||||
|
if w.E.BitLen() > 24 {
|
||||||
|
return nil, errors.New("invalid public key: exponent too large")
|
||||||
|
}
|
||||||
|
e := w.E.Int64()
|
||||||
|
if e < 3 || e&1 == 0 {
|
||||||
|
return nil, errors.New("invalid public key: incorrect exponent")
|
||||||
|
}
|
||||||
|
|
||||||
|
var key rsa.PublicKey
|
||||||
|
key.E = int(e)
|
||||||
|
key.N = w.N
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue