Skip to content

Commit

Permalink
feat: generate ecdsa keys (#3)
Browse files Browse the repository at this point in the history
* feat: generate ecdsa keys

Signed-off-by: Carlos A Becker <caarlos0@gmail.com>

* chore: rename
  • Loading branch information
caarlos0 committed Jan 31, 2022
1 parent aaf2959 commit 089ed92
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
37 changes: 37 additions & 0 deletions keygen.go
Expand Up @@ -3,7 +3,9 @@ package keygen

import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
Expand All @@ -27,6 +29,7 @@ type KeyType string
const (
RSA KeyType = "rsa"
Ed25519 KeyType = "ed25519"
ECDSA KeyType = "ecdsa"
)

const rsaDefaultBits = 4096
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
bts, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return err
}
pemBlock := pem.EncodeToMemory(&pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: bts,
})

// 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
Expand Down
66 changes: 66 additions & 0 deletions keygen_test.go
Expand Up @@ -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)
Expand Down

0 comments on commit 089ed92

Please sign in to comment.