Merge "Expose getCurrentSpi from crypto operations as hidden API."
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 9f6d827..fb0b207 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -116,16 +116,6 @@
LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
include $(BUILD_JAVA_LIBRARY)
-# Path to the ICU4C data files in the Android device file system:
-icu4c_data := /system/usr/icu
-# TODO: It's quite hideous that this double-slash between icu4j and main is required.
-# It's because we provide a variable substition of the make-rule generated jar command
-# to substitute a processed ICUProperties.config file in place of the original.
-#
-# We can avoid this by filtering out ICUConfig.properties from our list of resources.
-icu4j_config_root := $(LOCAL_PATH)/../external/icu/icu4j//main/classes/core/src
-include external/icu/icu4j/adjust_icudt_path.mk
-
ifeq ($(LIBCORE_SKIP_TESTS),)
# Make the core-tests library.
include $(CLEAR_VARS)
diff --git a/benchmarks/src/benchmarks/regression/ProviderBenchmark.java b/benchmarks/src/benchmarks/regression/ProviderBenchmark.java
new file mode 100644
index 0000000..649aa01
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/ProviderBenchmark.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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 benchmarks.regression;
+
+import java.security.Provider;
+import java.security.Security;
+import javax.crypto.Cipher;
+
+import com.google.caliper.Param;
+import com.google.caliper.SimpleBenchmark;
+import javax.net.ssl.SSLSocketFactory;
+
+public class ProviderBenchmark extends SimpleBenchmark {
+ public void timeStableProviders(int reps) throws Exception {
+ for (int i = 0; i < reps; ++i) {
+ Cipher c = Cipher.getInstance("RSA");
+ }
+ }
+
+ public void timeWithNewProvider(int reps) throws Exception {
+ for (int i = 0; i < reps; ++i) {
+ Security.addProvider(new MockProvider());
+ try {
+ Cipher c = Cipher.getInstance("RSA");
+ } finally {
+ Security.removeProvider("Mock");
+ }
+ }
+ }
+
+ private static class MockProvider extends Provider {
+ public MockProvider() {
+ super("Mock", 1.0, "Mock me!");
+ }
+ }
+}
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index a3e9197..6aac9eb 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1511,11 +1511,11 @@
]
},
{
- description: "ICU bug in formatting of dates that span month boundaries",
+ description: "OsTest.test_PacketSocketAddress needs CAP_NET_RAW",
+ bug: 19764047,
result: EXEC_FAILED,
- bug: 20708022,
names: [
- "libcore.icu.DateIntervalFormatTest#testEndOfDayOnLastDayOfMonth"
+ "libcore.io.OsTest#test_PacketSocketAddress"
]
}
]
diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java
index 1704b58..1a64ecc 100644
--- a/luni/src/main/java/java/security/Provider.java
+++ b/luni/src/main/java/java/security/Provider.java
@@ -368,8 +368,8 @@
}
/**
- * Get the service of the specified type
- *
+ * Get the service of the specified {@code type} (e.g. "SecureRandom",
+ * "Signature").
*/
synchronized Provider.Service getService(String type) {
updatePropertyServiceTable();
diff --git a/luni/src/main/java/java/util/ComparableTimSort.java b/luni/src/main/java/java/util/ComparableTimSort.java
index aba7573..f3da001 100644
--- a/luni/src/main/java/java/util/ComparableTimSort.java
+++ b/luni/src/main/java/java/util/ComparableTimSort.java
@@ -94,7 +94,7 @@
private final int[] runLen;
/**
- * Asserts have been placed in if-statements for performace. To enable them,
+ * Asserts have been placed in if-statements for performance. To enable them,
* set this field to true and enable them in VM with a command line flag.
* If you modify this class, please do test the asserts!
*/
@@ -363,11 +363,28 @@
*/
private void mergeCollapse() {
while (stackSize > 1) {
- int n = stackSize - 2;
+ final int n = stackSize - 2;
if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
- if (runLen[n - 1] < runLen[n + 1])
- n--;
- mergeAt(n);
+ // Merge the smaller of runLen[n-1] or runLen[n + 1] with runLen[n].
+ if (runLen[n - 1] < runLen[n + 1]) {
+ // runLen[n-1] is smallest. Merge runLen[n] into runLen[n - 1], leaving
+ // runLen[n+1] as the new runLen[n].
+ mergeAt(n - 1);
+ // n is now stackSize - 1, the top of the stack.
+ // Fix for http://b/19493779
+ // Because we modified runLen[n - 1] we might have affected invariant 1 as far
+ // back as runLen[n - 3]. Check we did not violate it.
+ if (n > 2 && runLen[n-3] <= runLen[n-2] + runLen[n-1]) {
+ // Avoid leaving invariant 1 still violated on the next loop by also merging
+ // runLen[n] into runLen[n - 1].
+ mergeAt(n - 1);
+ // Now the last three elements in the stack will again be the only elements
+ // that might break the invariant and we can loop again safely.
+ }
+ } else {
+ // runLen[n+1] is smallest. Merge runLen[n + 1] into runLen[n].
+ mergeAt(n);
+ }
} else if (runLen[n] <= runLen[n + 1]) {
mergeAt(n);
} else {
diff --git a/luni/src/main/java/java/util/List.java b/luni/src/main/java/java/util/List.java
index 8a9e1e3..54c8b65 100644
--- a/luni/src/main/java/java/util/List.java
+++ b/luni/src/main/java/java/util/List.java
@@ -84,7 +84,8 @@
* @throws IllegalArgumentException
* if an object cannot be added to this {@code List}.
* @throws IndexOutOfBoundsException
- * if {@code location < 0 || location > size()}
+ * if {@code location < 0 || location > size()}.
+ * @throws NullPointerException if {@code collection} is {@code null}.
*/
public boolean addAll(int location, Collection<? extends E> collection);
@@ -104,6 +105,7 @@
* {@code List}.
* @throws IllegalArgumentException
* if an object cannot be added to this {@code List}.
+ * @throws NullPointerException if {@code collection} is {@code null}.
*/
public boolean addAll(Collection<? extends E> collection);
@@ -135,6 +137,7 @@
* the collection of objects
* @return {@code true} if all objects in the specified collection are
* elements of this {@code List}, {@code false} otherwise.
+ * @throws NullPointerException if {@code collection} is {@code null}.
*/
public boolean containsAll(Collection<?> collection);
@@ -269,6 +272,7 @@
* @return {@code true} if this {@code List} is modified, {@code false} otherwise.
* @throws UnsupportedOperationException
* if removing from this {@code List} is not supported.
+ * @throws NullPointerException if {@code collection} is {@code null}.
*/
public boolean removeAll(Collection<?> collection);
@@ -281,6 +285,7 @@
* @return {@code true} if this {@code List} is modified, {@code false} otherwise.
* @throws UnsupportedOperationException
* if removing from this {@code List} is not supported.
+ * @throws NullPointerException if {@code collection} is {@code null}.
*/
public boolean retainAll(Collection<?> collection);
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index 1885f83..7226e1c 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -744,12 +744,13 @@
final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-');
final String[] subtags = normalizedValue.split("-");
+ final char normalizedKey = Character.toLowerCase(key);
// Lengths for subtags in the private use extension should be [1, 8] chars.
// For all other extensions, they should be [2, 8] chars.
//
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt
- final int minimumLength = (key == PRIVATE_USE_EXTENSION) ? 1 : 2;
+ final int minimumLength = (normalizedKey == PRIVATE_USE_EXTENSION) ? 1 : 2;
for (String subtag : subtags) {
if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) {
throw new IllformedLocaleException(
@@ -759,14 +760,14 @@
// We need to take special action in the case of unicode extensions,
// since we claim to understand their keywords and attributes.
- if (key == UNICODE_LOCALE_EXTENSION) {
+ if (normalizedKey == UNICODE_LOCALE_EXTENSION) {
// First clear existing attributes and keywords.
extensions.clear();
attributes.clear();
parseUnicodeExtension(subtags, keywords, attributes);
} else {
- extensions.put(key, normalizedValue);
+ extensions.put(normalizedKey, normalizedValue);
}
return this;
@@ -986,6 +987,27 @@
this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy);
this.extensions = Collections.unmodifiableMap(extensionsCopy);
} else {
+
+ // The locales ja_JP_JP and th_TH_TH are ill formed since their variant is too
+ // short, however they have been used to represent a locale with the japanese imperial
+ // calendar and thai numbering respectively. We add an extension in their constructor
+ // to modernize them.
+ if ("ja".equals(language) && "JP".equals(country) && "JP".equals(variant)) {
+ Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+ keywordsCopy.put("ca", "japanese");
+ unicodeKeywords = keywordsCopy;
+ } else if ("th".equals(language) && "TH".equals(country) && "TH".equals(variant)) {
+ Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+ keywordsCopy.put("nu", "thai");
+ unicodeKeywords = keywordsCopy;
+ }
+
+ if (!unicodeKeywords.isEmpty() || !unicodeAttributes.isEmpty()) {
+ Map<Character, String> extensionsCopy = new TreeMap<>(extensions);
+ addUnicodeExtensionToExtensionsMap(unicodeAttributes, unicodeKeywords, extensionsCopy);
+ extensions = extensionsCopy;
+ }
+
this.unicodeAttributes = unicodeAttributes;
this.unicodeKeywords = unicodeKeywords;
this.extensions = extensions;
@@ -2134,7 +2156,7 @@
return extensionKeyIndex;
}
- final String key = subtags[extensionKeyIndex];
+ final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
if (extensions.containsKey(key.charAt(0))) {
return extensionKeyIndex;
}
@@ -2146,7 +2168,7 @@
// Mark the start of the next extension. Also keep track of whether this
// is a private use extension, and throw an error if it doesn't come last.
extensionKeyIndex = i;
- if ("x".equals(subtag)) {
+ if ("x".equals(subtag.toLowerCase(Locale.ROOT))) {
privateUseExtensionIndex = i;
} else if (privateUseExtensionIndex != -1) {
// The private use extension must come last.
@@ -2169,7 +2191,7 @@
return extensionKeyIndex;
}
- final String key = subtags[extensionKeyIndex];
+ final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
if (extensions.containsKey(key.charAt(0))) {
return extensionKeyIndex;
}
diff --git a/luni/src/main/java/java/util/TimSort.java b/luni/src/main/java/java/util/TimSort.java
index ffe4e17..1a566c2 100644
--- a/luni/src/main/java/java/util/TimSort.java
+++ b/luni/src/main/java/java/util/TimSort.java
@@ -119,7 +119,7 @@
private final int[] runLen;
/**
- * Asserts have been placed in if-statements for performace. To enable them,
+ * Asserts have been placed in if-statements for performance. To enable them,
* set this field to true and enable them in VM with a command line flag.
* If you modify this class, please do test the asserts!
*/
@@ -397,11 +397,29 @@
*/
private void mergeCollapse() {
while (stackSize > 1) {
- int n = stackSize - 2;
+ final int n = stackSize - 2;
if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
- if (runLen[n - 1] < runLen[n + 1])
- n--;
- mergeAt(n);
+ // Merge the smaller of runLen[n-1] or runLen[n + 1] with runLen[n].
+ if (runLen[n - 1] < runLen[n + 1]) {
+ // runLen[n-1] is smallest. Merge runLen[n] into runLen[n - 1], leaving
+ // runLen[n+1] as the new runLen[n].
+ mergeAt(n - 1);
+ // n is now stackSize - 1, the top of the stack.
+
+ // Fix for http://b/19493779
+ // Because we modified runLen[n - 1] we might have affected invariant 1 as far
+ // back as runLen[n - 3]. Check we did not violate it.
+ if (n > 2 && runLen[n-3] <= runLen[n-2] + runLen[n-1]) {
+ // Avoid leaving invariant 1 still violated on the next loop by also merging
+ // runLen[n] into runLen[n - 1].
+ mergeAt(n - 1);
+ // Now the last three elements in the stack will again be the only elements
+ // that might break the invariant and we can loop again safely.
+ }
+ } else {
+ // runLen[n+1] is smallest. Merge runLen[n + 1] into runLen[n].
+ mergeAt(n);
+ }
} else if (runLen[n] <= runLen[n + 1]) {
mergeAt(n);
} else {
diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
index 509d0a0..7e7ad01 100644
--- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java
+++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
@@ -75,7 +75,7 @@
if (startMs != endMs && endsAtMidnight &&
((flags & DateUtilsBridge.FORMAT_SHOW_TIME) == 0
|| DateUtilsBridge.dayDistance(startCalendar, endCalendar) <= 1)) {
- endCalendar.roll(Calendar.DAY_OF_MONTH, false);
+ endCalendar.add(Calendar.DAY_OF_MONTH, -1);
}
String skeleton = DateUtilsBridge.toSkeleton(startCalendar, endCalendar, flags);
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
index 855a8c7..1c794e5 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java
@@ -152,7 +152,8 @@
}
/**
- * Returns a list of all possible matches for a given algorithm.
+ * Returns a list of all possible matches for a given algorithm. Returns
+ * {@code null} if no matches were found.
*/
public ArrayList<Provider.Service> getServices(String algorithm) {
int newCacheVersion = Services.getCacheVersion();
@@ -163,8 +164,7 @@
&& newCacheVersion == cacheEntry.cacheVersion) {
return cacheEntry.services;
}
- String name = this.serviceName + "." + algoUC;
- ArrayList<Provider.Service> services = Services.getServices(name);
+ ArrayList<Provider.Service> services = Services.getServices(serviceName, algoUC);
this.serviceCache = new ServiceCacheEntry(algoUC, newCacheVersion, services);
return services;
}
diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
index c81bf6b..5f3dfe0 100644
--- a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
+++ b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java
@@ -29,16 +29,6 @@
* implementations for all "serviceName.algName".
*/
public class Services {
-
- /**
- * The HashMap that contains information about preferred implementations for
- * all serviceName.algName in the registered providers.
- * Set the initial size to 600 so we don't grow to 1024 by default because
- * initialization adds a few entries more than the growth threshold.
- */
- private static final HashMap<String, ArrayList<Provider.Service>> services
- = new HashMap<String, ArrayList<Provider.Service>>(600);
-
/**
* Save default SecureRandom service as well.
* Avoids similar provider/services iteration in SecureRandom constructor.
@@ -71,7 +61,6 @@
Provider p = (Provider) providerClass.newInstance();
providers.add(p);
providersNames.put(p.getName(), p);
- initServiceInfo(p);
return true;
} catch (ClassNotFoundException ignored) {
} catch (IllegalAccessException ignored) {
@@ -104,7 +93,7 @@
}
/**
- * Returns a copy of the registered providers as an array.
+ * Returns the actual registered providers.
*/
public static synchronized ArrayList<Provider> getProviders() {
return providers;
@@ -144,54 +133,39 @@
}
/**
- * Adds information about provider services into HashMap.
+ * Looks up the requested service by type and algorithm. The service
+ * {@code type} and should be provided in the same format used when
+ * registering a service with a provider, for example, "KeyFactory.RSA".
+ * Callers can cache the returned service information but such caches should
+ * be validated against the result of Service.getCacheVersion() before use.
+ * Returns {@code null} if there are no services found.
*/
- public static synchronized void initServiceInfo(Provider p) {
- for (Provider.Service service : p.getServices()) {
- String type = service.getType();
- if (cachedSecureRandomService == null && type.equals("SecureRandom")) {
- cachedSecureRandomService = service;
- }
- String key = type + "." + service.getAlgorithm().toUpperCase(Locale.US);
- appendServiceLocked(key, service);
- for (String alias : Engine.door.getAliases(service)) {
- key = type + "." + alias.toUpperCase(Locale.US);
- appendServiceLocked(key, service);
+ public static synchronized ArrayList<Provider.Service> getServices(String type,
+ String algorithm) {
+ ArrayList<Provider.Service> services = null;
+ for (Provider p : providers) {
+ Provider.Service s = p.getService(type, algorithm);
+ if (s != null) {
+ if (services == null) {
+ services = new ArrayList<>(providers.size());
+ }
+ services.add(s);
}
}
+ return services;
}
/**
- * Add or append the service to the key.
+ * Finds the first service offered of {@code type} and returns it.
*/
- private static void appendServiceLocked(String key, Provider.Service service) {
- ArrayList<Provider.Service> serviceList = services.get(key);
- if (serviceList == null) {
- serviceList = new ArrayList<Provider.Service>(1);
- services.put(key, serviceList);
+ private static synchronized Provider.Service getFirstServiceOfType(String type) {
+ for (Provider p : providers) {
+ Provider.Service s = Engine.door.getService(p, type);
+ if (s != null) {
+ return s;
+ }
}
- serviceList.add(service);
- }
-
- /**
- * Returns true if services does not contain any provider information.
- */
- public static synchronized boolean isEmpty() {
- return services.isEmpty();
- }
-
- /**
- * Looks up the requested service by type and algorithm. The
- * service key should be provided in the same format used when
- * registering a service with a provider, for example,
- * "KeyFactory.RSA".
- *
- * Callers can cache the returned service information but such
- * caches should be validated against the result of
- * Service.getCacheVersion() before use.
- */
- public static synchronized ArrayList<Provider.Service> getServices(String key) {
- return services.get(key);
+ return null;
}
/**
@@ -219,13 +193,7 @@
public static synchronized int getCacheVersion() {
if (needRefresh) {
cacheVersion++;
- synchronized (services) {
- services.clear();
- }
- cachedSecureRandomService = null;
- for (Provider p : providers) {
- initServiceInfo(p);
- }
+ cachedSecureRandomService = getFirstServiceOfType("SecureRandom");
needRefresh = false;
}
return cacheVersion;
diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
index 6e0bce6..4c22371 100644
--- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
+++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java
@@ -429,7 +429,7 @@
final ULocale locale = new ULocale("en");
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
- assertEquals("April 30, 11:00 PM – May 1, 12:00 AM", formatDateRange(locale, timeZone,
+ assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
}
}
diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java
index 99679a7..5eab070 100644
--- a/luni/src/test/java/libcore/icu/ICUTest.java
+++ b/luni/src/test/java/libcore/icu/ICUTest.java
@@ -260,5 +260,8 @@
String icu4cTzVersion = ICU.getTZDataVersion();
String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion();
assertEquals(icu4cTzVersion, zoneInfoTzVersion);
+
+ String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion();
+ assertEquals(icu4jTzVersion, zoneInfoTzVersion);
}
}
diff --git a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
index 7e08b5f..1320a8a 100644
--- a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
+++ b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java
@@ -45,6 +45,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.crypto.interfaces.DHPrivateKey;
@@ -198,7 +199,6 @@
putKeySize("DiffieHellman", 512);
putKeySize("DiffieHellman", 512+64);
putKeySize("DiffieHellman", 1024);
- putKeySize("EC", 192);
putKeySize("EC", 224);
putKeySize("EC", 256);
putKeySize("EC", 384);
@@ -207,10 +207,10 @@
/** Elliptic Curve Crypto named curves that should be supported. */
private static final String[] EC_NAMED_CURVES = {
- // NIST P-192 aka SECG secp192r1 aka ANSI X9.62 prime192v1
- "secp192r1", "prime192v1",
// NIST P-256 aka SECG secp256r1 aka ANSI X9.62 prime256v1
"secp256r1", "prime256v1",
+ // NIST P-521 aka SECG secp521r1
+ "secp521r1",
};
private void test_KeyPairGenerator(KeyPairGenerator kpg) throws Exception {
@@ -267,7 +267,7 @@
}
private void test_Key(KeyPairGenerator kpg, Key k) throws Exception {
- String expectedAlgorithm = kpg.getAlgorithm().toUpperCase();
+ String expectedAlgorithm = kpg.getAlgorithm().toUpperCase(Locale.ROOT);
if (StandardNames.IS_RI && expectedAlgorithm.equals("DIFFIEHELLMAN")) {
expectedAlgorithm = "DH";
}
diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java
index 0be558e..f234122 100644
--- a/luni/src/test/java/libcore/java/security/ProviderTest.java
+++ b/luni/src/test/java/libcore/java/security/ProviderTest.java
@@ -547,6 +547,15 @@
}
}
+ public void testProvider_removeProvider_Success() throws Exception {
+ MockProvider provider = new MockProvider("MockProvider");
+ assertNotNull(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);
diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java
index e546f4f..f28446c 100644
--- a/luni/src/test/java/libcore/java/security/SignatureTest.java
+++ b/luni/src/test/java/libcore/java/security/SignatureTest.java
@@ -31,6 +31,11 @@
import java.security.SignatureException;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
@@ -1664,4 +1669,43 @@
es.shutdown();
assertTrue("Test should not timeout", es.awaitTermination(1, TimeUnit.MINUTES));
}
+
+ public void testArbitraryCurve() throws Exception {
+ // These are the parameters for the BitCoin curve (secp256k1). See
+ // https://en.bitcoin.it/wiki/Secp256k1.
+ final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
+ final BigInteger a = BigInteger.valueOf(0);
+ final BigInteger b = BigInteger.valueOf(7);
+ final BigInteger x = new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16);
+ final BigInteger y = new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16);
+ final BigInteger order = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
+ final int cofactor = 1;
+
+ final ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new ECFieldFp(p), a, b), new ECPoint(x, y), order, cofactor);
+ final KeyFactory factory = KeyFactory.getInstance("EC");
+
+ // $ openssl ecparam -name secp256k1 -genkey > key.pem
+ // $ openssl ec -text -noout < key.pem
+ final BigInteger Px = new BigInteger("2d45572747a625db5fd23b30f97044a682f2d42d31959295043c1fa0034c8ed3", 16);
+ final BigInteger Py = new BigInteger("4d330f52e4bba00145a331041c8bbcf300c4fbfdf3d63d8de7608155b2793808", 16);
+
+ final ECPublicKeySpec keySpec = new ECPublicKeySpec(new ECPoint(Px, Py), spec);
+ final PublicKey pub = factory.generatePublic(keySpec);
+
+ // $ echo -n "Satoshi Nakamoto" > signed
+ // $ openssl dgst -ecdsa-with-SHA1 -sign key.pem -out sig signed
+ final byte[] SIGNATURE = hexToBytes("304402205b41ece6dcc1c5bfcfdae74658d99c08c5e783f3926c11ecc1a8bea5d95cdf27022061a7d5fc687287e2e02dd7c6723e2e27fe0555f789590a37e96b1bb0355b4df0");
+
+ Signature ecdsaVerify = Signature.getInstance("SHA1withECDSA");
+ ecdsaVerify.initVerify(pub);
+ ecdsaVerify.update("Satoshi Nakamoto".getBytes("UTF-8"));
+ boolean result = ecdsaVerify.verify(SIGNATURE);
+ assertEquals(true, result);
+
+ ecdsaVerify = Signature.getInstance("SHA1withECDSA");
+ ecdsaVerify.initVerify(pub);
+ ecdsaVerify.update("Not Satoshi Nakamoto".getBytes("UTF-8"));
+ result = ecdsaVerify.verify(SIGNATURE);
+ assertEquals(false, result);
+ }
}
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index e1e84ab..9005f25 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -1203,4 +1203,38 @@
System.setUnchangeableSystemProperty("user.locale", userLocale);
}
}
+
+ // http://b/20252611
+ public void testLegacyLocalesWithExtensions() {
+ Locale ja_JP_JP = new Locale("ja", "JP", "JP");
+ assertEquals("ca-japanese", ja_JP_JP.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("japanese", ja_JP_JP.getUnicodeLocaleType("ca"));
+
+ Locale th_TH_TH = new Locale("th", "TH", "TH");
+ assertEquals("nu-thai", th_TH_TH.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("thai", th_TH_TH.getUnicodeLocaleType("nu"));
+ }
+
+ // http://b/20252611
+ public void testLowerCaseExtensionKeys() {
+ // We must lowercase extension keys in forLanguageTag..
+ Locale ar_EG = Locale.forLanguageTag("ar-EG-U-nu-arab");
+ assertEquals("nu-arab", ar_EG.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("ar-EG-u-nu-arab", ar_EG.toLanguageTag());
+
+ // ... and in builders.
+ Locale.Builder b = new Locale.Builder();
+ b.setLanguage("ar");
+ b.setRegion("EG");
+ b.setExtension('U', "nu-arab");
+ assertEquals("ar-EG-u-nu-arab", b.build().toLanguageTag());
+
+ // Corollary : extension keys are case insensitive.
+ b = new Locale.Builder();
+ b.setLanguage("ar");
+ b.setRegion("EG");
+ b.setExtension('U', "nu-arab");
+ b.setExtension('u', "nu-thai");
+ assertEquals("ar-EG-u-nu-thai", b.build().toLanguageTag());
+ }
}
diff --git a/luni/src/test/java/libcore/java/util/TimSortTest.java b/luni/src/test/java/libcore/java/util/TimSortTest.java
new file mode 100644
index 0000000..0e928ba
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/TimSortTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * This test is based on test data generated by
+ * https://github.com/abstools/java-timsort-bug/blob/master/TestTimSort.java
+ */
+public class TimSortTest extends TestCase {
+
+ private static final Comparator<Integer> NATURAL_ORDER_COMPARATOR = new Comparator<Integer>() {
+ public int compare(Integer first, Integer second) {
+ return first.compareTo(second);
+ }
+ };
+
+ private static final int BAD_DATA_SIZE = 65536;
+
+ private static int[] BAD_RUN_OFFSETS = {
+ 20204, 20221, 20237, 20255, 20289, 20363, 20521, 20837, 21469, 22733, 25260, 30315,
+ 40408, 40425, 40441, 40459, 40493, 40567, 40725, 41041, 41673, 42936, 45463, 50500,
+ 50517, 50533, 50551, 50585, 50659, 50817, 51133, 51764, 53027, 55536, 55553, 55569,
+ 55587, 55621, 55695, 55853, 56168, 56799, 58044, 58061, 58077, 58095, 58129, 58203,
+ 58360, 58675, 59288, 59305, 59321, 59339, 59373, 59446, 59603, 59900, 59917, 59933,
+ 59951, 59985, 60059, 60196, 60217, 60236, 60274, 60332, 60351, 60369, 60389, 60405,
+ };
+
+ public void testBug19493779WithComparable() throws Exception {
+ Integer[] array = createBugTriggerData();
+ Arrays.sort(array);
+ // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway.
+ assertSorted(array);
+ }
+
+ public void testBug19493779WithComparator() throws Exception {
+ Integer[] array = createBugTriggerData();
+ Arrays.sort(array, NATURAL_ORDER_COMPARATOR);
+ // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway.
+ assertSorted(array);
+ }
+
+ private static void assertSorted(Integer[] arrayToSort) {
+ for (int i = 1; i < arrayToSort.length; i++) {
+ if (arrayToSort[i - 1] > arrayToSort[i]) {
+ fail("Array not sorted at element " + i + ": " + Arrays.toString(arrayToSort));
+ }
+ }
+ }
+
+ private static Integer[] createBugTriggerData() {
+ final Integer zero = 0;
+ final Integer one = 1;
+
+ Integer[] bugTriggerData = new Integer[BAD_DATA_SIZE];
+ for (int i = 0; i < bugTriggerData.length; i++) {
+ bugTriggerData[i] = zero;
+ }
+
+ for (int i = 0; i < BAD_RUN_OFFSETS.length; i++) {
+ bugTriggerData[BAD_RUN_OFFSETS[i]] = one;
+ }
+ return bugTriggerData;
+ }
+}
\ No newline at end of file
diff --git a/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java b/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
index 693f0c2..0f8ed99 100644
--- a/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
+++ b/luni/src/test/java/libcore/java/util/prefs/OldAbstractPreferencesTest.java
@@ -37,6 +37,12 @@
static final String nodeName = "mock";
+ /** Timeout used for Object.wait when no action is expected. */
+ static final long NO_ACTION_EXPECTED_TIMEOUT = 200;
+
+ /** Timeout used for Object.wait when an action is expected. */
+ static final long ACTION_EXPECTED_TIMEOUT = 0; // Wait indefinitely
+
private PreferencesFactory defaultFactory;
AbstractPreferences pref;
@@ -885,7 +891,9 @@
}
public synchronized void assertChanged(boolean expected) throws InterruptedException {
- wait(100);
+ if (!flagChange) {
+ wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+ }
assertEquals(expected, flagChange);
flagChange = false;
}
@@ -933,12 +941,16 @@
}
public synchronized void assertAdded(boolean expected) throws InterruptedException {
- wait(100);
+ if (!flagAdded) {
+ wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+ }
assertEquals(expected, flagAdded);
}
public synchronized void assertRemoved(boolean expected) throws InterruptedException {
- wait(100);
+ if (!flagRemoved) {
+ wait(expected ? ACTION_EXPECTED_TIMEOUT : NO_ACTION_EXPECTED_TIMEOUT);
+ }
assertEquals(expected, flagRemoved);
}
}
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
index 65ddb3c..73357c6 100644
--- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java
+++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java
@@ -45,6 +45,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@@ -231,6 +232,10 @@
return algorithm.startsWith("PBE");
}
+ private static boolean isAEAD(String algorithm) {
+ return "GCM".equals(algorithm) || algorithm.contains("/GCM/");
+ }
+
private static boolean isStreamMode(String algorithm) {
return algorithm.contains("/CTR/") || algorithm.contains("/OFB")
|| algorithm.contains("/CFB");
@@ -295,10 +300,10 @@
setExpectedBlockSize("AES/ECB/PKCS5PADDING", 16);
setExpectedBlockSize("AES/ECB/PKCS7PADDING", 16);
setExpectedBlockSize("AES/ECB/NOPADDING", 16);
+ setExpectedBlockSize("AES/GCM/NOPADDING", 16);
setExpectedBlockSize("AES/OFB/PKCS5PADDING", 16);
setExpectedBlockSize("AES/OFB/PKCS7PADDING", 16);
setExpectedBlockSize("AES/OFB/NOPADDING", 16);
- setExpectedBlockSize("GCM", 16);
setExpectedBlockSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
setExpectedBlockSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
setExpectedBlockSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
@@ -453,9 +458,9 @@
setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16);
setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
+ setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8);
setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16);
setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16);
- setExpectedOutputSize("GCM", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8);
setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16);
setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16);
setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16);
@@ -486,9 +491,9 @@
setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
+ setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0);
- setExpectedOutputSize("GCM", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0);
@@ -750,8 +755,8 @@
new SecureRandom().nextBytes(salt);
return new PBEParameterSpec(salt, 1024);
}
- if (algorithm.equals("GCM")) {
- final byte[] iv = new byte[8];
+ if (algorithm.equals("AES/GCM/NOPADDING")) {
+ final byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv);
}
@@ -791,7 +796,7 @@
}
byte[] iv = encryptCipher.getIV();
if (iv != null) {
- if ("GCM".equals(algorithm)) {
+ if ("AES/GCM/NOPADDING".equals(algorithm)) {
return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv);
}
return new IvParameterSpec(iv);
@@ -988,9 +993,20 @@
Security.addProvider(mockProviderInvalid);
try {
- Cipher.getInstance("FOO");
- fail("Should not find any matching providers");
- } catch (NoSuchAlgorithmException expected) {
+ Cipher c = Cipher.getInstance("FOO");
+ if (StandardNames.IS_RI) {
+ c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[16], "FOO"));
+ } else {
+ fail("Should not find any matching providers; found: " + c);
+ }
+ } catch (NoSuchAlgorithmException maybe) {
+ if (StandardNames.IS_RI) {
+ throw maybe;
+ }
+ } catch (ClassCastException maybe) {
+ if (!StandardNames.IS_RI) {
+ throw maybe;
+ }
} finally {
Security.removeProvider(mockProviderInvalid.getName());
}
@@ -1143,9 +1159,9 @@
c.init(encryptMode, encryptKey, encryptSpec);
assertEquals(cipherID + " getBlockSize() encryptMode",
- getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
- assertEquals(cipherID + " getOutputSize(0) encryptMode",
- getExpectedOutputSize(algorithm, encryptMode, providerName), c.getOutputSize(0));
+ getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize());
+ assertTrue(cipherID + " getOutputSize(0) encryptMode",
+ getExpectedOutputSize(algorithm, encryptMode, providerName) <= c.getOutputSize(0));
if ((algorithm.endsWith("/PKCS5PADDING") || algorithm.endsWith("/PKCS7PADDING"))
&& isStreamMode(algorithm)) {
assertEquals(getExpectedOutputSize(algorithm, encryptMode, providerName),
@@ -1209,7 +1225,7 @@
// Test wrapping a key. Every cipher should be able to wrap. Except those that can't.
/* Bouncycastle is broken for wrapping because getIV() fails. */
if (isSupportedForWrapping(algorithm)
- && !algorithm.equals("GCM") && !providerName.equals("BC")) {
+ && !algorithm.equals("AES/GCM/NOPADDING") && !providerName.equals("BC")) {
// Generate a small SecretKey for AES.
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
@@ -1233,16 +1249,28 @@
if (!isOnlyWrappingAlgorithm(algorithm)) {
c.init(Cipher.ENCRYPT_MODE, encryptKey, encryptSpec);
+ if (isAEAD(algorithm)) {
+ c.updateAAD(new byte[24]);
+ }
byte[] cipherText = c.doFinal(getActualPlainText(algorithm));
+ if (isAEAD(algorithm)) {
+ c.updateAAD(new byte[24]);
+ }
byte[] cipherText2 = c.doFinal(getActualPlainText(algorithm));
assertEquals(cipherID,
Arrays.toString(cipherText),
Arrays.toString(cipherText2));
c.init(Cipher.DECRYPT_MODE, getDecryptKey(algorithm), decryptSpec);
+ if (isAEAD(algorithm)) {
+ c.updateAAD(new byte[24]);
+ }
byte[] decryptedPlainText = c.doFinal(cipherText);
assertEquals(cipherID,
Arrays.toString(getExpectedPlainText(algorithm, providerName)),
Arrays.toString(decryptedPlainText));
+ if (isAEAD(algorithm)) {
+ c.updateAAD(new byte[24]);
+ }
byte[] decryptedPlainText2 = c.doFinal(cipherText);
assertEquals(cipherID,
Arrays.toString(decryptedPlainText),
@@ -2502,6 +2530,80 @@
};
/*
+ * Taken from BoringSSL test vectors.
+ */
+ private static final byte[] AES_128_GCM_TestVector_1_Key = new byte[] {
+ (byte) 0xca, (byte) 0xbd, (byte) 0xcf, (byte) 0x54, (byte) 0x1a, (byte) 0xeb,
+ (byte) 0xf9, (byte) 0x17, (byte) 0xba, (byte) 0xc0, (byte) 0x19, (byte) 0xf1,
+ (byte) 0x39, (byte) 0x25, (byte) 0xd2, (byte) 0x67,
+ };
+
+ /*
+ * Taken from BoringSSL test vectors.
+ */
+ private static final byte[] AES_128_GCM_TestVector_1_IV = new byte[] {
+ (byte) 0x2c, (byte) 0x34, (byte) 0xc0, (byte) 0x0c, (byte) 0x42, (byte) 0xda,
+ (byte) 0xe3, (byte) 0x82, (byte) 0x27, (byte) 0x9d, (byte) 0x79, (byte) 0x74,
+ };
+
+ /*
+ * Taken from BoringSSL test vectors.
+ */
+ private static final byte[] AES_128_GCM_TestVector_1_AAD = new byte[] {
+ (byte) 0xdd, (byte) 0x10, (byte) 0xe3, (byte) 0x71, (byte) 0xb2, (byte) 0x2e,
+ (byte) 0x15, (byte) 0x67, (byte) 0x1c, (byte) 0x31, (byte) 0xaf, (byte) 0xee,
+ (byte) 0x55, (byte) 0x2b, (byte) 0xf1, (byte) 0xde, (byte) 0xa0, (byte) 0x7c,
+ (byte) 0xbb, (byte) 0xf6, (byte) 0x85, (byte) 0xe2, (byte) 0xca, (byte) 0xa0,
+ (byte) 0xe0, (byte) 0x36, (byte) 0x37, (byte) 0x16, (byte) 0xa2, (byte) 0x76,
+ (byte) 0xe1, (byte) 0x20, (byte) 0xc6, (byte) 0xc0, (byte) 0xeb, (byte) 0x4a,
+ (byte) 0xcb, (byte) 0x1a, (byte) 0x4d, (byte) 0x1b, (byte) 0xa7, (byte) 0x3f,
+ (byte) 0xde, (byte) 0x66, (byte) 0x15, (byte) 0xf7, (byte) 0x08, (byte) 0xaa,
+ (byte) 0xa4, (byte) 0x6b, (byte) 0xc7, (byte) 0x6c, (byte) 0x7f, (byte) 0xf3,
+ (byte) 0x45, (byte) 0xa4, (byte) 0xf7, (byte) 0x6b, (byte) 0xda, (byte) 0x11,
+ (byte) 0x7f, (byte) 0xe5, (byte) 0x6f, (byte) 0x0d, (byte) 0xc9, (byte) 0xb9,
+ (byte) 0x39, (byte) 0x04, (byte) 0x0d, (byte) 0xdd,
+ };
+
+ /*
+ * Taken from BoringSSL test vectors.
+ */
+ private static final byte[] AES_128_GCM_TestVector_1_Plaintext = new byte[] {
+ (byte) 0x88, (byte) 0xcc, (byte) 0x1e, (byte) 0x07, (byte) 0xdf, (byte) 0xde,
+ (byte) 0x8e, (byte) 0x08, (byte) 0x08, (byte) 0x2e, (byte) 0x67, (byte) 0x66,
+ (byte) 0xe0, (byte) 0xa8, (byte) 0x81, (byte) 0x03, (byte) 0x38, (byte) 0x47,
+ (byte) 0x42, (byte) 0xaf, (byte) 0x37, (byte) 0x8d, (byte) 0x7b, (byte) 0x6b,
+ (byte) 0x8a, (byte) 0x87, (byte) 0xfc, (byte) 0xe0, (byte) 0x36, (byte) 0xaf,
+ (byte) 0x74, (byte) 0x41, (byte) 0xc1, (byte) 0x39, (byte) 0x61, (byte) 0xc2,
+ (byte) 0x5a, (byte) 0xfe, (byte) 0xa7, (byte) 0xf6, (byte) 0xe5, (byte) 0x61,
+ (byte) 0x93, (byte) 0xf5, (byte) 0x4b, (byte) 0xee, (byte) 0x00, (byte) 0x11,
+ (byte) 0xcb, (byte) 0x78, (byte) 0x64, (byte) 0x2c, (byte) 0x3a, (byte) 0xb9,
+ (byte) 0xe6, (byte) 0xd5, (byte) 0xb2, (byte) 0xe3, (byte) 0x58, (byte) 0x33,
+ (byte) 0xec, (byte) 0x16, (byte) 0xcd, (byte) 0x35, (byte) 0x55, (byte) 0x15,
+ (byte) 0xaf, (byte) 0x1a, (byte) 0x19, (byte) 0x0f,
+ };
+
+ /*
+ * Taken from BoringSSL test vectors.
+ */
+ private static final byte[] AES_128_GCM_TestVector_1_Encrypted = new byte[] {
+ (byte) 0x04, (byte) 0x94, (byte) 0x53, (byte) 0xba, (byte) 0xf1, (byte) 0x57,
+ (byte) 0x87, (byte) 0x87, (byte) 0xd6, (byte) 0x8e, (byte) 0xd5, (byte) 0x47,
+ (byte) 0x87, (byte) 0x26, (byte) 0xc0, (byte) 0xb8, (byte) 0xa6, (byte) 0x36,
+ (byte) 0x33, (byte) 0x7a, (byte) 0x0b, (byte) 0x8a, (byte) 0x82, (byte) 0xb8,
+ (byte) 0x68, (byte) 0x36, (byte) 0xf9, (byte) 0x1c, (byte) 0xde, (byte) 0x25,
+ (byte) 0xe6, (byte) 0xe4, (byte) 0x4c, (byte) 0x34, (byte) 0x59, (byte) 0x40,
+ (byte) 0xe8, (byte) 0x19, (byte) 0xa0, (byte) 0xc5, (byte) 0x05, (byte) 0x75,
+ (byte) 0x1e, (byte) 0x60, (byte) 0x3c, (byte) 0xb8, (byte) 0xf8, (byte) 0xc4,
+ (byte) 0xfe, (byte) 0x98, (byte) 0x71, (byte) 0x91, (byte) 0x85, (byte) 0x56,
+ (byte) 0x27, (byte) 0x94, (byte) 0xa1, (byte) 0x85, (byte) 0xe5, (byte) 0xde,
+ (byte) 0xc4, (byte) 0x15, (byte) 0xc8, (byte) 0x1f, (byte) 0x2f, (byte) 0x16,
+ (byte) 0x2c, (byte) 0xdc, (byte) 0xd6, (byte) 0x50, (byte) 0xdc, (byte) 0xe7,
+ (byte) 0x19, (byte) 0x87, (byte) 0x28, (byte) 0xbf, (byte) 0xc1, (byte) 0xb5,
+ (byte) 0xf9, (byte) 0x49, (byte) 0xb9, (byte) 0xb5, (byte) 0x37, (byte) 0x41,
+ (byte) 0x99, (byte) 0xc6,
+ };
+
+ /*
* Test key generation:
* openssl rand -hex 16
* echo 'ceaa31952dfd3d0f5af4b2042ba06094' | sed 's/\(..\)/(byte) 0x\1, /g'
@@ -2570,17 +2672,20 @@
public final byte[] iv;
+ public final byte[] aad;
+
public final byte[] plaintext;
public final byte[] ciphertext;
public final byte[] plaintextPadded;
- public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] plaintext,
- byte[] plaintextPadded, byte[] ciphertext) {
- this.transformation = transformation;
+ public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] aad,
+ byte[] plaintext, byte[] plaintextPadded, byte[] ciphertext) {
+ this.transformation = transformation.toUpperCase(Locale.ROOT);
this.key = key;
this.iv = iv;
+ this.aad = aad;
this.plaintext = plaintext;
this.plaintextPadded = plaintextPadded;
this.ciphertext = ciphertext;
@@ -2591,23 +2696,34 @@
static {
CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS5Padding", AES_128_KEY,
null,
+ null,
AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext,
AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted));
// PKCS#5 is assumed to be equivalent to PKCS#7 -- same test vectors are thus used for both.
CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS7Padding", AES_128_KEY,
null,
+ null,
AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext,
AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded,
AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted));
+ CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/GCM/NOPADDING",
+ AES_128_GCM_TestVector_1_Key,
+ AES_128_GCM_TestVector_1_IV,
+ AES_128_GCM_TestVector_1_AAD,
+ AES_128_GCM_TestVector_1_Plaintext,
+ AES_128_GCM_TestVector_1_Plaintext,
+ AES_128_GCM_TestVector_1_Encrypted));
if (IS_UNLIMITED) {
CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS5Padding", AES_256_KEY,
AES_256_CBC_PKCS5Padding_TestVector_1_IV,
+ null,
AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext,
AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded,
AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext));
CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS7Padding", AES_256_KEY,
AES_256_CBC_PKCS5Padding_TestVector_1_IV,
+ null,
AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext,
AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded,
AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext));
@@ -2643,62 +2759,98 @@
private void checkCipher(CipherTestParam p, String provider) throws Exception {
SecretKey key = new SecretKeySpec(p.key, "AES");
Cipher c = Cipher.getInstance(p.transformation, provider);
+
AlgorithmParameterSpec spec = null;
if (p.iv != null) {
- spec = new IvParameterSpec(p.iv);
+ if (isAEAD(p.transformation)) {
+ spec = new GCMParameterSpec((p.ciphertext.length - p.plaintext.length) * 8, p.iv);
+ } else {
+ spec = new IvParameterSpec(p.iv);
+ }
}
+
c.init(Cipher.ENCRYPT_MODE, key, spec);
+ if (p.aad != null) {
+ c.updateAAD(p.aad);
+ }
final byte[] actualCiphertext = c.doFinal(p.plaintext);
- assertEquals(Arrays.toString(p.ciphertext), Arrays.toString(actualCiphertext));
+ assertEquals(p.transformation + " " + provider, Arrays.toString(p.ciphertext),
+ Arrays.toString(actualCiphertext));
+ c = Cipher.getInstance(p.transformation, provider);
+ c.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] emptyCipherText = c.doFinal();
assertNotNull(emptyCipherText);
c.init(Cipher.DECRYPT_MODE, key, spec);
- try {
- c.updateAAD(new byte[8]);
- fail("Cipher should not support AAD");
- } catch (UnsupportedOperationException expected) {
+ if (!isAEAD(p.transformation)) {
+ try {
+ c.updateAAD(new byte[8]);
+ fail("Cipher should not support AAD");
+ } catch (UnsupportedOperationException | IllegalStateException expected) {
+ }
}
- byte[] emptyPlainText = c.doFinal(emptyCipherText);
- assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText));
+ try {
+ byte[] emptyPlainText = c.doFinal(emptyCipherText);
+ assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText));
+ } catch (AEADBadTagException e) {
+ if (!"AndroidOpenSSL".equals(provider) || !isAEAD(p.transformation)) {
+ throw e;
+ }
+ }
// empty decrypt
{
- if (StandardNames.IS_RI) {
+ if (!isAEAD(p.transformation)
+ && (StandardNames.IS_RI || provider.equals("AndroidOpenSSL"))) {
assertEquals(Arrays.toString(new byte[0]),
Arrays.toString(c.doFinal()));
c.update(new byte[0]);
assertEquals(Arrays.toString(new byte[0]),
Arrays.toString(c.doFinal()));
- } else if (provider.equals("BC")) {
+ } else if (provider.equals("BC") || isAEAD(p.transformation)) {
try {
c.doFinal();
fail();
- } catch (IllegalBlockSizeException expected) {
+ } catch (IllegalBlockSizeException maybe) {
+ if (isAEAD(p.transformation)) {
+ throw maybe;
+ }
+ } catch (AEADBadTagException maybe) {
+ if (!isAEAD(p.transformation)) {
+ throw maybe;
+ }
}
try {
c.update(new byte[0]);
c.doFinal();
fail();
- } catch (IllegalBlockSizeException expected) {
+ } catch (IllegalBlockSizeException maybe) {
+ if (isAEAD(p.transformation)) {
+ throw maybe;
+ }
+ } catch (AEADBadTagException maybe) {
+ if (!isAEAD(p.transformation)) {
+ throw maybe;
+ }
}
- } else if (provider.equals("AndroidOpenSSL")) {
- assertNull(c.doFinal());
-
- c.update(new byte[0]);
- assertNull(c.doFinal());
} else {
throw new AssertionError("Define your behavior here for " + provider);
}
}
+ // Cipher might be in unspecified state from failures above.
+ c.init(Cipher.DECRYPT_MODE, key, spec);
+
// .doFinal(input)
{
+ if (p.aad != null) {
+ c.updateAAD(p.aad);
+ }
final byte[] actualPlaintext = c.doFinal(p.ciphertext);
assertEquals(Arrays.toString(p.plaintext), Arrays.toString(actualPlaintext));
}
@@ -2708,6 +2860,12 @@
final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 5];
System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
+ if (p.aad != null) {
+ final byte[] largerThanAad = new byte[p.aad.length + 100];
+ System.arraycopy(p.aad, 0, largerThanAad, 50, p.aad.length);
+ c.updateAAD(largerThanAad, 50, p.aad.length);
+ }
+
final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length)];
assertEquals(p.plaintext.length,
c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext));
@@ -2720,6 +2878,12 @@
final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 10];
System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);
+ if (p.aad != null) {
+ final byte[] largerThanAad = new byte[p.aad.length + 2];
+ System.arraycopy(p.aad, 0, largerThanAad, 2, p.aad.length);
+ c.updateAAD(largerThanAad, 2, p.aad.length);
+ }
+
final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length) + 2];
assertEquals(p.plaintext.length,
c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext, 1));
@@ -2727,13 +2891,18 @@
Arrays.toString(Arrays.copyOfRange(actualPlaintext, 1, p.plaintext.length + 1)));
}
- Cipher cNoPad = Cipher.getInstance(
- getCipherTransformationWithNoPadding(p.transformation), provider);
- cNoPad.init(Cipher.DECRYPT_MODE, key, spec);
+ if (!p.transformation.endsWith("NOPADDING")) {
+ Cipher cNoPad = Cipher.getInstance(
+ getCipherTransformationWithNoPadding(p.transformation), provider);
+ cNoPad.init(Cipher.DECRYPT_MODE, key, spec);
- final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
- assertEquals(provider + ":" + cNoPad.getAlgorithm(), Arrays.toString(p.plaintextPadded),
- Arrays.toString(actualPlaintextPadded));
+ if (p.aad != null) {
+ c.updateAAD(p.aad);
+ }
+ final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
+ assertEquals(provider + ":" + cNoPad.getAlgorithm(),
+ Arrays.toString(p.plaintextPadded), Arrays.toString(actualPlaintextPadded));
+ }
// Test wrapping a key. Every cipher should be able to wrap.
{
@@ -2743,6 +2912,7 @@
SecretKey sk = kg.generateKey();
// Wrap it
+ c = Cipher.getInstance(p.transformation, provider);
c.init(Cipher.WRAP_MODE, key, spec);
byte[] cipherText = c.wrap(sk);
@@ -2849,6 +3019,27 @@
fail("should not be able to call updateAAD with too large length");
} catch (IllegalArgumentException expected) {
}
+
+ try {
+ c.updateAAD(new byte[8]);
+ fail("should not be able to call updateAAD on non-AEAD cipher");
+ } catch (UnsupportedOperationException | IllegalStateException expected) {
+ }
+ }
+
+ public void testCipher_updateAAD_AfterInit_WithGcm_Success() throws Exception {
+ Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+ c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES"));
+ c.updateAAD(new byte[8]);
+ c.updateAAD(new byte[8]);
+ }
+
+ public void testCipher_updateAAD_AfterUpdate_WithGcm_Sucess() throws Exception {
+ Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
+ c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES"));
+ c.updateAAD(new byte[8]);
+ c.update(new byte[8]);
+ c.updateAAD(new byte[8]);
}
public void testCipher_ShortBlock_Failure() throws Exception {
@@ -2888,6 +3079,12 @@
}
private void checkCipher_ShortBlock_Failure(CipherTestParam p, String provider) throws Exception {
+ // Do not try to test ciphers with no padding already.
+ String noPaddingTransform = getCipherTransformationWithNoPadding(p.transformation);
+ if (p.transformation.equals(noPaddingTransform)) {
+ return;
+ }
+
SecretKey key = new SecretKeySpec(p.key, "AES");
Cipher c = Cipher.getInstance(
getCipherTransformationWithNoPadding(p.transformation), provider);
@@ -2895,12 +3092,14 @@
return;
}
- c.init(Cipher.ENCRYPT_MODE, key);
- try {
- c.doFinal(new byte[] { 0x01, 0x02, 0x03 });
- fail("Should throw IllegalBlockSizeException on wrong-sized block; provider="
- + provider);
- } catch (IllegalBlockSizeException expected) {
+ if (!p.transformation.endsWith("NOPADDING")) {
+ c.init(Cipher.ENCRYPT_MODE, key);
+ try {
+ c.doFinal(new byte[] { 0x01, 0x02, 0x03 });
+ fail("Should throw IllegalBlockSizeException on wrong-sized block; transform="
+ + p.transformation + " provider=" + provider);
+ } catch (IllegalBlockSizeException expected) {
+ }
}
}