summaryrefslogtreecommitdiff
path: root/keystore
diff options
context:
space:
mode:
Diffstat (limited to 'keystore')
-rw-r--r--keystore/java/android/security/AppUriAuthenticationPolicy.java26
-rw-r--r--keystore/java/android/security/IKeyChainService.aidl7
-rw-r--r--keystore/java/android/security/KeyChain.java212
-rw-r--r--keystore/java/android/security/UrisToAliases.java18
-rw-r--r--keystore/java/android/security/keystore/AttestationUtils.java35
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java1
6 files changed, 259 insertions, 40 deletions
diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.java b/keystore/java/android/security/AppUriAuthenticationPolicy.java
index 0244ce97c0d4..b3a89710cb06 100644
--- a/keystore/java/android/security/AppUriAuthenticationPolicy.java
+++ b/keystore/java/android/security/AppUriAuthenticationPolicy.java
@@ -18,6 +18,7 @@ package android.security;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,6 +28,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.security.Principal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -89,6 +91,13 @@ public final class AppUriAuthenticationPolicy implements Parcelable {
* <p>
* If this method is called with a package name and URI that was previously added, the
* previous alias will be overwritten.
+ * <p>
+ * When the system tries to determine which alias to return to a requesting app calling
+ * {@code KeyChain.choosePrivateKeyAlias}, it will choose the alias whose associated URI
+ * exactly matches the URI provided in {@link KeyChain#choosePrivateKeyAlias(
+ * Activity, KeyChainAliasCallback, String[], Principal[], Uri, String)} or the URI
+ * built from the host and port provided in {@link KeyChain#choosePrivateKeyAlias(
+ * Activity, KeyChainAliasCallback, String[], Principal[], String, int, String)}.
*
* @param appPackageName The app's package name to authenticate the user to.
* @param uri The URI to authenticate the user to.
@@ -238,4 +247,21 @@ public final class AppUriAuthenticationPolicy implements Parcelable {
return aliases;
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AppUriAuthenticationPolicy)) {
+ return false;
+ }
+ AppUriAuthenticationPolicy other = (AppUriAuthenticationPolicy) obj;
+ return Objects.equals(mAppToUris, other.mAppToUris);
+ }
+
+ @Override
+ public int hashCode() {
+ return mAppToUris.hashCode();
+ }
+
}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index d00f5f669594..6d3d84c75518 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -63,8 +63,13 @@ interface IKeyChainService {
AppUriAuthenticationPolicy getCredentialManagementAppPolicy();
String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri);
void removeCredentialManagementApp();
+ boolean isCredentialManagementApp(String packageName);
// APIs used by KeyChainActivity
- void setGrant(int uid, String alias, boolean value);
+ // setGrant may fail with value=false when ungrant operation fails in KeyStore.
+ boolean setGrant(int uid, String alias, boolean value);
boolean hasGrant(int uid, String alias);
+
+ // API used by Wifi
+ String getWifiKeyGrantAsUser(String alias);
}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index fd0db93350b9..02cdeef77bec 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -17,10 +17,14 @@ package android.security;
import static android.security.Credentials.ACTION_MANAGE_CREDENTIALS;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.PendingIntent;
@@ -31,6 +35,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
@@ -41,6 +46,7 @@ import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
+import android.util.Log;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -105,6 +111,11 @@ import javax.security.auth.x500.X500Principal;
public final class KeyChain {
/**
+ * @hide
+ */
+ public static final String LOG = "KeyChain";
+
+ /**
* @hide Also used by KeyChainService implementation
*/
public static final String ACCOUNT_TYPE = "com.android.keychain";
@@ -412,6 +423,15 @@ public final class KeyChain {
* credentials. This is limited to unmanaged devices. The authentication policy must be
* provided to be able to make this request successfully.
*
+ * <p> This intent should be started using {@link Activity#startActivityForResult(Intent, int)}
+ * to verify whether the request was successful and whether the user accepted or denied the
+ * request. If the user successfully receives and accepts the request, the result code will be
+ * {@link Activity#RESULT_OK}, otherwise the result code will be
+ * {@link Activity#RESULT_CANCELED}.
+ *
+ * <p> {@link KeyChain#isCredentialManagementApp(Context)} should be used to determine whether
+ * an app is already the credential management app.
+ *
* @param policy The authentication policy determines which alias for a private key and
* certificate pair should be used for authentication.
*/
@@ -574,10 +594,120 @@ public final class KeyChain {
}
intent.putExtra(EXTRA_ISSUERS, (Serializable) issuersList);
// the PendingIntent is used to get calling package name
- intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0));
+ intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE));
activity.startActivity(intent);
}
+ /**
+ * Check whether the caller is the credential management app {@code CredentialManagementApp}.
+ * The credential management app has the ability to manage the user's KeyChain credentials
+ * on unmanaged devices.
+ *
+ * <p> {@link KeyChain#createManageCredentialsIntent} should be used by an app to request to
+ * become the credential management app. The user must approve this request before the app can
+ * manage the user's credentials. There can only be one credential management on the device.
+ *
+ * @return {@code true} if the caller is the credential management app.
+ */
+ @WorkerThread
+ public static boolean isCredentialManagementApp(@NonNull Context context) {
+ boolean isCredentialManagementApp = false;
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ isCredentialManagementApp = keyChainConnection.getService()
+ .isCredentialManagementApp(context.getPackageName());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while checking whether the caller is the "
+ + "credential management app.", e);
+ } catch (SecurityException e) {
+ isCredentialManagementApp = false;
+ }
+ return isCredentialManagementApp;
+ }
+
+ /**
+ * Called by the credential management app to get the authentication policy
+ * {@link AppUriAuthenticationPolicy}.
+ *
+ * @return the credential management app's authentication policy.
+ * @throws SecurityException if the caller is not the credential management app.
+ */
+ @WorkerThread
+ @NonNull
+ public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy(
+ @NonNull Context context) throws SecurityException {
+ AppUriAuthenticationPolicy policy = null;
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ policy = keyChainConnection.getService().getCredentialManagementAppPolicy();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ "Interrupted while getting credential management app policy.", e);
+ }
+ return policy;
+ }
+
+ /**
+ * Set a credential management app. The credential management app has the ability to manage
+ * the user's KeyChain credentials on unmanaged devices.
+ *
+ * <p>There can only be one credential management on the device. If another app requests to
+ * become the credential management app, then the existing credential management app will
+ * no longer be able to manage credentials.
+ *
+ * @param packageName The package name of the credential management app
+ * @param authenticationPolicy The authentication policy of the credential management app. This
+ * policy determines which alias for a private key and certificate
+ * pair should be used for authentication.
+ * @return {@code true} if the credential management app was successfully added.
+ * @hide
+ */
+ @TestApi
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+ public static boolean setCredentialManagementApp(@NonNull Context context,
+ @NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ keyChainConnection.getService()
+ .setCredentialManagementApp(packageName, authenticationPolicy);
+ return true;
+ } catch (RemoteException | InterruptedException e) {
+ Log.w(LOG, "Set credential management app failed", e);
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ /**
+ * Called by the credential management app {@code CredentialManagementApp} to unregister as
+ * the credential management app and stop managing the user's credentials.
+ *
+ * <p> All credentials previously installed by the credential management app will be removed
+ * from the user's device.
+ *
+ * <p> An app holding {@code MANAGE_CREDENTIAL_MANAGEMENT_APP} permission can also call this
+ * method to remove the current credential management app, even if it's not the current
+ * credential management app itself.
+ *
+ * @return {@code true} if the credential management app was successfully removed.
+ */
+ @WorkerThread
+ @RequiresPermission(value = Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP,
+ conditional = true)
+ public static boolean removeCredentialManagementApp(@NonNull Context context) {
+ try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
+ keyChainConnection.getService().removeCredentialManagementApp();
+ return true;
+ } catch (RemoteException | InterruptedException e) {
+ Log.w(LOG, "Remove credential management app failed", e);
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
private static class AliasResponse extends IKeyChainAliasCallback.Stub {
private final KeyChainAliasCallback keyChainAliasResponse;
private AliasResponse(KeyChainAliasCallback keyChainAliasResponse) {
@@ -880,10 +1010,74 @@ public final class KeyChain {
@WorkerThread
public static KeyChainConnection bindAsUser(@NonNull Context context, UserHandle user)
throws InterruptedException {
+ return bindAsUser(context, null, user);
+ }
+
+ /**
+ * Returns a persistable grant string that allows WiFi stack to access the key using Keystore
+ * SSL engine.
+ *
+ * @return grant string or null if key is not granted or doesn't exist.
+ *
+ * The key should be granted to Process.WIFI_UID.
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ @WorkerThread
+ public static String getWifiKeyGrantAsUser(
+ @NonNull Context context, @NonNull UserHandle user, @NonNull String alias) {
+ try (KeyChainConnection keyChainConnection =
+ bindAsUser(context.getApplicationContext(), user)) {
+ return keyChainConnection.getService().getWifiKeyGrantAsUser(alias);
+ } catch (RemoteException | RuntimeException e) {
+ Log.i(LOG, "Couldn't get grant for wifi", e);
+ return null;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.i(LOG, "Interrupted while getting grant for wifi", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether the key is granted to WiFi stack.
+ * @hide
+ */
+ @SystemApi
+ @WorkerThread
+ public static boolean hasWifiKeyGrantAsUser(
+ @NonNull Context context, @NonNull UserHandle user, @NonNull String alias) {
+ try (KeyChainConnection keyChainConnection =
+ bindAsUser(context.getApplicationContext(), user)) {
+ return keyChainConnection.getService().hasGrant(Process.WIFI_UID, alias);
+ } catch (RemoteException | RuntimeException e) {
+ Log.i(LOG, "Couldn't query grant for wifi", e);
+ return false;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.i(LOG, "Interrupted while querying grant for wifi", e);
+ return false;
+ }
+ }
+
+ /**
+ * Bind to KeyChainService in the target user.
+ * Caller should call unbindService on the result when finished.
+ *
+ * @throws InterruptedException if interrupted during binding.
+ * @throws AssertionError if unable to bind to KeyChainService.
+ * @hide
+ */
+ public static KeyChainConnection bindAsUser(@NonNull Context context, @Nullable Handler handler,
+ UserHandle user) throws InterruptedException {
+
if (context == null) {
throw new NullPointerException("context == null");
}
- ensureNotOnMainThread(context);
+ if (handler == null) {
+ ensureNotOnMainThread(context);
+ }
if (!UserManager.get(context).isUserUnlocked(user)) {
throw new IllegalStateException("User must be unlocked");
}
@@ -910,9 +1104,19 @@ public final class KeyChain {
};
Intent intent = new Intent(IKeyChainService.class.getName());
ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
+ if (comp == null) {
+ throw new AssertionError("could not resolve KeyChainService");
+ }
intent.setComponent(comp);
- if (comp == null || !context.bindServiceAsUser(
- intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user)) {
+ final boolean bindSucceed;
+ if (handler != null) {
+ bindSucceed = context.bindServiceAsUser(
+ intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, handler, user);
+ } else {
+ bindSucceed = context.bindServiceAsUser(
+ intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user);
+ }
+ if (!bindSucceed) {
throw new AssertionError("could not bind to KeyChainService");
}
countDownLatch.await();
diff --git a/keystore/java/android/security/UrisToAliases.java b/keystore/java/android/security/UrisToAliases.java
index 65d433abe166..9a8b659f3db4 100644
--- a/keystore/java/android/security/UrisToAliases.java
+++ b/keystore/java/android/security/UrisToAliases.java
@@ -30,6 +30,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
/**
* The mapping from URI to alias, which determines the alias to use when the user visits a URI.
@@ -135,4 +136,21 @@ public final class UrisToAliases implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mUrisToAliases);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof UrisToAliases)) {
+ return false;
+ }
+ UrisToAliases other = (UrisToAliases) obj;
+ return Objects.equals(mUrisToAliases, other.mUrisToAliases);
+ }
+
+ @Override
+ public int hashCode() {
+ return mUrisToAliases.hashCode();
+ }
}
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index be865a02a945..66d842ec5614 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -21,13 +21,11 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.content.res.Resources;
import android.os.Build;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
import android.security.keymaster.KeymasterDefs;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.ArraySet;
import java.io.ByteArrayInputStream;
@@ -130,37 +128,6 @@ public abstract class AttestationUtils {
@NonNull public static KeymasterArguments prepareAttestationArguments(Context context,
@NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
DeviceIdAttestationException {
- return prepareAttestationArguments(context, idTypes,attestationChallenge, Build.BRAND);
- }
-
- /**
- * Prepares Keymaster Arguments with attestation data for misprovisioned Pixel 2 device.
- * See http://go/keyAttestationFailure and http://b/69471841 for more info.
- * @hide should only be used by KeyChain.
- */
- @NonNull public static KeymasterArguments prepareAttestationArgumentsIfMisprovisioned(
- Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
- DeviceIdAttestationException {
- Resources resources = context.getResources();
- String misprovisionedBrand = resources.getString(
- com.android.internal.R.string.config_misprovisionedBrandValue);
- if (!TextUtils.isEmpty(misprovisionedBrand) && !isPotentiallyMisprovisionedDevice(context)){
- return null;
- }
- return prepareAttestationArguments(
- context, idTypes, attestationChallenge, misprovisionedBrand);
- }
-
- @NonNull private static boolean isPotentiallyMisprovisionedDevice(Context context) {
- Resources resources = context.getResources();
- String misprovisionedModel = resources.getString(
- com.android.internal.R.string.config_misprovisionedDeviceModel);
- return (Build.MODEL.equals(misprovisionedModel));
- }
-
- @NonNull private static KeymasterArguments prepareAttestationArguments(Context context,
- @NonNull int[] idTypes, @NonNull byte[] attestationChallenge, String brand) throws
- DeviceIdAttestationException {
// Check method arguments, retrieve requested device IDs and prepare attestation arguments.
if (attestationChallenge == null) {
throw new NullPointerException("Missing attestation challenge");
@@ -217,7 +184,7 @@ public abstract class AttestationUtils {
}
}
attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
- brand.getBytes(StandardCharsets.UTF_8));
+ Build.BRAND.getBytes(StandardCharsets.UTF_8));
attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
Build.DEVICE.getBytes(StandardCharsets.UTF_8));
attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 88d1a5b295b6..c14c3c534cf4 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -1048,7 +1048,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
/**
* Sets whether this key should be protected by a StrongBox security chip.
- * @hide
*/
@NonNull
public Builder setIsStrongBoxBacked(boolean isStrongBoxBacked) {