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

OpenSSHKey: correctly parse aes-256-cbc/ctr keys #1682

Merged
merged 2 commits into from
Apr 5, 2018

Conversation

tycho
Copy link
Contributor

@tycho tycho commented Mar 8, 2018

Description

The code for parsing OpenSSH private key files is broken for aes-256-cbc and aes-256-ctr encrypted keys in PEM format.

Motivation and context

... to correctly load encrypted SSH keys.

How has this been tested?

Not fully tested yet.

Types of changes

  • ✅ Bug fix (non-breaking change which fixes an issue)

Checklist:

  • ✅ I have read the CONTRIBUTING document. [REQUIRED]
  • ✅ My code follows the code style of this project. [REQUIRED]
  • ✅ All new and existing tests passed. [REQUIRED]
  • ✅ I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
  • ✅ I have added tests to cover my changes.

@tycho
Copy link
Contributor Author

tycho commented Mar 8, 2018

There's some work to be done to fix the tests, working on that now.

@tycho tycho force-pushed the ssh-key-parse-fix branch from ecf3a7f to 7322148 Compare March 8, 2018 05:53
@tycho
Copy link
Contributor Author

tycho commented Mar 8, 2018

I've updated the tests to include an AES-256-CBC and AES-256-CTR key, as emitted by openssl rsa -aes-256-cbc and openssl rsa -aes-256-ctr. However, they fail to decrypt properly with KeePassXC for reasons unknown to me. OpenSSL doesn't have any trouble opening/decrypting them, but KeePassXC is doing something funny:

$ ninja testopensshkey && tests/testopensshkey
[11/11] Linking CXX executable tests/testopensshkey
********* Start testing of TestOpenSSHKey *********
Config: Using QtTest library 5.10.1, Qt 5.10.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 7.3.0)
PASS   : TestOpenSSHKey::initTestCase()
PASS   : TestOpenSSHKey::testParse()
PASS   : TestOpenSSHKey::testParseDSA()
PASS   : TestOpenSSHKey::testParseRSA()
PASS   : TestOpenSSHKey::testDecryptAES128CBC()
PASS   : TestOpenSSHKey::testDecryptAES256CBC()
FAIL!  : TestOpenSSHKey::testDecryptPemAES256CBC() 'key.openPrivateKey("correctpassphrase")' returned FALSE. ()
   Loc: [../tests/TestOpenSSHKey.cpp(260)]
PASS   : TestOpenSSHKey::testDecryptAES256CTR()
FAIL!  : TestOpenSSHKey::testDecryptPemAES256CTR() 'key.openPrivateKey("correctpassphrase")' returned FALSE. ()
   Loc: [../tests/TestOpenSSHKey.cpp(342)]
PASS   : TestOpenSSHKey::cleanupTestCase()
Totals: 8 passed, 2 failed, 0 skipped, 0 blacklisted, 782ms
********* Finished testing of TestOpenSSHKey *********

@tycho
Copy link
Contributor Author

tycho commented Mar 8, 2018

If anyone can tell me why it's not able to decrypt those two encrypted RSA keys in the two new tests, that'd be great. OpenSSL doesn't have any trouble with them, and I'm not sure what's going wrong in KeePassXC.

@tycho tycho force-pushed the ssh-key-parse-fix branch from 7322148 to c2d8886 Compare March 9, 2018 00:01
@tycho
Copy link
Contributor Author

tycho commented Mar 9, 2018

Found the problem. It was a key size issue. We were using a 16-byte key and not filling out the full 32-byte width required for AES256.

@hifi hifi removed the needs work label Mar 9, 2018
QByteArray keyData = hash.result();
QByteArray keyData;
QByteArray mdBuf;
mdBuf.resize(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Freshly constructed QByteArray is zero length so this would be superfluous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True enough. I'll drop that.

@@ -90,39 +90,39 @@ void TestOpenSSHKey::testParseDSA()
QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18"));
}

void TestOpenSSHKey::testDecryptAES128CBC()
void TestOpenSSHKey::testDecryptRSAAES128CBC()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nitpicking on something I started but maybe this naming scheme doesn't work any more. I'm thinking of using the KDF rather than "RSA" or "PEM". So "MD5" in this case and "BCrypt" for encrypted OpenSSH keys. Not really important anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming scheme made me cringe a bit as I was writing it. But there is a distinction to be made between RSA/OpenSSH key files.

@tycho tycho force-pushed the ssh-key-parse-fix branch from c2d8886 to 5e33c2c Compare March 9, 2018 05:45
@hifi
Copy link
Member

hifi commented Mar 9, 2018

Looks good to me. Could you please rebase this against release/2.3.2? Thanks.

@tycho tycho force-pushed the ssh-key-parse-fix branch from 5e33c2c to 8b7102d Compare March 9, 2018 16:27
@tycho
Copy link
Contributor Author

tycho commented Mar 9, 2018

Done!

@tycho tycho changed the base branch from develop to release/2.3.2 March 9, 2018 16:30
QByteArray keyData = hash.result();
QByteArray keyData;
QByteArray mdBuf;
for (;;) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use a do/while loop instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really nitpicky.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but it's ensuring we are keeping a consistent style. And TBH, an empty for loop header is just extremely ugly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, but I'm not sure it looks any better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. Thank you very much.

cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-ctr") {
} else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use comparison operator with the first one but compare()with the second one?

Copy link
Contributor Author

@tycho tycho Mar 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably just make it a comparison operator, but the .compare is copying the behavior used for the aes-128-cbc above this.

With the OpenSSH key type it is presumably fixed lower case while with encrypted RSA PEMs it could be upper or lower case? I don't think I've ever seen an encrypted RSA PEM with DEK-Info parameter of aes-128-cbc in lower case, but it seems this code was already prepared for that possibility. Could just move them both to == operators.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare is used for case-insensitive comparison. == cannot do that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DEK-Info cipher was normalized to lowercase as the OpenSSH key types are lowercase as well.

@@ -319,9 +319,9 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)

if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-cbc") {
} else if (m_cipherName == "aes256-cbc" || m_cipherName.compare("aes-256-cbc", Qt::CaseInsensitive) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same == versus .compare() here.

@tycho tycho force-pushed the ssh-key-parse-fix branch 2 times, most recently from 996a5e8 to a5e607b Compare March 10, 2018 18:52
@droidmonkey droidmonkey added this to the v2.3.2 milestone Mar 11, 2018
@tycho tycho force-pushed the ssh-key-parse-fix branch from a5e607b to e0d9918 Compare March 18, 2018 08:28
tycho added 2 commits March 25, 2018 00:08
AES-256 uses a 32-byte (256-bit) key size. This un-breaks the loader and
tests added for AES-256-CBC and AES-256-CTR PEM keys.

Signed-off-by: Steven Noonan <[email protected]>
@tycho tycho force-pushed the ssh-key-parse-fix branch from e0d9918 to 2202f07 Compare March 25, 2018 07:09
@droidmonkey
Copy link
Member

Ping @tycho @phoerious @hifi is this ready for merge?

@tycho
Copy link
Contributor Author

tycho commented Apr 5, 2018

I believe it's been ready for some time. Just waiting on someone to sign off on it...

@droidmonkey droidmonkey dismissed phoerious’s stale review April 5, 2018 01:57

changes were made

@droidmonkey droidmonkey merged commit c21f4b5 into keepassxreboot:release/2.3.2 Apr 5, 2018
@tycho tycho deleted the ssh-key-parse-fix branch April 5, 2018 02:03
droidmonkey added a commit that referenced this pull request May 8, 2018
- Enable high entropy ASLR on Windows [#1747]
- Enhance favicon fetching [#1786]
- Fix crash on Windows due to autotype [#1691]
- Fix dark tray icon changing all icons [#1680]
- Fix --pw-stdin not using getPassword function [#1686]
- Fix placeholders being resolved in notes [#1907]
- Enable auto-type start delay to be configurable [#1908]
- Browser: Fix native messaging reply size [#1719]
- Browser: Increase maximum buffer size [#1720]
- Browser: Enhance usability and functionality [#1810, #1822, #1830, #1884, #1906]
- SSH Agent: Parse aes-256-cbc/ctr keys [#1682]
- SSH Agent: Enhance usability and functionality [#1677, #1679, #1681, #1787]
jtl999 pushed a commit to jtl999/keepassxc that referenced this pull request Jul 29, 2018
AES-256 uses a 32-byte (256-bit) key size. This un-breaks the loader and
tests added for AES-256-CBC and AES-256-CTR PEM keys.

* OpenSSHKey: correctly parse encrypted PEM AES-256-CBC/AES-256-CTR keys
* OpenSSHKey: use correct key derivation for AES-256
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants