Java-Card-技术简介

Java Card 技术简介:第 1 部分

许多关于无线 Java 站点的文章都以 J2ME 平台为重点。本系列文章(共分为两部分)将介绍另一种重要的移动 Java 技术:支持智能卡编程的 Java Card 。

  由于这些可移植技术具有非常强的专用性,因此本系列文章涵盖了相当广泛的内容。本系列文章的第一部分将介绍智能卡、Java Card 技术和 Java Card 小应用程序(applet)元素。第二部分将介绍 Java Card 技术的开发部分。

  简介

  Java Card 技术适用于 Java 平台,可应用于环境高度专用化、内存和处理约束比 J2ME 设备更苛刻的智能卡和其他设备。

  智能卡在个人安全领域发挥着举足轻重的作用。它们可以用于添加身份验证,并对安全级别很高的信息系统提供安全访问。存储在智能卡中的信息是可移植的。借助 Java Card 技术,您可以携带有价值且敏感的个人信息,例如病历、信用卡号或者存储在压缩但非常安全的介质中的电子现金余额。

  什么是智能卡?

  智能卡不是什么新鲜事物。早在 20 年前,欧洲就以(非智能形式)内存卡的形式引入了智能卡的概念,使用它保存重要的电话信息,其作用是减少盗打付费电话的可能。

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

  智能卡是一个含有嵌入式集成电路(IC)的塑料卡片。类似于一张信用卡。当用作 SIM 卡时,这个塑料卡片很小,但大小刚好能插入手机中。智能卡从设计上保证高度安全性,窜改一点点内容都会导致毁坏它所包含的信息。

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

  智能卡不含电池,只有在和智能卡读取器相连时才被激活。当被连接时,在执行完一段复位序列后,智能卡仍保持被动状态,等待接受从客户机(主机)应用程序发来的命令请求。

  智能卡可以是 接触式的 或者 非接触式的。正如其名称所暗示的,接触式智能卡通过介于智能卡读取器与智能卡 8 触点之间的物理接触进行通信并工作;而非可接触式智能卡依靠在小于 2 英尺的一般距离之内的射频信号进行通信。非接触式智能卡的射频通信基于类似于用于保存反盗窃和记录清单的 无线射频识别(Radio Frequency ID,RFID)标记 技术。图 1 描述了接触式和非接触式智能卡:

图 1a.接触式智能卡

图 1b.非接触式智能卡

  Java Card 技术也存在不同于智能卡的外形规格,例如智能按钮和 USB 令牌(如图 2 所示)。它们可以同智能卡一样验证用户或传送敏感信息。智能按钮包括一块电池而且是基于可接触模式,而 USB 令牌则可以直接插入到个人计算机的 USB 端口,而无需使用接触式或非接触式读取器。这两种类型的 Java Card 均提供与智能卡相同的编程功能,并具有防篡改特性。

图 2a.带有 Java 功能的智能按钮

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

Java Card 规范

  多年以前,Sun Microsystem 就实现了智能卡和类似的资源约束设备的潜能,并为 Java 技术的子集定义一套规范,以便为 Java Card applet 创建应用程序。支持这些规范的设备简称 Java Card 平台。在 Java Card 平台上,来自不同供应商的多个应用程序可以安全地共存。

  一台典型的 Java Card 设备有一个运行于 3.7MHz 的 8 位或 16 位 CPU ,带有 1K 的 RAM 和多于 16K 的非易失内存(EEPROM 或闪存)。高性能的智能卡带有单独的处理器、加密芯片和内存加密,某些智能卡还带有 32 位 CPU。

  Java Card 技术规范的最新版本为 2.2,由三部分组成:

  Java Card 虚拟机规范,定义了用于智能卡的 Java 程序编程语言的一个子集和虚拟机。 

  Java Card 运行时环境规范,详细定义了基于 Java 的智能卡的运行时行为。 

  Java Card API 规范,定义了用于智能卡应用程序的核心框架和扩展 Java 软件包和类。 

  Sun 还提供了 Java Card 开发工具(JCDK),其中包括 Java Card RE 和 Java Card VM 的参考实现以及其他帮助开发的 Java Card applet 。本文第二部分将介绍详细 JCDK。

  Java Card 技术和 J2ME 平台

  让我们比较一下 Java Card 和 J2ME 平台技术:

图 3 Java Card 技术和 J2ME 平台

  CDC 和 CLDC 配置以及相关配置文件都是 J2ME 平台的组成部分,而 Java Card 是一个为智能卡环境而专门创建的单独平台专门。

  Java Card 应用程序的元素

  完整的 Java Card 应用程序由一个后端应用程序、系统、一个主机(卡片外部)应用程序、一个接口设备(卡片读取器)和卡片内部 applet、用户证书和支持软件组成。所有这些元素一起构成了一个安全的端到端应用程序:

图 4. Java Card 应用程序的架构

  一个典型的 Java Card 应用程序并不是独立的,而是包含卡片端、读取器端和后端元素。下面详细介绍一下每个元素。

  后端应用程序和系统

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

  读取器端主机应用程序

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

  主机应用程序处理用户、Java Card applet 和提供器的后端应用程序之间的通信。

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

  智能卡供应商通常不仅提供开发工具箱,还提供 API 以便支持读取器端应用程序以及 Java Card applet。这方面的示例包括基于 Java 的 API 集合的 OpenCard Framework,其中隐藏了与不同卡片供应商提供的卡片读取器进行交互的一些细节,还有两个将在本文后边讨论的 Java Card 远程方法调用(Java Card Remote Method Invocation,JCRMI)分布式对象模型和安全性和信任服务应用编程接口(SATSA)。

  读取器端卡片接入设备

  卡片接入设备(Card Acceptance Device,CAD)是一个位于主机应用程序和 Java Card 设备之间的接口设备。CAD 为卡片提供电源以及与之进行的电子的或 RF 通信。CAD 可能是使用串行端口与台式计算机相连的卡片读取器,也可能被集成为一个终端。例如饭店或者加油站内的电子付款终端。接口设备从主机应用程向卡片转发应用协议数据单元(Application Protocol Data Unit,APDU) 命令(稍后加以讨论),再将卡片发出的响应传递给主机应用程序。一些 CAD 有用于输入 PIN 入口点的键盘,有的可能还有显示屏。

  卡片端 Applet 和环境

  Java Card 平台是一个多应用程序环境。如图 4 所示,卡片上可能存在一个或多个 Java Card applet,与支持软件 —— 卡片操作系统和 Java Card 运行时环境(JCRE)一起存储在卡片上。JCRE 由 Java Card VM、Java Card Framework 和 API 以及一些扩展 API 组成。

  所有的 Java Card applet 扩展 Applet 基类,且必须实现 install() 和 process() 方法;当 JCRE 安装 applet 时,调用 install(),每当为 applet 传入 APDU 时,JCRE 都会调用 process()。

  当 Java Card applet 被加载时进行实例化,并且电源被切断时仍保持激活状态。卡片 applet 起到服务器的作用,是被动的。当卡片接上电源,每个 applet 一直处于未激活状态直到进行初始化时被选中。当向 applet 发送 APDU 时它才被激活。Applet 如何变为激活状态(被选中)的详细内容在 Java Card Applet 的生命周期 一节中进行了描述。

与 Java Card Applet 之间的通信(访问智能卡)

  您可以使用两种模式中的任意一种完成主机应用程序和 Java Card applet 之间的通信。第一种模式是基本的消息传递模式,第二种模式基于 Java 远程方法调用(Java RMI)和 J2SE RMI 分布式对象模型的子集。此外,通过基于通用连接框架(Generic Connection Framework,GCF)API 的一个更抽象的 API,SATSA 使您能够使用消息传递或 JCRMI 访问智能卡。

  消息传递模型

  在图 5 中说明的消息传递模型为所有 Java Card 通信提供基础。其核心是与 CAD 和 Java Card Framework 之间交换的逻辑数据包 —— 应用协议数据单元(APDU)。Java Card Framework 接收任何由 CAD 发送的 APDU 并 转发给适当的 applet。Applet 处理 APDU 命令 并返回一个 APDU 响应。APDU 遵循国际标准 ISO/IEC 7816-3 和 7816-4。

图 5 使用消息传递模型进行通信

  读取器和卡片之间的通信通常建立在两项链接协议基础上,面向字节的 T=0 或面向块的 T=1。可选协议简称 T=USB,且可能会采用 T=RF。JCRE APDU 类隐藏了来自应用程序的协议细节,但并非全部,因为 T=0 协议相当复杂。

  1. APDU 命令 

  APDU 命令的结构由第一个字节的值控制,大多数情况下,类似于:

图 6. APDU 命令

  APDU 命令有一个必须有的标题和可选的主体,其中包括:

  CLA (1 字节):这个必需的字段识别指令的一个特定于应用程序类。有效的 CLA 值在 ISO 7816-4 规范中做了界定: 

  表 1. ISO 7816 CLA 值

 CLA 值

 指令类

 0x0n,0x1n

 ISO 7816-4 卡片指令,例如用于文件存取和安全操作

 20 to 0x7F

 保留

 0x8n 或者 0x9n

 您可以使用特定于应用程序指令的 ISO/IEC 7816-4 格式,根据标准解释 'X'。

 0xAn

 特定于应用程序的指令或供应商指定的指令

 从 B0 到 CF

 您可以使用特定于应用程序指令的 ISO/IEC 7816-4 格式

 从 D0 到 FE

 特定于应用程序的指令或供应商指定的指令

 FF

 为协议类型选项所做的保留

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

  INS (1 字节):这个必需的字段说明在指令类中由 CLA 字段识别的特定指令。ISO 7816-4 标准指定了当卡片根据标准所定义的卡片内部文件系统接收到指令时,用于访问卡片内部数据的基本指令。在标准中的其它地方还指定了一些附加功能,其中包括安全功能。要了解 ISO 7816 部分指令的列表,参见表 2。只有当使用适当的 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

 附加记录

  P1 (1 字节):这个必需的字段定义指令参数 1。可以使用该字段限定 INS 字段,或者用于输入数据。 

  P2 (1 字节):这个必需的字段定义指令参数 2。可以使用该字段限定 INS 字段,或者用于输入数据。 

  Lc (1 字节):这个可选字段是命令的数据字段中的字节数。 

  Data 字段(变量,字节的 Lc 数):这个可选字段保存命令数据。 

  Le (1 字节):这个可选字段指定在期望响应的数据字段中的字节最大数。 

  根据所出现的命令数据和是否需要响应,为 APDU 命令配置了四个变量。只有使用协议 T=0 的情况下才有必要关注这些变量。

图 7. APDU 命令的四种可能结构

  典型的应用程序使用带有不同结构的各种 APDU 命令。

2. APDU 响应 

  APDU 响应的格式很简单的:

图 8 APDU 响应

  响应 APDU 与命令 APDU 类似,都具有可供选择的必需字段:

  Data 字段(变量长度,由 APDU 命令中的 Le 确定):该可选字段包含由 applet 所返回的数据。 

  SW1 (1 字节):这个必需的字段是状态字 1。 

  SW2 (1 字节):这个必需的字段是状态字 2。 

  在 ISO 7816-4 规范中定义了状态字的值:

图 9. 响应状态代码

  在 Java Card Framework API 中的 ISO7816 Java 接口中定义了许多常量以便实施返回错误代码的规范。

  3. 处理 APDU 

  每当为所选的 applet 提供传入的 APDU 时,JCRE 都会调用 applet 的 process() 方法,把传入的 APDU 作为参数来传递。Applet 必须解析 APDU 命令,处理数据,生成响应 APDU,然后把控制权返回给JCRE。

  您会在本文的第二部分“ process() 方法 —— 与 APDU 协同工作”中找到关于 APDU 命令和响应的更多信息。

  Java Card RMI(JCRMI)

  第二种通信模型基于 J2SE RMI 分布式对象模型的子集。

  在 RMI 模型中,服务器应用程序创建并访问远程对象,且客户机应用程序获得对远程对象的远程引用,然后为这些对象调用远程方法。在 JCRMI 中,Java Card applet 为服务器,主机应用程序为客户机。

  在扩展软件包 javacardx.rmi 中由类 RMIService 提供 JCRMI。JCRMI 消息被封装到传入的 RMIService 方法的 APDU 对象中。换句话说,JCRMI 提供了一个 熟练运用基于 APDU 消息传递模型 的分布式对象模型的机制,借助该机制,服务器和客户机进行通信,往复传递方法信息、参数、并返回值。

  安全性和信任服务应用编程接口(SATSA)

  在 JSR 177 中所定义的 SATSA 指定了为 J2ME 提供安全性和信任服务 API 的可选软件包。该 客户机 API 提供了对各种服务的访问,这些服务由安全存储、敏感信息的检索以及加密和身份验证服务等 安全元素 (例如智能卡)提供。

  SATSA 采用有限连接设备配置(Connected Limited Device Configuration,CLDC)的版本 1.0 中所定义的通用连接框架(Generic Connection Framework,GCF) 以便向消息传递和 JCRMI 通信模型提供更抽象的接口。为了支持消息传递,SATSA 定义了 apdu:URL 方案和 APDUConnection,为了支持 JCRMI,它还定义了 jcrmi:方案和 JavaCardRMIConnection。

  SATSA 由下列软件包组成:

  java.rmi 定义了 Java 2 标准版 java.rmi 软件包的子集,以及专门定义的 Remote 和 RemoteException。 

  javacard.framework 定义了远程方法可能引发的标准 Java Card API 异常:CardRuntimeException、ISOException、APDUException、CardException、PINException、SystemException、TransactionException 和 UserException。 

  javacard.framework.service 定义了远程方法可能引发的标准 Java Card API 服务 异常:ServiceException。 

  javacard.security —— 定义了远程方法会引发的标准 Java Card API 加密相关 异常。CryptoException。 

  javax.microedition.io 定义了两种连接接口,用于基于 APDU 协议而进行智能卡访问的 APDUConnection 和用于 Java Card RMI 协议的 JavaCardRMIConnection。 

  javax.microedition.jcrmi 定义了桩模块所使用的类和接口,该桩模块由 Java Card RMI 桩模块编译器生成。 

  javax.microedition.pki 定义了用于用户证书基础管理的类。 

  javax.microedition.securityservice 定义了生成应用程序级数字签名的类。 

Java Card VM

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

  用于 Java Card 平台的 VM 分两部分实现,一部分在卡片外部,另一部分在卡片上运行。卡片内部的 Java Card VM 解释字节码、管理类和对象等等。外部 Java VM 部分是一个开发工具,通常简称 Java Card 转换工具 ,加载、验证进而为卡片内部指令执行进一步准备卡片 applet 中的 Java 类。转换工具的输出是一个 Converted Applet(CAP)文件,保护所有可加载、可执行的二进制表示中的 Java 软件包。转换程序验证类是否遵循 Java Card 规范。

  JCVM 只支持 Java 编程语言的有限子集,但是保留了许多熟悉的特性,包括对象、继承、软件包、动态对象创建、虚拟方法、接口和异常。JCVM 规范放弃支持使用过多智能卡有限内存的语言元素。

  表 3. Java Card 语言限制汇总

 语言特性

 不支持动态类加载、安全管理器(java.lang.SecurityManager)、线程、对象克隆和软件包访问控制的某些方面。

 关键词

 不支持 native、synchronized、transient、volatile、strictfp。

 类型

 不支持 char, double, float 和 long 或多维数组。对 int 的支持是可选的。

 类和接口

 除 Object 和 Throwable 之外,不支持 Java 核心 API 类和接口(java.io, java.lang, java.util),绝大多数 Object 和 Throwable 方法不可用。

 异常

 一些 Exception 和 Error 子类被忽略,因为这些封装的异常和错误在 Java Card 平台中不出现。

  还存在编程模型的限制。例如,一个被加载的库类再也不会在卡片中被扩展;它被隐式地 结束。

  按照内存约束,JCVM 规范另外定义了许多程序属性的约束。表 4 总结 JCVM 资源约束。请注意许多约束通常对 Java Card 开发人员通常是透明的。

  表 4. Java Card VM 约束的摘要信息

 软件包

 一个软件包可以引用多达 128 个其他的软件包。

 完全合格的软件包名称限定为 255 字节。请注意字符大小取决于字符编码。

 一个软件包能够具备多至 255 个类。

 类

 一个类可以直接或间接实现多达 15 个接口。

 一个接口可以从多达 14 个接口继承。

 如果一个软件包含有 applet(一个 applet 软件包)它可以有多达 256 个静态方法,如果包不含有 applet(库软件包),它可以有 255 个静态方法。

 一个类可以实现 128 个公共的或受保护的实例方法,以及带有软件包可见性的 128 个公共的或受保护的实例方法。

  同 J2SE VM 中的类文件一样,在 Java Card VM 中的 类文件 是核心,但是 JCVM 规范定义了深化平台独立性的两个其他文件格式 Converted Applet (CAP) 和 导出 格式,二者在本系列文章第二部分的“开发 Java Card 应用程序”一节中加以描述。

  Java Card API

  Java Card API 规范定义了传统 Java 编程语言 API 的小型子集 —— 甚至比 J2ME 的 CLDC 子集还要小。它不支持 Strings 或多线程。在这个子集中存在像 Boolean 和 Integer 这样的包装类,不存在 Class 或 System 类。

  除了熟悉的 Java 核心类的小型子集外,Java Card Framework 定义了自己的核心类子集以便专门支持 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 Framework 的接口、类和异常。它还定义了像个人识别号码(Personal Identification Number,PIN)、应用程序数据单元(Application Protocol Data Unit,APDU)、Java Card applet(Applet)、Java Card System(JCSystem)和实用类这样的重要概念。它还定义了各种 ISO7816 常量和各种特定于 Java Card 的异常。表 5 总结了这个软件包的内容: 

  表 5. Java Card v2.2 javacard.framework

 接口

 ISO7816 定义了与 ISO 7816-3 和 ISO 7816-4 相关的常量。

 MultiSelectable 识别可以支持并发选项的 applet。

 PIN 表示用于安全目的(身份验证)的个人识别号码。

 Shareable 识别共享对象。那些能通过 applet 防火墙的对象必须实现这个接口。

 类

 AID 定义了遵循 ISO7816-5 标准并且与应用程序提供器相关的应用程序标识符,还定义了 applet 的强制属性。

 APDU 定义了遵循 ISO7816-4 标准的应用程序协议数据单元(APDU),它是 applet(卡片内部)和主机应用程序(卡片外部)之间使用的通信格式。

 Applet 定义了一个 Java Card 应用程序。所有的 applet 必需扩展这个抽象类。

 JCSystem 提供了控制 applet 生命周期、资源和事务管理以及 applet 内部对象共享和对象删除的方法。

 OwnerPIN 是 PIN 接口的一个实现。

 Util 提供了数组和短型变量操作的使用方法,包括 arrayCompare()、arrayCopy()、arrayCopyNonAtomic()、 arrayFillNonAtomic()、getShort()、makeShort() 和 setShort()。

 异常

 以下为所定义的 Java Card VM 异常类:APDUException、CardException、CardRuntimeException、ISOException、PINException、SystemException、TransactionException 和 UserException。

  javacard.framework.service 定义了用于各种 服务 的接口、类和异常。服务处理以 APDU 的格式传入的命令。表 6 总结了框架服务 API: 

  表 6. javacard.framework.service

 接口

 Service 是基本的服务接口,定义 processCommand()、 processDataIn() 和 processDataOut() 等方法。

 RemoteService 是一个通用 服务,给出对卡内部服务访问的远程进程。

 SecurityService 扩展了 Service 基接口,并提供方法以便查询包括 isAuthenticated()、isChannelSecure() 和 isCommandSecure() 在内的当前安全状态。

 类

 BasicService 是一个 服务 的默认实现;它提供了辅助方法以便处理 APDU 和服务协作。

 Dispatcher 维护服务的注册表。如果希望将 APDU 的处理委托给几个服务,请使用分发程序。分发程序可以使用 process() 方法完整处理 APDU,或者使用 dispatch() 方法通过几项服务将其分发进行处理。

 异常

 ServiceException 是一个与服务相关的异常。

  javacard.security 定义用于 Java Card 安全框架的类和接口。Java Card 规范定义了一个健壮的安全 API,其中包括各种类型的私人密钥和公共密钥及其算法、计算循环冗余码校验(CRC)的各种方法、消息摘要和签名: 

  表 7. javacard.security

 接口

 通用基接口 Key、PrivateKey、PublicKey 和 SecretKey 以及表示各种类型的安全密钥和算法的子接口:AESKey、DESKey、DSAKey、DSAPrivateKey、DSAPublicKey、ECKey、ECPrivateKey、ECPublicKey、RSAPrivateCrtKey、RSAPrivateKey 和 RSAPublicKey。

 类

 Checksum:一个用于 CRC 算法的抽象基类

 KeyAgreement:一个用于密钥协议算法的基类

 KeyBuilder:密钥对象厂

 KeyPair:一个存储密钥对(一个是私人密钥,一个是公共密钥)的容器

 MessageDigest:用于散列算法的基类

 RandomData:用于随机数生成器的基类

 Signature:一个用于签名算法的基抽象类

 异常

 CryptoException:与加密相关的异常,例如不支持的算法或未初始化的密钥。

  javacardx.crypto 是一个定义接口 KeyEncryption 和类 Cypher 的扩展软件包,其中接口和类在各自的软件包中,可以轻松实现导出控制。使用 KeyEncryption 来解密一个使用加密算法的输入密钥。Cypher 是所有加密器必须实现的基抽象类。 

  javacardx.rmi 是一个定义 Java Card RMI 类的扩展软件包。它定义了两个类,CardRemoteObject 和 RMIService。CardRemoteObject 定义了两种方法,export() 和 unexport(),用于启用和禁用对来自卡片外部对象的远程访问。RMIService 扩展 BasicService 并实现 RemoteService 以便处理 RMI 请求。

Java Card 运行时 

  JCRE 规范定义了 Java Card VM 的生命周期、applet 的生命周期、applet 被选择和与其它 applet 隔离的方式、事务、对象持久性和共享。JCRE 提供一个由卡片操作系统所提供服务的平台独立接口。该接口由 Java Card Virtual Machine、Java Card AP 和供应商指定的扩展组成:

图 10. Java Card 架构和运行时环境

  Java Card VM 的生命周期

  JCVM 的生命周期与卡片本身的生命周期一致:JCVM 的生命周期从卡片被制造并测试后且发行给卡片持有人前的某一时刻开始,在卡片被废弃或损坏之时结束。当接到卡片上的电源去掉时,JCVM 并不停止运行,因为其状态在卡的非易失内存中仍被保留下来。启动 JCVM 需要初始化 JCRE 并创建所有在 JCVM 整个生命周期中都处于激发状态的 JCRE 框架对象。在 JCVM 启动之后,所有与卡片的交互原则上受卡片中某个 applet 控制。当电源从卡片上移走时,所有包含在 RAM 中的数据全部丢失,但是存储在非易失内存中的状态仍然被保留下来。当电源重新供应时,VM 再一次被激发,此时 VM 的状态和对象的状态被存储,执行恢复,等待新的输入。

  Java Card Applet 的生命周期

  卡片上的 applet 由应用程序 ID (AID)惟一识别。AID 在 ISO 7816-5 中定义,是一个介于 5 个字节和 16 个字节的序列。所有 applet 必须扩展这个定义 JCRE 使用方法的 Applet 抽象基类,以便控制 applet 的生命周期,总结内容如图 10 所示:

图 11. Java Card Applet 的生命周期方法

  Applet 的生命周期始于 applet 被下载到卡片上且 JCRE 调用 applet 的静态 Applet.install() 方法,以及 applet 通过使用 JCRE 调用 Applet.register() 进行自我注册之时。一旦 applet 被安装和注册,它便处于未被选中的状态,可以用于选择并进行 APDU 处理。图 11 Applet 方法操作的总结

图 12 使用 Java Card Applet 方法

  当 applet 处于未被选中的状态时,它是无效的。当主机应用程序请求 JCRE 选择卡片中特定的 applet 时(通过向卡片读取器发出指令 SELECT APDU 或 MANAGE CHANNEL APDU 完成),applet 被选中用于 APDU 处理。要通知 applet 已经被主机应用程序选中,JCRE 会调用 select() 方法;applet 通常执行适当的初始化过程,准备用于 APDU 处理。

  一旦完成选择 applet,JCRE 将传入的 APDU 命令传递给 applet,通过调用 process() 方法进行处理。JCRE 可以捕捉任何 applet 捕捉失败的异常。

  当主机应用程序告诉 JCRE 选择另一个 applet 的时候,当前正在使用的 applet 被取消选中。通过调用通常执行清除逻辑并返回 applet 为无效、未被选中状态的 deselect() 方法,JCRE 通知处于激活状态的 applet 已经被取消选中。

Java Card 会话和逻辑通道

  当卡片接通电源并与卡片读取器进行 APDU 交换期间,卡片进行会话。

  Java Card 2.2 支持 逻辑通道 概念,允许卡片一次打开多至 16 个的应用程序会话,一个会话占用一个逻辑通道。由于卡片内的 APDU 处理过程不能被中断,每个 APDU 包含了对逻辑通道(以 CLA 字节 的形式)的引用,替换的 APDU 可以不同时访问卡片中的许多 applet。您还可以设计 applet 为 多项选择的;也就是一次可以在多于一个的逻辑通道上进行通信。多项选择的 applet 必须实现 javacard.framework.MultiSelectable 接口和相应的方法。

  在一些卡片部署中,默认的 applet 可以在卡片复位后被定义为自动选择,以便在基逻辑通道上(通道 0)进行通信。Java Card 2.2 许可定义默认的 applet,但是没有指定定义默认 applet 的方式;此种机制是由供应商指定的。

  Applet 的隔离和对象共享

  Java Card 平台是一个安全的多应用程序环境 —— 许多不同供应商生产的不同 applet 可以在同一张卡片上安全地共存。每个 applet 被分配给一个 执行上下文,用于控制所分配对象的访问。在两个扩展上下文之间的边界通常被称作 applet 防火墙。它是沙箱的 Java 安全概念的 Java Card 运行时增强,结合了类加载器功能、java.ClassLoader 访问控制器和 java.AccessController。Java Card 防火墙创建了 虚拟堆,这样,一个对象只能访问在相同防火墙内的(公共的)方法和这些对象的数据。防火墙可能包含许多 applet 和其他对象,例如常见的安全密钥。Java Card 当前执行的上下文在软件包范围内。当每个对象被创建时,为对象分配调用程序的执行上下文。

  Java Card 平台支持跨越防火墙的安全对象共享。图 12 描述 applet 隔离和对象共享:

图 13. Applet 防火墙和对象共享

  在图 12 中描述了典型的流程,如图所示:

  Appleta 通过调用系统的 JCSystem.getAppletShareableInterfaceObject() 方法请求访问 Appletc 的可共享接口。 

  为了表示 Appleta,JCRE 通过调用 applet 的 getShareableInterfaceObject() 方法请求 Appletc 的可共享接口。 

  如果 Appletc 允许共享,Appleta 将获得对一个 Appletc 共享对象的引用。Appleta 现在就可以访问 Appletc 了。Appleta 将拥有自己创建的任意对象,即使这些对象是在 Appletc 中定义的。 

  在同一个执行上下文中的 Applet 在默认情况下能够互相访问,因此,Appleta 和 Appletb 不需要遵循这一程序来共享对象。

  管理内存和对象

  在 Java Card 设备上,内存是最有价值的资源。在某些 Java Card 实现中,垃圾收集器有可能不可用。当一个对象被创建时,对象及其内容被保存在非易失内存中,使得这些内容在会话过程中可用。在某些情况下,应用程序数据不需要是持久性的 —— 它可以是临时性的或者是 瞬态的。要减少对智能卡持久性内存的损坏,因此要最大限度地增加此类内存的生命周期,尽可能将经常被更新的数据作为瞬态类型的数据处理。

  Java Card 技术不支持 瞬态 关键字。Java Card API(javacard.framework.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) 

  还可以创建 byte 或 short 的基本数据类型的瞬态数组,或者创建一个瞬态的 对象。但是请记住,下列行为可用于瞬态数据:

  瞬态对象的状态在会话过程中不是持久的。请注意其内容(不是对象本身)是瞬态的。对于任意其他 Java 语言对象,只要引用瞬态对象,它就会一直存在。 

  当发生卡片复位或 applet 取消选中时,瞬态对象的内容可能会被复位为字段的默认值(零,false 或 null)。 

  出于安全原因,瞬态对象的字段不在持久内存中储存。 

  瞬态对象字段的更新 不是 原子的,且不受事务的影响。 

  在 Java Card 环境中,数组和基本类型在对象声明位置被声明,为了有利于对象的重用,应该最大限度减少对象实例化。在 applet 生命周期内只对对象进行一次实例化,最好是在 applet 初始化时通过在 生命周期内惟一调用一次 install() 方法进行实例化。

  为了利于重用,对象保留在原有位置或在生命周期内被引用,且这些对象的状态(成员变量的值)在重用前适当复位。由于垃圾收集器并不是总是可用的,应用程序可能不再回收分配给超出范围的对象的存储空间。

  持久性事务

  JCRE 支持原子事务安全地更新一个或多个持久性对象。即使发生断电或程序错误,事务也要确保数据完整性。事务通过下列方法得到系统级支持:

  JCSystem.beginTransaction() 

  JCSystem.commitTransaction() 

  JCSystem.abortTransaction() 

  在许多事务模型常见的模型中,Java Card 事务从调用 beginTransaction() 开始,并从调用 commitTransaction() 或 abortTransaction() 结束。让我们看一看使用这些 API 的代码片断:

...
private short balance;
...
JCSystem.beginTransaction();
balance = (short)(balance + creditAmount);
JCSystem.commitTransaction();
...

  务必确保实例变量余额的更新操作为原子操作。如果发生程序错误或电源重置,确保事务会恢复 余额 的前值。

  JCRE 不支持嵌套事务。

  结束语

  本系列文章的第一部分介绍了相当广泛的内容:使用智能卡存储敏感信息和安全处理事务,Java Card 技术的方方面面 —— Java Card VM、运行时环境、相关的 API 和 Java Card applet 的行为。系列文章的第二部分将介绍 Java Card 技术的开发部分。

  使用 Java Card 技术的智能卡具备最佳的可移植性,并且是携带数字个人信息计算功能的安全方式;在当今的数字世界中,它是一项强大的必备技术。

 

Java Card 技术简介  第 2 部分,Java Card Applet

开发 Java Card 应用程序

  创建 Java Card 应用程序的典型步骤如下:

  编写 Java 源代码。 

  编译 源代码。 

  将类文件 转换 为 Converted Applet(CAP)文件。 

  验证 CAP 是否有效;此步骤为可选。 

  安装 CAP 文件。 

  使用 Java 程序设计语言开发传统程序时,前两个步骤是相同的:编写 .java 文件并将它们编译为 .class 文件。虽然您已经创建了 Java Card 类文件,但是该过程仍可改变。

  Java Card Virtual Machine(JCVM)被划分为卡片外部 JVM 和卡片内部 JVM。这种划分移除了开销较大的卡片外部操作,并且考虑到了卡片内部的内存占用量较小,但是这会导致开发 Java Card 应用程序的步骤增加。

  将 Java Card 类载入 Java Card 设备之前,必须将它们转换为标准的 CAP 文件格式,然后选择性地进行验证:

  转换时必须将每个 Java 软件包转换为 CAP 文件,其中一个软件包中包含了类和接口的联合二进制表示法。转换是一项卡片外部的操作。 

  验证是一个可选过程,目的是验证 CAP 文件结构、有效的字节码子集、软件包之间的依赖关系。您可能想对要使用的第三方供应商的软件包进行验证,或者,如果您的转换器工具由第三方供应商提供,您希望对其进行验证。验证通常是一个卡片外部的操作,但是一些卡片产品可能包含机载的检验器。 

  完成验证之后,便可以将 CAP 安装到 Java Card 设备中了。

  Sun Java Card 开发工具箱

  您可以使用 Sun Java Card 开发工具箱编写 Java Card applet,甚至在没有智能卡或卡片读取器的情况下对它们进行测试。该工具箱包含了需要开发和测试 Java Card applet 的所有基本工具:

  Java Card 工作站开发环境(JCWDE)是一个方便且易于使用的 Java Card 模拟工具,使开发人员无需转换和安装 CAP 文件而直接执行类文件。JCWDE 可以使用调试器和 IDE 进行集成。

  从这个开发工具箱的2.2.1版本开始,JCWDE 支持 Java Card RMI(JCRMI)。请注意 JCWDE 不是一个成熟的 Java Card 仿真器。它还不支持 JCRE 的许多功能,例如软件包安装、applet 实例创建、防火墙和事务。要了解更多信息,请参考开发工具箱的《用户指南》。

  C 语言 Java Card 运行时环境(C-JCRE)是一个用 C 语言编写的可执行参考实现。C-JCRE 是 Java Card API、VM 和运行时环境的完全兼容的实现。它能让开发人员在工作站环境下准确地测试 applet 的行为。

  C-JCRE 的限制很少:在卡片会话期间,它支持多达 8 个可以返回的远程引用、多达 16 个可以同时导出的远程对象、在远程方法中多达 8 个数组类型的参数、多达 32 个受支持的 Java 软件包和多达 16 个的 Java Card applet。要了解关于限制方面的更多信息,请参考《 Java Card 开发工具箱用户指南》。

  Java Card 转换工具,用于生成 CAP 文件。 

  Java Card 检验器,用于可选地检查 CAP 和导出文件的有效性。 

  一个用于发送和接收应用程序协议数据单元(APDU)的 APDU 工具(apdutool)。这就是测试期间如何将 APDU 发送给 Java Card applet 的过程。您可以创建 apdutool 读取的脚本文件,以便将 APDU 发送到 C-JCRE 或 JCWDE。 

  一个 capdump工具,用于转储 CAP 的内容和一个打印 EXP 文件的 exp2text。 

  一个 scriptgen 工具,用于将 CAP 文件转换为 APDU 脚本文件。该工具简称为卡片外部安装程序。 

  支持库(用于 Java Card API 的类文件和导出文件)、文档和示例。 

  虽然 Sun Java Card 开发工具箱允许编写并测试 Java Card applet,部署真正的端到端智能卡应用程序却要求这些工具不被包含在开发工具箱内,例如:像 OpenCard 和 Global Platform API 这样的终端 API 的使用。可能还要求使用像用户识别模块(Subscriber Identification Module,SIM)这样的工具箱来帮助你管理 SIM。

  表 1 显示了工具箱的目录结构(Windows 版本),以及包含开发工具的 bin 目录内容。

  图 1a. 开发工具箱目录结构

  

  图 1b. Bin 目录的内容

  现在让我们重新访问 Java Card 开发步骤,记住这次要使用 Sun Java Card 开发工具箱:

  使用您喜欢的编辑器或 IDE 编写 Java 源代码。 

  使用您喜欢的编译器或 IDE 编译 源代码。 

  使用 JCWDE 仿真器测试 Java Card applet。这是一个可选的步骤。请记住 JCWDE 不是一个成熟的 Java Card 仿真器。 

  使用工具箱中 bin 目录中的 转换器 工具将类文件转换 为 Converted Applet(CAP)。请注意,除了类文件之外,向转换工具的另一个输入为 导出文件,提供由应用程序所导入软件包的相关信息;这些软件包还会被加载到卡片中。导出文件还是转换器工具的一个输出结果。 

  验证 CAP 的有效性。这一步是可选的。这一步包括使用 verifycap 脚本来验证 CAP 文件的有效性,使用 verifyexp 来验证导出文件,并且使用 verifyrev 验证软件包修订版本间的二进制兼容性。工具 verifycap、verifyexp 和 verifyrev 脚本都在 bin 目录中可以找到。 

  安装 CAP 文件。使用 scriptgen 工具将 CAP 文件转换为(安装) APDU 脚本文件。然后使用 apdutool 将脚本文件(安装 APDU 命令和 CAP 文件)发送到 Java Card 设备上的 C-JCRE 或 JCRE。JCRE 将 CAP 文件保存卡片内存中。 

  下列图总结了这些步骤。请注意,每个Java Card 供应商提供自己的工具,但是开发 Java Card applet 的步骤通常在开发工具箱之间是相同的:

图 2. Java Card 开发步骤

  要了解关于如何使用 Sun 的 Java Card 开发工具箱方面的更多信息,请参见《 Java Card 开发工具箱用户指南 》,从中找到工具箱文档目录。另一个优秀的参考资料是文章“使用 Java Card 开发工具箱”。

编写卡片端 Java Card Applet

  Sun 提供了两个模型用来编写 Java Card applet(javacard.framework.Applet):传统的 Java Card API 或者 Java Card 远程方法调用(JCRMI) API。我们可以使用其中任何一个模型来编写 Java Card applet。

  使用 Java Card API

  开发 Java Card applet 是一个两步完成的过程:

  定义负责主机应用程序和 applet 之间接口的 APDU 命令和响应。 

  编写 Java Card applet 本身 

  首先,让我们看一下 Java Card applet 的结构。

  Applet 结构

  清单 1 说明了构造 Java Card applet 的方式:

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
...
}

  清单 1. Java Card Applet 的结构

  Java Card applet 通常定义它的 APDU 相关指令、它的构造函数和 Java Card applet 生命周期方法:install()、select()、deselect() 和 process()。最后,它还定义任意适当的专用方法。

  定义 APDU 指令

  不同的 Java Card 应用程序具有不同的接口(APDU)需求。信用卡 applet 可能支持验证 PIN 号的方式,完成信用和借记事务,并检查帐户的卡片余额。健康保险 applet 可能提供对健康保险信息、保险总额限制、医生、病人信息等内容的访问。需要根据应用程序的需求定义的准确的 APDU 。

  举例来说,让我们完成经典的 Wallet 信用卡示例的一些部分。您能在 Sun Java Card 开发工具箱中的示例目录下发现本示例和其他示例的完整源代码。

  首先,我们定义一个 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 命令会定义传入的数据。下面给出一个示例。让我们定义 Verify PIN APDU 命令,它验证从卡片读取器中传递来的 PIN 号。下一个表格定义了 Verify APDU:

  表 2 — Verify APDU 命令 

 名称

 CLA

 INS

 P1

 P2

 Lc

 Data Field

 Le (size of response)

 验证 PIN

 0x80

 0x20

 0

 0

 PIN Len

 PIN Value

 N/A

  请注意,Le 字段、响应的大小是 N/A。这是由于不存在特定于应用程序的响应对 PIN 进行验证;成功或失败通过响应中 APDU 的状态字显示。

  为了便于 APDU 处理,javacard.framework.ISO7816 接口定义了许多常量,我们可以用来从输入缓冲器中检索各个 APDU 字段,其中输入缓冲器通过 process() 方法被传递到 applet。

...
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 常量

  现在,我们将定义类(CLA)和指令(INS),用于 Get Balance 和 Verify 命令,所获取卡片余额响应的大小,以及如果 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. Applet 的 APDU 定义

接下来,让我们定义一些 applet 构造函数和生命周期方法。

  构造函数

  定义一个初始化对象状态的专用构造函数。从 install() 方法中调用该构造函数;换句话说,构造函数在 applet 生命周期内只被调用一次。

/**
* 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. Applet 构造函数

  在这个示例中,我们使用对象 javacard.framework.OwnerPIN表示个人识别号码;这个对象将存在于 Java Card Applet 的生命周期期间。回忆一下本系列文章的第 1 部分中的“管理内存和对象”部分,在 Java Card 环境中,数组和基本类型在对象声明中被声明,为了有利于对象的重用,应该最大限度减少对象实例化。在 applet 生命周期内只创建对象一次。轻松完成这一操作的一个简易的方法是在构造函数中创建对象,从 install() 方法(在 applet 生命周期内只创建一次)调用这个构造函数。为了利于重用,对象保留在原有存储范围内或在 applet 生命周期内被充分地引用,且这些成员变量的值在重用前应适当重置。由于垃圾收集器并不是总是可用的,应用程序可能从不回收分配给超出范围的对象的存储空间。

  install() 方法

  JCRE 在安装过程中调用 install()。您必须覆盖这个从 javacard.framework.Applet 类中继承的方法,且 install() 方法必须实例化 applet,如下所示:

/**
* 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() Applet 生命周期方法

  install() 方法必须直接或间接调用 register() 方法来完成安装;如果这步失败将导致安装失败。在这个示例中,构造函数调用 register()。

  select() 方法

  JCRE 调用 select() 以便通知 applet 已经被选中用于 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() Applet 生命周期方法

deselect() 方法

  JCRE 调用 deselect() 来通知 applet 已经被取消选中。除非想提供会话清除,否则不必实现该方法。通过 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() Applet 生命周期方法

  在本示例中,我们将重置 PIN。

  process() 方法 — 与 APDU 协同工作

  一旦 applet 已经被选择,它就准备接收 APDU 命令,如 第 1 部分 中“Java Card Applet 的生命周期”一节中所述。

  回想一下,APDU 命令从主机端(客户机端)应用程序中被发送到卡片上,如下所示:

图 3. APDU 命令、主机应用程序和 Java Card Applet 之间的响应流程

  每当 JCRE 接收到 APDU 命令(从主机应用程序通过卡片读取器完成,或者如果使用 Sun Java Card 开发工具箱时通过 apdutool 完成)时,JCRE 调用 applet 的 process() 方法,将之作为一个参数传递给传入的命令(该参数位于 APDU 命令输入缓冲器中)。然后 process() 方法执行下列操作:

  提取 APDU CLA 和 INS 字段 

  检索特定于应用程序的 P1、P2 和数据字段 

  处理 APDU 数据 

  生成并发送响应 

  适度返回或抛弃适当的 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() Applet 生命周期方法

  process() 方法调用 getBalance() 和 verify() 方法。清单 9 显示了处理获取卡片余额 APDU 的方法 getBalance(),并将卡片余额返回并保存在卡片内。

/**
* 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 缓冲器的引用。在返回响应(当前的卡片余额)前,applet 设置 JCRE 的模式以便通过调用方便返回预期响应大小的 APDU.setOutgoing() 方法发送。通过调用 APDU.setOutgoingLenth(),我们还必须设置在响应数据字段中字节的实际个数。在 APDU 缓冲器中的响应实际上通过调用 APDU.sendBytes() 发送。

  Applet 并不直接返回代码(状态字);一旦 applet 调用 APDU.setOutgoing() 并提供所请求的信息,JCRE 便负责这项操作。状态字的值根据 process() 方法返回到 JCRE 的方式而发生变化。如果一切进展顺利,JCRE 将返回 9000,指示没有错误。Applet 可以通过抛出在 ISO7816 接口中所定义的一种异常或者应用程序制定的值来返回错误代码。在清单 9 中,如果预期响应不正确,方法 getBalance() 会抛出一个 ISO7816.SW_WRONG_LENGTH 代码。有效的状态代码值参考 ISO7816 接口的定义,或参考本文中第一部分中“响应 APDU”一节。

  现在让我们查看一下清单 10 中的 verify() 方法。因为我们定义了 verify PIN APDU 命令要包含数据,因此 verify() 方法必须调用设置 JCRE 为接收模式的 APDU.setIncomingAndReceive() 方法,然后接收传入的数据。

/**
* 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. 处理 Verify APDU

  这个方法通过调用 APDU.getBuffer() 获得对 APDU 缓冲器的引用,调用APDU.setIncomingAndReceive() 来接收命令数据,从传入的 APDU 缓冲器中获取 PIN 数据并验证 PIN。验证失败将引起状态代码 6900 被发送回主机应用程序。

  有时所传入的数据会超过 APDU 缓冲器的数据容量,applet 必须以块的形式读取数据,直到不再有要读取的数据。在这种情况下,我们必须首先调用 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. 读取大量的传入数据

  当每个块被读取时,applet 只得将其附加到另一个缓冲器上,否则只好处理它。

  使用 Java Card RMI API

  第二个可以进行 Java Card applet 编程的模型是基于 J2SE RMI 分布式对象模型的 Java Card RMI(JCRMI)。

  该方法提供了以对象为中心的模型,其中 APDU 通信和处理在前面一节中被抽象化;而现在您需要处理这些对象。这一过程可以简化了基于 Java Card 技术的设备编程和集成。

  在 RMI 模型中,服务器应用程序创建并生成可访问的远程对象,并且客户机应用程序获得对服务器的远程对象的远程引用,然后为这些对象调用它们的远程方法。在 JCRMI 中,Java Card applet 是服务器,主机应用程序是客户机。

Java Card RMI 简介

  两个软件包提供了对 Java Card RMI 的支持:

  java.rmi 定义了 Java 2 标准版 java.rmi 软件包的子集。它还定义了 Remote 接口和 RemoteException 类。除此之外,不包含其它传统的 java.rmi 类。 

  javacard.framework.service 定义了 Java Card applet 服务类,其中包括 RMI 服务类 CardRemoteObject 和 RMIService。

  类 CardRemoteObject 定义了两种方法来启用和禁用对来自卡片外部的 对象 的远程访问。类 RMIService 处理 RMI 请求(将传入的 APDU 命令转换为远程方法调用)。 

  编写 JCRMI 应用程序类似于编写典型的基于 RMI 的应用程序:

  将远程类的行为设为一个接口。 

  编写远程类的服务器实现和支持类。 

  编写一个使用远程服务的客户机程序和支持类。 

  请注意,正如您稍候将要看到的,JCRMI 不改变 applet 的基本结构和生命周期。

  远程接口

  创建远程服务的第一步是定义它的可见行为。远程接口定义 applet 提供的服务。正如标准 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.
...
}

  清单 12. 远程接口  

  MyRemoteInterface 定义了检索存储在智能卡中卡片余额的远程方法,在本示例中为 getBalance() 方法。请注意,除了特定于 Java Card 的导入之外,这个远程接口看起来完全象一个标准的 RMI 远程接口。

  服务器实现

  下一步是实现服务器的行为。服务器实现由 Java Card applet、已经定义的任意远程接口的实现和任意特定于应用程序的相关类组成。

  Java Card Applet

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

图 4. 典型的 Java Card RMI Applet 的结构

  当与显式处理 APDU 消息的 applet 比较时,基于 JCRMI 的 applet 更像一个对象容器。正如您在图 4 中看到的,基于 JCRMI 的 applet 有一个或多个远程对象,一个 APDU Dispatcher 和一个接收 APDU 并将它们转换为远程方法调用的 RMIService。Java Card 远程类可以扩展 CardRemoteObject 类,以便自动导出对象,使得远程应用为可见状态。

  JCRMI applet 必须扩展 javacard.framework.Applet,遵循标准的 applet 结构,并定义适当的生命周期方法。还必须安装和自我注册,并分发 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();
}
...

  Applet 创建一个 Dispatcher 和 RMIService 来处理传入的 JCRMI APDU。

...
/**
* 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();
}

  在 Java Card 环境下,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);
}
}

  Applet 的 process() 接收了一个 APDU 命令并将该命令分发给 RMIService,RMIService 通过将该命令转换为 RMI 调用和后续响应而进行处理。

  清单 13. Java Card RMI Applet

实现 Remote 对象

  实现 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
...
}

  清单 14. 远程对象实现

  完整的 Java Card RMI 应用程序流程

  现在让我们总结一下 JCRMI 应用程序的流程图。客户机(主机)应用程序通过将 RMI APDU 传递给卡片内部的 JCRE,继而依次将这些 APDU 转发给适当的 JCRMI applet 来调用 RMI。这个 applet 将所接收到的 APDU 分发给 RMIService,继而依次处理 APDU 并将其转换为 RMI 调用。JCRMI applet 的典型流程如下所示:

图 5. 基于 Java Card RMI 的 Applet 的流程

  简言之,熟练运用基于 APDU 的消息传递模型,JCRMI 提供了一个分发模型机制。JCRMI 消息被封装在被传递到 RMIService 的 APDU 消息内。RMIService负责解码 APDU 命令,将它们转换为方法调用和响应。这使得服务器往复地和客户机通信、传递方法信息、参数并返回值。

  结束语

  “Java Card 技术简介”的第 2 部分介绍了 Java Card applet 开发的方方面面:Java Card applet 的结构、Sun Java Card 开发工具箱、API 和可用于编写 applet 的编程模型:Java Card API 和 Java Card RMI API。

  本系列文章的第 3 部分将介绍主机应用程序和可用于编写主机应用程序的 Java API:OpenCard Framework、Java Card RMI Client API 和用于 J2ME 的安全性和信任服务应用编程接口(SATSA)。

使用 Java Card 技术的智能卡具备最佳的可移植性,它是携带数字个人信息计算功能的安全方式;在当今的数字世界中,它是一项强大的必备技术。

 

Java Card 技术简介:第 3 部分, 智能卡主机应用程序

Java Card 应用程序的元素

  请记住,Java Card 应用程序并不是独立的,而是端到端应用程序的一部分:

图 1. Java Card 应用程序的典型组件 

  Java Card 应用程序通常由以下部分组成:

  提供后台服务(例如保存在数据库中的安全或者电子支付信息)访问的 后台应用程序。如何开发后台应用程序超出了本文的范围。 

  主机应用程序 位于卡片外部的卡片终端,它可以使用许多接口(如 Java Card RMI、OpenCard Framework API 或安全性和信任服务应用编程接口 [SATSA])访问智能卡上的 applet。 

  卡片读取器、卡片终端 或者 卡片接入设备 ,提供了主机应用程序和卡片内部 applet 之间的物理接口。 

  卡片内部的物理接口是 Java Card applet 和 Java Card 框架。请注意,在访问 applet 之前,主机应用程序必须提供证书并进行自我身份验证。 

  编写主机应用程序 —— 访问 Applet

  位于客户端的主机应用程序处理用户、Java Card applet 和提供器的后端应用程序之间的通信。主机程序访问由 applet 所提供的服务。它存储在终端或卡片接入设备上,例如工作站、销售终端点( POS )、手机或者机顶盒。回想一下,主机和 applet 使用 ISO-7816 APDU 命令通过卡片读取器或终端进行交互。

  通常,读取器端应用程序使用 C 语言编写,但是主机程序用 Java 编程语言或其他语言编写,只要该语言准备了与 applet 交换的有效 ISO-7816 APDU 命令就没有问题。

  目前,大多数经过部署的手机集成了智能卡读取器,以便访问与该读取器捆绑的 SIM 卡片。使用即将引入的 JSR 177, 用于 J2ME 的安全性和信任服务应用编程接口(SATSA)、J2ME 设备的广泛采用,我们能够预计各种主机应用程序将使用移动设备上的 Java 技术编写。SATSA 的意图就是启用 Java Card 主机应用程序来运行基于 J2ME 的设备。目前,JSR 177 处于 JCP 团体审查阶段。

  当编写客户端应用程序时,有三个主要的 API 可以使用:OpenCard Framework、Java Card RMI Client API 和用于 J2ME 的安全性和信任服务应用编程接口(SATSA)。我们将逐一研究每一个应用程序编程接口。

  OpenCard Framework 简介

  智能卡供应商通常不仅提供开发工具箱,还提供 API 以便支持读取器端应用程序以及 Java Card applet。供应商提供的许多产品都支持 OpenCard Framework ( OCF )和基于 Java 的 API 集合,该集合隐藏了与不同供应商提供的卡片读取器交互的一些细节。

  OpenCard 联盟 是一群推动定义和采用 OpenCard Framework( 当前版本为1.2)的公司。OCF 的目标是向主机端应用程序的开发者提供跨不同的卡片读取器供应商工作的 API。

  为了实现厂商无关性,OCF 定义了两个软件层:

  CardTerminal 层提供了卡片读取器抽象,例如 CardTerminal ( 表示物理卡片读取器 ) APDU、CommandADPU 和 ResponseAPDU。 

  OCF 为您定义了许多标准卡片服务。其中两个是 FileAccessCardService 和 SignatureCardService。一个特殊的类型是 ApplicationManagerCardService,提供生命周期管理方法以便安装、注册和删除卡片内部的 applet。

  当编写主机端基于 OCF 应用程序时,您基本上要将该应用程序分为两个部分:

  主要的应用程序对象,它与终端或读取器进行交互(初始化 OCF、等待卡片插入、中断 OCF ),并公开高级卡片访问方法,例如 getBalance()。 

  Applet 代理,它实现实际的低级通道管理和 APDU I/O。当把来自应用程序的 APDU 细节隐藏起来的时候,该代理设计模式允许公开面向对象的接口。 

图 2. OCF 应用程序的结构 

  总而言之,OCF 应用程序有一个或多个 main 对象,它们在主机上或者在执行的线程上创建。这些 main 应用程序对象公开特定于应用程序的高级调用,最终将它们委托给 applet 代理。这些对象利用 OCF 应用程序入口点的 SmartCard 对象,启用应用程序初始化并中断 OCF,并等待卡片被插入。main 对象可以实现即将看到的 CTListener,这个侦听器提供卡片插入和拔出等类似事件的异步通知。

您可以使用同步或异步模型编写应用程序。

  在同步模型中,主机应用程序初始化 OCF,然后等待卡片被插入。然后它执行主机应用程序逻辑,当操作完成时,中断 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();
}
}
...

  清单 1. 同步 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() 方法。在更为充实的下列清单中,插入卡片将初始化 applet 代理的创建,拔出卡片将触发 applet 代理的清除。清单还显示了用于信用卡余额的请求被委托给代理。

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();
}
}
...
}

清单 3. 基于侦听器的放大 OCF 应用程序。

  接下来是一个来自 applet 代理的引用。OCF 应用程序将服务调用委托给实现( 复杂 ) APDU 管理片断的 applet 代理:

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. Applet 代理示例

  在 OpenCard Framework 1.2 编程人员指南 中您会找到关于  OCF 使用的更多信息。还可以参考 OpenCard Framework 参考实现 中的示例文件。

  本文中未涉及,但是值得一提的是主机端 API,称作 Global Platform。Global Platform Card Committee 提供了补充 Java Card API 的卡片 API 集合,除其他内容以外,还提供了卡片管理功能。这些规范都在2.1.1版本中。

  Java Card RMI 客户机 API

  前面你已经学到了如何编写一个基于 JCRMI 的 applet。如果您的 Java Card applet 基于 JCRMI,您可以使用 Java Card RMI Client API编写一个主机应用程序,访问智能卡中保存的 apple 对象。

  对于智能卡管理和访问,JCRMI Client API 需要一个卡片终端和诸如刚刚描述的 OpenCard Framework 这样的服务 API。

  当我们把这两个 API 放在一起时,我们得到一个非常简单、非常完整的面向对象编程模型,有以下几个优点:

  不必知道智能卡和卡片读取器的细节

  不必知道低级的 APDU 通信

  便于设计和维护的代码,从而缩短开发时间

  JCRMI Client API 在下面的软件包中定义:

  com.sun.javacard.javax.smartcard.rmiclient 包含核心 JCRMI Client API。它定义了以下内容:

  JCRMI 桩模块用来访问智能卡的 CardAccessor 接口

  CardObjectFactory 类,用于 JCRMI 桩模块生成实现的基类。这个类的实例与一个 Java Card applet 选择会话有关。

  客户机应用程序使用的 JavaCardRMIConnect 类,用于初始化一个 JCRMI 会话并且获得初始的远程引用。

  许多 Java Card 的异常子类,例如 APDUExceptionSubclass、CardExceptionSubclass、CardRuntimeExceptionSubclass、CryptoExceptionSubclass、ISOExceptionSubclass、PINExceptionSubclass、PINException、ServiceExceptionSubclass、SystemExceptionSubclass、TransactionExceptionSubclass 和 UserExceptionSubclass。

javacard.framework 定义了许多客户机上的许多可以被再次抛出的 Java Card 异常:APDUException、CardException、CardRuntimeException、ISOException、PINException、SystemException、TransactionException 和 UserException。

  javacard.framework.service 定义了 ServiceException,表示与服务框架有关的异常。

  javacard.security 定义了 CryptoException,用来表示一个有关加密的异常。

  生成 RMI 客户机桩模块

  您选择使用标准的 Java RMI 编译程序(rmic)生成客户机桩模块。您必须使用下面格式的命令运行 rmic,用于你的 applet 中的每个远程类:

rmic -v1.2 -classpath path -d output_dir class_name

  ......其中:

  -v1.2 是一个 Java Card RMI 客户机框架所需要的标记。

  -classpath path 确定到远程类的路径。

  output_dir 是存放结果桩模块的目录

  class_name 是远程类的名称。

  然而,推荐生成 RMI 客户机桩模块的方法使用 J2SE SDK 1.3 中的 动态代理 生成机制。当您选择 JCRMI applet 的时候,如果使用 CardObjectFactory 子类型 JCCardProxyFactory 的话,JavaCard RMI Client API 的 2.2 版本将为你自动生成桩模块,你不必再生成任何桩模块!这个方法在清单 5 中加以说明。

  用法限制

  因为 Java Card 是一个有限制的运行时环境,我们可以发现关于 JCRMI 实际支持的限制。Java Card 不支持序列化和 JCRMI 参数,并且返回值也有限制:

  每个到远程方法的参数必须是 Java Card 支持的类型之一,不包括 char, double, float, long 或者多维数组。对 int 的支持为可选的。

  任何远程方法的返回值必须是受支持的类型之一,或者 void,或者一个远程接口类型。

  JCRMI 客户机应用程序

  因为 JCRMI Client API 依靠 OCF 用于卡片管理和通信,所以 JCRMI 客户机应用程序类似您前面看到的 OCF 主机应用程序。

  下面的代码片断首先初始化 OCF,并且等待智能卡被插入。然后它创建一个 OCFCardAccessor 实现,用于把我们的 JCRMI 连接到卡片上,如有必要,客户机桩模块将动态生成,applet 被选中,我们取得了远程引用,最后我们实现到 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 的新的可选软件包,定义一个客户端 API 来访问 安全元素:例如智能卡这样的设备。在本节中,我将仅仅介绍 SATSA 的通信部分;本文中将不介绍 SATSA PKI 和加密 API。

  SATSA 通信 API 被分解成下面几部分:

  SATSA-APDU 定义一个 API,用于和遵循 ISO-7816-4 的智能卡进行通信。这个可选软件包由单独的 javax.microedition.io.APDUConnection 软件包组成。 

  SATSA-JCRMI 定义了 Java Card RMI 客户机 API。这个可选软件包由下面的 Java 软件包组成: 

  javax.microedition.io.JavaCardRMIConnection 

  javax.microedition.jcrmi.RemoteRef 

  javax.microedition.jcrmi.RemoteStub 

  java.rmi.Remote 

  java.rmi.RemoteException 

  javacard.framework.service.ServiceException 

  javacard.framework.CardRuntimeException 

  javacard.framework.ISOException 

  javacard.framework.APDUException 

  javacard.framework.CardException 

  javacard.framework.PINException 

  javacard.framework.SystemException 

  javacard.framework.TransactionException 

  javacard.framework.UserException 

  javacard.security.CryptoException 

  SATSA 将 J2ME 和 Java Card 平台紧密地结合在一起。SATSA 使用 CLDC 1.0 Generic Connection Framework(GCF)用于基于 J2ME 的设备和智能卡之间的通信,如下所示:

图 3. 通用连接框架和 SATSA 连接 

  因为 SATSA 基于 GCF,开发使用手机上的智能卡的 MIDlet 相对不容易,但是对于 J2ME 开发人员来说很熟悉。

  SATSA 考虑到用于 Java Card 应用程序的两个总体编程模型:APDU-消息传递模型和 Java Card RMI 面向对象分布式模型。SATSA 为了每一个模型定义了一个新的 GCF 连接类型:

  APDUConnection 允许一个 J2ME 应用程序使用 ISO-7816 APDU 协议以便与智能卡应用程序交换 APDU。 

  JavaCardRMIConnection 允许一个 J2ME 应用程序使用 Java Card RMI 来调用智能卡上的远程方法。 

  指定 SATSA 的连接类型

  所有的 GCF 连接都使用 Connector.open() 方法创建。Connector.open() 的一个参数是指示要创建的连接类型的 URL。CLDC GCF 使用下面的格式定义这个 URL 为一个字符串:

scheme:[target][params] 

  ......其中:

  scheme 是要创建的连接类型(和要使用的协议)。 

  target 一般是某种网络地址。 

  params 是可选参数,表达形式是名称=值,通过分号隔开。 

  对于 SATSA,URL 的格式是:

protocol:[slotID]; AID 

  ......其中:

  protocol 要么是 apdu,用于基于 APDU 的连接,要么是 jcrmi,用于基于 JCRMI 的连接。 

  slotID 是指示卡片插入的插槽。slotID 字段是可选的;默认值为 0。 

  AID 是用于智能卡应用程序的应用程序标识符。AID 是一个由句号分隔开的 5 到 16 个十六进制字节值的字符串;例如,“ A0.0.0.67.4.7.1F.3.2C.3”。 

  使用 APDUConnection

  APDUConnection 定义允许我们使用 GCF 与遵循 ISO-7816 的卡片进行通信的各种方法。它定义了三个方法是:

  enterPIN() 提示用户输入一个个人识别号码。 

  exchangeAPDU() 与智能卡应用程序交换 APDU。这个调用直到一个响应从智能卡返回的时候(或者处理被中断)才会阻断。

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 命令和响应的格式和您在本系列文章的第 2 部分的“编写客户端 JavaCard Applet”一节中看到的相同,告诉您如何编写一个基于 APDU 消息传递的 Java Card Applet。

  使用 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 应用编程接口,允许我们调用本系列文章的第 2 部分中所定义的 getBalance() 方法,向你说明如何编写一个基于 RMI 的 JavaCard applet。

  结束语

  本系列文章的第 1 部分介绍了使用智能卡存储敏感信息和安全处理事务以及 Java Card 技术的方方面面:Java Card VM、运行时环境、相关的 API 和 Java Card applet 的行为。

  第 2 部分介绍 了 Java Card applet 的开发、Java Card applet 的结构、Sun Java Card 开发工具箱、API 以及编写基于卡片的 applet 时可用的编程模型:Java Card API 和 Java Card RMI API。

  本系列文章的最后一部分介绍了主机应用程序的开发、可用于编写主机应用程序的 Java API:OpenCard Framework、Java Card RMI Client API 和针对 J2ME 的安全性和信任服务应用编程接口(SATSA)。

  使用 Java Card 技术的智能卡具备最佳的可移植性,它是携带数字个人信息计算功能的安全方式;在当今的数字世界中,它是一项强大的必须技术。

 

 

 

 

 

 

 

 

 

 

Java Card 开发快速入门指南

编写人:Tim Boudreau,维护人:Ruth Kusterer

Java Card 是一个使用起来非常有趣的平台 - 在智能卡上运行的 JVM 以及手掌大小的微型设备,无不令开发过程充满乐趣。Java Card 自版本 3.0 起分为两种类型: 

传统: 

这种类型与 Java Card 的早期版本一样。平台功能极其受限。例如,不存在 java.lang.String,也没有 java.lang.Object.hashCode() 方法和浮点数。 

扩展: 

适用于更新且功能更强大的智能卡 - 这是 Java Card 3.0 新增的一种类型。它支持更加完整地实现 Java 平台。最酷的恐怕就是它在本机支持 Servlet 的功能了 - 实际上,您使用熟悉的 API 就能编写在智能卡上运行的 Web 应用程序! 

目录

· 所需的软件和硬件 

· 设置项目环境 

· 了解 Java Card 项目类型 

· 使用项目 

· 使用特殊的插件功能 

· 集成第三方 SDK 

· 相关链接 

要求

要学完本教程,您需要具备以下软件和资源:

软件或资源

要求的版本

NetBeans IDE

6.9

Java Card SDK

3.02 

适用于 NetBeans 的 Java Card 插件

1.3 

Java Development Kit (JDK)

版本 6 

设置项目环境

由于智能卡没有用户界面,因此您需要一个用于在卡上读写数据的智能卡读卡器,或者使用 Java Card 引用实现 (Reference Implementation, RI) 中包含的仿真器。本教程将使用仿真器。 

注意:Java Card 插件适用于任何操作系统,但 Java Card 引用实现仿真器只可用于 Windows。不过,您也可以在其他操作系统上将其设置为 Java 平台,方法是将 NetBeans 指向 RI 在 Mac 或 Linux 系统上的 Windows 分区中的安装,但您不能使用此设置运行项目。 

在 NetBeans IDE 中安装插件

1. 下载并安装 NetBeans IDE 6.9。 

2. 安装完成后,转至“工具”>“插件”菜单。 

3. 在“可用插件”下,有两个与 Java Card 相关的插件:Java Card 和 Java Card Runtime Bundle。 

Java Card 插件用于在 NetBeans IDE 中添加 Java Card 项目支持。 

仅当您尚未安装 Java Card 引用实现的副本时,才需要 Java Card 3.0.2 Runtime Bundle。 

4. 安装插件后,需要重新启动 IDE 才能继续执行下面的教程步骤。 

注册 Java Card 平台

如果从插件管理器下载了 Java Card 3.0.2 Runtime Bundle,则已将 Java Card SDK 设置为平台。但是,如果从 java.sun.com 下载并安装了平台,则可以使用“工具”>“Java 平台”菜单将 Java Card 平台添加到 IDE 中,具体的操作方法与注册任何 Java 平台相同。

设置 Java Card 平台后,IDE 的“服务”标签中会列出该平台。如果未显示“服务”标签,请从菜单中选择“窗口”>“服务”。 

一个“平台”可以有多台“设备”。您可以将项目部署到特定平台的特定设备上。 

了解 Java Card 项目类型

从菜单中选择“文件”>“新建项目”,然后单击 "Java Card" 类别。此处有多种 Java Card 项目可供您创建。所有这些项目与 NetBeans Java SE 项目一样,都使用 Apache Ant 生成。 

传统的 Applet 项目为较小的设备创建传统的 Java Card Applet,这与 Java Card 2.0 和更早版本中使用的 Applet 类似。传统的库项目类似于传统的 Applet 项目,它没有 Applet,是您希望存放在设备上且可在 Applet 之间共享的一些代码。 扩展的 Applet 和库项目使用 Java Card 3.0 中的扩展 API,以便您能够使用 java.lang.String 等。传统项目和扩展项目的引导类路径有所不同,例如,传统项目的代码完成不显示 java.lang.String,但扩展项目的代码完成却显示 java.lang.String。 

Web 应用程序项目可能是 Java Card 3.0 最酷的功能。您可以获得一个实现了 Servlet 的框架项目,并且有权访问完整的 Servlet API。这比使用任一类型的 Applet 式应用程序要简单得多 - 您在客户端上无需任何特殊代码,即可与设备上运行的应用程序进行交互,此操作只需一个 Web 浏览器而已!您可以使用引用实现和桌面 Web 浏览器在本地测试应用程序。 

使用项目

对于本教程,我们将创建一个新的 Web 项目。

在新的 Web 项目中,输入 "Card Web Application" 作为项目名称,并将项目位置设置为 NetBeansProjects 目录。单击“完成”后,该项目出现在“项目”标签中。 

右键单击“项目”标签中的项目节点,然后从上下文菜单中选择“属性”。在“项目属性”窗口的“运行”部分中,您可以更改项目所部署到的平台和设备。单击“关闭”以保存更改。 

Java Card Web 应用程序的使用与部署到 Servlet 容器的任何其他 Web 应用程序类似。按工具栏上的“运行”按钮,以运行 "Hello World" 样例。运行 Java Card Web 应用程序时,将打开一个 Web 浏览器窗口,其中显示 Servlet 的输出:Hello from webapplication1.MyServlet。 

运行 Applet 类型的项目时,NetBeans IDE 将提供两个有用的界面:命令行输出和 Java Card 控制台。使用此控制台可与 Applet 进行交互:您可以发送十六进制数据并读取回复。 

提示:RI 包含可在 NetBeans IDE 中打开和运行的更多样例项目。 

使用特殊的插件功能

Java Card 有两个令人不可思议的特性,这在其他 Java 平台中都不曾遇到过: 

应用程序标识符 (Application Identifier, AID) 

这些唯一标识符类似于 //aid//720A75E082/0058AEFC20。十六进制数值的第一部分是供应商 ID(需从国际标准化组织 (International Standards Organization, ISO) 获得);第二部分是您想出来的一个唯一值。AID 用来标识 Applet 类、Java 包(仅传统的 Applet 和库项目)以及 Applet 的唯一实例(您可以在一个设备上多次部署相同的 Applet,使用实例 AID 可以选择要将信息发送给哪个 Applet)。 

APDU 脚本 

这些脚本用于向 Applet 发送数据。它包含过多需手动键入的十六进制数值;该脚本需要选择一个特定的 Applet 实例,然后向该实例发送数据。您也可以使用 Java Card 控制台,而不是发送预先编写的脚本。 

这两项特性都有些复杂,不过,NetBeans 插件可以最大限度地简化其操作,如下所示: 

· 当创建项目时,将会自动为 Applet AID、传统的包 AID 和实例 AID 生成合理的值。 

· 在“项目属性”对话框中选择 "Applet" 标签后,项目将扫描其类路径,以查找所有 Java Card Applet 子类: 

· 找到子类后,便可以在该对话框中选择实际部署的 Applet,并定制所用的 AID 值、部署参数等。IDE 会验证您输入的所有数据,以便确保不存在任何无效数据: 

· 如果要部署同一 Applet 的两个实例,则可以进行此设置;但是,对于仅需部署一个 Applet 实例的简单情况,则不需要考虑此设置。 

· 要测试处于运行状态的 Applet,您无需手动编写整个 APDU 脚本,使用内置控制台即可直接与部署的 Applet 进行交互: 

· 传统项目(这些项目只允许包含一个 Java 包)的“包 AID”也由 IDE 生成,不过可以定制。 

· 项目中的所有 AID 值有一部分将是 ISO 指定的供应商 ID(称为 RID)。为快速着手操作,IDE 会生成一个随机 RID 值,以便进行开发和测试。如果您有正式的 RID,则可以在“工具”>“选项”中输入该 RID,以便将其用于所有的新项目。单击“项目属性”中的“生成”按钮,可以更新现有项目中的值。 

集成第三方 SDK

目前,工具仅支持 Java Card 3.0.2 引用实现,但是,这些工具有一个可扩展的 API,可用于集成供应商卡。平台和设备定义仅仅是由生成脚本导入的属性文件。 

部署将通过由卡供应商提供的一组 Ant 任务来完成。这意味着创建的项目可以在 IDE 外部运行,而不存在任何锁定。Ant 任务的源文件(Java Card RI 的一部分)以及现成的 NetBeans 样例项目可以从 Java Card 项目门户下载。 

您是创建 Java Card 部署工具的卡供应商吗?请联系插件的创建者 Tim Boudreau ,以获取有关如何集成卡的详细信息!根据您要在 IDE 内为卡提供多少支持,可以在多个级别进行集成。 

 

你可能感兴趣的:(Java-Card-技术简介)