How to add TLS/SSL support b/t Java's Netty and Open vSwitch

TLS/SSL plus certificate authentication between OpenFlow switches and the controller is included in Floodlight v1.1 and up. There is no need to implement the tutorial below to use TLS in Floodlight. You may wish to follow the steps to create and distribute you keys/certificates though.

Table of Contents

Introduction

Secure connections between any two entities is oftentimes desirable. In the context of Floodlight, perhaps you have the controller linked to switches over the public Internet or an unsecured network. In this case, adding security to your OpenFlow channels will prevent unwanted eyes from monitoring your connections and even worse, hijacking them and performing an attack.

SSL/TLS uses public and private key pairs in order to authenticate two hosts and encrypt the data being transmitted between them. The OpenFlow specification recommends encrypting the control channels for the reasons stated above, and the popular Open vSwitch (OVS) software switch supports SSL.

Floodlight v1.X will soon support TLS/SSL in the master branch. This page is merely documentation of the steps I took to get it working on my local branch. Here's how to do the same yourself if you'd like to play around before support is official.

What is TLS/SSL?

TLS stands for Transport Layer Security, and SSL stands for Secure Sockets Layer. They are different but are used interchangeably in conversation. Regardless of which implementation is used, they both boil down to the same encryption technique under the hood, umbrellaed underneath asymmetric cryptography. So, for our purposes, we can think of them as one in the same. Both facilitate secure communication of data from point A to point B.

In order for the secure connection to be established, we must first configure both endpoints with public and private keys. I won't give a full SSL/TLS primer, but in short, each end of a secure connection has a public key, a private key, and a certificate signed by a certificate authority. The public keys are just that – public. Anyone can see then and you won't disclose any secret information by disclosing them. The same is true for the certificates – they are public as well and can be seen by anyone. On the other hand, the private keys are secret and should never be accessible by anyone or anything besides the application using that key.

Here's a day in the life of a SSL connection:

When a secure handshake first takes place, each side will provide the other side with it's own public key. For example, A will send A_public to B and B will send B_public to A. Upon receipt of the public keys, each side will cross-reference the public keys to the certificates stored in the local keystore. Each side will try to match the public key in hand with a valid certificate. For example, A will take B_public and look for the corresponding B_certificate in its keystore. The certificates serve as validators for the public keys received. They are typically issued by a certificate authority, which is a 3rd party entity that will only grant certificates to trusted machines. So, if a public key is matched to a valid certificate, then the endpoint will assume the public key can be trusted. (Now, it's often a hassle if not a cost issue to have an official certificate made for a public key. So, many machines self-sign their certificates, which is okay, but it's not as secure, since anyone can sign their own certificates. We will self-sign our certificates in this tutorial.)

After each side of the connection verifies the public key received matches a valid certificate, the secure connection will be initialized. You might wonder how the connection can be secure if the public keys are known to anyone. The answer lies in the private keys, which are not disclosed. When a public/private key pair is made, the public key can be used to encrypt some plaintext data, but after encrypted, only the private key made with that public key can decrypt the data or ciphertext. The reason for this phenomenon is way beyond the scope of this tutorial but is backed by some serious discrete math theory. Given this fact, if B has A_public and A has B_public, B can send a message to A by encrypting the plaintext into ciphertext using its copy of A_public. If it sends the ciphertext to A, A will decrypt the message into plaintext using A_private (which is a secret). No matter who might see the message in between A and B, even if they know A_public, they will be unable to render the message as plaintext without also having knowledge of A_private. In this way, A and B can communicate both directions in a secure manner by simply encrypting each message with the public key of the recipient.

Again, the key (no pun intended) to all of this working is keeping all private keys secret. As soon as a private key is leaked, a man-in-the-middle attack is possible, since anyone with a private key can decrypt a message encrypted with the private key's corresponding public key.

In order for two devices to be able to communicate securely with TLS/SSL, they each must:

  1. Have a private and public key pair.
  2. Have a signed certificate (either self or authority).
  3. Provide their signed certificate to the correspondent node ahead of time (web browsers normally mask this process from you).

Preparing Your Keys and Certificates

In the context of Floodlight (a Java application) and OVS, we need to prepare two pairs of keys – one for Floodlight and one for OVS. We also need to generate a certificate for each. (We will share the certificates later on.)

Floodlight Key/Cert. Preparation

Java has its own key management framework, accessible from the keytool terminal program. keytool can be used to generate, import, export, view, and do pretty much any operation on keys and the keystore. By default, your JVM keystore is in $JAVA_HOME/jre/lib/security/<keystore-name>.jks, however, we will start with a clean slate and to make sure we don't mess up any existing keystore you might have. First, change directories to a known location where you want the keystore to reside. I'll change to my checked out copy of Floodlight. Then, invoke the keytool utility to create a new certificate, public, and private key. I specify an alias for the key, which will be helpful when referring to it later on. The keystore parameter specifies the name of the keystore file. Also, the storepass parameter creates a password for the keystore. By default, the JVM keystore has the password of changeit, so I am using the same for this tutorial (but if you are going to have a permanent or semi-permanent deployment, I highly recommend you come up with something else). The keytool utility will prompt you for general information. The last thing it will ask for is a password for the key itself, which can be different from the keystore password. For simplicity, I am setting the key's password the same as the keystore itself.

Ryans-MacBook-Pro:~ ryan$ cd /Users/ryan/Desktop/floodlight/
Ryans-MacBook-Pro:floodlight ryan$ keytool -genkey -keyalg RSA -alias floodlight -keystore keystore.jks -storepass changeit -validity 360 -keysize 2048
What is your first and last name?
  [Unknown]:  Ryan Izard
What is the name of your organizational unit?
  [Unknown]:  BSN
What is the name of your organization?
  [Unknown]:  BSN
What is the name of your City or Locality?
  [Unknown]:  Santa Clara
What is the name of your State or Province?
  [Unknown]:  CA
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Ryan Izard, OU=BSN, O=BSN, L=Santa Clara, ST=CA, C=US correct?
  [no]:  yes
Enter key password for <floodlight>
	(RETURN if same as keystore password):
Ryans-MacBook-Pro:floodlight ryan$

At this point, you will have a new file called keystore.jks in your working directory. This is your keystore and contains the key of the alias you defined; mine is "floodlight."

What we want to do next is copy the certificate to OVS. However, the key formats of the Java keystore and OVS's are different, so we need to convert the certificate to a format the OVS keystore can understand. This involves two steps. First, export the JKS keystore to the PKCS12 format.

Ryans-MacBook-Pro:floodlight ryan$ keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -srcstoretype jks -deststoretype pkcs12
Enter destination keystore password:
Re-enter new password:
Enter source keystore password:
Entry for alias floodlight successfully imported.
Import command completed:  1 entry successfully imported
Ryans-MacBook-Pro:floodlight ryan$

Then, use openssl to convert the PKCS12 file to a PEM file, which is readable by OVS.

Ryans-MacBook-Pro:floodlight ryan$ openssl pkcs12 -in keystore.p12 -out keystore.pem
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Ryans-MacBook-Pro:floodlight ryan$

At this point, our working directory contains keystore.jks – the Java keystore, keystore.p12 – the intermediate PKCS12 keystore, and keystore.pem – the PEM keystore readable by OVS.

Display the contents of keystore.pem. You will see the private key and a certificate for alias "floodight." The private key is of course private. The certificate, however, is what we want to give to OVS and looks something like this:

Ryans-MacBook-Pro:floodlight ryan$ cat keystore.pem
<private key here>
Bag Attributes
    friendlyName: floodlight
    localKeyID: <some hex key ID here>
subject=/C=US/ST=CA/L=Santa Clara/O=BSN/OU=BSN/CN=Ryan Izard
issuer=/C=US/ST=CA/L=Santa Clara/O=BSN/OU=BSN/CN=Ryan Izard
-----BEGIN CERTIFICATE-----
<some obscure-looking ASCII version of the actual certificate here>
-----END CERTIFICATE-----

From this output, copy the certificate from "Bag Attributes" to "-----END CERTIFICATE-----" to another plain text file and save it as "cacert.pem".

As soon as you have saved this file, you no longer need the files above we generated to expose the certificate in PEM format. So, for security reasons, remove them from the directory. (Note this method does not securely remove the files, so the contents are still on the disk just not accessible by the file names anymore. You might wish to securely erase the files on disk in a production environment.)

Ryans-MacBook-Pro:floodlight ryan$ rm keystore.pem keystore.p12

So, at this point, you will be left with keystore.jks – the password-protected Java keystore and cacert.pm – the certificate signing alias "floodlight"'s public key. The latter is what we want to provide OVS in order for it to verify the public key sent by the controller.

To finish up, copy cacert.pem from your working directory to your OVS installation at "/var/lib/openvswitch/pki/controllerca/cacert.pem". Note, this can be on the localhost if OVS is also running there, or on another machine entirely. It is important to use this directory, since we will assume Floodlight's certificate will be located there in the following steps.

Open vSwitch Key/Cert. Preparation

A prerequisite to using OVS with SSL is that you have built OVS with SSL support. If you have not done so, the following may fail to execute. Simply download the necessary SSL packages for your distribution according to the OVS installation guide and reinstall OVS before proceeding.

Just as we had to create a public and private key, along with a certificate for Floodlight, we must do the same for OVS. This time around though, things will be much simpler, since OVS has built-in tools for just this purpose and we do not have to work around the Java keystore. Create the public key, private key, and certificate for OVS by doing the following:

$ cd /etc/openvswitch
$ sudo ovs-pki req+sign sc switch
  sc-req.pem	Mon Mar  9 13:32:31 PDT 2015
  fingerprint 6024524eb2284248735a526f1f75e2e3c06b3cdd
$

At the conclusion of the above, we have created three new files –  the OVS public key, the OVS private key, and the OVS certificate, which is self-signed. We need to point OVS to it's newly-generated public and private keys, along with the controller certificate "cacert.pem" we created in the steps for Floodlight/Java above. (This should be in the correct directory as depicted below, otherwise OVS will not be able to locate it.)

$ sudo ovs-vsctl set-ssl \
>     /etc/openvswitch/sc-privkey.pem \
>     /etc/openvswitch/sc-cert.pem \
>     /var/lib/openvswitch/pki/controllerca/cacert.pem
$

OVS now has a public and private key, and also has a copy of the controller's certificate. Let's provide a copy of the certificate to the controller so that it can validate the OVS public key upon the TLS/SSL handshake. Copy OVS's switch certificate PEM file from "/var/lib/openvswitch/pki/switchca/cacert.pem" to the Floodlight controller machine. Once it's there import it to your existing Java keystore as show below, replacing "/path/to/certificate/copied/from/ovs/cacert.pem" with the actual location of your copied PEM file. Be certain to type "yes" when prompted to trust the certificate.

Ryans-MacBook-Pro:floodlight ryan$ keytool -import -alias "openvswitch" -keystore keystore.jks -file /path/to/certificate/copied/from/ovs/cacert.pem
Enter keystore password:
Owner: CN=OVS switchca CA Certificate (2014 May 22 15:25:49), OU=switchca, O=Open vSwitch, ST=CA, C=US
Issuer: CN=OVS switchca CA Certificate (2014 May 22 15:25:49), OU=switchca, O=Open vSwitch, ST=CA, C=US
Serial number: 1
Valid from: Thu May 22 18:25:49 EDT 2014 until: Sun May 19 18:25:49 EDT 2024
Certificate fingerprints:
	 MD5:  <some hex string here>
	 SHA1: <ditto>
	 SHA256: <ditto>
	 Signature algorithm name: MD5withRSA
	 Version: 1
Trust this certificate? [no]:  yes
Certificate was added to keystore

Congratulations! Your Java keystore now has a copy of the OVS key's certificate, and (if you completed the prior steps) your OVS now has a copy of the Java key's certificate. When the two applications try to conduct a handshake over SSL/TLS, the public keys exchanged should be able to be verified/cross-referenced against the stored certificates for each party.

Configuring Netty with SSL/TLS

We aren't done yet though, at least from Floodlight's perspective. Floodlight uses Netty as the underlying library that handles all network sockets. We need to configure Netty in such a way that it will try to setup a secure connection instead of vanilla TCP. In other words, we need to configure Netty so that it "knows" about the Java keystore we just created and can use the keys/certificates within.

Fortunately, the steps required are fairly straightforward and only require the modification of one file: src/main/java/net/floodlightcontroller/core/internal/OpenFlowPipelineFactory.java. In here, you will find the function getPipeline(), which can be replace with the following:

@Override
public ChannelPipeline getPipeline() throws Exception {
    ChannelPipeline pipeline = Channels.pipeline();
    OFChannelHandler handler = new OFChannelHandler(switchManager,
                                                    connectionListener,
                                                    pipeline,
                                                    debugCounters,
                                                    timer);
    TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    KeyStore tmpKS = null;
    tmFactory.init(tmpKS);
    KeyStore ks = KeyStore.getInstance("JKS");
 
	String pass = "changeit";
    ks.load(new FileInputStream("/Users/ryan/Desktop/floodlight/keystore.jks"), pass.toCharArray());

    // Set up key manager factory to use our key store
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(ks, pass.toCharArray());

    KeyManager[] km = kmf.getKeyManagers();
    TrustManager[] tm = tmFactory.getTrustManagers();

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(km, tm, null);
    SSLEngine sslEngine = sslContext.createSSLEngine();
    sslEngine.setUseClientMode(false);
    sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());
    sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
    sslEngine.setEnableSessionCreation(true);

    pipeline.addLast("ofchannelcrypto", new SslHandler(sslEngine));
    pipeline.addLast(PipelineHandler.OF_MESSAGE_DECODER, new OFMessageDecoder());
    pipeline.addLast(PipelineHandler.OF_MESSAGE_ENCODER, new OFMessageEncoder());
    pipeline.addLast(PipelineHandler.MAIN_IDLE, idleHandler);
    pipeline.addLast(PipelineHandler.READ_TIMEOUT, readTimeoutHandler);
    pipeline.addLast(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT,
                     new HandshakeTimeoutHandler(handler,
                                                 timer,
                                                 PipelineHandshakeTimeout.CHANNEL));
    pipeline.addLast(PipelineHandler.CHANNEL_HANDLER, handler);
    return pipeline;
}

This assumes all connections to the controller from switches will be via TLS/SSL, which is obviously a poor assumption; however, it demonstrates the basic Java/Netty components necessary in order to establish a secure connection between Floodlight and OVS. If you set your password as "changeit" when creating the JKS keystore, then you will notice it is correct as given in the Java code above; however, if you set it as something different, please note you should change the password as defined in String "pass" accordingly. Also, please be sure to update the FileInputStream with the correct path of your JKS keystore. For this tutorial, mine is at the path given, but yours is likely at a different location.

Testing/Using the Secure Connection

After you have completed the modifications to the Floodlight source code above, recompile the controller and run it. It will be waiting for switches to connect securely at port 6653 by default.

Tell your OVS to connect to the controller via SSL by setting the controller appropriately. On your OVS machine issue the following command, changing the IP address and switch name as necessary:

$ sudo ovs-vsctl set-controller s1 ssl:<your-controller-ip>:6653

 

If all goes according to plan, you should see the switch connect in Floodlight with no errors, and you should also be able to verify the connection on the OVS:

$ sudo ovs-vsctl show
873c293e-912d-4067-82ad-d1116d2ad39f
    Bridge "s1"
        Controller "ssl:<your-controller-ip>:6653"
            is_connected: true
        Port "s1"
            Interface "s1"
                type: internal
    ovs_version: "2.1.0"