JavaCard小应用程序结构

王朝厨房·作者佚名  2007-01-04
窄屏简体版  字體:   |    |    |  超大  

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

一个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命令

NameCLAINSP1P2 LcData FieldLe (size of response)

GetBalance0x80 0x30 0 0 N/AN/A 2

虽然Get Balance命令未定义输入数据,但是有一些命令APDU将定义输入数据。举例来说,让我们定义验证从卡片读取器中传递来的PIN号码的Verify PIN APDU命令。下一个表格定义Verify APDU:

表格2- Verify APDU命令

NameCLAINSP1 P2 Lc Data FieldLe (size of response)

Verify PIN 0x800x200 0 PIN LenPIN ValueN/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,如在本文第一部分中"Java Card小应用程序的生命周期"描写的。

回想一下被从主机端(客户端)应用程序发送到卡片的APDU命令,如下面的说明:

Figure 3. APDU 指令和响应流程

每次JCRE接收一个APDU命令(通过卡片读取器从主应用程序,或者如果使用Sun Java Card Development工具箱就通过apdutool),它调用小应用程序的process()方法,把输入命令当作一个参数传送给它(APDU命令输入缓冲中的参数)。process()方法然后:

1.摘录APDU CLA和INS字段

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

3.处理APDU数据

4.生成并发送一个响应

5.优雅地返回,或者抛出相应的ISO异常

在此时,JCRE发送合适的状态字回到主应用程序,通过读卡器。

列表8显示一个样本process()方法。

/**

* Called by the JCRE to process an incoming APDU command. An

* applet is expected to perform the action requested and return

* response data if any to the terminal.

*

* Upon normal return from this method the JCRE sends the ISO-

* 7816-4-defined success status (90 00) in the APDU response. If

* this method throws an ISOException the JCRE sends the

* associated reason code as the response status instead.

* @param apdu is the incoming APDU.

* @throw ISOException if the process method fails.

*/

public void process(APDU apdu) throws ISOException {

// Get the incoming APDU buffer.

byte[] buffer = apdu.getBuffer();

// Get the CLA; mask out the logical-channel info.

buffer[ISO7816.OFFSET_CLA] =

(byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0xFC);

// If INS is Select, return - no need to process select

// here.

if ((buffer[ISO7816.OFFSET_CLA] == 0) &&

(buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) )

return;

// If unrecognized class, return "unsupported class."

if (buffer[ISO7816.OFFSET_CLA] != MyAPPLET_CLA)

ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

// Process (application-specific) APDU commands aimed at

// MyApplet.

switch (buffer[ISO7816.OFFSET_INS]) {

case VERIFY_INS:

verify(apdu);

break;

case GET_BALANCE_INS:

getBalance(apdu);

break;

default:

ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);

break;

}

}

列表8、process()小应用程序生命周期方法

我们的process()方法调用getBalance()和verify()方法。列表9显示getBalance ()方法,处理get balance APDU并且返回保存在卡片中的余额。

/**

* Retrieves and returns the balance stored in this card.

* @param apdu is the incoming APDU.

*/

private void getBalance(APDU apdu) {

// Get the incoming APDU buffer.

byte[] buffer = apdu.getBuffer();

// Set the data transfer direction to outbound and obtain

// the expected length of response (Le).

short le = apdu.setOutgoing();

// If the expected size is incorrect, send a wrong-length

// status word.

if (le != GET_BALANCE_RESPONSE_SZ)

ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

// Set the actual number of bytes in the response data field.

apdu.setOutgoingLength((byte)GET_BALANCE_RESPONSE_SZ);

// Set the response data field; split the balance into 2

// separate bytes.

buffer[0] = (byte)(balance >> 8);

buffer[1] = (byte)(balance & 0xFF);

// Send the 2-byte balance starting at the offset in the APDU

// buffer.

apdu.sendBytes((short)0, (short)GET_BALANCE_RESPONSE_SZ);

}

列表9、处理Get Balance APDU

getBalance ()方法通过调用APDU.getBuffer ()方法取得一个引用到APDU缓冲。在返回响应(当前余额)之前,小应用程序必须设置JCRE模式通过调用APDU.setOutgoing()方法来发送,方便地返回期望的响应大小。我们还必须设置响应数据字段中的字节的实际数字,通过调用APDU.setOutgoingLenth()。APDU缓冲中的响应事实上通过调用APDU.sendBytes ()发送。

小应用程序不直接发送返回码(状态字);一旦小应用程序调用APDU.setOutgoing ()并且提供任何请求的信息,JCRE注意这个状态字。状态字的值依靠process()方法如何使返回到JCRE来变化。如果所有的已经正常运行,JCRE将返回9000,指明无错。你的小应用程序可以通过抛出一个定义在ISO7816接口中的异常返回一个错误代码。 在列表9中,如果期望响应的大小不正确,方法getBalance()抛出一个ISO7816.SW_WRONG_LENGTH代码。对于有效的状态码值,请参阅ISO7816接口的定义,或者回到本文第一部分的"响应APDU"。

现在让我们看看在列表10中的verify()方法。因为我们定义的验证PIN APDU命令包含数据,verify()方法必须调用APDU.setIncomingAndReceive ()方法,设置JCRE为接收模式,然后接收输入数据。

/**

* Validates (verifies) the Owner’s PIN number.

* @param apdu is the incoming APDU.

*/

private void verify(APDU apdu) {

// Get the incoming APDU buffer.

byte[] buffer = apdu.getBuffer();

// Get the PIN data.

byte bytesRead = (byte)apdu.setIncomingAndReceive();

// Check/verify the PIN number. Read bytesRead number of PIN

// bytes into the APDU buffer at the offset

// ISO7816.OFFSET_CDATA.

if (ownerPin.check(buffer, ISO7816.OFFSET_CDATA, byteRead)

== false )

ISOException.throwIt(SW_PINVERIFY_FAILED);

}

列表10、处理验证APDU

这个方法通过调用APDU.getBuffer()取得一个到APDU缓冲的引用,调用APDU.setIncomingAndReceive()来接收命令数据,从输入的APDU缓冲中取得PIN数据,并且验证PIN。一个验证失败导致状态码6900被发送回主应用程序。

有时输入的数据比填充到APDU缓冲中的数据要多,并且小应用程序必须大块的读取数据知道没有数据可以读取。在此情况下,我们必须首先调用APDU.setIncomingAndReceive(),然后重复地调用APDU.receiveBytes(),直到不再有数据可用。列表11显示如何读取大量输入数据。

...

byte[] buffer = apdu.getBuffer();

short bytes_left = (short) buffer[ISO.OFFSET_LC];

short readCount = apdu.setIncomingAndReceive();

while (bytes_left > 0) {

// Process received data in buffer; copy chunk to temp buf.

Util.arrayCopy(buffer, ISO.OFFSET_CDATA, tbuf, 0, readCount);

bytes_left -= readCount;

// Get more data

readCount = apdu.receiveBytes(ISO.OFFSET_CDDATA);

}

...

列表11、读取大量输入数据

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

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航