Skip to content

Commit

Permalink
Merge pull request #104 from mwiede/opensshconfig
Browse files Browse the repository at this point in the history
implement openssh config behavior to handle append, prepend and removal of algorithms
  • Loading branch information
mwiede authored Dec 20, 2021
2 parents c13401c + 0a98fe5 commit 66db587
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.137.0/containers/java/.devcontainer/base.Dockerfile
ARG VARIANT="15"
ARG VARIANT="17"
FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}

# [Optional] Install Maven or Gradle
Expand Down
3 changes: 1 addition & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"dockerfile": "Dockerfile",
"args": {
// Update the VARIANT arg to pick a Java version: 11, 14
"VARIANT": "15",
"VARIANT": "17",
// Options
"INSTALL_MAVEN": "true",
"INSTALL_GRADLE": "false",
Expand All @@ -24,7 +24,6 @@
},
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"java.home": "/docker-java-home",
"maven.executable.path": "/usr/local/sdkman/candidates/maven/current/bin/mvn"
},
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"java.configuration.updateBuildConfiguration": "automatic",
"terminal.integrated.defaultProfile.linux": "bash"
}
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
* [0.1.72](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.72)
* Switch chacha20-poly1305@<!-- -->openssh.com algorithm to a pure [Bouncy Castle](https://www.bouncycastle.org/java.html) based implementation
* implement openssh config behavior to handle append, prepend and removal of algorithms [#104](https://github.com/mwiede/jsch/pull/104)
* [0.1.71](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.71)
* Address [#98](https://github.com/mwiede/jsch/issues/98) by restoring JSch.VERSION
* [0.1.70](https://github.com/mwiede/jsch/releases/tag/jsch-0.1.70)
Expand Down
46 changes: 41 additions & 5 deletions src/main/java/com/jcraft/jsch/OpenSSHConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,20 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING

package com.jcraft.jsch;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* This class implements ConfigRepository interface, and parses
Expand All @@ -64,7 +68,7 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* <li>CompressionLevel</li>
* <li>ForwardAgent</li>
* <li>RequestTTY</li>
* <li>ServerAliveInterval</li>
* <li>ServerAliveInterval</li>
* <li>LocalForward</li>
* <li>RemoteForward</li>
* <li>ClearAllForwardings</li>
Expand All @@ -74,6 +78,10 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
*/
public class OpenSSHConfig implements ConfigRepository {

private static final Set<String> keysWithListAdoption = Stream
.of("KexAlgorithms", "Ciphers","HostKeyAlgorithms", "MACs", "PubkeyAcceptedAlgorithms", "PubkeyAcceptedKeyTypes")
.map(String::toUpperCase).collect(Collectors.toSet());

/**
* Parses the given string, and returns an instance of ConfigRepository.
*
Expand Down Expand Up @@ -143,6 +151,15 @@ public Config getConfig(String host) {
return new MyConfig(host);
}

/**
* Returns mapping of jsch config property names to OpenSSH property names.
*
* @return map
*/
static Hashtable<String, String> getKeymap() {
return keymap;
}

private static final Hashtable<String, String> keymap = new Hashtable<>();
static {
keymap.put("kex", "KexAlgorithms");
Expand Down Expand Up @@ -192,6 +209,7 @@ else if(negate){
}

private String find(String key) {
String originalKey=key;
if(keymap.get(key)!=null) {
key = keymap.get(key);
}
Expand Down Expand Up @@ -223,6 +241,24 @@ private String find(String key) {
}
}
*/

if (keysWithListAdoption.contains(key) && value != null && (value.startsWith("+") || value.startsWith("-") || value.startsWith("^"))) {

String origConfig = JSch.getConfig(originalKey).trim();

if (value.startsWith("+")) {
value=origConfig + "," + value.substring(1).trim();
} else if (value.startsWith("-")) {
List<String> algList = Arrays.stream(Util.split(origConfig,",")).collect(Collectors.toList());
for (String alg : Util.split(value.substring(1).trim(),",")) {
algList.remove(alg.trim());
}
value = String.join(",", algList);
} else if (value.startsWith("^")) {
value = value.substring(1).trim() + "," + origConfig;
}
}

return value;
}

Expand All @@ -242,7 +278,7 @@ private String[] multiFind(String key) {
}
}
}
String[] result = new String[value.size()];
String[] result = new String[value.size()];
value.toArray(result);
return result;
}
Expand Down
72 changes: 68 additions & 4 deletions src/test/java/com/jcraft/jsch/OpenSSHConfigTest.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,86 @@
package com.jcraft.jsch;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.*;

class OpenSSHConfigTest {

@org.junit.jupiter.api.Test
Map<String, String> keyMap = OpenSSHConfig.getKeymap().entrySet().stream().collect(Collectors.toMap(entry->entry.getValue().toUpperCase(), Map.Entry::getKey, (s, s2) -> s2));

@Test
void parseFile() throws IOException, URISyntaxException {
final String configFile = Paths.get(ClassLoader.getSystemResource("config").toURI()).toFile().getAbsolutePath();
final OpenSSHConfig openSSHConfig = OpenSSHConfig.parseFile(configFile);
final ConfigRepository.Config config = openSSHConfig.getConfig("host2");
assertNotNull(config);
assertEquals("foobar", config.getUser());
assertEquals("host2.somewhere.edu", config.getHostname());
assertEquals("~/.ssh/old_keys/host2_key",config.getValue("IdentityFile"));
assertEquals("~/.ssh/old_keys/host2_key", config.getValue("IdentityFile"));
}

@ParameterizedTest
@ValueSource(strings = {"MACs", "Macs"})
void parseMacsCaseInsensitive(String key) throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse(key + " someValue");
ConfigRepository.Config config = parse.getConfig("");
assertEquals("someValue", config.getValue("mac.c2s"));
assertEquals("someValue", config.getValue("mac.s2c"));
}

@Test
void appendKexAlgorithms() throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse("KexAlgorithms +diffie-hellman-group1-sha1");
ConfigRepository.Config kex = parse.getConfig("");
assertEquals(JSch.getConfig("kex") + "," + "diffie-hellman-group1-sha1", kex.getValue("kex"));
}

@ParameterizedTest
@ValueSource(strings = {"KexAlgorithms", "Ciphers", "HostKeyAlgorithms", "MACs", "PubkeyAcceptedAlgorithms", "PubkeyAcceptedKeyTypes"})
void appendAlgorithms(String key) throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse(key + " +someValue,someValue1");
ConfigRepository.Config config = parse.getConfig("");
String mappedKey = Optional.ofNullable(keyMap.get(key.toUpperCase())).orElse(key);
assertEquals(JSch.getConfig(mappedKey) + "," + "someValue,someValue1", config.getValue(mappedKey));
}

@ParameterizedTest
@ValueSource(strings = {"KexAlgorithms", "Ciphers", "HostKeyAlgorithms", "MACs", "PubkeyAcceptedAlgorithms", "PubkeyAcceptedKeyTypes"})
void prependAlgorithms(String key) throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse(key + " ^someValue,someValue1");
ConfigRepository.Config config = parse.getConfig("");
String mappedKey = Optional.ofNullable(keyMap.get(key.toUpperCase())).orElse(key);
assertEquals("someValue,someValue1,"+JSch.getConfig(mappedKey) , config.getValue(mappedKey));
}

@Test
void prependKexAlgorithms() throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse("KexAlgorithms ^diffie-hellman-group1-sha1");
ConfigRepository.Config kex = parse.getConfig("");
assertEquals("diffie-hellman-group1-sha1," + JSch.getConfig("kex"), kex.getValue("kex"));
}

@Test
void removeKexAlgorithm() throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse("KexAlgorithms -ecdh-sha2-nistp256");
ConfigRepository.Config kex = parse.getConfig("");
assertEquals(JSch.getConfig("kex").replaceAll(",ecdh-sha2-nistp256", ""), kex.getValue("kex"));
}

@Test
void replaceKexAlgorithms() throws IOException {
OpenSSHConfig parse = OpenSSHConfig.parse("KexAlgorithms diffie-hellman-group1-sha1");
ConfigRepository.Config kex = parse.getConfig("");
assertEquals("diffie-hellman-group1-sha1", kex.getValue("kex"));
}

}

0 comments on commit 66db587

Please sign in to comment.