JAVA card

更新时间:2023-10-11 19:05:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

JavaCard开发教程之智能卡简介

智能卡不是新鲜事物。它们在二十年前在欧洲就以记忆卡片的形式推出了,用于保存关键的电话信息,以减少盗打付费电话的可能。

智能卡技术是ISO国际标准组织的连接技术委员会1(JTC1)和国际电子委员会(IEC)定义并控制的一种行业标准。1987年推出的ISO/IEC 7816国际标准系列在2003年推出了它的最新的升级版本,定义了智能卡的各个方面,包括物理特征、物理接触界面、电子信号和传输协议、命令、安全体系、应用程序标识符和公用数据元素等。

智能卡是一个包含嵌入集成电路(IC)的塑料卡片,类似于一张信用卡。当用作SIM卡时,这个塑料卡片很小,正好能放入手机中。智能卡设计时就极注重高度安全性,窜改一点点内容都会导致毁坏它包含的信息。

在智能卡使用的某些领域,它们只是仅仅提供受保护的非易失性存储。更高级的智能卡还有微处理器和内存,用于安全的处理和储存,并且可以用于使用公共密钥或者共享密钥算法的安全应用程序。智能卡上的非易失性存储是最宝贵的资源,可用于保存秘钥和数字证书。一些智能卡有单独的加密协处理器,支持象RSA、AEC和(3)DES这样的算法。

智能卡不包含电池,只有在和读卡机连接的时候才被激活。当它被连接时,在执行一个复位序列之后,卡片处于非激活状态,等待接收来自客户端(主机)应用程序的命令请求。

智能卡可以分为可接触和非可接触。可接触智能卡通过读卡器和智能卡的8个触点物理接触来通讯并工作,而非可接触智能卡依靠在小于2英尺的一般距离之内的射频信号通讯。非接触智能卡的射频通信基于类似于用于保存反盗窃和记录清单的射频标识符( RFID)标记的技术。图1描述了可接触和非可接触智能卡:

图1a.接触式智能卡

图1b.非接触式智能卡

Java Card技术还存在除了智能卡之外的其它的形态,例如智能按钮和USB令牌,这两种如图2所示。这些的功能和智能卡差不多,例如用于验证用户或者传送敏感信息。智能按钮包含一块电池而且是基于可接触模式,而USB令牌则可以直接插入个人计算机的USB端口,而不需要任何可接触或者非可接触读卡器。这两种类型的Java Card具有与智能卡相同的编程能力并且具有防篡改能力。

图2a. 带有Java功能的智能纽扣

图2b. 带有Java功能的USB 令牌

请参阅What is a Smart Card?http://java.sun.com/products/javacard/smartcards.html获取更详细的信息。

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 Card应用程序由一个后端应用程序和系统、一个主机(卡外)应用程序、一个接口设备(读卡器)和卡上小应用程序、用户证书和支持软件组成。所有的这些元素共同组成一个安全的端到端应用程序:

图 1. Java Card应用程序的体系结构

一个典型的Java Card应用程序不是孤立的,而是包含卡端、读取端和后端元素。让我们更详细的讲述一下每个元素。

后端应用程序和系统

后端应用程序提供了支持卡上Java小应用程序的服务。 例如,一个后端应用程序可以提供到安全系统和卡上的证书的连接,提供强大的安全性。在一个电子付款系统中,后端应用程序可以提供到信用卡及其他付款信息的访问。

读取端主应用程序

主应用程序存在于一个例如个人计算机这样的台式机或者终端、电子付款终端、手机或者一个安全子系统中。

主应用程序处理用户、Java Card小应用程序和供应商的后端应用程序之间的通讯。

传统的读取端应用程序是使用C编写的。近来J2ME技术的广泛普及有望使用Java实现主应用程序;例如,它可以在一台支持MIDP和安全信赖服务应用编程接口(Security and Trust Services API)手机上运行。

智能卡供应商一般不仅提供开发工具箱,而且提供支持读取端应用程序和Java Card小应用程序的应用程序编程接口。例如OpenCard Framework http://www.opencard.org/,就是一个基于Java的应用程序编程接口集,隐藏了来自不同供应商的读取器的一些细节,并且

提供了Java Card远程方法调用分布式对象模型和安全信任服务应用编程接口(SATSA),我在本文后面一部分讨论它们。

读取端卡片接受设备

卡片接受设备(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 RMI分布式对象模型的一个子集。此外,SATSA通过一个基于更加抽象的应用编程接口的普通连接框架(Generic Connection Framework,简称GCF)应用编程接口,让你要么使用消息传递要么使用JCRMI来访问智能卡。

JavaCard开发教程之消息模型

图1中说明的消息传递模型是所有Java Card通信的基础。它的核心就是应用程序协议数据单元(APDU),CAD和Java Card框架之间交换的一个逻辑数据包。JavaCard框架接收任何CAD发送进来的APDU命令并且传送到相应的小应用程序中。小应用程序处理APDU命令,然后返回一个响应APDU。那些APDU遵守国际标准规格ISO/IEC 7816 - 3和7816 - 4。

图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 20 to 0x7F 指令类 ISO 7816 - 4卡指令,比如文件存取和安全操作 保留 0x8n or 0x9n 你可以用作你的特定的应用程序指令的ISO/IEC 7816 - 4格式,根据标准解释' X ' 0xAn B0 to CF D0 to FE FF 特定的应用程序或者供应商的指令 你可以用作特定应用程序的ISO/IEC 7816 - 4格式 特定的应用程序或者供应商的指令 保留给协议类型选择

· 理论上,你可以使用所有的CLA值0x80或者更高值来用于特定应用程序指令,但是在许多现在的Java Card实现中,只有黑体显示的是实际认可的。

· INS(1字节):这个必需的字段指明CLA字段中标示的指令类中的一个特定指令。ISO 7816 - 4标准指定用于访问卡上的数据的基本指令,当它根据在像标准中定义的卡上的文件系统那样结构化的时候。附加功能已经在这个标准中的其它地方说明,其中一些是安全功能。表2中是一个ISO 7816指令的列表。只有当使用一个相应的CLA字节值时,你才可以根据标准定义你自己的特定应用程序的INS值,。

表格2、当 CLA = 0x时的ISO 7816 - 4 INS值

INS 值 0E 20 70 82 84 88 A4 B0 B2 C0 C2 CA D0 D2 D6 DA DC E2 命令描述 Erase Binary Verify Manage Channel External Authenticate Get Challenge Internal Authenticate Select File Read Binary Read Record(s) Get Response Envelope Get Data Write Binary Write Record Update Binary Put Data Update Record 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开发教程之虚拟机

Java Card虚拟机(JCVM)规范定义了Java程序设计语言的一个子集和一个用于智能卡的兼容Java的虚拟机,包括二进制数据表示和文件格式,以及JCVM指令集。

用于Java Card平台的虚拟机是两部分实现,一部分在卡外,一部分运行在卡本身。卡上的Java Card虚拟机解释字节码、管理类和对象等等。外部Java虚拟机部分是一个开发工具,一般称为Java Card转换工具,装载、检验和进一步地准备卡片小应用程序Java类,用于在卡上执行。转换工具输出的是一个Converted Applet(CAP)文件,这是一个包含一个Java程序包中所有类的文件。转换程序检验类是否遵循Java Card规范。

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)格式,这将后面的文章中讲述。

JavaCard开发教程之生存周期

卡片上的每个小应用程序由一个Application标识符(AID)唯一标识。 定义在ISO 7816 - 5中的AID是一段5到16字节之间的序列。 所有的小应用程序必须扩展Applet抽象基本类,这个类定义了JCRE使用的方法来控制小应用程序的生存周期,如图10概括:

图表1. Java Card小应用程序生命周期方法

小应用程序生存周期在小应用程序被下载到卡片中并且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小应用程序简介

当创建一个Java Card应用程序的时候的典型的步骤是:

1、编写Java源代码。

2、编译你的源代码。

3、把类文件改变为一个Converted Applet(CAP)文件。

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 JavaCard 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)

Sun提供了两个模型用来设计JavaCard应用程序(javacard.framework.Applet):传统的JavaCard API和JavaCard Remote Method Invocation(Java Card远程方法调用,JCRMI)编程接口。我们可以使用其中任何一个来编写Java Card小应用程序,开发Java Card小应用程序是一个两步的过程:

1.定义负责主应用程序和小应用程序之间接口的命令和响应APDU。 2.编写Java Card小应用程序本身 JavaCard小应用程序结构

首先,让我们看一下Java Card小应用程序的结构。

列表1说明了一个典型的JavaCard小应用程序是如何构造的:

import javacard.framework.* ... public class MyApplet extends Applet { // Definitions of APDU-related instruction codes ...

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(\ }

// 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个十六进制字节值得字符串;例如,\。 使用一个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 = \

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 = \ JavaCardRMIConnection jc = (JavaCardRMIConnection) Connector.open(url);

MyApplet() {...} // Constructor // Life-cycle methods install() {...} select() {...} deselect() {...} process() {...} // Private methods ... } 列表⒈一个JavaCard小应用程序的结构

一个JavaCard小应用程序通常定义它的APDU相关指令、它的构造器,然后是Java Card小应用程序的生命周期方法:install ()、select ()、deselect ()和process ()。最后,它定义任何合适的私有方法。 定义APDU指令

不同的Java Card应用程序有不同的接口(APDU)需求。一个信用卡小应用程序可能支持验证PIN号码的方法,产生信用和借记事务,并且核对帐目余额。一个健康保险小应用程序可能提供访问健康保险信息、保险总额限制、医生、病人信息等等信息的权限。你定义的精确的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 Get Balance CLA INS P1 P2 Lc Data Field Le (size of response) 2 0x80 0x30 0 0 N/A N/A

虽然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.ISO7816接口定义了许多常数,我们可以用来从process ()方法传送到小应用程序中的输入缓冲器中检索各个的APDU字段:

... 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、小应用程序构造器

在这个示例中,我们使用一个javacard.framework.OwnerPIN,一个描述个人识别号码的对象;这个对象将存在于Java Card小应用程序的一生。回忆一下本文第一部分中的\管理内存和对象\,在一个Java Card环境中,数组和基本类型将在对象声明中被声明,而且你应该最小化对象实例,以利于对象重用。在小应用程序生命周期期间,以创建对象一次。做到这点的一个简易的方法是在构造器中创建对象,并且从install()方法中调用这个构造器-- install()本身在小应用程序生命周期中只被调用一次。为了利于再使用,对象应该保持在范围中或者适当的引用中,用于小应用程序的生命周期,并且它们的成员的值在再使用之前适当的重置。因为一个垃圾收集程序并不总是可用,一个应用程序可能从不回收被分配给对象的存储空间。

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()来通知小应用程序,它已经被取消选定了。你不必实现这个方法,除非你想提供会话清除。javacard.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,如在本文第一部分中\小应用程序的生命周期\描写的。

回想一下被从主机端(客户端)应用程序发送到卡片的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 \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、读取大量输入数据

由于每个大块被读取,小应用程序可以把它添加到另一个缓冲中,否则仅仅处理它。

使用JavaCard RMI接口

Java Card小应用程序编程的第二个模型是JavaCard RMI ( JCRMI),严格的说它是J2SE 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.service定义了Java Card小应用程序服务类,包括RMI服务类CardRemoteObject和RMIService。

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小应用程序

Java Card小应用程序是JCRMI服务器,并且是主机(客户端)应用程序可用的远程对象的所有者。一个典型的Java Card RMI小应用程序的结构在下面的图表中说明:

Figure 4. 典型的JavaCard RMI应用程序结构

当和明确地处理APDU消息的小应用程序相比,基于JCRMI的小应用程序更像一个对象容器。如图4所示,基于JCRMI的小应用程序有一个或多个远程对象,一个APDU Dispatcher和一个接收APDU并且把它们转化为远程方法调用的RMIService。Java Card远程类可以扩展CardRemoteObject类,自动导出对象,使之可用于远程使用。

JCRMI小应用程序必须扩展javacard.framework.Applet,遵循标准的小应用程序结构,并且定义适当的生命周期方法。它必须安装并且登记本身,并且分配APDU。下面这一代码片断说明一个基于JCRMI的小应用程序的典型结构:

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 RMI远程对象。主要区别是在

JCRMI中你的远程对象有扩展CardRemoteObject的选择(除了实现你的远程接口之外)。

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主应用程序开发教程

在这一部分中我们将主要介绍:

主应用程序的开发,以及一些可用的Java应用程序编程接口:OpenCard Framework、Java Card RMI Client编程接口和用于J2ME的Security and Trust Services编程接口( SATSA)。

JavaCard主应用程序开发教程

[导读]本文是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应用程序的组成元素

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(\ } } 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路径标明到远程类的路径。

本文来源:https://www.bwwdw.com/article/4xnf.html

Top