设备标识符

Android 10 更改了设备标识符的权限,现在所有设备标识符都受到 READ_PRIVILEGED_PHONE_STATE 权限的保护。在 Android 10 之前,持久性设备标识符(IMEI/MEID、IMSI、SIM 和 build serial)受到 READ_PHONE_STATE 运行时权限的保护。READ_PRIVILEGED_PHONE_STATE 权限仅授予使用平台密钥签名的应用和特权系统应用。

有关新权限要求的更多信息,请参阅 TelephonyManager.javaBuild.java 的 Javadoc 页面。

此更改会影响以下 API

  • TelephonyManager#getDeviceId
  • TelephonyManager#getImei
  • TelephonyManager#getMeid
  • TelephonyManager#getSimSerialNumber
  • TelephonyManager#getSubscriberId
  • Build#getSerial

没有 READ_PRIVILEGED_PHONE_STATE 权限的运营商应用的访问权限

不符合 READ_PRIVILEGED_PHONE_STATE 权限条件的预加载运营商应用可以实现下表中的选项之一。

选项 描述 限制
UICC 运营商权限 Android 平台加载存储在 UICC 上的证书,并授予由这些证书签名的应用权限来调用特殊方法。 传统运营商拥有庞大且已建立的 SIM 用户群,这些 SIM 卡不易更新。此外,对于没有新 SIM 卡编写权限的运营商(例如,MVNO 从 MNO 发行的 SIM 卡),无法在 SIM 卡上添加或更新证书。
OEM 允许列表 OEM 可以使用 OP_READ_DEVICE_IDENTIFIER 向允许列表中的运营商应用提供设备标识符。 此解决方案不适用于所有运营商。
型号分配代码 (TAC) 使用 Android 10 中引入的 getTypeAllocationCode 方法,以公开返回制造商和型号信息的 TAC。 TAC 中的信息不足以识别特定设备。
MSISDN 运营商可以使用电话号码 (MSISDN),通过具有 PHONE 权限组的 TelephonyManager 获取,在其后端系统中查找 IMEI。 这需要运营商进行大量投资。使用 IMSI 映射其网络密钥的运营商需要大量的技术资源才能切换到 MSISDN

所有 运营商应用都可以通过使用运营商应用的签名证书哈希更新 CarrierConfig.xml 文件来访问设备标识符。当运营商应用调用方法以读取特权信息时,平台会在 CarrierConfig.xml 文件中查找与应用签名证书哈希(证书的 SHA-1 或 SHA-256 签名)的匹配项。如果找到匹配项,则返回请求的信息。如果未找到匹配项,则返回安全异常。

为了实施此解决方案,运营商必须遵循以下步骤

  1. 使用运营商应用的签名证书哈希更新 CarrierConfig.xml,并提交补丁
  2. 请求 OEM 使用 QPR1+(推荐)或这些 必需的平台补丁 以及包含来自上述步骤 1 的更新 CarrierConfig.xml 文件的补丁来更新其版本。

实现

更新您的特权权限允许列表,以向需要访问设备标识符的特权应用授予 READ_PRIVILEGED_PHONE_STATE 权限。

要了解有关允许列表的更多信息,请参阅特权权限允许列表

要调用受影响的 API,应用必须满足以下要求之一

  • 如果应用是预加载的特权应用,则它需要 AndroidManifest.xml 中声明的 READ_PRIVILEGED_PHONE_STATE 权限。应用还需要将此特权权限加入允许列表。
  • 通过 Google Play 交付的应用需要运营商特权。在UICC 运营商特权页面上了解有关授予运营商特权的更多信息。
  • 已被授予 READ_PHONE_STATE 权限的设备或个人资料所有者应用。

不满足任何这些要求的应用具有以下行为

  • 如果应用的目标是 Q 之前的版本且未被授予 READ_PHONE_STATE 权限,则会触发 SecurityException。这是当前 Q 之前的行为,因为调用这些 API 需要此权限。
  • 如果应用的目标是 Q 之前的版本且已被授予 READ_PHONE_STATE 权限,则它会收到所有 TelephonyManager API 的空值以及 Build#getSerial 方法的 Build.UNKNOWN
  • 如果应用的目标是 Android 10 或更高版本且不满足任何一项新要求,则它会收到 SecurityException。

验证和测试

兼容性测试套件 (CTS) 包括用于验证具有运营商特权、设备和个人资料所有者的应用以及预期无法访问设备标识符的应用的预期设备标识符访问行为的测试。

以下 CTS 测试特定于此功能。

cts-tradefed run cts -m CtsCarrierApiTestCases -t
    android.carrierapi.cts.CarrierApiTest

cts-tradefed run cts -m CtsTelephonyTestCases -t
    android.telephony.cts.TelephonyManagerTest

cts-tradefed run cts -m CtsTelephony3TestCases

cts-tradefed run cts -m CtsPermissionTestCases -t
    android.permission.cts.TelephonyManagerPermissionTest

cts-tradefed run cts -m CtsDevicePolicyManagerTestCases -t
    com.android.cts.devicepolicy.DeviceOwnerTest#testDeviceOwnerCanGetDeviceIdentifiers

cts-tradefed run cts -m CtsDevicePolicyManagerTestCases -t
    com.android.cts.devicepolicy.ManagedProfileTest#testProfileOwnerCanGetDeviceIdentifiers

cts-tradefed run cts -m CtsDevicePolicyManagerTestCases -t
    com.android.cts.devicepolicy.ManagedProfileTest#testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission

cts-tradefed run cts -m CtsDevicePolicyManagerTestCases -t
    com.android.cts.devicepolicy.DeviceOwnerTest#testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission

常见问题解答

对于给定的 (MCC, MNC),可以在 CarrierConfig.xml 中允许多少个应用加入允许列表?

数组中包含的证书哈希数量没有限制。

我需要在 CarrierConfig.xml 中使用哪些 CarrierConfig 参数才能将应用加入允许列表?

在您配置的 AOSP 选项中的特定 CarrierConfig.xml 中使用以下顶级配置项

<string-array name="carrier_certificate_string_array" num="2">
    <item value="BF02262E5EF59FDD53E57059082F1A7914F284B"/>
    <item value="9F3868A3E1DD19A5311D511A60CF94D975A344B"/>
</string-array>

是否有我可以使用的基本 CarrierConfig 模板?

使用以下模板。这应添加到相关资产中。

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<carrier_config>
    <string-array name="carrier_certificate_string_array"
num="1">
        <item value="CERTIFICATE_HASH_HERE"/>
    </string-array>
</carrier_config>

运营商 SIM 卡是否必须在设备中才能访问设备标识符?

使用的 CarrierConfig.xml 是根据当前插入的 SIM 卡确定的。这意味着,如果运营商 X 的应用尝试在插入运营商 Y 的 SIM 卡时获取访问权限,则设备将找不到哈希的匹配项并返回安全异常。

在多 SIM 卡设备上,运营商 #1 仅对 SIM 卡 #1 具有访问权限,反之亦然。

运营商如何将应用的签名证书转换为哈希?

要在将签名证书添加到 CarrierConfig.xml 之前将其转换为哈希,请执行以下操作

  1. 使用 toByteArray 将签名证书的签名转换为字节数组。
  2. 使用 MessageDigest 将字节数组转换为 byte[] 类型的哈希。
  3. 将哈希从 byte[] 转换为十六进制字符串格式。有关示例,请参阅 IccUtils.java

    List<String> certHashes = new ArrayList<>();
    PackageInfo pInfo; // Carrier app PackageInfo
    MessageDigest md =
    MessageDigest.getInstance("SHA-256");
    for (Signature signature : pInfo.signatures) {
        certHashes.add(bytesToHexString(md.digest(signature.toByteArray()));
    }
  4. 如果 certHashes 是大小为 2 的数组,值为 1234554321,请将以下内容添加到运营商配置文件。

    <string-array name="carrier_certificate_string_array" num="2">
        <item value="12345"/>
        <item value="54321"/>
    </string-array>