Skip to content

Commit

Permalink
Merge branch 'release/0.8.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nbari committed Dec 20, 2016
2 parents 1fc36d6 + 28c6e95 commit 3c8fb06
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 8 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ go:

before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/keybase/go-keychain
- go get github.com/kr/pty
- go get github.com/mattn/goveralls
- go get github.com/ssh-vault/ssh2pem
- go get github.com/ssh-vault/crypto
- go get github.com/ssh-vault/crypto/aead
- go get github.com/ssh-vault/crypto/oaep
- go get github.com/ssh-vault/ssh2pem
- go get golang.org/x/crypto/ssh/terminal
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi

Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ build: get
${GO} get -u github.com/ssh-vault/crypto
${GO} get -u github.com/ssh-vault/crypto/aead
${GO} get -u github.com/ssh-vault/crypto/oaep
${GO} get -u github.com/keybase/go-keychain
${GO} get -u github.com/kr/pty
${GO} build -ldflags "-X main.version=${VERSION}" -o ${BIN_NAME} cmd/ssh-vault/main.go;

clean:
Expand Down
10 changes: 10 additions & 0 deletions get_password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build !darwin

// For platforms without managed ssh private key passwords,
// fallback to prompting the user.

package sshvault

func (v *vault) GetPassword() ([]byte, error) {
return v.GetPasswordPrompt()
}
38 changes: 38 additions & 0 deletions get_password_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// +build darwin

// Apple's OpenSSH fork uses Keychain for private key passphrases.
// They're indexed by the absolute file path to the private key,
// e.g. ~/.ssh/id_rsa
// ssh-add -K ~/.ssh/[your-private-key]
// If the passphrase isn't in keychain, prompt the user.

package sshvault

import (
"fmt"
"path/filepath"

"github.com/keybase/go-keychain"
)

func (v *vault) GetPassword() ([]byte, error) {
var keyPassword []byte

key_path, err := filepath.Abs(v.key)
if err != nil {
return nil, fmt.Errorf("Error finding private key: %s", err)
}

keyPassword, err = keychain.GetGenericPassword("SSH", key_path, "", "")
if err == nil {
return keyPassword, nil
}

// Darn, Keychain doesn't have the password for that file. Prompt the user.
keyPassword, err = v.GetPasswordPrompt()
if err != nil {
return nil, err
}

return keyPassword, nil
}
97 changes: 97 additions & 0 deletions get_password_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// +build darwin

package sshvault

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"testing"

"github.com/keybase/go-keychain"
"github.com/kr/pty"
)

func InjectKeychainPassword(path, pw string) error {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassGenericPassword)
item.SetLabel(fmt.Sprintf("SSH: %s", path))
item.SetService("SSH")
item.SetAccount(path)
item.SetData([]byte(pw))
item.SetSynchronizable(keychain.SynchronizableNo)

return keychain.AddItem(item)
}

func DeleteKeychainPassword(path string) error {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassGenericPassword)
item.SetService("SSH")
item.SetAccount(path)

return keychain.DeleteItem(item)
}

func TestKeychain(t *testing.T) {
key_pw := "argle-bargle"
key_bad_pw := "totally-bogus\n"

dir, err := ioutil.TempDir("", "vault")
if err != nil {
t.Error(err)
}
defer os.RemoveAll(dir) // clean up

tmpfile := filepath.Join(dir, "vault")

vault, err := New("test_data/id_rsa.pub", "", "create", tmpfile)
if err != nil {
t.Error(err)
}
key_path, err := filepath.Abs(vault.key)
if err != nil {
t.Errorf("Error finding private key: %s", err)
}
err = InjectKeychainPassword(key_path, key_pw)
if err != nil {
t.Errorf("Error setting up keychain for testing: %s", err)
}
defer DeleteKeychainPassword(key_path) // clean up

_, tty, err := pty.Open()
if err != nil {
t.Errorf("Unable to open pty: %s", err)
}

// File Descriptor magic. GetPasswordPrompt() reads the password
// from stdin. For the test, we save stdin to a spare FD,
// point stdin at the file, run the system under test, and
// finally restore the original stdin
old_stdin, _ := syscall.Dup(int(syscall.Stdin))
old_stdout, _ := syscall.Dup(int(syscall.Stdout))
syscall.Dup2(int(tty.Fd()), int(syscall.Stdin))
syscall.Dup2(int(tty.Fd()), int(syscall.Stdout))

// go PtyWriteback(pty, key_bad_pw)

key_pw_test, err := vault.GetPassword()

syscall.Dup2(old_stdin, int(syscall.Stdin))
syscall.Dup2(old_stdout, int(syscall.Stdout))

if err != nil {
t.Error(err)
}
if strings.Trim(string(key_pw_test), "\n") == strings.Trim(key_bad_pw, "\n") {
t.Errorf("PTY-based password prompt used, not keychain!")
}

if strings.Trim(string(key_pw_test), "\n") != strings.Trim(key_pw, "\n") {
t.Errorf("keychain error: %s expected %s, got %s\n", key_path, key_pw, key_pw_test)
}

}
18 changes: 18 additions & 0 deletions get_password_prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package sshvault

import (
"fmt"
"syscall"

"golang.org/x/crypto/ssh/terminal"
)

func (v *vault) GetPasswordPrompt() ([]byte, error) {
fmt.Printf("Enter key password (%s): ", v.key)
keyPassword, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return nil, err
}

return keyPassword, nil
}
40 changes: 40 additions & 0 deletions vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"

"github.com/kr/pty"
"github.com/ssh-vault/crypto"
"github.com/ssh-vault/crypto/aead"
)

// zomg this is a race condition
func PtyWriteback(pty *os.File, msg string) {
time.Sleep(500 * time.Millisecond)
defer pty.Sync()
pty.Write([]byte(msg))
}

// These are done in one function to avoid declaring global variables in a test
// file.
func TestVaultFunctions(t *testing.T) {
Expand All @@ -30,6 +41,35 @@ func TestVaultFunctions(t *testing.T) {
t.Error(err)
}

key_pw := string("argle-bargle\n")
pty, tty, err := pty.Open()
if err != nil {
t.Errorf("Unable to open pty: %s", err)
}

// File Descriptor magic. GetPasswordPrompt() reads the password
// from stdin. For the test, we save stdin to a spare FD,
// point stdin at the file, run the system under test, and
// finally restore the original stdin
old_stdin, _ := syscall.Dup(int(syscall.Stdin))
old_stdout, _ := syscall.Dup(int(syscall.Stdout))
syscall.Dup2(int(tty.Fd()), int(syscall.Stdin))
syscall.Dup2(int(tty.Fd()), int(syscall.Stdout))

go PtyWriteback(pty, key_pw)

key_pw_test, err := vault.GetPasswordPrompt()

syscall.Dup2(old_stdin, int(syscall.Stdin))
syscall.Dup2(old_stdout, int(syscall.Stdout))

if err != nil {
t.Error(err)
}
if string(strings.Trim(key_pw, "\n")) != string(key_pw_test) {
t.Errorf("password prompt: expected %s, got %s\n", key_pw, key_pw_test)
}

if err = vault.PKCS8(); err != nil {
t.Error(err)
}
Expand Down
10 changes: 3 additions & 7 deletions view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import (
"io/ioutil"
"os"
"strings"
"syscall"

"github.com/ssh-vault/crypto/aead"
"github.com/ssh-vault/crypto/oaep"

"golang.org/x/crypto/ssh/terminal"
)

// View decrypts data and print it to stdout
Expand Down Expand Up @@ -64,12 +61,11 @@ func (v *vault) View() ([]byte, error) {
}

if x509.IsEncryptedPEMBlock(block) {
fmt.Print("Enter key password: ")
keyPassword, err := terminal.ReadPassword(int(syscall.Stdin))
keyPassword, err := v.GetPassword()
if err != nil {
return nil, err
return nil, fmt.Errorf("Unable to get private key password, Decryption failed.")
}
fmt.Println()

block.Bytes, err = x509.DecryptPEMBlock(block, keyPassword)
if err != nil {
return nil, fmt.Errorf("Password incorrect, Decryption failed.")
Expand Down

0 comments on commit 3c8fb06

Please sign in to comment.