You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
813 lines
29 KiB
813 lines
29 KiB
/* |
|
* Copyright (C) 2018, 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.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 org.eclipse.jgit.api.errors.TransportException; |
|
import org.eclipse.jgit.transport.CredentialItem; |
|
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 SshTestHarness { |
|
|
|
@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" }; |
|
|
|
protected File defaultCloneDir; |
|
|
|
@Override |
|
public void setUp() throws Exception { |
|
super.setUp(); |
|
defaultCloneDir = new File(getTemporaryDirectory(), "cloned"); |
|
} |
|
|
|
@Test(expected = TransportException.class) |
|
public void testSshWithoutConfig() throws Exception { |
|
cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort |
|
+ "/doesntmatter", defaultCloneDir, null); |
|
} |
|
|
|
@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 testSshWithConfig() throws Exception { |
|
cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // |
|
"Host localhost", // |
|
"HostName localhost", // |
|
"Port " + testPort, // |
|
"User " + TEST_USER, // |
|
"IdentityFile " + privateKey1.getAbsolutePath()); |
|
} |
|
|
|
@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<LogEntry> 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<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 { |
|
// 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())); |
|
} |
|
}
|
|
|