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.text.TextUtils;
22
23import com.android.emailcommon.Logging;
24import com.android.emailcommon.provider.HostAuth;
25import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
26import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
27import com.android.mail.utils.LogUtils;
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 int STANDARD_PORT = 80;
46    private static final int STANDARD_SSL_PORT = 443;
47    private static final boolean LOG_ENABLED = false;
48
49    /**
50     * A {@link KeyManager} to track client certificate requests from servers.
51     */
52    private final TrackingKeyManager mTrackingKeyManager;
53
54    /**
55     * Not publicly instantiable except via {@link #newInstance(HttpParams)}
56     */
57    private EmailClientConnectionManager(
58            HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) {
59        super(params, registry);
60        mTrackingKeyManager = keyManager;
61    }
62
63    public static EmailClientConnectionManager newInstance(Context context, HttpParams params,
64            HostAuth hostAuth) {
65        TrackingKeyManager keyManager = new TrackingKeyManager();
66        boolean ssl = hostAuth.shouldUseSsl();
67        int port = hostAuth.mPort;
68
69        // Create a registry for our three schemes; http and https will use built-in factories
70        SchemeRegistry registry = new SchemeRegistry();
71        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
72                ssl ? STANDARD_PORT : port));
73        // Register https with the secure factory
74        registry.register(new Scheme("https",
75                SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, false),
76                ssl ? port : STANDARD_SSL_PORT));
77        // Register the httpts scheme with our insecure factory
78        registry.register(new Scheme("httpts",
79                SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, true),
80                ssl ? port : STANDARD_SSL_PORT));
81
82        return new EmailClientConnectionManager(params, registry, keyManager);
83    }
84
85    /**
86     * Ensures that a client SSL certificate is known to be used for the specified connection
87     * manager.
88     * A {@link SchemeRegistry} is used to denote which client certificates to use for a given
89     * connection, so clients of this connection manager should use
90     * {@link #makeSchemeForClientCert(String, boolean)}.
91     */
92    public synchronized void registerClientCert(Context context, HostAuth hostAuth)
93            throws CertificateException {
94        if (TextUtils.isEmpty(hostAuth.mClientCertAlias)) {
95            return;
96        }
97        SchemeRegistry registry = getSchemeRegistry();
98        String schemeName = makeSchemeForClientCert(hostAuth.mClientCertAlias,
99                hostAuth.shouldTrustAllServerCerts());
100        Scheme existing = registry.get(schemeName);
101        if (existing == null) {
102            if (LOG_ENABLED) {
103                LogUtils.i(Logging.LOG_TAG, "Registering socket factory for certificate alias ["
104                        + hostAuth.mClientCertAlias + "]");
105            }
106            KeyManager keyManager =
107                    KeyChainKeyManager.fromAlias(context, hostAuth.mClientCertAlias);
108            boolean insecure = hostAuth.shouldTrustAllServerCerts();
109            SSLSocketFactory ssf =
110                    SSLUtils.getHttpSocketFactory(context, hostAuth, keyManager, insecure);
111            registry.register(new Scheme(schemeName, ssf, hostAuth.mPort));
112        }
113    }
114
115    /**
116     * Unregisters a custom connection type that uses a client certificate on the connection
117     * manager.
118     * @see #registerClientCert(Context, String, boolean)
119     */
120    public synchronized void unregisterClientCert(
121            String clientCertAlias, boolean trustAllServerCerts) {
122        SchemeRegistry registry = getSchemeRegistry();
123        String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
124        Scheme existing = registry.get(schemeName);
125        if (existing != null) {
126            registry.unregister(schemeName);
127        }
128    }
129
130    /**
131     * Builds a custom scheme name to be used in a connection manager according to the connection
132     * parameters.
133     */
134    public static String makeScheme(
135            boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) {
136        if (!TextUtils.isEmpty(clientCertAlias)) {
137            return makeSchemeForClientCert(clientCertAlias, trustAllServerCerts);
138        } else {
139            return useSsl ? (trustAllServerCerts ? "httpts" : "https") : "http";
140        }
141    }
142
143    /**
144     * Builds a unique scheme name for an SSL connection that uses a client user certificate.
145     */
146    private static String makeSchemeForClientCert(
147            String clientCertAlias, boolean trustAllServerCerts) {
148        String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias);
149        return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias;
150    }
151
152    /**
153     * @param since A timestamp in millis from epoch from which to check
154     * @return whether or not this connection manager has detected any unsatisfied requests for
155     *     a client SSL certificate by any servers
156     */
157    public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) {
158        return mTrackingKeyManager.getLastCertReqTime() >= since;
159    }
160}
161