On 21/11/2018 15:29, Christopher Schultz wrote: > All, > > With this last patch, I'm ready for a back-port to tc8.5.x, but I'm > waiting for a user who is trying to get this working on tc9.0 to be > successful. > > If anyone else can confirm that this is all working in a real cluster > (dev/test is okay) then I'll go ahead and back-port, assuming there is > some kind of configuration error in that particular user's case.
I'll fire up my 4 node test cluster and let you know. It may take me a while - there are usually a bunch of OS updates waiting for me when I start it up. Mark > > -chris > > On 11/21/18 10:15, schu...@apache.org wrote: >> 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/Encryp > tInterceptor.java > > > tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStr > ings.properties >> tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEn > cryptInterceptor.java > >> Modified: >> tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/Encryp > tInterceptor.java > > > URL: > http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribe > s/group/interceptors/EncryptInterceptor.java?rev=1847118&r1=1847117&r2=1 > 847118&view=diff >> ====================================================================== > ======== > > > --- > tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/EncryptI > nterceptor.java > (original) >> +++ >> tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/Encryp > tInterceptor.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.sho > rt-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.re > quires-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.un > supported-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.re > quired")); > > > - int end = algorithm.indexOf('/', start + 1); >> - if(end < 0) - throw new >> IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.re > quired")); > > > - >> - 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/LocalS > trings.properties > > > URL: > http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/tribe > s/group/interceptors/LocalStrings.properties?rev=1847118&r1=1847117&r2=1 > 847118&view=diff >> ====================================================================== > ======== > > > --- > tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalStr > ings.properties > [UTF-8] (original) >> +++ >> tomcat/trunk/java/org/apache/catalina/tribes/group/interceptors/LocalS > trings.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/TestEn > cryptInterceptor.java > > > URL: > http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/tribe > s/group/interceptors/TestEncryptInterceptor.java?rev=1847118&r1=1847117& > r2=1847118&view=diff >> ====================================================================== > ======== > > > --- > tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEncr > yptInterceptor.java > (original) >> +++ >> tomcat/trunk/test/org/apache/catalina/tribes/group/interceptors/TestEn > cryptInterceptor.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 > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org > For additional commands, e-mail: dev-h...@tomcat.apache.org > --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org