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 2015/06/03 13:55:01 UTC
[2/2] mina-sshd git commit: [SSHD-482] Add support for
authorized_keys file
[SSHD-482] Add support for authorized_keys file
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/e12434d0
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/e12434d0
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/e12434d0
Branch: refs/heads/master
Commit: e12434d0d42a24a312b924de7d3731606377c40a
Parents: 330d17c
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Wed Jun 3 14:54:50 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Wed Jun 3 14:54:50 2015 +0300
----------------------------------------------------------------------
.../main/java/org/apache/sshd/SshBuilder.java | 27 ++
.../sshd/agent/common/AbstractAgentClient.java | 2 +-
.../sshd/client/auth/UserAuthPublicKey.java | 2 +-
.../org/apache/sshd/client/kex/DHGClient.java | 2 +-
.../org/apache/sshd/client/kex/DHGEXClient.java | 2 +-
.../keyverifier/StaticServerKeyVerifier.java | 2 +-
.../org/apache/sshd/common/cipher/ECCurves.java | 23 ++
.../keys/AbstractPublicKeyEntryDecoder.java | 150 +++++++++
.../config/keys/DSSPublicKeyEntryDecoder.java | 63 ++++
.../config/keys/ECDSAPublicKeyEntryDecoder.java | 236 +++++++++++++
.../sshd/common/config/keys/KeyUtils.java | 314 +++++++++++++++++
.../sshd/common/config/keys/PublicKeyEntry.java | 218 ++++++++++++
.../config/keys/PublicKeyEntryDecoder.java | 56 ++++
.../common/config/keys/RSAPublicKeyDecoder.java | 61 ++++
.../sshd/common/kex/BuiltinDHFactories.java | 6 +-
.../java/org/apache/sshd/common/kex/ECDH.java | 6 +-
.../keyprovider/AbstractKeyPairProvider.java | 2 +-
.../common/keyprovider/KeyPairProvider.java | 8 +-
.../org/apache/sshd/common/util/Base64.java | 64 +++-
.../apache/sshd/common/util/GenericUtils.java | 44 +++
.../org/apache/sshd/common/util/IoUtils.java | 62 ++++
.../org/apache/sshd/common/util/KeyUtils.java | 100 ------
.../common/util/io/ModifiableFileWatcher.java | 169 ++++++++++
.../sshd/server/PublickeyAuthenticator.java | 41 ++-
.../auth/CachingPublicKeyAuthenticator.java | 3 +-
.../server/config/keys/AuthorizedKeyEntry.java | 335 +++++++++++++++++++
.../keys/AuthorizedKeysAuthenticator.java | 126 +++++++
.../DefaultAuthorizedKeysAuthenticator.java | 161 +++++++++
.../apache/sshd/SinglePublicKeyAuthTest.java | 2 +-
.../sshd/common/util/GenericUtilsTest.java | 59 ++++
.../apache/sshd/deprecated/UserAuthAgent.java | 2 +-
.../sshd/deprecated/UserAuthPublicKey.java | 2 +-
.../config/keys/AuthorizedKeyEntryTest.java | 108 ++++++
.../keys/AuthorizedKeysAuthenticatorTest.java | 124 +++++++
.../DefaultAuthorizedKeysAuthenticatorTest.java | 67 ++++
.../sshd/server/config/keys/authorized_keys | 16 +
36 files changed, 2536 insertions(+), 129 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java b/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
index 367e379..cd4d7d1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshBuilder.java
@@ -56,7 +56,9 @@ import org.apache.sshd.common.session.ConnectionService;
import org.apache.sshd.common.signature.BuiltinSignatures;
import org.apache.sshd.common.util.ObjectBuilder;
import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
import org.apache.sshd.server.global.CancelTcpipForwardHandler;
import org.apache.sshd.server.global.KeepAliveHandler;
import org.apache.sshd.server.global.NoMoreSessionsHandler;
@@ -122,6 +124,7 @@ public class SshBuilder {
if (compressionFactories == null) {
compressionFactories = Arrays.<NamedFactory<Compression>>asList(BuiltinCompressions.none);
}
+
if (macFactories == null) {
macFactories = setUpDefaultMacs(false);
}
@@ -129,6 +132,7 @@ public class SshBuilder {
if (fileSystemFactory == null) {
fileSystemFactory = new NativeFileSystemFactory();
}
+
if (tcpipForwarderFactory == null) {
tcpipForwarderFactory = new DefaultTcpipForwarderFactory();
}
@@ -425,6 +429,17 @@ public class SshBuilder {
}
};
+ protected PublickeyAuthenticator pubkeyAuthenticator;
+
+ public ServerBuilder() {
+ super();
+ }
+
+ public ServerBuilder publickeyAuthenticator(PublickeyAuthenticator auth) {
+ pubkeyAuthenticator = auth;
+ return this;
+ }
+
@Override
protected ServerBuilder fillWithDefaultValues() {
super.fillWithDefaultValues();
@@ -446,9 +461,21 @@ public class SshBuilder {
if (factory == null) {
factory = SshServer.DEFAULT_SSH_SERVER_FACTORY;
}
+
+ if (pubkeyAuthenticator == null) {
+ pubkeyAuthenticator = DefaultAuthorizedKeysAuthenticator.INSTANCE;
+ }
+
return me();
}
+ @Override
+ public SshServer build(boolean isFillWithDefaultValues) {
+ SshServer server = super.build(isFillWithDefaultValues);
+ server.setPublickeyAuthenticator(pubkeyAuthenticator);
+ return server;
+ }
+
/**
* @param ignoreUnsupported If {@code true} then all the default
* key exchanges are included, regardless of whether they are currently
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
index 9e48c7d..20510ec 100644
--- a/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/agent/common/AbstractAgentClient.java
@@ -33,8 +33,8 @@ import java.security.PublicKey;
import java.util.List;
import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
index d3a0279..205d6c2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/UserAuthPublicKey.java
@@ -18,7 +18,7 @@
*/
package org.apache.sshd.client.auth;
-import static org.apache.sshd.common.util.KeyUtils.getKeyType;
+import static org.apache.sshd.common.config.keys.KeyUtils.getKeyType;
import java.io.IOException;
import java.security.KeyPair;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index 5e98ac2..6efe31a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -23,10 +23,10 @@ import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.kex.AbstractDH;
import org.apache.sshd.common.kex.DHFactory;
import org.apache.sshd.common.session.AbstractSession;
-import org.apache.sshd.common.util.KeyUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
index b1a05f9..afe5269 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGEXClient.java
@@ -26,10 +26,10 @@ import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.kex.AbstractDH;
import org.apache.sshd.common.kex.DHFactory;
import org.apache.sshd.common.session.AbstractSession;
-import org.apache.sshd.common.util.KeyUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
index f0f8aa4..9ebf0d4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/keyverifier/StaticServerKeyVerifier.java
@@ -24,8 +24,8 @@ import java.security.PublicKey;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.ServerKeyVerifier;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
/**
* Returns the same constant answer {@code true/false} regardless
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
index 2cc250c..ad42865 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java
@@ -23,6 +23,7 @@ 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.Map;
import java.util.TreeMap;
@@ -187,4 +188,26 @@ public class ECCurves {
new BigInteger("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", 16),
1);
}
+
+ private static final class LazySpecsMapHolder {
+ private static final Map<String,ECParameterSpec> specsMap =
+ Collections.unmodifiableMap(new TreeMap<String, ECParameterSpec>(String.CASE_INSENSITIVE_ORDER) {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ put(NISTP256, EllipticCurves.nistp256);
+ put(NISTP384, EllipticCurves.nistp384);
+ put(NISTP521, EllipticCurves.nistp521);
+ }
+ });
+ }
+
+ @SuppressWarnings("synthetic-access")
+ public static ECParameterSpec getECParameterSpec(String curveName) {
+ if (GenericUtils.isEmpty(curveName)) {
+ return null;
+ } else {
+ return LazySpecsMapHolder.specsMap.get(curveName);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..fc97f13
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/AbstractPublicKeyEntryDecoder.java
@@ -0,0 +1,150 @@
+/*
+ * 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.StreamCorruptedException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractPublicKeyEntryDecoder<K extends PublicKey> implements PublicKeyEntryDecoder<K> {
+ private final Class<K> keyType;
+ private final Collection<String> names;
+
+ protected AbstractPublicKeyEntryDecoder(Class<K> keyType, Collection<String> names) {
+ this.keyType = ValidateUtils.checkNotNull(keyType, "No key type specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ }
+
+ @Override
+ public final Class<K> getKeyType() {
+ return keyType;
+ }
+
+ @Override
+ public Collection<String> getSupportedTypeNames() {
+ return names;
+ }
+
+ @Override
+ public K decodePublicKey(byte... keyData) throws IOException, GeneralSecurityException {
+ return decodePublicKey(keyData, 0, GenericUtils.length(keyData));
+ }
+
+ @Override
+ public K 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);
+ }
+ }
+
+ @Override
+ public K decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException {
+ // the actual data is preceded by a string that repeats the key type
+ String type = 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);
+ }
+
+ public K generatePublicKey(KeySpec keySpec) throws GeneralSecurityException {
+ KeyFactory factory = getKeyFactoryInstance();
+ Class<K> keyType = getKeyType();
+ return keyType.cast(factory.generatePublic(keySpec));
+ }
+
+ public abstract KeyFactory getKeyFactoryInstance() throws GeneralSecurityException;
+
+ /**
+ * @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
+ */
+ public abstract K decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException;
+
+ @Override
+ public String toString() {
+ return getKeyType().getSimpleName() + ": " + getSupportedTypeNames();
+ }
+
+ public static final String decodeString(InputStream s) throws IOException {
+ return decodeString(s, StandardCharsets.UTF_8);
+ }
+
+ public static final String decodeString(InputStream s, String charset) throws IOException {
+ return decodeString(s, Charset.forName(charset));
+ }
+
+ public static final String decodeString(InputStream s, Charset cs) throws IOException {
+ byte[] bytes=readRLEBytes(s);
+ return new String(bytes, cs);
+ }
+
+ public static final BigInteger decodeBigInt(InputStream s) throws IOException {
+ return new BigInteger(readRLEBytes(s));
+ }
+
+ public static final byte[] readRLEBytes(InputStream s) throws IOException {
+ int len=decodeInt(s);
+ byte[] bytes=new byte[len];
+ IoUtils.readFully(s, bytes);
+ return bytes;
+ }
+
+ public static final int decodeInt(InputStream s) throws IOException {
+ byte[] bytes={ 0, 0, 0, 0 };
+ IoUtils.readFully(s, bytes);
+ return ((bytes[0] & 0xFF) << 24)
+ | ((bytes[1] & 0xFF) << 16)
+ | ((bytes[2] & 0xFF) << 8)
+ | (bytes[3] & 0xFF);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..62acb4c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/DSSPublicKeyEntryDecoder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Collections;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DSSPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<DSAPublicKey> {
+ public static final DSSPublicKeyEntryDecoder INSTANCE = new DSSPublicKeyEntryDecoder();
+
+ public DSSPublicKeyEntryDecoder() {
+ super(DSAPublicKey.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("Unepected key type: " + keyType);
+ }
+
+ BigInteger p=decodeBigInt(keyData);
+ BigInteger q=decodeBigInt(keyData);
+ BigInteger g=decodeBigInt(keyData);
+ BigInteger y=decodeBigInt(keyData);
+
+ return generatePublicKey(new DSAPublicKeySpec(y, p, q, g));
+ }
+
+ @Override
+ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+ return SecurityUtils.getKeyFactory("DSA");
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
new file mode 100644
index 0000000..29be249
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/ECDSAPublicKeyEntryDecoder.java
@@ -0,0 +1,236 @@
+/*
+ * 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.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchProviderException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<ECPublicKey> {
+ public static final ECDSAPublicKeyEntryDecoder INSTANCE = new ECDSAPublicKeyEntryDecoder();
+
+ public ECDSAPublicKeyEntryDecoder() {
+ super(ECPublicKey.class,
+ Collections.unmodifiableList(
+ Arrays.asList(KeyPairProvider.ECDSA_SHA2_NISTP256, KeyPairProvider.ECDSA_SHA2_NISTP384, KeyPairProvider.ECDSA_SHA2_NISTP521)));
+ }
+
+ @Override
+ public ECPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException {
+ if (GenericUtils.isEmpty(keyType) || (!keyType.startsWith(ECCurves.ECDSA_SHA2_PREFIX))) {
+ throw new InvalidKeySpecException("Not an EC curve name: " + keyType);
+ }
+
+ if (!SecurityUtils.hasEcc()) {
+ throw new NoSuchProviderException("ECC not supported");
+ }
+
+ String keyCurveName = keyType.substring(ECCurves.ECDSA_SHA2_PREFIX.length());
+ ECParameterSpec paramSpec = ECCurves.getECParameterSpec(keyCurveName);
+ if (paramSpec == null) {
+ throw new InvalidKeySpecException("Unknown EC key curve name: " + keyCurveName);
+ }
+
+ // see rfc5656 section 3.1
+ String encCurveName = decodeString(keyData);
+ if (!keyCurveName.equals(encCurveName)) {
+ throw new InvalidKeySpecException("Mismatched key curve name (" + keyCurveName + ") vs. encoded one (" + encCurveName + ")");
+ }
+
+ byte[] octets = readRLEBytes(keyData);
+ final ECPoint w;
+ try {
+ if ((w = octetStringToEcPoint(octets)) == null) {
+ throw new InvalidKeySpecException("No ECPoint generated for curve=" + keyCurveName + " from octets=" + BufferUtils.printHex(':', octets));
+ }
+ } catch(RuntimeException e) {
+ throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")"
+ + " to generate ECPoint for curve=" + keyCurveName
+ + " from octets=" + BufferUtils.printHex(':', octets)
+ + ": " + e.getMessage());
+ }
+
+ return generatePublicKey(new ECPublicKeySpec(w, paramSpec));
+ }
+
+ @Override
+ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+ if (SecurityUtils.hasEcc()) {
+ return SecurityUtils.getKeyFactory("EC");
+ } else {
+ throw new NoSuchProviderException("ECC not supported");
+ }
+ }
+
+ // see rfc5480 section 2.2
+ public static final byte ECPOINT_UNCOMPRESSED_FORM_INDICATOR=0x04;
+ public static final byte ECPOINT_COMPRESSED_VARIANT_2=0x02;
+ public static final byte ECPOINT_COMPRESSED_VARIANT_3=0x02;
+
+ public static ECPoint octetStringToEcPoint(byte ... octets) {
+ if (GenericUtils.isEmpty(octets)) {
+ return null;
+ }
+
+ int startIndex=findFirstNonZeroIndex(octets);
+ if (startIndex < 0) {
+ throw new IllegalArgumentException("All zeroes ECPoint N/A");
+ }
+
+ byte indicator=octets[startIndex];
+ ECPointCompression compression=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 (GenericUtils.isEmpty(octets)) {
+ return (-1);
+ }
+
+ for (int index=0; index < octets.length; index++) {
+ if (octets[index] != 0) {
+ return index;
+ }
+ }
+
+ return (-1); // all zeroes
+ }
+ /**
+ * 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], 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);
+ }
+ };
+
+ private final byte indicatorValue;
+ public final byte getIndicatorValue() {
+ return indicatorValue;
+ }
+
+ ECPointCompression(byte indicator) {
+ indicatorValue = indicator;
+ }
+
+ public abstract ECPoint octetStringToEcPoint(byte[] octets, int startIndex, int len);
+
+ public static final Set<ECPointCompression> VALUES=
+ Collections.unmodifiableSet(EnumSet.allOf(ECPointCompression.class));
+ public static final 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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
new file mode 100644
index 0000000..4d9b2af
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.common.config.keys;
+
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import org.apache.sshd.common.Digest;
+import org.apache.sshd.common.cipher.ECCurves;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * Utility class for keys
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class KeyUtils {
+ private static final Map<String,PublicKeyEntryDecoder<? extends PublicKey>> byKeyTypeDecodersMap =
+ new TreeMap<String, PublicKeyEntryDecoder<? extends PublicKey>>(String.CASE_INSENSITIVE_ORDER);
+ private static final Map<Class<?>,PublicKeyEntryDecoder<? extends PublicKey>> byKeyClassDecodersMap =
+ new HashMap<Class<?>, PublicKeyEntryDecoder<? extends PublicKey>>();
+
+ static {
+ registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE);
+ registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE);
+ registerPublicKeyEntryDecoder(ECDSAPublicKeyEntryDecoder.INSTANCE);
+ }
+
+ private KeyUtils() {
+ throw new UnsupportedOperationException("No instance");
+ }
+
+ public static void registerPublicKeyEntryDecoder(PublicKeyEntryDecoder<? extends PublicKey> decoder) {
+ ValidateUtils.checkNotNull(decoder, "No decoder specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+ Class<?> keyType = ValidateUtils.checkNotNull(decoder.getKeyType(), "No key type declared", GenericUtils.EMPTY_OBJECT_ARRAY);
+ synchronized(byKeyClassDecodersMap) {
+ byKeyClassDecodersMap.put(keyType, decoder);
+ }
+
+ Collection<String> names = ValidateUtils.checkNotNullAndNotEmpty(decoder.getSupportedTypeNames(), "No supported key type", GenericUtils.EMPTY_OBJECT_ARRAY);
+ synchronized(byKeyTypeDecodersMap) {
+ for (String n : names) {
+ PublicKeyEntryDecoder<? extends PublicKey> prev = byKeyTypeDecodersMap.put(n, decoder);
+ if (prev != null) {
+ continue; // debug breakpoint
+ }
+ }
+ }
+ }
+
+ /**
+ * @param keyType The {@code OpenSSH} key type string - ignored if {@code null}/empty
+ * @return The registered {@link PublicKeyEntryDecoder} or {code null} if not found
+ */
+ public static PublicKeyEntryDecoder<? extends PublicKey> getPublicKeyEntryDecoder(String keyType) {
+ if (GenericUtils.isEmpty(keyType)) {
+ return null;
+ }
+
+ synchronized(byKeyTypeDecodersMap) {
+ return byKeyTypeDecodersMap.get(keyType);
+ }
+ }
+
+ /**
+ * @param key The {@link PublicKey} - 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<? extends PublicKey> getPublicKeyEntryDecoder(PublicKey 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 PublicKey}
+ * compatible type
+ * @return The registered {@link PublicKeyEntryDecoder} or {code null} if no match found
+ */
+ public static PublicKeyEntryDecoder<? extends PublicKey> getPublicKeyEntryDecoder(Class<?> keyType) {
+ if ((keyType == null) || (!PublicKey.class.isAssignableFrom(keyType))) {
+ return null;
+ }
+
+ synchronized(byKeyTypeDecodersMap) {
+ {
+ PublicKeyEntryDecoder<? extends PublicKey> decoder=byKeyTypeDecodersMap.get(keyType);
+ if (decoder != null) {
+ return decoder;
+ }
+ }
+
+ // in case it is a derived class
+ for (PublicKeyEntryDecoder<? extends PublicKey> decoder : byKeyTypeDecodersMap.values()) {
+ Class<?> t = decoder.getKeyType();
+ if (t.isAssignableFrom(keyType)) {
+ return decoder;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the public key fingerprint
+ *
+ * @param key the public key - ignored if {@code null}
+ * @return the fingerprint or {@code null} if no key
+ */
+ public static String getFingerPrint(PublicKey key) {
+ if (key == null) {
+ return null;
+ }
+
+ try {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putRawPublicKey(key);
+ Digest md5 = BuiltinDigests.md5.create();
+ md5.init();
+ md5.update(buffer.array(), 0, buffer.wpos());
+ byte[] data = md5.digest();
+ return BufferUtils.printHex(data, 0, data.length, ':');
+ } catch(Exception e) {
+ return "Unable to compute fingerprint";
+ }
+ }
+
+ /**
+ * Retrieve the key type
+ *
+ * @param kp a key pair
+ * @return the key type
+ */
+ public static String getKeyType(KeyPair kp) {
+ return getKeyType(kp.getPrivate() != null ? kp.getPrivate() : kp.getPublic());
+ }
+
+ /**
+ * Retrieve the key type
+ *
+ * @param key a public or private key
+ * @return the key type
+ */
+ public static String getKeyType(Key key) {
+ 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();
+ return ECCurves.ECDSA_SHA2_PREFIX + ECCurves.getCurveName(ecSpec);
+ }
+ return null;
+ }
+
+ /**
+ * @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 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 {
+ 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 if (Objects.equals(k1.getPublicExponent(), k2.getPublicExponent())
+ && Objects.equals(k1.getModulus(), k2.getModulus())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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 if (Objects.equals(k1.getY(), k2.getY())
+ && compareDSAParams(k1.getParams(), k2.getParams())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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 if (Objects.equals(p1.getG(), p2.getG())
+ && Objects.equals(p1.getP(), p2.getP())
+ && Objects.equals(p1.getQ(), p2.getQ())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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 if (Objects.equals(k1.getW(), k2.getW())
+ && compareECParams(k1.getParams(), k2.getParams())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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 if (Objects.equals(s1.getOrder(), s2.getOrder())
+ && (s1.getCofactor() == s2.getCofactor())
+ && Objects.equals(s1.getGenerator(), s2.getGenerator())
+ && Objects.equals(s1.getCurve(), s2.getCurve())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
new file mode 100644
index 0000000..a3dbf8b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntry.java
@@ -0,0 +1,218 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.Base64;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * <P>Represents a {@link PublicKey} whose data is formatted according to
+ * the <A HREF="http://en.wikibooks.org/wiki/OpenSSH">OpenSSH</A> format:</P></BR>
+ * <CODE><PRE>
+ * <key-type> <base64-encoded-public-key-data>
+ * </CODE></PRE>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PublicKeyEntry implements Serializable {
+ 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;
+ }
+
+ /**
+ * @return The resolved {@link PublicKey} - never {@code null}.
+ * <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() throws IOException, GeneralSecurityException {
+ String kt = getKeyType();
+ PublicKeyEntryDecoder<? extends PublicKey> decoder = KeyUtils.getPublicKeyEntryDecoder(kt);
+ if (decoder == null) {
+ throw new InvalidKeySpecException("No decoder registered for key type=" + kt);
+ }
+
+ byte[] data = getKeyData();
+ PublicKey key = decoder.decodePublicKey(data);
+ if (key == null) {
+ throw new InvalidKeyException("No key of type=" + kt + " decoded for data=" + BufferUtils.printHex(':', data));
+ }
+
+ 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;
+ }
+
+ if (Objects.equals(getKeyType(), e.getKeyType())
+ && Arrays.equals(getKeyData(), e.getKeyData())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (this == obj)
+ return true;
+ if (getClass() != obj.getClass())
+ return false;
+
+ if (isEquivalent((PublicKeyEntry) obj)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ byte[] data = getKeyData();
+ return getKeyType() + " " + (GenericUtils.isEmpty(data) ? "<no-key>" : Base64.encodeToString(data));
+ }
+
+ /**
+ * @param data 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 final PublicKeyEntry parsePublicKeyEntry(String data) throws IllegalArgumentException {
+ if (GenericUtils.isEmpty(data)) {
+ return null;
+ } else {
+ return parsePublicKeyEntry(new PublicKeyEntry(), data);
+ }
+ }
+
+ /**
+ * @param entry The {@link PublicKeyEntry} whose contents are to be
+ * updated - ignored if {@code null}
+ * @param data 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 final <E extends PublicKeyEntry> E parsePublicKeyEntry(E entry, String data) throws IllegalArgumentException {
+ 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();
+ byte[] keyData = Base64.decodeString(b64Data);
+ if (GenericUtils.isEmpty(keyData)) {
+ throw new IllegalArgumentException("Bad format (no BASE64 key data): " + data);
+ }
+
+ entry.setKeyType(keyType);
+ entry.setKeyData(keyData);
+ return entry;
+ }
+
+ /**
+ * 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 class LazyDefaultKeysFolderHolder {
+ private static final File folder=
+ new File(System.getProperty("user.home") + File.separator + STD_KEYFILE_FOLDER_NAME);
+ }
+
+ /**
+ * @return The default OpenSSH folder used to hold key files - e.g.,
+ * {@code known_hosts}, {@code authorized_keys}, etc.
+ */
+ @SuppressWarnings("synthetic-access")
+ public static final File getDefaultKeysFolder() {
+ return LazyDefaultKeysFolderHolder.folder;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.java
new file mode 100644
index 0000000..1ac3ab3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/PublicKeyEntryDecoder.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.keys;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.util.Collection;
+
+/**
+ * Represents a decoder of an {@code OpenSSH} encoded key data
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PublicKeyEntryDecoder<K extends PublicKey> {
+ /**
+ * @return The {@link Class} of the {@link PublicKey} that is the result
+ * of decoding
+ */
+ Class<K> getKeyType();
+
+ /**
+ * @return The {@link Collection} of {@code OpenSSH} key type names that
+ * are supported by this decoder - e.g., ECDSA keys have several curve names.
+ * <B>Caveat:</B> this collection may be un-modifiable...
+ */
+ Collection<String> getSupportedTypeNames();
+
+ /**
+ * @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
+ */
+ K decodePublicKey(byte ... keyData) throws IOException, GeneralSecurityException;
+ K decodePublicKey(byte[] keyData, int offset, int length) throws IOException, GeneralSecurityException;
+ K decodePublicKey(InputStream keyData) throws IOException, GeneralSecurityException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
new file mode 100644
index 0000000..08d5bc7
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/RSAPublicKeyDecoder.java
@@ -0,0 +1,61 @@
+/*
+ * 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.io.InputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Collections;
+
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class RSAPublicKeyDecoder extends AbstractPublicKeyEntryDecoder<RSAPublicKey> {
+ public static final RSAPublicKeyDecoder INSTANCE = new RSAPublicKeyDecoder();
+
+ public RSAPublicKeyDecoder() {
+ super(RSAPublicKey.class, Collections.unmodifiableList(Collections.singletonList(KeyPairProvider.SSH_RSA)));
+ }
+
+ @Override
+ public RSAPublicKey decodePublicKey(String keyType, InputStream keyData) throws IOException, GeneralSecurityException {
+ if (!KeyPairProvider.SSH_RSA.equals(keyType)) { // just in case we were invoked directly
+ throw new InvalidKeySpecException("Unepected key type: " + keyType);
+ }
+
+ BigInteger e=decodeBigInt(keyData);
+ BigInteger n=decodeBigInt(keyData);
+
+ return generatePublicKey(new RSAPublicKeySpec(n, e));
+ }
+
+ @Override
+ public KeyFactory getKeyFactoryInstance() throws GeneralSecurityException {
+ return SecurityUtils.getKeyFactory("RSA");
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
index 9503486..0d4b5ef 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/BuiltinDHFactories.java
@@ -110,7 +110,7 @@ public enum BuiltinDHFactories implements DHFactory {
if (!GenericUtils.isEmpty(params)) {
throw new IllegalArgumentException("No accepted parameters for " + getName());
}
- return new ECDH(ECCurves.EllipticCurves.nistp256);
+ return new ECDH(ECCurves.NISTP256);
}
@Override
@@ -124,7 +124,7 @@ public enum BuiltinDHFactories implements DHFactory {
if (!GenericUtils.isEmpty(params)) {
throw new IllegalArgumentException("No accepted parameters for " + getName());
}
- return new ECDH(ECCurves.EllipticCurves.nistp384);
+ return new ECDH(ECCurves.NISTP384);
}
@Override
@@ -138,7 +138,7 @@ public enum BuiltinDHFactories implements DHFactory {
if (!GenericUtils.isEmpty(params)) {
throw new IllegalArgumentException("No accepted parameters for " + getName());
}
- return new ECDH(ECCurves.EllipticCurves.nistp521);
+ return new ECDH(ECCurves.NISTP521);
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
index 08dc695..741f99f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/ECDH.java
@@ -51,7 +51,11 @@ public class ECDH extends AbstractDH {
private KeyAgreement myKeyAgree;
public ECDH() throws Exception {
- this(null);
+ this((ECParameterSpec) null);
+ }
+
+ public ECDH(String curveName) throws Exception {
+ this(ValidateUtils.checkNotNull(ECCurves.getECParameterSpec(curveName), "Unknown curve name: %s", curveName));
}
public ECDH(ECParameterSpec paramSpec) throws Exception {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
index 4a5dd6e..03ba5f2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/AbstractKeyPairProvider.java
@@ -22,9 +22,9 @@ import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.util.AbstractLoggingBean;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.KeyUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
index 445fc11..37af11c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java
@@ -21,6 +21,8 @@ package org.apache.sshd.common.keyprovider;
import java.security.KeyPair;
import java.util.Collections;
+import org.apache.sshd.common.cipher.ECCurves;
+
/**
* Provider for key pairs. This provider is used on the server side to provide
* the host key, or on the client side to provide the user key.
@@ -42,17 +44,17 @@ public interface KeyPairProvider {
/**
* SSH identifier for EC keys in NIST curve P-256
*/
- String ECDSA_SHA2_NISTP256 = "ecdsa-sha2-nistp256";
+ String ECDSA_SHA2_NISTP256 = ECCurves.ECDSA_SHA2_PREFIX + ECCurves.NISTP256;
/**
* SSH identifier for EC keys in NIST curve P-384
*/
- String ECDSA_SHA2_NISTP384 = "ecdsa-sha2-nistp384";
+ String ECDSA_SHA2_NISTP384 = ECCurves.ECDSA_SHA2_PREFIX + ECCurves.NISTP384;
/**
* SSH identifier for EC keys in NIST curve P-521
*/
- String ECDSA_SHA2_NISTP521 = "ecdsa-sha2-nistp521";
+ String ECDSA_SHA2_NISTP521 = ECCurves.ECDSA_SHA2_PREFIX + ECCurves.NISTP521;
/**
* Load available keys.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java b/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
index ffc476c..a7cc8ca 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/Base64.java
@@ -19,6 +19,7 @@
*/
package org.apache.sshd.common.util;
+import java.nio.charset.StandardCharsets;
import java.security.InvalidParameterException;
/**
@@ -33,6 +34,7 @@ import java.security.InvalidParameterException;
* This class was
* @author Apache Software Foundation commons codec (http://commons.apache.org/codec/)
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ * TODO replace this class with {@code java.util.Base64} when upgrading to JDK 1.8
*/
public class Base64 {
@@ -168,6 +170,10 @@ public class Base64 {
return true;
}
+ public static String encodeToString(byte ... bytes) {
+ return new String(encodeBase64(bytes), StandardCharsets.UTF_8);
+ }
+
/**
* Encodes binary data using the base64 algorithm but
* does not chunk the output.
@@ -334,6 +340,14 @@ public class Base64 {
return encodedData;
}
+ public static byte[] decodeString(String s) {
+ if (GenericUtils.isEmpty(s)) {
+ return GenericUtils.EMPTY_BYTE_ARRAY;
+ } else {
+ return decodeBase64(s.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
/**
* Decodes Base64 data into octects
*
@@ -345,8 +359,8 @@ public class Base64 {
base64Data = discardNonBase64(base64Data);
// handle the edge case, so we don't have to worry about it later
- if (base64Data.length == 0) {
- return new byte[0];
+ if (GenericUtils.isEmpty(base64Data)) {
+ return GenericUtils.EMPTY_BYTE_ARRAY;
}
int numberQuadruple = base64Data.length / FOURBYTE;
@@ -363,7 +377,7 @@ public class Base64 {
// ignore the '=' padding
while (base64Data[lastData - 1] == PAD) {
if (--lastData == 0) {
- return new byte[0];
+ return GenericUtils.EMPTY_BYTE_ARRAY;
}
}
decodedData = new byte[lastData - numberQuadruple];
@@ -437,22 +451,52 @@ public class Base64 {
* encoded data."
*
* @param data The base-64 encoded data to groom
- * @return The data, less non-base64 characters (see RFC 2045).
+ * @return The data, less non-base64 characters (see RFC 2045) -
+ * may be same as input if all data was base-64
*/
- static byte[] discardNonBase64(byte[] data) {
- byte groomedData[] = new byte[data.length];
+ public static byte[] discardNonBase64(byte[] data) {
+ if (GenericUtils.isEmpty(data)) {
+ return data;
+ }
+
+ byte groomedData[] = null;
int bytesCopied = 0;
for (int i = 0; i < data.length; i++) {
- if (isBase64(data[i])) {
- groomedData[bytesCopied++] = data[i];
+ byte b = data[i];
+
+ if (isBase64(b)) {
+ if (groomedData != null) {
+ // we had to filter out some non-BASE64 bytes
+ groomedData[bytesCopied++] = b;
+ }
+ } else {
+ // this means ALL the characters up to this index were BASE64
+ if (groomedData == null) {
+ groomedData = new byte[data.length - 1 /* the current character, which is NOT BASE64 */];
+
+ if ((bytesCopied=i) > 0) {
+ System.arraycopy(data, 0, groomedData, 0, bytesCopied);
+ }
+ }
}
}
- byte packedData[] = new byte[bytesCopied];
+ if (groomedData == null) {
+ return data; // all characters where BASE64
+ }
- System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+ if (bytesCopied <= 0) {
+ return GenericUtils.EMPTY_BYTE_ARRAY;
+ }
+
+ // if we were lucky and only ONE character was groomed
+ if (bytesCopied == groomedData.length) {
+ return groomedData;
+ }
+ byte packedData[] = new byte[bytesCopied];
+ System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
return packedData;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index c75235b..657a6ad 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -289,4 +289,48 @@ public class GenericUtils {
return set;
}
+
+ public static final String QUOTES="\"'";
+
+ /**
+ * @param s The {@link CharSequence} to be checked
+ * @return If the sequence contains any of the {@link #QUOTES}
+ * on <U>both</U> ends, then they are stripped, otherwise
+ * nothing is done
+ * @see #stripDelimiters(CharSeqeuence, char)
+ */
+ public static final CharSequence stripQuotes(CharSequence s) {
+ if (isEmpty(s)) {
+ return s;
+ }
+
+ for (int index=0; index < QUOTES.length(); index++) {
+ char delim = QUOTES.charAt(index);
+ CharSequence v = stripDelimiters(s, delim);
+ if (v != s) { // if stripped one don't continue
+ return v;
+ }
+ }
+
+ return s;
+ }
+
+ /**
+ * @param s The {@link CharSequence} to be checked
+ * @param delim The expected delimiter
+ * @return If the sequence contains the delimiter on <U>both</U> ends,
+ * then it is are stripped, otherwise nothing is done
+ */
+ public static final CharSequence stripDelimiters(CharSequence s, char delim) {
+ if (isEmpty(s) || (s.length() < 2)) {
+ return s;
+ }
+
+ int lastPos = s.length() - 1;
+ if ((s.charAt(0) != delim) || (s.charAt(lastPos) != delim)) {
+ return s;
+ } else {
+ return s.subSequence(1, lastPos);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
index 7483542..da17c22 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/IoUtils.java
@@ -19,6 +19,7 @@
package org.apache.sshd.common.util;
import java.io.Closeable;
+import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -226,4 +227,65 @@ public class IoUtils {
return null;
}
}
+
+ /**
+ * Read the requested number of bytes or fail if there are not enough left.
+ * @param input where to read input from
+ * @param buffer destination
+ * @throws IOException if there is a problem reading the file
+ * @throws EOFException if the number of bytes read was incorrect
+ */
+ public static void readFully(InputStream input, byte[] buffer) throws IOException {
+ readFully(input, buffer, 0, buffer.length);
+ }
+
+ /**
+ * Read the requested number of bytes or fail if there are not enough left.
+ * @param input where to read input from
+ * @param buffer destination
+ * @param offset initial offset into buffer
+ * @param length length to read, must be >= 0
+ * @throws IOException if there is a problem reading the file
+ * @throws EOFException if the number of bytes read was incorrect
+ */
+ public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException {
+ int actual = read(input, buffer, offset, length);
+ if (actual != length) {
+ throw new EOFException("Premature EOF - expected=" + length + ", actual=" + actual);
+ }
+ }
+
+ /**
+ * Read as many bytes as possible until EOF or achieved required length
+ * @param input where to read input from
+ * @param buffer destination
+ * @return actual length read; may be less than requested if EOF was reached
+ * @throws IOException if a read error occurs
+ */
+ public static int read(InputStream input, byte[] buffer) throws IOException {
+ return read(input, buffer, 0, buffer.length);
+ }
+
+ /**
+ * Read as many bytes as possible until EOF or achieved required length
+ * @param input where to read input from
+ * @param buffer destination
+ * @param offset initial offset into buffer
+ * @param length length to read - ignored if non-positive
+ * @return actual length read; may be less than requested if EOF was reached
+ * @throws IOException if a read error occurs
+ */
+ public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException {
+ for (int remaining = length, curOffset = offset; remaining > 0; ) {
+ int count = input.read(buffer, curOffset, remaining);
+ if (count == (-1)) { // EOF before achieved required length
+ return curOffset - offset;
+ }
+
+ remaining -= count;
+ curOffset += count;
+ }
+
+ return length;
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
deleted file mode 100644
index 6bea4d3..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/KeyUtils.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.util;
-
-import java.security.Key;
-import java.security.KeyPair;
-import java.security.PublicKey;
-import java.security.interfaces.DSAKey;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.ECParameterSpec;
-
-import org.apache.sshd.common.Digest;
-import org.apache.sshd.common.cipher.ECCurves;
-import org.apache.sshd.common.digest.BuiltinDigests;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.common.util.buffer.Buffer;
-import org.apache.sshd.common.util.buffer.BufferUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-
-/**
- * Utility class for keys
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class KeyUtils {
-
- /**
- * Retrieve the public key fingerprint
- *
- * @param key the public key - ignored if {@code null}
- * @return the fingerprint or {@code null} if no key
- */
- public static String getFingerPrint(PublicKey key) {
- if (key == null) {
- return null;
- }
-
- try {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putRawPublicKey(key);
- Digest md5 = BuiltinDigests.md5.create();
- md5.init();
- md5.update(buffer.array(), 0, buffer.wpos());
- byte[] data = md5.digest();
- return BufferUtils.printHex(data, 0, data.length, ':');
- } catch(Exception e) {
- return "Unable to compute fingerprint";
- }
- }
-
- /**
- * Retrieve the key type
- *
- * @param kp a key pair
- * @return the key type
- */
- public static String getKeyType(KeyPair kp) {
- return getKeyType(kp.getPrivate() != null ? kp.getPrivate() : kp.getPublic());
- }
-
- /**
- * Retrieve the key type
- *
- * @param key a public or private key
- * @return the key type
- */
- public static String getKeyType(Key key) {
- 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();
- return ECCurves.ECDSA_SHA2_PREFIX + ECCurves.getCurveName(ecSpec);
- }
- return null;
- }
-
- private KeyUtils() {
- }
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
new file mode 100644
index 0000000..83c1656
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java
@@ -0,0 +1,169 @@
+/*
+ * 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.util.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.common.util.AbstractLoggingBean;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Watches over changes for a file and re-loads them if file has changed - including
+ * if file is deleted or (re-)created
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ModifiableFileWatcher extends AbstractLoggingBean {
+ private final Path file;
+ private final AtomicBoolean lastExisted = new AtomicBoolean(false);
+ private final AtomicLong lastSize = new AtomicLong(Long.MIN_VALUE);
+ private final AtomicLong lastModified = new AtomicLong(-1L);
+ protected final LinkOption[] options;
+
+ public ModifiableFileWatcher(File file) {
+ this(ValidateUtils.checkNotNull(file, "No file to watch", GenericUtils.EMPTY_OBJECT_ARRAY).toPath());
+ }
+
+ public ModifiableFileWatcher(Path file) {
+ this(file, IoUtils.getLinkOptions(false));
+ }
+
+ public ModifiableFileWatcher(Path file, LinkOption ... options) {
+ this.file = ValidateUtils.checkNotNull(file, "No path to watch", GenericUtils.EMPTY_OBJECT_ARRAY);
+ // use a clone to avoid being sensitive to changes in the passed array
+ this.options = (options == null) ? IoUtils.EMPTY_OPTIONS : options.clone();
+ }
+
+ /**
+ * @return The watched {@link Path}
+ */
+ public final Path getPath() {
+ return file;
+ }
+
+ public final boolean exists() throws IOException {
+ return Files.exists(getPath(), options);
+ }
+
+ public final long size() throws IOException {
+ if (exists()) {
+ return Files.size(getPath());
+ } else {
+ return (-1L);
+ }
+ }
+
+ public final FileTime lastModified() throws IOException {
+ if (exists()) {
+ BasicFileAttributes attrs = Files.readAttributes(getPath(), BasicFileAttributes.class, options);
+ return attrs.lastModifiedTime();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @return {@code true} if the watched file has probably been changed
+ * @throws IOException If failed to query file data
+ */
+ public boolean checkReloadRequired() throws IOException {
+ boolean exists = exists();
+ // if existence state changed from last time
+ if (exists != lastExisted.getAndSet(exists)) {
+ return true;
+ }
+
+ if (!exists) {
+ // file did not exist and still does not exist
+ resetReloadAttributes();
+ return false;
+ }
+
+ long size = size();
+ if (size < 0L) {
+ // means file no longer exists
+ resetReloadAttributes();
+ return true;
+ }
+
+ // if size changed then obviously need reload
+ if (size != lastSize.getAndSet(size)) {
+ return true;
+ }
+
+ FileTime modifiedTime = lastModified();
+ if (modifiedTime == null) {
+ // means file no longer exists
+ resetReloadAttributes();
+ return true;
+ }
+
+ long timestamp = modifiedTime.toMillis();
+ if (timestamp != lastModified.getAndSet(timestamp)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Resets the state attributes used to detect changes to the initial
+ * construction values - i.e., file assumed not to exist and no known
+ * size of modify time
+ */
+ public void resetReloadAttributes() {
+ lastExisted.set(false);
+ lastSize.set(Long.MIN_VALUE);
+ lastModified.set(-1L);
+ }
+
+ /**
+ * May be called to refresh the state attributes used to detect changes
+ * e.g., file existence, size and last-modified time once re-loading is
+ * successfully completed. If the file does not exist then the attributes
+ * are reset to an "unknown" state.
+ * @throws IOException If failed to access the file (if exists)
+ * @see #resetReloadAttributes()
+ */
+ public void updateReloadAttributes() throws IOException {
+ if (exists()) {
+ long size = size();
+ FileTime modifiedTime = lastModified();
+
+ if ((size >= 0L) && (modifiedTime != null)) {
+ lastExisted.set(true);
+ lastSize.set(size);
+ lastModified.set(modifiedTime.toMillis());
+ return;
+ }
+ }
+
+ resetReloadAttributes();
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
index 78c8adf..d6224fb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/PublickeyAuthenticator.java
@@ -19,9 +19,12 @@
package org.apache.sshd.server;
import java.security.PublicKey;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.util.AbstractLoggingBean;
-import org.apache.sshd.common.util.KeyUtils;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.server.session.ServerSession;
/**
@@ -42,6 +45,42 @@ public interface PublickeyAuthenticator {
boolean authenticate(String username, PublicKey key, ServerSession session);
/**
+ * Checks against a {@link Collection} of {@link PublicKey}s
+ */
+ public static class KeySetPublickeyAuthenticator extends AbstractLoggingBean implements PublickeyAuthenticator {
+ private final Collection<? extends PublicKey> keySet;
+
+ public KeySetPublickeyAuthenticator(Collection<? extends PublicKey> keySet) {
+ this.keySet = (keySet == null) ? Collections.<PublicKey>emptyList() : keySet;
+ }
+
+ public final Collection<? extends PublicKey> getKeySet() {
+ return keySet;
+ }
+
+ @Override
+ public boolean authenticate(String username, PublicKey key, ServerSession session) {
+ return authenticate(username, key, session, getKeySet());
+ }
+
+ public boolean authenticate(String username, PublicKey key, ServerSession session, Collection<? extends PublicKey> keys) {
+ if (GenericUtils.isEmpty(keys)) {
+ if (log.isDebugEnabled()) {
+ log.debug("authenticate(" + username + ")[" + session + "] no keys");
+ }
+
+ return false;
+ }
+
+ PublicKey matchKey = KeyUtils.findMatchingKey(key, keys);
+ boolean matchFound = (matchKey != null);
+ if (log.isDebugEnabled()) {
+ log.debug("authenticate(" + username + ")[" + session + "] match found=" + matchFound);
+ }
+ return matchFound;
+ }
+ }
+ /**
* Returns the same constant result {@code true/false} regardless
*/
public static abstract class StaticPublickeyAuthenticator extends AbstractLoggingBean implements PublickeyAuthenticator {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e12434d0/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java b/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
index d836f4e..0e0880b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/CachingPublicKeyAuthenticator.java
@@ -28,8 +28,7 @@ import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
/**
- * TODO Add javadoc
- *
+ * Caches the result per session
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener {