Merge "Late binding: add support to Mac"
diff --git a/luni/src/main/java/javax/crypto/Mac.java b/luni/src/main/java/javax/crypto/Mac.java
index d74c2b9..5a73dc5 100644
--- a/luni/src/main/java/javax/crypto/Mac.java
+++ b/luni/src/main/java/javax/crypto/Mac.java
@@ -24,8 +24,10 @@
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
+import java.security.ProviderException;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
import org.apache.harmony.security.fortress.Engine;
@@ -35,18 +37,29 @@
*/
public class Mac implements Cloneable {
+ // The service name.
+ private static final String SERVICE = "Mac";
+
//Used to access common engine functionality
- private static final Engine ENGINE = new Engine("Mac");
+ private static final Engine ENGINE = new Engine(SERVICE);
// Store used provider
- private final Provider provider;
+ private Provider provider;
+
+ // Provider that was requested during creation.
+ private final Provider specifiedProvider;
// Store used spi implementation
- private final MacSpi spiImpl;
+ private MacSpi spiImpl;
// Store used algorithm name
private final String algorithm;
+ /**
+ * Lock held while the SPI is initializing.
+ */
+ private final Object initLock = new Object();
+
// Store Mac state (initialized or not initialized)
private boolean isInitMac;
@@ -61,7 +74,7 @@
* the name of the MAC algorithm.
*/
protected Mac(MacSpi macSpi, Provider provider, String algorithm) {
- this.provider = provider;
+ this.specifiedProvider = provider;
this.algorithm = algorithm;
this.spiImpl = macSpi;
this.isInitMac = false;
@@ -82,6 +95,7 @@
* @return the provider of this {@code Mac} instance.
*/
public final Provider getProvider() {
+ getSpi();
return provider;
}
@@ -100,11 +114,7 @@
*/
public static final Mac getInstance(String algorithm)
throws NoSuchAlgorithmException {
- if (algorithm == null) {
- throw new NullPointerException("algorithm == null");
- }
- Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
- return new Mac((MacSpi) sap.spi, sap.provider, algorithm);
+ return getMac(algorithm, null);
}
/**
@@ -136,7 +146,7 @@
if (impProvider == null) {
throw new NoSuchProviderException(provider);
}
- return getInstance(algorithm, impProvider);
+ return getMac(algorithm, impProvider);
}
/**
@@ -163,11 +173,102 @@
if (provider == null) {
throw new IllegalArgumentException("provider == null");
}
+ return getMac(algorithm, provider);
+ }
+
+ private static Mac getMac(String algorithm, Provider provider)
+ throws NoSuchAlgorithmException {
if (algorithm == null) {
throw new NullPointerException("algorithm == null");
}
- Object spi = ENGINE.getInstance(algorithm, provider, null);
- return new Mac((MacSpi) spi, provider, algorithm);
+
+ if (tryAlgorithm(null, provider, algorithm) == null) {
+ if (provider == null) {
+ throw new NoSuchAlgorithmException("No provider found for " + algorithm);
+ } else {
+ throw new NoSuchAlgorithmException("Provider " + provider.getName()
+ + " does not provide " + algorithm);
+ }
+ }
+ return new Mac(null, provider, algorithm);
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) {
+ if (provider != null) {
+ Provider.Service service = provider.getService(SERVICE, algorithm);
+ if (service == null) {
+ return null;
+ }
+ return tryAlgorithmWithProvider(key, service);
+ }
+ ArrayList<Provider.Service> services = ENGINE.getServices(algorithm);
+ if (services == null) {
+ return null;
+ }
+ for (Provider.Service service : services) {
+ Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service);
+ if (sap != null) {
+ return sap;
+ }
+ }
+ return null;
+ }
+
+ private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) {
+ try {
+ if (key != null && !service.supportsParameter(key)) {
+ return null;
+ }
+
+ Engine.SpiAndProvider sap = ENGINE.getInstance(service, null);
+ if (sap.spi == null || sap.provider == null) {
+ return null;
+ }
+ if (!(sap.spi instanceof MacSpi)) {
+ return null;
+ }
+ return sap;
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ return null;
+ }
+
+ /**
+ * Makes sure a MacSpi that matches this type is selected.
+ */
+ private MacSpi getSpi(Key key) {
+ synchronized (initLock) {
+ if (spiImpl != null && provider != null && key == null) {
+ return spiImpl;
+ }
+
+ if (algorithm == null) {
+ return null;
+ }
+
+ final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm);
+ if (sap == null) {
+ throw new ProviderException("No provider for " + getAlgorithm());
+ }
+
+ /*
+ * Set our Spi if we've never been initialized or if we have the Spi
+ * specified and have a null provider.
+ */
+ if (spiImpl == null || provider != null) {
+ spiImpl = (MacSpi) sap.spi;
+ }
+ provider = sap.provider;
+
+ return spiImpl;
+ }
+ }
+
+ /**
+ * Convenience call when the Key is not available.
+ */
+ private MacSpi getSpi() {
+ return getSpi(null);
}
/**
@@ -176,7 +277,7 @@
* @return the length of this MAC (in bytes).
*/
public final int getMacLength() {
- return spiImpl.engineGetMacLength();
+ return getSpi().engineGetMacLength();
}
/**
@@ -199,7 +300,7 @@
if (key == null) {
throw new InvalidKeyException("key == null");
}
- spiImpl.engineInit(key, params);
+ getSpi(key).engineInit(key, params);
isInitMac = true;
}
@@ -220,7 +321,7 @@
throw new InvalidKeyException("key == null");
}
try {
- spiImpl.engineInit(key, null);
+ getSpi(key).engineInit(key, null);
isInitMac = true;
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
@@ -239,7 +340,7 @@
if (!isInitMac) {
throw new IllegalStateException();
}
- spiImpl.engineUpdate(input);
+ getSpi().engineUpdate(input);
}
/**
@@ -270,7 +371,7 @@
+ " input.length=" + input.length
+ " offset=" + offset + ", len=" + len);
}
- spiImpl.engineUpdate(input, offset, len);
+ getSpi().engineUpdate(input, offset, len);
}
/**
@@ -286,7 +387,7 @@
throw new IllegalStateException();
}
if (input != null) {
- spiImpl.engineUpdate(input, 0, input.length);
+ getSpi().engineUpdate(input, 0, input.length);
}
}
@@ -305,7 +406,7 @@
throw new IllegalStateException();
}
if (input != null) {
- spiImpl.engineUpdate(input);
+ getSpi().engineUpdate(input);
} else {
throw new IllegalArgumentException("input == null");
}
@@ -327,7 +428,7 @@
if (!isInitMac) {
throw new IllegalStateException();
}
- return spiImpl.engineDoFinal();
+ return getSpi().engineDoFinal();
}
/**
@@ -362,11 +463,12 @@
if ((outOffset < 0) || (outOffset >= output.length)) {
throw new ShortBufferException("Incorrect outOffset: " + outOffset);
}
- int t = spiImpl.engineGetMacLength();
+ MacSpi spi = getSpi();
+ int t = spi.engineGetMacLength();
if (t > (output.length - outOffset)) {
throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes.");
}
- byte[] result = spiImpl.engineDoFinal();
+ byte[] result = spi.engineDoFinal();
System.arraycopy(result, 0, output, outOffset, result.length);
}
@@ -390,10 +492,11 @@
if (!isInitMac) {
throw new IllegalStateException();
}
+ MacSpi spi = getSpi();
if (input != null) {
- spiImpl.engineUpdate(input, 0, input.length);
+ spi.engineUpdate(input, 0, input.length);
}
- return spiImpl.engineDoFinal();
+ return spi.engineDoFinal();
}
/**
@@ -404,7 +507,7 @@
* initialized with different parameters.
*/
public final void reset() {
- spiImpl.engineReset();
+ getSpi().engineReset();
}
/**
@@ -416,7 +519,11 @@
*/
@Override
public final Object clone() throws CloneNotSupportedException {
- MacSpi newSpiImpl = (MacSpi)spiImpl.clone();
+ MacSpi newSpiImpl = null;
+ final MacSpi spi = getSpi();
+ if (spi != null) {
+ newSpiImpl = (MacSpi) spi.clone();
+ }
Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm);
mac.isInitMac = this.isInitMac;
return mac;
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
index aaf2a15..ddd0695 100644
--- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java
@@ -27,27 +27,26 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.spec.PSSParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
-
import javax.crypto.Mac;
import javax.crypto.MacSpi;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DHGenParameterSpec;
-
import javax.crypto.spec.SecretKeySpec;
-
import org.apache.harmony.crypto.tests.support.MyMacSpi;
import org.apache.harmony.security.tests.support.SpiEngUtils;
-
import junit.framework.TestCase;
-
import junit.framework.Test;
import junit.framework.TestSuite;
+import libcore.java.security.StandardNames;
+import libcore.javax.crypto.MockKey;
+import libcore.javax.crypto.MockKey2;
/**
* Tests for Mac class constructors and methods
@@ -766,15 +765,14 @@
}
MacSpi spi = new MyMacSpi();
Mac mac = new myMac(spi, defaultProvider, defaultAlgorithm);
- assertEquals("Incorrect algorithm", mac.getAlgorithm(),
- defaultAlgorithm);
- assertEquals("Incorrect provider", mac.getProvider(), defaultProvider);
+ assertEquals("Incorrect algorithm", defaultAlgorithm, mac.getAlgorithm());
+ assertEquals("Incorrect provider", defaultProvider, mac.getProvider());
try {
mac.init(null, null);
fail("Exception should be thrown because init(..) uses incorrect parameters");
} catch (Exception e) {
}
- assertEquals("Invalid mac length", mac.getMacLength(), 0);
+ assertEquals("Invalid mac length", 0, mac.getMacLength());
mac = new myMac(null, null, null);
assertNull("Algorithm must be null", mac.getAlgorithm());
@@ -877,6 +875,127 @@
}
}
+ 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();
+ }
+
+ public void testMac_getInstance_SuppliedProviderNotRegistered_Success() throws Exception {
+ Provider mockProvider = new MockProvider("MockProvider") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName());
+ }
+ };
+
+ {
+ Mac s = Mac.getInstance("FOO", mockProvider);
+ s.init(new MockKey());
+ assertEquals(mockProvider, s.getProvider());
+ }
+ }
+
+ public void testMac_getInstance_OnlyUsesSpecifiedProvider_SameNameAndClass_Success()
+ throws Exception {
+ Provider mockProvider = new MockProvider("MockProvider") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName());
+ }
+ };
+
+ Security.addProvider(mockProvider);
+ try {
+ {
+ Provider mockProvider2 = new MockProvider("MockProvider") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName());
+ }
+ };
+ Mac s = Mac.getInstance("FOO", mockProvider2);
+ assertEquals(mockProvider2, s.getProvider());
+ }
+ } finally {
+ Security.removeProvider(mockProvider.getName());
+ }
+ }
+
+ public void testMac_getInstance_DelayedInitialization_KeyType() throws Exception {
+ Provider mockProviderSpecific = new MockProvider("MockProviderSpecific") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.SpecificKeyTypes.class.getName());
+ put("Mac.FOO SupportedKeyClasses", MockKey.class.getName());
+ }
+ };
+ Provider mockProviderSpecific2 = new MockProvider("MockProviderSpecific2") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.SpecificKeyTypes2.class.getName());
+ put("Mac.FOO SupportedKeyClasses", MockKey2.class.getName());
+ }
+ };
+ Provider mockProviderAll = new MockProvider("MockProviderAll") {
+ public void setup() {
+ put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName());
+ }
+ };
+
+ Security.addProvider(mockProviderSpecific);
+ Security.addProvider(mockProviderSpecific2);
+ Security.addProvider(mockProviderAll);
+
+ try {
+ {
+ Mac s = Mac.getInstance("FOO");
+ s.init(new MockKey());
+ assertEquals(mockProviderSpecific, s.getProvider());
+
+ try {
+ s.init(new MockKey2());
+ assertEquals(mockProviderSpecific2, s.getProvider());
+ if (StandardNames.IS_RI) {
+ fail("RI was broken before; fix tests now that it works!");
+ }
+ } catch (InvalidKeyException e) {
+ if (!StandardNames.IS_RI) {
+ fail("Non-RI should select the right provider");
+ }
+ }
+ }
+
+ {
+ Mac s = Mac.getInstance("FOO");
+ s.init(new PrivateKey() {
+ @Override
+ public String getAlgorithm() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public String getFormat() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+ });
+ assertEquals(mockProviderAll, s.getProvider());
+ }
+
+ {
+ Mac s = Mac.getInstance("FOO");
+ assertEquals(mockProviderSpecific, s.getProvider());
+ }
+ } finally {
+ Security.removeProvider(mockProviderSpecific.getName());
+ Security.removeProvider(mockProviderSpecific2.getName());
+ Security.removeProvider(mockProviderAll.getName());
+ }
+ }
+
public static Test suite() {
return new TestSuite(MacTest.class);
}
diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MockMacSpi.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MockMacSpi.java
new file mode 100644
index 0000000..6a28fb3e
--- /dev/null
+++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MockMacSpi.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014 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 org.apache.harmony.crypto.tests.javax.crypto;
+
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.MacSpi;
+import libcore.javax.crypto.MockKey;
+import libcore.javax.crypto.MockKey2;
+
+public class MockMacSpi extends MacSpi {
+ public static class SpecificKeyTypes extends MockMacSpi {
+ @Override
+ public void checkKeyType(Key key) throws InvalidKeyException {
+ if (!(key instanceof MockKey)) {
+ throw new InvalidKeyException("Must be MockKey!");
+ }
+ }
+ }
+
+ public static class SpecificKeyTypes2 extends MockMacSpi {
+ @Override
+ public void checkKeyType(Key key) throws InvalidKeyException {
+ if (!(key instanceof MockKey2)) {
+ throw new InvalidKeyException("Must be MockKey2!");
+ }
+ }
+ }
+
+ public static class AllKeyTypes extends MockMacSpi {
+ }
+
+ public void checkKeyType(Key key) throws InvalidKeyException {
+ }
+
+ @Override
+ protected int engineGetMacLength() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException,
+ InvalidParameterException {
+ checkKeyType(key);
+ }
+
+ @Override
+ protected void engineUpdate(byte input) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void engineUpdate(byte[] input, int offset, int len) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected byte[] engineDoFinal() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void engineReset() {
+ throw new UnsupportedOperationException();
+ }
+}