summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGunnar Wagenknecht2011-12-13 08:32:53 (EST)
committer Lazar Kirchev2011-12-13 08:32:53 (EST)
commit9feee7d4bc2425d1592b49ebd2d91f7964bc5c28 (patch)
tree9878a2e11d787a33d673b5b1f114c8292669a46a
parent0c431ddd8c9c2db4f3b12f15a899f9b15403f42a (diff)
downloadrt.equinox.bundles-9feee7d4bc2425d1592b49ebd2d91f7964bc5c28.zip
rt.equinox.bundles-9feee7d4bc2425d1592b49ebd2d91f7964bc5c28.tar.gz
rt.equinox.bundles-9feee7d4bc2425d1592b49ebd2d91f7964bc5c28.tar.bz2
Support for authorized_keys file
If the system property (bug 366188) 'ssh.server.authorized_keys' is specified it may point to a file which contains a list of keys that are allowed to connect to the SSH console. The file will be read every time a connection is made so that it can be modified dynamically at runtime.
-rw-r--r--[-rwxr-xr-x]bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/internal/ssh/AuthorizedKeys.java214
-rw-r--r--[-rwxr-xr-x]bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java76
3 files changed, 281 insertions, 10 deletions
diff --git a/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF
index 917ea0a..9de3d86 100755..100644
--- a/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.console.ssh/META-INF/MANIFEST.MF
@@ -13,6 +13,7 @@ Import-Package: javax.security.auth;resolution:=optional,
javax.security.auth.login;resolution:=optional,
javax.security.auth.spi;resolution:=optional,
org.apache.felix.service.command;status=provisional;version="0.8.0",
+ org.apache.mina.util;version="2.0.0";resolution:=optional,
org.apache.sshd;version="0.5.0";resolution:=optional,
org.apache.sshd.common;version="0.5.0";resolution:=optional,
org.apache.sshd.server;version="0.5.0";resolution:=optional,
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/internal/ssh/AuthorizedKeys.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/internal/ssh/AuthorizedKeys.java
new file mode 100644
index 0000000..1e3284d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/internal/ssh/AuthorizedKeys.java
@@ -0,0 +1,214 @@
+/**
+ * Copyright (c) 2011 Gunnar Wagenknecht and others.
+ * All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v1.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Gunnar Wagenknecht - initial API and implementation
+ */
+package org.eclipse.equinox.console.internal.ssh;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Scanner;
+
+import org.apache.mina.util.Base64;
+
+/**
+ * Reader for 'authorized_keys' file as typically found on Unix systems.
+ */
+public class AuthorizedKeys {
+
+ public static enum KeyType {
+ RSA, DSA
+ }
+
+ public static class ParseKeyException extends IOException {
+
+ /** serialVersionUID */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param message
+ * @param cause
+ */
+ public ParseKeyException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+
+ private static final String PREFIX_KEY_TYPE = "ssh-";
+ private static final String PREFIX_KEY_TYPE_DSA = "ssh-dsa ";
+ private static final String PREFIX_KEY_TYPE_RSA = "ssh-rsa ";
+ private static final String NEWLINE = "\n";
+
+ private static byte[] asBytes(final String string) {
+ try {
+ return string.getBytes("UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ return string.getBytes();
+ }
+ }
+
+ private static String asString(final byte[] bytes) {
+ try {
+ return new String(bytes, "UTF-8");
+ } catch (final UnsupportedEncodingException e) {
+ return new String(bytes);
+ }
+ }
+
+ public static void main(final String[] args) {
+ try {
+ final List<PublicKey> keys = new AuthorizedKeys(args[0]).getKeys();
+ for (final PublicKey key : keys) {
+ System.out.println(key);
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private final List<PublicKey> keys;
+
+ /**
+ * Creates a new instance.
+ *
+ * @throws FileNotFoundException
+ */
+ public AuthorizedKeys(final String authorizedKeysFile) throws FileNotFoundException, IOException {
+ // read file line-by-line
+ final File file = new File(authorizedKeysFile);
+ final Scanner scanner = new Scanner(file);
+ scanner.useDelimiter(NEWLINE);
+ int lineNumber = 0;
+ final List<PublicKey> keys = new ArrayList<PublicKey>();
+
+ try {
+ while (scanner.hasNext()) {
+ lineNumber++;
+
+ // get line (without leading and trailing blanks)
+ final String line = scanner.next().trim();
+
+ // ignore blank line and comments
+ if ((line.length() == 0) || (line.charAt(0) == '#')) {
+ continue;
+ }
+
+ // read key
+ try {
+ keys.add(readPublicKey(line));
+ } catch (final Exception e) {
+ throw new ParseKeyException("Line " + lineNumber + ": " + e.getMessage(), e);
+ }
+ }
+ } finally {
+ scanner.close();
+ }
+
+ this.keys = Collections.unmodifiableList(keys);
+ }
+
+ /**
+ * Returns the keys.
+ *
+ * @return the keys
+ */
+ public List<PublicKey> getKeys() {
+ return keys;
+ }
+
+ private BigInteger readBigInteger(final ByteBuffer buffer) {
+ final int len = buffer.getInt();
+ final byte[] bytes = new byte[len];
+ buffer.get(bytes);
+ final BigInteger pubExp = new BigInteger(bytes);
+ return pubExp;
+ }
+
+ private PublicKey readPublicKey(String line) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+ // [options] <type> <base64> <comment>
+ final KeyType type;
+ final byte[] key;
+
+ // skip options (if any)
+ if (!line.startsWith(PREFIX_KEY_TYPE)) {
+ final int keyTypeStart = line.indexOf(PREFIX_KEY_TYPE);
+ if (keyTypeStart == -1) {
+ throw new IOException("missing key type");
+ }
+ line = line.substring(keyTypeStart);
+ }
+
+ // key type
+ if (line.startsWith(PREFIX_KEY_TYPE_DSA)) {
+ line = line.substring(PREFIX_KEY_TYPE_DSA.length());
+ type = KeyType.DSA;
+ } else if (line.startsWith(PREFIX_KEY_TYPE_RSA)) {
+ line = line.substring(PREFIX_KEY_TYPE_RSA.length());
+ type = KeyType.RSA;
+ } else {
+ throw new IOException("unsupported key type");
+ }
+
+ // key
+ final int keyEndIdx = line.indexOf(' ');
+ if (keyEndIdx != -1) {
+ key = Base64.decodeBase64(asBytes(line.substring(0, keyEndIdx)));
+ line = line.substring(keyEndIdx + 1);
+ } else {
+ key = Base64.decodeBase64(asBytes(line));
+ }
+
+ // wrap key into byte buffer
+ final ByteBuffer buffer = ByteBuffer.wrap(key);
+
+ // skip key type
+ readString(buffer);
+
+ // parse key
+ switch (type) {
+ case RSA:
+ // exponent + modulus
+ final BigInteger pubExp = readBigInteger(buffer);
+ final BigInteger mod = readBigInteger(buffer);
+ return KeyFactory.getInstance(KeyType.RSA.name()).generatePublic(new RSAPublicKeySpec(mod, pubExp));
+ case DSA:
+ // p + q+ g + y
+ final BigInteger p = readBigInteger(buffer);
+ final BigInteger q = readBigInteger(buffer);
+ final BigInteger g = readBigInteger(buffer);
+ final BigInteger y = readBigInteger(buffer);
+ return KeyFactory.getInstance(KeyType.DSA.name()).generatePublic(new DSAPublicKeySpec(y, p, q, g));
+ default:
+ throw new IOException("not implemented: " + type);
+ }
+ }
+
+ private String readString(final ByteBuffer buffer) {
+ final int len = buffer.getInt();
+ final byte[] bytes = new byte[len];
+ buffer.get(bytes);
+ return asString(bytes);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java
index 088a31f..0927651 100755..100644
--- a/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java
+++ b/bundles/org.eclipse.equinox.console.ssh/src/org/eclipse/equinox/console/ssh/SshServ.java
@@ -4,22 +4,31 @@
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* Lazar Kirchev, SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.console.ssh;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
import java.util.List;
+import org.eclipse.equinox.console.internal.ssh.AuthorizedKeys;
+
+import org.osgi.framework.BundleContext;
+
import org.apache.felix.service.command.CommandProcessor;
import org.apache.sshd.SshServer;
import org.apache.sshd.server.PasswordAuthenticator;
+import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.osgi.framework.BundleContext;
+import org.apache.sshd.server.session.ServerSession;
/**
* This class configures and start an ssh server
@@ -30,17 +39,18 @@ public class SshServ extends Thread {
private String host;
private SshServer sshServer = null;
private SshShellFactory shellFactory = null;
-
+
private static final String SSH_KEYSTORE_PROP = "ssh.server.keystore";
private static final String SSH_KEYSTORE_PROP_DEFAULT = "hostkey.ser";
+ private static final String SSH_AUTHORIZED_KEYS_FILE_PROP = "ssh.server.authorized_keys";
private static final String EQUINOX_CONSOLE_DOMAIN = "equinox_console";
-
+
public SshServ(List<CommandProcessor> processors, BundleContext context, String host, int port) {
this.host = host;
this.port = port;
shellFactory = new SshShellFactory(processors, context);
}
-
+
public void run() throws RuntimeException {
sshServer = SshServer.setUpDefaultServer();
if (host != null) {
@@ -50,32 +60,78 @@ public class SshServ extends Thread {
sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(System.getProperty(SSH_KEYSTORE_PROP, SSH_KEYSTORE_PROP_DEFAULT)));
sshServer.setShellFactory(shellFactory);
sshServer.setPasswordAuthenticator(createJaasPasswordAuthenticator());
+ sshServer.setPublickeyAuthenticator(createSimpleAuthorizedKeysAuthenticator());
try {
sshServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
-
- public synchronized void stopSshServer() {
+
+
+ public synchronized void stopSshServer() {
try {
sshServer.stop(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
-
+
public synchronized void addCommandProcessor(CommandProcessor processor) {
shellFactory.addCommandProcessor(processor);
}
-
+
public synchronized void removeCommandProcessor(CommandProcessor processor) {
shellFactory.removeCommandProcessor(processor);
}
-
+
private PasswordAuthenticator createJaasPasswordAuthenticator() {
JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
jaasPasswordAuthenticator.setDomain(EQUINOX_CONSOLE_DOMAIN);
return jaasPasswordAuthenticator;
}
+
+ private PublickeyAuthenticator createSimpleAuthorizedKeysAuthenticator() {
+ // check if property is set
+ final String authorizedKeysFile = System.getProperty(SSH_AUTHORIZED_KEYS_FILE_PROP);
+ if (null == authorizedKeysFile)
+ return null;
+
+ // dynamically read key file at each login attempt
+ return new PublickeyAuthenticator() {
+ public boolean authenticate(String username, PublicKey key, ServerSession session) {
+ try {
+ AuthorizedKeys keys = new AuthorizedKeys(authorizedKeysFile);
+ for (PublicKey authorizedKey : keys.getKeys()) {
+ if (isSameKey(authorizedKey, key)) {
+ return true;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ System.err.println("Configured authorized_keys file not found! " + e.getMessage());
+ } catch (IOException e) {
+ System.err.println("Please check authorized_keys file! " + e.getMessage());
+ }
+ return false;
+ }
+
+ private boolean isSameKey(PublicKey k1, PublicKey k2) throws IOException {
+ if ((k1 instanceof DSAPublicKey) && (k2 instanceof DSAPublicKey)) {
+ return isSameDSAKey((DSAPublicKey) k1, (DSAPublicKey) k2);
+ } else if ((k1 instanceof RSAPublicKey) && (k2 instanceof RSAPublicKey)) {
+ return isSameRSAKey((RSAPublicKey) k1, (RSAPublicKey) k2);
+ } else {
+ throw new IOException("Unsupported key types detected!");
+ }
+ }
+
+ private boolean isSameRSAKey(RSAPublicKey k1, RSAPublicKey k2) {
+ return k1.getPublicExponent().equals(k2.getPublicExponent()) && k1.getModulus().equals(k2.getModulus());
+ }
+
+ private boolean isSameDSAKey(DSAPublicKey k1, DSAPublicKey k2) {
+ return k1.getY().equals(k2.getY()) && k1.getParams().getG().equals(k2.getParams().getG()) && k1.getParams().getP().equals(k2.getParams().getP()) && k1.getParams().getQ().equals(k2.getParams().getQ());
+ }
+ };
+ }
}