第 4章 Java 卡对象
Java 卡技术不支持对象序列化和 transient 关键字。
4.2.永久对象
永久对象的存储器和数据是跨 CAD sessions 保留的。一个永久对象具有下列属性:
z 一个永久对象是利用 new 操作符建立的
z 一个永久对象的状态和值是跨 CAD sessions 保持不变的。
z 对一个永久对象的单一域的任何修改都是原子型的。即,如果在本修改过程中掉电或发生故
障,这个域被恢复为其原来的值。
z 一个永久对象可由一个临时对象中的一个域引用。
z 一个永久对象中的域可以引用临时对象。
z 如果一个永久对象不被其它对象引用,它便成为不可接近的,或可作为垃圾收集的对象。
4.3.临时对象
临时对象这个词有点用词不当。它可能被不正确地解释为对象本身是临时的:当去电的时候,临
时对象就被废掉。事实上,临时对象这个词意味着该对象的域的内容具有临时性的属性。和永久对象
一样,为临时对象分配的空间是被保留的并且不能被回收,除非系统实现了一个垃圾收集器。
一个 applet应当在其生命期中对一个临时对象只建立一次,并且应将该对象的引用保存在一个永
久域中,如图 4.1 所示。在卡片下次加电时,applet 利用同一个对象引用访问该临时对象,即使前一个
session 的对象数据已经消失。
4.3.1.临时对象的属性
在 Java 卡 2.1 中,只有具有基本类型成分的数组或具有对象引用类型成分的数组才可能声明为临
时的。Java 卡平台中的基本类型为 byte、short、int,和 boolean 类型。本书通篇交替使用临时对象和
临时数组这两个词。在 Java 卡平台中,一个临时对象具有下列属性:
z 一个临时对象是通过调用 Java 卡 APIs 来建立的。
z 在两个 CAD sessions 之间,临时对象不保存其状态和值。在某些事件发生时,一个临时对象
的域被清为其缺省值(0、false,或 null)。
z 对一个临时对象中的一个域的任何修改都不是原子型的。即,如果在一次修改中卡片掉电或
出错,该域不会被恢复为其原来的值。如果把对一个临时域的写操作包含在一个事务中(见
第 5 章) ,一个废除的事务决不会将一个临时对象中的域恢复为其原来的值。
z 一个临时对象可由一个永久对象中的一个域引用。
z 一个临时对象中的域可由一个永久对象引用。
z 如果一个临时对象不被其它对象引用,它将成为不能接近的,或可被垃圾收集器收集。
z 对一个临时对象中的域的写操作没有性能方面的损失,因为 RAM 较 EEPROM 有快得多的写
周期。
临时对象的属性使之对于少量的,要经常修改的,但无需在 CAD sessions 间保留的临时 applet 数
据是很理想的对象。Applet 开发者应保证把这样的临时数据存放在临时数组中。这就会减少对永久存
储器的潜在损耗、保证良好的写性能,并且增加关于要保护的敏感数据的安全性。凭经验,如果每一
个 APDU 命令都要对一个数据修改多次的话,applet 开发者就应将它拿到一个临时数组中。
4.3.2.临时对象的类型
有两种类型的临时数据对象,即 CLEAR_ON_RESET 和 CLEAR_ON_DESELECT。不管是那种
类型的临时对象,都与一个事件相关联,当这个事件发生的时候,它就使 JCRE 清除该对象的域。
CLEAR_ON_RESET 临时对象被用作保持那些需要在各 applet 选择之间保存,但不需要在每次卡
片复位之间保存的数据。例如,一个卡片主 session 密钥应被声明为 CLEAR_ON_RESET 类型的,
这样一来,同一个 session 密钥可在一次 CAD session 期间被选择的各个 applets之间共享。当卡片被
复位时,CLEAR_ON_RESET 临时对象的域被清除。发送给卡片电路的复位信号(暖复位)或将电源
从断切换到开都可导致卡片重新复位。
CLEAR_ON_DESELECT 临时对象用作维护那些必须在 applet 被选择期间保留而无需跨其它
applet选择或卡片复位。 例如, 一个applet 拥有的 session密钥就需要被声明为CLEAR_ON_DESELECT
类型的临时对象。这样,当该 applet 被关闭(deselect)时,该 session 密钥就自动由 JCRE 清除。这
是一种安全防范措施,使另一个 applet 不能看到此 session 密钥数据并假冒原先选择的拥有该密钥对
象的 applet。
因为卡片复位隐含着关闭当前选择的 applet,CLEAR_ON_DESELECT 对象的域也由为
CLEAR_ON_RESET 制定的相同事件清除。换言之, CLEAR_ON_DESELECT 对象也是
CLEAR_ON_RESET 对象。另外,CLEAR_ON_DESELECT 临时对象还具有与 applet 防火墙有关的其
它属性(祥见第 9 章) 。
4.3.3.建立临时对象
在 Java 卡技术中,临时对象是利用 JCSystem 类中的工厂方法之一来建立的,如表 4.1 所示:
每个方法调用中的第一个参数 length 指出所请求的临时数组的长度。第二个参数 event 指
出哪种事件清除该对象。因此,方法调用指出临时数组的类型,CLEAR_ON_RESET 或
CLEAR_ON_DESELECT。类 JCSystem 中的两个常数被用来表示临时数组的类型:
//CLEAR_ON_RESET 类型的临时数组
public static final byte CLEAR_ON_RESET
//CLEAR_ON_DESELECT 类型的临时数组
public static final byte CLEAR_ON_DESELECT
下列代码段建立一个 CLEAR_ON_DESELECT 数组:
byte[] buffer =
JCSystem.makeTransientByteArray(BUFFER_LENGTH,
JCSystem.CLEAR_ON_DESELECT);
4.3.4.查询临时对象
一个 applet可能需要访问由其它 applet 建立的对象。类 JCSystem 为 applet 提供了一种方便的查
询方法,以确定是否一个正被访问的对象是临时的:
public static byte isTransient(Object theObject)
方法 isTransient 返回一个临时类型常数(CLEAR_ON_RESET 或 CLEAR_ON_DESELECT) ,或
者返回常数 JCSystem.NOT_A_TRANSIENT_OBJECT 指出该对象是 null 或者是一个永久对象。
4.4.关于对象建立和删除的几句话
因为智能卡中的存储器是很少的,不管是永久对象还是临时对象都不应杂乱无章地建立。当一个
applet力图利用new操作符建立一个永久对象的时候,如果没有足够的永久存储器可用的话,JCRE就以
原因码 1
JCSyste.NO_RESOURCE 抛出一个 SystemException 例外。当一个 applet 调用了
make-transient-object 方法之一而没有足够的 RAM 空间可用时, JCRE 便以原因码
JCSystem.NO_TRANSIENT_SPACE 抛出一个 SystemException 例外。
一旦建立了永久和临时对象,只要从栈、类的静态域、其它现有对象的域,或从 JCRE 引用它们,
便可访问到这些对象。当对一个对象的所有访问都被丢弃的时候,该对象便成为不可抵达的了。是否
由该对象所占用的空间能被回收取决于是否在虚拟机中实现了垃圾收集器。Java 卡技术不要求 JCRE
实现必须包括垃圾收集器,因为在低端智能卡中这样作是不现实的。第 13 章提供了在不支持垃圾收
集器时如何在 applets 中重用对象的提示。
第 5章 原子性和事物
智能卡正在诸如存储个人秘密数据和在移动和分布式环境中提供认证服务等应用领域里成为一种
优选设备。然而在使用智能卡的时候,在 applet 执行的任何时刻都存在着发生故障的风险。故障的原
因可能因通信错误,或更经常地是卡片的用户不经意地从 CAD 拔出卡片,从而导致断开向卡片 CPU
的供电并终止任何 applet 的执行。这种提早地从 CAD 取出智能卡的做法被称为拔出(tearing) ,或拔
卡。这种非完整执行的风险对保护智能卡中敏感数据操作的完整性提出了挑战。
JCRE 为保证原子操作提供了一种鲁棒机制。这种机制在两个层次上得到支持。首先,Java 卡平
台保证对永久对象域的修改或者对一个类域的修改是原子性的。第二, Java 卡平台支持一种事务模式,
按这种模式,一个 applet 可以把一组修改组织成一个事务。通过这种模式,保证了所有这些操作的原
子性。
这一章解释在Java卡平台中原子性意味着什么以及applet开发者如何才能利用保护数据完整性的
机制来编写 applet 程序。
5.1.原子性
在 Java 卡平台上,原子性意味着保证对一个永久对象域(包括数组元素)或对一个类域的修改要
么成功地完成,要么如果在修改过程中发生错误时就恢复为其原来的值。例如,一个对象中的某个域
当前含有值 1,并正在以值 2 来修改它。正当卡片在重写这个域的关键时刻,卡片被不经意地从 CAD
中拔出来。在重新加电的时候,这个域不会留下一个随机值,而是被恢复为其原先值 1。
原子性的概念只适用于永久存储器的内容。它定义在修改一个数据元素期间掉电或发生其它错误
的情况下,JCRE 如何处理这个数据元素。JCRE 的原子性特性不适用于临时数组。修改临时数组元素
的操作在掉电的情况下并不保留该元素的原先值。下次卡片插到 CAD 时,一个临时数组的诸元素被
置为其缺省值(0、false,或 null)。
5.2.数组中块数据修改
类 javacard.framework.Util 提供了一个方法 arrayCopy,它保证一个数组中的多个数据元素成块修
改的原子性:
public static short arrayCopy(byte[] src,short srcOff,byte[] dest,short desOff,short length);
Util.arrayCopy方法保证要么所有的字节被正确地拷贝,要么目标数组被恢复为其原来的字节值。
如果该目标数组是临时的,就不具有原子特性。
然而,arrayCopy需要额外的 EEPROM写操作来支持原子性,并因此它是很慢的。一个 applet 可
以不要求对数组修改的原子性。为此,提供了一个 Util.arrayCopyNonAtomic方法:
public static short arrayCopyNonAtomic(byte[] src,short srcOff,
byte[] dest,short desOff,short length);
方法 arrayCopyNonAtomic 在拷贝操作过程中不使用事务设施,即使一个事务正在执行之中。因
此,仅当在拷贝操作的中间出现掉电事件时,容许目标数组的内容可以处于部分修改状态的时候,才
应当使用这个方法。一个类似的方法,Util.arrayFillNonAtomic,用指定的值填入一个字节数组的诸元
素:
public static short arrayFillNonAtomic(byte[] bArray,short bOff,short bLen,byte bValue);
5.3.事物
原子性保证一个单一数据元素的原子修改。然而,一个 applet 可能需要原子地修改若干不同的对
象中的若干不同域。例如,一个贷记或借记交易可能要求一个钱包 applet 增加交易计数器、修改钱包
余额,以及写交易日志,要把所有这些操作作为一个原子的工作单位。
读者可能已经熟悉数据库事物概念的使用,这个概念包括开始、提交,和回退,以保证对多个值
的修改或者全部完成,或者一样都不做。Java 卡技术支持一种类似的事物模式,具有提交和回退能力,
以保证能够原子地执行一些复合的操作;或者它们都成功地完成,或者它们的部分结果全部无效。事
物机制保护这样一些事件:诸如在一个事物处理中间掉电和可能导致数据破坏的程序错误,这些事件
将不会使一个交易的所有步骤都正常完成。
5.3.1.提交事物
一个事物通过调用方法 JCSysten.beginTransaction 开始和通过调用方法 JCSysten.commitTransaction
结束:
//开始一个交易
JCSysten.beginTransaction();
//在该交易提交之前,一组永久数据的修改中的全部修改都是暂时的
……
//提交一个事物
JCSysten.commitTransaction();
一个事物中的修改是有条件的――域或数组元素被修改。读回这些域或数组元素得到它们的最后
的有条件的值,但是在调用方法 JCSysten.commitTransaction之前这些修改并未被执行。
5.3.2.废除事物
applet 或 JCRE 都能够废除事务。如果 applet 遇到某个内部问题,它可调用方法
JCSystem.abortTransaction明确地删除该事务。删除一个事务会使 JCRE 抛弃在该事务执行过程中所做
出的任何修改,并有条件地恢复被修改的域或数组元素为它们原来的值。在调用 abortTransaction 方法
时,必须有一个事务正在执行之中;否则,JCRE 就抛出一个例外 TransactionException。
在从一个具有仍在执行中的事务的 applet 返回(即该 applet既没有明确提交,也未废除一个正在
处理中的事务)之时,JCRE 重新获得程序控制的时后,JCRE 就自动调用 abortTransaction 方法。类似
地,如果在一个事务内抛出一个例外并且 applet 未对其进行处理,JCRE也要废除这个事务。
如果在一个事务处理中掉电或发生错误,在下次卡片上电时 JCRE 就调用其内部的回退设施,将
事务中所涉及的数据恢复为它们的事务之前的值。
在任何情况下,在一个失败的事务(因掉电、卡片复位、计算错误,或程序的废除动作)中所
建立的临时和永久对象都由 JCRE 删除并释放它们的存储空间。
5.3.3.嵌套的事物
不像大多数数据库事务那样,Java卡平台中的事务不能嵌套。一次只能有一个事务在进行。这个
要求是由于智能卡的有限的计算资源所限制的。
如果正当一个事务已经在进行中又调用了方法 JCSystem.beginTransaction,JCRE 就抛出一个
TransactionException 例外。一个 applet 通过调用方法 JCSystem.transactionDepth 就能发现是否有事务
正在进行中。如果某个事务正在进行,该方法返回 1,否则返回 0。
5.3.4.提交能力
为了支持未被处理的事务的回退,JCRE 维护一个 commit buffer,被修改的域的原来内容就存放
在这里,直至事务被提交。万一在事务完成之前发生故障,事务中涉及的域就从该 buffer 恢复成其原
先的内容。在一个事务块内的操作越多,就需要更大的 commit buffer来容纳它们。
commit buffer的大小不同的实现是不一样的,这取决于可用的卡片存储器的大小。一般来说,在
一个 JCRE 的实现中所分配的 commit buffer 足够大,以满足大多数 applet 的需要――一般一个 applet
在一个事务中累计有几十个字节。但是,因为智能卡的资源是有限的,在一个事务中只包括一个逻辑
操作单位中的修改,这一点是很重要的。把太多的事情放在一个事务中也许是不可能的。
在打算执行一个事务之前,一个 applet 可以检查是否可用的 commit buffer的大小能够满足需要作
为一个事务修改的数据块的大小。类 JCSystem提供两个方法来帮助 applets 确定在一个 Java 卡平台实
现中可用的提交能力有多大。
z JCSystem.getMaxCommitCapacity()返回一个 commit buffer中的字节总数。
z JCSystem.getUnusedCommitCapacity()返回在该 commit buffer中剩下未用的字节数。
除了存储一个事物期间被修改的域的内容之外, commit buffer还包含一些其附加的管理数据字节,
例如域的地址。管理数据的数量取决于正被修改的域的数目和事物系统的内部实现。由两种方法返回
的提交能力就是在一个事物中能被修改的永久数据的总字节数(包括管理开销)。
如果在一个事物中超过了提交能力,JCRE 就抛出一个 TransactionException 例外。即使如此,事
物仍在执行中,除非由 applet或 JCRE 明确地废除。
5.3.5.TransactionException例外
如果在一个事物内检测到某种问题(例如嵌套的事物或 commit buffer 溢出),JCRE 就抛出一个
TransactionException 例外。
TransactionException 例外是 RuntimeException 类的一个子类。它提供一个原因码指示该例外的原
因。在第 6章中将解释 Java 卡例外和原因码。下面是在 TransactionException类中定义的原因码:
z IN_PROGRESS-调用了方法 beginTransaction,事物已经在进行中。
z NOT_IN_PROGRESS-调用了方法 commitTransaction 或 abortTransaction,事物不在进行之
中。
z BUFFER_FULL-在一个事物期间,已经启动对永久存储器的修改,但已导致了 commit buffer
溢出。
z INTERNAL_FALURE-在事物系统内部出现了严重的内部问题。
如果 applet 不捕获 TransactionException 例外,它将由 JCRE 捕获。在后一情况下,JCRE 将自动
废除这个事物。
5.3.6.在一个事物期间的局部变量和临时对象
读者应当知道:只有对永久对象的修改才会参与一个事物。对临时对象和局部变量(包括方法参
数)的修改从不会被废除,不管它们是否在一个事物“内部”。局部变量被建立在 Java 卡栈上,栈驻
留于 RAM 之中。
下面的代码段演示关于一个临时数组 key_buffer 的三个拷贝操作。当事物废除的时候,无论数组
拷贝操作还是任何一个对包含在 for循环中的 key_buffer元素的修改都没有受到事物的保护。类似地,
局部变量 a_local 保持新值 1。
Byte[] key_buffer = JCSystem.makeTransientByteArray
(KEY_LENGTH,JCSystem.CLEAR_ON_RESET);
JCSystem.beginTransaction();
Util.arrayCopy(src,src_off,key_buffer,0,KEY_LENGTH);
Util.arrayCopyNonAtomic(src,src_off,key_buffer,0,KEY_LENGTH);
For (byte i = 0; i < KEY_LENGTH; i++)
Key_buffer[i] = 0;
Byte a_local = 1;
JCSystem.abortTransaction();
因为局部变量或临时数组元素不参与事物,建立一个对象并将该对象赋给一个局部变量或一个临
时数组元素不需要认真考虑。这里有一个代码例子:
JCSystem.beginTransaction();
//ref_1 是一个实例(对象)域
ref_1 = JCSystem.makeTransientObjectArray
(LENGTH,JCSystem.CLEAR_ON_DESELECT);
//ref_2 是一个局部变量
ref_2 = new someClass();
//检查状态
if (!condition)
JCSystem.abortTransaction();
Else
JCSystem.commitTransaction();
Return ref_2;
在这个例子中,实例域 ref_1 保存了一个对一个临时对象的引用,而局部变量 ref_2保存了一个对
一个永久对象的引用。如前所述,如果一个事物废除,则在事物中所建立的永久和临时对象均被自动
废弃。这对实例域 ref_1 没有什么影响,如果事物未正常完成,它的内容被恢复为其原来的值。但是
在下一行,当把一个新建立的对象赋给一个局部变量时,就出现了潜在问题。在事物出故障时,JCRE
删除这个对象。然而,ref_2 仍指向哪个不再有对象存在的单元。如果 ref_2 后来被用作一个返回值,
情况就更加糟糕。在这种情况下,调用者就会收到一个悬空的指针。
为了避免产生悬空的指针(这有损于 Java 语言类型的安全性),JCRE保证对在一个被废除的事物
期间建立的对象的引用被置为 null。在这个例子中,如果调用了方法 abortTransaction,局部变量 ref_2
被置为 null。这种状况并不很理想,但是它在使系统开销最小的情况下避免了安全违例。
这个例子并不能用于大多数 applets,因为实在是非常不鼓励在一个方法中随意建立对象。在可能
的时候,一个 applet 应当在其初始化期间分配它所需要的全部对象的空间(见第 7 章)。然而,Java
卡安装程序(installer)的实现者在一个事物中可能需要处理相当可观的对象建立工作,并应避免像在
上述代码中所描述的情况。