Browse Source

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>
master^2
Thomas Wolf 4 years ago
parent
commit
eb67862cba
  1. 1
      org.eclipse.jgit.junit.ssh/BUILD
  2. 3
      org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF
  3. 10
      org.eclipse.jgit.junit.ssh/pom.xml
  4. 71
      org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java
  5. 87
      org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java

1
org.eclipse.jgit.junit.ssh/BUILD

@ -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",

3
org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF

@ -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)",

10
org.eclipse.jgit.junit.ssh/pom.xml

@ -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>

71
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java

@ -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 class SshTestGitServer {
}); });
} }
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 class SshTestGitServer {
*/ */
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);
} }
/** /**

87
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java

@ -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.

Loading…
Cancel
Save