EmailClientConnectionManager.java revision b5bc501df9c2df2c2f3eb7780c5e192d0dc78a3a
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.emailcommon.utility;
19
20import android.content.Context;
21import android.net.SSLCertificateSocketFactory;
22import android.util.Log;
23
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.HostAuth;
26import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
27import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
28
29import org.apache.http.conn.scheme.PlainSocketFactory;
30import org.apache.http.conn.scheme.Scheme;
31import org.apache.http.conn.scheme.SchemeRegistry;
32import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
33import org.apache.http.params.HttpParams;
34
35import java.security.cert.CertificateException;
36
37import javax.net.ssl.KeyManager;
38
39/**
40 * A thread-safe client connection manager that manages the use of client certificates from the
41 * {@link android.security.KeyChain} for SSL connections.
42 */
43public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
44
45    private static final boolean LOG_ENABLED = false;
46    private static final int STANDARD_PORT = 80;
47    private static final int STANDARD_SSL_PORT = 443;
48    /**
49     * A {@link KeyManager} to track client certificate requests from servers.
50     */
51    private final TrackingKeyManager mTrackingKeyManager;
52
53    /**
54     * Not publicly instantiable except via {@link #newInstance(HttpParams)}
55     */
56    private EmailClientConnectionManager(
57            HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) {
58        super(params, registry);
59        mTrackingKeyManager = keyManager;
60    }
61
62    public static EmailClientConnectionManager newInstance(HttpParams params, boolean ssl,
63            int port) {
64        TrackingKeyManager keyManager = new TrackingKeyManager();
65
66        // Create a registry for our three schemes; http and https will use built-in factories
67        SchemeRegistry registry = new SchemeRegistry();
68        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
69                ssl ? STANDARD_PORT : port));
70        // Register https with the secure factory
71        registry.register(new Scheme("https",
72                SSLUtils.getHttpSocketFactory(false, keyManager), ssl ? port : STANDARD_SSL_PORT));
73        // Register the httpts scheme with our insecure factory
74        registry.register(new Scheme("httpts",
75                SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager),
76                ssl ? port : STANDARD_SSL_PORT));
77
78        return new EmailClientConnectionManager(params, registry, keyManager);
79    }
80
81    /**
82     * Ensures that a client SSL certificate is known to be used for the specified connection
83     * manager.
84     * A {@link SchemeRegistry} is used to denote which client certificates to use for a given
85     * connection, so clients of this connection manager should use
86     * {@link #makeSchemeForClientCert(String, boolean)}.
87     */
88    public synchronized void registerClientCert(Context context, HostAuth hostAuth)
89            throws CertificateException {
90        SchemeRegistry registry = getSchemeRegistry();
91        String schemeName = makeSchemeForClientCert(hostAuth.mClientCertAlias,
92                hostAuth.shouldTrustAllServerCerts());
93        Scheme existing = registry.get(schemeName);
94        if (existing == null) {
95            if (LOG_ENABLED) {
96                Log.i(Logging.LOG_TAG, "Registering socket factory for certificate alias ["
97                        + hostAuth.mClientCertAlias + "]");
98            }
99            KeyManager keyManager =
100                    KeyChainKeyManager.fromAlias(context, hostAuth.mClientCertAlias);
101            SSLCertificateSocketFactory underlying = SSLUtils.getSSLSocketFactory(
102                    hostAuth.shouldTrustAllServerCerts());
103            underlying.setKeyManagers(new KeyManager[] { keyManager });
104            registry.register(
105                    new Scheme(schemeName, new SSLSocketFactory(underlying), hostAuth.mPort));
106        }
107    }
108
109    /**
110     * Unregisters a custom connection type that uses a client certificate on the connection
111     * manager.
112     * @see #registerClientCert(Context, String, boolean)
113     */
114    public synchronized void unregisterClientCert(
115            String clientCertAlias, boolean trustAllServerCerts) {
116        SchemeRegistry registry = getSchemeRegistry();
117        String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
118        Scheme existing = registry.get(schemeName);
119        if (existing != null) {
120            registry.unregister(schemeName);
121        }
122    }
123
124    /**
125     * Builds a custom scheme name to be used in a connection manager according to the connection
126     * parameters.
127     */
128    public static String makeScheme(
129            boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) {
130        if (clientCertAlias != null) {
131            return makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
132        } else {
133            return useSsl ? (trustAllServerCerts ? "httpts" : "https") : "http";
134        }
135    }
136
137    /**
138     * Builds a unique scheme name for an SSL connection that uses a client user certificate.
139     */
140    private static String makeSchemeForClientCert(
141            String clientCertAlias, boolean trustAllServerCerts) {
142        String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias);
143        return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias;
144    }
145
146    /**
147     * @param since A timestamp in millis from epoch from which to check
148     * @return whether or not this connection manager has detected any unsatisfied requests for
149     *     a client SSL certificate by any servers
150     */
151    public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) {
152        return mTrackingKeyManager.getLastCertReqTime() >= since;
153    }
154}
155