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 package android.security; 17 18 import android.app.Activity; 19 import android.app.PendingIntent; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.IBinder; 25 import android.os.Looper; 26 import android.os.RemoteException; 27 import java.io.ByteArrayInputStream; 28 import java.io.Closeable; 29 import java.security.InvalidKeyException; 30 import java.security.Principal; 31 import java.security.PrivateKey; 32 import java.security.cert.Certificate; 33 import java.security.cert.CertificateException; 34 import java.security.cert.CertificateFactory; 35 import java.security.cert.X509Certificate; 36 import java.util.List; 37 import java.util.concurrent.BlockingQueue; 38 import java.util.concurrent.LinkedBlockingQueue; 39 40 import org.apache.harmony.xnet.provider.jsse.OpenSSLEngine; 41 import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore; 42 43 /** 44 * The {@code KeyChain} class provides access to private keys and 45 * their corresponding certificate chains in credential storage. 46 * 47 * <p>Applications accessing the {@code KeyChain} normally go through 48 * these steps: 49 * 50 * <ol> 51 * 52 * <li>Receive a callback from an {@link javax.net.ssl.X509KeyManager 53 * X509KeyManager} that a private key is requested. 54 * 55 * <li>Call {@link #choosePrivateKeyAlias 56 * choosePrivateKeyAlias} to allow the user to select from a 57 * list of currently available private keys and corresponding 58 * certificate chains. The chosen alias will be returned by the 59 * callback {@link KeyChainAliasCallback#alias}, or null if no private 60 * key is available or the user cancels the request. 61 * 62 * <li>Call {@link #getPrivateKey} and {@link #getCertificateChain} to 63 * retrieve the credentials to return to the corresponding {@link 64 * javax.net.ssl.X509KeyManager} callbacks. 65 * 66 * </ol> 67 * 68 * <p>An application may remember the value of a selected alias to 69 * avoid prompting the user with {@link #choosePrivateKeyAlias 70 * choosePrivateKeyAlias} on subsequent connections. If the alias is 71 * no longer valid, null will be returned on lookups using that value 72 * 73 * <p>An application can request the installation of private keys and 74 * certificates via the {@code Intent} provided by {@link 75 * #createInstallIntent}. Private keys installed via this {@code 76 * Intent} will be accessible via {@link #choosePrivateKeyAlias} while 77 * Certificate Authority (CA) certificates will be trusted by all 78 * applications through the default {@code X509TrustManager}. 79 */ 80 // TODO reference intent for credential installation when public 81 public final class KeyChain { 82 83 private static final String TAG = "KeyChain"; 84 85 /** 86 * @hide Also used by KeyChainService implementation 87 */ 88 public static final String ACCOUNT_TYPE = "com.android.keychain"; 89 90 /** 91 * Action to bring up the KeyChainActivity 92 */ 93 private static final String ACTION_CHOOSER = "com.android.keychain.CHOOSER"; 94 95 /** 96 * Extra for use with {@link #ACTION_CHOOSER} 97 * @hide Also used by KeyChainActivity implementation 98 */ 99 public static final String EXTRA_RESPONSE = "response"; 100 101 /** 102 * Extra for use with {@link #ACTION_CHOOSER} 103 * @hide Also used by KeyChainActivity implementation 104 */ 105 public static final String EXTRA_HOST = "host"; 106 107 /** 108 * Extra for use with {@link #ACTION_CHOOSER} 109 * @hide Also used by KeyChainActivity implementation 110 */ 111 public static final String EXTRA_PORT = "port"; 112 113 /** 114 * Extra for use with {@link #ACTION_CHOOSER} 115 * @hide Also used by KeyChainActivity implementation 116 */ 117 public static final String EXTRA_ALIAS = "alias"; 118 119 /** 120 * Extra for use with {@link #ACTION_CHOOSER} 121 * @hide Also used by KeyChainActivity implementation 122 */ 123 public static final String EXTRA_SENDER = "sender"; 124 125 /** 126 * Action to bring up the CertInstaller. 127 */ 128 private static final String ACTION_INSTALL = "android.credentials.INSTALL"; 129 130 /** 131 * Optional extra to specify a {@code String} credential name on 132 * the {@code Intent} returned by {@link #createInstallIntent}. 133 */ 134 // Compatible with old com.android.certinstaller.CredentialHelper.CERT_NAME_KEY 135 public static final String EXTRA_NAME = "name"; 136 137 /** 138 * Optional extra to specify an X.509 certificate to install on 139 * the {@code Intent} returned by {@link #createInstallIntent}. 140 * The extra value should be a PEM or ASN.1 DER encoded {@code 141 * byte[]}. An {@link X509Certificate} can be converted to DER 142 * encoded bytes with {@link X509Certificate#getEncoded}. 143 * 144 * <p>{@link #EXTRA_NAME} may be used to provide a default alias 145 * name for the installed certificate. 146 */ 147 // Compatible with old android.security.Credentials.CERTIFICATE 148 public static final String EXTRA_CERTIFICATE = "CERT"; 149 150 /** 151 * Optional extra for use with the {@code Intent} returned by 152 * {@link #createInstallIntent} to specify a PKCS#12 key store to 153 * install. The extra value should be a {@code byte[]}. The bytes 154 * may come from an external source or be generated with {@link 155 * java.security.KeyStore#store} on a "PKCS12" instance. 156 * 157 * <p>The user will be prompted for the password to load the key store. 158 * 159 * <p>The key store will be scanned for {@link 160 * java.security.KeyStore.PrivateKeyEntry} entries and both the 161 * private key and associated certificate chain will be installed. 162 * 163 * <p>{@link #EXTRA_NAME} may be used to provide a default alias 164 * name for the installed credentials. 165 */ 166 // Compatible with old android.security.Credentials.PKCS12 167 public static final String EXTRA_PKCS12 = "PKCS12"; 168 169 170 /** 171 * Broadcast Action: Indicates the trusted storage has changed. Sent when 172 * one of this happens: 173 * 174 * <ul> 175 * <li>a new CA is added, 176 * <li>an existing CA is removed or disabled, 177 * <li>a disabled CA is enabled, 178 * <li>trusted storage is reset (all user certs are cleared), 179 * <li>when permission to access a private key is changed. 180 * </ul> 181 */ 182 public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED"; 183 184 /** 185 * Returns an {@code Intent} that can be used for credential 186 * installation. The intent may be used without any extras, in 187 * which case the user will be able to install credentials from 188 * their own source. 189 * 190 * <p>Alternatively, {@link #EXTRA_CERTIFICATE} or {@link 191 * #EXTRA_PKCS12} maybe used to specify the bytes of an X.509 192 * certificate or a PKCS#12 key store for installation. These 193 * extras may be combined with {@link #EXTRA_NAME} to provide a 194 * default alias name for credentials being installed. 195 * 196 * <p>When used with {@link Activity#startActivityForResult}, 197 * {@link Activity#RESULT_OK} will be returned if a credential was 198 * successfully installed, otherwise {@link 199 * Activity#RESULT_CANCELED} will be returned. 200 */ createInstallIntent()201 public static Intent createInstallIntent() { 202 Intent intent = new Intent(ACTION_INSTALL); 203 intent.setClassName("com.android.certinstaller", 204 "com.android.certinstaller.CertInstallerMain"); 205 return intent; 206 } 207 208 /** 209 * Launches an {@code Activity} for the user to select the alias 210 * for a private key and certificate pair for authentication. The 211 * selected alias or null will be returned via the 212 * KeyChainAliasCallback callback. 213 * 214 * <p>{@code keyTypes} and {@code issuers} may be used to 215 * highlight suggested choices to the user, although to cope with 216 * sometimes erroneous values provided by servers, the user may be 217 * able to override these suggestions. 218 * 219 * <p>{@code host} and {@code port} may be used to give the user 220 * more context about the server requesting the credentials. 221 * 222 * <p>{@code alias} allows the chooser to preselect an existing 223 * alias which will still be subject to user confirmation. 224 * 225 * @param activity The {@link Activity} context to use for 226 * launching the new sub-Activity to prompt the user to select 227 * a private key; used only to call startActivity(); must not 228 * be null. 229 * @param response Callback to invoke when the request completes; 230 * must not be null 231 * @param keyTypes The acceptable types of asymmetric keys such as 232 * "RSA" or "DSA", or a null array. 233 * @param issuers The acceptable certificate issuers for the 234 * certificate matching the private key, or null. 235 * @param host The host name of the server requesting the 236 * certificate, or null if unavailable. 237 * @param port The port number of the server requesting the 238 * certificate, or -1 if unavailable. 239 * @param alias The alias to preselect if available, or null if 240 * unavailable. 241 */ choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, String[] keyTypes, Principal[] issuers, String host, int port, String alias)242 public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, 243 String[] keyTypes, Principal[] issuers, 244 String host, int port, 245 String alias) { 246 /* 247 * TODO currently keyTypes, issuers are unused. They are meant 248 * to follow the semantics and purpose of X509KeyManager 249 * method arguments. 250 * 251 * keyTypes would allow the list to be filtered and typically 252 * will be set correctly by the server. In practice today, 253 * most all users will want only RSA, rarely DSA, and usually 254 * only a small number of certs will be available. 255 * 256 * issuers is typically not useful. Some servers historically 257 * will send the entire list of public CAs known to the 258 * server. Others will send none. If this is used, if there 259 * are no matches after applying the constraint, it should be 260 * ignored. 261 */ 262 if (activity == null) { 263 throw new NullPointerException("activity == null"); 264 } 265 if (response == null) { 266 throw new NullPointerException("response == null"); 267 } 268 Intent intent = new Intent(ACTION_CHOOSER); 269 intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response)); 270 intent.putExtra(EXTRA_HOST, host); 271 intent.putExtra(EXTRA_PORT, port); 272 intent.putExtra(EXTRA_ALIAS, alias); 273 // the PendingIntent is used to get calling package name 274 intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0)); 275 activity.startActivity(intent); 276 } 277 278 private static class AliasResponse extends IKeyChainAliasCallback.Stub { 279 private final KeyChainAliasCallback keyChainAliasResponse; AliasResponse(KeyChainAliasCallback keyChainAliasResponse)280 private AliasResponse(KeyChainAliasCallback keyChainAliasResponse) { 281 this.keyChainAliasResponse = keyChainAliasResponse; 282 } alias(String alias)283 @Override public void alias(String alias) { 284 keyChainAliasResponse.alias(alias); 285 } 286 } 287 288 /** 289 * Returns the {@code PrivateKey} for the requested alias, or null 290 * if no there is no result. 291 * 292 * @param alias The alias of the desired private key, typically 293 * returned via {@link KeyChainAliasCallback#alias}. 294 * @throws KeyChainException if the alias was valid but there was some problem accessing it. 295 */ getPrivateKey(Context context, String alias)296 public static PrivateKey getPrivateKey(Context context, String alias) 297 throws KeyChainException, InterruptedException { 298 if (alias == null) { 299 throw new NullPointerException("alias == null"); 300 } 301 KeyChainConnection keyChainConnection = bind(context); 302 try { 303 final IKeyChainService keyChainService = keyChainConnection.getService(); 304 final String keyId = keyChainService.requestPrivateKey(alias); 305 if (keyId == null) { 306 throw new KeyChainException("keystore had a problem"); 307 } 308 309 final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore"); 310 return engine.getPrivateKeyById(keyId); 311 } catch (RemoteException e) { 312 throw new KeyChainException(e); 313 } catch (RuntimeException e) { 314 // only certain RuntimeExceptions can be propagated across the IKeyChainService call 315 throw new KeyChainException(e); 316 } catch (InvalidKeyException e) { 317 throw new KeyChainException(e); 318 } finally { 319 keyChainConnection.close(); 320 } 321 } 322 323 /** 324 * Returns the {@code X509Certificate} chain for the requested 325 * alias, or null if no there is no result. 326 * 327 * @param alias The alias of the desired certificate chain, typically 328 * returned via {@link KeyChainAliasCallback#alias}. 329 * @throws KeyChainException if the alias was valid but there was some problem accessing it. 330 */ getCertificateChain(Context context, String alias)331 public static X509Certificate[] getCertificateChain(Context context, String alias) 332 throws KeyChainException, InterruptedException { 333 if (alias == null) { 334 throw new NullPointerException("alias == null"); 335 } 336 KeyChainConnection keyChainConnection = bind(context); 337 try { 338 IKeyChainService keyChainService = keyChainConnection.getService(); 339 byte[] certificateBytes = keyChainService.getCertificate(alias); 340 TrustedCertificateStore store = new TrustedCertificateStore(); 341 List<X509Certificate> chain = store 342 .getCertificateChain(toCertificate(certificateBytes)); 343 return chain.toArray(new X509Certificate[chain.size()]); 344 } catch (RemoteException e) { 345 throw new KeyChainException(e); 346 } catch (RuntimeException e) { 347 // only certain RuntimeExceptions can be propagated across the IKeyChainService call 348 throw new KeyChainException(e); 349 } finally { 350 keyChainConnection.close(); 351 } 352 } 353 toCertificate(byte[] bytes)354 private static X509Certificate toCertificate(byte[] bytes) { 355 if (bytes == null) { 356 throw new IllegalArgumentException("bytes == null"); 357 } 358 try { 359 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 360 Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); 361 return (X509Certificate) cert; 362 } catch (CertificateException e) { 363 throw new AssertionError(e); 364 } 365 } 366 367 /** 368 * @hide for reuse by CertInstaller and Settings. 369 * @see KeyChain#bind 370 */ 371 public final static class KeyChainConnection implements Closeable { 372 private final Context context; 373 private final ServiceConnection serviceConnection; 374 private final IKeyChainService service; KeyChainConnection(Context context, ServiceConnection serviceConnection, IKeyChainService service)375 private KeyChainConnection(Context context, 376 ServiceConnection serviceConnection, 377 IKeyChainService service) { 378 this.context = context; 379 this.serviceConnection = serviceConnection; 380 this.service = service; 381 } close()382 @Override public void close() { 383 context.unbindService(serviceConnection); 384 } getService()385 public IKeyChainService getService() { 386 return service; 387 } 388 } 389 390 /** 391 * @hide for reuse by CertInstaller and Settings. 392 * 393 * Caller should call unbindService on the result when finished. 394 */ bind(Context context)395 public static KeyChainConnection bind(Context context) throws InterruptedException { 396 if (context == null) { 397 throw new NullPointerException("context == null"); 398 } 399 ensureNotOnMainThread(context); 400 final BlockingQueue<IKeyChainService> q = new LinkedBlockingQueue<IKeyChainService>(1); 401 ServiceConnection keyChainServiceConnection = new ServiceConnection() { 402 volatile boolean mConnectedAtLeastOnce = false; 403 @Override public void onServiceConnected(ComponentName name, IBinder service) { 404 if (!mConnectedAtLeastOnce) { 405 mConnectedAtLeastOnce = true; 406 try { 407 q.put(IKeyChainService.Stub.asInterface(service)); 408 } catch (InterruptedException e) { 409 // will never happen, since the queue starts with one available slot 410 } 411 } 412 } 413 @Override public void onServiceDisconnected(ComponentName name) {} 414 }; 415 boolean isBound = context.bindService(new Intent(IKeyChainService.class.getName()), 416 keyChainServiceConnection, 417 Context.BIND_AUTO_CREATE); 418 if (!isBound) { 419 throw new AssertionError("could not bind to KeyChainService"); 420 } 421 return new KeyChainConnection(context, keyChainServiceConnection, q.take()); 422 } 423 ensureNotOnMainThread(Context context)424 private static void ensureNotOnMainThread(Context context) { 425 Looper looper = Looper.myLooper(); 426 if (looper != null && looper == context.getMainLooper()) { 427 throw new IllegalStateException( 428 "calling this from your main thread can lead to deadlock"); 429 } 430 } 431 } 432