Skip to content

Commit 9b9459a

Browse files
committed
Add support for new algorithms provided by JPA providers
1 parent f297013 commit 9b9459a

File tree

10 files changed

+233
-86
lines changed

10 files changed

+233
-86
lines changed

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

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.security.NoSuchProviderException;
2323
import java.security.SecureRandom;
2424
import java.security.spec.AlgorithmParameterSpec;
25+
import java.util.Locale;
2526
import java.util.concurrent.ConcurrentLinkedQueue;
2627

2728
import javax.crypto.Cipher;
@@ -42,7 +43,6 @@
4243
import org.apache.juli.logging.Log;
4344
import org.apache.juli.logging.LogFactory;
4445

45-
4646
/**
4747
* Adds encryption using a pre-shared key. The length of the key (in bytes) must be acceptable for the encryption
4848
* algorithm being used. For example, for AES, you must use a key of either 16 bytes (128 bits, 24 bytes 192 bits), or
@@ -54,7 +54,7 @@ public class EncryptInterceptor extends ChannelInterceptorBase implements Encryp
5454
private static final Log log = LogFactory.getLog(EncryptInterceptor.class);
5555
protected static final StringManager sm = StringManager.getManager(EncryptInterceptor.class);
5656

57-
private static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding";
57+
private static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES/GCM/NoPadding";
5858

5959
private String providerName;
6060
private String encryptionAlgorithm = DEFAULT_ENCRYPTION_ALGORITHM;
@@ -140,17 +140,17 @@ public void messageReceived(ChannelMessage msg) {
140140
xbb.clear();
141141
xbb.append(data, 0, data.length);
142142

143-
super.messageReceived(msg);
144143
} catch (GeneralSecurityException gse) {
145144
log.error(sm.getString("encryptInterceptor.decrypt.failed"), gse);
146145
}
146+
super.messageReceived(msg);
147147
}
148148

149149
/**
150150
* Sets the encryption algorithm to be used for encrypting and decrypting channel messages. You must specify the
151151
* <code>algorithm/mode/padding</code>. Information on standard algorithm names may be found in the
152152
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html">Java
153-
* documentation</a>. Default is <code>AES/CBC/PKCS5Padding</code>.
153+
* documentation</a>. Default is <code>AES/GCM/NoPadding</code>.
154154
*
155155
* @param algorithm The algorithm to use.
156156
*/
@@ -314,33 +314,68 @@ private static BaseEncryptionManager createEncryptionManager(String algorithm, b
314314

315315
String algorithmName;
316316
String algorithmMode;
317+
String algorithmPadding;
317318

318-
// We need to break-apart the algorithm name e.g. AES/CBC/PKCS5Padding
319+
// We need to break-apart the algorithm name e.g. AES/GCM/NoPadding
319320
// take just the algorithm part.
320321
int pos = algorithm.indexOf('/');
321322

322323
if (pos >= 0) {
323-
algorithmName = algorithm.substring(0, pos);
324+
algorithmName = algorithm.substring(0, pos).toUpperCase(Locale.ENGLISH);
324325
int pos2 = algorithm.indexOf('/', pos + 1);
325326

326327
if (pos2 >= 0) {
327-
algorithmMode = algorithm.substring(pos + 1, pos2);
328+
algorithmMode = algorithm.substring(pos + 1, pos2).toUpperCase(Locale.ENGLISH);
329+
algorithmPadding = algorithm.substring(pos2 + 1).toUpperCase(Locale.ENGLISH);
328330
} else {
329-
algorithmMode = "CBC";
331+
algorithmMode = "GCM";
332+
algorithmPadding = "NOPADDING";
330333
}
331334
} else {
332335
algorithmName = algorithm;
333-
algorithmMode = "CBC";
336+
algorithmMode = "GCM";
337+
algorithmPadding = "NOPADDING";
334338
}
335339

336-
if ("GCM".equalsIgnoreCase(algorithmMode)) {
340+
/*
341+
* Limit the cipher algorithm modes available. The limits are based on the cipher algorithm modes listed in the
342+
* Java Standard Names documentation. Those modes that are not appropriate or provide no protection are blocked.
343+
* Where there are performance or security concerns regarding a mode, a warning is logged. Unrecognised modes,
344+
* such as those provided by custom JCA providers are allowed but will be rejected if there is no JCA provider
345+
* to support them.
346+
*/
347+
if ("NONE".equals(algorithmMode) || "ECB".equals(algorithmMode) || "PCBC".equals(algorithmMode) ||
348+
"CTS".equals(algorithmMode) || "KW".equals(algorithmMode) || "KWP".equals(algorithmMode) ||
349+
"CTR".equals(algorithmMode) ||
350+
("CBC".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) ||
351+
("CFB".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding)) ||
352+
("GCM".equals(algorithmMode) && "PKCS5PADDING".equals(algorithmPadding)) ||
353+
("OFB".equals(algorithmMode) && "NOPADDING".equals(algorithmPadding))) {
354+
// Insecure, unsuitable or unsupported
355+
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported", algorithm));
356+
357+
} else if (("CBC".equals(algorithmMode) && "PKCS5PADDING".equals(algorithmPadding)) ||
358+
("CFB".equals(algorithmMode) && "PKCS5PADDING".equals(algorithmPadding)) ||
359+
("OFB".equals(algorithmMode) && "PKCS5PADDING".equals(algorithmPadding))) {
360+
// Supported but not recommended as more secure modes are available
361+
log.warn(sm.getString("encryptInterceptor.algorithm.switch", algorithm));
362+
363+
} else if (algorithmMode.startsWith("CFB") || algorithmMode.startsWith("OFB")) {
364+
// Using a non-default block size. Not supported as insecure and/or inefficient.
365+
throw new IllegalArgumentException(
366+
sm.getString("encryptInterceptor.algorithm.unsupported", algorithm));
367+
368+
} else if ("GCM".equalsIgnoreCase(algorithmMode) && "NOPADDING".equals(algorithmPadding)) {
369+
// Needs a specialised encryption manager to handle the differences between GCM and other modes
337370
return new GCMEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName);
338-
} else if ("CBC".equalsIgnoreCase(algorithmMode) || "OFB".equalsIgnoreCase(algorithmMode) ||
339-
"CFB".equalsIgnoreCase(algorithmMode)) {
371+
}
372+
373+
// Use the default encryption manager
374+
try {
340375
return new BaseEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName);
341-
} else {
342-
throw new IllegalArgumentException(
343-
sm.getString("encryptInterceptor.algorithm.unsupported-mode", algorithmMode));
376+
} catch (NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException ex) {
377+
throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported", algorithmMode),
378+
ex);
344379
}
345380
}
346381

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
domainFilterInterceptor.member.refused=Member [{0}] was refused to join cluster
1717
domainFilterInterceptor.message.refused=Received message from cluster[{0}] was refused.
1818

19-
encryptInterceptor.algorithm.required=Encryption algorithm is required, fully-specified e.g. AES/CBC/PKCS5Padding
20-
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor does not support block cipher mode [{0}]
19+
encryptInterceptor.algorithm.required=Encryption algorithm is required, fully-specified e.g. AES/GCM/NoPadding
20+
encryptInterceptor.algorithm.switch=The EncryptInterceptor is using the algorithm [{0}]. It is recommended to switch to using AES/GCM/NoPadding.
21+
encryptInterceptor.algorithm.unsupported=EncryptInterceptor does not support algorithm [{0}]
2122
encryptInterceptor.decrypt.error.short-message=Failed to decrypt message: premature end-of-message
2223
encryptInterceptor.decrypt.failed=Failed to decrypt message
2324
encryptInterceptor.encrypt.failed=Failed to encrypt message

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ domainFilterInterceptor.member.refused=Le membre [{0}] a été refusé dans le c
2020
domainFilterInterceptor.message.refused=Le message reçu du cluster [{0}] a été refusé
2121

2222
encryptInterceptor.algorithm.required=Un algorithme de cryptage est requis, avec une spécification complète telle que AES/CBC/PKCS5Padding
23-
encryptInterceptor.algorithm.unsupported-mode=L''EncryptInterceptor ne supporte pas le mode de chiffrage de bloc [{0}]
2423
encryptInterceptor.decrypt.error.short-message=Echec du décryptage du message : fin de message prématuré
2524
encryptInterceptor.decrypt.failed=Echec de décryptage du message
2625
encryptInterceptor.encrypt.failed=Erreur de cryptage du message

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ domainFilterInterceptor.member.refused=メンバーはクラスター [{0}] へ
2020
domainFilterInterceptor.message.refused=クラスター [{0}] から受信したメッセージは拒否されました。
2121

2222
encryptInterceptor.algorithm.required=暗号化アルゴリズムが必要です。完全指定。 AES/CBC/PKCS5Padding
23-
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptorはブロック暗号モード [{0}]をサポートしていません。
2423
encryptInterceptor.decrypt.error.short-message=メッセージの復号に失敗: メッセージの末尾が途切れています
2524
encryptInterceptor.decrypt.failed=メッセージの復号に失敗しました。
2625
encryptInterceptor.encrypt.failed=メッセージを暗号化できません。

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ domainFilterInterceptor.member.refused=멤버 [{0}]이(가) 클러스터에 참
2020
domainFilterInterceptor.message.refused=클러스터 [{0}](으)로부터 받은 메시지가 거부되었습니다.
2121

2222
encryptInterceptor.algorithm.required=암호화 알고리즘을 완전하게 지정해야 합니다. 예) AES/CBC/PKCS5Padding.
23-
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor가 블록 cipher 모드 [{0}]을(를) 지원하지 않습니다.
2423
encryptInterceptor.decrypt.error.short-message=메시지를 해독하지 못했습니다: 메시지가 너무 일찍 끝났습니다 (premature end-of-message).
2524
encryptInterceptor.decrypt.failed=메시지를 해독하지 못했습니다.
2625
encryptInterceptor.encrypt.failed=메시지를 암호화하지 못했습니다.

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ domainFilterInterceptor.member.refused=成员被拒绝加入集群 cluster[{0}]
2020
domainFilterInterceptor.message.refused=从集群[{0}]中接收的消息被拒绝
2121

2222
encryptInterceptor.algorithm.required=加密算法是必需的,充分说明,例如AES / CBC / PKCS5Padding
23-
encryptInterceptor.algorithm.unsupported-mode=EncryptInterceptor不支持分组密码模式[{0}]
2423
encryptInterceptor.decrypt.error.short-message=解密消息失败: 结尾消息提前结束
2524
encryptInterceptor.decrypt.failed=无法解密信息
2625
encryptInterceptor.encrypt.failed=无法加密信息

test/org/apache/catalina/tribes/group/interceptors/TestEncryptInterceptor.java

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -160,64 +160,6 @@ public void test256BitKey() throws Exception {
160160
roundTrip(testInput, src, dest));
161161
}
162162

163-
@Test
164-
public void testOFB() throws Exception {
165-
src.setEncryptionAlgorithm("AES/OFB/PKCS5Padding");
166-
src.start(Channel.SND_TX_SEQ);
167-
dest.setEncryptionAlgorithm("AES/OFB/PKCS5Padding");
168-
dest.start(Channel.SND_TX_SEQ);
169-
170-
String testInput = "The quick brown fox jumps over the lazy dog.";
171-
172-
Assert.assertEquals("Failed in OFB mode",
173-
testInput,
174-
roundTrip(testInput, src, dest));
175-
}
176-
177-
@Test
178-
public void testCFB() throws Exception {
179-
src.setEncryptionAlgorithm("AES/CFB/PKCS5Padding");
180-
src.start(Channel.SND_TX_SEQ);
181-
dest.setEncryptionAlgorithm("AES/CFB/PKCS5Padding");
182-
dest.start(Channel.SND_TX_SEQ);
183-
184-
String testInput = "The quick brown fox jumps over the lazy dog.";
185-
186-
Assert.assertEquals("Failed in CFB mode",
187-
testInput,
188-
roundTrip(testInput, src, dest));
189-
}
190-
191-
@Test
192-
public void testGCM() throws Exception {
193-
src.setEncryptionAlgorithm("AES/GCM/NoPadding");
194-
src.start(Channel.SND_TX_SEQ);
195-
dest.setEncryptionAlgorithm("AES/GCM/NoPadding");
196-
dest.start(Channel.SND_TX_SEQ);
197-
198-
String testInput = "The quick brown fox jumps over the lazy dog.";
199-
200-
Assert.assertEquals("Failed in GCM mode",
201-
testInput,
202-
roundTrip(testInput, src, dest));
203-
}
204-
205-
/*
206-
* ECB mode isn't supported because it's insecure.
207-
*/
208-
@Test
209-
public void testECB() throws Exception {
210-
try {
211-
src.setEncryptionAlgorithm("AES/ECB/PKCS5Padding");
212-
src.start(Channel.SND_TX_SEQ);
213-
214-
// start() should trigger IllegalArgumentException
215-
Assert.fail("ECB mode is not being refused");
216-
} catch (IllegalArgumentException iae) {
217-
// Expected
218-
}
219-
}
220-
221163
@Test
222164
public void testViaFile() throws Exception {
223165
src.start(Channel.SND_TX_SEQ);
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.catalina.tribes.group.interceptors;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
import org.junit.Assert;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.junit.runners.Parameterized;
27+
import org.junit.runners.Parameterized.Parameter;
28+
import org.junit.runners.Parameterized.Parameters;
29+
30+
import org.apache.catalina.tribes.Channel;
31+
32+
@RunWith(Parameterized.class)
33+
public class TestEncryptInterceptorAlgorithms extends EncryptionInterceptorBaseTest {
34+
35+
@Parameters(name = "{index} {0}/{1}/{2}")
36+
public static Collection<Object[]> inputs() {
37+
38+
List<Object[]> result = new ArrayList<>();
39+
// Covers all cipher algorithm modes currently listed in Java Standard Names
40+
41+
// Not supported - Insecure
42+
result.add(new Object[] { "AES", "NONE", "NoPadding", Boolean.FALSE});
43+
// Not supported - Insecure - Padding makes no sense if there is no encryption
44+
result.add(new Object[] { "AES", "NONE", "PKCS5Padding", Boolean.FALSE});
45+
46+
// Not supported - NoPadding requires fixed block size and cluster messages are variable length
47+
result.add(new Object[] { "AES", "CBC", "NoPadding", Boolean.FALSE});
48+
// Supported but not recommended - possible security issues in some configurations - backwards compatibility
49+
result.add(new Object[] { "AES", "CBC", "PKCS5Padding", Boolean.TRUE});
50+
51+
// Not supported - JCA provider doesn't included it
52+
result.add(new Object[] { "AES", "CCM", "NoPadding", Boolean.FALSE});
53+
// Not supported - JCA provider doesn't included it - CCM doesn't need (support?) padding
54+
result.add(new Object[] { "AES", "CCM", "PKCS5Padding", Boolean.FALSE});
55+
56+
// Not supported - NoPadding requires fixed block size and cluster messages are variable length
57+
result.add(new Object[] { "AES", "CFB", "NoPadding", Boolean.FALSE});
58+
// Supported but not recommended - possible security issues in some configurations - backwards compatibility
59+
result.add(new Object[] { "AES", "CFB", "PKCS5Padding", Boolean.TRUE});
60+
61+
// Not supported - Insecure and/or slow
62+
result.add(new Object[] { "AES", "CFB8", "NoPadding", Boolean.FALSE});
63+
result.add(new Object[] { "AES", "CFB8", "PKCS5Padding", Boolean.FALSE});
64+
result.add(new Object[] { "AES", "CFB16", "NoPadding", Boolean.FALSE});
65+
result.add(new Object[] { "AES", "CFB16", "PKCS5Padding", Boolean.FALSE});
66+
// large block sizes not tested but will be rejected as well
67+
68+
// Not supported - Insecure
69+
result.add(new Object[] { "AES", "CTR", "NoPadding", Boolean.FALSE});
70+
// Not supported - Configuration not recommended
71+
result.add(new Object[] { "AES", "CTR", "PKCS5Padding", Boolean.FALSE});
72+
73+
// Not supported - has minimum length
74+
result.add(new Object[] { "AES", "CTS", "NoPadding", Boolean.FALSE});
75+
result.add(new Object[] { "AES", "CTS", "PKCS5Padding", Boolean.FALSE});
76+
77+
// Not supported - Insecure
78+
result.add(new Object[] { "AES", "ECB", "NoPadding", Boolean.FALSE});
79+
result.add(new Object[] { "AES", "ECB", "PKCS5Padding", Boolean.FALSE});
80+
81+
// Default for Tomcat 12 onwards
82+
result.add(new Object[] { "AES", "GCM", "NoPadding", Boolean.TRUE});
83+
// Not supported - GCM doesn't need (support?) padding
84+
result.add(new Object[] { "AES", "GCM", "PKCS5Padding", Boolean.FALSE});
85+
86+
// Not supported - KW not appropriate for encrypting cluster messages
87+
result.add(new Object[] { "AES", "KW", "NoPadding", Boolean.FALSE});
88+
result.add(new Object[] { "AES", "KW", "PKCS5Padding", Boolean.FALSE});
89+
90+
// Not supported - KWP not appropriate for encrypting cluster messages
91+
result.add(new Object[] { "AES", "KWP", "NoPadding", Boolean.FALSE});
92+
result.add(new Object[] { "AES", "KWP", "PKCS5Padding", Boolean.FALSE});
93+
94+
// Not supported - NoPadding requires fixed block size and cluster messages are variable length
95+
result.add(new Object[] { "AES", "OFB", "NoPadding", Boolean.FALSE});
96+
97+
// Supported but not recommended - possible security issues in some configurations - backwards compatibility
98+
result.add(new Object[] { "AES", "OFB", "PKCS5Padding", Boolean.TRUE});
99+
100+
// Not supported - Insecure and/or slow
101+
result.add(new Object[] { "AES", "OFB8", "NoPadding", Boolean.FALSE});
102+
result.add(new Object[] { "AES", "OFB8", "PKCS5Padding", Boolean.FALSE});
103+
result.add(new Object[] { "AES", "OFB16", "NoPadding", Boolean.FALSE});
104+
result.add(new Object[] { "AES", "OFB16", "PKCS5Padding", Boolean.FALSE});
105+
// large block sizes not tested but will be rejected as well
106+
107+
// Not supported - Insecure
108+
result.add(new Object[] { "AES", "PCBC", "NoPadding", Boolean.FALSE});
109+
result.add(new Object[] { "AES", "PCBC", "PKCS5Padding", Boolean.FALSE});
110+
111+
return result;
112+
}
113+
114+
@Parameter(0)
115+
public String algorithm;
116+
117+
@Parameter(1)
118+
public String mode;
119+
120+
@Parameter(2)
121+
public String padding;
122+
123+
@Parameter(3)
124+
public boolean shouldSucceed;
125+
126+
@Test
127+
public void testAlgorithm() throws Exception {
128+
if (shouldSucceed) {
129+
doTestShouldSucceed();
130+
} else {
131+
doTestShouldNotSucceed();
132+
}
133+
}
134+
135+
private void doTestShouldSucceed() throws Exception {
136+
String transformation = String.format("%s/%s/%s", algorithm, mode, padding);
137+
138+
src.setEncryptionAlgorithm(transformation);
139+
src.start(Channel.SND_TX_SEQ);
140+
dest.setEncryptionAlgorithm(transformation);
141+
dest.start(Channel.SND_TX_SEQ);
142+
143+
String testInput = "The quick brown fox jumps over the lazy dog.";
144+
145+
Assert.assertEquals("Failed in " + transformation + " mode",
146+
testInput,
147+
roundTrip(testInput, src, dest));
148+
}
149+
150+
private void doTestShouldNotSucceed() throws Exception {
151+
try {
152+
String transformation = String.format("%s/%s/%s", algorithm, mode, padding);
153+
src.setEncryptionAlgorithm(transformation);
154+
src.start(Channel.SND_TX_SEQ);
155+
156+
// start() should trigger IllegalArgumentException
157+
Assert.fail(transformation + " mode is not being refused");
158+
} catch (IllegalArgumentException iae) {
159+
// Expected
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)