Open chenpengcong opened 3 years ago
本文介绍Android app如何使用TelephonyManager和SEService提供的API与SIM卡应用(Applet)进行交互,并重点介绍不同API对于SIM卡访问规则(access rules)数据的要求
tips: GlobalPlatform Secure Element Access Control specification规范内容是预备知识,描述了Secure Element是如何进行权限控制的,本文不作展开
首先介绍TelephonyManager API,从官方文档UICC Carrier Privileges描述可知使用该API访问SIM卡需要App具有carrier权限且该权限基于GlobalPlatform Secure Element Access Control specification规范,访问规则(access rules)数据存放在SIM卡上的ARA-M(Access Rule Application Master)应用或者ARF(Access Rule File)文件中
下文以ARF架构为例进行介绍
如Access rule file (ARF) support描述:
Android then reads the access control rules file (ACRF) at 0x4300 and looks for entries with AID FFFFFFFFFFFF
Android会寻找4300文件中AID为FFFFFFFFFFFF的那一条访问控制规则
因此,我这里往SIM卡4300文件中写入AC规则如下
30 10 A0 08 04 06 FF FF FF FF FF FF 30 04 04 02 43 04
4300文件每条规则记录了AID与ACCF(Access Control Conditions File)文件的对应关系,比如上面这条记录表示AID为FFFFFFFFFFFF的应用的访问控制条件数据存放在4304文件
接下来往4304文件写入APK签名证书的SHA1值,签名证书SHA1值可以使用keytool工具从签名文件*.jks读取出来
$ keytool.exe -list -v -keystore <name.jks>
这里我用于演示的App的签名证书SHA1值为: 1AE0C6D304289DAA7B06E7A2DD11822FFFF19D25
4304文件最终写入数据如下
30 16 04 14 1A E0 C6 D3 04 28 9D AA 7B 06 E7 A2 DD 11 82 2F FF F1 9D 25
到这里,App已具备使用TelephonyManager API访问SIM卡上应用权限了
值得注意的是:使用TelephonyManager API访问SIM卡时,一旦授予了UICC Carrier权限,App可以访问SIM卡上的所有卡应用,这与下文使用SEService API有所区别
Android App的示例代码如下,已验证成功访问SIM卡的PKCS15应用(AID: A000000063504B43532D3135)
public class MainActivity extends AppCompatActivity { private static final String TAG = "UICC_ACCESS"; private static final String PKCS15_AID_STR = "A000000063504B43532D3135"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View view) { switch (view.getId()) { case R.id.accessByTelephonyApiBtn: accessUiccByTelephonyApi(); break; } } private void accessUiccByTelephonyApi() { TelephonyManager manager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE); if (manager != null && !manager.hasCarrierPrivileges()) { Log.d(TAG, "没有运营商权限"); return; } IccOpenLogicalChannelResponse res = manager.iccOpenLogicalChannel(PKCS15_AID_STR, 0x00); int channelId = res.getChannel(); Log.d(TAG, "已打开访问PKCS15 Application的逻辑通道,channel id:" + channelId); manager.iccCloseLogicalChannel(channelId); Log.d(TAG, "已正常关闭逻辑通道"); } }
SEService API实际是Open Mobile API规范的具体实现, 在https://globalplatform.org/specs-library/ 可以下载,该文档对API的描述更为详细
与TelephonyManager API相比,SEService API可以更细粒度的控制App访问某个SIM卡应用的权限,App要想访问指定卡应用必须在4300文件中有该卡应用AID的一条规则。不像TelephonyManager只需要一条AID为6字节FF的规则,且无法控制App仅允许访问指定卡应用
接下来使用SEService API访问PKCS15应用(AID:A000000063504B43532D3135),仍旧以ARF架构为例进行演示
首先往4300文件写入AC规则
30 16 A0 0E 04 0C A0 00 00 00 63 50 4B 43 53 2D 31 35 30 04 04 02 43 04
该记录表示AID为A000000063504B43532D3135的应用的访问控制条件数据存放在4304文件
接下来往4304文件写入签名证书SHA1值,与上文一致
Android App的示例代码如下,已验证成功访问SIM卡的PKCS15应用
public class MainActivity extends AppCompatActivity { private static final String TAG = "UICC_ACCESS"; private static final byte[] PKCS15_AID_ARR = {(byte) 0xA0, 0x00, 0x00, 0x00, 0x63, 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35}; private SEService seService = null; private Session session = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View view) { switch (view.getId()) { case R.id.initSEServiceBtn: initSEService(); break; case R.id.accessBySEServiceApiBtn: accessUiccBySEServiceApi(); break; default: break; } } private void initSEService() { this.seService = new SEService(this, new Executor() { public void execute(Runnable command) { command.run(); } }, new SEService.OnConnectedListener() { public void onConnected() { Log.d("UICC_ACCESS", "SEService connected"); for (Reader reader : MainActivity.this.seService.getReaders()) { if (reader.isSecureElementPresent() && reader.getName().contains("SIM")) { try { MainActivity.this.session = reader.openSession(); if (MainActivity.this.session == null) { Log.d(TAG, "openSession return null"); return; } Log.d(TAG, "openSession success"); } catch (Exception e) { Log.d(TAG, "init se service exception:" + e.toString()); } } } } }); } private void accessUiccBySEServiceApi() { if (this.session == null) { Log.d(TAG, "session is null, can't open channel"); return; } Channel channel; try { channel = MainActivity.this.session.openLogicalChannel(PKCS15_AID_ARR); } catch (Exception e) { Log.d(TAG, "openLogicalChannel error: " + e.toString()); return; } if (channel == null) { Log.d(TAG, "the Secure Element is unable to provide a new logical channel"); return; } Log.d(TAG, "已正常打开访问PKCS15 Application的逻辑通道"); session.closeChannels(); Log.d(TAG, "已正常关闭逻辑通道"); } }
实际生产环境中,为了兼容两套API,可能会往4300文件写入超过256字节大小的AC规则,要特别注意AID为6字节FF的那一条AC规则需要写在4300文件的前256字节,因为TelephonyManager API的内部实现仅会读取4300文件的前256字节数据进行解析,这个问题我们实际踩过坑
通过阅读TelephonyManager源码,定位到解析ACRF文件的实现类是UiccPkcs15.java,可以看到FileHandler的readBinary方法实现逻辑是固定发送一次read binary指令00 B0 00 00 00读取256字节数据,并没有根据文件实际大小进行读取,因此使用TelephonyManager API时如果6字节FF的AC规则所在位置超过了256字节将会无法被读取到
SEService API则不存在该问题,内部实现会根据ACRF文件实际大小进行读取,实现类见com/android/se/security/arf/PKCS15/EF.java的readbinary方法
本文介绍Android app如何使用TelephonyManager和SEService提供的API与SIM卡应用(Applet)进行交互,并重点介绍不同API对于SIM卡访问规则(access rules)数据的要求
TelephonyManager API
首先介绍TelephonyManager API,从官方文档UICC Carrier Privileges描述可知使用该API访问SIM卡需要App具有carrier权限且该权限基于GlobalPlatform Secure Element Access Control specification规范,访问规则(access rules)数据存放在SIM卡上的ARA-M(Access Rule Application Master)应用或者ARF(Access Rule File)文件中
下文以ARF架构为例进行介绍
如Access rule file (ARF) support描述:
Android会寻找4300文件中AID为FFFFFFFFFFFF的那一条访问控制规则
因此,我这里往SIM卡4300文件中写入AC规则如下
4300文件每条规则记录了AID与ACCF(Access Control Conditions File)文件的对应关系,比如上面这条记录表示AID为FFFFFFFFFFFF的应用的访问控制条件数据存放在4304文件
接下来往4304文件写入APK签名证书的SHA1值,签名证书SHA1值可以使用keytool工具从签名文件*.jks读取出来
$ keytool.exe -list -v -keystore <name.jks>
这里我用于演示的App的签名证书SHA1值为: 1AE0C6D304289DAA7B06E7A2DD11822FFFF19D25
4304文件最终写入数据如下
到这里,App已具备使用TelephonyManager API访问SIM卡上应用权限了
值得注意的是:使用TelephonyManager API访问SIM卡时,一旦授予了UICC Carrier权限,App可以访问SIM卡上的所有卡应用,这与下文使用SEService API有所区别
Android App的示例代码如下,已验证成功访问SIM卡的PKCS15应用(AID: A000000063504B43532D3135)
SEService API
SEService API实际是Open Mobile API规范的具体实现, 在https://globalplatform.org/specs-library/ 可以下载,该文档对API的描述更为详细
与TelephonyManager API相比,SEService API可以更细粒度的控制App访问某个SIM卡应用的权限,App要想访问指定卡应用必须在4300文件中有该卡应用AID的一条规则。不像TelephonyManager只需要一条AID为6字节FF的规则,且无法控制App仅允许访问指定卡应用
接下来使用SEService API访问PKCS15应用(AID:A000000063504B43532D3135),仍旧以ARF架构为例进行演示
首先往4300文件写入AC规则
该记录表示AID为A000000063504B43532D3135的应用的访问控制条件数据存放在4304文件
接下来往4304文件写入签名证书SHA1值,与上文一致
Android App的示例代码如下,已验证成功访问SIM卡的PKCS15应用
使用TelephonyManager API的注意点
实际生产环境中,为了兼容两套API,可能会往4300文件写入超过256字节大小的AC规则,要特别注意AID为6字节FF的那一条AC规则需要写在4300文件的前256字节,因为TelephonyManager API的内部实现仅会读取4300文件的前256字节数据进行解析,这个问题我们实际踩过坑
通过阅读TelephonyManager源码,定位到解析ACRF文件的实现类是UiccPkcs15.java,可以看到FileHandler的readBinary方法实现逻辑是固定发送一次read binary指令00 B0 00 00 00读取256字节数据,并没有根据文件实际大小进行读取,因此使用TelephonyManager API时如果6字节FF的AC规则所在位置超过了256字节将会无法被读取到
SEService API则不存在该问题,内部实现会根据ACRF文件实际大小进行读取,实现类见com/android/se/security/arf/PKCS15/EF.java的readbinary方法
参考