From e582ce03a5ec408889f0d5fe244de42b16c1eb39 Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Thu, 13 Jan 2022 12:42:08 -0600 Subject: [PATCH] ARTEMIS-3644 add cert info to CONNECTION_CREATED notification --- .../artemis/utils/CertificateUtil.java | 70 ------------------- .../core/remoting/CertificateUtil.java | 69 ++++++++++++++++-- .../core/security/impl/SecurityStoreImpl.java | 14 +--- .../NotificationActiveMQServerPlugin.java | 3 + .../SSLSecurityNotificationTest.java | 41 ++++++++++- 5 files changed, 109 insertions(+), 88 deletions(-) delete mode 100644 artemis-commons/src/main/java/org/apache/activemq/artemis/utils/CertificateUtil.java diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/CertificateUtil.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/CertificateUtil.java deleted file mode 100644 index bd23a74c25..0000000000 --- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/CertificateUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.activemq.artemis.utils; - -import javax.net.ssl.SSLPeerUnverifiedException; -import java.io.ByteArrayInputStream; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.handler.ssl.SslHandler; -import org.jboss.logging.Logger; - -public class CertificateUtil { - private static final Logger logger = Logger.getLogger(CertificateUtil.class); - - public static X509Certificate[] getCertsFromChannel(Channel channel) { - Certificate[] plainCerts = null; - ChannelHandler channelHandler = channel.pipeline().get("ssl"); - if (channelHandler != null && channelHandler instanceof SslHandler) { - SslHandler sslHandler = (SslHandler) channelHandler; - try { - plainCerts = sslHandler.engine().getSession().getPeerCertificates(); - } catch (SSLPeerUnverifiedException e) { - // ignore - } - } - - X509Certificate[] x509Certs = null; - if (plainCerts != null && plainCerts.length > 0) { - x509Certs = new X509Certificate[plainCerts.length]; - for (int i = 0; i < plainCerts.length; i++) { - if (plainCerts[i] instanceof X509Certificate) { - x509Certs[i] = (X509Certificate) plainCerts[i]; - } else { - try { - x509Certs[i] = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(plainCerts[i].getEncoded())); - } catch (Exception ex) { - if (logger.isTraceEnabled()) { - logger.trace("Failed to convert SSL cert", ex); - } - return null; - } - } - if (logger.isTraceEnabled()) { - logger.trace("Cert #" + i + " = " + x509Certs[i]); - } - } - } - - return x509Certs; - } -} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/CertificateUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/CertificateUtil.java index cc1da5b2f7..00fd0fdb41 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/CertificateUtil.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/CertificateUtil.java @@ -17,26 +17,42 @@ package org.apache.activemq.artemis.core.remoting; +import javax.net.ssl.SSLPeerUnverifiedException; +import java.io.ByteArrayInputStream; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.handler.ssl.SslHandler; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Connection; - -import javax.net.ssl.SSLPeerUnverifiedException; -import java.security.cert.X509Certificate; -import java.security.Principal; +import org.jboss.logging.Logger; public class CertificateUtil { + private static final Logger logger = Logger.getLogger(CertificateUtil.class); + private static final String SSL_HANDLER_NAME = "ssl"; + public static String getCertSubjectDN(RemotingConnection connection) { + String certSubjectDN = "unavailable"; + X509Certificate[] certs = getCertsFromConnection(connection); + if (certs != null && certs.length > 0 && certs[0] != null) { + certSubjectDN = certs[0].getSubjectDN().getName(); + } + return certSubjectDN; + } + public static X509Certificate[] getCertsFromConnection(RemotingConnection remotingConnection) { X509Certificate[] certificates = null; if (remotingConnection != null) { Connection transportConnection = remotingConnection.getTransportConnection(); if (transportConnection instanceof NettyConnection) { - certificates = org.apache.activemq.artemis.utils.CertificateUtil.getCertsFromChannel(((NettyConnection) transportConnection).getChannel()); + certificates = getCertsFromChannel(((NettyConnection) transportConnection).getChannel()); } } return certificates; @@ -72,4 +88,47 @@ public class CertificateUtil { return result; } + + private static X509Certificate[] getCertsFromChannel(Channel channel) { + Certificate[] plainCerts = null; + ChannelHandler channelHandler = channel.pipeline().get("ssl"); + if (channelHandler != null && channelHandler instanceof SslHandler) { + SslHandler sslHandler = (SslHandler) channelHandler; + try { + plainCerts = sslHandler.engine().getSession().getPeerCertificates(); + } catch (SSLPeerUnverifiedException e) { + // ignore + } + } + + /* + * When using the OpenSSL provider on the broker the getPeerCertificates() method does *not* return a + * X509Certificate[] so we need to convert the Certificate[] that is returned. This code is inspired by Tomcat's + * org.apache.tomcat.util.net.jsse.JSSESupport class. + */ + X509Certificate[] x509Certs = null; + if (plainCerts != null && plainCerts.length > 0) { + x509Certs = new X509Certificate[plainCerts.length]; + for (int i = 0; i < plainCerts.length; i++) { + if (plainCerts[i] instanceof X509Certificate) { + x509Certs[i] = (X509Certificate) plainCerts[i]; + } else { + try { + x509Certs[i] = (X509Certificate) CertificateFactory + .getInstance("X.509").generateCertificate(new ByteArrayInputStream(plainCerts[i].getEncoded())); + } catch (Exception ex) { + if (logger.isTraceEnabled()) { + logger.trace("Failed to convert SSL cert", ex); + } + return null; + } + } + if (logger.isTraceEnabled()) { + logger.trace("Cert #" + i + " = " + x509Certs[i]); + } + } + } + + return x509Certs; + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java index 39257f0ed0..67613fe8fe 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java @@ -17,7 +17,6 @@ package org.apache.activemq.artemis.core.security.impl; import javax.security.auth.Subject; -import java.security.cert.X509Certificate; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -203,15 +202,6 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC return null; } - public String getCertSubjectDN(RemotingConnection connection) { - String certSubjectDN = "unavailable"; - X509Certificate[] certs = CertificateUtil.getCertsFromConnection(connection); - if (certs != null && certs.length > 0 && certs[0] != null) { - certSubjectDN = certs[0].getSubjectDN().getName(); - } - return certSubjectDN; - } - @Override public void check(final SimpleString address, final CheckType checkType, @@ -356,7 +346,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC } private void authenticationFailed(String user, RemotingConnection connection) throws Exception { - String certSubjectDN = getCertSubjectDN(connection); + String certSubjectDN = CertificateUtil.getCertSubjectDN(connection); if (notificationService != null) { TypedProperties props = new TypedProperties(); @@ -429,7 +419,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC } private String createAuthenticationCacheKey(String username, String password, RemotingConnection connection) { - return username + password + getCertSubjectDN(connection); + return username + password + CertificateUtil.getCertSubjectDN(connection); } private String createAuthorizationCacheKey(String user, CheckType checkType) { diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/plugin/impl/NotificationActiveMQServerPlugin.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/plugin/impl/NotificationActiveMQServerPlugin.java index 880f970a01..ebc84738ff 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/plugin/impl/NotificationActiveMQServerPlugin.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/plugin/impl/NotificationActiveMQServerPlugin.java @@ -23,6 +23,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.management.CoreNotificationType; import org.apache.activemq.artemis.api.core.management.ManagementHelper; +import org.apache.activemq.artemis.core.remoting.CertificateUtil; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.ServerConsumer; @@ -170,8 +171,10 @@ public class NotificationActiveMQServerPlugin implements ActiveMQServerPlugin { if (managementService != null && sendConnectionNotifications) { try { + String certSubjectDN = CertificateUtil.getCertSubjectDN(connection); final TypedProperties props = new TypedProperties(); props.putSimpleStringProperty(ManagementHelper.HDR_CONNECTION_NAME, SimpleString.toSimpleString(connection.getID().toString())); + props.putSimpleStringProperty(ManagementHelper.HDR_CERT_SUBJECT_DN, SimpleString.toSimpleString(certSubjectDN)); props.putSimpleStringProperty(ManagementHelper.HDR_REMOTE_ADDRESS, SimpleString.toSimpleString(connection.getRemoteAddress())); managementService.sendNotification(new Notification(null, type, props)); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/SSLSecurityNotificationTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/SSLSecurityNotificationTest.java index 2a517a8a35..6987ad4a37 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/SSLSecurityNotificationTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/SSLSecurityNotificationTest.java @@ -40,6 +40,8 @@ import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.core.security.Role; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServers; +import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerPlugin; +import org.apache.activemq.artemis.core.server.plugin.impl.NotificationActiveMQServerPlugin; import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; import org.apache.activemq.artemis.tests.integration.security.SecurityTest; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; @@ -48,6 +50,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import static org.apache.activemq.artemis.api.core.management.CoreNotificationType.CONNECTION_CREATED; import static org.apache.activemq.artemis.api.core.management.CoreNotificationType.CONSUMER_CREATED; import static org.apache.activemq.artemis.api.core.management.CoreNotificationType.SECURITY_AUTHENTICATION_VIOLATION; @@ -77,7 +80,6 @@ public class SSLSecurityNotificationTest extends ActiveMQTestBase { @Test public void testSECURITY_AUTHENTICATION_VIOLATION() throws Exception { - SSLSecurityNotificationTest.flush(notifConsumer); TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY); tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); @@ -89,6 +91,7 @@ public class SSLSecurityNotificationTest extends ActiveMQTestBase { ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + SSLSecurityNotificationTest.flush(notifConsumer); long start = System.currentTimeMillis(); try { sf.createSession(); @@ -148,6 +151,36 @@ public class SSLSecurityNotificationTest extends ActiveMQTestBase { guestSession.close(); } + @Test + public void testCONNECTION_CREATED() throws Exception { + Role role = new Role("notif", true, true, true, true, false, true, true, true, true, true); + Set roles = new HashSet<>(); + roles.add(role); + server.getSecurityRepository().addMatch("#", roles); + + TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY); + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "server-ca-truststore.jks"); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "securepass"); + tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client-keystore.jks"); + tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "securepass"); + + SSLSecurityNotificationTest.flush(notifConsumer); + + long start = System.currentTimeMillis(); + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + + ClientMessage notification = SecurityNotificationTest.consumeMessages(1, notifConsumer)[0]; + Assert.assertEquals(CONNECTION_CREATED.toString(), notification.getObjectProperty(ManagementHelper.HDR_NOTIFICATION_TYPE).toString()); + Assert.assertNotNull(notification.getObjectProperty(ManagementHelper.HDR_CERT_SUBJECT_DN)); + Assert.assertEquals("CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, ST=AMQ, C=AMQ", notification.getObjectProperty(ManagementHelper.HDR_CERT_SUBJECT_DN).toString()); + Assert.assertTrue(notification.getObjectProperty(ManagementHelper.HDR_REMOTE_ADDRESS).toString().startsWith("/127.0.0.1")); + Assert.assertTrue(notification.getTimestamp() >= start); + Assert.assertTrue((long) notification.getObjectProperty(ManagementHelper.HDR_NOTIFICATION_TIMESTAMP) >= start); + Assert.assertEquals(notification.getTimestamp(), (long) notification.getObjectProperty(ManagementHelper.HDR_NOTIFICATION_TIMESTAMP)); + } + @Override @Before public void setUp() throws Exception { @@ -165,6 +198,12 @@ public class SSLSecurityNotificationTest extends ActiveMQTestBase { server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params)); + ActiveMQServerPlugin plugin = new NotificationActiveMQServerPlugin(); + Map init = new HashMap(); + init.put(NotificationActiveMQServerPlugin.SEND_CONNECTION_NOTIFICATIONS, "true"); + plugin.init(init); + server.registerBrokerPlugin(plugin); + server.start(); notifQueue = RandomUtil.randomSimpleString();