|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others |
|
|
|
* Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others |
|
|
|
* |
|
|
|
* |
|
|
|
* This program and the accompanying materials are made available under the |
|
|
|
* This program and the accompanying materials are made available under the |
|
|
|
* terms of the Eclipse Distribution License v. 1.0 which is available at |
|
|
|
* terms of the Eclipse Distribution License v. 1.0 which is available at |
|
|
@ -9,27 +9,31 @@ |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
package org.eclipse.jgit.junit.ssh; |
|
|
|
package org.eclipse.jgit.junit.ssh; |
|
|
|
|
|
|
|
|
|
|
|
import static java.nio.charset.StandardCharsets.US_ASCII; |
|
|
|
|
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8; |
|
|
|
|
|
|
|
import static org.junit.Assert.assertEquals; |
|
|
|
import static org.junit.Assert.assertEquals; |
|
|
|
import static org.junit.Assert.assertFalse; |
|
|
|
import static org.junit.Assert.assertFalse; |
|
|
|
import static org.junit.Assert.assertNotEquals; |
|
|
|
import static org.junit.Assert.assertNotEquals; |
|
|
|
import static org.junit.Assert.assertNotNull; |
|
|
|
import static org.junit.Assert.assertNotNull; |
|
|
|
import static org.junit.Assert.assertTrue; |
|
|
|
import static org.junit.Assert.assertTrue; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
import java.io.BufferedWriter; |
|
|
|
import java.io.File; |
|
|
|
import java.io.File; |
|
|
|
import java.io.FileOutputStream; |
|
|
|
import java.io.FileOutputStream; |
|
|
|
import java.io.IOException; |
|
|
|
import java.io.IOException; |
|
|
|
import java.io.InputStream; |
|
|
|
import java.io.InputStream; |
|
|
|
import java.io.OutputStream; |
|
|
|
import java.io.OutputStream; |
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.nio.file.Files; |
|
|
|
import java.nio.file.Files; |
|
|
|
|
|
|
|
import java.security.KeyPair; |
|
|
|
|
|
|
|
import java.security.KeyPairGenerator; |
|
|
|
|
|
|
|
import java.security.PrivateKey; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Arrays; |
|
|
|
|
|
|
|
import java.util.Base64; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Iterator; |
|
|
|
import java.util.Iterator; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import org.apache.sshd.common.config.keys.PublicKeyEntry; |
|
|
|
import org.eclipse.jgit.api.CloneCommand; |
|
|
|
import org.eclipse.jgit.api.CloneCommand; |
|
|
|
import org.eclipse.jgit.api.Git; |
|
|
|
import org.eclipse.jgit.api.Git; |
|
|
|
import org.eclipse.jgit.api.PushCommand; |
|
|
|
import org.eclipse.jgit.api.PushCommand; |
|
|
@ -48,9 +52,6 @@ import org.eclipse.jgit.transport.URIish; |
|
|
|
import org.eclipse.jgit.util.FS; |
|
|
|
import org.eclipse.jgit.util.FS; |
|
|
|
import org.junit.After; |
|
|
|
import org.junit.After; |
|
|
|
|
|
|
|
|
|
|
|
import com.jcraft.jsch.JSch; |
|
|
|
|
|
|
|
import com.jcraft.jsch.KeyPair; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Root class for ssh tests. Sets up the ssh test server. A set of pre-computed |
|
|
|
* Root class for ssh tests. Sets up the ssh test server. A set of pre-computed |
|
|
|
* keys for testing is provided in the bundle and can be used in test cases via |
|
|
|
* keys for testing is provided in the bundle and can be used in test cases via |
|
|
@ -104,50 +105,71 @@ public abstract class SshTestHarness extends RepositoryTestCase { |
|
|
|
File serverDir = new File(getTemporaryDirectory(), "srv"); |
|
|
|
File serverDir = new File(getTemporaryDirectory(), "srv"); |
|
|
|
assertTrue(serverDir.mkdir()); |
|
|
|
assertTrue(serverDir.mkdir()); |
|
|
|
// Create two key pairs. Let's not call them "id_rsa".
|
|
|
|
// Create two key pairs. Let's not call them "id_rsa".
|
|
|
|
|
|
|
|
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); |
|
|
|
|
|
|
|
generator.initialize(2048); |
|
|
|
privateKey1 = new File(sshDir, "first_key"); |
|
|
|
privateKey1 = new File(sshDir, "first_key"); |
|
|
|
privateKey2 = new File(sshDir, "second_key"); |
|
|
|
privateKey2 = new File(sshDir, "second_key"); |
|
|
|
publicKey1 = createKeyPair(privateKey1); |
|
|
|
publicKey1 = createKeyPair(generator.generateKeyPair(), privateKey1); |
|
|
|
createKeyPair(privateKey2); |
|
|
|
createKeyPair(generator.generateKeyPair(), privateKey2); |
|
|
|
ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream(); |
|
|
|
// Create a host key
|
|
|
|
|
|
|
|
KeyPair hostKey = generator.generateKeyPair(); |
|
|
|
// Start a server with our test user and the first key.
|
|
|
|
// Start a server with our test user and the first key.
|
|
|
|
server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db, |
|
|
|
server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db, |
|
|
|
createHostKey(publicHostKey)); |
|
|
|
hostKey); |
|
|
|
testPort = server.start(); |
|
|
|
testPort = server.start(); |
|
|
|
assertTrue(testPort > 0); |
|
|
|
assertTrue(testPort > 0); |
|
|
|
knownHosts = new File(sshDir, "known_hosts"); |
|
|
|
knownHosts = new File(sshDir, "known_hosts"); |
|
|
|
Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:" |
|
|
|
StringBuilder knownHostsLine = new StringBuilder(); |
|
|
|
+ testPort + ' ' |
|
|
|
knownHostsLine.append("[localhost]:").append(testPort).append(' '); |
|
|
|
+ publicHostKey.toString(US_ASCII.name()))); |
|
|
|
PublicKeyEntry.appendPublicKeyEntry(knownHostsLine, |
|
|
|
|
|
|
|
hostKey.getPublic()); |
|
|
|
|
|
|
|
Files.write(knownHosts.toPath(), |
|
|
|
|
|
|
|
Collections.singleton(knownHostsLine.toString())); |
|
|
|
factory = createSessionFactory(); |
|
|
|
factory = createSessionFactory(); |
|
|
|
SshSessionFactory.setInstance(factory); |
|
|
|
SshSessionFactory.setInstance(factory); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static File createKeyPair(File privateKeyFile) throws Exception { |
|
|
|
private static File createKeyPair(KeyPair newKey, File privateKeyFile) |
|
|
|
// Found no way to do this with MINA sshd except rolling it all
|
|
|
|
throws Exception { |
|
|
|
// ourselves...
|
|
|
|
// Write PKCS#8 PEM unencrypted. Both JSch and sshd can read that.
|
|
|
|
JSch jsch = new JSch(); |
|
|
|
PrivateKey privateKey = newKey.getPrivate(); |
|
|
|
KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048); |
|
|
|
String format = privateKey.getFormat(); |
|
|
|
try (OutputStream out = new FileOutputStream(privateKeyFile)) { |
|
|
|
if (!"PKCS#8".equalsIgnoreCase(format)) { |
|
|
|
pair.writePrivateKey(out); |
|
|
|
throw new IOException("Cannot write " + privateKey.getAlgorithm() |
|
|
|
|
|
|
|
+ " key in " + format + " format"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
try (BufferedWriter writer = Files.newBufferedWriter( |
|
|
|
|
|
|
|
privateKeyFile.toPath(), StandardCharsets.US_ASCII)) { |
|
|
|
|
|
|
|
writer.write("-----BEGIN PRIVATE KEY-----"); |
|
|
|
|
|
|
|
writer.newLine(); |
|
|
|
|
|
|
|
write(writer, privateKey.getEncoded(), 64); |
|
|
|
|
|
|
|
writer.write("-----END PRIVATE KEY-----"); |
|
|
|
|
|
|
|
writer.newLine(); |
|
|
|
} |
|
|
|
} |
|
|
|
File publicKeyFile = new File(privateKeyFile.getParentFile(), |
|
|
|
File publicKeyFile = new File(privateKeyFile.getParentFile(), |
|
|
|
privateKeyFile.getName() + ".pub"); |
|
|
|
privateKeyFile.getName() + ".pub"); |
|
|
|
|
|
|
|
StringBuilder builder = new StringBuilder(); |
|
|
|
|
|
|
|
PublicKeyEntry.appendPublicKeyEntry(builder, newKey.getPublic()); |
|
|
|
|
|
|
|
builder.append(' ').append(TEST_USER); |
|
|
|
try (OutputStream out = new FileOutputStream(publicKeyFile)) { |
|
|
|
try (OutputStream out = new FileOutputStream(publicKeyFile)) { |
|
|
|
pair.writePublicKey(out, TEST_USER); |
|
|
|
out.write(builder.toString().getBytes(StandardCharsets.US_ASCII)); |
|
|
|
} |
|
|
|
} |
|
|
|
return publicKeyFile; |
|
|
|
return publicKeyFile; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static byte[] createHostKey(OutputStream publicKey) |
|
|
|
private static void write(BufferedWriter out, byte[] bytes, int lineLength) |
|
|
|
throws Exception { |
|
|
|
throws IOException { |
|
|
|
JSch jsch = new JSch(); |
|
|
|
String data = Base64.getEncoder().encodeToString(bytes); |
|
|
|
KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048); |
|
|
|
int last = data.length(); |
|
|
|
pair.writePublicKey(publicKey, ""); |
|
|
|
for (int i = 0; i < last; i += lineLength) { |
|
|
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { |
|
|
|
if (i + lineLength <= last) { |
|
|
|
pair.writePrivateKey(out); |
|
|
|
out.write(data.substring(i, i + lineLength)); |
|
|
|
out.flush(); |
|
|
|
} else { |
|
|
|
return out.toByteArray(); |
|
|
|
out.write(data.substring(i)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
out.newLine(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Arrays.fill(bytes, (byte) 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
@ -167,7 +189,8 @@ public abstract class SshTestHarness extends RepositoryTestCase { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
protected static String createKnownHostsFile(File file, String host, |
|
|
|
protected static String createKnownHostsFile(File file, String host, |
|
|
|
int port, File publicKey) throws IOException { |
|
|
|
int port, File publicKey) throws IOException { |
|
|
|
List<String> lines = Files.readAllLines(publicKey.toPath(), UTF_8); |
|
|
|
List<String> lines = Files.readAllLines(publicKey.toPath(), |
|
|
|
|
|
|
|
StandardCharsets.UTF_8); |
|
|
|
assertEquals("Public key has too many lines", 1, lines.size()); |
|
|
|
assertEquals("Public key has too many lines", 1, lines.size()); |
|
|
|
String pubKey = lines.get(0); |
|
|
|
String pubKey = lines.get(0); |
|
|
|
// Strip off the comment.
|
|
|
|
// Strip off the comment.
|
|
|
|