2019-01-30 21:59:56 -05:00
|
|
|
package common
|
|
|
|
|
|
|
|
// TODO: Make this available to other packer APIs.
|
|
|
|
// Perhaps through 'helper/ssh'?
|
|
|
|
|
|
|
|
import (
|
2019-02-03 10:36:06 -05:00
|
|
|
"bytes"
|
2019-01-30 21:59:56 -05:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
|
|
|
"crypto/x509"
|
|
|
|
"encoding/pem"
|
|
|
|
"errors"
|
|
|
|
"strconv"
|
2019-02-04 12:07:32 -05:00
|
|
|
"strings"
|
2019-01-30 21:59:56 -05:00
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// That's a lot of bits.
|
|
|
|
defaultRsaBits = 4096
|
|
|
|
|
|
|
|
// rsaSsh is a SSH key pair of RSA type.
|
|
|
|
rsaSsh sshKeyPairType = "rsa"
|
|
|
|
|
|
|
|
// ecdsaSsh is a SSH key pair of ECDSA type.
|
|
|
|
ecdsaSsh sshKeyPairType = "ecdsa"
|
|
|
|
)
|
|
|
|
|
|
|
|
// sshKeyPairType represents different types of SSH key pairs.
|
|
|
|
// For example, RSA.
|
|
|
|
type sshKeyPairType string
|
|
|
|
|
|
|
|
func (o sshKeyPairType) String() string {
|
|
|
|
return string(o)
|
|
|
|
}
|
|
|
|
|
2019-02-03 10:36:06 -05:00
|
|
|
const (
|
|
|
|
// unixNewLine is a unix new line.
|
|
|
|
unixNewLine newLineOption = "\n"
|
|
|
|
|
|
|
|
// windowsNewLine is a Windows new line.
|
|
|
|
windowsNewLine newLineOption = "\r\n"
|
|
|
|
|
|
|
|
// noNewLine will not append a new line.
|
|
|
|
noNewLine newLineOption = ""
|
|
|
|
)
|
|
|
|
|
|
|
|
// newLineOption specifies the type of new line to append to a string.
|
|
|
|
// See the 'const' block for choices.
|
|
|
|
type newLineOption string
|
|
|
|
|
|
|
|
func (o newLineOption) String() string {
|
|
|
|
return string(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o newLineOption) Bytes() []byte {
|
|
|
|
return []byte(o)
|
|
|
|
}
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
// sshKeyPairBuilder builds SSH key pairs.
|
|
|
|
type sshKeyPairBuilder interface {
|
|
|
|
// SetType sets the key pair type.
|
|
|
|
SetType(sshKeyPairType) sshKeyPairBuilder
|
|
|
|
|
|
|
|
// SetBits sets the key pair's bits of entropy.
|
|
|
|
SetBits(int) sshKeyPairBuilder
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
// SetName sets the name of the key pair. This is primarily used
|
|
|
|
// to identify the public key in the authorized_keys file.
|
|
|
|
SetName(string) sshKeyPairBuilder
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
// Build returns a SSH key pair.
|
|
|
|
//
|
|
|
|
// The following defaults are used if not specified:
|
|
|
|
// Default type: ECDSA
|
|
|
|
// Default bits of entropy:
|
|
|
|
// - RSA: 4096
|
|
|
|
// - ECDSA: 521
|
2019-02-04 12:07:32 -05:00
|
|
|
// Default name: (empty string)
|
2019-01-30 21:59:56 -05:00
|
|
|
Build() (sshKeyPair, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type defaultSshKeyPairBuilder struct {
|
|
|
|
// kind describes the resulting key pair's type.
|
|
|
|
kind sshKeyPairType
|
|
|
|
|
|
|
|
// bits is the resulting key pair's bits of entropy.
|
|
|
|
bits int
|
2019-02-04 12:07:32 -05:00
|
|
|
|
|
|
|
// name is the resulting key pair's name.
|
|
|
|
name string
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o *defaultSshKeyPairBuilder) SetType(kind sshKeyPairType) sshKeyPairBuilder {
|
|
|
|
o.kind = kind
|
|
|
|
return o
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *defaultSshKeyPairBuilder) SetBits(bits int) sshKeyPairBuilder {
|
|
|
|
o.bits = bits
|
|
|
|
return o
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
func (o *defaultSshKeyPairBuilder) SetName(name string) sshKeyPairBuilder {
|
|
|
|
o.name = name
|
|
|
|
return o
|
|
|
|
}
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
func (o *defaultSshKeyPairBuilder) Build() (sshKeyPair, error) {
|
|
|
|
switch o.kind {
|
|
|
|
case rsaSsh:
|
2019-02-04 12:07:32 -05:00
|
|
|
return o.newRsaSshKeyPair()
|
2019-01-30 21:59:56 -05:00
|
|
|
case ecdsaSsh:
|
|
|
|
// Default case.
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
return o.newEcdsaSshKeyPair()
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// sshKeyPair represents a SSH key pair.
|
|
|
|
type sshKeyPair interface {
|
|
|
|
// Type returns the key pair's type.
|
|
|
|
Type() sshKeyPairType
|
|
|
|
|
|
|
|
// Bits returns the bits of entropy.
|
|
|
|
Bits() int
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
// Name returns the key pair's name. An empty string is
|
|
|
|
// returned is no name was specified.
|
|
|
|
Name() string
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
// Description returns a brief description of the key pair that
|
|
|
|
// is suitable for log messages or printing.
|
|
|
|
Description() string
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
// PrivateKeyPemBlock returns a slice of bytes representing
|
2019-02-04 10:02:07 -05:00
|
|
|
// the private key in ASN.1 Distinguished Encoding Rules (DER)
|
|
|
|
// format in a Privacy-Enhanced Mail (PEM) block.
|
2019-01-30 21:59:56 -05:00
|
|
|
PrivateKeyPemBlock() []byte
|
|
|
|
|
|
|
|
// PublicKeyAuthorizedKeysFormat returns a slice of bytes
|
2019-02-03 10:36:06 -05:00
|
|
|
// representing the public key in OpenSSH authorized_keys format
|
|
|
|
// with the specified new line.
|
|
|
|
PublicKeyAuthorizedKeysFormat(newLineOption) []byte
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type defaultSshKeyPair struct {
|
|
|
|
// kind is the key pair's type.
|
|
|
|
kind sshKeyPairType
|
|
|
|
|
|
|
|
// bits is the key pair's bits of entropy.
|
|
|
|
bits int
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
// name is the key pair's name.
|
|
|
|
name string
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
// privateKeyDerBytes is the private key's bytes
|
|
|
|
// in ASN.1 DER format
|
|
|
|
privateKeyDerBytes []byte
|
|
|
|
|
|
|
|
// publicKey is the key pair's public key.
|
|
|
|
publicKey ssh.PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o defaultSshKeyPair) Type() sshKeyPairType {
|
|
|
|
return o.kind
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o defaultSshKeyPair) Bits() int {
|
|
|
|
return o.bits
|
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
func (o defaultSshKeyPair) Name() string {
|
|
|
|
return o.name
|
|
|
|
}
|
|
|
|
|
2019-02-03 09:17:18 -05:00
|
|
|
func (o defaultSshKeyPair) Description() string {
|
|
|
|
return o.kind.String() + " " + strconv.Itoa(o.bits)
|
|
|
|
}
|
|
|
|
|
2019-01-30 21:59:56 -05:00
|
|
|
func (o defaultSshKeyPair) PrivateKeyPemBlock() []byte {
|
|
|
|
t := "UNKNOWN PRIVATE KEY"
|
|
|
|
|
|
|
|
switch o.kind {
|
|
|
|
case ecdsaSsh:
|
|
|
|
t = "EC PRIVATE KEY"
|
|
|
|
case rsaSsh:
|
|
|
|
t = "RSA PRIVATE KEY"
|
|
|
|
}
|
|
|
|
|
|
|
|
return pem.EncodeToMemory(&pem.Block{
|
|
|
|
Type: t,
|
|
|
|
Headers: nil,
|
|
|
|
Bytes: o.privateKeyDerBytes,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-02-03 10:36:06 -05:00
|
|
|
func (o defaultSshKeyPair) PublicKeyAuthorizedKeysFormat(nl newLineOption) []byte {
|
|
|
|
result := ssh.MarshalAuthorizedKey(o.publicKey)
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
if len(strings.TrimSpace(o.name)) > 0 {
|
|
|
|
// Awful, but the go ssh library automatically appends
|
|
|
|
// a unix new line.
|
|
|
|
result = bytes.TrimSuffix(result, unixNewLine.Bytes())
|
|
|
|
result = append(result, ' ')
|
|
|
|
result = append(result, o.name...)
|
|
|
|
}
|
|
|
|
|
2019-02-03 10:36:06 -05:00
|
|
|
switch nl {
|
|
|
|
case noNewLine:
|
|
|
|
result = bytes.TrimSuffix(result, unixNewLine.Bytes())
|
|
|
|
case windowsNewLine:
|
|
|
|
result = bytes.TrimSuffix(result, unixNewLine.Bytes())
|
|
|
|
result = append(result, nl.Bytes()...)
|
|
|
|
case unixNewLine:
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
// This is how all the other "SSH key pair" code works in
|
|
|
|
// the different builders.
|
|
|
|
if !bytes.HasSuffix(result, unixNewLine.Bytes()) {
|
|
|
|
result = append(result, unixNewLine.Bytes()...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// newEcdsaSshKeyPair returns a new ECDSA SSH key pair for the given bits
|
|
|
|
// of entropy.
|
2019-02-04 12:07:32 -05:00
|
|
|
func (o *defaultSshKeyPairBuilder) newEcdsaSshKeyPair() (sshKeyPair, error) {
|
2019-01-30 21:59:56 -05:00
|
|
|
var curve elliptic.Curve
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
switch o.bits {
|
2019-01-30 21:59:56 -05:00
|
|
|
case 0:
|
2019-02-04 12:07:32 -05:00
|
|
|
o.bits = 521
|
2019-01-30 21:59:56 -05:00
|
|
|
fallthrough
|
|
|
|
case 521:
|
|
|
|
curve = elliptic.P521()
|
|
|
|
case 384:
|
|
|
|
elliptic.P384()
|
|
|
|
case 256:
|
|
|
|
elliptic.P256()
|
|
|
|
case 224:
|
|
|
|
// Not supported by "golang.org/x/crypto/ssh".
|
|
|
|
return &defaultSshKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " +
|
2019-02-04 12:07:32 -05:00
|
|
|
strconv.Itoa(o.bits) + " bits")
|
2019-01-30 21:59:56 -05:00
|
|
|
default:
|
|
|
|
return &defaultSshKeyPair{}, errors.New("crypto/elliptic does not support " +
|
2019-02-04 12:07:32 -05:00
|
|
|
strconv.Itoa(o.bits) + " bits")
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
return &defaultSshKeyPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return &defaultSshKeyPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
raw, err := x509.MarshalECPrivateKey(privateKey)
|
|
|
|
if err != nil {
|
|
|
|
return &defaultSshKeyPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &defaultSshKeyPair{
|
|
|
|
kind: ecdsaSsh,
|
2019-02-04 12:07:32 -05:00
|
|
|
bits: o.bits,
|
|
|
|
name: o.name,
|
2019-01-30 21:59:56 -05:00
|
|
|
privateKeyDerBytes: raw,
|
|
|
|
publicKey: sshPublicKey,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newRsaSshKeyPair returns a new RSA SSH key pair for the given bits
|
|
|
|
// of entropy.
|
2019-02-04 12:07:32 -05:00
|
|
|
func (o *defaultSshKeyPairBuilder) newRsaSshKeyPair() (sshKeyPair, error) {
|
|
|
|
if o.bits == 0 {
|
|
|
|
o.bits = defaultRsaBits
|
2019-01-30 21:59:56 -05:00
|
|
|
}
|
|
|
|
|
2019-02-04 12:07:32 -05:00
|
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, o.bits)
|
2019-01-30 21:59:56 -05:00
|
|
|
if err != nil {
|
|
|
|
return &defaultSshKeyPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sshPublicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
|
|
|
|
if err != nil {
|
|
|
|
return &defaultSshKeyPair{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &defaultSshKeyPair{
|
|
|
|
kind: rsaSsh,
|
2019-02-04 12:07:32 -05:00
|
|
|
bits: o.bits,
|
|
|
|
name: o.name,
|
2019-01-30 21:59:56 -05:00
|
|
|
privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
|
|
publicKey: sshPublicKey,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newSshKeyPairBuilder() sshKeyPairBuilder {
|
|
|
|
return &defaultSshKeyPairBuilder{}
|
|
|
|
}
|