Java卡

JavaCard应用程序开发三部曲 - 主应用程序开发

2020-12-05 11:04:49 明申科技 1143

1、JavaCard 应用程序的组成元素

JavaCard 应用程序的组成元素

JavaCard 应用程序不是独立的,而是一个端对端的应用程序的一部分:

明申智能卡电子标签,IC卡厂家定制

Figure 1.JavaCard 应用程序的典型组成

一个典型的 JavaCard 应用程序由以下部分组成:

1、提供访问例如保存在数据库中的安全或者电子付款信息的 back-office 服务的后端应用程序。后端应用程序如何开发超出了本文的范围。

2、在卡外,驻留在一个卡片终端上,主应用程序使用许多用于卡片访问的接口之一来访问智能卡上的小应用程序,例如 Java Card RMI、OpenCard Framework 应用编程接口或者 Security and Trust Services 应用编程接口( SATSA)。

3、读卡器,卡片终端或者卡片接收设备,提供主应用程序和卡上小应用程序之间的物理接口。

4、卡上的是 Java Card 小应用程序和 Java Card 框架。注意,在访问小应用程序之前,主应用程序必须提供证书并且验证自己。

编写一个主应用程序-访问你的小应用程序

客户端上的主应用程序处理用户、JavaCard 小应用程序和供应商的后端应用程序之间的通讯。

主程序访问你的小应用程序提供的服务。它存在于终端或者卡片接收设备上,例如一个工作站、一个售货点( POS)终端、一个手提电话或者一个机顶盒。回想一下一个主机应用程序和小应用程序使用 ISO - 7816 APDU 命令经由读卡器或终端进行交互。

传统的读卡端应用程序使用 C 语言编写,但是主机程序可以使用 Java 程序语言或者其他语言编写,只要它能够与小应用程序交换有效的 ISO - 7816 APDU 命令。

现在部署的大部分的手提电话整合一个智能卡阅读器访问捆绑在它上面的 SIM 卡。使用即将到来的 JSR 177、用于 J2ME 的安全和信任服务应用编程接口(SATSA)和 J2ME 设备的广泛采用,我们可以想象有许多主应用程序将使用移动设备上的 Java 技术编写。SATSA 的意图是启动一个运行在基于 J2ME 的设备上的 Java Card 主应用程序。JSR 177 目前处在 JCP 团体审查阶段。

当你编写客户端应用程序的时候,有三个主要的应用程序编程接口可用:OpenCard Framework、JavaCard RMI Client 应用编程接口和安全与信任服务应用编程接口( SATSA)。我们将依次看看这些应用程序编程接口。

2、OpenCard 框架介绍

OpenCard 框架介绍

智能卡供应商一般不仅提供开发工具箱,而且提供支持读取端应用程序和 JavaCard 小应用程 序的应用程序编程接口。许多供应商支持 OpenCard 框架( OCF),这是一套基于 Java 的应用程序编程接口,把一些来自不同的供应商的与读卡器交互的细节隐藏起来。

OpenCard 联盟是一群推动 OpenCard 框架定义与采用的公司,目前 OpenCard 框架是 1.2 版本。OCF 的目标是提供给主机端应用程序的开发者跨不同的卡片读取器供应商工作的应用编程接口。

OCF 为你定义许多标准卡服务。其中两个是 FileAccessCardService 和 SignatureCardService。

一个特殊的类型是 ApplicationManagerCardService,提供了在卡上安装、注册和删除小应用程序的生命周期管理方法。

当编写一个主机端基于 OCF 的应用程序时,你基本上要把它分离成两个部分:

1、和终端或者读取器交互的主应用程序对象(初始化 OCF,等待卡片插入并且终止 OCF),并且能够显露高级的卡片访问方法,例如 getBalance ()。

2、一个实现实际的低级通道管理和 APDU 输入/输出的小应用程序代理。当把 APDU 细节从应用程序中隐藏起来的时候,这个代理(Proxy)设计模式允许你显露一个面向对象接口。

明申智能卡电子标签,IC卡厂家定制

Figure 2. OCF 应用程序的结构

总之,一个典型的 OCF 应用程序具有一个或多个 main 对象,都是在主机上创建,可能再它们自己执行的线程上。这些 main 应用程序对象显露了高级的特定应用程序调用,这些最终都被委托给小应用程序代理。它们使用 SmartCard 对象,这是应用程序到 OCF 的入口点,启动应用程序来初始化并且关闭 OCF,并且等待卡片被插入。main 对象可以实现一个 CTListener,你不久将看到,这个监听者提供了诸如卡片插入和拔出等事件的异步标志信息。

你可以使用一个同步或者异步模型编写你的应用程序。

在同步模型中,你的主应用程序初始化 OCF,然后等待卡片被插入。然后它执行你的 main 应用程序逻辑,并且在完成的时候关闭 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();

}

}

...

列表⒈一个同步 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()方法。在下面的代码中,插入卡片初始化小应用程序代理的创建,并且拔出卡片触发小应用程序代理的清除。代码列表还说明了信用卡余额请求代理。

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

  }

}

...

}

列表⒊一个增强的基于监听者的 OCF 应用程序

接下来是小应用程序代理的选节。OCF 应用程序委托服务调用到小应用程序代理,实现复杂的APDU 管理模块:

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、一个小应用程序代理示例

你可以在 OpenCard Framework 1.2 Programmer ' s Guide(http://www.opencard.org/docs/pguide/PGuide.html)中获得更多的关于 OCF 使用的消息。还引用了 OpenCard Framework 引用实现(http://www.opencard.org/index-download.shtml)中的示例文件。

本文中未涉及,但是值得一提的是称为 Global Platform( http://www.globalplatform.org)的主机端应用程序编程接口。Global Platform Card Committee 提供了一组补充 Java Card 应用程序编程接口的卡片应用程序编程接口,提供了卡片管理特性。这些规范都在 2.1.1 版本中。

3、RMI 客户端编程接口

JavaCard RMI 客户端应用编程接口

前面你已经学到了如何编写一个基于 JCRMI 的小应用程序。如果你的 Java Card 小应用程序基于 JCRMI,你可以使用 Java Card RMI 客户端应用编程接口编写一个主应用程序,访问智能卡中保存的小应用程序对象。

为了智能卡管理和访问,JCRMI 客户端应用编程接口需要一个卡片终端和诸如刚刚描述的OpenCard Framework 这样的服务应用编程接口。

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

1、不必知道智能卡和读卡器的细节

2、不必知道低级的 APDU 通讯

3、便于设计和维护代码,缩短开发时间

JCRMI 客户端应用编程接口在下面的程序包中定义:

1 com.sun.javacard.javax.smartcard.rmiclient 包含核心 JCRMI 客户端应用编程接口。它定义:

    1)JCRMI 代码程序用来访问智能卡的 CardAccessor 接口。

    2)用于 JCRMI 程序生成实现的基本类 CardObjectFactory 类。这个类的实例与一个 Java Card小应用程序选择的会话有关。

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

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

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

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

4javacard.security 定义了描述一个有关加密异常的 CryptoException。

4、生成 RMI 客户端程序

生成 RMI 客户端程序

你可以使用标准的 Java RMI 编译程序(rmic)生成客户端程序。你必须使用下面格式的命令运行 rmic,用于你的小应用程序中的每个远程类: rmic -v1.2 -classpath path -d output_dir class_name

在这里:

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

2 - classpath 路径标明到远程类的路径。

3 output_dir 是存放结果程序的目录。

4 class_name 是远程类的名称。

然而,推荐生成 RMI 客户端程序的方法是使用 J2SE SDK 1.3 中的动态代理生成机制。如果当你选择 JCRMI 小应用程序的时候使用 CardObjectFactory 子类型 JCCardProxyFactory 的话,JavaCard RMI 客户端应用编程接口的 2.2 版本将为你自动生成程序,你不必再生成程序。这个方法在列表 5 中说明。

用法限制

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

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

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

JCRMI 客户应用程序

一个 JCRMI 客户应用程序类似你前面看到的 OCF 主应用程序,因为 JCRMI 客户端应用编程接口依靠 OCF 用于卡片管理和通讯。

下面的代码片断首先初始化 OCF,并且等待智能卡插入。它然后创建一个 OCFCardAccessor实现,用于把我们的 JCRMI 连接到卡片上,如果必要,客户端程序动态生成,小应用程序被选中,我们取得远程引用,最后我们产生到 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(UserExceptione) { 

  // Handle exception

  ...

}

catch (Exception e){

  //Handle exception

  ...

} finally {

  //Clean up

  try{

    SmartCard.shutdown();

  }catch (Exception e){

  System.out.println(e);

  }

}

列表 5、示例 JCRMI 客户端

如你所见,你必须让代码简化。

5、用于 J2ME 的安全和信任服务编程接口

用于 J2ME 的安全和信任服务编程接口

SATSA 是一套用于 J2ME 的新的可选程序包,定义一个客户端应用编程接口来访问安全元素:

例如智能卡这样的设备。在本节,我将仅仅介绍 SATSA 的通讯应用编程接口;本文中将不涉及SATSA PKI 和加密应用程序编程接口。

SATSA 通讯应用编程接口被分解成下面几部分:

1 SATSA-APDU 定义一个应用编程接口,用于和遵循 ISO-7816-4 的智能卡通讯。这个可选程 序包由单独的 javax.microedition.io.APDUConnection 程序包组成。

2 SATSA-JCRMI 定义一个 Java Card RMI 客户端应用编程接口。这个可选程序包由下面的Java 程序包组成:

a)javax.microedition.io.JavaCardRMIConnection

b)javax.microedition.jcrmi.RemoteRef

c)javax.microedition.jcrmi.RemoteStub

d)java.rmi.Remote

e)java.rmi.RemoteException

f)javacard.framework.service.ServiceException

g)javacard.framework.CardRuntimeException

h)javacard.framework.ISOException

i)javacard.framework.APDUException

j)javacard.framework.CardException

k)javacard.framework.PINException

l)javacard.framework.SystemException

m)javacard.framework.TransactionException

n)javacard.framework.UserException

o)javacard.security.CryptoException

SATSA 把 J2ME 和 Java Card 平台紧密的结合在一起。SATSA 使用 CLDC 1.0 Generic Connection Framework ( GCF)用于在基于 J2ME 的设备和一个如下面所图解的智能卡之间通讯:

明申智能卡电子标签,IC卡厂家定制

Figure 3. 普通连接和 SATSA 连接

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

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

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

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

6、指定 SATSA 连接类型

指定 SATSA 连接类型

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

scheme:[target][params]

在这里:

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

2 target 一般是某种网络地址。

3 params 是可选参数,通过分号隔开。

对于 SATSA,URL 的格式是:

  protocol:[slotID]; AID

在这里:

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

2 slotID 是指明卡片插入的插孔号。slotID 字段是可选的;默认值为 0。

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

7、使用 APDUConnection

使用一个 APDUConnection

一个 APDUConnection 定义方法,允许我们使用 GCF 与遵循 ISO-7816 的卡片通讯。它定义三个方法:

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

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

3 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 的格式和你在本系列第二部分《JavaCard 小应用程序开发教程》看到的相同,告诉你如何编写一个基于 APDU 消息传送的 Java Card 小应用程序。

8、JavaCardRMIConnection

使用一个 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 应用编程接口,允许我们调用本系列第二部分(《JavaCard 小应用程序开发教程》)定义的 getBalance()方法,向你说明如何编写一个基于 RMI 的 JavaCard 小应用程序。

首页
产品
新闻
联系