import ietf-inet-types {prefix inet; revision-date "2013-07-15";}
+ import system-notifications { prefix system-notifications; revision-date "2013-09-27"; }
+
description "Openflow node ssl connection error.";
revision "2019-07-23" {
notification ssl-error {
description "Model for Node SSL Error Messages.";
uses error-message;
+ container switch-certificate {
+ uses system-notifications:switch-certificate;
+ }
}
}
import com.google.common.annotations.Beta;
import java.math.BigInteger;
import java.net.InetSocketAddress;
+import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.opendaylight.openflowjava.protocol.api.extensibility.AlienMessageListener;
*/
void fireConnectionReadyNotification();
+ /**
+ * Notify listener about switch certificate information.
+ * @param switchcertificate X509Certificate of switch
+ */
+ void onSwitchCertificateIdentified(X509Certificate switchcertificate);
+
/**
* Set listener for connection became ready-to-use event.
*
namespace "urn:opendaylight:openflow:system";
prefix "ofs";
+ import ietf-yang-types {prefix yang; revision-date "2013-07-15";}
+
revision "2013-09-27" {
description "#NOT_PUBLISHED# Model of system messages used in OpenFlow Protocol Library";
}
+ grouping x500-principal {
+ leaf country {
+ type string;
+ }
+ leaf state {
+ type string;
+ }
+ leaf locality {
+ type string;
+ }
+ leaf organization {
+ type string;
+ }
+ leaf organization-unit {
+ type string;
+ }
+ leaf common-name {
+ type string;
+ }
+ }
+ grouping switch-certificate {
+ container subject {
+ uses x500-principal;
+ }
+ container issuer {
+ uses x500-principal;
+ }
+ leaf valid-from {
+ type yang:date-and-time;
+ }
+ leaf valid-to {
+ type yang:date-and-time;
+ }
+ leaf serial-number {
+ type uint64;
+ }
+ leaf-list subject-alternate-names {
+ type string;
+ }
+ }
+
notification disconnect-event {
description "Disconnect notification";
leaf info {
leaf info {
type string;
}
+ container switch-certificate{
+ uses switch-certificate;
+ }
}
-}
\ No newline at end of file
+}
package org.opendaylight.openflowjava.protocol.impl.core;
import java.io.IOException;
+import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedTrustManager;
import org.opendaylight.openflowjava.protocol.api.connection.TlsConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Use "TLSv1", "TLSv1.1", "TLSv1.2" for specific TLS version
private static final String PROTOCOL = "TLS";
private final TlsConfiguration tlsConfig;
+ private static X509Certificate switchCertificate = null;
+ private static boolean isCustomTrustManagerEnabled;
private static final Logger LOG = LoggerFactory
.getLogger(SslContextFactory.class);
this.tlsConfig = tlsConfig;
}
+ public X509Certificate getSwitchCertificate() {
+ return switchCertificate;
+ }
+
+ public boolean isCustomTrustManagerEnabled() {
+ return isCustomTrustManagerEnabled;
+ }
+
+ public static void setSwitchCertificate(X509Certificate certificate) {
+ switchCertificate = certificate;
+ }
+
+ public static void setIsCustomTrustManagerEnabled(boolean customTrustManagerEnabled) {
+ isCustomTrustManagerEnabled = customTrustManagerEnabled;
+ }
+
public SSLContext getServerContext() {
String algorithm = Security
.getProperty("ssl.KeyManagerFactory.algorithm");
tmf.init(ts);
serverContext = SSLContext.getInstance(PROTOCOL);
- serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ if (isCustomTrustManagerEnabled) {
+ CustomTrustManager[] customTrustManager = new CustomTrustManager[tmf.getTrustManagers().length];
+ for (int i = 0; i < tmf.getTrustManagers().length; i++) {
+ customTrustManager[i] = new CustomTrustManager((X509ExtendedTrustManager)
+ tmf.getTrustManagers()[i]);
+ }
+ serverContext.init(kmf.getKeyManagers(), customTrustManager, null);
+ } else {
+ serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ }
} catch (IOException e) {
LOG.warn("IOException - Failed to load keystore / truststore."
+ " Failed to initialize the server-side SSLContext", e);
}
return serverContext;
}
+
+
+ private static class CustomTrustManager extends X509ExtendedTrustManager {
+ private final X509ExtendedTrustManager trustManager;
+
+ CustomTrustManager(final X509ExtendedTrustManager trustManager) {
+ this.trustManager = trustManager;
+ }
+
+ @Override
+ public void checkClientTrusted(final X509Certificate[] x509Certificates, final String authType,
+ final Socket socket) throws CertificateException {
+ SslContextFactory.setSwitchCertificate(x509Certificates[0]);
+ trustManager.checkClientTrusted(x509Certificates, authType, socket);
+ }
+
+ @Override
+ public void checkClientTrusted(final X509Certificate[] x509Certificates, final String authType)
+ throws CertificateException {
+ SslContextFactory.setSwitchCertificate(x509Certificates[0]);
+ trustManager.checkClientTrusted(x509Certificates, authType);
+ }
+
+ @Override
+ public void checkClientTrusted(final X509Certificate[] x509Certificates, final String authType,
+ final SSLEngine sslEngine) throws CertificateException {
+ SslContextFactory.setSwitchCertificate(x509Certificates[0]);
+ trustManager.checkClientTrusted(x509Certificates, authType, sslEngine);
+ }
+
+ @Override
+ public void checkServerTrusted(final X509Certificate[] x509Certificates, final String authType,
+ final SSLEngine sslEngine) throws CertificateException {
+ trustManager.checkServerTrusted(x509Certificates, authType, sslEngine);
+ }
+
+ @Override
+ public void checkServerTrusted(final X509Certificate[] x509Certificates, final String authType)
+ throws CertificateException {
+ trustManager.checkServerTrusted(x509Certificates, authType);
+ }
+
+ @Override
+ public void checkServerTrusted(final X509Certificate[] x509Certificates, final String authType,
+ final Socket socket) throws CertificateException {
+ trustManager.checkServerTrusted(x509Certificates, authType, socket);
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return trustManager.getAcceptedIssuers();
+ }
+
+ }
+
}
final SslHandler ssl = new SslHandler(engine);
final Future<Channel> handshakeFuture = ssl.handshakeFuture();
final ConnectionFacade finalConnectionFacade = connectionFacade;
+ if (sslFactory.isCustomTrustManagerEnabled()) {
+ handshakeFuture.addListener(future -> finalConnectionFacade
+ .onSwitchCertificateIdentified(sslFactory.getSwitchCertificate()));
+ }
handshakeFuture.addListener(future -> finalConnectionFacade.fireConnectionReadyNotification());
ch.pipeline().addLast(PipelineHandlers.SSL_HANDLER.name(), ssl);
}
import io.netty.channel.Channel;
import java.math.BigInteger;
import java.net.InetSocketAddress;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandler;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandlerRegistration;
import org.opendaylight.openflowjava.protocol.api.extensibility.AlienMessageListener;
import org.opendaylight.openflowjava.protocol.impl.core.OFVersionDetector;
import org.opendaylight.openflowjava.protocol.impl.core.PipelineHandlers;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoRequestMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ErrorMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortStatusMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEvent;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SslConnectionError;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SslConnectionErrorBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SwitchIdleEvent;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SystemNotificationsListener;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927._switch.certificate.IssuerBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927._switch.certificate.SubjectBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.ssl.connection.error.SwitchCertificate;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.ssl.connection.error.SwitchCertificateBuilder;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Notification;
import org.slf4j.Logger;
private BigInteger datapathId;
private ExecutorService executorService;
private final boolean useBarrier;
+ private X509Certificate switchCertificate;
/**
* Default constructor.
* @param channel the channel to be set - used for communication
} else if (message instanceof SwitchIdleEvent) {
systemListener.onSwitchIdleEvent((SwitchIdleEvent) message);
} else if (message instanceof SslConnectionError) {
- systemListener.onSslConnectionError((SslConnectionError) message);
+ systemListener.onSslConnectionError((SslConnectionError) new SslConnectionErrorBuilder()
+ .setInfo(((SslConnectionError) message).getInfo())
+ .setSwitchCertificate(buildSwitchCertificate()).build());
// OpenFlow messages
} else if (message instanceof EchoRequestMessage) {
if (outputManager != null) {
executorService.execute(() -> connectionReadyListener.onConnectionReady());
}
+ @Override
+ public void onSwitchCertificateIdentified(X509Certificate switchcertificate) {
+ this.switchCertificate = switchcertificate;
+ }
+
@Override
public <T extends OutboundQueueHandler> OutboundQueueHandlerRegistration<T> registerOutboundQueueHandler(
final T handler, final int maxQueueDepth, final long maxBarrierNanos) {
LOG.debug("PacketIn filtering {}abled", enabled ? "en" : "dis");
}
+ private SwitchCertificate buildSwitchCertificate() {
+ if (switchCertificate != null) {
+ SwitchCertificateBuilder switchCertificateBuilder = new SwitchCertificateBuilder();
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'-00:00'");
+ formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ try {
+ Map<String, String> subjectMap = new ConcurrentHashMap<>();
+ Map<String, String> issuerMap = new ConcurrentHashMap<>();
+ subjectMap.putAll(new LdapName(switchCertificate.getSubjectX500Principal().getName())
+ .getRdns().stream()
+ .collect(Collectors.toMap(rdn -> rdn.getType(), rdn -> rdn.getValue().toString())));
+ issuerMap.putAll(new LdapName(switchCertificate.getIssuerX500Principal().getName())
+ .getRdns().stream()
+ .collect(Collectors.toMap(rdn -> rdn.getType(), rdn -> rdn.getValue().toString())));
+ SubjectBuilder subjectBuilder = new SubjectBuilder();
+ subjectBuilder.setCommonName(subjectMap.get("CN"));
+ subjectBuilder.setCountry(subjectMap.get("C"));
+ subjectBuilder.setLocality(subjectMap.get("L"));
+ subjectBuilder.setOrganization(subjectMap.get("O"));
+ subjectBuilder.setOrganizationUnit(subjectMap.get("OU"));
+ subjectBuilder.setState(subjectMap.get("ST"));
+ IssuerBuilder issuerBuilder = new IssuerBuilder();
+ issuerBuilder.setCommonName(issuerMap.get("CN"));
+ issuerBuilder.setCountry(issuerMap.get("C"));
+ issuerBuilder.setLocality(issuerMap.get("L"));
+ issuerBuilder.setOrganization(issuerMap.get("O"));
+ issuerBuilder.setOrganizationUnit(issuerMap.get("OU"));
+ issuerBuilder.setState(issuerMap.get("ST"));
+ switchCertificateBuilder.setSubject(subjectBuilder.build());
+ switchCertificateBuilder.setIssuer(issuerBuilder.build());
+ } catch (InvalidNameException e) {
+ LOG.error("Exception ", e);
+ }
+ switchCertificateBuilder.setSerialNumber(switchCertificate.getSerialNumber());
+ try {
+ if (switchCertificate.getSubjectAlternativeNames() != null) {
+ List<String> subjectAlternateNames = new ArrayList<>();
+ switchCertificate.getSubjectAlternativeNames().forEach(generalName -> {
+ final Object value = generalName.get(1);
+ if (value instanceof String) {
+ subjectAlternateNames.add(((String) value));
+ }
+ });
+ switchCertificateBuilder.setSubjectAlternateNames(subjectAlternateNames);
+ } else {
+ switchCertificateBuilder.setSubjectAlternateNames(null);
+ }
+ } catch (CertificateParsingException e) {
+ LOG.error("Error encountered while parsing certificate ", e);
+ }
+ switchCertificateBuilder.setValidFrom(new DateAndTime(formatter.format(switchCertificate.getNotBefore())));
+ switchCertificateBuilder.setValidTo(new DateAndTime(formatter.format(switchCertificate.getNotAfter())));
+ return switchCertificateBuilder.build();
+ }
+ return null;
+ }
+
@Override
public void setDatapathId(final BigInteger datapathId) {
this.datapathId = datapathId;
/**
* Delay for Device removal from Operational DataStore.
*/
- DEVICE_DATASTORE_REMOVAL_DELAY;
+ DEVICE_DATASTORE_REMOVAL_DELAY,
+ /**
+ * Enable or disable customtrustmanager which adds switch certificate attributes to TLS
+ * failure notification property type.
+ */
+ ENABLE_CUSTOM_TRUST_MANAGER;
private static final Map<String, ConfigurationProperty> KEY_VALUE_MAP;
type non-zero-uint32-type;
default 500;
}
+
+ leaf enable-custom-trust-manager {
+ description "When true would use customtrustmanager to get switch certificate for TLS
+ authentication failure notification. ";
+ type boolean;
+ default "false";
+ }
}
}
#
# device-datastore-removal-delay=500
+#
+# When true, uses customtrustmanager to add switch certificate attributes to TLS authentification failure
+# notification
+#
+# enable-custom-trust-manager=false
+
#############################################################################
# #
# Forwarding Rule Manager Application Configuration #
return new NonZeroUint32Type(property);
}
+
+ @Override
+ public Boolean isEnableCustomTrustManager() {
+ return service.getProperty(ConfigurationProperty.ENABLE_CUSTOM_TRUST_MANAGER.toString(),
+ Boolean::valueOf);
+ }
}
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener;
+import org.opendaylight.openflowjava.protocol.impl.core.SslContextFactory;
import org.opendaylight.openflowplugin.api.OFConstants;
import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionManager;
final SystemNotificationsListener systemListener = new SystemNotificationsListenerImpl(connectionContext,
config.getEchoReplyTimeout().getValue().toJava(), executorService, notificationPublishService);
connectionAdapter.setSystemListener(systemListener);
+ SslContextFactory.setIsCustomTrustManagerEnabled(config.isEnableCustomTrustManager());
LOG.trace("connection ballet finished");
}
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.node.ssl.connection.error.service.rev190723.SslErrorBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.node.ssl.connection.error.service.rev190723.SslErrorType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.node.ssl.connection.error.service.rev190723.ssl.error.SwitchCertificateBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FeaturesReply;
ip = IpAddressBuilder.getDefaultInstance(
connectionContext.getConnectionAdapter().getRemoteAddress().getAddress().getHostAddress());
}
+ SwitchCertificateBuilder switchCertificateBuilder = new SwitchCertificateBuilder();
+ if (notification.getSwitchCertificate() != null) {
+ switchCertificateBuilder = new SwitchCertificateBuilder(notification.getSwitchCertificate());
+ }
notificationPublishService
.offerNotification(
new SslErrorBuilder().setType(SslErrorType.SslConFailed)
.setCode(SslErrorType.SslConFailed.getIntValue())
.setNodeIpAddress(ip)
.setData(notification.getInfo())
+ .setSwitchCertificate(notification.getSwitchCertificate() != null
+ ? switchCertificateBuilder.build() : null)
.build());
}
}
private static final long ECHO_REPLY_TIMEOUT = 500;
private static final int DEVICE_CONNECTION_RATE_LIMIT_PER_MIN = 0;
private static final int DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS = 60;
+ private static final boolean ENABLE_CUSTOM_TRUST_MANAGER = false;
@Before
public void setUp() {
.setEchoReplyTimeout(new NonZeroUint32Type(ECHO_REPLY_TIMEOUT))
.setDeviceConnectionRateLimitPerMin(DEVICE_CONNECTION_RATE_LIMIT_PER_MIN)
.setDeviceConnectionHoldTimeInSeconds(DEVICE_CONNECTION_HOLD_TIME_IN_SECONDS)
+ .setEnableCustomTrustManager(ENABLE_CUSTOM_TRUST_MANAGER)
.build(), threadPool, dataBroker, notificationPublishService);
connectionManagerImpl.setDeviceConnectedHandler(deviceConnectedHandler);