| /* | |
| * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others | |
| * | |
| * This program and the accompanying materials are made available under the | |
| * terms of the Eclipse Distribution License v. 1.0 which is available at | |
| * https://www.eclipse.org/org/documents/edl-v10.php. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| package org.eclipse.jgit.junit.ssh; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.junit.Assert.assertArrayEquals; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertFalse; | |
| import static org.junit.Assert.assertNotNull; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.assertTrue; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.concurrent.TimeUnit; | |
| import org.eclipse.jgit.api.errors.TransportException; | |
| import org.eclipse.jgit.errors.CommandFailedException; | |
| import org.eclipse.jgit.transport.CredentialItem; | |
| import org.eclipse.jgit.transport.URIish; | |
| import org.eclipse.jgit.util.FS; | |
| import org.eclipse.jgit.util.SshSupport; | |
| import org.junit.Test; | |
| import org.junit.experimental.theories.DataPoints; | |
| import org.junit.experimental.theories.Theory; | |
| /** | |
| * The ssh tests. Concrete subclasses can re-use these tests by implementing the | |
| * abstract operations from {@link SshTestHarness}. This gives a way to test | |
| * different ssh clients against a unified test suite. | |
| */ | |
| public abstract class SshTestBase extends SshBasicTestBase { | |
| @DataPoints | |
| public static String[] KEY_RESOURCES = { // | |
| "id_dsa", // | |
| "id_rsa_1024", // | |
| "id_rsa_2048", // | |
| "id_rsa_3072", // | |
| "id_rsa_4096", // | |
| "id_ecdsa_256", // | |
| "id_ecdsa_384", // | |
| "id_ecdsa_521", // | |
| "id_ed25519", // | |
| // And now encrypted. Passphrase is "testpass". | |
| "id_dsa_testpass", // | |
| "id_rsa_1024_testpass", // | |
| "id_rsa_2048_testpass", // | |
| "id_rsa_3072_testpass", // | |
| "id_rsa_4096_testpass", // | |
| "id_ecdsa_256_testpass", // | |
| "id_ecdsa_384_testpass", // | |
| "id_ecdsa_521_testpass", // | |
| "id_ed25519_testpass", // | |
| "id_ed25519_expensive_testpass" }; | |
| @Test | |
| public void testSshWithoutConfig() throws Exception { | |
| assertThrows(TransportException.class, | |
| () -> cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort | |
| + "/doesntmatter", defaultCloneDir, null)); | |
| } | |
| @Test | |
| public void testSingleCommand() throws Exception { | |
| installConfig("IdentityFile " + privateKey1.getAbsolutePath()); | |
| String command = SshTestGitServer.ECHO_COMMAND + " 1 without timeout"; | |
| long start = System.nanoTime(); | |
| String reply = SshSupport.runSshCommand( | |
| new URIish("ssh://" + TEST_USER + "@localhost:" + testPort), | |
| null, FS.DETECTED, command, 0); // 0 == no timeout | |
| long elapsed = System.nanoTime() - start; | |
| assertEquals(command, reply); | |
| // Now that we have an idea how long this takes on the test | |
| // infrastructure, try again with a timeout. | |
| command = SshTestGitServer.ECHO_COMMAND + " 1 expecting no timeout"; | |
| // Still use a generous timeout. | |
| int timeout = 10 * ((int) TimeUnit.NANOSECONDS.toSeconds(elapsed) + 1); | |
| reply = SshSupport.runSshCommand( | |
| new URIish("ssh://" + TEST_USER + "@localhost:" + testPort), | |
| null, FS.DETECTED, command, timeout); | |
| assertEquals(command, reply); | |
| } | |
| @Test | |
| public void testSingleCommandWithTimeoutExpired() throws Exception { | |
| installConfig("IdentityFile " + privateKey1.getAbsolutePath()); | |
| String command = SshTestGitServer.ECHO_COMMAND + " 2 EXPECTING TIMEOUT"; | |
| CommandFailedException e = assertThrows(CommandFailedException.class, | |
| () -> SshSupport.runSshCommand(new URIish( | |
| "ssh://" + TEST_USER + "@localhost:" + testPort), null, | |
| FS.DETECTED, command, 1)); | |
| assertTrue(e.getMessage().contains(command)); | |
| assertTrue(e.getMessage().contains("time")); | |
| } | |
| @Test | |
| public void testSshWithGlobalIdentity() throws Exception { | |
| cloneWith( | |
| "ssh://" + TEST_USER + "@localhost:" + testPort | |
| + "/doesntmatter", | |
| defaultCloneDir, null, | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithDefaultIdentity() throws Exception { | |
| File idRsa = new File(privateKey1.getParentFile(), "id_rsa"); | |
| Files.copy(privateKey1.toPath(), idRsa.toPath()); | |
| // We expect the session factory to pick up these keys... | |
| cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort | |
| + "/doesntmatter", defaultCloneDir, null); | |
| } | |
| @Test | |
| public void testSshWithConfigEncryptedUnusedKey() throws Exception { | |
| // Copy the encrypted test key from the bundle. | |
| File encryptedKey = new File(sshDir, "id_dsa"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "testpass"); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| assertEquals("CredentialsProvider should not have been called", 0, | |
| provider.getLog().size()); | |
| } | |
| @Test | |
| public void testSshWithConfigEncryptedUnusedKeyInConfigLast() | |
| throws Exception { | |
| // Copy the encrypted test key from the bundle. | |
| File encryptedKey = new File(sshDir, "id_dsa_test_key"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "testpass"); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), | |
| "IdentityFile " + encryptedKey.getAbsolutePath()); | |
| assertEquals("CredentialsProvider should not have been called", 0, | |
| provider.getLog().size()); | |
| } | |
| @Test | |
| public void testSshWithConfigEncryptedUnusedKeyInConfigFirst() | |
| throws Exception { | |
| // Copy the encrypted test key from the bundle. | |
| File encryptedKey = new File(sshDir, "id_dsa_test_key"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "testpass"); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + encryptedKey.getAbsolutePath(), | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| assertEquals("CredentialsProvider should have been called once", 1, | |
| provider.getLog().size()); | |
| } | |
| @Test | |
| public void testSshEncryptedUsedKeyCached() throws Exception { | |
| // Make sure we are asked for the password only once if we do several | |
| // operations with an encrypted key. | |
| File encryptedKey = new File(sshDir, "id_dsa_test_key"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); | |
| copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); | |
| server.setTestUserPublicKey(encryptedPublicKey.toPath()); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "testpass"); | |
| pushTo(provider, | |
| cloneWith("ssh://localhost/doesntmatter", // | |
| defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + encryptedKey.getAbsolutePath())); | |
| assertEquals("CredentialsProvider should have been called once", 1, | |
| provider.getLog().size()); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshEncryptedUsedKeyWrongPassword() throws Exception { | |
| File encryptedKey = new File(sshDir, "id_dsa_test_key"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); | |
| copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); | |
| server.setTestUserPublicKey(encryptedPublicKey.toPath()); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass"); | |
| cloneWith("ssh://localhost/doesntmatter", // | |
| defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "NumberOfPasswordPrompts 1", // | |
| "IdentityFile " + encryptedKey.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshEncryptedUsedKeySeveralPassword() throws Exception { | |
| File encryptedKey = new File(sshDir, "id_dsa_test_key"); | |
| copyTestResource("id_dsa_testpass", encryptedKey); | |
| File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); | |
| copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); | |
| server.setTestUserPublicKey(encryptedPublicKey.toPath()); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass", "wrongpass2", "testpass"); | |
| cloneWith("ssh://localhost/doesntmatter", // | |
| defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + encryptedKey.getAbsolutePath()); | |
| assertEquals("CredentialsProvider should have been called 3 times", 3, | |
| provider.getLog().size()); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshWithoutKnownHosts() throws Exception { | |
| assertTrue("Could not delete known_hosts", knownHosts.delete()); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithoutKnownHostsWithProviderAsk() | |
| throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| // The provider will answer "yes" to all questions, so we should be able | |
| // to connect and end up with a new known_hosts file with the host key. | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| List<LogEntry> messages = provider.getLog(); | |
| assertFalse("Expected user interaction", messages.isEmpty()); | |
| assertEquals( | |
| "Expected to be asked about the key, and the file creation", | |
| 2, messages.size()); | |
| assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists()); | |
| // Instead of checking the file contents, let's just clone again | |
| // without provider. If it works, the server host key was written | |
| // correctly. | |
| File clonedAgain = new File(getTemporaryDirectory(), "cloned2"); | |
| cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithoutKnownHostsWithProviderAcceptNew() | |
| throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "StrictHostKeyChecking accept-new", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| assertEquals("Expected to be asked about the file creation", 1, | |
| provider.getLog().size()); | |
| assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists()); | |
| // Instead of checking the file contents, let's just clone again | |
| // without provider. If it works, the server host key was written | |
| // correctly. | |
| File clonedAgain = new File(getTemporaryDirectory(), "cloned2"); | |
| cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshWithoutKnownHostsDeny() throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "StrictHostKeyChecking yes", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshModifiedHostKeyDeny() | |
| throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| // Now produce a new known_hosts file containing some other key. | |
| createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "StrictHostKeyChecking yes", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshModifiedHostKeyWithProviderDeny() throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| // Now produce a new known_hosts file containing some other key. | |
| createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| try { | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "StrictHostKeyChecking yes", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } catch (Exception e) { | |
| assertEquals("Expected to be told about the modified key", 1, | |
| provider.getLog().size()); | |
| assertTrue("Only messages expected", provider.getLog().stream() | |
| .flatMap(l -> l.getItems().stream()).allMatch( | |
| c -> c instanceof CredentialItem.InformationalMessage)); | |
| throw e; | |
| } | |
| } | |
| private void checkKnownHostsModifiedHostKey(File backup, File newFile, | |
| String wrongKey) throws IOException { | |
| List<String> oldLines = Files.readAllLines(backup.toPath(), UTF_8); | |
| // Find the original entry. We should have that again in known_hosts. | |
| String oldKeyPart = null; | |
| for (String oldLine : oldLines) { | |
| if (oldLine.contains("[localhost]:")) { | |
| String[] parts = oldLine.split("\\s+"); | |
| if (parts.length > 2) { | |
| oldKeyPart = parts[parts.length - 2] + ' ' | |
| + parts[parts.length - 1]; | |
| break; | |
| } | |
| } | |
| } | |
| assertNotNull("Old key not found", oldKeyPart); | |
| List<String> newLines = Files.readAllLines(newFile.toPath(), UTF_8); | |
| assertFalse("Old host key still found in known_hosts file" + newFile, | |
| hasHostKey("localhost", testPort, wrongKey, newLines)); | |
| assertTrue("New host key not found in known_hosts file" + newFile, | |
| hasHostKey("localhost", testPort, oldKeyPart, newLines)); | |
| } | |
| @Test | |
| public void testSshModifiedHostKeyAllow() throws Exception { | |
| assertTrue("Failed to delete known_hosts", knownHosts.delete()); | |
| createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); | |
| File backup = new File(getTemporaryDirectory(), "backupKnownHosts"); | |
| Files.copy(knownHosts.toPath(), backup.toPath()); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "StrictHostKeyChecking no", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| // File should not have been updated! | |
| String[] oldLines = Files | |
| .readAllLines(backup.toPath(), UTF_8) | |
| .toArray(new String[0]); | |
| String[] newLines = Files | |
| .readAllLines(knownHosts.toPath(), UTF_8) | |
| .toArray(new String[0]); | |
| assertArrayEquals("Known hosts file should not be modified", oldLines, | |
| newLines); | |
| } | |
| @Test | |
| public void testSshModifiedHostKeyAsk() throws Exception { | |
| File copiedHosts = new File(knownHosts.getParentFile(), | |
| "copiedKnownHosts"); | |
| assertTrue("Failed to rename known_hosts", | |
| knownHosts.renameTo(copiedHosts)); | |
| String wrongKeyPart = createKnownHostsFile(knownHosts, "localhost", | |
| testPort, publicKey1); | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| checkKnownHostsModifiedHostKey(copiedHosts, knownHosts, wrongKeyPart); | |
| assertEquals("Expected to be asked about the modified key", 1, | |
| provider.getLog().size()); | |
| } | |
| @Test | |
| public void testSshCloneWithConfigAndPush() throws Exception { | |
| pushTo(cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath())); | |
| } | |
| @Test | |
| public void testSftpWithConfig() throws Exception { | |
| cloneWith("sftp://localhost/.git", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSftpCloneWithConfigAndPush() throws Exception { | |
| pushTo(cloneWith("sftp://localhost/.git", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath())); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshWithConfigWrongKey() throws Exception { | |
| cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey2.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithWrongUserNameInConfig() throws Exception { | |
| // Bug 526778 | |
| cloneWith( | |
| "ssh://" + TEST_USER + "@localhost:" + testPort | |
| + "/doesntmatter", | |
| defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "User sombody_else", // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithWrongPortInConfig() throws Exception { | |
| // Bug 526778 | |
| cloneWith( | |
| "ssh://" + TEST_USER + "@localhost:" + testPort | |
| + "/doesntmatter", | |
| defaultCloneDir, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port 22", // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithAliasInConfig() throws Exception { | |
| // Bug 531118 | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), "", // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port 22", // | |
| "User someone_else", // | |
| "IdentityFile " + privateKey2.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testSshWithUnknownCiphersInConfig() throws Exception { | |
| // Bug 535672 | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr"); | |
| } | |
| @Test | |
| public void testSshWithUnknownHostKeyAlgorithmsInConfig() | |
| throws Exception { | |
| // Bug 535672 | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "HostKeyAlgorithms foobar,ssh-rsa,ssh-dss"); | |
| } | |
| @Test | |
| public void testSshWithUnknownKexAlgorithmsInConfig() | |
| throws Exception { | |
| // Bug 535672 | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"); | |
| } | |
| @Test | |
| public void testSshWithMinimalHostKeyAlgorithmsInConfig() | |
| throws Exception { | |
| // Bug 537790 | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "HostKeyAlgorithms ssh-rsa,ssh-dss"); | |
| } | |
| @Test | |
| public void testSshWithUnknownAuthInConfig() throws Exception { | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testSshWithNoMatchingAuthInConfig() throws Exception { | |
| // Server doesn't do password, and anyway we set no password. | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath(), // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test | |
| public void testRsaHostKeySecond() throws Exception { | |
| // See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA | |
| // (preferred), RSA, we have RSA in known_hosts: client and server | |
| // should agree on RSA. | |
| File newHostKey = new File(getTemporaryDirectory(), "newhostkey"); | |
| copyTestResource("id_ecdsa_256", newHostKey); | |
| server.addHostKey(newHostKey.toPath(), true); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testEcDsaHostKey() throws Exception { | |
| // See https://git.eclipse.org/r/#/c/130402/ : server has RSA | |
| // (preferred), EcDSA, we have EcDSA in known_hosts: client and server | |
| // should agree on EcDSA. | |
| File newHostKey = new File(getTemporaryDirectory(), "newhostkey"); | |
| copyTestResource("id_ecdsa_256", newHostKey); | |
| server.addHostKey(newHostKey.toPath(), false); | |
| File newHostKeyPub = new File(getTemporaryDirectory(), | |
| "newhostkey.pub"); | |
| copyTestResource("id_ecdsa_256.pub", newHostKeyPub); | |
| createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey1.getAbsolutePath()); | |
| } | |
| @Test | |
| public void testPasswordAuth() throws Exception { | |
| server.enablePasswordAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test | |
| public void testPasswordAuthSeveralTimes() throws Exception { | |
| server.enablePasswordAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testPasswordAuthWrongPassword() throws Exception { | |
| server.enablePasswordAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass"); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testPasswordAuthNoPassword() throws Exception { | |
| server.enablePasswordAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testPasswordAuthCorrectPasswordTooLate() throws Exception { | |
| server.enablePasswordAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass", "wrongpass", "wrongpass", | |
| TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications password"); | |
| } | |
| @Test | |
| public void testKeyboardInteractiveAuth() throws Exception { | |
| server.enableKeyboardInteractiveAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications keyboard-interactive"); | |
| } | |
| @Test | |
| public void testKeyboardInteractiveAuthSeveralTimes() throws Exception { | |
| server.enableKeyboardInteractiveAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications keyboard-interactive"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testKeyboardInteractiveAuthWrongPassword() throws Exception { | |
| server.enableKeyboardInteractiveAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass"); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications keyboard-interactive"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testKeyboardInteractiveAuthNoPassword() throws Exception { | |
| server.enableKeyboardInteractiveAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider(); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications keyboard-interactive"); | |
| } | |
| @Test(expected = TransportException.class) | |
| public void testKeyboardInteractiveAuthCorrectPasswordTooLate() | |
| throws Exception { | |
| server.enableKeyboardInteractiveAuthentication(); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "wrongpass", "wrongpass", "wrongpass", | |
| TEST_USER.toUpperCase(Locale.ROOT)); | |
| cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // | |
| "Host git", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "PreferredAuthentications keyboard-interactive"); | |
| } | |
| @Theory | |
| public void testSshKeys(String keyName) throws Exception { | |
| File cloned = new File(getTemporaryDirectory(), "cloned"); | |
| String keyFileName = keyName + "_key"; | |
| File privateKey = new File(sshDir, keyFileName); | |
| copyTestResource(keyName, privateKey); | |
| File publicKey = new File(sshDir, keyFileName + ".pub"); | |
| copyTestResource(keyName + ".pub", publicKey); | |
| server.setTestUserPublicKey(publicKey.toPath()); | |
| TestCredentialsProvider provider = new TestCredentialsProvider( | |
| "testpass"); | |
| pushTo(provider, | |
| cloneWith("ssh://localhost/doesntmatter", // | |
| cloned, provider, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey.getAbsolutePath())); | |
| int expectedCalls = keyName.endsWith("testpass") ? 1 : 0; | |
| assertEquals("Unexpected calls to CredentialsProvider", expectedCalls, | |
| provider.getLog().size()); | |
| // Should now also work without credentials provider, even if the key | |
| // was encrypted. | |
| cloned = new File(getTemporaryDirectory(), "cloned2"); | |
| pushTo(null, | |
| cloneWith("ssh://localhost/doesntmatter", // | |
| cloned, null, // | |
| "Host localhost", // | |
| "HostName localhost", // | |
| "Port " + testPort, // | |
| "User " + TEST_USER, // | |
| "IdentityFile " + privateKey.getAbsolutePath())); | |
| } | |
| } |