Resolve Bug:623 : Generate private key using bouncycastle. 34/5934/3
authorTomas Olvecky <tolvecky@cisco.com>
Mon, 7 Apr 2014 07:44:31 +0000 (09:44 +0200)
committerTomas Olvecky <tolvecky@cisco.com>
Mon, 7 Apr 2014 09:49:27 +0000 (11:49 +0200)
Add dependency on bouncycastle and use it to generate private key in PEM
format when key file is not present on the filesystem.

Change-Id: I07290e0f361151743a50559c26255eab23cababb
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
opendaylight/commons/opendaylight/pom.xml
opendaylight/distribution/opendaylight/pom.xml
opendaylight/distribution/opendaylight/src/main/resources/configuration/RSA.pk [deleted file]
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java
opendaylight/netconf/netconf-ssh/pom.xml
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java [moved from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java with 80% similarity]
opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java [new file with mode: 0644]
opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java

index e7c7bbe..1a068ec 100644 (file)
             <artifactId>configuration</artifactId>
             <version>${configuration.version}</version>
           </dependency>
-
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15on</artifactId>
+                <version>1.50</version>
+            </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcpkix-jdk15on</artifactId>
+                <version>1.50</version>
+            </dependency>
     </dependencies>
   </dependencyManagement>
 
index a38a9b4..8a9318e 100644 (file)
                     <artifactId>org.apache.xml.resolver</artifactId>
                     <version>1.2.0</version>
                 </dependency>
+                <dependency>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcprov-jdk15on</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.bouncycastle</groupId>
+                    <artifactId>bcpkix-jdk15on</artifactId>
+                </dependency>
 
                 <!-- threadpool -->
                 <dependency>
diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/RSA.pk b/opendaylight/distribution/opendaylight/src/main/resources/configuration/RSA.pk
deleted file mode 100644 (file)
index c0266c7..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAuC9hbEacpewvylI0mwFwjy3Wou2hpr/ncN9BBiFDSaG5yW2k
-3Oy+SCAcFCL+ZKWb6cc6Ch4gUeCwyEHRojZguuhliKtak9YQf6qbvpPLe00842Lx
-iqNAGurMpzizCDsGFq8ChaAkBZQI3TvcHuPoSUWSMJ+K8xHpRyUdVr6g2yEjezKJ
-sTXBtWaeCCh6YUafFujuDJk7fvYcPW7Je5KRBBStIKvxcMW0zB+7eq04deTHwGbJ
-gGjKWilQ72hsDDP3Hbp5CJMAYg1r4GlCmFx3KyHRGztgWgNgaD7nNpKCkTLjtmA6
-b4x7TA+jrzZ6Af2z5TMrI4dv5w1SrxHaZ+ziLQIDAQABAoIBAHTndeGgq/rQf8De
-Do+4CTaHtK0zQSAyu/azbXUzlZ7drKuCEVs8VMY4wzmwwGEnkF+A2YDkgEUX5X0l
-8aYQ97KKoS9u+43MGCrAIhyDeGrpqlT1TzRcy+qJz53v6gq2U/X/3QztiQ+VV078
-mIluxNgE9XYxPaNsYfGLSCTv1+9c8y/hjGVX2kwFK+u4ut0ZZETggNa8UxfaHVDS
-fIJQX9Gm3J3GSUV30fDGMBIUW6ESLc2L8b7u8Mp9TRP39ZeQSuEUjBe8MYKv0Rel
-oEpjZvcnniMTpFbLpndBYn7/AoIiEBvtCN8faVTuRRcvvLcsRm09IctzKQYnMh6M
-6PLKV+ECgYEA8HFRYaKHUzxpzE/fyon82GQbzqFFY0/bbWrfWICMfNbIgshJUie6
-FmH5iUFMfeqaT7v557HFM0GB9FeIeSbvd88YmiBAcRopZ3DfMkDH+DT73yJ+/TKG
-2nrQtdhyuTIs4bwHqeS2BBJYs7PK9R2rratF3l34Tf7mjlvyOgygHdUCgYEAxBo2
-8hEBlAVNcNb1hTYUxe1w1B6675/mFlmw98Xmj9dRYfICXNhahs8tX3/lsBEd+vBu
-fI0oyHaff8m5bPgGzD1ZMybfeROujNrgxaKVk7Ef0FDRRCop4bm18OroFlFAt9l8
-wMp++ToACbdvQvL/mjWMPYlIxhB/YxHswICZZvkCgYAexxKYwdo6sGAGlC7cWT9x
-X5cjowcjyEQZRHXkeUgCbufpvcOM7aLnXJE5nY8yCwbHsBM0MlBA2GDPKylAANjk
-aDEJAZneIHAuWodngl1Wi0m2bU7+ECqs6s2uiU9eH2sZVh1RBQK7kLGkBx6ys6KX
-L3ZZGYRAT6GplWFzRsx0JQKBgCeVlxPD5QqpC1nEumi6YvUVGdpnnZpzL3HBhxxs
-wT612wKnZFyze4qM1X7ahVXGDsQxtkvD/sCAWW/lG13orw6ZL6FIroF1PJ3ILOkY
-CZN3hJF7TtKwpCWhZB2OfWzL2AGEkE8mUP0j/Q/5DCd6f6f0OSvOw3bfq6cm3iB5
-lP2ZAoGAXsRN5TZTX4AQ2xTlrDQ8A5XgcvyWQpJOmEXMTyHV7VaJVzmNWFVAvndK
-5UIq8ALDwB2t7vjmMUW6euvIwqtXiop7G79UOb3e3NhzeyWFGQyBLqCRznGaXQTT
-dlFy73xhukZMhFnj006bjKCYvOPnwuGl3+0fuWil5Rq3jOuY5c8=
------END RSA PRIVATE KEY-----
index 677d0df..4ca9690 100644 (file)
@@ -8,33 +8,13 @@
 
 package org.opendaylight.controller.netconf.it;
 
-import static java.util.Collections.emptyList;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import io.netty.channel.ChannelFuture;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-import javax.management.ObjectName;
-import javax.xml.parsers.ParserConfigurationException;
-
 import junit.framework.Assert;
-
+import org.apache.commons.io.IOUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -42,7 +22,6 @@ import org.junit.Test;
 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
 import org.opendaylight.controller.config.spi.ModuleFactory;
 import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
-import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException;
 import org.opendaylight.controller.config.yang.test.impl.DepTestImplModuleFactory;
 import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleFactory;
 import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleMXBean;
@@ -52,6 +31,7 @@ import org.opendaylight.controller.netconf.api.NetconfMessage;
 import org.opendaylight.controller.netconf.client.NetconfClient;
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
 import org.opendaylight.controller.netconf.confignetconfconnector.osgi.NetconfOperationServiceFactoryImpl;
+import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException;
 import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
 import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
 import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl;
@@ -72,17 +52,34 @@ import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
 
-import ch.ethz.ssh2.Connection;
-import ch.ethz.ssh2.Session;
+import javax.management.ObjectName;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import static java.util.Collections.emptyList;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 public class NetconfITTest extends AbstractNetconfConfigTest {
 
     // TODO refactor, pull common code up to AbstractNetconfITTest
 
-    private static final Logger logger =  LoggerFactory.getLogger(NetconfITTest.class);
+    private static final Logger logger = LoggerFactory.getLogger(NetconfITTest.class);
 
     private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
     private static final InetSocketAddress sshAddress = new InetSocketAddress("127.0.0.1", 10830);
@@ -90,7 +87,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
     private static final String PASSWORD = "netconf";
 
     private NetconfMessage getConfig, getConfigCandidate, editConfig,
-    closeSession, startExi, stopExi;
+            closeSession, startExi, stopExi;
     private DefaultCommitNotificationProducer commitNot;
     private NetconfServerDispatcher dispatch;
 
@@ -113,7 +110,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         ChannelFuture s = dispatch.createServer(tcpAddress);
         s.await();
 
-        clientDispatcher = new NetconfClientDispatcher( nettyThreadgroup, nettyThreadgroup, 5000);
+        clientDispatcher = new NetconfClientDispatcher(nettyThreadgroup, nettyThreadgroup, 5000);
     }
 
     private NetconfServerDispatcher createDispatcher(NetconfOperationServiceFactoryListenerImpl factoriesListener) {
@@ -164,13 +161,14 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
                 yangDependencies.add(resourceAsStream);
             }
         }
-        assertEquals("Some yang files were not found",emptyList(), failedToFind);
+        assertEquals("Some yang files were not found", emptyList(), failedToFind);
         return yangDependencies;
     }
 
     protected List<ModuleFactory> getModuleFactories() {
         return getModuleFactoriesS();
     }
+
     static List<ModuleFactory> getModuleFactoriesS() {
         return Lists.newArrayList(new TestImplModuleFactory(), new DepTestImplModuleFactory(),
                 new NetconfTestImplModuleFactory());
@@ -193,7 +191,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
 
     @Test
     public void testTwoSessions() throws Exception {
-        try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 10000, clientDispatcher))  {
+        try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 10000, clientDispatcher)) {
             try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 10000, clientDispatcher)) {
             }
         }
@@ -258,7 +256,7 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         NetconfTestImplModuleMXBean proxy = configRegistryClient
                 .newMXBeanProxy(impl, NetconfTestImplModuleMXBean.class);
         proxy.setTestingDep(dep);
-        proxy.setSimpleShort((short)0);
+        proxy.setSimpleShort((short) 0);
 
         transaction.commit();
 
@@ -400,12 +398,15 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         return netconfClient;
     }
 
-    private void startSSHServer() throws Exception{
+    private void startSSHServer() throws Exception {
         logger.info("Creating SSH server");
-        StubUserManager um = new StubUserManager(USERNAME,PASSWORD);
-        InputStream is = getClass().getResourceAsStream("/RSA.pk");
-        AuthProvider ap = new AuthProvider(um, is);
-        Thread sshServerThread = new Thread(NetconfSSHServer.start(10830,tcpAddress,ap));
+        StubUserManager um = new StubUserManager(USERNAME, PASSWORD);
+        String pem;
+        try (InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
+            pem = IOUtils.toString(is);
+        }
+        AuthProvider ap = new AuthProvider(um, pem);
+        Thread sshServerThread = new Thread(NetconfSSHServer.start(10830, tcpAddress, ap));
         sshServerThread.setDaemon(true);
         sshServerThread.start();
         logger.info("SSH server on");
@@ -415,11 +416,11 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
     public void sshTest() throws Exception {
         startSSHServer();
         logger.info("creating connection");
-        Connection conn = new Connection(sshAddress.getHostName(),sshAddress.getPort());
+        Connection conn = new Connection(sshAddress.getHostName(), sshAddress.getPort());
         Assert.assertNotNull(conn);
         logger.info("connection created");
         conn.connect();
-        boolean isAuthenticated = conn.authenticateWithPassword(USERNAME,PASSWORD);
+        boolean isAuthenticated = conn.authenticateWithPassword(USERNAME, PASSWORD);
         assertTrue(isAuthenticated);
         logger.info("user authenticated");
         final Session sess = conn.openSession();
@@ -427,10 +428,10 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
         logger.info("user authenticated");
         sess.getStdin().write(XmlUtil.toString(this.getConfig.getDocument()).getBytes());
 
-        new Thread(){
+        new Thread() {
             @Override
-            public void run(){
-                while (true){
+            public void run() {
+                while (true) {
                     byte[] bytes = new byte[1024];
                     int c = 0;
                     try {
@@ -438,8 +439,10 @@ public class NetconfITTest extends AbstractNetconfConfigTest {
                     } catch (IOException e) {
                         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                     }
-                    logger.info("got data:"+bytes);
-                    if (c == 0) break;
+                    logger.info("got data:" + bytes);
+                    if (c == 0) {
+                        break;
+                    }
                 }
             }
         }.join();
index 73e0a46..23a69b2 100644 (file)
@@ -1,4 +1,5 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>netconf-subsystem</artifactId>
         <groupId>org.opendaylight.controller</groupId>
             <groupId>org.opendaylight.controller</groupId>
             <artifactId>usermanager</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>mockito-configuration</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -58,7 +72,8 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
-                        <Bundle-Activator>org.opendaylight.controller.netconf.osgi.NetconfSSHActivator</Bundle-Activator>
+                        <Bundle-Activator>org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator
+                        </Bundle-Activator>
                         <Import-Package>
                             com.google.common.base,
                             ch.ethz.ssh2,
@@ -71,6 +86,7 @@
                             org.osgi.framework,
                             org.osgi.util.tracker,
                             org.slf4j,
+                            org.bouncycastle.openssl
                         </Import-Package>
                     </instructions>
                 </configuration>
index 3d53180..2d38048 100644 (file)
@@ -7,64 +7,48 @@
  */
 package org.opendaylight.controller.netconf.ssh.authentication;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.commons.io.IOUtils;
 import org.opendaylight.controller.sal.authorization.AuthResultEnum;
 import org.opendaylight.controller.sal.authorization.UserLevel;
 import org.opendaylight.controller.usermanager.IUserManager;
 import org.opendaylight.controller.usermanager.UserConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 public class AuthProvider implements AuthProviderInterface {
 
-    private static IUserManager um;
+    private static IUserManager um; //FIXME static mutable state, no locks
     private static final String DEFAULT_USER = "netconf";
     private static final String DEFAULT_PASSWORD = "netconf";
-    private String PEM;
-
-    private static final Logger logger =  LoggerFactory.getLogger(AuthProvider.class);
-
-    public AuthProvider(IUserManager ium,InputStream privateKeyFileInputStream) throws Exception {
+    private final String pem;
 
+    public AuthProvider(IUserManager ium, String pemCertificate) throws Exception {
+        checkNotNull(pemCertificate, "Parameter 'pemCertificate' is null");
         AuthProvider.um = ium;
-        if (AuthProvider.um  == null){
+        if (AuthProvider.um == null) {
             throw new Exception("No usermanager service available.");
         }
 
         List<String> roles = new ArrayList<String>(1);
         roles.add(UserLevel.SYSTEMADMIN.toString());
-        AuthProvider.um.addLocalUser(new UserConfig(DEFAULT_USER, DEFAULT_PASSWORD, roles));
-
-        try {
-            PEM = IOUtils.toString(privateKeyFileInputStream);
-        } catch (IOException e) {
-            logger.error("Error reading RSA key from file.");
-            throw new IllegalStateException("Error reading RSA key from file.");
-        }
+        AuthProvider.um.addLocalUser(new UserConfig(DEFAULT_USER, DEFAULT_PASSWORD, roles)); //FIXME hardcoded auth
+        pem = pemCertificate;
     }
+
     @Override
-    public boolean authenticated(String username, String password)  throws Exception {
-        if (AuthProvider.um  == null){
+    public boolean authenticated(String username, String password) throws Exception {
+        if (AuthProvider.um == null) {
             throw new Exception("No usermanager service available.");
         }
-        AuthResultEnum authResult = AuthProvider.um.authenticate(username,password);
-        if (authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC)){
-            return true;
-        }
-        return false;
+        AuthResultEnum authResult = AuthProvider.um.authenticate(username, password);
+        return authResult.equals(AuthResultEnum.AUTH_ACCEPT) || authResult.equals(AuthResultEnum.AUTH_ACCEPT_LOC);
     }
 
     @Override
-    public char[] getPEMAsCharArray() throws Exception {
-        if (null == PEM){
-            logger.error("Missing RSA key string.");
-            throw new Exception("Missing RSA key.");
-        }
-        return PEM.toCharArray();
+    public char[] getPEMAsCharArray() {
+        return pem.toCharArray();
     }
 
     @Override
@@ -76,6 +60,4 @@ public class AuthProvider implements AuthProviderInterface {
     public void addUserManagerService(IUserManager userManagerService) {
         AuthProvider.um = userManagerService;
     }
-
-
 }
diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java
new file mode 100644 (file)
index 0000000..73886c4
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. 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
+ */
+
+package org.opendaylight.controller.netconf.ssh.authentication;
+
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.openssl.PEMWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+
+public class PEMGenerator {
+    private static final Logger logger = LoggerFactory.getLogger(PEMGenerator.class);
+    private static final int KEY_SIZE = 4096;
+
+    public static String generateTo(File privateFile) throws Exception {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        SecureRandom sr = new SecureRandom();
+        keyGen.initialize(KEY_SIZE, sr);
+        KeyPair keypair = keyGen.generateKeyPair();
+        logger.info("Generating private key to {}", privateFile.getAbsolutePath());
+        String privatePEM = toString(keypair.getPrivate());
+        FileUtils.write(privateFile, privatePEM);
+        return privatePEM;
+    }
+
+    private static String toString(Key key) throws IOException {
+        try (StringWriter writer = new StringWriter()) {
+            try (PEMWriter pemWriter = new PEMWriter(writer)) {
+                pemWriter.writeObject(key);
+            }
+            return writer.toString();
+        }
+    }
+}
@@ -5,13 +5,13 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-package org.opendaylight.controller.netconf.osgi;
+package org.opendaylight.controller.netconf.ssh.osgi;
 
 import com.google.common.base.Optional;
-import java.io.FileInputStream;
-import java.net.InetSocketAddress;
+import org.apache.commons.io.IOUtils;
 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
 import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
 import org.opendaylight.controller.usermanager.IUserManager;
 import org.osgi.framework.BundleActivator;
@@ -22,6 +22,11 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
 /**
  * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
  * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
@@ -60,7 +65,7 @@ public class NetconfSSHActivator implements BundleActivator{
         @Override
         public void removedService(ServiceReference<IUserManager> reference, IUserManager service) {
             logger.trace("Removing service {} from netconf SSH. " +
-                    "SSH won't authenticate users until IUserManeger service will be started.", reference);
+                    "SSH won't authenticate users until IUserManager service will be started.", reference);
             removeUserManagerService();
         }
     };
@@ -87,15 +92,27 @@ public class NetconfSSHActivator implements BundleActivator{
 
         if (sshSocketAddressOptional.isPresent()){
             String path = NetconfConfigUtil.getPrivateKeyPath(context);
-            path = path.replace("\\", "/");
+            path = path.replace("\\", "/");  // FIXME: shouldn't this convert lines to system dependent path separator?
             if (path.equals("")){
                 throw new Exception("Missing netconf.ssh.pk.path key in configuration file.");
             }
 
-            try (FileInputStream fis = new FileInputStream(path)){
-                AuthProvider authProvider = new AuthProvider(iUserManager,fis);
-                this.server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress,authProvider);
+            File privateKeyFile = new File(path);
+            String privateKeyPEMString;
+            if (privateKeyFile.exists() == false) {
+                // generate & save to file
+                privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
+            } else {
+                // read from file
+                try (FileInputStream fis = new FileInputStream(path)) {
+                    privateKeyPEMString = IOUtils.toString(fis);
+                } catch (IOException e) {
+                    logger.error("Error reading RSA key from file '{}'", path);
+                    throw new IllegalStateException("Error reading RSA key from file " + path);
+                }
             }
+            AuthProvider authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
+            this.server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress,authProvider);
 
             Thread serverThread = new  Thread(server,"netconf SSH server thread");
             serverThread.setDaemon(true);
index e07c7ed..ce26910 100644 (file)
@@ -8,16 +8,6 @@
 package org.opendaylight.controller.netconf.ssh.threads;
 
 
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import ch.ethz.ssh2.AuthenticationResult;
 import ch.ethz.ssh2.PtySettings;
 import ch.ethz.ssh2.ServerAuthenticationCallback;
@@ -26,10 +16,18 @@ import ch.ethz.ssh2.ServerConnectionCallback;
 import ch.ethz.ssh2.ServerSession;
 import ch.ethz.ssh2.ServerSessionCallback;
 import ch.ethz.ssh2.SimpleServerSessionCallback;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.concurrent.ThreadSafe;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
 
 @ThreadSafe
 public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback {
-    private static final Logger logger =  LoggerFactory.getLogger(SocketThread.class);
+    private static final Logger logger = LoggerFactory.getLogger(SocketThread.class);
 
     private final Socket socket;
     private final InetSocketAddress clientAddress;
@@ -43,11 +41,12 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
     public static void start(Socket socket,
                              InetSocketAddress clientAddress,
                              long sessionId,
-                             AuthProvider authProvider) throws IOException{
-        Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket,clientAddress,sessionId,authProvider));
+                             AuthProvider authProvider) throws IOException {
+        Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket, clientAddress, sessionId, authProvider));
         netconf_ssh_socket_thread.setDaemon(true);
         netconf_ssh_socket_thread.start();
     }
+
     private SocketThread(Socket socket,
                          InetSocketAddress clientAddress,
                          long sessionId,
@@ -56,7 +55,7 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
         this.socket = socket;
         this.clientAddress = clientAddress;
         this.sessionId = sessionId;
-        this.remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replaceFirst("/","");
+        this.remoteAddressWithPort = socket.getRemoteSocketAddress().toString().replaceFirst("/", "");
         this.authProvider = authProvider;
 
     }
@@ -65,7 +64,7 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
     public void run() {
         conn = new ServerConnection(socket);
         try {
-            conn.setPEMHostKey(authProvider.getPEMAsCharArray(),"netconf");
+            conn.setPEMHostKey(authProvider.getPEMAsCharArray(), "netconf");
         } catch (Exception e) {
             logger.debug("Server authentication setup failed.");
         }
@@ -74,24 +73,21 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
         try {
             conn.connect();
         } catch (IOException e) {
-            logger.error("SocketThread error ",e);
+            logger.error("SocketThread error ", e);
         }
     }
+
     @Override
-    public ServerSessionCallback acceptSession(final ServerSession session)
-    {
-        SimpleServerSessionCallback cb = new SimpleServerSessionCallback()
-        {
+    public ServerSessionCallback acceptSession(final ServerSession session) {
+        SimpleServerSessionCallback cb = new SimpleServerSessionCallback() {
             @Override
-            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException
-            {
-                return new Runnable(){
+            public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException {
+                return new Runnable() {
                     @Override
-                    public void run()
-                    {
-                        if (subsystem.equals("netconf")){
+                    public void run() {
+                        if (subsystem.equals("netconf")) {
                             IOThread netconf_ssh_input = null;
-                            IOThread  netconf_ssh_output = null;
+                            IOThread netconf_ssh_output = null;
                             try {
                                 String hostName = clientAddress.getHostName();
                                 int portNumber = clientAddress.getPort();
@@ -99,13 +95,13 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
                                 logger.trace("echo socket created");
 
                                 logger.trace("starting netconf_ssh_input thread");
-                                netconf_ssh_input =  new IOThread(echoSocket.getInputStream(),ss.getStdin(),"input_thread_"+sessionId,ss,conn);
+                                netconf_ssh_input = new IOThread(echoSocket.getInputStream(), ss.getStdin(), "input_thread_" + sessionId, ss, conn);
                                 netconf_ssh_input.setDaemon(false);
                                 netconf_ssh_input.start();
 
                                 logger.trace("starting netconf_ssh_output thread");
-                                final String customHeader = "["+currentUser+";"+remoteAddressWithPort+";ssh;;;;;;]\n";
-                                netconf_ssh_output = new IOThread(ss.getStdout(),echoSocket.getOutputStream(),"output_thread_"+sessionId,ss,conn,customHeader);
+                                final String customHeader = "[" + currentUser + ";" + remoteAddressWithPort + ";ssh;;;;;;]\n";
+                                netconf_ssh_output = new IOThread(ss.getStdout(), echoSocket.getOutputStream(), "output_thread_" + sessionId, ss, conn, customHeader);
                                 netconf_ssh_output.setDaemon(false);
                                 netconf_ssh_output.start();
 
@@ -113,56 +109,57 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
                                 logger.error("SSH bridge could not create echo socket: {}", t.getMessage(), t);
 
                                 try {
-                                    if (netconf_ssh_input!=null){
+                                    if (netconf_ssh_input != null) {
                                         netconf_ssh_input.join();
                                     }
                                 } catch (InterruptedException e) {
                                     Thread.currentThread().interrupt();
-                                   logger.error("netconf_ssh_input join error ",e);
+                                    logger.error("netconf_ssh_input join error ", e);
                                 }
 
                                 try {
-                                    if (netconf_ssh_output!=null){
+                                    if (netconf_ssh_output != null) {
                                         netconf_ssh_output.join();
                                     }
                                 } catch (InterruptedException e) {
                                     Thread.currentThread().interrupt();
-                                    logger.error("netconf_ssh_output join error ",e);
+                                    logger.error("netconf_ssh_output join error ", e);
                                 }
                             }
                         } else {
-                            try {
-                                ss.getStdin().write("wrong subsystem requested - closing connection".getBytes());
-                                ss.close();
-                            } catch (IOException e) {
-                                logger.debug("excpetion while sending bad subsystem response",e);
-                            }
+                            String reason = "Only netconf subsystem is supported, requested:" + subsystem;
+                            closeSession(ss, reason);
                         }
                     }
                 };
             }
+
+            public void closeSession(ServerSession ss, String reason) {
+                logger.trace("Closing session - {}", reason);
+                try {
+                    ss.getStdin().write(reason.getBytes());
+                } catch (IOException e) {
+                    logger.debug("Exception while closing session", e);
+                }
+                ss.close();
+            }
+
             @Override
-            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException
-            {
-                return new Runnable()
-                {
+            public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException {
+                return new Runnable() {
                     @Override
-                    public void run()
-                    {
-                        //noop
+                    public void run() {
+                        closeSession(ss, "PTY request not supported");
                     }
                 };
             }
 
             @Override
-            public Runnable requestShell(final ServerSession ss) throws IOException
-            {
-                return new Runnable()
-                {
+            public Runnable requestShell(final ServerSession ss) throws IOException {
+                return new Runnable() {
                     @Override
-                    public void run()
-                    {
-                        //noop
+                    public void run() {
+                        closeSession(ss, "Shell not supported");
                     }
                 };
             }
@@ -172,35 +169,31 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
     }
 
     @Override
-    public String initAuthentication(ServerConnection sc)
-    {
-        logger.trace("Established connection with host {}",remoteAddressWithPort);
-        return "Established connection with host "+remoteAddressWithPort+"\r\n";
+    public String initAuthentication(ServerConnection sc) {
+        logger.trace("Established connection with host {}", remoteAddressWithPort);
+        return "Established connection with host " + remoteAddressWithPort + "\r\n";
     }
 
     @Override
-    public String[] getRemainingAuthMethods(ServerConnection sc)
-    {
-        return new String[] { ServerAuthenticationCallback.METHOD_PASSWORD };
+    public String[] getRemainingAuthMethods(ServerConnection sc) {
+        return new String[]{ServerAuthenticationCallback.METHOD_PASSWORD};
     }
 
     @Override
-    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username)
-    {
+    public AuthenticationResult authenticateWithNone(ServerConnection sc, String username) {
         return AuthenticationResult.FAILURE;
     }
 
     @Override
-    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password)
-    {
+    public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password) {
 
         try {
-            if (authProvider.authenticated(username,password)){
+            if (authProvider.authenticated(username, password)) {
                 currentUser = username;
-                logger.trace("user {}@{} authenticated",currentUser,remoteAddressWithPort);
+                logger.trace("user {}@{} authenticated", currentUser, remoteAddressWithPort);
                 return AuthenticationResult.SUCCESS;
             }
-        } catch (Exception e){
+        } catch (Exception e) {
             logger.warn("Authentication failed due to :" + e.getLocalizedMessage());
         }
         return AuthenticationResult.FAILURE;
@@ -208,8 +201,7 @@ public class SocketThread implements Runnable, ServerAuthenticationCallback, Ser
 
     @Override
     public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
-            byte[] publickey, byte[] signature)
-    {
+                                                          byte[] publickey, byte[] signature) {
         return AuthenticationResult.FAILURE;
     }
 
diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java
new file mode 100644 (file)
index 0000000..298f91c
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. 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
+ */
+
+package org.opendaylight.controller.netconf;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
+import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
+import org.opendaylight.controller.usermanager.IUserManager;
+import org.opendaylight.controller.usermanager.UserConfig;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+
+// This test is intended to be verified using ssh
+@Ignore
+public class KeyGeneratorTest {
+
+    @Mock
+    private IUserManager iUserManager;
+    File tempFile;
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+        doReturn(null).when(iUserManager).addLocalUser(any(UserConfig.class));
+        tempFile = File.createTempFile("odltest", ".tmp");
+        tempFile.deleteOnExit();
+    }
+
+    @After
+    public void tearDown() {
+        assertTrue(tempFile.delete());
+    }
+
+    @Test
+    public void test() throws Exception {
+        String pem = PEMGenerator.generateTo(tempFile);
+
+        AuthProvider authProvider = new AuthProvider(iUserManager, pem);
+        InetSocketAddress inetSocketAddress = new InetSocketAddress(Inet4Address.getLoopbackAddress().getHostAddress(), 8383);
+        NetconfSSHServer server = NetconfSSHServer.start(1830, inetSocketAddress, authProvider);
+
+        Thread serverThread = new  Thread(server,"netconf SSH server thread");
+        serverThread.start();
+        serverThread.join();
+    }
+}
index 91783ff..663a0b4 100644 (file)
@@ -8,15 +8,17 @@
 package org.opendaylight.controller.netconf;
 
 import ch.ethz.ssh2.Connection;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
 import junit.framework.Assert;
+import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
 import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+
 
 public class SSHServerTest {
 
@@ -34,8 +36,11 @@ public class SSHServerTest {
     public void startSSHServer() throws Exception{
         logger.info("Creating SSH server");
         StubUserManager um = new StubUserManager(USER,PASSWORD);
-        InputStream is = getClass().getResourceAsStream("/RSA.pk");
-        AuthProvider ap = new AuthProvider(um, is);
+        String pem;
+        try(InputStream is = getClass().getResourceAsStream("/RSA.pk")) {
+            pem = IOUtils.toString(is);
+        }
+        AuthProvider ap = new AuthProvider(um, pem);
         NetconfSSHServer server = NetconfSSHServer.start(PORT,tcpAddress,ap);
         sshServerThread = new Thread(server);
         sshServerThread.setDaemon(true);