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.

ApacheSshTest.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  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.transport.sshd;
  11. import static org.apache.sshd.core.CoreModuleProperties.MAX_CONCURRENT_SESSIONS;
  12. import static org.junit.Assert.assertEquals;
  13. import static org.junit.Assert.assertFalse;
  14. import static org.junit.Assert.assertNotNull;
  15. import static org.junit.Assert.assertThrows;
  16. import static org.junit.Assert.assertTrue;
  17. import java.io.BufferedWriter;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.UncheckedIOException;
  21. import java.net.URISyntaxException;
  22. import java.nio.charset.StandardCharsets;
  23. import java.nio.file.Files;
  24. import java.nio.file.StandardOpenOption;
  25. import java.security.KeyPair;
  26. import java.security.KeyPairGenerator;
  27. import java.security.PublicKey;
  28. import java.util.Arrays;
  29. import java.util.Collections;
  30. import java.util.List;
  31. import java.util.stream.Collectors;
  32. import org.apache.sshd.client.config.hosts.KnownHostEntry;
  33. import org.apache.sshd.client.config.hosts.KnownHostHashValue;
  34. import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
  35. import org.apache.sshd.common.config.keys.KeyUtils;
  36. import org.apache.sshd.common.config.keys.PublicKeyEntry;
  37. import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
  38. import org.apache.sshd.common.session.Session;
  39. import org.apache.sshd.common.util.net.SshdSocketAddress;
  40. import org.apache.sshd.server.ServerAuthenticationManager;
  41. import org.apache.sshd.server.SshServer;
  42. import org.apache.sshd.server.forward.StaticDecisionForwardingFilter;
  43. import org.eclipse.jgit.api.Git;
  44. import org.eclipse.jgit.api.errors.TransportException;
  45. import org.eclipse.jgit.junit.ssh.SshTestBase;
  46. import org.eclipse.jgit.lib.Constants;
  47. import org.eclipse.jgit.transport.RemoteSession;
  48. import org.eclipse.jgit.transport.SshSessionFactory;
  49. import org.eclipse.jgit.transport.URIish;
  50. import org.eclipse.jgit.util.FS;
  51. import org.junit.Test;
  52. import org.junit.experimental.theories.Theories;
  53. import org.junit.runner.RunWith;
  54. @RunWith(Theories.class)
  55. public class ApacheSshTest extends SshTestBase {
  56. @Override
  57. protected SshSessionFactory createSessionFactory() {
  58. SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(),
  59. null);
  60. // The home directory is mocked at this point!
  61. result.setHomeDirectory(FS.DETECTED.userHome());
  62. result.setSshDirectory(sshDir);
  63. return result;
  64. }
  65. @Override
  66. protected void installConfig(String... config) {
  67. File configFile = new File(sshDir, Constants.CONFIG);
  68. if (config != null) {
  69. try {
  70. Files.write(configFile.toPath(), Arrays.asList(config));
  71. } catch (IOException e) {
  72. throw new UncheckedIOException(e);
  73. }
  74. }
  75. }
  76. @Test
  77. public void testEd25519HostKey() throws Exception {
  78. // Using ed25519 user identities is tested in the super class in
  79. // testSshKeys().
  80. File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
  81. copyTestResource("id_ed25519", newHostKey);
  82. server.addHostKey(newHostKey.toPath(), true);
  83. File newHostKeyPub = new File(getTemporaryDirectory(),
  84. "newhostkey.pub");
  85. copyTestResource("id_ed25519.pub", newHostKeyPub);
  86. createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
  87. cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
  88. "Host git", //
  89. "HostName localhost", //
  90. "Port " + testPort, //
  91. "User " + TEST_USER, //
  92. "IdentityFile " + privateKey1.getAbsolutePath());
  93. }
  94. @Test
  95. public void testHashedKnownHosts() throws Exception {
  96. assertTrue("Failed to delete known_hosts", knownHosts.delete());
  97. // The provider will answer "yes" to all questions, so we should be able
  98. // to connect and end up with a new known_hosts file with the host key.
  99. TestCredentialsProvider provider = new TestCredentialsProvider();
  100. cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
  101. "HashKnownHosts yes", //
  102. "Host localhost", //
  103. "HostName localhost", //
  104. "Port " + testPort, //
  105. "User " + TEST_USER, //
  106. "IdentityFile " + privateKey1.getAbsolutePath());
  107. List<LogEntry> messages = provider.getLog();
  108. assertFalse("Expected user interaction", messages.isEmpty());
  109. assertEquals(
  110. "Expected to be asked about the key, and the file creation", 2,
  111. messages.size());
  112. assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
  113. // Let's clone again without provider. If it works, the server host key
  114. // was written correctly.
  115. File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
  116. cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
  117. "Host localhost", //
  118. "HostName localhost", //
  119. "Port " + testPort, //
  120. "User " + TEST_USER, //
  121. "IdentityFile " + privateKey1.getAbsolutePath());
  122. // Check that the first line contains neither "localhost" nor
  123. // "127.0.0.1", but does contain the expected hash.
  124. List<String> lines = Files.readAllLines(knownHosts.toPath()).stream()
  125. .filter(s -> s != null && s.length() >= 1 && s.charAt(0) != '#'
  126. && !s.trim().isEmpty())
  127. .collect(Collectors.toList());
  128. assertEquals("Unexpected number of known_hosts lines", 1, lines.size());
  129. String line = lines.get(0);
  130. assertFalse("Found host in line", line.contains("localhost"));
  131. assertFalse("Found IP in line", line.contains("127.0.0.1"));
  132. assertTrue("Hash not found", line.contains("|"));
  133. KnownHostEntry entry = KnownHostEntry.parseKnownHostEntry(line);
  134. assertTrue("Hash doesn't match localhost",
  135. entry.isHostMatch("localhost", testPort)
  136. || entry.isHostMatch("127.0.0.1", testPort));
  137. }
  138. @Test
  139. public void testPreamble() throws Exception {
  140. // Test that the client can deal with strange lines being sent before
  141. // the server identification string.
  142. StringBuilder b = new StringBuilder();
  143. for (int i = 0; i < 257; i++) {
  144. b.append('a');
  145. }
  146. server.setPreamble("A line with a \000 NUL",
  147. "A long line: " + b.toString());
  148. cloneWith(
  149. "ssh://" + TEST_USER + "@localhost:" + testPort
  150. + "/doesntmatter",
  151. defaultCloneDir, null,
  152. "IdentityFile " + privateKey1.getAbsolutePath());
  153. }
  154. @Test
  155. public void testLongPreamble() throws Exception {
  156. // Test that the client can deal with a long (about 60k) preamble.
  157. StringBuilder b = new StringBuilder();
  158. for (int i = 0; i < 1024; i++) {
  159. b.append('a');
  160. }
  161. String line = b.toString();
  162. String[] lines = new String[60];
  163. for (int i = 0; i < lines.length; i++) {
  164. lines[i] = line;
  165. }
  166. server.setPreamble(lines);
  167. cloneWith(
  168. "ssh://" + TEST_USER + "@localhost:" + testPort
  169. + "/doesntmatter",
  170. defaultCloneDir, null,
  171. "IdentityFile " + privateKey1.getAbsolutePath());
  172. }
  173. @Test
  174. public void testHugePreamble() throws Exception {
  175. // Test that the connection fails when the preamble is longer than 64k.
  176. StringBuilder b = new StringBuilder();
  177. for (int i = 0; i < 1024; i++) {
  178. b.append('a');
  179. }
  180. String line = b.toString();
  181. String[] lines = new String[70];
  182. for (int i = 0; i < lines.length; i++) {
  183. lines[i] = line;
  184. }
  185. server.setPreamble(lines);
  186. TransportException e = assertThrows(TransportException.class,
  187. () -> cloneWith(
  188. "ssh://" + TEST_USER + "@localhost:" + testPort
  189. + "/doesntmatter",
  190. defaultCloneDir, null,
  191. "IdentityFile " + privateKey1.getAbsolutePath()));
  192. // The assertions test that we don't run into bug 565394 / SSHD-1050
  193. assertFalse(e.getMessage().contains("timeout"));
  194. assertTrue(e.getMessage().contains("65536")
  195. || e.getMessage().contains("closed"));
  196. }
  197. /**
  198. * Test for SSHD-1028. If the server doesn't close sessions, the second
  199. * fetch will fail. Occurs on sshd 2.5.[01].
  200. *
  201. * @throws Exception
  202. * on errors
  203. * @see <a href=
  204. * "https://issues.apache.org/jira/projects/SSHD/issues/SSHD-1028">SSHD-1028</a>
  205. */
  206. @Test
  207. public void testCloneAndFetchWithSessionLimit() throws Exception {
  208. MAX_CONCURRENT_SESSIONS
  209. .set(server.getPropertyResolver(), Integer.valueOf(2));
  210. File localClone = cloneWith("ssh://localhost/doesntmatter",
  211. defaultCloneDir, null, //
  212. "Host localhost", //
  213. "HostName localhost", //
  214. "Port " + testPort, //
  215. "User " + TEST_USER, //
  216. "IdentityFile " + privateKey1.getAbsolutePath());
  217. // Fetch a couple of times
  218. try (Git git = Git.open(localClone)) {
  219. git.fetch().call();
  220. git.fetch().call();
  221. }
  222. }
  223. /**
  224. * Creates a simple SSH server without git setup.
  225. *
  226. * @param user
  227. * to accept
  228. * @param userKey
  229. * public key of that user at this server
  230. * @return the {@link SshServer}, not yet started
  231. * @throws Exception
  232. */
  233. private SshServer createServer(String user, File userKey) throws Exception {
  234. SshServer srv = SshServer.setUpDefaultServer();
  235. // Give the server its own host key
  236. KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
  237. generator.initialize(2048);
  238. KeyPair proxyHostKey = generator.generateKeyPair();
  239. srv.setKeyPairProvider(
  240. session -> Collections.singletonList(proxyHostKey));
  241. // Allow (only) publickey authentication
  242. srv.setUserAuthFactories(Collections.singletonList(
  243. ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
  244. // Install the user's public key
  245. PublicKey userProxyKey = AuthorizedKeyEntry
  246. .readAuthorizedKeys(userKey.toPath()).get(0)
  247. .resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
  248. srv.setPublickeyAuthenticator(
  249. (userName, publicKey, session) -> user.equals(userName)
  250. && KeyUtils.compareKeys(userProxyKey, publicKey));
  251. return srv;
  252. }
  253. /**
  254. * Writes the server's host key to our knownhosts file.
  255. *
  256. * @param srv to register
  257. * @throws Exception
  258. */
  259. private void registerServer(SshServer srv) throws Exception {
  260. // Add the proxy's host key to knownhosts
  261. try (BufferedWriter writer = Files.newBufferedWriter(
  262. knownHosts.toPath(), StandardCharsets.US_ASCII,
  263. StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
  264. writer.append('\n');
  265. KnownHostHashValue.appendHostPattern(writer, "localhost",
  266. srv.getPort());
  267. writer.append(',');
  268. KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
  269. srv.getPort());
  270. writer.append(' ');
  271. PublicKeyEntry.appendPublicKeyEntry(writer,
  272. srv.getKeyPairProvider().loadKeys(null).iterator().next().getPublic());
  273. writer.append('\n');
  274. }
  275. }
  276. /**
  277. * Creates a simple proxy server. Accepts only publickey authentication from
  278. * the given user with the given key, allows all forwardings. Adds the
  279. * proxy's host key to {@link #knownHosts}.
  280. *
  281. * @param user
  282. * to accept
  283. * @param userKey
  284. * public key of that user at this server
  285. * @param report
  286. * single-element array to report back the forwarded address.
  287. * @return the started server
  288. * @throws Exception
  289. */
  290. private SshServer createProxy(String user, File userKey,
  291. SshdSocketAddress[] report) throws Exception {
  292. SshServer proxy = createServer(user, userKey);
  293. // Allow forwarding
  294. proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {
  295. @Override
  296. protected boolean checkAcceptance(String request, Session session,
  297. SshdSocketAddress target) {
  298. report[0] = target;
  299. return super.checkAcceptance(request, session, target);
  300. }
  301. });
  302. proxy.start();
  303. registerServer(proxy);
  304. return proxy;
  305. }
  306. @Test
  307. public void testJumpHost() throws Exception {
  308. SshdSocketAddress[] forwarded = { null };
  309. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  310. forwarded)) {
  311. try {
  312. // Now try to clone via the proxy
  313. cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
  314. "Host server", //
  315. "HostName localhost", //
  316. "Port " + testPort, //
  317. "User " + TEST_USER, //
  318. "IdentityFile " + privateKey1.getAbsolutePath(), //
  319. "ProxyJump " + TEST_USER + "X@proxy:" + proxy.getPort(), //
  320. "", //
  321. "Host proxy", //
  322. "Hostname localhost", //
  323. "IdentityFile " + privateKey2.getAbsolutePath());
  324. assertNotNull(forwarded[0]);
  325. assertEquals(testPort, forwarded[0].getPort());
  326. } finally {
  327. proxy.stop();
  328. }
  329. }
  330. }
  331. @Test
  332. public void testJumpHostWrongKeyAtProxy() throws Exception {
  333. // Test that we find the proxy server's URI in the exception message
  334. SshdSocketAddress[] forwarded = { null };
  335. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  336. forwarded)) {
  337. try {
  338. // Now try to clone via the proxy
  339. TransportException e = assertThrows(TransportException.class,
  340. () -> cloneWith("ssh://server/doesntmatter",
  341. defaultCloneDir, null, //
  342. "Host server", //
  343. "HostName localhost", //
  344. "Port " + testPort, //
  345. "User " + TEST_USER, //
  346. "IdentityFile " + privateKey1.getAbsolutePath(),
  347. "ProxyJump " + TEST_USER + "X@proxy:"
  348. + proxy.getPort(), //
  349. "", //
  350. "Host proxy", //
  351. "Hostname localhost", //
  352. "IdentityFile "
  353. + privateKey1.getAbsolutePath()));
  354. String message = e.getMessage();
  355. assertTrue(message.contains("localhost:" + proxy.getPort()));
  356. assertTrue(message.contains("proxy:" + proxy.getPort()));
  357. } finally {
  358. proxy.stop();
  359. }
  360. }
  361. }
  362. @Test
  363. public void testJumpHostWrongKeyAtServer() throws Exception {
  364. // Test that we find the target server's URI in the exception message
  365. SshdSocketAddress[] forwarded = { null };
  366. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  367. forwarded)) {
  368. try {
  369. // Now try to clone via the proxy
  370. TransportException e = assertThrows(TransportException.class,
  371. () -> cloneWith("ssh://server/doesntmatter",
  372. defaultCloneDir, null, //
  373. "Host server", //
  374. "HostName localhost", //
  375. "Port " + testPort, //
  376. "User " + TEST_USER, //
  377. "IdentityFile " + privateKey2.getAbsolutePath(),
  378. "ProxyJump " + TEST_USER + "X@proxy:"
  379. + proxy.getPort(), //
  380. "", //
  381. "Host proxy", //
  382. "Hostname localhost", //
  383. "IdentityFile "
  384. + privateKey2.getAbsolutePath()));
  385. String message = e.getMessage();
  386. assertTrue(message.contains("localhost:" + testPort));
  387. assertTrue(message.contains("ssh://server"));
  388. } finally {
  389. proxy.stop();
  390. }
  391. }
  392. }
  393. @Test
  394. public void testJumpHostNonSsh() throws Exception {
  395. SshdSocketAddress[] forwarded = { null };
  396. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  397. forwarded)) {
  398. try {
  399. TransportException e = assertThrows(TransportException.class,
  400. () -> cloneWith("ssh://server/doesntmatter",
  401. defaultCloneDir, null, //
  402. "Host server", //
  403. "HostName localhost", //
  404. "Port " + testPort, //
  405. "User " + TEST_USER, //
  406. "IdentityFile " + privateKey1.getAbsolutePath(), //
  407. "ProxyJump http://" + TEST_USER + "X@proxy:"
  408. + proxy.getPort(), //
  409. "", //
  410. "Host proxy", //
  411. "Hostname localhost", //
  412. "IdentityFile "
  413. + privateKey2.getAbsolutePath()));
  414. // Find the expected message
  415. Throwable t = e;
  416. while (t != null) {
  417. if (t instanceof URISyntaxException) {
  418. break;
  419. }
  420. t = t.getCause();
  421. }
  422. assertNotNull(t);
  423. assertTrue(t.getMessage().contains("Non-ssh"));
  424. } finally {
  425. proxy.stop();
  426. }
  427. }
  428. }
  429. @Test
  430. public void testJumpHostWithPath() throws Exception {
  431. SshdSocketAddress[] forwarded = { null };
  432. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  433. forwarded)) {
  434. try {
  435. TransportException e = assertThrows(TransportException.class,
  436. () -> cloneWith("ssh://server/doesntmatter",
  437. defaultCloneDir, null, //
  438. "Host server", //
  439. "HostName localhost", //
  440. "Port " + testPort, //
  441. "User " + TEST_USER, //
  442. "IdentityFile " + privateKey1.getAbsolutePath(), //
  443. "ProxyJump ssh://" + TEST_USER + "X@proxy:"
  444. + proxy.getPort() + "/wrongPath", //
  445. "", //
  446. "Host proxy", //
  447. "Hostname localhost", //
  448. "IdentityFile "
  449. + privateKey2.getAbsolutePath()));
  450. // Find the expected message
  451. Throwable t = e;
  452. while (t != null) {
  453. if (t instanceof URISyntaxException) {
  454. break;
  455. }
  456. t = t.getCause();
  457. }
  458. assertNotNull(t);
  459. assertTrue(t.getMessage().contains("wrongPath"));
  460. } finally {
  461. proxy.stop();
  462. }
  463. }
  464. }
  465. @Test
  466. public void testJumpHostWithPathShort() throws Exception {
  467. SshdSocketAddress[] forwarded = { null };
  468. try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
  469. forwarded)) {
  470. try {
  471. TransportException e = assertThrows(TransportException.class,
  472. () -> cloneWith("ssh://server/doesntmatter",
  473. defaultCloneDir, null, //
  474. "Host server", //
  475. "HostName localhost", //
  476. "Port " + testPort, //
  477. "User " + TEST_USER, //
  478. "IdentityFile " + privateKey1.getAbsolutePath(), //
  479. "ProxyJump " + TEST_USER + "X@proxy:wrongPath", //
  480. "", //
  481. "Host proxy", //
  482. "Hostname localhost", //
  483. "Port " + proxy.getPort(), //
  484. "IdentityFile "
  485. + privateKey2.getAbsolutePath()));
  486. // Find the expected message
  487. Throwable t = e;
  488. while (t != null) {
  489. if (t instanceof URISyntaxException) {
  490. break;
  491. }
  492. t = t.getCause();
  493. }
  494. assertNotNull(t);
  495. assertTrue(t.getMessage().contains("wrongPath"));
  496. } finally {
  497. proxy.stop();
  498. }
  499. }
  500. }
  501. @Test
  502. public void testJumpHostChain() throws Exception {
  503. SshdSocketAddress[] forwarded1 = { null };
  504. SshdSocketAddress[] forwarded2 = { null };
  505. try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
  506. forwarded1);
  507. SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
  508. try {
  509. // Clone proxy1 -> proxy2 -> server
  510. cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
  511. "Host server", //
  512. "HostName localhost", //
  513. "Port " + testPort, //
  514. "User " + TEST_USER, //
  515. "IdentityFile " + privateKey1.getAbsolutePath(), //
  516. "ProxyJump proxy2," + TEST_USER + "X@proxy:"
  517. + proxy1.getPort(), //
  518. "", //
  519. "Host proxy", //
  520. "Hostname localhost", //
  521. "IdentityFile " + privateKey2.getAbsolutePath(), //
  522. "", //
  523. "Host proxy2", //
  524. "Hostname localhost", //
  525. "User foo", //
  526. "Port " + proxy2.getPort(), //
  527. "IdentityFile " + privateKey1.getAbsolutePath());
  528. assertNotNull(forwarded1[0]);
  529. assertEquals(proxy2.getPort(), forwarded1[0].getPort());
  530. assertNotNull(forwarded2[0]);
  531. assertEquals(testPort, forwarded2[0].getPort());
  532. } finally {
  533. proxy1.stop();
  534. proxy2.stop();
  535. }
  536. }
  537. }
  538. @Test
  539. public void testJumpHostCascade() throws Exception {
  540. SshdSocketAddress[] forwarded1 = { null };
  541. SshdSocketAddress[] forwarded2 = { null };
  542. try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
  543. forwarded1);
  544. SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
  545. try {
  546. // Clone proxy2 -> proxy1 -> server
  547. cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
  548. "Host server", //
  549. "HostName localhost", //
  550. "Port " + testPort, //
  551. "User " + TEST_USER, //
  552. "IdentityFile " + privateKey1.getAbsolutePath(), //
  553. "ProxyJump " + TEST_USER + "X@proxy", //
  554. "", //
  555. "Host proxy", //
  556. "Hostname localhost", //
  557. "Port " + proxy1.getPort(), //
  558. "ProxyJump ssh://proxy2:" + proxy2.getPort(), //
  559. "IdentityFile " + privateKey2.getAbsolutePath(), //
  560. "", //
  561. "Host proxy2", //
  562. "Hostname localhost", //
  563. "User foo", //
  564. "IdentityFile " + privateKey1.getAbsolutePath());
  565. assertNotNull(forwarded1[0]);
  566. assertEquals(testPort, forwarded1[0].getPort());
  567. assertNotNull(forwarded2[0]);
  568. assertEquals(proxy1.getPort(), forwarded2[0].getPort());
  569. } finally {
  570. proxy1.stop();
  571. proxy2.stop();
  572. }
  573. }
  574. }
  575. @Test
  576. public void testJumpHostRecursion() throws Exception {
  577. SshdSocketAddress[] forwarded1 = { null };
  578. SshdSocketAddress[] forwarded2 = { null };
  579. try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
  580. forwarded1);
  581. SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
  582. try {
  583. TransportException e = assertThrows(TransportException.class,
  584. () -> cloneWith(
  585. "ssh://server/doesntmatter", defaultCloneDir, null, //
  586. "Host server", //
  587. "HostName localhost", //
  588. "Port " + testPort, //
  589. "User " + TEST_USER, //
  590. "IdentityFile " + privateKey1.getAbsolutePath(), //
  591. "ProxyJump " + TEST_USER + "X@proxy", //
  592. "", //
  593. "Host proxy", //
  594. "Hostname localhost", //
  595. "Port " + proxy1.getPort(), //
  596. "ProxyJump ssh://proxy2:" + proxy2.getPort(), //
  597. "IdentityFile " + privateKey2.getAbsolutePath(), //
  598. "", //
  599. "Host proxy2", //
  600. "Hostname localhost", //
  601. "User foo", //
  602. "ProxyJump " + TEST_USER + "X@proxy", //
  603. "IdentityFile " + privateKey1.getAbsolutePath()));
  604. assertTrue(e.getMessage().contains("proxy"));
  605. } finally {
  606. proxy1.stop();
  607. proxy2.stop();
  608. }
  609. }
  610. }
  611. /**
  612. * Tests that one can log in to an old server that doesn't handle
  613. * rsa-sha2-512 if one puts ssh-rsa first in the client's list of public key
  614. * signature algorithms.
  615. *
  616. * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
  617. * 572056</a>
  618. * @throws Exception
  619. * on failure
  620. */
  621. @Test
  622. public void testConnectAuthSshRsaPubkeyAcceptedAlgorithms()
  623. throws Exception {
  624. try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
  625. oldServer.setSignatureFactoriesNames("ssh-rsa");
  626. oldServer.start();
  627. registerServer(oldServer);
  628. installConfig("Host server", //
  629. "HostName localhost", //
  630. "Port " + oldServer.getPort(), //
  631. "User " + TEST_USER, //
  632. "IdentityFile " + privateKey1.getAbsolutePath(), //
  633. "PubkeyAcceptedAlgorithms ^ssh-rsa");
  634. RemoteSession session = getSessionFactory().getSession(
  635. new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
  636. 10000);
  637. assertNotNull(session);
  638. session.disconnect();
  639. }
  640. }
  641. /**
  642. * Tests that one can log in to an old server that knows only the ssh-rsa
  643. * signature algorithm. The client has by default the list of signature
  644. * algorithms for RSA as "rsa-sha2-512,rsa-sha2-256,ssh-rsa". It should try
  645. * all three with the single key configured, and finally succeed.
  646. * <p>
  647. * The re-ordering mechanism (see
  648. * {@link #testConnectAuthSshRsaPubkeyAcceptedAlgorithms()}) is still
  649. * important; servers may impose a penalty (back-off delay) for subsequent
  650. * attempts with signature algorithms unknown to the server. So a user
  651. * connecting to such a server and noticing delays may still want to put
  652. * ssh-rsa first in the list for that host.
  653. * </p>
  654. *
  655. * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=572056">bug
  656. * 572056</a>
  657. * @throws Exception
  658. * on failure
  659. */
  660. @Test
  661. public void testConnectAuthSshRsa() throws Exception {
  662. try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
  663. oldServer.setSignatureFactoriesNames("ssh-rsa");
  664. oldServer.start();
  665. registerServer(oldServer);
  666. installConfig("Host server", //
  667. "HostName localhost", //
  668. "Port " + oldServer.getPort(), //
  669. "User " + TEST_USER, //
  670. "IdentityFile " + privateKey1.getAbsolutePath());
  671. RemoteSession session = getSessionFactory().getSession(
  672. new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
  673. 10000);
  674. assertNotNull(session);
  675. session.disconnect();
  676. }
  677. }
  678. }