From: Tomas Olvecky Date: Mon, 7 Apr 2014 07:44:31 +0000 (+0200) Subject: Resolve Bug:623 : Generate private key using bouncycastle. X-Git-Tag: autorelease-tag-v20140601202136_82eb3f9~269^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=58bbc881391e1119198fdebedab57c5cf6b5af1d Resolve Bug:623 : Generate private key using bouncycastle. 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 --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index e7c7bbe637..1a068eca69 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -1481,7 +1481,16 @@ configuration ${configuration.version} - + + org.bouncycastle + bcprov-jdk15on + 1.50 + + + org.bouncycastle + bcpkix-jdk15on + 1.50 + diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index a38a9b4f93..8a9318ea0b 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -287,6 +287,14 @@ org.apache.xml.resolver 1.2.0 + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + 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 index c0266c7bd2..0000000000 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/RSA.pk +++ /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----- diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java index 677d0dff8c..4ca9690211 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITTest.java @@ -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 getModuleFactories() { return getModuleFactoriesS(); } + static List 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(); diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml index 73e0a467c0..23a69b23fc 100644 --- a/opendaylight/netconf/netconf-ssh/pom.xml +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -1,4 +1,5 @@ - + netconf-subsystem org.opendaylight.controller @@ -36,6 +37,19 @@ org.opendaylight.controller usermanager + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.opendaylight.yangtools + mockito-configuration + test + @@ -58,7 +72,8 @@ maven-bundle-plugin - org.opendaylight.controller.netconf.osgi.NetconfSSHActivator + org.opendaylight.controller.netconf.ssh.osgi.NetconfSSHActivator + com.google.common.base, ch.ethz.ssh2, @@ -71,6 +86,7 @@ org.osgi.framework, org.osgi.util.tracker, org.slf4j, + org.bouncycastle.openssl diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java index 3d5318073d..2d380482ba 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/AuthProvider.java @@ -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 roles = new ArrayList(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 index 0000000000..73886c4f46 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/authentication/PEMGenerator.java @@ -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(); + } + } +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java similarity index 80% rename from opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java rename to opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java index 91624007d0..112bf67f69 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/osgi/NetconfSSHActivator.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/osgi/NetconfSSHActivator.java @@ -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 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); diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java index e07c7ed6ac..ce26910b97 100644 --- a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/netconf/ssh/threads/SocketThread.java @@ -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 index 0000000000..298f91ce8d --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/KeyGeneratorTest.java @@ -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(); + } +} diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java index 91783ff755..663a0b4a82 100644 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/SSHServerTest.java @@ -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);