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:55 UTC
[45/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/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
new file mode 100644
index 0000000..23937c2
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -0,0 +1,937 @@
+/*
+ * 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.io.IOException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder;
+import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder;
+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.digest.DigestUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * Utility class for keys
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class KeyUtils {
+ /**
+ * Name of algorithm for RSA keys to be used when calling security provider
+ */
+ public static final String RSA_ALGORITHM = "RSA";
+
+ /**
+ * The most commonly used RSA public key exponent
+ */
+ public static final BigInteger DEFAULT_RSA_PUBLIC_EXPONENT = new BigInteger("65537");
+
+ /**
+ * Name of algorithm for DSS keys to be used when calling security provider
+ */
+ public static final String DSS_ALGORITHM = "DSA";
+
+ /**
+ * Name of algorithm for EC keys to be used when calling security provider
+ */
+ public static final String EC_ALGORITHM = "EC";
+
+ /**
+ * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if strict
+ * permissions are enforced on key files
+ */
+ public static final Set<PosixFilePermission> STRICTLY_PROHIBITED_FILE_PERMISSION =
+ Collections.unmodifiableSet(
+ EnumSet.of(PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE,
+ PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE, PosixFilePermission.OTHERS_EXECUTE));
+
+ /**
+ * System property that can be used to control the default fingerprint factory used for keys.
+ * If not set the {@link #DEFAULT_FINGERPRINT_DIGEST_FACTORY} is used
+ */
+ public static final String KEY_FINGERPRINT_FACTORY_PROP = "org.apache.sshd.keyFingerprintFactory";
+
+ /**
+ * The default {@link Factory} of {@link Digest}s initialized
+ * as the value of {@link #getDefaultFingerPrintFactory()} if not
+ * overridden by {@link #KEY_FINGERPRINT_FACTORY_PROP} or
+ * {@link #setDefaultFingerPrintFactory(DigestFactory)}
+ */
+ public static final DigestFactory DEFAULT_FINGERPRINT_DIGEST_FACTORY = BuiltinDigests.sha256;
+
+ private static final AtomicReference<DigestFactory> DEFAULT_DIGEST_HOLDER = new AtomicReference<>();
+
+ private static final Map<String, PublicKeyEntryDecoder<?, ?>> BY_KEY_TYPE_DECODERS_MAP =
+ new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+ private static final Map<Class<?>, PublicKeyEntryDecoder<?, ?>> BY_KEY_CLASS_DECODERS_MAP =
+ new HashMap<>();
+
+ static {
+ registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
+ registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
+
+ if (SecurityUtils.isECCSupported()) {
+ registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
+ }
+ if (SecurityUtils.isEDDSACurveSupported()) {
+ registerPublicKeyEntryDecoder(SecurityUtils.getEDDSAPublicKeyEntryDecoder());
+ }
+ }
+
+ private KeyUtils() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ /**
+ * <P>Checks if a path has strict permissions</P>
+ * <UL>
+ * <LI><P>
+ * The path may not have {@link PosixFilePermission#OTHERS_EXECUTE}
+ * permission
+ * </P></LI>
+ *
+ * <LI><P>
+ * (For {@code Unix}) The path may not have group or others permissions
+ * </P></LI>
+ *
+ * <LI><P>
+ * (For {@code Unix}) If the path is a file, then its folder may not have
+ * group or others permissions
+ * </P></LI>
+ *
+ * <LI><P>
+ * The path must be owned by current user.
+ * </P></LI>
+ *
+ * <LI><P>
+ * (For {@code Unix}) The path may be owned by root.
+ * </P></LI>
+ *
+ * <LI><P>
+ * (For {@code Unix}) If the path is a file, then its folder must also
+ * have valid owner.
+ * </P></LI>
+ *
+ * </UL>
+ *
+ * @param path The {@link Path} to be checked - ignored if {@code null}
+ * or does not exist
+ * @param options The {@link LinkOption}s to use to query the file's permissions
+ * @return The violated permission as {@link SimpleImmutableEntry} where key is a message and
+ * value is the offending object {@link PosixFilePermission} or {@link String} for owner - {@code null}
+ * if no violations detected
+ * @throws IOException If failed to retrieve the permissions
+ * @see #STRICTLY_PROHIBITED_FILE_PERMISSION
+ */
+ public static SimpleImmutableEntry<String, Object> validateStrictKeyFilePermissions(Path path, LinkOption... options) throws IOException {
+ if ((path == null) || (!Files.exists(path, options))) {
+ return null;
+ }
+
+ Collection<PosixFilePermission> perms = IoUtils.getPermissions(path, options);
+ if (GenericUtils.isEmpty(perms)) {
+ return null;
+ }
+
+ if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) {
+ PosixFilePermission p = PosixFilePermission.OTHERS_EXECUTE;
+ return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
+ }
+
+ if (OsUtils.isUNIX()) {
+ PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
+ if (p != null) {
+ return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
+ }
+
+ if (Files.isRegularFile(path, options)) {
+ Path parent = path.getParent();
+ p = IoUtils.validateExcludedPermissions(IoUtils.getPermissions(parent, options), STRICTLY_PROHIBITED_FILE_PERMISSION);
+ if (p != null) {
+ return new SimpleImmutableEntry<>(String.format("Parent permissions violation (%s)", p), p);
+ }
+ }
+ }
+
+ String owner = IoUtils.getFileOwner(path, options);
+ if (GenericUtils.isEmpty(owner)) {
+ // we cannot get owner
+ // general issue: jvm does not support permissions
+ // security issue: specific filesystem does not support permissions
+ return null;
+ }
+
+ String current = OsUtils.getCurrentUser();
+ Set<String> expected = new HashSet<>();
+ expected.add(current);
+ if (OsUtils.isUNIX()) {
+ // Windows "Administrator" was considered however in Windows most likely a group is used.
+ expected.add(OsUtils.ROOT_USER);
+ }
+
+ if (!expected.contains(owner)) {
+ return new SimpleImmutableEntry<>(String.format("Owner violation (%s)", owner), owner);
+ }
+
+ if (OsUtils.isUNIX()) {
+ if (Files.isRegularFile(path, options)) {
+ String parentOwner = IoUtils.getFileOwner(path.getParent(), options);
+ if ((!GenericUtils.isEmpty(parentOwner)) && (!expected.contains(parentOwner))) {
+ return new SimpleImmutableEntry<>(String.format("Parent owner violation (%s)", parentOwner), parentOwner);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param keyType The key type - {@code OpenSSH} name - e.g., {@code ssh-rsa, ssh-dss}
+ * @param keySize The key size (in bits)
+ * @return A {@link KeyPair} of the specified type and size
+ * @throws GeneralSecurityException If failed to generate the key pair
+ * @see #getPublicKeyEntryDecoder(String)
+ * @see PublicKeyEntryDecoder#generateKeyPair(int)
+ */
+ public static KeyPair generateKeyPair(String keyType, int keySize) throws GeneralSecurityException {
+ PublicKeyEntryDecoder<?, ?> decoder = getPublicKeyEntryDecoder(keyType);
+ if (decoder == null) {
+ throw new InvalidKeySpecException("No decoder for key type=" + keyType);
+ }
+
+ return decoder.generateKeyPair(keySize);
+ }
+
+ /**
+ * Performs a deep-clone of the original {@link KeyPair} - i.e., creates
+ * <U>new</U> public/private keys that are clones of the original one
+ *
+ * @param keyType The key type - {@code OpenSSH} name - e.g., {@code ssh-rsa, ssh-dss}
+ * @param kp The {@link KeyPair} to clone - ignored if {@code null}
+ * @return The cloned instance
+ * @throws GeneralSecurityException If failed to clone the pair
+ */
+ public static KeyPair cloneKeyPair(String keyType, KeyPair kp) throws GeneralSecurityException {
+ PublicKeyEntryDecoder<?, ?> decoder = getPublicKeyEntryDecoder(keyType);
+ if (decoder == null) {
+ throw new InvalidKeySpecException("No decoder for key type=" + keyType);
+ }
+
+ return decoder.cloneKeyPair(kp);
+ }
+
+ /**
+ * @param decoder The decoder to register
+ * @throws IllegalArgumentException if no decoder or not key type or no
+ * supported names for the decoder
+ * @see PublicKeyEntryDecoder#getPublicKeyType()
+ * @see PublicKeyEntryDecoder#getSupportedTypeNames()
+ */
+ public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<?, ?> decoder) {
+ Objects.requireNonNull(decoder, "No decoder specified");
+
+ Class<?> pubType = Objects.requireNonNull(decoder.getPublicKeyType(), "No public key type declared");
+ Class<?> prvType = Objects.requireNonNull(decoder.getPrivateKeyType(), "No private key type declared");
+ synchronized (BY_KEY_CLASS_DECODERS_MAP) {
+ BY_KEY_CLASS_DECODERS_MAP.put(pubType, decoder);
+ BY_KEY_CLASS_DECODERS_MAP.put(prvType, decoder);
+ }
+
+ Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type");
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ for (String n : names) {
+ PublicKeyEntryDecoder<?, ?> prev = BY_KEY_TYPE_DECODERS_MAP.put(n, decoder);
+ if (prev != null) {
+ //noinspection UnnecessaryContinue
+ continue; // debug breakpoint
+ }
+ }
+ }
+ }
+
+ /**
+ * @param keyType The {@code OpenSSH} key type string - e.g., {@code ssh-rsa, ssh-dss}
+ * - ignored if {@code null}/empty
+ * @return The registered {@link PublicKeyEntryDecoder} or {code null} if not found
+ */
+ public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(String keyType) {
+ if (GenericUtils.isEmpty(keyType)) {
+ return null;
+ }
+
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ return BY_KEY_TYPE_DECODERS_MAP.get(keyType);
+ }
+ }
+
+ /**
+ * @param kp The {@link KeyPair} to examine - ignored if {@code null}
+ * @return The matching {@link PublicKeyEntryDecoder} provided <U>both</U>
+ * the public and private keys have the same decoder - {@code null} if no
+ * match found
+ * @see #getPublicKeyEntryDecoder(Key)
+ */
+ public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(KeyPair kp) {
+ if (kp == null) {
+ return null;
+ }
+
+ PublicKeyEntryDecoder<?, ?> d1 = getPublicKeyEntryDecoder(kp.getPublic());
+ PublicKeyEntryDecoder<?, ?> d2 = getPublicKeyEntryDecoder(kp.getPrivate());
+ if (d1 == d2) {
+ return d1;
+ } else {
+ return null; // some kind of mixed keys...
+ }
+ }
+
+ /**
+ * @param key The {@link Key} (public or private) - ignored if {@code null}
+ * @return The registered {@link PublicKeyEntryDecoder} for this key or {code null} if no match found
+ * @see #getPublicKeyEntryDecoder(Class)
+ */
+ public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(Key key) {
+ if (key == null) {
+ return null;
+ } else {
+ return getPublicKeyEntryDecoder(key.getClass());
+ }
+ }
+
+ /**
+ * @param keyType The key {@link Class} - ignored if {@code null} or not a {@link Key}
+ * compatible type
+ * @return The registered {@link PublicKeyEntryDecoder} or {code null} if no match found
+ */
+ public static PublicKeyEntryDecoder<?, ?> getPublicKeyEntryDecoder(Class<?> keyType) {
+ if ((keyType == null) || (!Key.class.isAssignableFrom(keyType))) {
+ return null;
+ }
+
+ synchronized (BY_KEY_TYPE_DECODERS_MAP) {
+ PublicKeyEntryDecoder<?, ?> decoder = BY_KEY_CLASS_DECODERS_MAP.get(keyType);
+ if (decoder != null) {
+ return decoder;
+ }
+
+ // in case it is a derived class
+ for (PublicKeyEntryDecoder<?, ?> dec : BY_KEY_CLASS_DECODERS_MAP.values()) {
+ Class<?> pubType = dec.getPublicKeyType();
+ Class<?> prvType = dec.getPrivateKeyType();
+ if (pubType.isAssignableFrom(keyType) || prvType.isAssignableFrom(keyType)) {
+ return dec;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return The default {@link DigestFactory}
+ * by the {@link #getFingerPrint(PublicKey)} and {@link #getFingerPrint(String)}
+ * methods
+ * @see #KEY_FINGERPRINT_FACTORY_PROP
+ * @see #setDefaultFingerPrintFactory(DigestFactory)
+ */
+ public static DigestFactory getDefaultFingerPrintFactory() {
+ DigestFactory factory = null;
+ synchronized (DEFAULT_DIGEST_HOLDER) {
+ factory = DEFAULT_DIGEST_HOLDER.get();
+ if (factory != null) {
+ return factory;
+ }
+
+ String propVal = System.getProperty(KEY_FINGERPRINT_FACTORY_PROP);
+ if (GenericUtils.isEmpty(propVal)) {
+ factory = DEFAULT_FINGERPRINT_DIGEST_FACTORY;
+ } else {
+ factory = ValidateUtils.checkNotNull(BuiltinDigests.fromFactoryName(propVal), "Unknown digest factory: %s", propVal);
+ }
+
+ ValidateUtils.checkTrue(factory.isSupported(), "Selected fingerprint digest not supported: %s", factory.getName());
+ DEFAULT_DIGEST_HOLDER.set(factory);
+ }
+
+ return factory;
+ }
+
+ /**
+ * @param f The {@link DigestFactory} of {@link Digest}s to be used - may
+ * not be {@code null}
+ */
+ public static void setDefaultFingerPrintFactory(DigestFactory f) {
+ synchronized (DEFAULT_DIGEST_HOLDER) {
+ DEFAULT_DIGEST_HOLDER.set(Objects.requireNonNull(f, "No digest factory"));
+ }
+ }
+
+ /**
+ * @param key the public key - ignored if {@code null}
+ * @return the fingerprint or {@code null} if no key.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see #getFingerPrint(Factory, PublicKey)
+ */
+ public static String getFingerPrint(PublicKey key) {
+ return getFingerPrint(getDefaultFingerPrintFactory(), key);
+ }
+
+ /**
+ * @param password The {@link String} to digest - ignored if {@code null}/empty,
+ * otherwise its UTF-8 representation is used as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see #getFingerPrint(String, Charset)
+ */
+ public static String getFingerPrint(String password) {
+ return getFingerPrint(password, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * @param password The {@link String} to digest - ignored if {@code null}/empty
+ * @param charset The {@link Charset} to use in order to convert the
+ * string to its byte representation to use as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see #getFingerPrint(Factory, String, Charset)
+ * @see #getDefaultFingerPrintFactory()
+ */
+ public static String getFingerPrint(String password, Charset charset) {
+ return getFingerPrint(getDefaultFingerPrintFactory(), password, charset);
+ }
+
+ /**
+ * @param f The {@link Factory} to create the {@link Digest} to use
+ * @param key the public key - ignored if {@code null}
+ * @return the fingerprint or {@code null} if no key.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see #getFingerPrint(Digest, PublicKey)
+ */
+ public static String getFingerPrint(Factory<? extends Digest> f, PublicKey key) {
+ return (key == null) ? null : getFingerPrint(Objects.requireNonNull(f, "No digest factory").create(), key);
+ }
+
+ /**
+ * @param d The {@link Digest} to use
+ * @param key the public key - ignored if {@code null}
+ * @return the fingerprint or {@code null} if no key.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see DigestUtils#getFingerPrint(Digest, byte[], int, int)
+ */
+ public static String getFingerPrint(Digest d, PublicKey key) {
+ if (key == null) {
+ return null;
+ }
+
+ try {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putRawPublicKey(key);
+ return DigestUtils.getFingerPrint(d, buffer.array(), 0, buffer.wpos());
+ } catch (Exception e) {
+ return e.getClass().getSimpleName();
+ }
+ }
+
+ public static byte[] getRawFingerprint(PublicKey key) throws Exception {
+ return getRawFingerprint(getDefaultFingerPrintFactory(), key);
+ }
+
+ public static byte[] getRawFingerprint(Factory<? extends Digest> f, PublicKey key) throws Exception {
+ return (key == null) ? null : getRawFingerprint(Objects.requireNonNull(f, "No digest factory").create(), key);
+ }
+
+ public static byte[] getRawFingerprint(Digest d, PublicKey key) throws Exception {
+ if (key == null) {
+ return null;
+ }
+
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putRawPublicKey(key);
+ return DigestUtils.getRawFingerprint(d, buffer.array(), 0, buffer.wpos());
+ }
+
+ /**
+ * @param f The {@link Factory} to create the {@link Digest} to use
+ * @param s The {@link String} to digest - ignored if {@code null}/empty,
+ * otherwise its UTF-8 representation is used as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see #getFingerPrint(Digest, String, Charset)
+ */
+ public static String getFingerPrint(Factory<? extends Digest> f, String s) {
+ return getFingerPrint(f, s, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * @param f The {@link Factory} to create the {@link Digest} to use
+ * @param s The {@link String} to digest - ignored if {@code null}/empty
+ * @param charset The {@link Charset} to use in order to convert the
+ * string to its byte representation to use as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see DigestUtils#getFingerPrint(Digest, String, Charset)
+ */
+ public static String getFingerPrint(Factory<? extends Digest> f, String s, Charset charset) {
+ return getFingerPrint(f.create(), s, charset);
+ }
+
+ /**
+ * @param d The {@link Digest} to use
+ * @param s The {@link String} to digest - ignored if {@code null}/empty,
+ * otherwise its UTF-8 representation is used as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see DigestUtils#getFingerPrint(Digest, String, Charset)
+ */
+ public static String getFingerPrint(Digest d, String s) {
+ return getFingerPrint(d, s, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * @param d The {@link Digest} to use to calculate the fingerprint
+ * @param s The string to digest - ignored if {@code null}/empty
+ * @param charset The {@link Charset} to use in order to convert the
+ * string to its byte representation to use as input for the fingerprint
+ * @return The fingerprint - {@code null} if {@code null}/empty input.
+ * <B>Note:</B> if exception encountered then returns the exception's simple class name
+ * @see DigestUtils#getFingerPrint(Digest, String, Charset)
+ */
+ public static String getFingerPrint(Digest d, String s, Charset charset) {
+ if (GenericUtils.isEmpty(s)) {
+ return null;
+ }
+
+ try {
+ return DigestUtils.getFingerPrint(d, s, charset);
+ } catch (Exception e) {
+ return e.getClass().getSimpleName();
+ }
+ }
+
+ /**
+ * @param expected The expected fingerprint if {@code null} or empty then returns a failure
+ * with the default fingerprint.
+ * @param key the {@link PublicKey} - if {@code null} then returns null.
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
+ * {@code null} if no key.
+ * @see #getDefaultFingerPrintFactory()
+ * @see #checkFingerPrint(String, Factory, PublicKey)
+ */
+ public static SimpleImmutableEntry<Boolean, String> checkFingerPrint(String expected, PublicKey key) {
+ return checkFingerPrint(expected, getDefaultFingerPrintFactory(), key);
+ }
+
+ /**
+ * @param expected The expected fingerprint if {@code null} or empty then returns a failure
+ * with the default fingerprint.
+ * @param f The {@link Factory} to be used to generate the default {@link Digest} for the key
+ * @param key the {@link PublicKey} - if {@code null} then returns null.
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
+ * {@code null} if no key.
+ */
+ public static SimpleImmutableEntry<Boolean, String> checkFingerPrint(String expected, Factory<? extends Digest> f, PublicKey key) {
+ return checkFingerPrint(expected, Objects.requireNonNull(f, "No digest factory").create(), key);
+ }
+
+ /**
+ * @param expected The expected fingerprint if {@code null} or empty then returns a failure
+ * with the default fingerprint.
+ * @param d The {@link Digest} to be used to generate the default fingerprint for the key
+ * @param key the {@link PublicKey} - if {@code null} then returns null.
+ * @return SimpleImmutableEntry<Boolean, String> - key is success indicator, value is actual fingerprint,
+ * {@code null} if no key.
+ */
+ public static SimpleImmutableEntry<Boolean, String> checkFingerPrint(String expected, Digest d, PublicKey key) {
+ if (key == null) {
+ return null;
+ }
+
+ if (GenericUtils.isEmpty(expected)) {
+ return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
+ }
+
+ // de-construct fingerprint
+ int pos = expected.indexOf(':');
+ if ((pos < 0) || (pos >= (expected.length() - 1))) {
+ return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
+ }
+
+ String name = expected.substring(0, pos);
+ String value = expected.substring(pos + 1);
+ DigestFactory expectedFactory;
+ // We know that all digest names have a length > 2 - if 2 (or less) then assume a pure HEX value
+ if (name.length() > 2) {
+ expectedFactory = BuiltinDigests.fromFactoryName(name);
+ if (expectedFactory == null) {
+ return new SimpleImmutableEntry<>(false, getFingerPrint(d, key));
+ }
+
+ expected = name.toUpperCase() + ":" + value;
+ } else {
+ expectedFactory = BuiltinDigests.md5;
+ expected = expectedFactory.getName().toUpperCase() + ":" + expected;
+ }
+
+ String fingerprint = getFingerPrint(expectedFactory, key);
+ boolean matches = BuiltinDigests.md5.getName().equals(expectedFactory.getName())
+ ? expected.equalsIgnoreCase(fingerprint) // HEX is case insensitive
+ : expected.equals(fingerprint);
+ return new SimpleImmutableEntry<>(matches, fingerprint);
+ }
+
+ /**
+ * @param kp a key pair - ignored if {@code null}. If the private
+ * key is non-{@code null} then it is used to determine the type,
+ * otherwise the public one is used.
+ * @return the key type or {@code null} if cannot determine it
+ * @see #getKeyType(Key)
+ */
+ public static String getKeyType(KeyPair kp) {
+ if (kp == null) {
+ return null;
+ }
+ PrivateKey key = kp.getPrivate();
+ if (key != null) {
+ return getKeyType(key);
+ } else {
+ return getKeyType(kp.getPublic());
+ }
+ }
+
+ /**
+ * @param key a public or private key
+ * @return the key type or {@code null} if cannot determine it
+ */
+ public static String getKeyType(Key key) {
+ if (key == null) {
+ return null;
+ } else if (key instanceof DSAKey) {
+ return KeyPairProvider.SSH_DSS;
+ } else if (key instanceof RSAKey) {
+ return KeyPairProvider.SSH_RSA;
+ } else if (key instanceof ECKey) {
+ ECKey ecKey = (ECKey) key;
+ ECParameterSpec ecSpec = ecKey.getParams();
+ ECCurves curve = ECCurves.fromCurveParameters(ecSpec);
+ if (curve == null) {
+ return null; // debug breakpoint
+ } else {
+ return curve.getKeyType();
+ }
+ } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
+ return KeyPairProvider.SSH_ED25519;
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines the key size in bits
+ *
+ * @param key The {@link Key} to examine - ignored if {@code null}
+ * @return The key size - non-positive value if cannot determine it
+ */
+ public static int getKeySize(Key key) {
+ if (key == null) {
+ return -1;
+ } else if (key instanceof RSAKey) {
+ BigInteger n = ((RSAKey) key).getModulus();
+ return n.bitLength();
+ } else if (key instanceof DSAKey) {
+ DSAParams params = ((DSAKey) key).getParams();
+ BigInteger p = params.getP();
+ return p.bitLength();
+ } else if (key instanceof ECKey) {
+ ECParameterSpec ecSpec = ((ECKey) key).getParams();
+ ECCurves curve = ECCurves.fromCurveParameters(ecSpec);
+ if (curve != null) {
+ return curve.getKeySize();
+ }
+ } else if (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
+ return SecurityUtils.getEDDSAKeySize(key);
+ }
+
+ return -1;
+ }
+
+ /**
+ * @param key The {@link PublicKey} to be checked - ignored if {@code null}
+ * @param keySet The keys to be searched - ignored if {@code null}/empty
+ * @return The matching {@link PublicKey} from the keys or {@code null} if
+ * no match found
+ * @see #compareKeys(PublicKey, PublicKey)
+ */
+ public static PublicKey findMatchingKey(PublicKey key, PublicKey... keySet) {
+ if (key == null || GenericUtils.isEmpty(keySet)) {
+ return null;
+ } else {
+ return findMatchingKey(key, Arrays.asList(keySet));
+ }
+ }
+
+ /**
+ * @param key The {@link PublicKey} to be checked - ignored if {@code null}
+ * @param keySet The keys to be searched - ignored if {@code null}/empty
+ * @return The matching {@link PublicKey} from the keys or {@code null} if
+ * no match found
+ * @see #compareKeys(PublicKey, PublicKey)
+ */
+ public static PublicKey findMatchingKey(PublicKey key, Collection<? extends PublicKey> keySet) {
+ if (key == null || GenericUtils.isEmpty(keySet)) {
+ return null;
+ }
+ for (PublicKey k : keySet) {
+ if (compareKeys(key, k)) {
+ return k;
+ }
+ }
+ return null;
+ }
+
+ public static boolean compareKeyPairs(KeyPair k1, KeyPair k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if ((k1 == null) || (k2 == null)) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return compareKeys(k1.getPublic(), k2.getPublic())
+ && compareKeys(k1.getPrivate(), k2.getPrivate());
+ }
+ }
+
+ public static boolean compareKeys(PublicKey k1, PublicKey k2) {
+ if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
+ return compareRSAKeys(RSAPublicKey.class.cast(k1), RSAPublicKey.class.cast(k2));
+ } else if ((k1 instanceof DSAPublicKey) && (k2 instanceof DSAPublicKey)) {
+ return compareDSAKeys(DSAPublicKey.class.cast(k1), DSAPublicKey.class.cast(k2));
+ } else if ((k1 instanceof ECPublicKey) && (k2 instanceof ECPublicKey)) {
+ return compareECKeys(ECPublicKey.class.cast(k1), ECPublicKey.class.cast(k2));
+ } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
+ && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
+ return SecurityUtils.compareEDDSAPPublicKeys(k1, k2);
+ } else {
+ return false; // either key is null or not of same class
+ }
+ }
+
+ public static PublicKey recoverPublicKey(PrivateKey key) throws GeneralSecurityException {
+ if (key instanceof RSAPrivateKey) {
+ return recoverRSAPublicKey((RSAPrivateKey) key);
+ } else if (key instanceof DSAPrivateKey) {
+ return recoverDSAPublicKey((DSAPrivateKey) key);
+ } else if ((key != null) && SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) {
+ return SecurityUtils.recoverEDDSAPublicKey(key);
+ } else {
+ return null;
+ }
+ }
+
+ public static boolean compareKeys(PrivateKey k1, PrivateKey k2) {
+ if ((k1 instanceof RSAPrivateKey) && (k2 instanceof RSAPrivateKey)) {
+ return compareRSAKeys(RSAPrivateKey.class.cast(k1), RSAPrivateKey.class.cast(k2));
+ } else if ((k1 instanceof DSAPrivateKey) && (k2 instanceof DSAPrivateKey)) {
+ return compareDSAKeys(DSAPrivateKey.class.cast(k1), DSAPrivateKey.class.cast(k2));
+ } else if ((k1 instanceof ECPrivateKey) && (k2 instanceof ECPrivateKey)) {
+ return compareECKeys(ECPrivateKey.class.cast(k1), ECPrivateKey.class.cast(k2));
+ } else if ((k1 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k1.getAlgorithm())
+ && (k2 != null) && SecurityUtils.EDDSA.equalsIgnoreCase(k2.getAlgorithm())) {
+ return SecurityUtils.compareEDDSAPrivateKeys(k1, k2);
+ } else {
+ return false; // either key is null or not of same class
+ }
+ }
+
+ public static boolean compareRSAKeys(RSAPublicKey k1, RSAPublicKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getPublicExponent(), k2.getPublicExponent())
+ && Objects.equals(k1.getModulus(), k2.getModulus());
+ }
+ }
+
+ public static boolean compareRSAKeys(RSAPrivateKey k1, RSAPrivateKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getModulus(), k2.getModulus())
+ && Objects.equals(k1.getPrivateExponent(), k2.getPrivateExponent());
+ }
+ }
+
+ public static RSAPublicKey recoverRSAPublicKey(RSAPrivateKey privateKey) throws GeneralSecurityException {
+ if (privateKey instanceof RSAPrivateCrtKey) {
+ return recoverFromRSAPrivateCrtKey((RSAPrivateCrtKey) privateKey);
+ } else {
+ // Not ideal, but best we can do under the circumstances
+ return recoverRSAPublicKey(privateKey.getModulus(), DEFAULT_RSA_PUBLIC_EXPONENT);
+ }
+ }
+
+ public static RSAPublicKey recoverFromRSAPrivateCrtKey(RSAPrivateCrtKey rsaKey) throws GeneralSecurityException {
+ return recoverRSAPublicKey(rsaKey.getPrimeP(), rsaKey.getPrimeQ(), rsaKey.getPublicExponent());
+ }
+
+ public static RSAPublicKey recoverRSAPublicKey(BigInteger p, BigInteger q, BigInteger publicExponent) throws GeneralSecurityException {
+ return recoverRSAPublicKey(p.multiply(q), publicExponent);
+ }
+
+ public static RSAPublicKey recoverRSAPublicKey(BigInteger modulus, BigInteger publicExponent) throws GeneralSecurityException {
+ KeyFactory kf = SecurityUtils.getKeyFactory(RSA_ALGORITHM);
+ return (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
+ }
+
+ public static boolean compareDSAKeys(DSAPublicKey k1, DSAPublicKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getY(), k2.getY())
+ && compareDSAParams(k1.getParams(), k2.getParams());
+ }
+ }
+
+ public static boolean compareDSAKeys(DSAPrivateKey k1, DSAPrivateKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getX(), k2.getX())
+ && compareDSAParams(k1.getParams(), k2.getParams());
+ }
+ }
+
+ public static boolean compareDSAParams(DSAParams p1, DSAParams p2) {
+ if (Objects.equals(p1, p2)) {
+ return true;
+ } else if (p1 == null || p2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(p1.getG(), p2.getG())
+ && Objects.equals(p1.getP(), p2.getP())
+ && Objects.equals(p1.getQ(), p2.getQ());
+ }
+ }
+
+ // based on code from https://github.com/alexo/SAML-2.0/blob/master/java-opensaml/opensaml-security-api/src/main/java/org/opensaml/xml/security/SecurityHelper.java
+ public static DSAPublicKey recoverDSAPublicKey(DSAPrivateKey privateKey) throws GeneralSecurityException {
+ DSAParams keyParams = privateKey.getParams();
+ BigInteger p = keyParams.getP();
+ BigInteger x = privateKey.getX();
+ BigInteger q = keyParams.getQ();
+ BigInteger g = keyParams.getG();
+ BigInteger y = g.modPow(x, p);
+ KeyFactory kf = SecurityUtils.getKeyFactory(DSS_ALGORITHM);
+ return (DSAPublicKey) kf.generatePublic(new DSAPublicKeySpec(y, p, q, g));
+ }
+
+ public static boolean compareECKeys(ECPrivateKey k1, ECPrivateKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getS(), k2.getS())
+ && compareECParams(k1.getParams(), k2.getParams());
+ }
+ }
+
+ public static boolean compareECKeys(ECPublicKey k1, ECPublicKey k2) {
+ if (Objects.equals(k1, k2)) {
+ return true;
+ } else if (k1 == null || k2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(k1.getW(), k2.getW())
+ && compareECParams(k1.getParams(), k2.getParams());
+ }
+ }
+
+ public static boolean compareECParams(ECParameterSpec s1, ECParameterSpec s2) {
+ if (Objects.equals(s1, s2)) {
+ return true;
+ } else if (s1 == null || s2 == null) {
+ return false; // both null is covered by Objects#equals
+ } else {
+ return Objects.equals(s1.getOrder(), s2.getOrder())
+ && (s1.getCofactor() == s2.getCofactor())
+ && Objects.equals(s1.getGenerator(), s2.getGenerator())
+ && Objects.equals(s1.getCurve(), s2.getCurve());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
new file mode 100644
index 0000000..6dbeee9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryDecoder.java
@@ -0,0 +1,142 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @param <PUB> Type of {@link PublicKey}
+ * @param <PRV> Type of {@link PrivateKey}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
+ extends KeyEntryResolver<PUB, PRV>, PrivateKeyEntryResolver {
+
+ @Override
+ default PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+ Collection<String> supported = getSupportedTypeNames();
+ if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
+ return decodePrivateKey(FilePasswordProvider.EMPTY, keyData);
+ }
+
+ throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
+ }
+
+ /**
+ * @param passwordProvider The {@link FilePasswordProvider} to use
+ * in case the data is encrypted - may be {@code null} if no encrypted
+ * data is expected
+ * @param keyData The key data bytes in {@code OpenSSH} format (after
+ * BASE64 decoding) - ignored if {@code null}/empty
+ * @return The decoded {@link PrivateKey} - or {@code null} if no data
+ * @throws IOException If failed to decode the key
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ default PRV decodePrivateKey(FilePasswordProvider passwordProvider, byte... keyData)
+ throws IOException, GeneralSecurityException {
+ return decodePrivateKey(passwordProvider, keyData, 0, NumberUtils.length(keyData));
+ }
+
+ default PRV decodePrivateKey(FilePasswordProvider passwordProvider, byte[] keyData, int offset, int length)
+ throws IOException, GeneralSecurityException {
+ if (length <= 0) {
+ return null;
+ }
+
+ try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) {
+ return decodePrivateKey(passwordProvider, stream);
+ }
+ }
+
+ default PRV decodePrivateKey(FilePasswordProvider passwordProvider, InputStream keyData)
+ throws IOException, GeneralSecurityException {
+ // the actual data is preceded by a string that repeats the key type
+ String type = KeyEntryResolver.decodeString(keyData);
+ if (GenericUtils.isEmpty(type)) {
+ throw new StreamCorruptedException("Missing key type string");
+ }
+
+ Collection<String> supported = getSupportedTypeNames();
+ if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
+ throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported);
+ }
+
+ return decodePrivateKey(type, passwordProvider, keyData);
+ }
+
+ /**
+ * @param keyType The reported / encode key type
+ * @param passwordProvider The {@link FilePasswordProvider} to use
+ * in case the data is encrypted - may be {@code null} if no encrypted
+ * data is expected
+ * @param keyData The key data bytes stream positioned after the key type decoding
+ * and making sure it is one of the supported types
+ * @return The decoded {@link PrivateKey}
+ * @throws IOException If failed to read from the data stream
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ PRV decodePrivateKey(String keyType, FilePasswordProvider passwordProvider, InputStream keyData)
+ throws IOException, GeneralSecurityException;
+
+ /**
+ * Encodes the {@link PrivateKey} using the {@code OpenSSH} format - same
+ * one used by the {@code decodePublicKey} method(s)
+ *
+ * @param s The {@link OutputStream} to write the data to
+ * @param key The {@link PrivateKey} - may not be {@code null}
+ * @return The key type value - one of the {@link #getSupportedTypeNames()} or
+ * {@code null} if encoding not supported
+ * @throws IOException If failed to generate the encoding
+ */
+ default String encodePrivateKey(OutputStream s, PRV key) throws IOException {
+ Objects.requireNonNull(key, "No private key provided");
+ return null;
+ }
+
+ default boolean isPublicKeyRecoverySupported() {
+ return false;
+ }
+
+ /**
+ * Attempts to recover the public key given the private one
+ *
+ * @param prvKey The {@link PrivateKey}
+ * @return The recovered {@link PublicKey} - {@code null} if cannot recover it
+ * @throws GeneralSecurityException If failed to generate the public key
+ */
+ default PUB recoverPublicKey(PRV prvKey) throws GeneralSecurityException {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java
new file mode 100644
index 0000000..1e4c91e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PrivateKeyEntryResolver.java
@@ -0,0 +1,70 @@
+/*
+ * 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PrivateKeyEntryResolver {
+ /**
+ * A resolver that ignores all input
+ */
+ PrivateKeyEntryResolver IGNORING = new PrivateKeyEntryResolver() {
+ @Override
+ public PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "IGNORING";
+ }
+ };
+
+ /**
+ * A resolver that fails on all input
+ */
+ PrivateKeyEntryResolver FAILING = new PrivateKeyEntryResolver() {
+ @Override
+ public PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ throw new InvalidKeySpecException("Failing resolver on key type=" + keyType);
+ }
+
+ @Override
+ public String toString() {
+ return "FAILING";
+ }
+ };
+
+ /**
+ * @param keyType The {@code OpenSSH} reported key type
+ * @param keyData The {@code OpenSSH} encoded key data
+ * @return The extracted {@link PrivateKey} - ignored if {@code null}
+ * @throws IOException If failed to parse the key data
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ PrivateKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
new file mode 100644
index 0000000..1db3d2b
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -0,0 +1,286 @@
+/*
+ * 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.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StreamCorruptedException;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+
+/**
+ * <P>Represents a {@link PublicKey} whose data is formatted according to
+ * the <A HREF="http://en.wikibooks.org/wiki/OpenSSH">OpenSSH</A> format:</P>
+ *
+ * <PRE>
+ * <key-type> <base64-encoded-public-key-data>
+ * </PRE>
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PublicKeyEntry implements Serializable {
+
+ /**
+ * Character used to denote a comment line in the keys file
+ */
+ public static final char COMMENT_CHAR = '#';
+
+
+ /**
+ * Standard folder name used by OpenSSH to hold key files
+ */
+ public static final String STD_KEYFILE_FOLDER_NAME = ".ssh";
+
+ private static final long serialVersionUID = -585506072687602760L;
+
+ private String keyType;
+ private byte[] keyData;
+
+ public PublicKeyEntry() {
+ super();
+ }
+
+ public PublicKeyEntry(String keyType, byte... keyData) {
+ this.keyType = keyType;
+ this.keyData = keyData;
+ }
+
+ public String getKeyType() {
+ return keyType;
+ }
+
+ public void setKeyType(String value) {
+ this.keyType = value;
+ }
+
+ public byte[] getKeyData() {
+ return keyData;
+ }
+
+ public void setKeyData(byte[] value) {
+ this.keyData = value;
+ }
+
+ /**
+ * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
+ * none of the built-in ones can be used. If {@code null} and no built-in
+ * resolver can be used then an {@link InvalidKeySpecException} is thrown.
+ * @return The resolved {@link PublicKey} - or {@code null} if could not be
+ * resolved. <B>Note:</B> may be called only after key type and data bytes
+ * have been set or exception(s) may be thrown
+ * @throws IOException If failed to decode the key
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ public PublicKey resolvePublicKey(PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException {
+ String kt = getKeyType();
+ PublicKeyEntryResolver decoder = KeyUtils.getPublicKeyEntryDecoder(kt);
+ if (decoder == null) {
+ decoder = fallbackResolver;
+ }
+ if (decoder == null) {
+ throw new InvalidKeySpecException("No decoder available for key type=" + kt);
+ }
+
+ return decoder.resolve(kt, getKeyData());
+ }
+
+ /**
+ * @param sb The {@link Appendable} instance to encode the data into
+ * @param fallbackResolver The {@link PublicKeyEntryResolver} to consult if
+ * none of the built-in ones can be used. If {@code null} and no built-in
+ * resolver can be used then an {@link InvalidKeySpecException} is thrown.
+ * @return The {@link PublicKey} or {@code null} if could not resolve it
+ * @throws IOException If failed to decode/encode the key
+ * @throws GeneralSecurityException If failed to generate the key
+ * @see #resolvePublicKey(PublicKeyEntryResolver)
+ */
+ public PublicKey appendPublicKey(Appendable sb, PublicKeyEntryResolver fallbackResolver) throws IOException, GeneralSecurityException {
+ PublicKey key = resolvePublicKey(fallbackResolver);
+ if (key != null) {
+ appendPublicKeyEntry(sb, key);
+ }
+ return key;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getKeyType()) + Arrays.hashCode(getKeyData());
+ }
+
+ /*
+ * In case some derived class wants to define some "extended" equality
+ * without having to repeat this code
+ */
+ protected boolean isEquivalent(PublicKeyEntry e) {
+ if (this == e) {
+ return true;
+ }
+ return Objects.equals(getKeyType(), e.getKeyType())
+ && Arrays.equals(getKeyData(), e.getKeyData());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ return isEquivalent((PublicKeyEntry) obj);
+ }
+
+ @Override
+ public String toString() {
+ byte[] data = getKeyData();
+ Base64.Encoder encoder = Base64.getEncoder();
+ return getKeyType() + " " + (NumberUtils.isEmpty(data) ? "<no-key>" : encoder.encodeToString(data));
+ }
+
+ /**
+ * @param encData Assumed to contain at least {@code key-type base64-data}
+ * (anything beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+ * @return A {@link PublicKeyEntry} or {@code null} if no data
+ * @throws IllegalArgumentException if bad format found
+ * @see #parsePublicKeyEntry(PublicKeyEntry, String)
+ */
+ public static PublicKeyEntry parsePublicKeyEntry(String encData) throws IllegalArgumentException {
+ String data = GenericUtils.replaceWhitespaceAndTrim(encData);
+ if (GenericUtils.isEmpty(data)) {
+ return null;
+ } else {
+ return parsePublicKeyEntry(new PublicKeyEntry(), data);
+ }
+ }
+
+ /**
+ * @param <E> The generic entry type
+ * @param entry The {@link PublicKeyEntry} whose contents are to be
+ * updated - ignored if {@code null}
+ * @param encData Assumed to contain at least {@code key-type base64-data} (anything
+ * beyond the BASE64 data is ignored) - ignored if {@code null}/empty
+ * @return The updated entry instance
+ * @throws IllegalArgumentException if bad format found
+ */
+ public static <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String encData) throws IllegalArgumentException {
+ String data = GenericUtils.replaceWhitespaceAndTrim(encData);
+ if (GenericUtils.isEmpty(data) || (entry == null)) {
+ return entry;
+ }
+
+ int startPos = data.indexOf(' ');
+ if (startPos <= 0) {
+ throw new IllegalArgumentException("Bad format (no key data delimiter): " + data);
+ }
+
+ int endPos = data.indexOf(' ', startPos + 1);
+ if (endPos <= startPos) { // OK if no continuation beyond the BASE64 encoded data
+ endPos = data.length();
+ }
+
+ String keyType = data.substring(0, startPos);
+ String b64Data = data.substring(startPos + 1, endPos).trim();
+ Base64.Decoder decoder = Base64.getDecoder();
+ byte[] keyData = decoder.decode(b64Data);
+ if (NumberUtils.isEmpty(keyData)) {
+ throw new IllegalArgumentException("Bad format (no BASE64 key data): " + data);
+ }
+
+ entry.setKeyType(keyType);
+ entry.setKeyData(keyData);
+ return entry;
+ }
+
+ /**
+ * @param key The {@link PublicKey}
+ * @return The {@code OpenSSH} encoded data
+ * @throws IllegalArgumentException If failed to encode
+ * @see #appendPublicKeyEntry(Appendable, PublicKey)
+ */
+ public static String toString(PublicKey key) throws IllegalArgumentException {
+ try {
+ return appendPublicKeyEntry(new StringBuilder(Byte.MAX_VALUE), key).toString();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ") to encode: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Encodes a public key data the same way as the {@link #parsePublicKeyEntry(String)} expects it
+ *
+ * @param <A> The generic appendable class
+ * @param sb The {@link Appendable} instance to encode the data into
+ * @param key The {@link PublicKey} - ignored if {@code null}
+ * @return The updated appendable instance
+ * @throws IOException If failed to append the data
+ */
+ public static <A extends Appendable> A appendPublicKeyEntry(A sb, PublicKey key) throws IOException {
+ if (key == null) {
+ return sb;
+ }
+
+ @SuppressWarnings("unchecked")
+ PublicKeyEntryDecoder<PublicKey, ?> decoder =
+ (PublicKeyEntryDecoder<PublicKey, ?>) KeyUtils.getPublicKeyEntryDecoder(key);
+ if (decoder == null) {
+ throw new StreamCorruptedException("Cannot retrieve decoder for key=" + key.getAlgorithm());
+ }
+
+ try (ByteArrayOutputStream s = new ByteArrayOutputStream(Byte.MAX_VALUE)) {
+ String keyType = decoder.encodePublicKey(s, key);
+ byte[] bytes = s.toByteArray();
+ Base64.Encoder encoder = Base64.getEncoder();
+ String b64Data = encoder.encodeToString(bytes);
+ sb.append(keyType).append(' ').append(b64Data);
+ }
+
+ return sb;
+ }
+
+ private static final class LazyDefaultKeysFolderHolder {
+ private static final Path PATH =
+ IdentityUtils.getUserHomeFolder().resolve(STD_KEYFILE_FOLDER_NAME);
+
+ private LazyDefaultKeysFolderHolder() {
+ throw new UnsupportedOperationException("No instance allowed");
+ }
+ }
+
+ /**
+ * @return The default OpenSSH folder used to hold key files - e.g.,
+ * {@code known_hosts}, {@code authorized_keys}, etc.
+ */
+ @SuppressWarnings("synthetic-access")
+ public static Path getDefaultKeysFolderPath() {
+ return LazyDefaultKeysFolderHolder.PATH;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
new file mode 100644
index 0000000..7462e4a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
@@ -0,0 +1,114 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Represents a decoder of an {@code OpenSSH} encoded key data
+ *
+ * @param <PUB> Type of {@link PublicKey}
+ * @param <PRV> Type of {@link PrivateKey}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
+ extends KeyEntryResolver<PUB, PRV>, PublicKeyEntryResolver {
+
+ @Override
+ default PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ ValidateUtils.checkNotNullAndNotEmpty(keyType, "No key type provided");
+ Collection<String> supported = getSupportedTypeNames();
+ if ((GenericUtils.size(supported) > 0) && supported.contains(keyType)) {
+ return decodePublicKey(keyData);
+ }
+
+ throw new InvalidKeySpecException("resolve(" + keyType + ") not in listed supported types: " + supported);
+ }
+
+ /**
+ * @param keyData The key data bytes in {@code OpenSSH} format (after
+ * BASE64 decoding) - ignored if {@code null}/empty
+ * @return The decoded {@link PublicKey} - or {@code null} if no data
+ * @throws IOException If failed to decode the key
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ default PUB decodePublicKey(byte... keyData) throws IOException, GeneralSecurityException {
+ return decodePublicKey(keyData, 0, NumberUtils.length(keyData));
+ }
+
+ default PUB decodePublicKey(byte[] keyData, int offset, int length) throws IOException, GeneralSecurityException {
+ if (length <= 0) {
+ return null;
+ }
+
+ try (InputStream stream = new ByteArrayInputStream(keyData, offset, length)) {
+ return decodePublicKey(stream);
+ }
+ }
+
+ default PUB decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException {
+ // the actual data is preceded by a string that repeats the key type
+ String type = KeyEntryResolver.decodeString(keyData);
+ if (GenericUtils.isEmpty(type)) {
+ throw new StreamCorruptedException("Missing key type string");
+ }
+
+ Collection<String> supported = getSupportedTypeNames();
+ if (GenericUtils.isEmpty(supported) || (!supported.contains(type))) {
+ throw new InvalidKeySpecException("Reported key type (" + type + ") not in supported list: " + supported);
+ }
+
+ return decodePublicKey(type, keyData);
+ }
+
+ /**
+ * @param keyType The reported / encode key type
+ * @param keyData The key data bytes stream positioned after the key type decoding
+ * and making sure it is one of the supported types
+ * @return The decoded {@link PublicKey}
+ * @throws IOException If failed to read from the data stream
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ PUB decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException;
+
+ /**
+ * Encodes the {@link PublicKey} using the {@code OpenSSH} format - same
+ * one used by the {@code decodePublicKey} method(s)
+ *
+ * @param s The {@link OutputStream} to write the data to
+ * @param key The {@link PublicKey} - may not be {@code null}
+ * @return The key type value - one of the {@link #getSupportedTypeNames()}
+ * @throws IOException If failed to generate the encoding
+ */
+ String encodePublicKey(OutputStream s, PUB key) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
new file mode 100644
index 0000000..e5eb0ed
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryResolver.java
@@ -0,0 +1,70 @@
+/*
+ * 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PublicKeyEntryResolver {
+ /**
+ * A resolver that ignores all input
+ */
+ PublicKeyEntryResolver IGNORING = new PublicKeyEntryResolver() {
+ @Override
+ public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "IGNORING";
+ }
+ };
+
+ /**
+ * A resolver that fails on all input
+ */
+ PublicKeyEntryResolver FAILING = new PublicKeyEntryResolver() {
+ @Override
+ public PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException {
+ throw new InvalidKeySpecException("Failing resolver on key type=" + keyType);
+ }
+
+ @Override
+ public String toString() {
+ return "FAILING";
+ }
+ };
+
+ /**
+ * @param keyType The {@code OpenSSH} reported key type
+ * @param keyData The {@code OpenSSH} encoded key data
+ * @return The extracted {@link PublicKey} - ignored if {@code null}
+ * @throws IOException If failed to parse the key data
+ * @throws GeneralSecurityException If failed to generate the key
+ */
+ PublicKey resolve(String keyType, byte[] keyData) throws IOException, GeneralSecurityException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
new file mode 100644
index 0000000..a2412bd
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java
@@ -0,0 +1,62 @@
+/*
+ * 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.impl;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.IdentityResourceLoader;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @param <PUB> Generic public key type
+ * @param <PRV> Generic private key type
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractIdentityResourceLoader<PUB extends PublicKey, PRV extends PrivateKey>
+ extends AbstractLoggingBean
+ implements IdentityResourceLoader<PUB, PRV> {
+ private final Class<PUB> pubType;
+ private final Class<PRV> prvType;
+ private final Collection<String> names;
+
+ protected AbstractIdentityResourceLoader(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
+ this.pubType = Objects.requireNonNull(pubType, "No public key type specified");
+ this.prvType = Objects.requireNonNull(prvType, "No private key type specified");
+ this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided");
+ }
+
+ @Override
+ public final Class<PUB> getPublicKeyType() {
+ return pubType;
+ }
+
+ @Override
+ public final Class<PRV> getPrivateKeyType() {
+ return prvType;
+ }
+
+ @Override
+ public Collection<String> getSupportedTypeNames() {
+ return names;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
new file mode 100644
index 0000000..7afa3a6
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java
@@ -0,0 +1,59 @@
+/*
+ * 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.impl;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.KeySpec;
+import java.util.Collection;
+
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+
+/**
+ * @param <PUB> Type of {@link PublicKey}
+ * @param <PRV> Type of {@link PrivateKey}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractKeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey>
+ extends AbstractIdentityResourceLoader<PUB, PRV>
+ implements KeyEntryResolver<PUB, PRV> {
+ protected AbstractKeyEntryResolver(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
+ super(pubType, prvType, names);
+ }
+
+ public PUB generatePublicKey(KeySpec keySpec) throws GeneralSecurityException {
+ KeyFactory factory = getKeyFactoryInstance();
+ Class<PUB> keyType = getPublicKeyType();
+ return keyType.cast(factory.generatePublic(keySpec));
+ }
+
+ public PRV generatePrivateKey(KeySpec keySpec) throws GeneralSecurityException {
+ KeyFactory factory = getKeyFactoryInstance();
+ Class<PRV> keyType = getPrivateKeyType();
+ return keyType.cast(factory.generatePrivate(keySpec));
+ }
+
+ @Override
+ public String toString() {
+ return getPublicKeyType().getSimpleName() + ": " + getSupportedTypeNames();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.java
new file mode 100644
index 0000000..574412e
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPrivateKeyEntryDecoder.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.config.keys.impl;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Collection;
+
+import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder;
+
+/**
+ * @param <PUB> Type of {@link PublicKey}
+ * @param <PRV> Type of {@link PrivateKey}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPrivateKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
+ extends AbstractKeyEntryResolver<PUB, PRV>
+ implements PrivateKeyEntryDecoder<PUB, PRV> {
+ protected AbstractPrivateKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
+ super(pubType, prvType, names);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..59cbf3a
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractPublicKeyEntryDecoder.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.config.keys.impl;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Collection;
+
+import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
+
+/**
+ * Useful base class implementation for a decoder of an {@code OpenSSH} encoded key data
+ *
+ * @param <PUB> Type of {@link PublicKey}
+ * @param <PRV> Type of {@link PrivateKey}
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPublicKeyEntryDecoder<PUB extends PublicKey, PRV extends PrivateKey>
+ extends AbstractKeyEntryResolver<PUB, PRV>
+ implements PublicKeyEntryDecoder<PUB, PRV> {
+ protected AbstractPublicKeyEntryDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) {
+ super(pubType, prvType, names);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..464d2b0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/DSSPublicKeyEntryDecoder.java
@@ -0,0 +1,119 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+import java.util.Objects;
+
+import org.apache.sshd.common.config.keys.KeyEntryResolver;
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAPublicKey, DSAPrivateKey> {
+ public static final DSSPublicKeyEntryDecoder INSTANCE = new DSSPublicKeyEntryDecoder();
+
+ public DSSPublicKeyEntryDecoder() {
+ super(DSAPublicKey.class, DSAPrivateKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_DSS)));
+ }
+
+ @Override
+ public DSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException {
+ if (!KeyPairProvider.SSH_DSS.equals(keyType)) { // just in case we were invoked directly
+ throw new InvalidKeySpecException("Unexpected key type: " + keyType);
+ }
+
+ BigInteger p = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger q = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger g = KeyEntryResolver.decodeBigInt(keyData);
+ BigInteger y = KeyEntryResolver.decodeBigInt(keyData);
+
+ return generatePublicKey(new DSAPublicKeySpec(y, p, q, g));
+ }
+
+ @Override
+ public String encodePublicKey(OutputStream s, DSAPublicKey key) throws IOException {
+ Objects.requireNonNull(key, "No public key provided");
+
+ DSAParams keyParams = Objects.requireNonNull(key.getParams(), "No DSA params available");
+ KeyEntryResolver.encodeString(s, KeyPairProvider.SSH_DSS);
+ KeyEntryResolver.encodeBigInt(s, keyParams.getP());
+ KeyEntryResolver.encodeBigInt(s, keyParams.getQ());
+ KeyEntryResolver.encodeBigInt(s, keyParams.getG());
+ KeyEntryResolver.encodeBigInt(s, key.getY());
+
+ return KeyPairProvider.SSH_DSS;
+ }
+
+ @Override
+ public DSAPublicKey clonePublicKey(DSAPublicKey key) throws GeneralSecurityException {
+ if (key == null) {
+ return null;
+ }
+
+ DSAParams params = key.getParams();
+ if (params == null) {
+ throw new InvalidKeyException("Missing parameters in key");
+ }
+
+ return generatePublicKey(new DSAPublicKeySpec(key.getY(), params.getP(), params.getQ(), params.getG()));
+ }
+
+ @Override
+ public DSAPrivateKey clonePrivateKey(DSAPrivateKey key) throws GeneralSecurityException {
+ if (key == null) {
+ return null;
+ }
+
+ DSAParams params = key.getParams();
+ if (params == null) {
+ throw new InvalidKeyException("Missing parameters in key");
+ }
+
+ return generatePrivateKey(new DSAPrivateKeySpec(key.getX(), params.getP(), params.getQ(), params.getG()));
+ }
+
+ @Override
+ public KeyPairGenerator getKeyPairGenerator() throws GeneralSecurityException {
+ return SecurityUtils.getKeyPairGenerator(KeyUtils.DSS_ALGORITHM);
+ }
+
+ @Override
+ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+ return SecurityUtils.getKeyFactory(KeyUtils.DSS_ALGORITHM);
+ }
+}