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