Merge "Switch TLS Channel ID API from ECPrivateKey to PrivateKey."
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index 8320ff6..e1038a6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -226,6 +226,9 @@
 
     public static native long EC_KEY_get_public_key(long keyRef);
 
+    public static native int ECDH_compute_key(
+            byte[] out, int outOffset, long publicKeyRef, long privateKeyRef);
+
     // --- Message digest functions --------------
 
     public static native long EVP_get_digestbyname(String name);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
index 6ecd6ab..df62c6d 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
@@ -117,6 +117,10 @@
 
     @Override
     public BigInteger getX() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private key value X cannot be extracted");
+        }
+
         ensureReadParams();
         return params.getX();
     }
@@ -218,6 +222,7 @@
         if (getOpenSSLKey().isEngineBased()) {
             throw new NotSerializableException("engine-based keys can not be serialized");
         }
+
         stream.defaultWriteObject();
 
         ensureReadParams();
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
new file mode 100644
index 0000000..096e300
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 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 org.apache.harmony.xnet.provider.jsse;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ */
+public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
+
+    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+    private OpenSSLKey mOpenSslPrivateKey;
+
+    /**
+     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+     * engine has been initialized.
+     */
+    private int mExpectedResultLength;
+
+    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+    private byte[] mResult;
+
+    @Override
+    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+        if (mOpenSslPrivateKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (!lastPhase) {
+            throw new IllegalStateException("ECDH only has one phase");
+        }
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPublicKey)) {
+            throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: "
+                + key.getClass());
+        }
+        ECPublicKey publicKey = (ECPublicKey) key;
+
+        OpenSSLKey openSslPublicKey;
+        if (publicKey instanceof OpenSSLECPublicKey) {
+            // OpenSSL-backed key
+            openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding
+            if (!"X.509".equals(publicKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") offers unsupported encoding format: " + publicKey.getFormat());
+            }
+            byte[] encoded = publicKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode X.509 encoded public key", e);
+            }
+        }
+
+        byte[] buffer = new byte[mExpectedResultLength];
+        int actualResultLength = NativeCrypto.ECDH_compute_key(
+                buffer,
+                0,
+                openSslPublicKey.getPkeyContext(),
+                mOpenSslPrivateKey.getPkeyContext());
+        byte[] result;
+        if (actualResultLength == -1) {
+            throw new RuntimeException("Engine returned " + actualResultLength);
+        } else if (actualResultLength == mExpectedResultLength) {
+            // The output is as long as expected -- use the whole buffer
+            result = buffer;
+        } else if (actualResultLength < mExpectedResultLength) {
+            // The output is shorter than expected -- use only what's produced by the engine
+            result = new byte[actualResultLength];
+            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+        } else {
+            // The output is longer than expected
+            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+                + mExpectedResultLength + ", actual: " + actualResultLength);
+        }
+        mResult = result;
+
+        return null; // No intermediate key
+    }
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws ShortBufferException {
+        checkCompleted();
+        int available = sharedSecret.length - offset;
+        if (mResult.length > available) {
+            throw new ShortBufferException(
+                    "Needed: " + mResult.length + ", available: " + available);
+        }
+
+        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+        return mResult.length;
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() {
+        checkCompleted();
+        return mResult;
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm) {
+        checkCompleted();
+        return new SecretKeySpec(engineGenerateSecret(), algorithm);
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof ECPrivateKey)) {
+            throw new InvalidKeyException("Not an EC private key: " + key.getClass());
+        }
+        ECPrivateKey privateKey = (ECPrivateKey) key;
+        mExpectedResultLength =
+                (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
+
+        OpenSSLKey openSslPrivateKey;
+        if (privateKey instanceof OpenSSLECPrivateKey) {
+            // OpenSSL-backed key
+            openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
+        } else {
+            // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding
+            if (!"PKCS#8".equals(privateKey.getFormat())) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") offers unsupported encoding format: " + privateKey.getFormat());
+            }
+            byte[] encoded = privateKey.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+                    + ") does not provide encoded form");
+            }
+            try {
+                openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
+            } catch (Exception e) {
+                throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e);
+            }
+        }
+        mOpenSslPrivateKey = openSslPrivateKey;
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        // ECDH doesn't need an AlgorithmParameterSpec
+        if (params != null) {
+          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+        }
+        engineInit(key, random);
+    }
+
+    private void checkCompleted() {
+        if (mResult == null) {
+            throw new IllegalStateException("Key agreement not completed");
+        }
+    }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
index cc6c0a3..10ccf61 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
@@ -95,6 +95,10 @@
 
     @Override
     public BigInteger getS() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private key value S cannot be extracted");
+        }
+
         return getPrivateKey();
     }
 
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
index 7ac4022..59e20d6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
@@ -86,6 +86,9 @@
 
         put("KeyFactory.EC", OpenSSLECKeyFactory.class.getName());
 
+        /* == KeyAgreement == */
+        put("KeyAgreement.ECDH", OpenSSLECDHKeyAgreement.class.getName());
+
         /* == Signatures == */
         put("Signature.MD5WithRSA", OpenSSLSignature.MD5RSA.class.getName());
         put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
index 862b201..c5254ad 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
@@ -322,6 +322,7 @@
         if (getOpenSSLKey().isEngineBased()) {
             throw new NotSerializableException("engine-based keys can not be serialized");
         }
+
         ensureReadParams();
         stream.defaultWriteObject();
     }
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
index 269c355..c0e0848 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
@@ -139,6 +139,10 @@
 
     @Override
     public final BigInteger getPrivateExponent() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private exponent cannot be extracted");
+        }
+
         ensureReadParams();
         return privateExponent;
     }
@@ -260,6 +264,7 @@
         if (getOpenSSLKey().isEngineBased()) {
             throw new NotSerializableException("engine-based keys can not be serialized");
         }
+
         ensureReadParams();
         stream.defaultWriteObject();
     }
diff --git a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index ba68bd0..56b771a 100644
--- a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -356,6 +356,87 @@
     jniThrowException(env, "java/security/NoSuchAlgorithmException", message);
 }
 
+static void throwForAsn1Error(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case ASN1_R_UNABLE_TO_DECODE_RSA_KEY:
+    case ASN1_R_UNABLE_TO_DECODE_RSA_PRIVATE_KEY:
+    case ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE:
+    case ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE:
+    case ASN1_R_WRONG_PUBLIC_KEY_TYPE:
+        throwInvalidKeyException(env, message);
+        break;
+    case ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForEvpError(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case EVP_R_BAD_DECRYPT:
+        throwBadPaddingException(env, message);
+        break;
+    case EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
+        throwIllegalBlockSizeException(env, message);
+        break;
+    case EVP_R_BAD_KEY_LENGTH:
+    case EVP_R_BN_DECODE_ERROR:
+    case EVP_R_BN_PUBKEY_ERROR:
+    case EVP_R_INVALID_KEY_LENGTH:
+    case EVP_R_MISSING_PARAMETERS:
+    case EVP_R_UNSUPPORTED_KEY_SIZE:
+    case EVP_R_UNSUPPORTED_KEYLENGTH:
+        throwInvalidKeyException(env, message);
+        break;
+    case EVP_R_WRONG_PUBLIC_KEY_TYPE:
+        throwSignatureException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForRsaError(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case RSA_R_BLOCK_TYPE_IS_NOT_01:
+    case RSA_R_BLOCK_TYPE_IS_NOT_02:
+        throwBadPaddingException(env, message);
+        break;
+    case RSA_R_ALGORITHM_MISMATCH:
+    case RSA_R_BAD_SIGNATURE:
+    case RSA_R_DATA_GREATER_THAN_MOD_LEN:
+    case RSA_R_INVALID_MESSAGE_LENGTH:
+    case RSA_R_WRONG_SIGNATURE_LENGTH:
+        throwSignatureException(env, message);
+        break;
+    case RSA_R_UNKNOWN_ALGORITHM_TYPE:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    case RSA_R_MODULUS_TOO_LARGE:
+    case RSA_R_NO_PUBLIC_EXPONENT:
+        throwInvalidKeyException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
+static void throwForX509Error(JNIEnv* env, int reason, const char *message) {
+    switch (reason) {
+    case X509_R_UNSUPPORTED_ALGORITHM:
+        throwNoSuchAlgorithmException(env, message);
+        break;
+    default:
+        jniThrowRuntimeException(env, message);
+        break;
+    }
+}
+
 /*
  * Checks this thread's OpenSSL error queue and throws a RuntimeException if
  * necessary.
@@ -378,26 +459,25 @@
         JNI_TRACE("OpenSSL error in %s error=%lx library=%x reason=%x (%s:%d): %s %s",
                   location, error, library, reason, file, line, message,
                   (flags & ERR_TXT_STRING) ? data : "(no data)");
-        if ((library == ERR_LIB_RSA)
-                && ((reason == RSA_R_BLOCK_TYPE_IS_NOT_01)
-                    || (reason == RSA_R_BLOCK_TYPE_IS_NOT_02))) {
-            throwBadPaddingException(env, message);
-        } else if (library == ERR_LIB_RSA && reason == RSA_R_DATA_GREATER_THAN_MOD_LEN) {
-            throwSignatureException(env, message);
-        } else if (library == ERR_LIB_RSA && reason == RSA_R_WRONG_SIGNATURE_LENGTH) {
-            throwSignatureException(env, message);
-        } else if (library == ERR_LIB_ASN1 && reason == ASN1_R_WRONG_PUBLIC_KEY_TYPE) {
+        switch (library) {
+        case ERR_LIB_RSA:
+            throwForRsaError(env, reason, message);
+            break;
+        case ERR_LIB_ASN1:
+            throwForAsn1Error(env, reason, message);
+            break;
+        case ERR_LIB_EVP:
+            throwForEvpError(env, reason, message);
+            break;
+        case ERR_LIB_X509:
+            throwForX509Error(env, reason, message);
+            break;
+        case ERR_LIB_DSA:
             throwInvalidKeyException(env, message);
-        } else if (library == ERR_LIB_EVP && reason == EVP_R_BAD_DECRYPT) {
-            throwBadPaddingException(env, message);
-        } else if (library == ERR_LIB_EVP && reason == EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH) {
-            throwIllegalBlockSizeException(env, message);
-        } else if (library == ERR_LIB_X509 && reason == X509_R_UNSUPPORTED_ALGORITHM) {
-            throwNoSuchAlgorithmException(env, message);
-        } else if (library == ERR_LIB_DSA) {
-            throwInvalidKeyException(env, message);
-        } else {
+            break;
+        default:
             jniThrowRuntimeException(env, message);
+            break;
         }
         result = true;
     }
@@ -2672,6 +2752,60 @@
     return reinterpret_cast<uintptr_t>(dup.release());
 }
 
+static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass,
+     jbyteArray outArray, jint outOffset, jlong pubkeyRef, jlong privkeyRef)
+{
+    EVP_PKEY* pubPkey = reinterpret_cast<EVP_PKEY*>(pubkeyRef);
+    EVP_PKEY* privPkey = reinterpret_cast<EVP_PKEY*>(privkeyRef);
+    JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubPkey, privPkey);
+
+    ScopedByteArrayRW out(env, outArray);
+    if (out.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer",
+                outArray, outOffset, pubPkey, privPkey);
+        return -1;
+    }
+
+    if ((outOffset < 0) || ((size_t) outOffset >= out.size())) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return -1;
+    }
+
+    Unique_EC_KEY pubkey(EVP_PKEY_get1_EC_KEY(pubPkey));
+    if (pubkey.get() == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get());
+    if (pubkeyPoint == NULL) {
+        JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey);
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+        return -1;
+    }
+
+    Unique_EC_KEY privkey(EVP_PKEY_get1_EC_KEY(privPkey));
+    if (privkey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY private");
+        return -1;
+    }
+
+    int outputLength = ECDH_compute_key(
+            &out[outOffset],
+            out.size() - outOffset,
+            pubkeyPoint,
+            privkey.get(),
+            NULL // No KDF
+            );
+    if (outputLength == -1) {
+        throwExceptionIfNecessary(env, "ECDH_compute_key");
+        return -1;
+    }
+
+    return outputLength;
+}
+
 static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
     JNI_TRACE("EVP_MD_CTX_create()");
 
@@ -7681,6 +7815,7 @@
     NATIVE_METHOD(NativeCrypto, EC_KEY_get0_group, "(J)J"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_private_key, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_public_key, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, ECDH_compute_key, "([BIJJ)I"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_create, "()J"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(J)V"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(J)V"),
diff --git a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
index c1f73eb..5f34f3e 100644
--- a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
+++ b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
@@ -66,10 +66,10 @@
     // Korean (sorts Korean, then English)
     // …, ᄀ, ᄂ, ᄃ, ᄅ, ᄆ, ᄇ, ᄉ, ᄋ, ᄌ, ᄎ, ᄏ, ᄐ, ᄑ, ᄒ, …
     AlphabeticIndex.ImmutableIndex ko = createIndex(Locale.KOREAN);
-    assertHasLabel(ko, "\u1100", "\u1100");
-    assertHasLabel(ko, "\u3131", "\u1100");
-    assertHasLabel(ko, "\u1101", "\u1100");
-    assertHasLabel(ko, "\u1161", "\u1112");
+    assertHasLabel(ko, "\u1100", "\u3131");
+    assertHasLabel(ko, "\u3131", "\u3131");
+    assertHasLabel(ko, "\u1101", "\u3131");
+    assertHasLabel(ko, "\u1161", "\u314e");
   }
 
   public void test_cs() throws Exception {
@@ -104,9 +104,12 @@
   }
 
   public void test_de() throws Exception {
-    // German: [A-Z] (no ß or umlauted characters in standard alphabet)
+    // German: [A-S,Sch,St,T-Z] (no ß or umlauted characters in standard alphabet)
     AlphabeticIndex.ImmutableIndex de = createIndex(Locale.GERMAN);
     assertHasLabel(de, "ßind", "S");
+    assertHasLabel(de, "Sacher", "S");
+    assertHasLabel(de, "Schiller", "Sch");
+    assertHasLabel(de, "Steiff", "St");
   }
 
   public void test_th() throws Exception {
@@ -142,18 +145,18 @@
     // Shen/Chen
     assertHasLabel(zh_CN, "\u6c88", "C"); // icu4c 50 does not specialize for names.
     // Shen/Chen (traditional)
-    assertHasLabel(zh_CN, "\u700b", "S"); // icu4c 50 gets this wrong.
+    assertHasLabel(zh_CN, "\u700b", "S");
   }
 
   public void test_zh_TW() throws Exception {
     // Traditional Chinese
-    // …, 一, 丁, 丈, 不, 且, 丞, 串, 並, 亭, 乘, 乾, 傀, 亂, 僎, 僵, 儐, 償, 叢, 儳, 嚴, 儷, 儻, 囌, 囑, 廳, …
+    // …, [1-33, 35, 36, 39, 48]劃, …
     // Shen/Chen
     AlphabeticIndex.ImmutableIndex zh_TW = createIndex(new Locale("zh", "TW"));
-    assertHasLabel(zh_TW, "\u6c88", "\u4e32");
-    assertHasLabel(zh_TW, "\u700b", "\u53e2");
+    assertHasLabel(zh_TW, "\u6c88", "7\u5283");
+    assertHasLabel(zh_TW, "\u700b", "18\u5283");
     // Jia/Gu
-    assertHasLabel(zh_TW, "\u8d3e", "\u4e58");
+    assertHasLabel(zh_TW, "\u8d3e", "10\u5283");
   }
 
   public void test_constructor_NPE() throws Exception {