TestSSHAgent.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /*
  2. * Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
  3. *
  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.
  8. *
  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.
  13. *
  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/>.
  16. */
  17. #include "TestSSHAgent.h"
  18. #include "config-keepassx-tests.h"
  19. #include "core/Config.h"
  20. #include "crypto/Crypto.h"
  21. #include "sshagent/KeeAgentSettings.h"
  22. #include "sshagent/OpenSSHKeyGen.h"
  23. #include "sshagent/SSHAgent.h"
  24. #include <QTest>
  25. QTEST_GUILESS_MAIN(TestSSHAgent)
  26. void TestSSHAgent::initTestCase()
  27. {
  28. QVERIFY(Crypto::init());
  29. // Create temporary config file
  30. Config::createConfigFromFile(TemporaryFile::createTempConfigFile(), {});
  31. // default config must not enable agent
  32. SSHAgent agent;
  33. QVERIFY(!agent.isEnabled());
  34. m_agentSocketFile.reset(new TemporaryFile(this));
  35. m_agentSocketFileName = m_agentSocketFile->fileName();
  36. QVERIFY(!m_agentSocketFileName.isEmpty());
  37. QStringList arguments;
  38. arguments << "-D" << "-a" << m_agentSocketFileName;
  39. QElapsedTimer timer;
  40. timer.start();
  41. qDebug() << "ssh-agent starting with arguments" << arguments;
  42. m_agentProcess.setProcessChannelMode(QProcess::ForwardedChannels);
  43. m_agentProcess.start("ssh-agent", arguments);
  44. m_agentProcess.closeWriteChannel();
  45. if (!m_agentProcess.waitForStarted()) {
  46. QSKIP("ssh-agent could not be started");
  47. }
  48. qDebug() << "ssh-agent started as pid" << m_agentProcess.processId();
  49. // we need to wait for the agent to open the socket before going into real tests
  50. QFileInfo socketFileInfo(m_agentSocketFileName);
  51. while (!timer.hasExpired(2000)) {
  52. if (socketFileInfo.exists()) {
  53. break;
  54. }
  55. QTest::qWait(10);
  56. }
  57. QVERIFY(socketFileInfo.exists());
  58. qDebug() << "ssh-agent initialized in" << timer.elapsed() << "ms";
  59. // initialize test key
  60. const QString keyString = QString("-----BEGIN OPENSSH PRIVATE KEY-----\n"
  61. "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
  62. "QyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazAAAAKjgCfj94An4\n"
  63. "/QAAAAtzc2gtZWQyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazA\n"
  64. "AAAEBe1iilZFho8ZGAliiSj5URvFtGrgvmnEKdiLZow5hOR92U7kXaQXZbN52sEAcGL3AE\n"
  65. "d4hLNdnQi4iqunQTN5rMAAAAH29wZW5zc2hrZXktdGVzdC1wYXJzZUBrZWVwYXNzeGMBAg\n"
  66. "MEBQY=\n"
  67. "-----END OPENSSH PRIVATE KEY-----\n");
  68. const QByteArray keyData = keyString.toLatin1();
  69. QVERIFY(m_key.parsePKCS1PEM(keyData));
  70. }
  71. void TestSSHAgent::init()
  72. {
  73. // Reset the config state
  74. SSHAgent agent;
  75. agent.setEnabled(false);
  76. QString empty;
  77. agent.setAuthSockOverride(empty);
  78. }
  79. void TestSSHAgent::testConfiguration()
  80. {
  81. SSHAgent agent;
  82. agent.setEnabled(true);
  83. QVERIFY(agent.isEnabled());
  84. // this will either be an empty string or the real ssh-agent socket path, doesn't matter
  85. QString defaultSocketPath = agent.socketPath(false);
  86. // overridden path must match default before setting an override
  87. QCOMPARE(agent.socketPath(true), defaultSocketPath);
  88. agent.setAuthSockOverride(m_agentSocketFileName);
  89. // overridden path must match what we set
  90. QCOMPARE(agent.socketPath(true), m_agentSocketFileName);
  91. // non-overridden path must match the default
  92. QCOMPARE(agent.socketPath(false), defaultSocketPath);
  93. }
  94. void TestSSHAgent::testIdentity()
  95. {
  96. SSHAgent agent;
  97. agent.setEnabled(true);
  98. agent.setAuthSockOverride(m_agentSocketFileName);
  99. QVERIFY(agent.isAgentRunning());
  100. KeeAgentSettings settings;
  101. bool keyInAgent;
  102. // test adding a key works
  103. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  104. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  105. // test non-conflicting key ownership doesn't throw an error
  106. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  107. // test conflicting key ownership throws an error
  108. QUuid secondUuid("{11111111-1111-1111-1111-111111111111}");
  109. QVERIFY(!agent.addIdentity(m_key, settings, secondUuid));
  110. // test removing a key works
  111. QVERIFY(agent.removeIdentity(m_key));
  112. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  113. }
  114. void TestSSHAgent::testRemoveOnClose()
  115. {
  116. SSHAgent agent;
  117. agent.setEnabled(true);
  118. agent.setAuthSockOverride(m_agentSocketFileName);
  119. QVERIFY(agent.isAgentRunning());
  120. KeeAgentSettings settings;
  121. bool keyInAgent;
  122. settings.setRemoveAtDatabaseClose(true);
  123. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  124. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  125. agent.setEnabled(false);
  126. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  127. }
  128. void TestSSHAgent::testLifetimeConstraint()
  129. {
  130. SSHAgent agent;
  131. agent.setEnabled(true);
  132. agent.setAuthSockOverride(m_agentSocketFileName);
  133. QVERIFY(agent.isAgentRunning());
  134. KeeAgentSettings settings;
  135. bool keyInAgent;
  136. settings.setUseLifetimeConstraintWhenAdding(true);
  137. settings.setLifetimeConstraintDuration(2); // two seconds
  138. // identity should be in agent immediately after adding
  139. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  140. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  141. QElapsedTimer timer;
  142. timer.start();
  143. // wait for the identity to time out
  144. while (!timer.hasExpired(5000)) {
  145. QVERIFY(agent.checkIdentity(m_key, keyInAgent));
  146. if (!keyInAgent) {
  147. break;
  148. }
  149. QTest::qWait(100);
  150. }
  151. QVERIFY(!keyInAgent);
  152. }
  153. void TestSSHAgent::testConfirmConstraint()
  154. {
  155. SSHAgent agent;
  156. agent.setEnabled(true);
  157. agent.setAuthSockOverride(m_agentSocketFileName);
  158. QVERIFY(agent.isAgentRunning());
  159. KeeAgentSettings settings;
  160. bool keyInAgent;
  161. settings.setUseConfirmConstraintWhenAdding(true);
  162. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  163. // we can't test confirmation itself is working but we can test the agent accepts the key
  164. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  165. QVERIFY(agent.removeIdentity(m_key));
  166. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  167. }
  168. void TestSSHAgent::testToOpenSSHKey()
  169. {
  170. KeeAgentSettings settings;
  171. settings.setSelectedType("file");
  172. settings.setFileName(QString("%1/id_rsa-encrypted-asn1").arg(QString(KEEPASSX_TEST_DATA_DIR)));
  173. OpenSSHKey key;
  174. settings.toOpenSSHKey("username", "correctpassphrase", QString(), nullptr, key, false);
  175. QVERIFY(!key.publicKey().isEmpty());
  176. }
  177. void TestSSHAgent::testKeyGenRSA()
  178. {
  179. SSHAgent agent;
  180. agent.setEnabled(true);
  181. agent.setAuthSockOverride(m_agentSocketFileName);
  182. QVERIFY(agent.isAgentRunning());
  183. OpenSSHKey key;
  184. KeeAgentSettings settings;
  185. bool keyInAgent;
  186. QVERIFY(OpenSSHKeyGen::generateRSA(key, 2048));
  187. QVERIFY(agent.addIdentity(key, settings, m_uuid));
  188. QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
  189. QVERIFY(agent.removeIdentity(key));
  190. QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
  191. }
  192. void TestSSHAgent::testKeyGenECDSA()
  193. {
  194. SSHAgent agent;
  195. agent.setEnabled(true);
  196. agent.setAuthSockOverride(m_agentSocketFileName);
  197. QVERIFY(agent.isAgentRunning());
  198. OpenSSHKey key;
  199. KeeAgentSettings settings;
  200. bool keyInAgent;
  201. QVERIFY(OpenSSHKeyGen::generateECDSA(key, 256));
  202. QVERIFY(agent.addIdentity(key, settings, m_uuid));
  203. QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
  204. QVERIFY(agent.removeIdentity(key));
  205. QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
  206. }
  207. void TestSSHAgent::testKeyGenEd25519()
  208. {
  209. SSHAgent agent;
  210. agent.setEnabled(true);
  211. agent.setAuthSockOverride(m_agentSocketFileName);
  212. QVERIFY(agent.isAgentRunning());
  213. OpenSSHKey key;
  214. KeeAgentSettings settings;
  215. bool keyInAgent;
  216. QVERIFY(OpenSSHKeyGen::generateEd25519(key));
  217. QVERIFY(agent.addIdentity(key, settings, m_uuid));
  218. QVERIFY(agent.checkIdentity(key, keyInAgent) && keyInAgent);
  219. QVERIFY(agent.removeIdentity(key));
  220. QVERIFY(agent.checkIdentity(key, keyInAgent) && !keyInAgent);
  221. }
  222. void TestSSHAgent::cleanupTestCase()
  223. {
  224. if (m_agentProcess.state() != QProcess::NotRunning) {
  225. qDebug() << "Killing ssh-agent pid" << m_agentProcess.processId();
  226. m_agentProcess.terminate();
  227. m_agentProcess.waitForFinished();
  228. }
  229. }