You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SshTestBase.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. /*
  2. * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.junit.ssh;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import static org.junit.Assert.assertArrayEquals;
  13. import static org.junit.Assert.assertEquals;
  14. import static org.junit.Assert.assertFalse;
  15. import static org.junit.Assert.assertNotNull;
  16. import static org.junit.Assert.assertThrows;
  17. import static org.junit.Assert.assertTrue;
  18. import static org.junit.Assume.assumeTrue;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.nio.file.Files;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.concurrent.TimeUnit;
  25. import org.eclipse.jgit.api.errors.TransportException;
  26. import org.eclipse.jgit.errors.CommandFailedException;
  27. import org.eclipse.jgit.transport.CredentialItem;
  28. import org.eclipse.jgit.transport.URIish;
  29. import org.eclipse.jgit.util.FS;
  30. import org.eclipse.jgit.util.SshSupport;
  31. import org.junit.Test;
  32. import org.junit.experimental.theories.DataPoints;
  33. import org.junit.experimental.theories.Theory;
  34. /**
  35. * The ssh tests. Concrete subclasses can re-use these tests by implementing the
  36. * abstract operations from {@link SshTestHarness}. This gives a way to test
  37. * different ssh clients against a unified test suite.
  38. */
  39. public abstract class SshTestBase extends SshBasicTestBase {
  40. @DataPoints
  41. public static String[] KEY_RESOURCES = { //
  42. "id_dsa", //
  43. "id_rsa_1024", //
  44. "id_rsa_2048", //
  45. "id_rsa_3072", //
  46. "id_rsa_4096", //
  47. "id_ecdsa_256", //
  48. "id_ecdsa_384", //
  49. "id_ecdsa_521", //
  50. "id_ed25519", //
  51. // And now encrypted. Passphrase is "testpass".
  52. "id_dsa_testpass", //
  53. "id_rsa_1024_testpass", //
  54. "id_rsa_2048_testpass", //
  55. "id_rsa_3072_testpass", //
  56. "id_rsa_4096_testpass", //
  57. "id_ecdsa_256_testpass", //
  58. "id_ecdsa_384_testpass", //
  59. "id_ecdsa_521_testpass", //
  60. "id_ed25519_testpass", //
  61. "id_ed25519_expensive_testpass" };
  62. @Test
  63. public void testSshWithoutConfig() throws Exception {
  64. assertThrows(TransportException.class,
  65. () -> cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
  66. + "/doesntmatter", defaultCloneDir, null));
  67. }
  68. @Test
  69. public void testSingleCommand() throws Exception {
  70. installConfig("IdentityFile " + privateKey1.getAbsolutePath());
  71. String command = SshTestGitServer.ECHO_COMMAND + " 1 without timeout";
  72. long start = System.nanoTime();
  73. String reply = SshSupport.runSshCommand(
  74. new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
  75. null, FS.DETECTED, command, 0); // 0 == no timeout
  76. long elapsed = System.nanoTime() - start;
  77. assertEquals(command, reply);
  78. // Now that we have an idea how long this takes on the test
  79. // infrastructure, try again with a timeout.
  80. command = SshTestGitServer.ECHO_COMMAND + " 1 expecting no timeout";
  81. // Still use a generous timeout.
  82. int timeout = 10 * ((int) TimeUnit.NANOSECONDS.toSeconds(elapsed) + 1);
  83. reply = SshSupport.runSshCommand(
  84. new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
  85. null, FS.DETECTED, command, timeout);
  86. assertEquals(command, reply);
  87. }
  88. @Test
  89. public void testSingleCommandWithTimeoutExpired() throws Exception {
  90. installConfig("IdentityFile " + privateKey1.getAbsolutePath());
  91. String command = SshTestGitServer.ECHO_COMMAND + " 2 EXPECTING TIMEOUT";
  92. CommandFailedException e = assertThrows(CommandFailedException.class,
  93. () -> SshSupport.runSshCommand(new URIish(
  94. "ssh://" + TEST_USER + "@localhost:" + testPort), null,
  95. FS.DETECTED, command, 1));
  96. assertTrue(e.getMessage().contains(command));
  97. assertTrue(e.getMessage().contains("time"));
  98. }
  99. @Test
  100. public void testSshWithGlobalIdentity() throws Exception {
  101. cloneWith(
  102. "ssh://" + TEST_USER + "@localhost:" + testPort
  103. + "/doesntmatter",
  104. defaultCloneDir, null,
  105. "IdentityFile " + privateKey1.getAbsolutePath());
  106. }
  107. @Test
  108. public void testSshWithDefaultIdentity() throws Exception {
  109. File idRsa = new File(privateKey1.getParentFile(), "id_rsa");
  110. Files.copy(privateKey1.toPath(), idRsa.toPath());
  111. // We expect the session factory to pick up these keys...
  112. cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
  113. + "/doesntmatter", defaultCloneDir, null);
  114. }
  115. @Test
  116. public void testSshWithConfigEncryptedUnusedKey() throws Exception {
  117. // Copy the encrypted test key from the bundle.
  118. File encryptedKey = new File(sshDir, "id_dsa");
  119. copyTestResource("id_dsa_testpass", encryptedKey);
  120. TestCredentialsProvider provider = new TestCredentialsProvider(
  121. "testpass");
  122. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  123. "Host localhost", //
  124. "HostName localhost", //
  125. "Port " + testPort, //
  126. "User " + TEST_USER, //
  127. "IdentityFile " + privateKey1.getAbsolutePath());
  128. assertEquals("CredentialsProvider should not have been called", 0,
  129. provider.getLog().size());
  130. }
  131. @Test
  132. public void testSshWithConfigEncryptedUnusedKeyInConfigLast()
  133. throws Exception {
  134. // Copy the encrypted test key from the bundle.
  135. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  136. copyTestResource("id_dsa_testpass", encryptedKey);
  137. TestCredentialsProvider provider = new TestCredentialsProvider(
  138. "testpass");
  139. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  140. "Host localhost", //
  141. "HostName localhost", //
  142. "Port " + testPort, //
  143. "User " + TEST_USER, //
  144. "IdentityFile " + privateKey1.getAbsolutePath(),
  145. "IdentityFile " + encryptedKey.getAbsolutePath());
  146. // This test passes with JSch per chance because JSch completely ignores
  147. // the second IdentityFile
  148. assertEquals("CredentialsProvider should not have been called", 0,
  149. provider.getLog().size());
  150. }
  151. private boolean isJsch() {
  152. return getSessionFactory().getType().equals("jsch");
  153. }
  154. @Test
  155. public void testSshWithConfigEncryptedUnusedKeyInConfigFirst()
  156. throws Exception {
  157. // Test cannot pass with JSch; it handles only one IdentityFile.
  158. // assumeTrue(!(getSessionFactory() instanceof
  159. // JschConfigSessionFactory)); gives in bazel a failure with "Never
  160. // found parameters that satisfied method assumptions."
  161. // In maven it's fine!?
  162. if (isJsch()) {
  163. return;
  164. }
  165. // Copy the encrypted test key from the bundle.
  166. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  167. copyTestResource("id_dsa_testpass", encryptedKey);
  168. TestCredentialsProvider provider = new TestCredentialsProvider(
  169. "testpass");
  170. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  171. "Host localhost", //
  172. "HostName localhost", //
  173. "Port " + testPort, //
  174. "User " + TEST_USER, //
  175. "IdentityFile " + encryptedKey.getAbsolutePath(),
  176. "IdentityFile " + privateKey1.getAbsolutePath());
  177. assertEquals("CredentialsProvider should have been called once", 1,
  178. provider.getLog().size());
  179. }
  180. @Test
  181. public void testSshEncryptedUsedKeyCached() throws Exception {
  182. // Make sure we are asked for the password only once if we do several
  183. // operations with an encrypted key.
  184. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  185. copyTestResource("id_dsa_testpass", encryptedKey);
  186. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  187. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  188. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  189. TestCredentialsProvider provider = new TestCredentialsProvider(
  190. "testpass");
  191. pushTo(provider,
  192. cloneWith("ssh://localhost/doesntmatter", //
  193. defaultCloneDir, provider, //
  194. "Host localhost", //
  195. "HostName localhost", //
  196. "Port " + testPort, //
  197. "User " + TEST_USER, //
  198. "IdentityFile " + encryptedKey.getAbsolutePath()));
  199. assertEquals("CredentialsProvider should have been called once", 1,
  200. provider.getLog().size());
  201. }
  202. @Test(expected = TransportException.class)
  203. public void testSshEncryptedUsedKeyWrongPassword() throws Exception {
  204. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  205. copyTestResource("id_dsa_testpass", encryptedKey);
  206. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  207. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  208. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  209. TestCredentialsProvider provider = new TestCredentialsProvider(
  210. "wrongpass");
  211. cloneWith("ssh://localhost/doesntmatter", //
  212. defaultCloneDir, provider, //
  213. "Host localhost", //
  214. "HostName localhost", //
  215. "Port " + testPort, //
  216. "User " + TEST_USER, //
  217. "NumberOfPasswordPrompts 1", //
  218. "IdentityFile " + encryptedKey.getAbsolutePath());
  219. }
  220. @Test
  221. public void testSshEncryptedUsedKeySeveralPassword() throws Exception {
  222. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  223. copyTestResource("id_dsa_testpass", encryptedKey);
  224. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  225. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  226. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  227. TestCredentialsProvider provider = new TestCredentialsProvider(
  228. "wrongpass", "wrongpass2", "testpass");
  229. cloneWith("ssh://localhost/doesntmatter", //
  230. defaultCloneDir, provider, //
  231. "Host localhost", //
  232. "HostName localhost", //
  233. "Port " + testPort, //
  234. "User " + TEST_USER, //
  235. "IdentityFile " + encryptedKey.getAbsolutePath());
  236. assertEquals("CredentialsProvider should have been called 3 times", 3,
  237. provider.getLog().size());
  238. }
  239. @Test(expected = TransportException.class)
  240. public void testSshWithoutKnownHosts() throws Exception {
  241. assertTrue("Could not delete known_hosts", knownHosts.delete());
  242. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  243. "Host localhost", //
  244. "HostName localhost", //
  245. "Port " + testPort, //
  246. "User " + TEST_USER, //
  247. "IdentityFile " + privateKey1.getAbsolutePath());
  248. }
  249. @Test
  250. public void testSshWithoutKnownHostsWithProviderAsk()
  251. throws Exception {
  252. File copiedHosts = new File(knownHosts.getParentFile(),
  253. "copiedKnownHosts");
  254. assertTrue("Failed to rename known_hosts",
  255. knownHosts.renameTo(copiedHosts));
  256. // The provider will answer "yes" to all questions, so we should be able
  257. // to connect and end up with a new known_hosts file with the host key.
  258. TestCredentialsProvider provider = new TestCredentialsProvider();
  259. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  260. "Host localhost", //
  261. "HostName localhost", //
  262. "Port " + testPort, //
  263. "User " + TEST_USER, //
  264. "IdentityFile " + privateKey1.getAbsolutePath());
  265. List<LogEntry> messages = provider.getLog();
  266. assertFalse("Expected user interaction", messages.isEmpty());
  267. if (isJsch()) {
  268. // JSch doesn't create a non-existing file.
  269. assertEquals("Expected to be asked about the key", 1,
  270. messages.size());
  271. return;
  272. }
  273. assertEquals(
  274. "Expected to be asked about the key, and the file creation",
  275. 2, messages.size());
  276. assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
  277. // Instead of checking the file contents, let's just clone again
  278. // without provider. If it works, the server host key was written
  279. // correctly.
  280. File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
  281. cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
  282. "Host localhost", //
  283. "HostName localhost", //
  284. "Port " + testPort, //
  285. "User " + TEST_USER, //
  286. "IdentityFile " + privateKey1.getAbsolutePath());
  287. }
  288. @Test
  289. public void testSshWithoutKnownHostsWithProviderAcceptNew()
  290. throws Exception {
  291. File copiedHosts = new File(knownHosts.getParentFile(),
  292. "copiedKnownHosts");
  293. assertTrue("Failed to rename known_hosts",
  294. knownHosts.renameTo(copiedHosts));
  295. TestCredentialsProvider provider = new TestCredentialsProvider();
  296. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  297. "Host localhost", //
  298. "HostName localhost", //
  299. "Port " + testPort, //
  300. "User " + TEST_USER, //
  301. "StrictHostKeyChecking accept-new", //
  302. "IdentityFile " + privateKey1.getAbsolutePath());
  303. if (isJsch()) {
  304. // JSch doesn't create new files.
  305. assertTrue("CredentialsProvider not called",
  306. provider.getLog().isEmpty());
  307. return;
  308. }
  309. assertEquals("Expected to be asked about the file creation", 1,
  310. provider.getLog().size());
  311. assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
  312. // Instead of checking the file contents, let's just clone again
  313. // without provider. If it works, the server host key was written
  314. // correctly.
  315. File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
  316. cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
  317. "Host localhost", //
  318. "HostName localhost", //
  319. "Port " + testPort, //
  320. "User " + TEST_USER, //
  321. "IdentityFile " + privateKey1.getAbsolutePath());
  322. }
  323. @Test(expected = TransportException.class)
  324. public void testSshWithoutKnownHostsDeny() throws Exception {
  325. File copiedHosts = new File(knownHosts.getParentFile(),
  326. "copiedKnownHosts");
  327. assertTrue("Failed to rename known_hosts",
  328. knownHosts.renameTo(copiedHosts));
  329. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  330. "Host localhost", //
  331. "HostName localhost", //
  332. "Port " + testPort, //
  333. "User " + TEST_USER, //
  334. "StrictHostKeyChecking yes", //
  335. "IdentityFile " + privateKey1.getAbsolutePath());
  336. }
  337. @Test(expected = TransportException.class)
  338. public void testSshModifiedHostKeyDeny()
  339. throws Exception {
  340. File copiedHosts = new File(knownHosts.getParentFile(),
  341. "copiedKnownHosts");
  342. assertTrue("Failed to rename known_hosts",
  343. knownHosts.renameTo(copiedHosts));
  344. // Now produce a new known_hosts file containing some other key.
  345. createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
  346. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  347. "Host localhost", //
  348. "HostName localhost", //
  349. "Port " + testPort, //
  350. "User " + TEST_USER, //
  351. "StrictHostKeyChecking yes", //
  352. "IdentityFile " + privateKey1.getAbsolutePath());
  353. }
  354. @Test(expected = TransportException.class)
  355. public void testSshModifiedHostKeyWithProviderDeny() throws Exception {
  356. File copiedHosts = new File(knownHosts.getParentFile(),
  357. "copiedKnownHosts");
  358. assertTrue("Failed to rename known_hosts",
  359. knownHosts.renameTo(copiedHosts));
  360. // Now produce a new known_hosts file containing some other key.
  361. createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
  362. TestCredentialsProvider provider = new TestCredentialsProvider();
  363. try {
  364. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  365. "Host localhost", //
  366. "HostName localhost", //
  367. "Port " + testPort, //
  368. "User " + TEST_USER, //
  369. "StrictHostKeyChecking yes", //
  370. "IdentityFile " + privateKey1.getAbsolutePath());
  371. } catch (Exception e) {
  372. assertEquals("Expected to be told about the modified key", 1,
  373. provider.getLog().size());
  374. assertTrue("Only messages expected", provider.getLog().stream()
  375. .flatMap(l -> l.getItems().stream()).allMatch(
  376. c -> c instanceof CredentialItem.InformationalMessage));
  377. throw e;
  378. }
  379. }
  380. private void checkKnownHostsModifiedHostKey(File backup, File newFile,
  381. String wrongKey) throws IOException {
  382. List<String> oldLines = Files.readAllLines(backup.toPath(), UTF_8);
  383. // Find the original entry. We should have that again in known_hosts.
  384. String oldKeyPart = null;
  385. for (String oldLine : oldLines) {
  386. if (oldLine.contains("[localhost]:")) {
  387. String[] parts = oldLine.split("\\s+");
  388. if (parts.length > 2) {
  389. oldKeyPart = parts[parts.length - 2] + ' '
  390. + parts[parts.length - 1];
  391. break;
  392. }
  393. }
  394. }
  395. assertNotNull("Old key not found", oldKeyPart);
  396. List<String> newLines = Files.readAllLines(newFile.toPath(), UTF_8);
  397. assertFalse("Old host key still found in known_hosts file" + newFile,
  398. hasHostKey("localhost", testPort, wrongKey, newLines));
  399. assertTrue("New host key not found in known_hosts file" + newFile,
  400. hasHostKey("localhost", testPort, oldKeyPart, newLines));
  401. }
  402. @Test
  403. public void testSshModifiedHostKeyAllow() throws Exception {
  404. assertTrue("Failed to delete known_hosts", knownHosts.delete());
  405. createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
  406. File backup = new File(getTemporaryDirectory(), "backupKnownHosts");
  407. Files.copy(knownHosts.toPath(), backup.toPath());
  408. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  409. "Host localhost", //
  410. "HostName localhost", //
  411. "Port " + testPort, //
  412. "User " + TEST_USER, //
  413. "StrictHostKeyChecking no", //
  414. "IdentityFile " + privateKey1.getAbsolutePath());
  415. // File should not have been updated!
  416. String[] oldLines = Files
  417. .readAllLines(backup.toPath(), UTF_8)
  418. .toArray(new String[0]);
  419. String[] newLines = Files
  420. .readAllLines(knownHosts.toPath(), UTF_8)
  421. .toArray(new String[0]);
  422. assertArrayEquals("Known hosts file should not be modified", oldLines,
  423. newLines);
  424. }
  425. @Test
  426. public void testSshModifiedHostKeyAsk() throws Exception {
  427. File copiedHosts = new File(knownHosts.getParentFile(),
  428. "copiedKnownHosts");
  429. assertTrue("Failed to rename known_hosts",
  430. knownHosts.renameTo(copiedHosts));
  431. String wrongKeyPart = createKnownHostsFile(knownHosts, "localhost",
  432. testPort, publicKey1);
  433. TestCredentialsProvider provider = new TestCredentialsProvider();
  434. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  435. "Host localhost", //
  436. "HostName localhost", //
  437. "Port " + testPort, //
  438. "User " + TEST_USER, //
  439. "IdentityFile " + privateKey1.getAbsolutePath());
  440. checkKnownHostsModifiedHostKey(copiedHosts, knownHosts, wrongKeyPart);
  441. assertEquals("Expected to be asked about the modified key", 1,
  442. provider.getLog().size());
  443. }
  444. @Test
  445. public void testSshCloneWithConfigAndPush() throws Exception {
  446. pushTo(cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  447. "Host localhost", //
  448. "HostName localhost", //
  449. "Port " + testPort, //
  450. "User " + TEST_USER, //
  451. "IdentityFile " + privateKey1.getAbsolutePath()));
  452. }
  453. @Test
  454. public void testSftpWithConfig() throws Exception {
  455. cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
  456. "Host localhost", //
  457. "HostName localhost", //
  458. "Port " + testPort, //
  459. "User " + TEST_USER, //
  460. "IdentityFile " + privateKey1.getAbsolutePath());
  461. }
  462. @Test
  463. public void testSftpCloneWithConfigAndPush() throws Exception {
  464. pushTo(cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
  465. "Host localhost", //
  466. "HostName localhost", //
  467. "Port " + testPort, //
  468. "User " + TEST_USER, //
  469. "IdentityFile " + privateKey1.getAbsolutePath()));
  470. }
  471. @Test(expected = TransportException.class)
  472. public void testSshWithConfigWrongKey() throws Exception {
  473. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  474. "Host localhost", //
  475. "HostName localhost", //
  476. "Port " + testPort, //
  477. "User " + TEST_USER, //
  478. "IdentityFile " + privateKey2.getAbsolutePath());
  479. }
  480. @Test
  481. public void testSshWithWrongUserNameInConfig() throws Exception {
  482. // Bug 526778
  483. cloneWith(
  484. "ssh://" + TEST_USER + "@localhost:" + testPort
  485. + "/doesntmatter",
  486. defaultCloneDir, null, //
  487. "Host localhost", //
  488. "HostName localhost", //
  489. "User sombody_else", //
  490. "IdentityFile " + privateKey1.getAbsolutePath());
  491. }
  492. @Test
  493. public void testSshWithWrongPortInConfig() throws Exception {
  494. // Bug 526778
  495. cloneWith(
  496. "ssh://" + TEST_USER + "@localhost:" + testPort
  497. + "/doesntmatter",
  498. defaultCloneDir, null, //
  499. "Host localhost", //
  500. "HostName localhost", //
  501. "Port 22", //
  502. "User " + TEST_USER, //
  503. "IdentityFile " + privateKey1.getAbsolutePath());
  504. }
  505. @Test
  506. public void testSshWithAliasInConfig() throws Exception {
  507. // Bug 531118
  508. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  509. "Host git", //
  510. "HostName localhost", //
  511. "Port " + testPort, //
  512. "User " + TEST_USER, //
  513. "IdentityFile " + privateKey1.getAbsolutePath(), "", //
  514. "Host localhost", //
  515. "HostName localhost", //
  516. "Port 22", //
  517. "User someone_else", //
  518. "IdentityFile " + privateKey2.getAbsolutePath());
  519. }
  520. @Test
  521. public void testSshWithUnknownCiphersInConfig() throws Exception {
  522. // Bug 535672
  523. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  524. "Host git", //
  525. "HostName localhost", //
  526. "Port " + testPort, //
  527. "User " + TEST_USER, //
  528. "IdentityFile " + privateKey1.getAbsolutePath(), //
  529. "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
  530. }
  531. @Test
  532. public void testSshWithUnknownHostKeyAlgorithmsInConfig()
  533. throws Exception {
  534. // Bug 535672
  535. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  536. "Host git", //
  537. "HostName localhost", //
  538. "Port " + testPort, //
  539. "User " + TEST_USER, //
  540. "IdentityFile " + privateKey1.getAbsolutePath(), //
  541. "HostKeyAlgorithms foobar,ssh-rsa,ssh-dss");
  542. }
  543. @Test
  544. public void testSshWithUnknownKexAlgorithmsInConfig()
  545. throws Exception {
  546. // Bug 535672
  547. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  548. "Host git", //
  549. "HostName localhost", //
  550. "Port " + testPort, //
  551. "User " + TEST_USER, //
  552. "IdentityFile " + privateKey1.getAbsolutePath(), //
  553. "KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521");
  554. }
  555. @Test
  556. public void testSshWithMinimalHostKeyAlgorithmsInConfig()
  557. throws Exception {
  558. // Bug 537790
  559. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  560. "Host git", //
  561. "HostName localhost", //
  562. "Port " + testPort, //
  563. "User " + TEST_USER, //
  564. "IdentityFile " + privateKey1.getAbsolutePath(), //
  565. "HostKeyAlgorithms ssh-rsa,ssh-dss");
  566. }
  567. @Test
  568. public void testSshWithUnknownAuthInConfig() throws Exception {
  569. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  570. "Host git", //
  571. "HostName localhost", //
  572. "Port " + testPort, //
  573. "User " + TEST_USER, //
  574. "IdentityFile " + privateKey1.getAbsolutePath(), //
  575. "PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password");
  576. }
  577. @Test(expected = TransportException.class)
  578. public void testSshWithNoMatchingAuthInConfig() throws Exception {
  579. // Server doesn't do password, and anyway we set no password.
  580. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  581. "Host git", //
  582. "HostName localhost", //
  583. "Port " + testPort, //
  584. "User " + TEST_USER, //
  585. "IdentityFile " + privateKey1.getAbsolutePath(), //
  586. "PreferredAuthentications password");
  587. }
  588. @Test
  589. public void testRsaHostKeySecond() throws Exception {
  590. // See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA
  591. // (preferred), RSA, we have RSA in known_hosts: client and server
  592. // should agree on RSA.
  593. File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
  594. copyTestResource("id_ecdsa_256", newHostKey);
  595. server.addHostKey(newHostKey.toPath(), true);
  596. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  597. "Host git", //
  598. "HostName localhost", //
  599. "Port " + testPort, //
  600. "User " + TEST_USER, //
  601. "IdentityFile " + privateKey1.getAbsolutePath());
  602. }
  603. @Test
  604. public void testEcDsaHostKey() throws Exception {
  605. // See https://git.eclipse.org/r/#/c/130402/ : server has RSA
  606. // (preferred), EcDSA, we have EcDSA in known_hosts: client and server
  607. // should agree on EcDSA.
  608. File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
  609. copyTestResource("id_ecdsa_256", newHostKey);
  610. server.addHostKey(newHostKey.toPath(), false);
  611. File newHostKeyPub = new File(getTemporaryDirectory(),
  612. "newhostkey.pub");
  613. copyTestResource("id_ecdsa_256.pub", newHostKeyPub);
  614. createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
  615. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  616. "Host git", //
  617. "HostName localhost", //
  618. "Port " + testPort, //
  619. "User " + TEST_USER, //
  620. "IdentityFile " + privateKey1.getAbsolutePath());
  621. }
  622. @Test
  623. public void testPasswordAuth() throws Exception {
  624. server.enablePasswordAuthentication();
  625. TestCredentialsProvider provider = new TestCredentialsProvider(
  626. TEST_USER.toUpperCase(Locale.ROOT));
  627. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  628. "Host git", //
  629. "HostName localhost", //
  630. "Port " + testPort, //
  631. "User " + TEST_USER, //
  632. "PreferredAuthentications password");
  633. }
  634. @Test
  635. public void testPasswordAuthSeveralTimes() throws Exception {
  636. server.enablePasswordAuthentication();
  637. TestCredentialsProvider provider = new TestCredentialsProvider(
  638. "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
  639. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  640. "Host git", //
  641. "HostName localhost", //
  642. "Port " + testPort, //
  643. "User " + TEST_USER, //
  644. "PreferredAuthentications password");
  645. }
  646. @Test(expected = TransportException.class)
  647. public void testPasswordAuthWrongPassword() throws Exception {
  648. server.enablePasswordAuthentication();
  649. TestCredentialsProvider provider = new TestCredentialsProvider(
  650. "wrongpass");
  651. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  652. "Host git", //
  653. "HostName localhost", //
  654. "Port " + testPort, //
  655. "User " + TEST_USER, //
  656. "PreferredAuthentications password");
  657. }
  658. @Test(expected = TransportException.class)
  659. public void testPasswordAuthNoPassword() throws Exception {
  660. server.enablePasswordAuthentication();
  661. TestCredentialsProvider provider = new TestCredentialsProvider();
  662. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  663. "Host git", //
  664. "HostName localhost", //
  665. "Port " + testPort, //
  666. "User " + TEST_USER, //
  667. "PreferredAuthentications password");
  668. }
  669. @Test(expected = TransportException.class)
  670. public void testPasswordAuthCorrectPasswordTooLate() throws Exception {
  671. server.enablePasswordAuthentication();
  672. TestCredentialsProvider provider = new TestCredentialsProvider(
  673. "wrongpass", "wrongpass", "wrongpass",
  674. TEST_USER.toUpperCase(Locale.ROOT));
  675. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  676. "Host git", //
  677. "HostName localhost", //
  678. "Port " + testPort, //
  679. "User " + TEST_USER, //
  680. "PreferredAuthentications password");
  681. }
  682. @Test
  683. public void testKeyboardInteractiveAuth() throws Exception {
  684. server.enableKeyboardInteractiveAuthentication();
  685. TestCredentialsProvider provider = new TestCredentialsProvider(
  686. TEST_USER.toUpperCase(Locale.ROOT));
  687. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  688. "Host git", //
  689. "HostName localhost", //
  690. "Port " + testPort, //
  691. "User " + TEST_USER, //
  692. "PreferredAuthentications keyboard-interactive");
  693. }
  694. @Test
  695. public void testKeyboardInteractiveAuthSeveralTimes() throws Exception {
  696. server.enableKeyboardInteractiveAuthentication();
  697. TestCredentialsProvider provider = new TestCredentialsProvider(
  698. "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
  699. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  700. "Host git", //
  701. "HostName localhost", //
  702. "Port " + testPort, //
  703. "User " + TEST_USER, //
  704. "PreferredAuthentications keyboard-interactive");
  705. }
  706. @Test(expected = TransportException.class)
  707. public void testKeyboardInteractiveAuthWrongPassword() throws Exception {
  708. server.enableKeyboardInteractiveAuthentication();
  709. TestCredentialsProvider provider = new TestCredentialsProvider(
  710. "wrongpass");
  711. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  712. "Host git", //
  713. "HostName localhost", //
  714. "Port " + testPort, //
  715. "User " + TEST_USER, //
  716. "PreferredAuthentications keyboard-interactive");
  717. }
  718. @Test(expected = TransportException.class)
  719. public void testKeyboardInteractiveAuthNoPassword() throws Exception {
  720. server.enableKeyboardInteractiveAuthentication();
  721. TestCredentialsProvider provider = new TestCredentialsProvider();
  722. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  723. "Host git", //
  724. "HostName localhost", //
  725. "Port " + testPort, //
  726. "User " + TEST_USER, //
  727. "PreferredAuthentications keyboard-interactive");
  728. }
  729. @Test(expected = TransportException.class)
  730. public void testKeyboardInteractiveAuthCorrectPasswordTooLate()
  731. throws Exception {
  732. server.enableKeyboardInteractiveAuthentication();
  733. TestCredentialsProvider provider = new TestCredentialsProvider(
  734. "wrongpass", "wrongpass", "wrongpass",
  735. TEST_USER.toUpperCase(Locale.ROOT));
  736. cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
  737. "Host git", //
  738. "HostName localhost", //
  739. "Port " + testPort, //
  740. "User " + TEST_USER, //
  741. "PreferredAuthentications keyboard-interactive");
  742. }
  743. @Theory
  744. public void testSshKeys(String keyName) throws Exception {
  745. // JSch fails on ECDSA 384/521 keys. Compare
  746. // https://sourceforge.net/p/jsch/patches/10/
  747. assumeTrue(!(isJsch() && (keyName.contains("ed25519")
  748. || keyName.startsWith("id_ecdsa_384")
  749. || keyName.startsWith("id_ecdsa_521"))));
  750. File cloned = new File(getTemporaryDirectory(), "cloned");
  751. String keyFileName = keyName + "_key";
  752. File privateKey = new File(sshDir, keyFileName);
  753. copyTestResource(keyName, privateKey);
  754. File publicKey = new File(sshDir, keyFileName + ".pub");
  755. copyTestResource(keyName + ".pub", publicKey);
  756. server.setTestUserPublicKey(publicKey.toPath());
  757. TestCredentialsProvider provider = new TestCredentialsProvider(
  758. "testpass");
  759. pushTo(provider,
  760. cloneWith("ssh://localhost/doesntmatter", //
  761. cloned, provider, //
  762. "Host localhost", //
  763. "HostName localhost", //
  764. "Port " + testPort, //
  765. "User " + TEST_USER, //
  766. "IdentityFile " + privateKey.getAbsolutePath()));
  767. int expectedCalls = keyName.endsWith("testpass") ? 1 : 0;
  768. assertEquals("Unexpected calls to CredentialsProvider", expectedCalls,
  769. provider.getLog().size());
  770. // Should now also work without credentials provider, even if the key
  771. // was encrypted.
  772. cloned = new File(getTemporaryDirectory(), "cloned2");
  773. pushTo(null,
  774. cloneWith("ssh://localhost/doesntmatter", //
  775. cloned, null, //
  776. "Host localhost", //
  777. "HostName localhost", //
  778. "Port " + testPort, //
  779. "User " + TEST_USER, //
  780. "IdentityFile " + privateKey.getAbsolutePath()));
  781. }
  782. }