2013-10-10 编写
前言
在“十问Android NFC手机上的卡模拟”文中仅仅简单的介绍了一下相关的概念,如果需要了解基于SE的卡模拟的更多细节,也就是,究竟在Android的NFC手机上,目前能够做到何种程度的卡模拟,以及如何实现,则需要更深入的讨论。
我们已经了解,NFC RF模块可以支持卡模拟工作方式,而且可以通过两种方式实现卡模拟,一种是基于硬件的,被称为虚拟卡模式(Virual Card Mode);一种是基于软件的,被称为主机卡模式(Host Card Mode)。无论哪种方式,都是NFC RF模块将外部读写器的指令转发到相关的处理模块,SE或手机上的应用程序,然后将回复信息发回外部读写器。
本文不讨论基于软件的方式,因为在Android中,必须修改相关固件以支持该功能,也就是必须使用第三方ROM,例如Cyanogenmod。本文的重点是,如果使用硬件SE的方式,我们是否能够做到:
1, 从手机内部访问SE,建立手机应用程序与SE之间的通讯连接并发送命令。
2, 将NFC模块和SE置于卡模拟工作方式,使用外部读写器中的命令转向SE。
3, 在SE中安装自己的应用,实现最终的卡模拟。
这里先预告一下本文的结论,以免浪费大家的时间,在目前看来,没有SE密钥的用户,只能在特定条件下实现功能1和2,而功能3则是不可能的。而功能1和2的条件对一般用户也是非常苛刻的,包括
- l 手机支持NFC,并且SE为内置SE或SWP-SIM,
- l 手机已经ROOT,
- l Android版本在Android 4.0.4 (API Level 15)上,而且因为用到了一些未公开类,所以不能保证在今后的版本中还能使用。(经测试这些未公开类在目前最新版本4.3中还可以工作)。
当然,深入讨论需要更多的专业知识,包括Android编程,智能卡等,虽然不需要全部精通,但至少有所了解。
SE硬件形态
SE是一个CPU卡,可以运行智能卡应用程序(称为小应用或卡应用)。一个智能卡从本质上讲就是在单一芯片上的微型计算环境,具有完备的CPU,ROM,EEPROM,RAM和I/O接口。一般智能卡还具有密钥算法协处理器,可以支持常用的加解密算法,例如DES,AES和RSA等。智能卡通过多种技术实现抗攻击特性,很难通过分解或分析芯片提取数据。事实上手机用户对SE都不陌生,因为手机的SIM卡本身就是一个SE(技术上讲只能在GSM中叫做SIM卡,更通用的应该叫做UICC)。
SE可以有多种集成形态:UICC,内置SE或在SD插槽上的插卡。本文主要讨论内置SE的方式,但首先简要了解一下其它形态的SE。
- l UICC形式的SE
普通的UICC仅仅和手机中的基带处理器相连,但基带处理器与运行Android的应用处理器是分离的,因此不能通过Android应用程序直接访问。所有的通讯需要通过射频界面层Radio Interface Layer (RIL),这是与基带处理器的IPC界面。UICC SE的通讯基于扩展AT命令 (AT+CCHO,AT+CCHC, AT+CGLA等),在目前Android中的telephony manager不支持。目前还有没有能够通过RIL访问UICC SE的标准方式(尽管有些带有定制化固件的商业设备据说支持这种方式),因为这个原因,普通的UICC并不适合NFC应用。还有一种方法是使用Single Wire Protocol (SWP)方式,SWP类型的UICC通过SWP连接到NFC控制器,目前很多移动支付都使用该方式,只要手机支持NFC功能,就可以通过更换UICC实现移动支付应用。
- l SD卡形式
另一种形态是AdvancedSecurity SD card (ASSD),本质上是一个带有嵌入式SE芯片的SD卡。将SD卡插入Android设备SD插槽,并运行一个SEEK补丁过的Android版本,可以通过SmartCard API访问SE。但是并不是所有手机都具有SD插槽,因此ASSD方式不太可能成为主流。
- l 内置SE模式
正如其名,内置SE是设备主板的一部分,并作为NFC芯片的专用芯片,或者干脆集成为NFC芯片的一部分,因此内置SE不能从手机上移除。第一个支持内置SE的设备是Nexus S,这款手机也是首款支持NFC的Android手机。我们实验用的设备,Galaxy Nexus,带有内置的NXPPN65N 芯片,该芯片在一个单独的封装中集成了一个NFC射频控制器和一个SE(NXPSmartMX系列的P5CN072)。下图为P5CN081的硬件架构图,由于没有找到P5CN072的图,用P5CN081代替,它们之间的区别仅仅是EEPROM大小不同。
P5xyzzz SmartMX 型号命名
x 产品类型:
C= PKI 控制器 + 3-DES 协处理器 + AES协处理器
y 接口类型:
C= 接触界面 - ISO/IEC 7816
D= 接触和非接触双界面 - ISO/IEC 7816 +ISO/IEC 14443 contactless interface
N= ISO/IEC 7816 + S2C NFC接口
zzz非易失存储器大小,单位KB
SE与手机的连接
由于SE的硬件形态很多,因此从手机应用程序访问起来也有很多不同的路径。所幸Android下的SEEK(Secure Element Evaluation Kit https://code.google.com/p/seek-for-android/)项目试图为开发人员屏蔽这些不同的硬件类型,提供一个统一的访问接口。感兴趣的可以去参考一下,本文使用SEEK的架构图说明不同SE的不同访问途径。
从手机访问SE被称为在WIRED CARD 模式,可以分别通过NFC API, ASSD, RIL访问Ese(包括SWPSIM), ?SD和SIM(非SWPSIM)类型的SE,同时通过第三方驱动支持外置模块SE。从图中可以看到:
1, 内置SE(eSE) 手机APP – NFC LIB – CLF-内置SE
2, SWP-UICC 手机APP – NFCLIB – CLF-SWP-UICC
3, 普通UICC 手机APP – RIL –基带处理器 –UICC
4, SD卡(ASSD) 手机APP – ASSD -?SD
5, 第三方外置模块(PLUGIN) 手机APP –外置模块驱动 -外置模块SE
其中CLF为ContactlessFrontend的缩写,一般指NFC RF模块和NFC天线。为了简化测试程序,本文并没有使用SEEK库,而仅仅使用Android中未公开的访问类,因此只能对内置SE和SWP-UICC进行测试。
在手机中通过连线模式访问SE
内置式SE或SWP UICC分别通过SignalIn/SignalOut接口(S2C,即NFCWI)和SWP接口与NFC控制器连接,具有三种操作模式:关闭,连线和虚拟。
- l 关闭模式,NFC CLF模块与SE没有通讯
- l 连线模式,NFC CLF模块使SE对Andorid操作系统可见,就像与RF读写器连接的(非接触式)智能卡
- l 虚拟模式,NFC CLF模块使SE对外部读写器可见,这时手机就像一个非接触智能卡
这三种模式本质上是互斥的,因此我们可以通过外部非接触界面与SE通讯(例如外部读写器),或者通过内部连线接口访问(例如通过Android上的应用程序),但无法同时使用。
在手机开机后,缺省状态下,SE是处于关闭状态。因此为了实现卡模拟,将SE置于虚拟模式,并在这三种模式之间自由切换,我们必须先实现在手机内部对SE的访问。
我们在前面提到,实现手机内部应用程序对SE的访问需要几个苛刻的条件,现在就具体解释一下为什么需要这些条件
1, 为什么需要支持内置或SWP-UICC的SE的NFC手机
NFC模块就不用说了,没有NFC模块,就不可能实现外部的读写器访问。为什么需要内置或SWP-UICC形式的SE在上面也有说明。
2, 为什么要求Android版本高于Android4.0.4 (API Level 15)?
在Android 2.3.4中引入了访问内置SE的内部API,并在这个版本上发布了谷歌钱包。但这些API依然在SDK中隐藏,另外在2.3.4和接下来的Gingerbread发布版中,还需要系统级权限(WRITE_SECURE_SETTINGS或NFCEE_ADMIN)才能使用这些API。早期的Ice Cream Sandwich发布版(4.0, API Level 14)中也是如此。这就意味着只有谷歌(对Nexus手机)和手机制造商(对他们自己品牌的手机)才能发布使用SE的应用程序,因为他们要么能够访问操作系统核心,要么能够拥有硬件平台密钥。但在Android 4.0.4 (API Level 15)中使用签名证书代替了系统级的权限许可(也就是在Android架构术语中的签名),因此,只要在Android系统上进行白名单登记,不需要制造商密钥,就可以访问内置的SE,这就大大简化了发布流程。另外,由于签名在白名单中文件中保存,这就可以通过OTA方式更新该列表,以便添加使用SE的应用程序。
3, 为什么要求ROOT
上面提到的白名单文件就是/etc/nfcee_access.xml,该文件是一个XML格式的文件,保存了允许访问SE的包名称和签名证书列表。下面是该文件的示例:
<?xmlversion="1.0" encoding="utf-8"?>
<resourcesxmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<signerandroid:signature="30820...90">
<package android:name="com.example.embeddedseaccess">
</package></signer>
</resources>
例子中表明允许'com.example.embeddedseaccess' 包访问SE。因此允许应用访问SE的第一步就是在nfcee_access.xml中添加签名证书和包名。该文件位于系统分区(/etc 是 /system/etc的符号链接),因此我们需要root权限,以便在读写模式下修改该文件。
满足上述条件后,我们可以开始实际的编程工作,建立访问SE的应用程序
1, 在AndroidManifest.xml添加必要的权限和库
<uses-permissionandroid:name="android.permission.NFC" />
<uses-library
android:name="com.android.nfc_extras"
android:required="true" />
2, 使用未公开的类
在配置完这些文件后,终于可以使用SEAPI的时候了。目前Android并没有实现标准的智能卡通讯API,例如JSR 177 or the Open Mobile API,而仅仅在NfcExecutionEnvironment (NFC-EE)类中提供了一个非常基础的通讯接口,它只有三个公共方法。
publicclass NfcExecutionEnvironment {
public void open() throws IOException {...}
public void close() throws IOException{...}
public byte[] transceive(byte[] in) throwsIOException {...}
}
通过这个简单的接口就足以与SE通讯了,我们现在需要实例化一个访问接口。通过NfcAdapterExtras类的一个静态方法,可以完成对卡模拟流程(目前仅支持内置SE,因为缺少UICC接口方法)和NFC-EE的管理。向SE发送一条命令的完整代码如下:
NfcAdapterExtrasadapterExtras = NfcAdapterExtras.get( NfcAdapter.getDefaultAdapter(context) );
NfcExecutionEnvironmentnfceEe = adapterExtras.getEmbeddedExecutionEnvironment();
nfcEe.open();
byte[]response = nfcEe.transceive(command);
nfcEe.close();
然而,正如我们上面讲到的,com.android.nfc_extras是一个可选库,不是SDK中的一部分。我们不能直接的引入它,因此我们要么在Android源代码中编译(将其放在/packages/apps/目录下),或者使用反射机制。由于SE接口很小,为了便于编译和测试,我们选择反射方式。获取,打开和使用NFC-EE实例的代码就变成了下面的形式:
ClassnfcExtrasClazz =Class.forName("com.android.nfc_extras.NfcAdapterExtras");
MethodgetMethod = nfcExtrasClazz .getMethod("get",Class.forName("android.nfc.NfcAdapter"));
NfcAdapteradapter = NfcAdapter.getDefaultAdapter(context);
ObjectnfcExtras = getMethod .invoke(nfcExtrasClazz, adapter);
MethodgetEEMethod = nfcExtras.getClass().getMethod("getEmbeddedExecutionEnvironment",
(Class[]) null);
Object ee= getEEMethod.invoke(nfcExtras , (Object[]) null);
ClasseeClazz = se.getClass();
MethodopenMethod = eeClazz.getMethod("open", (Class[]) null);
MethodtransceiveMethod = ee.getClass().getMethod("transceive",
new Class[] { byte[].class});
MethodcloseMethod = eeClazz.getMethod("close", (Class[]) null);
openMethod.invoke(se,(Object[]) null);
Objectresponse = transceiveMethod.invoke(se, command);
closeMethod.invoke(se,(Object[]) null);
当然我们可以更优雅的方式将其封装在一个单独的类中,正如在测试程序中使用的方式。现在我们拥有了一个与SE的有效连接,可以发送一些测试数据了。
3, 打开SE,发送命令,最后关闭SE。
我们先发送一个空的选择命令
SEConnectionsec = new SEConnection(self);
booleanconnStatus = sec.connect();
byte[]cmd = {00, (byte)0xA4, 04, 00, 00};
byte[]response = sec.sendApdu(cmd);
Stringinfo = ByteTool.ByteArrToHexString(response, 0, response.length);
Log.d(TAG,String.format("Select result: %s", info));
infoTextView.setText(info);
sec.disconnect();
需要注意的是一定要记得在最后调用close(),因为当NFC-EE被打开后,会阻塞非接触通讯界面。
4, 编译应用程序,提取签名证书并修改手机上的/etc/nfcee_access.xml
4.1,测试中使用debug方式编译应用程序,因此需要找到debug签名用的keystore,文件的路径可以从Window-->Preferences-->Android-->Build中得到
4.2,使用JRE下的keytool导出android程序debug版证书到一个文件
C:\Program Files\Java\jre6\bin>keytool -exportcert -v-keystore debug.keystore -alias androiddebugkey -storepass android >>a.bin
注意我们要的是文本编码的签名证书,而不是二进制的,因此需要将a.bin转换为HEX字符串形式。
4.3,将HEX字符串拷贝到nfcee_access.xml 中,内容如下:
<signerandroid:signature="308203……20712F" >
<package android:name="com.example.embeddedseaccess"/>
</signer>
4.4,将文件上传到手机上SD卡中,并用root权限的文件管理器覆盖/etc下的文件(原文件先备份,或在原文件上添加,否则原有的使用SE的应用可能会失效)
4.5,重启手机以使新文件生效
5, 运行访问SE的测试程序
由于SE符合GlobalPlatform(GP)标准中,因此可以通过一个空选择APDU命令得到卡和主安全域Issuer Security Domain (ISD)的相关信息。该APDU指令数据为00 A4 0400 00,下面为发送的例子。
APP发送:
00A4040000
SE返回:
6F658408....9000这是一个TLV格式的HEX数据,结尾处的9000表明指令成功。具体内容可以根据格式自行解析。如果你的手机上装有SE上的应用,例如谷歌钱包,或其它移动支付应用,并知道其AID,就可以通过APDU选择访问。
既然我们可以访问SE了,那么能否实现
1, 控制其工作模式,使SE工作在卡模拟模式
2, 在SE中添加应用以实现自己的卡模拟