<name>md5-server-channel-factory</name>
</md5-server-channel-factory>
-->
+
+ <!--
+ Uncomment this block to enable PCEPS TLS communication. Change example values as needed.
+ <tls>
+ <keystore>configuration/ctl.jks</keystore>
+ <keystore-type>JKS</keystore-type>
+ <keystore-path-type>PATH</keystore-path-type>
+ <keystore-password>opendaylight</keystore-password>
+ <truststore>configuration/truststore.jks</truststore>
+ <truststore-type>JKS</truststore-type>
+ <truststore-path-type>PATH</truststore-path-type>
+ <truststore-password>opendaylight</truststore-password>
+ <certificate-password>opendaylight</certificate-password>
+ </tls>
+ -->
</module>
</modules>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-handler</artifactId>
+ </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
*/
public final class PCEPDispatcherImplModule extends org.opendaylight.controller.config.yang.pcep.impl.AbstractPCEPDispatcherImplModule {
+ private static final String VALUE_IS_NOT_SET = "value is not set.";
+ private static final String KEYSTORE = "keystore";
+ private static final String TRUSTSTORE = "truststore";
+ private static final String PASSWORD = " password ";
+ private static final String LOCATION = " location ";
+ private static final String TYPE = " type ";
+ private static final String PATH_TYPE = " path" + TYPE;
+
public PCEPDispatcherImplModule(final org.opendaylight.controller.config.api.ModuleIdentifier name,
final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
super(name, dependencyResolver);
@Override
public void validate() {
super.validate();
- JmxAttributeValidationException.checkNotNull(getMaxUnknownMessages(), "value is not set.", maxUnknownMessagesJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getMaxUnknownMessages(), VALUE_IS_NOT_SET, maxUnknownMessagesJmxAttribute);
JmxAttributeValidationException.checkCondition(getMaxUnknownMessages() > 0, "Parameter 'maxUnknownMessages' "
+ "must be greater than 0", maxUnknownMessagesJmxAttribute);
+ if (getTls() != null) {
+ JmxAttributeValidationException.checkNotNull(getTls().getCertificatePassword(), "certificate" + PASSWORD + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getKeystore(), KEYSTORE + LOCATION + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getKeystorePassword(), KEYSTORE + PASSWORD + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getKeystorePathType(), KEYSTORE + PATH_TYPE + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getKeystoreType(), KEYSTORE + TYPE + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getTruststore(), TRUSTSTORE + LOCATION + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getTruststorePassword(), TRUSTSTORE + PASSWORD + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getTruststorePathType(), TRUSTSTORE + PATH_TYPE + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(getTls().getTruststoreType(), TRUSTSTORE + TYPE + VALUE_IS_NOT_SET, tlsJmxAttribute);
+ }
}
@Override
public java.lang.AutoCloseable createInstance() {
Open localPrefs = getPcepSessionProposalFactoryDependency().getSessionProposal(null, 0);
- DefaultPCEPSessionNegotiatorFactory negFactory = new DefaultPCEPSessionNegotiatorFactory(localPrefs, getMaxUnknownMessages());
+ DefaultPCEPSessionNegotiatorFactory negFactory = new DefaultPCEPSessionNegotiatorFactory(localPrefs, getMaxUnknownMessages(), getTls());
return new PCEPDispatcherImpl(getPcepExtensionsDependency().getMessageHandlerRegistry(), negFactory, getBossGroupDependency(), getWorkerGroupDependency(), getMd5ChannelFactoryDependency(), getMd5ServerChannelFactoryDependency());
}
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
+import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Promise;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
import org.opendaylight.protocol.framework.AbstractSessionNegotiator;
+import org.opendaylight.protocol.pcep.impl.tls.SslContextFactory;
import org.opendaylight.protocol.pcep.spi.PCEPErrors;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.StarttlsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.Message;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcep.error.object.ErrorObject;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.error.type.SessionCase;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.start.tls.message.StartTlsMessageBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* point we move into OpenWait state.
*/
IDLE,
+ /**
+ * Waiting for the peer's StartTLS message
+ */
+ START_TLS_WAIT,
/**
* Waiting for the peer's OPEN message.
*/
private Future<?> failTimer;
private Open localPrefs;
private Open remotePrefs;
+ private Tls tlsConfiguration;
protected AbstractPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel) {
super(promise, channel);
case FINISHED:
case IDLE:
break;
+ case START_TLS_WAIT:
+ sendErrorMessage(PCEPErrors.STARTTLS_TIMER_EXP);
+ negotiationFailed(new TimeoutException("StartTLSWait timer expired"));
+ AbstractPCEPSessionNegotiator.this.state = State.FINISHED;
case KEEP_WAIT:
sendErrorMessage(PCEPErrors.NO_MSG_BEFORE_EXP_KEEPWAIT);
negotiationFailed(new TimeoutException("KeepWait timer expired"));
@Override
protected final void startNegotiation() {
Preconditions.checkState(this.state == State.IDLE);
+ if (this.tlsConfiguration != null) {
+ this.sendMessage(new StarttlsBuilder().setStartTlsMessage(new StartTlsMessageBuilder().build()).build());
+ this.state = State.START_TLS_WAIT;
+ scheduleFailTimer();
+ LOG.info("Started TLS connection negotiation with peer {}", this.channel);
+ } else {
+ startNegotiationWithOpen();
+ }
+ }
+
+ private void startNegotiationWithOpen() {
this.localPrefs = getInitialProposal();
final OpenMessage m = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder().setOpenMessage(
new OpenMessageBuilder().setOpen(this.localPrefs).build()).build();
return true;
}
+ private boolean handleMessageStartTlsWait(final Message msg) {
+ if (msg instanceof Starttls) {
+ final SslContextFactory sslFactory = new SslContextFactory(this.tlsConfiguration);
+ final SSLContext sslContext = sslFactory.getServerContext();
+ if (sslContext == null) {
+ this.sendErrorMessage(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS);
+ negotiationFailed(new IllegalStateException("Failed to establish a TLS connection."));
+ this.state = State.FINISHED;
+ return true;
+ }
+ final SSLEngine engine = sslContext.createSSLEngine();
+ engine.setNeedClientAuth(true);
+ engine.setUseClientMode(false);
+ this.channel.pipeline().addFirst(new SslHandler(engine));
+ LOG.info("PCEPS TLS connection with peer: {} established succesfully.", this.channel);
+ startNegotiationWithOpen();
+ return true;
+ } else if (! (msg instanceof Pcerr)) {
+ this.sendErrorMessage(PCEPErrors.NON_STARTTLS_MSG_RCVD);
+ negotiationFailed(new IllegalStateException("Unexpected message recieved."));
+ this.state = State.FINISHED;
+ return true;
+ }
+ return false;
+ }
+
@Override
protected final void handleMessage(final Message msg) {
this.failTimer.cancel(false);
case FINISHED:
case IDLE:
throw new IllegalStateException("Unexpected handleMessage in state " + this.state);
+ case START_TLS_WAIT:
+ if (handleMessageStartTlsWait(msg)) {
+ return;
+ }
+ break;
case KEEP_WAIT:
if (handleMessageKeepWait(msg)) {
return;
State getState() {
return this.state;
}
+
+ public void setTlsConfiguration(final Tls tlsConfiguration) {
+ this.tlsConfiguration = tlsConfiguration;
+ }
}
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-
import io.netty.channel.Channel;
import io.netty.util.concurrent.Promise;
-
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
import org.opendaylight.protocol.pcep.PCEPSessionListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.OpenBuilder;
private final int maxUnknownMessages;
public DefaultPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel,
- final PCEPSessionListener listener, final short sessionId, final int maxUnknownMessages, final Open localPrefs) {
+ final PCEPSessionListener listener, final short sessionId, final int maxUnknownMessages, final Open localPrefs, final Tls tlsConfiguration) {
super(promise, channel);
+ super.setTlsConfiguration(tlsConfiguration);
this.maxUnknownMessages = maxUnknownMessages;
this.myLocalPrefs = new OpenBuilder().setKeepalive(localPrefs.getKeepalive()).setDeadTimer(localPrefs.getDeadTimer()).setSessionId(
sessionId).setTlvs(localPrefs.getTlvs()).build();
this.listener = Preconditions.checkNotNull(listener);
}
+ public DefaultPCEPSessionNegotiator(final Promise<PCEPSessionImpl> promise, final Channel channel,
+ final PCEPSessionListener listener, final short sessionId, final int maxUnknownMessages, final Open localPrefs) {
+ this(promise, channel, listener, sessionId, maxUnknownMessages, localPrefs, null);
+ }
+
private final Open myLocalPrefs;
@Override
package org.opendaylight.protocol.pcep.impl;
import com.google.common.base.Preconditions;
-
import io.netty.channel.Channel;
import io.netty.util.concurrent.Promise;
-
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
import org.opendaylight.protocol.pcep.PCEPSessionListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open;
public final class DefaultPCEPSessionNegotiatorFactory extends AbstractPCEPSessionNegotiatorFactory {
private final Open localPrefs;
private final int maxUnknownMessages;
+ private final Tls tlsConfiguration;
public DefaultPCEPSessionNegotiatorFactory(final Open localPrefs, final int maxUnknownMessages) {
+ this(localPrefs, maxUnknownMessages, null);
+ }
+
+ public DefaultPCEPSessionNegotiatorFactory(final Open localPrefs, final int maxUnknownMessages, final Tls tlsConfiguration) {
this.localPrefs = Preconditions.checkNotNull(localPrefs);
this.maxUnknownMessages = maxUnknownMessages;
+ this.tlsConfiguration = tlsConfiguration;
}
@Override
protected AbstractPCEPSessionNegotiator createNegotiator(final Promise<PCEPSessionImpl> promise, final PCEPSessionListener listener,
final Channel channel, final short sessionId) {
- return new DefaultPCEPSessionNegotiator(promise, channel, listener, sessionId, this.maxUnknownMessages, this.localPrefs);
+ return new DefaultPCEPSessionNegotiator(promise, channel, listener, sessionId, this.maxUnknownMessages, this.localPrefs, this.tlsConfiguration);
}
}
--- /dev/null
+/*
+ * Copyright (c) 2015 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.protocol.pcep.impl.tls;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class for setting up TLS connection.
+ */
+public class SslContextFactory {
+
+ private static final String PROTOCOL = "TLS";
+ private final Tls tlsConfig;
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(SslContextFactory.class);
+
+ /**
+ * @param tlsConfig
+ * TLS configuration object, contains keystore locations and
+ * keystore types
+ */
+ public SslContextFactory(final Tls tlsConfig) {
+ this.tlsConfig = Preconditions.checkNotNull(tlsConfig);
+ }
+
+ public SSLContext getServerContext() {
+ try {
+ final KeyStore ks = KeyStore.getInstance(this.tlsConfig.getKeystoreType().name());
+ ks.load(SslKeyStore.asInputStream(this.tlsConfig.getKeystore(), this.tlsConfig.getKeystorePathType()),
+ this.tlsConfig.getKeystorePassword().toCharArray());
+ final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, this.tlsConfig.getCertificatePassword().toCharArray());
+
+ final KeyStore ts = KeyStore.getInstance(this.tlsConfig.getTruststoreType().name());
+ ts.load(SslKeyStore.asInputStream(this.tlsConfig.getTruststore(), this.tlsConfig.getTruststorePathType()),
+ this.tlsConfig.getTruststorePassword().toCharArray());
+ final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ts);
+
+ final SSLContext serverContext = SSLContext.getInstance(PROTOCOL);
+ serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ return serverContext;
+ } catch (final IOException e) {
+ LOG.warn("IOException - Failed to load keystore / truststore."
+ + " Failed to initialize the server-side SSLContext", e);
+ } catch (final NoSuchAlgorithmException e) {
+ LOG.warn("NoSuchAlgorithmException - Unsupported algorithm."
+ + " Failed to initialize the server-side SSLContext", e);
+ } catch (final CertificateException e) {
+ LOG.warn("CertificateException - Unable to access certificate (check password)."
+ + " Failed to initialize the server-side SSLContext", e);
+ } catch (final Exception e) {
+ LOG.warn("Exception - Failed to initialize the server-side SSLContext", e);
+ }
+ //TODO try to use default SSLContext instance?
+ return null;
+ }
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2015 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.protocol.pcep.impl.tls;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.impl.rev130627.PathType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class SslKeyStore {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SslKeyStore.class);
+
+ private SslKeyStore() {
+ throw new UnsupportedOperationException("Utility class shouldn't be instantiated");
+ }
+
+ /**
+ * InputStream instance of key - key location is on classpath or specific path
+ *
+ * @param filename
+ * keystore location
+ * @param pathType
+ * keystore location type - "classpath" or "path"
+ *
+ * @return key as InputStream
+ */
+ public static InputStream asInputStream(final String filename, final PathType pathType) {
+ InputStream in;
+ switch (pathType) {
+ case CLASSPATH:
+ in = SslKeyStore.class.getResourceAsStream(filename);
+ if (in == null) {
+ throw new IllegalStateException("KeyStore file not found: "
+ + filename);
+ }
+ break;
+ case PATH:
+ LOG.debug("Current dir using System: {}", System.getProperty("user.dir"));
+ final File keystorefile = new File(filename);
+ try {
+ in = new FileInputStream(keystorefile);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("KeyStore file not found: "
+ + filename,e);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown path type: " + pathType);
+ }
+ return in;
+ }
+}
+
"Initial revision";
}
+ typedef path-type {
+ type enumeration {
+ enum "PATH";
+ enum "CLASSPATH";
+ }
+ }
+
+ typedef store-type {
+ type enumeration {
+ enum "JKS";
+ enum "PKCS12";
+ }
+ }
+
identity pcep-parser-base {
base config:module-type;
config:provided-service spi:extension;
}
}
}
+
+ container tls {
+ leaf keystore {
+ description "keystore location";
+ type string;
+ mandatory true;
+ }
+ leaf keystore-type {
+ description "keystore type (JKS or PKCS12)";
+ type store-type;
+ mandatory true;
+ }
+ leaf keystore-path-type {
+ description "keystore path type (CLASSPATH or PATH)";
+ type path-type;
+ mandatory true;
+ }
+ leaf keystore-password {
+ description "password protecting keystore";
+ type string;
+ mandatory true;
+ }
+ leaf certificate-password {
+ description "password protecting certificate";
+ type string;
+ mandatory true;
+ }
+ leaf truststore {
+ description "truststore location";
+ type string;
+ mandatory true;
+ }
+ leaf truststore-type {
+ description "truststore type (JKS or PKCS12)";
+ type store-type;
+ mandatory true;
+ }
+ leaf truststore-path-type {
+ description "truststore path type (CLASSPATH or PATH)";
+ type path-type;
+ mandatory true;
+ }
+ leaf truststore-password {
+ description "password protecting truststore";
+ type string;
+ mandatory true;
+ }
+ }
}
}
import org.opendaylight.controller.config.yang.netty.threadgroup.NettyThreadgroupModuleMXBean;
import org.opendaylight.controller.config.yang.pcep.spi.SimplePCEPExtensionProviderContextModuleFactory;
import org.opendaylight.controller.config.yang.pcep.spi.SimplePCEPExtensionProviderContextModuleMXBean;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.impl.rev130627.PathType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.impl.rev130627.StoreType;
public class PCEPDispatcherImplModuleTest extends AbstractConfigTest {
private static final String EXTENSION_INSTANCE_NAME = "pcep-extension-privider";
private static final String EXTENSION_FACTORYNAME = SimplePCEPExtensionProviderContextModuleFactory.NAME;
+ private static final String STORE_PASSWORD = "opendaylight";
+ private static final String KEYSTORE = "ctl.jks";
+ private static final String TRUSTSTORE = "truststore.jks";
+
@Before
public void setUp() throws Exception {
super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(this.mockedContext, new PCEPDispatcherImplModuleFactory(), new PCEPSessionProposalFactoryImplModuleFactory(), new NettyThreadgroupModuleFactory(), new SimplePCEPExtensionProviderContextModuleFactory()));
}
}
+ @Test
+ public void testValidationExceptionCertificatePassword() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(null, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("certificate password"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionKeystoreLocation() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, null, STORE_PASSWORD, PathType.PATH, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("keystore location"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionKeystorePassword() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, null, PathType.PATH, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("keystore password"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionKeystorePathType() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, null, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("keystore path type"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionKeystoreType() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, null, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("keystore type"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionTruststoreLocation() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS, null, STORE_PASSWORD,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("truststore location"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionTruststorePassword() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS, TRUSTSTORE, null,
+ PathType.PATH, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("truststore password"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionTruststorePathType() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ null, StoreType.JKS));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("truststore path type"));
+ }
+ }
+
+ @Test
+ public void testValidationExceptionTruststoreType() throws Exception {
+ try {
+ createDispatcherInstance(1, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS, TRUSTSTORE, STORE_PASSWORD,
+ PathType.PATH, null));
+ fail();
+ } catch (final ValidationException e) {
+ assertTrue(e.getMessage().contains("truststore type"));
+ }
+ }
+
@Test
public void testCreateBean() throws Exception {
final CommitStatus status = createDispatcherInstance(5);
assertStatus(status, 0, 1, 4);
}
+ @Test
+ public void testCreateBeanWithTls() throws Exception {
+ final CommitStatus status = createDispatcherInstance(5, createTls(STORE_PASSWORD, KEYSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS,
+ TRUSTSTORE, STORE_PASSWORD, PathType.PATH, StoreType.JKS));
+ assertBeanCount(1, FACTORY_NAME);
+ assertStatus(status, 5, 0, 0);
+ }
+
private CommitStatus createDispatcherInstance(final Integer maxUnknownMessages) throws Exception {
final ConfigTransactionJMXClient transaction = this.configRegistryClient.createTransaction();
createDispatcherInstance(transaction, maxUnknownMessages);
return transaction.commit();
}
+ private CommitStatus createDispatcherInstance(final Integer maxUnknownMessages, final Tls tls) throws Exception {
+ final ConfigTransactionJMXClient transaction = this.configRegistryClient.createTransaction();
+ createDispatcherInstance(transaction, maxUnknownMessages, tls);
+ return transaction.commit();
+ }
+
public static ObjectName createDispatcherInstance(final ConfigTransactionJMXClient transaction, final Integer maxUnknownMessages)
throws Exception {
final ObjectName nameCreated = transaction.createModule(FACTORY_NAME, INSTANCE_NAME);
return nameCreated;
}
+ private static ObjectName createDispatcherInstance(final ConfigTransactionJMXClient transaction, final Integer maxUnknownMessages,
+ final Tls tls) throws Exception {
+ final ObjectName objName = createDispatcherInstance(transaction, maxUnknownMessages);
+ final PCEPDispatcherImplModuleMXBean mxBean = transaction.newMXBeanProxy(objName, PCEPDispatcherImplModuleMXBean.class);
+ mxBean.setTls(tls);
+ return objName;
+ }
+
private static ObjectName createExtensionsInstance(final ConfigTransactionJMXClient transaction) throws InstanceAlreadyExistsException {
final ObjectName nameCreated = transaction.createModule(EXTENSION_FACTORYNAME, EXTENSION_INSTANCE_NAME);
transaction.newMXBeanProxy(nameCreated, SimplePCEPExtensionProviderContextModuleMXBean.class);
return nameCreated;
}
+ private static Tls createTls(final String certificatePassword, final String keystore, final String keystorePassword,
+ final PathType keystorePathType, final StoreType keystoreType, final String truststore,
+ final String truststorePassword, final PathType truststorePathType, final StoreType truststoreType) {
+ final Tls tls = new Tls();
+ tls.setCertificatePassword(certificatePassword);
+ tls.setKeystore(keystore);
+ tls.setKeystorePassword(keystorePassword);
+ tls.setKeystorePathType(keystorePathType);
+ tls.setKeystoreType(keystoreType);
+ tls.setTruststore(truststore);
+ tls.setTruststorePassword(truststorePassword);
+ tls.setTruststorePathType(truststorePathType);
+ tls.setTruststoreType(truststoreType);
+ return tls;
+ }
+
}
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.KeepaliveBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.OpenBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.StarttlsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.keepalive.message.KeepaliveMessageBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.message.OpenMessageBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.start.tls.message.StartTlsMessageBuilder;
import org.opendaylight.yangtools.yang.binding.Notification;
public class AbstractPCEPSessionTest {
protected Open openMsg;
+ protected Starttls startTlsMsg;
+
protected Keepalive kaMsg;
protected SimpleSessionListener listener;
doReturn(true).when(this.future).cancel(false);
doReturn(this.future).when(this.eventLoop).schedule(any(Runnable.class), any(long.class), any(TimeUnit.class));
doReturn(this.pipeline).when(this.pipeline).replace(any(ChannelHandler.class), any(String.class), any(ChannelHandler.class));
+ doReturn(this.pipeline).when(this.pipeline).addFirst(any(ChannelHandler.class));
doReturn(true).when(this.channel).isActive();
doReturn(mock(ChannelFuture.class)).when(this.channel).close();
doReturn(new InetSocketAddress(IP_ADDRESS, 4189)).when(this.channel).remoteAddress();
new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.OpenBuilder().setDeadTimer(
DEADTIMER).setKeepalive(KEEP_ALIVE).setSessionId((short) 0).build()).build()).build();
this.kaMsg = new KeepaliveBuilder().setKeepaliveMessage(new KeepaliveMessageBuilder().build()).build();
+ this.startTlsMsg = new StarttlsBuilder().setStartTlsMessage(new StartTlsMessageBuilder().build()).build();
+
this.listener = new SimpleSessionListener();
}
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
import org.opendaylight.protocol.pcep.spi.PCEPErrors;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Keepalive;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Open;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Pcerr;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.message.rev131007.Starttls;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.OpenMessage;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.OpenBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.pcerr.message.pcerr.message.Errors;
import org.opendaylight.yangtools.yang.binding.Notification;
public class FiniteStateMachineTest extends AbstractPCEPSessionTest {
private DefaultPCEPSessionNegotiator serverSession;
+ private DefaultPCEPSessionNegotiator tlsSessionNegotiator;
@Before
public void setup() {
- final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open localPrefs = new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.OpenBuilder().setKeepalive(
+ final org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.pcep.types.rev131005.open.object.Open localPrefs = new OpenBuilder().setKeepalive(
(short) 1).build();
- this.serverSession = new DefaultPCEPSessionNegotiator(new DefaultPromise<PCEPSessionImpl>(GlobalEventExecutor.INSTANCE), this.channel, this.listener, (short) 1, 20, localPrefs);
+ this.serverSession = new DefaultPCEPSessionNegotiator(new DefaultPromise<PCEPSessionImpl>(GlobalEventExecutor.INSTANCE),
+ this.channel, this.listener, (short) 1, 20, localPrefs);
+ this.tlsSessionNegotiator = new DefaultPCEPSessionNegotiator(new DefaultPromise<PCEPSessionImpl>(GlobalEventExecutor.INSTANCE),
+ this.channel, this.listener, (short) 1, 20, localPrefs, new Tls());
}
/**
assertEquals(this.serverSession.getState(), DefaultPCEPSessionNegotiator.State.FINISHED);
}
+ /**
+ * Establish PCEPS TLS connection with peer
+ */
+ @Test
+ public void testEstablishTLS() {
+ final DefaultPCEPSessionNegotiator negotiator = new DefaultPCEPSessionNegotiator(new DefaultPromise<PCEPSessionImpl>(GlobalEventExecutor.INSTANCE),
+ this.channel, this.listener, (short) 1, 20, new OpenBuilder().setKeepalive((short) 1).build(),
+ SslContextFactoryTest.createTlsConfig());
+ negotiator.channelActive(null);
+ assertEquals(1, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(0) instanceof Starttls);
+ assertEquals(DefaultPCEPSessionNegotiator.State.START_TLS_WAIT, negotiator.getState());
+ negotiator.handleMessage(this.startTlsMsg);
+ assertEquals(DefaultPCEPSessionNegotiator.State.OPEN_WAIT, negotiator.getState());
+ assertEquals(2, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(1) instanceof Open);
+ negotiator.handleMessage(this.openMsg);
+ assertEquals(DefaultPCEPSessionNegotiator.State.KEEP_WAIT, negotiator.getState());
+ }
+
+ /**
+ * As Tls is not configured properly, PCE will send error PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testFailedToEstablishTLS() throws Exception {
+ this.tlsSessionNegotiator.channelActive(null);
+ assertEquals(1, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(0) instanceof Starttls);
+ assertEquals(DefaultPCEPSessionNegotiator.State.START_TLS_WAIT, this.tlsSessionNegotiator.getState());
+ this.tlsSessionNegotiator.handleMessage(this.startTlsMsg);
+ assertEquals(2, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(1) instanceof Pcerr);
+ final Errors obj = ((Pcerr) this.msgsSend.get(1)).getPcerrMessage().getErrors().get(0);
+ assertEquals(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS.getErrorType(), obj.getErrorObject().getType().shortValue());
+ assertEquals(PCEPErrors.NOT_POSSIBLE_WITHOUT_TLS.getErrorValue(), obj.getErrorObject().getValue().shortValue());
+ assertEquals(DefaultPCEPSessionNegotiator.State.FINISHED, this.tlsSessionNegotiator.getState());
+ }
+
+ /**
+ * As PCE does not receive expected message (StartTLS), error PCEPErrors.NON_STARTTLS_MSG_RCVD is send
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testTLSUnexpectedMessage() {
+ this.tlsSessionNegotiator.channelActive(null);
+ assertEquals(1, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(0) instanceof Starttls);
+ assertEquals(DefaultPCEPSessionNegotiator.State.START_TLS_WAIT, this.tlsSessionNegotiator.getState());
+ this.tlsSessionNegotiator.handleMessage(this.openMsg);
+ assertEquals(2, this.msgsSend.size());
+ assertTrue(this.msgsSend.get(1) instanceof Pcerr);
+ final Errors obj = ((Pcerr) this.msgsSend.get(1)).getPcerrMessage().getErrors().get(0);
+ assertEquals(PCEPErrors.NON_STARTTLS_MSG_RCVD.getErrorType(), obj.getErrorObject().getType().shortValue());
+ assertEquals(PCEPErrors.NON_STARTTLS_MSG_RCVD.getErrorValue(), obj.getErrorObject().getValue().shortValue());
+ assertEquals(this.tlsSessionNegotiator.getState(), DefaultPCEPSessionNegotiator.State.FINISHED);
+ }
+
/**
* Mock PCE does not accept session characteristics the first time.
*
--- /dev/null
+/*
+ * Copyright (c) 2015 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.protocol.pcep.impl;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.net.ssl.SSLContext;
+import org.junit.Test;
+import org.opendaylight.controller.config.yang.pcep.impl.Tls;
+import org.opendaylight.protocol.pcep.impl.tls.SslContextFactory;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.impl.rev130627.PathType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.pcep.impl.rev130627.StoreType;
+
+public class SslContextFactoryTest {
+
+ @Test
+ public void testSslContextFactory() {
+ final SslContextFactory sslContextFactory = new SslContextFactory(createTlsConfig());
+ final SSLContext sslContext = sslContextFactory.getServerContext();
+ assertNotNull(sslContext);
+ }
+
+ public static Tls createTlsConfig() {
+ final Tls tlsConfig = new Tls();
+ tlsConfig.setCertificatePassword("opendaylight");
+ tlsConfig.setKeystore("/exemplary-ctlKeystore");
+ tlsConfig.setKeystorePassword("opendaylight");
+ tlsConfig.setKeystorePathType(PathType.CLASSPATH);
+ tlsConfig.setKeystoreType(StoreType.JKS);
+ tlsConfig.setTruststore("/exemplary-ctlTrustStore");
+ tlsConfig.setTruststorePassword("opendaylight");
+ tlsConfig.setTruststorePathType(PathType.CLASSPATH);
+ tlsConfig.setTruststoreType(StoreType.JKS);
+ return tlsConfig;
+ }
+
+}
/**
* MONITORING object missing
*/
- MONITORING_OBJECT_MISSING(6, 4);
+ MONITORING_OBJECT_MISSING(6, 4),
+ /**
+ * Reception of StartTLS after any PCEP exchange
+ * TODO: error code to be assigned by IANA
+ */
+ STARTTLS_RCVD_INCORRECTLY(30, 1),
+ /**
+ * Reception of non-StartTLS or non-PCErr message
+ * TODO: error code to be assigned by IANA
+ */
+ NON_STARTTLS_MSG_RCVD(30, 2),
+ /**
+ * Failure, connection without TLS not possible
+ * TODO: error code to be assigned by IANA
+ */
+ NOT_POSSIBLE_WITHOUT_TLS(30, 3),
+ /**
+ * Failure, connection without TLS possible
+ * TODO: error code to be assigned by IANA
+ */
+ POSSIBLE_WITHOUT_TLS(30, 4),
+ /**
+ * No StartTLS message before StartTLSWait timer expired
+ * TODO: error code to be assigned by IANA
+ */
+ STARTTLS_TIMER_EXP(30, 5);
private PCEPErrorIdentifier errorId;
private static final Map<PCEPErrorIdentifier, PCEPErrors> VALUE_MAP;