Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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 java.nio.charset.StandardCharsets.UTF_8;
  45. import static org.junit.Assert.assertArrayEquals;
  46. import static org.junit.Assert.assertEquals;
  47. import static org.junit.Assert.assertFalse;
  48. import static org.junit.Assert.assertNotNull;
  49. import static org.junit.Assert.assertTrue;
  50. import static org.junit.Assume.assumeTrue;
  51. import java.io.File;
  52. import java.io.IOException;
  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. "id_ed25519_testpass", //
  89. "id_ed25519_expensive_testpass" };
  90. protected File defaultCloneDir;
  91. @Override
  92. public void setUp() throws Exception {
  93. super.setUp();
  94. defaultCloneDir = new File(getTemporaryDirectory(), "cloned");
  95. }
  96. @Test(expected = TransportException.class)
  97. public void testSshWithoutConfig() throws Exception {
  98. cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
  99. + "/doesntmatter", defaultCloneDir, null);
  100. }
  101. @Test
  102. public void testSshWithGlobalIdentity() throws Exception {
  103. cloneWith(
  104. "ssh://" + TEST_USER + "@localhost:" + testPort
  105. + "/doesntmatter",
  106. defaultCloneDir, null,
  107. "IdentityFile " + privateKey1.getAbsolutePath());
  108. }
  109. @Test
  110. public void testSshWithDefaultIdentity() throws Exception {
  111. File idRsa = new File(privateKey1.getParentFile(), "id_rsa");
  112. Files.copy(privateKey1.toPath(), idRsa.toPath());
  113. // We expect the session factory to pick up these keys...
  114. cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
  115. + "/doesntmatter", defaultCloneDir, null);
  116. }
  117. @Test
  118. public void testSshWithConfig() throws Exception {
  119. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  120. "Host localhost", //
  121. "HostName localhost", //
  122. "Port " + testPort, //
  123. "User " + TEST_USER, //
  124. "IdentityFile " + privateKey1.getAbsolutePath());
  125. }
  126. @Test
  127. public void testSshWithConfigEncryptedUnusedKey() throws Exception {
  128. // Copy the encrypted test key from the bundle.
  129. File encryptedKey = new File(sshDir, "id_dsa");
  130. copyTestResource("id_dsa_testpass", encryptedKey);
  131. TestCredentialsProvider provider = new TestCredentialsProvider(
  132. "testpass");
  133. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  134. "Host localhost", //
  135. "HostName localhost", //
  136. "Port " + testPort, //
  137. "User " + TEST_USER, //
  138. "IdentityFile " + privateKey1.getAbsolutePath());
  139. assertEquals("CredentialsProvider should not have been called", 0,
  140. provider.getLog().size());
  141. }
  142. @Test
  143. public void testSshWithConfigEncryptedUnusedKeyInConfigLast()
  144. throws Exception {
  145. // Copy the encrypted test key from the bundle.
  146. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  147. copyTestResource("id_dsa_testpass", encryptedKey);
  148. TestCredentialsProvider provider = new TestCredentialsProvider(
  149. "testpass");
  150. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  151. "Host localhost", //
  152. "HostName localhost", //
  153. "Port " + testPort, //
  154. "User " + TEST_USER, //
  155. "IdentityFile " + privateKey1.getAbsolutePath(),
  156. "IdentityFile " + encryptedKey.getAbsolutePath());
  157. // This test passes with JSch per chance because JSch completely ignores
  158. // the second IdentityFile
  159. assertEquals("CredentialsProvider should not have been called", 0,
  160. provider.getLog().size());
  161. }
  162. @Test
  163. public void testSshWithConfigEncryptedUnusedKeyInConfigFirst()
  164. throws Exception {
  165. // Test cannot pass with JSch; it handles only one IdentityFile.
  166. // assumeTrue(!(getSessionFactory() instanceof
  167. // JschConfigSessionFactory)); gives in bazel a failure with "Never
  168. // found parameters that satisfied method assumptions."
  169. // In maven it's fine!?
  170. if (getSessionFactory() instanceof JschConfigSessionFactory) {
  171. return;
  172. }
  173. // Copy the encrypted test key from the bundle.
  174. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  175. copyTestResource("id_dsa_testpass", encryptedKey);
  176. TestCredentialsProvider provider = new TestCredentialsProvider(
  177. "testpass");
  178. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  179. "Host localhost", //
  180. "HostName localhost", //
  181. "Port " + testPort, //
  182. "User " + TEST_USER, //
  183. "IdentityFile " + encryptedKey.getAbsolutePath(),
  184. "IdentityFile " + privateKey1.getAbsolutePath());
  185. assertEquals("CredentialsProvider should have been called once", 1,
  186. provider.getLog().size());
  187. }
  188. @Test
  189. public void testSshEncryptedUsedKeyCached() throws Exception {
  190. // Make sure we are asked for the password only once if we do several
  191. // operations with an encrypted key.
  192. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  193. copyTestResource("id_dsa_testpass", encryptedKey);
  194. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  195. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  196. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  197. TestCredentialsProvider provider = new TestCredentialsProvider(
  198. "testpass");
  199. pushTo(provider,
  200. cloneWith("ssh://localhost/doesntmatter", //
  201. defaultCloneDir, provider, //
  202. "Host localhost", //
  203. "HostName localhost", //
  204. "Port " + testPort, //
  205. "User " + TEST_USER, //
  206. "IdentityFile " + encryptedKey.getAbsolutePath()));
  207. assertEquals("CredentialsProvider should have been called once", 1,
  208. provider.getLog().size());
  209. }
  210. @Test(expected = TransportException.class)
  211. public void testSshEncryptedUsedKeyWrongPassword() throws Exception {
  212. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  213. copyTestResource("id_dsa_testpass", encryptedKey);
  214. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  215. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  216. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  217. TestCredentialsProvider provider = new TestCredentialsProvider(
  218. "wrongpass");
  219. cloneWith("ssh://localhost/doesntmatter", //
  220. defaultCloneDir, provider, //
  221. "Host localhost", //
  222. "HostName localhost", //
  223. "Port " + testPort, //
  224. "User " + TEST_USER, //
  225. "NumberOfPasswordPrompts 1", //
  226. "IdentityFile " + encryptedKey.getAbsolutePath());
  227. }
  228. @Test
  229. public void testSshEncryptedUsedKeySeveralPassword() throws Exception {
  230. File encryptedKey = new File(sshDir, "id_dsa_test_key");
  231. copyTestResource("id_dsa_testpass", encryptedKey);
  232. File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
  233. copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
  234. server.setTestUserPublicKey(encryptedPublicKey.toPath());
  235. TestCredentialsProvider provider = new TestCredentialsProvider(
  236. "wrongpass", "wrongpass2", "testpass");
  237. cloneWith("ssh://localhost/doesntmatter", //
  238. defaultCloneDir, provider, //
  239. "Host localhost", //
  240. "HostName localhost", //
  241. "Port " + testPort, //
  242. "User " + TEST_USER, //
  243. "IdentityFile " + encryptedKey.getAbsolutePath());
  244. assertEquals("CredentialsProvider should have been called 3 times", 3,
  245. provider.getLog().size());
  246. }
  247. @Test(expected = TransportException.class)
  248. public void testSshWithoutKnownHosts() throws Exception {
  249. assertTrue("Could not delete known_hosts", knownHosts.delete());
  250. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  251. "Host localhost", //
  252. "HostName localhost", //
  253. "Port " + testPort, //
  254. "User " + TEST_USER, //
  255. "IdentityFile " + privateKey1.getAbsolutePath());
  256. }
  257. @Test
  258. public void testSshWithoutKnownHostsWithProviderAsk()
  259. throws Exception {
  260. File copiedHosts = new File(knownHosts.getParentFile(),
  261. "copiedKnownHosts");
  262. assertTrue("Failed to rename known_hosts",
  263. knownHosts.renameTo(copiedHosts));
  264. // The provider will answer "yes" to all questions, so we should be able
  265. // to connect and end up with a new known_hosts file with the host key.
  266. TestCredentialsProvider provider = new TestCredentialsProvider();
  267. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  268. "Host localhost", //
  269. "HostName localhost", //
  270. "Port " + testPort, //
  271. "User " + TEST_USER, //
  272. "IdentityFile " + privateKey1.getAbsolutePath());
  273. List<LogEntry> messages = provider.getLog();
  274. assertFalse("Expected user interaction", messages.isEmpty());
  275. if (getSessionFactory() instanceof JschConfigSessionFactory) {
  276. // JSch doesn't create a non-existing file.
  277. assertEquals("Expected to be asked about the key", 1,
  278. messages.size());
  279. return;
  280. }
  281. assertEquals(
  282. "Expected to be asked about the key, and the file creation",
  283. 2, messages.size());
  284. assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
  285. // Instead of checking the file contents, let's just clone again
  286. // without provider. If it works, the server host key was written
  287. // correctly.
  288. File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
  289. cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
  290. "Host localhost", //
  291. "HostName localhost", //
  292. "Port " + testPort, //
  293. "User " + TEST_USER, //
  294. "IdentityFile " + privateKey1.getAbsolutePath());
  295. }
  296. @Test
  297. public void testSshWithoutKnownHostsWithProviderAcceptNew()
  298. throws Exception {
  299. File copiedHosts = new File(knownHosts.getParentFile(),
  300. "copiedKnownHosts");
  301. assertTrue("Failed to rename known_hosts",
  302. knownHosts.renameTo(copiedHosts));
  303. TestCredentialsProvider provider = new TestCredentialsProvider();
  304. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  305. "Host localhost", //
  306. "HostName localhost", //
  307. "Port " + testPort, //
  308. "User " + TEST_USER, //
  309. "StrictHostKeyChecking accept-new", //
  310. "IdentityFile " + privateKey1.getAbsolutePath());
  311. if (getSessionFactory() instanceof JschConfigSessionFactory) {
  312. // JSch doesn't create new files.
  313. assertTrue("CredentialsProvider not called",
  314. provider.getLog().isEmpty());
  315. return;
  316. }
  317. assertEquals("Expected to be asked about the file creation", 1,
  318. provider.getLog().size());
  319. assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
  320. // Instead of checking the file contents, let's just clone again
  321. // without provider. If it works, the server host key was written
  322. // correctly.
  323. File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
  324. cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
  325. "Host localhost", //
  326. "HostName localhost", //
  327. "Port " + testPort, //
  328. "User " + TEST_USER, //
  329. "IdentityFile " + privateKey1.getAbsolutePath());
  330. }
  331. @Test(expected = TransportException.class)
  332. public void testSshWithoutKnownHostsDeny() throws Exception {
  333. File copiedHosts = new File(knownHosts.getParentFile(),
  334. "copiedKnownHosts");
  335. assertTrue("Failed to rename known_hosts",
  336. knownHosts.renameTo(copiedHosts));
  337. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  338. "Host localhost", //
  339. "HostName localhost", //
  340. "Port " + testPort, //
  341. "User " + TEST_USER, //
  342. "StrictHostKeyChecking yes", //
  343. "IdentityFile " + privateKey1.getAbsolutePath());
  344. }
  345. @Test(expected = TransportException.class)
  346. public void testSshModifiedHostKeyDeny()
  347. throws Exception {
  348. File copiedHosts = new File(knownHosts.getParentFile(),
  349. "copiedKnownHosts");
  350. assertTrue("Failed to rename known_hosts",
  351. knownHosts.renameTo(copiedHosts));
  352. // Now produce a new known_hosts file containing some other key.
  353. createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
  354. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
  355. "Host localhost", //
  356. "HostName localhost", //
  357. "Port " + testPort, //
  358. "User " + TEST_USER, //
  359. "StrictHostKeyChecking yes", //
  360. "IdentityFile " + privateKey1.getAbsolutePath());
  361. }
  362. @Test(expected = TransportException.class)
  363. public void testSshModifiedHostKeyWithProviderDeny() throws Exception {
  364. File copiedHosts = new File(knownHosts.getParentFile(),
  365. "copiedKnownHosts");
  366. assertTrue("Failed to rename known_hosts",
  367. knownHosts.renameTo(copiedHosts));
  368. // Now produce a new known_hosts file containing some other key.
  369. createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
  370. TestCredentialsProvider provider = new TestCredentialsProvider();
  371. try {
  372. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  373. "Host localhost", //
  374. "HostName localhost", //
  375. "Port " + testPort, //
  376. "User " + TEST_USER, //
  377. "StrictHostKeyChecking yes", //
  378. "IdentityFile " + privateKey1.getAbsolutePath());
  379. } catch (Exception e) {
  380. assertEquals("Expected to be told about the modified key", 1,
  381. provider.getLog().size());
  382. assertTrue("Only messages expected", provider.getLog().stream()
  383. .flatMap(l -> l.getItems().stream()).allMatch(
  384. c -> c instanceof CredentialItem.InformationalMessage));
  385. throw e;
  386. }
  387. }
  388. private void checkKnownHostsModifiedHostKey(File backup, File newFile,
  389. String wrongKey) throws IOException {
  390. List<String> oldLines = Files.readAllLines(backup.toPath(), UTF_8);
  391. // Find the original entry. We should have that again in known_hosts.
  392. String oldKeyPart = null;
  393. for (String oldLine : oldLines) {
  394. if (oldLine.contains("[localhost]:")) {
  395. String[] parts = oldLine.split("\\s+");
  396. if (parts.length > 2) {
  397. oldKeyPart = parts[parts.length - 2] + ' '
  398. + parts[parts.length - 1];
  399. break;
  400. }
  401. }
  402. }
  403. assertNotNull("Old key not found", oldKeyPart);
  404. List<String> newLines = Files.readAllLines(newFile.toPath(), 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(), UTF_8)
  426. .toArray(new String[0]);
  427. String[] newLines = Files
  428. .readAllLines(knownHosts.toPath(), 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. }