In this example we will setup the Qpid Broker-J and two clients who will send each other encrypted messages. The clients will use self signed certificates and the certificates will be distributed by the Broker.
For this example it is assumed the Broker is already running and that Management is enabled on port 8443.
The example requires two (one for each client) self-signed X.509 certificates and the corresponding
keys. We refer to these as
client_1/2.cert
and
client_1/2.key
throughout the text below.
The following openssl commands can be used to generate self signed certicates suitable for this test.
openssl req -x509 -newkey rsa:4096 -keyout client_1.key -out client_1.cert -days 365 -nodes -subj "/C=US/O=Apache/OU=Qpid/CN=client1" openssl req -x509 -newkey rsa:4096 -keyout client_2.key -out client_2.cert -days 365 -nodes -subj "/C=US/O=Apache/OU=Qpid/CN=client2"
In this example we want the broker to distribute the client certificates. Essentially, we want the broker to act as a Key Server. Use java's keytool to create a trust store containing the two client certificates.
keytool -importcert -file client_1.cert -alias client1 -keystore mytruststore.jks keytool -importcert -file client_2.cert -alias client2 -keystore mytruststore.jks
Now a FileTrustStore can be created on the broker pointing to the java trust store that was just created. This can be done via the Web Management Console or the REST API:
curl -v -u admin https://localhost:8443/api/v6.1/truststore/clientcerts -X PUT -d '{"type": "FileTrustStore", "stroeUrl": "<path_to_truststore>", "password": "<your_truststore_password>"}'
The TrustStore must be configured to expose the certificates as a message source to the clients:
curl -v -u admin https://localhost:8443/api/v6.1/truststore/clientcerts -X POST -d '{"exposedAsMessageSource": true}'
The configuration for the clients happens in the connection URL. In this example this is provided via a JNDI properties file.
On the producing side, in order to encrypt a message for a recipient, the Qpid client needs the
recipient's public certificate which is distributed by the Broker following our above broker setup. The
encryption_remote_trust_store
element within the connection URL provides the name of the truststore.
On the receiving side, in order to decrypt a message it needs a JKS keystore with the private key matching the public certificate. For this example, the keystores can again be created with a two-step process involving openssl and java's keytool.
openssl pkcs12 -export -in client_1.cert -inkey client_1.key -out client_1.pkcs12 -name "client1" openssl pkcs12 -export -in client_2.cert -inkey client_2.key -out client_2.pkcs12 -name "client2" keytool -importkeystore -destkeystore client_1.jks -srckeystore client_1.pkcs12 -srcstoretype PKCS12 keytool -importkeystore -destkeystore client_2.jks -srckeystore client_2.pkcs12 -srcstoretype PKCS12
The final JNDI properties file should look similar to this:
java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialContextFactory # connection factories. This is where end-to-end encryption is configured on the client. # connectionfactory.[jndiname] = [ConnectionURL] connectionfactory.producerConnectionFactory = amqp://<username>:<password>@/?brokerlist='tcp://localhost:5672?encryption_remote_trust_store='$certificates%255c/clientcerts'' connectionfactory.consumer1ConnectionFactory = amqp://<username>:<password>@/?brokerlist='tcp://localhost:5672?encryption_key_store='path/to/client_1.jks'&encryption_key_store_password='<keystore_password>'' connectionfactory.consumer2ConnectionFactory = amqp://<username>:<password>@/?brokerlist='tcp://localhost:5672?encryption_key_store='path/to/client_2.jks'&encryption_key_store_password='<keystore_password>'' # Rest of JNDI configuration. For example # destination.[jniName] = [Address Format] queue.myTestQueue = testQueue
On the producing side, the application needs to enable encryption and indicate the intended recipient(s)
of each message. This is done via the
x-qpid-encrypt
and
x-qpid-encrypt-recipients
message properties. Note that the order of the relative distinguished name (RDN) entries within the
recipent's distinguished name (DNs) is significant. If the order does not match that recorded in
truststore, a
CertificateException
will be encountered.
On the receiving side, there is nothing to do. The application code does not have to add decryption code as this is handled transparently by the Qpid client library. However, the receiving application should gracefully handle failures in decryption in which case the encrypted message will be delivered as a BytesMessage.
// imports omitted for brevity public class EncryptionExample { public EncryptionExample() { } public static void main(String[] args) throws Exception { EncryptionExample encryptionExampleApp = new EncryptionExample(); encryptionExampleApp.runProducerExample(); encryptionExampleApp.runReceiverExample(); } private void runProducerExample() throws Exception { Connection connection = createConnection("producerConnectionFactory"); try { Session session = connection.createSession(true, Session.SESSION_TRANSACTED); Destination destination = createDesination("myTestQueue"); MessageProducer messageProducer = session.createProducer(destination); TextMessage message = session.createTextMessage("Hello world!"); // ============== Enable encryption for this message ============== message.setBooleanProperty("x-qpid-encrypt", true); // ============== Configure recipients for encryption ============== message.setStringProperty("x-qpid-encrypt-recipients", "CN=client1, OU=Qpid, O=Apache, C=US"); messageProducer.send(message); session.commit(); } finally { connection.close(); } } private void runReceiverExample() throws Exception { Connection connection = createConnection("consumer1ConnectionFactory"); try { connection.start(); Session session = connection.createSession(true, Session.SESSION_TRANSACTED); Destination destination = createDesination("myTestQueue"); MessageConsumer messageConsumer = session.createConsumer(destination); Message message = messageConsumer.receive(); if (message instanceof TextMessage) { // application logic System.out.println(((TextMessage) message).getText()); } else if (message instanceof BytesMessage) { // handle potential decryption failure System.out.println("Potential decryption problem. Application not in list of intended recipients?"); } session.commit(); } finally { connection.close(); } } /////////////////////////////////////// // The following is boilerplate code // /////////////////////////////////////// private Connection createConnection(final String connectionFactoryName) throws JMSException, IOException, NamingException { try (InputStream resourceAsStream = this.getClass().getResourceAsStream("example.properties")) { Properties properties = new Properties(); properties.load(resourceAsStream); Context context = new InitialContext(properties); ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup(connectionFactoryName); final Connection connection = connectionFactory.createConnection(); context.close(); return connection; } } private Destination createDesination(String desinationJndiName) throws IOException, NamingException { try (InputStream resourceAsStream = this.getClass().getResourceAsStream("example.properties")) { Properties properties = new Properties(); properties.load(resourceAsStream); Context context = new InitialContext(properties); Destination destination = (Destination) context.lookup(desinationJndiName); context.close(); return destination; } } }
Apache Qpid, Messaging built on AMQP; Copyright © 2015 The Apache Software Foundation; Licensed under the Apache License, Version 2.0; Apache Qpid, Qpid, Qpid Proton, Proton, Apache, the Apache feather logo, and the Apache Qpid project logo are trademarks of The Apache Software Foundation; All other marks mentioned may be trademarks or registered trademarks of their respective owners