Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion ssh/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,15 @@ func (*PassphraseMissingError) Error() string {
return "ssh: this private key is passphrase protected"
}

type unsupportedCipherError struct {
BadCipher string
SupportedCiphers []string
}

func (e *unsupportedCipherError) Error() string {
return fmt.Sprintf("ssh: unknown cipher %q, only supports one of %q", e.BadCipher, strings.Join(e.SupportedCiphers, ","))
}

// ParseRawPrivateKey returns a private key from a PEM encoded private key. It supports
// RSA, DSA, ECDSA, and Ed25519 private keys in PKCS#1, PKCS#8, OpenSSL, and OpenSSH
// formats. If the private key is encrypted, it will return a PassphraseMissingError.
Expand Down Expand Up @@ -1429,7 +1438,10 @@ func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
cbc := cipher.NewCBCDecrypter(c, iv)
cbc.CryptBlocks(privKeyBlock, privKeyBlock)
default:
return nil, fmt.Errorf("ssh: unknown cipher %q, only supports %q or %q", cipherName, "aes256-ctr", "aes256-cbc")
return nil, &unsupportedCipherError{
BadCipher: cipherName,
SupportedCiphers: []string{"aes256-ctr", "aes256-cbc"},
}
}

return privKeyBlock, nil
Expand Down Expand Up @@ -1490,6 +1502,7 @@ type openSSHEncryptedPrivateKey struct {
NumKeys uint32
PubKey []byte
PrivKeyBlock []byte
Rest []byte `ssh:"rest"`
}

type openSSHPrivateKey struct {
Expand Down
16 changes: 16 additions & 0 deletions ssh/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,22 @@ func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
}
}

func TestParseEncryptedPrivateKeysWithUnsupportedCiphers(t *testing.T) {
for _, tt := range testdata.PEMEncryptedKeysForUnsupportedCiphers {
t.Run(tt.Name, func(t *testing.T) {
_, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
var e *unsupportedCipherError
if !errors.As(err, &e) {
t.Errorf("got error %v, want UnsupportedCipherError", err)
}

if e.BadCipher != tt.Cipher {
t.Errorf("got badcipher %q, wanted %q", e.BadCipher, tt.Cipher)
}
})
}
}

func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
pem := testdata.PEMEncryptedKeys[0].PEMBytes
for i := 0; i < 4096; i++ {
Expand Down
55 changes: 53 additions & 2 deletions ssh/testdata/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,15 @@ var SSHCertificates = map[string][]byte{
`),
}

var PEMEncryptedKeys = []struct {
type PEMEncryptedKey struct {
Name string
EncryptionKey string
IncludesPublicKey bool
Cipher string
PEMBytes []byte
}{
}

var PEMEncryptedKeys = []PEMEncryptedKey{
0: {
Name: "rsa-encrypted",
EncryptionKey: "r54-G0pher_t3st$",
Expand Down Expand Up @@ -310,6 +313,54 @@ gbDGyT3bXMQtagvCwoW+/oMTKXiZP5jCJpEO8=
},
}

var PEMEncryptedKeysForUnsupportedCiphers = []PEMEncryptedKey{
0: {
Name: "ed25519-encrypted-chacha20-poly1305",
EncryptionKey: "password",
IncludesPublicKey: true,
Cipher: "[email protected]",
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm
JjcnlwdAAAABgAAAAQdPyPIjXDRAVHskY0yp9SWwAAAGQAAAABAAAAMwAAAAtzc2gtZWQy
NTUxOQAAACBi6qXITEUrmNce/c2lfozxALlKH3o/6sll8G7wzl1lvQAAAJDNlW1sEkvnK0
8EecF1vHdPk85yClbh3KkHv09mbGAX/Gk6cJpYEGgJSkO7OEF4kG9DVGGd17+TZbTnM4LD
vYAJZExx2XLgJFEtHCVmJjYzwxx7yC7+s6u/XjrSlZS60RHunOPKyq+C+s48sejXvmX+t5
0ZoVCI8aftT0ycis3gvLU9sCwJ2UnF6kAV226Z4g2aLkuJbgCDTEcYCRD64K1r
-----END OPENSSH PRIVATE KEY-----
`),
},
1: {
Name: "ed25519-encrypted-aes128-gcm",
EncryptionKey: "password",
IncludesPublicKey: true,
Cipher: "[email protected]",
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
AAGAAAABBeMJIOqiyFwNCvDv6f8tQeAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
IGYpUcb3tGp9kF6pppcUdq3EPMr85BaSUdhiXGbhS5YNAAAAkNBtMEu0UlLgToThuQc+4m
/o0DfFIERu0sspQivn5RJHCtulVKfU9BMiEnF0+LOMOABMlYesgLOtoMxwm4ZCSWH54kZk
vaFyyvvxY+RLDuWNQZCryffIA4+iLCUQR1EdxMDiJweKnGJuD64a+9xTJt47A3Vq4SYzji
EuVmM0FqS8lbT2ynYSe3va0Qyw13jEO5qbtCuyG+C5GejL7kX4Z64=
-----END OPENSSH PRIVATE KEY-----
`),
},
2: {
Name: "ed25519-encrypted-aes256-gcm",
EncryptionKey: "password",
IncludesPublicKey: true,
Cipher: "[email protected]",
PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAFmFlczI1Ni1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA
AAGAAAABBR1p3vH2Wr/HPL+q20L2rjAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA
IM3tT1xrAuOHcrBdoLRo/ojWZsAw2lHfF5hJgFEOts5MAAAAkH/YGrDhDw8u+F8e4P+84B
tAzvp55Lf1Yl7y34BrVmqlWqw/7boqahOp6iYJHNpcuanzc5T6s7Z3wSSYodbY1uvFOfbj
rtP6rIHQIY5J2C40WOYJN8IkZlkwDXwZY0qoE9699ZYmWdwsXRZ7QDhjd2W8ziyZBsttiB
kv2ceuJMLT04TrKc2+RUkj4CQYnz7p8EkgZlUozx8wBSxKFGnkP7k=
-----END OPENSSH PRIVATE KEY-----
`),
},
}

// SKData contains a list of PubKeys backed by U2F/FIDO2 Security Keys and their test data.
var SKData = []struct {
Name string
Expand Down