1 /* 2 * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 package org.eclipse.jgit.junit.ssh; 11 12 import static java.nio.charset.StandardCharsets.UTF_8; 13 import static org.junit.Assert.assertArrayEquals; 14 import static org.junit.Assert.assertEquals; 15 import static org.junit.Assert.assertFalse; 16 import static org.junit.Assert.assertNotNull; 17 import static org.junit.Assert.assertThrows; 18 import static org.junit.Assert.assertTrue; 19 import static org.junit.Assume.assumeTrue; 20 21 import java.io.File; 22 import java.io.IOException; 23 import java.nio.file.Files; 24 import java.util.List; 25 import java.util.Locale; 26 import java.util.concurrent.TimeUnit; 27 28 import org.eclipse.jgit.api.errors.TransportException; 29 import org.eclipse.jgit.errors.CommandFailedException; 30 import org.eclipse.jgit.transport.CredentialItem; 31 import org.eclipse.jgit.transport.URIish; 32 import org.eclipse.jgit.util.FS; 33 import org.eclipse.jgit.util.SshSupport; 34 import org.junit.Test; 35 import org.junit.experimental.theories.DataPoints; 36 import org.junit.experimental.theories.Theory; 37 38 /** 39 * The ssh tests. Concrete subclasses can re-use these tests by implementing the 40 * abstract operations from {@link SshTestHarness}. This gives a way to test 41 * different ssh clients against a unified test suite. 42 */ 43 public abstract class SshTestBase extends SshBasicTestBase { 44 45 @DataPoints 46 public static String[] KEY_RESOURCES = { // 47 "id_dsa", // 48 "id_rsa_1024", // 49 "id_rsa_2048", // 50 "id_rsa_3072", // 51 "id_rsa_4096", // 52 "id_ecdsa_256", // 53 "id_ecdsa_384", // 54 "id_ecdsa_521", // 55 "id_ed25519", // 56 // And now encrypted. Passphrase is "testpass". 57 "id_dsa_testpass", // 58 "id_rsa_1024_testpass", // 59 "id_rsa_2048_testpass", // 60 "id_rsa_3072_testpass", // 61 "id_rsa_4096_testpass", // 62 "id_ecdsa_256_testpass", // 63 "id_ecdsa_384_testpass", // 64 "id_ecdsa_521_testpass", // 65 "id_ed25519_testpass", // 66 "id_ed25519_expensive_testpass" }; 67 68 @Test testSshWithoutConfig()69 public void testSshWithoutConfig() throws Exception { 70 assertThrows(TransportException.class, 71 () -> cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort 72 + "/doesntmatter", defaultCloneDir, null)); 73 } 74 75 @Test testSingleCommand()76 public void testSingleCommand() throws Exception { 77 installConfig("IdentityFile " + privateKey1.getAbsolutePath()); 78 String command = SshTestGitServer.ECHO_COMMAND + " 1 without timeout"; 79 long start = System.nanoTime(); 80 String reply = SshSupport.runSshCommand( 81 new URIish("ssh://" + TEST_USER + "@localhost:" + testPort), 82 null, FS.DETECTED, command, 0); // 0 == no timeout 83 long elapsed = System.nanoTime() - start; 84 assertEquals(command, reply); 85 // Now that we have an idea how long this takes on the test 86 // infrastructure, try again with a timeout. 87 command = SshTestGitServer.ECHO_COMMAND + " 1 expecting no timeout"; 88 // Still use a generous timeout. 89 int timeout = 10 * ((int) TimeUnit.NANOSECONDS.toSeconds(elapsed) + 1); 90 reply = SshSupport.runSshCommand( 91 new URIish("ssh://" + TEST_USER + "@localhost:" + testPort), 92 null, FS.DETECTED, command, timeout); 93 assertEquals(command, reply); 94 } 95 96 @Test testSingleCommandWithTimeoutExpired()97 public void testSingleCommandWithTimeoutExpired() throws Exception { 98 installConfig("IdentityFile " + privateKey1.getAbsolutePath()); 99 String command = SshTestGitServer.ECHO_COMMAND + " 2 EXPECTING TIMEOUT"; 100 101 CommandFailedException e = assertThrows(CommandFailedException.class, 102 () -> SshSupport.runSshCommand(new URIish( 103 "ssh://" + TEST_USER + "@localhost:" + testPort), null, 104 FS.DETECTED, command, 1)); 105 assertTrue(e.getMessage().contains(command)); 106 assertTrue(e.getMessage().contains("time")); 107 } 108 109 @Test testSshWithGlobalIdentity()110 public void testSshWithGlobalIdentity() throws Exception { 111 cloneWith( 112 "ssh://" + TEST_USER + "@localhost:" + testPort 113 + "/doesntmatter", 114 defaultCloneDir, null, 115 "IdentityFile " + privateKey1.getAbsolutePath()); 116 } 117 118 @Test testSshWithDefaultIdentity()119 public void testSshWithDefaultIdentity() throws Exception { 120 File idRsa = new File(privateKey1.getParentFile(), "id_rsa"); 121 Files.copy(privateKey1.toPath(), idRsa.toPath()); 122 // We expect the session factory to pick up these keys... 123 cloneWith("ssh://" + TEST_USER + "@localhost:" + testPort 124 + "/doesntmatter", defaultCloneDir, null); 125 } 126 127 @Test testSshWithConfigEncryptedUnusedKey()128 public void testSshWithConfigEncryptedUnusedKey() throws Exception { 129 // Copy the encrypted test key from the bundle. 130 File encryptedKey = new File(sshDir, "id_dsa"); 131 copyTestResource("id_dsa_testpass", encryptedKey); 132 TestCredentialsProvider provider = new TestCredentialsProvider( 133 "testpass"); 134 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 135 "Host localhost", // 136 "HostName localhost", // 137 "Port " + testPort, // 138 "User " + TEST_USER, // 139 "IdentityFile " + privateKey1.getAbsolutePath()); 140 assertEquals("CredentialsProvider should not have been called", 0, 141 provider.getLog().size()); 142 } 143 144 @Test testSshWithConfigEncryptedUnusedKeyInConfigLast()145 public void testSshWithConfigEncryptedUnusedKeyInConfigLast() 146 throws Exception { 147 // Copy the encrypted test key from the bundle. 148 File encryptedKey = new File(sshDir, "id_dsa_test_key"); 149 copyTestResource("id_dsa_testpass", encryptedKey); 150 TestCredentialsProvider provider = new TestCredentialsProvider( 151 "testpass"); 152 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 153 "Host localhost", // 154 "HostName localhost", // 155 "Port " + testPort, // 156 "User " + TEST_USER, // 157 "IdentityFile " + privateKey1.getAbsolutePath(), 158 "IdentityFile " + encryptedKey.getAbsolutePath()); 159 // This test passes with JSch per chance because JSch completely ignores 160 // the second IdentityFile 161 assertEquals("CredentialsProvider should not have been called", 0, 162 provider.getLog().size()); 163 } 164 isJsch()165 private boolean isJsch() { 166 return getSessionFactory().getType().equals("jsch"); 167 } 168 169 @Test testSshWithConfigEncryptedUnusedKeyInConfigFirst()170 public void testSshWithConfigEncryptedUnusedKeyInConfigFirst() 171 throws Exception { 172 // Test cannot pass with JSch; it handles only one IdentityFile. 173 // assumeTrue(!(getSessionFactory() instanceof 174 // JschConfigSessionFactory)); gives in bazel a failure with "Never 175 // found parameters that satisfied method assumptions." 176 // In maven it's fine!? 177 if (isJsch()) { 178 return; 179 } 180 // Copy the encrypted test key from the bundle. 181 File encryptedKey = new File(sshDir, "id_dsa_test_key"); 182 copyTestResource("id_dsa_testpass", encryptedKey); 183 TestCredentialsProvider provider = new TestCredentialsProvider( 184 "testpass"); 185 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 186 "Host localhost", // 187 "HostName localhost", // 188 "Port " + testPort, // 189 "User " + TEST_USER, // 190 "IdentityFile " + encryptedKey.getAbsolutePath(), 191 "IdentityFile " + privateKey1.getAbsolutePath()); 192 assertEquals("CredentialsProvider should have been called once", 1, 193 provider.getLog().size()); 194 } 195 196 @Test testSshEncryptedUsedKeyCached()197 public void testSshEncryptedUsedKeyCached() throws Exception { 198 // Make sure we are asked for the password only once if we do several 199 // operations with an encrypted key. 200 File encryptedKey = new File(sshDir, "id_dsa_test_key"); 201 copyTestResource("id_dsa_testpass", encryptedKey); 202 File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); 203 copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); 204 server.setTestUserPublicKey(encryptedPublicKey.toPath()); 205 TestCredentialsProvider provider = new TestCredentialsProvider( 206 "testpass"); 207 pushTo(provider, 208 cloneWith("ssh://localhost/doesntmatter", // 209 defaultCloneDir, provider, // 210 "Host localhost", // 211 "HostName localhost", // 212 "Port " + testPort, // 213 "User " + TEST_USER, // 214 "IdentityFile " + encryptedKey.getAbsolutePath())); 215 assertEquals("CredentialsProvider should have been called once", 1, 216 provider.getLog().size()); 217 } 218 219 @Test(expected = TransportException.class) testSshEncryptedUsedKeyWrongPassword()220 public void testSshEncryptedUsedKeyWrongPassword() throws Exception { 221 File encryptedKey = new File(sshDir, "id_dsa_test_key"); 222 copyTestResource("id_dsa_testpass", encryptedKey); 223 File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); 224 copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); 225 server.setTestUserPublicKey(encryptedPublicKey.toPath()); 226 TestCredentialsProvider provider = new TestCredentialsProvider( 227 "wrongpass"); 228 cloneWith("ssh://localhost/doesntmatter", // 229 defaultCloneDir, provider, // 230 "Host localhost", // 231 "HostName localhost", // 232 "Port " + testPort, // 233 "User " + TEST_USER, // 234 "NumberOfPasswordPrompts 1", // 235 "IdentityFile " + encryptedKey.getAbsolutePath()); 236 } 237 238 @Test testSshEncryptedUsedKeySeveralPassword()239 public void testSshEncryptedUsedKeySeveralPassword() throws Exception { 240 File encryptedKey = new File(sshDir, "id_dsa_test_key"); 241 copyTestResource("id_dsa_testpass", encryptedKey); 242 File encryptedPublicKey = new File(sshDir, "id_dsa_test_key.pub"); 243 copyTestResource("id_dsa_testpass.pub", encryptedPublicKey); 244 server.setTestUserPublicKey(encryptedPublicKey.toPath()); 245 TestCredentialsProvider provider = new TestCredentialsProvider( 246 "wrongpass", "wrongpass2", "testpass"); 247 cloneWith("ssh://localhost/doesntmatter", // 248 defaultCloneDir, provider, // 249 "Host localhost", // 250 "HostName localhost", // 251 "Port " + testPort, // 252 "User " + TEST_USER, // 253 "IdentityFile " + encryptedKey.getAbsolutePath()); 254 assertEquals("CredentialsProvider should have been called 3 times", 3, 255 provider.getLog().size()); 256 } 257 258 @Test(expected = TransportException.class) testSshWithoutKnownHosts()259 public void testSshWithoutKnownHosts() throws Exception { 260 assertTrue("Could not delete known_hosts", knownHosts.delete()); 261 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 262 "Host localhost", // 263 "HostName localhost", // 264 "Port " + testPort, // 265 "User " + TEST_USER, // 266 "IdentityFile " + privateKey1.getAbsolutePath()); 267 } 268 269 @Test testSshWithoutKnownHostsWithProviderAsk()270 public void testSshWithoutKnownHostsWithProviderAsk() 271 throws Exception { 272 File copiedHosts = new File(knownHosts.getParentFile(), 273 "copiedKnownHosts"); 274 assertTrue("Failed to rename known_hosts", 275 knownHosts.renameTo(copiedHosts)); 276 // The provider will answer "yes" to all questions, so we should be able 277 // to connect and end up with a new known_hosts file with the host key. 278 TestCredentialsProvider provider = new TestCredentialsProvider(); 279 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 280 "Host localhost", // 281 "HostName localhost", // 282 "Port " + testPort, // 283 "User " + TEST_USER, // 284 "IdentityFile " + privateKey1.getAbsolutePath()); 285 List<LogEntry> messages = provider.getLog(); 286 assertFalse("Expected user interaction", messages.isEmpty()); 287 if (isJsch()) { 288 // JSch doesn't create a non-existing file. 289 assertEquals("Expected to be asked about the key", 1, 290 messages.size()); 291 return; 292 } 293 assertEquals( 294 "Expected to be asked about the key, and the file creation", 295 2, messages.size()); 296 assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists()); 297 // Instead of checking the file contents, let's just clone again 298 // without provider. If it works, the server host key was written 299 // correctly. 300 File clonedAgain = new File(getTemporaryDirectory(), "cloned2"); 301 cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, // 302 "Host localhost", // 303 "HostName localhost", // 304 "Port " + testPort, // 305 "User " + TEST_USER, // 306 "IdentityFile " + privateKey1.getAbsolutePath()); 307 } 308 309 @Test testSshWithoutKnownHostsWithProviderAcceptNew()310 public void testSshWithoutKnownHostsWithProviderAcceptNew() 311 throws Exception { 312 File copiedHosts = new File(knownHosts.getParentFile(), 313 "copiedKnownHosts"); 314 assertTrue("Failed to rename known_hosts", 315 knownHosts.renameTo(copiedHosts)); 316 TestCredentialsProvider provider = new TestCredentialsProvider(); 317 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 318 "Host localhost", // 319 "HostName localhost", // 320 "Port " + testPort, // 321 "User " + TEST_USER, // 322 "StrictHostKeyChecking accept-new", // 323 "IdentityFile " + privateKey1.getAbsolutePath()); 324 if (isJsch()) { 325 // JSch doesn't create new files. 326 assertTrue("CredentialsProvider not called", 327 provider.getLog().isEmpty()); 328 return; 329 } 330 assertEquals("Expected to be asked about the file creation", 1, 331 provider.getLog().size()); 332 assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists()); 333 // Instead of checking the file contents, let's just clone again 334 // without provider. If it works, the server host key was written 335 // correctly. 336 File clonedAgain = new File(getTemporaryDirectory(), "cloned2"); 337 cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, // 338 "Host localhost", // 339 "HostName localhost", // 340 "Port " + testPort, // 341 "User " + TEST_USER, // 342 "IdentityFile " + privateKey1.getAbsolutePath()); 343 } 344 345 @Test(expected = TransportException.class) testSshWithoutKnownHostsDeny()346 public void testSshWithoutKnownHostsDeny() throws Exception { 347 File copiedHosts = new File(knownHosts.getParentFile(), 348 "copiedKnownHosts"); 349 assertTrue("Failed to rename known_hosts", 350 knownHosts.renameTo(copiedHosts)); 351 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 352 "Host localhost", // 353 "HostName localhost", // 354 "Port " + testPort, // 355 "User " + TEST_USER, // 356 "StrictHostKeyChecking yes", // 357 "IdentityFile " + privateKey1.getAbsolutePath()); 358 } 359 360 @Test(expected = TransportException.class) testSshModifiedHostKeyDeny()361 public void testSshModifiedHostKeyDeny() 362 throws Exception { 363 File copiedHosts = new File(knownHosts.getParentFile(), 364 "copiedKnownHosts"); 365 assertTrue("Failed to rename known_hosts", 366 knownHosts.renameTo(copiedHosts)); 367 // Now produce a new known_hosts file containing some other key. 368 createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); 369 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 370 "Host localhost", // 371 "HostName localhost", // 372 "Port " + testPort, // 373 "User " + TEST_USER, // 374 "StrictHostKeyChecking yes", // 375 "IdentityFile " + privateKey1.getAbsolutePath()); 376 } 377 378 @Test(expected = TransportException.class) testSshModifiedHostKeyWithProviderDeny()379 public void testSshModifiedHostKeyWithProviderDeny() throws Exception { 380 File copiedHosts = new File(knownHosts.getParentFile(), 381 "copiedKnownHosts"); 382 assertTrue("Failed to rename known_hosts", 383 knownHosts.renameTo(copiedHosts)); 384 // Now produce a new known_hosts file containing some other key. 385 createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); 386 TestCredentialsProvider provider = new TestCredentialsProvider(); 387 try { 388 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 389 "Host localhost", // 390 "HostName localhost", // 391 "Port " + testPort, // 392 "User " + TEST_USER, // 393 "StrictHostKeyChecking yes", // 394 "IdentityFile " + privateKey1.getAbsolutePath()); 395 } catch (Exception e) { 396 assertEquals("Expected to be told about the modified key", 1, 397 provider.getLog().size()); 398 assertTrue("Only messages expected", provider.getLog().stream() 399 .flatMap(l -> l.getItems().stream()).allMatch( 400 c -> c instanceof CredentialItem.InformationalMessage)); 401 throw e; 402 } 403 } 404 checkKnownHostsModifiedHostKey(File backup, File newFile, String wrongKey)405 private void checkKnownHostsModifiedHostKey(File backup, File newFile, 406 String wrongKey) throws IOException { 407 List<String> oldLines = Files.readAllLines(backup.toPath(), UTF_8); 408 // Find the original entry. We should have that again in known_hosts. 409 String oldKeyPart = null; 410 for (String oldLine : oldLines) { 411 if (oldLine.contains("[localhost]:")) { 412 String[] parts = oldLine.split("\\s+"); 413 if (parts.length > 2) { 414 oldKeyPart = parts[parts.length - 2] + ' ' 415 + parts[parts.length - 1]; 416 break; 417 } 418 } 419 } 420 assertNotNull("Old key not found", oldKeyPart); 421 List<String> newLines = Files.readAllLines(newFile.toPath(), UTF_8); 422 assertFalse("Old host key still found in known_hosts file" + newFile, 423 hasHostKey("localhost", testPort, wrongKey, newLines)); 424 assertTrue("New host key not found in known_hosts file" + newFile, 425 hasHostKey("localhost", testPort, oldKeyPart, newLines)); 426 427 } 428 429 @Test testSshModifiedHostKeyAllow()430 public void testSshModifiedHostKeyAllow() throws Exception { 431 assertTrue("Failed to delete known_hosts", knownHosts.delete()); 432 createKnownHostsFile(knownHosts, "localhost", testPort, publicKey1); 433 File backup = new File(getTemporaryDirectory(), "backupKnownHosts"); 434 Files.copy(knownHosts.toPath(), backup.toPath()); 435 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 436 "Host localhost", // 437 "HostName localhost", // 438 "Port " + testPort, // 439 "User " + TEST_USER, // 440 "StrictHostKeyChecking no", // 441 "IdentityFile " + privateKey1.getAbsolutePath()); 442 // File should not have been updated! 443 String[] oldLines = Files 444 .readAllLines(backup.toPath(), UTF_8) 445 .toArray(new String[0]); 446 String[] newLines = Files 447 .readAllLines(knownHosts.toPath(), UTF_8) 448 .toArray(new String[0]); 449 assertArrayEquals("Known hosts file should not be modified", oldLines, 450 newLines); 451 } 452 453 @Test testSshModifiedHostKeyAsk()454 public void testSshModifiedHostKeyAsk() throws Exception { 455 File copiedHosts = new File(knownHosts.getParentFile(), 456 "copiedKnownHosts"); 457 assertTrue("Failed to rename known_hosts", 458 knownHosts.renameTo(copiedHosts)); 459 String wrongKeyPart = createKnownHostsFile(knownHosts, "localhost", 460 testPort, publicKey1); 461 TestCredentialsProvider provider = new TestCredentialsProvider(); 462 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, // 463 "Host localhost", // 464 "HostName localhost", // 465 "Port " + testPort, // 466 "User " + TEST_USER, // 467 "IdentityFile " + privateKey1.getAbsolutePath()); 468 checkKnownHostsModifiedHostKey(copiedHosts, knownHosts, wrongKeyPart); 469 assertEquals("Expected to be asked about the modified key", 1, 470 provider.getLog().size()); 471 } 472 473 @Test testSshCloneWithConfigAndPush()474 public void testSshCloneWithConfigAndPush() throws Exception { 475 pushTo(cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 476 "Host localhost", // 477 "HostName localhost", // 478 "Port " + testPort, // 479 "User " + TEST_USER, // 480 "IdentityFile " + privateKey1.getAbsolutePath())); 481 } 482 483 @Test testSftpWithConfig()484 public void testSftpWithConfig() throws Exception { 485 cloneWith("sftp://localhost/.git", defaultCloneDir, null, // 486 "Host localhost", // 487 "HostName localhost", // 488 "Port " + testPort, // 489 "User " + TEST_USER, // 490 "IdentityFile " + privateKey1.getAbsolutePath()); 491 } 492 493 @Test testSftpCloneWithConfigAndPush()494 public void testSftpCloneWithConfigAndPush() throws Exception { 495 pushTo(cloneWith("sftp://localhost/.git", defaultCloneDir, null, // 496 "Host localhost", // 497 "HostName localhost", // 498 "Port " + testPort, // 499 "User " + TEST_USER, // 500 "IdentityFile " + privateKey1.getAbsolutePath())); 501 } 502 503 @Test(expected = TransportException.class) testSshWithConfigWrongKey()504 public void testSshWithConfigWrongKey() throws Exception { 505 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, null, // 506 "Host localhost", // 507 "HostName localhost", // 508 "Port " + testPort, // 509 "User " + TEST_USER, // 510 "IdentityFile " + privateKey2.getAbsolutePath()); 511 } 512 513 @Test testSshWithWrongUserNameInConfig()514 public void testSshWithWrongUserNameInConfig() throws Exception { 515 // Bug 526778 516 cloneWith( 517 "ssh://" + TEST_USER + "@localhost:" + testPort 518 + "/doesntmatter", 519 defaultCloneDir, null, // 520 "Host localhost", // 521 "HostName localhost", // 522 "User sombody_else", // 523 "IdentityFile " + privateKey1.getAbsolutePath()); 524 } 525 526 @Test testSshWithWrongPortInConfig()527 public void testSshWithWrongPortInConfig() throws Exception { 528 // Bug 526778 529 cloneWith( 530 "ssh://" + TEST_USER + "@localhost:" + testPort 531 + "/doesntmatter", 532 defaultCloneDir, null, // 533 "Host localhost", // 534 "HostName localhost", // 535 "Port 22", // 536 "User " + TEST_USER, // 537 "IdentityFile " + privateKey1.getAbsolutePath()); 538 } 539 540 @Test testSshWithAliasInConfig()541 public void testSshWithAliasInConfig() throws Exception { 542 // Bug 531118 543 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 544 "Host git", // 545 "HostName localhost", // 546 "Port " + testPort, // 547 "User " + TEST_USER, // 548 "IdentityFile " + privateKey1.getAbsolutePath(), "", // 549 "Host localhost", // 550 "HostName localhost", // 551 "Port 22", // 552 "User someone_else", // 553 "IdentityFile " + privateKey2.getAbsolutePath()); 554 } 555 556 @Test testSshWithUnknownCiphersInConfig()557 public void testSshWithUnknownCiphersInConfig() throws Exception { 558 // Bug 535672 559 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 560 "Host git", // 561 "HostName localhost", // 562 "Port " + testPort, // 563 "User " + TEST_USER, // 564 "IdentityFile " + privateKey1.getAbsolutePath(), // 565 "Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr"); 566 } 567 568 @Test testSshWithUnknownHostKeyAlgorithmsInConfig()569 public void testSshWithUnknownHostKeyAlgorithmsInConfig() 570 throws Exception { 571 // Bug 535672 572 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 573 "Host git", // 574 "HostName localhost", // 575 "Port " + testPort, // 576 "User " + TEST_USER, // 577 "IdentityFile " + privateKey1.getAbsolutePath(), // 578 "HostKeyAlgorithms foobar,ssh-rsa,ssh-dss"); 579 } 580 581 @Test testSshWithUnknownKexAlgorithmsInConfig()582 public void testSshWithUnknownKexAlgorithmsInConfig() 583 throws Exception { 584 // Bug 535672 585 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 586 "Host git", // 587 "HostName localhost", // 588 "Port " + testPort, // 589 "User " + TEST_USER, // 590 "IdentityFile " + privateKey1.getAbsolutePath(), // 591 "KexAlgorithms foobar,diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"); 592 } 593 594 @Test testSshWithMinimalHostKeyAlgorithmsInConfig()595 public void testSshWithMinimalHostKeyAlgorithmsInConfig() 596 throws Exception { 597 // Bug 537790 598 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 599 "Host git", // 600 "HostName localhost", // 601 "Port " + testPort, // 602 "User " + TEST_USER, // 603 "IdentityFile " + privateKey1.getAbsolutePath(), // 604 "HostKeyAlgorithms ssh-rsa,ssh-dss"); 605 } 606 607 @Test testSshWithUnknownAuthInConfig()608 public void testSshWithUnknownAuthInConfig() throws Exception { 609 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 610 "Host git", // 611 "HostName localhost", // 612 "Port " + testPort, // 613 "User " + TEST_USER, // 614 "IdentityFile " + privateKey1.getAbsolutePath(), // 615 "PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password"); 616 } 617 618 @Test(expected = TransportException.class) testSshWithNoMatchingAuthInConfig()619 public void testSshWithNoMatchingAuthInConfig() throws Exception { 620 // Server doesn't do password, and anyway we set no password. 621 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 622 "Host git", // 623 "HostName localhost", // 624 "Port " + testPort, // 625 "User " + TEST_USER, // 626 "IdentityFile " + privateKey1.getAbsolutePath(), // 627 "PreferredAuthentications password"); 628 } 629 630 @Test testRsaHostKeySecond()631 public void testRsaHostKeySecond() throws Exception { 632 // See https://git.eclipse.org/r/#/c/130402/ : server has EcDSA 633 // (preferred), RSA, we have RSA in known_hosts: client and server 634 // should agree on RSA. 635 File newHostKey = new File(getTemporaryDirectory(), "newhostkey"); 636 copyTestResource("id_ecdsa_256", newHostKey); 637 server.addHostKey(newHostKey.toPath(), true); 638 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 639 "Host git", // 640 "HostName localhost", // 641 "Port " + testPort, // 642 "User " + TEST_USER, // 643 "IdentityFile " + privateKey1.getAbsolutePath()); 644 } 645 646 @Test testEcDsaHostKey()647 public void testEcDsaHostKey() throws Exception { 648 // See https://git.eclipse.org/r/#/c/130402/ : server has RSA 649 // (preferred), EcDSA, we have EcDSA in known_hosts: client and server 650 // should agree on EcDSA. 651 File newHostKey = new File(getTemporaryDirectory(), "newhostkey"); 652 copyTestResource("id_ecdsa_256", newHostKey); 653 server.addHostKey(newHostKey.toPath(), false); 654 File newHostKeyPub = new File(getTemporaryDirectory(), 655 "newhostkey.pub"); 656 copyTestResource("id_ecdsa_256.pub", newHostKeyPub); 657 createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub); 658 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, // 659 "Host git", // 660 "HostName localhost", // 661 "Port " + testPort, // 662 "User " + TEST_USER, // 663 "IdentityFile " + privateKey1.getAbsolutePath()); 664 } 665 666 @Test testPasswordAuth()667 public void testPasswordAuth() throws Exception { 668 server.enablePasswordAuthentication(); 669 TestCredentialsProvider provider = new TestCredentialsProvider( 670 TEST_USER.toUpperCase(Locale.ROOT)); 671 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 672 "Host git", // 673 "HostName localhost", // 674 "Port " + testPort, // 675 "User " + TEST_USER, // 676 "PreferredAuthentications password"); 677 } 678 679 @Test testPasswordAuthSeveralTimes()680 public void testPasswordAuthSeveralTimes() throws Exception { 681 server.enablePasswordAuthentication(); 682 TestCredentialsProvider provider = new TestCredentialsProvider( 683 "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT)); 684 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 685 "Host git", // 686 "HostName localhost", // 687 "Port " + testPort, // 688 "User " + TEST_USER, // 689 "PreferredAuthentications password"); 690 } 691 692 @Test(expected = TransportException.class) testPasswordAuthWrongPassword()693 public void testPasswordAuthWrongPassword() throws Exception { 694 server.enablePasswordAuthentication(); 695 TestCredentialsProvider provider = new TestCredentialsProvider( 696 "wrongpass"); 697 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 698 "Host git", // 699 "HostName localhost", // 700 "Port " + testPort, // 701 "User " + TEST_USER, // 702 "PreferredAuthentications password"); 703 } 704 705 @Test(expected = TransportException.class) testPasswordAuthNoPassword()706 public void testPasswordAuthNoPassword() throws Exception { 707 server.enablePasswordAuthentication(); 708 TestCredentialsProvider provider = new TestCredentialsProvider(); 709 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 710 "Host git", // 711 "HostName localhost", // 712 "Port " + testPort, // 713 "User " + TEST_USER, // 714 "PreferredAuthentications password"); 715 } 716 717 @Test(expected = TransportException.class) testPasswordAuthCorrectPasswordTooLate()718 public void testPasswordAuthCorrectPasswordTooLate() throws Exception { 719 server.enablePasswordAuthentication(); 720 TestCredentialsProvider provider = new TestCredentialsProvider( 721 "wrongpass", "wrongpass", "wrongpass", 722 TEST_USER.toUpperCase(Locale.ROOT)); 723 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 724 "Host git", // 725 "HostName localhost", // 726 "Port " + testPort, // 727 "User " + TEST_USER, // 728 "PreferredAuthentications password"); 729 } 730 731 @Test testKeyboardInteractiveAuth()732 public void testKeyboardInteractiveAuth() throws Exception { 733 server.enableKeyboardInteractiveAuthentication(); 734 TestCredentialsProvider provider = new TestCredentialsProvider( 735 TEST_USER.toUpperCase(Locale.ROOT)); 736 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 737 "Host git", // 738 "HostName localhost", // 739 "Port " + testPort, // 740 "User " + TEST_USER, // 741 "PreferredAuthentications keyboard-interactive"); 742 } 743 744 @Test testKeyboardInteractiveAuthSeveralTimes()745 public void testKeyboardInteractiveAuthSeveralTimes() throws Exception { 746 server.enableKeyboardInteractiveAuthentication(); 747 TestCredentialsProvider provider = new TestCredentialsProvider( 748 "wrongpass", "wrongpass", TEST_USER.toUpperCase(Locale.ROOT)); 749 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 750 "Host git", // 751 "HostName localhost", // 752 "Port " + testPort, // 753 "User " + TEST_USER, // 754 "PreferredAuthentications keyboard-interactive"); 755 } 756 757 @Test(expected = TransportException.class) testKeyboardInteractiveAuthWrongPassword()758 public void testKeyboardInteractiveAuthWrongPassword() throws Exception { 759 server.enableKeyboardInteractiveAuthentication(); 760 TestCredentialsProvider provider = new TestCredentialsProvider( 761 "wrongpass"); 762 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 763 "Host git", // 764 "HostName localhost", // 765 "Port " + testPort, // 766 "User " + TEST_USER, // 767 "PreferredAuthentications keyboard-interactive"); 768 } 769 770 @Test(expected = TransportException.class) testKeyboardInteractiveAuthNoPassword()771 public void testKeyboardInteractiveAuthNoPassword() throws Exception { 772 server.enableKeyboardInteractiveAuthentication(); 773 TestCredentialsProvider provider = new TestCredentialsProvider(); 774 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 775 "Host git", // 776 "HostName localhost", // 777 "Port " + testPort, // 778 "User " + TEST_USER, // 779 "PreferredAuthentications keyboard-interactive"); 780 } 781 782 @Test(expected = TransportException.class) testKeyboardInteractiveAuthCorrectPasswordTooLate()783 public void testKeyboardInteractiveAuthCorrectPasswordTooLate() 784 throws Exception { 785 server.enableKeyboardInteractiveAuthentication(); 786 TestCredentialsProvider provider = new TestCredentialsProvider( 787 "wrongpass", "wrongpass", "wrongpass", 788 TEST_USER.toUpperCase(Locale.ROOT)); 789 cloneWith("ssh://git/doesntmatter", defaultCloneDir, provider, // 790 "Host git", // 791 "HostName localhost", // 792 "Port " + testPort, // 793 "User " + TEST_USER, // 794 "PreferredAuthentications keyboard-interactive"); 795 } 796 797 @Theory testSshKeys(String keyName)798 public void testSshKeys(String keyName) throws Exception { 799 // JSch fails on ECDSA 384/521 keys. Compare 800 // https://sourceforge.net/p/jsch/patches/10/ 801 assumeTrue(!(isJsch() && (keyName.contains("ed25519") 802 || keyName.startsWith("id_ecdsa_384") 803 || keyName.startsWith("id_ecdsa_521")))); 804 File cloned = new File(getTemporaryDirectory(), "cloned"); 805 String keyFileName = keyName + "_key"; 806 File privateKey = new File(sshDir, keyFileName); 807 copyTestResource(keyName, privateKey); 808 File publicKey = new File(sshDir, keyFileName + ".pub"); 809 copyTestResource(keyName + ".pub", publicKey); 810 server.setTestUserPublicKey(publicKey.toPath()); 811 TestCredentialsProvider provider = new TestCredentialsProvider( 812 "testpass"); 813 pushTo(provider, 814 cloneWith("ssh://localhost/doesntmatter", // 815 cloned, provider, // 816 "Host localhost", // 817 "HostName localhost", // 818 "Port " + testPort, // 819 "User " + TEST_USER, // 820 "IdentityFile " + privateKey.getAbsolutePath())); 821 int expectedCalls = keyName.endsWith("testpass") ? 1 : 0; 822 assertEquals("Unexpected calls to CredentialsProvider", expectedCalls, 823 provider.getLog().size()); 824 // Should now also work without credentials provider, even if the key 825 // was encrypted. 826 cloned = new File(getTemporaryDirectory(), "cloned2"); 827 pushTo(null, 828 cloneWith("ssh://localhost/doesntmatter", // 829 cloned, null, // 830 "Host localhost", // 831 "HostName localhost", // 832 "Port " + testPort, // 833 "User " + TEST_USER, // 834 "IdentityFile " + privateKey.getAbsolutePath())); 835 } 836 } 837