Merge "Enterprise Policy for Private DNS Setting"
diff --git a/api/current.txt b/api/current.txt
index fabfc10..3b965f1 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -6522,6 +6522,8 @@
method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.lang.CharSequence getEndUserSessionMessage(android.content.ComponentName);
+ method public java.lang.String getGlobalPrivateDnsHost(android.content.ComponentName);
+ method public int getGlobalPrivateDnsMode(android.content.ComponentName);
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public java.util.List<java.lang.String> getKeepUninstalledPackages(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6625,6 +6627,7 @@
method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setEndUserSessionMessage(android.content.ComponentName, java.lang.CharSequence);
+ method public void setGlobalPrivateDns(android.content.ComponentName, int, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean);
@@ -6801,6 +6804,10 @@
field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
+ field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1
+ field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2
+ field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3
+ field public static final int PRIVATE_DNS_MODE_UNKNOWN = 0; // 0x0
field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 74fb4df..b289a3e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1898,6 +1898,36 @@
public static final String ACTION_PROFILE_OWNER_CHANGED =
"android.app.action.PROFILE_OWNER_CHANGED";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"PRIVATE_DNS_MODE_"}, value = {
+ PRIVATE_DNS_MODE_UNKNOWN,
+ PRIVATE_DNS_MODE_OFF,
+ PRIVATE_DNS_MODE_OPPORTUNISTIC,
+ PRIVATE_DNS_MODE_PROVIDER_HOSTNAME
+ })
+ public @interface PrivateDnsMode {}
+
+ /**
+ * Specifies that the Private DNS setting is in an unknown state.
+ */
+ public static final int PRIVATE_DNS_MODE_UNKNOWN = 0;
+
+ /**
+ * Specifies that Private DNS was turned off completely.
+ */
+ public static final int PRIVATE_DNS_MODE_OFF = 1;
+
+ /**
+ * Specifies that the device owner requested opportunistic DNS over TLS
+ */
+ public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+
+ /**
+ * Specifies that the device owner configured a specific host to use for Private DNS.
+ */
+ public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+
/**
* Return true if the given administrator component is currently active (enabled) in the system.
*
@@ -9754,4 +9784,80 @@
throw re.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Sets the global Private DNS mode and host to be used.
+ * May only be called by the device owner.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @param mode Which mode to set - either {@code PRIVATE_DNS_MODE_OPPORTUNISTIC} or
+ * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME}.
+ * Since the opportunistic mode defaults to ordinary DNS lookups, the
+ * option to turn it completely off is not available, so this method
+ * may not be called with {@code PRIVATE_DNS_MODE_OFF}.
+ * @param privateDnsHost The hostname of a server that implements DNS over TLS (RFC7858), if
+ * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} was specified as the mode,
+ * null otherwise.
+ * @throws IllegalArgumentException in the following cases: if a {@code privateDnsHost} was
+ * provided but the mode was not {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME}, if the mode
+ * specified was {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} but {@code privateDnsHost} does
+ * not look like a valid hostname, or if the mode specified is not one of the two valid modes.
+ *
+ * @throws SecurityException if the caller is not the device owner.
+ */
+ public void setGlobalPrivateDns(@NonNull ComponentName admin,
+ @PrivateDnsMode int mode, @Nullable String privateDnsHost) {
+ throwIfParentInstance("setGlobalPrivateDns");
+ if (mService == null) {
+ return;
+ }
+
+ try {
+ mService.setGlobalPrivateDns(admin, mode, privateDnsHost);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the system-wide Private DNS mode.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @return one of {@code PRIVATE_DNS_MODE_OFF}, {@code PRIVATE_DNS_MODE_OPPORTUNISTIC},
+ * {@code PRIVATE_DNS_MODE_PROVIDER_HOSTNAME} or {@code PRIVATE_DNS_MODE_UNKNOWN}.
+ * @throws SecurityException if the caller is not the device owner.
+ */
+ public int getGlobalPrivateDnsMode(@NonNull ComponentName admin) {
+ throwIfParentInstance("setGlobalPrivateDns");
+ if (mService == null) {
+ return PRIVATE_DNS_MODE_UNKNOWN;
+ }
+
+ try {
+ return mService.getGlobalPrivateDnsMode(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the system-wide Private DNS host.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with.
+ * @return The hostname used for Private DNS queries.
+ * @throws SecurityException if the caller is not the device owner.
+ */
+ public String getGlobalPrivateDnsHost(@NonNull ComponentName admin) {
+ throwIfParentInstance("setGlobalPrivateDns");
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getGlobalPrivateDnsHost(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5e45450..cf0cad8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -413,4 +413,8 @@
boolean isOverrideApnEnabled(in ComponentName admin);
boolean isMeteredDataDisabledPackageForUser(in ComponentName admin, String packageName, int userId);
+
+ void setGlobalPrivateDns(in ComponentName admin, int mode, in String privateDnsHost);
+ int getGlobalPrivateDnsMode(in ComponentName admin);
+ String getGlobalPrivateDnsHost(in ComponentName admin);
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 34e9476..c431e40e 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -16,8 +16,13 @@
package android.net;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
+import android.system.Os;
import android.util.Log;
import android.util.Pair;
@@ -570,4 +575,30 @@
}
return routedIPCount;
}
+
+ private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET, AF_INET6};
+
+ /**
+ * Returns true if the hostname is weakly validated.
+ * @param hostname Name of host to validate.
+ * @return True if it's a valid-ish hostname.
+ *
+ * @hide
+ */
+ public static boolean isWeaklyValidatedHostname(@NonNull String hostname) {
+ // TODO(b/34953048): Use a validation method that permits more accurate,
+ // but still inexpensive, checking of likely valid DNS hostnames.
+ final String weakHostnameRegex = "^[a-zA-Z0-9_.-]+$";
+ if (!hostname.matches(weakHostnameRegex)) {
+ return false;
+ }
+
+ for (int address_family : ADDRESS_FAMILIES) {
+ if (Os.inet_pton(address_family, hostname) != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 4350596..b06be1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,7 +15,9 @@
*/
package com.android.server.devicepolicy;
+import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
import com.android.server.SystemService;
@@ -71,4 +73,18 @@
public boolean checkDeviceIdentifierAccess(String packageName, int userHandle) {
return false;
}
+
+ @Override
+ public void setGlobalPrivateDns(ComponentName who, int mode, String privateDnsHost) {
+ }
+
+ @Override
+ public int getGlobalPrivateDnsMode(ComponentName who) {
+ return DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN;
+ }
+
+ @Override
+ public String getGlobalPrivateDnsHost(ComponentName who) {
+ return null;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 913b844..70cdba2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -55,12 +55,18 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
+import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Telephony.Carriers.DPC_URI;
import static android.provider.Telephony.Carriers.ENFORCE_KEY;
import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
@@ -145,6 +151,7 @@
import android.media.IAudioService;
import android.net.ConnectivityManager;
import android.net.IIpConnectivityMetrics;
+import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.Uri;
import android.net.metrics.IpConnectivityLog;
@@ -395,6 +402,8 @@
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_SLEEP_POLICY);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.PRIVATE_DNS_MODE);
+ GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
@@ -13114,4 +13123,78 @@
private static String getManagedProvisioningPackage(Context context) {
return context.getResources().getString(R.string.config_managed_provisioning_package);
}
+
+ private void putPrivateDnsSettings(@Nullable String mode, @Nullable String host) {
+ // Set Private DNS settings using system permissions, as apps cannot write
+ // to global settings.
+ long origId = mInjector.binderClearCallingIdentity();
+ try {
+ mInjector.settingsGlobalPutString(PRIVATE_DNS_MODE, mode);
+ mInjector.settingsGlobalPutString(PRIVATE_DNS_SPECIFIER, host);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public void setGlobalPrivateDns(@NonNull ComponentName who, int mode, String privateDnsHost) {
+ if (!mHasFeature) {
+ return;
+ }
+
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ enforceDeviceOwner(who);
+
+ switch (mode) {
+ case PRIVATE_DNS_MODE_OPPORTUNISTIC:
+ if (!TextUtils.isEmpty(privateDnsHost)) {
+ throw new IllegalArgumentException("A DNS host should not be provided when " +
+ "setting opportunistic mode.");
+ }
+ putPrivateDnsSettings(ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC, null);
+ break;
+ case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
+ if (!NetworkUtils.isWeaklyValidatedHostname(privateDnsHost)) {
+ throw new IllegalArgumentException(
+ String.format("Provided hostname is not valid: %s", privateDnsHost));
+ }
+ putPrivateDnsSettings(ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+ privateDnsHost);
+ break;
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported mode: %d", mode));
+ }
+ }
+
+ @Override
+ public int getGlobalPrivateDnsMode(@NonNull ComponentName who) {
+ if (!mHasFeature) {
+ return PRIVATE_DNS_MODE_UNKNOWN;
+ }
+
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ enforceDeviceOwner(who);
+ switch (mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE)) {
+ case ConnectivityManager.PRIVATE_DNS_MODE_OFF:
+ return PRIVATE_DNS_MODE_OFF;
+ case ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC:
+ return PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ case ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
+ return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+ }
+
+ return PRIVATE_DNS_MODE_UNKNOWN;
+ }
+
+ @Override
+ public String getGlobalPrivateDnsHost(@NonNull ComponentName who) {
+ if (!mHasFeature) {
+ return null;
+ }
+
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ enforceDeviceOwner(who);
+
+ return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER);
+ }
}