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