diff options
10 files changed, 303 insertions, 29 deletions
diff --git a/luni/src/main/native/libcore_icu_NativeIDN.cpp b/luni/src/main/native/libcore_icu_NativeIDN.cpp index 43f3ce5..9786b9d 100644 --- a/luni/src/main/native/libcore_icu_NativeIDN.cpp +++ b/luni/src/main/native/libcore_icu_NativeIDN.cpp @@ -37,7 +37,8 @@ static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring javaSrc, jint if (src.get() == NULL) { return NULL; } - UChar dst[256]; + static const size_t kDstSize = 512; + UChar dst[kDstSize]; UErrorCode status = U_ZERO_ERROR; // We're stuck implementing IDNA-2003 for now since that's what we specify. @@ -47,10 +48,10 @@ static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring javaSrc, jint #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" size_t resultLength = toAscii - ? uidna_IDNToASCII(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status) - : uidna_IDNToUnicode(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status); + ? uidna_IDNToASCII(src.get(), src.size(), &dst[0], kDstSize, flags, NULL, &status) + : uidna_IDNToUnicode(src.get(), src.size(), &dst[0], kDstSize, flags, NULL, &status); #pragma GCC diagnostic pop - + if (U_FAILURE(status)) { jniThrowException(env, "java/lang/IllegalArgumentException", u_errorName(status)); return NULL; diff --git a/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java b/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java index 8708214..fce8507 100644 --- a/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java +++ b/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java @@ -19,6 +19,7 @@ package com.android.org.bouncycastle.crypto.digests; import junit.framework.TestCase; import com.android.org.bouncycastle.crypto.Digest; import com.android.org.bouncycastle.crypto.ExtendedDigest; +import tests.util.SummaryStatistics; /** * Implements unit tests for our JNI wrapper around OpenSSL. We use the @@ -36,6 +37,7 @@ public class DigestTest extends TestCase { * @param newDigest The new digest implementation, provided by OpenSSL */ public void doTestMessageDigest(Digest oldDigest, Digest newDigest) { + final int WARMUP = 10; final int ITERATIONS = 100; byte[] data = new byte[1024]; @@ -54,27 +56,31 @@ public class DigestTest extends TestCase { data[i] = (byte)i; } - long oldTime = 0; - long newTime = 0; + SummaryStatistics oldTime = new SummaryStatistics(); + SummaryStatistics newTime = new SummaryStatistics(); - for (int j = 0; j < ITERATIONS; j++) { - long t0 = System.currentTimeMillis(); + for (int j = 0; j < ITERATIONS + WARMUP; j++) { + long t0 = System.nanoTime(); for (int i = 0; i < 4; i++) { oldDigest.update(data, 0, data.length); } int oldLength = oldDigest.doFinal(oldHash, 0); - long t1 = System.currentTimeMillis(); + long t1 = System.nanoTime(); - oldTime = oldTime + (t1 - t0); + if (j >= WARMUP) { + oldTime.add(t1 - t0); + } - long t2 = System.currentTimeMillis(); + long t2 = System.nanoTime(); for (int i = 0; i < 4; i++) { newDigest.update(data, 0, data.length); } int newLength = newDigest.doFinal(newHash, 0); - long t3 = System.currentTimeMillis(); + long t3 = System.nanoTime(); - newTime = newTime + (t3 - t2); + if (j >= WARMUP) { + newTime.add(t3 - t2); + } assertEquals("Hash sizes must be equal", oldLength, newLength); @@ -83,10 +89,13 @@ public class DigestTest extends TestCase { } } - System.out.println("Time for " + ITERATIONS + " x old hash processing: " + oldTime + " ms"); - System.out.println("Time for " + ITERATIONS + " x new hash processing: " + newTime + " ms"); + System.out.println("Time for " + ITERATIONS + " x old hash processing: " + + oldTime.toString()); + System.out.println("Time for " + ITERATIONS + " x new hash processing: " + + newTime.toString()); - assertTrue("New hash should be faster", newTime < oldTime); + assertTrue("New hash should be faster:\nold=" + oldTime.toString() + "\nnew=" + + newTime.toString(), newTime.mean() < oldTime.mean()); } /** diff --git a/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java b/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java index ef303bd..d71b5b0 100644 --- a/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java +++ b/luni/src/test/java/libcore/java/lang/ref/FinalizeTest.java @@ -69,13 +69,18 @@ public final class FinalizeTest extends TestCase { static class X {} - // http://b/issue?id=2136462 - public void testBackFromTheDead() throws Exception { + // Helper function since we do not want a vreg to keep the allocated object live. + // For b/25851249 + private void exceptionInConstructor() { try { new ConstructionFails(); } catch (AssertionError expected) { } + } + // http://b/issue?id=2136462 + public void testBackFromTheDead() throws Exception { + exceptionInConstructor(); FinalizationTester.induceFinalization(); assertTrue("object whose constructor threw was not finalized", ConstructionFails.finalized); } diff --git a/luni/src/test/java/libcore/java/net/IDNTest.java b/luni/src/test/java/libcore/java/net/IDNTest.java index f01eca3..37f3505 100644 --- a/luni/src/test/java/libcore/java/net/IDNTest.java +++ b/luni/src/test/java/libcore/java/net/IDNTest.java @@ -37,4 +37,15 @@ public class IDNTest extends TestCase { String longInput = makePunyString(512); assertEquals(longInput, IDN.toUnicode(longInput)); } + + // http://b/30765246 + public void testLongDomainName() { + String label63 = "123456789-123456789-123456789-123456789-123456789-123456789-123"; + String host255 = label63 + "." + label63 + "." + label63 + "." + label63; + try { + IDN.toASCII(host255.substring(3) + ".com"); + fail(); + } catch (IllegalArgumentException expected) { + } + } } diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java index dd7d6e7..398c688 100644 --- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java +++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java @@ -3203,6 +3203,29 @@ public final class CipherTest extends TestCase { } } + // Test that when reading GCM parameters encoded using ASN1, a value for the tag size + // not present indicates a value of 12. + // https://b/29876633 + public void test_DefaultGCMTagSizeAlgorithmParameterSpec() throws Exception { + final String AES = "AES"; + final String AES_GCM = "AES/GCM/NoPadding"; + byte[] input = new byte[16]; + byte[] key = new byte[16]; + Cipher cipher = Cipher.getInstance(AES_GCM, "BC"); + AlgorithmParameters param = AlgorithmParameters.getInstance("GCM"); + param.init(new byte[] { + (byte) 48, // DER encoding : tag_Sequence + (byte) 14, // DER encoding : total length + (byte) 4, // DER encoding : tag_OctetString + (byte) 12, // DER encoding : counter length + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 }); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, AES), param); + byte[] ciphertext = cipher.update(input); + byte[] tag = cipher.doFinal(); + assertEquals(12, tag.length); + } + public void testAES_ECB_PKCS5Padding_ShortBuffer_Failure() throws Exception { for (String provider : AES_PROVIDERS) { testAES_ECB_PKCS5Padding_ShortBuffer_Failure(provider); @@ -3515,4 +3538,72 @@ public final class CipherTest extends TestCase { cipher.init(Cipher.ENCRYPT_MODE, keyGen.generateKeyPair().getPublic()); cipher.doFinal(new byte[] {1,2,3,4}); } + + /* + * Check that two AAD updates are equivalent to one. + * http://b/27371173 + */ + public void test_AESGCMNoPadding_UpdateAADTwice_Success() throws Exception { + SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, new byte[12]); + Cipher c1 = Cipher.getInstance("AES/GCM/NoPadding"); + Cipher c2 = Cipher.getInstance("AES/GCM/NoPadding"); + + c1.init(Cipher.ENCRYPT_MODE, key, spec); + c1.updateAAD(new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, + }); + c1.updateAAD(new byte[] { + 0x06, 0x07, 0x08, 0x09, 0x10, + }); + + c2.init(Cipher.ENCRYPT_MODE, key, spec); + c2.updateAAD(new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x10, + }); + + assertEquals(Arrays.toString(c1.doFinal()), Arrays.toString(c2.doFinal())); + } + + /* + * Check that GCM encryption with old and new instances update correctly. + * http://b/26694388 + */ + public void test_AESGCMNoPadding_Reuse_Success() throws Exception { + SecretKeySpec key = new SecretKeySpec(new byte[16], "AES"); + GCMParameterSpec spec = new GCMParameterSpec(128, new byte[12]); + Cipher c1 = Cipher.getInstance("AES/GCM/NoPadding"); + Cipher c2 = Cipher.getInstance("AES/GCM/NoPadding"); + + // Pollute the c1 cipher with AAD + c1.init(Cipher.ENCRYPT_MODE, key, spec); + c1.updateAAD(new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, + }); + + // Now init each again and make sure the outputs are the same + c1.init(Cipher.ENCRYPT_MODE, key, spec); + c2.init(Cipher.ENCRYPT_MODE, key, spec); + + byte[] aad = new byte[] { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, + }; + c1.updateAAD(aad); + c2.updateAAD(aad); + + assertEquals(Arrays.toString(c1.doFinal()), Arrays.toString(c2.doFinal())); + + // .doFinal should also reset the state, so check that as well. + byte[] aad2 = new byte[] { + 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, + }; + + Cipher c3 = Cipher.getInstance("AES/GCM/NoPadding"); + c3.init(Cipher.ENCRYPT_MODE, key, spec); + + c1.updateAAD(aad2); + c3.updateAAD(aad2); + assertEquals(Arrays.toString(c1.doFinal()), Arrays.toString(c3.doFinal())); + } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java index a434d94..bc2b626 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java @@ -99,7 +99,7 @@ public class SSLSessionTest extends TestCase { assertTrue("s.server.getLastAccessedTime()=" + s.server.getLastAccessedTime() + " " + "s.client.getLastAccessedTime()=" + s.client.getLastAccessedTime(), Math.abs(s.server.getLastAccessedTime() - - s.client.getLastAccessedTime()) < 1 * 1000); + - s.client.getLastAccessedTime()) <= 1 * 1000); assertTrue(s.server.getLastAccessedTime() >= s.server.getCreationTime()); assertTrue(s.client.getLastAccessedTime() >= diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java index bf2d0f8..11dfb3d 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -1559,7 +1559,23 @@ public class SSLSocketTest extends TestCase { @Override public void run(SSLSocketFactory sslSocketFactory) throws Exception { ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); - String[] cipherSuites = new String[clientHello.cipherSuites.size()]; + final String[] cipherSuites; + + // RFC 5746 allows you to send an empty "renegotiation_info" extension *or* + // a special signaling cipher suite. The TLS API has no way to check or + // indicate that a certain TLS extension should be used. + HelloExtension renegotiationInfoExtension = clientHello.findExtensionByType( + HelloExtension.TYPE_RENEGOTIATION_INFO); + if (renegotiationInfoExtension != null && + renegotiationInfoExtension.data.length == 1 && + renegotiationInfoExtension.data[0] == 0) { + cipherSuites = new String[clientHello.cipherSuites.size() + 1]; + cipherSuites[clientHello.cipherSuites.size()] = + StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION; + } else { + cipherSuites = new String[clientHello.cipherSuites.size()]; + } + for (int i = 0; i < clientHello.cipherSuites.size(); i++) { CipherSuite cipherSuite = clientHello.cipherSuites.get(i); cipherSuites[i] = cipherSuite.getAndroidName(); diff --git a/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java b/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java index 5741072..a648cdf 100644 --- a/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java +++ b/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java @@ -31,6 +31,7 @@ public class HelloExtension { public static final int TYPE_SERVER_NAME = 0; public static final int TYPE_PADDING = 21; public static final int TYPE_SESSION_TICKET = 35; + public static final int TYPE_RENEGOTIATION_INFO = 65281; private static final Map<Integer, String> TYPE_TO_NAME = new HashMap<Integer, String>(); static { @@ -60,7 +61,7 @@ public class HelloExtension { TYPE_TO_NAME.put(13172, "next_protocol_negotiation"); TYPE_TO_NAME.put(30031, "Channel ID (old)"); TYPE_TO_NAME.put(30032, "Channel ID (new)"); - TYPE_TO_NAME.put(65281, "renegotiation_info"); + TYPE_TO_NAME.put(TYPE_RENEGOTIATION_INFO, "renegotiation_info"); } public int type; diff --git a/support/src/test/java/tests/util/SummaryStatistics.java b/support/src/test/java/tests/util/SummaryStatistics.java new file mode 100644 index 0000000..4ce0a04 --- /dev/null +++ b/support/src/test/java/tests/util/SummaryStatistics.java @@ -0,0 +1,82 @@ +/* + * Copyright 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 tests.util; + +public class SummaryStatistics { + /** The number of values seen. */ + private int numValues; + + /** Sum of the values. */ + private double sum; + + /** Sum of the squares of the values added. */ + private double squaresSum; + + /** The previously added value. */ + private double lastValue; + + public SummaryStatistics() { + } + + private double square(double value) { + return value * value; + } + + /** Add a new value to the values seen. */ + public void add(double value) { + sum += value - lastValue; + squaresSum += square(value) - square(lastValue); + numValues++; + lastValue = value; + } + + /** Mean of the values seen. */ + public double mean() { + return sum / numValues; + } + + /** Variance of the values seen. */ + public double var() { + return (squaresSum / numValues) - square(mean()); + } + + /** Standard deviation of the values seen. */ + public double stddev() { + return Math.sqrt(var()); + } + + /** Coefficient of variation of the values seen. */ + public double coeffVar() { + return stddev() / mean(); + } + + public String toString() { + StringBuilder sb = new StringBuilder("SummaryStatistics{"); + sb.append("n="); + sb.append(numValues); + sb.append(",mean="); + sb.append(mean()); + sb.append(",var="); + sb.append(var()); + sb.append(",stddev="); + sb.append(stddev()); + sb.append(",coeffVar="); + sb.append(coeffVar()); + sb.append('}'); + return sb.toString(); + } +} diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java index a90db3b..a75cc72 100644 --- a/xml/src/main/java/org/kxml2/io/KXmlParser.java +++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java @@ -157,6 +157,8 @@ public class KXmlParser implements XmlPullParser, Closeable { private boolean isWhitespace; private String namespace; private String prefix; + private String foundPrefix = null; + private String foundName = null; private String name; private String text; @@ -268,15 +270,21 @@ public class KXmlParser implements XmlPullParser, Closeable { } } - int cut = name.indexOf(':'); + if (foundPrefix != null && foundName != null) { + prefix = foundPrefix; + name = foundName; + } + else { + int cut = name.indexOf(':'); if (cut == 0) { checkRelaxed("illegal tag name: " + name); } - if (cut != -1) { - prefix = name.substring(0, cut); - name = name.substring(cut + 1); + if (cut != -1) { + prefix = name.substring(0, cut); + name = name.substring(cut + 1); + } } this.namespace = getNamespace(prefix); @@ -961,13 +969,19 @@ public class KXmlParser implements XmlPullParser, Closeable { } private void readEndTag() throws IOException, XmlPullParserException { + int sp = (depth - 1) * 4; read('<'); read('/'); - name = readName(); // TODO: pass the expected name in as a hint? + if (depth == 0) { + name = readName(); + } + else { + // Pass the expected name in as a hint. + name = readExpectedName(elementStack[sp + 3]); + } skip(); read('>'); - int sp = (depth - 1) * 4; if (depth == 0) { checkRelaxed("read end tag " + name + " with no tags open"); @@ -1055,7 +1069,7 @@ public class KXmlParser implements XmlPullParser, Closeable { if (!xmldecl) { read('<'); } - name = readName(); + name = readName(true); attributeCount = 0; while (true) { @@ -1429,7 +1443,12 @@ public class KXmlParser implements XmlPullParser, Closeable { } if (result == null) { - return stringPool.get(buffer, start, position - start); + if (isWhitespace) { + return stringPool.get(buffer, start, position - start); + } + else { + return new String(buffer, start, position - start); + } } else { result.append(buffer, start, position - start); return result.toString(); @@ -1525,13 +1544,44 @@ public class KXmlParser implements XmlPullParser, Closeable { * Returns an element or attribute name. This is always non-empty for * non-relaxed parsers. */ + private String readExpectedName(String expected) throws IOException, XmlPullParserException { + int length = expected.length(); + int end = position + length; + if (end < limit) { + // Fast path for normal case. + boolean match = true; + for (int i = 0; i < length; i++) { + if (buffer[position+i] != expected.charAt(i)) { + match = false; + break; + } + } + if (match) { + position += length; + return expected; + } + } + return readName(); + } + private String readName() throws IOException, XmlPullParserException { + return readName(false); + } + + /** + * Returns an element or attribute name. This is always non-empty for + * non-relaxed parsers. findPrefix should only be true for element names + */ + private String readName(boolean findPrefix) throws IOException, XmlPullParserException { if (position >= limit && !fillBuffer(1)) { checkRelaxed("name expected"); return ""; } int start = position; + int nameStart = -1; + foundPrefix = null; + foundName = null; StringBuilder result = null; // read the first character @@ -1575,12 +1625,20 @@ public class KXmlParser implements XmlPullParser, Closeable { || c == ':' || c == '.' || c >= '\u00b7') { // TODO: check the XML spec + // Fast path for common case + if (c == ':' && findPrefix && foundPrefix == null) { + foundPrefix = stringPool.get(buffer, start, position - start); + nameStart = position+1; + } position++; continue; } // we encountered a non-name character. done! if (result == null) { + if (foundPrefix != null && position != nameStart) { + foundName = stringPool.get(buffer, nameStart, position - nameStart); + } return stringPool.get(buffer, start, position - start); } else { result.append(buffer, start, position - start); |