Skip to content

Commit 3ce4e05

Browse files
ARTEMIS-5163 Artemis fails to send mqtt will message using mutual TLS
The client's certificate is cached in order to successfully authenticate when sending LWT using mutual TLS. - cache X509Certificate in RemotingConnection - try to get certificate from channel if not already cached (CertificateUtil)
1 parent 7c29b13 commit 3ce4e05

5 files changed

Lines changed: 91 additions & 2 deletions

File tree

artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/CertificateUtil.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ public static String getCertSubjectDN(RemotingConnection connection) {
5151
public static X509Certificate[] getCertsFromConnection(RemotingConnection remotingConnection) {
5252
X509Certificate[] certificates = null;
5353
if (remotingConnection != null) {
54-
Connection transportConnection = remotingConnection.getTransportConnection();
55-
if (transportConnection instanceof NettyConnection nettyConnection) {
54+
if (remotingConnection.getCertificates() != null) {
55+
certificates = remotingConnection.getCertificates();
56+
} else if (remotingConnection.getTransportConnection() instanceof NettyConnection nettyConnection) {
5657
certificates = getCertsFromChannel(nettyConnection.getChannel());
58+
remotingConnection.setCertificates(certificates);
5759
}
5860
}
5961
return certificates;

artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/AbstractRemotingConnection.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.activemq.artemis.spi.core.protocol;
1818

19+
import java.security.cert.X509Certificate;
1920
import java.util.ArrayList;
2021
import java.util.List;
2122
import java.util.concurrent.CopyOnWriteArrayList;
@@ -52,6 +53,7 @@ public abstract class AbstractRemotingConnection implements RemotingConnection {
5253
protected volatile boolean dataReceived;
5354
private String clientId;
5455
private Subject subject;
56+
private X509Certificate[] certificates;
5557

5658
public AbstractRemotingConnection(final Connection transportConnection, final Executor executor) {
5759
this.transportConnection = transportConnection;
@@ -282,6 +284,16 @@ public Subject getSubject() {
282284
return subject;
283285
}
284286

287+
@Override
288+
public void setCertificates(X509Certificate[] certificates) {
289+
this.certificates = certificates;
290+
}
291+
292+
@Override
293+
public X509Certificate[] getCertificates() {
294+
return certificates;
295+
}
296+
285297
@Override
286298
public void setClientID(String clientId) {
287299
this.clientId = clientId;

artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/protocol/RemotingConnection.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.activemq.artemis.spi.core.protocol;
1818

1919
import javax.security.auth.Subject;
20+
import java.security.cert.X509Certificate;
2021
import java.util.List;
2122
import java.util.concurrent.Future;
2223

@@ -219,6 +220,16 @@ default void disconnect(DisconnectReason reason, String targetNodeID, TransportC
219220
*/
220221
Subject getSubject();
221222

223+
/**
224+
* sets the associated certificates for this connection
225+
*/
226+
void setCertificates(X509Certificate[] certificates);
227+
228+
/**
229+
* the possibly null certificates associated with this connection
230+
*/
231+
X509Certificate[] getCertificates();
232+
222233
/**
223234
* {@return the name of the protocol for this Remoting Connection}
224235
*/

artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ManagementRemotingConnection.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.activemq.artemis.core.management.impl;
1919

2020
import javax.security.auth.Subject;
21+
import java.security.cert.X509Certificate;
2122
import java.util.List;
2223
import java.util.concurrent.Future;
2324

@@ -180,6 +181,15 @@ public Subject getSubject() {
180181
return subject;
181182
}
182183

184+
@Override
185+
public void setCertificates(X509Certificate[] certificates) {
186+
}
187+
188+
@Override
189+
public X509Certificate[] getCertificates() {
190+
return null;
191+
}
192+
183193
@Override
184194
public String getProtocolName() {
185195
return null;

tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt5/ssl/CertificateAuthenticationSslTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import org.apache.activemq.artemis.utils.RandomUtil;
3636
import org.apache.activemq.artemis.utils.Wait;
3737
import org.eclipse.paho.mqttv5.client.MqttClient;
38+
import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
39+
import org.eclipse.paho.mqttv5.common.MqttException;
3840
import org.eclipse.paho.mqttv5.common.MqttMessage;
41+
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
3942
import org.junit.jupiter.api.TestTemplate;
4043
import org.junit.jupiter.api.Timeout;
4144
import org.junit.jupiter.api.extension.ExtendWith;
@@ -120,4 +123,55 @@ public void messageArrived(String topic, MqttMessage message) {
120123
producer.publish(topic, body, 1, false);
121124
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
122125
}
126+
127+
// Send will message using mutual TLS with certificate-based authentication
128+
@TestTemplate
129+
@Timeout(DEFAULT_TIMEOUT_SEC)
130+
void testSendWillMessage() throws Exception {
131+
final String willSenderId = RandomUtil.randomUUIDString();
132+
final String willTopic = RandomUtil.randomUUIDString();
133+
final byte[] willBody = RandomUtil.randomBytes(32);
134+
135+
CountDownLatch latch = new CountDownLatch(1);
136+
MqttClient willSender = createConnectedWillSender(willSenderId, willTopic, willBody);
137+
MqttClient willConsumer = createConnectedWillConsumer(latch, willTopic, willBody);
138+
139+
if (protocol.equals(WSS)) {
140+
willSender.disconnectForcibly(0, 0, false);
141+
} else {
142+
// for some reason disconnectForcibly doesn't work in this case so we trick the broker into sending the LWT
143+
getSessionStates().get(willSenderId).setFailed(true);
144+
getSessionStates().get(willSenderId).setAttached(false);
145+
}
146+
assertTrue(latch.await(3, TimeUnit.SECONDS));
147+
willConsumer.disconnect();
148+
}
149+
150+
private MqttClient createConnectedWillSender(String clientId, String topic, byte[] body) throws MqttException {
151+
MqttClient willSender = createPahoClient(protocol, clientId);
152+
MqttConnectionOptions options = getSslMqttConnectOptions();
153+
options.setSessionExpiryInterval(5L);
154+
options.setWill(topic, new MqttMessage(body));
155+
MqttProperties willMessageProperties = new MqttProperties();
156+
willMessageProperties.setWillDelayInterval(1L);
157+
options.setWillMessageProperties(willMessageProperties);
158+
willSender.connect(options);
159+
return willSender;
160+
}
161+
162+
private MqttClient createConnectedWillConsumer(CountDownLatch latch,
163+
String topic,
164+
byte[] body) throws MqttException {
165+
MqttClient willConsumer = createPahoClient(protocol, RandomUtil.randomUUIDString());
166+
willConsumer.connect(getSslMqttConnectOptions());
167+
willConsumer.setCallback(new DefaultMqttCallback() {
168+
@Override
169+
public void messageArrived(String topic, MqttMessage message) {
170+
assertEqualsByteArrays(body, message.getPayload());
171+
latch.countDown();
172+
}
173+
});
174+
willConsumer.subscribe(topic, AT_LEAST_ONCE);
175+
return willConsumer;
176+
}
123177
}

0 commit comments

Comments
 (0)