Skip to content

Commit

Permalink
Merge pull request #44 from keybase/david/revert-ed25519-everywhere
Browse files Browse the repository at this point in the history
Revert ed25519 everywhere
  • Loading branch information
ddworken authored Sep 4, 2019
2 parents 7803a8d + c1b600a commit 3363044
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 33 deletions.
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ module github.com/keybase/bot-sshca
go 1.12

require (
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
github.com/google/uuid v1.1.1
github.com/keybase/go-keybase-chat-bot v0.0.0-20190812134859-bc54fd9cf83b
github.com/sirupsen/logrus v1.4.2
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681 h1:JS2rl38kZmHgWa0xINSaSYH0Whtvem64/4+Ef0+Y5pE=
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681/go.mod h1:WfDateMPQ/55dPbZRp5Zxrux5WiEaHsjk9puUhz0KgY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/keybase/go-keybase-chat-bot v0.0.0-20190812134859-bc54fd9cf83b h1:7Te2f9LQ/rd6XSzpntz6BaCBgglZ0uiCdv3/GdhX9VA=
Expand Down
80 changes: 58 additions & 22 deletions src/keybaseca/sshutils/generate.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
package sshutils

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"

"github.com/ScaleFT/sshkeys"
"github.com/keybase/bot-sshca/src/shared"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/ssh"

"github.com/keybase/bot-sshca/src/shared"
)

// Generate a new SSH key. Places the private key at filename and the public key at filename.pub.
// We use ed25519 keys since they may be more secure (and are smaller). The go crypto ssh library
// does not support marshalling ed25519 keys so we use ScaleFT/sshkeys to marshal them to the
// correct on disk format for SSH
// Generate a new SSH key and store the private key at filename and the public key at filename.pub
// If the ssh-keygen binary exists, generates an ed25519 ssh key using ssh-keygen. Otherwise,
// generates an ecdsa key using go's crypto library. Note that we use ecdsa rather than ed25519
// in this case since go's crypto library does not support marshalling ed25519 keys into the format
// expected by openssh. github.com/ScaleFT/sshkeys claims to support this but does not reliably
// work with all versions of ssh.
func generateNewSSHKey(filename string) error {
// Generate the key
pub, private, err := ed25519.GenerateKey(rand.Reader)
if sshKeygenBinaryExists() {
return generateNewSSHKeyEd25519(filename)
}

return generateNewSSHKeyEcdsa(filename)
}

// Returns true iff the ssh-keygen binary exists and is in the user's path
func sshKeygenBinaryExists() bool {
_, err := exec.LookPath("ssh-keygen")
return err == nil
}

// Generate an ed25519 ssh key via ssh-keygen. Stores the private key at filename and the public key at filename.pub
func generateNewSSHKeyEd25519(filename string) error {
cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-f", filename, "-m", "PEM", "-N", "")
bytes, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to generate ed25519 key: %v", err)
return fmt.Errorf("ssh-keygen failed: %s (%v)", strings.TrimSpace(string(bytes)), err)
}
return nil
}

// Write the private key
bytes, err := sshkeys.Marshal(private, &sshkeys.MarshalOptions{Format: sshkeys.FormatOpenSSHv1})
// Generate an ecdsa ssh key in pure go code. Stores the private key at filename and the public key at filename.pub
// Note that if you are editing this code, be careful to ensure you test it manually since the integration tests
// run in an environment with ssh-keygen and thus do not call this function. This function is manually used on windows.
func generateNewSSHKeyEcdsa(filename string) error {
// ssh-keygen -t ecdsa uses P256 by default
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return fmt.Errorf("failed to marshal ed25519 key: %v", err)
return err
}
err = ioutil.WriteFile(filename, bytes, 0600)

// 0600 are the correct permissions for an ssh private key
privateKeyFile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to write ssh private key to %s: %v", filename, err)
return err
}
defer privateKeyFile.Close()

// Write the public key
publicKey, err := ssh.NewPublicKey(pub)
bytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return fmt.Errorf("failed to create public key from ed25519 key: %v", err)
return err
}
bytes = ssh.MarshalAuthorizedKey(publicKey)
err = ioutil.WriteFile(shared.KeyPathToPubKey(filename), bytes, 0600)

privateKeyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: bytes}
err = pem.Encode(privateKeyFile, privateKeyPEM)
if err != nil {
return fmt.Errorf("failed to write ssh public key to %s: %v", shared.KeyPathToPubKey(filename), err)
return err
}

return nil
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return err
}
return ioutil.WriteFile(shared.KeyPathToPubKey(filename), ssh.MarshalAuthorizedKey(pub), 0600)
}
2 changes: 1 addition & 1 deletion src/keybaseca/sshutils/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ func TestGenerateNewSSHKey(t *testing.T) {
bytes, err = ioutil.ReadFile(shared.KeyPathToPubKey(filename))
require.NoError(t, err)
require.False(t, strings.Contains(string(bytes), "PRIVATE"))
require.True(t, strings.HasPrefix(string(bytes), "ssh-"))
require.True(t, strings.HasPrefix(string(bytes), "ssh-ed25519") || strings.HasPrefix(string(bytes), "ecdsa-sha2-nistp256"))
}
2 changes: 1 addition & 1 deletion tests/tests/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def outputs_audit_log(tc: TestConfig, filename: str, expected_number: int):
cnt = 0
for line in new_lines:
line = line.decode('utf-8')
if line and f"Processing SignatureRequest from user={tc.username}" in line and f"principals:{tc.subteam}.ssh.staging,{tc.subteam}.ssh.root_everywhere, expiration:+1h, pubkey:ssh-ed25519" in line:
if line and f"Processing SignatureRequest from user={tc.username}" in line and f"principals:{tc.subteam}.ssh.staging,{tc.subteam}.ssh.root_everywhere, expiration:+1h, pubkey:" in line:
cnt += 1

if cnt != expected_number:
Expand Down
5 changes: 2 additions & 3 deletions tests/tests/test_env_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,10 @@ def test_keybaseca_backup(self):
if "----" in line and "PRIVATE" in line and "BEGIN" in line:
add = True
if add:
keyLines.append(line)
keyLines.append(line.strip())
if "----" in line and "PRIVATE" in line and "END" in line:
add = False
key = '\n'.join(keyLines)
print(key)
key = '\n'.join(keyLines) + '\n'
with open('/tmp/ssh/cakey', 'w+') as f:
f.write(key)
run_command("chmod 0600 /tmp/ssh/cakey")
Expand Down

0 comments on commit 3363044

Please sign in to comment.