JavaCard
开发教程之智能卡简介
智能卡不是新鲜事物。它们在二十年前在欧洲就以记忆卡片的形式推出了,用于保存关键的电话信息,以减少盗打付费电话的可能。
智能卡技术是ISO国际标准组织的连接技术委员会1(JTC1)和国际电子委员会(IEC)定义并控制的一种行业标准。1987年推出的ISO/IEC 7816国际标准系列在2003年推出了它的最新的升级版本,定义了智能卡的各个方面,包括物理特征、物理接触界面、电子信号和传输协议、命令、安全体系、应用程序标识符和公用数据元素等。
智能卡是一个包含嵌入集成电路(IC)的塑料卡片,类似于一张信用卡。当用作SIM卡时,这个塑料卡片很小,正好能放入手机中。智能卡设计时就极注重高度安全性,窜改一点点内容都会导致毁坏它包含的信息。
在智能卡使用的某些领域,它们只是仅仅提供受保护的非易失性存储。更高级的智能卡还有微处理器和内存,用于安全的处理和储存,并且可以用于使用公共密钥或者共享密钥算法的安全应用程序。智能卡上的非易失性存储是最宝贵的资源,可用于保存秘钥和数字证书。一些智能卡有单独的加密协处理器,支持象RSA、AEC和(3)DES这样的算法。
智能卡不包含电池,只有在和读卡机连接的时候才被激活。当它被连接时,在执行一个复位序列之后,卡片处于非激活状态,等待接收来自客户端(主机)应用程序的命令请求。
智能卡可以分为可接触和非可接触。可接触智能卡通过读卡器和智能卡的8个触点物理接触来通讯并工作,而非可接触智能卡依靠在小于2英尺的一般距离之内的射频信号通讯。非接触智能卡的射频通信基于类似于用于保存反盗窃和记录清单的射频标识符( RFID)标记的技术。图1描述了可接触和非可接触智能卡:
图1a.接触式智能卡
图1b.非接触式智能卡
图2a. 带有Java功能的智能纽扣
图2b. 带有Java功能的USB 令牌
请参阅What is a Smart Card?http://java.sun.com/products/javacard/smartcards.html获取更详细的信息。
JavaCard
开发教程之消息模型
图1中说明的消息传递模型是所有Java的基础。它的核心就是应用程序协议数据单元(APDU),CAD和Java Card框架之间交换的一个逻辑数据包。JavaCard框架接收任何CAD发送进来的APDU命令并且传送到相应的小应用程序中。小应用程序处理APDU命令,然后返回一个响应APDU。那些APDU遵守国际标准规格ISO/IEC 7816 - 3和7816 - 4。 Card通信
图1使用消息传递模型通讯
读卡器和卡之间的通信通常基于下面两种连接协议的一种,面向字节的T = 0,或者面向数据块的T = 1。还可能会用到被称为T = USB和T = RF的替换协议。JCRE APDU类向应用程序隐藏了一些协议细节,但不是全部,因为T = 0协议相当的复杂。
⒈APDU命令
一个APDU命令的结构由它的第一个字节的值控制,大部分情况下看上去如下所示:
图2、APDU命令
一个APDU命令有一个必须有的头和一个可选的体,包含:
· CLA(1字节):这个必要的字段识别指令的一个特定应用程序类。有效的CLA值在ISO 7816 - 4规范中定义:
表格1、ISO 7816 CLA值
CLA
值
|
指令类
|
0x0n, 0x1n
|
ISO 7816 - 4
卡指令,比如文件存取和安全操作
|
20 to 0x7F
|
保留
|
0x8n or 0x9n
|
你可以用作你的特定的应用程序指令的ISO/IEC 7816 - 4格式,根据标准解释' X '
|
0xAn
|
特定的应用程序或者供应商的指令
|
B0 to CF
|
你可以用作特定应用程序的ISO/IEC 7816 - 4格式
|
D0 to FE
|
特定的应用程序或者供应商的指令
|
FF
|
保留给协议类型选择
|
· 理论上,你可以使用所有的CLA值0x80或者更高值来用于特定应用程序指令,但是在许多现在的Java Card实现中,只有黑体显示的是实际认可的。
· INS(1字节):这个必需的字段指明CLA字段中标示的指令类中的一个特定指令。ISO 7816 - 4标准指定用于访问卡上的数据的基本指令,当它根据在像标准中定义的卡上的文件系统那样结构化的时候。附加功能已经在这个标准中的其它地方说明,其中一些是安全功能。表2中是一个ISO 7816指令的列表。只有当使用一个相应的CLA字节值时,你才可以根据标准定义你自己的特定应用程序的INS值,。
表格2、当 CLA = 0x时的ISO 7816 - 4 INS值
INS
值
|
命令描述
|
0E
|
Erase Binary
|
20
|
Verify
|
70
|
Manage Channel
|
82
|
External Authenticate
|
84
|
Get Challenge
|
88
|
Internal Authenticate
|
A4
|
Select File
|
B0
|
Read Binary
|
B2
|
Read Record(s)
|
C0
|
Get Response
|
C2
|
Envelope
|
CA
|
Get Data
|
D0
|
Write Binary
|
D2
|
Write Record
|
D6
|
Update Binary
|
DA
|
Put Data
|
DC
|
Update Record
|
E2
|
Append Record
|
· P1(1字节):这个必需的字段定义指令参数1。你可以使用这个字段来检验INS字段,或者用于输入数据。
· P2(1字节):这个必需的字段定义指令参数⒉你可以使用这个字段来检验INS字段,或者用于输入数据。
· Lc(1字节):这个可选的字段是命令的数据字段的字节数。
· 数据字段(可变的,字节Lc数):这个可选的字段保存命令数据。
· Le(1字节):这个可选的字段指定在期望响应的数据字段中的极限字节数。
取决于命令数据的存在与否以及相应是否必须,命令APDU有四种变化。只有在你使用协议T = 0时,你才需要关心这些变化:
图3、APDU命令的四个可能的结构
一个典型的应用程序将以不同的结构方式使用不同的APDU命令。
2、响应APDU
响应APDU的格式很简单的:
图4、响应APDU
和一个APDU命令相似,响应APDU有可选择的和必要的字段:
· 数据字段(可变长度,由APDU命令中的Le确定):这个可选择的字段包含小应用程序返回的数据。
· SW1(1字节):这个必要的字段是状态字1。
· SW2(1字节):这个必要的字段是状态字2。
这些状态字的值在ISO 7816 - 4规范中定义:
图5、响应状态码
Java Card框架应用编程接口中的ISO7816 Java接口定义了许多常数来帮助规范返回错误代码。
3、过程APDU
每当有一个进入的APDU用于所选择的小应用程序,JCRE就调用小应用程序的process ()方法,把进入的APDU作为一个参数传送。这个小应用程序必须解析APDU命令,处理数据、生成一个响应APDU,然后把控制权返回给JCRE。
RMI(JCRMI)通讯模型
第二种通信模型依靠J2SE分布式对象模型的一个子集。 RMI
在RMI模型中,一个服务器应用程序创建并生成可访问的远程对象,并且一个客户应用程序获得到远程对象的远程引用,然后调用它们的远程方法。在JCRMI中,Java Card小应用程序是服务器,而主应用程序是客户端。
JCRMI由类RMIService提供到扩展程序包javacardx.rmi中。JCRMI消息被封装到传入RMIService方法的APDU对象中,换句话说,JCRMI提供了一个基于APDU消息传递模型的分布式对象模型机制,通过这个机制服务器和客户端通信,来回传送方法信息、参数和返回值。
JavaCard
开发教程之生存周期
卡片上的每个小应用程序由一个
Application
标识符(
AID
)唯一标识。
定义在
ISO 7816 - 5
中的
AID
是一段
5
到
16
字节之间的序列。
所有的小应用程序必须扩展
Applet
抽象基本类,这个类定义了
JCRE
使用的方法来控制小应用程序的生存周期,如图
10
概括:
小应用程序生存周期在小应用程序被
下载
到卡片中并且
JCRE
调用小应用程序的
static Applet.install ()
方法的时候开始,并且小应用程序通过调用
Applet.reGISter ()
在
JCRE
中注册。
一旦小应用程序被安装并且注册,它处于未选择的状态,可以进行选择并且处理
APDU
。
图表
11.
总结小应用程序方法的操作。
图表
2
、使用
Java Card
小应用程序方法
当处在未选择的状态的时候,小应用程序是非激活状态。当主应用程序要求
JCRE
选择一个卡片中特定的小应用程序的时候(通过指示读卡器发送一个
SELECT APDU
或者
MANAGE CHANNEL APDU
),一个小应用程序被选择进行
APDU
处理。为了通知这个小应用程序主应用程序已经选择了它,
JCRE
调用它的
select()
方法;小应用程序一般执行相应的初始化来为进行
APDU
处理做准备。
一旦选择,
JCRE
传送输入的
APDU
命令到小应用程序,通过调用它的
process()
方法来进行处理。
JCRE
捕捉任何小应用程序没能捕捉的异常。
当主应用程序告诉
JCRE
选择另一个小应用程序的时候,前一个小应用程序取消选择。
JCRE
通知活动的小应用程序,它已经通过调用它的
deselect()
方法被取消了选择,小应用程序回到不活动的未经选择的状态。
Java Card
会话和逻辑通道
卡片会话是卡片被加电并且和读卡器交换
APDU
的一段时间。
Java Card 2.2
支持逻辑通道(
logical channels
)的概念,允许最多智能卡中的
16
个应用程序会话同时开启,每个逻辑通道一个会话。因为卡片中的
APDU
的处理不能中断,并且每个
APDU
包含一个到逻辑通道(在
CLA
字节)的引用,变动的
APDU
可以拟同步地访问卡片上的许多小应用程序。你可以设计一个小应用程序被多次选择;也就是说,每次和一个以上逻辑通道
通信
。多选的小应用程序必须实现
javacard.Framework.MultiSelectable
接口和相应方法。
在一些卡片部署中,一个默认小应用程序可以被定义为在卡片复位以后被自动地选择,用于在基本逻辑通道(通路
0
)上通信。
Java Card 2.2
允许你定义默认小应用程序,但是不指定的如何做;其机理由厂家特定。
JavaCard
开发教程之小应用程序
一个执行上下文和另一个执行上下文之间的界限经常被称为小应用程序
防火墙( Applet firewall)
。它是
Java
沙箱安全概念的一个
Java Card
运行时间改进本,联合类装入器
java.ClassLoader
和访问控制器、
java.AccessController
的功能。
Java Card
防火墙创建了一个虚拟堆,这样一个对象只能访问存在于相同的防火墙内的(公共的)方法和数据。
一个防火墙可能包含许多小应用程序及其他对象,比如公共的秘钥。一个
Java Card
执行上下文目前作用域是程序包。
当每个对象被创建的时候,它被指派去执行调用程序的上下文。
Java Card
平台支持跨防火墙的安全对象共用。图表
12
描述小应用程序隔离和对象共用:
图表
1.
小应用程序防火墙和对象共用
典型的流程,如图表
12
中的描述:
请求通过调用系统的
JCSystem.getAppletShareableInterfaceObject ()
方法访问
Appletc
的共享接口。
由于
Appleta
,
JCRE
通过调用小应用程序的
getShareableInterfaceObject ()
方法来要求
Appletc
的可共享的接口。
如果
Appletc
允许共用,
Appleta
将获得一个
Appletc
的共享对象的引用。
Appleta
现在就可以访问
Appletc
了。
Appleta
将拥有它创建的任意对象,即使是那些定义在
Appletc
的。
在同一个执行上下文中的小应用程序默认情况下能够相互访问,所以
Appleta
和
Appletb
不需要遵循这个程序来共享对象。
管理内存和对象
Java Card技术不支持关键字transient。取而代之,Java Card应用编程接口(javacard.Framework)定义了三个方法,允许你在运行时间创建transient数据,还定义了一个方法让你检查一个对象是否是transient的: .JCSystem
static byte[] makeTransientByteArray(short length, byte event)
static Object makeTransientObjectArray(short length, byte event)
static short[] makeTransientShortArray(short length, byte event)
static byte isTransient(java.lang.Object theObj)
你可以创建一个瞬变的字节或者short基本数据类型的数组,你也可以创建一个瞬变Object。但是记住下面用于瞬变数据的行为:
一个瞬变对象的状态在会话之间不能持久保存。注意内容(不是对象本身)是什么是瞬变的。和任何其他的Java语言对象一样,一个瞬变对象只要它被引用就一直存在。
当一个事件例如卡片复位或者小应用程序取消选择发生的时候,一个瞬变对象的内容可能重置为字段的缺省值(0、false或者null)。
因为安全的理由,瞬变对象的字段不被保存在持久内存中。
对瞬变对象字段的更新不是原子性的,不会受事务的影响。
在一个Java Card环境中,数组和基本类型应在对象声明中声明,并且你应该最小化对象实例化,以利于对象重用。实例化对象在小应用程序生命周期中只有一次,最好在小应用程序的初始化阶段,在小应用程序生命周期中只被调用一次的install()方法中。
为了促进对象的重用,对象应该保持在小应用程序的生命周期的范围内或引用中,并且它们的状态(成员变量的值)在重用之前根据情况重置。因为垃圾收集程序并不总是可用的,一个应用程序可能从不回收分配给不太占用内存的对象的存储器。
持久的事务
JCRE支持原子事务,原子事务安全地更新一个或多个持久对象。如果发生掉电或者程序错误等情况,事务将保护数据的完整性。事务是在系统级支持的,通过下面的方法:
JCSystem.beginTransaction()
JCSystem.commitTransaction()
JCSystem.abortTransaction()
在一个为许多事务模型所共用的模式中,一个Java Card事务以对beginTransaction()的调用开始,以对commitTransaction()或者abortTransaction()的调用结束。让我们来看看使用这些应用程序编程接口的代码片断:
...
private short balance;
...
JCSystem.beginTransaction();
balance = (short)(balance + creditAmount);
JCSystem.commitTransaction();
...
实例变量balance的更新是为了保证一个原子操作。如果一个程序错误或者电力重置等事件发生,这个事务就会保证前面的balance值余额被恢复。
JCRE不支持嵌套事务。
JavaCard
开发教程之虚拟机
用于Java Card平台的虚拟机是两部分实现,一部分在卡外,一部分运行在卡本身。卡上的Java Card虚拟机解释字节码、管理类和对象等等。外部Java虚拟机部分是一个开发工具,一般称为Java Card转换工具,装载、检验和进一步地准备卡片小应用程序Java类,用于在卡上执行。转换工具输出的是一个Converted Applet文件,这是一个包含一个Java程序包中所有类的文件。转换程序检验类是否遵循Java Card规范。(CAP)
JCVM只支持Java程序设计语言的一个有限的子集,然而它保留了许多熟悉的特性,包括对象、继承、程序包、动态对象创建、虚拟方法、接口和异常。JCVM规范放弃了对许多语言元素的支持,因为这些语言元素可能会用掉很多智能卡本来就很有限的内存:
表格1、Java Card语言限制的摘要信息
语言特性
|
动态类装载、安全管理(java.lang.securitymanager)、线程、对象克隆和某些方面的程序包访问控制不支持。
|
关键字
|
不支持native、synchronized、transient、volatile、strictfp。
|
类型
|
不支持char、double、float和long,也不支持多维数组。对int的支持是可选的。
|
类和接口
|
不支持除了Object和Throwable以外的Java核心应用编程接口类和接口(java.io、java.lang、java.util),并且Object和Throwable的大部分方法不可用。
|
异常
|
一些Exception和Error子类被省去,因为它们封装的异常和错误不可能在Java Card平台上出现。
|
还有程序模型限制。例如一个装载库类不能再扩展到卡上;它隐含地成为final类型。
为了符合存储限制,JCVM规范额外定义了许多程序属性的约束。表格4 JCVM资源限制总结。 注意这些约束中许多对于Java Card开发者来说是很明白的。
表格2、Java Card虚拟机约束的摘要信息
程序包
|
一个程序包可以引用128个其他的程序包
|
一个完全合乎要求的程序包名限于255字节以内。 注意字符大小取决于字符编码。
| |
一个完全合乎要求的程序包名限于255字节以内。
| |
类
|
一个类最多可以直接或者间接地实现15个接口。
|
一个接口最多可以继承于14个接口。
| |
一个程序包如果包含小应用程序(一个小应用程序程序包),它最多可以有256个静态方法;如果没有小应用程序(库程序包),它最多只能有255个静态方法。
| |
一个类最多可以实现128个public或者protected实例方法。
|
在Java Card虚拟机中,象在J2SE虚拟机中一样,class文件是核心,但是JCVM规范定义了两种其他文件格式来进一步使平台独立,转换小应用程序(Converted Applet,CAP)和导出(Export)格式,这将后面的文章中讲述。
虚拟机的生命周期
JCVM的生命周期与卡片本身的生命周期一致:在卡片制造并测试之后至发行到持卡人手中的一段时间内它就开始了生命周期,当卡片丢失或者毁坏的时候它的生命周期也就结束了。卡片没有电力的时候JCVM也不会停止,因为它的状态被保存在卡片的非易失性存储器中。启动JCVM初始化JCRE并且创建所有的JCRE框架对象,这些在JCVM的整个生命周期都是运转着的。JCVM启动之后,与卡片所有的相互作用原则上都是被卡片上的某个小应用程序控制。当卡片没电的时候,保存在RAM中的任何数据都会丢失,但是保存在永久性存储器中的任何状态都被保留。当再次加电以后,虚拟机又再次激活,这时虚拟机和对象的状态被恢复,并且重新开始执行等待进一步地输入。
JavaCard
开发教程之规范
多年以前,Sun微系统公司实现了智能卡和类似的资源约束设备的潜能,并且定义了一组Java技术子集规范来为它们创建应用程序,Java Card小应用程序。支持这些规范的设备称为Java Card平台。在一个Java Card平台上,来自不同的供应商的多个应用程序可以安全地共存。
一个典型的Java Card设备有一个8或16位的运行在3.7MHz的中央处理器,带有1K的RAM和多于16K的非易失性存储器(可编程只读存储器或者闪存)。高性能的智能卡带有单独的处理器和加密芯片,以及用于加密的内存,并且有一些还带有32位的中央处理器。
Java Card技术规范目前是2.2版,由三部分组成:
·Java Card虚拟机规范,定义了用于智能卡的Java程序语言的一个子集和虚拟机。
·Java Card运行时环境规范,进一步定义了用于基于Java的智能卡的运行期行为。
·Java Card应用编程接口规范,定义了用于智能卡应用程序核心框架和扩展Java程序包和类。
Sun还提供了Java Card开发工具箱(JCDK) http://java.sun.com/products/javacard/,包含了Java Card运行期环境和Java Card虚拟机的引用实现,和其它帮助开发Java Card小应用程序的工具。本文的第二部分将详细讲述JCDK。
Java Card技术和J2ME平台
让我们比较一下Java Card和J2ME平台技术:
图. Java Card技术和J2ME平台
CDC和CLDC配置以及它们相关的简表是J2ME平台的一部分,而Java Card是一个单独创建来用于智能卡环境的平台。
JavaCard
开发教程之接口
Java应用编程接口规范定义了传统的Java程序设计语言应用编程接口的一个小的子集--甚至小于J2ME的CLDC。不支持字符串也不支持多线程。没有象Boolean和Integer这样的包装类,也没有Class和System类。 Card
除Java核心类的小子集以外,Java Card框架还定义了它自己的特定支持Java Card应用程序的核心类。这些包含在下面的程序包中:
· java.io定义了一个异常类,基本的IOException类,来完成RMI异常层次。除此之外,没有包含其他传统的java.io类。
· java.lang定义了Object和Throwable类,但是没有J2SE中那么多方法。它还定义了许多异常类:Exception基本类,各种运行时间异常和CardException。除此之外,没有包含其他传统的java.lang类。
· java.rmi定义了Remote接口和RemoteException类。 除此之外,没有包含其他传统的java.rmi类。对远程方法调用(Remote Method Invocation,RMI)的支持被包含来简化的移植并整合到使用Java Card技术的设备中。
· javacard.Framework定义了组成核心Java Card框架的接口,类和异常。 它定义了重要的概念,例如个人识别号(Personal Identification Number,PIN),应用程序协议数据单元(Application Protocol Data Unit,APDU),Java Card小应用程序Applet,Java Card System(JCSystem)和一个utility类。它还定义了各种ISO7816常数和各种Java Card特定的异常。表格5总结了这些程序包的内容:
Table 5. 表格Java Card v2.2 javacard.framework
接口
|
ISO7816
定义与ISO 7816-3和ISO 7816-4相关的常数。
|
MultiSelectable
识别可以支持并发选择的小应用程序。
| |
个人识别号码(PIN)描述一个被用于安全(验证)目的的个人识别号。
| |
Shareable
识别一个共享对象。能通过小应用程序防火墙的对象必须实现这个接口。
| |
类
|
AID
定义了一个遵循ISO7816-5与应用程序提供者关联的Application标识符;一个小应用程序必备的属性。
|
APDU
定义了一个遵循ISO7816-4的应用程序协议数据单元,是小应用程序(卡上)和主应用程序(卡外)之间使用的通信格式。
| |
小应用程序定义了一个Java Card应用程序。所有的小应用程序必须扩展这个抽象类。
| |
JCSystem
提供了控制小应用程序生命周期、资源和事务管理,和小应用程序内部对象共享和对象删除的方法。
| |
OwnERPIN
是PIN接口的一个实现。
| |
Util
提供用于操作数组和各种short的方法,包括arrayCompare()、arrayCopy()、arrayCopyNonAtomic()、arrayFillNonAtomic()、getShort()、makeShort()、setShort()。
| |
异常
|
定义了各种的Java Card虚拟机异常类:APDUException、CardException、CardRuntimeException、ISOException、PINException、SystemException、TransactionException、UserException。
|
javacard.framework.service定义了用于服务的接口、类和异常。服务处理APDU格式的进入的命令。 表格6总结了框架服务应用编程接口:
表格6. javacard.framework.service
接口
|
Service
,基本的服务接口,定义了processCommand()、processDataIn()和processDataOut()方法。
|
RemoteService
是一个普通Service,提供到卡上的服务的远程处理。
| |
SecurityService
扩展了Service基本接口,并且提供了查询当前安全状况的方法,包括isAuthenticated ()、isChannelSecure ()和isCommandSecure ()。
| |
类
|
BasicService
是一个服务的默认实现;它提供帮助方法来处理APDU和服务协作。
|
Dispatcher
维护一个服务的注册。如果你想委托一个APDU的处理到几个服务上,你可以使用一个dispatcher。 一个dispatcher可以使用process ()方法完整的处理一个APDU,或者使用dispatch ()方法把它发送到几个服务上让其处理。
| |
异常
|
ServiceException
一个服务相关的异常
|
javacard.security定义了用于Java Card安全框架的类和接口。 Java Card规范定义了一个强健的安全应用编程接口,包括各种型式的私钥和公钥及其算法、用于计算循环码校验(CRCs)的方法、消息摘要和签名:
表格7. javacard.security
接口
|
普通的基本接口Key,PrivateKey、PublicKey和SecretKey,以及描述各种类型安全密钥和算法的子接口:AESKey、DESKey、DSAKey、DSAP、DSAPublicKey、ECKey、ECPrivateKey、ECPublicKey、RSAPrivateCrtKey、RSAPrivateKey、RSAPublicKeyrivateKey
|
类
|
Checksum
:用于循环冗余码校验算法抽象基本类
|
KeyAgreement
:用于秘钥约定算法的基本类
| |
KeyBuilder
:秘钥-对象工厂
| |
KeyPair
:一个保存一对秘钥的容器,一个私钥一个公钥
| |
MessageDigest
:用于散列算法的基本类
| |
RandomData
:用于生成随机数的基本类
| |
Signature
:用于签名算法的基本抽象类
| |
异常
|
CryptoException
:与加密有关异常,比如不支持的算法或者未初始化的秘钥。
|
javacardx.crypto是一个扩展程序包,定义了接口KeyEncryption和Cypher类,都在自己的程序包中,便于控制导出。使用KeyEncryption来解密一个使用加密算法的输入秘钥。 Cypher是所有的密码必须实现的基本抽象类。
CardRemoteObject定义两个方法export()和unexport(),允许或者禁止从卡外到对象的远程访问。RMIService扩展了BasicService,并且实现RemoteService来处理RMI请求。
安全和信任服务应用编程接口(SATSA)
定义在JSR177中的SATSA,指定一个提供用于J2ME的安全和信任应用编程接口的可选程序包。客户端应用编程接口提供了到通过一个安全元素(例如一张智能卡)提供的服务的访问,包括敏感信息的安全存储与检索,以及加密和验证服务。
SATSA利用定义在CLDC 1.0版本中的普通连接框架(GCF)来提供到消息传递和JCRMI通信模型的更抽象的接口。为了支持信息传送,SATSA定义了APDU:URL模式和APDUConnection,并且为了支持JCRMI,它定义了JCRMI:模式和JavaCardRMIConnection。
SATSA有下面的程序包组成:
java.rmi定义了Java2标准版java.rmi程序包的一个子集,特别是Remote和RemoteException。
javacard.framework定义了一个远程方法可能抛出的标准Java Card应用编程接口异常:CardRuntimeException、ISOException、APDUException、CardException、PINException、SystemException、TransactionException和UserException。
javacard.framework.service定义了远程的方法可能抛出的一个标准的Java Card应用编程接口服务异常:ServiceException。
javacard.security定义了一个远程方法可能抛出的标准的Java Card应用编程接口与加密相关的异常:CryptoException。
javax.microedition.io定义了两个连接子接口,APDUConnection用于基于APDU协议的智能卡的访问,JavaCardRMIConnection用于Java Card RMI协议。
javax.microedition.jcrmi定义了Java Card RMI stub编译程序生成的stub使用的类和接口。
javax.microedition.pki定义了用于用户证书基本管理的类。
javax.microedition.securityservice定义了用于生成应用程序级别的数字签名的类。
Java Card运行时环境
JCRE规范定义了Java Card虚拟机的生命周期,小应用程序生命周期,小应用程序如何被选择并相互隔离,事务和对象持久性和共享。这JCRE提供一个平台无关的接口到卡片的操作系统提供的服务。它由Java Card虚拟机、Java Card应用编程接口和任何特定供应商的扩展组成:
图Java Card体系结构和运行时环境
JavaCard
开发教程之程序元素
图 1. Java Card应用程序的体系结构
一个典型的Java Card应用程序不是孤立的,而是包含卡端、读取端和后端元素。让我们更详细的讲述一下每个元素。
后端应用程序和系统
后端应用程序提供了支持卡上Java小应用程序的服务。例如,一个后端应用程序可以提供到安全系统和卡上的证书的连接,提供强大的安全性。在一个电子付款系统中,后端应用程序可以提供到信用卡及其他付款信息的访问。
读取端主应用程序
主应用程序存在于一个例如个人计算机这样的台式机或者终端、电子付款终端、手机或者一个安全子系统中。
主应用程序处理用户、Java Card小应用程序和供应商的后端应用程序之间的通讯。
传统的读取端应用程序是使用C编写的。近来J2ME技术的广泛普及有望使用Java实现主应用程序;例如,它可以在一台支持MIDP和安全信赖服务应用编程接口(Security and Trust Services API)手机上运行。
智能卡供应商一般不仅提供开发工具箱,而且提供支持读取端应用程序和Java Card小应用程序的应用程序编程接口。例如OpenCard Framework,就是一个基于Java的应用程序编程接口集,隐藏了来自不同供应商的读取器的一些细节,并且提供了Java Card远程方法调用分布式对象模型和安全信任服务应用编程接口(SATSA),我在本文后面一部分讨论它们。 http://www.opencard.org/
读取端卡片接受设备
卡片接受设备(CAD)是处于主应用程序和Java Card设备之间的接口设备。一个CAD为卡片提供电力,以及与之进行电子或者射频通信。一个CAD可能是一个使用串行端口附于台式计算机的读卡器,或者可能被整合到终端内,例如饭店或者加油站内的电子付款终端。接口设备从主应用程序到卡片转送应用程序协议数据单元( Application Protocol Data Unit,简称APDU)命令(在后面讨论),并且从卡片向主应用程序转送响应。一些CAD有用于输入个人识别号码的键盘,有的可能还有显示屏。
卡片端小应用程序和环境
Java Card平台是一个多应用程序环境。在图4中我们可以看到,卡片上可能存在一个或多个Java Card小应用程序,还有支持软件--卡片的操作系统和Java Card运行时环境(JCRE)一起。JCRE由Java Card虚拟机、Java Card Framework和应用程序编程接口以及一些扩展应用程序编程接口组成。
所有的Java Card小应用程序扩展Applet基本类,并且必须实现install()和process()方法;JCRE在安装小应用程序的时候调用install(),并且在每次有一个进入的用于小应用程序的APDU的时候调用process()。
Java Card
小应用程序在被装载的时候实例化,并且在断电的时候保持运行。Java Card小应用程序起一个服务器的作用,并且是无源的。在一张卡片被加电以后,每个小应用程序都保持非运行的状态直到它被选择,在此时可能会做初始化。小应用程序只有在一个APDU被发送给它以后才被激活。一个小应用程序如何激活(被选择)在"一个Java Card小应用程序的生命周期"一节中描述。
与Java Card小应用程序通讯(访问智能卡)
你可以使用两种模型中的任何一种来在一个主应用程序和一个Java Card小应用程序之间通信。第一个模型是基本消息传送模型,第二种是基于Java Card远程方法调用(JCRMI),这是J2SE分布式对象模型的一个子集。此外,SATSA通过一个基于更加抽象的应用编程接口的普通连接框架(Generic Connection Framework,简称GCF)应用编程接口,让你要么使用消息传递要么使用JCRMI来访问智能卡。 RMI
JavaCard
小应用程序简介
1
、编写
Java
源代码。
2
、编译你的源代码。
4
、检验这个
CAP
是否有效;这个步骤是可选的。
5
、安装这个
CAP
文件。
当用
Java
程序设计语言开发传统的程序的时候,头两个步骤是相同的:编写
.java
文件并且把它们编译成
.class
文件。可是,一旦你已经创建
Java Card
类文件,过程会变化的。
Java Card
虚拟机
(JCVM)
被分成卡外虚拟机和卡内虚拟机。这个分解移除了昂贵的卡外操作,并且考虑到了在卡本身上的小的内存空间,但是它导致在开发
Java Card
应用程序的时候的额外步骤。
在
Java Card
类可以被导入一个
Java Card
设备之前,他们必须被转化成标准的
CAP
文件格式,然后选择性地检验:
·
转化必然伴有把每个
Java
程序包变换到一个
CAP
文件中,在一个程序包中包含类和接口的联合二进制表示法。转化是一个卡外操作。
·
验证是一个可选择的过程,来确认
CAP
文件的结构、有效的字节码子集和程序包内依赖性。你可能想在你使用的第三方供应商程序包上进行验证,或者如果你的转换工具来自一个第三方供应商。验证一般来说是一个卡外操作,但是一些卡片产品可能包括一个机载的检验器。
一旦检验,
CAP
文件就即将安装在
Java Card
设备上了。
Sun Java工具箱Card Development
你可以使用Sun JavaCard开发工具箱编写JavaCard小应用程序,并且甚至可以不使用一个智能卡或者读卡器来测试它们。这个工具箱包括所有你开发和测试所需要的Java Card小应用程序的基本工具:
1、 Java Card Workstation Development Environment ( JCWDE),一个便利的易于使用的JavaCard模拟工具,允许开发者直接执行类文件,而不要转化和安装CAP文件。JCWDE可以和调试程序和IDE整合。
从这个开发工具箱的2.2.1版本开始,JCWDE支持Java Card RMI ( JCRMI)。注意JCWDE不是一个成熟的Java Card模拟器。它不支持许多JCRE特性,例如包安装、小应用程序实例创建、防火墙和事务。请参阅这个开发工具箱的用户指南获取更多信息。
2、 C语言Java Card运行时环境(C-JCRE),一个使用C语言编写的可执行参考实现。C-JCRE是一个Java Card应用程序编程接口、虚拟机和运行时环境完全兼容的实现。它能让一个开发者在一个工作站环境中精确地测试小应用程序的行为。
C-JCRE有一些限制:它在一个卡片会话期间支持多达八个可以返回的引用,多达16个可以同时被导出的远程对象,8个远程方法中的数组类型参数,32个支持的Java程序包和16个Java Card小应用程序。想要获得这些限制条件,请参阅Java Card开发工具箱用户指南。
3、 JavaCard转化工具,用于生成CAP文件。
4、 JavaCard检验,用于选择性地核对CAP和导出文件的有效性。
5、 一个发送和接收应用程序协议数据单元(Application Protocol Data Units,APDUs)的APDU工具(apdutool)。这样你就可以在Java Card小应用程序测试期间发送APDU。你可以apdutool读取的脚本文件,发送APDUs到C-JCRE或者JCWDE中。
6、 一个capdump工具,用于转出CAP的内容,和一个打印EXP文件的exp2text。
7、 一个scriptgen工具,转换CAP文件为APDU脚本文件。这个工具还被认为是卡外安装程序。
8、 支持库(用于Java Card应用编程接口的类文件和导出文件)文档和范例。
当Sun JavaCard开发工具箱允许你编写和测试Java Card小应用程序的时候,部署一个现实的端对端的智能卡应用程序需要开发工具箱中没有包含的工具,例如利用了终端应用程序编程接口,如OpenCard和Global Platform应用程序编程接口。它可能还需要利用例如Subscriber Identification Module (用户识别模块,SIM)工具包这样的工具来帮助你管理SIM。
图1显示了这个工具包的目录结构(Windows版本),以及包含开发工具的bin目录的内容。
|
|
Figure 1a. Development Kit Directory Structure
|
Figure 1b. Contents of bin directory
|
现在让我们在看一次Java Card开发步骤,这次使用Sun Java Card Development工具箱:
1.使用你喜爱的编辑器或者IDE编写Java源程序。
2.使用你喜爱的编译程序或者IDE编译Java源程序。
3.选择性地,使用JCWDE模拟器测试你的Java Card小应用程序。重申一下,JCWDE不是一个成熟的Java Card模拟器。
4.使用工具包的bin目录下的转换程序把类文件转化成一个Converted Applet转化过的小应用程序,CAP)文件。注意,除类文件之外,另一个输入到这个转换工具中的文件是导出文件,提供了关于你的应用程序导入的(引用)的程序包的信息。这些是还被装载到卡片中的程序包。导出文件还是转换工具的一个输出。 (
5.选择性地,检验CAP的有效性。这一步包括使用verifycap脚本来验证CAP文件的有效性,使用verifyexp来验证导出文件,并且使用verifyrev来检验程序包修正之间的二进制兼容性。工具verifycap、verifyexp和verifyrev脚本全部都可在bin目录中得到。
6.安装CAP文件。使用scriptgen工具转换CAP文件为一个(安装)APDU脚本文件。然后使用apdutool发送脚本文件(安装APDU命令和CAP文件)到Java Card设备上的C-JCRE或者一个JCRE。JCRE保存CAP文件到卡片的内存中。
下面的图总结了这些步骤。注意每个Java Card供应商提供它自己的工具,但是这些用于开发一个Java Card小应用程序的步骤在开发工具箱之间通常是相同的:
Figure 2. Java Card Development Steps(click for larger image)
使用
JavaCard RMI
接口
这是一个以对象为中心的模型,根据这个模型你在上一节看到的APDU通信和句柄将被抽象化;取而代之的是你处理对象。这简化了编程和集成基于Java Card技术的设备。
在RMI模型中,一个服务器应用程序创建并生成可访问的远程对象,并且一个客户应用程序获得到服务器的远程对象的远程引用,然后调用它们的远程方法。在JCRMI中,Java Card小应用程序是服务器,而主应用程序是客户端。
JavaCard RMI简介
两个程序包提供了JavaCard RMI支持:
1、java.rmi定义了Java 2标准版java.rmi程序包的一个子集。它定义了Remote接口和RemoteException类。除此之外,没有包含其他传统的java.rmi类。
2、Javacard.Framework定义了Java Card小应用程序服务类,包括RMI服务类CardRemoteObject和RMIService。.service
3、Class CardRemoteObject定义了两个方法启动和禁止卡片外的对象的远程访问。类RMIService处理RMI请求(转化输入的命令APDU为远程方法调用)。
编写一个JCRMI应用程序类似于编写一个典型的基于RMI的应用程序:
1.定义远程类的行为为一个接口。
2.编写远程类的服务器实现,和支持类。
3.编写一个使用远程服务的客户机程序和支持类。
注意JCRMI没有改变小应用程序的基本结构或者生命周期,你不久就会看到。
远程接口
创建一个远程服务中的第一步是定义它的可见行为。远程接口定义你的小应用程序提供的服务。和在标准的J2SE RMI中一样,所有的Java Card RMI远程接口必须扩展java.rmi.Remote接口。为了说明,这里有一个远程接口,揭示一个取得保存在卡片里的余额的方法:
import java.rmi.*;
import javacard.framework.*;
public interface MyRemoteInterface extends Remote {
...
public short getBalance() throws RemoteException;
...
// A complete credit card application would also define other
// methods such as credit() and debit() methods.
...
}
|
列表1、远程接口
MyRemoteInterface定义这个远程方法,在我们的示例中一个getBalance ()方法,来取得保存在智能卡中的余额。注意,除了Java Card特定的导入之外,这个远程接口看起来完全象一个标准的RMI远程接口。
服务器实现
下一步是实现服务器的行为。服务器实现包含Java Card小应用程序,在所有的你已经定义的远程接口当中的实现,和任何与你的应用程序相关的特定类。
Java Card小应用程序是JCRMI服务器,并且是主机(客户端)应用程序可用的远程对象的所有者。一个典型的Java Card RMI小应用程序的结构在下面的图表中说明:
Figure 4.
典型的JavaCard RMI应用程序结构
当和明确地处理APDU消息的小应用程序相比,基于JCRMI的小应用程序更像一个对象容器。如图4所示,基于JCRMI的小应用程序有一个或多个远程对象,一个APDU Dispatcher和一个接收APDU并且把它们转化为远程方法调用的RMIService。Java Card远程类可以扩展CardRemoteObject类,自动导出对象,使之可用于远程使用。
JCRMI小应用程序必须扩展javacard.Framework,遵循标准的小应用程序结构,并且定义适当的生命周期方法。它必须安装并且登记本身,并且分配APDU。下面这一代码片断说明一个基于JCRMI的小应用程序的典型结构:.Applet
public class MyApplet extends javacard.framework.Applet {
private Dispatcher disp;
private RemoteService serv;
private Remote myRemoteInterface;
/**
* Construct the applet. Here instantiate the remote
* implementation(s), the APDU Dispatcher, and the
* RMIService. Before returning, reGISter the applet.
*/
public MyApplet () {
// Create the implementation for my applet.
myRemoteInterface = new MyRemoteInterfaceImpl();
// Create a new Dispatcher that can hold a maximum of 1
// service, the RMIService.
disp = new Dispatcher((short)1);
// Create the RMIService
serv = new RMIService(myRemoteInterface);
disp.addService(serv, Dispatcher.PROCESS_COMMAND);
// Complete the registration process
register();
}
...
|
小应用程序创建一个Dispatcher和一个处理输入的JCRMI APDU的RMIService。
...
/**
* Installs the Applet. Creates an instance of MyApplet.
* The JCRE calls this static method during applet
* installation.
* @param bArray install parameter array.
* @param bOffset where install data begins.
* @param bLength install parameter data length.
*/
public static void install(byte[] aid, short s, byte b) {
new MyApplet();
}
|
在JavaCard环境中,JVM的生命周期是物理卡片的生命周期,而不是提供垃圾收集器的所有的Java Card实现的,所以你需要最小化内存分配。在安装时间创建对象,这样内存只被分配给它们一次。
/**
* Called by the JCRE to process an incoming APDU command. An
* applet is expected to perform the action requested and
* return response data, if any.
*
* This JCRMI version of the applet dispatches remote
* invocation APDUs by invoking the Dispatcher.
*
* Upon normal return from this method the JCRE sends the ISO-
* 7816-4-defined success status (90 00) in the APDU response.
* If this method throws an ISOException, the JCRE sends the
* associated reason code as the response status instead.
* @param apdu is the incoming APDU.
* @throw ISOException if the install method fails.
*/
public void process(APDU apdu) throws ISOException {
// Dispatch the incoming command APDU to the RMIService.
disp.process(apdu);
}
|
代码列表13.Java Card RMI小应用程序
小应用程序的process()方法接收一个APDU命令并且把它发送到RMIService,RMIService通过把它转化为一个RMI调用和后续响应处理这条命令。
实现远程对象
实现一个JCRMI远程对象类似于实现标准的J2SE远程对象。主要区别是在JCRMI中你的远程对象有扩展CardRemoteObject的选择(除了实现你的远程接口之外)。 RMI
CardRemoteObject定义两个方法export()和unexport(),分别允许或者禁止从卡外到对象的访问。通过扩展CardRemoteObject,你自动地导出你的远程对象所有的方法。如果你决定不扩展CardRemoteObject,你将要负责通过调用CardRemoteObject.export()导出它们。
import Java.rmi.RemoteException;
import javacard.Framework.service.CardRemoteObject;
import javacard.framework.Util;
import javacard.framework.UserException;
/**
* Provides the implementation for MyRemoteInterface.
*/
public class MyRemoteImpl extends CardRemoteObject implements MyRemoteInterface {
/** The balance. */
private short balance = 0;
/**
* The Constructor invokes the superclass constructor,
* which exports this remote implementation.
*/
public MyRemoteImpl() {
super(); // make this remote object visible
}
/**
* This method returns the balance.
* @return the stored balance.
* @throws RemoteException if a JCRMI exception is
* encountered
*/
public short getBalance() throws RemoteException {
return balance;
}
// Other methods
...
}
|
列表⒕远程对象实现
一个完整的Java Card RMI应用程序的流程
让我们概述一个JCRMI应用程序的流程。客户端(主机)应用程序通过传递RMI APDU到卡上的JCRE来产生RMI调用,依次转送这些APDU到相应的JCRMI小应用程序。这个小应用程序分配接收的APDU到RMIService,依次处理APDU并且转化它为一个RMI调用。一个JCRMI小应用程序的典型流程在下面说明:
Figure 5.
基于JavaCard RMI模型的应用程序流程
简言之,JCRMI提供一个基于APDU的消息传递模型的分布式对象模型机制。JCRMI消息被封装到传送到RMIService的APDU消息中,负责解码APDU命令,并且转化这些命名到方法调用和响应。这允许服务器和客户端通信,来回传送方法信息、参数和返回值。
JavaCard
小应用程序结构
Sun
提供了两个模型用来设计Java应用程序(javacard.Framework):传统的JavaCard API和JavaCard Remote Method Invocation(Java Card远程方法调用,JCRMI)编程接口。我们可以使用其中任何一个来编写Java Card小应用程序,开发Java Card小应用程序是一个两步的过程:.AppletCard
1.定义负责主应用程序和小应用程序之间接口的命令和响应APDU。
2.编写Java Card小应用程序本身
JavaCard小应用程序结构
首先,让我们看一下Java Card小应用程序的结构。
列表1说明了一个典型的JavaCard小应用程序是如何构造的:
import javacard.framework.*
...
public class MyApplet extends Applet {
// Definitions of APDU-related instruction codes
...
MyApplet() {...} // Constructor
// Life-cycle methods
install() {...}
select() {...}
deselect() {...}
process() {...}
// Private methods
...
}
|
列表⒈一个JavaCard小应用程序的结构
一个JavaCard小应用程序通常定义它的APDU相关指令、它的构造器,然后是Java Card小应用程序的生命周期方法:install ()、select ()、deselect ()和process ()。最后,它定义任何合适的私有方法。
定义APDU指令
举例来说,让我们亲身感受一下如何开发经典的Wallet信用卡示例。你可以在Sun Java Card Development工具箱的samples目录下得到这个及其他示例的完整的代码。
我们将开始定义一个APDU命令来查询保存在Java Card设备上的当前余额数。注意,在一个实际信用卡应用程序中,我们还将定义信用并且借记命令。我们将分配我们的Get Balance APDU一个0x80指令类和一个0x30指令。Get Balance APDU不需要任何指令参数或者数据区,并且预期的响应由包含余额的两个字节组成。下一个表格描述Get Balance APDU命令:
表1 - Get Balance APDU命令
Name
|
CLA
|
INS
|
P1
|
P2
|
Lc
|
Data Field
|
Le (size of response)
|
Get
Balance
|
0x80
|
0x30
|
0
|
0
|
N/A
|
N/A
|
2
|
虽然Get Balance命令未定义输入数据,但是有一些命令APDU将定义输入数据。举例来说,让我们定义验证从卡片读取器中传递来的PIN号码的Verify PIN APDU命令。下一个表格定义Verify APDU:
表格2- Verify APDU命令
Name
|
CLA
|
INS
|
P1
|
P2
|
Lc
|
Data Field
|
Le (size of response)
|
Verify PIN
|
0x80
|
0x20
|
0
|
0
|
PIN Len
|
PIN Value
|
N/A
|
注意Le字段,响应的大小是N/A。这是因为没有到Verify PIN的应用程序特定响应;成功或者失败通过响应APDU中的状态字标明。
为了简化APDU过程,javacard.Framework接口定义了许多常数,我们可以用来从process ()方法传送到小应用程序中的输入缓冲器中检索各个的APDU字段:.ISO7816
...
byte cla = buf[ISO7816.OFFSET_CLA];
byte ins = buf[ISO7816.OFFSET_INS];
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
byte lc = buf[ISO7816.OFFSET_LC];
...
// Get APDU data, by copying lc bytes from OFFSET_CDATA, into
// reusable buffer databuf.
Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, databuf, 0, lc);
...
|
列表2、使用ISO-7816-4常数
现在我们将定义用于Get Balance和Verify命令的类(CLA)和指令(INS),Get Balance响应的大小,以及在如果PIN验证失败后的出错返回代码。
...
// MyApplet 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;
// Exception (return code) if PIN verify fails.
final static short SW_PINVERIFY_FAILED = (short)0x6900;
...
|
列表3、小应用程序的APDU定义
接下来,让我们定义小应用程序构造器和生命循环方法。
构造器
定义一个初始化这个对象的状态的私有构造器。这个构造器被从install()方法调用;换句话说,构造器只在小应用程序的生命周期期间被调用:
/**
* Private Constructor.
*/
private MyApplet() {
super();
// ... Allocate all objects needed during the applet's
// lifetime.
ownERPin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
...
// ReGISter this applet instance with the JCRE.
register();
}
|
列表4、小应用程序构造器
在这个示例中,我们使用一个Java,一个描述个人识别号码的对象;这个对象将存在于Java Card小应用程序的一生。回忆一下本文第一部分中的"管理内存和对象",在一个Java Card环境中,数组和基本类型将在对象声明中被声明,而且你应该最小化对象实例,以利于对象重用。在小应用程序生命周期期间,以创建对象一次。做到这点的一个简易的方法是在构造器中创建对象,并且从install()方法中调用这个构造器-- install()本身在小应用程序生命周期中只被调用一次。为了利于再使用,对象应该保持在范围中或者适当的引用中,用于小应用程序的生命周期,并且它们的成员的值在再使用之前适当的重置。因为一个垃圾收集程序并不总是可用,一个应用程序可能从不回收被分配给对象的存储空间。card.Framework.OwnerPIN
install ()方法
JCRE在安装过程期间调用install()。你必须覆盖这个从javacard.framework.Applet类继承来的方法,并且你的install ()方法必须实例化这个小应用程序,如下:
/**
* Installs the Applet. Creates an instance of MyApplet. The
* JCRE calls this static method during applet installation.
* @param bArray install parameter array.
* @param bOffset where install data begins.
* @param bLength install parameter data length.
* @throw ISOException if the install method fails.
*/
public static void install(byte[] bArray, short bOffset, byte bLength)
throws ISOException {
// Instantiate MyApplet
new MyApplet();
...
}
|
列表5、install ()小应用程序生命周期方法
install ()方法必须直接或者间接地调用register ()方法来完成安装;如果这步失败将导致安装失败。在我们的范例中,构造器调用register()。
select()方法
JCRE调用select()来通知已经被选作APDU过程的小应用程序。你不必实现这个方法,除非你想提供会话初始化或者个性化。select()方法必须返回true来指明它即将处理进入的APDU,或者返回false来拒绝选择。javacard.framework.Applet类的默认实现返回true。
/**
* Called by the JCRE to inform this applet that it has been
* selected. Perform any initialization that may be required to
* process APDU commands. This method returns a boolean to
* indicate whether it is ready to accept incoming APDU commands
* via its process() method.
* @return If this method returns false, it indicates to the JCRE
* that this Applet declines to be selected.
*/
public boolean select() {
// Perform any applet-specific session initialization.
return true;
}
|
列表6、select()小应用程序生命周期方法
deselect()
方法
JCRE调用deselect()来通知小应用程序,它已经被取消选定了。你不必实现这个方法,除非你想提供会话清除。Java类的默认实现什么都不做。card.Framework.Applet
/**
* Called by the JCRE to inform this currently selected applet
* it is being deselected on this logical channel. Performs
* the session cleanup.
*/
public void deselect() {
// Perform appropriate cleanup.
ownERPin.reset();
}
|
列表7、deselect()小应用程序生命周期方法
在我们的示例中,我们重置了PIN(个人识别号码)。
process()方法--感受APDU的全过程
一旦一个小应用程序已经被选择,它将准备接收命令APDUs,如在本文第一部分中"Java Card小应用程序的生命周期"描写的。
回想一下被从主机端(客户端)应用程序发送到卡片的APDU命令,如下面的说明:
Figure 3. APDU
指令和响应流程
每次JCRE接收一个APDU命令(通过卡片读取器从主应用程序,或者如果使用Sun Java Card Development工具箱就通过apdutool),它调用小应用程序的process()方法,把输入命令当作一个参数传送给它(APDU命令输入缓冲中的参数)。process()方法然后:
1.摘录APDU CLA和INS字段
2.检索应用程序特定的P1、P2和数据字段
3.处理APDU数据
4.生成并发送一个响应
5.优雅地返回,或者抛出相应的ISO异常
在此时,JCRE发送合适的状态字回到主应用程序,通过读卡器。
列表8显示一个样本process()方法。
/**
* Called by the JCRE to process an incoming APDU command. An
* applet is expected to perform the action requested and return
* response data if any to the terminal.
*
* Upon normal return from this method the JCRE sends the ISO-
* 7816-4-defined success status (90 00) in the APDU response. If
* this method throws an ISOException the JCRE sends the
* associated reason code as the response status instead.
* @param apdu is the incoming APDU.
* @throw ISOException if the process method fails.
*/
public void process(APDU apdu) throws ISOException {
// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();
// Get the CLA; mask out the logical-channel info.
buffer[ISO7816.OFFSET_CLA] =
(byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0xFC);
// If INS is Select, return - no need to process select
// here.
if ((buffer[ISO7816.OFFSET_CLA] == 0) &&
(buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) )
return;
// If unrecognized class, return "unsupported class."
if (buffer[ISO7816.OFFSET_CLA] != MyAPPLET_CLA)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
// Process (application-specific) APDU commands aimed at
// MyApplet.
switch (buffer[ISO7816.OFFSET_INS]) {
case VERIFY_INS:
verify(apdu);
break;
case GET_BALANCE_INS:
getBalance(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
}
}
|
列表8、process()小应用程序生命周期方法
我们的process()方法调用getBalance()和verify()方法。列表9显示getBalance ()方法,处理get balance APDU并且返回保存在卡片中的余额。
/**
* Retrieves and returns the balance stored in this card.
* @param apdu is the incoming APDU.
*/
private void getBalance(APDU apdu) {
// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();
// Set the data transfer direction to outbound and obtain
// the expected length of response (Le).
short le = apdu.setOutgoing();
// If the expected size is incorrect, send a wrong-length
// status word.
if (le != GET_BALANCE_RESPONSE_SZ)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
// Set the actual number of bytes in the response data field.
apdu.setOutgoingLength((byte)GET_BALANCE_RESPONSE_SZ);
// Set the response data field; split the balance into 2
// separate bytes.
buffer[0] = (byte)(balance >> 8);
buffer[1] = (byte)(balance & 0xFF);
// Send the 2-byte balance starting at the offset in the APDU
// buffer.
apdu.sendBytes((short)0, (short)GET_BALANCE_RESPONSE_SZ);
}
|
列表9、处理Get Balance APDU
getBalance ()方法通过调用APDU.getBuffer ()方法取得一个引用到APDU缓冲。在返回响应(当前余额)之前,小应用程序必须设置JCRE模式通过调用APDU.setOutgoing()方法来发送,方便地返回期望的响应大小。我们还必须设置响应数据字段中的字节的实际数字,通过调用APDU.setOutgoingLenth()。APDU缓冲中的响应事实上通过调用APDU.sendBytes ()发送。
小应用程序不直接发送返回码(状态字);一旦小应用程序调用APDU.setOutgoing ()并且提供任何请求的信息,JCRE注意这个状态字。状态字的值依靠process()方法如何使返回到JCRE来变化。如果所有的已经正常运行,JCRE将返回9000,指明无错。你的小应用程序可以通过抛出一个定义在ISO7816接口中的异常返回一个错误代码。 在列表9中,如果期望响应的大小不正确,方法getBalance()抛出一个ISO7816.SW_WRONG_LENGTH代码。对于有效的状态码值,请参阅ISO7816接口的定义,或者回到本文第一部分的"响应APDU"。
现在让我们看看在列表10中的verify()方法。因为我们定义的验证PIN APDU命令包含数据,verify()方法必须调用APDU.setIncomingAndReceive ()方法,设置JCRE为接收模式,然后接收输入数据。
/**
* Validates (verifies) the Owner's PIN number.
* @param apdu is the incoming APDU.
*/
private void verify(APDU apdu) {
// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();
// Get the PIN data.
byte bytesRead = (byte)apdu.setIncomingAndReceive();
// Check/verify the PIN number. Read bytesRead number of PIN
// bytes into the APDU buffer at the offset
// ISO7816.OFFSET_CDATA.
if (ownERPin.check(buffer, ISO7816.OFFSET_CDATA, byteRead)
== false )
ISOException.throwIt(SW_PINVERIFY_FAILED);
}
|
列表10、处理验证APDU
这个方法通过调用APDU.getBuffer()取得一个到APDU缓冲的引用,调用APDU.setIncomingAndReceive()来接收命令数据,从输入的APDU缓冲中取得PIN数据,并且验证PIN。一个验证失败导致状态码6900被发送回主应用程序。
有时输入的数据比填充到APDU缓冲中的数据要多,并且小应用程序必须大块的读取数据知道没有数据可以读取。在此情况下,我们必须首先调用APDU.setIncomingAndReceive(),然后重复地调用APDU.receiveBytes(),直到不再有数据可用。列表11显示如何读取大量输入数据。
...
byte[] buffer = apdu.getBuffer();
short bytes_left = (short) buffer[ISO.OFFSET_LC];
short readCount = apdu.setIncomingAndReceive();
while (bytes_left > 0) {
// Process received data in buffer; copy chunk to temp buf.
Util.arrayCopy(buffer, ISO.OFFSET_CDATA, tbuf, 0, readCount);
bytes_left -= readCount;
// Get more data
readCount = apdu.receiveBytes(ISO.OFFSET_CDDATA);
}
...
|
列表11、读取大量输入数据
由于每个大块被读取,小应用程序可以把它添加到另一个缓冲中,否则仅仅处理它。