Skip to content

Commit 66e05c4

Browse files
committed
Revert the replay protection. The deisgn had fundamental issues.
A new approach will be required.
1 parent 2e3985d commit 66e05c4

9 files changed

Lines changed: 70 additions & 632 deletions

File tree

java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java

Lines changed: 13 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
import java.security.SecureRandom;
2424
import java.security.spec.AlgorithmParameterSpec;
2525
import java.util.Locale;
26-
import java.util.Map;
27-
import java.util.concurrent.ConcurrentHashMap;
2826
import java.util.concurrent.ConcurrentLinkedQueue;
29-
import java.util.concurrent.atomic.AtomicLong;
3027

3128
import javax.crypto.Cipher;
3229
import javax.crypto.NoSuchPaddingException;
@@ -42,7 +39,6 @@
4239
import org.apache.catalina.tribes.group.ChannelInterceptorBase;
4340
import org.apache.catalina.tribes.group.InterceptorPayload;
4441
import org.apache.catalina.tribes.io.XByteBuffer;
45-
import org.apache.catalina.tribes.util.CyclicTracker;
4642
import org.apache.catalina.tribes.util.StringManager;
4743
import org.apache.juli.logging.Log;
4844
import org.apache.juli.logging.LogFactory;
@@ -67,7 +63,6 @@ public class EncryptInterceptor extends ChannelInterceptorBase implements Encryp
6763
private String encryptionAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
6864
private byte[] encryptionKeyBytes;
6965
private String encryptionKeyString;
70-
private int replayWindowSize = 1024;
7166

7267

7368
private BaseEncryptionManager encryptionManager;
@@ -85,7 +80,7 @@ public void start(int svc) throws ChannelException {
8580
if (Channel.SND_TX_SEQ == (svc & Channel.SND_TX_SEQ)) {
8681
try {
8782
encryptionManager = createEncryptionManager(getEncryptionAlgorithm(), getEncryptionKeyInternal(),
88-
getProviderName(), getReplayWindowSize());
83+
getProviderName());
8984
} catch (GeneralSecurityException gse) {
9085
throw new ChannelException(sm.getString("encryptInterceptor.init.failed"), gse);
9186
}
@@ -119,12 +114,9 @@ public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPay
119114
throws ChannelException {
120115
try {
121116
byte[] data = msg.getMessage().getBytes();
122-
byte[] message = new byte[data.length + 8];
123-
XByteBuffer.toBytes(encryptionManager.getAndIncrementMessageNumber(), message, 0);
124-
System.arraycopy(data, 0, message, 8, data.length);
125117

126118
// See #encrypt(byte[]) for an explanation of the return value
127-
byte[][] bytes = encryptionManager.encrypt(message);
119+
byte[][] bytes = encryptionManager.encrypt(data);
128120

129121
XByteBuffer xbb = msg.getMessage();
130122

@@ -147,34 +139,19 @@ public void messageReceived(ChannelMessage msg) {
147139
byte[] data = msg.getMessage().getBytes();
148140

149141
data = encryptionManager.decrypt(data);
150-
if (data.length < 8) {
151-
throw new GeneralSecurityException(sm.getString("encryptInterceptor.decrypt.error.short-message"));
152-
}
153-
if (!encryptionManager.checkIncomingMessageNumber(msg.getAddress(), XByteBuffer.toLong(data, 0))) {
154-
log.error(sm.getString("encryptInterceptor.decrypt.replay"));
155-
return;
156-
}
157142

158143
XByteBuffer xbb = msg.getMessage();
159144

160145
// Completely replace the message with the decrypted one
161146
xbb.clear();
162-
xbb.append(data, 8, data.length - 8);
147+
xbb.append(data, 0, data.length);
163148

164149
super.messageReceived(msg);
165150
} catch (GeneralSecurityException gse) {
166151
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
167152
}
168153
}
169154

170-
@Override
171-
public void memberDisappeared(Member member) {
172-
if (encryptionManager != null) {
173-
encryptionManager.memberDisappeared(member);
174-
}
175-
super.memberDisappeared(member);
176-
}
177-
178155
/**
179156
* Sets the encryption algorithm to be used for encrypting and decrypting channel messages. You must specify the
180157
* <code>algorithm/mode/padding</code>. Information on standard algorithm names may be found in the
@@ -297,36 +274,6 @@ public String getProviderName() {
297274
return providerName;
298275
}
299276

300-
/**
301-
* Returns the number of message sequence numbers remembered for replay detection.
302-
*
303-
* @return The replay window size
304-
*/
305-
@Override
306-
public int getReplayWindowSize() {
307-
return replayWindowSize;
308-
}
309-
310-
/**
311-
* Sets the number of message sequence numbers remembered for replay detection.
312-
*
313-
* @param replayWindowSize The replay window size
314-
*/
315-
@Override
316-
public void setReplayWindowSize(int replayWindowSize) {
317-
if (replayWindowSize < 1) {
318-
throw new IllegalArgumentException(sm.getString("encryptInterceptor.replayWindow.tooSmall"));
319-
}
320-
this.replayWindowSize = replayWindowSize;
321-
}
322-
323-
Long getRemovedMemberHeadValue(Member member) {
324-
if (encryptionManager == null) {
325-
return null;
326-
}
327-
return encryptionManager.getRemovedMemberHeadValue(member);
328-
}
329-
330277
// Copied from org.apache.tomcat.util.buf.HexUtils
331278
// @formatter:off
332279
private static final int[] DEC = {
@@ -373,8 +320,7 @@ private static byte[] fromHexString(String input) {
373320
}
374321

375322
private static BaseEncryptionManager createEncryptionManager(String algorithm, byte[] encryptionKey,
376-
String providerName, int replayWindowSize)
377-
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
323+
String providerName) throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
378324
if (null == encryptionKey) {
379325
throw new IllegalStateException(sm.getString("encryptInterceptor.key.required"));
380326
}
@@ -413,7 +359,8 @@ private static BaseEncryptionManager createEncryptionManager(String algorithm, b
413359
*/
414360
if ("NONE".equals(algorithmMode) || "ECB".equals(algorithmMode) || "PCBC".equals(algorithmMode) ||
415361
"CTS".equals(algorithmMode) || "KW".equals(algorithmMode) || "KWP".equals(algorithmMode) ||
416-
"CTR".equals(algorithmMode) || ("CBC".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) ||
362+
"CTR".equals(algorithmMode) ||
363+
("CBC".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) ||
417364
("CFB".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) ||
418365
("GCM".equals(algorithmMode) && "PKCS5PADDING".equals(algorithmPadding)) ||
419366
("OFB".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding))) {
@@ -428,18 +375,17 @@ private static BaseEncryptionManager createEncryptionManager(String algorithm, b
428375

429376
} else if (algorithmMode.startsWith("CFB") || algorithmMode.startsWith("OFB")) {
430377
// Using a non-default block size. Not supported as insecure and/or inefficient.
431-
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported", algorithm));
378+
throw new IllegalArgumentException(
379+
sm.getString("encryptInterceptor.algorithm.unsupported", algorithm));
432380

433381
} else if ("GCM".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) {
434382
// Needs a specialised encryption manager to handle the differences between GCM and other modes
435-
return new GCMEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName,
436-
replayWindowSize);
383+
return new GCMEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName);
437384
}
438385

439386
// Use the default encryption manager
440387
try {
441-
return new BaseEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName,
442-
replayWindowSize);
388+
return new BaseEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName);
443389
} catch (NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException ex) {
444390
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported", algorithm), ex);
445391
}
@@ -477,80 +423,24 @@ private static class BaseEncryptionManager {
477423
* SecureRandom is thread-safe, but sharing a single instance will likely be a bottleneck.
478424
*/
479425
private final ConcurrentLinkedQueue<SecureRandom> randomPool;
480-
private final AtomicLong messageNumberGenerator = new AtomicLong();
481-
private final Map<Member,CyclicTracker> receivedMessageNumbersByMember = new ConcurrentHashMap<>();
482-
private final Map<Member,Long> messageNumbersByRemovedMember = new ConcurrentHashMap<>();
483-
private final CyclicTracker receivedMessageNumbersForUnknownSender;
484-
private final int replayWindowSize;
485426

486-
BaseEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName, int replayWindowSize)
427+
BaseEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName)
487428
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
488429
this.algorithm = algorithm;
489430
this.providerName = providerName;
490431
this.secretKey = secretKey;
491-
this.replayWindowSize = replayWindowSize;
492432

493433
cipherPool = new ConcurrentLinkedQueue<>();
494434
Cipher cipher = createCipher();
495435
blockSize = cipher.getBlockSize();
496436
cipherPool.offer(cipher);
497437
randomPool = new ConcurrentLinkedQueue<>();
498-
receivedMessageNumbersForUnknownSender = new CyclicTracker(replayWindowSize);
499438
}
500439

501440
public void shutdown() {
502441
// Individual Cipher and SecureRandom objects need no explicit tear down
503442
cipherPool.clear();
504443
randomPool.clear();
505-
receivedMessageNumbersByMember.clear();
506-
messageNumbersByRemovedMember.clear();
507-
}
508-
509-
public long getAndIncrementMessageNumber() {
510-
return messageNumberGenerator.getAndIncrement();
511-
}
512-
513-
public boolean checkIncomingMessageNumber(Member sender, long messageNumber) {
514-
if (sender == null) {
515-
return receivedMessageNumbersForUnknownSender.track(messageNumber);
516-
}
517-
return receivedMessageNumbersByMember.computeIfAbsent(sender, this::createTrackerForMember)
518-
.track(messageNumber);
519-
}
520-
521-
public void memberDisappeared(Member member) {
522-
CyclicTracker tracker = receivedMessageNumbersByMember.remove(member);
523-
if (tracker != null) {
524-
/*
525-
* There is a security trade off here.
526-
*
527-
* Entries are only removed from this Map if the Member reappears. That means there is a potential DoS
528-
* risks due to the growth of this Map. That is considered unlikely as only Members with the encryption
529-
* key will be added to this Map and the size of the Map.Entry is minimal.
530-
*
531-
* If entries are removed from this Map based either on Map size or time, that exposes the risk of a
532-
* replay attack using any message the Member may have previously sent.
533-
*
534-
* The replay attack is viewed as the higher risk, hence there are no limits on the size of this Map.
535-
*/
536-
messageNumbersByRemovedMember.put(member, Long.valueOf(tracker.getHeadValue()));
537-
}
538-
}
539-
540-
public Long getRemovedMemberHeadValue(Member member) {
541-
return messageNumbersByRemovedMember.get(member);
542-
}
543-
544-
private CyclicTracker createTrackerForMember(Member member) {
545-
CyclicTracker tracker = new CyclicTracker(replayWindowSize);
546-
Long headValue = messageNumbersByRemovedMember.remove(member);
547-
if (headValue == null) {
548-
// This is a new member. First valid message will be 0. Therefore set last message to -1.
549-
tracker.track(-1);
550-
} else {
551-
tracker.track(headValue.longValue());
552-
}
553-
return tracker;
554444
}
555445

556446
private String getAlgorithm() {
@@ -721,9 +611,9 @@ protected AlgorithmParameterSpec generateIV(byte[] ivBytes, int offset, int leng
721611
* number of bits supported 128-bit provide the best security.
722612
*/
723613
private static class GCMEncryptionManager extends BaseEncryptionManager {
724-
GCMEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName, int replayWindowSize)
614+
GCMEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName)
725615
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
726-
super(algorithm, secretKey, providerName, replayWindowSize);
616+
super(algorithm, secretKey, providerName);
727617
}
728618

729619
@Override

java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptorMBean.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,4 @@ public interface EncryptInterceptorMBean {
7676
* @return the JCA provider name, or {@code null} for default
7777
*/
7878
String getProviderName();
79-
80-
/**
81-
* Returns the number of message sequence numbers remembered for replay detection.
82-
*
83-
* @return the replay window size
84-
*/
85-
int getReplayWindowSize();
86-
87-
/**
88-
* Sets the number of message sequence numbers remembered for replay detection.
89-
*
90-
* @param replayWindowSize the replay window size
91-
*/
92-
void setReplayWindowSize(int replayWindowSize);
9379
}

java/org/apache/catalina/tribes/group/interceptors/LocalStrings.properties

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ encryptInterceptor.algorithm.switch=The EncryptInterceptor is using the algorith
2121
encryptInterceptor.algorithm.unsupported=EncryptInterceptor does not support algorithm [{0}]
2222
encryptInterceptor.decrypt.error.short-message=Failed to decrypt message: premature end-of-message
2323
encryptInterceptor.decrypt.failed=Failed to decrypt message
24-
encryptInterceptor.decrypt.replay=Failed to decrypt message: replay attack detected
2524
encryptInterceptor.encrypt.failed=Failed to encrypt message
2625
encryptInterceptor.init.failed=Failed to initialize EncryptInterceptor
2726
encryptInterceptor.key.required=Encryption key is required
28-
encryptInterceptor.replayWindow.tooSmall=replayWindowSize must be greater than zero
2927
encryptInterceptor.tcpFailureDetector.ordering=EncryptInterceptor must be upstream of TcpFailureDetector. Please re-order EncryptInterceptor to be listed before TcpFailureDetector in your channel interceptor pipeline.
3028

3129
fragmentationInterceptor.fragments.missing=Fragments are missing.

0 commit comments

Comments
 (0)