JavaCard小应用程序结构

 
Java Card 小应用程序结构
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
...
MyApplet() {...} // Constructor
// Life-cycle methods
install() {...}
select() {...}
deselect() {...}
process() {...}
// Private methods
...
}
 
         一个 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
CLA
INS
P1
P2
Lc
Data Field
Le (size of response)
Get   Balance
0x80
0x30
0
0
N/A
N/A
2
 
       虽然 Get Balance 命令未定义输入数据,但是有一些命令 APDU 将定义输入数据。举例来说,让我们定义验证从卡片读取器中传递来的 PIN 号码的 Verify PIN APDU 命令。下一个表格定义 Verify APDU
       表格 2- Verify APDU 命令
Name
CLA
INS
P1
P2
Lc
Data Field
Le (size of response)
Verify PIN
0x80
0x20
0
0
PIN Len
PIN Value
N/A
       注意 Le 字段,响应的大小是 N/A 。这是因为没有到 Verify PIN 的应用程序特定响应;成功或者失败通过响应 APDU 中的状态字标明。
为了简化 APDU 过程, javacard.framework.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;
...
         接下来,让我们定义小应用程序构造器和生命循环方法。
       构造器
       定义一个初始化这个对象的状态的私有构造器。这个构造器被从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;
}
        
         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();
}
         在我们的示例中,我们重置了 PIN (个人识别号码)。
       process() 方法 -- 感受 APDU 的全过程
       一旦一个小应用程序已经被选择,它将准备接收命令 APDUs ,如在本文第一部分中 "Java Card 小应用程序的生命周期 " 描写的。
       回想一下被从主机端(客户端)应用程序发送到卡片的 APDU 命令,如下面的说明:
Figure 3. APDU 指令和响应流程
      
       每次 JCRE 接收一个 APDU 命令(通过卡片读取器从主应用程序,或者如果使用 Sun Java Card Development 工具箱就通过 apdutool ),它调用小应用程序的 process() 方法,把输入命令当作一个参数传送给它( APDU 命令输入缓冲中的参数)。 process() 方法然后:
 
Ø         1. 摘录 APDU CLA INS 字段
Ø         2. 检索应用程序特定的 P1 P2 和数据字段
Ø         3. 处理 APDU 数据
Ø         4. 生成并发送一个响应
Ø         5. 优雅地返回,或者抛出相应的 ISO 异常
在此时, JCRE 发送合适的状态字回到主应用程序,通过读卡器。
列表 8 显示一个样本 process() 方法。
/**
* Called by the JCRE to process an incoming APDU command. An
* applet is expected to perform the action requested and return
* response data if any to the terminal.
*
* Upon normal return from this method the JCRE sends the ISO-
* 7816-4-defined success status (90 00) in the APDU response. If
* this method throws an ISOException the JCRE sends the
* associated reason code as the response status instead.
* @param apdu is the incoming APDU.
* @throw ISOException if the process method fails.
*/
public void process(APDU apdu) throws ISOException {

// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();

// Get the CLA; mask out the logical-channel info.
buffer[ISO7816.OFFSET_CLA] =
(byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0xFC);

// If INS is Select, return - no need to process select
// here.
if ((buffer[ISO7816.OFFSET_CLA] == 0) &&
(buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) )
return;

// If unrecognized class, return "unsupported class."
if (buffer[ISO7816.OFFSET_CLA] != MyAPPLET_CLA)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

// Process (application-specific) APDU commands aimed at
// MyApplet.
switch (buffer[ISO7816.OFFSET_INS]) {

case VERIFY_INS:
verify(apdu);
break;

case GET_BALANCE_INS:
getBalance(apdu);
break;

default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
}
}
 
         我们的 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);
}
 
         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);
}
         这个方法通过调用 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);
}
...
         由于每个大块被读取,小应用程序可以把它添加到另一个缓冲中,否则仅仅处理它。
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值