diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java index b035d61a63..9f09ac4369 100644 --- a/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java +++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/messaging/impl/NettyMessagingManager.java @@ -23,9 +23,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.net.ConnectException; -import java.security.Key; import java.security.KeyStore; import java.security.MessageDigest; +import java.security.PublicKey; import java.security.cert.Certificate; import java.time.Duration; import java.util.ArrayList; @@ -241,19 +241,15 @@ public class NettyMessagingManager implements MessagingService { try { for (Enumeration e = ks.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); - Key key = ks.getKey(alias, ksPwd); - Certificate[] certs = ks.getCertificateChain(alias); - log.debug("{} -> {}", alias, certs); - final byte[] encodedKey; - if (certs != null && certs.length > 0) { - encodedKey = certs[0].getEncoded(); - } else { - log.info("Could not find cert chain for {}, using fingerprint of key instead...", alias); - encodedKey = key.getEncoded(); + Certificate cert = ks.getCertificate(alias); + if (cert == null) { + log.info("No certificate for alias {}", alias); + continue; } + PublicKey key = cert.getPublicKey(); // Compute the certificate's fingerprint (use the key if certificate cannot be found) MessageDigest digest = MessageDigest.getInstance("SHA1"); - digest.update(encodedKey); + digest.update(key.getEncoded()); StringJoiner fingerprint = new StringJoiner(":"); for (byte b : digest.digest()) { fingerprint.add(String.format("%02X", b)); diff --git a/protocols/openflow/ctl/BUCK b/protocols/openflow/ctl/BUCK index 26a6a5819a..9b68ba95a4 100644 --- a/protocols/openflow/ctl/BUCK +++ b/protocols/openflow/ctl/BUCK @@ -9,6 +9,7 @@ COMPILE_DEPS = [ '//lib:netty-handler', '//lib:netty-transport', '//lib:netty-transport-native-epoll', + '//lib:JACKSON', ] TEST_DEPS = [ diff --git a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/OpenFlowDeviceConfig.java b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/OpenFlowDeviceConfig.java new file mode 100644 index 0000000000..148860a583 --- /dev/null +++ b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/OpenFlowDeviceConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * Licensed 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.onosproject.openflow.config; + +import com.google.common.annotations.Beta; +import org.onosproject.net.DeviceId; +import org.onosproject.net.config.Config; + +import java.util.Optional; + +/** + * Configuration for OpenFlow devices. + */ +@Beta +public class OpenFlowDeviceConfig extends Config { + + /** + * netcfg ConfigKey. + */ + public static final String CONFIG_KEY = "openflow"; + + public static final String CLIENT_KEY_ALIAS = "keyAlias"; + + @Override + public boolean isValid() { + return hasOnlyFields(CLIENT_KEY_ALIAS); + } + + /** + * Gets the certFile for the switch. + * + * @return cert file + */ + public Optional keyAlias() { + String keyAlias = get(CLIENT_KEY_ALIAS, ""); + return (keyAlias.isEmpty() ? Optional.empty() : Optional.ofNullable(keyAlias)); + } + + /** + * Sets the key alias for the Device. + * + * @param keyAlias key alias as string + * @return instance for chaining + */ + public OpenFlowDeviceConfig setKeyAlias(String keyAlias) { + return (OpenFlowDeviceConfig) setOrClear(CLIENT_KEY_ALIAS, keyAlias); + } +} diff --git a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/package-info.java b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/package-info.java new file mode 100644 index 0000000000..f746d88ba6 --- /dev/null +++ b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/config/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018-present Open Networking Foundation + * + * Licensed 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. + */ + +/** + * Implementation of the OpenFlow controller IO subsystem. + */ +package org.onosproject.openflow.config; diff --git a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java index 324e06d567..6122e63c43 100644 --- a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java +++ b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java @@ -30,10 +30,12 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.concurrent.GlobalEventExecutor; import org.onlab.util.ItemNotFoundException; import org.onosproject.net.DeviceId; +import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.net.driver.DefaultDriverData; import org.onosproject.net.driver.DefaultDriverHandler; import org.onosproject.net.driver.Driver; import org.onosproject.net.driver.DriverService; +import org.onosproject.openflow.config.OpenFlowDeviceConfig; import org.onosproject.openflow.controller.Dpid; import org.onosproject.openflow.controller.driver.OpenFlowAgent; import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver; @@ -54,11 +56,14 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -99,12 +104,14 @@ public class Controller { protected char[] ksPwd; protected char[] tsPwd; protected SSLContext sslContext; + protected KeyStore keyStore; // Perf. related configuration protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024; private DriverService driverService; private boolean enableOfTls = TLS_DISABLED; + private NetworkConfigRegistry netCfgService; // ************** // Initialization @@ -241,9 +248,9 @@ public class Controller { tmFactory.init(ts); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(new FileInputStream(ksLocation), ksPwd); - kmf.init(ks, ksPwd); + keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream(ksLocation), ksPwd); + kmf.init(keyStore, ksPwd); sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), null); @@ -275,6 +282,36 @@ public class Controller { return (this.systemStartTime); } + public boolean isValidCertificate(Long dpid, Certificate peerCert) { + if (netCfgService == null) { + // netcfg service not available; accept any cert + return true; + } + + DeviceId deviceId = DeviceId.deviceId(Dpid.uri(new Dpid(dpid))); + OpenFlowDeviceConfig config = + netCfgService.getConfig(deviceId, OpenFlowDeviceConfig.class); + if (config == null) { + // Config not set for device, accept any certificate + return true; + } + + Optional alias = config.keyAlias(); + if (!alias.isPresent()) { + // Config for device does not specify a certificate chain, accept any cert + return true; + } + + try { + Certificate configuredCert = keyStore.getCertificate(alias.get()); + //FIXME there's probably a better way to compare these + return Objects.deepEquals(peerCert, configuredCert); + } catch (KeyStoreException e) { + log.info("failed to load key", e); + } + return false; + } + /** * Forward to the driver-manager to get an IOFSwitch instance. * @@ -317,10 +354,17 @@ public class Controller { return ofSwitchDriver; } + @Deprecated public void start(OpenFlowAgent ag, DriverService driverService) { + start(ag, driverService, null); + } + + public void start(OpenFlowAgent ag, DriverService driverService, + NetworkConfigRegistry netCfgService) { log.info("Starting OpenFlow IO"); this.agent = ag; this.driverService = driverService; + this.netCfgService = netCfgService; this.init(); this.run(); } diff --git a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java index ac046d9736..55ea65e799 100644 --- a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java +++ b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; +import java.security.cert.Certificate; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -36,6 +37,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; + import org.onlab.packet.IpAddress; import org.onosproject.openflow.controller.Dpid; import org.onosproject.openflow.controller.OpenFlowSession; @@ -86,10 +88,13 @@ import org.slf4j.LoggerFactory; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.ReadTimeoutException; import io.netty.util.ReferenceCountUtil; +import javax.net.ssl.SSLPeerUnverifiedException; + /** * Channel handler deals with the switch connection and dispatches * switch messages to the appropriate locations. @@ -310,9 +315,15 @@ class OFChannelHandler extends ChannelInboundHandlerAdapter */ WAIT_FEATURES_REPLY(false) { @Override - void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) + void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) throws IOException { - h.thisdpid = m.getDatapathId().getLong(); + Long dpid = m.getDatapathId().getLong(); + if (!h.setDpid(dpid, h.channel)) { + log.error("Switch presented invalid certificate for dpid {}. Disconnecting", + dpid); + h.channel.disconnect(); + return; + } log.debug("Received features reply for switch at {} with dpid {}", h.getSwitchInfoString(), h.thisdpid); @@ -1505,6 +1516,25 @@ class OFChannelHandler extends ChannelInboundHandlerAdapter this.lastStateChange = System.currentTimeMillis(); } + private boolean setDpid(Long dpid, Channel channel) { + ChannelHandlerContext sslContext = channel.pipeline().context(SslHandler.class); + if (sslContext != null) { + try { + SslHandler sslHandler = (SslHandler) sslContext.handler(); + Certificate[] certs = sslHandler.engine().getSession().getPeerCertificates(); + Certificate cert = certs.length > 0 ? certs[0] : null; + if (!controller.isValidCertificate(dpid, cert)) { + return false; + } + } catch (SSLPeerUnverifiedException e) { + log.info("Switch with dpid {} is an unverified SSL peer.", dpid, e); + return false; + } + } + this.thisdpid = dpid; + return true; + } + /** * Send hello message to the switch using the handshake transactions ids. * @throws IOException diff --git a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java index ab7009f301..9113e5c595 100644 --- a/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java +++ b/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java @@ -28,7 +28,12 @@ import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onosproject.cfg.ComponentConfigService; import org.onosproject.core.CoreService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.basics.SubjectFactories; import org.onosproject.net.driver.DriverService; +import org.onosproject.openflow.config.OpenFlowDeviceConfig; import org.onosproject.openflow.controller.DefaultOpenFlowPacketContext; import org.onosproject.openflow.controller.Dpid; import org.onosproject.openflow.controller.OpenFlowController; @@ -107,6 +112,9 @@ public class OpenFlowControllerImpl implements OpenFlowController { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected ComponentConfigService cfgService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry netCfgService; + @Property(name = "openflowPorts", value = DEFAULT_OFPORT, label = "Port numbers (comma separated) used by OpenFlow protocol; default is 6633,6653") private String openflowPorts = DEFAULT_OFPORT; @@ -173,14 +181,25 @@ public class OpenFlowControllerImpl implements OpenFlowController { protected Multimap fullQueueStats = ArrayListMultimap.create(); + protected final ConfigFactory factory = + new ConfigFactory( + SubjectFactories.DEVICE_SUBJECT_FACTORY, + OpenFlowDeviceConfig.class, OpenFlowDeviceConfig.CONFIG_KEY) { + @Override + public OpenFlowDeviceConfig createConfig() { + return new OpenFlowDeviceConfig(); + } + }; + private final Controller ctrl = new Controller(); @Activate public void activate(ComponentContext context) { coreService.registerApplication(APP_ID, this::cleanup); cfgService.registerProperties(getClass()); + netCfgService.registerConfigFactory(factory); ctrl.setConfigParams(context.getProperties()); - ctrl.start(agent, driverService); + ctrl.start(agent, driverService, netCfgService); } private void cleanup() { @@ -197,13 +216,14 @@ public class OpenFlowControllerImpl implements OpenFlowController { public void deactivate() { cleanup(); cfgService.unregisterProperties(getClass(), false); + netCfgService.unregisterConfigFactory(factory); } @Modified public void modified(ComponentContext context) { ctrl.stop(); ctrl.setConfigParams(context.getProperties()); - ctrl.start(agent, driverService); + ctrl.start(agent, driverService, netCfgService); } @Override diff --git a/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java b/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java index f3f776960d..16fea6bb17 100644 --- a/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java +++ b/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java @@ -32,6 +32,7 @@ import org.junit.Before; import org.junit.Test; import org.onosproject.cfg.ComponentConfigService; import org.onosproject.core.CoreService; +import org.onosproject.net.config.NetworkConfigRegistry; import org.onosproject.openflow.OpenflowSwitchDriverAdapter; import org.onosproject.openflow.controller.Dpid; import org.onosproject.openflow.controller.OpenFlowSwitch; @@ -149,6 +150,9 @@ public class OpenFlowControllerImplTest { controller.cfgService = mockConfigService; replay(mockConfigService); + NetworkConfigRegistry netConfigService = EasyMock.createMock(NetworkConfigRegistry.class); + controller.netCfgService = netConfigService; + ComponentContext mockContext = EasyMock.createMock(ComponentContext.class); Dictionary properties = new Hashtable<>(); properties.put("openflowPorts",