xref: /JGit/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestBase.java (revision 0853a2410f22c8bd97a179dec14e3c083a27abbb)
18d2d6836SMatthias Sohn /*
224fdc1d0SThomas Wolf  * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
38d2d6836SMatthias Sohn  *
48d2d6836SMatthias Sohn  * This program and the accompanying materials are made available under the
58d2d6836SMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
68d2d6836SMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
78d2d6836SMatthias Sohn  *
88d2d6836SMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
98d2d6836SMatthias Sohn  */
108d2d6836SMatthias Sohn package org.eclipse.jgit.junit.ssh;
118d2d6836SMatthias Sohn 
128d2d6836SMatthias Sohn import static java.nio.charset.StandardCharsets.UTF_8;
138d2d6836SMatthias Sohn import static org.junit.Assert.assertArrayEquals;
148d2d6836SMatthias Sohn import static org.junit.Assert.assertEquals;
158d2d6836SMatthias Sohn import static org.junit.Assert.assertFalse;
168d2d6836SMatthias Sohn import static org.junit.Assert.assertNotNull;
1724fdc1d0SThomas Wolf import static org.junit.Assert.assertThrows;
188d2d6836SMatthias Sohn import static org.junit.Assert.assertTrue;
198d2d6836SMatthias Sohn import static org.junit.Assume.assumeTrue;
208d2d6836SMatthias Sohn 
218d2d6836SMatthias Sohn import java.io.File;
228d2d6836SMatthias Sohn import java.io.IOException;
238d2d6836SMatthias Sohn import java.nio.file.Files;
248d2d6836SMatthias Sohn import java.util.List;
258d2d6836SMatthias Sohn import java.util.Locale;
2624fdc1d0SThomas Wolf import java.util.concurrent.TimeUnit;
278d2d6836SMatthias Sohn 
288d2d6836SMatthias Sohn import org.eclipse.jgit.api.errors.TransportException;
2924fdc1d0SThomas Wolf import org.eclipse.jgit.errors.CommandFailedException;
308d2d6836SMatthias Sohn import org.eclipse.jgit.transport.CredentialItem;
3124fdc1d0SThomas Wolf import org.eclipse.jgit.transport.URIish;
3224fdc1d0SThomas Wolf import org.eclipse.jgit.util.FS;
3324fdc1d0SThomas Wolf import org.eclipse.jgit.util.SshSupport;
348d2d6836SMatthias Sohn import org.junit.Test;
358d2d6836SMatthias Sohn import org.junit.experimental.theories.DataPoints;
368d2d6836SMatthias Sohn import org.junit.experimental.theories.Theory;
378d2d6836SMatthias Sohn 
388d2d6836SMatthias Sohn /**
398d2d6836SMatthias Sohn  * The ssh tests. Concrete subclasses can re-use these tests by implementing the
408d2d6836SMatthias Sohn  * abstract operations from {@link SshTestHarness}. This gives a way to test
418d2d6836SMatthias Sohn  * different ssh clients against a unified test suite.
428d2d6836SMatthias Sohn  */
43*0853a241SThomas Wolf public abstract class SshTestBase extends SshBasicTestBase {
448d2d6836SMatthias Sohn 
458d2d6836SMatthias Sohn 	@DataPoints
468d2d6836SMatthias Sohn 	public static String[] KEY_RESOURCES = { //
478d2d6836SMatthias Sohn 			"id_dsa", //
488d2d6836SMatthias Sohn 			"id_rsa_1024", //
498d2d6836SMatthias Sohn 			"id_rsa_2048", //
508d2d6836SMatthias Sohn 			"id_rsa_3072", //
518d2d6836SMatthias Sohn 			"id_rsa_4096", //
528d2d6836SMatthias Sohn 			"id_ecdsa_256", //
538d2d6836SMatthias Sohn 			"id_ecdsa_384", //
548d2d6836SMatthias Sohn 			"id_ecdsa_521", //
558d2d6836SMatthias Sohn 			"id_ed25519", //
568d2d6836SMatthias Sohn 			// And now encrypted. Passphrase is "testpass".
578d2d6836SMatthias Sohn 			"id_dsa_testpass", //
588d2d6836SMatthias Sohn 			"id_rsa_1024_testpass", //
598d2d6836SMatthias Sohn 			"id_rsa_2048_testpass", //
608d2d6836SMatthias Sohn 			"id_rsa_3072_testpass", //
618d2d6836SMatthias Sohn 			"id_rsa_4096_testpass", //
628d2d6836SMatthias Sohn 			"id_ecdsa_256_testpass", //
638d2d6836SMatthias Sohn 			"id_ecdsa_384_testpass", //
648d2d6836SMatthias Sohn 			"id_ecdsa_521_testpass", //
658d2d6836SMatthias Sohn 			"id_ed25519_testpass", //
668d2d6836SMatthias Sohn 			"id_ed25519_expensive_testpass" };
678d2d6836SMatthias Sohn 
6824fdc1d0SThomas Wolf 	@Test
testSshWithoutConfig()698d2d6836SMatthias Sohn 	public void testSshWithoutConfig() throws Exception {
7024fdc1d0SThomas Wolf 		assertThrows(TransportException.class,
7124fdc1d0SThomas Wolf 				() -> cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
7224fdc1d0SThomas Wolf 						+ "/doesntmatter", defaultCloneDir, null));
7324fdc1d0SThomas Wolf 	}
7424fdc1d0SThomas Wolf 
7524fdc1d0SThomas Wolf 	@Test
testSingleCommand()7624fdc1d0SThomas Wolf 	public void testSingleCommand() throws Exception {
7724fdc1d0SThomas Wolf 		installConfig("IdentityFile " + privateKey1.getAbsolutePath());
7824fdc1d0SThomas Wolf 		String command = SshTestGitServer.ECHO_COMMAND + " 1 without timeout";
7924fdc1d0SThomas Wolf 		long start = System.nanoTime();
8024fdc1d0SThomas Wolf 		String reply = SshSupport.runSshCommand(
8124fdc1d0SThomas Wolf 				new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
8224fdc1d0SThomas Wolf 				null, FS.DETECTED, command, 0); // 0 == no timeout
8324fdc1d0SThomas Wolf 		long elapsed = System.nanoTime() - start;
8424fdc1d0SThomas Wolf 		assertEquals(command, reply);
8524fdc1d0SThomas Wolf 		// Now that we have an idea how long this takes on the test
8624fdc1d0SThomas Wolf 		// infrastructure, try again with a timeout.
8724fdc1d0SThomas Wolf 		command = SshTestGitServer.ECHO_COMMAND + " 1 expecting no timeout";
8824fdc1d0SThomas Wolf 		// Still use a generous timeout.
8924fdc1d0SThomas Wolf 		int timeout = 10 * ((int) TimeUnit.NANOSECONDS.toSeconds(elapsed) + 1);
9024fdc1d0SThomas Wolf 		reply = SshSupport.runSshCommand(
9124fdc1d0SThomas Wolf 				new URIish("ssh://" + TEST_USER + "@localhost:" + testPort),
9224fdc1d0SThomas Wolf 				null, FS.DETECTED, command, timeout);
9324fdc1d0SThomas Wolf 		assertEquals(command, reply);
9424fdc1d0SThomas Wolf 	}
9524fdc1d0SThomas Wolf 
9624fdc1d0SThomas Wolf 	@Test
testSingleCommandWithTimeoutExpired()9724fdc1d0SThomas Wolf 	public void testSingleCommandWithTimeoutExpired() throws Exception {
9824fdc1d0SThomas Wolf 		installConfig("IdentityFile " + privateKey1.getAbsolutePath());
9924fdc1d0SThomas Wolf 		String command = SshTestGitServer.ECHO_COMMAND + " 2 EXPECTING TIMEOUT";
10024fdc1d0SThomas Wolf 
10124fdc1d0SThomas Wolf 		CommandFailedException e = assertThrows(CommandFailedException.class,
10224fdc1d0SThomas Wolf 				() -> SshSupport.runSshCommand(new URIish(
10324fdc1d0SThomas Wolf 						"ssh://" + TEST_USER + "@localhost:" + testPort), null,
10424fdc1d0SThomas Wolf 						FS.DETECTED, command, 1));
10524fdc1d0SThomas Wolf 		assertTrue(e.getMessage().contains(command));
10624fdc1d0SThomas Wolf 		assertTrue(e.getMessage().contains("time"));
1078d2d6836SMatthias Sohn 	}
1088d2d6836SMatthias Sohn 
1098d2d6836SMatthias Sohn 	@Test
testSshWithGlobalIdentity()1108d2d6836SMatthias Sohn 	public void testSshWithGlobalIdentity() throws Exception {
1118d2d6836SMatthias Sohn 		cloneWith(
1128d2d6836SMatthias Sohn 				"ssh://" + TEST_USER + "@localhost:" + testPort
1138d2d6836SMatthias Sohn 						+ "/doesntmatter",
1148d2d6836SMatthias Sohn 				defaultCloneDir, null,
1158d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
1168d2d6836SMatthias Sohn 	}
1178d2d6836SMatthias Sohn 
1188d2d6836SMatthias Sohn 	@Test
testSshWithDefaultIdentity()1198d2d6836SMatthias Sohn 	public void testSshWithDefaultIdentity() throws Exception {
1208d2d6836SMatthias Sohn 		File idRsa = new File(privateKey1.getParentFile(), "id_rsa");
1218d2d6836SMatthias Sohn 		Files.copy(privateKey1.toPath(), idRsa.toPath());
1228d2d6836SMatthias Sohn 		// We expect the session factory to pick up these keys...
1238d2d6836SMatthias Sohn 		cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort
1248d2d6836SMatthias Sohn 				+ "/doesntmatter", defaultCloneDir, null);
1258d2d6836SMatthias Sohn 	}
1268d2d6836SMatthias Sohn 
1278d2d6836SMatthias Sohn 	@Test
testSshWithConfigEncryptedUnusedKey()1288d2d6836SMatthias Sohn 	public void testSshWithConfigEncryptedUnusedKey() throws Exception {
1298d2d6836SMatthias Sohn 		// Copy the encrypted test key from the bundle.
1308d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa");
1318d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
1328d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
1338d2d6836SMatthias Sohn 				"testpass");
1348d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
1358d2d6836SMatthias Sohn 				"Host localhost", //
1368d2d6836SMatthias Sohn 				"HostName localhost", //
1378d2d6836SMatthias Sohn 				"Port " + testPort, //
1388d2d6836SMatthias Sohn 				"User " + TEST_USER, //
1398d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
1408d2d6836SMatthias Sohn 		assertEquals("CredentialsProvider should not have been called", 0,
1418d2d6836SMatthias Sohn 				provider.getLog().size());
1428d2d6836SMatthias Sohn 	}
1438d2d6836SMatthias Sohn 
1448d2d6836SMatthias Sohn 	@Test
testSshWithConfigEncryptedUnusedKeyInConfigLast()1458d2d6836SMatthias Sohn 	public void testSshWithConfigEncryptedUnusedKeyInConfigLast()
1468d2d6836SMatthias Sohn 			throws Exception {
1478d2d6836SMatthias Sohn 		// Copy the encrypted test key from the bundle.
1488d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa_test_key");
1498d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
1508d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
1518d2d6836SMatthias Sohn 				"testpass");
1528d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
1538d2d6836SMatthias Sohn 				"Host localhost", //
1548d2d6836SMatthias Sohn 				"HostName localhost", //
1558d2d6836SMatthias Sohn 				"Port " + testPort, //
1568d2d6836SMatthias Sohn 				"User " + TEST_USER, //
1578d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(),
1588d2d6836SMatthias Sohn 				"IdentityFile " + encryptedKey.getAbsolutePath());
1598d2d6836SMatthias Sohn 		// This test passes with JSch per chance because JSch completely ignores
1608d2d6836SMatthias Sohn 		// the second IdentityFile
1618d2d6836SMatthias Sohn 		assertEquals("CredentialsProvider should not have been called", 0,
1628d2d6836SMatthias Sohn 				provider.getLog().size());
1638d2d6836SMatthias Sohn 	}
1648d2d6836SMatthias Sohn 
isJsch()1658d2d6836SMatthias Sohn 	private boolean isJsch() {
1668d2d6836SMatthias Sohn 		return getSessionFactory().getType().equals("jsch");
1678d2d6836SMatthias Sohn 	}
1688d2d6836SMatthias Sohn 
1698d2d6836SMatthias Sohn 	@Test
testSshWithConfigEncryptedUnusedKeyInConfigFirst()1708d2d6836SMatthias Sohn 	public void testSshWithConfigEncryptedUnusedKeyInConfigFirst()
1718d2d6836SMatthias Sohn 			throws Exception {
1728d2d6836SMatthias Sohn 		// Test cannot pass with JSch; it handles only one IdentityFile.
1738d2d6836SMatthias Sohn 		// assumeTrue(!(getSessionFactory() instanceof
1748d2d6836SMatthias Sohn 		// JschConfigSessionFactory)); gives in bazel a failure with "Never
1758d2d6836SMatthias Sohn 		// found parameters that satisfied method assumptions."
1768d2d6836SMatthias Sohn 		// In maven it's fine!?
1778d2d6836SMatthias Sohn 		if (isJsch()) {
1788d2d6836SMatthias Sohn 			return;
1798d2d6836SMatthias Sohn 		}
1808d2d6836SMatthias Sohn 		// Copy the encrypted test key from the bundle.
1818d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa_test_key");
1828d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
1838d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
1848d2d6836SMatthias Sohn 				"testpass");
1858d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
1868d2d6836SMatthias Sohn 				"Host localhost", //
1878d2d6836SMatthias Sohn 				"HostName localhost", //
1888d2d6836SMatthias Sohn 				"Port " + testPort, //
1898d2d6836SMatthias Sohn 				"User " + TEST_USER, //
1908d2d6836SMatthias Sohn 				"IdentityFile " + encryptedKey.getAbsolutePath(),
1918d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
1928d2d6836SMatthias Sohn 		assertEquals("CredentialsProvider should have been called once", 1,
1938d2d6836SMatthias Sohn 				provider.getLog().size());
1948d2d6836SMatthias Sohn 	}
1958d2d6836SMatthias Sohn 
1968d2d6836SMatthias Sohn 	@Test
testSshEncryptedUsedKeyCached()1978d2d6836SMatthias Sohn 	public void testSshEncryptedUsedKeyCached() throws Exception {
1988d2d6836SMatthias Sohn 		// Make sure we are asked for the password only once if we do several
1998d2d6836SMatthias Sohn 		// operations with an encrypted key.
2008d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa_test_key");
2018d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
2028d2d6836SMatthias Sohn 		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
2038d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
2048d2d6836SMatthias Sohn 		server.setTestUserPublicKey(encryptedPublicKey.toPath());
2058d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
2068d2d6836SMatthias Sohn 				"testpass");
2078d2d6836SMatthias Sohn 		pushTo(provider,
2088d2d6836SMatthias Sohn 				cloneWith("ssh://localhost/doesntmatter", //
2098d2d6836SMatthias Sohn 						defaultCloneDir, provider, //
2108d2d6836SMatthias Sohn 						"Host localhost", //
2118d2d6836SMatthias Sohn 						"HostName localhost", //
2128d2d6836SMatthias Sohn 						"Port " + testPort, //
2138d2d6836SMatthias Sohn 						"User " + TEST_USER, //
2148d2d6836SMatthias Sohn 						"IdentityFile " + encryptedKey.getAbsolutePath()));
2158d2d6836SMatthias Sohn 		assertEquals("CredentialsProvider should have been called once", 1,
2168d2d6836SMatthias Sohn 				provider.getLog().size());
2178d2d6836SMatthias Sohn 	}
2188d2d6836SMatthias Sohn 
2198d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshEncryptedUsedKeyWrongPassword()2208d2d6836SMatthias Sohn 	public void testSshEncryptedUsedKeyWrongPassword() throws Exception {
2218d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa_test_key");
2228d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
2238d2d6836SMatthias Sohn 		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
2248d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
2258d2d6836SMatthias Sohn 		server.setTestUserPublicKey(encryptedPublicKey.toPath());
2268d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
2278d2d6836SMatthias Sohn 				"wrongpass");
2288d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", //
2298d2d6836SMatthias Sohn 				defaultCloneDir, provider, //
2308d2d6836SMatthias Sohn 				"Host localhost", //
2318d2d6836SMatthias Sohn 				"HostName localhost", //
2328d2d6836SMatthias Sohn 				"Port " + testPort, //
2338d2d6836SMatthias Sohn 				"User " + TEST_USER, //
2348d2d6836SMatthias Sohn 				"NumberOfPasswordPrompts 1", //
2358d2d6836SMatthias Sohn 				"IdentityFile " + encryptedKey.getAbsolutePath());
2368d2d6836SMatthias Sohn 	}
2378d2d6836SMatthias Sohn 
2388d2d6836SMatthias Sohn 	@Test
testSshEncryptedUsedKeySeveralPassword()2398d2d6836SMatthias Sohn 	public void testSshEncryptedUsedKeySeveralPassword() throws Exception {
2408d2d6836SMatthias Sohn 		File encryptedKey = new File(sshDir, "id_dsa_test_key");
2418d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass", encryptedKey);
2428d2d6836SMatthias Sohn 		File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub");
2438d2d6836SMatthias Sohn 		copyTestResource("id_dsa_testpass.pub", encryptedPublicKey);
2448d2d6836SMatthias Sohn 		server.setTestUserPublicKey(encryptedPublicKey.toPath());
2458d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
2468d2d6836SMatthias Sohn 				"wrongpass", "wrongpass2", "testpass");
2478d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", //
2488d2d6836SMatthias Sohn 				defaultCloneDir, provider, //
2498d2d6836SMatthias Sohn 				"Host localhost", //
2508d2d6836SMatthias Sohn 				"HostName localhost", //
2518d2d6836SMatthias Sohn 				"Port " + testPort, //
2528d2d6836SMatthias Sohn 				"User " + TEST_USER, //
2538d2d6836SMatthias Sohn 				"IdentityFile " + encryptedKey.getAbsolutePath());
2548d2d6836SMatthias Sohn 		assertEquals("CredentialsProvider should have been called 3 times", 3,
2558d2d6836SMatthias Sohn 				provider.getLog().size());
2568d2d6836SMatthias Sohn 	}
2578d2d6836SMatthias Sohn 
2588d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshWithoutKnownHosts()2598d2d6836SMatthias Sohn 	public void testSshWithoutKnownHosts() throws Exception {
2608d2d6836SMatthias Sohn 		assertTrue("Could not delete known_hosts", knownHosts.delete());
2618d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
2628d2d6836SMatthias Sohn 				"Host localhost", //
2638d2d6836SMatthias Sohn 				"HostName localhost", //
2648d2d6836SMatthias Sohn 				"Port " + testPort, //
2658d2d6836SMatthias Sohn 				"User " + TEST_USER, //
2668d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
2678d2d6836SMatthias Sohn 	}
2688d2d6836SMatthias Sohn 
2698d2d6836SMatthias Sohn 	@Test
testSshWithoutKnownHostsWithProviderAsk()2708d2d6836SMatthias Sohn 	public void testSshWithoutKnownHostsWithProviderAsk()
2718d2d6836SMatthias Sohn 			throws Exception {
2728d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
2738d2d6836SMatthias Sohn 				"copiedKnownHosts");
2748d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
2758d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
2768d2d6836SMatthias Sohn 		// The provider will answer "yes" to all questions, so we should be able
2778d2d6836SMatthias Sohn 		// to connect and end up with a new known_hosts file with the host key.
2788d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
2798d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
2808d2d6836SMatthias Sohn 				"Host localhost", //
2818d2d6836SMatthias Sohn 				"HostName localhost", //
2828d2d6836SMatthias Sohn 				"Port " + testPort, //
2838d2d6836SMatthias Sohn 				"User " + TEST_USER, //
2848d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
2858d2d6836SMatthias Sohn 		List<LogEntry> messages = provider.getLog();
2868d2d6836SMatthias Sohn 		assertFalse("Expected user interaction", messages.isEmpty());
2878d2d6836SMatthias Sohn 		if (isJsch()) {
2888d2d6836SMatthias Sohn 			// JSch doesn't create a non-existing file.
2898d2d6836SMatthias Sohn 			assertEquals("Expected to be asked about the key", 1,
2908d2d6836SMatthias Sohn 					messages.size());
2918d2d6836SMatthias Sohn 			return;
2928d2d6836SMatthias Sohn 		}
2938d2d6836SMatthias Sohn 		assertEquals(
2948d2d6836SMatthias Sohn 				"Expected to be asked about the key, and the file creation",
2958d2d6836SMatthias Sohn 				2, messages.size());
2968d2d6836SMatthias Sohn 		assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
2978d2d6836SMatthias Sohn 		// Instead of checking the file contents, let's just clone again
2988d2d6836SMatthias Sohn 		// without provider. If it works, the server host key was written
2998d2d6836SMatthias Sohn 		// correctly.
3008d2d6836SMatthias Sohn 		File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
3018d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
3028d2d6836SMatthias Sohn 				"Host localhost", //
3038d2d6836SMatthias Sohn 				"HostName localhost", //
3048d2d6836SMatthias Sohn 				"Port " + testPort, //
3058d2d6836SMatthias Sohn 				"User " + TEST_USER, //
3068d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
3078d2d6836SMatthias Sohn 	}
3088d2d6836SMatthias Sohn 
3098d2d6836SMatthias Sohn 	@Test
testSshWithoutKnownHostsWithProviderAcceptNew()3108d2d6836SMatthias Sohn 	public void testSshWithoutKnownHostsWithProviderAcceptNew()
3118d2d6836SMatthias Sohn 			throws Exception {
3128d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
3138d2d6836SMatthias Sohn 				"copiedKnownHosts");
3148d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
3158d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
3168d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
3178d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
3188d2d6836SMatthias Sohn 				"Host localhost", //
3198d2d6836SMatthias Sohn 				"HostName localhost", //
3208d2d6836SMatthias Sohn 				"Port " + testPort, //
3218d2d6836SMatthias Sohn 				"User " + TEST_USER, //
3228d2d6836SMatthias Sohn 				"StrictHostKeyChecking accept-new", //
3238d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
3248d2d6836SMatthias Sohn 		if (isJsch()) {
3258d2d6836SMatthias Sohn 			// JSch doesn't create new files.
3268d2d6836SMatthias Sohn 			assertTrue("CredentialsProvider not called",
3278d2d6836SMatthias Sohn 					provider.getLog().isEmpty());
3288d2d6836SMatthias Sohn 			return;
3298d2d6836SMatthias Sohn 		}
3308d2d6836SMatthias Sohn 		assertEquals("Expected to be asked about the file creation", 1,
3318d2d6836SMatthias Sohn 				provider.getLog().size());
3328d2d6836SMatthias Sohn 		assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
3338d2d6836SMatthias Sohn 		// Instead of checking the file contents, let's just clone again
3348d2d6836SMatthias Sohn 		// without provider. If it works, the server host key was written
3358d2d6836SMatthias Sohn 		// correctly.
3368d2d6836SMatthias Sohn 		File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
3378d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
3388d2d6836SMatthias Sohn 				"Host localhost", //
3398d2d6836SMatthias Sohn 				"HostName localhost", //
3408d2d6836SMatthias Sohn 				"Port " + testPort, //
3418d2d6836SMatthias Sohn 				"User " + TEST_USER, //
3428d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
3438d2d6836SMatthias Sohn 	}
3448d2d6836SMatthias Sohn 
3458d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshWithoutKnownHostsDeny()3468d2d6836SMatthias Sohn 	public void testSshWithoutKnownHostsDeny() throws Exception {
3478d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
3488d2d6836SMatthias Sohn 				"copiedKnownHosts");
3498d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
3508d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
3518d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
3528d2d6836SMatthias Sohn 				"Host localhost", //
3538d2d6836SMatthias Sohn 				"HostName localhost", //
3548d2d6836SMatthias Sohn 				"Port " + testPort, //
3558d2d6836SMatthias Sohn 				"User " + TEST_USER, //
3568d2d6836SMatthias Sohn 				"StrictHostKeyChecking yes", //
3578d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
3588d2d6836SMatthias Sohn 	}
3598d2d6836SMatthias Sohn 
3608d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshModifiedHostKeyDeny()3618d2d6836SMatthias Sohn 	public void testSshModifiedHostKeyDeny()
3628d2d6836SMatthias Sohn 			throws Exception {
3638d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
3648d2d6836SMatthias Sohn 				"copiedKnownHosts");
3658d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
3668d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
3678d2d6836SMatthias Sohn 		// Now produce a new known_hosts file containing some other key.
3688d2d6836SMatthias Sohn 		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
3698d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
3708d2d6836SMatthias Sohn 				"Host localhost", //
3718d2d6836SMatthias Sohn 				"HostName localhost", //
3728d2d6836SMatthias Sohn 				"Port " + testPort, //
3738d2d6836SMatthias Sohn 				"User " + TEST_USER, //
3748d2d6836SMatthias Sohn 				"StrictHostKeyChecking yes", //
3758d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
3768d2d6836SMatthias Sohn 	}
3778d2d6836SMatthias Sohn 
3788d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshModifiedHostKeyWithProviderDeny()3798d2d6836SMatthias Sohn 	public void testSshModifiedHostKeyWithProviderDeny() throws Exception {
3808d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
3818d2d6836SMatthias Sohn 				"copiedKnownHosts");
3828d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
3838d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
3848d2d6836SMatthias Sohn 		// Now produce a new known_hosts file containing some other key.
3858d2d6836SMatthias Sohn 		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
3868d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
3878d2d6836SMatthias Sohn 		try {
3888d2d6836SMatthias Sohn 			cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
3898d2d6836SMatthias Sohn 					"Host localhost", //
3908d2d6836SMatthias Sohn 					"HostName localhost", //
3918d2d6836SMatthias Sohn 					"Port " + testPort, //
3928d2d6836SMatthias Sohn 					"User " + TEST_USER, //
3938d2d6836SMatthias Sohn 					"StrictHostKeyChecking yes", //
3948d2d6836SMatthias Sohn 					"IdentityFile " + privateKey1.getAbsolutePath());
3958d2d6836SMatthias Sohn 		} catch (Exception e) {
3968d2d6836SMatthias Sohn 			assertEquals("Expected to be told about the modified key", 1,
3978d2d6836SMatthias Sohn 					provider.getLog().size());
3988d2d6836SMatthias Sohn 			assertTrue("Only messages expected", provider.getLog().stream()
3998d2d6836SMatthias Sohn 					.flatMap(l -> l.getItems().stream()).allMatch(
4008d2d6836SMatthias Sohn 							c -> c instanceof CredentialItem.InformationalMessage));
4018d2d6836SMatthias Sohn 			throw e;
4028d2d6836SMatthias Sohn 		}
4038d2d6836SMatthias Sohn 	}
4048d2d6836SMatthias Sohn 
checkKnownHostsModifiedHostKey(File backup, File newFile, String wrongKey)4058d2d6836SMatthias Sohn 	private void checkKnownHostsModifiedHostKey(File backup, File newFile,
4068d2d6836SMatthias Sohn 			String wrongKey) throws IOException {
4078d2d6836SMatthias Sohn 		List<String> oldLines = Files.readAllLines(backup.toPath(), UTF_8);
4088d2d6836SMatthias Sohn 		// Find the original entry. We should have that again in known_hosts.
4098d2d6836SMatthias Sohn 		String oldKeyPart = null;
4108d2d6836SMatthias Sohn 		for (String oldLine : oldLines) {
4118d2d6836SMatthias Sohn 			if (oldLine.contains("[localhost]:")) {
4128d2d6836SMatthias Sohn 				String[] parts = oldLine.split("\\s+");
4138d2d6836SMatthias Sohn 				if (parts.length > 2) {
4148d2d6836SMatthias Sohn 					oldKeyPart = parts[parts.length - 2] + ' '
4158d2d6836SMatthias Sohn 							+ parts[parts.length - 1];
4168d2d6836SMatthias Sohn 					break;
4178d2d6836SMatthias Sohn 				}
4188d2d6836SMatthias Sohn 			}
4198d2d6836SMatthias Sohn 		}
4208d2d6836SMatthias Sohn 		assertNotNull("Old key not found", oldKeyPart);
4218d2d6836SMatthias Sohn 		List<String> newLines = Files.readAllLines(newFile.toPath(), UTF_8);
4228d2d6836SMatthias Sohn 		assertFalse("Old host key still found in known_hosts file" + newFile,
4238d2d6836SMatthias Sohn 				hasHostKey("localhost", testPort, wrongKey, newLines));
4248d2d6836SMatthias Sohn 		assertTrue("New host key not found in known_hosts file" + newFile,
4258d2d6836SMatthias Sohn 				hasHostKey("localhost", testPort, oldKeyPart, newLines));
4268d2d6836SMatthias Sohn 
4278d2d6836SMatthias Sohn 	}
4288d2d6836SMatthias Sohn 
4298d2d6836SMatthias Sohn 	@Test
testSshModifiedHostKeyAllow()4308d2d6836SMatthias Sohn 	public void testSshModifiedHostKeyAllow() throws Exception {
4318d2d6836SMatthias Sohn 		assertTrue("Failed to delete known_hosts", knownHosts.delete());
4328d2d6836SMatthias Sohn 		createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1);
4338d2d6836SMatthias Sohn 		File backup = new File(getTemporaryDirectory(), "backupKnownHosts");
4348d2d6836SMatthias Sohn 		Files.copy(knownHosts.toPath(), backup.toPath());
4358d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
4368d2d6836SMatthias Sohn 				"Host localhost", //
4378d2d6836SMatthias Sohn 				"HostName localhost", //
4388d2d6836SMatthias Sohn 				"Port " + testPort, //
4398d2d6836SMatthias Sohn 				"User " + TEST_USER, //
4408d2d6836SMatthias Sohn 				"StrictHostKeyChecking no", //
4418d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
4428d2d6836SMatthias Sohn 		// File should not have been updated!
4438d2d6836SMatthias Sohn 		String[] oldLines = Files
4448d2d6836SMatthias Sohn 				.readAllLines(backup.toPath(), UTF_8)
4458d2d6836SMatthias Sohn 				.toArray(new String[0]);
4468d2d6836SMatthias Sohn 		String[] newLines = Files
4478d2d6836SMatthias Sohn 				.readAllLines(knownHosts.toPath(), UTF_8)
4488d2d6836SMatthias Sohn 				.toArray(new String[0]);
4498d2d6836SMatthias Sohn 		assertArrayEquals("Known hosts file should not be modified", oldLines,
4508d2d6836SMatthias Sohn 				newLines);
4518d2d6836SMatthias Sohn 	}
4528d2d6836SMatthias Sohn 
4538d2d6836SMatthias Sohn 	@Test
testSshModifiedHostKeyAsk()4548d2d6836SMatthias Sohn 	public void testSshModifiedHostKeyAsk() throws Exception {
4558d2d6836SMatthias Sohn 		File copiedHosts = new File(knownHosts.getParentFile(),
4568d2d6836SMatthias Sohn 				"copiedKnownHosts");
4578d2d6836SMatthias Sohn 		assertTrue("Failed to rename known_hosts",
4588d2d6836SMatthias Sohn 				knownHosts.renameTo(copiedHosts));
4598d2d6836SMatthias Sohn 		String wrongKeyPart = createKnownHostsFile(knownHosts, "localhost",
4608d2d6836SMatthias Sohn 				testPort, publicKey1);
4618d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
4628d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
4638d2d6836SMatthias Sohn 				"Host localhost", //
4648d2d6836SMatthias Sohn 				"HostName localhost", //
4658d2d6836SMatthias Sohn 				"Port " + testPort, //
4668d2d6836SMatthias Sohn 				"User " + TEST_USER, //
4678d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
4688d2d6836SMatthias Sohn 		checkKnownHostsModifiedHostKey(copiedHosts, knownHosts, wrongKeyPart);
4698d2d6836SMatthias Sohn 		assertEquals("Expected to be asked about the modified key", 1,
4708d2d6836SMatthias Sohn 				provider.getLog().size());
4718d2d6836SMatthias Sohn 	}
4728d2d6836SMatthias Sohn 
4738d2d6836SMatthias Sohn 	@Test
testSshCloneWithConfigAndPush()4748d2d6836SMatthias Sohn 	public void testSshCloneWithConfigAndPush() throws Exception {
4758d2d6836SMatthias Sohn 		pushTo(cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
4768d2d6836SMatthias Sohn 				"Host localhost", //
4778d2d6836SMatthias Sohn 				"HostName localhost", //
4788d2d6836SMatthias Sohn 				"Port " + testPort, //
4798d2d6836SMatthias Sohn 				"User " + TEST_USER, //
4808d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath()));
4818d2d6836SMatthias Sohn 	}
4828d2d6836SMatthias Sohn 
4838d2d6836SMatthias Sohn 	@Test
testSftpWithConfig()4848d2d6836SMatthias Sohn 	public void testSftpWithConfig() throws Exception {
4858d2d6836SMatthias Sohn 		cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
4868d2d6836SMatthias Sohn 				"Host localhost", //
4878d2d6836SMatthias Sohn 				"HostName localhost", //
4888d2d6836SMatthias Sohn 				"Port " + testPort, //
4898d2d6836SMatthias Sohn 				"User " + TEST_USER, //
4908d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
4918d2d6836SMatthias Sohn 	}
4928d2d6836SMatthias Sohn 
4938d2d6836SMatthias Sohn 	@Test
testSftpCloneWithConfigAndPush()4948d2d6836SMatthias Sohn 	public void testSftpCloneWithConfigAndPush() throws Exception {
4958d2d6836SMatthias Sohn 		pushTo(cloneWith("sftp://localhost/.git", defaultCloneDir, null, //
4968d2d6836SMatthias Sohn 				"Host localhost", //
4978d2d6836SMatthias Sohn 				"HostName localhost", //
4988d2d6836SMatthias Sohn 				"Port " + testPort, //
4998d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5008d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath()));
5018d2d6836SMatthias Sohn 	}
5028d2d6836SMatthias Sohn 
5038d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshWithConfigWrongKey()5048d2d6836SMatthias Sohn 	public void testSshWithConfigWrongKey() throws Exception {
5058d2d6836SMatthias Sohn 		cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, //
5068d2d6836SMatthias Sohn 				"Host localhost", //
5078d2d6836SMatthias Sohn 				"HostName localhost", //
5088d2d6836SMatthias Sohn 				"Port " + testPort, //
5098d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5108d2d6836SMatthias Sohn 				"IdentityFile " + privateKey2.getAbsolutePath());
5118d2d6836SMatthias Sohn 	}
5128d2d6836SMatthias Sohn 
5138d2d6836SMatthias Sohn 	@Test
testSshWithWrongUserNameInConfig()5148d2d6836SMatthias Sohn 	public void testSshWithWrongUserNameInConfig() throws Exception {
5158d2d6836SMatthias Sohn 		// Bug 526778
5168d2d6836SMatthias Sohn 		cloneWith(
5178d2d6836SMatthias Sohn 				"ssh://" + TEST_USER + "@localhost:" + testPort
5188d2d6836SMatthias Sohn 						+ "/doesntmatter",
5198d2d6836SMatthias Sohn 				defaultCloneDir, null, //
5208d2d6836SMatthias Sohn 				"Host localhost", //
5218d2d6836SMatthias Sohn 				"HostName localhost", //
5228d2d6836SMatthias Sohn 				"User sombody_else", //
5238d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
5248d2d6836SMatthias Sohn 	}
5258d2d6836SMatthias Sohn 
5268d2d6836SMatthias Sohn 	@Test
testSshWithWrongPortInConfig()5278d2d6836SMatthias Sohn 	public void testSshWithWrongPortInConfig() throws Exception {
5288d2d6836SMatthias Sohn 		// Bug 526778
5298d2d6836SMatthias Sohn 		cloneWith(
5308d2d6836SMatthias Sohn 				"ssh://" + TEST_USER + "@localhost:" + testPort
5318d2d6836SMatthias Sohn 						+ "/doesntmatter",
5328d2d6836SMatthias Sohn 				defaultCloneDir, null, //
5338d2d6836SMatthias Sohn 				"Host localhost", //
5348d2d6836SMatthias Sohn 				"HostName localhost", //
5358d2d6836SMatthias Sohn 				"Port 22", //
5368d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5378d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
5388d2d6836SMatthias Sohn 	}
5398d2d6836SMatthias Sohn 
5408d2d6836SMatthias Sohn 	@Test
testSshWithAliasInConfig()5418d2d6836SMatthias Sohn 	public void testSshWithAliasInConfig() throws Exception {
5428d2d6836SMatthias Sohn 		// Bug 531118
5438d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
5448d2d6836SMatthias Sohn 				"Host git", //
5458d2d6836SMatthias Sohn 				"HostName localhost", //
5468d2d6836SMatthias Sohn 				"Port " + testPort, //
5478d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5488d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), "", //
5498d2d6836SMatthias Sohn 				"Host localhost", //
5508d2d6836SMatthias Sohn 				"HostName localhost", //
5518d2d6836SMatthias Sohn 				"Port 22", //
5528d2d6836SMatthias Sohn 				"User someone_else", //
5538d2d6836SMatthias Sohn 				"IdentityFile " + privateKey2.getAbsolutePath());
5548d2d6836SMatthias Sohn 	}
5558d2d6836SMatthias Sohn 
5568d2d6836SMatthias Sohn 	@Test
testSshWithUnknownCiphersInConfig()5578d2d6836SMatthias Sohn 	public void testSshWithUnknownCiphersInConfig() throws Exception {
5588d2d6836SMatthias Sohn 		// Bug 535672
5598d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
5608d2d6836SMatthias Sohn 				"Host git", //
5618d2d6836SMatthias Sohn 				"HostName localhost", //
5628d2d6836SMatthias Sohn 				"Port " + testPort, //
5638d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5648d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
5658d2d6836SMatthias Sohn 				"Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
5668d2d6836SMatthias Sohn 	}
5678d2d6836SMatthias Sohn 
5688d2d6836SMatthias Sohn 	@Test
testSshWithUnknownHostKeyAlgorithmsInConfig()5698d2d6836SMatthias Sohn 	public void testSshWithUnknownHostKeyAlgorithmsInConfig()
5708d2d6836SMatthias Sohn 			throws Exception {
5718d2d6836SMatthias Sohn 		// Bug 535672
5728d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
5738d2d6836SMatthias Sohn 				"Host git", //
5748d2d6836SMatthias Sohn 				"HostName localhost", //
5758d2d6836SMatthias Sohn 				"Port " + testPort, //
5768d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5778d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
5788d2d6836SMatthias Sohn 				"HostKeyAlgorithms foobar,ssh-rsa,ssh-dss");
5798d2d6836SMatthias Sohn 	}
5808d2d6836SMatthias Sohn 
5818d2d6836SMatthias Sohn 	@Test
testSshWithUnknownKexAlgorithmsInConfig()5828d2d6836SMatthias Sohn 	public void testSshWithUnknownKexAlgorithmsInConfig()
5838d2d6836SMatthias Sohn 			throws Exception {
5848d2d6836SMatthias Sohn 		// Bug 535672
5858d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
5868d2d6836SMatthias Sohn 				"Host git", //
5878d2d6836SMatthias Sohn 				"HostName localhost", //
5888d2d6836SMatthias Sohn 				"Port " + testPort, //
5898d2d6836SMatthias Sohn 				"User " + TEST_USER, //
5908d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
5918d2d6836SMatthias Sohn 				"KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521");
5928d2d6836SMatthias Sohn 	}
5938d2d6836SMatthias Sohn 
5948d2d6836SMatthias Sohn 	@Test
testSshWithMinimalHostKeyAlgorithmsInConfig()5958d2d6836SMatthias Sohn 	public void testSshWithMinimalHostKeyAlgorithmsInConfig()
5968d2d6836SMatthias Sohn 			throws Exception {
5978d2d6836SMatthias Sohn 		// Bug 537790
5988d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
5998d2d6836SMatthias Sohn 				"Host git", //
6008d2d6836SMatthias Sohn 				"HostName localhost", //
6018d2d6836SMatthias Sohn 				"Port " + testPort, //
6028d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6038d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
6048d2d6836SMatthias Sohn 				"HostKeyAlgorithms ssh-rsa,ssh-dss");
6058d2d6836SMatthias Sohn 	}
6068d2d6836SMatthias Sohn 
6078d2d6836SMatthias Sohn 	@Test
testSshWithUnknownAuthInConfig()6088d2d6836SMatthias Sohn 	public void testSshWithUnknownAuthInConfig() throws Exception {
6098d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
6108d2d6836SMatthias Sohn 				"Host git", //
6118d2d6836SMatthias Sohn 				"HostName localhost", //
6128d2d6836SMatthias Sohn 				"Port " + testPort, //
6138d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6148d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
6158d2d6836SMatthias Sohn 				"PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password");
6168d2d6836SMatthias Sohn 	}
6178d2d6836SMatthias Sohn 
6188d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testSshWithNoMatchingAuthInConfig()6198d2d6836SMatthias Sohn 	public void testSshWithNoMatchingAuthInConfig() throws Exception {
6208d2d6836SMatthias Sohn 		// Server doesn't do password, and anyway we set no password.
6218d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
6228d2d6836SMatthias Sohn 				"Host git", //
6238d2d6836SMatthias Sohn 				"HostName localhost", //
6248d2d6836SMatthias Sohn 				"Port " + testPort, //
6258d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6268d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath(), //
6278d2d6836SMatthias Sohn 				"PreferredAuthentications password");
6288d2d6836SMatthias Sohn 	}
6298d2d6836SMatthias Sohn 
6308d2d6836SMatthias Sohn 	@Test
testRsaHostKeySecond()6318d2d6836SMatthias Sohn 	public void testRsaHostKeySecond() throws Exception {
6328d2d6836SMatthias Sohn 		// See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA
6338d2d6836SMatthias Sohn 		// (preferred), RSA, we have RSA in known_hosts: client and server
6348d2d6836SMatthias Sohn 		// should agree on RSA.
6358d2d6836SMatthias Sohn 		File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
6368d2d6836SMatthias Sohn 		copyTestResource("id_ecdsa_256", newHostKey);
6378d2d6836SMatthias Sohn 		server.addHostKey(newHostKey.toPath(), true);
6388d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
6398d2d6836SMatthias Sohn 				"Host git", //
6408d2d6836SMatthias Sohn 				"HostName localhost", //
6418d2d6836SMatthias Sohn 				"Port " + testPort, //
6428d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6438d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
6448d2d6836SMatthias Sohn 	}
6458d2d6836SMatthias Sohn 
6468d2d6836SMatthias Sohn 	@Test
testEcDsaHostKey()6478d2d6836SMatthias Sohn 	public void testEcDsaHostKey() throws Exception {
6488d2d6836SMatthias Sohn 		// See https://git.eclipse.org/r/#/c/130402/ : server has RSA
6498d2d6836SMatthias Sohn 		// (preferred), EcDSA, we have EcDSA in known_hosts: client and server
6508d2d6836SMatthias Sohn 		// should agree on EcDSA.
6518d2d6836SMatthias Sohn 		File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
6528d2d6836SMatthias Sohn 		copyTestResource("id_ecdsa_256", newHostKey);
6538d2d6836SMatthias Sohn 		server.addHostKey(newHostKey.toPath(), false);
6548d2d6836SMatthias Sohn 		File newHostKeyPub = new File(getTemporaryDirectory(),
6558d2d6836SMatthias Sohn 				"newhostkey.pub");
6568d2d6836SMatthias Sohn 		copyTestResource("id_ecdsa_256.pub", newHostKeyPub);
6578d2d6836SMatthias Sohn 		createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
6588d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
6598d2d6836SMatthias Sohn 				"Host git", //
6608d2d6836SMatthias Sohn 				"HostName localhost", //
6618d2d6836SMatthias Sohn 				"Port " + testPort, //
6628d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6638d2d6836SMatthias Sohn 				"IdentityFile " + privateKey1.getAbsolutePath());
6648d2d6836SMatthias Sohn 	}
6658d2d6836SMatthias Sohn 
6668d2d6836SMatthias Sohn 	@Test
testPasswordAuth()6678d2d6836SMatthias Sohn 	public void testPasswordAuth() throws Exception {
6688d2d6836SMatthias Sohn 		server.enablePasswordAuthentication();
6698d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
6708d2d6836SMatthias Sohn 				TEST_USER.toUpperCase(Locale.ROOT));
6718d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
6728d2d6836SMatthias Sohn 				"Host git", //
6738d2d6836SMatthias Sohn 				"HostName localhost", //
6748d2d6836SMatthias Sohn 				"Port " + testPort, //
6758d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6768d2d6836SMatthias Sohn 				"PreferredAuthentications password");
6778d2d6836SMatthias Sohn 	}
6788d2d6836SMatthias Sohn 
6798d2d6836SMatthias Sohn 	@Test
testPasswordAuthSeveralTimes()6808d2d6836SMatthias Sohn 	public void testPasswordAuthSeveralTimes() throws Exception {
6818d2d6836SMatthias Sohn 		server.enablePasswordAuthentication();
6828d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
6838d2d6836SMatthias Sohn 				"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
6848d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
6858d2d6836SMatthias Sohn 				"Host git", //
6868d2d6836SMatthias Sohn 				"HostName localhost", //
6878d2d6836SMatthias Sohn 				"Port " + testPort, //
6888d2d6836SMatthias Sohn 				"User " + TEST_USER, //
6898d2d6836SMatthias Sohn 				"PreferredAuthentications password");
6908d2d6836SMatthias Sohn 	}
6918d2d6836SMatthias Sohn 
6928d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testPasswordAuthWrongPassword()6938d2d6836SMatthias Sohn 	public void testPasswordAuthWrongPassword() throws Exception {
6948d2d6836SMatthias Sohn 		server.enablePasswordAuthentication();
6958d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
6968d2d6836SMatthias Sohn 				"wrongpass");
6978d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
6988d2d6836SMatthias Sohn 				"Host git", //
6998d2d6836SMatthias Sohn 				"HostName localhost", //
7008d2d6836SMatthias Sohn 				"Port " + testPort, //
7018d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7028d2d6836SMatthias Sohn 				"PreferredAuthentications password");
7038d2d6836SMatthias Sohn 	}
7048d2d6836SMatthias Sohn 
7058d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testPasswordAuthNoPassword()7068d2d6836SMatthias Sohn 	public void testPasswordAuthNoPassword() throws Exception {
7078d2d6836SMatthias Sohn 		server.enablePasswordAuthentication();
7088d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
7098d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7108d2d6836SMatthias Sohn 				"Host git", //
7118d2d6836SMatthias Sohn 				"HostName localhost", //
7128d2d6836SMatthias Sohn 				"Port " + testPort, //
7138d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7148d2d6836SMatthias Sohn 				"PreferredAuthentications password");
7158d2d6836SMatthias Sohn 	}
7168d2d6836SMatthias Sohn 
7178d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testPasswordAuthCorrectPasswordTooLate()7188d2d6836SMatthias Sohn 	public void testPasswordAuthCorrectPasswordTooLate() throws Exception {
7198d2d6836SMatthias Sohn 		server.enablePasswordAuthentication();
7208d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
7218d2d6836SMatthias Sohn 				"wrongpass", "wrongpass", "wrongpass",
7228d2d6836SMatthias Sohn 				TEST_USER.toUpperCase(Locale.ROOT));
7238d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7248d2d6836SMatthias Sohn 				"Host git", //
7258d2d6836SMatthias Sohn 				"HostName localhost", //
7268d2d6836SMatthias Sohn 				"Port " + testPort, //
7278d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7288d2d6836SMatthias Sohn 				"PreferredAuthentications password");
7298d2d6836SMatthias Sohn 	}
7308d2d6836SMatthias Sohn 
7318d2d6836SMatthias Sohn 	@Test
testKeyboardInteractiveAuth()7328d2d6836SMatthias Sohn 	public void testKeyboardInteractiveAuth() throws Exception {
7338d2d6836SMatthias Sohn 		server.enableKeyboardInteractiveAuthentication();
7348d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
7358d2d6836SMatthias Sohn 				TEST_USER.toUpperCase(Locale.ROOT));
7368d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7378d2d6836SMatthias Sohn 				"Host git", //
7388d2d6836SMatthias Sohn 				"HostName localhost", //
7398d2d6836SMatthias Sohn 				"Port " + testPort, //
7408d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7418d2d6836SMatthias Sohn 				"PreferredAuthentications keyboard-interactive");
7428d2d6836SMatthias Sohn 	}
7438d2d6836SMatthias Sohn 
7448d2d6836SMatthias Sohn 	@Test
testKeyboardInteractiveAuthSeveralTimes()7458d2d6836SMatthias Sohn 	public void testKeyboardInteractiveAuthSeveralTimes() throws Exception {
7468d2d6836SMatthias Sohn 		server.enableKeyboardInteractiveAuthentication();
7478d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
7488d2d6836SMatthias Sohn 				"wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT));
7498d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7508d2d6836SMatthias Sohn 				"Host git", //
7518d2d6836SMatthias Sohn 				"HostName localhost", //
7528d2d6836SMatthias Sohn 				"Port " + testPort, //
7538d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7548d2d6836SMatthias Sohn 				"PreferredAuthentications keyboard-interactive");
7558d2d6836SMatthias Sohn 	}
7568d2d6836SMatthias Sohn 
7578d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testKeyboardInteractiveAuthWrongPassword()7588d2d6836SMatthias Sohn 	public void testKeyboardInteractiveAuthWrongPassword() throws Exception {
7598d2d6836SMatthias Sohn 		server.enableKeyboardInteractiveAuthentication();
7608d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
7618d2d6836SMatthias Sohn 				"wrongpass");
7628d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7638d2d6836SMatthias Sohn 				"Host git", //
7648d2d6836SMatthias Sohn 				"HostName localhost", //
7658d2d6836SMatthias Sohn 				"Port " + testPort, //
7668d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7678d2d6836SMatthias Sohn 				"PreferredAuthentications keyboard-interactive");
7688d2d6836SMatthias Sohn 	}
7698d2d6836SMatthias Sohn 
7708d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testKeyboardInteractiveAuthNoPassword()7718d2d6836SMatthias Sohn 	public void testKeyboardInteractiveAuthNoPassword() throws Exception {
7728d2d6836SMatthias Sohn 		server.enableKeyboardInteractiveAuthentication();
7738d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider();
7748d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7758d2d6836SMatthias Sohn 				"Host git", //
7768d2d6836SMatthias Sohn 				"HostName localhost", //
7778d2d6836SMatthias Sohn 				"Port " + testPort, //
7788d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7798d2d6836SMatthias Sohn 				"PreferredAuthentications keyboard-interactive");
7808d2d6836SMatthias Sohn 	}
7818d2d6836SMatthias Sohn 
7828d2d6836SMatthias Sohn 	@Test(expected = TransportException.class)
testKeyboardInteractiveAuthCorrectPasswordTooLate()7838d2d6836SMatthias Sohn 	public void testKeyboardInteractiveAuthCorrectPasswordTooLate()
7848d2d6836SMatthias Sohn 			throws Exception {
7858d2d6836SMatthias Sohn 		server.enableKeyboardInteractiveAuthentication();
7868d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
7878d2d6836SMatthias Sohn 				"wrongpass", "wrongpass", "wrongpass",
7888d2d6836SMatthias Sohn 				TEST_USER.toUpperCase(Locale.ROOT));
7898d2d6836SMatthias Sohn 		cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, //
7908d2d6836SMatthias Sohn 				"Host git", //
7918d2d6836SMatthias Sohn 				"HostName localhost", //
7928d2d6836SMatthias Sohn 				"Port " + testPort, //
7938d2d6836SMatthias Sohn 				"User " + TEST_USER, //
7948d2d6836SMatthias Sohn 				"PreferredAuthentications keyboard-interactive");
7958d2d6836SMatthias Sohn 	}
7968d2d6836SMatthias Sohn 
7978d2d6836SMatthias Sohn 	@Theory
testSshKeys(String keyName)7988d2d6836SMatthias Sohn 	public void testSshKeys(String keyName) throws Exception {
7998d2d6836SMatthias Sohn 		// JSch fails on ECDSA 384/521 keys. Compare
8008d2d6836SMatthias Sohn 		// https://sourceforge.net/p/jsch/patches/10/
8018d2d6836SMatthias Sohn 		assumeTrue(!(isJsch() && (keyName.contains("ed25519")
8028d2d6836SMatthias Sohn 				|| keyName.startsWith("id_ecdsa_384")
8038d2d6836SMatthias Sohn 				|| keyName.startsWith("id_ecdsa_521"))));
8048d2d6836SMatthias Sohn 		File cloned = new File(getTemporaryDirectory(), "cloned");
8058d2d6836SMatthias Sohn 		String keyFileName = keyName + "_key";
8068d2d6836SMatthias Sohn 		File privateKey = new File(sshDir, keyFileName);
8078d2d6836SMatthias Sohn 		copyTestResource(keyName, privateKey);
8088d2d6836SMatthias Sohn 		File publicKey = new File(sshDir, keyFileName + ".pub");
8098d2d6836SMatthias Sohn 		copyTestResource(keyName + ".pub", publicKey);
8108d2d6836SMatthias Sohn 		server.setTestUserPublicKey(publicKey.toPath());
8118d2d6836SMatthias Sohn 		TestCredentialsProvider provider = new TestCredentialsProvider(
8128d2d6836SMatthias Sohn 				"testpass");
8138d2d6836SMatthias Sohn 		pushTo(provider,
8148d2d6836SMatthias Sohn 				cloneWith("ssh://localhost/doesntmatter", //
8158d2d6836SMatthias Sohn 						cloned, provider, //
8168d2d6836SMatthias Sohn 						"Host localhost", //
8178d2d6836SMatthias Sohn 						"HostName localhost", //
8188d2d6836SMatthias Sohn 						"Port " + testPort, //
8198d2d6836SMatthias Sohn 						"User " + TEST_USER, //
8208d2d6836SMatthias Sohn 						"IdentityFile " + privateKey.getAbsolutePath()));
8218d2d6836SMatthias Sohn 		int expectedCalls = keyName.endsWith("testpass") ? 1 : 0;
8228d2d6836SMatthias Sohn 		assertEquals("Unexpected calls to CredentialsProvider", expectedCalls,
8238d2d6836SMatthias Sohn 				provider.getLog().size());
8248d2d6836SMatthias Sohn 		// Should now also work without credentials provider, even if the key
8258d2d6836SMatthias Sohn 		// was encrypted.
8268d2d6836SMatthias Sohn 		cloned = new File(getTemporaryDirectory(), "cloned2");
8278d2d6836SMatthias Sohn 		pushTo(null,
8288d2d6836SMatthias Sohn 				cloneWith("ssh://localhost/doesntmatter", //
8298d2d6836SMatthias Sohn 						cloned, null, //
8308d2d6836SMatthias Sohn 						"Host localhost", //
8318d2d6836SMatthias Sohn 						"HostName localhost", //
8328d2d6836SMatthias Sohn 						"Port " + testPort, //
8338d2d6836SMatthias Sohn 						"User " + TEST_USER, //
8348d2d6836SMatthias Sohn 						"IdentityFile " + privateKey.getAbsolutePath()));
8358d2d6836SMatthias Sohn 	}
8368d2d6836SMatthias Sohn }
837