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