/* * Copyright (c) 2013, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package ca.psiphon.ploggy; import java.io.IOException; import java.net.ServerSocket; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.SecureRandom; import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory; /** * Helpers for building custom TLS connections. * * Each TLS connection (both client- and server-side): * - Requires TLS 1.2 * - Requires a strong CipherSuite (limited by what's commonly available on Android 4.1+) * which includes perfect forward secrecy * - Requires mutual authentication using self key material and friend certificates */ public class TransportSecurity { private static final String LOG_TAG = "Transport Security"; public static ServerSocket makeServerSocket( X509.KeyMaterial transportKeyMaterial, List<String> friendCertificates) throws Utils.ApplicationError { try { SSLContext sslContext = TransportSecurity.getSSLContext(transportKeyMaterial, friendCertificates); SSLServerSocket sslServerSocket = (SSLServerSocket)(sslContext.getServerSocketFactory().createServerSocket()); sslServerSocket.setNeedClientAuth(true); sslServerSocket.setEnabledCipherSuites(TLS_REQUIRED_CIPHER_SUITES); sslServerSocket.setEnabledProtocols(TLS_REQUIRED_PROTOCOLS); return sslServerSocket; } catch (IllegalArgumentException e) { throw new Utils.ApplicationError(LOG_TAG, e); } catch (IOException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } private static class ClientSSLSocketFactory extends SSLSocketFactory { public ClientSSLSocketFactory(SSLContext sslContext) { // Using ALLOW_ALL effectively disables hostname verification. Ploggy // simply checks that the peer is authenticating with the sole friend // certificate expected for this connection. super(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); } @Override protected void prepareSocket(SSLSocket socket) throws IOException { socket.setEnabledCipherSuites(TLS_REQUIRED_CIPHER_SUITES); socket.setEnabledProtocols(TLS_REQUIRED_PROTOCOLS); } } public static ClientSSLSocketFactory getClientSSLSocketFactory(SSLContext sslContext) { return new ClientSSLSocketFactory(sslContext); } public static SSLContext getSSLContext( X509.KeyMaterial x509KeyMaterial, List<String> friendCertificates) throws Utils.ApplicationError { try { KeyManager[] keyManagers = null; if (x509KeyMaterial != null) { KeyStore selfKeyStore = X509.makeKeyStore(); X509.loadKeyMaterial(selfKeyStore, x509KeyMaterial); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509"); keyManagerFactory.init(selfKeyStore, null); keyManagers = keyManagerFactory.getKeyManagers(); } KeyStore peerKeyStore = X509.makeKeyStore(); for (String friendCertificate : friendCertificates) { X509.loadKeyMaterial(peerKeyStore, friendCertificate, null); } TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); trustManagerFactory.init(peerKeyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance(TLS_REQUIRED_PROTOCOL); sslContext.init(keyManagers, trustManagers, new SecureRandom()); return sslContext; } catch (IllegalArgumentException e) { throw new Utils.ApplicationError(LOG_TAG, e); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } // Protocol specification // TODO: ECC disabled -- key generation works, but TLS fails in ClientHello //private static final String[] TLS_REQUIRED_CIPHER_SUITES = new String [] { "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" }; //private static final String TLS_REQUIRED_PROTOCOL = "TLSv1.2"; // TODO: TLS_..._CBC_SHA256? // TODO: no GCM-SHA256 built-in, even on Android 4.1?; no JCCE for SpongyCastle to use its GCM-SHA256 with Android TLS? // TODO: TLS 1.2 only available on Android 4.1+? private static final String[] TLS_REQUIRED_CIPHER_SUITES = new String [] { "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" }; private static final String TLS_REQUIRED_PROTOCOL = "TLSv1.2"; private static final String[] TLS_REQUIRED_PROTOCOLS = new String [] { TLS_REQUIRED_PROTOCOL }; }