@@ -7,6 +7,8 @@ package ssh
7
7
import (
8
8
"bytes"
9
9
"crypto"
10
+ "crypto/aes"
11
+ "crypto/cipher"
10
12
"crypto/dsa"
11
13
"crypto/ecdsa"
12
14
"crypto/elliptic"
@@ -25,6 +27,7 @@ import (
25
27
"strings"
26
28
27
29
"golang.org/x/crypto/ed25519"
30
+ "golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
28
31
)
29
32
30
33
// These constants represent the algorithm names for key types supported by this
@@ -904,21 +907,25 @@ func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
904
907
case "DSA PRIVATE KEY" :
905
908
return ParseDSAPrivateKey (block .Bytes )
906
909
case "OPENSSH PRIVATE KEY" :
907
- return parseOpenSSHPrivateKey (block .Bytes )
910
+ return parseOpenSSHPrivateKey (block .Bytes , unencryptedOpenSSHKey )
908
911
default :
909
912
return nil , fmt .Errorf ("ssh: unsupported key type %q" , block .Type )
910
913
}
911
914
}
912
915
913
916
// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with
914
- // passphrase from a PEM encoded private key. If wrong passphrase, return
915
- // x509.IncorrectPasswordError.
917
+ // passphrase from a PEM encoded private key. If the passphrase is wrong, it
918
+ // will return x509.IncorrectPasswordError.
916
919
func ParseRawPrivateKeyWithPassphrase (pemBytes , passphrase []byte ) (interface {}, error ) {
917
920
block , _ := pem .Decode (pemBytes )
918
921
if block == nil {
919
922
return nil , errors .New ("ssh: no key found" )
920
923
}
921
924
925
+ if block .Type == "OPENSSH PRIVATE KEY" {
926
+ return parseOpenSSHPrivateKey (block .Bytes , passphraseProtectedOpenSSHKey (passphrase ))
927
+ }
928
+
922
929
if ! encryptedBlock (block ) || ! x509 .IsEncryptedPEMBlock (block ) {
923
930
return nil , errors .New ("ssh: not an encrypted key" )
924
931
}
@@ -975,9 +982,60 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
975
982
}, nil
976
983
}
977
984
978
- // Implemented based on the documentation at
979
- // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
980
- func parseOpenSSHPrivateKey (key []byte ) (crypto.PrivateKey , error ) {
985
+ func unencryptedOpenSSHKey (CipherName , KdfName , KdfOpts string , PrivKeyBlock []byte ) ([]byte , error ) {
986
+ if KdfName != "none" || CipherName != "none" {
987
+ return nil , & PassphraseNeededError {}
988
+ }
989
+ if KdfOpts != "" {
990
+ return nil , errors .New ("ssh: invalid openssh private key" )
991
+ }
992
+ return PrivKeyBlock , nil
993
+ }
994
+
995
+ func passphraseProtectedOpenSSHKey (passphrase []byte ) openSSHDecryptFunc {
996
+ return func (CipherName , KdfName , KdfOpts string , PrivKeyBlock []byte ) ([]byte , error ) {
997
+ if KdfName == "none" || CipherName == "none" {
998
+ return nil , errors .New ("ssh: key is not password protected" )
999
+ }
1000
+ if KdfName != "bcrypt" {
1001
+ return nil , errors .New ("ssh: unknown KDF: " + KdfName )
1002
+ }
1003
+
1004
+ var kdfOpts struct {
1005
+ Salt string
1006
+ Rounds uint32
1007
+ }
1008
+ if err := Unmarshal ([]byte (KdfOpts ), & kdfOpts ); err != nil {
1009
+ return nil , err
1010
+ }
1011
+
1012
+ k , err := bcrypt_pbkdf .Key (passphrase , []byte (kdfOpts .Salt ), int (kdfOpts .Rounds ), 32 + 16 )
1013
+ if err != nil {
1014
+ return nil , err
1015
+ }
1016
+ key , iv := k [:32 ], k [32 :]
1017
+
1018
+ if CipherName != "aes256-ctr" {
1019
+ return nil , errors .New ("ssh: unknown cipher: " + CipherName )
1020
+ }
1021
+ c , err := aes .NewCipher (key )
1022
+ if err != nil {
1023
+ return nil , err
1024
+ }
1025
+ ctr := cipher .NewCTR (c , iv )
1026
+ ctr .XORKeyStream (PrivKeyBlock , PrivKeyBlock )
1027
+
1028
+ return PrivKeyBlock , nil
1029
+ }
1030
+ }
1031
+
1032
+ type openSSHDecryptFunc func (CipherName , KdfName , KdfOpts string , PrivKeyBlock []byte ) ([]byte , error )
1033
+
1034
+ // parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt
1035
+ // function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used
1036
+ // as the decrypt function to parse an unencrypted private key. See
1037
+ // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
1038
+ func parseOpenSSHPrivateKey (key []byte , decrypt openSSHDecryptFunc ) (crypto.PrivateKey , error ) {
981
1039
const magic = "openssh-key-v1\x00 "
982
1040
if len (key ) < len (magic ) || string (key [:len (magic )]) != magic {
983
1041
return nil , errors .New ("ssh: invalid openssh private key format" )
@@ -996,9 +1054,22 @@ func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
996
1054
if err := Unmarshal (remaining , & w ); err != nil {
997
1055
return nil , err
998
1056
}
1057
+ if w .NumKeys != 1 {
1058
+ // We only support single key files, and so does OpenSSH.
1059
+ // https://github.com/openssh/openssh-portable/blob/4103a3ec7/sshkey.c#L4171
1060
+ return nil , errors .New ("ssh: multi-key files are not supported" )
1061
+ }
999
1062
1000
- if w .KdfName != "none" || w .CipherName != "none" {
1001
- return nil , errors .New ("ssh: cannot decode encrypted private keys" )
1063
+ privKeyBlock , err := decrypt (w .CipherName , w .KdfName , w .KdfOpts , w .PrivKeyBlock )
1064
+ if err != nil {
1065
+ if err , ok := err .(* PassphraseNeededError ); ok {
1066
+ pub , errPub := ParsePublicKey (w .PubKey )
1067
+ if errPub != nil {
1068
+ return nil , fmt .Errorf ("ssh: failed to parse embedded public key: %v" , errPub )
1069
+ }
1070
+ err .PublicKey = pub
1071
+ }
1072
+ return nil , err
1002
1073
}
1003
1074
1004
1075
pk1 := struct {
@@ -1008,12 +1079,11 @@ func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
1008
1079
Rest []byte `ssh:"rest"`
1009
1080
}{}
1010
1081
1011
- if err := Unmarshal (w .PrivKeyBlock , & pk1 ); err != nil {
1012
- return nil , err
1013
- }
1014
-
1015
- if pk1 .Check1 != pk1 .Check2 {
1016
- return nil , errors .New ("ssh: checkint mismatch" )
1082
+ if err := Unmarshal (privKeyBlock , & pk1 ); err != nil || pk1 .Check1 != pk1 .Check2 {
1083
+ if w .CipherName != "none" {
1084
+ return nil , x509 .IncorrectPasswordError
1085
+ }
1086
+ return nil , errors .New ("ssh: malformed OpenSSH key" )
1017
1087
}
1018
1088
1019
1089
// we only handle ed25519 and rsa keys currently
@@ -1035,10 +1105,8 @@ func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
1035
1105
return nil , err
1036
1106
}
1037
1107
1038
- for i , b := range key .Pad {
1039
- if int (b ) != i + 1 {
1040
- return nil , errors .New ("ssh: padding not as expected" )
1041
- }
1108
+ if err := checkOpenSSHKeyPadding (key .Pad ); err != nil {
1109
+ return nil , err
1042
1110
}
1043
1111
1044
1112
pk := & rsa.PrivateKey {
@@ -1073,10 +1141,8 @@ func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
1073
1141
return nil , errors .New ("ssh: private key unexpected length" )
1074
1142
}
1075
1143
1076
- for i , b := range key .Pad {
1077
- if int (b ) != i + 1 {
1078
- return nil , errors .New ("ssh: padding not as expected" )
1079
- }
1144
+ if err := checkOpenSSHKeyPadding (key .Pad ); err != nil {
1145
+ return nil , err
1080
1146
}
1081
1147
1082
1148
pk := ed25519 .PrivateKey (make ([]byte , ed25519 .PrivateKeySize ))
@@ -1087,6 +1153,15 @@ func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) {
1087
1153
}
1088
1154
}
1089
1155
1156
+ func checkOpenSSHKeyPadding (pad []byte ) error {
1157
+ for i , b := range pad {
1158
+ if int (b ) != i + 1 {
1159
+ return errors .New ("ssh: padding not as expected" )
1160
+ }
1161
+ }
1162
+ return nil
1163
+ }
1164
+
1090
1165
// FingerprintLegacyMD5 returns the user presentation of the key's
1091
1166
// fingerprint as described by RFC 4716 section 4.
1092
1167
func FingerprintLegacyMD5 (pubKey PublicKey ) string {
0 commit comments