From 4b649f7ce40c3ce5878b8aea6118b352db6b2dc2 Mon Sep 17 00:00:00 2001 From: Stephen Fox Date: Tue, 5 Feb 2019 11:53:12 -0500 Subject: [PATCH] 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. --- helper/ssh/ecdsa_key_pair.go | 38 ++++++++++ helper/ssh/key_pair.go | 131 ++++++++++++++++------------------- helper/ssh/rsa_key_pair.go | 38 ++++++++++ 3 files changed, 134 insertions(+), 73 deletions(-) create mode 100644 helper/ssh/ecdsa_key_pair.go create mode 100644 helper/ssh/rsa_key_pair.go diff --git a/helper/ssh/ecdsa_key_pair.go b/helper/ssh/ecdsa_key_pair.go new file mode 100644 index 000000000..9f3dde341 --- /dev/null +++ b/helper/ssh/ecdsa_key_pair.go @@ -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) +} diff --git a/helper/ssh/key_pair.go b/helper/ssh/key_pair.go index 51a21e786..708ecd3a7 100644 --- a/helper/ssh/key_pair.go +++ b/helper/ssh/key_pair.go @@ -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{} -} diff --git a/helper/ssh/rsa_key_pair.go b/helper/ssh/rsa_key_pair.go new file mode 100644 index 000000000..9a83f193b --- /dev/null +++ b/helper/ssh/rsa_key_pair.go @@ -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) +}