diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 1ec8a2cf6..1ba42a537 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -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: diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index eab834956..0c683d224 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -31,6 +31,7 @@ class SymmetricCipher public: enum Algorithm { + Aes128, Aes256, Twofish, Salsa20, diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index b1abd5250..97d53cd83 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -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; diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp index 808408ab6..ce867a95f 100644 --- a/src/sshagent/OpenSSHKey.cpp +++ b/src/sshagent/OpenSSHKey.cpp @@ -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; } diff --git a/src/sshagent/OpenSSHKey.h b/src/sshagent/OpenSSHKey.h index 539d01892..e06af2201 100644 --- a/src/sshagent/OpenSSHKey.h +++ b/src/sshagent/OpenSSHKey.h @@ -63,6 +63,7 @@ private: QString m_type; QString m_cipherName; + QByteArray m_cipherIV; QString m_kdfName; QByteArray m_kdfOptions; QByteArray m_rawPrivateData; diff --git a/tests/TestOpenSSHKey.cpp b/tests/TestOpenSSHKey.cpp index 7f94365e7..8ac129866 100644 --- a/tests/TestOpenSSHKey.cpp +++ b/tests/TestOpenSSHKey.cpp @@ -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( diff --git a/tests/TestOpenSSHKey.h b/tests/TestOpenSSHKey.h index 6c2041074..5d2724410 100644 --- a/tests/TestOpenSSHKey.h +++ b/tests/TestOpenSSHKey.h @@ -31,6 +31,7 @@ private slots: void testParse(); void testParseDSA(); void testParseRSA(); + void testDecryptAES128CBC(); void testDecryptAES256CBC(); void testDecryptAES256CTR(); }; diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index f1e7c0e06..a5159e0d6 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -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 diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 40e3b49cf..9b82fd88a 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -27,6 +27,8 @@ class TestSymmetricCipher : public QObject private slots: void initTestCase(); + void testAes128CbcEncryption(); + void testAes128CbcDecryption(); void testAes256CbcEncryption(); void testAes256CbcDecryption(); void testAes256CtrEncryption();