Use case: As a user of the Spring AMQP client connecting to RabbitMQ brokers over TLS, I want to verify that the hostname(s) either in the X.509 certificate Common Name field or in one of the Subject Alternative Names in the certificate X.509 extensions matches the hostname I used to connect to the broker.
One possible solution: The Spring Rabbit connection factory bean org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean has a setSocketConfigurator(com.rabbitmq.client.SocketConfigurator) method on it that can be used configure the SSLSocket with a javax.net.ssl.HandshakeCompletedListener as follows in this simple SocketConfigurator
static class MySocketConfigurator implements SocketConfigurator {
private final String[] validHostnames;
public MySocketConfigurator(String[] validHostnames) {
this.validHostnames = validHostnames;
}
#Override
public void configure(final Socket socket) throws IOException {
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
#Override
public void handshakeCompleted(final HandshakeCompletedEvent event) {
try {
if (event.getPeerCertificates()[0] instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) event.getPeerCertificates()[0];
boolean verified = verifyHost(validHostnames, x509Certificate);
if (!verified) {
event.getSocket().close();
}
} else {
event.getSocket().close();
}
} catch (SSLPeerUnverifiedException e) {
throw new RuntimeException(e);
} catch (CertificateParsingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
// verify that one of the validHostname items matches a host found in the broker certificate
boolean verifyHost(String[] validHostnames, X509Certificate serverCertificate) throws CertificateParsingException {
...
}
}
Closing the socket referenced in event.getSocket().close() seems a bit heavy-handed, but does suffice to shut the connection down if the application deems the hostnames in the certificate are not a close enough match. I had originally conceived to throw a RuntimeException upon determining that the hostnames did not match, but it appears the Spring stack swallows those and does not induce the desired application-induced connection setup failure.
Is the SocketConfigurator approach shown above, with its direct call to socket.close(), the recommended way to fail TLS connection setup if the certificate hostnames are deemed an insufficient match?
I am not sure what you mean by "spring stack swallows the exception"; doesn't sound right; if you can point me to the code that does that I can take a look.
The spring connection factory just delegates to the rabbit connection factory.
I don't know the answer to your basic question about best practices; you might want to ping the rabbit guys on the rabbitmq-users google group.
Related
I am trying to add interceptors for securing spring-ws by reading this tutorial at https://memorynotfound.com/spring-ws-certificate-authentication-wss4j/
I need to use two seperate public-private keys (one for signing,second for encryption) in a single keystore(server.jks- file).But i am not able to configure the security interceptor.
It works fine as in example if use a single keystore , but how should i set the following when seperate keys for signing and encryption
#Bean
public KeyStoreCallbackHandler securityCallbackHandler(){
KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler();
callbackHandler.setPrivateKeyPassword("changeit");
return callbackHandler;
}
#Bean
public Wss4jSecurityInterceptor securityInterceptor() throws Exception {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
// validate incoming request
securityInterceptor.setValidationActions("Timestamp Signature Encrypt");
securityInterceptor.setValidationSignatureCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationDecryptionCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationCallbackHandler(securityCallbackHandler());
// encrypt the response
securityInterceptor.setSecurementEncryptionUser("client-public");
securityInterceptor.setSecurementEncryptionParts("{Content}{https://memorynotfound.com/beer}getBeerResponse");
securityInterceptor.setSecurementEncryptionCrypto(getCryptoFactoryBean().getObject());
// sign the response
securityInterceptor.setSecurementActions("Signature Encrypt");
securityInterceptor.setSecurementUsername("server");
securityInterceptor.setSecurementPassword("changeit");
securityInterceptor.setSecurementSignatureCrypto(getCryptoFactoryBean().getObject());
return securityInterceptor;
}
#Bean
public CryptoFactoryBean getCryptoFactoryBean() throws IOException {
CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
cryptoFactoryBean.setKeyStorePassword("changeit");
cryptoFactoryBean.setKeyStoreLocation(new ClassPathResource("server.jks"));
return cryptoFactoryBean;
}
For encryption we have the method setSecurementEncryptionUser, but how do we configure setValidationDecryptionCrypto and setValidationSignatureCrypto with the alias to decrypt/validate
Could you try having 2 securityInterceptor with 2 keystores? One for signature and one for encryption. Then add both interceptors to the list of interceptors.
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
try {
interceptors.add(signatureSecurityInterceptor());
interceptors.add(encryptionSecurityInterceptor());
} catch (Exception e) {
throw new RuntimeException("could not initialize security interceptor");
}
}
I'm using Guacamole 0.9.12-incubating - on server-side extending GuacamoleHTTPTunnelServlet, on client using official JavaScript code. Guacamole server is compiled from source and is running on Ubuntu 17.04.
SSH connection is successfully established, but disconnects after 15 seconds. No keyboard strokes nor mouse are working.
May 7 17:14:09 dev guacd[4071]: Creating new client for protocol "ssh"
May 7 17:14:09 dev guacd[4071]: Connection ID is "$30e2e833-5640-4bc9-92c3-929ced3d6e0e"
May 7 17:14:09 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" joined connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" (1 users now present)
May 7 17:14:10 dev guacd[4382]: SSH connection successful.
May 7 17:14:24 dev guacd[4382]: User is not responding.
May 7 17:14:24 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" disconnected (0 users remain)
May 7 17:14:24 dev guacd[4382]: Last user of connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" disconnected
May 7 17:14:25 dev guacd[4382]: SSH connection ended.
May 7 17:14:25 dev guacd[4071]: Connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" removed.
Client JavaScript is the same as in documentation - https://guacamole.incubator.apache.org/doc/gug/writing-you-own-guacamole-app.html .
When I Override methods in servlet, they show me that key strokes go to it. So problem is probably somewhere between servlet and guacd?
#Override
protected void doWrite(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do WRITE to session " + tunnelUUID);
super.doWrite(request, response, tunnelUUID);
}
#Override
protected void doRead(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do read to session " + tunnelUUID);
super.doRead(request, response, tunnelUUID);
}
Connection is established, but no key strokes are working:
Thanks.
Problem was in Spring Boot, as discussed there - https://glyptodon.org/jira/si/jira.issueviews:issue-html/GUAC-1252/GUAC-1252.html .
Solution is to create own implementation of HiddenHttpMethodFilter:
#Configuration
public class GuacamoleServletConfiguration {
#Bean
public GuacamoleServlet guacamoleTunnelServlet() {
return new GuacamoleServlet();
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(guacamoleTunnelServlet(), "/remote/tunnel");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("POST".equals(request.getMethod()) && request.getRequestURI().startsWith("/remote/tunnel")) {
filterChain.doFilter(request, response);
} else {
super.doFilterInternal(request, response, filterChain);
}
}
};
}
}
I am creating a websocket server that interfaces with a web service endpoint on one side and another which receives web socket connection requests from multiple clients. Here are two approaches that I found:
Implement a web socket configurer and web socket handler as such:
Configurer
#Configuration
#EnableWebSocket
public class TestConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(testHandler(), "/testHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS();
}
#Bean
public WebSocketHandler testHandler() {
return new TestHandler();
}
Handler
public class TestHandler extends TextWebSocketHandler {
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//Take request params and check if a current subscription to external webservice exists, if yes then directly add this session to a map cache repository with the subscription id as key
//If it is a new request then add session to a map cache repository and make new subscription to the external webservice
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
}
Configure a message broker endpoint to be subscribed to called /subscribe
public class TestWebSocketConfig implement WebSocketMessageBrokerConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> arg0) {}
#Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> arg0) {}
#Override
public void configureClientInboundChannel(ChannelRegistration arg0) {
System.out.println("");
}
#Override
public void configureClientOutboundChannel(ChannelRegistration arg0) {
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
return true;
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration arg0) {}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/subscribe").withSockJS();
}
Create controller where websocket clients can communicate with
#Controller
public class SubscriptionController {
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/subscribe1")
#SendTo("/subscribe")
public void addSubscription(String message) {
System.out.println("hi");
}
Here is my question, am I misunderstanding somewhere where these two methods I speak of meant to be combined together? I was using a tomcat implementation of websocket before which matches method 1 which gives me easy direct control over sessions as I would like to be able to reuse web service subscriptions to avoid duplicate request from distinct clients and also a single requests may map to more than one subscription requests to the external webservice. Yet it seems method 2 would push all data requests to the same "/subscribe" endpoint and all connected clients would be receiving the same data, which is not what I am trying to accomplish. It also seems like the message broker api is limited as it does not allow me access to the subscribed sessions where I can control which sessions the receiving data will be sent to. I realized I had to switch to spring websocket as I needed built in browser compatibility fallback offered by SockJS and automatic heartbeat function offered by Stomp.js.
i think i found my answer, method 1 and 2 can be used side by side but not together. Method 2 is used when i want to implement a message broker that can create multiple channel destinations which many users can subscribe to the same destination. Now the question is how i can check whether i can check the number of subscriptions periodically for each existing destination
When I try the below code to connect to the mosquitto broker, as you know, connecting to the broker might takes few seconds/minutes, and during that time when the button pressed to connect, it remains pressed till the connection established and when the connection established the button released back to its normal state. As far as I know, there are two way for connecting a client using paho java API, the blocking method and unblocking method. my question is, how to use the unblocking method? beow is my attempt to use the blocking method
Code_1:
//mqttFactory
public final class MQTTClientFactory {
public static MqttClient newClient(String ip, int port, String clientID) throws MqttException {
String serverURI = formURI(ip, port);
MqttClient client = new MqttClient(serverURI, clientID).;
return client;
}
MqttConnectOptions opts = getClientOptions();
client = MQTTClientFactory.newClient(broker, port, clientID);
if (client != null) {
System.out.println("Client is not Null");
client.setCallback(AsynchCallBack);
if (opts != null) {
client.connectWithResult(opts).setActionCallback(synchCallBack);
if (client.isConnected()) {
System.out.println("Client CONNECTED.");
}
}
}
what button? establishing a connection is almost instantly.
There are asynchronous versions of mqtt. Code samples for that. If you want to make the synchronous non-blocking. You could start it up in another thread.
I used JNDI connection in my application and it is working. But I need to write Junits to test the connection. We dont use any spring framework. This is the method i wrote to get JNDI connection.
public Connection getConnection() throws SQLException {
DataSource ds = null;
InitialContext ic = null;
Connection con = null;
try {
ic = new InitialContext();
ds = (DataSource) ic.lookup("java:/DBs");
con = ds.getConnection();
return con;
} catch (Exception e) {
throw new SQLException(e);
}
}
You can make use of the SimpleNamingContextBuilder that comes with the spring-test library. You can use this even if you aren't using Spring as it isn't Spring specific.
Below is an example of setting up a JNDI connection in the #Before of the JUnit test.
package com.example;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
public class SomeTest
{
#Before
public void contextSetup () throws Exception
{
SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
DriverManagerDataSource dataSource = new DriverManagerDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:testdb", "sa", "");
builder.bind("java:comp/env/jdbc/ds1", dataSource);
builder.bind("java:comp/env/jdbc/ds2", dataSource);
}
#Test
public void testSomething () throws Exception
{
/// test with JNDI
}
}
UPDATE: This solution also uses Spring's DriverManagerDataSource. If you want to use that you will also need the spring-jdbc library. But you don't have to use this, you can create any object you like and put it into the SimpleNamingContextBuilder. For example, a DBCP connection pool, a JavaMail Session, etc.
OK. After lot of searching i found a solution.And it is working for me. I want to share this to everybody. Hope this thing might help people who are having the same issue. Please add the below code.Add ojdb6.jar and naming-common-4.1.31.jar in your test libraries
#BeforeClass
public static void setUpClass() throws Exception {
try {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,"org.apache.naming");
InitialContext ic = new InitialContext();
ic.createSubcontext("java:");
ic.createSubcontext("java:/comp");
ic.createSubcontext("java:/comp/env");
ic.createSubcontext("java:/comp/env/jdbc");
OracleConnectionPoolDataSource ocpds = new OracleConnectionPoolDataSource();
ocpds.setURL("your URL");
ocpds.setUser("your username");
ocpds.setPassword("your password");
ic.bind("java:/yourJNDIName", ocpds);
} catch (NamingException ex) {
Logger.getLogger(yourTesTClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
If this is running outside the app server, then you'll likely need to supply parameters to the call for the InitialContext. But also realize that many DataSource implementations are not serializable so they won't work outside the container.
What you're writing is an integration test and it should be run in the container.