mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-12-15 22:31:50 +01:00
Fix for CVE-2018-1000155
Denial of Service, Improper Authentication and Authorization, and Covert Channel in the OpenFlow 1.0+ handshake Change-Id: Ifd285208266a1f331f3b802cb656349aad1782a9
This commit is contained in:
parent
4ef245e8ea
commit
f69e3e3409
@ -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<String> 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));
|
||||
|
||||
@ -9,6 +9,7 @@ COMPILE_DEPS = [
|
||||
'//lib:netty-handler',
|
||||
'//lib:netty-transport',
|
||||
'//lib:netty-transport-native-epoll',
|
||||
'//lib:JACKSON',
|
||||
]
|
||||
|
||||
TEST_DEPS = [
|
||||
|
||||
@ -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<DeviceId> {
|
||||
|
||||
/**
|
||||
* 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<String> 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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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<String> 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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<Dpid, OFQueueStatsEntry> fullQueueStats =
|
||||
ArrayListMultimap.create();
|
||||
|
||||
protected final ConfigFactory factory =
|
||||
new ConfigFactory<DeviceId, OpenFlowDeviceConfig>(
|
||||
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
|
||||
|
||||
@ -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<String, Object> properties = new Hashtable<>();
|
||||
properties.put("openflowPorts",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user