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 
18 package com.android.emailcommon.utility;
19 
20 import android.content.Context;
21 import android.text.TextUtils;
22 
23 import com.android.emailcommon.Logging;
24 import com.android.emailcommon.provider.HostAuth;
25 import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
26 import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
27 import com.android.mail.utils.LogUtils;
28 
29 import org.apache.http.conn.scheme.PlainSocketFactory;
30 import org.apache.http.conn.scheme.Scheme;
31 import org.apache.http.conn.scheme.SchemeRegistry;
32 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
33 import org.apache.http.params.HttpParams;
34 
35 import java.security.cert.CertificateException;
36 
37 import 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  */
43 public 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      */
EmailClientConnectionManager( HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager)57     private EmailClientConnectionManager(
58             HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) {
59         super(params, registry);
60         mTrackingKeyManager = keyManager;
61     }
62 
newInstance(Context context, HttpParams params, HostAuth hostAuth)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      */
registerClientCert(Context context, HostAuth hostAuth)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      */
unregisterClientCert( String clientCertAlias, boolean trustAllServerCerts)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      */
makeScheme( boolean useSsl, boolean trustAllServerCerts, String clientCertAlias)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      */
makeSchemeForClientCert( String clientCertAlias, boolean trustAllServerCerts)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      */
hasDetectedUnsatisfiedCertReq(long since)157     public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) {
158         return mTrackingKeyManager.getLastCertReqTime() >= since;
159     }
160 }
161