SSLUtils.java revision 78959916e771114ff8c48fc181e34a7dff0aa672
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.X509Certificate; 29import java.util.Arrays; 30 31import javax.net.ssl.KeyManager; 32import javax.net.ssl.X509ExtendedKeyManager; 33 34public class SSLUtils { 35 private static SSLCertificateSocketFactory sInsecureFactory; 36 private static SSLCertificateSocketFactory sSecureFactory; 37 38 private static final boolean LOG_ENABLED = false; 39 private static final String TAG = "Email.Ssl"; 40 41 /** 42 * Returns a {@link javax.net.ssl.SSLSocketFactory}. 43 * Optionally bypass all SSL certificate checks. 44 * 45 * @param insecure if true, bypass all SSL certificate checks 46 */ 47 public synchronized static final SSLCertificateSocketFactory getSSLSocketFactory( 48 boolean insecure) { 49 if (insecure) { 50 if (sInsecureFactory == null) { 51 sInsecureFactory = (SSLCertificateSocketFactory) 52 SSLCertificateSocketFactory.getInsecure(0, null); 53 } 54 return sInsecureFactory; 55 } else { 56 if (sSecureFactory == null) { 57 sSecureFactory = (SSLCertificateSocketFactory) 58 SSLCertificateSocketFactory.getDefault(0, null); 59 } 60 return sSecureFactory; 61 } 62 } 63 64 /** 65 * Returns a {@link org.apache.http.conn.ssl.SSLSocketFactory SSLSocketFactory} for use with the 66 * Apache HTTP stack. 67 */ 68 public static SSLSocketFactory getHttpSocketFactory(boolean insecure) { 69 SSLCertificateSocketFactory underlying = getSSLSocketFactory(insecure); 70 // TODO: register a keymanager that will simply listen for requests for a client 71 // certificate so that higher levels know to ask the user for such credentials. 72 return new SSLSocketFactory(underlying); 73 } 74 75 /** 76 * Escapes the contents a string to be used as a safe scheme name in the URI according to 77 * http://tools.ietf.org/html/rfc3986#section-3.1 78 * 79 * This does not ensure that the first character is a letter (which is required by the RFC). 80 */ 81 public static String escapeForSchemeName(String s) { 82 // According to the RFC, scheme names are case-insensitive. 83 s = s.toLowerCase(); 84 85 StringBuilder sb = new StringBuilder(); 86 for (int i = 0; i < s.length(); i++) { 87 char c = s.charAt(i); 88 if (Character.isLetter(c) || Character.isDigit(c) 89 || ('-' == c) || ('.' == c)) { 90 // Safe - use as is. 91 sb.append(c); 92 } else if ('+' == c) { 93 // + is used as our escape character, so double it up. 94 sb.append("++"); 95 } else { 96 // Unsafe - escape. 97 sb.append('+').append((int) c); 98 } 99 } 100 return sb.toString(); 101 } 102 103 @SuppressWarnings("unused") 104 private static abstract class StubKeyManager extends X509ExtendedKeyManager { 105 @Override public abstract String chooseClientAlias( 106 String[] keyTypes, Principal[] issuers, Socket socket); 107 108 @Override public abstract X509Certificate[] getCertificateChain(String alias); 109 110 @Override public abstract PrivateKey getPrivateKey(String alias); 111 112 113 // The following methods are unused. 114 115 @Override 116 public final String chooseServerAlias( 117 String keyType, Principal[] issuers, Socket socket) { 118 // not a client SSLSocket callback 119 throw new UnsupportedOperationException(); 120 } 121 122 @Override 123 public final String[] getClientAliases(String keyType, Principal[] issuers) { 124 // not a client SSLSocket callback 125 throw new UnsupportedOperationException(); 126 } 127 128 @Override 129 public final String[] getServerAliases(String keyType, Principal[] issuers) { 130 // not a client SSLSocket callback 131 throw new UnsupportedOperationException(); 132 } 133 } 134 135 /** 136 * A {@link KeyManager} that reads uses credentials stored in the system {@link KeyChain}. 137 */ 138 public static class KeyChainKeyManager extends StubKeyManager { 139 private final String mClientAlias; 140 private final X509Certificate[] mCertificateChain; 141 private final PrivateKey mPrivateKey; 142 143 /** 144 * Builds an instance of a KeyChainKeyManager using the given certificate alias. 145 * If for any reason retrieval of the credentials from the system {@link KeyChain} fails, 146 * a {@code null} value will be returned. 147 */ 148 public static KeyChainKeyManager fromAlias(Context context, String alias) { 149 X509Certificate[] certificateChain; 150 try { 151 certificateChain = KeyChain.getCertificateChain(context, alias); 152 } catch (KeyChainException e) { 153 Log.e(TAG, "Unable to retrieve certificate chain for [" + alias + "] due to " 154 + e); 155 return null; 156 } catch (InterruptedException e) { 157 Log.e(TAG, "Unable to retrieve certificate chain for [" + alias + "] due to " 158 + e); 159 return null; 160 } 161 162 PrivateKey privateKey; 163 try { 164 privateKey = KeyChain.getPrivateKey(context, alias); 165 } catch (KeyChainException e) { 166 Log.e(TAG, "Unable to retrieve private key for [" + alias + "] due to " + e); 167 return null; 168 } catch (InterruptedException e) { 169 Log.e(TAG, "Unable to retrieve private key for [" + alias + "] due to " + e); 170 return null; 171 } 172 173 return new KeyChainKeyManager(alias, certificateChain, privateKey); 174 } 175 176 private KeyChainKeyManager( 177 String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) { 178 mClientAlias = clientAlias; 179 mCertificateChain = certificateChain; 180 mPrivateKey = privateKey; 181 } 182 183 184 @Override 185 public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 186 if (LOG_ENABLED) { 187 Log.i(TAG, "Requesting a client cert alias for " + Arrays.toString(keyTypes)); 188 } 189 return mClientAlias; 190 } 191 192 @Override 193 public X509Certificate[] getCertificateChain(String alias) { 194 if (LOG_ENABLED) { 195 Log.i(TAG, "Requesting a client certificate chain for alias [" + alias + "]"); 196 } 197 return mCertificateChain; 198 } 199 200 @Override 201 public PrivateKey getPrivateKey(String alias) { 202 if (LOG_ENABLED) { 203 Log.i(TAG, "Requesting a client private key for alias [" + alias + "]"); 204 } 205 return mPrivateKey; 206 } 207 } 208} 209