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>
tags/v5.9.0.202008260805-m3
Thomas Wolf 3 years ago
parent
commit
eb67862cba

+ 0
- 1
org.eclipse.jgit.junit.ssh/BUILD View File

@@ -13,7 +13,6 @@ java_library(
"//org.eclipse.jgit.ssh.jsch.test:__pkg__",
],
deps = [
"//lib:jsch",
"//lib:junit",
"//lib:sshd-osgi",
"//lib:sshd-sftp",

+ 1
- 2
org.eclipse.jgit.junit.ssh/META-INF/MANIFEST.MF View File

@@ -8,8 +8,7 @@ Bundle-Localization: plugin
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: com.jcraft.jsch;version="0.1.55",
org.apache.sshd.common;version="[2.4.0,2.5.0)",
Import-Package: 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.file.virtualfs;version="[2.4.0,2.5.0)",
org.apache.sshd.common.helpers;version="[2.4.0,2.5.0)",

+ 0
- 10
org.eclipse.jgit.junit.ssh/pom.xml View File

@@ -57,16 +57,6 @@
<version>${apache-sshd-version}</version>
</dependency>

<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
</dependency>

<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

+ 60
- 11
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestGitServer.java View File

@@ -91,8 +91,7 @@ public class SshTestGitServer {
* @param testUser
* user name of the test user
* @param testKey
* <em>private</em> key file of the test user; the server will
* only user the public key from it
* public key file of the test user
* @param repository
* to serve
* @param hostKey
@@ -103,17 +102,54 @@ public class SshTestGitServer {
public SshTestGitServer(@NonNull String testUser, @NonNull Path testKey,
@NonNull Repository repository, @NonNull byte[] hostKey)
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;
setTestUserPublicKey(testKey);
this.repository = repository;
server = SshServer.setUpDefaultServer();
// Set host key
try (ByteArrayInputStream in = new ByteArrayInputStream(hostKey)) {
SecurityUtils.loadKeyPairIdentities(null, null, in, null)
.forEach((k) -> hostKeys.add(k));
} catch (IOException | GeneralSecurityException e) {
// Ignore.
}
hostKeys.add(hostKey);
server.setKeyPairProvider((session) -> hostKeys);

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 {
@Override
protected Boolean doAuth(Buffer buffer, boolean initial)
@@ -343,8 +393,7 @@ public class SshTestGitServer {
*/
public void setTestUserPublicKey(Path key)
throws IOException, GeneralSecurityException {
this.testKey = AuthorizedKeyEntry.readAuthorizedKeys(key).get(0)
.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
this.testKey = readPublicKey(key);
}

/**

+ 55
- 32
org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java View File

@@ -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
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -9,27 +9,31 @@
*/
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.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
@@ -48,9 +52,6 @@ import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
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
* 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");
assertTrue(serverDir.mkdir());
// 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");
privateKey2 = new File(sshDir, "second_key");
publicKey1 = createKeyPair(privateKey1);
createKeyPair(privateKey2);
ByteArrayOutputStream publicHostKey = new ByteArrayOutputStream();
publicKey1 = createKeyPair(generator.generateKeyPair(), privateKey1);
createKeyPair(generator.generateKeyPair(), privateKey2);
// Create a host key
KeyPair hostKey = generator.generateKeyPair();
// Start a server with our test user and the first key.
server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db,
createHostKey(publicHostKey));
hostKey);
testPort = server.start();
assertTrue(testPort > 0);
knownHosts = new File(sshDir, "known_hosts");
Files.write(knownHosts.toPath(), Collections.singleton("[localhost]:"
+ testPort + ' '
+ publicHostKey.toString(US_ASCII.name())));
StringBuilder knownHostsLine = new StringBuilder();
knownHostsLine.append("[localhost]:").append(testPort).append(' ');
PublicKeyEntry.appendPublicKeyEntry(knownHostsLine,
hostKey.getPublic());
Files.write(knownHosts.toPath(),
Collections.singleton(knownHostsLine.toString()));
factory = createSessionFactory();
SshSessionFactory.setInstance(factory);
}

private static File createKeyPair(File privateKeyFile) throws Exception {
// Found no way to do this with MINA sshd except rolling it all
// ourselves...
JSch jsch = new JSch();
KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
try (OutputStream out = new FileOutputStream(privateKeyFile)) {
pair.writePrivateKey(out);
private static File createKeyPair(KeyPair newKey, File privateKeyFile)
throws Exception {
// Write PKCS#8 PEM unencrypted. Both JSch and sshd can read that.
PrivateKey privateKey = newKey.getPrivate();
String format = privateKey.getFormat();
if (!"PKCS#8".equalsIgnoreCase(format)) {
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(),
privateKeyFile.getName() + ".pub");
StringBuilder builder = new StringBuilder();
PublicKeyEntry.appendPublicKeyEntry(builder, newKey.getPublic());
builder.append(' ').append(TEST_USER);
try (OutputStream out = new FileOutputStream(publicKeyFile)) {
pair.writePublicKey(out, TEST_USER);
out.write(builder.toString().getBytes(StandardCharsets.US_ASCII));
}
return publicKeyFile;
}

private static byte[] createHostKey(OutputStream publicKey)
throws Exception {
JSch jsch = new JSch();
KeyPair pair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 2048);
pair.writePublicKey(publicKey, "");
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
pair.writePrivateKey(out);
out.flush();
return out.toByteArray();
private static void write(BufferedWriter out, byte[] bytes, int lineLength)
throws IOException {
String data = Base64.getEncoder().encodeToString(bytes);
int last = data.length();
for (int i = 0; i < last; i += lineLength) {
if (i + lineLength <= last) {
out.write(data.substring(i, i + lineLength));
} else {
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,
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());
String pubKey = lines.get(0);
// Strip off the comment.

Loading…
Cancel
Save