2019-01-30 21:59:56 -05:00
|
|
|
package common
|
|
|
|
|
|
|
|
import (
|
2019-02-04 12:07:32 -05:00
|
|
|
"bytes"
|
2019-01-30 21:59:56 -05:00
|
|
|
"crypto/rand"
|
|
|
|
"errors"
|
2019-02-03 09:17:18 -05:00
|
|
|
"strconv"
|
2019-01-30 21:59:56 -05:00
|
|
|
"testing"
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
"github.com/hashicorp/packer/common/uuid"
|
2019-01-30 21:59:56 -05:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
// expected contains the data that the key pair should contain.
|
|
|
|
type expected struct {
|
|
|
|
kind sshKeyPairType
|
|
|
|
bits int
|
|
|
|
desc string
|
2019-02-04 12:07:32 -05:00
|
|
|
name string
|
|
|
|
data []byte
|
2019-02-03 09:17:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o expected) matches(kp sshKeyPair) error {
|
|
|
|
if o.kind.String() == "" {
|
|
|
|
return errors.New("expected kind's value cannot be empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
if o.bits <= 0 {
|
|
|
|
return errors.New("expected bits' value cannot be less than or equal to 0")
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
if o.desc == "" {
|
|
|
|
return errors.New("expected description's value cannot be empty")
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
if len(o.data) == 0 {
|
|
|
|
return errors.New("expected random data value cannot be nothing")
|
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
if kp.Type() != o.kind {
|
2019-02-04 12:07:32 -05:00
|
|
|
return errors.New("key pair type should be " + o.kind.String() +
|
|
|
|
" - got '" + kp.Type().String() + "'")
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
if kp.Bits() != o.bits {
|
2019-02-04 12:07:32 -05:00
|
|
|
return errors.New("key pair bits should be " + strconv.Itoa(o.bits) +
|
|
|
|
" - got " + strconv.Itoa(kp.Bits()))
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(o.name) > 0 && kp.Name() != o.name {
|
|
|
|
return errors.New("key pair name should be '" + o.name +
|
|
|
|
"' - got '" + kp.Name() + "'")
|
2019-02-03 09:17:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
expDescription := kp.Type().String() + " " + strconv.Itoa(o.bits)
|
|
|
|
if kp.Description() != expDescription {
|
2019-02-04 12:07:32 -05:00
|
|
|
return errors.New("key pair description should be '" +
|
2019-02-03 09:17:18 -05:00
|
|
|
expDescription + "' - got '" + kp.Description() + "'")
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
err := o.verifyPublicKeyAuthorizedKeysFormat(kp)
|
2019-02-03 10:36:06 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
err = o.verifySshKeyPair(kp)
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
2019-02-03 09:17:18 -05:00
|
|
|
return err
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
2019-02-03 09:17:18 -05:00
|
|
|
|
|
|
|
return nil
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
func (o expected) verifyPublicKeyAuthorizedKeysFormat(kp sshKeyPair) error {
|
|
|
|
newLines := []newLineOption{
|
|
|
|
unixNewLine,
|
|
|
|
noNewLine,
|
|
|
|
windowsNewLine,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, nl := range newLines {
|
|
|
|
publicKeyAk := kp.PublicKeyAuthorizedKeysFormat(nl)
|
|
|
|
|
|
|
|
if len(publicKeyAk) < 2 {
|
|
|
|
return errors.New("expected public key in authorized keys format to be at least 2 bytes")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch nl {
|
|
|
|
case noNewLine:
|
|
|
|
if publicKeyAk[len(publicKeyAk) - 1] == '\n' {
|
|
|
|
return errors.New("public key in authorized keys format has trailing new line when none was specified")
|
|
|
|
}
|
|
|
|
case unixNewLine:
|
|
|
|
if publicKeyAk[len(publicKeyAk) - 1] != '\n' {
|
|
|
|
return errors.New("public key in authorized keys format does not have unix new line when unix was specified")
|
|
|
|
}
|
|
|
|
if string(publicKeyAk[len(publicKeyAk) - 2:]) == windowsNewLine.String() {
|
|
|
|
return errors.New("public key in authorized keys format has windows new line when unix was specified")
|
|
|
|
}
|
|
|
|
case windowsNewLine:
|
|
|
|
if string(publicKeyAk[len(publicKeyAk) - 2:]) != windowsNewLine.String() {
|
|
|
|
return errors.New("public key in authorized keys format does not have windows new line when windows was specified")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(o.name) > 0 {
|
|
|
|
if len(publicKeyAk) < len(o.name) {
|
|
|
|
return errors.New("public key in authorized keys format is shorter than the key pair's name")
|
|
|
|
}
|
|
|
|
|
|
|
|
suffix := []byte{' '}
|
|
|
|
suffix = append(suffix, o.name...)
|
|
|
|
suffix = append(suffix, nl.Bytes()...)
|
|
|
|
if !bytes.HasSuffix(publicKeyAk, suffix) {
|
|
|
|
return errors.New("public key in authorized keys format with name does not have name in suffix - got '" +
|
|
|
|
string(publicKeyAk) + "'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o expected) verifySshKeyPair(kp sshKeyPair) error {
|
|
|
|
signer, err := ssh.ParsePrivateKey(kp.PrivateKeyPemBlock())
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to parse private key during verification - " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
signature, err := signer.Sign(rand.Reader, o.data)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to sign test data during verification - " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
err = signer.PublicKey().Verify(o.data, signature)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("failed to verify test data - " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
func TestDefaultSshKeyPairBuilder_Build_Default(t *testing.T) {
|
|
|
|
kp, err := newSshKeyPairBuilder().Build()
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
err = expected{
|
|
|
|
kind: ecdsaSsh,
|
|
|
|
bits: 521,
|
|
|
|
desc: "ecdsa 521",
|
2019-02-04 12:07:32 -05:00
|
|
|
data: []byte(uuid.TimeOrderedUUID()),
|
2019-02-03 09:17:18 -05:00
|
|
|
}.matches(kp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
2019-02-03 09:17:18 -05:00
|
|
|
}
|
2019-01-30 21:59:56 -05:00
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
func TestDefaultSshKeyPairBuilder_Build_EcdsaDefault(t *testing.T) {
|
2019-02-04 12:07:32 -05:00
|
|
|
kp, err := newSshKeyPairBuilder().
|
|
|
|
SetType(ecdsaSsh).
|
|
|
|
Build()
|
2019-02-03 09:17:18 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
err = expected{
|
|
|
|
kind: ecdsaSsh,
|
|
|
|
bits: 521,
|
|
|
|
desc: "ecdsa 521",
|
2019-02-04 12:07:32 -05:00
|
|
|
data: []byte(uuid.TimeOrderedUUID()),
|
2019-02-03 09:17:18 -05:00
|
|
|
}.matches(kp)
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDefaultSshKeyPairBuilder_Build_RsaDefault(t *testing.T) {
|
2019-02-04 12:07:32 -05:00
|
|
|
kp, err := newSshKeyPairBuilder().
|
|
|
|
SetType(rsaSsh).
|
|
|
|
Build()
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
err = expected{
|
|
|
|
kind: rsaSsh,
|
|
|
|
bits: 4096,
|
|
|
|
desc: "rsa 4096",
|
2019-02-04 12:07:32 -05:00
|
|
|
data: []byte(uuid.TimeOrderedUUID()),
|
2019-02-03 09:17:18 -05:00
|
|
|
}.matches(kp)
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
func TestDefaultSshKeyPairBuilder_Build_NamedEcdsa(t *testing.T) {
|
|
|
|
name := uuid.TimeOrderedUUID()
|
2019-01-30 21:59:56 -05:00
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
kp, err := newSshKeyPairBuilder().
|
|
|
|
SetType(ecdsaSsh).
|
|
|
|
SetName(name).
|
|
|
|
Build()
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
2019-02-04 12:07:32 -05:00
|
|
|
t.Fatal(err.Error())
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
err = expected{
|
|
|
|
kind: ecdsaSsh,
|
|
|
|
bits: 521,
|
|
|
|
desc: "ecdsa 521",
|
|
|
|
data: []byte(uuid.TimeOrderedUUID()),
|
|
|
|
name: name,
|
|
|
|
}.matches(kp)
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
2019-02-04 12:07:32 -05:00
|
|
|
t.Fatal(err.Error())
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
}
|
2019-02-03 10:36:06 -05:00
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
func TestDefaultSshKeyPairBuilder_Build_NamedRsa(t *testing.T) {
|
|
|
|
name := uuid.TimeOrderedUUID()
|
2019-02-03 10:36:06 -05:00
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
kp, err := newSshKeyPairBuilder().
|
|
|
|
SetType(rsaSsh).
|
|
|
|
SetName(name).
|
|
|
|
Build()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
2019-02-03 10:36:06 -05:00
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
err = expected{
|
|
|
|
kind: rsaSsh,
|
|
|
|
bits: 4096,
|
|
|
|
desc: "rsa 4096",
|
|
|
|
data: []byte(uuid.TimeOrderedUUID()),
|
|
|
|
name: name,
|
|
|
|
}.matches(kp)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err.Error())
|
|
|
|
}
|
2019-02-03 10:36:06 -05:00
|
|
|
}
|