Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ public interface AcceptorControl extends ActiveMQComponentControl {
* Returns the parameters used to configure this acceptor
*/
Map<String, Object> getParameters();

/**
* Re-create the acceptor with the existing configuration values. Useful, for example, for reloading key/trust
* stores on acceptors which support SSL.
*/
void reload();
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ public Map<String, Object> getParameters() {
}
}

@Override
public void reload() {
clearIO();
try {
acceptor.reload();
}
finally {
blockOnIO();
}
}

@Override
public boolean isStarted() {
clearIO();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ public boolean isUnsecurable() {
return true;
}

@Override
public void reload() {
throw new UnsupportedOperationException();
}

@Override
public void setDefaultActiveMQPrincipal(ActiveMQPrincipal defaultActiveMQPrincipal) {
this.defaultActiveMQPrincipal = defaultActiveMQPrincipal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
Expand Down Expand Up @@ -121,7 +120,8 @@ public class NettyAcceptor extends AbstractAcceptor {

private final String keyStoreProvider;

private final String keyStorePath;
// non-final for testing purposes
private String keyStorePath;

private final String keyStorePassword;

Expand Down Expand Up @@ -282,87 +282,13 @@ public ActiveMQThreadFactory run() {
bootstrap = new ServerBootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(channelClazz);
final SSLContext context;
if (sslEnabled) {
try {
if (keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider))
throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME +
"\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " +
"unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified.");
context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword);
}
catch (Exception e) {
IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host +
":" + port);
ise.initCause(e);
throw ise;
}
}
else {
context = null; // Unused
}

final AtomicBoolean warningPrinted = new AtomicBoolean(false);

ChannelInitializer<Channel> factory = new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
if (sslEnabled) {
SSLEngine engine = context.createSSLEngine();

engine.setUseClientMode(false);

if (needClientAuth)
engine.setNeedClientAuth(true);

// setting the enabled cipher suites resets the enabled protocols so we need
// to save the enabled protocols so that after the customer cipher suite is enabled
// we can reset the enabled protocols if a customer protocol isn't specified
String[] originalProtocols = engine.getEnabledProtocols();

if (enabledCipherSuites != null) {
try {
engine.setEnabledCipherSuites(SSLSupport.parseCommaSeparatedListIntoArray(enabledCipherSuites));
}
catch (IllegalArgumentException e) {
ActiveMQServerLogger.LOGGER.invalidCipherSuite(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedCipherSuites()));
throw e;
}
}

if (enabledProtocols != null) {
try {
engine.setEnabledProtocols(SSLSupport.parseCommaSeparatedListIntoArray(enabledProtocols));
}
catch (IllegalArgumentException e) {
ActiveMQServerLogger.LOGGER.invalidProtocol(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedProtocols()));
throw e;
}
}
else {
engine.setEnabledProtocols(originalProtocols);
}

// Strip "SSLv3" from the current enabled protocols to address the POODLE exploit.
// This recommendation came from http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html
String[] protocols = engine.getEnabledProtocols();
Set<String> set = new HashSet<>();
for (String s : protocols) {
if (s.equals("SSLv3") || s.equals("SSLv2Hello")) {
if (!warningPrinted.get()) {
ActiveMQServerLogger.LOGGER.disallowedProtocol(s);
}
continue;
}
set.add(s);
}
warningPrinted.set(true);
engine.setEnabledProtocols(set.toArray(new String[set.size()]));

SslHandler handler = new SslHandler(engine);

pipeline.addLast("ssl", handler);
pipeline.addLast("ssl", getSslHandler());
}
pipeline.addLast(protocolHandler.getProtocolDecoder());
}
Expand Down Expand Up @@ -421,6 +347,11 @@ public String getName() {
return name;
}

// only for testing purposes
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}

/**
* Transfers the Netty channel that has been created outside of this NettyAcceptor
* to control it and configure it according to this NettyAcceptor setting.
Expand All @@ -434,6 +365,77 @@ public void transfer(Channel channel) {
channel.pipeline().addLast(protocolHandler.getProtocolDecoder());
}

public void reload() {
serverChannelGroup.disconnect();
serverChannelGroup.clear();
startServerChannels();
}

public synchronized SslHandler getSslHandler() throws Exception {
final SSLContext context;
try {
if (keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider))
throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME +
"\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " +
"unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified.");
context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword);
}
catch (Exception e) {
IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port);
ise.initCause(e);
throw ise;
}
SSLEngine engine = context.createSSLEngine();

engine.setUseClientMode(false);

if (needClientAuth)
engine.setNeedClientAuth(true);

// setting the enabled cipher suites resets the enabled protocols so we need
// to save the enabled protocols so that after the customer cipher suite is enabled
// we can reset the enabled protocols if a customer protocol isn't specified
String[] originalProtocols = engine.getEnabledProtocols();

if (enabledCipherSuites != null) {
try {
engine.setEnabledCipherSuites(SSLSupport.parseCommaSeparatedListIntoArray(enabledCipherSuites));
}
catch (IllegalArgumentException e) {
ActiveMQServerLogger.LOGGER.invalidCipherSuite(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedCipherSuites()));
throw e;
}
}

if (enabledProtocols != null) {
try {
engine.setEnabledProtocols(SSLSupport.parseCommaSeparatedListIntoArray(enabledProtocols));
}
catch (IllegalArgumentException e) {
ActiveMQServerLogger.LOGGER.invalidProtocol(SSLSupport.parseArrayIntoCommandSeparatedList(engine.getSupportedProtocols()));
throw e;
}
}
else {
engine.setEnabledProtocols(originalProtocols);
}

// Strip "SSLv3" from the current enabled protocols to address the POODLE exploit.
// This recommendation came from http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html
String[] protocols = engine.getEnabledProtocols();
Set<String> set = new HashSet<>();
for (String s : protocols) {
if (s.equalsIgnoreCase("SSLv3") || s.equals("SSLv2Hello")) {
ActiveMQServerLogger.LOGGER.disallowedProtocol(s);
continue;
}
set.add(s);
}

engine.setEnabledProtocols(set.toArray(new String[set.size()]));
return new SslHandler(engine);
}

private void startServerChannels() {
String[] hosts = TransportConfiguration.splitHosts(host);
for (String h : hosts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ public interface Acceptor extends ActiveMQComponent {
* @throws java.lang.IllegalStateException if false @setDefaultActiveMQPrincipal
*/
boolean isUnsecurable();

/**
* Re-create the acceptor with the existing configuration values. Useful, for example, for reloading key/trust
* stores on acceptors which support SSL.
*/
void reload();
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ public Map<String, Object> getParameters() {
return (Map<String, Object>) proxy.retrieveAttributeValue("parameters");
}

@Override
public void reload() {
try {
proxy.invokeOperation("reload");
}
catch (Exception e) {
e.printStackTrace();
}
}

@Override
public boolean isStarted() {
return (Boolean) proxy.retrieveAttributeValue("started");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.RandomUtil;
Expand Down Expand Up @@ -73,10 +74,18 @@ public CoreClientOverOneWaySSLTest(String storeType) {
* keytool -export -keystore server-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore other-server-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=Other ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore other-server-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore other-client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* Commands to create the JCEKS artifacts:
* keytool -genkey -keystore server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore other-server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=Other ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore other-server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore other-client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*/
private String storeType;
private String SERVER_SIDE_KEYSTORE;
Expand Down Expand Up @@ -114,6 +123,67 @@ public void testOneWaySSL() throws Exception {
Assert.assertEquals(text, m.getBodyBuffer().readString());
}

@Test
public void testOneWaySSLReloaded() throws Exception {
createCustomSslServer();
server.createQueue(CoreClientOverOneWaySSLTest.QUEUE, CoreClientOverOneWaySSLTest.QUEUE, null, false, false);
String text = RandomUtil.randomString();

// create a valid SSL connection and keep it for use later
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE);
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD);

ServerLocator existingLocator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
existingLocator.setCallTimeout(3000);
ClientSessionFactory existingSessionFactory = addSessionFactory(createSessionFactory(existingLocator));
ClientSession existingSession = addClientSession(existingSessionFactory.createSession(false, true, true));
ClientConsumer existingConsumer = addClientConsumer(existingSession.createConsumer(CoreClientOverOneWaySSLTest.QUEUE));

// create an invalid SSL connection
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "other-client-side-truststore." + storeType.toLowerCase());
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD);

ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)).setCallTimeout(3000);
try {
addSessionFactory(createSessionFactory(locator));
fail("Creating session here should fail due to SSL handshake problems.");
}
catch (Exception e) {
// ignore
}

// reload the acceptor to reload the SSL stores
NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL");
acceptor.setKeyStorePath("other-server-side-keystore." + storeType.toLowerCase());
acceptor.reload();

// create a session with the locator which failed previously proving that the SSL stores have been reloaded
ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator));
ClientSession session = addClientSession(sf.createSession(false, true, true));
ClientProducer producer = addClientProducer(session.createProducer(CoreClientOverOneWaySSLTest.QUEUE));

ClientMessage message = createTextMessage(session, text);
producer.send(message);
producer.send(message);

ClientConsumer consumer = addClientConsumer(session.createConsumer(CoreClientOverOneWaySSLTest.QUEUE));
session.start();
Message m = consumer.receive(1000);
Assert.assertNotNull(m);
Assert.assertEquals(text, m.getBodyBuffer().readString());
consumer.close();

// use the existing connection to prove it wasn't lost when the acceptor was reloaded
existingSession.start();
m = existingConsumer.receive(1000);
Assert.assertNotNull(m);
Assert.assertEquals(text, m.getBodyBuffer().readString());
}

@Test
public void testOneWaySSLWithBadClientCipherSuite() throws Exception {
createCustomSslServer();
Expand Down Expand Up @@ -533,7 +603,7 @@ private void createCustomSslServer(String cipherSuites, String protocols) throws
params.put(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, protocols);
}

ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL"));
server = createServer(false, config);
server.start();
waitForServerToStart(server);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public boolean isUnsecurable() {
return false;
}

@Override
public void reload() {

}

@Override
public void start() throws Exception {
started = true;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.