<?xml version="1.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.

-->

<chapter xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="JMS-Client-Message-Encryption">
    <title>Message Encryption</title>

    <para>
        In some cases it is desirable to ensure no-one but the intended recipient(s) of a message will be able to read
        its contents. Using SSL/TLS to encrypt traffic travelling between client and broker only ensures that those
        snooping the network cannot read messages, however once the message arrives at the broker it is decrypted and
        so anyone with access to the broker can read the message.  For such confidential information it is necessary to
        implement a mechanism of end-to-end encryption such that the sender of the message encrypts the message before
        sending, and the recipient(s), upon receiving the message, decrypt it with some secret known only to them.
    </para>
    <para>
        Neither JMS nor AMQP provide any defined mechanism for message encryption, however it is possible for any
        application to build a message encryption scheme on top of a JMS API. For convenience the Client
        provides a built in mechanism for encryption and decrypting messages. This mechanism is currently only
        implemented in the Client for AMQP 0-8/0-9/0-9-1/0-10. If you use a different client you will be
        unable to read encrypted messages.
    </para>

    <section xml:id="JMS-Client-Message-Encryption-Overview">
        <title>Overview</title>
        <para>
            For each encrypted message which the client sends, a new message-specific secret key is generated. This
            secret key is used encrypt the message contents using symmetric encryption (currently only AES-256 is
            supported, although other algorithms may be added at a later date).  For each intended recipient of the
            message, the client encrypts the secret key using the public key associated with the recipient, and adds
            this as a message header. On receipt of an encrypted message, the client looks to see if it has a private
            key which can decrypt the secret key. If the client is unable to decrypt the message (for instance, because
            they were not one of the intended recipients) then the message will be presented to the application as a
            BytesMessage containing the encrypted data.
        </para>
        <para>
            In order to send an encrypted message it is necessary to know the Certificates of the intended recipients.
            Certificates can be distributed either through out-of-band mechanisms, or the Apache Qpid Broker for Java can be used
            to distribute them to clients.
        </para>
        <para>
            In order to receive an encrypted message it is necessary to have a Certificate (which needs to be
            distributed to those who you wish to send messages to you) and to have the private key associated with the
            certificate so that you can decrypt messages sent to you.
        </para>
    </section>

    <section xml:id="JMS-Client-Message-Encryption-Sending">
    <title>Sending an Encrypted Message</title>
        <section xml:id="JMS-Client-Message-Encryption-Sending-Setting-TrustStore">
            <title>Providing the Trust Store</title>
            <para>
                In order for a connection to be capable of sending encrypted messages, it must be provided with a trust
                store which contains the X509 certificates of the entities to which you wish to send. The details of the
                trust store are supplied in the <link linkend="JMS-Client-0-8-Connection-URL">connection URL</link>.
            </para>
            <para>
                There are two distinct mechanisms for providing the encryption trust store. Firstly you can supply a
                standard password-protected trust store file on the file system.  The location and password for this must
                be specified using the <link linkend="JMS-Client-0-8-Connection-URL-BrokerOptions-EncryptionTrustStore">
                encryption_trust_store</link> and
                <link linkend="JMS-Client-0-8-Connection-URL-BrokerOptions-EncryptionTrustStorePassword">encryption_trust_store_password
                </link> options respectively. Such a connection URL might look somthing like:
            </para>
            <programlisting>amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672?encryption_trust_store='/home/qpid/certificates.jks'&amp;encryption_trust_store_password='password''</programlisting>
            <para>
                Alternatively, where available, you can configure the broker to distribute certificates from a trust
                store (this is currently only available in the Apache Qpid Broker for Java). In order to use this method, the broker
                details in the connection url must contain the correctly configured
                <link linkend="JMS-Client-0-8-Connection-URL-BrokerOptions-EncryptionRemoteTrustStore">encryption_remote_trust_store</link>
                option. Such a connection URL might look somthing like:
            </para>
            <programlisting>amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672?encryption_remote_trust_store='$certificates%255c/certstore''</programlisting>
            <para>
                The <literal>$certificates/</literal> prefix is mandatory.
                However, in order to prevent the client from interpreting this the wrong way several layers of escaping and encoding need to take place.
                The slash character ('/') needs to be escaped by a backslash ('\') which needs to be doubly URL encoded resulting in <literal>$certificates%255c/</literal>.
            </para>
            <para>
                Note that to use the broker-distributed certificates the broker must be configured to expose the trust store as a message source.
                See the broker documentation on TrustStores for more details.
            </para>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Sending-Enabling-Encryption">
            <title>Enabling Encryption</title>
            <para>
                Message encryption can be enabled individually on each sent message, or - using configuration - all
                messages sent to a Destination can be encrypted.
            </para>
            <para>
                In order to encrypt messages on a case by case basis, the appliation must set the boolean property
                <literal>x-qpid-encrypt</literal> to true on the message before sending. The intended recipients of the
                message must also be set (see
                <link linkend="JMS-Client-Message-Encryption-Sending-Choosing-Recipients">Choosing Recipients</link>).
            </para>
            <programlisting>message.setBooleanProperty("x-qpid-encrypt", true);</programlisting>
            <para>
                In order to encrypt all messages sent to a given Destination, the option
                <link linkend="JMS-Client-0-8-Binding-URL-Options-SendEncrypted">sendencrypted</link> can be used.  Note
                that enabling encryption on the address can be overridden by explicitly setting the property
                <literal>x-qpid-encrypt</literal> to false on an individual message. An example address would look like:
            </para>
            <programlisting>direct:///queue/queue?sendencrypted='true'</programlisting>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Sending-Choosing-Recipients">
            <title>Choosing Recipients</title>
            <para>
                Any message which is to be sent encrypted must also have a list of recipients who the sender wishes to
                be able to decrypt the message. The recipients must be encoded as a semi-colon separated list of the
                names given in the respective certificates of the recipients, e.g.
                <literal>cn=first@example.org,ou=example,o=example,l=ny,st=ny,c=us;cn=second@example.org,ou=example,o=example,l=ny,st=ny,c=us</literal>.
            </para>
            <para>
                As with enabling encryption, the recipients can be set either on a per-message basis or for all messages
                sent to a given address. If both forms are used, the former overrides the latter. To set on an individual
                message, set the String property <literal>x-qpid-encrypt-recipients</literal>.
            </para>
            <programlisting>message.setStringProperty("x-qpid-encrypt-recipients", "cn=only@example.org,ou=example,o=example");</programlisting>
            <para>
                To set the recipients on an address, use the address option
                <link linkend="JMS-Client-0-8-Binding-URL-Options-EncryptedRecipients">encryptedrecipients</link>.
            </para>
            <programlisting>direct:///queue/queue?sendencrypted='true'&amp;encryptedrecipients='cn=another@example.org,ou=example,o=example'</programlisting>

        </section>
        <section xml:id="JMS-Client-Message-Encryption-Sending-Exposing-Properties">
            <title>Exposing Properties</title>
            <para>
                Message Encryption encrypts the message content and the properties set by the application.  Sometimes
                it is important to expose properties to allow (for example) message routing or message selectors within
                the broker to work. To enable this it is possible to specify for each message all the properties which
                the application wishes to make available to the broker.  Note that exposing properties in this way means
                that they are now visibe to anyone who can inspect the broker memory or file system stores.
            </para>
            <para>
                To make message properties visible to the broker, set the String property
                <literal>x-qpid-unencrypted-properties</literal> with a semi-colon separated list of the names of the
                properties to be exposed.
            </para>
            <programlisting>message.setStringProperty("x-qpid-unencrypted-properties", "foo;bar;baz");</programlisting>
        </section>
    </section>
    <section  xml:id="JMS-Client-Message-Encryption-Receiving">
        <title>Receiving an Encrypted Message</title>
        <section xml:id="JMS-Client-Message-Encryption-Sending-Setting-KeyStore">
            <title>Providing the Key Store</title>
            <para>
                In order for a connection to be capable of decrypting received encrypted messages, it must be provided
                with a key store which contains the X509 certificates and associated Private Keys of the identities
                on behalf of which it will be able to decrypt. The details of the
                key store are supplied in the <link linkend="JMS-Client-0-8-Connection-URL">connection URL</link>.
                The location and password for this must
                be specified using the <link linkend="JMS-Client-0-8-Connection-URL-BrokerOptions-EncryptionKeyStore">
                encryption_key_store</link> and
                <link linkend="JMS-Client-0-8-Connection-URL-BrokerOptions-EncryptionKeyStorePassword">encryption_trust_store_password
                </link> options respectively. Such a connection URL might look somthing like:
            </para>
            <programlisting>amqp://username:password@clientid/test?brokerlist='tcp://localhost:5672?encryption_key_store='/home/qpid/identities.jks'&amp;encryption_key_store_password='password''</programlisting>


        </section>
    </section>
    <section  xml:id="JMS-Client-Message-Encryption-Example">
        <title>Message Encryption Example</title>
        <section xml:id="JMS-Client-Message-Encryption-Example-Introduction">
            <title>Introduction</title>
            <para>
                In this example we will setup the Qpid Broker for Java 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.
            </para>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Example-Prerequisites">
            <title>Prerequisites</title>
            <para>
                For this example it is assumed the Broker is already running and that Management is enabled on port
                8443.
            </para>
            <para>
                The example requires two (one for each client) self-signed X.509 certificates and the corresponding
                keys. We refer to these as
                <literal>client_1/2.cert</literal>
                and
                <literal>client_1/2.key</literal>
                throughout the text below.
            </para>
            <para>
                The following
                <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://www.openssl.org">openssl</link>
                commands can be used to generate self signed certicates suitable for this test.
                <programlisting>
<![CDATA[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"]]>
                </programlisting>
            </para>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Example-Broker-Config">
            <title>Broker Configuration</title>
            <para>
                In this example we want the broker to distribute the client certificates.
                Essentially, we want the broker to act as a<link xmlns:xlink="http://www.w3.org/1999/xlink"
                                                                 xlink:href="https://en.wikipedia.org/wiki/Key_server_(cryptographic)">
                Key Server</link>.
                Use
                <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${oracleKeytool}">java's keytool</link>
                to create a trust store containing the two client certificates.
                <programlisting>
<![CDATA[keytool -importcert -file client_1.cert -alias client1 -keystore mytruststore.jks
keytool -importcert -file client_2.cert -alias client2 -keystore mytruststore.jks]]>
                </programlisting>
                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:
                <programlisting>curl -v -u admin https://localhost:8443/api/v6.1/truststore/clientcerts -X PUT -d
                    '{"type": "FileTrustStore", "stroeUrl": "&lt;path_to_truststore&gt;", "password": "&lt;your_truststore_password&gt;"}'
                </programlisting>
                The TrustStore must be configured to expose the certificates as a message source to the clients:
                <programlisting>curl -v -u admin https://localhost:8443/api/v6.1/truststore/clientcerts -X POST -d
                    '{"exposedAsMessageSource": true}'
                </programlisting>
            </para>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Example-Client-Config">
            <title>Client Configuration</title>
            <para>
                The configuration for the clients happens in the connection URL. In this example this is provided via a
                JNDI properties file.
            </para>
            <para>
                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
                <literal>encryption_remote_trust_store</literal>
                element within the connection URL provides the name of the truststore.
            </para>
            <para>
                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
                <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://www.openssl.org">openssl</link>
                and <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="${oracleKeytool}">java's keytool</link>.
                <programlisting>
<![CDATA[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]]>
                </programlisting>

            </para>
            <para>
                The final JNDI properties file should look similar to this:
                <programlisting>
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://&lt;username&gt;:&lt;password&gt;@?brokerlist='tcp://localhost:5672?encryption_remote_trust_store='$certificates%255c/clientcerts''
connectionfactory.consumer1ConnectionFactory = amqp://&lt;username&gt;:&lt;password&gt;@?brokerlist='tcp://localhost:5672?encryption_key_store='path/to/client_1.jks'&amp;encryption_key_store_password='&lt;keystore_password&gt;''
connectionfactory.consumer2ConnectionFactory = amqp://&lt;username&gt;:&lt;password&gt;@?brokerlist='tcp://localhost:5672?encryption_key_store='path/to/client_2.jks'&amp;encryption_key_store_password='&lt;keystore_password&gt;''

# Rest of JNDI configuration. For example
# destination.[jniName] = [Address Format]
queue.myTestQueue = testQueue
                </programlisting>
            </para>
        </section>
        <section xml:id="JMS-Client-Message-Encryption-Example-Application">
            <title>Application Code</title>
            <para>
                On the producing side, the application needs to enable encryption and indicate the intended recipient(s)
                of each message. This is done via the
                <literal>x-qpid-encrypt</literal>
                and
                <literal>x-qpid-encrypt-recipients</literal>
                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
                <link linkend="JMS-Client-0-8-Appendix-Exceptions-CertificateException">CertificateException</link>
                will be encountered.
            </para>
            <para>
                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.
                <programlisting language="java">
// 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;
        }
    }
}
                </programlisting>
            </para>
        </section>
    </section>
</chapter>
