6447816: Provider filtering (getProviders) is not working with OR'd conditions

Reviewed-by: weijun
This commit is contained in:
Valerie Peng 2022-09-09 00:30:54 +00:00
parent 43e191d64b
commit 812d805a48
2 changed files with 295 additions and 204 deletions

View File

@ -519,14 +519,20 @@ public final class Security {
public static Provider[] getProviders(String filter) {
String key;
String value;
int index = filter.indexOf(':');
if (index == -1) {
key = filter;
if (index == -1) { // <crypto_service>.<algo_or_type> only
key = filter.trim();
value = "";
} else {
key = filter.substring(0, index);
value = filter.substring(index + 1);
// <crypto_service>.<algo_or_type> <attr_name>:<attr_value>
key = filter.substring(0, index).trim();
value = filter.substring(index + 1).trim();
// ensure value is not empty here; rest will be checked in Criteria
if (value.isEmpty()) {
throw new InvalidParameterException("Invalid filter");
}
}
Hashtable<String, String> hashtableFilter = new Hashtable<>(1);
@ -591,42 +597,31 @@ public final class Security {
// Get all installed providers first.
// Then only return those providers who satisfy the selection criteria.
Provider[] allProviders = Security.getProviders();
Set<String> keySet = filter.keySet();
LinkedHashSet<Provider> candidates = new LinkedHashSet<>(5);
Set<Map.Entry<String, String>> entries = filter.entrySet();
// Returns all installed providers
// if the selection criteria is null.
if ((keySet == null) || (allProviders == null)) {
if (allProviders == null || allProviders.length == 0) {
return null;
} else if (entries == null) {
// return all installed providers if the selection criteria is null
return allProviders;
} else if (entries.isEmpty()) {
// return null if the selection criteria is empty; this is to match
// earlier behavior
return null;
}
boolean firstSearch = true;
LinkedList<Provider> candidates =
new LinkedList<>(Arrays.asList(allProviders));
// For each selection criterion, remove providers
// which don't satisfy the criterion from the candidate set.
for (String key : keySet) {
String value = filter.get(key);
LinkedHashSet<Provider> newCandidates = getAllQualifyingCandidates(key, value,
allProviders);
if (firstSearch) {
candidates = newCandidates;
firstSearch = false;
for (var e : entries) {
Criteria cr = new Criteria(e.getKey(), e.getValue());
candidates.removeIf(p -> !cr.isCriterionSatisfied(p));
if (candidates.isEmpty()) {
return null;
}
if (!newCandidates.isEmpty()) {
// For each provider in the candidates set, if it
// isn't in the newCandidate set, we should remove
// it from the candidate set.
candidates.removeIf(prov -> !newCandidates.contains(prov));
} else {
candidates = null;
break;
}
}
if (candidates == null || candidates.isEmpty())
return null;
};
return candidates.toArray(new Provider[0]);
}
@ -822,194 +817,134 @@ public final class Security {
}
}
/*
* Returns all providers who satisfy the specified
* criterion.
*/
private static LinkedHashSet<Provider> getAllQualifyingCandidates(
String filterKey,
String filterValue,
Provider[] allProviders) {
String[] filterComponents = getFilterComponents(filterKey,
filterValue);
private static class Criteria {
private final String serviceName;
private final String algName;
private final String attrName;
private final String attrValue;
// The first component is the service name.
// The second is the algorithm name.
// If the third isn't null, that is the attribute name.
String serviceName = filterComponents[0];
String algName = filterComponents[1];
String attrName = filterComponents[2];
Criteria(String key, String value) throws InvalidParameterException {
return getProvidersNotUsingCache(serviceName, algName, attrName,
filterValue, allProviders);
}
private static LinkedHashSet<Provider> getProvidersNotUsingCache(
String serviceName,
String algName,
String attrName,
String filterValue,
Provider[] allProviders) {
LinkedHashSet<Provider> candidates = new LinkedHashSet<>(5);
for (int i = 0; i < allProviders.length; i++) {
if (isCriterionSatisfied(allProviders[i], serviceName,
algName,
attrName, filterValue)) {
candidates.add(allProviders[i]);
int snEndIndex = key.indexOf('.');
if (snEndIndex <= 0) {
// There must be a dot in the filter, and the dot
// shouldn't be at the beginning of this string.
throw new InvalidParameterException("Invalid filter");
}
}
return candidates;
}
/*
* Returns {@code true} if the given provider satisfies
* the selection criterion key:value.
*/
private static boolean isCriterionSatisfied(Provider prov,
String serviceName,
String algName,
String attrName,
String filterValue) {
String key = serviceName + '.' + algName;
serviceName = key.substring(0, snEndIndex);
attrValue = value;
if (attrName != null) {
key += ' ' + attrName;
}
// Check whether the provider has a property
// whose key is the same as the given key.
String propValue = getProviderProperty(key, prov);
if (propValue == null) {
// Check whether we have an alias instead
// of a standard name in the key.
String standardName = getProviderProperty("Alg.Alias." +
serviceName + "." +
algName,
prov);
if (standardName != null) {
key = serviceName + "." + standardName;
if (attrName != null) {
key += ' ' + attrName;
if (value.isEmpty()) {
// value is empty. So the key should be in the format of
// <crypto_service>.<algorithm_or_type>.
algName = key.substring(snEndIndex + 1);
attrName = null;
} else {
// value is non-empty. So the key must be in the format
// of <crypto_service>.<algorithm_or_type>(one or more
// spaces)<attribute_name>
int algEndIndex = key.indexOf(' ', snEndIndex);
if (algEndIndex == -1) {
throw new InvalidParameterException
("Invalid filter - need algorithm name");
}
algName = key.substring(snEndIndex + 1, algEndIndex);
attrName = key.substring(algEndIndex + 1).trim();
if (attrName.isEmpty()) {
throw new InvalidParameterException
("Invalid filter - need attribute name");
} else if (isCompositeValue() && attrValue.indexOf('|') != -1) {
throw new InvalidParameterException
("Invalid filter - composite values unsupported");
}
propValue = getProviderProperty(key, prov);
}
// check required values
if (serviceName.isEmpty() || algName.isEmpty()) {
throw new InvalidParameterException
("Invalid filter - need service and algorithm");
}
}
// returns true when this criteria contains a standard attribute
// whose value may be composite, i.e. multiple values separated by "|"
private boolean isCompositeValue() {
return (attrName != null &&
(attrName.equalsIgnoreCase("SupportedKeyClasses") ||
attrName.equalsIgnoreCase("SupportedPaddings") ||
attrName.equalsIgnoreCase("SupportedModes") ||
attrName.equalsIgnoreCase("SupportedKeyFormats")));
}
/*
* Returns {@code true} if the given provider satisfies
* the selection criterion key:value.
*/
private boolean isCriterionSatisfied(Provider prov) {
// Constructed key have ONLY 1 space between algName and attrName
String key = serviceName + '.' + algName +
(attrName != null ? (' ' + attrName) : "");
// Check whether the provider has a property
// whose key is the same as the given key.
String propValue = getProviderProperty(key, prov);
if (propValue == null) {
// The provider doesn't have the given
// key in its property list.
return false;
}
}
// Check whether we have an alias instead
// of a standard name in the key.
String standardName = getProviderProperty("Alg.Alias." +
serviceName + "." + algName, prov);
if (standardName != null) {
key = serviceName + "." + standardName +
(attrName != null ? ' ' + attrName : "");
propValue = getProviderProperty(key, prov);
}
// If the key is in the format of:
// <crypto_service>.<algorithm_or_type>,
// there is no need to check the value.
if (attrName == null) {
return true;
}
// If we get here, the key must be in the
// format of <crypto_service>.<algorithm_or_provider> <attribute_name>.
if (isStandardAttr(attrName)) {
return isConstraintSatisfied(attrName, filterValue, propValue);
} else {
return filterValue.equalsIgnoreCase(propValue);
}
}
/*
* Returns {@code true} if the attribute is a standard attribute;
* otherwise, returns {@code false}.
*/
private static boolean isStandardAttr(String attribute) {
// For now, we just have two standard attributes:
// KeySize and ImplementedIn.
if (attribute.equalsIgnoreCase("KeySize"))
return true;
return attribute.equalsIgnoreCase("ImplementedIn");
}
/*
* Returns {@code true} if the requested attribute value is supported;
* otherwise, returns {@code false}.
*/
private static boolean isConstraintSatisfied(String attribute,
String value,
String prop) {
// For KeySize, prop is the max key size the
// provider supports for a specific <crypto_service>.<algorithm>.
if (attribute.equalsIgnoreCase("KeySize")) {
int requestedSize = Integer.parseInt(value);
int maxSize = Integer.parseInt(prop);
return requestedSize <= maxSize;
}
// For Type, prop is the type of the implementation
// for a specific <crypto service>.<algorithm>.
if (attribute.equalsIgnoreCase("ImplementedIn")) {
return value.equalsIgnoreCase(prop);
}
return false;
}
static String[] getFilterComponents(String filterKey, String filterValue) {
int algIndex = filterKey.indexOf('.');
if (algIndex < 0) {
// There must be a dot in the filter, and the dot
// shouldn't be at the beginning of this string.
throw new InvalidParameterException("Invalid filter");
}
String serviceName = filterKey.substring(0, algIndex);
String algName;
String attrName = null;
if (filterValue.isEmpty()) {
// The filterValue is an empty string. So the filterKey
// should be in the format of <crypto_service>.<algorithm_or_type>.
algName = filterKey.substring(algIndex + 1).trim();
if (algName.isEmpty()) {
// There must be an algorithm or type name.
throw new InvalidParameterException("Invalid filter");
}
} else {
// The filterValue is a non-empty string. So the filterKey must be
// in the format of
// <crypto_service>.<algorithm_or_type> <attribute_name>
int attrIndex = filterKey.indexOf(' ');
if (attrIndex == -1) {
// There is no attribute name in the filter.
throw new InvalidParameterException("Invalid filter");
} else {
attrName = filterKey.substring(attrIndex + 1).trim();
if (attrName.isEmpty()) {
// There is no attribute name in the filter.
throw new InvalidParameterException("Invalid filter");
if (propValue == null) {
// The provider doesn't have the given
// key in its property list.
return false;
}
}
// There must be an algorithm name in the filter.
if ((attrIndex < algIndex) ||
(algIndex == attrIndex - 1)) {
throw new InvalidParameterException("Invalid filter");
// If the key is in the format of:
// <crypto_service>.<algorithm_or_type>,
// there is no need to check the value.
if (attrName == null) {
return true;
}
// If we get here, the key must be in the
// format of <crypto_service>.<algorithm_or_type> <attribute_name>.
// Check the "Java Security Standard Algorithm Names" guide for the
// list of supported Service Attributes
// For KeySize, prop is the max key size the provider supports
// for a specific <crypto_service>.<algorithm>.
if (attrName.equalsIgnoreCase("KeySize")) {
int requestedSize = Integer.parseInt(attrValue);
int maxSize = Integer.parseInt(propValue);
return requestedSize <= maxSize;
}
// Handle attributes with composite values
if (isCompositeValue()) {
String attrValue2 = attrValue.toUpperCase(Locale.ENGLISH);
propValue = propValue.toUpperCase(Locale.ENGLISH);
// match value to the property components
String[] propComponents = propValue.split("\\|");
for (String pc : propComponents) {
if (attrValue2.equals(pc)) return true;
}
return false;
} else {
algName = filterKey.substring(algIndex + 1, attrIndex);
// direct string compare (ignore case)
return attrValue.equalsIgnoreCase(propValue);
}
}
String[] result = new String[3];
result[0] = serviceName;
result[1] = algName;
result[2] = attrName;
return result;
}
/**

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @library /test/lib
* @bug 6447816
* @summary Check that provider service matching/filtering is done correctly
* @run main/othervm ProviderFiltering
*/
import jdk.test.lib.Utils;
import java.util.*;
import java.security.*;
public class ProviderFiltering {
private static void doit(Object filter, String... expectedPNs) {
System.out.println("Filter: " + filter);
System.out.println("Expected Provider(s): " +
(expectedPNs.length > 0 ? Arrays.toString(expectedPNs) :
"<NONE>"));
Provider ps[];
if (filter instanceof String filterStr) {
ps = Security.getProviders(filterStr);
} else if (filter instanceof Map filterMap) {
ps = Security.getProviders(filterMap);
} else {
throw new RuntimeException("Error: unknown input type: " + filter);
}
if (ps == null) {
if (expectedPNs.length != 0) {
throw new RuntimeException("Fail: expected provider(s) " +
"not found");
}
} else {
if (ps.length == expectedPNs.length) {
// check the provider names
for (int i = 0; i < ps.length; i++) {
if (!ps[i].getName().equals(expectedPNs[i])) {
throw new RuntimeException("Fail: provider name " +
"mismatch at index " + i + ", got " +
ps[i].getName());
}
}
} else {
throw new RuntimeException("Fail: # of providers mismatch");
}
}
System.out.println("=> Passed");
}
public static void main(String[] args)
throws NoSuchAlgorithmException {
// test filter parsing
String[] invalidFilters = { "", "Cipher.", ".RC2 ", "Cipher.RC2 :",
"Cipher.RC2 a: ", "Cipher.RC2 :b",
"Cipher.RC2 SupportedKeyClasses:a|b"
};
for (String i : invalidFilters) {
System.out.println("Testing IPE for :" + i);
Utils.runAndCheckException(()-> Security.getProviders(i),
InvalidParameterException.class);
}
String p = "SUN";
// test alias
doit("Signature.NONEwithDSA", p);
String sigService = "Signature.SHA256withDSA";
// javadoc allows extra spaces in between
String key = sigService + " SupportedKeyClasses";
String valComp1 = "java.security.interfaces.DSAPublicKey";
String valComp2 = "java.security.interfaces.DSAPrivateKey";
String valComp2CN = valComp2.substring(valComp2.lastIndexOf('.') + 1);
// test using String filter
doit(key + ":" + valComp1, p);
doit(key + ":" + valComp2, p);
// current impl does matching on individual attribute value for
// attributes with composite values; no partial match
doit(key + ":" + valComp2CN);
// repeat above tests using filter Map
Map<String,String> filters = new HashMap<>();
// match existing behavior; return null if empty filter map
doit(filters);
filters.put(key, valComp1);
doit(filters, p);
filters.put(key, valComp2);
doit(filters, p);
filters.put(key, valComp2CN);
doit(filters);
// try non-attribute filters
filters.clear();
filters.put(sigService, "");
doit(filters, p);
filters.put("Cipher.RC2", "");
doit(filters);
// test against a custom provider and attribute
filters.clear();
String customKey = "customAttr";
String customValue = "customValue";
String pName = "TestProv";
Provider testProv = new TestProvider(pName, sigService, customKey,
customValue);
Security.insertProviderAt(testProv, 1);
// should find both TestProv and SUN and in this order
doit(sigService, pName, "SUN");
filters.put(sigService, "");
doit(filters, pName, "SUN");
String specAttr = sigService + " " + customKey + ":" + customValue;
// should find only TestProv
doit(specAttr, pName);
filters.put(sigService + " " + customKey, customValue);
doit(filters, pName);
// should find no provider now that TestProv is removed
Security.removeProvider(pName);
doit(specAttr);
}
private static class TestProvider extends Provider {
TestProvider(String name, String service, String attrKey,
String attrValue) {
super(name, "0.0", "Not for use in production systems!");
put(service, "a.b.c");
put(service + " " + attrKey, attrValue);
}
}
}