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 31KB

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