You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:43 UTC

[33/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common utilities code from sshd-core into sshd-common (new artifact)

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
new file mode 100644
index 0000000..0c8f43c
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/HostConfigEntryTest.java
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.hosts;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class HostConfigEntryTest extends JUnitTestSupport {
+    public HostConfigEntryTest() {
+        super();
+    }
+
+    @Test
+    public void testNegatingPatternOverridesAll() {
+        String testHost = "37.77.34.7";
+        String[] elements = GenericUtils.split(testHost, '.');
+        StringBuilder sb = new StringBuilder(testHost.length() + Byte.SIZE);
+        List<HostPatternValue> patterns = new ArrayList<>(elements.length + 1);
+        // all wildcard patterns are not negated - only the actual host
+        patterns.add(HostPatternsHolder.toPattern(Character.toString(HostPatternsHolder.NEGATION_CHAR_PATTERN) + testHost));
+
+        for (int i = 0; i < elements.length; i++) {
+            sb.setLength(0);
+
+            for (int j = 0; j < elements.length; j++) {
+                if (j > 0) {
+                    sb.append('.');
+                }
+                if (i == j) {
+                    sb.append(HostPatternsHolder.WILDCARD_PATTERN);
+                } else {
+                    sb.append(elements[j]);
+                }
+            }
+
+            patterns.add(HostPatternsHolder.toPattern(sb));
+        }
+
+        for (int index = 0; index < patterns.size(); index++) {
+            assertFalse("Unexpected match for " + patterns, HostPatternsHolder.isHostMatch(testHost, 0, patterns));
+            Collections.shuffle(patterns);
+        }
+    }
+
+    @Test
+    public void testHostWildcardPatternMatching() {
+        String pkgName = getClass().getPackage().getName();
+        String[] elements = GenericUtils.split(pkgName, '.');
+        StringBuilder sb = new StringBuilder(pkgName.length() + Long.SIZE + 1).append(HostPatternsHolder.WILDCARD_PATTERN);
+        for (int index = elements.length - 1; index >= 0; index--) {
+            sb.append('.').append(elements[index]);
+        }
+
+        String value = sb.toString();
+        HostPatternValue pp = HostPatternsHolder.toPattern(value);
+        Pattern pattern = pp.getPattern();
+        String domain = value.substring(1); // chomp the wildcard prefix
+        for (String host : new String[] {
+                getClass().getSimpleName(),
+                getCurrentTestName(),
+                getClass().getSimpleName() + "-" + getCurrentTestName(),
+                getClass().getSimpleName() + "." + getCurrentTestName(),
+        }) {
+            sb.setLength(0); // start from scratch
+            sb.append(host).append(domain);
+
+            testCaseInsensitivePatternMatching(sb.toString(), pattern, true);
+        }
+    }
+
+    @Test
+    public void testIPAddressWildcardPatternMatching() {
+        StringBuilder sb = new StringBuilder().append("10.0.0.");
+        int sbLen = sb.length();
+
+        Pattern pattern = HostPatternsHolder.toPattern(sb.append(HostPatternsHolder.WILDCARD_PATTERN)).getPattern();
+        for (int v = 0; v <= 255; v++) {
+            sb.setLength(sbLen);    // start from scratch
+            sb.append(v);
+
+            String address = sb.toString();
+            assertTrue("No match for " + address, HostPatternsHolder.isHostMatch(address, pattern));
+        }
+    }
+
+    @Test
+    public void testHostSingleCharPatternMatching() {
+        String value = getCurrentTestName();
+        StringBuilder sb = new StringBuilder(value);
+        for (boolean restoreOriginal : new boolean[] {true, false}) {
+            for (int index = 0; index < value.length(); index++) {
+                sb.setCharAt(index, HostPatternsHolder.SINGLE_CHAR_PATTERN);
+                testCaseInsensitivePatternMatching(value, HostPatternsHolder.toPattern(sb.toString()).getPattern(), true);
+                if (restoreOriginal) {
+                    sb.setCharAt(index, value.charAt(index));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testIPAddressSingleCharPatternMatching() {
+        StringBuilder sb = new StringBuilder().append("10.0.0.");
+        int sbLen = sb.length();
+
+        for (int v = 0; v <= 255; v++) {
+            sb.setLength(sbLen);    // start from scratch
+            sb.append(v);
+
+            String address = sb.toString();
+            // replace the added digits with single char pattern
+            for (int index = sbLen; index < sb.length(); index++) {
+                sb.setCharAt(index, HostPatternsHolder.SINGLE_CHAR_PATTERN);
+            }
+
+            String pattern = sb.toString();
+            HostPatternValue pp = HostPatternsHolder.toPattern(pattern);
+            assertTrue("No match for " + address + " on pattern=" + pattern, HostPatternsHolder.isHostMatch(address, 0, Collections.singletonList(pp)));
+        }
+    }
+
+    @Test
+    public void testIsValidPatternChar() {
+        for (char ch = '\0'; ch <= ' '; ch++) {
+            assertFalse("Unexpected valid character (0x" + Integer.toHexString(ch & 0xFF) + ")", HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch = 'a'; ch <= 'z'; ch++) {
+            assertTrue("Valid character not recognized: " + Character.toString(ch), HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch = 'A'; ch <= 'Z'; ch++) {
+            assertTrue("Valid character not recognized: " + Character.toString(ch), HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch = '0'; ch <= '9'; ch++) {
+            assertTrue("Valid character not recognized: " + Character.toString(ch), HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch : new char[] {'-', '_', '.', HostPatternsHolder.SINGLE_CHAR_PATTERN, HostPatternsHolder.WILDCARD_PATTERN}) {
+            assertTrue("Valid character not recognized: " + Character.toString(ch), HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch : new char[] {
+            '(', ')', '{', '}', '[', ']', '@',
+            '#', '$', '^', '&', '%', '~', '<', '>',
+            ',', '/', '\\', '\'', '"', ':', ';'
+        }) {
+            assertFalse("Unexpected valid character: " + Character.toString(ch), HostPatternsHolder.isValidPatternChar(ch));
+        }
+
+        for (char ch = 0x7E; ch <= 0xFF; ch++) {
+            assertFalse("Unexpected valid character (0x" + Integer.toHexString(ch & 0xFF) + ")", HostPatternsHolder.isValidPatternChar(ch));
+        }
+    }
+
+    @Test
+    public void testResolvePort() {
+        final int originalPort = Short.MAX_VALUE;
+        final int preferredPort = 7365;
+        assertEquals("Mismatched entry port preference",
+            preferredPort, HostConfigEntry.resolvePort(originalPort, preferredPort));
+
+        for (int entryPort : new int[] {-1, 0}) {
+            assertEquals("Non-preferred original port for entry port=" + entryPort,
+                originalPort, HostConfigEntry.resolvePort(originalPort, entryPort));
+        }
+    }
+
+    @Test
+    public void testResolveUsername() {
+        final String originalUser = getCurrentTestName();
+        final String preferredUser = getClass().getSimpleName();
+        assertSame("Mismatched entry user preference",
+                preferredUser, HostConfigEntry.resolveUsername(originalUser, preferredUser));
+
+        for (String entryUser : new String[] {null, ""}) {
+            assertSame("Non-preferred original user for entry user='" + entryUser + "'",
+                originalUser, HostConfigEntry.resolveUsername(originalUser, entryUser));
+        }
+    }
+
+    @Test
+    public void testReadSimpleHostsConfigEntries() throws IOException {
+        validateHostConfigEntries(readHostConfigEntries());
+    }
+
+    @Test
+    public void testReadGlobalHostsConfigEntries() throws IOException {
+        List<HostConfigEntry> entries = validateHostConfigEntries(readHostConfigEntries());
+        assertTrue("Not enough entries read", GenericUtils.size(entries) > 1);
+
+        // global entry MUST be 1st one
+        HostConfigEntry globalEntry = entries.get(0);
+        assertEquals("Mismatched global entry pattern", HostPatternsHolder.ALL_HOSTS_PATTERN, globalEntry.getHost());
+
+        for (int index = 1; index < entries.size(); index++) {
+            HostConfigEntry entry = entries.get(index);
+            assertFalse("No target host for " + entry, GenericUtils.isEmpty(entry.getHostName()));
+            assertTrue("No target port for " + entry, entry.getPort() > 0);
+            assertFalse("No username for " + entry, GenericUtils.isEmpty(entry.getUsername()));
+            assertFalse("No identities for " + entry, GenericUtils.isEmpty(entry.getIdentities()));
+            assertFalse("No properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+        }
+    }
+
+    @Test
+    public void testReadMultipleHostPatterns() throws IOException {
+        List<HostConfigEntry> entries = validateHostConfigEntries(readHostConfigEntries());
+        assertEquals("Mismatched number of entries", 1, GenericUtils.size(entries));
+        assertEquals("Mismatched number of patterns", 3, GenericUtils.size(entries.get(0).getPatterns()));
+    }
+
+    @Test
+    public void testResolveIdentityFilePath() throws Exception {
+        final String hostValue = getClass().getSimpleName();
+        final int portValue = 7365;
+        final String userValue = getCurrentTestName();
+
+        Exception err = null;
+        for (String pattern : new String[] {
+            "~/.ssh/%h.key",
+            "%d/.ssh/%h.key",
+            "/home/%u/.ssh/id_rsa_%p",
+            "/home/%u/.ssh/id_%r_rsa",
+            "/home/%u/.ssh/%h/%l.key"
+        }) {
+            try {
+                String result = HostConfigEntry.resolveIdentityFilePath(pattern, hostValue, portValue, userValue);
+                System.out.append('\t').append(pattern).append(" => ").println(result);
+            } catch (Exception e) {
+                System.err.append("Failed (").append(e.getClass().getSimpleName())
+                          .append(") to process pattern=").append(pattern)
+                          .append(": ").println(e.getMessage());
+                err = e;
+            }
+        }
+
+        if (err != null) {
+            throw err;
+        }
+    }
+
+    @Test
+    public void testFindBestMatch() {
+        final String hostValue = getCurrentTestName();
+        HostConfigEntry expected = new HostConfigEntry(hostValue, hostValue, 7365, hostValue);
+        List<HostConfigEntry> matches = new ArrayList<>();
+        matches.add(new HostConfigEntry(HostPatternsHolder.ALL_HOSTS_PATTERN,
+            getClass().getSimpleName(), Short.MAX_VALUE, getClass().getSimpleName()));
+        matches.add(new HostConfigEntry(hostValue + Character.toString(HostPatternsHolder.WILDCARD_PATTERN),
+            getClass().getSimpleName(), Byte.MAX_VALUE, getClass().getSimpleName()));
+        matches.add(expected);
+
+        for (int index = 0; index < matches.size(); index++) {
+            HostConfigEntry actual = HostConfigEntry.findBestMatch(matches);
+            assertSame("Mismatched best match for " + matches, expected, actual);
+            Collections.shuffle(matches);
+        }
+    }
+
+    private static <C extends Collection<HostConfigEntry>> C validateHostConfigEntries(C entries) {
+        assertFalse("No entries", GenericUtils.isEmpty(entries));
+
+        for (HostConfigEntry entry : entries) {
+            assertFalse("No pattern for " + entry, GenericUtils.isEmpty(entry.getHost()));
+            assertFalse("No extra properties for " + entry, GenericUtils.isEmpty(entry.getProperties()));
+        }
+
+        return entries;
+    }
+
+    private List<HostConfigEntry> readHostConfigEntries() throws IOException {
+        return readHostConfigEntries(getCurrentTestName() + ".config.txt");
+    }
+
+    private List<HostConfigEntry> readHostConfigEntries(String resourceName) throws IOException {
+        URL url = getClass().getResource(resourceName);
+        assertNotNull("Missing resource " + resourceName, url);
+        return HostConfigEntry.readHostConfigEntries(url);
+    }
+
+    private static void testCaseInsensitivePatternMatching(String value, Pattern pattern, boolean expected) {
+        for (int index = 0; index < value.length(); index++) {
+            boolean actual = HostPatternsHolder.isHostMatch(value, pattern);
+            assertEquals("Mismatched match result for " + value + " on pattern=" + pattern.pattern(), expected, actual);
+            value = shuffleCase(value);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/KnownHostHashValueTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/KnownHostHashValueTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/KnownHostHashValueTest.java
new file mode 100644
index 0000000..80d58d0
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/hosts/KnownHostHashValueTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.hosts;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class KnownHostHashValueTest extends JUnitTestSupport {
+    private final String hostName;
+    private final String hashValue;
+    private final KnownHostHashValue hash;
+
+    public KnownHostHashValueTest(String hostName, String hashValue) {
+        this.hostName = hostName;
+        this.hashValue = hashValue;
+        this.hash = KnownHostHashValue.parse(hashValue);
+    }
+
+    @Parameters(name = "host={0}, hash={1}")
+    public static Collection<Object[]> parameters() {
+        return Arrays.<Object[]>asList(
+                (Object[]) new String[]{"192.168.1.61", "|1|F1E1KeoE/eEWhi10WpGv4OdiO6Y=|3988QV0VE8wmZL7suNrYQLITLCg="});
+    }
+
+    @Test
+    public void testDecodeEncode() {
+        assertSame("Mismatched digester", KnownHostHashValue.DEFAULT_DIGEST, hash.getDigester());
+        assertEquals("Mismatched encoded form", hashValue, hash.toString());
+    }
+
+    @Test
+    public void testHostMatch() {
+        assertTrue("Specified host does not match", hash.isHostMatch(hostName));
+        assertFalse("Unexpected host match", hash.isHostMatch(getCurrentTestName()));
+    }
+
+    @Test
+    public void testCalculateHashValue() throws Exception {
+        byte[] expected = hash.getDigestValue();
+        byte[] actual = KnownHostHashValue.calculateHashValue(hostName, hash.getDigester(), hash.getSaltValue());
+        assertArrayEquals("Mismatched hash value", expected, actual);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
new file mode 100644
index 0000000..19375ec
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/BuiltinClientIdentitiesWatcherTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class BuiltinClientIdentitiesWatcherTest extends JUnitTestSupport {
+    public BuiltinClientIdentitiesWatcherTest() {
+        super();
+    }
+
+    @Test
+    public void testMultipleFilesWatch() throws Exception {
+        KeyPair identity = CommonTestSupportUtils.getFirstKeyPair(createTestHostKeyProvider());
+        String keyType = ValidateUtils.checkNotNullAndNotEmpty(KeyUtils.getKeyType(identity), "Cannot determine identity key type");
+
+        Path dir = assertHierarchyTargetFolderExists(getTempTargetRelativeFile(getClass().getSimpleName()));
+        Map<BuiltinIdentities, Path> locationsMap = new EnumMap<>(BuiltinIdentities.class);
+        Map<BuiltinIdentities, KeyPair> idsMap = new EnumMap<>(BuiltinIdentities.class);
+        for (BuiltinIdentities id : BuiltinIdentities.VALUES) {
+            Path idFile = dir.resolve(ClientIdentity.getIdentityFileName(id));
+            Files.deleteIfExists(idFile);
+            assertNull("Multiple file mappings for " + id, locationsMap.put(id, idFile));
+            assertNull("Multiple identity mappings for " + id, idsMap.put(id, KeyUtils.cloneKeyPair(keyType, identity)));
+        }
+
+        ClientIdentityLoader loader = new ClientIdentityLoader() {
+            @Override
+            public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+                BuiltinIdentities id = findIdentity(location);
+                assertNotNull("Invalid location: " + location, id);
+                return idsMap.get(id);
+            }
+
+            @Override
+            public boolean isValidLocation(String location) throws IOException {
+                return findIdentity(location) != null;
+            }
+
+            private BuiltinIdentities findIdentity(String location) {
+                if (GenericUtils.isEmpty(location)) {
+                    return null;
+                }
+
+                for (Map.Entry<BuiltinIdentities, Path> le : locationsMap.entrySet()) {
+                    Path path = le.getValue();
+                    if (String.CASE_INSENSITIVE_ORDER.compare(location, path.toString()) == 0) {
+                        return le.getKey();
+                    }
+                }
+
+                return null;
+            }
+        };
+
+        Map<BuiltinIdentities, KeyPair> existing = new EnumMap<>(BuiltinIdentities.class);
+        KeyPairProvider watcher = new BuiltinClientIdentitiesWatcher(dir, false, loader, FilePasswordProvider.EMPTY, false);
+        testMultipleFilesWatch("No files", watcher, existing.values());
+
+        for (BuiltinIdentities id : BuiltinIdentities.VALUES) {
+            String phase = id + " + " + Objects.toString(existing.keySet());
+            touchIdentityFile(locationsMap.get(id));
+            existing.put(id, idsMap.get(id));
+
+            for (int index = 0; index < Byte.SIZE; index++) {
+                testMultipleFilesWatch(phase + "[" + index + "]", watcher, existing.values());
+            }
+        }
+
+        testMultipleFilesWatch("All files", watcher, existing.values());
+
+        for (BuiltinIdentities id : BuiltinIdentities.VALUES) {
+            existing.remove(id);
+            Files.deleteIfExists(locationsMap.get(id));
+            String phase = Objects.toString(existing.keySet()) + " - " + id;
+
+            for (int index = 0; index < Byte.SIZE; index++) {
+                testMultipleFilesWatch(phase + "[" + index + "]", watcher, existing.values());
+            }
+        }
+    }
+
+    private static void touchIdentityFile(Path idFile) throws IOException {
+        OpenOption[] options = IoUtils.EMPTY_OPEN_OPTIONS;
+        if (Files.exists(idFile, IoUtils.EMPTY_LINK_OPTIONS)) {
+            options = new OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.APPEND};
+        }
+
+        try (OutputStream out = Files.newOutputStream(idFile, options)) {
+            out.write(new Date(System.currentTimeMillis()).toString().getBytes(StandardCharsets.UTF_8));
+            out.write('\n');
+        }
+    }
+
+    private static void testMultipleFilesWatch(String phase, KeyIdentityProvider watcher, Collection<? extends KeyPair> expected) {
+        Iterable<KeyPair> keys = watcher.loadKeys();
+        Collection<KeyPair> actual = new ArrayList<>();
+        for (KeyPair kp : keys) {
+            actual.add(kp);
+        }
+        assertEquals(phase + ": mismatched sizes", GenericUtils.size(expected), GenericUtils.size(actual));
+
+        if (!GenericUtils.isEmpty(expected)) {
+            for (KeyPair kp : expected) {
+                assertTrue(phase + ": missing key", actual.contains(kp));
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java
new file mode 100644
index 0000000..144a3b0
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityFileWatcherTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Date;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class ClientIdentityFileWatcherTest extends JUnitTestSupport {
+    public ClientIdentityFileWatcherTest() {
+        super();
+    }
+
+    @Test
+    public void testIdentityReload() throws Exception {
+        Path dir = assertHierarchyTargetFolderExists(getTempTargetRelativeFile(getClass().getSimpleName()));
+        Path idFile = dir.resolve(getCurrentTestName() + ".pem");
+        KeyPair identity = CommonTestSupportUtils.getFirstKeyPair(createTestHostKeyProvider());
+        ClientIdentityLoader loader = new ClientIdentityLoader() {
+            @Override
+            public KeyPair loadClientIdentity(String location, FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+                assertTrue("Invalid location: " + location, isValidLocation(location));
+                return identity;
+            }
+
+            @Override
+            public boolean isValidLocation(String location) throws IOException {
+                return Objects.equals(location, toString());
+            }
+
+            @Override
+            public String toString() {
+                return Objects.toString(idFile);
+            }
+        };
+
+        AtomicInteger reloadCount = new AtomicInteger(0);
+        ClientIdentityProvider idProvider = new ClientIdentityFileWatcher(idFile, loader, FilePasswordProvider.EMPTY, false) {
+            @Override
+            protected KeyPair reloadClientIdentity(Path path) throws IOException, GeneralSecurityException {
+                assertEquals("Mismatched client identity path", idFile, path);
+                reloadCount.incrementAndGet();
+                return super.reloadClientIdentity(path);
+            }
+        };
+        Files.deleteIfExists(idFile);
+
+        testIdentityReload("Non-existing", reloadCount, idProvider, null, 0);
+
+        touchIdentityFile(idFile);
+        for (int index = 1; index < Byte.SIZE; index++) {
+            testIdentityReload("Created iteration " + 1, reloadCount, idProvider, identity, 1);
+        }
+
+        touchIdentityFile(idFile);
+        for (int index = 1; index < Byte.SIZE; index++) {
+            testIdentityReload("Modified iteration " + 1, reloadCount, idProvider, identity, 2);
+        }
+    }
+
+    private static void touchIdentityFile(Path idFile) throws IOException {
+        OpenOption[] options = IoUtils.EMPTY_OPEN_OPTIONS;
+        if (Files.exists(idFile, IoUtils.EMPTY_LINK_OPTIONS)) {
+            options = new OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.APPEND};
+        }
+
+        try (OutputStream out = Files.newOutputStream(idFile, options)) {
+            out.write(new Date(System.currentTimeMillis()).toString().getBytes(StandardCharsets.UTF_8));
+            out.write('\n');
+        }
+    }
+
+    private static void testIdentityReload(
+            String phase, Number reloadCount, ClientIdentityProvider provider, KeyPair expectedIdentity, int expectedCount)
+                throws Exception {
+        KeyPair actualIdentity = provider.getClientIdentity();
+        assertSame(phase + ": mismatched identity", expectedIdentity, actualIdentity);
+        assertEquals(phase + ": mismatched re-load count", expectedCount, reloadCount.intValue());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
new file mode 100644
index 0000000..bc2f02c
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/client/config/keys/ClientIdentityTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.config.keys;
+
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Map;
+
+import org.apache.sshd.common.config.keys.BuiltinIdentities;
+import org.apache.sshd.common.config.keys.IdentityUtils;
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class ClientIdentityTest extends JUnitTestSupport {
+    public ClientIdentityTest() {
+        super();
+    }
+
+    @Test
+    public void testLoadClientIdentities() throws Exception {
+        Path resFolder = getTestResourcesFolder();
+        LinkOption[] options = IoUtils.getLinkOptions(true);
+        Collection<BuiltinIdentities> expected = EnumSet.noneOf(BuiltinIdentities.class);
+        for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
+            String fileName = ClientIdentity.getIdentityFileName(type);
+            Path file = resFolder.resolve(fileName);
+            if (!Files.exists(file, options)) {
+                System.out.println("Skip non-existing identity file " + file);
+                continue;
+            }
+
+            if (!type.isSupported()) {
+                System.out.println("Skip unsupported identity file " + file);
+                continue;
+            }
+
+            expected.add(type);
+        }
+
+        Map<String, KeyPair> ids = ClientIdentity.loadDefaultIdentities(
+                resFolder,
+                false,   // don't be strict
+                null,    // none of the files is password protected
+                options);
+        assertEquals("Mismatched loaded ids count", GenericUtils.size(expected), GenericUtils.size(ids));
+
+        Collection<KeyPair> pairs = new ArrayList<>(ids.size());
+        for (BuiltinIdentities type : BuiltinIdentities.VALUES) {
+            if (expected.contains(type)) {
+                KeyPair kp = ids.get(type.getName());
+                assertNotNull("No key pair loaded for " + type, kp);
+                pairs.add(kp);
+            }
+        }
+
+        KeyIdentityProvider provider = IdentityUtils.createKeyPairProvider(ids, true /* supported only */);
+        assertNotNull("No provider generated", provider);
+
+        Iterable<KeyPair> keys = provider.loadKeys();
+        for (KeyPair kp : keys) {
+            assertTrue("Unexpected loaded key: " + kp, pairs.remove(kp));
+        }
+
+        assertEquals("Not all pairs listed", 0, pairs.size());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/SshConstantsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/SshConstantsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/SshConstantsTest.java
new file mode 100644
index 0000000..1d123d3
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/SshConstantsTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common;
+
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class SshConstantsTest extends JUnitTestSupport {
+    public SshConstantsTest() {
+        super();
+    }
+
+    @Test
+    public void testGetDisconnectReason() {
+        for (int reason = SshConstants.SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT; reason <= SshConstants.SSH2_DISCONNECT_ILLEGAL_USER_NAME; reason++) {
+            String name = SshConstants.getDisconnectReasonName(reason);
+            assertTrue("Mismatched name for reason=" + reason + ": " + name, name.startsWith("SSH2_DISCONNECT_"));
+        }
+    }
+
+    @Test
+    public void testGetOpenErrorName() {
+        for (int code = SshConstants.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED; code <= SshConstants.SSH_OPEN_RESOURCE_SHORTAGE; code++) {
+            String name = SshConstants.getOpenErrorCodeName(code);
+            assertTrue("Mismatched name for code=" + code + ": " + name, name.startsWith("SSH_OPEN_"));
+        }
+    }
+
+    @Test
+    public void testAmbiguousOpcodes() throws Exception {
+        int[] knownAmbiguities = {30, 31, 60};
+        Collection<Integer> opcodes = SshConstants.getAmbiguousOpcodes();
+        assertTrue("Not enough ambiguities found", GenericUtils.size(opcodes) >= knownAmbiguities.length);
+
+        for (int cmd : knownAmbiguities) {
+            assertEquals("Mismatched mnemonic for known ambiguity=" + cmd, Integer.toString(cmd), SshConstants.getCommandMessageName(cmd));
+            assertTrue("Known ambiguity not reported as such: " + cmd, SshConstants.isAmbiguousOpcode(cmd));
+            assertTrue("Known ambiguity=" + cmd + " not listed: " + opcodes, opcodes.contains(cmd));
+        }
+
+        for (Integer cmd : opcodes) {
+            assertEquals("Mismatched mnemonic for " + cmd, cmd.toString(), SshConstants.getCommandMessageName(cmd));
+            assertTrue("Opcode not detected as ambiguous: " + cmd, SshConstants.isAmbiguousOpcode(cmd));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
new file mode 100644
index 0000000..c0978d4
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/VersionPropertiesTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common;
+
+import java.util.Map;
+
+import org.apache.sshd.common.config.VersionProperties;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class VersionPropertiesTest extends JUnitTestSupport {
+    public VersionPropertiesTest() {
+        super();
+    }
+
+    @Test
+    public void testNonEmptyProperties() {
+        Map<?, ?> props = VersionProperties.getVersionProperties();
+        assertTrue(GenericUtils.isNotEmpty(props));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
new file mode 100644
index 0000000..6611702
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES192CTRTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AES192CTRTest extends BaseCipherTest {
+    public AES192CTRTest() {
+        super();
+    }
+
+    @Test
+    public void testEncryptDecrypt() throws Exception {
+        // for AES 256 bits we need the JCE unlimited strength policy
+        ensureKeySizeSupported(16, 24, "AES", "AES/CTR/NoPadding");
+        testEncryptDecrypt(BuiltinCiphers.aes192ctr);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
new file mode 100644
index 0000000..dd39fd4
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/AES256CBCTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AES256CBCTest extends BaseCipherTest {
+    public AES256CBCTest() {
+        super();
+    }
+
+    @Test
+    public void testEncryptDecrypt() throws Exception {
+        // for AES 256 bits we need the JCE unlimited strength policy
+        ensureKeySizeSupported(16, 32, "AES", "AES/CBC/NoPadding");
+        testEncryptDecrypt(BuiltinCiphers.aes256cbc);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR128Test.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR128Test.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR128Test.java
new file mode 100644
index 0000000..1c37449
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR128Test.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ARCFOUR128Test extends BaseCipherTest {
+    public ARCFOUR128Test() {
+        super();
+    }
+
+    @Test
+    public void testEncryptDecrypt() throws Exception {
+        testEncryptDecrypt(BuiltinCiphers.arcfour128);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
new file mode 100644
index 0000000..5511e0f
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ARCFOUR256Test.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ARCFOUR256Test extends BaseCipherTest {
+    public ARCFOUR256Test() {
+        super();
+    }
+
+    @Test
+    public void testEncryptDecrypt() throws Exception {
+        // for RC4 256 bits we need the JCE unlimited strength policy
+        ensureKeySizeSupported(32, "ARCFOUR", "RC4");
+        testEncryptDecrypt(BuiltinCiphers.arcfour256);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
new file mode 100644
index 0000000..514cba4
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/BaseCipherTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.cipher.Cipher.Mode;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.Assume;
+import org.junit.experimental.categories.Category;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@Category({ NoIoTestCase.class })
+public abstract class BaseCipherTest extends JUnitTestSupport {
+    protected BaseCipherTest() {
+        super();
+    }
+
+    protected void ensureKeySizeSupported(int bsize, String algorithm, String transformation) throws GeneralSecurityException {
+        try {
+            javax.crypto.Cipher cipher = SecurityUtils.getCipher(transformation);
+            byte[] key = new byte[bsize];
+            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, new SecretKeySpec(key, algorithm));
+        } catch (GeneralSecurityException e) {
+            if (e instanceof InvalidKeyException) {    // NOTE: assumption violations are NOT test failures...
+                Assume.assumeTrue(algorithm + "/" + transformation + "[" + bsize + "] N/A", false);
+            }
+
+            throw e;
+        }
+    }
+
+    protected void ensureKeySizeSupported(int ivsize, int bsize, String algorithm, String transformation) throws GeneralSecurityException {
+        try {
+            javax.crypto.Cipher cipher = SecurityUtils.getCipher(transformation);
+            byte[] key = new byte[bsize];
+            byte[] iv = new byte[ivsize];
+            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, new SecretKeySpec(key, algorithm), new IvParameterSpec(iv));
+        } catch (GeneralSecurityException e) {
+            if (e instanceof InvalidKeyException) {
+                Assume.assumeTrue(algorithm + "/" + transformation + "[" + bsize + "/" + ivsize + "]", false /* force exception */);
+            }
+
+            throw e;
+        }
+    }
+
+    protected void testEncryptDecrypt(NamedFactory<Cipher> factory) throws Exception {
+        String facName = factory.getName();
+        Cipher enc = factory.create();
+        int keySize = enc.getBlockSize();
+        int ivSize = enc.getIVSize();
+        byte[] key = new byte[keySize];
+        byte[] iv = new byte[ivSize];
+        enc.init(Mode.Encrypt, key, iv);
+
+        byte[] expected = facName.getBytes(StandardCharsets.UTF_8);
+        byte[] workBuf = expected.clone();    // need to clone since the cipher works in-line
+        enc.update(workBuf, 0, workBuf.length);
+
+        Cipher dec = factory.create();
+        dec.init(Mode.Decrypt, key, iv);
+        byte[] actual = workBuf.clone();
+        dec.update(actual, 0, actual.length);
+
+        assertArrayEquals(facName, expected, actual);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/cipher/ECCurvesTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/cipher/ECCurvesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ECCurvesTest.java
new file mode 100644
index 0000000..2294606
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/cipher/ECCurvesTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.cipher;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class ECCurvesTest extends JUnitTestSupport {
+    public ECCurvesTest() {
+        super();
+    }
+
+    @Test
+    public void testFromName() {
+        for (ECCurves expected : ECCurves.VALUES) {
+            String name = expected.getName();
+            for (int index = 0; index < name.length(); index++) {
+                ECCurves actual = ECCurves.fromCurveName(name);
+                assertSame(name, expected, actual);
+                name = shuffleCase(name);
+            }
+        }
+    }
+
+    @Test
+    public void testAllNamesListed() {
+        Set<ECCurves> listed = EnumSet.noneOf(ECCurves.class);
+        for (String name : ECCurves.NAMES) {
+            ECCurves c = ECCurves.fromCurveName(name);
+            assertNotNull("No curve for listed name=" + name, c);
+            assertTrue("Duplicated listed name: " + name, listed.add(c));
+        }
+
+        assertEquals("Mismatched listed vs. values", ECCurves.VALUES, listed);
+    }
+
+    @Test
+    public void testFromKeySize() {
+        for (ECCurves expected : ECCurves.VALUES) {
+            String name = expected.getName();
+            ECCurves actual = ECCurves.fromCurveSize(expected.getKeySize());
+            assertSame(name, expected, actual);
+        }
+    }
+
+    @Test
+    public void testFromCurveParameters() {
+        for (ECCurves expected : ECCurves.VALUES) {
+            String name = expected.getName();
+            ECCurves actual = ECCurves.fromCurveParameters(expected.getParameters());
+            assertSame(name, expected, actual);
+        }
+    }
+
+    @Test
+    public void testFromKeyType() {
+        for (ECCurves expected : ECCurves.VALUES) {
+            String keyType = expected.getKeyType();
+            for (int index = 0; index < keyType.length(); index++) {
+                ECCurves actual = ECCurves.fromKeyType(keyType);
+                assertSame(keyType, expected, actual);
+                keyType = shuffleCase(keyType);
+            }
+        }
+    }
+
+    @Test
+    public void testAllKeyTypesListed() {
+        Set<ECCurves> listed = EnumSet.noneOf(ECCurves.class);
+        for (String name : ECCurves.KEY_TYPES) {
+            ECCurves c = ECCurves.fromKeyType(name);
+            assertNotNull("No curve for listed key type=" + name, c);
+            assertTrue("Duplicated listed key type: " + name, listed.add(c));
+        }
+
+        assertEquals("Mismatched listed vs. values", ECCurves.VALUES, listed);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/compression/BuiltinCompressionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/compression/BuiltinCompressionsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/compression/BuiltinCompressionsTest.java
new file mode 100644
index 0000000..3d1ed00
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/compression/BuiltinCompressionsTest.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.compression;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.compression.BuiltinCompressions.ParseResult;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class BuiltinCompressionsTest extends JUnitTestSupport {
+    public BuiltinCompressionsTest() {
+        super();
+    }
+
+    @Test
+    public void testFromFactoryName() {
+        for (BuiltinCompressions expected : BuiltinCompressions.VALUES) {
+            String name = expected.getName();
+
+            for (int index = 0; index < name.length(); index++) {
+                BuiltinCompressions actual = BuiltinCompressions.fromFactoryName(name);
+                assertSame(name, expected, actual);
+                name = shuffleCase(name);
+            }
+        }
+    }
+
+    @Test
+    public void testAllConstantsCovered() throws Exception {
+        Set<BuiltinCompressions> avail = EnumSet.noneOf(BuiltinCompressions.class);
+        Field[] fields = BuiltinCompressions.Constants.class.getFields();
+        for (Field f : fields) {
+            String name = (String) f.get(null);
+            BuiltinCompressions value = BuiltinCompressions.fromFactoryName(name);
+            assertNotNull("No match found for " + name, value);
+            assertTrue(name + " re-specified", avail.add(value));
+        }
+
+        assertEquals("Incomplete coverage", BuiltinCompressions.VALUES, avail);
+    }
+
+    @Test
+    public void testParseCompressionsList() {
+        List<String> builtin = NamedResource.getNameList(BuiltinCompressions.VALUES);
+        List<String> unknown = Arrays.asList(getClass().getPackage().getName(), getClass().getSimpleName(), getCurrentTestName());
+        Random rnd = new Random();
+        for (int index = 0; index < (builtin.size() + unknown.size()); index++) {
+            Collections.shuffle(builtin, rnd);
+            Collections.shuffle(unknown, rnd);
+
+            List<String> weavedList = new ArrayList<>(builtin.size() + unknown.size());
+            for (int bIndex = 0, uIndex = 0; (bIndex < builtin.size()) || (uIndex < unknown.size());) {
+                boolean useBuiltin = false;
+                if (bIndex < builtin.size()) {
+                    useBuiltin = uIndex >= unknown.size() || rnd.nextBoolean();
+                }
+
+                if (useBuiltin) {
+                    weavedList.add(builtin.get(bIndex));
+                    bIndex++;
+                } else if (uIndex < unknown.size()) {
+                    weavedList.add(unknown.get(uIndex));
+                    uIndex++;
+                }
+            }
+
+            String fullList = GenericUtils.join(weavedList, ',');
+            ParseResult result = BuiltinCompressions.parseCompressionsList(fullList);
+            List<String> parsed = NamedResource.getNameList(result.getParsedFactories());
+            List<String> missing = result.getUnsupportedFactories();
+
+            // makes sure not only that the contents are the same but also the order
+            assertListEquals(fullList + "[parsed]", builtin, parsed);
+            assertListEquals(fullList + "[unsupported]", unknown, missing);
+        }
+    }
+
+    @Test
+    public void testResolveFactoryOnBuiltinValues() {
+        for (NamedFactory<Compression> expected : BuiltinCompressions.VALUES) {
+            String name = expected.getName();
+            NamedFactory<Compression> actual = BuiltinCompressions.resolveFactory(name);
+            assertSame(name, expected, actual);
+        }
+    }
+
+    @Test
+    public void testNotAllowedToRegisterBuiltinFactories() {
+        for (CompressionFactory expected : BuiltinCompressions.VALUES) {
+            try {
+                BuiltinCompressions.registerExtension(expected);
+                fail("Unexpected success for " + expected.getName());
+            } catch (IllegalArgumentException e) {
+                // expected - ignored
+            }
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNotAllowedToOverrideRegisteredFactories() {
+        CompressionFactory expected = Mockito.mock(CompressionFactory.class);
+        Mockito.when(expected.getName()).thenReturn(getCurrentTestName());
+
+        String name = expected.getName();
+        try {
+            for (int index = 1; index <= Byte.SIZE; index++) {
+                BuiltinCompressions.registerExtension(expected);
+                assertEquals("Unexpected success at attempt #" + index, 1, index);
+            }
+        } finally {
+            BuiltinCompressions.unregisterExtension(name);
+        }
+    }
+
+    @Test
+    public void testResolveFactoryOnRegisteredExtension() {
+        CompressionFactory expected = Mockito.mock(CompressionFactory.class);
+        Mockito.when(expected.getName()).thenReturn(getCurrentTestName());
+
+        String name = expected.getName();
+        try {
+            assertNull("Extension already registered", BuiltinCompressions.resolveFactory(name));
+            BuiltinCompressions.registerExtension(expected);
+
+            NamedFactory<Compression> actual = BuiltinCompressions.resolveFactory(name);
+            assertSame("Mismatched resolved instance", expected, actual);
+        } finally {
+            NamedFactory<Compression> actual = BuiltinCompressions.unregisterExtension(name);
+            assertSame("Mismatched unregistered instance", expected, actual);
+            assertNull("Extension not un-registered", BuiltinCompressions.resolveFactory(name));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/config/TimeValueConfigTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/TimeValueConfigTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/TimeValueConfigTest.java
new file mode 100644
index 0000000..0466d95
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/TimeValueConfigTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class TimeValueConfigTest extends JUnitTestSupport {
+    public TimeValueConfigTest() {
+        super();
+    }
+
+    @Test
+    public void testDurationOf() {
+        Object[] values = {
+            "600", TimeUnit.SECONDS.toMillis(600L),
+            "10m", TimeUnit.MINUTES.toMillis(10L),
+            "1h30m", TimeUnit.MINUTES.toMillis(90L),
+            "2d", TimeUnit.DAYS.toMillis(2L),
+            "3w", TimeUnit.DAYS.toMillis(3L * 7L)
+        };
+        for (int index = 0; index < values.length; index += 2) {
+            String s = (String) values[index];
+            Number expected = (Number) values[index + 1];
+            long actual = TimeValueConfig.durationOf(s);
+            assertEquals(s, expected.longValue(), actual);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryLoginOptionsParseTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryLoginOptionsParseTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryLoginOptionsParseTest.java
new file mode 100644
index 0000000..d912998
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntryLoginOptionsParseTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.config.keys;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class AuthorizedKeyEntryLoginOptionsParseTest extends JUnitTestSupport {
+    private final String value;
+    private final String loginPart;
+    private final String keyPart;
+    private final Map<String, String> options;
+
+    public AuthorizedKeyEntryLoginOptionsParseTest(String value, String loginPart, String keyPart, Map<String, String> options) {
+        this.value = value;
+        this.loginPart = loginPart;
+        this.keyPart = keyPart;
+        this.options = options;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        List<Object[]> params = new ArrayList<>();
+        addData(params, "ssh-rsa AAAAB2...19Q==", "john@example.net", "from=\"*.sales.example.net,!pc.sales.example.net\"");
+        addData(params, "ssh-dss AAAAC3...51R==", "example.net", "command=\"dump /home\"", "no-pty", "no-port-forwarding");
+        addData(params, "ssh-dss AAAAB5...21S==", "", "permitopen=\"192.0.2.1:80\"", "permitopen=\"192.0.2.2:25\"");
+        addData(params, "ssh-rsa AAAA...==", "jane@example.net", "tunnel=\"0\"", "command=\"sh /etc/netstart tun0\"");
+        addData(params, "ssh-rsa AAAA1C8...32Tv==", "user@example.net", "!restrict", "command=\"uptime\"");
+        addData(params, "ssh-rsa AAAA1f8...IrrC5==", "user@example.net", "restrict", "!pty", "command=\"nethack\"");
+        return params;
+    }
+
+    private static void addData(List<Object[]> params, String keyData, String comment, String... comps) {
+        StringBuilder sb = new StringBuilder();
+
+        Map<String, String> optionsMap = new HashMap<>();
+        for (String c : comps) {
+            if (sb.length() > 0) {
+                sb.append(',');
+            }
+            sb.append(c);
+
+            int pos = c.indexOf('=');
+            if (pos > 0) {
+                String name = c.substring(0, pos);
+                String value = GenericUtils.stripQuotes(c.substring(pos + 1)).toString();
+                String prev = optionsMap.put(name, value);
+                if (prev != null) {
+                    optionsMap.put(name, prev + "," + value);
+                }
+            } else {
+                optionsMap.put(c, Boolean.toString(c.charAt(0) != AuthorizedKeyEntry.BOOLEAN_OPTION_NEGATION_INDICATOR));
+            }
+        }
+
+        int pos = sb.length();
+
+        sb.append(' ').append(keyData);
+        if (GenericUtils.isNotEmpty(comment)) {
+            sb.append(' ').append(comment);
+        }
+
+        String value = sb.toString();
+        params.add(new Object[] {value, value.substring(0, pos), value.substring(pos + 1), optionsMap});
+    }
+
+    @Test
+    public void testResolveEntryComponents() {
+        Map.Entry<String, String> actual = AuthorizedKeyEntry.resolveEntryComponents(value);
+        assertNotNull(value, actual);
+        assertEquals("login(" + value + ")", loginPart, actual.getKey());
+        assertEquals("remainder(" + value + ")", keyPart, actual.getValue());
+    }
+
+    @Test
+    public void testParseLoginOptions() {
+        Map<String, String> parsed = AuthorizedKeyEntry.parseLoginOptions(loginPart);
+        options.forEach((key, expected) -> {
+            String actual = parsed.get(key);
+            assertEquals(key, expected, actual);
+        });
+        assertEquals("Mismatched size", options.size(), parsed.size());
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" + value + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
new file mode 100644
index 0000000..4735846
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/BuiltinIdentitiesTest.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.util.List;
+
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class BuiltinIdentitiesTest extends JUnitTestSupport {
+    private final BuiltinIdentities expected;
+
+    public BuiltinIdentitiesTest(BuiltinIdentities expected) {
+        this.expected = expected;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return parameterize(BuiltinIdentities.VALUES);
+    }
+
+    @BeforeClass    // Dirty hack around the parameterized run
+    public static void testAllConstantsCovered() throws Exception {
+        Field[] fields = BuiltinIdentities.Constants.class.getFields();
+        for (Field f : fields) {
+            int mods = f.getModifiers();
+            if (!Modifier.isStatic(mods)) {
+                continue;
+            }
+
+            if (!Modifier.isFinal(mods)) {
+                continue;
+            }
+
+            Class<?> type = f.getType();
+            if (!String.class.isAssignableFrom(type)) {
+                continue;
+            }
+
+            String name = f.getName();
+            String value = (String) f.get(null);
+            BuiltinIdentities id = BuiltinIdentities.fromName(value);
+            assertNotNull("No match found for field " + name + "=" + value, id);
+        }
+    }
+
+    @Test
+    public void testFromName() {
+        String name = expected.getName();
+        for (int index = 0, count = name.length(); index < count; index++) {
+            assertSame(name, expected, BuiltinIdentities.fromName(name));
+            name = shuffleCase(name);
+        }
+    }
+
+    @Test
+    public void testFromAlgorithm() {
+        String algorithm = expected.getAlgorithm();
+        for (int index = 0, count = algorithm.length(); index < count; index++) {
+            assertSame(algorithm, expected, BuiltinIdentities.fromAlgorithm(algorithm));
+            algorithm = shuffleCase(algorithm);
+        }
+    }
+
+    @Test
+    public void testFromKey() throws GeneralSecurityException {
+        Assume.assumeTrue("Unsupported built-in identity", expected.isSupported());
+        KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(expected.getAlgorithm());
+        KeyPair kp = gen.generateKeyPair();
+        outputDebugMessage("Checking built-in identity: %s", expected);
+        assertSame(expected + "[pair]", expected, BuiltinIdentities.fromKeyPair(kp));
+        assertSame(expected + "[public]", expected, BuiltinIdentities.fromKey(kp.getPublic()));
+        assertSame(expected + "[private]", expected, BuiltinIdentities.fromKey(kp.getPrivate()));
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
new file mode 100644
index 0000000..eedbfe7
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/config/keys/KeyRandomArtTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.config.keys;
+
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.AfterClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@Category({ NoIoTestCase.class })
+public class KeyRandomArtTest extends JUnitTestSupport {
+    private static final Collection<KeyPair> KEYS = new LinkedList<>();
+
+    private final String algorithm;
+    private final int keySize;
+    private final KeyPair keyPair;
+
+    public KeyRandomArtTest(String algorithm, int keySize) throws Exception {
+        this.algorithm = algorithm;
+        this.keySize = keySize;
+        this.keyPair = CommonTestSupportUtils.generateKeyPair(algorithm, keySize);
+        KEYS.add(this.keyPair);
+    }
+
+    @Parameters(name = "algorithm={0}, key-size={1}")
+    public static List<Object[]> parameters() {
+        List<Object[]> params = new ArrayList<>();
+        for (int keySize : RSA_SIZES) {
+            params.add(new Object[]{KeyUtils.RSA_ALGORITHM, keySize});
+        }
+
+        for (int keySize : DSS_SIZES) {
+            params.add(new Object[]{KeyUtils.DSS_ALGORITHM, keySize});
+        }
+
+        if (SecurityUtils.isECCSupported()) {
+            for (ECCurves curve : ECCurves.VALUES) {
+                params.add(new Object[]{KeyUtils.EC_ALGORITHM, curve.getKeySize()});
+            }
+        }
+
+        if (SecurityUtils.isEDDSACurveSupported()) {
+            for (int keySize : ED25519_SIZES) {
+                params.add(new Object[]{SecurityUtils.EDDSA, keySize});
+            }
+        }
+        return params;
+    }
+
+    @AfterClass
+    public static void dumpAllArts() throws Exception {
+        KeyRandomArt.combine(System.out, ' ', () -> KEYS);
+    }
+
+    @Test
+    public void testRandomArtString() throws Exception {
+        KeyRandomArt art = new KeyRandomArt(keyPair.getPublic());
+        assertEquals("Mismatched algorithm", algorithm, art.getAlgorithm());
+        assertEquals("Mismatched key size", keySize, art.getKeySize());
+
+        String s = art.toString();
+        String[] lines = GenericUtils.split(s, '\n');
+        assertEquals("Mismatched lines count", KeyRandomArt.FLDSIZE_Y + 2, lines.length);
+
+        for (int index = 0; index < lines.length; index++) {
+            String l = lines[index];
+            if ((l.length() > 0) && (l.charAt(l.length() - 1) == '\r')) {
+                l = l.substring(0, l.length() - 1);
+                lines[index] = l;
+            }
+            System.out.append('\t').println(l);
+
+            assertTrue("Mismatched line length #" + (index + 1) + ": " + l.length(), l.length() >= (KeyRandomArt.FLDSIZE_X + 2));
+        }
+    }
+}