转载:http://blog.csdn.net/firefight/article/details/12647355#comments
前言
在“十问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的条件对一般用户也是非常苛刻的,包括
- 手机支持NFC,并且SE为内置SE或SWP-SIM,
- 手机已经ROOT,
- 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。
- 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实现移动支付应用。
- SD卡形式
另一种形态是AdvancedSecurity SD card (ASSD),本质上是一个带有嵌入式SE芯片的SD卡。将SD卡插入Android设备SD插槽,并运行一个SEEK补丁过的Android版本,可以通过SmartCard API访问SE。但是并不是所有手机都具有SD插槽,因此ASSD方式不太可能成为主流。
- 内置SE模式
正如其名,内置SE是设备主板的一部分,并作为NFC芯片的专用芯片,或者干脆集成为NFC芯片的一部分,因此内置SE不能从手机上移除。第一个支持内置SE的设备是Nexus S,这款手机也是首款支持NFC的Android手机。我们实验用的设备,Galaxy Nexus,带有内置的NXP PN65N 芯片,该芯片在一个单独的封装中集成了一个NFC射频控制器和一个SE(NXP SmartMX系列的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
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(包括SWPUICC), µSD和UICC(非SWPUICC)类型的SE,同时通过第三方驱动支持外置模块SE。
从图中可以看到:
1, 内置SE(eSE) 手机APP – NFC LIB – CLF– 内置SE
2, SWP-UICC 手机APP – NFCLIB – CLF– SWPUICC
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控制器连接,具有三种操作模式:关闭,连线和虚拟。
- 关闭模式,NFC CLF模块与SE没有通讯
- 连线模式,NFC CLF模块使SE对Andorid操作系统可见,就像与RF读写器连接的(非接触式)智能卡
- 虚拟模式,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或 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类的一个方法,可以得到NFC-EE的实例管理。向SE发送一条命令的完整代码如下:
NfcAdapterExtras adapterExtras = NfcAdapterExtras.get( NfcAdapter.getDefaultAdapter(context) );
NfcExecutionEnvironment nfceEe = adapterExtras.getEmbeddedExecutionEnvironment();
nfcEe.open();
byte[] response = nfcEe.transceive(command);
nfcEe.close();
然而,正如我们上面讲到的,com.android.nfc_extras是一个可选库,不是SDK中的一部分。我们不能直接的引入它,因此我们要么在Android源代码中编译(将其放在/packages/apps/目录下),或者使用反射机制。由于SE接口很小,为了便于编译和测试,我们选择反射方式。获取,打开和使用NFC-EE实例的代码就变成了下面的形式:
Class nfcExtrasClazz =Class.forName("com.android.nfc_extras.NfcAdapterExtras");
Method getMethod = nfcExtrasClazz .getMethod("get",Class.forName("android.nfc.NfcAdapter"));
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
Object nfcExtras = getMethod .invoke(nfcExtrasClazz, adapter);
Method getEEMethod = nfcExtras.getClass().getMethod("getEmbeddedExecutionEnvironment", (Class[]) null);
Object ee= getEEMethod.invoke(nfcExtras , (Object[]) null);
Class eeClazz = se.getClass();
Method openMethod = eeClazz.getMethod("open", (Class[]) null);
Method transceiveMethod = ee.getClass().getMethod("transceive", new Class[] { byte[].class});
Method closeMethod = eeClazz.getMethod("close", (Class[]) null);
openMethod.invoke(se,(Object[]) null);
Object response = transceiveMethod.invoke(se, command);
closeMethod.invoke(se,(Object[]) null);
当然我们可以更优雅的方式将其封装在一个单独的类中,正如在测试程序中使用的方式。现在我们拥有了一个与SE的有效连接,可以发送一些测试数据了。
3, 打开SE,发送命令,最后关闭SE。
我们先发送一个空的选择命令
SEConnection sec = new SEConnection(self);
boolean connStatus = sec.connect();
byte[] cmd = {00, (byte)0xA4, 04, 00, 00};
byte[] response = sec.sendApdu(cmd);
String info = ByteTool.ByteArrToHexString(response, 0, response.length);
Log.d(TAG,String.format("Select result: %s", info));
infoTextView.setText(info);
sec.disconnect();
需要注意的是一定要记得在最后关闭NFC-EE接口,因为当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 04 00 00,下面为发送的例子。
APP发送:
00A4040000
SE返回:
6F658408....9000
这是一个TLV格式的HEX数据,结尾处的9000表明指令成功。具体内容可以根据格式自行解析。如果你的手机上装有SE上的应用,例如谷歌钱包,或其它移动支付应用,并知道其AID,就可以通过APDU选择命令访问。
既然我们可以访问SE了,那么能否实现
1, 控制其工作模式,使SE工作在卡模拟模式
2, 在SE中添加应用以实现自己的卡模拟
改变SE工作模式
在NfcAdapterExtras类中,有两个关于卡模拟的函数,getCardEmulationRoute和setCardEmulationRoute,分别用于得到和设置卡模拟工作模式,其中getCardEmulationRoute返回一个CardEmulationRoute类对象,而setCardEmulationRoute需要构造一个CardEmulationRoute类对象作为参数。
CardEmulationRoute类为NfcAdapterExtras中定义的内部类,主要包括:
nfcEe | |
int | route |
由于CardEmulationRoute是一个内部类,我们甚至不能用类名反射得到类定义,只能从NfcAdapterExtras类中提取,由于NfcAdapterExtras中只有一个内部类,我们可以简单的取第一个即可。通过构造CardEmulationRoute类,可以将相关SE设置为ROUTE_OFF或ROUTE_ON_WHEN_SCREEN_ON,即卡模拟关闭和卡模拟在屏幕开启时打开。以下 具体的实现:
int routeOption = ROUTE_ON_WHEN_SCREEN_ON;
//Get route inner class
Class nfcAdapterExtrasClazz =Class.forName("com.android.nfc_extras.NfcAdapterExtras");
Class[] innerClazz =nfcAdapterExtrasClazz.getDeclaredClasses();
Constructor clazzConstuctor = innerClazz[0].getConstructor(Integer.TYPE,Class.forName("com.android.nfc_extras.NfcExecutionEnvironment"));
Object route = clazzConstuctor.newInstance(routeOption, se);
//Setcard emulation route
Method setCardEmulationRouteMethod = nfcExtras.getClass().getMethod("setCardEmulationRoute",innerClazz[0]);
setCardEmulationRouteMethod.invoke(nfcExtras,route);
通过这些代码,我们可以将得到的SE(NFcExecutionEnvironment
)设为卡模拟模式,
相应的,通过
int routeOption = ROUTE_OFF;
Object route =clazzConstuctor.newInstance(routeOption, null);
setCardEmulationRouteMethod.invoke(nfcExtras,route);
可以关闭卡模拟模拟。
如果不进行模式设置,使用getCardEmulationRoute得到的CardEmulationRoute中,NFCEE为空,状态为ROUTE_OFF,可见缺省状态下,卡模拟为关闭,且不关联到任何NFCEE上。
Object cardEmulationRoute = getCardEmulationRouteMethod.invoke(nfcExtras, null);
Field route = cardEmulationRoute.getClass().getDeclaredField("route");
Object r= route.get(cardEmulationRoute);
return Integer.parseInt(r.toString());
为了检查手机是否真正工作在卡模式下,我们需要一个读写器。注意不能用另外一个NFC手机,因为NFC手机之间总是优先使用NFC的P2P模式,而不会使用读写器-卡模式工作。
以下是使用读写器读取的例子
GALAXY NEXUSI9250
ISO/IEC 14443A(106 kbps) target:
ATQA (SENS_RES):00 02
UID (NFCID1):2f 8a c6 99
SAK (SEL_RES):38
ATS: 78 80 70 02 00 31 c1 73 c8 40 00 00 90 00
可见模拟卡的UID为2f 8a c6 99,符合ISO14443-4协议。
再选择空的AID,能够得到从内部访问同样的GP数据。
[root@TestAgmnewDisk]# ./readnfccc
SELECT_APP-------------------------106
< 6f658408a000000003000000a5599f6501ff9f6e06479100783300734a06072a864886fc6b01600c060
a2a864886fc6b02020101630906072a864886fc6b03640b06092a864886fc6b040215650b06092b8510
864864020103660c060a2b060104012a026e01029000
在SE中添加应用以实现自己的卡模拟
既然我们建立了与SE的连接,是否就可以安装应用了呢?当然不是,如果这样,SE也没有安全性可言了。前面提到SE是一个CPU卡,一般符合GP标准。GP标准的目的就是支持卡上的多应用的同时,保证卡内容的安全。
SE的发行者拥有GP卡管理器的密钥,即主安全域(ISD)密钥,可以进行辅助安全域和应用的创建和安装。
为了让不同的应用提供商都可以使用SE上的空间,应用提供商可以向SE发行者申请,建立辅助安全域,并通过密钥转换,控制一部分SE空间,并进行应用的下载/更新/删除。为了在后台自动完成这些流程,就需要一个代理中介实现,即所谓的授信服务管理器(Trusted Service ManagerTSM)。SE发行者通过建立发行者TSM(SE-TSM),提供对其拥有的SE的操作。应用提供商通过与SE发行者的达成合作协议,实现自己应用的下载安装。
因此,目前有以下几种方式可以获得在SE上安装应用的权力:
1, 自行发卡,自己控制SE的主密钥。
2, 从SE发行者得到密钥,在一般情况下,SE发行者是不可能提供这些密钥的,但是有些开发测试机是公开密钥的,以便提供给应用合作伙伴进行测试。
3, 不获取密钥,而是通过SE-TSM代理完成。
4, 破解,但由于SE一般有自锁功能,因此暴力破解是很困难的。
OK,感谢你看完这篇晦涩的文章,希望能有所帮助,如果有错误,请指正。本文主要参考:
http://nelenkov.blogspot.com/2012/08/accessing-embedded-secure-element-in.html
http://nelenkov.blogspot.com/2012/08/android-secure-element-execution.html