[导读]本文是javacard开发教程的最后一部分,首先我们回顾一下前面所讲述的内容。在第一部分《javacard开发教程入门篇》中,我们介绍了:
如何使用智能卡保存敏感信息并且安全地处理事务,以及javacard技术的各个方面:javacard虚拟机、运行时环境、有关的应用程序编程接口和javacard小应用程序的行为。
在第二部分《javacard小应用程序开发教程》中,我们介绍了:
开发设计一个javacard小应用程序的步骤以及一个java card小应用程序的结构,sun javacard development工具箱和应用程序编程接口以及可用于你编写小应用程序的程序设计模型:java card应用编程接口和java card rmi应用程序编程接口。
在这一部分中我们将主要介绍:
介绍主应用程序的开发,以及一些可用的java应用程序编程接口:opencard framework、java card rmi client编程接口和用于j2me的security and trust services编程接口( satsa)。
阅读目录:
javacard应用程序的组成元素
javacard应用程序不是独立的,主要由四个部分组成......
opencard框架介绍
opencard联盟是一群推动opencard框架定义与采用的公司,目前opencard框架是1.2版本。ocf的目标是提供给主机端应用程序的开发者跨不同的卡片读取器供应商工作的应用编程接口......
javacard rmi客户端编程接口
为了智能卡管理和访问,jcrmi客户端应用编程接口需要一个卡片终端和诸如刚刚描述的opencard framework这样的服务应用编程接口......
生成rmi客户端程序
在一部分作者解释了生成客户端程序的编译命令和使用限制......
用于j2me的安全和信任服务编程接口
介绍satsa的通讯应用编程接口......
指定satsa连接类型
gcf连接都是使用connector.open()方法创建,connector.open()的一个参数是指明创建的连接类型的url......
使用一个apduconnection
讲述apduconnection的定义方法,并用一个示例解释了它的使用方法......
使用一个javacardrmiconnection
以示例解释javacardrmiconnection的定义方法和使用方法......
| 本文章的留言内容: |
javacard应用程序不是独立的,而是一个端对端的应用程序的一部分:

figure 1.javacard应用程序的典型组成
一个典型的javacard应用程序由以下部分组成:
1、提供访问例如保存在数据库中的安全或者电子付款信息的back-office服务的后端应用程序。后端应用程序如何开发超出了本文的范围。
2、在卡外,驻留在一个卡片终端上,主应用程序使用许多用于卡片访问的接口之一来访问智能卡上的小应用程序,例如java card rmi、opencard framework应用编程接口或者security and trust services应用编程接口( satsa)。
3、读卡器,卡片终端或者卡片接收设备,提供主应用程序和卡上小应用程序之间的物理接口。
4、卡上的是java card小应用程序和java card框架。注意,在访问小应用程序之前,主应用程序必须提供证书并且验证自己。
编写一个主应用程序-访问你的小应用程序
客户端上的主应用程序处理用户、javacard小应用程序和供应商的后端应用程序之间的通讯。主程序访问你的小应用程序提供的服务。它存在于终端或者卡片接收设备上,例如一个工作站、一个售货点( pos)终端、一个手提电话或者一个机顶盒。回想一下一个主机应用程序和小应用程序使用iso - 7816 apdu命令经由读卡器或终端进行交互。
传统的读卡端应用程序使用c语言编写,但是主机程序可以使用java程序语言或者其他语言编写,只要它能够与小应用程序交换有效的iso - 7816 apdu命令。
现在部署的大部分的手提电话整合一个智能卡阅读器访问捆绑在它上面的sim卡。使用即将到来的jsr 177、用于j2me的安全和信任服务应用编程接口(satsa)和j2me设备的广泛采用,我们可以想象有许多主应用程序将使用移动设备上的java技术编写。satsa的意图是启动一个运行在基于j2me的设备上的java card主应用程序。jsr 177目前处在jcp团体审查阶段。
当你编写客户端应用程序的时候,有三个主要的应用程序编程接口可用:opencard framework、javacard rmi client应用编程接口和安全与信任服务应用编程接口( satsa)。我们将依次看看这些应用程序编程接口。 opencard框架介绍
智能卡供应商一般不仅提供开发工具箱,而且提供支持读取端应用程序和javacard小应用程序的应用程序编程接口。许多供应商支持opencard框架( ocf),这是一套基于java的应用程序编程接口,把一些来自不同的供应商的与读卡器交互的细节隐藏起来。
opencard联盟是一群推动opencard框架定义与采用的公司,目前opencard框架是1.2版本。ocf的目标是提供给主机端应用程序的开发者跨不同的卡片读取器供应商工作的应用编程接口。
ocf为你定义许多标准卡服务。其中两个是fileaccesscardservice和signaturecardservice。一个特殊的类型是applicationmanagercardservice,提供了在卡上安装、注册和删除小应用程序的生命周期管理方法。
当编写一个主机端基于ocf的应用程序时,你基本上要把它分离成两个部分:
1、和终端或者读取器交互的主应用程序对象(初始化ocf,等待卡片插入并且终止ocf),并且能够显露高级的卡片访问方法,例如getbalance ()。
2、一个实现实际的低级通道管理和apdu输入/输出的小应用程序代理。当把apdu细节从应用程序中隐藏起来的时候,这个代理(proxy)设计模式允许你显露一个面向对象接口。

figure 2. ocf应用程序的结构
总之,一个典型的ocf应用程序具有一个或多个main对象,都是在主机上创建,可能再它们的自己执行的线程上。这些main应用程序对象显露了高级的特定应用程序调用,这些最终都被委托给小应用程序代理。它们使用smartcard对象,这是应用程序到ocf的入口点,启动应用程序来初始化并且关闭ocf,并且等待卡片被插入。main对象可以实现一个ctlistener,你不久将看到,这个监听者提供了诸如卡片插入和拔出等事件的异步标志信息。
你可以使用一个同步或者异步模型编写你的应用程序。
在同步模型中,你的主应用程序初始化ocf,然后等待卡片被插入。然后它执行你的main应用程序逻辑,并且在完成的时候关闭ocf:
| ... try { // initialize ocf smartcard.start(); // wait for a smart card cardrequest cr = new cardrequest(cardrequest.newcard, null, ocfcardaccessor.class); smartcard mycard = smartcard.waitforcard(cr); // main client work is done here... ... } catch (exception e){ // handle exception } finally { try { // shut down ocf smartcard.shutdown(); } catch (exception e) { e.printstacktrace(); } } ... |
列表⒈一个同步ocf应用程序的典型结构
如果你喜欢使用异步的途径,你的类必须实现ctlistener接口,并且,在初始化阶段,注册它自己用于诸如插入和拔出等卡片终端事件的通知。下面的应用程序骨架以初始化ocf和注册监听者开始,然后定义了用于有效事件的回调方法。
| public class myhostsideapp implements ctlistener ... public myhostsideapp() { try { // initialize the framework smartcard.start (); // register this as a card terminal event listener cardterminalregistry.getregistry().addctlistener(this); } catch (exception e) { // handle error... } } public void cardinserted(cardterminalevent ctevent) { ... } public void cardremoved(cardterminalevent ctevent) { ... } ... } |
列表2、一个异步ocf应用程序的典型结构
当一张卡片被插入时,运行时间调用cardinserted ()方法,并且当卡片被拔出时,运行时间调用cardremoved()方法。在下面的代码中,插入卡片初始化小应用程序代理的创建,并且拔出卡片触发小应用程序代理的清除。代码列表还说明了信用卡余额请求代理。
| import opencard.core.event.ctlistener; import opencard.core.event.cardterminalevent; import opencard.core.service.smartcard; import opencard.core.service.cardservice; ... public class myhostsideapp implements ctlistener { public void myhostsideapp() { try { // initialize the framework smartcard.start (); // register this as a card terminal event listener cardterminalregistry.getregistry().addctlistener(this); } catch (exception e) { // handle error. ... } } /** * card insertion event. get new card and card service * @param ctevent the card insertion event. */ public void cardinserted(cardterminalevent ctevent) { try { // get a smartcard object card = smartcard.getsmartcard(ctevent); // get the card proxy instance. mycardproxy = (mycardproxy) card.getcardservice(mycardproxy.class, true); } catch (exception e) { e.printstacktrace(); } } /** * card removal event. invalidate card and card service. * @param ctevent the card removal event. */ public synchronized void cardremoved(cardterminalevent ctevent) { card = null; mycardproxy = null; // initialize the framework smartcard.shutdown(); } /** * get balance from the smart card. */ public int getbalance() { try { // get mutex to prevent other card services from modifying // data. delegate the call to the applet proxy. card.beginmutex(); return integer.parseint(mycardproxy.getbalance()); } catch (throwable e) { return 0; } finally { // end mutual exclusion card.endmutex(); } } ... } |
列表⒊一个增强的基于监听者的ocf应用程序
接下来是小应用程序代理的选节。ocf应用程序委托服务调用到小应用程序代理,实现复杂的apdu管理模块:
| public class mycardproxy extends appletproxy { // my apdu definitions. final static byte myapplet_cla = (byte)0x80; final static byte verify_ins = (byte)0x20; final static byte get_balance_ins = (byte) 0x30; final static short get_balance_response_sz = 2; protected final static int ok = 0x9000; final static short sw_pinverify_failed = (short)0x6900; /** * reusable command apdu for getting an information * entry field. */ private commandapdu getbalanceapdu = new commandapdu(14); ... /** application identifier of the businesscard applet */ private static final applicationid my_card_aid = new applicationid(new byte[] { (byte)0xd4, (byte)0x55, (byte)0x00, (byte)0x00, (byte)0x22, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xff}); /** * create a mycardproxy instance. * * @param scheduler the scheduler from which channels * have to be obtained. * @param card the smartcard object to which this * service belongs. * @param blocking currently not used. * * @throws opencard.core.service.cardserviceexception * thrown when instantiation fails. */ protected void initialize(cardservicescheduler scheduler, smartcard card, boolean blocking) throws cardserviceexception { super.initialize(my_card_aid, scheduler, card, blocking); try { // allocate the card channel. this gives us // exclusive access to the card until we release the // channel. allocatecardchannel(); // get the card state. ... } finally { releasecardchannel(); } } /** * gets the balance. * @return the balance. */ public string getbalance() throws cardserviceinvalidcredentialexception, cardserviceoperationfailedexception, cardserviceinvalidparameterexception, cardserviceunexpectedresponseexception, cardserviceexception, cardterminalexception { try { allocatecardchannel(); // set up the command apdu and send it to the card. getbalanceapdu.setlength(0); getbalanceapdu.append(myapplet_cla); // class getbalanceapdu.append(get_balance_ins); // instr'n getbalanceapdu.append((byte) 0x00); // p1 getbalanceapdu.append((byte) 0x00); // p2 getbalanceapdu.append((byte) 0x00); // lc getbalanceapdu.append((byte) 0x00); // le // send command apdu and check the response. responseapdu response = sendcommandapdu(getcardchannel(), my_card_aid, getbalanceapdu); switch (response.sw() & 0xffff) { case ok : return new string(response.data()); default : throw new cardserviceunexpectedresponseexception("rc=" + response.sw()); } } finally { releasecardchannel(); } } ... } |
列表4、一个小应用程序代理示例
你可以在opencard framework 1.2 programmer ' s guide(http://www.opencard.org/docs/pguide/pguide.html)中获得更多的关于ocf使用的消息。还引用了opencard framework引用实现(http://www.opencard.org/index-download.shtml)中的示例文件。
本文中未涉及,但是值得一提的是称为global platform(http://www.globalplatform.org)的主机端应用程序编程接口。global platform card committee提供了一组补充java card应用程序编程接口的卡片应用程序编程接口,提供了卡片管理特性。这些规范都在2.1.1版本中。 javacard rmi客户端应用编程接口
前面你已经学到了如何编写一个基于jcrmi的小应用程序。如果你的java card小应用程序基于jcrmi,你可以使用java card rmi客户端应用编程接口编写一个主应用程序,访问智能卡中保存的小应用程序对象。
为了智能卡管理和访问,jcrmi客户端应用编程接口需要一个卡片终端和诸如刚刚描述的opencard framework这样的服务应用编程接口。
当我们把这两个应用程序编程接口放在一起,我们得到一个非常简单、非常完整的面向对象编程模型,有以下几个优点:
1、不必知道智能卡和读卡器的细节
2、不必知道低级的apdu通讯
3、便于设计和维护代码,缩短开发时间
jcrmi客户端应用编程接口在下面的程序包中定义:
1 com.sun.javacard.javax.smartcard.rmiclient包含核心jcrmi客户端应用编程接口。它定义:
1)jcrmi代码程序用来访问智能卡的cardaccessor接口。
2)用于jcrmi程序生成实现的基本类cardobjectfactory类。这个类的实例与一个java card小应用程序选择的会话有关。
3)客户端应用程序使用的javacardrmiconnect类,用于初始化一个jcrmi会话并且获得初始的远程引用。
4)许多java card异常子类,例如apduexceptionsubclass、cardexceptionsubclass、cardruntimeexceptionsubclass、cryptoexceptionsubclass、isoexceptionsubclass、pinexceptionsubclass、pinexception、serviceexceptionsubclass、systemexceptionsubclass、transactionexceptionsubclass和userexceptionsubclass。
2 javacard.framework定义了许多客户端上的许多可以被再次抛出的java card异常:apduexception、cardexception、cardruntimeexception、isoexception、pinexception、systemexception、transactionexception和userexception。
3 javacard.framework.service定义了serviceexception,描述与服务框架有关的异常。
4 javacard.security定义了描述一个有关加密异常的cryptoexception。 生成rmi客户端程序
你可以使用标准的java rmi编译程序(rmic)生成客户端程序。你必须使用下面格式的命令运行rmic,用于你的小应用程序中的每个远程类:
rmic -v1.2 -classpath path -d output_dir class_name
在这里:
1 -v1.2是一个java card rmi客户端框架所需要的标志。
2 - classpath路径标明到远程类的路径。
3 output_dir是存放结果程序的目录。
4 class_name是远程类的名称。
然而,推荐生成rmi客户端程序的方法是使用j2se sdk 1.3中的动态代理生成机制。如果当你选择jcrmi小应用程序的时候使用cardobjectfactory子类型jccardproxyfactory的话,javacard rmi客户端应用编程接口的2.2版本将为你自动生成程序,你不必再生成程序。这个方法在列表5中说明。
用法限制
因为java card是一个有限制的运行时环境,我们可以发现jcrmi的限制。java card不支持序列化,并且jcrmi参数和返回值也有限制:
1、每个到远程方法的参数必须是java card支持的类型之一,不包括char、double、float、long或多维数组。对于int的支持是自选的。
2、任何远程方法的返回值必须是支持的类型之一,或者void,或者一个远程接口类型。
jcrmi客户应用程序
一个jcrmi客户应用程序类似你前面看到的ocf主应用程序,因为jcrmi客户端应用编程接口依靠ocf用于卡片管理和通讯。
下面的代码片断首先初始化ocf,并且等待智能卡插入。它然后创建一个ocfcardaccessor实现,用于把我们的jcrmi连接到卡片上,如果必要,客户端程序动态生成,小应用程序被选中,我们取得远程引用,最后我们产生到getbalance()的我们的远程调用:
| ... try { // initialize ocf smartcard.start(); // wait for a smart card cardrequest cr = new cardrequest(cardrequest.newcard, null,ocfcardaccessor.class); smartcard mycard = smartcard.waitforcard(cr); // get an ocfcardaccessor for java card rmi cardaccessor ca = (cardaccessor) mycard.getcardservice(ocfcardaccessor.class, true); // create a java card rmi instance javacardrmiconnect jcrmi = new javacardrmiconnect(ca); // create a java card proxy factory that is used for dynamic // proxy generation. cardobjectfactory factory = new jccardproxyfactory(ca); // select the java card applet jcrmi.selectapplet(my_applet_aid, factory); // get the initial reference myremoteinterface myremoteinterface = (myremoteinterface) jcrmi.getinitialreference(); if(myremoteinterface == null) { throw new exception("received null instead of the initial ref"); } // invoke the remote getbalance() method short balance = myremoteinterface.getbalance(); } catch(userexception e) { // handle exception ... } catch (exception e){ // handle exception ... } finally { // clean up try{ smartcard.shutdown(); }catch (exception e){ system.out.println(e); } } |
列表5、示例jcrmi客户端
如你所见,你必须让代码简化。 用于j2me的安全和信任服务编程接口
satsa是一套用于j2me的新的可选程序包,定义一个客户端应用编程接口来访问安全元素:例如智能卡这样的设备。在本节,我将仅仅介绍satsa的通讯应用编程接口;本文中将不涉及satsa pki和加密应用程序编程接口。
satsa通讯应用编程接口被分解成下面几部分:
1 satsa-apdu定义一个应用编程接口,用于和遵循iso-7816-4的智能卡通讯。这个可选程序包由单独的javax.microedition.io.apduconnection程序包组成。
2 satsa-jcrmi定义一个java card rmi客户端应用编程接口。这个可选程序包由下面的java程序包组成:
a)javax.microedition.io.javacardrmiconnection
b) javax.microedition.jcrmi.remoteref
c) javax.microedition.jcrmi.remotestub
d) java.rmi.remote
e) java.rmi.remoteexception
f) javacard.framework.service.serviceexception
g) javacard.framework.cardruntimeexception
h) javacard.framework.isoexception
i) javacard.framework.apduexception
j) javacard.framework.cardexception
k) javacard.framework.pinexception
l) javacard.framework.systemexception
m) javacard.framework.transactionexception
n) javacard.framework.userexception
o) javacard.security.cryptoexception
satsa把j2me和java card平台紧密的结合在一起。satsa使用cldc 1.0 generic connection framework ( gcf)用于在基于j2me的设备和一个如下面所图解的智能卡之间通讯:

figure 3. 普通连接和satsa连接
因为 satsa基于gcf,开发使用手机上的智能卡的midlet相对不容易,但是对于j2me开发者来说很熟悉。
satsa考虑到用于java card应用程序的两个总体变成模型:apdu-消息传递模型和java card rmi面向对象分布式模型。satsa为了这些模型中的每一个定义了一个新的gcf连接类型:
1 apduconnection允许一个j2me应用程序使用iso-7816 apdu协议来与智能卡应用程序交换apdu。
2 javacardrmiconnection允许一个j2me应用程序使用java card rmi来调用智能卡上的远程方法。 指定satsa连接类型
所有的gcf连接都是使用connector.open()方法创建。connector.open()的一个参数是指明创建的连接类型的url。cldc gcf使用下面的格式定义这个url为一个字符串:
scheme:[target][params]
在这里:
1 scheme是创建的连接类型(和使用的协议)。
2 target一般是某种网络地址。
3 params是可选参数,通过分号隔开。
对于satsa,url的格式是:
protocol:[slotid]; aid
在这里:
1 protocol要么是apdu,用于基于apdu的连接,或者是jcrmi,用于基于jcrmi的连接。
2 slotid是指明卡片插入的插孔号。slotid字段是可选的;默认值为0。
3 aid是用于智能卡应用程序的应用程序标识符。aid是一个由句号分隔开的5到16个十六进制字节值得字符串;例如," a0.0.0.67.4.7.1f.3.2c.3"。 使用一个apduconnection
一个apduconnection定义方法,允许我们使用gcf与遵循iso-7816的卡片通讯。它定义三个方法:
1 enterpin()提示用户输入一个个人识别号码。
2 exchangeapdu()与智能卡应用程序交换apdu。这个调用直到一个响应从智能卡返回的时候(或者处理被中断)才会阻断。
3 getatr()返回智能卡发送的answer to reset(atr)消息,作为重置操作的响应。
下面的代码片断显示如何打开一个apduconnection,如何关闭它,以及如何交换一个命令apdu并且接收一个响应apdu:
| ... try { // create an apduconnection string url = "apdu:0;aid=a1.0.0.67.4.7.1f.3.2c.5"; apduconnection ac = (apduconnection) connector.open(url); // send a command apdu and receive a response apdu byte[] responseapdu = ac.exchangeapdu(commandapdu); ... // close connection. ac.close(); } catch(ioexception e){ ... } ... |
列表6、使用satsa-apdu
satsa使apdu通讯简单化。注意,这个命令和响应apdu的格式和你在本系列第二部分《javacard小应用程序开发教程》看到的相同,告诉你如何编写一个基于apdu消息传送的java card小应用程序。 使用一个javacardrmiconnection
一个javacardrmiconnection定义方法,允许我们使用java card rmi程序设计模型。javacardrmiconnection定义方法getinitialreference(),返回初始远程引用的程序对象。
| ... try { // create a javacardrmiconnection string url = "jcrmi:0;aid=a0.0.0.67.4.7.1f.3.2c.3"; javacardrmiconnection jc = (javacardrmiconnection) connector.open(url); myremoteobject robj = (myremoteobject) jc.getinitialreference(); ... short balance = robj.getbalance(); ... // close connection jc.close(); } catch (exception e) { ... } ... |
列表7、使用satsa-jcrmi
采用上面所说的satsa-jcrmi应用编程接口,允许我们调用本系列第二部分(《javacard小应用程序开发教程》)定义的getbalance()方法,向你说明如何编写一个基于rmi的javacard小应用程序。
闽公网安备 35060202000074号