diff options
36 files changed, 1892 insertions, 474 deletions
diff --git a/JavaLibrary.mk b/JavaLibrary.mk index 59ac2e1..26a586a 100644 --- a/JavaLibrary.mk +++ b/JavaLibrary.mk @@ -55,9 +55,11 @@ core_resource_dirs := $(call all-core-resource-dirs,main) test_resource_dirs := $(call all-core-resource-dirs,test) ifeq ($(EMMA_INSTRUMENT),true) +ifneq ($(EMMA_INSTRUMENT_STATIC),true) core_src_files += $(call all-java-files-under, ../external/emma/core ../external/emma/pregenerated) core_resource_dirs += ../external/emma/core/res ../external/emma/pregenerated/res endif +endif local_javac_flags=-encoding UTF-8 #local_javac_flags+=-Xlint:all -Xlint:-serial,-deprecation,-unchecked @@ -78,8 +80,6 @@ LOCAL_NO_STANDARD_LIBRARIES := true LOCAL_JAVACFLAGS := $(local_javac_flags) LOCAL_DX_FLAGS := --core-library -LOCAL_NO_EMMA_INSTRUMENT := true -LOCAL_NO_EMMA_COMPILE := true LOCAL_MODULE_TAGS := optional LOCAL_MODULE := core LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk @@ -100,8 +100,6 @@ LOCAL_JAVACFLAGS := $(local_javac_flags) LOCAL_MODULE_TAGS := tests LOCAL_MODULE := core-tests LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk -LOCAL_NO_EMMA_INSTRUMENT := true -LOCAL_NO_EMMA_COMPILE := true include $(BUILD_STATIC_JAVA_LIBRARY) # This one's tricky. One of our tests needs to have a @@ -136,8 +134,6 @@ ifeq ($(WITH_HOST_DALVIK),true) LOCAL_JAVACFLAGS := $(local_javac_flags) LOCAL_DX_FLAGS := --core-library - LOCAL_NO_EMMA_INSTRUMENT := true - LOCAL_NO_EMMA_COMPILE := true LOCAL_BUILD_HOST_DEX := true LOCAL_MODULE_TAGS := optional @@ -157,8 +153,6 @@ ifeq ($(WITH_HOST_DALVIK),true) LOCAL_MODULE_TAGS := optional LOCAL_MODULE := core-tests-hostdex LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/JavaLibrary.mk - LOCAL_NO_EMMA_INSTRUMENT := true - LOCAL_NO_EMMA_COMPILE := true LOCAL_BUILD_HOST_DEX := true include $(BUILD_HOST_JAVA_LIBRARY) endif diff --git a/dalvik/src/main/java/dalvik/system/Zygote.java b/dalvik/src/main/java/dalvik/system/Zygote.java index ec114ed..c06314e 100644 --- a/dalvik/src/main/java/dalvik/system/Zygote.java +++ b/dalvik/src/main/java/dalvik/system/Zygote.java @@ -41,6 +41,15 @@ public class Zygote { /** Enable logging of third-party JNI activity. */ public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; + /** No external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_NONE = 0; + /** Single-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_SINGLEUSER = 1; + /** Multi-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_MULTIUSER = 2; + /** All multi-user external storage should be mounted. */ + public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3; + /** * When set by the system server, all subsequent apps will be launched in * VM safe mode. @@ -114,27 +123,17 @@ public class Zygote { * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */ - public static int forkAndSpecialize(int uid, int gid, int[] gids, - int debugFlags, int[][] rlimits, String seInfo, String niceName) { + public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName) { preFork(); - int pid = nativeForkAndSpecialize(uid, gid, gids, debugFlags, rlimits, seInfo, niceName); + int pid = nativeForkAndSpecialize( + uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName); postFork(); return pid; } - native public static int nativeForkAndSpecialize(int uid, int gid, - int[] gids, int debugFlags, int[][] rlimits, String seInfo, String niceName); - - /** - * Forks a new VM instance. - * @deprecated use {@link Zygote#forkAndSpecialize(int, int, int[], int, int[][])} - */ - @Deprecated - public static int forkAndSpecialize(int uid, int gid, int[] gids, - boolean enableDebugger, int[][] rlimits) { - int debugFlags = enableDebugger ? DEBUG_ENABLE_DEBUGGER : 0; - return forkAndSpecialize(uid, gid, gids, debugFlags, rlimits, null, null); - } + native public static int nativeForkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName); /** * Special method to start the system server process. In addition to the @@ -159,31 +158,17 @@ public class Zygote { * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */ - public static int forkSystemServer(int uid, int gid, int[] gids, - int debugFlags, int[][] rlimits, - long permittedCapabilities, long effectiveCapabilities) { + public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { preFork(); - int pid = nativeForkSystemServer(uid, gid, gids, debugFlags, rlimits, - permittedCapabilities, - effectiveCapabilities); + int pid = nativeForkSystemServer( + uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities); postFork(); return pid; } - /** - * Special method to start the system server process. - * @deprecated use {@link Zygote#forkSystemServer(int, int, int[], int, int[][])} - */ - @Deprecated - public static int forkSystemServer(int uid, int gid, int[] gids, - boolean enableDebugger, int[][] rlimits) { - int debugFlags = enableDebugger ? DEBUG_ENABLE_DEBUGGER : 0; - return forkAndSpecialize(uid, gid, gids, debugFlags, rlimits, null, null); - } - - native public static int nativeForkSystemServer(int uid, int gid, - int[] gids, int debugFlags, int[][] rlimits, - long permittedCapabilities, long effectiveCapabilities); + native public static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** * Executes "/system/bin/sh -c <command>" using the exec() system call. diff --git a/expectations/brokentests.txt b/expectations/brokentests.txt index 6fc48cd..e1d81e0 100644 --- a/expectations/brokentests.txt +++ b/expectations/brokentests.txt @@ -8,7 +8,12 @@ bug: 5834665 }, { - description: "libcore.java.net.URLConnectionTest#testServerShutdownInput fails on ICL27 mysid-userdebug", + description: "FIONREAD/SIOCINQ returns the wrong result on sockets (5731252 is the root cause of 5534202)", + name: "libcore.java.net.SocketTest#testAvailable", + bug: 5731252 +}, +{ + description: "libcore.java.net.URLConnectionTest#testServerShutdownInput fails on ICL27 mysid-userdebug (5534202 is caused by 5731252)", name: "libcore.java.net.URLConnectionTest#testServerShutdownInput", bug: 5534202 }, diff --git a/luni/src/main/java/java/lang/Object.java b/luni/src/main/java/java/lang/Object.java index 7f4b490..4bca034 100644 --- a/luni/src/main/java/java/lang/Object.java +++ b/luni/src/main/java/java/lang/Object.java @@ -361,7 +361,7 @@ public class Object { * @see java.lang.Thread */ public final void wait() throws InterruptedException { - wait(0 ,0); + wait(0, 0); } /** diff --git a/luni/src/main/java/libcore/io/DropBox.java b/luni/src/main/java/libcore/io/DropBox.java new file mode 100644 index 0000000..cf88106 --- /dev/null +++ b/luni/src/main/java/libcore/io/DropBox.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012 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.io; + +public final class DropBox { + + /** + * Hook for customizing how events are reported. + */ + private static volatile Reporter REPORTER = new DefaultReporter(); + + /** + * Used to replace default Reporter for logging events. Must be non-null. + */ + public static void setReporter(Reporter reporter) { + if (reporter == null) { + throw new NullPointerException("reporter == null"); + } + REPORTER = reporter; + } + + /** + * Returns non-null Reporter. + */ + public static Reporter getReporter() { + return REPORTER; + } + + /** + * Interface to allow customization of reporting behavior. + */ + public static interface Reporter { + public void addData(String tag, byte[] data, int flags); + public void addText(String tag, String data); + } + + /** + * Default Reporter which reports events to the log. + */ + private static final class DefaultReporter implements Reporter { + + public void addData(String tag, byte[] data, int flags) { + System.out.println(tag + ": " + Base64.encode(data)); + } + + public void addText(String tag, String data) { + System.out.println(tag + ": " + data); + } + } + + public static void addData(String tag, byte[] data, int flags) { + getReporter().addData(tag, data, flags); + } + + public static void addText(String tag, String data) { + getReporter().addText(tag, data); + } +} diff --git a/luni/src/main/java/libcore/io/EventLogger.java b/luni/src/main/java/libcore/io/EventLogger.java new file mode 100644 index 0000000..9709cc9 --- /dev/null +++ b/luni/src/main/java/libcore/io/EventLogger.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 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.io; + +public final class EventLogger { + + /** + * Hook for customizing how events are reported. + */ + private static volatile Reporter REPORTER = new DefaultReporter(); + + /** + * Used to replace default Reporter for logging events. Must be non-null. + */ + public static void setReporter(Reporter reporter) { + if (reporter == null) { + throw new NullPointerException("reporter == null"); + } + REPORTER = reporter; + } + + /** + * Returns non-null Reporter. + */ + public static Reporter getReporter() { + return REPORTER; + } + + /** + * Interface to allow customization of reporting behavior. + */ + public static interface Reporter { + public void report (int code, Object... list); + } + + /** + * Default Reporter which reports events to the log. + */ + private static final class DefaultReporter implements Reporter { + @Override + public void report (int code, Object... list) { + StringBuilder sb = new StringBuilder(); + sb.append(code); + for (Object o : list) { + sb.append(","); + sb.append(o.toString()); + } + System.out.println(sb); + } + } + + public static void writeEvent(int code, Object... list) { + getReporter().report(code, list); + } +} diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java new file mode 100644 index 0000000..4cdd8d2 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2012 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.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.net.ssl.DefaultHostnameVerifier; +import libcore.io.IoUtils; +import libcore.util.BasicLruCache; + +/** + * This class provides a simple interface for cert pinning. + */ +public class CertPinManager { + + private long lastModified; + + private final Map<String, PinListEntry> entries = new HashMap<String, PinListEntry>(); + private final BasicLruCache<String, String> hostnameCache = new BasicLruCache<String, String>(10); + private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier(); + + private boolean initialized = false; + private static final boolean DEBUG = false; + + private final File pinFile; + private final TrustedCertificateStore certStore; + + public CertPinManager(TrustedCertificateStore store) throws PinManagerException { + pinFile = new File("/data/misc/keychain/pins"); + certStore = store; + rebuild(); + } + + /** Test only */ + public CertPinManager(String path, TrustedCertificateStore store) throws PinManagerException { + if (path == null) { + throw new NullPointerException("path == null"); + } + pinFile = new File(path); + certStore = store; + rebuild(); + } + + /** + * This is the public interface for cert pinning. + * + * Given a hostname and a certificate chain this verifies that the chain includes + * certs from the pinned list provided. + * + * If the chain doesn't include those certs and is in enforcing mode, then this method + * returns true and the certificate check should fail. + */ + public boolean chainIsNotPinned(String hostname, List<X509Certificate> chain) + throws PinManagerException { + // lookup the entry + PinListEntry entry = lookup(hostname); + + // return its result or false if there's no pin + if (entry != null) { + return entry.chainIsNotPinned(chain); + } + return false; + } + + private synchronized void rebuild() throws PinManagerException { + // reread the pin file + String pinFileContents = readPinFile(); + + if (pinFileContents != null) { + // rebuild the pinned certs + for (String entry : getPinFileEntries(pinFileContents)) { + try { + PinListEntry pin = new PinListEntry(entry, certStore); + entries.put(pin.getCommonName(), pin); + } catch (PinEntryException e) { + log("Pinlist contains a malformed pin: " + entry, e); + } + } + + // clear the cache + hostnameCache.evictAll(); + + // set the last modified time + lastModified = pinFile.lastModified(); + + // we've been fully initialized and are ready to go + initialized = true; + } + } + + private String readPinFile() throws PinManagerException { + try { + return IoUtils.readFileAsString(pinFile.getPath()); + } catch (FileNotFoundException e) { + // there's no pin list, all certs are unpinned + return null; + } catch (IOException e) { + // this is unexpected, fail + throw new PinManagerException("Unexpected error reading pin list; failing.", e); + } + } + + private static String[] getPinFileEntries(String pinFileContents) { + return pinFileContents.split("\n"); + } + + private synchronized PinListEntry lookup(String hostname) throws PinManagerException { + + // if we don't have any data, don't bother + if (!initialized) { + return null; + } + + // check to see if our cache is valid + if (cacheIsNotValid()) { + rebuild(); + } + + // if so, check the hostname cache + String cn = hostnameCache.get(hostname); + if (cn != null) { + // if we hit, return the corresponding entry + return entries.get(cn); + } + + // otherwise, get the matching cn + cn = getMatchingCN(hostname); + if (cn != null) { + hostnameCache.put(hostname, cn); + // we have a matching CN, return that entry + return entries.get(cn); + } + + // if we got here, we don't have a matching CN for this hostname + return null; + } + + private boolean cacheIsNotValid() { + return pinFile.lastModified() != lastModified; + } + + private String getMatchingCN(String hostname) { + String bestMatch = ""; + for (String cn : entries.keySet()) { + // skip shorter CNs since they can't be better matches + if (cn.length() < bestMatch.length()) { + continue; + } + // now verify that the CN matches at all + if (verifier.verifyHostName(hostname, cn)) { + bestMatch = cn; + } + } + return bestMatch; + } + + private static void log(String s, Exception e) { + if (DEBUG) { + System.out.println("PINFILE: " + s); + if (e != null) { + e.printStackTrace(); + } + } + } +} diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java index 83f86ae..c855c0c 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java @@ -37,6 +37,7 @@ import javax.crypto.spec.DHPublicKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; /** @@ -88,7 +89,7 @@ public class ClientHandshakeImpl extends HandshakeProtocol { if (engineOwner != null) { session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort()); } else { - session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort()); + session.setPeer(socketOwner.getPeerHostName(), socketOwner.getPeerPort()); } session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols()); recordProtocol.setVersion(session.protocol.version); @@ -109,7 +110,7 @@ public class ClientHandshakeImpl extends HandshakeProtocol { if (engineOwner != null) { session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort()); } else { - session.setPeer(socketOwner.getInetAddress().getHostName(), socketOwner.getPort()); + session.setPeer(socketOwner.getPeerHostName(), socketOwner.getPeerPort()); } session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols()); recordProtocol.setVersion(session.protocol.version); @@ -527,8 +528,21 @@ public class ClientHandshakeImpl extends HandshakeProtocol { if (authType == null) { return; } + String hostname = null; + if (engineOwner != null) { + hostname = engineOwner.getPeerHost(); + } else { + // we don't want to do an inet address lookup here in case we're talking to a proxy + hostname = socketOwner.getWrappedHostName(); + } try { - parameters.getTrustManager().checkServerTrusted(serverCert.certs, authType); + X509TrustManager x509tm = parameters.getTrustManager(); + if (x509tm instanceof TrustManagerImpl) { + TrustManagerImpl tm = (TrustManagerImpl) x509tm; + tm.checkServerTrusted(serverCert.certs, authType, hostname); + } else { + x509tm.checkServerTrusted(serverCert.certs, authType); + } } catch (CertificateException e) { fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e); return; @@ -559,8 +573,8 @@ public class ClientHandshakeImpl extends HandshakeProtocol { host = engineOwner.getPeerHost(); port = engineOwner.getPeerPort(); } else { - host = socketOwner.getInetAddress().getHostName(); - port = socketOwner.getPort(); + host = socketOwner.getPeerHostName(); + port = socketOwner.getPeerPort(); } if (host == null || port == -1) { return null; // starts new session 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 84f33f2..65373ff 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 @@ -78,6 +78,8 @@ public final class NativeCrypto { public static native void EVP_PKEY_free(int pkey); + public static native int EVP_PKEY_cmp(int pkey1, int pkey2); + public static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int pkey); public static native int d2i_PKCS8_PRIV_KEY_INFO(byte[] data); @@ -643,7 +645,7 @@ public final class NativeCrypto { public static native int SSL_read(int sslNativePointer, FileDescriptor fd, SSLHandshakeCallbacks shc, - byte[] b, int off, int len, int timeoutMillis) + byte[] b, int off, int len, int readTimeoutMillis) throws IOException; /** @@ -652,7 +654,7 @@ public final class NativeCrypto { public static native void SSL_write(int sslNativePointer, FileDescriptor fd, SSLHandshakeCallbacks shc, - byte[] b, int off, int len) + byte[] b, int off, int len, int writeTimeoutMillis) throws IOException; public static native void SSL_interrupt(int sslNativePointer); 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 e1d0fb0..fd9be17 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 @@ -80,26 +80,26 @@ public final class OpenSSLProvider extends Provider { put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5WithRSAEncryption"); - put("Signature.SHA1WithRSAEncryption", OpenSSLSignature.SHA1RSA.class.getName()); - put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSAEncryption"); - put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1WithRSAEncryption"); - - put("Signature.SHA256WithRSAEncryption", OpenSSLSignature.SHA256RSA.class.getName()); - put("Alg.Alias.Signature.SHA256WithRSA", "SHA256WithRSAEncryption"); - put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256WithRSAEncryption"); - - put("Signature.SHA384WithRSAEncryption", OpenSSLSignature.SHA384RSA.class.getName()); - put("Alg.Alias.Signature.SHA384WithRSA", "SHA384WithRSAEncryption"); - put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384WithRSAEncryption"); - - put("Signature.SHA512WithRSAEncryption", OpenSSLSignature.SHA512RSA.class.getName()); - put("Alg.Alias.Signature.SHA512WithRSA", "SHA512WithRSAEncryption"); - put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512WithRSAEncryption"); + put("Signature.SHA1WithRSA", OpenSSLSignature.SHA1RSA.class.getName()); + put("Alg.Alias.Signature.SHA1WithRSA", "SHA1WithRSA"); + put("Alg.Alias.Signature.SHA1/RSA", "SHA1WithRSA"); + put("Alg.Alias.Signature.SHA-1/RSA", "SHA1WithRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1WithRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1WithRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1WithRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1WithRSA"); + + put("Signature.SHA256WithRSA", OpenSSLSignature.SHA256RSA.class.getName()); + put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256WithRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256WithRSA"); + + put("Signature.SHA384WithRSA", OpenSSLSignature.SHA384RSA.class.getName()); + put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384WithRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384WithRSA"); + + put("Signature.SHA512WithRSA", OpenSSLSignature.SHA512RSA.class.getName()); + put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512WithRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512WithRSA"); put("Signature.SHA1withDSA", OpenSSLSignature.SHA1DSA.class.getName()); put("Alg.Alias.Signature.SHA/DSA", "SHA1withDSA"); 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 f1e12c2..0f43bf9 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 @@ -197,8 +197,8 @@ public class OpenSSLRSAPrivateCrtKey extends OpenSSLRSAPrivateKey implements RSA return true; } - if (o instanceof OpenSSLRSAPrivateCrtKey) { - OpenSSLRSAPrivateCrtKey other = (OpenSSLRSAPrivateCrtKey) o; + if (o instanceof OpenSSLRSAPrivateKey) { + OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o; /* * We can shortcut the true case, but it still may be equivalent but @@ -208,28 +208,35 @@ public class OpenSSLRSAPrivateCrtKey extends OpenSSLRSAPrivateKey implements RSA return true; } - /* - * If this key is ENGINE-based, it could be equivalent to another - * ENGINE-based key. The modulus may be equal, but that occurrence - * should be so improbably low as to never happen. - */ - if (getOpenSSLKey().isEngineBased() || other.getOpenSSLKey().isEngineBased()) { - return publicExponent.equals(other.getPublicExponent()) - && getModulus().equals(other.getModulus()); - } + return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1; } if (o instanceof RSAPrivateCrtKey) { ensureReadParams(); RSAPrivateCrtKey other = (RSAPrivateCrtKey) o; - return getModulus().equals(other.getModulus()) - && publicExponent.equals(other.getPublicExponent()) - && getPrivateExponent().equals(other.getPrivateExponent()) - && primeP.equals(other.getPrimeP()) && primeQ.equals(other.getPrimeQ()) - && primeExponentP.equals(other.getPrimeExponentP()) - && primeExponentQ.equals(other.getPrimeExponentQ()) - && crtCoefficient.equals(other.getCrtCoefficient()); + if (getOpenSSLKey().isEngineBased()) { + return getModulus().equals(other.getModulus()) + && publicExponent.equals(other.getPublicExponent()); + } else { + return getModulus().equals(other.getModulus()) + && publicExponent.equals(other.getPublicExponent()) + && getPrivateExponent().equals(other.getPrivateExponent()) + && primeP.equals(other.getPrimeP()) && primeQ.equals(other.getPrimeQ()) + && primeExponentP.equals(other.getPrimeExponentP()) + && primeExponentQ.equals(other.getPrimeExponentQ()) + && crtCoefficient.equals(other.getCrtCoefficient()); + } + } else if (o instanceof RSAPrivateKey) { + ensureReadParams(); + RSAPrivateKey other = (RSAPrivateKey) o; + + if (getOpenSSLKey().isEngineBased()) { + return getModulus().equals(other.getModulus()); + } else { + return getModulus().equals(other.getModulus()) + && getPrivateExponent().equals(other.getPrivateExponent()); + } } return false; 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 fa455c7..6ad89b2 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 @@ -205,14 +205,7 @@ public class OpenSSLRSAPrivateKey implements RSAPrivateKey { return true; } - /* - * If this key is ENGINE-based, it could be equivalent to another - * ENGINE-based key. The modulus may be equal, but that occurrence - * should be so improbably low as to never happen. - */ - if (key.isEngineBased() || other.getOpenSSLKey().isEngineBased()) { - return modulus.equals(other.getModulus()); - } + return NativeCrypto.EVP_PKEY_cmp(getPkeyContext(), other.getPkeyContext()) == 1; } if (o instanceof RSAPrivateKey) { diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java index 3720ba2..4cc16e6 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java @@ -42,7 +42,11 @@ import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; +import static libcore.io.OsConstants.*; +import libcore.io.ErrnoException; +import libcore.io.Libcore; import libcore.io.Streams; +import libcore.io.StructTimeval; import org.apache.harmony.security.provider.cert.X509CertImpl; /** @@ -93,7 +97,8 @@ public class OpenSSLSocketImpl * OpenSSLSocketImplWrapper overrides setSoTimeout and * getSoTimeout to delegate to the wrapped socket. */ - private int timeoutMilliseconds = 0; + private int readTimeoutMilliseconds = 0; + private int writeTimeoutMilliseconds = 0; private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite private String wrappedHost; @@ -361,9 +366,11 @@ public class OpenSSLSocketImpl } // Temporarily use a different timeout for the handshake process - int savedTimeoutMilliseconds = getSoTimeout(); + int savedReadTimeoutMilliseconds = getSoTimeout(); + int savedWriteTimeoutMilliseconds = getSoWriteTimeout(); if (handshakeTimeoutMilliseconds >= 0) { setSoTimeout(handshakeTimeoutMilliseconds); + setSoWriteTimeout(handshakeTimeoutMilliseconds); } int sslSessionNativePointer; @@ -399,7 +406,8 @@ public class OpenSSLSocketImpl // Restore the original timeout now that the handshake is complete if (handshakeTimeoutMilliseconds >= 0) { - setSoTimeout(savedTimeoutMilliseconds); + setSoTimeout(savedReadTimeoutMilliseconds); + setSoWriteTimeout(savedWriteTimeoutMilliseconds); } // if not, notifyHandshakeCompletedListeners later in handshakeCompleted() callback @@ -418,7 +426,7 @@ public class OpenSSLSocketImpl } } - private String getPeerHostName() { + String getPeerHostName() { if (wrappedHost != null) { return wrappedHost; } @@ -429,7 +437,7 @@ public class OpenSSLSocketImpl return null; } - private int getPeerPort() { + int getPeerPort() { return wrappedHost == null ? super.getPort() : wrappedPort; } @@ -570,8 +578,13 @@ public class OpenSSLSocketImpl } boolean client = sslParameters.getUseClientMode(); if (client) { - sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain, - authMethod); + X509TrustManager x509tm = sslParameters.getTrustManager(); + if (x509tm instanceof TrustManagerImpl) { + TrustManagerImpl tm = (TrustManagerImpl) x509tm; + tm.checkServerTrusted(peerCertificateChain, authMethod, wrappedHost); + } else { + x509tm.checkServerTrusted(peerCertificateChain, authMethod); + } } else { String authType = peerCertificateChain[0].getPublicKey().getAlgorithm(); sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain, @@ -691,7 +704,7 @@ public class OpenSSLSocketImpl return; } NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(), - OpenSSLSocketImpl.this, buf, offset, byteCount); + OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds); } } } @@ -822,21 +835,42 @@ public class OpenSSLSocketImpl throw new SocketException("Methods sendUrgentData, setOOBInline are not supported."); } - @Override public void setSoTimeout(int timeoutMilliseconds) throws SocketException { - super.setSoTimeout(timeoutMilliseconds); - this.timeoutMilliseconds = timeoutMilliseconds; + @Override public void setSoTimeout(int readTimeoutMilliseconds) throws SocketException { + super.setSoTimeout(readTimeoutMilliseconds); + this.readTimeoutMilliseconds = readTimeoutMilliseconds; } @Override public int getSoTimeout() throws SocketException { - return timeoutMilliseconds; + return readTimeoutMilliseconds; + } + + /** + * Note write timeouts are not part of the javax.net.ssl.SSLSocket API + */ + public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException { + this.writeTimeoutMilliseconds = writeTimeoutMilliseconds; + + StructTimeval tv = StructTimeval.fromMillis(writeTimeoutMilliseconds); + try { + Libcore.os.setsockoptTimeval(getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, tv); + } catch (ErrnoException errnoException) { + throw errnoException.rethrowAsSocketException(); + } + } + + /** + * Note write timeouts are not part of the javax.net.ssl.SSLSocket API + */ + public int getSoWriteTimeout() throws SocketException { + return writeTimeoutMilliseconds; } /** * Set the handshake timeout on this socket. This timeout is specified in * milliseconds and will be used only during the handshake process. */ - public void setHandshakeTimeout(int timeoutMilliseconds) throws SocketException { - this.handshakeTimeoutMilliseconds = timeoutMilliseconds; + public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException { + this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds; } @Override public void close() throws IOException { diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java new file mode 100644 index 0000000..8b74514 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinEntryException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 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; + +// public for testing by CertPinManagerTest +public class PinEntryException extends Exception { + + PinEntryException() { + } + + PinEntryException(String msg) { + super(msg); + } +} + diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java new file mode 100644 index 0000000..40b1838 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 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.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.List; +import libcore.io.Base64; +import libcore.io.DropBox; + +public class PinFailureLogger { + + private static final long LOG_INTERVAL_NANOS = 1000 * 1000 * 1000 * 60 * 60; + + private static long lastLoggedNanos = 0; + + public static synchronized void log(String cn, boolean chainContainsUserCert, + boolean pinIsEnforcing, + List<X509Certificate> chain) { + // if we've logged recently, don't do it again + if (!timeToLog()) { + return; + } + // otherwise, log the event + writeToLog(cn, chainContainsUserCert, pinIsEnforcing, chain); + // update the last logged time + lastLoggedNanos = System.nanoTime(); + } + + protected static synchronized void writeToLog(String cn, boolean chainContainsUserCert, + boolean pinIsEnforcing, + List<X509Certificate> chain) { + StringBuilder sb = new StringBuilder(); + sb.append(cn); + sb.append("|"); + sb.append(chainContainsUserCert); + sb.append("|"); + sb.append(pinIsEnforcing); + sb.append("|"); + for (X509Certificate cert : chain) { + try { + sb.append(Base64.encode(cert.getEncoded())); + } catch (CertificateEncodingException e) { + sb.append("Error: could not encode certificate"); + } + sb.append("|"); + } + DropBox.addText("cert_pin_failure", sb.toString()); + } + + protected static boolean timeToLog() { + long currentTimeNanos = System.nanoTime(); + return ((currentTimeNanos - lastLoggedNanos) > LOG_INTERVAL_NANOS); + } +} + diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java new file mode 100644 index 0000000..c05a391 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012 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.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import libcore.io.EventLogger; + +/** + * This class represents a single entry in the pin file. + */ +// public for testing by CertPinManagerTest +public class PinListEntry { + + /** The Common Name (CN) as used on the SSL certificate */ + private final String cn; + + /** + * Determines whether a failed match here will prevent the chain from being accepted. If true, + * an unpinned chain will log and cause a match failure. If false, it will merely log. + */ + private final boolean enforcing; + + private final Set<String> pinnedFingerprints = new HashSet<String>(); + + private static final boolean DEBUG = false; + + private final TrustedCertificateStore certStore; + + public String getCommonName() { + return cn; + } + + public boolean getEnforcing() { + return enforcing; + } + + public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException { + if (entry == null) { + throw new NullPointerException("entry == null"); + } + certStore = store; + // Examples: + // *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d + // *.android.com=true|ca05f...8a0d34c + // clients.google.com=false|9e04ca05f...34c8a0d,34c8a0d...9e04ca05f + String[] values = entry.split("[=,|]"); + // entry must have a CN, an enforcement value, and at least one pin + if (values.length < 3) { + throw new PinEntryException("Received malformed pin entry"); + } + // get the cn + cn = values[0]; // is there more validation we can do here? + enforcing = enforcementValueFromString(values[1]); + // the remainder should be pins + addPins(Arrays.copyOfRange(values, 2, values.length)); + } + + private static boolean enforcementValueFromString(String val) throws PinEntryException { + if (val.equals("true")) { + return true; + } else if (val.equals("false")) { + return false; + } else { + throw new PinEntryException("Enforcement status is not a valid value"); + } + } + + /** + * Checks the given chain against the pin list corresponding to this entry. + * + * If the pin list does not contain the required certs and the enforcing field is true then + * this returns true, indicating a verification error. Otherwise, it returns false and + * verification should proceed. + */ + public boolean chainIsNotPinned(List<X509Certificate> chain) { + for (X509Certificate cert : chain) { + String fingerprint = getFingerprint(cert); + if (pinnedFingerprints.contains(fingerprint)) { + return false; + } + } + logPinFailure(chain); + return enforcing; + } + + private static String getFingerprint(X509Certificate cert) { + try { + MessageDigest dgst = MessageDigest.getInstance("SHA512"); + byte[] encoded = cert.getPublicKey().getEncoded(); + byte[] fingerprint = dgst.digest(encoded); + return IntegralToString.bytesToHexString(fingerprint, false); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private void addPins(String[] pins) { + for (String pin : pins) { + validatePin(pin); + } + Collections.addAll(pinnedFingerprints, pins); + } + + private static void validatePin(String pin) { + // check to make sure the length is correct + if (pin.length() != 128) { + throw new IllegalArgumentException("Pin is not a valid length"); + } + // check to make sure that it's a valid hex string + try { + new BigInteger(pin, 16); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Pin is not a valid hex string", e); + } + } + + private boolean chainContainsUserCert(List<X509Certificate> chain) { + if (certStore == null) { + return false; + } + for (X509Certificate cert : chain) { + if (certStore.isUserAddedCertificate(cert)) { + return true; + } + } + return false; + } + + private void logPinFailure(List<X509Certificate> chain) { + PinFailureLogger.log(cn, chainContainsUserCert(chain), enforcing, chain); + } +} + diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java new file mode 100644 index 0000000..74b3c65 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinManagerException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 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; + +class PinManagerException extends Exception { + + PinManagerException() { + } + + PinManagerException(String msg) { + super(msg); + } + + PinManagerException(String msg, Exception e) { + super(msg, e); + } +} + diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java index be9a7fc..93496cf 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java @@ -88,7 +88,7 @@ public class SSLSocketFactoryImpl extends SSLSocketFactory { if (instantiationException != null) { throw instantiationException; } - return new SSLSocketWrapper(s, autoClose, (SSLParametersImpl) sslParameters + return new SSLSocketWrapper(s, host, port, autoClose, (SSLParametersImpl) sslParameters .clone()); } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java index 6e5fddd..2cd2cf5 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketImpl.java @@ -41,6 +41,10 @@ public class SSLSocketImpl extends SSLSocket { // indicates if handshake has been started private boolean handshake_started = false; + // used when we're wrapping a socket + private final String wrappedHost; + private final int wrappedPort; + // record protocol to be used protected SSLRecordProtocol recordProtocol; // handshake protocol to be used @@ -83,6 +87,8 @@ public class SSLSocketImpl extends SSLSocket { */ protected SSLSocketImpl(SSLParametersImpl sslParameters) { this.sslParameters = sslParameters; + this.wrappedHost = null; + this.wrappedPort = -1; // init should be called after creation! } @@ -99,6 +105,8 @@ public class SSLSocketImpl extends SSLSocket { protected SSLSocketImpl(String host, int port, SSLParametersImpl sslParameters) throws IOException, UnknownHostException { super(host, port); + this.wrappedHost = host; + this.wrappedPort = port; this.sslParameters = sslParameters; init(); } @@ -120,6 +128,8 @@ public class SSLSocketImpl extends SSLSocket { SSLParametersImpl sslParameters) throws IOException, UnknownHostException { super(host, port, localHost, localPort); + this.wrappedHost = host; + this.wrappedPort = port; this.sslParameters = sslParameters; init(); } @@ -138,6 +148,8 @@ public class SSLSocketImpl extends SSLSocket { SSLParametersImpl sslParameters) throws IOException { super(host, port); this.sslParameters = sslParameters; + this.wrappedHost = null; + this.wrappedPort = -1; init(); } @@ -158,6 +170,8 @@ public class SSLSocketImpl extends SSLSocket { SSLParametersImpl sslParameters) throws IOException { super(address, port, localAddress, localPort); this.sslParameters = sslParameters; + this.wrappedHost = null; + this.wrappedPort = -1; init(); } @@ -193,6 +207,29 @@ public class SSLSocketImpl extends SSLSocket { } } + String getWrappedHostName() { + return wrappedHost; + } + + int getWrappedPort() { + return wrappedPort; + } + + String getPeerHostName() { + if (wrappedHost != null) { + return wrappedHost; + } + InetAddress inetAddress = super.getInetAddress(); + if (inetAddress != null) { + return inetAddress.getHostName(); + } + return null; + } + + int getPeerPort() { + return (wrappedPort == -1) ? super.getPort() : wrappedPort; + } + // --------------- SSLParameters based methods --------------------- /** diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java index 27bbead..a393e24 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketWrapper.java @@ -32,8 +32,9 @@ public class SSLSocketWrapper extends SSLSocketImpl { private final Socket socket; private final boolean autoClose; - protected SSLSocketWrapper(Socket socket, boolean autoClose, SSLParametersImpl sslParameters) throws IOException { - super(sslParameters); + protected SSLSocketWrapper(Socket socket, String host, int port, boolean autoClose, + SSLParametersImpl sslParameters) throws IOException { + super(host, port, sslParameters); if (!socket.isConnected()) { throw new SocketException("Socket is not connected."); } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java index 3f362c5..0218249 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java @@ -35,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import javax.net.ssl.X509TrustManager; +import libcore.io.EventLogger; /** * @@ -52,6 +53,11 @@ public final class TrustManagerImpl implements X509TrustManager { private final KeyStore rootKeyStore; /** + * The CertPinManager, which validates the chain against a host-to-pin mapping + */ + private CertPinManager pinManager; + + /** * The backing store for the AndroidCAStore if non-null. This will * be null when the rootKeyStore is null, implying we are not * using the AndroidCAStore. @@ -83,6 +89,13 @@ public final class TrustManagerImpl implements X509TrustManager { * @param ks */ public TrustManagerImpl(KeyStore keyStore) { + this(keyStore, null); + } + + /** + * For testing only + */ + public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) { CertPathValidator validatorLocal = null; CertificateFactory factoryLocal = null; KeyStore rootKeyStoreLocal = null; @@ -111,6 +124,17 @@ public final class TrustManagerImpl implements X509TrustManager { } catch (Exception e) { errLocal = e; } + + if (manager != null) { + this.pinManager = manager; + } else { + try { + pinManager = new CertPinManager(trustedCertificateStoreLocal); + } catch (PinManagerException e) { + throw new SecurityException("Could not initialize CertPinManager", e); + } + } + this.rootKeyStore = rootKeyStoreLocal; this.trustedCertificateStore = trustedCertificateStoreLocal; this.validator = validatorLocal; @@ -155,12 +179,22 @@ public final class TrustManagerImpl implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - checkTrusted(chain, authType); + checkTrusted(chain, authType, null); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - checkTrusted(chain, authType); + checkTrusted(chain, authType, null); + } + + /** + * Validates whether a server is trusted. If hostname is given and non-null it also checks if + * chain is pinned appropriately for that host. If null, it does not check for pinned certs. + * The return value is a list of the certificates used for making the trust decision. + */ + public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType, + String host) throws CertificateException { + return checkTrusted(chain, authType, host); } public void handleTrustStorageUpdate() { @@ -168,10 +202,10 @@ public final class TrustManagerImpl implements X509TrustManager { trustedCertificateIndex.reset(); } else { trustedCertificateIndex.reset(trustAnchors(acceptedIssuers)); - } + } } - private void checkTrusted(X509Certificate[] chain, String authType) + private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType, String host) throws CertificateException { if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { throw new IllegalArgumentException("null or zero-length parameter"); @@ -180,21 +214,71 @@ public final class TrustManagerImpl implements X509TrustManager { throw new CertificateException(err); } - Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(); - X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors); + // get the cleaned up chain and trust anchor + Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one! + X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor); + + // add the first trust anchor to the chain, which may be an intermediate + List<X509Certificate> wholeChain = new ArrayList<X509Certificate>(); + wholeChain.addAll(Arrays.asList(newChain)); + // trustAnchor is actually just a single element + for (TrustAnchor trust : trustAnchor) { + wholeChain.add(trust.getTrustedCert()); + } + + // add all the cached certificates from the cert index, avoiding loops + // this gives us a full chain from leaf to root, which we use for cert pinning and pass + // back out to callers when we return. + X509Certificate last = wholeChain.get(wholeChain.size() - 1); + while (true) { + TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last); + // the cachedTrust can be null if there isn't anything in the index or if a user has + // trusted a non-self-signed cert. + if (cachedTrust == null) { + break; + } + + // at this point we have a cached trust anchor, but don't know if its one we got from + // the server. Extract the cert, compare it to the last element in the chain, and add it + // if we haven't seen it before. + X509Certificate next = cachedTrust.getTrustedCert(); + if (next != last) { + wholeChain.add(next); + last = next; + } else { + // if next == last then we found a self-signed cert and the chain is done + break; + } + } + + // build the cert path from the array of certs sans trust anchors + CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); + + if (host != null) { + boolean chainIsNotPinned = true; + try { + chainIsNotPinned = pinManager.chainIsNotPinned(host, wholeChain); + } catch (PinManagerException e) { + throw new CertificateException(e); + } + if (chainIsNotPinned) { + throw new CertificateException(new CertPathValidatorException( + "Certificate path is not properly pinned.", null, certPath, -1)); + } + } + if (newChain.length == 0) { // chain was entirely trusted, skip the validator - return; + return wholeChain; } - CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); - if (trustAnchors.isEmpty()) { + if (trustAnchor.isEmpty()) { throw new CertificateException(new CertPathValidatorException( "Trust anchor for certification path not found.", null, certPath, -1)); } try { - PKIXParameters params = new PKIXParameters(trustAnchors); + PKIXParameters params = new PKIXParameters(trustAnchor); params.setRevocationEnabled(false); validator.validate(certPath, params); // Add intermediate CAs to the index to tolerate sites @@ -211,6 +295,8 @@ public final class TrustManagerImpl implements X509TrustManager { } catch (CertPathValidatorException e) { throw new CertificateException(e); } + + return wholeChain; } /** @@ -232,17 +318,9 @@ public final class TrustManagerImpl implements X509TrustManager { // Start with the first certificate in the chain, assuming it // is the leaf certificate (server or client cert). for (currIndex = 0; currIndex < chain.length; currIndex++) { - // If the current cert is a TrustAnchor, we can ignore the rest of the chain. - // This avoids including "bridge" CA certs that added for legacy compatability. - TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[currIndex]); - if (trustAnchor != null) { - trustAnchors.add(trustAnchor); - currIndex--; - break; - } - // Walk the rest of the chain to find a "subject" matching + // Walk the chain to find a "subject" matching // the "issuer" of the current certificate. In a properly - // order chain this should be the next cert and be fast. + // ordered chain this should be the next cert and be fast. // If not, we reorder things to be as the validator will // expect. boolean foundNext = false; @@ -271,15 +349,27 @@ public final class TrustManagerImpl implements X509TrustManager { } } - // 2. If the chain is now shorter, copy to an appropriately sized array. - int chainLength = currIndex + 1; + // 2. Find the trust anchor in the chain, if any + int anchorIndex; + for (anchorIndex = 0; anchorIndex < chain.length; anchorIndex++) { + // If the current cert is a TrustAnchor, we can ignore the rest of the chain. + // This avoids including "bridge" CA certs that added for legacy compatibility. + TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]); + if (trustAnchor != null) { + trustAnchors.add(trustAnchor); + break; + } + } + + // 3. If the chain is now shorter, copy to an appropriately sized array. + int chainLength = anchorIndex; X509Certificate[] newChain = ((chainLength == chain.length) ? chain : Arrays.copyOf(chain, chainLength)); - // 3. If no TrustAnchor was found in cleanup, look for one now + // 4. If we didn't find a trust anchor earlier, look for one now if (trustAnchors.isEmpty()) { - TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[chainLength-1]); + TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]); if (trustAnchor != null) { trustAnchors.add(trustAnchor); } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java index abb6e55..e7b1a7c 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStore.java @@ -298,6 +298,14 @@ public final class TrustedCertificateStore { } /** + * Returns true to indicate that the certificate was added by the + * user, false otherwise. + */ + public boolean isUserAddedCertificate(X509Certificate cert) { + return getCertificateFile(addedDir, cert).exists(); + } + + /** * Returns a File for where the certificate is found if it exists * or where it should be installed if it does not exist. The * caller can disambiguate these cases by calling {@code 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 a04bebf..83f28d2 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 @@ -923,6 +923,26 @@ static void NativeCrypto_EVP_PKEY_free(JNIEnv*, jclass, EVP_PKEY* pkey) { } } +static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jint pkey1Ref, jint pkey2Ref) { + EVP_PKEY* pkey1 = reinterpret_cast<EVP_PKEY*>(pkey1Ref); + EVP_PKEY* pkey2 = reinterpret_cast<EVP_PKEY*>(pkey2Ref); + JNI_TRACE("EVP_PKEY_cmp(%p, %p)", pkey1, pkey2); + + if (pkey1 == NULL) { + JNI_TRACE("EVP_PKEY_cmp(%p, %p) => failed pkey1 == NULL", pkey1, pkey2); + jniThrowNullPointerException(env, "pkey1 == NULL"); + return -1; + } else if (pkey2 == NULL) { + JNI_TRACE("EVP_PKEY_cmp(%p, %p) => failed pkey2 == NULL", pkey1, pkey2); + jniThrowNullPointerException(env, "pkey2 == NULL"); + return -1; + } + + int result = EVP_PKEY_cmp(pkey1, pkey2); + JNI_TRACE("EVP_PKEY_cmp(%p, %p) => %d", pkey1, pkey2, result); + return result; +} + /* * static native byte[] i2d_PKCS8_PRIV_KEY_INFO(int, byte[]) */ @@ -3735,7 +3755,7 @@ static jobjectArray NativeCrypto_SSL_get_peer_cert_chain(JNIEnv* env, jclass, ji * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown. */ static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* buf, jint len, - int* sslReturnCode, int* sslErrorCode, int timeout_millis) { + int* sslReturnCode, int* sslErrorCode, int read_timeout_millis) { JNI_TRACE("ssl=%p sslRead buf=%p len=%d", ssl, buf, len); if (len == 0) { @@ -3814,7 +3834,7 @@ static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* b // Need to wait for availability of underlying layer, then retry. case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - int selectResult = sslSelect(env, sslError, fdObject, appData, timeout_millis); + int selectResult = sslSelect(env, sslError, fdObject, appData, read_timeout_millis); if (selectResult == THROWN_EXCEPTION) { return THROWN_EXCEPTION; } @@ -3866,11 +3886,11 @@ static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* b */ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jint ssl_address, jobject fdObject, jobject shc, jbyteArray b, jint offset, jint len, - jint timeout_millis) + jint read_timeout_millis) { SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read fd=%p shc=%p b=%p offset=%d len=%d timeout_millis=%d", - ssl, fdObject, shc, b, offset, len, timeout_millis); + JNI_TRACE("ssl=%p NativeCrypto_SSL_read fd=%p shc=%p b=%p offset=%d len=%d read_timeout_millis=%d", + ssl, fdObject, shc, b, offset, len, read_timeout_millis); if (ssl == NULL) { return 0; } @@ -3894,7 +3914,7 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jint ssl_address, jobject int sslErrorCode = SSL_ERROR_NONE;; int ret = sslRead(env, ssl, fdObject, shc, reinterpret_cast<char*>(bytes.get() + offset), len, - &returnCode, &sslErrorCode, timeout_millis); + &returnCode, &sslErrorCode, read_timeout_millis); int result; switch (ret) { @@ -3934,7 +3954,7 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jint ssl_address, jobject * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown. */ static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len, - int* sslReturnCode, int* sslErrorCode) { + int* sslReturnCode, int* sslErrorCode, int write_timeout_millis) { JNI_TRACE("ssl=%p sslWrite buf=%p len=%d", ssl, buf, len); if (len == 0) { @@ -4020,7 +4040,7 @@ static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const // it's also not standard Java behavior, so we wait forever here. case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - int selectResult = sslSelect(env, sslError, fdObject, appData, 0); + int selectResult = sslSelect(env, sslError, fdObject, appData, write_timeout_millis); if (selectResult == THROWN_EXCEPTION) { return THROWN_EXCEPTION; } @@ -4071,7 +4091,7 @@ static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const * OpenSSL write function (2): write into buffer at offset n chunks. */ static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jint ssl_address, jobject fdObject, - jobject shc, jbyteArray b, jint offset, jint len) + jobject shc, jbyteArray b, jint offset, jint len, jint write_timeout_millis) { SSL* ssl = to_SSL(env, ssl_address, true); JNI_TRACE("ssl=%p NativeCrypto_SSL_write fd=%p shc=%p b=%p offset=%d len=%d", @@ -4098,7 +4118,7 @@ static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jint ssl_address, jobjec int returnCode = 0; int sslErrorCode = SSL_ERROR_NONE; int ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast<const char*>(bytes.get() + offset), - len, &returnCode, &sslErrorCode); + len, &returnCode, &sslErrorCode, write_timeout_millis); switch (ret) { case THROW_SSLEXCEPTION: @@ -4404,6 +4424,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { NATIVE_METHOD(NativeCrypto, EVP_PKEY_type, "(I)I"), NATIVE_METHOD(NativeCrypto, EVP_PKEY_size, "(I)I"), NATIVE_METHOD(NativeCrypto, EVP_PKEY_free, "(I)V"), + NATIVE_METHOD(NativeCrypto, EVP_PKEY_cmp, "(II)I"), NATIVE_METHOD(NativeCrypto, i2d_PKCS8_PRIV_KEY_INFO, "(I)[B"), NATIVE_METHOD(NativeCrypto, d2i_PKCS8_PRIV_KEY_INFO, "([B)I"), NATIVE_METHOD(NativeCrypto, i2d_PUBKEY, "(I)[B"), @@ -4466,7 +4487,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { NATIVE_METHOD(NativeCrypto, SSL_get_certificate, "(I)[[B"), NATIVE_METHOD(NativeCrypto, SSL_get_peer_cert_chain, "(I)[[B"), NATIVE_METHOD(NativeCrypto, SSL_read, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"), - NATIVE_METHOD(NativeCrypto, SSL_write, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BII)V"), + NATIVE_METHOD(NativeCrypto, SSL_write, "(I" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)V"), NATIVE_METHOD(NativeCrypto, SSL_interrupt, "(I)V"), NATIVE_METHOD(NativeCrypto, SSL_shutdown, "(I" FILE_DESCRIPTOR SSL_CALLBACKS ")V"), NATIVE_METHOD(NativeCrypto, SSL_free, "(I)V"), diff --git a/luni/src/main/native/zip.h b/luni/src/main/native/zip.h index 0f3c0c1..303c940 100644 --- a/luni/src/main/native/zip.h +++ b/luni/src/main/native/zip.h @@ -23,7 +23,7 @@ #include "UniquePtr.h" #include "jni.h" #include "zlib.h" -#include "zutil.h" +#include "zutil.h" // For DEF_WBITS and DEF_MEM_LEVEL. static void throwExceptionForZlibError(JNIEnv* env, const char* exceptionClassName, int error) { if (error == Z_MEM_ERROR) { diff --git a/luni/src/test/java/libcore/java/lang/OldObjectTest.java b/luni/src/test/java/libcore/java/lang/OldObjectTest.java index 08471b2..3ab0327 100644 --- a/luni/src/test/java/libcore/java/lang/OldObjectTest.java +++ b/luni/src/test/java/libcore/java/lang/OldObjectTest.java @@ -16,8 +16,6 @@ */ package libcore.java.lang; -import dalvik.annotation.SideEffect; -import java.util.Vector; import junit.framework.TestCase; public class OldObjectTest extends TestCase { @@ -187,7 +185,36 @@ public class OldObjectTest extends TestCase { fail("InterruptedException was thrown."); } assertEquals(3, status); + } + + public void test_waitJI_invalid() throws Exception { + Object o = new Object(); + synchronized (o) { + try { + o.wait(-1, 0); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + o.wait(0, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + o.wait(-1, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // The ms timeout must fit in 32 bits. + try { + o.wait(Integer.MAX_VALUE + 1, 0); + fail(); + } catch (IllegalArgumentException expected) { + } + } } public void test_waitJ() { diff --git a/luni/src/test/java/libcore/java/security/KeyStoreTest.java b/luni/src/test/java/libcore/java/security/KeyStoreTest.java index 15314c9..ddee6ce 100644 --- a/luni/src/test/java/libcore/java/security/KeyStoreTest.java +++ b/luni/src/test/java/libcore/java/security/KeyStoreTest.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -65,10 +66,12 @@ public class KeyStoreTest extends TestCase { private static final String ALIAS_SECRET = "secret"; private static final String ALIAS_ALT_CASE_PRIVATE = "pRiVaTe"; + private static final String ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE = "PrIvAtE-no-password"; private static final String ALIAS_ALT_CASE_CERTIFICATE = "cErTiFiCaTe"; private static final String ALIAS_ALT_CASE_SECRET = "sEcRet"; private static final String ALIAS_UNICODE_PRIVATE = "\u6400\u7902\u3101\u8c02\u5002\u8702\udd01"; + private static final String ALIAS_UNICODE_NO_PASSWORD_PRIVATE = "\u926c\u0967\uc65b\ubc78"; private static final String ALIAS_UNICODE_CERTIFICATE = "\u5402\udd01\u7902\u8702\u3101\u5f02\u3101\u5402\u5002\u8702\udd01"; private static final String ALIAS_UNICODE_SECRET = "\ue224\ud424\ud224\ue124\ud424\ue324"; @@ -146,7 +149,8 @@ public class KeyStoreTest extends TestCase { // JKS key stores cannot store secret keys, neither can the RI's PKCS12 return (!(ks.getType().equals("JKS") || ks.getType().equals("CaseExactJKS") - || (ks.getType().equals("PKCS12")))); + || (ks.getType().equals("PKCS12")) + || (ks.getType().equals("AndroidKeyStore")))); } private static boolean isCertificateEnabled(KeyStore ks) { @@ -157,13 +161,16 @@ public class KeyStoreTest extends TestCase { private static boolean isCaseSensitive(KeyStore ks) { return (ks.getType().equals("CaseExactJKS") || ks.getType().equals("BKS") - || ks.getType().equals("BouncyCastle")); + || ks.getType().equals("BouncyCastle") + || ks.getType().equals("AndroidKeyStore")); } private static boolean isUnsupported(KeyStore ks) { // Don't bother testing BC on RI - return (StandardNames.IS_RI && ks.getProvider().getName().equals("BC")); + // TODO enable AndroidKeyStore when CTS can set up the keystore + return (StandardNames.IS_RI && ks.getProvider().getName().equals("BC")) + || "AndroidKeyStore".equalsIgnoreCase(ks.getType()); } private static boolean isNullPasswordAllowed(KeyStore ks) { @@ -172,7 +179,9 @@ public class KeyStoreTest extends TestCase { || ks.getType().equals("JCEKS") || ks.getType().equals("PKCS12"))); } - + private static boolean isKeyPasswordSupported(KeyStore ks) { + return !ks.getType().equals("AndroidKeyStore"); + } private static boolean isKeyPasswordIgnored(KeyStore ks) { // BouncyCastle's PKCS12 ignores the key password unlike the RI which requires it return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC")); @@ -183,6 +192,14 @@ public class KeyStoreTest extends TestCase { return (ks.getType().equals("PKCS12") && ks.getProvider().getName().equals("BC")); } + private static boolean isPersistentStorage(KeyStore ks) { + return ks.getType().equalsIgnoreCase("AndroidKeyStore"); + } + + private static boolean isLoadStoreUnsupported(KeyStore ks) { + return ks.getType().equalsIgnoreCase("AndroidKeyStore"); + } + private static boolean isSetKeyByteArrayUnimplemented(KeyStore ks) { // All of BouncyCastle's // KeyStore.setKeyEntry(String,byte[],char[]) implementations @@ -203,16 +220,13 @@ public class KeyStoreTest extends TestCase { } public static void populate(KeyStore ks) throws Exception { - ks.load(null, null); - if (isReadOnly(ks)) { - try { - setPrivateKey(ks); - fail(ks.toString()); - } catch (UnsupportedOperationException e) { - } + boolean readOnly = clearKeyStore(ks); + if (readOnly) { return; } - setPrivateKey(ks); + if (isKeyPasswordSupported(ks)) { + setPrivateKey(ks); + } if (isNullPasswordAllowed(ks)) { ks.setKeyEntry(ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey().getPrivateKey(), @@ -234,6 +248,30 @@ public class KeyStoreTest extends TestCase { } } + private static boolean clearKeyStore(KeyStore ks) throws Exception { + ks.load(null, null); + if (isReadOnly(ks)) { + try { + setPrivateKey(ks); + fail(ks.toString()); + } catch (UnsupportedOperationException e) { + } + return true; + } + if (isPersistentStorage(ks)) { + Enumeration<String> aliases = ks.aliases(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + ks.deleteEntry(alias); + } + } + return false; + } + + public static void setPrivateKeyNoPassword(KeyStore ks, String alias, PrivateKeyEntry privateKey) + throws Exception { + ks.setKeyEntry(alias, privateKey.getPrivateKey(), null, privateKey.getCertificateChain()); + } public static void setPrivateKey(KeyStore ks) throws Exception { setPrivateKey(ks, ALIAS_PRIVATE); } @@ -492,7 +530,12 @@ public class KeyStoreTest extends TestCase { if (isReadOnly(keyStore)) { assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); } else { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); } else { @@ -503,21 +546,27 @@ public class KeyStoreTest extends TestCase { // test case insensitive if (isCaseSensitive(keyStore) || isReadOnly(keyStore)) { assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, PASSWORD_KEY)); assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } else { - assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } } // test with null passwords - if (isKeyPasswordIgnored(keyStore)) { + if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) { assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null)); } else { if (isReadOnly(keyStore)) { assertNull(keyStore.getKey(ALIAS_PRIVATE, null)); - } else { + } else if (isKeyPasswordSupported(keyStore)) { try { keyStore.getKey(ALIAS_PRIVATE, null); fail(keyStore.getType()); @@ -546,9 +595,9 @@ public class KeyStoreTest extends TestCase { // test with bad passwords if (isReadOnly(keyStore)) { assertNull(keyStore.getKey(ALIAS_PRIVATE, null)); - } else if (isKeyPasswordIgnored(keyStore)) { + } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) { assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null)); - } else { + } else if (isKeyPasswordSupported(keyStore)) { try { keyStore.getKey(ALIAS_PRIVATE, PASSWORD_BAD); fail(keyStore.getType()); @@ -593,8 +642,10 @@ public class KeyStoreTest extends TestCase { // test case sensitive if (isReadOnly(keyStore)) { assertNull(keyStore.getCertificateChain(ALIAS_PRIVATE)); - } else { + } else if (isKeyPasswordSupported(keyStore)) { assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + } else if (isNullPasswordAllowed(keyStore)) { + assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE)); } // test case insensitive @@ -657,9 +708,10 @@ public class KeyStoreTest extends TestCase { } long before = System.currentTimeMillis(); for (KeyStore keyStore : keyStores()) { + populate(keyStore); + // add 1000 since some key stores round of time to nearest second long after = System.currentTimeMillis() + 1000; - populate(keyStore); // test odd inputs try { @@ -673,8 +725,10 @@ public class KeyStoreTest extends TestCase { if (!isReadOnly(keyStore) && isCertificateEnabled(keyStore)) { Date date = keyStore.getCreationDate(ALIAS_CERTIFICATE); assertNotNull(date); - assertTrue(before <= date.getTime()); - assertTrue(date.getTime() <= after); + assertTrue("date should be after start time: " + date.getTime() + " >= " + before, + before <= date.getTime()); + assertTrue("date should be before expiry time: " + date.getTime() + " <= " + after, + date.getTime() <= after); } else { assertNull(keyStore.getCreationDate(ALIAS_CERTIFICATE)); } @@ -737,15 +791,24 @@ public class KeyStoreTest extends TestCase { PASSWORD_KEY, null); fail(keyStore.getType()); - } catch (IllegalArgumentException expected) { + } catch (Exception e) { + if (e.getClass() != IllegalArgumentException.class + && e.getClass() != KeyStoreException.class) { + throw e; + } } } for (KeyStore keyStore : keyStores()) { - keyStore.load(null, null); + clearKeyStore(keyStore); // test case sensitive - assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isReadOnly(keyStore)) { try { keyStore.setKeyEntry(ALIAS_SECRET, getSecretKey(), PASSWORD_KEY, null); @@ -754,9 +817,16 @@ public class KeyStoreTest extends TestCase { } continue; } - setPrivateKey(keyStore); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + setPrivateKey(keyStore); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + setPrivateKeyNoPassword(keyStore, ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey()); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE)); + } if (isSecretKeyEnabled(keyStore)) { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); setSecretKey(keyStore); @@ -783,11 +853,22 @@ public class KeyStoreTest extends TestCase { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } else if (isCaseSensitive(keyStore)) { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, + getPrivateKey2()); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -797,11 +878,22 @@ public class KeyStoreTest extends TestCase { assertSecretKey2(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } } else { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); - assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + setPrivateKey(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); + assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, null)); + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + setPrivateKey(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, getPrivateKey2()); + assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, null)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } + if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); assertSecretKey(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); @@ -913,10 +1005,15 @@ public class KeyStoreTest extends TestCase { continue; } - keyStore.load(null, null); + clearKeyStore(keyStore); // test case sensitive - assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isReadOnly(keyStore)) { try { setPrivateKeyBytes(keyStore); @@ -925,9 +1022,16 @@ public class KeyStoreTest extends TestCase { } continue; } - setPrivateKeyBytes(keyStore); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + setPrivateKeyBytes(keyStore); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + setPrivateKeyNoPassword(keyStore, ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey()); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE)); + } if (isSecretKeyEnabled(keyStore)) { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); setSecretKeyBytes(keyStore); @@ -959,11 +1063,21 @@ public class KeyStoreTest extends TestCase { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } else if (isCaseSensitive(keyStore)) { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, + getPrivateKey2()); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -973,11 +1087,21 @@ public class KeyStoreTest extends TestCase { assertSecretKey2(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } } else { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); - assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + setPrivateKeyBytes(keyStore, ALIAS_ALT_CASE_PRIVATE, getPrivateKey2()); + assertPrivateKey2(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertPrivateKey(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + setPrivateKeyNoPassword(keyStore, ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, + getPrivateKey2()); + assertPrivateKey2(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -1031,13 +1155,17 @@ public class KeyStoreTest extends TestCase { try { int size = keyStore.size(); keyStore.setCertificateEntry(ALIAS_CERTIFICATE, null); - assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE)); - assertEquals(size, keyStore.size()); - assertTrue(keyStore.isCertificateEntry(ALIAS_CERTIFICATE)); - assertTrue(Collections.list(keyStore.aliases()).contains(ALIAS_CERTIFICATE)); + assertNull(keyStore.getType(), keyStore.getCertificate(ALIAS_CERTIFICATE)); + assertEquals(keyStore.getType(), size, keyStore.size()); + assertTrue(keyStore.getType(), keyStore.isCertificateEntry(ALIAS_CERTIFICATE)); + assertTrue(keyStore.getType(), + Collections.list(keyStore.aliases()).contains(ALIAS_CERTIFICATE)); } catch (NullPointerException expectedSometimes) { - assertEquals("PKCS12", keyStore.getType()); - assertEquals("BC", keyStore.getProvider().getName()); + if (!("PKCS12".equalsIgnoreCase(keyStore.getType()) && + "BC".equalsIgnoreCase(keyStore.getProvider().getName())) + && !"AndroidKeyStore".equalsIgnoreCase(keyStore.getType())) { + throw expectedSometimes; + } } } else { try { @@ -1053,9 +1181,8 @@ public class KeyStoreTest extends TestCase { continue; } - keyStore.load(null, null); + clearKeyStore(keyStore); - // test case sensitive assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE)); if (isReadOnly(keyStore)) { try { @@ -1077,10 +1204,8 @@ public class KeyStoreTest extends TestCase { populate(keyStore); if (isReadOnly(keyStore)) { - assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); + assertNull(keyStore.getCertificate(ALIAS_CERTIFICATE)); + assertNull(keyStore.getCertificate(ALIAS_ALT_CASE_CERTIFICATE)); } else if (isCaseSensitive(keyStore)) { assertCertificate(keyStore.getCertificate(ALIAS_CERTIFICATE)); assertNull(keyStore.getCertificate(ALIAS_ALT_CASE_CERTIFICATE)); @@ -1146,10 +1271,18 @@ public class KeyStoreTest extends TestCase { } // test case sensitive - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); - keyStore.deleteEntry(ALIAS_PRIVATE); - assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + keyStore.deleteEntry(ALIAS_PRIVATE); + assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE)); + keyStore.deleteEntry(ALIAS_NO_PASSWORD_PRIVATE); + assertNull(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -1174,9 +1307,16 @@ public class KeyStoreTest extends TestCase { // test case insensitive if (isCaseSensitive(keyStore)) { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - keyStore.deleteEntry(ALIAS_ALT_CASE_PRIVATE); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + keyStore.deleteEntry(ALIAS_ALT_CASE_PRIVATE); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + } + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + keyStore.deleteEntry(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -1208,10 +1348,14 @@ public class KeyStoreTest extends TestCase { for (KeyStore keyStore : keyStores()) { keyStore.load(null, null); - if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.aliases().hasMoreElements()); + if (isPersistentStorage(keyStore)) { + assertNotNull("Should be able to query size: " + keyStore.getType(), + keyStore.aliases()); + } else if (hasDefaultContents(keyStore)) { + assertTrue("Should have more than one alias already: " + keyStore.getType(), + keyStore.aliases().hasMoreElements()); } else { - assertEquals(Collections.EMPTY_SET, + assertEquals("Should have no aliases:" + keyStore.getType(), Collections.EMPTY_SET, new HashSet(Collections.list(keyStore.aliases()))); } } @@ -1220,7 +1364,9 @@ public class KeyStoreTest extends TestCase { populate(keyStore); Set<String> expected = new HashSet<String>(); - expected.add(ALIAS_PRIVATE); + if (isKeyPasswordSupported(keyStore)) { + expected.add(ALIAS_PRIVATE); + } if (isNullPasswordAllowed(keyStore)) { expected.add(ALIAS_NO_PASSWORD_PRIVATE); } @@ -1233,7 +1379,10 @@ public class KeyStoreTest extends TestCase { if (isCertificateEnabled(keyStore)) { expected.add(ALIAS_CERTIFICATE); } - if (hasDefaultContents(keyStore)) { + if (isPersistentStorage(keyStore)) { + assertNotNull("Should be able to query size: " + keyStore.getType(), + keyStore.aliases()); + } else if (hasDefaultContents(keyStore)) { assertTrue(keyStore.aliases().hasMoreElements()); } else { assertEquals(expected, new HashSet<String>(Collections.list(keyStore.aliases()))); @@ -1271,7 +1420,11 @@ public class KeyStoreTest extends TestCase { assertFalse(keyStore.containsAlias(ALIAS_PRIVATE)); continue; } - assertTrue(keyStore.containsAlias(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + assertTrue(keyStore.containsAlias(ALIAS_PRIVATE)); + } else if (isNullPasswordAllowed(keyStore)) { + assertTrue(keyStore.containsAlias(ALIAS_NO_PASSWORD_PRIVATE)); + } assertEquals(isSecretKeyEnabled(keyStore), keyStore.containsAlias(ALIAS_SECRET)); assertEquals(isCertificateEnabled(keyStore), keyStore.containsAlias(ALIAS_CERTIFICATE)); @@ -1295,21 +1448,29 @@ public class KeyStoreTest extends TestCase { for (KeyStore keyStore : keyStores()) { keyStore.load(null, null); - if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.size() > 0); + if (isPersistentStorage(keyStore)) { + assertTrue("Should successfully query size: " + keyStore.getType(), + keyStore.size() >= 0); + } else if (hasDefaultContents(keyStore)) { + assertTrue("Should have non-empty store: " + keyStore.getType(), + keyStore.size() > 0); } else { - assertEquals(0, keyStore.size()); + assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size()); } } for (KeyStore keyStore : keyStores()) { populate(keyStore); if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.size() > 0); + assertTrue("Should have non-empty store: " + keyStore.getType(), + keyStore.size() > 0); continue; } - int expected = 1; + int expected = 0; + if (isKeyPasswordSupported(keyStore)) { + expected++; + } if (isNullPasswordAllowed(keyStore)) { expected++; } @@ -1355,7 +1516,12 @@ public class KeyStoreTest extends TestCase { assertFalse(keyStore.isKeyEntry(ALIAS_PRIVATE)); continue; } - assertTrue(keyStore.isKeyEntry(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + assertTrue(keyStore.isKeyEntry(ALIAS_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + assertTrue(keyStore.isKeyEntry(ALIAS_NO_PASSWORD_PRIVATE)); + } assertEquals(isSecretKeyEnabled(keyStore), keyStore.isKeyEntry(ALIAS_SECRET)); assertFalse(keyStore.isKeyEntry(ALIAS_CERTIFICATE)); @@ -1397,7 +1563,12 @@ public class KeyStoreTest extends TestCase { assertFalse(keyStore.isCertificateEntry("")); - assertFalse(keyStore.isCertificateEntry(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + assertFalse(keyStore.isCertificateEntry(ALIAS_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + assertFalse(keyStore.isCertificateEntry(ALIAS_NO_PASSWORD_PRIVATE)); + } assertFalse(keyStore.isCertificateEntry(ALIAS_SECRET)); assertEquals(isCertificateEnabled(keyStore) && !isReadOnly(keyStore), keyStore.isCertificateEntry(ALIAS_CERTIFICATE)); @@ -1429,7 +1600,9 @@ public class KeyStoreTest extends TestCase { populate(keyStore); Set<String> expected = new HashSet<String>(); - expected.add(ALIAS_PRIVATE); + if (isKeyPasswordSupported(keyStore)) { + expected.add(ALIAS_PRIVATE); + } if (isNullPasswordAllowed(keyStore)) { expected.add(ALIAS_NO_PASSWORD_PRIVATE); } @@ -1487,7 +1660,7 @@ public class KeyStoreTest extends TestCase { for (KeyStore keyStore : keyStores()) { keyStore.load(null, null); ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (isReadOnly(keyStore)) { + if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) { try { keyStore.store(out, null); fail(keyStore.getType()); @@ -1517,11 +1690,11 @@ public class KeyStoreTest extends TestCase { populate(keyStore); ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (isReadOnly(keyStore)) { + if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) { try { keyStore.store(out, null); fail(keyStore.getType()); - } catch (UnsupportedOperationException e) { + } catch (UnsupportedOperationException expected) { } } else if (isNullPasswordAllowed(keyStore)) { keyStore.store(out, null); @@ -1542,7 +1715,7 @@ public class KeyStoreTest extends TestCase { for (KeyStore keyStore : keyStores()) { keyStore.load(null, null); ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (isReadOnly(keyStore)) { + if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) { try { keyStore.store(out, PASSWORD_STORE); fail(keyStore.getType()); @@ -1557,7 +1730,7 @@ public class KeyStoreTest extends TestCase { for (KeyStore keyStore : keyStores()) { populate(keyStore); ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (isReadOnly(keyStore)) { + if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) { try { keyStore.store(out, PASSWORD_STORE); fail(keyStore.getType()); @@ -1596,19 +1769,30 @@ public class KeyStoreTest extends TestCase { public void test_KeyStore_load_InputStream() throws Exception { for (KeyStore keyStore : keyStores()) { keyStore.load(null, null); - if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.size() > 0); + if (isPersistentStorage(keyStore)) { + assertTrue("Should be able to query size: " + keyStore.getType(), + keyStore.size() >= 0); + } else if (hasDefaultContents(keyStore)) { + assertTrue("Should have non-empty store: " + keyStore.getType(), + keyStore.size() > 0); } else { - assertEquals(0, keyStore.size()); + assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size()); } } for (KeyStore keyStore : keyStores()) { + if (isLoadStoreUnsupported(keyStore)) { + continue; + } keyStore.load(null, PASSWORD_STORE); - if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.size() > 0); + if (isPersistentStorage(keyStore)) { + assertTrue("Should be able to query size: " + keyStore.getType(), + keyStore.size() >= 0); + } else if (hasDefaultContents(keyStore)) { + assertTrue("Should have non-empty store: " + keyStore.getType(), + keyStore.size() > 0); } else { - assertEquals(0, keyStore.size()); + assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size()); } } @@ -1618,10 +1802,14 @@ public class KeyStoreTest extends TestCase { public void test_KeyStore_load_LoadStoreParameter() throws Exception { for (KeyStore keyStore : keyStores()) { keyStore.load(null); - if (hasDefaultContents(keyStore)) { - assertTrue(keyStore.size() > 0); + if (isPersistentStorage(keyStore)) { + assertTrue("Should be able to query size: " + keyStore.getType(), + keyStore.size() >= 0); + } else if (hasDefaultContents(keyStore)) { + assertTrue("Should have non-empty store: " + keyStore.getType(), + keyStore.size() > 0); } else { - assertEquals(0, keyStore.size()); + assertEquals("Should have empty store: " + keyStore.getType(), 0, keyStore.size()); } } @@ -1668,7 +1856,11 @@ public class KeyStoreTest extends TestCase { if (isReadOnly(keyStore)) { assertNull(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY)); } else { - assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_KEY)); + } else if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getEntry(ALIAS_SECRET, PARAM_KEY)); } else { @@ -1704,9 +1896,9 @@ public class KeyStoreTest extends TestCase { assertNull(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null)); } else if (isNullPasswordAllowed(keyStore)) { assertPrivateKey(keyStore.getEntry(ALIAS_NO_PASSWORD_PRIVATE, null)); - } else if (isKeyPasswordIgnored(keyStore)) { + } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) { assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, null)); - } else { + } else if (isKeyPasswordIgnored(keyStore)) { try { keyStore.getEntry(ALIAS_PRIVATE, null); fail(keyStore.getType()); @@ -1734,9 +1926,9 @@ public class KeyStoreTest extends TestCase { // test with bad passwords if (isReadOnly(keyStore)) { assertNull(keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD)); - } else if (isKeyPasswordIgnored(keyStore)) { + } else if (isKeyPasswordSupported(keyStore) && isKeyPasswordIgnored(keyStore)) { assertPrivateKey(keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD)); - } else { + } else if (isKeyPasswordSupported(keyStore)) { try { keyStore.getEntry(ALIAS_PRIVATE, PARAM_BAD); fail(keyStore.getType()); @@ -1773,7 +1965,7 @@ public class KeyStoreTest extends TestCase { try { keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), new FakeProtectionParameter()); - fail("Should not accept unknown ProtectionParameter"); + fail("Should not accept unknown ProtectionParameter: " + keyStore.getProvider()); } catch (KeyStoreException expected) { } } @@ -1808,7 +2000,7 @@ public class KeyStoreTest extends TestCase { } for (KeyStore keyStore : keyStores()) { - keyStore.load(null, null); + clearKeyStore(keyStore); // test case sensitive assertNull(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); @@ -1820,9 +2012,16 @@ public class KeyStoreTest extends TestCase { } continue; } - keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), PARAM_KEY); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + keyStore.setEntry(ALIAS_PRIVATE, getPrivateKey(), PARAM_KEY); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + keyStore.setEntry(ALIAS_NO_PASSWORD_PRIVATE, getPrivateKey(), null); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_NO_PASSWORD_PRIVATE)); + } if (isSecretKeyEnabled(keyStore)) { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); keyStore.setEntry(ALIAS_SECRET, new SecretKeyEntry(getSecretKey()), PARAM_KEY); @@ -1849,9 +2048,17 @@ public class KeyStoreTest extends TestCase { } catch (KeyStoreException expected) { } } - keyStore.setEntry(ALIAS_UNICODE_PRIVATE, getPrivateKey(), PARAM_KEY); - assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_PRIVATE, PASSWORD_KEY)); - assertCertificateChain(keyStore.getCertificateChain(ALIAS_UNICODE_PRIVATE)); + if (isKeyPasswordSupported(keyStore)) { + keyStore.setEntry(ALIAS_UNICODE_PRIVATE, getPrivateKey(), PARAM_KEY); + assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_PRIVATE, PASSWORD_KEY)); + assertCertificateChain(keyStore.getCertificateChain(ALIAS_UNICODE_PRIVATE)); + } + if (isNullPasswordAllowed(keyStore)) { + keyStore.setEntry(ALIAS_UNICODE_NO_PASSWORD_PRIVATE, getPrivateKey(), null); + assertPrivateKey(keyStore.getKey(ALIAS_UNICODE_NO_PASSWORD_PRIVATE, null)); + assertCertificateChain(keyStore + .getCertificateChain(ALIAS_UNICODE_NO_PASSWORD_PRIVATE)); + } if (isSecretKeyEnabled(keyStore)) { assertNull(keyStore.getKey(ALIAS_UNICODE_SECRET, PASSWORD_KEY)); keyStore.setEntry(ALIAS_UNICODE_SECRET, new SecretKeyEntry(getSecretKey()), PARAM_KEY); @@ -1874,11 +2081,21 @@ public class KeyStoreTest extends TestCase { assertNull(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); assertNull(keyStore.getKey(ALIAS_ALT_CASE_SECRET, PASSWORD_KEY)); } else if (isCaseSensitive(keyStore)) { - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); - keyStore.setEntry(ALIAS_ALT_CASE_PRIVATE, getPrivateKey2(), PARAM_KEY); - assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); - assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + if (isKeyPasswordSupported(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + keyStore.setEntry(ALIAS_ALT_CASE_PRIVATE, getPrivateKey2(), PARAM_KEY); + assertPrivateKey(keyStore.getKey(ALIAS_PRIVATE, PASSWORD_KEY)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_PRIVATE, PASSWORD_KEY)); + } + + if (isNullPasswordAllowed(keyStore)) { + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertNull(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + keyStore.setEntry(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, getPrivateKey2(), null); + assertPrivateKey(keyStore.getKey(ALIAS_NO_PASSWORD_PRIVATE, null)); + assertPrivateKey2(keyStore.getKey(ALIAS_ALT_CASE_NO_PASSWORD_PRIVATE, null)); + } if (isSecretKeyEnabled(keyStore)) { assertSecretKey(keyStore.getKey(ALIAS_SECRET, PASSWORD_KEY)); @@ -2073,10 +2290,17 @@ public class KeyStoreTest extends TestCase { } // test case sensitive - assertTrue(keyStore.entryInstanceOf(ALIAS_PRIVATE, PrivateKeyEntry.class)); + assertEquals(isKeyPasswordSupported(keyStore), + keyStore.entryInstanceOf(ALIAS_PRIVATE, PrivateKeyEntry.class)); assertFalse(keyStore.entryInstanceOf(ALIAS_PRIVATE, SecretKeyEntry.class)); assertFalse(keyStore.entryInstanceOf(ALIAS_PRIVATE, TrustedCertificateEntry.class)); + assertEquals(isNullPasswordAllowed(keyStore), + keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE, PrivateKeyEntry.class)); + assertFalse(keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE, SecretKeyEntry.class)); + assertFalse(keyStore.entryInstanceOf(ALIAS_NO_PASSWORD_PRIVATE, + TrustedCertificateEntry.class)); + assertEquals(isSecretKeyEnabled(keyStore), keyStore.entryInstanceOf(ALIAS_SECRET, SecretKeyEntry.class)); assertFalse(keyStore.entryInstanceOf(ALIAS_SECRET, PrivateKeyEntry.class)); @@ -2173,7 +2397,7 @@ public class KeyStoreTest extends TestCase { OutputStream os = null; try { os = new FileOutputStream(file); - if (isReadOnly(keyStore)) { + if (isLoadStoreUnsupported(keyStore) || isReadOnly(keyStore)) { try { keyStore.store(os, PASSWORD_STORE); fail(keyStore.getType()); @@ -2204,6 +2428,9 @@ public class KeyStoreTest extends TestCase { } for (KeyStore keyStore : keyStores()) { + if (isLoadStoreUnsupported(keyStore)) { + continue; + } Builder builder = Builder.newInstance(keyStore.getType(), keyStore.getProvider(), PARAM_STORE); 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 8c9239e..4095081 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -19,6 +19,7 @@ package libcore.javax.net.ssl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; @@ -1012,7 +1013,7 @@ public class SSLSocketTest extends TestCase { assertEquals(0, wrapping.getSoTimeout()); // setting wrapper sets underlying and ... - int expectedTimeoutMillis = 1000; // Using a small value such as 10 was affected by rounding + int expectedTimeoutMillis = 1000; // 10 was too small because it was affected by rounding wrapping.setSoTimeout(expectedTimeoutMillis); assertEquals(expectedTimeoutMillis, wrapping.getSoTimeout()); assertEquals(expectedTimeoutMillis, underlying.getSoTimeout()); @@ -1050,6 +1051,52 @@ public class SSLSocketTest extends TestCase { listening.close(); } + public void test_SSLSocket_setSoWriteTimeout() throws Exception { + if (StandardNames.IS_RI) { + // RI does not support write timeout on sockets + return; + } + + final TestSSLContext c = TestSSLContext.create(); + SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, + c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future<Void> future = executor.submit(new Callable<Void>() { + @Override public Void call() throws Exception { + server.startHandshake(); + return null; + } + }); + executor.shutdown(); + client.startHandshake(); + + // Reflection is used so this can compile on the RI + String expectedClassName = "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"; + Class actualClass = client.getClass(); + assertEquals(expectedClassName, actualClass.getName()); + Method setSoWriteTimeout = actualClass.getMethod("setSoWriteTimeout", + new Class[] { Integer.TYPE }); + setSoWriteTimeout.invoke(client, 1); + + // Try to make the size smaller (it can be 512k or even megabytes). + // Note that it may not respect your request, so read back the actual value. + int sendBufferSize = 1024; + client.setSendBufferSize(sendBufferSize); + sendBufferSize = client.getSendBufferSize(); + + try { + client.getOutputStream().write(new byte[sendBufferSize + 1]); + fail(); + } catch (SocketTimeoutException expected) { + } + + future.get(); + client.close(); + server.close(); + c.close(); + } + public void test_SSLSocket_interrupt() throws Exception { ServerSocket listening = new ServerSocket(0); diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java index 7442210..c07b180 100644 --- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java +++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/CipherTest.java @@ -47,6 +47,7 @@ import javax.crypto.ShortBufferException; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import libcore.java.security.StandardNames; import org.apache.harmony.crypto.tests.support.MyCipher; import tests.support.resource.Support_Resources; @@ -545,7 +546,10 @@ public class CipherTest extends junit.framework.TestCase { try { c.doFinal(b, 3, 6, b1, 5); fail(); - } catch (ShortBufferException expected) { + } catch (IllegalBlockSizeException maybeExpected) { + assertTrue(StandardNames.IS_RI); + } catch (ShortBufferException maybeExpected) { + assertFalse(StandardNames.IS_RI); } } diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java index 0dad827..a6529e8 100644 --- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java +++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyRepTest.java @@ -26,199 +26,146 @@ import java.io.NotSerializableException; import java.io.ObjectStreamException; import java.security.KeyRep; import java.security.Security; -import java.util.Iterator; import java.util.Set; - import junit.framework.TestCase; -/** - * - * - */ public class KeyRepTest extends TestCase { - private static final Set<String> keyFactoryAlgorithm; + private static final Set<String> keyFactoryAlgorithms = Security.getAlgorithms("KeyFactory"); static { - keyFactoryAlgorithm = Security.getAlgorithms("KeyFactory"); + assertFalse(keyFactoryAlgorithms.isEmpty()); } public final void testKeyRep01() { - try { - assertNotNull(new KeyRep(KeyRep.Type.SECRET, "", "", new byte[] {})); - } catch (Exception e) { - fail("Unexpected exception " + e.getMessage()); - } - - try { - assertNotNull(new KeyRep(KeyRep.Type.PUBLIC, "", "", new byte[] {})); - } catch (Exception e) { - fail("Unexpected exception " + e.getMessage()); - } - - try { - assertNotNull(new KeyRep(KeyRep.Type.PRIVATE, "", "", new byte[] {})); - } catch (Exception e) { - fail("Unexpected exception " + e.getMessage()); - } + assertNotNull(new KeyRep(KeyRep.Type.SECRET, "", "", new byte[] {})); + assertNotNull(new KeyRep(KeyRep.Type.PUBLIC, "", "", new byte[] {})); + assertNotNull(new KeyRep(KeyRep.Type.PRIVATE, "", "", new byte[] {})); } public final void testKeyRep02() { try { new KeyRep(null, "", "", new byte[] {}); fail("NullPointerException has not been thrown (type)"); - } catch (NullPointerException ok) { - + } catch (NullPointerException expected) { } try { new KeyRep(KeyRep.Type.SECRET, null, "", new byte[] {}); fail("NullPointerException has not been thrown (alg)"); - } catch (NullPointerException ok) { - + } catch (NullPointerException expected) { } try { new KeyRep(KeyRep.Type.PRIVATE, "", null, new byte[] {}); fail("NullPointerException has not been thrown (format)"); - } catch (NullPointerException ok) { - + } catch (NullPointerException expected) { } try { new KeyRep(KeyRep.Type.PUBLIC, "", "", null); fail("NullPointerException has not been thrown (encoding)"); - } catch (NullPointerException ok) { - + } catch (NullPointerException expected) { } } - public final void testReadResolve01() throws ObjectStreamException { - KeyRepChild kr = new KeyRepChild(KeyRep.Type.SECRET, "", "", - new byte[] {}); + public final void testReadResolve01() throws Exception { + KeyRepChild kr = new KeyRepChild(KeyRep.Type.SECRET, "", "", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (no format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } kr = new KeyRepChild(KeyRep.Type.SECRET, "", "X.509", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (unacceptable format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } kr = new KeyRepChild(KeyRep.Type.SECRET, "", "RAW", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (empty key)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } } - public final void testReadResolve02() throws ObjectStreamException { - KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "", - new byte[] {}); + public final void testReadResolve02() throws Exception { + KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (no format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } kr = new KeyRepChild(KeyRep.Type.PUBLIC, "", "RAW", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (unacceptable format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } - kr = new KeyRepChild(KeyRep.Type.PUBLIC, "bla-bla", "X.509", - new byte[] {}); + kr = new KeyRepChild(KeyRep.Type.PUBLIC, "bla-bla", "X.509", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (unknown KeyFactory algorithm)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } } - public final void testReadResolve03() throws ObjectStreamException { - KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "", - new byte[] {}); + public final void testReadResolve03() throws Exception { + KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (no format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } kr = new KeyRepChild(KeyRep.Type.PRIVATE, "", "RAW", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (unacceptable format)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } - kr = new KeyRepChild(KeyRep.Type.PRIVATE, "bla-bla", "PKCS#8", - new byte[] {}); + kr = new KeyRepChild(KeyRep.Type.PRIVATE, "bla-bla", "PKCS#8", new byte[] {}); try { kr.readResolve(); fail("NotSerializableException has not been thrown (unknown KeyFactory algorithm)"); - } catch (NotSerializableException ok) { - + } catch (NotSerializableException expected) { } } - public final void testReadResolve04() throws ObjectStreamException { - if (keyFactoryAlgorithm.isEmpty()) { - System.err.println(getName() - + ": skipped - no KeyFactory algorithms available"); - return; - } else { - } - for (Iterator<String> i = keyFactoryAlgorithm.iterator(); i.hasNext();) { - KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, i.next(), - "X.509", new byte[] { 1, 2, 3 }); + public final void testReadResolve04() throws Exception { + for (String algorithm : keyFactoryAlgorithms) { + KeyRepChild kr = new KeyRepChild(KeyRep.Type.PUBLIC, algorithm, "X.509", + new byte[] { 1, 2, 3 }); try { kr.readResolve(); - fail("NotSerializableException has not been thrown (no format)"); - } catch (NotSerializableException ok) { - + fail("NotSerializableException has not been thrown (no format) " + algorithm); + } catch (NotSerializableException expected) { } } } - public final void testReadResolve05() throws ObjectStreamException { - if (keyFactoryAlgorithm.isEmpty()) { - System.err.println(getName() - + ": skipped - no KeyFactory algorithms available"); - return; - } else { - } - for (Iterator<String> i = keyFactoryAlgorithm.iterator(); i.hasNext();) { - KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, i.next(), - "PKCS#8", new byte[] { 1, 2, 3 }); + public final void testReadResolve05() throws Exception { + for (String algorithm : keyFactoryAlgorithms) { + KeyRepChild kr = new KeyRepChild(KeyRep.Type.PRIVATE, algorithm, "PKCS#8", + new byte[] { 1, 2, 3 }); try { kr.readResolve(); - fail("NotSerializableException has not been thrown (no format)"); - } catch (NotSerializableException ok) { - + fail("NotSerializableException has not been thrown (no format) " + algorithm); + } catch (NotSerializableException expected) { } } } class KeyRepChild extends KeyRep { - public KeyRepChild(KeyRep.Type type, String algorithm, String format, - byte[] encoded) { + public KeyRepChild(KeyRep.Type type, String algorithm, String format, byte[] encoded) { super(type, algorithm, format, encoded); } - public Object readResolve() throws ObjectStreamException { + // Overriden to make public for testing + @Override public Object readResolve() throws ObjectStreamException { return super.readResolve(); } - } } diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java deleted file mode 100644 index 81ec30d..0000000 --- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/KeyStoreLoadStoreParameterTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.apache.harmony.security.tests.java.security; - -import java.security.KeyStore; - -public class KeyStoreLoadStoreParameterTest { - - class MyLoadStoreParameter implements KeyStore.LoadStoreParameter { - public KeyStore.ProtectionParameter getProtectionParameter() { - return null; - } - } - - - -} diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java index d9f4dd7..68e7cbc 100644 --- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java +++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Security2Test.java @@ -20,14 +20,14 @@ package org.apache.harmony.security.tests.java.security; import java.security.InvalidParameterException; import java.security.Provider; import java.security.Security; -import java.util.Hashtable; -import java.util.Iterator; +import java.util.HashMap; import java.util.Map; import java.util.Set; +import junit.framework.TestCase; import tests.support.Support_ProviderTrust; import tests.support.Support_TestProvider; -public class Security2Test extends junit.framework.TestCase { +public class Security2Test extends TestCase { /** * java.security.Security#getProviders(java.lang.String) @@ -36,16 +36,13 @@ public class Security2Test extends junit.framework.TestCase { // Test for method void // java.security.Security.getProviders(java.lang.String) - Hashtable<String, Integer> allSupported = new Hashtable<String, Integer>(); + Map<String, Integer> allSupported = new HashMap<String, Integer>(); Provider[] allProviders = Security.getProviders(); // Add all non-alias entries to allSupported - for (int i = 0; i < allProviders.length; i++) { - Provider provider = allProviders[i]; - Iterator it = provider.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - String key = (String) entry.getKey(); + for (Provider provider : allProviders) { + for (Object k : provider.keySet()) { + String key = (String) k; // No aliases and no provider data if (!isAlias(key) && !isProviderData(key)) { addOrIncrementTable(allSupported, key); @@ -56,22 +53,18 @@ public class Security2Test extends junit.framework.TestCase { // Now walk through aliases. If an alias has actually been added // to the allSupported table then increment the count of the // entry that is being aliased. - for (int i = 0; i < allProviders.length; i++) { - Provider provider = allProviders[i]; - Iterator it = provider.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); + for (Provider provider : allProviders) { + for (Map.Entry entry : provider.entrySet()) { String key = (String) entry.getKey(); if (isAlias(key)) { String aliasVal = key.substring("ALG.ALIAS.".length()); - String aliasKey = aliasVal.substring(0, aliasVal - .indexOf(".") + 1) + String aliasKey = aliasVal.substring(0, aliasVal.indexOf(".") + 1) + entry.getValue(); // Skip over nonsense alias declarations where alias and // aliased are identical. Such entries can occur. - if (!aliasVal.equals(aliasKey)) { + if (!aliasVal.equalsIgnoreCase(aliasKey)) { // Has a real entry been added for aliasValue ? - if (allSupported.containsKey(aliasVal)) { + if (allSupported.containsKey(aliasVal.toUpperCase())) { // Add 1 to the provider count of the thing being // aliased addOrIncrementTable(allSupported, aliasKey); @@ -81,17 +74,13 @@ public class Security2Test extends junit.framework.TestCase { }// end while more entries }// end for all providers - Provider provTest[] = null; - Iterator it = allSupported.keySet().iterator(); - while (it.hasNext()) { - String filterString = (String) it.next(); + for (String filterString : allSupported.keySet()) { try { - provTest = Security.getProviders(filterString); - int expected = ((Integer) allSupported.get(filterString)) - .intValue(); - assertEquals( - "Unexpected number of providers returned for filter " - + filterString, expected, provTest.length); + Provider[] provTest = Security.getProviders(filterString); + int expected = allSupported.get(filterString); + assertEquals("Unexpected number of providers returned for filter " + filterString + + ":\n" + allSupported, + expected, provTest.length); } catch (InvalidParameterException e) { // NO OP } @@ -99,62 +88,43 @@ public class Security2Test extends junit.framework.TestCase { // exception try { - provTest = Security.getProviders("Signature.SHA1withDSA :512"); + Security.getProviders("Signature.SHA1withDSA :512"); fail("InvalidParameterException should be thrown <Signature.SHA1withDSA :512>"); } catch (InvalidParameterException e) { // Expected } } - /** - * @param key - * @return - */ private boolean isProviderData(String key) { return key.toUpperCase().startsWith("PROVIDER."); } - /** - * @param key - * @return - */ private boolean isAlias(String key) { return key.toUpperCase().startsWith("ALG.ALIAS."); } - /** - * @param table - * @param key - */ - private void addOrIncrementTable(Hashtable<String, Integer> table, String key) { + private void addOrIncrementTable(Map<String, Integer> table, String k) { + String key = k.toUpperCase(); if (table.containsKey(key)) { - Integer before = (Integer) table.get(key); - table.put(key, new Integer(before.intValue() + 1)); + int before = table.get(key); + table.put(key, before + 1); } else { - table.put(key, new Integer(1)); + table.put(key, 1); } } - /** - * @param filterMap - * @return - */ private int getProvidersCount(Map filterMap) { int result = 0; Provider[] allProviders = Security.getProviders(); // for each provider - for (int i = 0; i < allProviders.length; i++) { - Provider provider = allProviders[i]; + for (Provider provider : allProviders) { Set allProviderKeys = provider.keySet(); boolean noMatchFoundForFilterEntry = false; // for each filter item - Set allFilterKeys = filterMap.keySet(); - Iterator fkIter = allFilterKeys.iterator(); - while (fkIter.hasNext()) { - String filterString = ((String) fkIter.next()).trim(); - + for (Object filter : filterMap.keySet()) { + String filterString = (String) filter; // Remove any "=" characters that may be on the end of the // map keys (no, I don't know why they might be there either // but I have seen them) @@ -211,10 +181,10 @@ public class Security2Test extends junit.framework.TestCase { // Test for method void // java.security.Security.getProviders(java.util.Map) - Map<String, String> filter = new Hashtable<String, String>(); + Map<String, String> filter = new HashMap<String, String>(); filter.put("KeyStore.BKS", ""); filter.put("Signature.SHA1withDSA", ""); - Provider provTest[] = Security.getProviders(filter); + Provider[] provTest = Security.getProviders(filter); if (provTest == null) { assertEquals("Filter : <KeyStore.BKS>,<Signature.SHA1withDSA>", 0, getProvidersCount(filter)); @@ -223,7 +193,7 @@ public class Security2Test extends junit.framework.TestCase { getProvidersCount(filter), provTest.length); } - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("MessageDigest.SHA-384", ""); filter.put("CertificateFactory.X.509", ""); filter.put("KeyFactory.RSA", ""); @@ -237,7 +207,7 @@ public class Security2Test extends junit.framework.TestCase { getProvidersCount(filter), provTest.length); } - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("MessageDigest.SHA1", ""); filter.put("TrustManagerFactory.X509", ""); provTest = Security.getProviders(filter); @@ -250,7 +220,7 @@ public class Security2Test extends junit.framework.TestCase { getProvidersCount(filter), provTest.length); } - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("CertificateFactory.X509", ""); provTest = Security.getProviders(filter); if (provTest == null) { @@ -261,7 +231,7 @@ public class Security2Test extends junit.framework.TestCase { getProvidersCount(filter), provTest.length); } - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("Provider.id name", "DRLCertFactory"); provTest = Security.getProviders(filter); assertNull("Filter : <Provider.id name, DRLCertFactory >", @@ -270,7 +240,7 @@ public class Security2Test extends junit.framework.TestCase { // exception - no attribute name after the service.algorithm yet we // still supply an expected value. This is not valid. try { - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("Signature.SHA1withDSA", "512"); provTest = Security.getProviders(filter); fail("InvalidParameterException should be thrown <Signature.SHA1withDSA><512>"); @@ -280,7 +250,7 @@ public class Security2Test extends junit.framework.TestCase { // exception - space character in the service.algorithm pair. Not valid. try { - filter = new Hashtable<String, String>(); + filter = new HashMap<String, String>(); filter.put("Signature. KeySize", "512"); provTest = Security.getProviders(filter); fail("InvalidParameterException should be thrown <Signature. KeySize><512>"); @@ -320,11 +290,9 @@ public class Security2Test extends junit.framework.TestCase { assertTrue("Failed to add provider", addResult != -1); Security.removeProvider(entrust.getName()); - Provider provTest[] = Security.getProviders(); - for (int i = 0; i < provTest.length; i++) { - assertTrue( - "the provider entrust is found after it was removed", - provTest[i].getName() != entrust.getName()); + for (Provider provider : Security.getProviders()) { + assertTrue("the provider entrust is found after it was removed", + provider.getName() != entrust.getName()); } } finally { // Tidy up - the following calls do nothing if the providers were diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java new file mode 100644 index 0000000..8359c99 --- /dev/null +++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2012 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.io.File; +import java.io.FileWriter; +import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; +import libcore.java.security.TestKeyStore; + +public class CertPinManagerTest extends TestCase { + + private X509Certificate[] chain; + private List<X509Certificate> shortChain; + private List<X509Certificate> longChain; + private String shortPin; + private String longPin; + private List<File> tmpFiles = new ArrayList<File>(); + + private String writeTmpPinFile(String text) throws Exception { + File tmp = File.createTempFile("pins", null); + FileWriter fstream = new FileWriter(tmp); + fstream.write(text); + fstream.close(); + tmpFiles.add(tmp); + return tmp.getPath(); + } + + private static String getFingerprint(X509Certificate cert) throws NoSuchAlgorithmException { + MessageDigest dgst = MessageDigest.getInstance("SHA512"); + byte[] encoded = cert.getPublicKey().getEncoded(); + byte[] fingerprint = dgst.digest(encoded); + return IntegralToString.bytesToHexString(fingerprint, false); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + // build some valid chains + KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); + chain = (X509Certificate[]) pke.getCertificateChain(); + X509Certificate root = chain[2]; + X509Certificate server = chain[0]; + + // build the short and long chains + shortChain = new ArrayList<X509Certificate>(); + shortChain.add(root); + longChain = new ArrayList<X509Certificate>(); + longChain.add(server); + + // we'll use the root as the pin for the short entry and the server as the pin for the long + shortPin = getFingerprint(root); + longPin = getFingerprint(server); + } + + @Override + public void tearDown() throws Exception { + try { + for (File f : tmpFiles) { + f.delete(); + } + tmpFiles.clear(); + } finally { + super.tearDown(); + } + } + + public void testPinFileMaximumLookup() throws Exception { + + // write a pinfile with two entries, one longer than the other + String shortEntry = "*.google.com=true|" + shortPin; + String longEntry = "*.clients.google.com=true|" + longPin; + + // create the pinFile + String path = writeTmpPinFile(shortEntry + "\n" + longEntry); + CertPinManager pf = new CertPinManager(path, new TrustedCertificateStore()); + + // verify that the shorter chain doesn't work for a name matching the longer + assertTrue("short chain long uri failed", + pf.chainIsNotPinned("android.clients.google.com", shortChain)); + // verify that the longer chain doesn't work for a name matching the shorter + assertTrue("long chain short uri failed", + pf.chainIsNotPinned("android.google.com", longChain)); + // verify that the shorter chain works for the shorter domain + assertTrue("short chain short uri failed", + !pf.chainIsNotPinned("android.google.com", shortChain)); + // and the same for the longer + assertTrue("long chain long uri failed", + !pf.chainIsNotPinned("android.clients.google.com", longChain)); + } + + public void testPinEntryMalformedEntry() throws Exception { + // set up the pinEntry with a bogus entry + String entry = "*.google.com="; + try { + new PinListEntry(entry, new TrustedCertificateStore()); + fail("Accepted an empty pin list entry."); + } catch (PinEntryException expected) { + } + } + + public void testPinEntryNull() throws Exception { + // set up the pinEntry with a bogus entry + String entry = null; + try { + new PinListEntry(entry, new TrustedCertificateStore()); + fail("Accepted a basically wholly bogus entry."); + } catch (NullPointerException expected) { + } + } + + public void testPinEntryEmpty() throws Exception { + // set up the pinEntry with a bogus entry + try { + new PinListEntry("", new TrustedCertificateStore()); + fail("Accepted an empty entry."); + } catch (PinEntryException expected) { + } + } + + public void testPinEntryPinFailure() throws Exception { + // write a pinfile with two entries, one longer than the other + String shortEntry = "*.google.com=true|" + shortPin; + + // set up the pinEntry with a pinlist that doesn't match what we'll give it + PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore()); + assertTrue("Not enforcing!", e.getEnforcing()); + // verify that it doesn't accept + boolean retval = e.chainIsNotPinned(longChain); + assertTrue("Accepted an incorrect pinning, this is very bad", retval); + } + + public void testPinEntryPinSuccess() throws Exception { + // write a pinfile with two entries, one longer than the other + String shortEntry = "*.google.com=true|" + shortPin; + + // set up the pinEntry with a pinlist that matches what we'll give it + PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore()); + assertTrue("Not enforcing!", e.getEnforcing()); + // verify that it accepts + boolean retval = e.chainIsNotPinned(shortChain); + assertTrue("Failed on a correct pinning, this is very bad", !retval); + } + + public void testPinEntryNonEnforcing() throws Exception { + // write a pinfile with two entries, one longer than the other + String shortEntry = "*.google.com=false|" + shortPin; + + // set up the pinEntry with a pinlist that matches what we'll give it + PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore()); + assertFalse("Enforcing!", e.getEnforcing()); + // verify that it accepts + boolean retval = e.chainIsNotPinned(shortChain); + assertTrue("Failed on an unenforced pinning, this is bad-ish", !retval); + } +} diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java index 15b175c..a215445 100644 --- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java +++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/NativeCryptoTest.java @@ -21,10 +21,14 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStore.PrivateKeyEntry; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -131,6 +135,93 @@ public class NativeCryptoTest extends TestCase { assertEquals(Arrays.deepToString(expected), Arrays.deepToString(actual)); } + public void test_EVP_PKEY_cmp() throws Exception { + try { + NativeCrypto.EVP_PKEY_cmp(NULL, NULL); + fail("Should throw NullPointerException when arguments are NULL"); + } catch (NullPointerException expected) { + } + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(512); + + RSAPrivateCrtKey privKey1, privKey2; + + { + KeyPair kp1 = kpg.generateKeyPair(); + privKey1 = (RSAPrivateCrtKey) kp1.getPrivate(); + } + + { + KeyPair kp2 = kpg.generateKeyPair(); + privKey2 = (RSAPrivateCrtKey) kp2.getPrivate(); + } + + int pkey1 = 0, pkey1_copy = 0, pkey2 = 0; + try { + pkey1 = NativeCrypto.EVP_PKEY_new_RSA(privKey1.getModulus().toByteArray(), + privKey1.getPublicExponent().toByteArray(), + privKey1.getPrivateExponent().toByteArray(), + privKey1.getPrimeP().toByteArray(), + privKey1.getPrimeQ().toByteArray(), + privKey1.getPrimeExponentP().toByteArray(), + privKey1.getPrimeExponentQ().toByteArray(), + privKey1.getCrtCoefficient().toByteArray()); + assertNotSame(NULL, pkey1); + + pkey1_copy = NativeCrypto.EVP_PKEY_new_RSA(privKey1.getModulus().toByteArray(), + privKey1.getPublicExponent().toByteArray(), + privKey1.getPrivateExponent().toByteArray(), + privKey1.getPrimeP().toByteArray(), + privKey1.getPrimeQ().toByteArray(), + privKey1.getPrimeExponentP().toByteArray(), + privKey1.getPrimeExponentQ().toByteArray(), + privKey1.getCrtCoefficient().toByteArray()); + assertNotSame(NULL, pkey1_copy); + + pkey2 = NativeCrypto.EVP_PKEY_new_RSA(privKey2.getModulus().toByteArray(), + privKey2.getPublicExponent().toByteArray(), + privKey2.getPrivateExponent().toByteArray(), + privKey2.getPrimeP().toByteArray(), + privKey2.getPrimeQ().toByteArray(), + privKey2.getPrimeExponentP().toByteArray(), + privKey2.getPrimeExponentQ().toByteArray(), + privKey2.getCrtCoefficient().toByteArray()); + assertNotSame(NULL, pkey2); + + try { + NativeCrypto.EVP_PKEY_cmp(pkey1, NULL); + fail("Should throw NullPointerException when arguments are NULL"); + } catch (NullPointerException expected) { + } + + try { + NativeCrypto.EVP_PKEY_cmp(NULL, pkey1); + fail("Should throw NullPointerException when arguments are NULL"); + } catch (NullPointerException expected) { + } + + assertEquals("Same keys should be the equal", 1, + NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1)); + + assertEquals("Same keys should be the equal", 1, + NativeCrypto.EVP_PKEY_cmp(pkey1, pkey1_copy)); + + assertEquals("Different keys should not be equal", 0, + NativeCrypto.EVP_PKEY_cmp(pkey1, pkey2)); + } finally { + if (pkey1 != 0) { + NativeCrypto.EVP_PKEY_free(pkey1); + } + if (pkey1_copy != 0) { + NativeCrypto.EVP_PKEY_free(pkey1_copy); + } + if (pkey2 != 0) { + NativeCrypto.EVP_PKEY_free(pkey2); + } + } + } + public void test_SSL_CTX_new() throws Exception { int c = NativeCrypto.SSL_CTX_new(); assertTrue(c != NULL); @@ -781,7 +872,7 @@ public class NativeCryptoTest extends TestCase { NativeCrypto.SSL_set_options( s, NativeCrypto.SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); NativeCrypto.SSL_renegotiate(s); - NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1); + NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1, 0); super.afterHandshake(session, s, c, sock, fd, callback); } }; @@ -1150,7 +1241,7 @@ public class NativeCryptoTest extends TestCase { SSLHandshakeCallbacks callback) throws Exception { NativeCrypto.SSL_renegotiate(s); - NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1); + NativeCrypto.SSL_write(s, fd, callback, new byte[] { 42 }, 0, 1, 0); super.afterHandshake(session, s, c, sock, fd, callback); } }; @@ -1317,7 +1408,7 @@ public class NativeCryptoTest extends TestCase { Socket sock, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { - NativeCrypto.SSL_write(s, fd, callback, BYTES, 0, BYTES.length); + NativeCrypto.SSL_write(s, fd, callback, BYTES, 0, BYTES.length, 0); super.afterHandshake(session, s, c, sock, fd, callback); } }; @@ -1360,7 +1451,7 @@ public class NativeCryptoTest extends TestCase { public void test_SSL_write() throws Exception { try { - NativeCrypto.SSL_write(NULL, null, null, null, 0, 0); + NativeCrypto.SSL_write(NULL, null, null, null, 0, 0, 0); fail(); } catch (NullPointerException expected) { } @@ -1370,7 +1461,7 @@ public class NativeCryptoTest extends TestCase { int c = NativeCrypto.SSL_CTX_new(); int s = NativeCrypto.SSL_new(c); try { - NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1); + NativeCrypto.SSL_write(s, null, DUMMY_CB, null, 0, 1, 0); fail(); } catch (NullPointerException expected) { } @@ -1383,7 +1474,7 @@ public class NativeCryptoTest extends TestCase { int c = NativeCrypto.SSL_CTX_new(); int s = NativeCrypto.SSL_new(c); try { - NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1); + NativeCrypto.SSL_write(s, INVALID_FD, null, null, 0, 1, 0); fail(); } catch (NullPointerException expected) { } @@ -1396,7 +1487,7 @@ public class NativeCryptoTest extends TestCase { int c = NativeCrypto.SSL_CTX_new(); int s = NativeCrypto.SSL_new(c); try { - NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1); + NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, null, 0, 1, 0); fail(); } catch (NullPointerException expected) { } @@ -1409,7 +1500,7 @@ public class NativeCryptoTest extends TestCase { int c = NativeCrypto.SSL_CTX_new(); int s = NativeCrypto.SSL_new(c); try { - NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1); + NativeCrypto.SSL_write(s, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0); fail(); } catch (SSLException expected) { } diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java index 26ebc85..fe5f4f0 100644 --- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java +++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java @@ -16,9 +16,15 @@ package org.apache.harmony.xnet.provider.jsse; -import java.security.KeyStore; +import java.io.File; +import java.io.FileWriter; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; @@ -27,12 +33,41 @@ import libcore.java.security.TestKeyStore; public class TrustManagerImplTest extends TestCase { + private List<File> tmpFiles = new ArrayList<File>(); + + private String getFingerprint(X509Certificate cert) throws Exception { + MessageDigest dgst = MessageDigest.getInstance("SHA512"); + byte[] encoded = cert.getPublicKey().getEncoded(); + byte[] fingerprint = dgst.digest(encoded); + return IntegralToString.bytesToHexString(fingerprint, false); + } + + private String writeTmpPinFile(String text) throws Exception { + File tmp = File.createTempFile("pins", null); + FileWriter fstream = new FileWriter(tmp); + fstream.write(text); + fstream.close(); + tmpFiles.add(tmp); + return tmp.getPath(); + } + + @Override + public void tearDown() throws Exception { + try { + for (File f : tmpFiles) { + f.delete(); + } + tmpFiles.clear(); + } finally { + super.tearDown(); + } + } + /** * Ensure that our non-standard behavior of learning to trust new * intermediate CAs does not regress. http://b/3404902 */ public void testLearnIntermediate() throws Exception { - // chain3 should be server/intermediate/root KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); @@ -63,6 +98,52 @@ public class TrustManagerImplTest extends TestCase { assertValid(chain1, tm); } + public void testGetFullChain() throws Exception { + // build the trust manager + KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); + X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain(); + X509Certificate root = chain3[2]; + X509TrustManager tm = trustManager(root); + + // build the chains we'll use for testing + X509Certificate intermediate = chain3[1]; + X509Certificate server = chain3[0]; + X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; + X509Certificate[] chain1 = new X509Certificate[] { server }; + + assertTrue(tm instanceof TrustManagerImpl); + TrustManagerImpl tmi = (TrustManagerImpl) tm; + List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com"); + assertEquals(Arrays.asList(chain3), certs); + certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com"); + assertEquals(Arrays.asList(chain3), certs); + } + + public void testCertPinning() throws Exception { + // chain3 should be server/intermediate/root + KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA"); + X509Certificate[] chain3 = (X509Certificate[]) pke.getCertificateChain(); + X509Certificate root = chain3[2]; + X509Certificate intermediate = chain3[1]; + X509Certificate server = chain3[0]; + X509Certificate[] chain2 = new X509Certificate[] { server, intermediate }; + X509Certificate[] chain1 = new X509Certificate[] { server }; + + // test without a hostname, expecting failure + assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), null); + // test without a hostname, expecting success + assertValidPinned(chain3, trustManager(root, "gugle.com", root), null, chain3); + // test an unpinned hostname that should fail + assertInvalidPinned(chain1, trustManager(root, "gugle.com", root), "purple.com"); + // test an unpinned hostname that should succeed + assertValidPinned(chain3, trustManager(root, "gugle.com", root), "purple.com", chain3); + // test a pinned hostname that should fail + assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com"); + // test a pinned hostname that should succeed + assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com", + chain2); + } + private X509TrustManager trustManager(X509Certificate ca) throws Exception { KeyStore keyStore = TestKeyStore.createKeyStore(); keyStore.setCertificateEntry("alias", ca); @@ -73,10 +154,45 @@ public class TrustManagerImplTest extends TestCase { return (X509TrustManager) tmf.getTrustManagers()[0]; } + private TrustManagerImpl trustManager(X509Certificate ca, String hostname, X509Certificate pin) + throws Exception { + // build the cert pin manager + CertPinManager cm = certManager(hostname, pin); + // insert it into the trust manager + KeyStore keyStore = TestKeyStore.createKeyStore(); + keyStore.setCertificateEntry("alias", ca); + return new TrustManagerImpl(keyStore, cm); + } + + private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception { + String pinString = ""; + if (pin != null) { + pinString = hostname + "=true|" + getFingerprint(pin); + } + // write it to a pinfile + String path = writeTmpPinFile(pinString); + // build the certpinmanager + return new CertPinManager(path, new TrustedCertificateStore()); + } + private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception { - tm.checkClientTrusted(chain, "RSA"); + if (tm instanceof TrustManagerImpl) { + TrustManagerImpl tmi = (TrustManagerImpl) tm; + tmi.checkServerTrusted(chain, "RSA"); + } tm.checkServerTrusted(chain, "RSA"); } + + private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname, + X509Certificate[] fullChain) throws Exception { + if (tm instanceof TrustManagerImpl) { + TrustManagerImpl tmi = (TrustManagerImpl) tm; + List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", hostname); + assertEquals(checkedChain, Arrays.asList(fullChain)); + } + tm.checkServerTrusted(chain, "RSA"); + } + private void assertInvalid(X509Certificate[] chain, X509TrustManager tm) { try { tm.checkClientTrusted(chain, "RSA"); @@ -89,4 +205,15 @@ public class TrustManagerImplTest extends TestCase { } catch (CertificateException expected) { } } + + private void assertInvalidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname) + throws Exception { + assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl); + try { + TrustManagerImpl tmi = (TrustManagerImpl) tm; + tmi.checkServerTrusted(chain, "RSA", hostname); + fail(); + } catch (CertificateException expected) { + } + } } diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java index 52880df..8f9b7fa 100644 --- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java +++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustedCertificateStoreTest.java @@ -530,6 +530,26 @@ public class TrustedCertificateStoreTest extends TestCase { assertDeleted(getCa1(), getAliasSystemCa1()); } + public void testIsUserAddedCertificate() throws Exception { + assertFalse(store.isUserAddedCertificate(getCa1())); + assertFalse(store.isUserAddedCertificate(getCa2())); + install(getCa1(), getAliasSystemCa1()); + assertFalse(store.isUserAddedCertificate(getCa1())); + assertFalse(store.isUserAddedCertificate(getCa2())); + install(getCa1(), getAliasUserCa1()); + assertTrue(store.isUserAddedCertificate(getCa1())); + assertFalse(store.isUserAddedCertificate(getCa2())); + install(getCa2(), getAliasUserCa2()); + assertTrue(store.isUserAddedCertificate(getCa1())); + assertTrue(store.isUserAddedCertificate(getCa2())); + store.deleteCertificateEntry(getAliasUserCa1()); + assertFalse(store.isUserAddedCertificate(getCa1())); + assertTrue(store.isUserAddedCertificate(getCa2())); + store.deleteCertificateEntry(getAliasUserCa2()); + assertFalse(store.isUserAddedCertificate(getCa1())); + assertFalse(store.isUserAddedCertificate(getCa2())); + } + private void assertRootCa(X509Certificate x, String alias) { assertIntermediateCa(x, alias); assertEquals(x, store.findIssuer(x)); diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java index f01a9b9..4211a10 100644 --- a/support/src/test/java/libcore/java/security/StandardNames.java +++ b/support/src/test/java/libcore/java/security/StandardNames.java @@ -351,14 +351,6 @@ public final class StandardNames extends Assert { // different names: added "Encryption" suffix unprovide("Signature", "MD5withRSA"); provide("Signature", "MD5WithRSAEncryption"); - unprovide("Signature", "SHA1withRSA"); - provide("Signature", "SHA1WithRSAEncryption"); - unprovide("Signature", "SHA256WithRSA"); - provide("Signature", "SHA256WithRSAEncryption"); - unprovide("Signature", "SHA384WithRSA"); - provide("Signature", "SHA384WithRSAEncryption"); - unprovide("Signature", "SHA512WithRSA"); - provide("Signature", "SHA512WithRSAEncryption"); // Added to support Android KeyStore operations provide("Signature", "NONEwithRSA"); @@ -477,6 +469,12 @@ public final class StandardNames extends Assert { // Android's CA store provide("KeyStore", "AndroidCAStore"); + + // Android's KeyStore provider + if (Security.getProvider("AndroidKeyStoreProvider") != null) { + provide("KeyStore", "AndroidKeyStore"); + provide("KeyPairGenerator", "AndroidKeyPairGenerator"); + } } } |