Skip to content

Commit

Permalink
add key with passphrase support (fix #2)
Browse files Browse the repository at this point in the history
  • Loading branch information
seletskiy committed Jul 14, 2016
1 parent 5ff8ac1 commit e292c36
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 7 deletions.
92 changes: 88 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ package main

import (
"bufio"
"bytes"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
Expand Down Expand Up @@ -220,7 +225,8 @@ const (
)

var (
sshPasswordPrompt = "Password: "
sshPasswordPrompt = "Password: "
sshPassphrasePrompt = "Key passphrase: "
)

var (
Expand Down Expand Up @@ -281,7 +287,9 @@ func main() {
bar = barely.NewStatusBar(barStyle.Template)
} else {
bar = nil

sshPasswordPrompt = ""
sshPassphrasePrompt = ""
}

switch {
Expand Down Expand Up @@ -684,6 +692,73 @@ func connectAndLock(
return cluster, nil
}

func readSSHKey(path string) ([]byte, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, hierr.Errorf(
err,
`can't read SSH key from file`,
)
}

decoded, extra := pem.Decode(data)

if len(extra) != 0 {
return nil, hierr.Errorf(
errors.New(string(extra)),
`extra data found in the SSH key`,
)
}

if procType, ok := decoded.Headers[`Proc-Type`]; ok {
// according to pem_decrypt.go in stdlib
if procType == `4,ENCRYPTED` {
passphrase, err := readPassword(sshPassphrasePrompt)
if err != nil {
return nil, hierr.Errorf(
err,
`can't read key passphrase`,
)
}

data, err = x509.DecryptPEMBlock(decoded, []byte(passphrase))
if err != nil {
return nil, hierr.Errorf(
err,
`can't decrypt (using passphrase) SSH key`,
)
}

rsa, err := x509.ParsePKCS1PrivateKey(data)
if err != nil {
return nil, hierr.Errorf(
err,
`can't parse decrypted key as RSA key`,
)
}

pemBytes := bytes.Buffer{}
err = pem.Encode(
&pemBytes,
&pem.Block{
Type: `RSA PRIVATE KEY`,
Bytes: x509.MarshalPKCS1PrivateKey(rsa),
},
)
if err != nil {
return nil, hierr.Errorf(
err,
`can't convert decrypted RSA key into PEM format`,
)
}

data = pemBytes.Bytes()
}
}

return data, nil
}

func createRunnerFactory(
timeouts *runcmd.Timeouts,
sshKeyPath string,
Expand All @@ -707,8 +782,17 @@ func createRunnerFactory(
), nil

case sshKeyPath != "":
key, err := readSSHKey(sshKeyPath)
if err != nil {
return nil, hierr.Errorf(
err,
`can't read SSH key: '%s'`,
sshKeyPath,
)
}

return createRemoteRunnerFactoryWithKey(
sshKeyPath,
string(key),
timeouts,
), nil

Expand Down Expand Up @@ -780,7 +864,7 @@ func generateRunID() string {
}

func readPassword(prompt string) (string, error) {
fmt.Fprintf(os.Stderr, sshPasswordPrompt)
fmt.Fprintf(os.Stderr, prompt)

tty, err := os.Open("/dev/tty")
if err != nil {
Expand All @@ -799,7 +883,7 @@ func readPassword(prompt string) (string, error) {
)
}

if sshPasswordPrompt != "" {
if prompt != "" {
fmt.Fprintln(os.Stderr)
}

Expand Down
2 changes: 1 addition & 1 deletion runner_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func createRemoteRunnerFactoryWithKey(
) runnerFactory {
return func(address address) (runcmd.Runner, error) {
return createRunner(
runcmd.NewRemoteKeyAuthRunnerWithTimeouts,
runcmd.NewRemoteRawKeyAuthRunnerWithTimeouts,
key,
address,
*timeouts,
Expand Down
24 changes: 24 additions & 0 deletions tests/orgalorg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ orgalorg_user="orgalorg"
EXPECT
}

:orgalorg:with-key-passphrase() {
local passphrase="$1"
shift

:expect() {
expect -f <(cat) -- "${@}" </dev/tty
}

go-test:run :expect -u $orgalorg_user ${ips[*]/#/-o} \
-k "$(ssh-test:print-key-path)" "${@}" <<EXPECT
spawn -noecho orgalorg {*}\$argv
expect {
"Key passphrase:" {
send "$passphrase\r"
interact
} eof {
send_error "\$expect_out(buffer)"
exit 1
}
}
EXPECT
}

:orgalorg() {
tests:debug "!!! orgalorg ${@}"

Expand Down
5 changes: 3 additions & 2 deletions tests/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ssh-test:set-remote-runner :run-on-container
tests:ensure ssh-test:remote:keygen

tests:ensure containers:run "$container_name" -- \
< "$(ssh-test:print-key-path).pub" \
/usr/bin/sh -c "
useradd -G wheel $(ssh-test:print-username)
Expand All @@ -26,10 +27,10 @@ ssh-test:set-remote-runner :run-on-container
mkdir -p \\\\
/home/$(ssh-test:print-username)/.ssh
cat > /home/$(ssh-test:print-username)/.ssh/authorized_keys
chown -R \\\\
$(ssh-test:print-username): /home/$(ssh-test:print-username)"

tests:ensure ssh-test:remote:copy-id < "$(ssh-test:print-key-path).pub"
}

:start-ssh-daemon() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
passphrase="theone"

tests:ensure ssh-test:local:keygen -f "$(ssh-test:print-key-path)-encrypted" \
-b 4096 -P "$passphrase"

tests:ensure cat $(ssh-test:print-key-path)

:copy-key() {
local container_name="$1"
local container_ip="$2"

tests:ensure ssh-test:connect:by-key "$container_ip" \
'cat > ~/.ssh/authorized_keys' \
< "$(ssh-test:print-key-path)-encrypted.pub"
}

containers:do :copy-key

tests:ensure \
mv "$(ssh-test:print-key-path)-encrypted" \
"$(ssh-test:print-key-path)"

tests:eval :orgalorg:with-key-passphrase "bla-$passphrase" -C -- \
whoami

tests:assert-stdout "decryption password incorrect"

tests:ensure :orgalorg:with-key-passphrase "$passphrase" -C -- \
whoami

:check-output() {
local container_name="$1"
local container_ip="$2"

tests:assert-stdout "$container_ip $orgalorg_user"
}

containers:do :check-output

0 comments on commit e292c36

Please sign in to comment.