178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo/*
278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Copyright (C) 2011 The Android Open Source Project
378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo *
478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Licensed under the Apache License, Version 2.0 (the "License");
578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * you may not use this file except in compliance with the License.
678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * You may obtain a copy of the License at
778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo *
878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo *      http://www.apache.org/licenses/LICENSE-2.0
978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo *
1078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * Unless required by applicable law or agreed to in writing, software
1178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * distributed under the License is distributed on an "AS IS" BASIS,
1278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * See the License for the specific language governing permissions and
1478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * limitations under the License.
1578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */
1678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
1778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
1878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalopackage com.android.emailcommon.utility;
1978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
20f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komaloimport android.content.Context;
214b629cf25804fd74712b0f7acce1c2e59135b9bfTony Mantlerimport android.text.TextUtils;
22f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo
2378959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport com.android.emailcommon.Logging;
24f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blankimport com.android.emailcommon.provider.HostAuth;
2578959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
26f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komaloimport com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
27560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
2878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
2978959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport org.apache.http.conn.scheme.PlainSocketFactory;
3078959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport org.apache.http.conn.scheme.Scheme;
3178959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport org.apache.http.conn.scheme.SchemeRegistry;
3278959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
3378959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport org.apache.http.params.HttpParams;
3478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
35cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komaloimport java.security.cert.CertificateException;
36cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo
3778959916e771114ff8c48fc181e34a7dff0aa672Ben Komaloimport javax.net.ssl.KeyManager;
3878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
3978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo/**
4078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * A thread-safe client connection manager that manages the use of client certificates from the
4178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo * {@link android.security.KeyChain} for SSL connections.
4278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo */
4378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalopublic class EmailClientConnectionManager extends ThreadSafeClientConnManager {
4478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
45f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int STANDARD_PORT = 80;
46f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    private static final int STANDARD_SSL_PORT = 443;
4778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    private static final boolean LOG_ENABLED = false;
4878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
4978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    /**
50f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     * A {@link KeyManager} to track client certificate requests from servers.
51f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     */
52f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    private final TrackingKeyManager mTrackingKeyManager;
53f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo
54f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    /**
5578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * Not publicly instantiable except via {@link #newInstance(HttpParams)}
5678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     */
57f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    private EmailClientConnectionManager(
58f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo            HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) {
5978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        super(params, registry);
60f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo        mTrackingKeyManager = keyManager;
6178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
6278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
637d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    public static EmailClientConnectionManager newInstance(Context context, HttpParams params,
647d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank            HostAuth hostAuth) {
65f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo        TrackingKeyManager keyManager = new TrackingKeyManager();
667d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        boolean ssl = hostAuth.shouldUseSsl();
677d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        int port = hostAuth.mPort;
68f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo
6978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        // Create a registry for our three schemes; http and https will use built-in factories
7078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        SchemeRegistry registry = new SchemeRegistry();
71f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
72f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                ssl ? STANDARD_PORT : port));
73f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        // Register https with the secure factory
74f4dbbf10996e6bca926a5825bbc69e1e172c20c0Ben Komalo        registry.register(new Scheme("https",
757d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, false),
767d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                ssl ? port : STANDARD_SSL_PORT));
7778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        // Register the httpts scheme with our insecure factory
7878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        registry.register(new Scheme("httpts",
797d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, true),
80f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                ssl ? port : STANDARD_SSL_PORT));
8178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
82f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo        return new EmailClientConnectionManager(params, registry, keyManager);
8378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
8478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
8578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    /**
8678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * Ensures that a client SSL certificate is known to be used for the specified connection
8778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * manager.
8878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * A {@link SchemeRegistry} is used to denote which client certificates to use for a given
8978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * connection, so clients of this connection manager should use
9078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * {@link #makeSchemeForClientCert(String, boolean)}.
9178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     */
92f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank    public synchronized void registerClientCert(Context context, HostAuth hostAuth)
93cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo            throws CertificateException {
944b629cf25804fd74712b0f7acce1c2e59135b9bfTony Mantler        if (TextUtils.isEmpty(hostAuth.mClientCertAlias)) {
954b629cf25804fd74712b0f7acce1c2e59135b9bfTony Mantler            return;
964b629cf25804fd74712b0f7acce1c2e59135b9bfTony Mantler        }
9778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        SchemeRegistry registry = getSchemeRegistry();
98f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank        String schemeName = makeSchemeForClientCert(hostAuth.mClientCertAlias,
99f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                hostAuth.shouldTrustAllServerCerts());
10078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        Scheme existing = registry.get(schemeName);
10178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        if (existing == null) {
10278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            if (LOG_ENABLED) {
103560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.i(Logging.LOG_TAG, "Registering socket factory for certificate alias ["
104f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                        + hostAuth.mClientCertAlias + "]");
10578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            }
106f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank            KeyManager keyManager =
107f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank                    KeyChainKeyManager.fromAlias(context, hostAuth.mClientCertAlias);
1080e969171bb32050e845412fb51bc856bf8a2c954Marc Blank            boolean insecure = hostAuth.shouldTrustAllServerCerts();
1090e969171bb32050e845412fb51bc856bf8a2c954Marc Blank            SSLSocketFactory ssf =
1100e969171bb32050e845412fb51bc856bf8a2c954Marc Blank                    SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, insecure);
1110e969171bb32050e845412fb51bc856bf8a2c954Marc Blank            registry.register(new Scheme(schemeName, ssf, hostAuth.mPort));
11278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        }
11378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
11478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
11578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    /**
11678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * Unregisters a custom connection type that uses a client certificate on the connection
11778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * manager.
11878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * @see #registerClientCert(Context, String, boolean)
11978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     */
12078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    public synchronized void unregisterClientCert(
12178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            String clientCertAlias, boolean trustAllServerCerts) {
12278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        SchemeRegistry registry = getSchemeRegistry();
12378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
12478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        Scheme existing = registry.get(schemeName);
12578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        if (existing != null) {
12678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            registry.unregister(schemeName);
12778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        }
12878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
12978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
13078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    /**
13178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * Builds a custom scheme name to be used in a connection manager according to the connection
13278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * parameters.
13378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     */
13478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    public static String makeScheme(
13578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) {
136f1789afa535b113047ef20623d79fdfb257537aaTony Mantler        if (!TextUtils.isEmpty(clientCertAlias)) {
13778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            return makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
13878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        } else {
13978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            return useSsl ? (trustAllServerCerts ? "httpts" : "https") : "http";
14078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        }
14178959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
14278959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo
14378959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    /**
14478959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     * Builds a unique scheme name for an SSL connection that uses a client user certificate.
14578959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo     */
14678959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    private static String makeSchemeForClientCert(
14778959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo            String clientCertAlias, boolean trustAllServerCerts) {
14878959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias);
14978959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo        return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias;
15078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo    }
151f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo
152f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    /**
153f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     * @param since A timestamp in millis from epoch from which to check
154f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     * @return whether or not this connection manager has detected any unsatisfied requests for
155f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     *     a client SSL certificate by any servers
156f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo     */
157f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) {
158f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo        return mTrackingKeyManager.getLastCertReqTime() >= since;
159f4f10a3fdf3fdf94db4780017c4392823942b1d7Ben Komalo    }
16078959916e771114ff8c48fc181e34a7dff0aa672Ben Komalo}
161