Skip to content

Commit fe8c4bc

Browse files
mattnhanwen
authored andcommitted
ssh: add ParsePrivateKeysWithPassphrase
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]>
1 parent 8a5a3ff commit fe8c4bc

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

ssh/keys.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,18 @@ func ParsePrivateKey(pemBytes []byte) (Signer, error) {
756756
return NewSignerFromKey(key)
757757
}
758758

759+
// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private
760+
// key and passphrase. It supports the same keys as
761+
// ParseRawPrivateKeyWithPassphrase.
762+
func ParsePrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (Signer, error) {
763+
key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase)
764+
if err != nil {
765+
return nil, err
766+
}
767+
768+
return NewSignerFromKey(key)
769+
}
770+
759771
// encryptedBlock tells whether a private key is
760772
// encrypted by examining its Proc-Type header
761773
// for a mention of ENCRYPTED
@@ -790,6 +802,37 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
790802
}
791803
}
792804

805+
func ParseRawPrivateKeyWithPassphrase(pemBytes, passPhrase []byte) (interface{}, error) {
806+
block, _ := pem.Decode(pemBytes)
807+
if block == nil {
808+
return nil, errors.New("ssh: no key found")
809+
}
810+
buf := block.Bytes
811+
812+
if encryptedBlock(block) {
813+
if x509.IsEncryptedPEMBlock(block) {
814+
var err error
815+
buf, err = x509.DecryptPEMBlock(block, passPhrase)
816+
if err != nil {
817+
return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err)
818+
}
819+
}
820+
}
821+
822+
switch block.Type {
823+
case "RSA PRIVATE KEY":
824+
return x509.ParsePKCS1PrivateKey(buf)
825+
case "EC PRIVATE KEY":
826+
return x509.ParseECPrivateKey(buf)
827+
case "DSA PRIVATE KEY":
828+
return ParseDSAPrivateKey(buf)
829+
case "OPENSSH PRIVATE KEY":
830+
return parseOpenSSHPrivateKey(buf)
831+
default:
832+
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
833+
}
834+
}
835+
793836
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
794837
// specified by the OpenSSL DSA man page.
795838
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {

ssh/keys_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ func TestParseEncryptedPrivateKeysFails(t *testing.T) {
148148
}
149149
}
150150

151+
// Parse encrypted private keys with passphrase
152+
func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
153+
data := []byte("sign me")
154+
for _, tt := range testdata.PEMEncryptedKeys {
155+
s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
156+
if err != nil {
157+
t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
158+
continue
159+
}
160+
sig, err := s.Sign(rand.Reader, data)
161+
if err != nil {
162+
t.Fatalf("dsa.Sign: %v", err)
163+
}
164+
if err := s.PublicKey().Verify(data, sig); err != nil {
165+
t.Errorf("Verify failed: %v", err)
166+
}
167+
}
168+
}
169+
151170
func TestParseDSA(t *testing.T) {
152171
// We actually exercise the ParsePrivateKey codepath here, as opposed to
153172
// using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go

0 commit comments

Comments
 (0)