Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement openssh config behavior to handle append, prepend and removal of algorithms #104

Merged
merged 12 commits into from
Dec 20, 2021
Merged
47 changes: 42 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,11 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
*/
public class OpenSSHConfig implements ConfigRepository {

private static final Set<String> keysWithListAdoption = Stream
.of("KexAlgorithms", "Ciphers", "HostbasedAcceptedAlgorithms",
"HostKeyAlgorithms", "MACs", "PubkeyAcceptedAlgorithms", "CASignatureAlgorithms", "PubkeyAcceptedKeyTypes")
mwiede marked this conversation as resolved.
Show resolved Hide resolved
.map(String::toUpperCase).collect(Collectors.toSet());

/**
* Parses the given string, and returns an instance of ConfigRepository.
*
Expand Down Expand Up @@ -143,6 +152,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() {
mwiede marked this conversation as resolved.
Show resolved Hide resolved
return keymap;
}

private static final Hashtable<String, String> keymap = new Hashtable<>();
static {
keymap.put("kex", "KexAlgorithms");
Expand Down Expand Up @@ -192,6 +210,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 +242,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);
mwiede marked this conversation as resolved.
Show resolved Hide resolved
} else if (value.startsWith("-")) {
List<String> algList = Arrays.stream(Util.split(origConfig,",")).collect(Collectors.toList());
for (String alg : Util.split(value.substring(1),",")) {
mwiede marked this conversation as resolved.
Show resolved Hide resolved
algList.remove(alg.trim());
}
value = String.join(",", algList);
} else if (value.startsWith("^")) {
value = value.substring(1).trim() + "," + origConfig;
}
}

return value;
}

Expand All @@ -242,7 +279,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"));
}

}