JavaCard小应用程序结构

来源:互联网 发布:焊接仿真软件对比 编辑:程序博客网 时间:2024/05/21 09:20
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

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

 

  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;

  ...

  列表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、读取大量输入数据

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

<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>