Remove dependency on JSch from SSH test framework
Use standard java.security to generate test keys, use sshd to write public key files, and write PKCS#8 PEM files for our non-encrypted test private keys. This is a format that both JSch and Apache MINA sshd can read. Change-Id: I6ec55cfd7346b672a7fb6139d51abfb06d81a394 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
parent
d35f0ffb7c
commit
eb67862cba
|
@ -13,7 +13,6 @@ java_library(
|
||||||
"//org.eclipse.jgit.ssh.jsch.test:__pkg__",
|
"//org.eclipse.jgit.ssh.jsch.test:__pkg__",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//lib:jsch",
|
|
||||||
"//lib:junit",
|
"//lib:junit",
|
||||||
"//lib:sshd-osgi",
|
"//lib:sshd-osgi",
|
||||||
"//lib:sshd-sftp",
|
"//lib:sshd-sftp",
|
||||||
|
|
|
@ -8,8 +8,7 @@ Bundle-Localization: plugin
|
||||||
Bundle-Vendor: %Bundle-Vendor
|
Bundle-Vendor: %Bundle-Vendor
|
||||||
Bundle-ActivationPolicy: lazy
|
Bundle-ActivationPolicy: lazy
|
||||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||||
Import-Package: com.jcraft.jsch;version="0.1.55",
|
Import-Package: org.apache.sshd.common;version="[2.4.0,2.5.0)",
|
||||||
org.apache.sshd.common;version="[2.4.0,2.5.0)",
|
|
||||||
org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)",
|
org.apache.sshd.common.config.keys;version="[2.4.0,2.5.0)",
|
||||||
org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)",
|
org.apache.sshd.common.file.virtualfs;version="[2.4.0,2.5.0)",
|
||||||
org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)",
|
org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)",
|
||||||
|
|
|
@ -57,16 +57,6 @@
|
||||||
<version>${apache-sshd-version}</version>
|
<version>${apache-sshd-version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.jcraft</groupId>
|
|
||||||
<artifactId>jsch</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.jcraft</groupId>
|
|
||||||
<artifactId>jzlib</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -91,8 +91,7 @@ public class SshTestGitServer {
|
||||||
* @param testUser
|
* @param testUser
|
||||||
* user name of the test user
|
* user name of the test user
|
||||||
* @param testKey
|
* @param testKey
|
||||||
* <em>private</em> key file of the test user; the server will
|
* public key file of the test user
|
||||||
* only user the public key from it
|
|
||||||
* @param repository
|
* @param repository
|
||||||
* to serve
|
* to serve
|
||||||
* @param hostKey
|
* @param hostKey
|
||||||
|
@ -103,17 +102,54 @@ public class SshTestGitServer {
|
||||||
public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
|
public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
|
||||||
@NonNull Repository repository, @NonNull byte[] hostKey)
|
@NonNull Repository repository, @NonNull byte[] hostKey)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
this(testUser, readPublicKey(testKey), repository,
|
||||||
|
readKeyPair(hostKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ssh git <em>test</em> server. It serves one single repository,
|
||||||
|
* and accepts public-key authentication for exactly one test user.
|
||||||
|
*
|
||||||
|
* @param testUser
|
||||||
|
* user name of the test user
|
||||||
|
* @param testKey
|
||||||
|
* public key file of the test user
|
||||||
|
* @param repository
|
||||||
|
* to serve
|
||||||
|
* @param hostKey
|
||||||
|
* the unencrypted private key to use as host key
|
||||||
|
* @throws IOException
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
* @since 5.9
|
||||||
|
*/
|
||||||
|
public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
|
||||||
|
@NonNull Repository repository, @NonNull KeyPair hostKey)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
this(testUser, readPublicKey(testKey), repository, hostKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ssh git <em>test</em> server. It serves one single repository,
|
||||||
|
* and accepts public-key authentication for exactly one test user.
|
||||||
|
*
|
||||||
|
* @param testUser
|
||||||
|
* user name of the test user
|
||||||
|
* @param testKey
|
||||||
|
* the {@link PublicKey} of the test user
|
||||||
|
* @param repository
|
||||||
|
* to serve
|
||||||
|
* @param hostKey
|
||||||
|
* the {@link KeyPair} to use as host key
|
||||||
|
* @since 5.9
|
||||||
|
*/
|
||||||
|
public SshTestGitServer(@NonNull String testUser,
|
||||||
|
@NonNull PublicKey testKey, @NonNull Repository repository,
|
||||||
|
@NonNull KeyPair hostKey) {
|
||||||
this.testUser = testUser;
|
this.testUser = testUser;
|
||||||
setTestUserPublicKey(testKey);
|
setTestUserPublicKey(testKey);
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
server = SshServer.setUpDefaultServer();
|
server = SshServer.setUpDefaultServer();
|
||||||
// Set host key
|
hostKeys.add(hostKey);
|
||||||
try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
|
|
||||||
SecurityUtils.loadKeyPairIdentities(null, null, in, null)
|
|
||||||
.forEach((k) -> hostKeys.add(k));
|
|
||||||
} catch (IOException | GeneralSecurityException e) {
|
|
||||||
// Ignore.
|
|
||||||
}
|
|
||||||
server.setKeyPairProvider((session) -> hostKeys);
|
server.setKeyPairProvider((session) -> hostKeys);
|
||||||
|
|
||||||
configureAuthentication();
|
configureAuthentication();
|
||||||
|
@ -135,6 +171,20 @@ public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PublicKey readPublicKey(Path key)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
return AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
|
||||||
|
.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyPair readKeyPair(byte[] keyMaterial)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
try (ByteArrayInputStream in = new ByteArrayInputStream(keyMaterial)) {
|
||||||
|
return SecurityUtils.loadKeyPairIdentities(null, null, in, null)
|
||||||
|
.iterator().next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class FakeUserAuthGSS extends UserAuthGSS {
|
private static class FakeUserAuthGSS extends UserAuthGSS {
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doAuth(Buffer buffer, boolean initial)
|
protected Boolean doAuth(Buffer buffer, boolean initial)
|
||||||
|
@ -343,8 +393,7 @@ public void stop() throws IOException {
|
||||||
*/
|
*/
|
||||||
public void setTestUserPublicKey(Path key)
|
public void setTestUserPublicKey(Path key)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
|
this.testKey = readPublicKey(key);
|
||||||
.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.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 void setUp() throws Exception {
|
||||||
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 @@ private static byte[] createHostKey(OutputStream publicKey)
|
||||||
*/
|
*/
|
||||||
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.
|
||||||
|
|
Loading…
Reference in New Issue