Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

SshTestBase.java 31KB

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