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