Skip to content

Commit

Permalink
ssh: add ParsePrivateKeysWithPassphrase
Browse files Browse the repository at this point in the history
ssh package doesn't provide way to parse private keys with passphrase.

Fixes golang/go#18692

Change-Id: Ic139f11b6dfe7ef61690d6125e0673d50a48db16
Reviewed-on: https://go-review.googlesource.com/36079
Run-TryBot: Han-Wen Nienhuys <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Han-Wen Nienhuys <[email protected]>
  • Loading branch information
mattn authored and hanwen committed Jun 13, 2017
1 parent 8a5a3ff commit fe8c4bc
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 0 deletions.
43 changes: 43 additions & 0 deletions ssh/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,18 @@ func ParsePrivateKey(pemBytes []byte) (Signer, error) {
return NewSignerFromKey(key)
}

// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private
// key and passphrase. It supports the same keys as
// ParseRawPrivateKeyWithPassphrase.
func ParsePrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (Signer, error) {
key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase)
if err != nil {
return nil, err
}

return NewSignerFromKey(key)
}

// encryptedBlock tells whether a private key is
// encrypted by examining its Proc-Type header
// for a mention of ENCRYPTED
Expand Down Expand Up @@ -790,6 +802,37 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
}
}

func ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (interface{}, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("ssh: no key found")
}
buf := block.Bytes

if encryptedBlock(block) {
if x509.IsEncryptedPEMBlock(block) {
var err error
buf, err = x509.DecryptPEMBlock(block, passPhrase)
if err != nil {
return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err)
}
}
}

switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(buf)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(buf)
case "DSA PRIVATE KEY":
return ParseDSAPrivateKey(buf)
case "OPENSSH PRIVATE KEY":
return parseOpenSSHPrivateKey(buf)
default:
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
}
}

// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
// specified by the OpenSSL DSA man page.
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
Expand Down
19 changes: 19 additions & 0 deletions ssh/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,25 @@ func TestParseEncryptedPrivateKeysFails(t *testing.T) {
}
}

// Parse encrypted private keys with passphrase
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
data := []byte("sign me")
for _, tt := range testdata.PEMEncryptedKeys {
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
if err != nil {
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
continue
}
sig, err := s.Sign(rand.Reader, data)
if err != nil {
t.Fatalf("dsa.Sign: %v", err)
}
if err := s.PublicKey().Verify(data, sig); err != nil {
t.Errorf("Verify failed: %v", err)
}
}
}

func TestParseDSA(t *testing.T) {
// We actually exercise the ParsePrivateKey codepath here, as opposed to
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
Expand Down

0 comments on commit fe8c4bc

Please sign in to comment.