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