Merge pull request #1463 from hifi/feature/sshagent-encrypted-old-keys

SSH Agent: Support AES-128-CBC encrypted keys
This commit is contained in:
Janek Bevendorff 2018-02-11 15:41:39 +01:00 committed by GitHub
commit b143e93e5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 171 additions and 12 deletions

View File

@ -57,6 +57,7 @@ bool SymmetricCipher::isInitalized() const
SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction)
{
switch (algo) {
case Aes128:
case Aes256:
case Twofish:
case Salsa20:

View File

@ -31,6 +31,7 @@ class SymmetricCipher
public:
enum Algorithm
{
Aes128,
Aes256,
Twofish,
Salsa20,

View File

@ -37,6 +37,9 @@ SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
{
switch (algo) {
case SymmetricCipher::Aes128:
return GCRY_CIPHER_AES128;
case SymmetricCipher::Aes256:
return GCRY_CIPHER_AES256;

View File

@ -204,9 +204,10 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
rows.removeFirst();
} while (!rows.isEmpty());
if (pemOptions.contains("Proc-Type")) {
m_error = tr("Encrypted keys are not yet supported");
return false;
if (pemOptions.value("Proc-Type").compare("4,encrypted", Qt::CaseInsensitive) == 0) {
m_kdfName = "md5";
m_cipherName = pemOptions.value("DEK-Info").section(",", 0, 0);
m_cipherIV = QByteArray::fromHex(pemOptions.value("DEK-Info").section(",", 1, 1).toLatin1());
}
out = QByteArray::fromBase64(rows.join("").toLatin1());
@ -278,7 +279,7 @@ bool OpenSSHKey::parse(const QByteArray& in)
return false;
}
} else {
m_error = tr("Unsupported key type: %s").arg(m_privateType);
m_error = tr("Unsupported key type: %1").arg(m_privateType);
return false;
}
@ -308,12 +309,14 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
return false;
}
if (m_cipherName == "aes256-cbc") {
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") {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-ctr") {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
} else if (m_cipherName != "none") {
m_error = tr("Unknown cipher: %s").arg(m_cipherName);
m_error = tr("Unknown cipher: %1").arg(m_cipherName);
return false;
}
@ -355,8 +358,23 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
m_error = cipher->errorString();
return false;
}
} else if (m_kdfName == "md5") {
if (m_cipherIV.length() < 8) {
m_error = tr("Cipher IV is too short for MD5 kdf");
return false;
}
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(passphrase.toUtf8());
hash.addData(m_cipherIV.data(), 8);
QByteArray keyData = hash.result();
if (!cipher->init(keyData, m_cipherIV)) {
m_error = cipher->errorString();
return false;
}
} else if (m_kdfName != "none") {
m_error = tr("Unknown KDF: %s").arg(m_kdfName);
m_error = tr("Unknown KDF: %1").arg(m_kdfName);
return false;
}
@ -373,14 +391,14 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
if (m_privateType == TYPE_DSA) {
if (!ASN1Key::parseDSA(rawPrivateData, *this)) {
m_error = tr("Reading DSA private key failed, only unencrypted keys are supported at this time");
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
return true;
} else if (m_privateType == TYPE_RSA) {
if (!ASN1Key::parseRSA(rawPrivateData, *this)) {
m_error = tr("Reading RSA private key failed, only unencrypted keys are supported at this time");
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
@ -402,7 +420,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
return readPrivate(keyStream);
}
m_error = tr("Unsupported key type: %s").arg(m_privateType);
m_error = tr("Unsupported key type: %1").arg(m_privateType);
return false;
}
@ -425,7 +443,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream)
} else if (m_type == "ssh-ed25519") {
keyParts = 1;
} else {
m_error = tr("Unknown key type: %s").arg(m_type);
m_error = tr("Unknown key type: %1").arg(m_type);
return false;
}
@ -462,7 +480,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream)
} else if (m_type == "ssh-ed25519") {
keyParts = 2;
} else {
m_error = tr("Unknown key type: %s").arg(m_type);
m_error = tr("Unknown key type: %1").arg(m_type);
return false;
}

View File

@ -63,6 +63,7 @@ private:
QString m_type;
QString m_cipherName;
QByteArray m_cipherIV;
QString m_kdfName;
QByteArray m_kdfOptions;
QByteArray m_rawPrivateData;

View File

@ -90,6 +90,54 @@ void TestOpenSSHKey::testParseDSA()
QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18"));
}
void TestOpenSSHKey::testDecryptAES128CBC()
{
const QString keyString = QString(
"-----BEGIN RSA PRIVATE KEY-----\n"
"Proc-Type: 4,ENCRYPTED\n"
"DEK-Info: AES-128-CBC,804E4D214D1263FF94E3743FE799DBB4\n"
"\n"
"lM9TDfOTbiRhaGGDh7Hn+rqw8CCWcYBZYu7smyYLdnWKXKPmbne8CQFZBAS1FJwZ\n"
"6Mj6n075yFGyzN9/OfeqKiUA4adlbwLbGwB+yyKsC2FlsvRIEr4hup02WWM47vHj\n"
"DS4TRmNkE7MKFLhpNCyt5OGGM45s+/lwVTw51K0Hm99TBd72IrX4jfY9ZxAVbL3l\n"
"aTohL8x6oOTe7q318QgJoFi+DjJhDWLGLLJ7fBqD2imz2fmrY4j8Jpw2sDe1rj82\n"
"gMqqNG3FrfN0S4uYlWYH5pAh+BUcB1UdmTU/rV5wJMK1oUytmZv/J2+X/0k3Y93F\n"
"aw6JWOy28OizW+TQXvv8gREWsp5PEclqUZhhGQbVbCQCiDOxg+xiXNySdRH1IqjR\n"
"zQiKgD4SPzkxQekExPaIQT/KutWZdMNYybEqooCx8YyeDoN31z7Wa2rv6OulOn/j\n"
"wJFvyd2PT/6brHKI4ky8RYroDf4FbVYKfyEW5CSAg2OyL/tY/kSPgy/k0WT7fDwq\n"
"dPSuYM9yeWNL6kAhDqDOv8+s3xvOVEljktBvQvItQwVLmHszC3E2AcnaxzdblKPu\n"
"e3+mBT80NXHjERK2ht+/9JYseK1ujNbNAaG8SbKfU3FF0VlyJ0QW6TuIEdpNnymT\n"
"0fm0cDfKNaoeJIFnBRZhgIOJAic9DM0cTe/vSG69DaUYsaQPp36al7Fbux3GpFHS\n"
"OtJEySYGro/6zvJ9dDIEfIGZjA3RaMt6+DuyJZXQdT2RNXa9j60xW7dXh0En4n82\n"
"JUKTxYhDPLS5c8BzpJqoopxpKwElmrJ7Y3xpd6z2vIlD8ftuZrkk6siTMNQ2s7MI\n"
"Xl332O+0H4k7uSfczHPOOw36TFhNjGQAP0b7O+0/RVG0ttOIoAn7ZkX3nfdbtG5B\n"
"DWKvDaopvrcC2/scQ5uLUnqnBiGw1XiYpdg5ang7knHNzHZAIekVaYYZigpCAKp+\n"
"OtoaDeUEzqFhYVmF8ad1fgvC9ZUsuxS4XUHCKl0H6CJcvW9MJPVbveqYoK+j9qKd\n"
"iMIkQBP1kE2rzGZVGUkZTpM9LVD9nP0nsbr6E8BatFcNgRirsg2BTJglNpXlCmY6\n"
"ldzJ/ELBbzoXIn+0wTGai0o4eBPx55baef69JfPuZqEB9pLNE+mHstrqIwcfqYu4\n"
"M+Vzun1QshRMj9a1PVkIHfs1fLeebI4QCHO0vJlc9K4iYPM4rsDNO3YaAgGRuARS\n"
"f3McGiGFxkv5zxe8i05ZBnn+exE77jpRKxd223jAMe2wu4WiFB7ZVo4Db6b5Oo2T\n"
"TPh3VuY7TNMEKkcUi+mGLKjroocQ5j8WQYlfnyOaTalUVQDzOTNb67QIIoiszR0U\n"
"+AXGyxHj0QtotZFoPME+AbS9Zqy3SgSOuIzPBPU5zS4uoKNdD5NPE5YAuafCjsDy\n"
"MT4DVy+cPOQYUK022S7T2nsA1btmvUvD5LL2Mc8VuKsWOn/7FKZua6OCfipt6oX0\n"
"1tzYrw0/ALK+CIdVdYIiPPfxGZkr+JSLOOg7u50tpmen9GzxgNTv63miygwUAIDF\n"
"u0GbQwOueoA453/N75FcXOgrbqTdivyadUbRP+l7YJk/SfIytyJMOigejp+Z1lzF\n"
"-----END RSA PRIVATE KEY-----\n"
);
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
QVERIFY(key.parse(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("AES-128-CBC"));
QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
QVERIFY(key.openPrivateKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-rsa"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:1Hsebt2WWnmc72FERsUOgvaajIGHkrMONxXylcmk87U"));
}
void TestOpenSSHKey::testParseRSA()
{
const QString keyString = QString(

View File

@ -31,6 +31,7 @@ private slots:
void testParse();
void testParseDSA();
void testParseRSA();
void testDecryptAES128CBC();
void testDecryptAES256CBC();
void testDecryptAES256CTR();
};

View File

@ -32,6 +32,90 @@ void TestSymmetricCipher::initTestCase()
QVERIFY(Crypto::init());
}
void TestSymmetricCipher::testAes128CbcEncryption()
{
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
bool ok;
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.blockSize(), 16);
QCOMPARE(cipher.process(plainText, &ok), cipherText);
QVERIFY(ok);
QBuffer buffer;
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc,
SymmetricCipher::Encrypt);
QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::WriteOnly);
QVERIFY(stream.open(QIODevice::WriteOnly));
QVERIFY(stream.reset());
buffer.reset();
buffer.buffer().clear();
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
QCOMPARE(buffer.data(), cipherText.left(16));
QVERIFY(stream.reset());
// make sure padding is written
QCOMPARE(buffer.data().size(), 32);
buffer.reset();
buffer.buffer().clear();
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
QVERIFY(buffer.data().isEmpty());
QVERIFY(stream.reset());
buffer.reset();
buffer.buffer().clear();
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
stream.close();
QCOMPARE(buffer.data().size(), 16);
}
void TestSymmetricCipher::testAes128CbcDecryption()
{
QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c");
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d");
cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2"));
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
bool ok;
SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
QVERIFY(cipher.init(key, iv));
QCOMPARE(cipher.blockSize(), 16);
QCOMPARE(cipher.process(cipherText, &ok), plainText);
QVERIFY(ok);
// padded with 16 0x10 bytes
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538");
QBuffer buffer(&cipherTextPadded);
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc,
SymmetricCipher::Decrypt);
QVERIFY(stream.init(key, iv));
buffer.open(QIODevice::ReadOnly);
QVERIFY(stream.open(QIODevice::ReadOnly));
QCOMPARE(stream.read(10), plainText.left(10));
buffer.reset();
QVERIFY(stream.reset());
QCOMPARE(stream.read(20), plainText.left(20));
buffer.reset();
QVERIFY(stream.reset());
QCOMPARE(stream.read(16), plainText.left(16));
buffer.reset();
QVERIFY(stream.reset());
QCOMPARE(stream.read(100), plainText);
}
void TestSymmetricCipher::testAes256CbcEncryption()
{
// http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf

View File

@ -27,6 +27,8 @@ class TestSymmetricCipher : public QObject
private slots:
void initTestCase();
void testAes128CbcEncryption();
void testAes128CbcDecryption();
void testAes256CbcEncryption();
void testAes256CbcDecryption();
void testAes256CtrEncryption();