Merge "Late binding: Provider.Service#supportsParameter"
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index a6de19c..c93d4dd 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -804,6 +804,40 @@
* provider it belongs and other properties.
*/
public static class Service {
+ /** Attribute name of supported key classes. */
+ private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses";
+
+ /** Attribute name of supported key formats. */
+ private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats";
+
+ private static final HashMap<String, Boolean> supportsParameterTypes
+ = new HashMap<String, Boolean>();
+ static {
+ // Does not support parameter
+ supportsParameterTypes.put("AlgorithmParameterGenerator", false);
+ supportsParameterTypes.put("AlgorithmParameters", false);
+ supportsParameterTypes.put("CertificateFactory", false);
+ supportsParameterTypes.put("CertPathBuilder", false);
+ supportsParameterTypes.put("CertPathValidator", false);
+ supportsParameterTypes.put("CertStore", false);
+ supportsParameterTypes.put("KeyFactory", false);
+ supportsParameterTypes.put("KeyGenerator", false);
+ supportsParameterTypes.put("KeyManagerFactory", false);
+ supportsParameterTypes.put("KeyPairGenerator", false);
+ supportsParameterTypes.put("KeyStore", false);
+ supportsParameterTypes.put("MessageDigest", false);
+ supportsParameterTypes.put("SecretKeyFactory", false);
+ supportsParameterTypes.put("SecureRandom", false);
+ supportsParameterTypes.put("SSLContext", false);
+ supportsParameterTypes.put("TrustManagerFactory", false);
+
+ // Supports parameter
+ supportsParameterTypes.put("Cipher", true);
+ supportsParameterTypes.put("KeyAgreement", true);
+ supportsParameterTypes.put("Mac", true);
+ supportsParameterTypes.put("Signature", true);
+ }
+
// The provider
private Provider provider;
@@ -828,6 +862,15 @@
// For newInstance() optimization
private String lastClassName;
+ /** Indicates whether supportedKeyClasses and supportedKeyFormats. */
+ private volatile boolean supportedKeysInitialized;
+
+ /** List of classes that this service supports. */
+ private Class<?>[] keyClasses;
+
+ /** List of key formats this service supports. */
+ private String[] keyFormats;
+
/**
* Constructs a new instance of {@code Service} with the given
* attributes.
@@ -1005,7 +1048,7 @@
throw new InvalidParameterException(type + ": service cannot use the parameter");
}
- Class[] parameterTypes = new Class[1];
+ Class<?>[] parameterTypes = new Class<?>[1];
Object[] initargs = { constructorParameter };
try {
if (type.equalsIgnoreCase("CertStore")) {
@@ -1031,7 +1074,89 @@
* constructor parameter, {@code false} otherwise.
*/
public boolean supportsParameter(Object parameter) {
- return true;
+ Boolean supportsParameter = supportsParameterTypes.get(type);
+ if (supportsParameter == null) {
+ return true;
+ }
+ if (!supportsParameter) {
+ throw new InvalidParameterException("Cannot use a parameter with " + type);
+ }
+
+ /*
+ * Only key type parameters are allowed, but allow null since there
+ * might not be any listed classes or formats for this instance.
+ */
+ if (parameter != null && !(parameter instanceof Key)) {
+ throw new InvalidParameterException("Parameter should be of type Key");
+ }
+
+ ensureSupportedKeysInitialized();
+
+ // No restriction specified by Provider registration.
+ if (keyClasses == null && keyFormats == null) {
+ return true;
+ }
+
+ Key keyParam = (Key) parameter;
+ if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) {
+ return true;
+ }
+ if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Initialize the list of supported key classes and formats.
+ */
+ private void ensureSupportedKeysInitialized() {
+ if (supportedKeysInitialized) {
+ return;
+ }
+
+ final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES);
+ if (supportedClassesString != null) {
+ String[] keyClassNames = supportedClassesString.split("\\|");
+ ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>(
+ keyClassNames.length);
+ final ClassLoader classLoader = getProvider().getClass().getClassLoader();
+ for (String keyClassName : keyClassNames) {
+ try {
+ Class<?> keyClass = classLoader.loadClass(keyClassName);
+ if (Key.class.isAssignableFrom(keyClass)) {
+ supportedClassList.add(keyClass);
+ }
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+ keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]);
+ }
+
+ final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS);
+ if (supportedFormatString != null) {
+ keyFormats = supportedFormatString.split("\\|");
+ }
+
+ supportedKeysInitialized = true;
+ }
+
+ /**
+ * Check if an item is in the array. The array of supported key classes
+ * and formats is usually just a length of 1, so a simple array is
+ * faster than a Set.
+ */
+ private static final <T> boolean isInArray(T[] itemList, T target) {
+ if (target == null) {
+ return false;
+ }
+ for (T item : itemList) {
+ if (target.equals(item)) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java
index 8c91bc1..248d3ff 100644
--- a/luni/src/test/java/libcore/java/security/ProviderTest.java
+++ b/luni/src/test/java/libcore/java/security/ProviderTest.java
@@ -16,12 +16,15 @@
package libcore.java.security;
+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.interfaces.RSAPrivateKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -34,6 +37,7 @@
import javax.crypto.Cipher;
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;
@@ -220,11 +224,291 @@
}
}
+ 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_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());
+ }
+ }
+
/**
* http://code.google.com/p/android/issues/detail?id=21449
*/
public void testSecureRandomImplementationOrder() {
- Provider srp = new SRProvider();
+ @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);
@@ -237,16 +521,17 @@
}
}
- public static class SRProvider extends Provider {
-
- SRProvider() {
- super("SRProvider", 1.42, "SecureRandom Provider");
- put("SecureRandom.SecureRandom1", SecureRandom1.class.getName());
- put("SecureRandom.SecureRandom2", SecureRandom2.class.getName());
- put("SecureRandom.SecureRandom3", SecureRandom3.class.getName());
+ @SuppressWarnings("serial")
+ private static abstract class MockProvider extends Provider {
+ public MockProvider(String name) {
+ super(name, 1.0, "Mock provider used for testing");
+ setup();
}
+
+ public abstract void setup();
}
+ @SuppressWarnings("serial")
public static abstract class AbstractSecureRandom extends SecureRandomSpi {
protected void engineSetSeed(byte[] seed) {
throw new UnsupportedOperationException();
@@ -259,8 +544,13 @@
}
}
+ @SuppressWarnings("serial")
public static class SecureRandom1 extends AbstractSecureRandom {}
+
+ @SuppressWarnings("serial")
public static class SecureRandom2 extends AbstractSecureRandom {}
+
+ @SuppressWarnings("serial")
public static class SecureRandom3 extends AbstractSecureRandom {}
}
diff --git a/luni/src/test/java/libcore/javax/crypto/MockKey.java b/luni/src/test/java/libcore/javax/crypto/MockKey.java
new file mode 100644
index 0000000..248e2de
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/MockKey.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 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.javax.crypto;
+
+import java.security.Key;
+
+/**
+ * A mock Key class used for testing.
+ */
+@SuppressWarnings("serial")
+public class MockKey implements Key {
+ @Override
+ public String getAlgorithm() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public String getFormat() {
+ return "MOCK";
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+}