Author: schultz
Date: Wed Nov 21 15:15:34 2018
New Revision: 1847118

URL: http://svn.apache.org/viewvc?rev=1847118&view=rev
Log:
Use random IVs for encryption.
Support cipher block modes other than CBC.
Expand unit tests.


Modified:
    
tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
    
tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties
    
tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java

Modified: 
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=1847118&r1=1847117&r2=1847118&view=diff
==============================================================================
--- 
tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
 (original)
+++ 
tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java
 Wed Nov 21 15:15:34 2018
@@ -17,6 +17,8 @@
 package org.apache.catalina.tribes.group.interceptors;
 
 import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
 import java.security.SecureRandom;
 
 import javax.crypto.BadPaddingException;
@@ -59,6 +61,7 @@ public class EncryptInterceptor extends
     private String encryptionAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
     private byte[] encryptionKeyBytes;
     private String encryptionKeyString;
+    private SecretKeySpec secretKey;
 
     private Cipher encryptionCipher;
     private Cipher decryptionCipher;
@@ -92,7 +95,7 @@ public class EncryptInterceptor extends
             XByteBuffer xbb = msg.getMessage();
 
             // Completely replace the message
-            xbb.setLength(0);
+            xbb.clear();
             xbb.append(bytes[0], 0, bytes[0].length);
             xbb.append(bytes[1], 0, bytes[1].length);
 
@@ -104,6 +107,12 @@ public class EncryptInterceptor extends
         } catch (BadPaddingException bpe) {
             log.error(sm.getString("encryptInterceptor.encrypt.failed"));
             throw new ChannelException(bpe);
+        } catch (InvalidKeyException ike) {
+            log.error(sm.getString("encryptInterceptor.encrypt.failed"));
+            throw new ChannelException(ike);
+        } catch (InvalidAlgorithmParameterException iape) {
+            log.error(sm.getString("encryptInterceptor.encrypt.failed"));
+            throw new ChannelException(iape);
         }
     }
 
@@ -114,25 +123,21 @@ public class EncryptInterceptor extends
 
             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);
+            xbb.clear();
+            xbb.append(data, 0, data.length);
 
             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);
+        } catch (InvalidKeyException ike) {
+            log.error(sm.getString("encryptInterceptor.decrypt.failed"), ike);
+        } catch (InvalidAlgorithmParameterException iape) {
+            log.error(sm.getString("encryptInterceptor.decrypt.failed"), iape);
         }
     }
 
@@ -262,55 +267,51 @@ public class EncryptInterceptor extends
 
         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()];
+        String algorithmName;
+        String algorithmMode;
 
-        // 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,
+        // We need to break-apart the algorithm name e.g. AES/CBC/PKCS5Padding
         // take just the algorithm part.
         int pos = algorithm.indexOf('/');
 
-        String bareAlgorithm;
         if(pos >= 0) {
-            bareAlgorithm = algorithm.substring(0, pos);
+            algorithmName = algorithm.substring(0, pos);
+            int pos2 = algorithm.indexOf('/', pos+1);
+
+            if(pos2 >= 0) {
+                algorithmMode = algorithm.substring(pos + 1, pos2);
+            } else {
+                algorithmMode = "CBC";
+            }
         } else {
-            bareAlgorithm = algorithm;
+            algorithmName  = algorithm;
+            algorithmMode = "CBC";
         }
 
-        SecretKeySpec encryptionKey = new SecretKeySpec(getEncryptionKey(), 
bareAlgorithm);
+        // Note: ECB is not an appropriate mode for secure communications.
+        if(!("CBC".equalsIgnoreCase(algorithmMode)
+             || "OFB".equalsIgnoreCase(algorithmMode)
+             || "CFB".equalsIgnoreCase(algorithmMode)))
+            throw new 
IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported-mode",
 algorithmMode));
 
-        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, IV);
-
-        encryptionCipher = cipher;
+        setSecretKey(new SecretKeySpec(getEncryptionKeyInternal(), 
algorithmName));
 
+        String providerName = getProviderName();
         if(null == providerName) {
-            cipher = Cipher.getInstance(algorithm);
+            encryptionCipher = Cipher.getInstance(algorithm);
+            decryptionCipher = Cipher.getInstance(algorithm);
         } else {
-            cipher = Cipher.getInstance(algorithm, getProviderName());
+            encryptionCipher = Cipher.getInstance(algorithm, 
getProviderName());
+            decryptionCipher = Cipher.getInstance(algorithm, 
getProviderName());
         }
+    }
 
-        cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new 
IvParameterSpec(iv));
+    private void setSecretKey(SecretKeySpec secretKey) {
+        this.secretKey = secretKey;
+    }
 
-        decryptionCipher = cipher;
+    private SecretKeySpec getSecretKey() {
+        return secretKey;
     }
 
     private Cipher getEncryptionCipher() {
@@ -321,20 +322,9 @@ public class EncryptInterceptor extends
         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(end < 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)
+     * one for the random initialization vector (IV) used for this message,
      * and the second one containing the actual encrypted payload.
      *
      * This method returns a pair of byte arrays instead of a single
@@ -343,21 +333,32 @@ public class EncryptInterceptor extends
      *
      * @param bytes The data to encrypt.
      *
-     * @return The encrypted IV block in [0] and the encrypted data in [1].
+     * @return The IV in [0] and the encrypted data in [1].
      *
      * @throws IllegalBlockSizeException If the input data is not a multiple of
      *             the block size and no padding has been requested (for block
      *             ciphers) or if the input data cannot be encrypted
      * @throws BadPaddingException Declared but should not occur during 
encryption
+     * @throws InvalidAlgorithmParameterException If the algorithm is invalid
+     * @throws InvalidKeyException If the key is invalid
      */
-    private byte[][] encrypt(byte[] bytes) throws IllegalBlockSizeException, 
BadPaddingException {
+    private byte[][] encrypt(byte[] bytes) throws IllegalBlockSizeException, 
BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
         Cipher cipher = getEncryptionCipher();
 
-        // Adding the IV to the beginning of the encrypted data
-        byte[] iv = cipher.getIV();
+        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);
 
+        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(), IV);
+
+        // Prepend the IV to the beginning of the encrypted data
         byte[][] data = new byte[2][];
-        data[0] = cipher.update(iv, 0, iv.length);
+        data[0] = iv;
         data[1] = cipher.doFinal(bytes);
 
         return data;
@@ -373,10 +374,20 @@ public class EncryptInterceptor extends
      * @throws IllegalBlockSizeException If the input data cannot be encrypted
      * @throws BadPaddingException If the decrypted data does not include the
      *             expected number of padding bytes
-     *
+     * @throws InvalidAlgorithmParameterException If the algorithm is invalid
+     * @throws InvalidKeyException If the key is invalid
      */
-    private byte[] decrypt(byte[] bytes) throws IllegalBlockSizeException, 
BadPaddingException {
-        return getDecryptionCipher().doFinal(bytes);
+    private byte[] decrypt(byte[] bytes) throws IllegalBlockSizeException, 
BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
+        Cipher cipher = getDecryptionCipher();
+
+        int blockSize = cipher.getBlockSize();
+
+        // Use first-block of incoming data as IV
+        IvParameterSpec IV = new IvParameterSpec(bytes, 0, blockSize);
+        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), IV);
+
+        // Decrypt remainder of the message.
+        return cipher.doFinal(bytes, blockSize, bytes.length - blockSize);
     }
 
 

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=1847118&r1=1847117&r2=1847118&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] Wed Nov 21 15:15:34 2018
@@ -17,7 +17,7 @@ domainFilterInterceptor.member.refused=M
 domainFilterInterceptor.message.refused=Received message from cluster[{0}] was 
refused.
 
 encryptInterceptor.algorithm.required=Encryption algorithm is required, 
fully-specified e.g. AES/CBC/PKCS5Padding
-encryptInterceptor.algorithm.requires-cbc-mode=EncryptInterceptor only 
supports CBC cipher modes, not [{0}]
+encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor does not 
support block cipher mode [{0}]
 encryptInterceptor.decrypt.error.short-message=Failed to decrypt message: 
premature end-of-message
 encryptInterceptor.decrypt.failed=Failed to decrypt message
 encryptInterceptor.encrypt.failed=Failed to encrypt message

Modified: 
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=1847118&r1=1847117&r2=1847118&view=diff
==============================================================================
--- 
tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java
 (original)
+++ 
tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java
 Wed Nov 21 15:15:34 2018
@@ -18,8 +18,14 @@ package org.apache.catalina.tribes.group
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 
 import org.apache.catalina.tribes.Channel;
 import org.apache.catalina.tribes.ChannelException;
@@ -38,8 +44,9 @@ import org.apache.catalina.tribes.io.XBy
  * though the interceptor actually operates on byte arrays. This is done
  * for readability for the tests and their outputs.
  */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class TestEncryptInterceptor {
-    private static final String encryptionKey128 = 
"cafebabedeadbeefcafebabedeadbeef";
+    private static final String encryptionKey128 = 
"cafebabedeadbeefbeefcafecafebabe";
     private static final String encryptionKey192 = 
"cafebabedeadbeefbeefcafecafebabedeadbeefbeefcafe";
     private static final String encryptionKey256 = 
"cafebabedeadbeefcafebabedeadbeefcafebabedeadbeefcafebabedeadbeef";
 
@@ -95,7 +102,7 @@ public class TestEncryptInterceptor {
     }
 
     @Test
-    @Ignore // Too big for default settings. Breaks Gump, Eclipse, ...
+    @Ignore("Too big for default settings. Breaks Gump, Eclipse, ...")
     public void testHugePayload() throws Exception {
         src.start(Channel.SND_TX_SEQ);
         dest.start(Channel.SND_TX_SEQ);
@@ -157,7 +164,7 @@ public class TestEncryptInterceptor {
 
         bytes = roundTrip(bytes, src, dest);
 
-        return new 
String(((ValueCaptureInterceptor)dest.getPrevious()).getValue(), "UTF-8");
+        return new String(bytes, "UTF-8");
     }
 
     /**
@@ -171,6 +178,123 @@ public class TestEncryptInterceptor {
         return ((ValueCaptureInterceptor)dest.getPrevious()).getValue();
     }
 
+    @Test
+    @Ignore("ECB mode isn't because it's insecure")
+    public void testECB() throws Exception {
+        src.setEncryptionAlgorithm("AES/ECB/PKCS5Padding");
+        src.start(Channel.SND_TX_SEQ);
+        dest.setEncryptionAlgorithm("AES/ECB/PKCS5Padding");
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        Assert.assertEquals("Failed in ECB mode",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void testOFB() throws Exception {
+        src.setEncryptionAlgorithm("AES/OFB/PKCS5Padding");
+        src.start(Channel.SND_TX_SEQ);
+        dest.setEncryptionAlgorithm("AES/OFB/PKCS5Padding");
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        Assert.assertEquals("Failed in OFB mode",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void testCFB() throws Exception {
+        src.setEncryptionAlgorithm("AES/CFB/PKCS5Padding");
+        src.start(Channel.SND_TX_SEQ);
+        dest.setEncryptionAlgorithm("AES/CFB/PKCS5Padding");
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        Assert.assertEquals("Failed in CFB mode",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    @Ignore("GCM mode is unsupported because it requires a custom 
initialization vector")
+    public void testGCM() throws Exception {
+        src.setEncryptionAlgorithm("AES/GCM/PKCS5Padding");
+        src.start(Channel.SND_TX_SEQ);
+        dest.setEncryptionAlgorithm("AES/GCM/PKCS5Padding");
+        dest.start(Channel.SND_TX_SEQ);
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        Assert.assertEquals("Failed in GCM mode",
+                     testInput,
+                     roundTrip(testInput, src, dest));
+    }
+
+    @Test
+    public void testViaFile() throws Exception {
+        src.start(Channel.SND_TX_SEQ);
+        src.setNext(new ValueCaptureInterceptor());
+
+        String testInput = "The quick brown fox jumps over the lazy dog.";
+
+        ChannelData msg = new ChannelData(false);
+        msg.setMessage(new XByteBuffer(testInput.getBytes("UTF-8"), false));
+        src.sendMessage(null, msg, null);
+
+        byte[] bytes = ((ValueCaptureInterceptor)src.getNext()).getValue();
+
+        try (FileOutputStream out = new FileOutputStream("message.bin")) {
+            out.write(bytes);
+        }
+
+        dest.start(Channel.SND_TX_SEQ);
+
+        bytes = new byte[8192];
+        int read;
+
+        try (FileInputStream in = new FileInputStream("message.bin")) {
+            read = in.read(bytes);
+        }
+
+        msg = new ChannelData(false);
+        XByteBuffer xbb = new XByteBuffer(read, false);
+        xbb.append(bytes, 0, read);
+        msg.setMessage(xbb);
+
+        dest.messageReceived(msg);
+    }
+
+    @Test
+    public void testPickup() throws Exception {
+        File file = new File("message.bin");
+        if(!file.exists()) {
+            System.err.println("File message.bin does not exist. Skipping 
test.");
+            return;
+        }
+
+        dest.start(Channel.SND_TX_SEQ);
+
+        byte[] bytes = new byte[8192];
+        int read;
+
+        try (FileInputStream in = new FileInputStream("message.bin")) {
+            read = in.read(bytes);
+        }
+
+        ChannelData msg = new ChannelData(false);
+        XByteBuffer xbb = new XByteBuffer(read, false);
+        xbb.append(bytes, 0, read);
+        msg.setMessage(xbb);
+
+        dest.messageReceived(msg);
+    }
+
     /**
      * Interceptor that delivers directly to a destination.
      */



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

Reply via email to