You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by sc...@apache.org on 2018/10/29 18:11:57 UTC

svn commit: r1845157 - in /tomcat/trunk: java/org/apache/catalina/tribes/group/interceptors/ test/org/apache/catalina/tribes/group/interceptors/ webapps/docs/ webapps/docs/config/

Author: schultz
Date: Mon Oct 29 18:11:57 2018
New Revision: 1845157

URL: http://svn.apache.org/viewvc?rev=1845157&view=rev
Log:
Add EncryptInterceptor for clustering.

Added:
    tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
    tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java
    tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java
Modified:
    tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
    tomcat/trunk/webapps/docs/changelog.xml
    tomcat/trunk/webapps/docs/config/cluster-interceptor.xml

Added: tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java?rev=1845157&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java Mon Oct 29 18:11:57 2018
@@ -0,0 +1,356 @@
+/*
+ * 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.catalina.tribes.group.interceptors;
+
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.catalina.tribes.util.StringManager;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.HexUtils;
+
+/**
+ * Adds encryption using a pre-shared key.
+ *
+ * The length of the key (in bytes) must be acceptable for the encryption
+ * algorithm being used. For example, for AES, you must use a key of either
+ * 16 bytes (128 bits, 24 bytes 192 bits), or 32 bytes (256 bits).
+ *
+ * You can supply the raw key bytes by calling {@link #setEncryptionKey(byte[])}
+ * or the hex-encoded binary bytes by calling
+ * {@link #setEncryptionKey(String)}.
+ */
+public class EncryptInterceptor extends ChannelInterceptorBase implements EncryptInterceptorMBean {
+
+    private static final Log log = LogFactory.getLog(EncryptInterceptor.class);
+    protected static final StringManager sm = StringManager.getManager(EncryptInterceptor.class);
+
+    private static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
+
+    private String providerName;
+    private String encryptionAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
+    private byte[] encryptionKeyBytes;
+
+    private Cipher encryptionCipher;
+    private Cipher decryptionCipher;
+
+    public EncryptInterceptor() {
+    }
+
+    @Override
+    public void start(int svc) throws ChannelException {
+        if(Channel.SND_TX_SEQ == (svc & Channel.SND_TX_SEQ)) {
+            try {
+                initCiphers();
+            } catch (GeneralSecurityException gse) {
+                log.fatal(sm.getString("encryptInterceptor.init.failed"));
+                throw new ChannelException(sm.getString("encryptInterceptor.init.failed"), gse);
+            }
+        }
+
+        super.start(svc);
+    }
+
+    @Override
+    public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
+            throws ChannelException {
+        try {
+            byte[] data = msg.getMessage().getBytes();
+
+            // See #encrypt(byte[]) for an explanation of the return value
+            byte[][] bytes = encrypt(data);
+
+            XByteBuffer xbb = msg.getMessage();
+
+            // Completely replace the message
+            xbb.setLength(0);
+            xbb.append(bytes[0], 0, bytes[0].length);
+            xbb.append(bytes[1], 0, bytes[1].length);
+
+            super.sendMessage(destination, msg, payload);
+
+        } catch (IllegalBlockSizeException ibse) {
+            log.error(sm.getString("encryptInterceptor.encrypt.failed"));
+            throw new ChannelException(ibse);
+        } catch (BadPaddingException bpe) {
+            log.error(sm.getString("encryptInterceptor.encrypt.failed"));
+            throw new ChannelException(bpe);
+        }
+    }
+
+    @Override
+    public void messageReceived(ChannelMessage msg) {
+        try {
+            byte[] data = msg.getMessage().getBytes();
+
+            data = decrypt(data);
+
+            // Remove the decrypted IV/nonce block from the front of the message
+            int blockSize = getDecryptionCipher().getBlockSize();
+            int trimmedSize = data.length - blockSize;
+            if(trimmedSize < 0) {
+                log.error(sm.getString("encryptInterceptor.decrypt.error.short-message"));
+                throw new IllegalStateException(sm.getString("encryptInterceptor.decrypt.error.short-message"));
+            }
+
+            XByteBuffer xbb = msg.getMessage();
+
+            // Completely replace the message with the decrypted one
+            xbb.setLength(0);
+            xbb.append(data, blockSize, data.length - blockSize);
+
+            super.messageReceived(msg);
+        } catch (IllegalBlockSizeException ibse) {
+            log.error(sm.getString("encryptInterceptor.decrypt.failed"), ibse);
+        } catch (BadPaddingException bpe) {
+            log.error(sm.getString("encryptInterceptor.decrypt.failed"), bpe);
+        }
+    }
+
+    /**
+     * Sets the encryption algorithm to be used for encrypting and decrypting
+     * channel messages. You must specify the <code>algorithm/mode/padding</code>.
+     * Information on what standard algorithm names are, please see
+     * {@link https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html}.
+     *
+     * Default is <code>AES/CBC/PKCS5Padding</code>.
+     *
+     * @param algorithm The algorithm to use.
+     */
+    public void setEncryptionAlgorithm(String algorithm) {
+        if(null == getEncryptionAlgorithm())
+            throw new IllegalStateException(sm.getString("encryptInterceptor.algorithm.required"));
+
+        int pos = algorithm.indexOf('/');
+        if(pos < 0)
+            throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
+        pos = algorithm.indexOf('/', pos + 1);
+        if(pos < 0)
+            throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
+
+        encryptionAlgorithm = algorithm;
+    }
+
+    /**
+     * Gets the encryption algorithm being used to encrypt and decrypt channel
+     * messages.
+     *
+     * @return The algorithm being used, including the algorithm mode and padding.
+     */
+    public String getEncryptionAlgorithm() {
+        return encryptionAlgorithm;
+    }
+
+    /**
+     * Sets the encryption key for encryption and decryption. The length of the
+     * key must be appropriate for the algorithm being used.
+     *
+     * @param key The encryption key.
+     */
+    public void setEncryptionKey(byte[] key) {
+        if(null == key)
+            key = null;
+        else
+            encryptionKeyBytes = key.clone();
+    }
+
+    /**
+     * Gets the encryption key being used for encryption and decryption.
+     * The key is encoded using hex-encoding where e.g. the byte <code>0xab</code>
+     * will be shown as "ab". The length of the string in characters will
+     * be twice the length of the key in bytes.
+     *
+     * @return The encryption key.
+     */
+    public void setEncryptionKey(String keyBytes) {
+        if(null == keyBytes)
+            setEncryptionKey((byte[])null);
+        else
+            setEncryptionKey(HexUtils.fromHexString(keyBytes.trim()));
+    }
+
+    /**
+     * Gets the encryption key being used for encryption and decryption.
+     *
+     * @return The encryption key.
+     */
+    public byte[] getEncryptionKey() {
+        byte[] key = getEncryptionKeyInternal();
+
+        if(null != key)
+            key = key.clone();
+
+        return key;
+    }
+
+    private byte[] getEncryptionKeyInternal() {
+        return encryptionKeyBytes;
+    }
+
+    /**
+     * Sets the JCA provider name used for cryptographic activities.
+     *
+     * Default is the JVM platform default.
+     *
+     * @param provider The name of the JCA provider.
+     */
+    public void setProviderName(String provider) {
+        providerName = provider;
+    }
+
+    /**
+     * Gets the JCA provider name used for cryptographic activities.
+     *
+     * Default is the JVM platform default.
+     *
+     * @return The name of the JCA provider.
+     */
+    public String getProviderName() {
+        return providerName;
+    }
+
+    private void initCiphers() throws GeneralSecurityException {
+        if(null == getEncryptionKey())
+            throw new IllegalStateException(sm.getString("encryptInterceptor.key.required"));
+
+        String algorithm = getEncryptionAlgorithm();
+
+        String mode = getAlgorithmMode(algorithm);
+
+        if(!"CBC".equalsIgnoreCase(mode))
+            throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.requires-cbc-mode", mode));
+
+        Cipher cipher;
+
+        String providerName = getProviderName();
+        if(null == providerName) {
+            cipher = Cipher.getInstance(algorithm);
+        } else {
+            cipher = Cipher.getInstance(algorithm, getProviderName());
+        }
+
+        byte[] iv = new byte[cipher.getBlockSize()];
+
+        // Always use a random IV For cipher setup.
+        // The recipient doesn't need the (matching) IV because we will always
+        // pre-pad messages with the IV as a nonce.
+        new SecureRandom().nextBytes(iv);
+
+        IvParameterSpec IV = new IvParameterSpec(iv);
+
+        // If this is a cipher transform of the form ALGO/MODE/PAD,
+        // take just the algorithm part.
+        int pos = algorithm.indexOf('/');
+
+        String bareAlgorithm;
+        if(pos >= 0) {
+            bareAlgorithm = algorithm.substring(0, pos);
+        } else {
+            bareAlgorithm = algorithm;
+        }
+
+        SecretKeySpec encryptionKey = new SecretKeySpec(getEncryptionKey(), bareAlgorithm);
+
+        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, IV);
+
+        encryptionCipher = cipher;
+
+        if(null == providerName) {
+            cipher = Cipher.getInstance(algorithm);
+        } else {
+            cipher = Cipher.getInstance(algorithm, getProviderName());
+        }
+
+        cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv));
+
+        decryptionCipher = cipher;
+    }
+
+    private Cipher getEncryptionCipher() {
+        return encryptionCipher;
+    }
+
+    private Cipher getDecryptionCipher() {
+        return decryptionCipher;
+    }
+
+    private static String getAlgorithmMode(String algorithm) {
+        int start = algorithm.indexOf('/');
+        if(start < 0)
+            throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
+        int end = algorithm.indexOf('/', start + 1);
+        if(start < 0)
+            throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.required"));
+
+        return algorithm.substring(start + 1, end);
+    }
+
+    /**
+     * Encrypts the input <code>bytes</code> into two separate byte arrays:
+     * one for the initial block (which will be the encrypted random IV)
+     * and the second one containing the actual encrypted payload.
+     *
+     * This method returns a pair of byte arrays instead of a single
+     * concatenated one to reduce the number of byte buffers created
+     * and copied during the whole operation -- including message re-building.
+     *
+     * @param bytes The data to encrypt.
+     *
+     * @return The encrypted IV block in [0] and the encrypted data in [1].
+     *
+     * @throws GeneralSecurityException If there is a problem performing the encryption.
+     */
+    private byte[][] encrypt(byte[] bytes) throws IllegalBlockSizeException, BadPaddingException {
+        Cipher cipher = getEncryptionCipher();
+
+        // Adding the IV to the beginning of the encrypted data
+        byte[] iv = cipher.getIV();
+
+        byte[][] data = new byte[2][];
+        data[0] = cipher.update(iv, 0, iv.length);
+        data[1] = cipher.doFinal(bytes);
+
+        return data;
+    }
+
+    /**
+     * Decrypts the input <code>bytes</code>.
+     *
+     * @param bytes The data to decrypt.
+     *
+     * @return The decrypted data.
+     *
+     * @throws GeneralSecurityException If there is a problem performing the decryption.
+     */
+    private byte[] decrypt(byte[] bytes) throws IllegalBlockSizeException, BadPaddingException {
+        return getDecryptionCipher().doFinal(bytes);
+    }
+}

Added: tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java?rev=1845157&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java (added)
+++ tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java Mon Oct 29 18:11:57 2018
@@ -0,0 +1,31 @@
+/*
+ * 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.catalina.tribes.group.interceptors;
+
+public interface EncryptInterceptorMBean {
+
+    // Config
+    public int getOptionFlag();
+    public void setOptionFlag(int optionFlag);
+
+    public void setEncryptionAlgorithm(String algorithm);
+    public String getEncryptionAlgorithm();
+    public void setEncryptionKey(byte[] key);
+    public byte[] getEncryptionKey();
+    public void setProviderName(String provider);
+    public String getProviderName();
+}

Modified: tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties?rev=1845157&r1=1845156&r2=1845157&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties [UTF-8] (original)
+++ tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties [UTF-8] Mon Oct 29 18:11:57 2018
@@ -73,4 +73,11 @@ throughputInterceptor.report=ThroughputI
   \n\tRx Speed:{8} MB/sec (since 1st msg)\
   \n\tReceived:{9} MB]\n
 twoPhaseCommitInterceptor.originalMessage.missing=Received a confirmation, but original message is missing. Id:[{0}]
-twoPhaseCommitInterceptor.heartbeat.failed=Unable to perform heartbeat on the TwoPhaseCommit interceptor.
\ No newline at end of file
+twoPhaseCommitInterceptor.heartbeat.failed=Unable to perform heartbeat on the TwoPhaseCommit interceptor.
+encryptInterceptor.init.failed=Failed to initialize EncryptInterceptor
+encryptInterceptor.encrypt.failed=Failed to encrypt message
+encryptInterceptor.decrypt.failed=Failed to decrypt message
+encryptInterceptor.decrypt.error.short-message=Failed to decrypt message: premature end-of-message
+encryptInterceptor.algorithm.required=Encryption algorithm is required, fully-specified e.g. AES/CBC/PKCS5Padding
+encryptInterceptor.key.required=Encryption key is required
+encryptInterceptor.algorithm.requires-cbc-mode=EncryptInterceptor only supports CBC cipher modes, not [{0}]
\ No newline at end of file

Added: tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java?rev=1845157&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java (added)
+++ tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java Mon Oct 29 18:11:57 2018
@@ -0,0 +1,191 @@
+package org.apache.catalina.tribes.group.interceptors;
+
+import static org.junit.Assert.*;
+
+import java.nio.charset.StandardCharsets;
+import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
+import org.apache.catalina.tribes.ChannelInterceptor;
+import org.apache.catalina.tribes.ChannelMessage;
+import org.apache.catalina.tribes.Member;
+import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+import org.apache.catalina.tribes.group.InterceptorPayload;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the EncryptInterceptor.
+ *
+ * Many of the tests in this class use strings as input and output, even
+ * though the interceptor actually operates on byte arrays. This is done
+ * for readability for the tests and their outputs.
+ */
+public class TestEncryptInterceptor {
+    private static final String encryptionKey128 = HexUtils.toHexString("cafebabedeadbeef".getBytes(StandardCharsets.UTF_8));
+    private static final String encryptionKey192 = HexUtils.toHexString("cafebabedeadbeefbeefcafe".getBytes(StandardCharsets.UTF_8));
+    private static final String encryptionKey256 = HexUtils.toHexString("cafebabedeadbeefcafebabedeadbeef".getBytes(StandardCharsets.UTF_8));
+
+    EncryptInterceptor src;
+    EncryptInterceptor dest;
+
+    @Before
+    public void setup() {
+        src = new EncryptInterceptor();
+        src.setEncryptionKey(encryptionKey128);
+
+        dest = new EncryptInterceptor();
+        dest.setEncryptionKey(encryptionKey128);
+
+        src.setNext(new PipedInterceptor(dest));
+        dest.setPrevious(new ValueCaptureInterceptor());
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        assertEquals("Basic roundtrip failed",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void testTinyPayload() throws Exception {
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "x";
+
+        assertEquals("Tiny payload roundtrip failed",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void testHugePayload() throws Exception {
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        byte[] bytes = new byte[1073741824]; // 1MiB, all zeros
+
+        assertArrayEquals("Tiny payload roundtrip failed",
+                          bytes,
+                          roundTrip(bytes, src, dest));
+    }
+
+    @Test
+    public void testCustomProvider() throws Exception {
+        src.setProviderName("SunJCE"); // Explicitly set the provider name
+        dest.setProviderName("SunJCE");
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        assertEquals("Failed to set custom provider name",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void test192BitKey() throws Exception {
+        src.setEncryptionKey(encryptionKey192);
+        dest.setEncryptionKey(encryptionKey192);
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        assertEquals("Failed to set custom provider name",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void test256BitKey() throws Exception {
+        src.setEncryptionKey(encryptionKey256);
+        dest.setEncryptionKey(encryptionKey256);
+        src.start(Channel.SND_TX_SEQ);
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        assertEquals("Failed to set custom provider name",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    /**
+     * Actually go through the interceptor's send/receive message methods.
+     */
+    private static String roundTrip(String input, EncryptInterceptor src, EncryptInterceptor dest) throws Exception {
+        byte[] bytes = input.getBytes("UTF-8");
+
+        bytes = roundTrip(bytes, src, dest);
+
+        return new String(((ValueCaptureInterceptor)dest.getPrevious()).getValue(), "UTF-8");
+    }
+
+    /**
+     * Actually go through the interceptor's send/receive message methods.
+     */
+    private static byte[] roundTrip(byte[] input, EncryptInterceptor src, EncryptInterceptor dest) throws Exception {
+        ChannelData msg = new ChannelData(false);
+        msg.setMessage(new XByteBuffer(input, false));
+        src.sendMessage(null, msg, null);
+
+        return ((ValueCaptureInterceptor)dest.getPrevious()).getValue();
+    }
+
+    /**
+     * Interceptor that delivers directly to a destination.
+     */
+    private static class PipedInterceptor
+        extends ChannelInterceptorBase
+    {
+        private ChannelInterceptor dest;
+
+        public PipedInterceptor(ChannelInterceptor dest) {
+            if(null == dest)
+                throw new IllegalArgumentException("Destination must not be null");
+
+            this.dest = dest;
+        }
+
+        @Override
+        public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
+                throws ChannelException {
+            dest.messageReceived(msg);
+        }
+    }
+
+    /**
+     * Interceptor that simply captures the latest message sent to or received by it.
+     */
+    private static class ValueCaptureInterceptor
+        extends ChannelInterceptorBase
+    {
+        private byte[] value;
+
+        @Override
+        public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload)
+                throws ChannelException {
+            value = msg.getMessage().getBytes();
+        }
+
+        @Override
+        public void messageReceived(ChannelMessage msg) {
+            value = msg.getMessage().getBytes();
+        }
+
+        public byte[] getValue() {
+            return value;
+        }
+    }
+}

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1845157&r1=1845156&r2=1845157&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Oct 29 18:11:57 2018
@@ -131,6 +131,12 @@
         the <code>Endpoint</code> rather than a local field that could end up
         out of sync. (markt)
       </scode>
+      <add>
+        Add EncryptInterceptor to the portfolio of available clustering
+        interceptors. This adds symmetric encryption of session data
+        to Tomcat clustering regardless of the type of cluster manager
+        or membership being used. (schultz)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Coyote">

Modified: tomcat/trunk/webapps/docs/config/cluster-interceptor.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/cluster-interceptor.xml?rev=1845157&r1=1845156&r2=1845157&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/cluster-interceptor.xml (original)
+++ tomcat/trunk/webapps/docs/config/cluster-interceptor.xml Mon Oct 29 18:11:57 2018
@@ -36,7 +36,7 @@
 <section name="Introduction">
   <p>
   Apache Tribes supports an interceptor architecture to intercept both messages and membership notifications.
-  This architecture allows decoupling of logic and opens the way for some very kewl feature add ons.
+  This architecture allows decoupling of logic and opens the way for some very useful feature add ons.
   </p>
 </section>
 
@@ -54,6 +54,7 @@
     <li><code>org.apache.catalina.tribes.group.interceptors.FragmentationInterceptor</code></li>
     <li><code>org.apache.catalina.tribes.group.interceptors.GzipInterceptor</code></li>
     <li><code>org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor</code></li>
+    <li><code>org.apache.catalina.tribes.group.interceptors.EncryptInterceptor</code></li>
    </ul>
 </section>
 
@@ -196,6 +197,33 @@
      </attribute>
    </attributes>
   </subsection>
+  <subsection name="org.apache.catalina.tribes.group.interceptors.EncryptInterceptor Attributes">
+   <p>
+     The EncryptInterceptor adds encryption to the channel messages carrying
+     session data between nodes. Added in Tomcat 9.0.13.
+   </p>
+   <attributes>
+     <attribute name="encryptionAlgorithm" required="false">
+       The encryption algorithm to be used, including the mode and padding. Please see
+       <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html">https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html</a>
+       for the standard JCA names that can be used.
+
+       The <i>mode</i> is currently required to be <code>CBC</code>.
+
+       The length of the key will specify the flavor of the encryption algorithm
+       to be used, if applicable (e.g. AES-128 versus AES-256).
+
+       The default algorithm is <code>AES/CBC/PKCS5Padding</code>. 
+     </attribute>
+     <attribute name="encryptionKey" required="true">
+       The key to be used with the encryption algorithm.
+       
+       The key should be specified as hex-encoded bytes of the appropriate
+       length for the algorithm (e.g. 16 bytes / 32 characters / 128 bits for
+       AES-128, 32 bytes / 64 characters / 256 bits for AES-256, etc.).
+     </attribute>
+   </attributes>
+  </subsection>
 </section>
 
 <section name="Nested Components">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org