Android 10 更改了设备标识符的权限,让所有设备标识符现在都受到 READ_PRIVILEGED_PHONE_STATE
权限的保护。在 Android 10 之前的版本中,永久性设备标识符(IMEI/MEID、IMSI、SIM 和 build 序列号)受到 READ_PHONE_STATE
运行时权限的保护。系统仅会向使用平台密钥进行签名的应用以及特权系统应用授予 READ_PRIVILEGED_PHONE_STATE
权限。
如需详细了解新的权限要求,请访问 TelephonyManager.java 和 Build.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 签名)。如果找到匹配项,会返回所请求的信息。如果未找到匹配项,则返回安全异常。
如需实现此解决方案,运营商必须按以下步骤操作:
- 使用运营商应用的签名证书哈希值更新
CarrierConfig.xml
,并提交补丁。 - 请求 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
,请执行以下操作:
- 使用
toByteArray
将签名证书的签名转换为字节数组。 - 使用
MessageDigest
将字节数组转换为 byte[] 类型的哈希值。 -
将哈希值从 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())); }
如果
certHashes
是大小为2
且值为12345
和54321
的数组,请将以下内容添加到运营商配置文件中。<string-array name="carrier_certificate_string_array" num="2"> <item value="12345"/> <item value="54321"/> </string-array>