设备标识符

Android 10 更改了设备标识符的权限,让所有设备标识符现在都受到 READ_PRIVILEGED_PHONE_STATE 权限的保护。在 Android 10 之前的版本中,永久性设备标识符(IMEI/MEID、IMSI、SIM 和 build 序列号)受到 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 卡没有创作权的运营商(例如拥有 MNO 发行的 SIM 卡的 MVNO)无法在 SIM 卡上添加或更新证书。
OEM 许可名单 OEM 可以使用 OP_READ_DEVICE_IDENTIFIER 向已加入许可名单的运营商应用提供设备标识符。 此解决方案仅适用于部分运营商。
类型分配码 (TAC) 可使用 Android 10 中引入的 getTypeAllocationCode 方法公开 TAC,以返回制造商和型号信息。 TAC 中的信息不足以识别具体设备。
MSISDN 运营商可以使用具有 PHONE 权限组的 TelephonyManager 下的电话号码 (MSISDN),在其后端系统上查询 IMEI。 这需要运营商做出巨额投入。对于使用 IMSI 映射网络密钥的运营商而言,改用 MSISDN 需要大量的技术资源。

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

如需实现此解决方案,运营商必须按以下步骤操作:

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

实现

请更新您的特许权限许可名单,以向需要访问设备标识符的特权应用授予 READ_PRIVILEGED_PHONE_STATE 权限。

如需详细了解许可名单,请参阅特许权限许可名单

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

  • 如果应用是预加载的特权应用,则需要获得在 AndroidManifest.xml 中声明的 READ_PRIVILEGED_PHONE_STATE 权限。此外,该应用还需要将此特许权限列入许可名单。
  • 通过 Google Play 提供的应用需要运营商特权。请访问 UICC 运营商特权页面,详细了解如何授予运营商特权。
  • 应用是已获得 READ_PHONE_STATE 权限的设备所有者或资料所有者。

不符合上述任何要求的应用具有以下行为:

  • 如果应用以 Android Q 之前的版本为目标平台,且未获得 READ_PHONE_STATE 权限,则会触发 SecurityException。这是 Android Q 之前的版本目前会出现的行为,因为调用这些 API 需要此权限。
  • 如果应用以 Android Q 之前的版本为目标平台,且已获得 READ_PHONE_STATE 权限,则会在调用所有 TelephonyManager API 之后收到 null 值,还会在调用 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 卡是否必须安装在设备上才能访问设备标识符?

系统会根据当前插入的 SIM 卡来确定要使用的 CarrierConfig.xml。因此,如果运营商 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>