From cbb70cdc7c714354a9f5af2cf76875dc48e3978a Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Thu, 1 Feb 2018 19:00:35 +0200 Subject: [PATCH] SSH Agent: Support old unencrypted DSA and RSA keys --- src/gui/entry/EditEntryWidget.cpp | 4 + src/sshagent/ASN1Key.cpp | 186 ++++++++++++++++++++++++++++++ src/sshagent/ASN1Key.h | 31 +++++ src/sshagent/CMakeLists.txt | 1 + src/sshagent/OpenSSHKey.cpp | 162 +++++++++++++++++--------- src/sshagent/OpenSSHKey.h | 5 + src/sshagent/SSHAgent.cpp | 4 + tests/TestOpenSSHKey.cpp | 72 ++++++++++++ tests/TestOpenSSHKey.h | 2 + 9 files changed, 413 insertions(+), 54 deletions(-) create mode 100644 src/sshagent/ASN1Key.cpp create mode 100644 src/sshagent/ASN1Key.h diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index eef1b5ed8..c146da691 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -444,6 +444,10 @@ bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key) return false; } + if (key.comment().isEmpty()) { + key.setComment(m_entry->username()); + } + return true; } diff --git a/src/sshagent/ASN1Key.cpp b/src/sshagent/ASN1Key.cpp new file mode 100644 index 000000000..c46b8fa0b --- /dev/null +++ b/src/sshagent/ASN1Key.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2018 Toni Spets + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ASN1Key.h" +#include + +namespace { + constexpr quint8 TAG_INT = 0x02; + constexpr quint8 TAG_SEQUENCE = 0x30; + constexpr quint8 KEY_ZERO = 0x0; + + bool nextTag(BinaryStream& stream, quint8& tag, quint32& len) + { + stream.read(tag); + + quint8 lenByte; + stream.read(lenByte); + + if (lenByte & 0x80) { + quint32 bytes = lenByte & ~0x80; + if (bytes == 1) { + stream.read(lenByte); + len = lenByte; + } else if (bytes == 2) { + quint16 lenShort; + stream.read(lenShort); + len = lenShort; + } else if (bytes == 4) { + stream.read(len); + } else { + return false; + } + } else { + len = lenByte; + } + + return true; + } + + bool parseHeader(BinaryStream& stream, quint8 wantedType) + { + quint8 tag; + quint32 len; + + nextTag(stream, tag, len); + + if (tag != TAG_SEQUENCE) { + return false; + } + + nextTag(stream, tag, len); + + if (tag != TAG_INT || len != 1) { + return false; + } + + quint8 keyType; + stream.read(keyType); + + return (keyType == wantedType); + } + + bool readInt(BinaryStream& stream, QByteArray& target) + { + quint8 tag; + quint32 len; + + nextTag(stream, tag, len); + + if (tag != TAG_INT) { + return false; + } + + target.resize(len); + stream.read(target); + return true; + } + + QByteArray calculateIqmp(QByteArray& bap, QByteArray& baq) + { + gcry_mpi_t u, p, q; + QByteArray iqmp_hex; + + u = gcry_mpi_snew(bap.length() * 8); + gcry_mpi_scan(&p, GCRYMPI_FMT_HEX, bap.toHex().data(), 0, nullptr); + gcry_mpi_scan(&q, GCRYMPI_FMT_HEX, baq.toHex().data(), 0, nullptr); + + mpi_invm(u, q, p); + + iqmp_hex.resize((bap.length() + 1) * 2); + gcry_mpi_print(GCRYMPI_FMT_HEX, reinterpret_cast(iqmp_hex.data()), iqmp_hex.length(), nullptr, u); + + gcry_mpi_release(u); + gcry_mpi_release(p); + gcry_mpi_release(q); + + return QByteArray::fromHex(iqmp_hex); + } +} + +bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key) +{ + BinaryStream stream(&ba); + + if (!parseHeader(stream, KEY_ZERO)) { + return false; + } + + QByteArray p,q,g,y,x; + readInt(stream, p); + readInt(stream, q); + readInt(stream, g); + readInt(stream, y); + readInt(stream, x); + + QList publicData; + publicData.append(p); + publicData.append(q); + publicData.append(g); + publicData.append(y); + + QList privateData; + privateData.append(p); + privateData.append(q); + privateData.append(g); + privateData.append(y); + privateData.append(x); + + key.setType("ssh-dss"); + key.setPublicData(publicData); + key.setPrivateData(privateData); + key.setComment(""); + return true; +} + +bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key) +{ + BinaryStream stream(&ba); + + if (!parseHeader(stream, KEY_ZERO)) { + return false; + } + + QByteArray n,e,d,p,q,dp,dq,qinv; + readInt(stream, n); + readInt(stream, e); + readInt(stream, d); + readInt(stream, p); + readInt(stream, q); + readInt(stream, dp); + readInt(stream, dq); + readInt(stream, qinv); + + QList publicData; + publicData.append(e); + publicData.append(n); + + QList privateData; + privateData.append(n); + privateData.append(e); + privateData.append(d); + privateData.append(calculateIqmp(p, q)); + privateData.append(p); + privateData.append(q); + + key.setType("ssh-rsa"); + key.setPublicData(publicData); + key.setPrivateData(privateData); + key.setComment(""); + return true; +} diff --git a/src/sshagent/ASN1Key.h b/src/sshagent/ASN1Key.h new file mode 100644 index 000000000..59f8d4e81 --- /dev/null +++ b/src/sshagent/ASN1Key.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 Toni Spets + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ASN1KEY_H +#define ASN1KEY_H + +#include "OpenSSHKey.h" +#include + +namespace ASN1Key +{ + bool parseDSA(QByteArray& ba, OpenSSHKey& key); + bool parseRSA(QByteArray& ba, OpenSSHKey& key); +} + +#endif // ASN1KEY_H diff --git a/src/sshagent/CMakeLists.txt b/src/sshagent/CMakeLists.txt index 5d2ad6e3b..a612ff076 100644 --- a/src/sshagent/CMakeLists.txt +++ b/src/sshagent/CMakeLists.txt @@ -9,6 +9,7 @@ if(WITH_XC_SSHAGENT) BinaryStream.cpp KeeAgentSettings.cpp OpenSSHKey.cpp + ASN1Key.cpp SSHAgent.cpp ) diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp index 2c51ee459..808408ab6 100644 --- a/src/sshagent/OpenSSHKey.cpp +++ b/src/sshagent/OpenSSHKey.cpp @@ -17,11 +17,16 @@ */ #include "OpenSSHKey.h" +#include "ASN1Key.h" #include #include #include #include "crypto/SymmetricCipher.h" +const QString OpenSSHKey::TYPE_DSA = "DSA PRIVATE KEY"; +const QString OpenSSHKey::TYPE_RSA = "RSA PRIVATE KEY"; +const QString OpenSSHKey::TYPE_OPENSSH = "OPENSSH PRIVATE KEY"; + // bcrypt_pbkdf.cpp int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds); @@ -34,6 +39,7 @@ OpenSSHKey::OpenSSHKey(QObject *parent) , m_rawPrivateData(QByteArray()) , m_publicData(QList()) , m_privateData(QList()) + , m_privateType(QString()) , m_comment(QString()) , m_error(QString()) { @@ -178,14 +184,31 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out) return false; } - if (beginMatch.captured(1) != "OPENSSH PRIVATE KEY") { - m_error = tr("This is not an OpenSSH key, only modern keys are supported"); - return false; - } + m_privateType = beginMatch.captured(1); rows.removeFirst(); rows.removeLast(); + QRegularExpression keyValueExpr = QRegularExpression("^([A-Za-z0-9-]+): (.+)$"); + QMap pemOptions; + + do { + QRegularExpressionMatch keyValueMatch = keyValueExpr.match(rows.first()); + + if (!keyValueMatch.hasMatch()) { + break; + } + + pemOptions.insert(keyValueMatch.captured(1), keyValueMatch.captured(2)); + + rows.removeFirst(); + } while (!rows.isEmpty()); + + if (pemOptions.contains("Proc-Type")) { + m_error = tr("Encrypted keys are not yet supported"); + return false; + } + out = QByteArray::fromBase64(rows.join("").toLatin1()); if (out.isEmpty()) { @@ -204,51 +227,58 @@ bool OpenSSHKey::parse(const QByteArray& in) return false; } - BinaryStream stream(&data); + if (m_privateType == TYPE_DSA || m_privateType == TYPE_RSA) { + m_rawPrivateData = data; + } else if (m_privateType == TYPE_OPENSSH) { + BinaryStream stream(&data); - QByteArray magic; - magic.resize(15); + QByteArray magic; + magic.resize(15); - if (!stream.read(magic)) { - m_error = tr("Key file way too small."); - return false; - } - - if (QString::fromLatin1(magic) != "openssh-key-v1") { - m_error = tr("Key file magic header id invalid"); - return false; - } - - stream.readString(m_cipherName); - stream.readString(m_kdfName); - stream.readString(m_kdfOptions); - - quint32 numberOfKeys; - stream.read(numberOfKeys); - - if (numberOfKeys == 0) { - m_error = tr("Found zero keys"); - return false; - } - - for (quint32 i = 0; i < numberOfKeys; ++i) { - QByteArray publicKey; - if (!stream.readString(publicKey)) { - m_error = tr("Failed to read public key."); + if (!stream.read(magic)) { + m_error = tr("Key file way too small."); return false; } - if (i == 0) { - BinaryStream publicStream(&publicKey); - if (!readPublic(publicStream)) { + if (QString::fromLatin1(magic) != "openssh-key-v1") { + m_error = tr("Key file magic header id invalid"); + return false; + } + + stream.readString(m_cipherName); + stream.readString(m_kdfName); + stream.readString(m_kdfOptions); + + quint32 numberOfKeys; + stream.read(numberOfKeys); + + if (numberOfKeys == 0) { + m_error = tr("Found zero keys"); + return false; + } + + for (quint32 i = 0; i < numberOfKeys; ++i) { + QByteArray publicKey; + if (!stream.readString(publicKey)) { + m_error = tr("Failed to read public key."); return false; } - } - } - // padded list of keys - if (!stream.readString(m_rawPrivateData)) { - m_error = tr("Corrupted key file, reading private key failed"); + if (i == 0) { + BinaryStream publicStream(&publicKey); + if (!readPublic(publicStream)) { + return false; + } + } + } + + // padded list of keys + if (!stream.readString(m_rawPrivateData)) { + m_error = tr("Corrupted key file, reading private key failed"); + return false; + } + } else { + m_error = tr("Unsupported key type: %s").arg(m_privateType); return false; } @@ -283,7 +313,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) } 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: ") + m_cipherName; + m_error = tr("Unknown cipher: %s").arg(m_cipherName); return false; } @@ -320,8 +350,13 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) ivData.setRawData(decryptKey.data() + cipher->keySize(), cipher->blockSize()); cipher->init(keyData, ivData); + + if (!cipher->init(keyData, ivData)) { + m_error = cipher->errorString(); + return false; + } } else if (m_kdfName != "none") { - m_error = tr("Unknown KDF: ") + m_kdfName; + m_error = tr("Unknown KDF: %s").arg(m_kdfName); return false; } @@ -336,20 +371,39 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) } } - BinaryStream keyStream(&rawPrivateData); + 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"); + return false; + } - quint32 checkInt1; - quint32 checkInt2; + 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"); + return false; + } - keyStream.read(checkInt1); - keyStream.read(checkInt2); + return true; + } else if (m_privateType == TYPE_OPENSSH) { + BinaryStream keyStream(&rawPrivateData); - if (checkInt1 != checkInt2) { - m_error = tr("Decryption failed, wrong passphrase?"); - return false; + quint32 checkInt1; + quint32 checkInt2; + + keyStream.read(checkInt1); + keyStream.read(checkInt2); + + if (checkInt1 != checkInt2) { + m_error = tr("Decryption failed, wrong passphrase?"); + return false; + } + + return readPrivate(keyStream); } - return readPrivate(keyStream); + m_error = tr("Unsupported key type: %s").arg(m_privateType); + return false; } bool OpenSSHKey::readPublic(BinaryStream& stream) @@ -371,7 +425,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream) } else if (m_type == "ssh-ed25519") { keyParts = 1; } else { - m_error = tr("Unknown key type: ") + m_type; + m_error = tr("Unknown key type: %s").arg(m_type); return false; } @@ -408,7 +462,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream) } else if (m_type == "ssh-ed25519") { keyParts = 2; } else { - m_error = tr("Unknown key type: ") + m_type; + m_error = tr("Unknown key type: %s").arg(m_type); return false; } diff --git a/src/sshagent/OpenSSHKey.h b/src/sshagent/OpenSSHKey.h index eca6c9edd..539d01892 100644 --- a/src/sshagent/OpenSSHKey.h +++ b/src/sshagent/OpenSSHKey.h @@ -55,6 +55,10 @@ public: bool writePrivate(BinaryStream& stream); private: + static const QString TYPE_DSA; + static const QString TYPE_RSA; + static const QString TYPE_OPENSSH; + bool parsePEM(const QByteArray& in, QByteArray& out); QString m_type; @@ -64,6 +68,7 @@ private: QByteArray m_rawPrivateData; QList m_publicData; QList m_privateData; + QString m_privateType; QString m_comment; QString m_error; }; diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp index 2424bc97f..a65c6e4f1 100644 --- a/src/sshagent/SSHAgent.cpp +++ b/src/sshagent/SSHAgent.cpp @@ -260,6 +260,10 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode) continue; } + if (key.comment().isEmpty()) { + key.setComment(e->username()); + } + if (settings.removeAtDatabaseClose()) { removeIdentityAtLock(key, uuid); } diff --git a/tests/TestOpenSSHKey.cpp b/tests/TestOpenSSHKey.cpp index 55033d7e1..7f94365e7 100644 --- a/tests/TestOpenSSHKey.cpp +++ b/tests/TestOpenSSHKey.cpp @@ -50,6 +50,7 @@ void TestOpenSSHKey::testParse() QCOMPARE(key.cipherName(), QString("none")); QCOMPARE(key.type(), QString("ssh-ed25519")); QCOMPARE(key.comment(), QString("opensshkey-test-parse@keepassxc")); + QCOMPARE(key.fingerprint(), QString("SHA256:D1fVmA15YXzaJ5sdO9dXxo5coHL/pnNaIfCvokHzTA4")); QByteArray publicKey, privateKey; BinaryStream publicStream(&publicKey), privateStream(&privateKey); @@ -61,6 +62,77 @@ void TestOpenSSHKey::testParse() QVERIFY(privateKey.length() == 154); } +void TestOpenSSHKey::testParseDSA() +{ + const QString keyString = QString( + "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCudjbvSh8JxQOr2laCqZM1t4kNWBETVOXz5vgk9iw6Z5opB9/k\n" + "g4nFc1PVq7fdAIc8W/5WCAjugKcxPb9PIHfcwY2fimmiPWFK68/eHKLoCuIn2wxB\n" + "63ig2hAhx5U5aYG9QHkNCaT6VX7rc19nToSeZXlpja4x54/DaQaqOEWYsQIVAOer\n" + "UQWfccz7KXUu6+x7heGob6I3AoGAVDRFJIlL0DI/4nePIcgwgwbfgs2ojSu21g4w\n" + "dQoXvqU34XydPgPQ985XIIuiDkaomRw4yYd/Sh4ZapFcrP++iJ1V+WS6kLcWPHMq\n" + "poYwk8mq6GLbPFLEjr+n6HgX5ln15n3i4WAopNH7mEl0glY9L0rxmcN0XOpqw6Ux\n" + "ETGEfAwCgYAiOeYwblMkkTIGtVx5NvNsOlfrBYL4GqUP9oQMO5I+xLZLWQIf+7Jp\n" + "8t6mwxSBz0RHjNVQ11vZowNjq3587aLy57bVwf2lIm9KSvS6z9HoNbHgQimcBorR\n" + "J9l9RUrj7TnsZgiVw66j2r34nHRHRtggiO+qrMtw7MJc0Q7jiuTmzgIVAMXbk0T9\n" + "nBfSLWQz/L8RexU2GR4e\n" + "-----END DSA PRIVATE KEY-----\n" + ); + + const QByteArray keyData = keyString.toLatin1(); + + OpenSSHKey key; + QVERIFY(key.parse(keyData)); + QVERIFY(!key.encrypted()); + QCOMPARE(key.cipherName(), QString("none")); + QCOMPARE(key.type(), QString("ssh-dss")); + QCOMPARE(key.comment(), QString("")); + QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18")); +} + +void TestOpenSSHKey::testParseRSA() +{ + const QString keyString = QString( + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpAIBAAKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbB\n" + "y1QTy0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErN\n" + "mH8vU91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD\n" + "9T3lm2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/\n" + "VzF3MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pO\n" + "XDpy/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQABAoIBAQCnEUc1LUQxeM5K\n" + "wANNCqE+SgoIClPdeHC7fmrLh1ttqe6ib6ybBUFRS31yXs0hnfefunVEDKlaV8K2\n" + "N52UAMAsngFHQNRvGh6kEWeZPd9Xc+N98TZbNCjcT+DGKc+Om8wqH5DrodZlCq4c\n" + "GaoT4HnE4TjWtZTH2XXrWF9I66PKFWf070R44nvyVcvaZi4pC2YmURRPuGF6K1iK\n" + "dH8zM6HHG1UGu2W6hLNn+K01IulG0Lb8eWNaNYMmtQWaxyp7I2IWkkecUs3nCuiR\n" + "byFOoomCjdh8r9yZFvwxjGUhgtkALN9GCU0Mwve+s11IB2gevruN+q9/Qejbyfdm\n" + "IlgLAeTRAoGBANRcVzW9CYeobCf+U9hKJFEOur8XO+J2mTMaELA0EjWpTJFAeIT7\n" + "KeRpCRG4/vOSklxxRF6vP1EACA4Z+5BlN+FTipHHs+bSEgqkPZiiANDH7Zot5Iqv\n" + "1q0fRyldNRZNZK7DWp08BPNVWGA/EnEuKJiURxnxBaxNXbUyMCdjxvMvAoGBANRT\n" + "utbrqS/bAa/DcHKn3V6DRqBl3TDOfvCNjiKC84a67F2uXgzLIdMktr4d1NyCZVJd\n" + "7/zVgWORLIdg1eAi6rYGoOvNV39wwga7CF+m9sBY0wAaKYCELe6L26r4aQHVCX6n\n" + "rnIgUv+4o4itmU2iP0r3wlmDC9pDRQP82vfvQPlfAoGASwhleANW/quvq2HdViq8\n" + "Mje2HBalfhrRfpDTHK8JUBSFjTzuWG42GxJRtgVbb8x2ElujAKGDCaetMO5VSGu7\n" + "Fs5hw6iAFCpdXY0yhl+XUi2R8kwM2EPQ4lKO3jqkq0ClNmqn9a5jQWcCVt9yMLNS\n" + "fLbHeI8EpiCf34ngIcrLXNkCgYEAzlcEZuKkC46xB+dNew8pMTUwSKZVm53BfPKD\n" + "44QRN6imFbBjU9mAaJnwQbfp6dWKs834cGPolyM4++MeVfB42iZ88ksesgmZdUMD\n" + "szkl6O0pOJs0I+HQZVdjRbadDZvD22MHQ3+oST1dJ3FVXz3Cdo9qPuT8esMO6f4r\n" + "qfDH2s8CgYAXC/lWWHQ//PGP0pH4oiEXisx1K0X1u0xMGgrChxBRGRiKZUwNMIvJ\n" + "TqUu7IKizK19cLHF/NBvxHYHFw+m7puNjn6T1RtRCUjRZT7Dx1VHfVosL9ih5DA8\n" + "tpbZA5KGKcvHtB5DDgT0MHwzBZnb4Q//Rhovzn+HXZPsJTTgHHy3NQ==\n" + "-----END RSA PRIVATE KEY-----\n" + ); + + const QByteArray keyData = keyString.toLatin1(); + + OpenSSHKey key; + QVERIFY(key.parse(keyData)); + QVERIFY(!key.encrypted()); + QCOMPARE(key.cipherName(), QString("none")); + QCOMPARE(key.type(), QString("ssh-rsa")); + QCOMPARE(key.comment(), QString("")); + QCOMPARE(key.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk")); +} + void TestOpenSSHKey::testDecryptAES256CBC() { const QString keyString = QString( diff --git a/tests/TestOpenSSHKey.h b/tests/TestOpenSSHKey.h index f2d6d1fb9..6c2041074 100644 --- a/tests/TestOpenSSHKey.h +++ b/tests/TestOpenSSHKey.h @@ -29,6 +29,8 @@ class TestOpenSSHKey : public QObject private slots: void initTestCase(); void testParse(); + void testParseDSA(); + void testParseRSA(); void testDecryptAES256CBC(); void testDecryptAES256CTR(); };