diff options
Diffstat (limited to 'keystore')
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) { |