/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.emailcommon.utility; import android.content.Context; import android.net.SSLCertificateSocketFactory; import android.util.Log; import com.android.emailcommon.Logging; import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager; import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.HttpParams; import java.security.cert.CertificateException; import javax.net.ssl.KeyManager; /** * A thread-safe client connection manager that manages the use of client certificates from the * {@link android.security.KeyChain} for SSL connections. */ public class EmailClientConnectionManager extends ThreadSafeClientConnManager { private static final boolean LOG_ENABLED = false; /** * A {@link KeyManager} to track client certificate requests from servers. */ private final TrackingKeyManager mTrackingKeyManager; /** * Not publicly instantiable except via {@link #newInstance(HttpParams)} */ private EmailClientConnectionManager( HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) { super(params, registry); mTrackingKeyManager = keyManager; } public static EmailClientConnectionManager newInstance(HttpParams params) { TrackingKeyManager keyManager = new TrackingKeyManager(); // Create a registry for our three schemes; http and https will use built-in factories SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", SSLUtils.getHttpSocketFactory(false, keyManager), 443)); // Register the httpts scheme with our insecure factory registry.register(new Scheme("httpts", SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager), 443)); return new EmailClientConnectionManager(params, registry, keyManager); } /** * Ensures that a client SSL certificate is known to be used for the specified connection * manager. * A {@link SchemeRegistry} is used to denote which client certificates to use for a given * connection, so clients of this connection manager should use * {@link #makeSchemeForClientCert(String, boolean)}. */ public synchronized void registerClientCert( Context context, String clientCertAlias, boolean trustAllServerCerts) throws CertificateException { SchemeRegistry registry = getSchemeRegistry(); String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); Scheme existing = registry.get(schemeName); if (existing == null) { if (LOG_ENABLED) { Log.i(Logging.LOG_TAG, "Registering socket factory for certificate alias [" + clientCertAlias + "]"); } KeyManager keyManager = KeyChainKeyManager.fromAlias(context, clientCertAlias); SSLCertificateSocketFactory underlying = SSLUtils.getSSLSocketFactory( trustAllServerCerts); underlying.setKeyManagers(new KeyManager[] { keyManager }); registry.register(new Scheme(schemeName, new SSLSocketFactory(underlying), 443)); } } /** * Unregisters a custom connection type that uses a client certificate on the connection * manager. * @see #registerClientCert(Context, String, boolean) */ public synchronized void unregisterClientCert( String clientCertAlias, boolean trustAllServerCerts) { SchemeRegistry registry = getSchemeRegistry(); String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); Scheme existing = registry.get(schemeName); if (existing != null) { registry.unregister(schemeName); } } /** * Builds a custom scheme name to be used in a connection manager according to the connection * parameters. */ public static String makeScheme( boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) { if (clientCertAlias != null) { return makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); } else { return useSsl ? (trustAllServerCerts ? "httpts" : "https") : "http"; } } /** * Builds a unique scheme name for an SSL connection that uses a client user certificate. */ private static String makeSchemeForClientCert( String clientCertAlias, boolean trustAllServerCerts) { String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias); return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias; } /** * @param since A timestamp in millis from epoch from which to check * @return whether or not this connection manager has detected any unsatisfied requests for * a client SSL certificate by any servers */ public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) { return mTrackingKeyManager.getLastCertReqTime() >= since; } }