TestSSHAgent.cpp 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 "TestGlobal.h"
  19. #include "core/Config.h"
  20. #include "crypto/Crypto.h"
  21. #include "sshagent/SSHAgent.h"
  22. QTEST_GUILESS_MAIN(TestSSHAgent)
  23. void TestSSHAgent::initTestCase()
  24. {
  25. QVERIFY(Crypto::init());
  26. Config::createTempFileInstance();
  27. m_agentSocketFile.setAutoRemove(true);
  28. QVERIFY(m_agentSocketFile.open());
  29. m_agentSocketFileName = m_agentSocketFile.fileName();
  30. QVERIFY(!m_agentSocketFileName.isEmpty());
  31. // let ssh-agent re-create it as a socket
  32. QVERIFY(m_agentSocketFile.remove());
  33. QStringList arguments;
  34. arguments << "-D"
  35. << "-a" << m_agentSocketFileName;
  36. QElapsedTimer timer;
  37. timer.start();
  38. qDebug() << "ssh-agent starting with arguments" << arguments;
  39. m_agentProcess.setProcessChannelMode(QProcess::ForwardedChannels);
  40. m_agentProcess.start("ssh-agent", arguments);
  41. m_agentProcess.closeWriteChannel();
  42. if (!m_agentProcess.waitForStarted()) {
  43. QSKIP("ssh-agent could not be started");
  44. }
  45. qDebug() << "ssh-agent started as pid" << m_agentProcess.pid();
  46. // we need to wait for the agent to open the socket before going into real tests
  47. QFileInfo socketFileInfo(m_agentSocketFileName);
  48. while (!timer.hasExpired(2000)) {
  49. if (socketFileInfo.exists()) {
  50. break;
  51. }
  52. QTest::qWait(10);
  53. }
  54. QVERIFY(socketFileInfo.exists());
  55. qDebug() << "ssh-agent initialized in" << timer.elapsed() << "ms";
  56. // initialize test key
  57. const QString keyString = QString("-----BEGIN OPENSSH PRIVATE KEY-----\n"
  58. "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
  59. "QyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazAAAAKjgCfj94An4\n"
  60. "/QAAAAtzc2gtZWQyNTUxOQAAACDdlO5F2kF2WzedrBAHBi9wBHeISzXZ0IuIqrp0EzeazA\n"
  61. "AAAEBe1iilZFho8ZGAliiSj5URvFtGrgvmnEKdiLZow5hOR92U7kXaQXZbN52sEAcGL3AE\n"
  62. "d4hLNdnQi4iqunQTN5rMAAAAH29wZW5zc2hrZXktdGVzdC1wYXJzZUBrZWVwYXNzeGMBAg\n"
  63. "MEBQY=\n"
  64. "-----END OPENSSH PRIVATE KEY-----\n");
  65. const QByteArray keyData = keyString.toLatin1();
  66. QVERIFY(m_key.parsePKCS1PEM(keyData));
  67. }
  68. void TestSSHAgent::testConfiguration()
  69. {
  70. SSHAgent agent;
  71. // default config must not enable agent
  72. QVERIFY(!agent.isEnabled());
  73. agent.setEnabled(true);
  74. QVERIFY(agent.isEnabled());
  75. // this will either be an empty string or the real ssh-agent socket path, doesn't matter
  76. QString defaultSocketPath = agent.socketPath(false);
  77. // overridden path must match default before setting an override
  78. QCOMPARE(agent.socketPath(true), defaultSocketPath);
  79. agent.setAuthSockOverride(m_agentSocketFileName);
  80. // overridden path must match what we set
  81. QCOMPARE(agent.socketPath(true), m_agentSocketFileName);
  82. // non-overridden path must match the default
  83. QCOMPARE(agent.socketPath(false), defaultSocketPath);
  84. }
  85. void TestSSHAgent::testIdentity()
  86. {
  87. SSHAgent agent;
  88. agent.setEnabled(true);
  89. agent.setAuthSockOverride(m_agentSocketFileName);
  90. QVERIFY(agent.isAgentRunning());
  91. KeeAgentSettings settings;
  92. bool keyInAgent;
  93. // test adding a key works
  94. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  95. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  96. // test non-conflicting key ownership doesn't throw an error
  97. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  98. // test conflicting key ownership throws an error
  99. QUuid secondUuid("{11111111-1111-1111-1111-111111111111}");
  100. QVERIFY(!agent.addIdentity(m_key, settings, secondUuid));
  101. // test removing a key works
  102. QVERIFY(agent.removeIdentity(m_key));
  103. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  104. }
  105. void TestSSHAgent::testRemoveOnClose()
  106. {
  107. SSHAgent agent;
  108. agent.setEnabled(true);
  109. agent.setAuthSockOverride(m_agentSocketFileName);
  110. QVERIFY(agent.isAgentRunning());
  111. KeeAgentSettings settings;
  112. bool keyInAgent;
  113. settings.setRemoveAtDatabaseClose(true);
  114. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  115. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  116. agent.setEnabled(false);
  117. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  118. }
  119. void TestSSHAgent::testLifetimeConstraint()
  120. {
  121. SSHAgent agent;
  122. agent.setEnabled(true);
  123. agent.setAuthSockOverride(m_agentSocketFileName);
  124. QVERIFY(agent.isAgentRunning());
  125. KeeAgentSettings settings;
  126. bool keyInAgent;
  127. settings.setUseLifetimeConstraintWhenAdding(true);
  128. settings.setLifetimeConstraintDuration(2); // two seconds
  129. // identity should be in agent immediately after adding
  130. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  131. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  132. QElapsedTimer timer;
  133. timer.start();
  134. // wait for the identity to time out
  135. while (!timer.hasExpired(5000)) {
  136. QVERIFY(agent.checkIdentity(m_key, keyInAgent));
  137. if (!keyInAgent) {
  138. break;
  139. }
  140. QTest::qWait(100);
  141. }
  142. QVERIFY(!keyInAgent);
  143. }
  144. void TestSSHAgent::testConfirmConstraint()
  145. {
  146. SSHAgent agent;
  147. agent.setEnabled(true);
  148. agent.setAuthSockOverride(m_agentSocketFileName);
  149. QVERIFY(agent.isAgentRunning());
  150. KeeAgentSettings settings;
  151. bool keyInAgent;
  152. settings.setUseConfirmConstraintWhenAdding(true);
  153. QVERIFY(agent.addIdentity(m_key, settings, m_uuid));
  154. // we can't test confirmation itself is working but we can test the agent accepts the key
  155. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && keyInAgent);
  156. QVERIFY(agent.removeIdentity(m_key));
  157. QVERIFY(agent.checkIdentity(m_key, keyInAgent) && !keyInAgent);
  158. }
  159. void TestSSHAgent::cleanupTestCase()
  160. {
  161. if (m_agentProcess.state() != QProcess::NotRunning) {
  162. qDebug() << "Killing ssh-agent pid" << m_agentProcess.pid();
  163. m_agentProcess.terminate();
  164. m_agentProcess.waitForFinished();
  165. }
  166. m_agentSocketFile.remove();
  167. }