Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 3363044

Browse files
authored
Merge pull request #44 from keybase/david/revert-ed25519-everywhere
Revert ed25519 everywhere
2 parents 7803a8d + c1b600a commit 3363044

File tree

6 files changed

+62
-33
lines changed

6 files changed

+62
-33
lines changed

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module github.com/keybase/bot-sshca
33
go 1.12
44

55
require (
6-
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681
7-
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
86
github.com/google/uuid v1.1.1
97
github.com/keybase/go-keybase-chat-bot v0.0.0-20190812134859-bc54fd9cf83b
108
github.com/sirupsen/logrus v1.4.2

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2-
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681 h1:JS2rl38kZmHgWa0xINSaSYH0Whtvem64/4+Ef0+Y5pE=
3-
github.com/ScaleFT/sshkeys v0.0.0-20181112160850-82451a803681/go.mod h1:WfDateMPQ/55dPbZRp5Zxrux5WiEaHsjk9puUhz0KgY=
42
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
53
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
64
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
75
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8-
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
9-
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
106
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
117
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
128
github.com/keybase/go-keybase-chat-bot v0.0.0-20190812134859-bc54fd9cf83b h1:7Te2f9LQ/rd6XSzpntz6BaCBgglZ0uiCdv3/GdhX9VA=

src/keybaseca/sshutils/generate.go

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,83 @@
11
package sshutils
22

33
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
46
"crypto/rand"
7+
"crypto/x509"
8+
"encoding/pem"
59
"fmt"
610
"io/ioutil"
11+
"os"
12+
"os/exec"
13+
"strings"
714

8-
"github.com/ScaleFT/sshkeys"
9-
"github.com/keybase/bot-sshca/src/shared"
10-
"golang.org/x/crypto/ed25519"
1115
"golang.org/x/crypto/ssh"
16+
17+
"github.com/keybase/bot-sshca/src/shared"
1218
)
1319

14-
// Generate a new SSH key. Places the private key at filename and the public key at filename.pub.
15-
// We use ed25519 keys since they may be more secure (and are smaller). The go crypto ssh library
16-
// does not support marshalling ed25519 keys so we use ScaleFT/sshkeys to marshal them to the
17-
// correct on disk format for SSH
20+
// Generate a new SSH key and store the private key at filename and the public key at filename.pub
21+
// If the ssh-keygen binary exists, generates an ed25519 ssh key using ssh-keygen. Otherwise,
22+
// generates an ecdsa key using go's crypto library. Note that we use ecdsa rather than ed25519
23+
// in this case since go's crypto library does not support marshalling ed25519 keys into the format
24+
// expected by openssh. github.com/ScaleFT/sshkeys claims to support this but does not reliably
25+
// work with all versions of ssh.
1826
func generateNewSSHKey(filename string) error {
19-
// Generate the key
20-
pub, private, err := ed25519.GenerateKey(rand.Reader)
27+
if sshKeygenBinaryExists() {
28+
return generateNewSSHKeyEd25519(filename)
29+
}
30+
31+
return generateNewSSHKeyEcdsa(filename)
32+
}
33+
34+
// Returns true iff the ssh-keygen binary exists and is in the user's path
35+
func sshKeygenBinaryExists() bool {
36+
_, err := exec.LookPath("ssh-keygen")
37+
return err == nil
38+
}
39+
40+
// Generate an ed25519 ssh key via ssh-keygen. Stores the private key at filename and the public key at filename.pub
41+
func generateNewSSHKeyEd25519(filename string) error {
42+
cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-f", filename, "-m", "PEM", "-N", "")
43+
bytes, err := cmd.CombinedOutput()
2144
if err != nil {
22-
return fmt.Errorf("failed to generate ed25519 key: %v", err)
45+
return fmt.Errorf("ssh-keygen failed: %s (%v)", strings.TrimSpace(string(bytes)), err)
2346
}
47+
return nil
48+
}
2449

25-
// Write the private key
26-
bytes, err := sshkeys.Marshal(private, &sshkeys.MarshalOptions{Format: sshkeys.FormatOpenSSHv1})
50+
// Generate an ecdsa ssh key in pure go code. Stores the private key at filename and the public key at filename.pub
51+
// Note that if you are editing this code, be careful to ensure you test it manually since the integration tests
52+
// run in an environment with ssh-keygen and thus do not call this function. This function is manually used on windows.
53+
func generateNewSSHKeyEcdsa(filename string) error {
54+
// ssh-keygen -t ecdsa uses P256 by default
55+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
2756
if err != nil {
28-
return fmt.Errorf("failed to marshal ed25519 key: %v", err)
57+
return err
2958
}
30-
err = ioutil.WriteFile(filename, bytes, 0600)
59+
60+
// 0600 are the correct permissions for an ssh private key
61+
privateKeyFile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
3162
if err != nil {
32-
return fmt.Errorf("failed to write ssh private key to %s: %v", filename, err)
63+
return err
3364
}
65+
defer privateKeyFile.Close()
3466

35-
// Write the public key
36-
publicKey, err := ssh.NewPublicKey(pub)
67+
bytes, err := x509.MarshalECPrivateKey(privateKey)
3768
if err != nil {
38-
return fmt.Errorf("failed to create public key from ed25519 key: %v", err)
69+
return err
3970
}
40-
bytes = ssh.MarshalAuthorizedKey(publicKey)
41-
err = ioutil.WriteFile(shared.KeyPathToPubKey(filename), bytes, 0600)
71+
72+
privateKeyPEM := &pem.Block{Type: "EC PRIVATE KEY", Bytes: bytes}
73+
err = pem.Encode(privateKeyFile, privateKeyPEM)
4274
if err != nil {
43-
return fmt.Errorf("failed to write ssh public key to %s: %v", shared.KeyPathToPubKey(filename), err)
75+
return err
4476
}
4577

46-
return nil
78+
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
79+
if err != nil {
80+
return err
81+
}
82+
return ioutil.WriteFile(shared.KeyPathToPubKey(filename), ssh.MarshalAuthorizedKey(pub), 0600)
4783
}

src/keybaseca/sshutils/generate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ func TestGenerateNewSSHKey(t *testing.T) {
3131
bytes, err = ioutil.ReadFile(shared.KeyPathToPubKey(filename))
3232
require.NoError(t, err)
3333
require.False(t, strings.Contains(string(bytes), "PRIVATE"))
34-
require.True(t, strings.HasPrefix(string(bytes), "ssh-"))
34+
require.True(t, strings.HasPrefix(string(bytes), "ssh-ed25519") || strings.HasPrefix(string(bytes), "ecdsa-sha2-nistp256"))
3535
}

tests/tests/lib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def outputs_audit_log(tc: TestConfig, filename: str, expected_number: int):
137137
cnt = 0
138138
for line in new_lines:
139139
line = line.decode('utf-8')
140-
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:
140+
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:
141141
cnt += 1
142142

143143
if cnt != expected_number:

tests/tests/test_env_1.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,10 @@ def test_keybaseca_backup(self):
121121
if "----" in line and "PRIVATE" in line and "BEGIN" in line:
122122
add = True
123123
if add:
124-
keyLines.append(line)
124+
keyLines.append(line.strip())
125125
if "----" in line and "PRIVATE" in line and "END" in line:
126126
add = False
127-
key = '\n'.join(keyLines)
128-
print(key)
127+
key = '\n'.join(keyLines) + '\n'
129128
with open('/tmp/ssh/cakey', 'w+') as f:
130129
f.write(key)
131130
run_command("chmod 0600 /tmp/ssh/cakey")

0 commit comments

Comments
 (0)