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