From 9b319b4305cac4e6a2a802eb7b02d52e53932afb Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Mon, 31 Jan 2022 11:25:24 -0300 Subject: [PATCH 1/2] feat: generate ecdsa keys Signed-off-by: Carlos A Becker --- keygen.go | 37 ++++++++++++++++++++++++++++ keygen_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/keygen.go b/keygen.go index 5fb6687..03e3b7d 100644 --- a/keygen.go +++ b/keygen.go @@ -3,7 +3,9 @@ package keygen import ( "bytes" + "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -27,6 +29,7 @@ type KeyType string const ( RSA KeyType = "rsa" Ed25519 KeyType = "ed25519" + ECDSA KeyType = "ecdsa" ) const rsaDefaultBits = 4096 @@ -100,6 +103,8 @@ func New(path, name string, passphrase []byte, keyType KeyType) (*SSHKeyPair, er err = s.generateEd25519Keys() case RSA: err = s.generateRSAKeys(rsaDefaultBits, passphrase) + case ECDSA: + err = s.generateECDSAKeys() default: return nil, fmt.Errorf("unsupported key type %s", keyType) } @@ -151,6 +156,38 @@ func (s *SSHKeyPair) generateEd25519Keys() error { return nil } +// generateEd25519Keys creates a pair of EdD25519 keys for SSH auth. +func (s *SSHKeyPair) generateECDSAKeys() error { + // Generate keys + privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return err + } + + // Encode PEM + x509Encoded, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return err + } + pemBlock := pem.EncodeToMemory(&pem.Block{ + Type: "OPENSSH PRIVATE KEY", + Bytes: x509Encoded, + }) + + // Prepare public key + publicKey, err := ssh.NewPublicKey(privateKey.Public()) + if err != nil { + return err + } + + // serialize for public key file on disk + serializedPublicKey := ssh.MarshalAuthorizedKey(publicKey) + + s.PrivateKeyPEM = pemBlock + s.PublicKey = pubKeyWithMemo(serializedPublicKey) + return nil +} + // generateRSAKeys creates a pair for RSA keys for SSH auth. func (s *SSHKeyPair) generateRSAKeys(bitSize int, passphrase []byte) error { // Generate private key diff --git a/keygen_test.go b/keygen_test.go index f5f5e21..8028080 100644 --- a/keygen_test.go +++ b/keygen_test.go @@ -80,6 +80,72 @@ func TestGenerateEd25519Keys(t *testing.T) { }) } +func TestGenerateECDSAKeys(t *testing.T) { + // Create temp directory for keys + dir := t.TempDir() + + k := &SSHKeyPair{ + KeyDir: dir, + Filename: "test", + } + + t.Run("test generate SSH keys", func(t *testing.T) { + err := k.generateECDSAKeys() + if err != nil { + t.Errorf("error creating SSH key pair: %v", err) + } + + // TODO: is there a good way to validate these? Lengths seem to vary a bit, + // so far now we're just asserting that the keys indeed exist. + if len(k.PrivateKeyPEM) == 0 { + t.Error("error creating SSH private key PEM; key is 0 bytes") + } + if len(k.PublicKey) == 0 { + t.Error("error creating SSH public key; key is 0 bytes") + } + }) + + t.Run("test write SSH keys", func(t *testing.T) { + k.KeyDir = filepath.Join(dir, "ssh1") + if err := k.prepFilesystem(); err != nil { + t.Errorf("filesystem error: %v\n", err) + } + if err := k.WriteKeys(); err != nil { + t.Errorf("error writing SSH keys to %s: %v", k.KeyDir, err) + } + if testing.Verbose() { + t.Logf("Wrote keys to %s", k.KeyDir) + } + }) + + t.Run("test not overwriting existing keys", func(t *testing.T) { + k.KeyDir = filepath.Join(dir, "ssh2") + if err := k.prepFilesystem(); err != nil { + t.Errorf("filesystem error: %v\n", err) + } + + // Private key + filePath := filepath.Join(k.KeyDir, k.Filename) + if !createEmptyFile(t, filePath) { + return + } + if err := k.WriteKeys(); err == nil { + t.Errorf("we wrote the private key over an existing file, but we were not supposed to") + } + if err := os.Remove(filePath); err != nil { + t.Errorf("could not remove file %s", filePath) + } + + // Public key + if !createEmptyFile(t, filePath+".pub") { + return + } + if err := k.WriteKeys(); err == nil { + t.Errorf("we wrote the public key over an existing file, but we were not supposed to") + } + }) +} + // touchTestFile is a utility function we're using in testing. func createEmptyFile(t *testing.T, path string) (ok bool) { dir := filepath.Dir(path) From 02321cd4e53ee8715d3c275605d1e798eee95677 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Mon, 31 Jan 2022 11:26:38 -0300 Subject: [PATCH 2/2] chore: rename --- keygen.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keygen.go b/keygen.go index 03e3b7d..b3d20a1 100644 --- a/keygen.go +++ b/keygen.go @@ -165,13 +165,13 @@ func (s *SSHKeyPair) generateECDSAKeys() error { } // Encode PEM - x509Encoded, err := x509.MarshalECPrivateKey(privateKey) + bts, err := x509.MarshalECPrivateKey(privateKey) if err != nil { return err } pemBlock := pem.EncodeToMemory(&pem.Block{ Type: "OPENSSH PRIVATE KEY", - Bytes: x509Encoded, + Bytes: bts, }) // Prepare public key