Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for encrypted SSH key with new OpenSSH format #476

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 5 additions & 4 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions crypto/sshkeys/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"fmt"
"strings"

"gopkg.in/errgo.v1"
"github.com/Scalingo/go-scalingo/debug"
"github.com/Scalingo/cli/term"
"github.com/Scalingo/go-scalingo/debug"
"gopkg.in/errgo.v1"
)

func (p *PrivateKey) Decrypt() error {
Expand All @@ -33,7 +33,7 @@ func (p *PrivateKey) Decrypt() error {
p.PasswordMethod = term.Password
}

password, err := p.PasswordMethod("Encrypted SSH Key, password: ")
password, err := p.PasswordMethod("Encrypted SSH key, password: ")
if err != nil {
return errgo.Mask(err)
}
Expand All @@ -43,13 +43,13 @@ func (p *PrivateKey) Decrypt() error {
key := genDES3Key(password, iv)
decryptedKey, err = decryptKey(p.Block.Bytes, iv, key, des.NewTripleDESCipher)
if err != nil {
return errgo.Newf("Key is tagged DES-ECE3-CBC, but is not: %v", err)
return errgo.Newf("key is tagged DES-ECE3-CBC, but is not: %v", err)
}
case "AES-128-CBC":
key := genAESKey(password, iv)
decryptedKey, err = decryptKey(p.Block.Bytes, iv, key, aes.NewCipher)
if err != nil {
return errgo.Newf("Key is tagged AES-128-CBC, but is not: %v", err)
return errgo.Newf("key is tagged AES-128-CBC, but is not: %v", err)
}
}
decryptedBlock := &pem.Block{}
Expand Down
3 changes: 2 additions & 1 deletion crypto/sshkeys/private_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sshkeys

import (
"encoding/pem"
"strings"

"golang.org/x/crypto/ssh"
)
Expand All @@ -21,7 +22,7 @@ func (p *PrivateKey) Signer() (ssh.Signer, error) {
}

func (p *PrivateKey) IsEncrypted() bool {
return p.Block.Headers["Proc-Type"] == "4,ENCRYPTED"
return strings.Contains(p.Block.Headers["Proc-Type"], "ENCRYPTED")
}

func (p *PrivateKey) IsCipherImplemented(cipher string) bool {
Expand Down
17 changes: 9 additions & 8 deletions crypto/sshkeys/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sshkeys
import (
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"path/filepath"
Expand All @@ -20,39 +21,39 @@ var (
func ReadPrivateKey(path string) (ssh.Signer, error) {
privateKeyContent, err := ioutil.ReadFile(path)
if err != nil {
return nil, errgo.Mask(err)
return nil, errgo.Notef(err, "fail to read the private key file")
}
return ReadPrivateKeyWithContent(path, privateKeyContent)
return readPrivateKeyWithContent(path, privateKeyContent)
}

func ReadPrivateKeyWithContent(path string, privateKeyContent []byte) (ssh.Signer, error) {
func readPrivateKeyWithContent(path string, privateKeyContent []byte) (ssh.Signer, error) {
// We parse the private key on our own first so that we can
// show a nicer error if the private key has a password.
block, _ := pem.Decode(privateKeyContent)
if block == nil {
return nil, fmt.Errorf(
"Failed to read key '%s': is not in the PEM format", path)
"failed to read key '%s': is not in the PEM format", path)
}

privateKey := &PrivateKey{Block: block, Path: path}
if privateKey.IsEncrypted() {
err := privateKey.Decrypt()
if err != nil {
return nil, errgo.Mask(err)
return nil, errgo.Notef(err, "fail to decrypt the private key")
}
}

signer, err := privateKey.Signer()
if err != nil {
if err, ok := err.(asn1.StructuralError); ok && strings.HasPrefix(err.Msg, "tags don't match") || err.Msg == "length too large" {
return nil, errgo.Newf("Fail to decrypt SSH key, invalid password.")
return nil, errors.New("fail to decrypt SSH key, invalid password")
}
if err, ok := err.(asn1.SyntaxError); ok && err.Msg == "trailing data" {
return nil, errgo.Newf("The password was OK, but something went wrong.\n" +
return nil, errors.New("The password was OK, but something went wrong.\n" +
"Please re-run the command with the environment variable DEBUG=1 " +
"and create an issue with the command output: https://github.com/Scalingo/cli/issues")
}
return nil, errgo.Newf("Invalid SSH key or password: %v", err)
return nil, errgo.Notef(err, "invalid SSH key or password")
}

return signer, nil
Expand Down
4 changes: 2 additions & 2 deletions crypto/sshkeys/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
)

func TestInvalidKey(t *testing.T) {
_, err := ReadPrivateKeyWithContent("n/a", invalidKey)
_, err := readPrivateKeyWithContent("n/a", invalidKey)
if err == nil {
t.Error("expect error, got nil")
}
Expand All @@ -31,7 +31,7 @@ func TestInvalidKey(t *testing.T) {
}

func TestUnencryptedKey(t *testing.T) {
s, err := ReadPrivateKeyWithContent("n/a", unencryptedRSAKey)
s, err := readPrivateKeyWithContent("n/a", unencryptedRSAKey)
if err != nil {
t.Error("expect nil, got", err)
}
Expand Down
4 changes: 2 additions & 2 deletions net/ssh/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func Connect(opts ConnectOpts) (*ssh.Client, ssh.Signer, error) {
var agentConnection stdio.Closer
privateKeys, agentConnection, err = sshkeys.ReadPrivateKeysFromAgent()
if err != nil {
return nil, nil, errgo.Mask(err)
return nil, nil, errgo.Notef(err, "fail to read the private key from SSH agent")
}
defer agentConnection.Close()
}
Expand All @@ -39,7 +39,7 @@ func Connect(opts ConnectOpts) (*ssh.Client, ssh.Signer, error) {
}
privateKey, err := sshkeys.ReadPrivateKey(opts.Identity)
if err != nil {
return nil, nil, errgo.Mask(err)
return nil, nil, errgo.Notef(err, "fail to read the private key")
}
privateKeys = append(privateKeys, privateKey)
}
Expand Down
11 changes: 9 additions & 2 deletions vendor/golang.org/x/crypto/cast5/cast5.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading