SSLUtils.java revision cb24e515b7983133133ca38bd3e3e6354daaab76
1/* 2 * Copyright (C) 2010 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 17package com.android.emailcommon.utility; 18 19import android.content.Context; 20import android.net.SSLCertificateSocketFactory; 21import android.security.KeyChain; 22import android.security.KeyChainException; 23import android.util.Log; 24 25import java.net.Socket; 26import java.security.Principal; 27import java.security.PrivateKey; 28import java.security.cert.CertificateException; 29import java.security.cert.X509Certificate; 30import java.util.Arrays; 31 32import javax.net.ssl.KeyManager; 33import javax.net.ssl.X509ExtendedKeyManager; 34 35public class SSLUtils { 36 private static SSLCertificateSocketFactory sInsecureFactory; 37 private static SSLCertificateSocketFactory sSecureFactory; 38 39 private static final boolean LOG_ENABLED = false; 40 private static final String TAG = "Email.Ssl"; 41 42 /** 43 * Returns a {@link javax.net.ssl.SSLSocketFactory}. 44 * Optionally bypass all SSL certificate checks. 45 * 46 * @param insecure if true, bypass all SSL certificate checks 47 */ 48 public synchronized static final SSLCertificateSocketFactory getSSLSocketFactory( 49 boolean insecure) { 50 if (insecure) { 51 if (sInsecureFactory == null) { 52 sInsecureFactory = (SSLCertificateSocketFactory) 53 SSLCertificateSocketFactory.getInsecure(0, null); 54 } 55 return sInsecureFactory; 56 } else { 57 if (sSecureFactory == null) { 58 sSecureFactory = (SSLCertificateSocketFactory) 59 SSLCertificateSocketFactory.getDefault(0, null); 60 } 61 return sSecureFactory; 62 } 63 } 64 65 /** 66 * Returns a {@link org.apache.http.conn.ssl.SSLSocketFactory SSLSocketFactory} for use with the 67 * Apache HTTP stack. 68 */ 69 public static SSLSocketFactory getHttpSocketFactory(boolean insecure, KeyManager keyManager) { 70 SSLCertificateSocketFactory underlying = getSSLSocketFactory(insecure); 71 if (keyManager != null) { 72 underlying.setKeyManagers(new KeyManager[] { keyManager }); 73 } 74 return new SSLSocketFactory(underlying); 75 } 76 77 /** 78 * Escapes the contents a string to be used as a safe scheme name in the URI according to 79 * http://tools.ietf.org/html/rfc3986#section-3.1 80 * 81 * This does not ensure that the first character is a letter (which is required by the RFC). 82 */ 83 public static String escapeForSchemeName(String s) { 84 // According to the RFC, scheme names are case-insensitive. 85 s = s.toLowerCase(); 86 87 StringBuilder sb = new StringBuilder(); 88 for (int i = 0; i < s.length(); i++) { 89 char c = s.charAt(i); 90 if (Character.isLetter(c) || Character.isDigit(c) 91 || ('-' == c) || ('.' == c)) { 92 // Safe - use as is. 93 sb.append(c); 94 } else if ('+' == c) { 95 // + is used as our escape character, so double it up. 96 sb.append("++"); 97 } else { 98 // Unsafe - escape. 99 sb.append('+').append((int) c); 100 } 101 } 102 return sb.toString(); 103 } 104 105 @SuppressWarnings("unused") 106 private static abstract class StubKeyManager extends X509ExtendedKeyManager { 107 @Override public abstract String chooseClientAlias( 108 String[] keyTypes, Principal[] issuers, Socket socket); 109 110 @Override public abstract X509Certificate[] getCertificateChain(String alias); 111 112 @Override public abstract PrivateKey getPrivateKey(String alias); 113 114 115 // The following methods are unused. 116 117 @Override 118 public final String chooseServerAlias( 119 String keyType, Principal[] issuers, Socket socket) { 120 // not a client SSLSocket callback 121 throw new UnsupportedOperationException(); 122 } 123 124 @Override 125 public final String[] getClientAliases(String keyType, Principal[] issuers) { 126 // not a client SSLSocket callback 127 throw new UnsupportedOperationException(); 128 } 129 130 @Override 131 public final String[] getServerAliases(String keyType, Principal[] issuers) { 132 // not a client SSLSocket callback 133 throw new UnsupportedOperationException(); 134 } 135 } 136 137 /** 138 * A dummy {@link KeyManager} which throws a {@link CertificateRequestedException} if the 139 * server requests a certificate. 140 */ 141 public static class TrackingKeyManager extends StubKeyManager { 142 @Override 143 public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 144 if (LOG_ENABLED) { 145 Log.i(TAG, "TrackingKeyManager: requesting a client cert alias for " 146 + Arrays.toString(keyTypes)); 147 } 148 throw new CertificateRequestedException(); 149 } 150 151 @Override 152 public X509Certificate[] getCertificateChain(String alias) { 153 return null; 154 } 155 156 @Override 157 public PrivateKey getPrivateKey(String alias) { 158 return null; 159 } 160 } 161 162 /** 163 * An exception indicating that a server requested a client certificate but none was 164 * available to be presented. 165 */ 166 public static class CertificateRequestedException extends RuntimeException { 167 } 168 169 /** 170 * A {@link KeyManager} that reads uses credentials stored in the system {@link KeyChain}. 171 */ 172 public static class KeyChainKeyManager extends StubKeyManager { 173 private final String mClientAlias; 174 private final X509Certificate[] mCertificateChain; 175 private final PrivateKey mPrivateKey; 176 177 /** 178 * Builds an instance of a KeyChainKeyManager using the given certificate alias. 179 * If for any reason retrieval of the credentials from the system {@link KeyChain} fails, 180 * a {@code null} value will be returned. 181 */ 182 public static KeyChainKeyManager fromAlias(Context context, String alias) 183 throws CertificateException { 184 X509Certificate[] certificateChain; 185 try { 186 certificateChain = KeyChain.getCertificateChain(context, alias); 187 } catch (KeyChainException e) { 188 logError(alias, "certificate chain", e); 189 throw new CertificateException(e); 190 } catch (InterruptedException e) { 191 logError(alias, "certificate chain", e); 192 throw new CertificateException(e); 193 } 194 195 PrivateKey privateKey; 196 try { 197 privateKey = KeyChain.getPrivateKey(context, alias); 198 } catch (KeyChainException e) { 199 logError(alias, "private key", e); 200 throw new CertificateException(e); 201 } catch (InterruptedException e) { 202 logError(alias, "private key", e); 203 throw new CertificateException(e); 204 } 205 206 if (certificateChain == null || privateKey == null) { 207 throw new CertificateException( 208 "Can't access certificate from keystore for alias [" + alias + "]"); 209 } 210 211 return new KeyChainKeyManager(alias, certificateChain, privateKey); 212 } 213 214 private static void logError(String alias, String type, Exception ex) { 215 Log.e(TAG, "Unable to retrieve " + type + " for [" + alias + "] due to " + ex); 216 } 217 218 private KeyChainKeyManager( 219 String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) { 220 mClientAlias = clientAlias; 221 mCertificateChain = certificateChain; 222 mPrivateKey = privateKey; 223 } 224 225 226 @Override 227 public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 228 if (LOG_ENABLED) { 229 Log.i(TAG, "Requesting a client cert alias for " + Arrays.toString(keyTypes)); 230 } 231 return mClientAlias; 232 } 233 234 @Override 235 public X509Certificate[] getCertificateChain(String alias) { 236 if (LOG_ENABLED) { 237 Log.i(TAG, "Requesting a client certificate chain for alias [" + alias + "]"); 238 } 239 return mCertificateChain; 240 } 241 242 @Override 243 public PrivateKey getPrivateKey(String alias) { 244 if (LOG_ENABLED) { 245 Log.i(TAG, "Requesting a client private key for alias [" + alias + "]"); 246 } 247 return mPrivateKey; 248 } 249 } 250} 251