8359388: Stricter checking for cipher transformations

Reviewed-by: mullan
This commit is contained in:
Valerie Peng 2025-07-07 23:36:19 +00:00
parent 197fde5363
commit ec7c6be6a9
2 changed files with 85 additions and 55 deletions

View File

@ -316,19 +316,22 @@ public class Cipher {
private static final String SHA512TRUNCATED = "SHA512/2";
// Parse the specified cipher transformation for algorithm and the
// optional mode and padding. If the transformation contains only
// algorithm, then only algorithm is returned. Otherwise, the
// transformation must contain all 3 and they must be non-empty.
private static String[] tokenizeTransformation(String transformation)
throws NoSuchAlgorithmException {
if (transformation == null) {
throw new NoSuchAlgorithmException("No transformation given");
}
/*
* array containing the components of a cipher transformation:
* Components of a cipher transformation:
*
* index 0: algorithm component (e.g., AES)
* index 1: feedback component (e.g., CFB)
* index 2: padding component (e.g., PKCS5Padding)
* 1) algorithm component (e.g., AES)
* 2) feedback component (e.g., CFB) - optional
* 3) padding component (e.g., PKCS5Padding) - optional
*/
String[] parts = { "", "", "" };
// check if the transformation contains algorithms with "/" in their
// name which can cause the parsing logic to go wrong
@ -337,27 +340,35 @@ public class Cipher {
int startIdx = (sha512Idx == -1 ? 0 :
sha512Idx + SHA512TRUNCATED.length());
int endIdx = transformation.indexOf('/', startIdx);
if (endIdx == -1) {
// algorithm
parts[0] = transformation.trim();
boolean algorithmOnly = (endIdx == -1);
String algo = (algorithmOnly ? transformation.trim() :
transformation.substring(0, endIdx).trim());
if (algo.isEmpty()) {
throw new NoSuchAlgorithmException("Invalid transformation: " +
"algorithm not specified-"
+ transformation);
}
if (algorithmOnly) { // done
return new String[] { algo };
} else {
// algorithm/mode/padding
parts[0] = transformation.substring(0, endIdx).trim();
// continue parsing mode and padding
startIdx = endIdx+1;
endIdx = transformation.indexOf('/', startIdx);
if (endIdx == -1) {
throw new NoSuchAlgorithmException("Invalid transformation"
+ " format:" + transformation);
}
parts[1] = transformation.substring(startIdx, endIdx).trim();
parts[2] = transformation.substring(endIdx+1).trim();
}
if (parts[0].isEmpty()) {
throw new NoSuchAlgorithmException("Invalid transformation: " +
"algorithm not specified-"
String mode = transformation.substring(startIdx, endIdx).trim();
String padding = transformation.substring(endIdx+1).trim();
// ensure mode and padding are specified
if (mode.isEmpty() || padding.isEmpty()) {
throw new NoSuchAlgorithmException("Invalid transformation: " +
"missing mode and/or padding-"
+ transformation);
}
return new String[] { algo, mode, padding };
}
return parts;
}
// Provider attribute name for supported chaining mode
@ -453,28 +464,17 @@ public class Cipher {
throws NoSuchAlgorithmException {
String[] parts = tokenizeTransformation(transformation);
String alg = parts[0];
String mode = (parts[1].length() == 0 ? null : parts[1]);
String pad = (parts[2].length() == 0 ? null : parts[2]);
if ((mode == null) && (pad == null)) {
if (parts.length == 1) {
// Algorithm only
Transform tr = new Transform(alg, "", null, null);
return Collections.singletonList(tr);
return List.of(new Transform(parts[0], "", null, null));
} else {
// Algorithm w/ at least mode or padding or both
List<Transform> list = new ArrayList<>(4);
if ((mode != null) && (pad != null)) {
list.add(new Transform(alg, "/" + mode + "/" + pad, null, null));
}
if (mode != null) {
list.add(new Transform(alg, "/" + mode, null, pad));
}
if (pad != null) {
list.add(new Transform(alg, "//" + pad, mode, null));
}
list.add(new Transform(alg, "", mode, pad));
return list;
// Algorithm w/ both mode and padding
return List.of(
new Transform(parts[0], "/" + parts[1] + "/" + parts[2],
null, null),
new Transform(parts[0], "/" + parts[1], null, parts[2]),
new Transform(parts[0], "//" + parts[2], parts[1], null),
new Transform(parts[0], "", parts[1], parts[2]));
}
}

View File

@ -24,33 +24,63 @@
/*
* @test
* @bug 8358159
* @summary test that the Cipher.getInstance() handles
* transformations with empty mode and/or padding
* @run main TestEmptyModePadding
* @bug 8359388
* @summary test that the Cipher.getInstance() would reject improper
* transformations with empty mode and/or padding.
*/
import java.security.*;
import javax.crypto.*;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import javax.crypto.Cipher;
public class TestEmptyModePadding {
public static void main(String[] args) throws Exception {
Provider provider = Security.getProvider(System.getProperty("test.provider.name", "SunJCE"));
Provider provider = Security.getProvider(
System.getProperty("test.provider.name", "SunJCE"));
test("AES", provider);
test("AES/ECB/PKCS5Padding", provider);
test("AES//PKCS5Padding", provider); // Empty mode
test("AES/CBC/", provider); // Empty padding
test("AES/ /NoPadding", provider); // Mode is a space
test("AES/CBC/ ", provider); // Padding is a space
test("AES/ / ", provider); // Both mode and padding are spaces
test("AES//", provider); // Both mode and padding are missing
System.out.println("Testing against " + provider.getName());
String[] testTransformations = {
// transformations w/ only 1 component, i.e. algo
" ",
// transformations w/ only 2 components
"AES/",
"AES/ ",
"AES/CBC",
"PBEWithHmacSHA512/224AndAES_128/",
"PBEWithHmacSHA512/256AndAES_128/ ",
"PBEWithHmacSHA512/224AndAES_128/CBC",
// 3-component transformations w/ empty component(s)
"AES//",
"AES/ /",
"AES// ",
"AES/ / ",
"AES/CBC/", "AES/CBC/ ",
"AES//PKCS5Padding", "AES/ /NoPadding",
"PBEWithHmacSHA512/224AndAES_128//",
"PBEWithHmacSHA512/224AndAES_128/ /",
"PBEWithHmacSHA512/224AndAES_128// ",
"PBEWithHmacSHA512/224AndAES_128/ / ",
"PBEWithHmacSHA512/256AndAES_128/CBC/",
"PBEWithHmacSHA512/256AndAES_128/CBC/ ",
"PBEWithHmacSHA512/256AndAES_128//PKCS5Padding",
"PBEWithHmacSHA512/256AndAES_128/ /PKCS5Padding",
};
for (String t : testTransformations) {
test(t, provider);
}
}
private static void test(String transformation, Provider provider) throws Exception {
Cipher c = Cipher.getInstance(transformation, provider);
private static void test(String t, Provider p) throws Exception {
try {
Cipher c = Cipher.getInstance(t, p);
throw new RuntimeException("Should throw NSAE for \'" + t + "\'");
} catch (NoSuchAlgorithmException nsae) {
// transformation info is already in the NSAE message
System.out.println("Expected NSAE: " + nsae.getMessage());
}
}
}