/* * Copyright (C) 2010 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.javax.net.ssl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyStore; import java.security.Principal; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collections; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import junit.framework.Assert; /** * TestSSLContext is a convenience class for other tests that * want a canned SSLContext and related state for testing so they * don't have to duplicate the logic. */ public final class TestSSLContext extends Assert { /* * The RI and Android have very different default SSLSession cache behaviors. * The RI keeps an unlimited number of SSLSesions around for 1 day. * Android keeps 10 SSLSessions forever. */ private static final boolean IS_RI = true; public static final int EXPECTED_DEFAULT_CLIENT_SSL_SESSION_CACHE_SIZE = (IS_RI) ? 0 : 10; public static final int EXPECTED_DEFAULT_SERVER_SSL_SESSION_CACHE_SIZE = (IS_RI) ? 0 : 100; public static final int EXPECTED_DEFAULT_SSL_SESSION_CACHE_TIMEOUT = (IS_RI) ? 24 * 3600 : 8 * 3600; /** * The Android SSLSocket and SSLServerSocket implementations are * based on a version of OpenSSL which includes support for RFC * 4507 session tickets. When using session tickets, the server * does not need to keep a cache mapping session IDs to SSL * sessions for reuse. Instead, the client presents the server * with a session ticket it received from the server earlier, * which is an SSL session encrypted by the server's secret * key. Since in this case the server does not need to keep a * cache, some tests may find different results depending on * whether or not the session tickets are in use. These tests can * use this function to determine if loopback SSL connections are * expected to use session tickets and conditionalize their * results appropriately. */ public static boolean sslServerSocketSupportsSessionTickets () { // Disabled session tickets for better compatability b/2682876 // return !IS_RI; return false; } public final KeyStore clientKeyStore; public final char[] clientStorePassword; public final KeyStore serverKeyStore; public final char[] serverStorePassword; public final KeyManager[] clientKeyManagers; public final KeyManager[] serverKeyManagers; public final SSLContext clientContext; public final SSLContext serverContext; public final SSLServerSocket serverSocket; public final InetAddress host; public final int port; /** * Used for replacing the hostname in an InetSocketAddress object during * serialization. */ private static class HostnameRewritingObjectOutputStream extends ObjectOutputStream { private final String hostname; public HostnameRewritingObjectOutputStream(OutputStream out, String hostname) throws IOException { super(out); this.hostname = hostname; } @Override public PutField putFields() throws IOException { return new PutFieldProxy(super.putFields(), hostname); } private static class PutFieldProxy extends ObjectOutputStream.PutField { private final PutField delegate; private final String hostname; public PutFieldProxy(ObjectOutputStream.PutField delegate, String hostname) { this.delegate = delegate; this.hostname = hostname; } @Override public void put(String name, boolean val) { delegate.put(name, val); } @Override public void put(String name, byte val) { delegate.put(name, val); } @Override public void put(String name, char val) { delegate.put(name, val); } @Override public void put(String name, short val) { delegate.put(name, val); } @Override public void put(String name, int val) { delegate.put(name, val); } @Override public void put(String name, long val) { delegate.put(name, val); } @Override public void put(String name, float val) { delegate.put(name, val); } @Override public void put(String name, double val) { delegate.put(name, val); } @Override public void put(String name, Object val) { if ("hostname".equals(name)) { delegate.put(name, hostname); } else { delegate.put(name, val); } } @SuppressWarnings("deprecation") @Override public void write(ObjectOutput out) throws IOException { delegate.write(out); } } } /** * Creates an InetSocketAddress where the hostname points to an arbitrary * hostname, but the address points to the loopback address. Useful for * testing SNI where both "localhost" and IP addresses are not allowed. */ public InetSocketAddress getLoopbackAsHostname(String hostname, int port) throws IOException, ClassNotFoundException { InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port); ByteArrayOutputStream baos = new ByteArrayOutputStream(); HostnameRewritingObjectOutputStream oos = new HostnameRewritingObjectOutputStream(baos, hostname); oos.writeObject(addr); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); return (InetSocketAddress) ois.readObject(); } private TestSSLContext(KeyStore clientKeyStore, char[] clientStorePassword, KeyStore serverKeyStore, char[] serverStorePassword, KeyManager[] clientKeyManagers, KeyManager[] serverKeyManagers, SSLContext clientContext, SSLContext serverContext, SSLServerSocket serverSocket, InetAddress host, int port) { this.clientKeyStore = clientKeyStore; this.clientStorePassword = clientStorePassword; this.serverKeyStore = serverKeyStore; this.serverStorePassword = serverStorePassword; this.clientKeyManagers = clientKeyManagers; this.serverKeyManagers = serverKeyManagers; this.clientContext = clientContext; this.serverContext = serverContext; this.serverSocket = serverSocket; this.host = host; this.port = port; } public void close() { try { serverSocket.close(); } catch (Exception e) { throw new RuntimeException(e); } } /** * TestSSLContext creation method that allows separate creation of client and server key store */ public static TestSSLContext create(KeyStore clientKeyStore, char[] clientStorePassword, KeyStore serverKeyStore, char[] serverStorePassword, KeyManager[] clientKeyManagers, KeyManager[] serverKeyManagers, TrustManager clientTrustManagers, TrustManager serverTrustManagers, SSLContext clientContext, SSLContext serverContext) { try { SSLServerSocket serverSocket = (SSLServerSocket) serverContext.getServerSocketFactory().createServerSocket(0); InetAddress host = InetAddress.getLocalHost(); int port = serverSocket.getLocalPort(); return new TestSSLContext(clientKeyStore, clientStorePassword, serverKeyStore, serverStorePassword, clientKeyManagers, serverKeyManagers, clientContext, serverContext, serverSocket, host, port); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } /** * Create a SSLContext with a KeyManager using the private key and * certificate chain from the given KeyStore and a TrustManager * using the certificates authorities from the same KeyStore. */ public static final SSLContext createSSLContext(final String protocol, final KeyManager[] keyManagers, final TrustManager[] trustManagers) { try { SSLContext context = SSLContext.getInstance(protocol); context.init(keyManagers, trustManagers, new SecureRandom()); return context; } catch (Exception e) { throw new RuntimeException(e); } } public static void assertCertificateInKeyStore(Principal principal, KeyStore keyStore) throws Exception { String subjectName = principal.getName(); boolean found = false; for (String alias: Collections.list(keyStore.aliases())) { if (!keyStore.isCertificateEntry(alias)) { continue; } X509Certificate keyStoreCertificate = (X509Certificate) keyStore.getCertificate(alias); if (subjectName.equals(keyStoreCertificate.getSubjectDN().getName())) { found = true; break; } } assertTrue(found); } public static void assertCertificateInKeyStore(Certificate certificate, KeyStore keyStore) throws Exception { boolean found = false; for (String alias: Collections.list(keyStore.aliases())) { if (!keyStore.isCertificateEntry(alias)) { continue; } Certificate keyStoreCertificate = keyStore.getCertificate(alias); if (certificate.equals(keyStoreCertificate)) { found = true; break; } } assertTrue(found); } public static void assertServerCertificateChain(X509TrustManager trustManager, Certificate[] serverChain) throws CertificateException { X509Certificate[] chain = (X509Certificate[]) serverChain; trustManager.checkServerTrusted(chain, chain[0].getPublicKey().getAlgorithm()); } public static void assertClientCertificateChain(X509TrustManager trustManager, Certificate[] clientChain) throws CertificateException { X509Certificate[] chain = (X509Certificate[]) clientChain; trustManager.checkClientTrusted(chain, chain[0].getPublicKey().getAlgorithm()); } /** * Returns an SSLSocketFactory that calls setWantClientAuth and * setNeedClientAuth as specified on all returned sockets. */ public static SSLSocketFactory clientAuth(final SSLSocketFactory sf, final boolean want, final boolean need) { return new SSLSocketFactory() { private SSLSocket set(Socket socket) { SSLSocket s = (SSLSocket) socket; s.setWantClientAuth(want); s.setNeedClientAuth(need); return s; } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return set(sf.createSocket(host, port)); } public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return set(sf.createSocket(host, port, localHost, localPort)); } public Socket createSocket(InetAddress host, int port) throws IOException { return set(sf.createSocket(host, port)); } public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return set(sf.createSocket(address, port)); } public String[] getDefaultCipherSuites() { return sf.getDefaultCipherSuites(); } public String[] getSupportedCipherSuites() { return sf.getSupportedCipherSuites(); } public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { return set(sf.createSocket(s, host, port, autoClose)); } }; } }