JAVA 智能的可执行文件(CAP 文件)是编译多个应用程序(Applet)的生成结果,包含了一个包中定义的所有类和接口,与包之间是一一对应的关系。实际发卡操作时,首先需要将该可执行文件下载至卡片中,并安装需要的应用实例;用户使用该安装的应用实例执行操作功能。
CAP文件包含12个组件:
Component Type |
Value |
COMPONENT_Header |
1 |
COMPONENT_Directory |
2 |
COMPONENT_Applet |
3 |
COMPONENT_Import |
4 |
COMPONENT_ConstantPool |
5 |
COMPONENT_Class |
6 |
COMPONENT_Method |
7 |
COMPONENT_StaticField |
8 |
COMPONENT_ReferenceLocation |
9 |
COMPONENT_Export |
10 |
COMPONENT_Descriptor |
11 |
COMPONENT_Debug |
12 |
注意:
一个完整的CAP文件,除Applet、Export 和Debug组件是可选外,其他均为必选。每个组件封装成一个CAP包,包含在Jar包中。最后在卡上只保留了5个组件:COMPONET_Method,COMPONET_Class,COMPONET_ConstantPool,COMPONET_StaticField和 COMPONET_Export。其余的组件只是安装时提取有用信息而不在卡中保存。
12个组件中,类class组件保存本应用声明的所有类和接口的信息; 方法method组件保存本应用声明的所有方法和接口,method中利用2字节索引index引用类、方法和域;常数池constant pool组件保存method组件引用的所有类、方法和域信息,分为类、实例域、虚方法、父方法、静态域和静态方法6类,每组信息为4个字节;相关地址reference location组件保存method组件中索引的偏移。
对于JavaCard而言,应用程序的下载过程是即CAP文件写入到EEPROM的过程,即是对CAP文件的下载过程。在CAP文件的下载过程中,需要将一部分组件进行解析,同时对reference location中指定的位置进行链接,能够链接到method组件中的一个索引号,并根据索引号查找constant pool中保存的、与该索引号对应的类、方法或域在 EEPROM中的实际地址,调用实际地址中存储的数据。也就是说,方法的调用其实是需要两个步骤来实现的:
- 根据reference location中指定的位置进行链接,获取method组件中的索引号;
- 根据索引号查找constant pool中保存的、与该索引号对应的类、方法或域在EEPROM中的实际地址,调用实际地址中存储的数据。
查看一个CAP文件的组件,可以通过两种方法实现:
- 解压缩软件直接解压得到
- 通过JCK中的capdump将其分解成各个组件
下面是加压cap的java源码:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.os.Environment;
import android.util.Log;
public class Cap {
static final String TAG = "cap";
static public final String CAP_DIRECTORY = "//mnt//sdcard//cap//";
static final int READ_LENGTH = 1024;
private List<String> mCapFileList;
public void loadCapFile(String CapFileName) {
try {
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
Log.e(TAG, "SD card not available!");
return;
}
File file = new File(CAP_DIRECTORY + CapFileName);
if (!file.exists()) {
Log.e(TAG, CAP_DIRECTORY + "safe.cap not available!");
return;
}
mCapFileList = new ArrayList<String>();
mCapFileList.clear();
BufferedOutputStream dest = null;
BufferedInputStream is = null;
ZipEntry entry;
ZipFile zipfile = new ZipFile(file);
Enumeration<? extends ZipEntry> e = zipfile.entries();
while (e.hasMoreElements()) {
entry = (ZipEntry) e.nextElement();
String fileName = entry.getName();
if (fileName.endsWith(".MF"))
continue;
if (fileName.contains("/")) {
int dir = fileName.lastIndexOf("/");
String root = CAP_DIRECTORY + fileName.substring(0, dir);
File path = new File(root);
if (!path.exists()) {
path.mkdirs();
}
}
int count;
byte data[] = new byte[READ_LENGTH];
mCapFileList.add(CAP_DIRECTORY + entry.getName());
FileOutputStream fos = new FileOutputStream(CAP_DIRECTORY
+ entry.getName());
dest = new BufferedOutputStream(fos, READ_LENGTH);
is = new BufferedInputStream(zipfile.getInputStream(entry));
while ((count = is.read(data, 0, READ_LENGTH)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
is.close();
fos.close();
}
} catch (Exception e) {
Log.e(TAG, "loadCap: " + e.getMessage());
}
}
public List<String> getCapFileList() {
return mCapFileList;
}
}
使用方法
Cap cap = new Cap();
cap.loadCapFile("HelloWorld.cap");
就可以解压出各个组件,他们也是cap文件。
所有的组件均有通用结构格式,如下:
component {
u1 tag //u1表示无符号单字节类型的数据变量类型;tag为组件索引号,按照上面组件名称的顺序从1至12排列
u2 size //u2表示无符号双字节的数据变量类型;size为可变长度数组info[]的元素个数
u1 info[] //数组info[]中含了组件的所有信息,依据各组件属性不同而各不相同
}
比如Applet.cap的十六进制数据是:
0300090105c0c1c2c3c40085
第一个03是tag,然后是0009是size,说明info为9个字符,01标示了这是包中的第一个applet,05为该Applet的AID的长度,后面的c0c1c2c3c4即为applet的AID,后面的0085是该Applet的install_method_offset,即当前Applet中的install()在Method组件info[]中的偏移。
推荐的CAP组件安装顺序:
- COMPONENT_Header
- COMPONENT_Directory
- COMPONENT_Import
- COMPONENT_Applet
- COMPONENT_Class
- COMPONENT_Method
- COMPONENT_StaticField
- COMPONENT_Export
- COMPONENT_ConstantPool
- COMPONENT_ReferenceLocation
- COMPONENT_Descriptor (optional)
COMPONENT_Debug组件不需要下载到卡内。
作者:fish