2
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation, either version 2 or (at your option)
7
* version 3 of the License.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18
#include "TestSSHAgent.h"
19
#include "config-keepassx-tests.h"
20
#include "core/Config.h"
21
#include "crypto/Crypto.h"
22
#include "sshagent/KeeAgentSettings.h"
23
#include "sshagent/OpenSSHKeyGen.h"
24
#include "sshagent/SSHAgent.h"
28
QTEST_GUILESS_MAIN(TestSSHAgent)
30
void TestSSHAgent::initTestCase()
32
QVERIFY(Crypto::init());
33
Config::createTempFileInstance();
35
m_agentSocketFile.setAutoRemove(true);
36
QVERIFY(m_agentSocketFile.open());
38
m_agentSocketFileName = m_agentSocketFile.fileName();
39
QVERIFY(!m_agentSocketFileName.isEmpty());
41
// let ssh-agent re-create it as a socket
42
QVERIFY(m_agentSocketFile.remove());
44
QStringList arguments;
46
<< "-a" << m_agentSocketFileName;
51
qDebug() << "ssh-agent starting with arguments" << arguments;
52
m_agentProcess.setProcessChannelMode(QProcess::ForwardedChannels);
53
m_agentProcess.start("ssh-agent", arguments);
54
m_agentProcess.closeWriteChannel();
56
if (!m_agentProcess.waitForStarted()) {
57
QSKIP("ssh-agent could not be started");
60
qDebug() << "ssh-agent started as pid" << m_agentProcess.pid();
62
// we need to wait for the agent to open the socket before going into real tests
63
QFileInfo socketFileInfo(m_agentSocketFileName);
64
while (!timer.hasExpired(2000)) {
65
if (socketFileInfo.exists()) {
71
QVERIFY(socketFileInfo.exists());
72
qDebug() << "ssh-agent initialized in" << timer.elapsed() << "ms";
74
// initialize test key
75
const QString keyString = QString("-----BEGIN OPENSSH PRIVATE KEY-----\n"
76
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
77
"QyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazAAAAKjgCfj94An4\n"
78
"/QAAAAtzc2gtZWQyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazA\n"
79
"AAAEBe1iilZFho8ZGAliiSj5URvFtGrgvmnEKdiLZow5hOR92U7kXaQXZbN52sEAcGL3AE\n"
80
"d4hLNdnQi4iqunQTN5rMAAAAH29wZW5zc2hrZXktdGVzdC1wYXJzZUBrZWVwYXNzeGMBAg\n"
82
"-----END OPENSSH PRIVATE KEY-----\n");
84
const QByteArray keyData = keyString.toLatin1();
86
QVERIFY(m_key.parsePKCS1PEM(keyData));
89
void TestSSHAgent::testConfiguration()
93
// default config must not enable agent
94
QVERIFY(!agent.isEnabled());
96
agent.setEnabled(true);
97
QVERIFY(agent.isEnabled());
99
// this will either be an empty string or the real ssh-agent socket path, doesn't matter
100
QString defaultSocketPath = agent.socketPath(false);
102
// overridden path must match default before setting an override
103
QCOMPARE(agent.socketPath(true), defaultSocketPath);
105
agent.setAuthSockOverride(m_agentSocketFileName);
107
// overridden path must match what we set
108
QCOMPARE(agent.socketPath(true), m_agentSocketFileName);
110
// non-overridden path must match the default
111
QCOMPARE(agent.socketPath(false), defaultSocketPath);
114
void TestSSHAgent::testIdentity()
117
agent.setEnabled(true);
118
agent.setAuthSockOverride(m_agentSocketFileName);
120
QVERIFY(agent.isAgentRunning());
122
KeeAgentSettings settings;
125
// test adding a key works
126
QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
127
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
129
// test non-conflicting key ownership doesn't throw an error
130
QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
132
// test conflicting key ownership throws an error
133
QUuid secondUuid("{11111111-1111-1111-1111-111111111111}");
134
QVERIFY(!agent.addIdentity(m_key, settings, secondUuid));
136
// test removing a key works
137
QVERIFY(agent.removeIdentity(m_key));
138
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
141
void TestSSHAgent::testRemoveOnClose()
144
agent.setEnabled(true);
145
agent.setAuthSockOverride(m_agentSocketFileName);
147
QVERIFY(agent.isAgentRunning());
149
KeeAgentSettings settings;
152
settings.setRemoveAtDatabaseClose(true);
153
QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
154
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
155
agent.setEnabled(false);
156
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
159
void TestSSHAgent::testLifetimeConstraint()
162
agent.setEnabled(true);
163
agent.setAuthSockOverride(m_agentSocketFileName);
165
QVERIFY(agent.isAgentRunning());
167
KeeAgentSettings settings;
170
settings.setUseLifetimeConstraintWhenAdding(true);
171
settings.setLifetimeConstraintDuration(2); // two seconds
173
// identity should be in agent immediately after adding
174
QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
175
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
180
// wait for the identity to time out
181
while (!timer.hasExpired(5000)) {
182
QVERIFY(agent.checkIdentity(m_key, keyInAgent));
191
QVERIFY(!keyInAgent);
194
void TestSSHAgent::testConfirmConstraint()
197
agent.setEnabled(true);
198
agent.setAuthSockOverride(m_agentSocketFileName);
200
QVERIFY(agent.isAgentRunning());
202
KeeAgentSettings settings;
205
settings.setUseConfirmConstraintWhenAdding(true);
207
QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
209
// we can't test confirmation itself is working but we can test the agent accepts the key
210
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
212
QVERIFY(agent.removeIdentity(m_key));
213
QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
216
void TestSSHAgent::testToOpenSSHKey()
218
KeeAgentSettings settings;
219
settings.setSelectedType("file");
220
settings.setFileName(QString("%1/id_rsa-encrypted-asn1").arg(QString(KEEPASSX_TEST_DATA_DIR)));
223
settings.toOpenSSHKey("username", "correctpassphrase", QString(), nullptr, key, false);
225
QVERIFY(!key.publicKey().isEmpty());
228
void TestSSHAgent::testKeyGenRSA()
231
agent.setEnabled(true);
232
agent.setAuthSockOverride(m_agentSocketFileName);
234
QVERIFY(agent.isAgentRunning());
237
KeeAgentSettings settings;
240
QVERIFY(OpenSSHKeyGen::generateRSA(key, 2048));
242
QVERIFY(agent.addIdentity(key, settings, m_uuid));
243
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
244
QVERIFY(agent.removeIdentity(key));
245
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
248
void TestSSHAgent::testKeyGenECDSA()
251
agent.setEnabled(true);
252
agent.setAuthSockOverride(m_agentSocketFileName);
254
QVERIFY(agent.isAgentRunning());
257
KeeAgentSettings settings;
260
QVERIFY(OpenSSHKeyGen::generateECDSA(key, 256));
262
QVERIFY(agent.addIdentity(key, settings, m_uuid));
263
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
264
QVERIFY(agent.removeIdentity(key));
265
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
268
void TestSSHAgent::testKeyGenEd25519()
271
agent.setEnabled(true);
272
agent.setAuthSockOverride(m_agentSocketFileName);
274
QVERIFY(agent.isAgentRunning());
277
KeeAgentSettings settings;
280
QVERIFY(OpenSSHKeyGen::generateEd25519(key));
282
QVERIFY(agent.addIdentity(key, settings, m_uuid));
283
QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
284
QVERIFY(agent.removeIdentity(key));
285
QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
288
void TestSSHAgent::cleanupTestCase()
290
if (m_agentProcess.state() != QProcess::NotRunning) {
291
qDebug() << "Killing ssh-agent pid" << m_agentProcess.pid();
292
m_agentProcess.terminate();
293
m_agentProcess.waitForFinished();
296
m_agentSocketFile.remove();