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:57 UTC

[47/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/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
new file mode 100644
index 0000000..ae39dd3
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -0,0 +1,580 @@
+/*
+ * 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.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.math.BigInteger;
+import java.security.interfaces.ECKey;
+import java.security.spec.ECField;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.OptionalFeature;
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.digest.Digest;
+import org.apache.sshd.common.digest.DigestFactory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Utilities for working with elliptic curves.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum ECCurves implements NamedResource, OptionalFeature {
+    nistp256(Constants.NISTP256, new int[]{1, 2, 840, 10045, 3, 1, 7},
+            new ECParameterSpec(
+                    new EllipticCurve(
+                            new ECFieldFp(new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16)),
+                            new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16),
+                            new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)),
+                    new ECPoint(
+                            new BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16),
+                            new BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)),
+                    new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16),
+                    1),
+            32,
+            BuiltinDigests.sha256),
+    nistp384(Constants.NISTP384, new int[]{1, 3, 132, 0, 34},
+            new ECParameterSpec(
+                    new EllipticCurve(
+                            new ECFieldFp(new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", 16)),
+                            new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", 16),
+                            new BigInteger("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", 16)),
+                    new ECPoint(
+                            new BigInteger("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", 16),
+                            new BigInteger("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", 16)),
+                    new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", 16),
+                    1),
+            48,
+            BuiltinDigests.sha384),
+    nistp521(Constants.NISTP521, new int[]{1, 3, 132, 0, 35},
+            new ECParameterSpec(
+                    new EllipticCurve(
+                            new ECFieldFp(new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+                                                          + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)),
+                            new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+                                                + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", 16),
+                            new BigInteger("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951"
+                                            + "EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", 16)),
+                    new ECPoint(
+                            new BigInteger("00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77"
+                                            + "EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", 16),
+                            new BigInteger("011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE7299"
+                                            + "5EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650", 16)),
+                    new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B"
+                                    + "7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16),
+                    1),
+            66,
+            BuiltinDigests.sha512);
+
+    /**
+     * A {@link Set} of all the known curves
+     */
+    public static final Set<ECCurves> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(ECCurves.class));
+
+    /**
+     * A {@link Set} of all the known curves names
+     */
+    public static final NavigableSet<String> NAMES =
+            Collections.unmodifiableNavigableSet(GenericUtils.mapSort(
+                    VALUES,
+                    ECCurves::getName,
+                    String.CASE_INSENSITIVE_ORDER));
+
+    /**
+     * A {@link Set} of all the known curves key types
+     */
+    public static final NavigableSet<String> KEY_TYPES =
+            Collections.unmodifiableNavigableSet(GenericUtils.mapSort(
+                    VALUES,
+                    ECCurves::getKeyType,
+                    String.CASE_INSENSITIVE_ORDER));
+
+    public static final Comparator<ECCurves> BY_KEY_SIZE = (o1, o2) -> {
+        int k1 = (o1 == null) ? Integer.MAX_VALUE : o1.getKeySize();
+        int k2 = (o2 == null) ? Integer.MAX_VALUE : o2.getKeySize();
+        return Integer.compare(k1, k2);
+    };
+
+    public static final List<ECCurves> SORTED_KEY_SIZE =
+            Collections.unmodifiableList(VALUES.stream()
+                    .sorted(BY_KEY_SIZE)
+                    .collect(Collectors.toList()));
+
+    private final String name;
+    private final String keyType;
+    private final String oidString;
+    private final List<Integer> oidValue;
+    private final ECParameterSpec params;
+    private final int keySize;
+    private final int numOctets;
+    private final DigestFactory digestFactory;
+
+    ECCurves(String name, int[] oid, ECParameterSpec params, int numOctets, DigestFactory digestFactory) {
+        this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No curve name");
+        this.oidString = NumberUtils.join('.', ValidateUtils.checkNotNullAndNotEmpty(oid, "No OID"));
+        this.oidValue = Collections.unmodifiableList(NumberUtils.asList(oid));
+        this.keyType = Constants.ECDSA_SHA2_PREFIX + name;
+        this.params = ValidateUtils.checkNotNull(params, "No EC params for %s", name);
+        this.keySize = getCurveSize(params);
+        this.numOctets = numOctets;
+        this.digestFactory = Objects.requireNonNull(digestFactory, "No digestFactory");
+    }
+
+    @Override   // The curve name
+    public final String getName() {
+        return name;
+    }
+
+    public final String getOID() {
+        return oidString;
+    }
+
+    public final List<Integer> getOIDValue() {
+        return oidValue;
+    }
+
+    /**
+     * @return The standard key type used to represent this curve
+     */
+    public final String getKeyType() {
+        return keyType;
+    }
+
+    @Override
+    public final boolean isSupported() {
+        return SecurityUtils.isECCSupported() && digestFactory.isSupported();
+    }
+
+    public final ECParameterSpec getParameters() {
+        return params;
+    }
+
+    /**
+     * @return The size (in bits) of the key
+     */
+    public final int getKeySize() {
+        return keySize;
+    }
+
+    /**
+     * @return The number of octets used to represent the point(s) for the curve
+     */
+    public final int getNumPointOctets() {
+        return numOctets;
+    }
+
+    /**
+     * @return The {@link Digest} to use when hashing the curve's parameters
+     */
+    public final Digest getDigestForParams() {
+        return digestFactory.create();
+    }
+
+    /**
+     * @param type The key type value - ignored if {@code null}/empty
+     * @return The matching {@link ECCurves} constant - {@code null} if
+     * no match found case <U>insensitive</U>
+     */
+    public static ECCurves fromKeyType(String type) {
+        if (GenericUtils.isEmpty(type)) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            if (type.equalsIgnoreCase(c.getKeyType())) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param name The curve name (case <U>insensitive</U> - ignored if
+     *             {@code null}/empty
+     * @return The matching {@link ECCurves} instance - {@code null} if no
+     * match found
+     */
+    public static ECCurves fromCurveName(String name) {
+        return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    /**
+     * @param key The {@link ECKey} - ignored if {@code null}
+     * @return The matching {@link ECCurves} instance - {@code null} if no
+     * match found
+     */
+    public static ECCurves fromECKey(ECKey key) {
+        return fromCurveParameters((key == null) ? null : key.getParams());
+    }
+
+    /**
+     * @param params The curve's {@link ECParameterSpec} - ignored if {@code null}
+     * @return The matching {@link ECCurves} value - {@code null} if no match found
+     * @see #getCurveSize(ECParameterSpec)
+     * @see #fromCurveSize(int)
+     */
+    public static ECCurves fromCurveParameters(ECParameterSpec params) {
+        if (params == null) {
+            return null;
+        } else {
+            return fromCurveSize(getCurveSize(params));
+        }
+    }
+
+    /**
+     * @param keySize The key size (in bits)
+     * @return The matching {@link ECCurves} value - {@code null} if no
+     * match found
+     */
+    public static ECCurves fromCurveSize(int keySize) {
+        if (keySize <= 0) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            if (keySize == c.getKeySize()) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    public static ECCurves fromOIDValue(List<? extends Number> oid) {
+        if (GenericUtils.isEmpty(oid)) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            List<? extends Number> v = c.getOIDValue();
+            if (oid.size() != v.size()) {
+                continue;
+            }
+
+            boolean matches = true;
+            for (int index = 0; index < v.size(); index++) {
+                Number exp = v.get(index);
+                Number act = oid.get(index);
+                if (exp.intValue() != act.intValue()) {
+                    matches = false;
+                    break;
+                }
+            }
+
+            if (matches) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    public static ECCurves fromOID(String oid) {
+        if (GenericUtils.isEmpty(oid)) {
+            return null;
+        }
+
+        for (ECCurves c : VALUES) {
+            if (oid.equalsIgnoreCase(c.getOID())) {
+                return c;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param params The curve's {@link ECParameterSpec}
+     * @return The curve's key size in bits
+     * @throws IllegalArgumentException if invalid parameters provided
+     */
+    public static int getCurveSize(ECParameterSpec params) {
+        EllipticCurve curve = Objects.requireNonNull(params, "No EC params").getCurve();
+        ECField field = Objects.requireNonNull(curve, "No EC curve").getField();
+        return Objects.requireNonNull(field, "No EC field").getFieldSize();
+    }
+
+    public static byte[] encodeECPoint(ECPoint group, ECParameterSpec params) {
+        return encodeECPoint(group, params.getCurve());
+    }
+
+    public static byte[] encodeECPoint(ECPoint group, EllipticCurve curve) {
+        // M has len 2 ceil(log_2(q)/8) + 1 ?
+        int elementSize = (curve.getField().getFieldSize() + 7) / 8;
+        byte[] m = new byte[2 * elementSize + 1];
+
+        // Uncompressed format
+        m[0] = 0x04;
+
+        byte[] affineX = removeLeadingZeroes(group.getAffineX().toByteArray());
+        System.arraycopy(affineX, 0, m, 1 + elementSize - affineX.length, affineX.length);
+
+        byte[] affineY = removeLeadingZeroes(group.getAffineY().toByteArray());
+        System.arraycopy(affineY, 0, m, 1 + elementSize + elementSize - affineY.length, affineY.length);
+
+        return m;
+    }
+
+    private static byte[] removeLeadingZeroes(byte[] input) {
+        if (input[0] != 0x00) {
+            return input;
+        }
+
+        int pos = 1;
+        while (pos < input.length - 1 && input[pos] == 0x00) {
+            pos++;
+        }
+
+        byte[] output = new byte[input.length - pos];
+        System.arraycopy(input, pos, output, 0, output.length);
+        return output;
+    }
+
+    /**
+     * Converts the given octet string (defined by ASN.1 specifications) to a {@link BigInteger}
+     * As octet strings always represent positive integers, a zero-byte is prepended to
+     * the given array if necessary (if is MSB equal to 1), then this is converted to BigInteger
+     * The conversion is defined in the Section 2.3.8
+     *
+     * @param octets - octet string bytes to be converted
+     * @return The {@link BigInteger} representation of the octet string
+     */
+    public static BigInteger octetStringToInteger(byte... octets) {
+        if (octets == null) {
+            return null;
+        } else if (octets.length == 0) {
+            return BigInteger.ZERO;
+        } else {
+            return new BigInteger(1, octets);
+        }
+    }
+
+    public static ECPoint octetStringToEcPoint(byte... octets) {
+        if (NumberUtils.isEmpty(octets)) {
+            return null;
+        }
+
+        int startIndex = findFirstNonZeroIndex(octets);
+        if (startIndex < 0) {
+            throw new IllegalArgumentException("All zeroes ECPoint N/A");
+        }
+
+        byte indicator = octets[startIndex];
+        ECCurves.ECPointCompression compression = ECCurves.ECPointCompression.fromIndicatorValue(indicator);
+        if (compression == null) {
+            throw new UnsupportedOperationException("Unknown compression indicator value: 0x" + Integer.toHexString(indicator & 0xFF));
+        }
+
+        // The coordinates actually start after the compression indicator
+        return compression.octetStringToEcPoint(octets, startIndex + 1, octets.length - startIndex - 1);
+    }
+
+    private static int findFirstNonZeroIndex(byte... octets) {
+        if (NumberUtils.isEmpty(octets)) {
+            return -1;
+        }
+
+        for (int index = 0; index < octets.length; index++) {
+            if (octets[index] != 0) {
+                return index;
+            }
+        }
+
+        return -1;    // all zeroes
+    }
+
+    public static final class Constants {
+        /**
+         * Standard prefix of NISTP key types when encoded
+         */
+        public static final String ECDSA_SHA2_PREFIX = "ecdsa-sha2-";
+
+        public static final String NISTP256 = "nistp256";
+        public static final String NISTP384 = "nistp384";
+        public static final String NISTP521 = "nistp521";
+
+        private Constants() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+    }
+
+    /**
+     * The various {@link ECPoint} representation compression indicators
+     *
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     * @see <A HREF="https://www.ietf.org/rfc/rfc5480.txt">RFC-5480 - section 2.2</A>
+     */
+    public enum ECPointCompression {
+        // see http://tools.ietf.org/html/draft-jivsov-ecc-compact-00
+        // see http://crypto.stackexchange.com/questions/8914/ecdsa-compressed-public-key-point-back-to-uncompressed-public-key-point
+        VARIANT2((byte) 0x02) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
+                byte[] xp = new byte[len];
+                System.arraycopy(octets, startIndex, xp, 0, len);
+                BigInteger x = octetStringToInteger(xp);
+
+                // TODO derive even Y...
+                throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A");
+            }
+        },
+        VARIANT3((byte) 0x03) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
+                byte[] xp = new byte[len];
+                System.arraycopy(octets, startIndex, xp, 0, len);
+                BigInteger x = octetStringToInteger(xp);
+
+                // TODO derive odd Y...
+                throw new UnsupportedOperationException("octetStringToEcPoint(" + name() + ")(X=" + x + ") compression support N/A");
+            }
+        },
+        UNCOMPRESSED((byte) 0x04) {
+            @Override
+            public ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len) {
+                int numElements = len / 2;    /* x, y */
+                if (len != (numElements * 2)) {    // make sure length is not odd
+                    throw new IllegalArgumentException("octetStringToEcPoint(" + name() + ") "
+                            + " invalid remainder octets representation: "
+                            + " expected=" + (2 * numElements) + ", actual=" + len);
+                }
+
+                byte[] xp = new byte[numElements];
+                byte[] yp = new byte[numElements];
+                System.arraycopy(octets, startIndex, xp, 0, numElements);
+                System.arraycopy(octets, startIndex + numElements, yp, 0, numElements);
+
+                BigInteger x = octetStringToInteger(xp);
+                BigInteger y = octetStringToInteger(yp);
+                return new ECPoint(x, y);
+            }
+
+            @Override
+            public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
+                ECCurves curve = fromCurveName(curveName);
+                if (curve == null) {
+                    throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + curveName + "] cannot determine octets count");
+                }
+
+                int numElements = curve.getNumPointOctets();
+                KeyEntryResolver.encodeInt(s, 1 /* the indicator */ + 2 * numElements);
+                s.write(getIndicatorValue());
+                writeCoordinate(s, "X", p.getAffineX(), numElements);
+                writeCoordinate(s, "Y", p.getAffineY(), numElements);
+            }
+        };
+
+        public static final Set<ECPointCompression> VALUES =
+                Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
+
+        private final byte indicatorValue;
+
+        ECPointCompression(byte indicator) {
+            indicatorValue = indicator;
+        }
+
+        public final byte getIndicatorValue() {
+            return indicatorValue;
+        }
+
+        public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len);
+
+        public byte[] ecPointToOctetString(String curveName, ECPoint p) {
+            try (ByteArrayOutputStream baos = new ByteArrayOutputStream((2 * 66) + Long.SIZE)) {
+                writeECPoint(baos, curveName, p);
+                return baos.toByteArray();
+            } catch (IOException e) {
+                throw new RuntimeException("ecPointToOctetString(" + curveName + ")"
+                        + " failed (" + e.getClass().getSimpleName() + ")"
+                        + " to write data: " + e.getMessage(),
+                        e);
+            }
+        }
+
+        public void writeECPoint(OutputStream s, String curveName, ECPoint p) throws IOException {
+            if (s == null) {
+                throw new EOFException("No output stream");
+            }
+
+            throw new StreamCorruptedException("writeECPoint(" + name() + ")[" + p + "] N/A");
+        }
+
+        protected void writeCoordinate(OutputStream s, String n, BigInteger v, int numElements) throws IOException {
+            byte[] vp = v.toByteArray();
+            int startIndex = 0;
+            int vLen = vp.length;
+            if (vLen > numElements) {
+                if (vp[0] == 0) {   // skip artificial positive sign
+                    startIndex++;
+                    vLen--;
+                }
+            }
+
+            if (vLen > numElements) {
+                throw new StreamCorruptedException("writeCoordinate(" + name() + ")[" + n + "]"
+                        + " value length (" + vLen + ") exceeds max. (" + numElements + ")"
+                        + " for " + v);
+            }
+
+            if (vLen < numElements) {
+                byte[] tmp = new byte[numElements];
+                System.arraycopy(vp, startIndex, tmp, numElements - vLen, vLen);
+                vp = tmp;
+            }
+
+            s.write(vp, startIndex, vLen);
+        }
+
+        public static ECPointCompression fromIndicatorValue(int value) {
+            if ((value < 0) || (value > 0xFF)) {
+                return null;    // must be a byte value
+            }
+
+            for (ECPointCompression c : VALUES) {
+                if (value == c.getIndicatorValue()) {
+                    return c;
+                }
+            }
+
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html b/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html
new file mode 100644
index 0000000..197a89d
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/cipher/package.html
@@ -0,0 +1,26 @@
+<!--
+    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.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<a href="{@docRoot}/org/apache/sshd/common/cipher/Cipher.html"><code>Cipher</code></a>
+implementations.
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java
new file mode 100644
index 0000000..ff31947
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/BaseCompression.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class BaseCompression implements Compression {
+    private final String name;
+
+    protected BaseCompression(String name) {
+        this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No compression name");
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isCompressionExecuted() {
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java
new file mode 100644
index 0000000..49ad0ab
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/BuiltinCompressions.java
@@ -0,0 +1,239 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.NamedFactoriesListParseResult;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinCompressions implements CompressionFactory {
+    none(Constants.NONE) {
+        @Override
+        public Compression create() {
+            return new CompressionNone();
+        }
+
+        @Override
+        public boolean isCompressionExecuted() {
+            return false;
+        }
+    },
+    zlib(Constants.ZLIB) {
+        @Override
+        public Compression create() {
+            return new CompressionZlib();
+        }
+    },
+    delayedZlib(Constants.DELAYED_ZLIB) {
+        @Override
+        public Compression create() {
+            return new CompressionDelayedZlib();
+        }
+
+        @Override
+        public boolean isDelayed() {
+            return true;
+        }
+    };
+
+    public static final Set<BuiltinCompressions> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(BuiltinCompressions.class));
+
+    private static final Map<String, CompressionFactory> EXTENSIONS =
+            new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+    private final String name;
+
+    BuiltinCompressions(String n) {
+        name = n;
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean isDelayed() {
+        return false;
+    }
+
+    @Override
+    public boolean isCompressionExecuted() {
+        return true;
+    }
+
+    @Override
+    public final String toString() {
+        return getName();
+    }
+
+    @Override
+    public final boolean isSupported() {
+        return true;
+    }
+
+    /**
+     * Registered a {@link org.apache.sshd.common.NamedFactory} to be available besides the built-in
+     * ones when parsing configuration
+     *
+     * @param extension The factory to register
+     * @throws IllegalArgumentException if factory instance is {@code null},
+     * or overrides a built-in one or overrides another registered factory
+     * with the same name (case <U>insensitive</U>).
+     */
+    public static void registerExtension(CompressionFactory extension) {
+        String name = Objects.requireNonNull(extension, "No extension provided").getName();
+        ValidateUtils.checkTrue(fromFactoryName(name) == null, "Extension overrides built-in: %s", name);
+
+        synchronized (EXTENSIONS) {
+            ValidateUtils.checkTrue(!EXTENSIONS.containsKey(name), "Extension overrides existing: %s", name);
+            EXTENSIONS.put(name, extension);
+        }
+    }
+
+    /**
+     * @return A {@link SortedSet} of the currently registered extensions, sorted
+     * according to the factory name (case <U>insensitive</U>)
+     */
+    public static NavigableSet<CompressionFactory> getRegisteredExtensions() {
+        synchronized (EXTENSIONS) {
+            return GenericUtils.asSortedSet(NamedResource.BY_NAME_COMPARATOR, EXTENSIONS.values());
+        }
+    }
+
+    /**
+     * Unregisters specified extension
+     *
+     * @param name The factory name - ignored if {@code null}/empty
+     * @return The registered extension - {@code null} if not found
+     */
+    public static CompressionFactory unregisterExtension(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return null;
+        }
+
+        synchronized (EXTENSIONS) {
+            return EXTENSIONS.remove(name);
+        }
+    }
+
+    public static BuiltinCompressions fromFactoryName(String name) {
+        return NamedResource.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    /**
+     * @param compressions A comma-separated list of Compressions' names - ignored
+     *                     if {@code null}/empty
+     * @return A {@link ParseResult} containing the successfully parsed
+     * factories and the unknown ones. <B>Note:</B> it is up to caller to
+     * ensure that the lists do not contain duplicates
+     */
+    public static ParseResult parseCompressionsList(String compressions) {
+        return parseCompressionsList(GenericUtils.split(compressions, ','));
+    }
+
+    public static ParseResult parseCompressionsList(String... compressions) {
+        return parseCompressionsList(GenericUtils.isEmpty((Object[]) compressions) ? Collections.emptyList() : Arrays.asList(compressions));
+    }
+
+    public static ParseResult parseCompressionsList(Collection<String> compressions) {
+        if (GenericUtils.isEmpty(compressions)) {
+            return ParseResult.EMPTY;
+        }
+
+        List<CompressionFactory> factories = new ArrayList<>(compressions.size());
+        List<String> unknown = Collections.emptyList();
+        for (String name : compressions) {
+            CompressionFactory c = resolveFactory(name);
+            if (c != null) {
+                factories.add(c);
+            } else {
+                // replace the (unmodifiable) empty list with a real one
+                if (unknown.isEmpty()) {
+                    unknown = new ArrayList<>();
+                }
+                unknown.add(name);
+            }
+        }
+
+        return new ParseResult(factories, unknown);
+    }
+
+    /**
+     * @param name The factory name
+     * @return The factory or {@code null} if it is neither a built-in one
+     * or a registered extension
+     */
+    public static CompressionFactory resolveFactory(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return null;
+        }
+
+        CompressionFactory c = fromFactoryName(name);
+        if (c != null) {
+            return c;
+        }
+
+        synchronized (EXTENSIONS) {
+            return EXTENSIONS.get(name);
+        }
+    }
+
+    /**
+     * Holds the result of {@link BuiltinCompressions#parseCompressionsList(String)}
+     *
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    public static class ParseResult extends NamedFactoriesListParseResult<Compression, CompressionFactory> {
+        public static final ParseResult EMPTY = new ParseResult(Collections.emptyList(), Collections.emptyList());
+
+        public ParseResult(List<CompressionFactory> parsed, List<String> unsupported) {
+            super(parsed, unsupported);
+        }
+    }
+
+    public static final class Constants {
+        public static final String NONE = "none";
+        public static final String ZLIB = "zlib";
+        public static final String DELAYED_ZLIB = "zlib@openssh.com";
+
+        private Constants() {
+            throw new UnsupportedOperationException("No instance allowed");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java
new file mode 100644
index 0000000..91ac27e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/Compression.java
@@ -0,0 +1,71 @@
+/*
+ * 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.io.IOException;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * Interface used to compress the stream of data between the
+ * SSH server and clients.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface Compression extends CompressionInformation {
+
+    /**
+     * Enum identifying if this object will be used to compress
+     * or uncompress data.
+     */
+    enum Type {
+        Inflater,
+        Deflater
+    }
+
+    /**
+     * Initialize this object to either compress or uncompress data.
+     * This method must be called prior to any calls to either
+     * <code>compress</code> or <code>uncompress</code>.
+     * Once the object has been initialized, only one of
+     * <code>compress</code> or <code>uncompress</code> methods can be
+     * called.
+     *
+     * @param type  compression type
+     * @param level compression level
+     */
+    void init(Type type, int level);
+
+    /**
+     * Compress the given buffer in place.
+     *
+     * @param buffer the buffer containing the data to compress
+     * @throws IOException if an error occurs
+     */
+    void compress(Buffer buffer) throws IOException;
+
+    /**
+     * Uncompress the data in a buffer into another buffer.
+     *
+     * @param from the buffer containing the data to uncompress
+     * @param to   the buffer receiving the uncompressed data
+     * @throws IOException if an error occurs
+     */
+    void uncompress(Buffer from, Buffer to) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java
new file mode 100644
index 0000000..c062e38
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionDelayedZlib.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+
+/**
+ * ZLib delayed compression.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see Compression#isDelayed()
+ */
+public class CompressionDelayedZlib extends CompressionZlib {
+    /**
+     * Create a new instance of a delayed ZLib compression
+     */
+    public CompressionDelayedZlib() {
+        super(BuiltinCompressions.Constants.DELAYED_ZLIB);
+    }
+
+    @Override
+    public boolean isDelayed() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java
new file mode 100644
index 0000000..355594a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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 org.apache.sshd.common.BuiltinFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+// CHECKSTYLE:OFF
+public interface CompressionFactory extends BuiltinFactory<Compression>, CompressionInformation {
+    // nothing extra
+}
+//CHECKSTYLE:ON

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java
new file mode 100644
index 0000000..428689f
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionInformation.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.apache.sshd.common.NamedResource;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface CompressionInformation extends NamedResource {
+    /**
+     * Delayed compression is an Open-SSH specific feature which
+     * informs both the client and server to not compress data before
+     * the session has been authenticated.
+     *
+     * @return if the compression is delayed after authentication or not
+     */
+    boolean isDelayed();
+
+    /**
+     * @return {@code true} if there is any compression executed by
+     * this &quot;compressor&quot; - special case for 'none'
+     */
+    boolean isCompressionExecuted();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java
new file mode 100644
index 0000000..815e8ce
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionNone.java
@@ -0,0 +1,76 @@
+/*
+ * 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.io.IOException;
+import java.io.StreamCorruptedException;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CompressionNone extends BaseCompression {
+    private Type type;
+    private int level;
+
+    public CompressionNone() {
+        super(BuiltinCompressions.Constants.NONE);
+    }
+
+    @Override
+    public void init(Type type, int level) {
+        this.type = type;
+        this.level = level;
+    }
+
+    @Override
+    public boolean isCompressionExecuted() {
+        return false;
+    }
+
+    @Override
+    public void compress(Buffer buffer) throws IOException {
+        if (!Type.Deflater.equals(type)) {
+            throw new StreamCorruptedException("Not set up for compression: " + type);
+        }
+    }
+
+    @Override
+    public void uncompress(Buffer from, Buffer to) throws IOException {
+        if (!Type.Inflater.equals(type)) {
+            throw new StreamCorruptedException("Not set up for de-compression: " + type);
+        }
+
+        if (from != to) {
+            throw new StreamCorruptedException("Separate de-compression buffers provided");
+        }
+    }
+
+    @Override
+    public boolean isDelayed() {
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[" + type + "/" + level + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
new file mode 100644
index 0000000..e365b91
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/CompressionZlib.java
@@ -0,0 +1,85 @@
+/*
+ * 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.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * ZLib based Compression.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CompressionZlib extends BaseCompression {
+
+    private static final int BUF_SIZE = 4096;
+
+    private byte[] tmpbuf = new byte[BUF_SIZE];
+    private Deflater compresser;
+    private Inflater decompresser;
+
+    /**
+     * Create a new instance of a ZLib base compression
+     */
+    public CompressionZlib() {
+        this(BuiltinCompressions.Constants.ZLIB);
+    }
+
+    protected CompressionZlib(String name) {
+        super(name);
+    }
+
+    @Override
+    public boolean isDelayed() {
+        return false;
+    }
+
+    @Override
+    public void init(Type type, int level) {
+        compresser = new Deflater(level);
+        decompresser = new Inflater();
+    }
+
+    @Override
+    public void compress(Buffer buffer) throws IOException {
+        compresser.setInput(buffer.array(), buffer.rpos(), buffer.available());
+        buffer.wpos(buffer.rpos());
+        for (int len = compresser.deflate(tmpbuf, 0, tmpbuf.length, Deflater.SYNC_FLUSH);
+                len > 0;
+                len = compresser.deflate(tmpbuf, 0, tmpbuf.length, Deflater.SYNC_FLUSH)) {
+            buffer.putRawBytes(tmpbuf, 0, len);
+        }
+    }
+
+    @Override
+    public void uncompress(Buffer from, Buffer to) throws IOException {
+        decompresser.setInput(from.array(), from.rpos(), from.available());
+        try {
+            for (int len = decompresser.inflate(tmpbuf); len > 0; len = decompresser.inflate(tmpbuf)) {
+                to.putRawBytes(tmpbuf, 0, len);
+            }
+        } catch (DataFormatException e) {
+            throw new IOException("Error decompressing data", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html b/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html
new file mode 100644
index 0000000..9bd4652
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/compression/package.html
@@ -0,0 +1,25 @@
+<!--
+    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.
+-->
+<html>
+<head>
+</head>
+<body>
+
+<a href="{@docRoot}/org/apache/sshd/common/compression/Compression.html"><code>Compression</code></a> implementations.
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java
new file mode 100644
index 0000000..e153adc
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/CompressionConfigValue.java
@@ -0,0 +1,94 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.compression.BuiltinCompressions;
+import org.apache.sshd.common.compression.Compression;
+import org.apache.sshd.common.compression.CompressionFactory;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Provides a &quot;bridge&quot; between the configuration values and the
+ * actual {@link org.apache.sshd.common.NamedFactory} for the {@link Compression}.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum CompressionConfigValue implements CompressionFactory {
+    YES(BuiltinCompressions.zlib),
+    NO(BuiltinCompressions.none),
+    DELAYED(BuiltinCompressions.delayedZlib);
+
+    public static final Set<CompressionConfigValue> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(CompressionConfigValue.class));
+
+    private final CompressionFactory factory;
+
+    CompressionConfigValue(CompressionFactory delegate) {
+        factory = delegate;
+    }
+
+    @Override
+    public final String getName() {
+        return factory.getName();
+    }
+
+    @Override
+    public final Compression create() {
+        return factory.create();
+    }
+
+    @Override
+    public boolean isSupported() {
+        return factory.isSupported();
+    }
+
+    @Override
+    public final String toString() {
+        return getName();
+    }
+
+    @Override
+    public boolean isDelayed() {
+        return factory.isDelayed();
+    }
+
+    @Override
+    public boolean isCompressionExecuted() {
+        return factory.isCompressionExecuted();
+    }
+
+    public static CompressionConfigValue fromName(String n) {
+        if (GenericUtils.isEmpty(n)) {
+            return null;
+        }
+
+        for (CompressionConfigValue v : VALUES) {
+            if (n.equalsIgnoreCase(v.name())) {
+                return v;
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
new file mode 100644
index 0000000..10b131c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ConfigFileReaderSupport.java
@@ -0,0 +1,230 @@
+/*
+ * 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.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StreamCorruptedException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.io.NoCloseReader;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <a href="https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5">ssh_config(5)</a>
+ */
+public final class ConfigFileReaderSupport {
+
+    public static final char COMMENT_CHAR = '#';
+
+    public static final String COMPRESSION_PROP = "Compression";
+    public static final String DEFAULT_COMPRESSION = CompressionConfigValue.NO.getName();
+    public static final String MAX_SESSIONS_CONFIG_PROP = "MaxSessions";
+    public static final int DEFAULT_MAX_SESSIONS = 10;
+    public static final String PASSWORD_AUTH_CONFIG_PROP = "PasswordAuthentication";
+    public static final String DEFAULT_PASSWORD_AUTH = "no";
+    public static final boolean DEFAULT_PASSWORD_AUTH_VALUE = parseBooleanValue(DEFAULT_PASSWORD_AUTH);
+    public static final String LISTEN_ADDRESS_CONFIG_PROP = "ListenAddress";
+    public static final String DEFAULT_BIND_ADDRESS = SshdSocketAddress.IPV4_ANYADDR;
+    public static final String PORT_CONFIG_PROP = "Port";
+    public static final int DEFAULT_PORT = 22;
+    public static final String KEEP_ALIVE_CONFIG_PROP = "TCPKeepAlive";
+    public static final boolean DEFAULT_KEEP_ALIVE = true;
+    public static final String USE_DNS_CONFIG_PROP = "UseDNS";
+    // NOTE: the usual default is TRUE
+    public static final boolean DEFAULT_USE_DNS = true;
+    public static final String PUBKEY_AUTH_CONFIG_PROP = "PubkeyAuthentication";
+    public static final String DEFAULT_PUBKEY_AUTH = "yes";
+    public static final boolean DEFAULT_PUBKEY_AUTH_VALUE = parseBooleanValue(DEFAULT_PUBKEY_AUTH);
+    public static final String AUTH_KEYS_FILE_CONFIG_PROP = "AuthorizedKeysFile";
+    public static final String MAX_AUTH_TRIES_CONFIG_PROP = "MaxAuthTries";
+    public static final int DEFAULT_MAX_AUTH_TRIES = 6;
+    public static final String MAX_STARTUPS_CONFIG_PROP = "MaxStartups";
+    public static final int DEFAULT_MAX_STARTUPS = 10;
+    public static final String LOGIN_GRACE_TIME_CONFIG_PROP = "LoginGraceTime";
+    public static final long DEFAULT_LOGIN_GRACE_TIME = TimeUnit.SECONDS.toMillis(120);
+    public static final String KEY_REGENERATE_INTERVAL_CONFIG_PROP = "KeyRegenerationInterval";
+    public static final long DEFAULT_REKEY_TIME_LIMIT = TimeUnit.HOURS.toMillis(1L);
+    // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
+    public static final String CIPHERS_CONFIG_PROP = "Ciphers";
+    public static final String DEFAULT_CIPHERS =
+            "aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour";
+    // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
+    public static final String MACS_CONFIG_PROP = "MACs";
+    public static final String DEFAULT_MACS =
+            "hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-ripemd160,hmac-sha1-96,hmac-md5-96,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96";
+    // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
+    public static final String KEX_ALGORITHMS_CONFIG_PROP = "KexAlgorithms";
+    public static final String DEFAULT_KEX_ALGORITHMS =
+            "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"
+                    + "," + "diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1"
+                    // RFC-8268 groups
+                    + "," + "diffie-hellman-group18-sha512,diffie-hellman-group17-sha512"
+                    + "," + "diffie-hellman-group16-sha512,diffie-hellman-group15-sha512"
+                    + "," + "diffie-hellman-group14-sha256"
+                    // Legacy groups
+                    + "," + "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1";
+    // see http://linux.die.net/man/5/ssh_config
+    public static final String HOST_KEY_ALGORITHMS_CONFIG_PROP = "HostKeyAlgorithms";
+    // see https://tools.ietf.org/html/rfc5656
+    public static final String DEFAULT_HOST_KEY_ALGORITHMS =
+            KeyPairProvider.SSH_RSA + "," + KeyPairProvider.SSH_DSS;
+    // see http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html
+    public static final String LOG_LEVEL_CONFIG_PROP = "LogLevel";
+    public static final LogLevelValue DEFAULT_LOG_LEVEL = LogLevelValue.INFO;
+    // see https://www.freebsd.org/cgi/man.cgi?query=sshd_config&sektion=5
+    public static final String SYSLOG_FACILITY_CONFIG_PROP = "SyslogFacility";
+    public static final SyslogFacilityValue DEFAULT_SYSLOG_FACILITY = SyslogFacilityValue.AUTH;
+    public static final String SUBSYSTEM_CONFIG_PROP = "Subsystem";
+
+    private ConfigFileReaderSupport() {
+        throw new UnsupportedOperationException("No instance");
+    }
+
+    public static Properties readConfigFile(File file) throws IOException {
+        return readConfigFile(file.toPath(), IoUtils.EMPTY_OPEN_OPTIONS);
+    }
+
+    public static Properties readConfigFile(Path path, OpenOption... options) throws IOException {
+        try (InputStream input = Files.newInputStream(path, options)) {
+            return readConfigFile(input, true);
+        }
+    }
+
+    public static Properties readConfigFile(URL url) throws IOException {
+        try (InputStream input = url.openStream()) {
+            return readConfigFile(input, true);
+        }
+    }
+
+    public static Properties readConfigFile(String path) throws IOException {
+        try (InputStream input = new FileInputStream(path)) {
+            return readConfigFile(input, true);
+        }
+    }
+
+    public static Properties readConfigFile(InputStream input, boolean okToClose) throws IOException {
+        try (Reader reader = new InputStreamReader(NoCloseInputStream.resolveInputStream(input, okToClose), StandardCharsets.UTF_8)) {
+            return readConfigFile(reader, true);
+        }
+    }
+
+    public static Properties readConfigFile(Reader reader, boolean okToClose) throws IOException {
+        try (BufferedReader buf = new BufferedReader(NoCloseReader.resolveReader(reader, okToClose))) {
+            return readConfigFile(buf);
+        }
+    }
+
+    /**
+     * Reads the configuration file contents into a {@link Properties} instance.
+     * <B>Note:</B> multiple keys value are concatenated using a comma - it is up to
+     * the caller to know which keys are expected to have multiple values and handle
+     * the split accordingly
+     *
+     * @param rdr The {@link BufferedReader} for reading the file
+     * @return The read properties
+     * @throws IOException If failed to read or malformed content
+     */
+    public static Properties readConfigFile(BufferedReader rdr) throws IOException {
+        Properties props = new Properties();
+        int lineNumber = 1;
+        for (String line = rdr.readLine(); line != null; line = rdr.readLine(), lineNumber++) {
+            line = GenericUtils.replaceWhitespaceAndTrim(line);
+            if (GenericUtils.isEmpty(line)) {
+                continue;
+            }
+
+            int pos = line.indexOf(COMMENT_CHAR);
+            if (pos == 0) {
+                continue;
+            }
+
+            if (pos > 0) {
+                line = line.substring(0, pos);
+                line = line.trim();
+            }
+
+            /*
+             * Some options use '=', others use ' ' - try both
+             * NOTE: we do not validate the format for each option separately
+             */
+            pos = line.indexOf(' ');
+            if (pos < 0) {
+                pos = line.indexOf('=');
+            }
+
+            if (pos < 0) {
+                throw new StreamCorruptedException("No delimiter at line " + lineNumber + ": " + line);
+            }
+
+            String key = line.substring(0, pos);
+            String value = line.substring(pos + 1).trim();
+            // see if need to concatenate multi-valued keys
+            String prev = props.getProperty(key);
+            if (!GenericUtils.isEmpty(prev)) {
+                value = prev + "," + value;
+            }
+
+            props.setProperty(key, value);
+        }
+
+        return props;
+    }
+
+    /**
+     * @param v Checks if the value is &quot;yes&quot;, &quot;y&quot;
+     *          or &quot;on&quot; or &quot;true&quot;.
+     * @return The result - <B>Note:</B> {@code null}/empty values are
+     * interpreted as {@code false}
+     */
+    public static boolean parseBooleanValue(String v) {
+        return "yes".equalsIgnoreCase(v)
+            || "y".equalsIgnoreCase(v)
+            || "on".equalsIgnoreCase(v)
+            || "true".equalsIgnoreCase(v);
+    }
+
+    /**
+     * Returns a &quot;yes&quot; or &quot;no&quot; value based on the input
+     * parameter
+     *
+     * @param flag The required state
+     * @return &quot;yes&quot; if {@code true}, &quot;no&quot; otherwise
+     */
+    public static String yesNoValueOf(boolean flag) {
+        return flag ? "yes" : "no";
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java
new file mode 100644
index 0000000..7a1cee1
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/FactoriesListParseResult.java
@@ -0,0 +1,51 @@
+/*
+ * 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.List;
+
+import org.apache.sshd.common.Factory;
+
+/**
+ * @param <T> Result type
+ * @param <F> Factory type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class FactoriesListParseResult<T, F extends Factory<T>> extends ListParseResult<F> {
+    protected FactoriesListParseResult(List<F> parsed, List<String> unsupported) {
+        super(parsed, unsupported);
+    }
+
+    /**
+     * @return The {@link List} of successfully parsed {@link Factory} instances
+     * in the <U>same order</U> as they were encountered during parsing
+     */
+    public final List<F> getParsedFactories() {
+        return getParsedValues();
+    }
+
+    /**
+     * @return A {@link List} of unknown/unsupported configuration values for
+     * the factories
+     */
+    public List<String> getUnsupportedFactories() {
+        return getUnsupportedValues();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java
new file mode 100644
index 0000000..97590cb
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/ListParseResult.java
@@ -0,0 +1,66 @@
+/*
+ * 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.List;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Used to hold the result of parsing a list of value. Such result contains known
+ * and unknown values - which are accessible via the respective {@link #getParsedValues()}
+ * and {@link #getUnsupportedValues()} methods. <B>Note:</B> the returned {@link List}s may
+ * be un-modifiable, so it is recommended to avoid attempting changing the, returned
+ * list(s)
+ *
+ * @param <E> Type of list item
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class ListParseResult<E> {
+    private final List<E> parsed;
+    private final List<String> unsupported;
+
+    protected ListParseResult(List<E> parsed, List<String> unsupported) {
+        this.parsed = parsed;
+        this.unsupported = unsupported;
+    }
+
+    /**
+     * @return The {@link List} of successfully parsed value instances
+     * in the <U>same order</U> as they were encountered during parsing
+     */
+    public final List<E> getParsedValues() {
+        return parsed;
+    }
+
+    /**
+     * @return A {@link List} of unknown/unsupported configuration values for
+     * the factories
+     */
+    public List<String> getUnsupportedValues() {
+        return unsupported;
+    }
+
+    @Override
+    public String toString() {
+        return "parsed=" + GenericUtils.join(getParsedValues(), ',')
+                + ";unsupported=" + GenericUtils.join(getUnsupportedValues(), ',');
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java
new file mode 100644
index 0000000..2fbdc80
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/LogLevelValue.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html"><I>LogLevel</I> configuration value</A>
+ */
+public enum LogLevelValue {
+    /*
+     * NOTE(s):
+     * 1. DEBUG and DEBUG1 are EQUIVALENT
+     * 2. Order is important (!!!)
+     */
+    QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3;
+
+    public static final Set<LogLevelValue> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(LogLevelValue.class));
+
+    public static LogLevelValue fromName(String n) {
+        if (GenericUtils.isEmpty(n)) {
+            return null;
+        }
+
+        for (LogLevelValue l : VALUES) {
+            if (n.equalsIgnoreCase(l.name())) {
+                return l;
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java
new file mode 100644
index 0000000..246cae0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedFactoriesListParseResult.java
@@ -0,0 +1,47 @@
+/*
+ * 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.List;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Holds the result of parsing a list of {@link NamedFactory}ies
+ *
+ * @param <T> Result type
+ * @param <F> Factory type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class NamedFactoriesListParseResult<T, F extends NamedFactory<T>>
+        extends FactoriesListParseResult<T, F> {
+
+    protected NamedFactoriesListParseResult(List<F> parsed, List<String> unsupported) {
+        super(parsed, unsupported);
+    }
+
+    @Override
+    public String toString() {
+        return "parsed=" + NamedResource.getNames(getParsedFactories())
+                + ";unknown=" + GenericUtils.join(getUnsupportedFactories(), ',');
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.java
new file mode 100644
index 0000000..feb45f4
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/NamedResourceListParseResult.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.List;
+
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @param <R> Type of result {@link NamedResource}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class NamedResourceListParseResult<R extends NamedResource> extends ListParseResult<R> {
+    protected NamedResourceListParseResult(List<R> parsed, List<String> unsupported) {
+        super(parsed, unsupported);
+    }
+
+    /**
+     * @return The {@link List} of successfully parsed {@link NamedResource} instances
+     * in the <U>same order</U> as they were encountered during parsing
+     */
+    public final List<R> getParsedResources() {
+        return getParsedValues();
+    }
+
+    /**
+     * @return A {@link List} of unknown/unsupported configuration values for
+     * the resources
+     */
+    public List<String> getUnsupportedResources() {
+        return getUnsupportedValues();
+    }
+
+    @Override
+    public String toString() {
+        return "parsed=" + NamedResource.getNames(getParsedResources())
+                + ";unknown=" + GenericUtils.join(getUnsupportedResources(), ',');
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java b/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java
new file mode 100644
index 0000000..f52ae36
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/SyslogFacilityValue.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://www.freebsd.org/cgi/man.cgi?query=sshd_config&sektion=5"><I>SyslogFacility</I> configuration value</A>
+ */
+public enum SyslogFacilityValue {
+    DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7;
+
+    public static final Set<SyslogFacilityValue> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(SyslogFacilityValue.class));
+
+    public static SyslogFacilityValue fromName(String n) {
+        if (GenericUtils.isEmpty(n)) {
+            return null;
+        }
+
+        for (SyslogFacilityValue f : VALUES) {
+            if (n.equalsIgnoreCase(f.name())) {
+                return f;
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
new file mode 100644
index 0000000..222cf84
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/TimeValueConfig.java
@@ -0,0 +1,180 @@
+/*
+ * 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.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://unixhelp.ed.ac.uk/CGI/man-cgi?sshd_config+5">Time formats for SSH configuration values</A>
+ */
+public enum TimeValueConfig {
+    SECONDS('s', 'S', TimeUnit.SECONDS.toMillis(1L)),
+    MINUTES('m', 'M', TimeUnit.MINUTES.toMillis(1L)),
+    HOURS('h', 'H', TimeUnit.HOURS.toMillis(1L)),
+    DAYS('d', 'D', TimeUnit.DAYS.toMillis(1L)),
+    WEEKS('w', 'W', TimeUnit.DAYS.toMillis(7L));
+
+    public static final Set<TimeValueConfig> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(TimeValueConfig.class));
+
+    private final char loChar;
+    private final char hiChar;
+    private final long interval;
+
+    TimeValueConfig(char lo, char hi, long interval) {
+        loChar = lo;
+        hiChar = hi;
+        this.interval = interval;
+    }
+
+    public final char getLowerCaseValue() {
+        return loChar;
+    }
+
+    public final char getUpperCaseValue() {
+        return hiChar;
+    }
+
+    public final long getInterval() {
+        return interval;
+    }
+
+    public static TimeValueConfig fromValueChar(char ch) {
+        if ((ch <= ' ') || (ch >= 0x7F)) {
+            return null;
+        }
+
+        for (TimeValueConfig v : VALUES) {
+            if ((v.getLowerCaseValue() == ch) || (v.getUpperCaseValue() == ch)) {
+                return v;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param s A time specification
+     * @return The specified duration in milliseconds
+     * @see #parse(String)
+     * @see #durationOf(Map)
+     */
+    public static long durationOf(String s) {
+        Map<TimeValueConfig, Long> spec = parse(s);
+        return durationOf(spec);
+    }
+
+    /**
+     * @param s An input time specification containing possibly mixed numbers
+     *          and units - e.g., {@code 3h10m} to indicate 3 hours and 10 minutes
+     * @return A {@link Map} specifying for each time unit its count
+     * @throws NumberFormatException    If bad numbers found - e.g., negative counts
+     * @throws IllegalArgumentException If bad format - e.g., unknown unit
+     */
+    public static Map<TimeValueConfig, Long> parse(String s) throws IllegalArgumentException {
+        if (GenericUtils.isEmpty(s)) {
+            return Collections.emptyMap();
+        }
+
+        int lastPos = 0;
+        Map<TimeValueConfig, Long> spec = new EnumMap<>(TimeValueConfig.class);
+        for (int curPos = 0; curPos < s.length(); curPos++) {
+            char ch = s.charAt(curPos);
+            if ((ch >= '0') && (ch <= '9')) {
+                continue;
+            }
+
+            if (curPos <= lastPos) {
+                throw new IllegalArgumentException("parse(" + s + ") missing count value at index=" + curPos);
+            }
+
+            TimeValueConfig c = fromValueChar(ch);
+            if (c == null) {
+                throw new IllegalArgumentException("parse(" + s + ") unknown time value character: '" + ch + "'");
+            }
+
+            String v = s.substring(lastPos, curPos);
+            long count = Long.parseLong(v);
+            if (count < 0L) {
+                throw new IllegalArgumentException("parse(" + s + ") negative count (" + v + ") for " + c.name());
+            }
+
+            Long prev = spec.put(c, count);
+            if (prev != null) {
+                throw new IllegalArgumentException("parse(" + s + ") " + c.name() + " value re-specified: current=" + count + ", previous=" + prev);
+            }
+
+            lastPos = curPos + 1;
+            if (lastPos >= s.length()) {
+                break;
+            }
+        }
+
+        if (lastPos < s.length()) {
+            String v = s.substring(lastPos);
+            long count = Long.parseLong(v);
+            if (count < 0L) {
+                throw new IllegalArgumentException("parse(" + s + ") negative count (" + v + ") for last component");
+            }
+
+            Long prev = spec.put(SECONDS, count);
+            if (prev != null) {
+                throw new IllegalArgumentException("parse(" + s + ") last component (" + SECONDS.name() + ") value re-specified: current=" + count + ", previous=" + prev);
+            }
+        }
+
+        return spec;
+    }
+
+    /**
+     * @param spec The {@link Map} specifying the count for each {@link TimeValueConfig}
+     * @return The total duration in milliseconds
+     * @throws IllegalArgumentException If negative count for a time unit
+     */
+    public static long durationOf(Map<TimeValueConfig, ? extends Number> spec) throws IllegalArgumentException {
+        if (GenericUtils.isEmpty(spec)) {
+            return -1L;
+        }
+
+        long total = 0L;
+        // Cannot use forEach because the total value is not effectively final
+        for (Map.Entry<TimeValueConfig, ? extends Number> se : spec.entrySet()) {
+            TimeValueConfig v = se.getKey();
+            Number c = se.getValue();
+            long factor = c.longValue();
+            if (factor < 0L) {
+                throw new IllegalArgumentException("valueOf(" + spec + ") bad factor (" + c + ") for " + v.name());
+            }
+
+            long added = v.getInterval() * factor;
+            total += added;
+        }
+
+        return total;
+    }
+}