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.net.SSLCertificateSocketFactory; 22 import android.util.Log; 23 24 import com.android.emailcommon.Logging; 25 import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager; 26 import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager; 27 28 import org.apache.http.conn.scheme.PlainSocketFactory; 29 import org.apache.http.conn.scheme.Scheme; 30 import org.apache.http.conn.scheme.SchemeRegistry; 31 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 32 import org.apache.http.params.HttpParams; 33 34 import java.security.cert.CertificateException; 35 36 import javax.net.ssl.KeyManager; 37 38 /** 39 * A thread-safe client connection manager that manages the use of client certificates from the 40 * {@link android.security.KeyChain} for SSL connections. 41 */ 42 public class EmailClientConnectionManager extends ThreadSafeClientConnManager { 43 44 private static final boolean LOG_ENABLED = false; 45 46 /** 47 * A {@link KeyManager} to track client certificate requests from servers. 48 */ 49 private final TrackingKeyManager mTrackingKeyManager; 50 51 /** 52 * Not publicly instantiable except via {@link #newInstance(HttpParams)} 53 */ 54 private EmailClientConnectionManager( 55 HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) { 56 super(params, registry); 57 mTrackingKeyManager = keyManager; 58 } 59 60 public static EmailClientConnectionManager newInstance(HttpParams params) { 61 TrackingKeyManager keyManager = new TrackingKeyManager(); 62 63 // Create a registry for our three schemes; http and https will use built-in factories 64 SchemeRegistry registry = new SchemeRegistry(); 65 registry.register(new Scheme("http", 66 PlainSocketFactory.getSocketFactory(), 80)); 67 registry.register(new Scheme("https", 68 SSLUtils.getHttpSocketFactory(false, keyManager), 443)); 69 70 // Register the httpts scheme with our insecure factory 71 registry.register(new Scheme("httpts", 72 SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager), 443)); 73 74 return new EmailClientConnectionManager(params, registry, keyManager); 75 } 76 77 /** 78 * Ensures that a client SSL certificate is known to be used for the specified connection 79 * manager. 80 * A {@link SchemeRegistry} is used to denote which client certificates to use for a given 81 * connection, so clients of this connection manager should use 82 * {@link #makeSchemeForClientCert(String, boolean)}. 83 */ 84 public synchronized void registerClientCert( 85 Context context, String clientCertAlias, boolean trustAllServerCerts) 86 throws CertificateException { 87 SchemeRegistry registry = getSchemeRegistry(); 88 String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); 89 Scheme existing = registry.get(schemeName); 90 if (existing == null) { 91 if (LOG_ENABLED) { 92 Log.i(Logging.LOG_TAG, "Registering socket factory for certificate alias [" 93 + clientCertAlias + "]"); 94 } 95 KeyManager keyManager = KeyChainKeyManager.fromAlias(context, clientCertAlias); 96 SSLCertificateSocketFactory underlying = SSLUtils.getSSLSocketFactory( 97 trustAllServerCerts); 98 underlying.setKeyManagers(new KeyManager[] { keyManager }); 99 registry.register(new Scheme(schemeName, new SSLSocketFactory(underlying), 443)); 100 } 101 } 102 103 /** 104 * Unregisters a custom connection type that uses a client certificate on the connection 105 * manager. 106 * @see #registerClientCert(Context, String, boolean) 107 */ 108 public synchronized void unregisterClientCert( 109 String clientCertAlias, boolean trustAllServerCerts) { 110 SchemeRegistry registry = getSchemeRegistry(); 111 String schemeName = makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); 112 Scheme existing = registry.get(schemeName); 113 if (existing != null) { 114 registry.unregister(schemeName); 115 } 116 } 117 118 /** 119 * Builds a custom scheme name to be used in a connection manager according to the connection 120 * parameters. 121 */ 122 public static String makeScheme( 123 boolean useSsl, boolean trustAllServerCerts, String clientCertAlias) { 124 if (clientCertAlias != null) { 125 return makeSchemeForClientCert(clientCertAlias, trustAllServerCerts); 126 } else { 127 return useSsl ? (trustAllServerCerts ? "httpts" : "https") : "http"; 128 } 129 } 130 131 /** 132 * Builds a unique scheme name for an SSL connection that uses a client user certificate. 133 */ 134 private static String makeSchemeForClientCert( 135 String clientCertAlias, boolean trustAllServerCerts) { 136 String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias); 137 return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias; 138 } 139 140 /** 141 * @param since A timestamp in millis from epoch from which to check 142 * @return whether or not this connection manager has detected any unsatisfied requests for 143 * a client SSL certificate by any servers 144 */ 145 public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) { 146 return mTrackingKeyManager.getLastCertReqTime() >= since; 147 } 148 } 149