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; 2378959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport android.security.KeyChain; 2478959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport android.security.KeyChainException; 25c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki 267d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.EmailContent.HostAuthColumns; 277d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.HostAuth; 28560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 29f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komaloimport com.google.common.annotations.VisibleForTesting; 30f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo 317d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.io.ByteArrayInputStream; 327d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport java.io.IOException; 33f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komaloimport java.net.InetAddress; 3478959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport java.net.Socket; 354ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdonimport java.security.KeyManagementException; 364ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdonimport java.security.NoSuchAlgorithmException; 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 141303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon public static abstract class ExternalSecurityProviderInstaller { 142303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon abstract public void installIfNeeded(final Context context); 143601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 144601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 145303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon private static ExternalSecurityProviderInstaller sExternalSecurityProviderInstaller; 146601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 147303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon public static void setExternalSecurityProviderInstaller ( 148303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon ExternalSecurityProviderInstaller installer) { 149303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon sExternalSecurityProviderInstaller = installer; 150601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 151601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon 1527d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank /** 15378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Returns a {@link javax.net.ssl.SSLSocketFactory}. 15478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Optionally bypass all SSL certificate checks. 155c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * 156c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki * @param insecure if true, bypass all SSL certificate checks 157c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki */ 158601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon public synchronized static javax.net.ssl.SSLSocketFactory getSSLSocketFactory( 159601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final Context context, final HostAuth hostAuth, final KeyManager keyManager, 160601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon final boolean insecure) { 161303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon // If we have an external security provider installer, then install. This will 162303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon // potentially replace the default implementation of SSLSocketFactory. 163303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon if (sExternalSecurityProviderInstaller != null) { 164303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon sExternalSecurityProviderInstaller.installIfNeeded(context); 165303b553b13a02529fc8cb22b9b37672887759ce9Martin Hibdon } 1664ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon try { 1674ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon final KeyManager[] keyManagers = (keyManager == null ? null : 1684ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon new KeyManager[]{keyManager}); 1694ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon if (insecure) { 1704ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon final TrustManager[] trustManagers = new TrustManager[]{ 1714ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon new SameCertificateCheckingTrustManager(context, hostAuth)}; 1724ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon SSLSocketFactoryWrapper insecureFactory = 1734ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon (SSLSocketFactoryWrapper) SSLSocketFactoryWrapper.getInsecure( 1744ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon keyManagers, trustManagers, SSL_HANDSHAKE_TIMEOUT); 1754ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon return insecureFactory; 1764ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon } else { 1774ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon if (sSecureFactory == null) { 1784ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon SSLSocketFactoryWrapper secureFactory = 1794ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon (SSLSocketFactoryWrapper) SSLSocketFactoryWrapper.getDefault( 1804ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon keyManagers, SSL_HANDSHAKE_TIMEOUT); 1814ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon sSecureFactory = secureFactory; 182601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon } 1834ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon return sSecureFactory; 184c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 1854ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon } catch (NoSuchAlgorithmException e) { 1864ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory"); 1874ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon // TODO: what can we do about this? 1884ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon } catch (KeyManagementException e) { 1894ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory"); 1904ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon // TODO: what can we do about this? 191c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 1924ecd51a794c5a2af1a5a838f22997b4e361acb8bMartin Hibdon return null; 193c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki } 194724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo 195724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo /** 196601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon * Returns a com.android.emailcommon.utility.SSLSocketFactory 19778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 1987d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public static SSLSocketFactory getHttpSocketFactory(Context context, HostAuth hostAuth, 1997d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank KeyManager keyManager, boolean insecure) { 200601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon javax.net.ssl.SSLSocketFactory underlying = getSSLSocketFactory(context, hostAuth, 201601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon keyManager, insecure); 2024d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo SSLSocketFactory wrapped = new SSLSocketFactory(underlying); 2034d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo if (insecure) { 2044d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo wrapped.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 2054d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo } 2064d3f3f3ab95c03d4c1ab308801b92ba1d9df2276Ben Komalo return wrapped; 20778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 20878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 2092ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // Character.isLetter() is locale-specific, and will potentially return true for characters 2102ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // outside of ascii a-z,A-Z 2112ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler private static boolean isAsciiLetter(char c) { 2122ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); 2132ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler } 2142ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler 2152ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // Character.isDigit() is locale-specific, and will potentially return true for characters 2162ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler // outside of ascii 0-9 2172ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler private static boolean isAsciiNumber(char c) { 2182ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler return ('0' <= c && c <= '9'); 2192ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler } 2202ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler 22178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 222724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * Escapes the contents a string to be used as a safe scheme name in the URI according to 223724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * http://tools.ietf.org/html/rfc3986#section-3.1 224724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * 225724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo * This does not ensure that the first character is a letter (which is required by the RFC). 226724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo */ 227745b33b8ff55e9a9c4871f07f9d97db893f784b2Makoto Onuki @VisibleForTesting 228724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo public static String escapeForSchemeName(String s) { 229724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // According to the RFC, scheme names are case-insensitive. 230724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo s = s.toLowerCase(); 231724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo 232724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo StringBuilder sb = new StringBuilder(); 233724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo for (int i = 0; i < s.length(); i++) { 234724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo char c = s.charAt(i); 2352ed113c7137e6f1059a22fa018332d56ec740a0aTony Mantler if (isAsciiLetter(c) || isAsciiNumber(c) 236724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo || ('-' == c) || ('.' == c)) { 237724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // Safe - use as is. 238724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append(c); 239724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } else if ('+' == c) { 240724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // + is used as our escape character, so double it up. 241724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append("++"); 242724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } else { 243724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo // Unsafe - escape. 244724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo sb.append('+').append((int) c); 245724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 246724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 247724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo return sb.toString(); 248724c3a81cd3649b48ab47c6e49cb42f73f20c815Ben Komalo } 24978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private static abstract class StubKeyManager extends X509ExtendedKeyManager { 25178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract String chooseClientAlias( 25278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String[] keyTypes, Principal[] issuers, Socket socket); 25378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract X509Certificate[] getCertificateChain(String alias); 25578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override public abstract PrivateKey getPrivateKey(String alias); 25778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 25978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // The following methods are unused. 26078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 26278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String chooseServerAlias( 26378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String keyType, Principal[] issuers, Socket socket) { 26478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 26578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 26678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 26778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 26878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 26978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String[] getClientAliases(String keyType, Principal[] issuers) { 27078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 27178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 27278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 27378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 27478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 27578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public final String[] getServerAliases(String keyType, Principal[] issuers) { 27678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo // not a client SSLSocket callback 27778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo throw new UnsupportedOperationException(); 27878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 27978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 28078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 28178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 282f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * A dummy {@link KeyManager} which keeps track of the last time a server has requested 283f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * a client certificate. 284f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo */ 285f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public static class TrackingKeyManager extends StubKeyManager { 286f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo private volatile long mLastTimeCertRequested = 0L; 287f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo 288f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 289f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 290f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo if (LOG_ENABLED) { 291f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo InetAddress address = socket.getInetAddress(); 292560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: requesting a client cert alias for " 293f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo + address.getCanonicalHostName()); 294f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 295f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo mLastTimeCertRequested = System.currentTimeMillis(); 296f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo return null; 297f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 298f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 299f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 300f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public X509Certificate[] getCertificateChain(String alias) { 301f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo if (LOG_ENABLED) { 302560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: returning a null cert chain"); 303f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 304f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo return null; 305f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 306f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 307f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo @Override 308f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo public PrivateKey getPrivateKey(String alias) { 309f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo if (LOG_ENABLED) { 310560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "TrackingKeyManager: returning a null private key"); 311f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 312f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo return null; 313f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 314f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 315f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo /** 316f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * @return the last time that this {@link KeyManager} detected a request by a server 317f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo * for a client certificate (in millis since epoch). 318f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo */ 319f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo public long getLastCertReqTime() { 320f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo return mLastTimeCertRequested; 321f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo } 322f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 323f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 324f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo /** 32578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * A {@link KeyManager} that reads uses credentials stored in the system {@link KeyChain}. 32678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 32778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public static class KeyChainKeyManager extends StubKeyManager { 32878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final String mClientAlias; 32978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final X509Certificate[] mCertificateChain; 33078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private final PrivateKey mPrivateKey; 33178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 33278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo /** 33378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Builds an instance of a KeyChainKeyManager using the given certificate alias. 33478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * If for any reason retrieval of the credentials from the system {@link KeyChain} fails, 33578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * a {@code null} value will be returned. 33678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */ 337cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo public static KeyChainKeyManager fromAlias(Context context, String alias) 338cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throws CertificateException { 33978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo X509Certificate[] certificateChain; 34078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo try { 34178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo certificateChain = KeyChain.getCertificateChain(context, alias); 34278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (KeyChainException e) { 343f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "certificate chain", e); 344cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 34578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (InterruptedException e) { 346f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "certificate chain", e); 347cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 34878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 34978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 35078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo PrivateKey privateKey; 35178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo try { 35278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo privateKey = KeyChain.getPrivateKey(context, alias); 35378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (KeyChainException e) { 354f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "private key", e); 355cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 35678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } catch (InterruptedException e) { 357f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo logError(alias, "private key", e); 358cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo throw new CertificateException(e); 359cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo } 360cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo 361cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo if (certificateChain == null || privateKey == null) { 362877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo throw new CertificateException("Can't access certificate from keystore"); 36378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 36478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 36578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return new KeyChainKeyManager(alias, certificateChain, privateKey); 36678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 36778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 368f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo private static void logError(String alias, String type, Exception ex) { 369877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo // Avoid logging PII when explicit logging is not on. 370877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo if (LOG_ENABLED) { 371560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(TAG, "Unable to retrieve " + type + " for [" + alias + "] due to " + ex); 372877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo } else { 373560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(TAG, "Unable to retrieve " + type + " due to " + ex); 374877b9070fa4d7a6b51ae1f75640a6c23cc86c963Ben Komalo } 375f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo } 376f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo 37778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo private KeyChainKeyManager( 37878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) { 37978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mClientAlias = clientAlias; 38078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mCertificateChain = certificateChain; 38178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo mPrivateKey = privateKey; 38278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 38378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 38478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 38578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 38678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 38778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 388560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client cert alias for " + Arrays.toString(keyTypes)); 38978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 39078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mClientAlias; 39178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 39278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 39378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 39478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public X509Certificate[] getCertificateChain(String alias) { 39578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 396560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client certificate chain for alias [" + alias + "]"); 39778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 39878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mCertificateChain; 39978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 40078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo 40178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo @Override 40278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo public PrivateKey getPrivateKey(String alias) { 40378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo if (LOG_ENABLED) { 404560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.i(TAG, "Requesting a client private key for alias [" + alias + "]"); 40578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 40678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo return mPrivateKey; 40778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 40878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo } 409c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki} 410