Index: src/test/java/org/apache/karaf/shell/ssh/TestAuthorizedKeysParsing.java
===================================================================
--- src/test/java/org/apache/karaf/shell/ssh/TestAuthorizedKeysParsing.java (Revision 0)
+++ src/test/java/org/apache/karaf/shell/ssh/TestAuthorizedKeysParsing.java (Revision 0)
@@ -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.karaf.shell.ssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.karaf.shell.ssh.KarafPublickeyAuthenticator.AuthorizedKey;
+
+/**
+ * Test parsing an authorized_keys file.
+ */
+public class TestAuthorizedKeysParsing extends TestCase {
+
+ public void testAuthorizedKeysParsing() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
+
+ InputStream is =
+ TestAuthorizedKeysParsing.class.getClassLoader().getResourceAsStream("org/apache/karaf/shell/ssh/authorized_keys");
+
+ Map keys = KarafPublickeyAuthenticator.parseAuthorizedKeys(is);
+
+ assertEquals(2,keys.size());
+
+ for (Map.Entry e : keys.entrySet()) {
+
+ assertSame(e.getKey(),e.getValue().getPubKey());
+ assertTrue("ssh-dss".equals(e.getValue().getFormat()) || "ssh-rsa".equals(e.getValue().getFormat()));
+
+ if ("ssh-dss".equals(e.getValue().getFormat())) {
+ assertTrue(e.getKey() instanceof DSAPublicKey);
+ assertEquals("dsa-test",e.getValue().getAlias());
+ }
+ if ("ssh-rsa".equals(e.getValue().getFormat())) {
+ assertTrue(e.getKey() instanceof RSAPublicKey);
+ assertEquals("rsa-test",e.getValue().getAlias());
+ }
+ }
+ }
+}
Eigenschaftsänderungen: src/test/java/org/apache/karaf/shell/ssh/TestAuthorizedKeysParsing.java
___________________________________________________________________
Hinzugefügt: svn:keywords
+ Id
Hinzugefügt: svn:eol-style
+ native
Index: src/test/resources/org/apache/karaf/shell/ssh/authorized_keys
===================================================================
--- src/test/resources/org/apache/karaf/shell/ssh/authorized_keys (Revision 0)
+++ src/test/resources/org/apache/karaf/shell/ssh/authorized_keys (Revision 0)
@@ -0,0 +1,2 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dsa-test
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRbYYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE= rsa-test
Index: src/main/java/org/apache/karaf/shell/ssh/KarafPublickeyAuthenticator.java
===================================================================
--- src/main/java/org/apache/karaf/shell/ssh/KarafPublickeyAuthenticator.java (Revision 0)
+++ src/main/java/org/apache/karaf/shell/ssh/KarafPublickeyAuthenticator.java (Revision 0)
@@ -0,0 +1,388 @@
+/*
+ * 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.karaf.shell.ssh;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeMap;
+
+import org.apache.mina.util.Base64;
+import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A public key authenticator, which reads an openssl2 authorized_keys file.
+ */
+public class KarafPublickeyAuthenticator implements PublickeyAuthenticator {
+
+ private static final Logger log = LoggerFactory.getLogger(KarafPublickeyAuthenticator.class);
+
+ private String authorizedKeys;
+ private boolean active;
+
+ public static final class AuthorizedKey {
+
+ private final String alias;
+ private final String format;
+ private final PublicKey pubKey;
+
+ public AuthorizedKey(String alias, String format, PublicKey pubKey) {
+
+ super();
+ this.alias = alias;
+ this.format = format;
+ this.pubKey = pubKey;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public PublicKey getPubKey() {
+ return pubKey;
+ }
+ }
+
+ private static final class PublicKeyComparator implements Comparator {
+
+ public int compare(PublicKey a, PublicKey b) {
+
+ if (a instanceof DSAPublicKey) {
+ if (b instanceof DSAPublicKey) {
+
+ DSAPublicKey da = (DSAPublicKey)a;
+ DSAPublicKey db = (DSAPublicKey)b;
+
+ int r = da.getParams().getG().compareTo(db.getParams().getG());
+ if (r != 0) {
+ return r;
+ }
+ r = da.getParams().getP().compareTo(db.getParams().getP());
+ if (r != 0) {
+ return r;
+ }
+ r = da.getParams().getQ().compareTo(db.getParams().getQ());
+ if (r != 0) {
+ return r;
+ }
+ return da.getY().compareTo(db.getY());
+ }
+ else {
+ return -1;
+ }
+ }
+ else if (a instanceof RSAPublicKey) {
+ if (b instanceof RSAPublicKey) {
+
+ RSAPublicKey da = (RSAPublicKey)a;
+ RSAPublicKey db = (RSAPublicKey)b;
+
+ int r = da.getPublicExponent().compareTo(db.getPublicExponent());
+ if (r != 0) {
+ return r;
+ }
+ return da.getModulus().compareTo(db.getModulus());
+ }
+ else {
+ return 1;
+ }
+ }
+ else {
+ throw new IllegalArgumentException("Only RSA and DAS keys are supported.");
+ }
+ }
+
+ }
+
+ private final class AuthorizedKeysProvider extends TimerTask {
+
+ private Map keys;
+ private Long lastModDate;
+ private Boolean fileAvailable;
+
+ @Override
+ public void run() {
+
+ try {
+ File af = new File (KarafPublickeyAuthenticator.this.authorizedKeys);
+
+ if (af.exists()) {
+
+ Long newModDate = Long.valueOf(af.lastModified());
+
+ if ((this.fileAvailable != null &&
+ !this.fileAvailable.booleanValue()) ||
+ !newModDate.equals(this.lastModDate)) {
+
+ log.info("Parsing authorized keys file [{}]...",
+ KarafPublickeyAuthenticator.this.authorizedKeys);
+
+ this.fileAvailable = Boolean.TRUE;
+ this.lastModDate = newModDate;
+
+ Map newKeys = KarafPublickeyAuthenticator.parseAuthorizedKeys(new FileInputStream(af));
+ this.setKeys(newKeys);
+ log.info("Successfully parsed [{}] keys from file [{}].",
+ newKeys.size(),KarafPublickeyAuthenticator.this.authorizedKeys);
+ }
+ }
+ else {
+
+ if (this.fileAvailable != null &&
+ this.fileAvailable.booleanValue()) {
+
+ log.info("Authorized keys file ["+KarafPublickeyAuthenticator.this.authorizedKeys+"] disappeared, will recheck every minute.");
+ }
+ else if (this.fileAvailable == null) {
+
+ log.info("Authorized keys file ["+KarafPublickeyAuthenticator.this.authorizedKeys+"] does not exist, will recheck every minute.");
+ }
+
+ this.fileAvailable = Boolean.FALSE;
+ this.lastModDate = null;
+ this.setKeys(null);
+ }
+
+ }
+ catch (Throwable e) {
+ log.error("Error parsing authorized keys file ["+KarafPublickeyAuthenticator.this.authorizedKeys+"]",e);
+ this.fileAvailable = Boolean.FALSE;
+ this.lastModDate = null;
+ this.setKeys(null);
+ }
+ }
+
+ private synchronized void setKeys(Map keys) {
+ this.keys = keys;
+ }
+
+ public synchronized AuthorizedKey getKey(PublicKey pk) {
+ if (this.keys == null) {
+ return null;
+ }
+ else {
+ return this.keys.get(pk);
+ }
+ }
+ }
+
+ private Timer parseAuthorizedKeysTimer;
+ private AuthorizedKeysProvider authorizedKeysProvider;
+
+ private static final int getInt(byte[] b, int pos) {
+
+ return (((int)b[pos] & 0xff) << 24) +
+ (((int)b[pos+1] & 0xff) << 16) +
+ (((int)b[pos+2] & 0xff) << 8) +
+ ((int)b[pos+3] & 0xff);
+ }
+
+ /**
+ * Parse an authorized_keys file in openssh style.
+ * @param is The input stream to read.
+ * @return A map of authorized public keys.
+ * @throws IOException
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeySpecException
+ */
+ public static final Map parseAuthorizedKeys(InputStream is) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
+
+ try {
+
+ Base64 decoder = new Base64();
+
+ KeyFactory rsaKeyGen = KeyFactory.getInstance("RSA");
+ KeyFactory dsaKeyGen = KeyFactory.getInstance("DSA");
+
+ LineNumberReader reader = new LineNumberReader(new InputStreamReader(is,"UTF-8"));
+
+ Map ret =
+ new TreeMap(new PublicKeyComparator());
+
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+
+ String[] tokens = line.split("[ \\t]+",3);
+
+ if (tokens.length != 3) {
+ throw new IOException("Authorized keys file line ["+reader.getLineNumber()+"] does not contain 3 tokens.");
+ }
+
+ byte[] rawKey = decoder.decode(tokens[1].getBytes("UTF-8"));
+
+ if (getInt(rawKey,0) != 7 ||
+ !new String(rawKey,4,7,"UTF-8").equals(tokens[0])) {
+
+ throw new IOException("Authorized keys file line ["+reader.getLineNumber()+"] contain a key with a format that does not match the first token.");
+ }
+
+ PublicKey pk;
+
+ if (tokens[0].equals("ssh-dss")) {
+
+ int pos = 11;
+
+ int n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger p = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger q = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger g = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger y = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ if (pos != rawKey.length) {
+ throw new IOException("Authorized keys file line ["+reader.getLineNumber()+"] contain a DSA key with extra garbage.");
+ }
+
+ DSAPublicKeySpec ps = new DSAPublicKeySpec(y,p,q,g);
+ pk = dsaKeyGen.generatePublic(ps);
+ }
+ else if (tokens[0].equals("ssh-rsa")) {
+
+ int pos = 11;
+
+ int n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger e = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ n = getInt(rawKey,pos);
+ pos += 4;
+ BigInteger modulus = new BigInteger(1,Arrays.copyOfRange(rawKey,pos,pos+n));
+ pos += n;
+
+ if (pos != rawKey.length) {
+ throw new IOException("Authorized keys file line ["+reader.getLineNumber()+"] contain a RSA key with extra garbage.");
+ }
+
+ RSAPublicKeySpec ps = new RSAPublicKeySpec(modulus,e);
+ pk = rsaKeyGen.generatePublic(ps);
+ }
+ else {
+ throw new IOException("Authorized keys file line ["+reader.getLineNumber()+"] does not start with ssh-dss or ssh-rsa.");
+
+ }
+
+ ret.put(pk,new AuthorizedKey(tokens[2],tokens[0],pk));
+ }
+ return ret;
+ }
+ finally {
+ is.close();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.sshd.server.PublickeyAuthenticator#authenticate(java.lang.String, java.security.PublicKey, org.apache.sshd.server.session.ServerSession)
+ */
+ public boolean authenticate(String username, PublicKey key,
+ ServerSession session) {
+
+ AuthorizedKey ak = this.authorizedKeysProvider.getKey(key);
+
+ if (ak == null) {
+
+ log.error("Failed authentication of user [{}] from [{}] with unknown public key.",
+ username,session.getIoSession().getRemoteAddress());
+ return false;
+ }
+ else {
+
+ log.info("Successful authentication of user [{}] from [{}] with public key [{}].",
+ new Object[]{username,session.getIoSession().getRemoteAddress(),ak.getAlias()});
+ return true;
+ }
+ }
+
+ /**
+ * @param path The path of the authorized keys file.
+ */
+ public void setAuthorizedKeys(String path) {
+
+ this.authorizedKeys = path;
+ }
+
+ /**
+ * @param active Whether the authorized_keys parsing timer should be started.
+ */
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ /**
+ * The blueprint init-method used to start the parsing timer
+ * if {@link #setActive(boolean)} was called with a value of true.
+ */
+ public void startTimer() {
+
+ if (this.active) {
+
+ this.parseAuthorizedKeysTimer = new Timer();
+ this.authorizedKeysProvider = new AuthorizedKeysProvider();
+ this.parseAuthorizedKeysTimer.schedule(this.authorizedKeysProvider,10,60000L);
+ }
+ }
+
+ /**
+ * The blueprint destroy-method used to stop the parsing timer.
+ */
+ public void stopTimer() {
+ if (this.parseAuthorizedKeysTimer != null) {
+ this.parseAuthorizedKeysTimer.cancel();
+ this.parseAuthorizedKeysTimer = null;
+ }
+ }
+}
Eigenschaftsänderungen: src/main/java/org/apache/karaf/shell/ssh/KarafPublickeyAuthenticator.java
___________________________________________________________________
Hinzugefügt: svn:keywords
+ Id
Hinzugefügt: svn:eol-style
+ native
Index: src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
===================================================================
--- src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java (Revision 0)
+++ src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java (Revision 0)
@@ -0,0 +1,126 @@
+/*
+ * 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.karaf.shell.ssh;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sshd.SshServer;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.UserAuth;
+import org.apache.sshd.server.auth.UserAuthPassword;
+import org.apache.sshd.server.auth.UserAuthPublicKey;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.osgi.service.blueprint.container.ReifiedType;
+
+/**
+ * A factory for user authentication factories to set on
+ * {@link SshServer#setUserAuthFactories(java.util.List)} based on a
+ * comma-separated list of authentication methods.
+ *
+ * Currently, the following methods are supported:
+ *
+ * password
+ * Password authentication against a given JAAS domain.
+ * publickey
+ * Public Key authentication against an openssh authorized_keys file.
+ *
+ *
+ */
+public class UserAuthFactoriesFactory {
+
+ public static final String PASSWORD_METHOD = "password";
+ public static final String PUBLICKEY_METHOD = "publickey";
+
+ private Set methodSet;
+ private List> factories;
+
+ public static Converter getConverter() {
+ return new Converter();
+ }
+
+ /**
+ * This blueprint typ converter silently converts instances of
+ * class X implements NameFactory<UserAuth>
+ * to the reified type cNameFactory<UserAuth>
+ * and therefore helps blueprint to set the returned factories on
+ * {@link SshServer#setUserAuthFactories(List)} without complaining
+ * about type conversion errors.
+ */
+ public static class Converter implements org.osgi.service.blueprint.container.Converter {
+
+ public boolean canConvert(Object sourceObject, ReifiedType targetType) {
+ return NamedFactory.class.isAssignableFrom(sourceObject.getClass())
+ && UserAuth.class.equals(((ParameterizedType)sourceObject.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0])
+ && NamedFactory.class.equals(targetType.getRawClass())
+ && UserAuth.class.equals(targetType.getActualTypeArgument(0).getRawClass());
+ }
+
+ public Object convert(Object sourceObject, ReifiedType targetType) throws Exception {
+ return sourceObject;
+ }
+ }
+
+ /**
+ * @param methods A comma-separated list of authentication methods.
+ */
+ public void setAuthMethods(String methods) {
+
+ this.methodSet = new HashSet();
+ this.factories = new ArrayList>();
+
+ String[] ams = methods.split(",");
+
+ for (String am : ams) {
+
+ if (PASSWORD_METHOD.equals(am)) {
+
+ this.factories.add(new UserAuthPassword.Factory());
+ }
+ else if (PUBLICKEY_METHOD.equals(am)) {
+
+ this.factories.add(new UserAuthPublicKey.Factory());
+ }
+ else {
+ throw new ComponentDefinitionException("Invalid authentication method ["+am+"] specified.");
+ }
+
+ this.methodSet.add(am);
+ }
+ }
+
+ /**
+ * @return the factories to set on {@link SshServer#setUserAuthFactories(List)}
+ */
+ public List> getFactories() {
+ return factories;
+ }
+
+ public boolean isPublickeyEnabled() {
+ return this.methodSet.contains(PUBLICKEY_METHOD);
+ }
+
+ public boolean isPasswordEnabled() {
+ return this.methodSet.contains(PASSWORD_METHOD);
+ }
+
+}
Eigenschaftsänderungen: src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
___________________________________________________________________
Hinzugefügt: svn:keywords
+ Id
Hinzugefügt: svn:eol-style
+ native
Index: src/main/resources/OSGI-INF/blueprint/shell-ssh.xml
===================================================================
--- src/main/resources/OSGI-INF/blueprint/shell-ssh.xml (Revision 1237229)
+++ src/main/resources/OSGI-INF/blueprint/shell-ssh.xml (Arbeitskopie)
@@ -25,6 +25,7 @@
+
@@ -41,6 +42,8 @@
+
+
@@ -64,6 +67,10 @@
+
+
+
+
@@ -83,9 +90,16 @@
+
+
+
+
+
+
@@ -96,6 +110,14 @@
+
+
+
+
+
+
+
Index: pom.xml
===================================================================
--- pom.xml (Revision 1237229)
+++ pom.xml (Arbeitskopie)
@@ -65,6 +65,11 @@
org.apache.sshd
sshd-core
+
+
+ org.slf4j
+ slf4j-simple
+