SSLUtils.java revision 601700a61e453c612e0dabe4e93002766b3751b7
1c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki/* 2c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * Copyright (C) 2010 The Android Open Source Project 3c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * 4c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License"); 5c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * you may not use this file except in compliance with the License. 6c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * You may obtain a copy of the License at 7c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * 8c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * http://www.apache.org/licenses/LICENSE-2.0 9c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * 10c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * Unless required by applicable law or agreed to in writing, software 11c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS, 12c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * See the License for the specific language governing permissions and 14c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * limitations under the License. 15c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki */ 16c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 173a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankpackage com.android.emailcommon.utility; 18c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 197d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport android.content.ContentUris; 207d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport android.content.ContentValues; 2178959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport android.content.Context; 227d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport android.database.Cursor; 23c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onukiimport android.net.SSLCertificateSocketFactory; 24601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdonimport android.net.SSLSessionCache; 2578959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport android.security.KeyChain; 2678959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport android.security.KeyChainException; 27c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 287d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.EmailContent.HostAuthColumns; 297d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.HostAuth; 30560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 31f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komaloimport com.google.common.annotations.VisibleForTesting; 32f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo 337d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.io.ByteArrayInputStream; 347d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.io.IOException; 35f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komaloimport java.net.InetAddress; 3678959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.net.Socket; 3778959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.security.Principal; 3878959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.security.PrivateKey; 397d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.security.PublicKey; 407d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.security.cert.Certificate; 41cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komaloimport java.security.cert.CertificateException; 427d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.security.cert.CertificateFactory; 4378959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.security.cert.X509Certificate; 4478959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.util.Arrays; 4578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 4678959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport javax.net.ssl.KeyManager; 477d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport javax.net.ssl.TrustManager; 4878959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport javax.net.ssl.X509ExtendedKeyManager; 497d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport javax.net.ssl.X509TrustManager; 50c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 51c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onukipublic class SSLUtils { 527d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // All secure factories are the same; all insecure factories are associated with HostAuth's 53601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon private static javax.net.ssl.SSLSocketFactory sSecureFactory; 5478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 5578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private static final boolean LOG_ENABLED = false; 5678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private static final String TAG = "Email.Ssl"; 57c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 5837a4c65e58728b321850b1e51d87eecfe9d1d805Anthony Lee // A 30 second SSL handshake should be more than enough. 5937a4c65e58728b321850b1e51d87eecfe9d1d805Anthony Lee private static final int SSL_HANDSHAKE_TIMEOUT = 30000; 6037a4c65e58728b321850b1e51d87eecfe9d1d805Anthony Lee 61c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki /** 627d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank * A trust manager specific to a particular HostAuth. The first time a server certificate is 637d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank * encountered for the HostAuth, its certificate is saved; subsequent checks determine whether 647d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank * the PublicKey of the certificate presented matches that of the saved certificate 657d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank * TODO: UI to ask user about changed certificates 667d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank */ 677d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private static class SameCertificateCheckingTrustManager implements X509TrustManager { 687d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private final HostAuth mHostAuth; 697d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private final Context mContext; 707d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // The public key associated with the HostAuth; we'll lazily initialize it 717d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private PublicKey mPublicKey; 727d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank 737d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank SameCertificateCheckingTrustManager(Context context, HostAuth hostAuth) { 747d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mContext = context; 757d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mHostAuth = hostAuth; 767d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // We must load the server cert manually (the ContentCache won't handle blobs 777d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank Cursor c = context.getContentResolver().query(HostAuth.CONTENT_URI, 783dd85723a1af5537e23e4b05bdc361cce9cd42beTony Mantler new String[] {HostAuthColumns.SERVER_CERT}, HostAuthColumns._ID + "=?", 797d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank new String[] {Long.toString(hostAuth.mId)}, null); 807d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (c != null) { 817d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank try { 827d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (c.moveToNext()) { 837d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mHostAuth.mServerCert = c.getBlob(0); 847d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 857d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } finally { 867d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank c.close(); 877d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 887d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 897d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 907d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank 917d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank @Override 927d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public void checkClientTrusted(X509Certificate[] chain, String authType) 937d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank throws CertificateException { 947d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // We don't check client certificates 957d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank throw new CertificateException("We don't check client certificates"); 967d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 977d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank 987d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank @Override 997d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public void checkServerTrusted(X509Certificate[] chain, String authType) 1007d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank throws CertificateException { 1017d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (chain.length == 0) { 1027d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank throw new CertificateException("No certificates?"); 1037d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } else { 1047d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank X509Certificate serverCert = chain[0]; 1057d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (mHostAuth.mServerCert != null) { 1067d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // Compare with the current public key 1077d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (mPublicKey == null) { 1087d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank ByteArrayInputStream bais = new ByteArrayInputStream(mHostAuth.mServerCert); 1097d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank Certificate storedCert = 1107d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank CertificateFactory.getInstance("X509").generateCertificate(bais); 1117d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mPublicKey = storedCert.getPublicKey(); 1127d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank try { 1137d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank bais.close(); 1147d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } catch (IOException e) { 1157d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // Yeah, right. 1167d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1177d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1187d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank if (!mPublicKey.equals(serverCert.getPublicKey())) { 1197d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank throw new CertificateException( 1207d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank "PublicKey has changed since initial connection!"); 1217d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1227d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } else { 1237d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank // First time; save this away 1247d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank byte[] encodedCert = serverCert.getEncoded(); 1257d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mHostAuth.mServerCert = encodedCert; 1267d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank ContentValues values = new ContentValues(); 1277d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank values.put(HostAuthColumns.SERVER_CERT, encodedCert); 1287d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mContext.getContentResolver().update( 1297d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank ContentUris.withAppendedId(HostAuth.CONTENT_URI, mHostAuth.mId), 1307d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank values, null, null); 1317d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1327d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1337d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1347d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank 1357d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank @Override 1367d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public X509Certificate[] getAcceptedIssuers() { 1377d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return null; 1387d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1397d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank } 1407d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank 141601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon public static abstract class ExternalSecureSocketFactoryBuilder { 142601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon abstract public javax.net.ssl.SSLSocketFactory createSecureSocketFactory( 143601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final Context context, final int handshakeTimeoutMillis); 144601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 145601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 146601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon private static ExternalSecureSocketFactoryBuilder sExternalSocketFactoryBuilder; 147601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 148601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon public static void setExternalSecureSocketFactoryBuilder( 149601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon ExternalSecureSocketFactoryBuilder builder) { 150601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon sExternalSocketFactoryBuilder = builder; 151601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 152601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 1537d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank /** 15478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Returns a {@link javax.net.ssl.SSLSocketFactory}. 15578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Optionally bypass all SSL certificate checks. 156c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * 157c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * @param insecure if true, bypass all SSL certificate checks 158c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki */ 159601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon public synchronized static javax.net.ssl.SSLSocketFactory getSSLSocketFactory( 160601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final Context context, final HostAuth hostAuth, final KeyManager keyManager, 161601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final boolean insecure) { 162c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki if (insecure) { 163601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final SSLCertificateSocketFactory insecureFactory = (SSLCertificateSocketFactory) 16437a4c65e58728b321850b1e51d87eecfe9d1d805Anthony Lee SSLCertificateSocketFactory.getInsecure(SSL_HANDSHAKE_TIMEOUT, null); 1657d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank insecureFactory.setTrustManagers( 1667d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank new TrustManager[] { 1677d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank new SameCertificateCheckingTrustManager(context, hostAuth)}); 168601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon if (keyManager != null) { 169601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon insecureFactory.setKeyManagers(new KeyManager[] { keyManager }); 170601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 1717d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return insecureFactory; 172c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } else { 173c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki if (sSecureFactory == null) { 174601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon // First try to get use an externally supplied, more secure SSLSocketBuilder. 175601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon // If so we should use that. 176601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon javax.net.ssl.SSLSocketFactory socketFactory = null; 177601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon if (sExternalSocketFactoryBuilder != null) { 178601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon socketFactory = sExternalSocketFactoryBuilder.createSecureSocketFactory( 179601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon context, SSL_HANDSHAKE_TIMEOUT); 180601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 181601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon if (socketFactory != null) { 182601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon sSecureFactory = socketFactory; 183601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon LogUtils.d(TAG, "Using externally created CertificateSocketFactory"); 184601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon return sSecureFactory; 185601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 186601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon // Only fall back to the platform one if that fails. 187601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon LogUtils.d(TAG, "Falling back to platform CertificateSocketFactory"); 188601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final SSLCertificateSocketFactory certificateSocketFactory = 189601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon (SSLCertificateSocketFactory) 190601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon SSLCertificateSocketFactory.getDefault(SSL_HANDSHAKE_TIMEOUT, 191601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon new SSLSessionCache(context)); 192601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon if (keyManager != null) { 193601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon certificateSocketFactory.setKeyManagers(new KeyManager[] { keyManager }); 194601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 195601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon sSecureFactory = certificateSocketFactory; 196c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 197c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki return sSecureFactory; 198c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 199c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 200724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo 201724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo /** 202601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon * Returns a com.android.emailcommon.utility.SSLSocketFactory 20378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 2047d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public static SSLSocketFactory getHttpSocketFactory(Context context, HostAuth hostAuth, 2057d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank KeyManager keyManager, boolean insecure) { 206601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon javax.net.ssl.SSLSocketFactory underlying = getSSLSocketFactory(context, hostAuth, 207601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon keyManager, insecure); 2084d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo SSLSocketFactory wrapped = new SSLSocketFactory(underlying); 2094d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo if (insecure) { 2104d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo wrapped.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 2114d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo } 2124d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo return wrapped; 21378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 21478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 2152ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // Character.isLetter() is locale-specific, and will potentially return true for characters 2162ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // outside of ascii a-z,A-Z 2172ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler private static boolean isAsciiLetter(char c) { 2182ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); 2192ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler } 2202ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler 2212ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // Character.isDigit() is locale-specific, and will potentially return true for characters 2222ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // outside of ascii 0-9 2232ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler private static boolean isAsciiNumber(char c) { 2242ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler return ('0' <= c && c <= '9'); 2252ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler } 2262ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler 22778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 228724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * Escapes the contents a string to be used as a safe scheme name in the URI according to 229724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * http://tools.ietf.org/html/rfc3986#section-3.1 230724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * 231724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * This does not ensure that the first character is a letter (which is required by the RFC). 232724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo */ 233745b33b8ff55e9a9c4871f07f9d97db893f784b2Makoto Onuki @VisibleForTesting 234724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo public static String escapeForSchemeName(String s) { 235724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // According to the RFC, scheme names are case-insensitive. 236724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo s = s.toLowerCase(); 237724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo 238724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo StringBuilder sb = new StringBuilder(); 239724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo for (int i = 0; i < s.length(); i++) { 240724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo char c = s.charAt(i); 2412ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler if (isAsciiLetter(c) || isAsciiNumber(c) 242724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo || ('-' == c) || ('.' == c)) { 243724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // Safe - use as is. 244724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append(c); 245724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } else if ('+' == c) { 246724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // + is used as our escape character, so double it up. 247724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append("++"); 248724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } else { 249724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // Unsafe - escape. 250724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append('+').append((int) c); 251724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 252724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 253724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo return sb.toString(); 254724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 25578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private static abstract class StubKeyManager extends X509ExtendedKeyManager { 25778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract String chooseClientAlias( 25878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String[] keyTypes, Principal[] issuers, Socket socket); 25978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract X509Certificate[] getCertificateChain(String alias); 26178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract PrivateKey getPrivateKey(String alias); 26378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // The following methods are unused. 26678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 26878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String chooseServerAlias( 26978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String keyType, Principal[] issuers, Socket socket) { 27078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 27178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 27278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 27378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 27478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 27578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String[] getClientAliases(String keyType, Principal[] issuers) { 27678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 27778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 27878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 27978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 28078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 28178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String[] getServerAliases(String keyType, Principal[] issuers) { 28278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 28378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 28478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 28578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 28678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 28778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 288f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * A dummy {@link KeyManager} which keeps track of the last time a server has requested 289f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * a client certificate. 290f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo */ 291f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public static class TrackingKeyManager extends StubKeyManager { 292f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo private volatile long mLastTimeCertRequested = 0L; 293f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo 294f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 295f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 296f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo if (LOG_ENABLED) { 297f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo InetAddress address = socket.getInetAddress(); 298560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: requesting a client cert alias for " 299f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo + address.getCanonicalHostName()); 300f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 301f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo mLastTimeCertRequested = System.currentTimeMillis(); 302f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo return null; 303f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 304f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 305f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 306f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public X509Certificate[] getCertificateChain(String alias) { 307f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo if (LOG_ENABLED) { 308560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: returning a null cert chain"); 309f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 310f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo return null; 311f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 312f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 313f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 314f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public PrivateKey getPrivateKey(String alias) { 315f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo if (LOG_ENABLED) { 316560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: returning a null private key"); 317f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 318f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo return null; 319f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 320f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 321f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo /** 322f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * @return the last time that this {@link KeyManager} detected a request by a server 323f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * for a client certificate (in millis since epoch). 324f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo */ 325f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo public long getLastCertReqTime() { 326f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo return mLastTimeCertRequested; 327f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 328f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 329f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 330f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo /** 33178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * A {@link KeyManager} that reads uses credentials stored in the system {@link KeyChain}. 33278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 33378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public static class KeyChainKeyManager extends StubKeyManager { 33478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final String mClientAlias; 33578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final X509Certificate[] mCertificateChain; 33678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final PrivateKey mPrivateKey; 33778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 33878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 33978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Builds an instance of a KeyChainKeyManager using the given certificate alias. 34078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * If for any reason retrieval of the credentials from the system {@link KeyChain} fails, 34178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * a {@code null} value will be returned. 34278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 343cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo public static KeyChainKeyManager fromAlias(Context context, String alias) 344cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throws CertificateException { 34578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo X509Certificate[] certificateChain; 34678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo try { 34778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo certificateChain = KeyChain.getCertificateChain(context, alias); 34878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (KeyChainException e) { 349f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "certificate chain", e); 350cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 35178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (InterruptedException e) { 352f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "certificate chain", e); 353cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 35478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 35578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 35678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo PrivateKey privateKey; 35778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo try { 35878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo privateKey = KeyChain.getPrivateKey(context, alias); 35978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (KeyChainException e) { 360f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "private key", e); 361cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 36278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (InterruptedException e) { 363f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "private key", e); 364cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 365cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo } 366cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo 367cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo if (certificateChain == null || privateKey == null) { 368877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo throw new CertificateException("Can't access certificate from keystore"); 36978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 37078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 37178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return new KeyChainKeyManager(alias, certificateChain, privateKey); 37278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 37378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 374f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo private static void logError(String alias, String type, Exception ex) { 375877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo // Avoid logging PII when explicit logging is not on. 376877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo if (LOG_ENABLED) { 377560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(TAG, "Unable to retrieve " + type + " for [" + alias + "] due to " + ex); 378877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo } else { 379560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(TAG, "Unable to retrieve " + type + " due to " + ex); 380877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo } 381f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 382f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 38378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private KeyChainKeyManager( 38478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) { 38578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mClientAlias = clientAlias; 38678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mCertificateChain = certificateChain; 38778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mPrivateKey = privateKey; 38878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 38978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 39078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 39178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 39278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 39378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 394560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client cert alias for " + Arrays.toString(keyTypes)); 39578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 39678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mClientAlias; 39778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 39878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 39978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 40078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public X509Certificate[] getCertificateChain(String alias) { 40178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 402560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client certificate chain for alias [" + alias + "]"); 40378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 40478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mCertificateChain; 40578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 40678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 40778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 40878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public PrivateKey getPrivateKey(String alias) { 40978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 410560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client private key for alias [" + alias + "]"); 41178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 41278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mPrivateKey; 41378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 41478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 415c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki} 416