blob: ac64315eab1b20e7d0a939e87d22c0b96708668f [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.java.security;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CRLSelector;
import java.security.cert.CertSelector;
import java.security.cert.CertStoreException;
import java.security.cert.CertStoreParameters;
import java.security.cert.CertStoreSpi;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import junit.framework.TestCase;
import libcore.javax.crypto.MockKey;
public class ProviderTest extends TestCase {
private static final boolean LOG_DEBUG = false;
/**
* Makes sure all all expected implementations (but not aliases)
* and that there are no extras, according to what we expect from
* StandardNames
*/
public void test_Provider_getServices() throws Exception {
// build set of expected algorithms
Map<String,Set<String>> remainingExpected
= new HashMap<String,Set<String>>(StandardNames.PROVIDER_ALGORITHMS);
for (Entry<String,Set<String>> entry : remainingExpected.entrySet()) {
entry.setValue(new HashSet<String>(entry.getValue()));
}
List<String> extra = new ArrayList<String>();
List<String> missing = new ArrayList<String>();
Provider[] providers = Security.getProviders();
for (Provider provider : providers) {
String providerName = provider.getName();
// ignore BouncyCastle provider if it is installed on the RI
if (StandardNames.IS_RI && providerName.equals("BC")) {
continue;
}
Set<Provider.Service> services = provider.getServices();
assertNotNull(services);
assertFalse(services.isEmpty());
for (Provider.Service service : services) {
String type = service.getType();
String algorithm = service.getAlgorithm().toUpperCase();
String className = service.getClassName();
if (LOG_DEBUG) {
System.out.println(providerName
+ " " + type
+ " " + algorithm
+ " " + className);
}
// remove from remaining, assert unknown if missing
Set<String> remainingAlgorithms = remainingExpected.get(type);
if (remainingAlgorithms == null || !remainingAlgorithms.remove(algorithm)) {
// seems to be missing, but sometimes the same
// algorithm is available from multiple providers
// (e.g. KeyFactory RSA is available from
// SunRsaSign and SunJSSE), so double check in
// original source before giving error
if (!(StandardNames.PROVIDER_ALGORITHMS.containsKey(type)
&& StandardNames.PROVIDER_ALGORITHMS.get(type).contains(algorithm))) {
extra.add("Unknown " + type + " " + algorithm + " " + providerName + "\n");
}
} else if ("Cipher".equals(type) && !algorithm.contains("/")) {
/*
* Cipher selection follows special rules where you can
* specify the mode and padding during the getInstance call.
* Try to see if the service supports this.
*/
Set<String> toRemove = new HashSet<String>();
for (String remainingAlgo : remainingAlgorithms) {
String[] parts = remainingAlgo.split("/");
if (parts.length == 3 && algorithm.equals(parts[0])) {
try {
Cipher.getInstance(remainingAlgo, provider);
toRemove.add(remainingAlgo);
} catch (NoSuchAlgorithmException ignored) {
} catch (NoSuchPaddingException ignored) {
}
}
}
remainingAlgorithms.removeAll(toRemove);
}
if (remainingAlgorithms != null && remainingAlgorithms.isEmpty()) {
remainingExpected.remove(type);
}
// make sure class exists and can be initialized
try {
assertNotNull(Class.forName(className,
true,
provider.getClass().getClassLoader()));
} catch (ClassNotFoundException e) {
// Sun forgot their own class
if (!className.equals("sun.security.pkcs11.P11MAC")) {
missing.add(className);
}
}
}
}
// assert that we don't have any extra in the implementation
Collections.sort(extra); // sort so that its grouped by type
assertEquals("Algorithms are provided but not present in StandardNames",
Collections.EMPTY_LIST, extra);
if (remainingExpected.containsKey("Cipher")) {
// For any remaining ciphers, they may be aliases for other ciphers or otherwise
// don't show up as a service but can still be instantiated.
for (Iterator<String> cipherIt = remainingExpected.get("Cipher").iterator();
cipherIt.hasNext(); ) {
String missingCipher = cipherIt.next();
try {
Cipher.getInstance(missingCipher);
cipherIt.remove();
} catch (NoSuchAlgorithmException|NoSuchPaddingException e) {
}
}
if (remainingExpected.get("Cipher").isEmpty()) {
remainingExpected.remove("Cipher");
}
}
// assert that we don't have any missing in the implementation
assertEquals("Algorithms are present in StandardNames but not provided",
Collections.EMPTY_MAP, remainingExpected);
// assert that we don't have any missing classes
Collections.sort(missing); // sort it for readability
assertEquals("Missing classes", Collections.EMPTY_LIST, missing);
}
private static final Pattern alias = Pattern.compile("Alg\\.Alias\\.([^.]*)\\.(.*)");
/**
* Makes sure all provider properties either point to a class
* implementation that exists or are aliases to known algorithms.
*/
public void test_Provider_Properties() throws Exception {
/*
* A useful reference on Provider properties
* <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/HowToImplAProvider.html">
* How to Implement a Provider in the Java &trade; Cryptography Architecture
* </a>
*/
Provider[] providers = Security.getProviders();
for (Provider provider : providers) {
// check Provider.id proprieties
assertEquals(provider.getName(),
provider.get("Provider.id name"));
assertEquals(String.valueOf(provider.getVersion()),
provider.get("Provider.id version"));
assertEquals(provider.getInfo(),
provider.get("Provider.id info"));
assertEquals(provider.getClass().getName(),
provider.get("Provider.id className"));
// build map of all known aliases and implementations
Map<String,String> aliases = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String,String> implementations = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Entry<Object,Object> entry : provider.entrySet()) {
Object k = entry.getKey();
Object v = entry.getValue();
assertEquals(String.class, k.getClass());
assertEquals(String.class, v.getClass());
String key = (String)k;
String value = (String)v;
// skip Provider.id keys, we check well known ones values above
if (key.startsWith("Provider.id ")) {
continue;
}
// skip property settings such as: "Signature.SHA1withDSA ImplementedIn" "Software"
if (key.indexOf(' ') != -1) {
continue;
}
Matcher m = alias.matcher(key);
if (m.find()) {
String type = m.group(1);
aliases.put(key, type + "." + value);
} else {
implementations.put(key, value);
}
}
// verify implementation classes are available
for (Entry<String,String> entry : implementations.entrySet()) {
String typeAndAlgorithm = entry.getKey();
String className = entry.getValue();
try {
assertNotNull(Class.forName(className,
true,
provider.getClass().getClassLoader()));
} catch (ClassNotFoundException e) {
// Sun forgot their own class
if (!className.equals("sun.security.pkcs11.P11MAC")) {
fail("Could not find class " + className + " for " + typeAndAlgorithm
+ " [provider=" + provider.getName() + "]");
}
}
}
// make sure all aliases point to some known implementation
for (Entry<String,String> entry : aliases.entrySet()) {
String alias = entry.getKey();
String actual = entry.getValue();
assertTrue("Could not find implementation " + actual + " for alias " + alias +
" [provider=" + provider.getName() + "]",
implementations.containsKey(actual));
}
}
}
private static final String[] TYPES_SERVICES_CHECKED = new String[] {
"KeyFactory", "CertPathBuilder", "Cipher", "SecureRandom",
"AlgorithmParameterGenerator", "Signature", "KeyPairGenerator", "CertificateFactory",
"MessageDigest", "KeyAgreement", "CertStore", "SSLContext", "AlgorithmParameters",
"TrustManagerFactory", "KeyGenerator", "Mac", "CertPathValidator", "SecretKeyFactory",
"KeyManagerFactory", "KeyStore",
};
private static final HashSet<String> TYPES_SUPPORTS_PARAMETER = new HashSet<String>(
Arrays.asList(new String[] {
"Mac", "KeyAgreement", "Cipher", "Signature",
}));
private static final HashSet<String> TYPES_NOT_SUPPORTS_PARAMETER = new HashSet<String>(
Arrays.asList(TYPES_SERVICES_CHECKED));
static {
TYPES_NOT_SUPPORTS_PARAMETER.removeAll(TYPES_SUPPORTS_PARAMETER);
}
public void test_Provider_getServices_supportsParameter() throws Exception {
HashSet<String> remainingTypes = new HashSet<String>(Arrays.asList(TYPES_SERVICES_CHECKED));
HashSet<String> supportsParameterTypes = new HashSet<String>();
HashSet<String> noSupportsParameterTypes = new HashSet<String>();
Provider[] providers = Security.getProviders();
for (Provider provider : providers) {
Set<Provider.Service> services = provider.getServices();
assertNotNull(services);
assertFalse(services.isEmpty());
for (Provider.Service service : services) {
final String type = service.getType();
remainingTypes.remove(type);
try {
service.supportsParameter(new MockKey());
supportsParameterTypes.add(type);
} catch (InvalidParameterException e) {
noSupportsParameterTypes.add(type);
try {
service.supportsParameter(new Object());
fail("Should throw on non-Key parameter");
} catch (InvalidParameterException expected) {
}
}
}
}
supportsParameterTypes.retainAll(TYPES_SUPPORTS_PARAMETER);
assertEquals("Types that should support parameters", TYPES_SUPPORTS_PARAMETER,
supportsParameterTypes);
noSupportsParameterTypes.retainAll(TYPES_NOT_SUPPORTS_PARAMETER);
assertEquals("Types that should not support parameters", TYPES_NOT_SUPPORTS_PARAMETER,
noSupportsParameterTypes);
assertEquals("Types that should be checked", Collections.EMPTY_SET, remainingTypes);
}
public static class MockSpi {
public Object parameter;
public MockSpi(MockKey parameter) {
this.parameter = parameter;
}
};
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_UnknownService_Success() throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Fake.FOO", MockSpi.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Fake", "FOO");
assertTrue(service.supportsParameter(new Object()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_KnownService_NoClassInitialization_Success()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", getClass().getName()
+ ".UninitializedMockKey");
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertFalse(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public static class UninitializedMockKey extends MockKey {
static {
fail("This should not be initialized");
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_TypeDoesNotSupportParameter_Failure()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("KeyFactory.FOO", MockSpi.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("KeyFactory", "FOO");
try {
service.supportsParameter(new MockKey());
fail("Should always throw exception");
} catch (InvalidParameterException expected) {
}
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_SupportedKeyClasses_NonKeyClass_Success()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", MockSpi.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertFalse(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_KnownService_NonKey_Failure()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
try {
service.supportsParameter(new Object());
fail("Should throw when non-Key passed in");
} catch (InvalidParameterException expected) {
}
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_KnownService_SupportedKeyClasses_NonKey_Failure()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", RSAPrivateKey.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
try {
service.supportsParameter(new Object());
fail("Should throw on non-Key instance passed in");
} catch (InvalidParameterException expected) {
}
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_KnownService_Null_Failure() throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", RSAPrivateKey.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertFalse(service.supportsParameter(null));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_SupportedKeyClasses_Success()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", MockKey.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertTrue(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_SupportedKeyClasses_Failure()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyClasses", RSAPrivateKey.class.getName());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertFalse(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_SupportedKeyFormats_Success()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyFormats", new MockKey().getFormat());
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertTrue(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_supportsParameter_SupportedKeyFormats_Failure()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Signature.FOO SupportedKeyFormats", "Invalid");
}
};
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("Signature", "FOO");
assertFalse(service.supportsParameter(new MockKey()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_newInstance_DoesNotCallSupportsParameter_Success()
throws Exception {
MockProvider provider = new MockProvider("MockProvider");
provider.putServiceForTest(new Provider.Service(provider, "CertStore", "FOO",
MyCertStoreSpi.class.getName(), null, null) {
@Override
public boolean supportsParameter(Object parameter) {
fail("This should not be called");
return false;
}
});
Security.addProvider(provider);
try {
Provider.Service service = provider.getService("CertStore", "FOO");
assertNotNull(service.newInstance(new MyCertStoreParameters()));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_newInstance_PrivateClass_throws()
throws Exception {
MockProvider provider = new MockProvider("MockProvider");
provider.putServiceForTest(new Provider.Service(provider, "CertStore", "FOO",
CertStoreSpiPrivateClass.class.getName(), null, null));
Security.addProvider(provider);
// The class for the service is private, it must fail with NoSuchAlgorithmException
try {
Provider.Service service = provider.getService("CertStore", "FOO");
service.newInstance(null);
fail();
} catch (NoSuchAlgorithmException expected) {
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_newInstance_PrivateEmptyConstructor_throws()
throws Exception {
MockProvider provider = new MockProvider("MockProvider");
provider.putServiceForTest(new Provider.Service(provider, "CertStore", "FOO",
CertStoreSpiPrivateEmptyConstructor.class.getName(), null, null));
Security.addProvider(provider);
// The empty constructor is private, it must fail with NoSuchAlgorithmException
try {
Provider.Service service = provider.getService("CertStore", "FOO");
service.newInstance(null);
fail();
} catch (NoSuchAlgorithmException expected) {
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_AliasDoesNotEraseCanonical_Success()
throws Exception {
// Make sure we start with a "known good" alias for this OID.
{
EncryptedPrivateKeyInfo epki1 = new EncryptedPrivateKeyInfo("OID.1.2.840.113549.1.1.5",
new byte[1]);
assertEquals("SHA1WITHRSA", epki1.getAlgName().toUpperCase(Locale.US));
}
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.FOO", MockSpi.class.getName());
put("Alg.Alias.Signature.OID.1.2.840.113549.1.1.5", "FOO");
}
};
Security.addProvider(provider);
try {
// This triggers a re-indexing of the algorithm id data:
try {
new EncryptedPrivateKeyInfo("nonexistent", new byte[1]);
fail("Should not find 'nonexistent' algorithm");
} catch (NoSuchAlgorithmException expected) {
}
EncryptedPrivateKeyInfo epki2 = new EncryptedPrivateKeyInfo("OID.1.2.840.113549.1.1.5", new byte[1]);
assertEquals("SHA1WITHRSA", epki2.getAlgName().toUpperCase(Locale.US));
} finally {
Security.removeProvider(provider.getName());
}
}
@SuppressWarnings("serial")
public void testProviderService_CanFindNewOID_Success()
throws Exception {
Provider provider = new MockProvider("MockProvider") {
public void setup() {
put("Signature.NEWALG", MockSpi.class.getName());
put("Alg.Alias.Signature.OID.1.2.9999.9999.9999", "NEWALG");
}
};
Security.addProvider(provider);
try {
EncryptedPrivateKeyInfo epki2 = new EncryptedPrivateKeyInfo("OID.1.2.9999.9999.9999", new byte[1]);
assertEquals("NEWALG", epki2.getAlgName().toUpperCase(Locale.US));
} finally {
Security.removeProvider(provider.getName());
}
}
public void testProvider_removeProvider_Success() throws Exception {
MockProvider provider = new MockProvider("MockProvider");
assertNull(Security.getProvider(provider.getName()));
Security.addProvider(provider);
assertNotNull(Security.getProvider(provider.getName()));
Security.removeProvider(provider.getName());
assertNull(Security.getProvider(provider.getName()));
}
public static class MyCertStoreSpi extends CertStoreSpi {
public MyCertStoreSpi(CertStoreParameters params) throws InvalidAlgorithmParameterException {
super(params);
}
@Override
public Collection<? extends Certificate> engineGetCertificates(CertSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
@Override
public Collection<? extends CRL> engineGetCRLs(CRLSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
}
private static class CertStoreSpiPrivateClass extends CertStoreSpi {
public CertStoreSpiPrivateClass()
throws InvalidAlgorithmParameterException {
super(null);
}
@Override
public Collection<? extends Certificate> engineGetCertificates(CertSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
@Override
public Collection<? extends CRL> engineGetCRLs(CRLSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
}
private static class CertStoreSpiPrivateEmptyConstructor extends CertStoreSpi {
private CertStoreSpiPrivateEmptyConstructor(CertStoreParameters params)
throws InvalidAlgorithmParameterException {
super(null);
}
@Override
public Collection<? extends Certificate> engineGetCertificates(CertSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
@Override
public Collection<? extends CRL> engineGetCRLs(CRLSelector selector)
throws CertStoreException {
throw new UnsupportedOperationException();
}
}
public static class MyCertStoreParameters implements CertStoreParameters {
public Object clone() {
return new MyCertStoreParameters();
}
}
/**
* http://code.google.com/p/android/issues/detail?id=21449
*/
public void testSecureRandomImplementationOrder() {
@SuppressWarnings("serial")
Provider srp = new MockProvider("SRProvider") {
public void setup() {
put("SecureRandom.SecureRandom1", SecureRandom1.class.getName());
put("SecureRandom.SecureRandom2", SecureRandom2.class.getName());
put("SecureRandom.SecureRandom3", SecureRandom3.class.getName());
}
};
try {
int position = Security.insertProviderAt(srp, 1); // first is one, not zero
assertEquals(1, position);
SecureRandom sr = new SecureRandom();
if (!sr.getAlgorithm().equals("SecureRandom1")) {
throw new IllegalStateException("Expected SecureRandom1 was " + sr.getAlgorithm());
}
} finally {
Security.removeProvider(srp.getName());
}
}
// TODO(29631070): this is a general testing mechanism to test other operations that are
// going to be added.
public void testHashMapOperations() {
performHashMapOperationAndCheckResults(
PUT /* operation */,
mapOf("class1.algorithm1", "impl1") /* initialStatus */,
new Pair("class2.algorithm2", "impl2") /* operationParameters */,
mapOf("class1.algorithm1", "impl1",
"class2.algorithm2", "impl2"),
true /* mustChangeSecurityVersion */);
performHashMapOperationAndCheckResults(
PUT_ALL,
mapOf("class1.algorithm1", "impl1"),
mapOf("class2.algorithm2", "impl2", "class3.algorithm3", "impl3"),
mapOf("class1.algorithm1", "impl1",
"class2.algorithm2", "impl2",
"class3.algorithm3", "impl3"),
true /* mustChangeSecurityVersion */);
performHashMapOperationAndCheckResults(
REMOVE,
mapOf("class1.algorithm1", "impl1"),
"class1.algorithm1",
mapOf(),
true /* mustChangeSecurityVersion */);
performHashMapOperationAndCheckResults(
REMOVE,
mapOf("class1.algorithm1", "impl1"),
"class2.algorithm1",
mapOf("class1.algorithm1", "impl1"),
true /* mustChangeSecurityVersion */);
performHashMapOperationAndCheckResults(
COMPUTE,
mapOf("class1.algorithm1", "impl1"),
// It's really difficult to find an example of this that sounds realistic
// for a Provider...
new Pair("class1.algorithm1", CONCAT),
mapOf("class1.algorithm1", "class1.algorithm1impl1"),
true);
performHashMapOperationAndCheckResults(
PUT_IF_ABSENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class1.algorithm1", "impl2"),
// Don't put because key is absent.
mapOf("class1.algorithm1", "impl1"),
true);
performHashMapOperationAndCheckResults(
PUT_IF_ABSENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class2.algorithm2", "impl2"),
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
PUT_IF_ABSENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class2.algorithm2", "impl2"),
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
COMPUTE_IF_PRESENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class1.algorithm1", CONCAT),
mapOf("class1.algorithm1", "class1.algorithm1impl1"),
true);
performHashMapOperationAndCheckResults(
COMPUTE_IF_PRESENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class2.algorithm2", CONCAT),
// Don't compute because is not present.
mapOf("class1.algorithm1", "impl1"),
true);
performHashMapOperationAndCheckResults(
COMPUTE_IF_ABSENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class2.algorithm2", TO_UPPER_CASE),
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "CLASS2.ALGORITHM2"),
true);
performHashMapOperationAndCheckResults(
COMPUTE_IF_ABSENT,
mapOf("class1.algorithm1", "impl1"),
new Pair("class1.algorithm1", TO_UPPER_CASE),
// Don't compute because if not absent.
mapOf("class1.algorithm1", "impl1"),
true);
performHashMapOperationAndCheckResults(
REPLACE_USING_KEY,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair("class1.algorithm1", "impl3"),
mapOf("class1.algorithm1", "impl3", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
REPLACE_USING_KEY,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair("class1.algorithm3", "impl3"),
// Do not replace as the key is not present.
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
REPLACE_USING_KEY_AND_VALUE,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair(new Pair("class1.algorithm1", "impl1"), "impl3"),
mapOf("class1.algorithm1", "impl3", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
REPLACE_USING_KEY_AND_VALUE,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair(new Pair("class1.algorithm1", "impl4"), "impl3"),
// Do not replace as the key/value pair is not present.
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
REPLACE_ALL,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
// Applying simply CONCAT will affect internal mappings of the provider (version,
// info, name, etc)
CONCAT_IF_STARTING_WITH_CLASS,
mapOf("class1.algorithm1", "class1.algorithm1impl1",
"class2.algorithm2", "class2.algorithm2impl2"),
true);
performHashMapOperationAndCheckResults(
MERGE,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair(new Pair("class1.algorithm1", "impl3"), CONCAT),
// The key is present, so the function is used.
mapOf("class1.algorithm1", "impl1impl3",
"class2.algorithm2", "impl2"),
true);
performHashMapOperationAndCheckResults(
MERGE,
mapOf("class1.algorithm1", "impl1", "class2.algorithm2", "impl2"),
new Pair(new Pair("class3.algorithm3", "impl3"), CONCAT),
// The key is not present, so the value is used.
mapOf("class1.algorithm1", "impl1",
"class2.algorithm2", "impl2",
"class3.algorithm3", "impl3"),
true);
}
public void test_getOrDefault() {
Provider p = new MockProvider("MockProvider");
p.put("class1.algorithm1", "impl1");
assertEquals("impl1", p.getOrDefault("class1.algorithm1", "default"));
assertEquals("default", p.getOrDefault("thisIsNotInTheProvider", "default"));
}
private static class Pair<A, B> {
private final A first;
private final B second;
Pair(A first, B second) {
this.first = first;
this.second = second;
}
}
/* Holder class for the provider parameter and the parameter for the operation. */
private static class ProviderAndOperationParameter<T> {
private final Provider provider;
private final T operationParameters;
ProviderAndOperationParameter(Provider p, T o) {
provider = p;
operationParameters = o;
}
}
private static final Consumer<ProviderAndOperationParameter<Pair<String, String>>> PUT =
provAndParam ->
provAndParam.provider.put(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final Consumer<ProviderAndOperationParameter<Map<String, String>>> PUT_ALL =
provAndParam -> provAndParam.provider.putAll(provAndParam.operationParameters);
private static final Consumer<ProviderAndOperationParameter<String>> REMOVE =
provAndParam -> provAndParam.provider.remove(provAndParam.operationParameters);
private static final Consumer<ProviderAndOperationParameter<
Pair<String, BiFunction<Object, Object, Object>>>> COMPUTE =
provAndParam -> provAndParam.provider.compute(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final BiFunction<Object, Object, Object> CONCAT =
(a, b) -> Objects.toString(a) + Objects.toString(b);
private static final Consumer<ProviderAndOperationParameter<Pair<String, String>>>
PUT_IF_ABSENT = provAndParam ->
provAndParam.provider.putIfAbsent(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final Consumer<ProviderAndOperationParameter<
Pair<String, BiFunction<Object, Object, Object>>>> COMPUTE_IF_PRESENT =
provAndParam -> provAndParam.provider.computeIfPresent(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final Consumer<ProviderAndOperationParameter<
Pair<String, Function<Object, Object>>>> COMPUTE_IF_ABSENT =
provAndParam -> provAndParam.provider.computeIfAbsent(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final Function<Object, Object> TO_UPPER_CASE =
s -> Objects.toString(s).toUpperCase();
private static final Consumer<ProviderAndOperationParameter<Pair<String, String>>>
REPLACE_USING_KEY = provAndParam ->
provAndParam.provider.replace(
provAndParam.operationParameters.first,
provAndParam.operationParameters.second);
private static final Consumer<ProviderAndOperationParameter<Pair<Pair<String, String>, String>>>
REPLACE_USING_KEY_AND_VALUE = provAndParam ->
provAndParam.provider.replace(
provAndParam.operationParameters.first.first,
provAndParam.operationParameters.first.second,
provAndParam.operationParameters.second);
private static final Consumer<ProviderAndOperationParameter<
BiFunction<Object, Object, Object>>> REPLACE_ALL =
provAndParam -> provAndParam.provider.replaceAll(
provAndParam.operationParameters);
private static final BiFunction<Object, Object, Object> CONCAT_IF_STARTING_WITH_CLASS =
(a, b) -> (Objects.toString(a).startsWith("class"))
? Objects.toString(a) + Objects.toString(b)
: b;
private static final Consumer<ProviderAndOperationParameter<
Pair<Pair<String, String>, BiFunction<Object, Object, Object>>>>
MERGE = provAndParam -> provAndParam.provider.merge(
provAndParam.operationParameters.first.first,
provAndParam.operationParameters.first.second,
provAndParam.operationParameters.second);
private static Map<String, String> mapOf(String... elements) {
Map<String, String> ret = new HashMap<String, String>();
for (int i = 0; i < elements.length; i += 2) {
ret.put(elements[i], elements[i + 1]);
}
return ret;
}
private <A> void performHashMapOperationAndCheckResults(
Consumer<ProviderAndOperationParameter<A>> operation,
Map<String, String> initialState,
A operationParameters,
Map<String, String> expectedResult,
boolean mustChangeVersion) {
Provider p = new MockProvider("MockProvider");
// Need to set as registered so that the security version will change on update.
p.setRegistered();
int securityVersionBeforeOperation = Security.getVersion();
p.putAll(initialState);
// Perform the operation.
operation.accept(new ProviderAndOperationParameter<A>(p, operationParameters));
// Check that elements are correctly mapped to services.
HashMap<String, String> services = new HashMap<String, String>();
for (Provider.Service s : p.getServices()) {
services.put(s.getType() + "." + s.getAlgorithm(), s.getClassName());
}
assertEquals(expectedResult.entrySet(), services.entrySet());
// Check that elements are in the provider hash map.
// The hash map in the provider has info other than services, include those in the
// expected results.
HashMap<String, String> hashExpectedResult = new HashMap<String, String>();
hashExpectedResult.putAll(expectedResult);
hashExpectedResult.put("Provider.id info", p.getInfo());
hashExpectedResult.put("Provider.id className", p.getClass().getName());
hashExpectedResult.put("Provider.id version", String.valueOf(p.getVersion()));
hashExpectedResult.put("Provider.id name", p.getName());
assertEquals(hashExpectedResult.entrySet(), p.entrySet());
if (mustChangeVersion) {
assertTrue(securityVersionBeforeOperation != Security.getVersion());
}
}
@SuppressWarnings("serial")
private static class MockProvider extends Provider {
public MockProvider(String name) {
super(name, 1.0, "Mock provider used for testing");
setup();
}
public void setup() {
}
public void putServiceForTest(Provider.Service service) {
putService(service);
}
}
@SuppressWarnings("serial")
public static abstract class AbstractSecureRandom extends SecureRandomSpi {
protected void engineSetSeed(byte[] seed) {
throw new UnsupportedOperationException();
}
protected void engineNextBytes(byte[] bytes) {
throw new UnsupportedOperationException();
}
protected byte[] engineGenerateSeed(int numBytes) {
throw new UnsupportedOperationException();
}
}
@SuppressWarnings("serial")
public static class SecureRandom1 extends AbstractSecureRandom {}
@SuppressWarnings("serial")
public static class SecureRandom2 extends AbstractSecureRandom {}
@SuppressWarnings("serial")
public static class SecureRandom3 extends AbstractSecureRandom {}
}