Skip to content

Commit

Permalink
implement openssh config behavior to handle append, prepend and remov…
Browse files Browse the repository at this point in the history
…al of algorithms
  • Loading branch information
wiedemam-VU committed Dec 16, 2021
1 parent 88b6633 commit 41a6749
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 9 deletions.
44 changes: 39 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,19 @@ 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.Vector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* This class implements ConfigRepository interface, and parses
Expand All @@ -64,7 +67,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 +77,9 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
*/
public class OpenSSHConfig implements ConfigRepository {

private static final List<String> keysWithListAdoption = Stream.of("KexAlgorithms", "Ciphers", "HostbasedAcceptedAlgorithms",
"HostKeyAlgorithms", "MACs", "PubkeyAcceptedAlgorithms", "CASignatureAlgorithms").map(String::toUpperCase).collect(Collectors.toList());

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

/**
* Returns mapping of jsch config property names to OpenSSH property names.
*
* @return map
*/
public 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 +207,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 +239,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);
} else if (value.startsWith("-")) {
List<String> algList = Arrays.stream(origConfig.split(",")).collect(Collectors.toList());
for (String alg : value.substring(1).split(",")) {
algList.remove(alg.trim());
}
value = String.join(",", algList);
} else if (value.startsWith("^")) {
value = value.substring(1).trim() + "," + origConfig;
}
}

return value;
}

Expand All @@ -242,7 +276,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"})
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"})
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 41a6749

Please sign in to comment.