/* * Copyright (C) 2018, 2020 Thomas Wolf 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 static org.junit.Assume.assumeTrue; 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()); // This test passes with JSch per chance because JSch completely ignores // the second IdentityFile assertEquals("CredentialsProvider should not have been called", 0, provider.getLog().size()); } private boolean isJsch() { return getSessionFactory().getType().equals("jsch"); } @Test public void testSshWithConfigEncryptedUnusedKeyInConfigFirst() throws Exception { // Test cannot pass with JSch; it handles only one IdentityFile. // assumeTrue(!(getSessionFactory() instanceof // JschConfigSessionFactory)); gives in bazel a failure with "Never // found parameters that satisfied method assumptions." // In maven it's fine!? if (isJsch()) { return; } // 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 messages = provider.getLog(); assertFalse("Expected user interaction", messages.isEmpty()); if (isJsch()) { // JSch doesn't create a non-existing file. assertEquals("Expected to be asked about the key", 1, messages.size()); return; } 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()); if (isJsch()) { // JSch doesn't create new files. assertTrue("CredentialsProvider not called", provider.getLog().isEmpty()); return; } 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 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 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 { // JSch fails on ECDSA 384/521 keys. Compare // https://sourceforge.net/p/jsch/patches/10/ assumeTrue(!(isJsch() && (keyName.contains("ed25519") || keyName.startsWith("id_ecdsa_384") || keyName.startsWith("id_ecdsa_521")))); 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())); } }