diff --git a/core/api/current.txt b/core/api/current.txt index 6070cc7f6acc6..bd0db889801c0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7121,6 +7121,7 @@ package android.app.admin { method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName); method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName); method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); + method public boolean grantKeyPairToWifiAuth(@NonNull String); method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]); method public boolean hasGrantedPolicy(@NonNull android.content.ComponentName, int); method public boolean hasKeyPair(@NonNull String); @@ -7143,6 +7144,7 @@ package android.app.admin { method public boolean isDeviceIdAttestationSupported(); method public boolean isDeviceOwnerApp(String); method public boolean isEphemeralUser(@NonNull android.content.ComponentName); + method public boolean isKeyPairGrantedToWifiAuth(@NonNull String); method public boolean isLockTaskPermitted(String); method public boolean isLogoutEnabled(); method public boolean isManagedProfile(@NonNull android.content.ComponentName); @@ -7178,6 +7180,7 @@ package android.app.admin { method @Nullable public java.util.List retrievePreRebootSecurityLogs(@NonNull android.content.ComponentName); method @Nullable public java.util.List retrieveSecurityLogs(@NonNull android.content.ComponentName); method public boolean revokeKeyPairFromApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); + method public boolean revokeKeyPairFromWifiAuth(@NonNull String); method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean); method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set); method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 3a238e29dd232..53e1539b3339f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9198,6 +9198,15 @@ package android.se.omapi { } +package android.security { + + public final class KeyChain { + method @Nullable @WorkerThread public static String getWifiKeyGrantAsUser(@NonNull android.content.Context, @NonNull android.os.UserHandle, @NonNull String); + method @WorkerThread public static boolean hasWifiKeyGrantAsUser(@NonNull android.content.Context, @NonNull android.os.UserHandle, @NonNull String); + } + +} + package android.security.keystore { public class AndroidKeyStoreProvider extends java.security.Provider { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0635bd08e22bd..ccf41e5f30638 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6437,6 +6437,74 @@ public class DevicePolicyManager { return false; } + /** + * Called by a device or profile owner, or delegated certificate chooser (an app that has been + * delegated the {@link #DELEGATION_CERT_SELECTION} privilege), to allow using a KeyChain key + * pair for authentication to Wifi networks. The key can then be used in configurations passed + * to {@link android.net.wifi.WifiManager#addNetwork}. + * + * @param alias The alias of the key pair. + * @return {@code true} if the operation was set successfully, {@code false} otherwise. + * + * @throws SecurityException if the caller is not a device owner, a profile owner or + * delegated certificate chooser. + * @see #revokeKeyPairFromWifiAuth + */ + public boolean grantKeyPairToWifiAuth(@NonNull String alias) { + throwIfParentInstance("grantKeyPairToWifiAuth"); + try { + return mService.setKeyGrantToWifiAuth(mContext.getPackageName(), alias, true); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * Called by a device or profile owner, or delegated certificate chooser (an app that has been + * delegated the {@link #DELEGATION_CERT_SELECTION} privilege), to deny using a KeyChain key + * pair for authentication to Wifi networks. Configured networks using this key won't be able to + * authenticate. + * + * @param alias The alias of the key pair. + * @return {@code true} if the operation was set successfully, {@code false} otherwise. + * + * @throws SecurityException if the caller is not a device owner, a profile owner or + * delegated certificate chooser. + * @see #grantKeyPairToWifiAuth + */ + public boolean revokeKeyPairFromWifiAuth(@NonNull String alias) { + throwIfParentInstance("revokeKeyPairFromWifiAuth"); + try { + return mService.setKeyGrantToWifiAuth(mContext.getPackageName(), alias, false); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * Called by a device or profile owner, or delegated certificate chooser (an app that has been + * delegated the {@link #DELEGATION_CERT_SELECTION} privilege), to query whether a KeyChain key + * pair can be used for authentication to Wifi networks. + * + * @param alias The alias of the key pair. + * @return {@code true} if the key pair can be used, {@code false} otherwise. + * + * @throws SecurityException if the caller is not a device owner, a profile owner or + * delegated certificate chooser. + * @see #grantKeyPairToWifiAuth + */ + public boolean isKeyPairGrantedToWifiAuth(@NonNull String alias) { + throwIfParentInstance("isKeyPairGrantedToWifiAuth"); + try { + return mService.isKeyPairGrantedToWifiAuth(mContext.getPackageName(), alias); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + /** * Returns {@code true} if the device supports attestation of device identifiers in addition * to key attestation. See diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index ac1592d2d2a10..25ca59963d4b9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -479,6 +479,8 @@ interface IDevicePolicyManager { boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant); List getKeyPairGrants(in String callerPackage, in String alias); + boolean setKeyGrantToWifiAuth(String callerPackage, String alias, boolean hasGrant); + boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias); void setUserControlDisabledPackages(in ComponentName admin, in List packages); diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 684eebe6ffdeb..091f5795784f8 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -68,4 +68,7 @@ interface IKeyChainService { // APIs used by KeyChainActivity void 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 65a81cd57f417..11cb2b7c724b6 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -23,6 +23,7 @@ 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; @@ -1012,6 +1013,54 @@ public final class KeyChain { 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. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index ef7afc8d1894d..cdd5a92bec7a7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -143,4 +143,14 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean canAdminGrantSensorsPermissionsForUser(int userId) { return false; } + + @Override + public boolean setKeyGrantToWifiAuth(String callerPackage, String alias, boolean hasGrant) { + return false; + } + + @Override + public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 04af5c93160d6..79ae3599ad7b2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -5460,6 +5460,42 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); } + @Override + public boolean setKeyGrantToWifiAuth(String callerPackage, String alias, boolean hasGrant) { + Preconditions.checkStringNotEmpty(alias, "Alias to grant cannot be empty"); + + final CallerIdentity caller = getCallerIdentity(callerPackage); + Preconditions.checkCallAuthorization(canManageCertificates(caller)); + + return setKeyChainGrantInternal(alias, hasGrant, Process.WIFI_UID, caller.getUserHandle()); + } + + @Override + public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) { + Preconditions.checkStringNotEmpty(alias, "Alias to check cannot be empty"); + + final CallerIdentity caller = getCallerIdentity(callerPackage); + Preconditions.checkCallAuthorization(canManageCertificates(caller)); + + return mInjector.binderWithCleanCallingIdentity(() -> { + try (KeyChainConnection keyChainConnection = + KeyChain.bindAsUser(mContext, caller.getUserHandle())) { + final List result = new ArrayList<>(); + final int[] granteeUids = keyChainConnection.getService().getGrants(alias); + + for (final int uid : granteeUids) { + if (uid == Process.WIFI_UID) { + return true; + } + } + return false; + } catch (RemoteException e) { + Log.e(LOG_TAG, "Querying grant to wifi auth. ", e); + return false; + } + }); + } + @Override public boolean setKeyGrantForApp(ComponentName who, String callerPackage, String alias, String packageName, boolean hasGrant) { @@ -5482,19 +5518,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalStateException("Failure getting grantee uid", e); } + return setKeyChainGrantInternal(alias, hasGrant, granteeUid, caller.getUserHandle()); + } + + private boolean setKeyChainGrantInternal(String alias, boolean hasGrant, int granteeUid, + UserHandle userHandle) { final long id = mInjector.binderClearCallingIdentity(); try { - final KeyChainConnection keyChainConnection = - KeyChain.bindAsUser(mContext, caller.getUserHandle()); - try { + try (KeyChainConnection keyChainConnection = + KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); keyChain.setGrant(granteeUid, alias, hasGrant); return true; } catch (RemoteException e) { Log.e(LOG_TAG, "Setting grant for package.", e); - return false; - } finally { - keyChainConnection.close(); + return false; } } catch (InterruptedException e) { Log.w(LOG_TAG, "Interrupted while setting key grant", e);