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 */ 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