mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-30 09:58:15 -04:00
SSH Agent: Integration tests against ssh-agent
Windows testing is currently explicitly disabled due to too many different scenarios to run an agent and MSYS2 having its own.
This commit is contained in:
parent
2359742de1
commit
dce9af219f
@ -68,7 +68,8 @@ void AgentSettingsWidget::loadSettings()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (sshAgent()->testConnection()) {
|
QList<QSharedPointer<OpenSSHKey>> keys;
|
||||||
|
if (sshAgent()->listIdentities(keys)) {
|
||||||
m_ui->sshAuthSockMessageWidget->showMessage(tr("SSH Agent connection is working!"),
|
m_ui->sshAuthSockMessageWidget->showMessage(tr("SSH Agent connection is working!"),
|
||||||
MessageWidget::Positive);
|
MessageWidget::Positive);
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,36 +212,6 @@ bool SSHAgent::sendMessagePageant(const QByteArray& in, QByteArray& out)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if connection to SSH agent is working.
|
|
||||||
*
|
|
||||||
* @return true on success
|
|
||||||
*/
|
|
||||||
bool SSHAgent::testConnection()
|
|
||||||
{
|
|
||||||
if (!isAgentRunning()) {
|
|
||||||
m_error = tr("No agent running, cannot test connection.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray requestData;
|
|
||||||
BinaryStream request(&requestData);
|
|
||||||
|
|
||||||
request.write(SSH_AGENTC_REQUEST_IDENTITIES);
|
|
||||||
|
|
||||||
QByteArray responseData;
|
|
||||||
if (!sendMessage(requestData, responseData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseData.length() < 1 || static_cast<quint8>(responseData[0]) != SSH_AGENT_IDENTITIES_ANSWER) {
|
|
||||||
m_error = tr("Agent protocol error.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the identity to the SSH agent.
|
* Add the identity to the SSH agent.
|
||||||
*
|
*
|
||||||
@ -328,6 +298,99 @@ bool SSHAgent::removeIdentity(OpenSSHKey& key)
|
|||||||
return sendMessage(requestData, responseData);
|
return sendMessage(requestData, responseData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of identities from the SSH agent.
|
||||||
|
*
|
||||||
|
* @param list list of keys to append
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool SSHAgent::listIdentities(QList<QSharedPointer<OpenSSHKey>>& list)
|
||||||
|
{
|
||||||
|
if (!isAgentRunning()) {
|
||||||
|
m_error = tr("No agent running, cannot list identities.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray requestData;
|
||||||
|
BinaryStream request(&requestData);
|
||||||
|
|
||||||
|
request.write(SSH_AGENTC_REQUEST_IDENTITIES);
|
||||||
|
|
||||||
|
QByteArray responseData;
|
||||||
|
if (!sendMessage(requestData, responseData)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryStream response(&responseData);
|
||||||
|
|
||||||
|
quint8 responseType;
|
||||||
|
if (!response.read(responseType) || responseType != SSH_AGENT_IDENTITIES_ANSWER) {
|
||||||
|
m_error = tr("Agent protocol error.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 nKeys;
|
||||||
|
if (!response.read(nKeys)) {
|
||||||
|
m_error = tr("Agent protocol error.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (quint32 i = 0; i < nKeys; i++) {
|
||||||
|
QByteArray publicData;
|
||||||
|
QString comment;
|
||||||
|
|
||||||
|
if (!response.readString(publicData)) {
|
||||||
|
m_error = tr("Agent protocol error.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.readString(comment)) {
|
||||||
|
m_error = tr("Agent protocol error.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSSHKey* key = new OpenSSHKey();
|
||||||
|
key->setComment(comment);
|
||||||
|
|
||||||
|
list.append(QSharedPointer<OpenSSHKey>(key));
|
||||||
|
|
||||||
|
BinaryStream publicDataStream(&publicData);
|
||||||
|
if (!key->readPublic(publicDataStream)) {
|
||||||
|
m_error = key->errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this identity is loaded in the SSH Agent.
|
||||||
|
*
|
||||||
|
* @param key identity to remove
|
||||||
|
* @param loaded is the key laoded
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool SSHAgent::checkIdentity(OpenSSHKey& key, bool& loaded)
|
||||||
|
{
|
||||||
|
QList<QSharedPointer<OpenSSHKey>> list;
|
||||||
|
|
||||||
|
if (!listIdentities(list)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
|
||||||
|
for (const auto it : list) {
|
||||||
|
if (*it == key) {
|
||||||
|
loaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all identities known to this instance
|
* Remove all identities known to this instance
|
||||||
*/
|
*/
|
||||||
|
@ -47,8 +47,9 @@ public:
|
|||||||
|
|
||||||
const QString errorString() const;
|
const QString errorString() const;
|
||||||
bool isAgentRunning() const;
|
bool isAgentRunning() const;
|
||||||
bool testConnection();
|
|
||||||
bool addIdentity(OpenSSHKey& key, KeeAgentSettings& settings);
|
bool addIdentity(OpenSSHKey& key, KeeAgentSettings& settings);
|
||||||
|
bool listIdentities(QList<QSharedPointer<OpenSSHKey>>& list);
|
||||||
|
bool checkIdentity(OpenSSHKey& key, bool& loaded);
|
||||||
bool removeIdentity(OpenSSHKey& key);
|
bool removeIdentity(OpenSSHKey& key);
|
||||||
void removeAllIdentities();
|
void removeAllIdentities();
|
||||||
void setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove);
|
void setAutoRemoveOnLock(const OpenSSHKey& key, bool autoRemove);
|
||||||
|
@ -165,6 +165,10 @@ endif()
|
|||||||
if(WITH_XC_CRYPTO_SSH)
|
if(WITH_XC_CRYPTO_SSH)
|
||||||
add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
|
add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
if(NOT WIN32)
|
||||||
|
add_unit_test(NAME testsshagent SOURCES TestSSHAgent.cpp
|
||||||
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
add_unit_test(NAME testentry SOURCES TestEntry.cpp
|
||||||
|
214
tests/TestSSHAgent.cpp
Normal file
214
tests/TestSSHAgent.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TestSSHAgent.h"
|
||||||
|
#include "TestGlobal.h"
|
||||||
|
#include "core/Config.h"
|
||||||
|
#include "crypto/Crypto.h"
|
||||||
|
#include "sshagent/SSHAgent.h"
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(TestSSHAgent)
|
||||||
|
|
||||||
|
void TestSSHAgent::initTestCase()
|
||||||
|
{
|
||||||
|
QVERIFY(Crypto::init());
|
||||||
|
Config::createTempFileInstance();
|
||||||
|
|
||||||
|
m_agentSocketFile.setAutoRemove(true);
|
||||||
|
QVERIFY(m_agentSocketFile.open());
|
||||||
|
|
||||||
|
m_agentSocketFileName = m_agentSocketFile.fileName();
|
||||||
|
QVERIFY(!m_agentSocketFileName.isEmpty());
|
||||||
|
|
||||||
|
// let ssh-agent re-create it as a socket
|
||||||
|
QVERIFY(m_agentSocketFile.remove());
|
||||||
|
|
||||||
|
QStringList arguments;
|
||||||
|
arguments << "-D"
|
||||||
|
<< "-a" << m_agentSocketFileName;
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
qDebug() << "ssh-agent starting with arguments" << arguments;
|
||||||
|
m_agentProcess.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
|
m_agentProcess.start("ssh-agent", arguments);
|
||||||
|
m_agentProcess.closeWriteChannel();
|
||||||
|
|
||||||
|
if (!m_agentProcess.waitForStarted()) {
|
||||||
|
QSKIP("ssh-agent could not be started");
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "ssh-agent started as pid" << m_agentProcess.pid();
|
||||||
|
|
||||||
|
// we need to wait for the agent to open the socket before going into real tests
|
||||||
|
QFileInfo socketFileInfo(m_agentSocketFileName);
|
||||||
|
while (!timer.hasExpired(2000)) {
|
||||||
|
if (socketFileInfo.exists()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
QTest::qWait(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY(socketFileInfo.exists());
|
||||||
|
qDebug() << "ssh-agent initialized in" << timer.elapsed() << "ms";
|
||||||
|
|
||||||
|
// initialize test key
|
||||||
|
const QString keyString = QString("-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
||||||
|
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
|
||||||
|
"QyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazAAAAKjgCfj94An4\n"
|
||||||
|
"/QAAAAtzc2gtZWQyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazA\n"
|
||||||
|
"AAAEBe1iilZFho8ZGAliiSj5URvFtGrgvmnEKdiLZow5hOR92U7kXaQXZbN52sEAcGL3AE\n"
|
||||||
|
"d4hLNdnQi4iqunQTN5rMAAAAH29wZW5zc2hrZXktdGVzdC1wYXJzZUBrZWVwYXNzeGMBAg\n"
|
||||||
|
"MEBQY=\n"
|
||||||
|
"-----END OPENSSH PRIVATE KEY-----\n");
|
||||||
|
|
||||||
|
const QByteArray keyData = keyString.toLatin1();
|
||||||
|
|
||||||
|
QVERIFY(m_key.parsePKCS1PEM(keyData));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::testConfiguration()
|
||||||
|
{
|
||||||
|
SSHAgent agent;
|
||||||
|
|
||||||
|
// default config must not enable agent
|
||||||
|
QVERIFY(!agent.isEnabled());
|
||||||
|
|
||||||
|
agent.setEnabled(true);
|
||||||
|
QVERIFY(agent.isEnabled());
|
||||||
|
|
||||||
|
// this will either be an empty string or the real ssh-agent socket path, doesn't matter
|
||||||
|
QString defaultSocketPath = agent.socketPath(false);
|
||||||
|
|
||||||
|
// overridden path must match default before setting an override
|
||||||
|
QCOMPARE(agent.socketPath(true), defaultSocketPath);
|
||||||
|
|
||||||
|
agent.setAuthSockOverride(m_agentSocketFileName);
|
||||||
|
|
||||||
|
// overridden path must match what we set
|
||||||
|
QCOMPARE(agent.socketPath(true), m_agentSocketFileName);
|
||||||
|
|
||||||
|
// non-overridden path must match the default
|
||||||
|
QCOMPARE(agent.socketPath(false), defaultSocketPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::testIdentity()
|
||||||
|
{
|
||||||
|
SSHAgent agent;
|
||||||
|
agent.setEnabled(true);
|
||||||
|
agent.setAuthSockOverride(m_agentSocketFileName);
|
||||||
|
|
||||||
|
QVERIFY(agent.isAgentRunning());
|
||||||
|
|
||||||
|
KeeAgentSettings settings;
|
||||||
|
bool keyInAgent;
|
||||||
|
|
||||||
|
// test adding a key works
|
||||||
|
QVERIFY(agent.addIdentity(m_key, settings));
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
|
||||||
|
|
||||||
|
// test removing a key works
|
||||||
|
QVERIFY(agent.removeIdentity(m_key));
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::testRemoveOnClose()
|
||||||
|
{
|
||||||
|
SSHAgent agent;
|
||||||
|
agent.setEnabled(true);
|
||||||
|
agent.setAuthSockOverride(m_agentSocketFileName);
|
||||||
|
|
||||||
|
QVERIFY(agent.isAgentRunning());
|
||||||
|
|
||||||
|
KeeAgentSettings settings;
|
||||||
|
bool keyInAgent;
|
||||||
|
|
||||||
|
settings.setRemoveAtDatabaseClose(true);
|
||||||
|
QVERIFY(agent.addIdentity(m_key, settings));
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
|
||||||
|
agent.setEnabled(false);
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::testLifetimeConstraint()
|
||||||
|
{
|
||||||
|
SSHAgent agent;
|
||||||
|
agent.setEnabled(true);
|
||||||
|
agent.setAuthSockOverride(m_agentSocketFileName);
|
||||||
|
|
||||||
|
QVERIFY(agent.isAgentRunning());
|
||||||
|
|
||||||
|
KeeAgentSettings settings;
|
||||||
|
bool keyInAgent;
|
||||||
|
|
||||||
|
settings.setUseLifetimeConstraintWhenAdding(true);
|
||||||
|
settings.setLifetimeConstraintDuration(2); // two seconds
|
||||||
|
|
||||||
|
// identity should be in agent immediately after adding
|
||||||
|
QVERIFY(agent.addIdentity(m_key, settings));
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
// wait for the identity to time out
|
||||||
|
while (!timer.hasExpired(5000)) {
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent));
|
||||||
|
|
||||||
|
if (!keyInAgent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTest::qWait(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY(!keyInAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::testConfirmConstraint()
|
||||||
|
{
|
||||||
|
SSHAgent agent;
|
||||||
|
agent.setEnabled(true);
|
||||||
|
agent.setAuthSockOverride(m_agentSocketFileName);
|
||||||
|
|
||||||
|
QVERIFY(agent.isAgentRunning());
|
||||||
|
|
||||||
|
KeeAgentSettings settings;
|
||||||
|
bool keyInAgent;
|
||||||
|
|
||||||
|
settings.setUseConfirmConstraintWhenAdding(true);
|
||||||
|
|
||||||
|
QVERIFY(agent.addIdentity(m_key, settings));
|
||||||
|
|
||||||
|
// we can't test confirmation itself is working but we can test the agent accepts the key
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
|
||||||
|
|
||||||
|
QVERIFY(agent.removeIdentity(m_key));
|
||||||
|
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSSHAgent::cleanupTestCase()
|
||||||
|
{
|
||||||
|
if (m_agentProcess.state() != QProcess::NotRunning) {
|
||||||
|
qDebug() << "Killing ssh-agent pid" << m_agentProcess.pid();
|
||||||
|
m_agentProcess.terminate();
|
||||||
|
m_agentProcess.waitForFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_agentSocketFile.remove();
|
||||||
|
}
|
46
tests/TestSSHAgent.h
Normal file
46
tests/TestSSHAgent.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TESTSSHAGENT_H
|
||||||
|
#define TESTSSHAGENT_H
|
||||||
|
|
||||||
|
#include "crypto/ssh/OpenSSHKey.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
|
class TestSSHAgent : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase();
|
||||||
|
void testConfiguration();
|
||||||
|
void testIdentity();
|
||||||
|
void testRemoveOnClose();
|
||||||
|
void testLifetimeConstraint();
|
||||||
|
void testConfirmConstraint();
|
||||||
|
void cleanupTestCase();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTemporaryFile m_agentSocketFile;
|
||||||
|
QString m_agentSocketFileName;
|
||||||
|
QProcess m_agentProcess;
|
||||||
|
OpenSSHKey m_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TESTSSHAGENT_H
|
Loading…
x
Reference in New Issue
Block a user