Use individual key pair implementations.

This allows us to store more information about the key pair.
In particular, we can query the private key for its bits of
entropy - avoiding the possibility of hardcoding the wrong value.
This commit is contained in:
Stephen Fox 2019-02-05 11:53:12 -05:00
parent d7510ecdf7
commit 4b649f7ce4
3 changed files with 134 additions and 73 deletions

View File

@ -0,0 +1,38 @@
package ssh
import (
"crypto/ecdsa"
gossh "golang.org/x/crypto/ssh"
)
type ecdsaKeyPair struct {
privateKey *ecdsa.PrivateKey
publicKey gossh.PublicKey
name string
privatePemBlock []byte
}
func (o ecdsaKeyPair) Type() KeyPairType {
return Ecdsa
}
func (o ecdsaKeyPair) Bits() int {
return o.privateKey.Curve.Params().BitSize
}
func (o ecdsaKeyPair) Name() string {
return o.name
}
func (o ecdsaKeyPair) Description() string {
return description(o)
}
func (o ecdsaKeyPair) PrivateKeyPemBlock() []byte {
return o.privatePemBlock
}
func (o ecdsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte {
return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl)
}

View File

@ -133,34 +133,42 @@ func (o *defaultKeyPairBuilder) newEcdsaKeyPair() (KeyPair, error) {
curve = elliptic.P256()
case 224:
// Not supported by "golang.org/x/crypto/ssh".
return &defaultKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " +
return &ecdsaKeyPair{}, errors.New("golang.org/x/crypto/ssh does not support " +
strconv.Itoa(o.bits) + " bits")
default:
return &defaultKeyPair{}, errors.New("crypto/elliptic does not support " +
return &ecdsaKeyPair{}, errors.New("crypto/elliptic does not support " +
strconv.Itoa(o.bits) + " bits")
}
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return &defaultKeyPair{}, err
return &ecdsaKeyPair{}, err
}
sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return &defaultKeyPair{}, err
return &ecdsaKeyPair{}, err
}
raw, err := x509.MarshalECPrivateKey(privateKey)
privateRaw, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return &defaultKeyPair{}, err
return &ecdsaKeyPair{}, err
}
return &defaultKeyPair{
kind: Ecdsa,
bits: privateKey.Curve.Params().BitSize,
name: o.name,
privateKeyDerBytes: raw,
publicKey: sshPublicKey,
privatePem, err := rawPemBlock(&pem.Block{
Type: "EC PRIVATE KEY",
Headers: nil,
Bytes: privateRaw,
})
if err != nil {
return &ecdsaKeyPair{}, err
}
return &ecdsaKeyPair{
privateKey: privateKey,
publicKey: sshPublicKey,
name: o.name,
privatePemBlock: privatePem,
}, nil
}
@ -172,20 +180,28 @@ func (o *defaultKeyPairBuilder) newRsaKeyPair() (KeyPair, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, o.bits)
if err != nil {
return &defaultKeyPair{}, err
return &rsaKeyPair{}, err
}
sshPublicKey, err := gossh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return &defaultKeyPair{}, err
return &rsaKeyPair{}, err
}
return &defaultKeyPair{
kind: Rsa,
bits: privateKey.N.BitLen(),
name: o.name,
privateKeyDerBytes: x509.MarshalPKCS1PrivateKey(privateKey),
publicKey: sshPublicKey,
privatePemBlock, err := rawPemBlock(&pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})
if err != nil {
return &rsaKeyPair{}, err
}
return &rsaKeyPair{
privateKey: privateKey,
publicKey: sshPublicKey,
name: o.name,
privatePemBlock: privatePemBlock,
}, nil
}
@ -216,68 +232,41 @@ type KeyPair interface {
PublicKeyAuthorizedKeysLine(NewLineOption) []byte
}
type defaultKeyPair struct {
// kind is the key pair's type.
kind KeyPairType
// bits is the key pair's bits of entropy.
bits int
// name is the key pair's name.
name string
// privateKeyDerBytes is the private key's bytes
// in ASN.1 DER format
privateKeyDerBytes []byte
// publicKey is the key pair's public key.
publicKey gossh.PublicKey
func NewKeyPairBuilder() KeyPairBuilder {
return &defaultKeyPairBuilder{}
}
func (o defaultKeyPair) Type() KeyPairType {
return o.kind
}
// rawPemBlock encodes a pem.Block to a slice of bytes.
func rawPemBlock(block *pem.Block) ([]byte, error) {
buffer := bytes.NewBuffer(nil)
func (o defaultKeyPair) Bits() int {
return o.bits
}
func (o defaultKeyPair) Name() string {
return o.name
}
func (o defaultKeyPair) Description() string {
return o.kind.String() + " " + strconv.Itoa(o.bits)
}
func (o defaultKeyPair) PrivateKeyPemBlock() []byte {
t := "UNKNOWN PRIVATE KEY"
switch o.kind {
case Ecdsa:
t = "EC PRIVATE KEY"
case Rsa:
t = "RSA PRIVATE KEY"
err := pem.Encode(buffer, block)
if err != nil {
return []byte{}, err
}
return pem.EncodeToMemory(&pem.Block{
Type: t,
Headers: nil,
Bytes: o.privateKeyDerBytes,
})
return buffer.Bytes(), nil
}
func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte {
result := gossh.MarshalAuthorizedKey(o.publicKey)
// TODO: Key pair name.
// description returns a string describing a key pair.
func description(kp KeyPair) string {
return kp.Type().String() + " " + strconv.Itoa(kp.Bits())
}
// publicKeyAuthorizedKeysLine returns a slice of bytes representing a SSH
// public key as a line in OpenSSH authorized_keys format.
func publicKeyAuthorizedKeysLine(publicKey gossh.PublicKey, name string, nl NewLineOption) []byte {
result := gossh.MarshalAuthorizedKey(publicKey)
// Remove the mandatory unix new line.
// Awful, but the go ssh library automatically appends
// a unix new line.
result = bytes.TrimSuffix(result, UnixNewLine.Bytes())
result = bytes.TrimSpace(result)
if len(strings.TrimSpace(o.name)) > 0 {
if len(strings.TrimSpace(name)) > 0 {
result = append(result, ' ')
result = append(result, o.name...)
result = append(result, name...)
}
switch nl {
@ -291,7 +280,3 @@ func (o defaultKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte {
return result
}
func NewKeyPairBuilder() KeyPairBuilder {
return &defaultKeyPairBuilder{}
}

View File

@ -0,0 +1,38 @@
package ssh
import (
"crypto/rsa"
gossh "golang.org/x/crypto/ssh"
)
type rsaKeyPair struct {
privateKey *rsa.PrivateKey
publicKey gossh.PublicKey
name string
privatePemBlock []byte
}
func (o rsaKeyPair) Type() KeyPairType {
return Rsa
}
func (o rsaKeyPair) Bits() int {
return o.privateKey.N.BitLen()
}
func (o rsaKeyPair) Name() string {
return o.name
}
func (o rsaKeyPair) Description() string {
return description(o)
}
func (o rsaKeyPair) PrivateKeyPemBlock() []byte {
return o.privatePemBlock
}
func (o rsaKeyPair) PublicKeyAuthorizedKeysLine(nl NewLineOption) []byte {
return publicKeyAuthorizedKeysLine(o.publicKey, o.name, nl)
}