文件系统创建—Java Card开发第二篇

首先要知道Java card里面的文件系统结构,也就是有哪些文件:

文件系统创建—Java Card开发第二篇_第1张图片


文件系统创建—Java Card开发第二篇_第2张图片



包括四大类文件,应用基本文件——也就是发卡方官方的一些信息,然后是持卡人个人信息的文件,再就是EP(电子钱包)文件,以及最重要的密钥文件。并且密钥文件必须先于其他三个文件之前创建,因为没密码你弄啥操作不科学呀。

几大文件反应到代码中就是几大数据结构:

文件系统创建—Java Card开发第二篇_第3张图片


理解了文件系统之后就要一一去看各个文件代码模块是怎么实现的:

举个例子:


这是二进制文件数据结构的部分代码,可以看到它的构造函数需要传进一个pdata参数,其实就是要传输apdu命令的data部分给它。由于这几大数据结构代码课上都给得差不多了,而且本篇只讲文件系统的创建,所以目前只需要知道这几大文件数据结构的接口就够了,主要还是看主程序代码,也就是purse.java。嗯,本次java card的开发就是以电子钱包的开发作为项目来学习的。

先直接上主程序purse.java的代码,其他几个java文件的代码在后面学习篇再放:

//主文件:电子钱包,调用了其他几个辅助类。
//注意这些所有的代码和应用都是运行在卡片中的!
//而终端只是在命令行敲几句命令发送的那里.
package purse;

//import Purse;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

public class Purse extends Applet {
	//APDU Object
	private Papdu papdu;
	
	//文件系统,每次run的时候都相当于一张空白卡片,全部需要重新建立,
	//并且建立之后就归当前run的applet所有,某个文件可以随时被调用(keyfile)
	private KeyFile keyfile;            //密钥文件
	private BinaryFile cardfile;       //应用基本文件
	private BinaryFile personfile;     //持卡人基本文件
	private EPFile EPfile;              //电子钱包文件 Electronic Purse
	
	public Purse(byte[] bArray, short bOffset, byte bLength){
		papdu = new Papdu();
		
		byte aidLen = bArray[bOffset];
		if(aidLen == (byte)0x00)
			register();
		else
			register(bArray, (short)(bOffset + 1), aidLen);//注册applet
	}
	//安装applet
	public static void install(byte[] bArray, short bOffset, byte bLength) {
		new Purse(bArray, bOffset, bLength);
	}
	//执行applet
	public void process(APDU apdu) {
		if (selectingApplet()) {
			return;
		}		
		//步骤1:取APDU缓冲区数组引用并将之赋给新建数组
		byte buffer[] = apdu.getBuffer();
		//步骤2:取APDU缓冲区中数据放到变量papdu
		short lc = apdu.setIncomingAndReceive();//将apdu读取到卡片缓冲区当中并返回data段的长度
		papdu.cla = buffer[ISO7816.OFFSET_CLA];
		papdu.ins = buffer[ISO7816.OFFSET_INS];
		papdu.p1 = buffer[ISO7816.OFFSET_P1];
		papdu.p2 = buffer[ISO7816.OFFSET_P2];
		Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, papdu.pdata, (short)0, lc);
		//步骤3:判断命令APDU是否包含数据段,有数据则获取数据长度,并对le赋值,
		 //否则,即不需要lc和data,则获取缓冲区原本lc实际上是le
		 	//获取le的方法,因为不确定papdu有le部分,所以IOS7816下标可选项并没有le而是放在数据块中的.
		 	//如果有数据快,那le就是buffer[ISO7816.OFFSET_CDATA+lc]
		if(papdu.pdata.length != 0)
		{
			papdu.le = buffer[ISO7816.OFFSET_CDATA+lc];
			papdu.lc = buffer[ISO7816.OFFSET_LC];
		}
		else
			papdu.le = buffer[ISO7816.OFFSET_LC];//若没data部分则lc部分实际是le
		boolean rc = handleEvent();
		//步骤4:判断是否需要返回数据,并设置apdu缓冲区	
		if(papdu.le != 0)
		{
			Util.arrayCopyNonAtomic(papdu.pdata, (short)0, buffer, ISO7816.OFFSET_CDATA, (short)papdu.pdata.length);
			apdu.setOutgoingAndSend((short)5, (short)papdu.pdata.length);//把缓冲区的数据返回给终端			
		}
	}

	/*
	 * 功能:对命令的分析和处理
	 * 参数:无
	 * 返回:是否成功处理了命令
	 */
	private boolean handleEvent(){
		switch(papdu.ins){
			case condef.INS_CREATE_FILE:       	return create_file();
			//todo:完成写二进制命令,读二进制命令,写密钥命令
			case condef.INS_WRITE_KEY:			return write_key();
			case condef.INS_WRITE_BIN:			return write_bin();
			case condef.INS_READ_BIN:			return read_bin();
			case condef.INS_NIIT_TRANS:
				if(papdu.p1 == (byte)0x00)		return init_load();
				if(papdu.p1 == (byte)0x01)		return init_purchase();
				ISOException.throwIt(ISO7816.SW_WRONG_P1P2);//else抛出异常
			case condef.INS_LOAD:				return load();
		}	
		ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		return false;
	}
	/*
	 * 功能:创建文件
	 */
	private boolean create_file() {
		switch(papdu.pdata[0]){ //data的第一位表示要创建文件的类型            
		case condef.EP_FILE:        return EP_file();  
		//todo:完成创建密钥文件,持卡人基本文件和应用基本文件
		case condef.KEY_FILE:		return Key_file();
		case condef.CARD_FILE:		return Card_file();
		case condef.PERSON_FILE:	return Person_file();
		default: 
			ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
		}
		return true;
	}
	/*
	 * 功能:创建电子钱包文件
	 */
	private boolean EP_file() {
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.lc != (byte)0x07)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(EPfile != null)//有文件了还重复创建则会报错
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建)
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		EPfile = new EPFile(keyfile);
		
		return true;
	}
	//创建密钥文件
	private boolean Key_file()
	{
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.lc != (byte)0x07)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(keyfile != null)//有文件了还重复创建则会报错
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		keyfile = new KeyFile();
		
		return true;
	}
	//创建应用基本文件
	private boolean Card_file()
	{
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.lc != (byte)0x07)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(cardfile != null)//有文件了还重复创建则会报错
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建)
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		cardfile = new BinaryFile(papdu.pdata);//传进的参数就是要写入的内容
		
		return true;
	}
	//创建持卡人信息文件
	private boolean Person_file()
	{
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.lc != (byte)0x07)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(personfile != null)//有文件了还重复创建则会报错
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建)
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		personfile = new BinaryFile(papdu.pdata);//传进的参数就是要写入的内容
		
		return true;
	}
	//写入各种密钥
	private boolean write_key()
	{
		return true;
	}
	//写入二进制文件
	private boolean write_bin()
	{
		return true;
	}
	//读取二进制文件
	private boolean read_bin()
	{
		return true;
	}
	/*
	 * 功能:圈存命令的实现
	 */
	private boolean load() {
		short rc;
		
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.p1 != (byte)0x00 && papdu.p2 != (byte)0x00)
			ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
		
		if(EPfile == null)
			ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
		
		if(papdu.lc != (short)0x0B)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		rc = EPfile.load(papdu.pdata);
		
		if(rc == 1)
			ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
		else if(rc == 2)
			ISOException.throwIt(condef.SW_LOAD_FULL);
		else if(rc == 3)
			ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
		
		papdu.le = (short)4;
		
		return true;
	}

	/*
	 * 功能:圈存初始化命令的实现
	 */
	private boolean init_load() {
		short num,rc;
		
		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.p1 != (byte)0x00 && papdu.p2 != (byte)0x02)
			ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
		
		if(papdu.lc != (short)0x0B)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(EPfile == null)
			ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
		
		num = keyfile.findkey(papdu.pdata[0]);
		
		if(num == 0x00)
			ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
		
		rc = EPfile.init4load(num, papdu.pdata);
		
		if(rc == 2)
			ISOException.throwIt((condef.SW_LOAD_FULL));
		
		papdu.le = (short)0x10;
		
		return true;
	}
	
	/*
	 * 功能:消费命令的实现
	 */
	private boolean purchase(){
		return true;
	}
	/*
	 * 功能:余额查询功能的实现
	 */
	private boolean get_balance(){
		return true;
	}
	
	/*
	 * 功能:消费初始化的实现
	 */
	private boolean init_purchase(){
		return true;
	}
}

既然是要创建文件,当然就要先从终端获取apdu命令,判断它的命令里面是不是要卡片创建文件,怎么判断呢?通过ins值,就是这句代码:

case condef.INS_CREATE_FILE:       	return create_file();


这里有个常量,在condef.java中定义:

//这个文件表示的是一些常量
package purse;
/**
 * 已给出部分常数值,其他根据需要自行添加 
 */
//存储INS值文件,ins值表示命令的具体意思
public class condef {
	//----------------- INS Byte -------------------
	final static byte INS_CREATE_FILE     = (byte)0xE0;         //文件建立命令的INS值
	final static byte INS_WRITE_KEY       = (byte)0xD4;         //写入密钥命令的INS值
	final static byte INS_WRITE_BIN       = (byte)0xD6;         //写入二进制命令的INS值
	final static byte INS_READ_BIN        = (byte)0xB0;         //读取二进制文件的INS值
	final static byte INS_NIIT_TRANS      = (byte)0x50;         //初始化圈存和初始化消费命令的INS值
	final static byte INS_LOAD            = (byte)0x52;         //圈存命令的INS值
	
	//-------------- FILE TYPE Byte ---------------
	final static byte KEY_FILE            = (byte)0x3F;         //密钥文件的文件类型
	final static byte CARD_FILE           = (byte)0x38;         //应用基本文件的文件类型
	final static byte PERSON_FILE         = (byte)0x39;         //持卡人基本文件 的文件类型
	final static byte EP_FILE             = (byte)0x2F;         //电子钱包文件的文件类型
	
	//------------------------ SW --------------------- 
	final static short SW_LOAD_FULL = (short)0x9501;      //圈存超额	
}

condef.java文件里面定义了一些ins值常量,还有就是如果是要创建文件,那要创建的是什么文件呢,通过pdata的第一位判断,也就是pdata[0]:

文件系统创建—Java Card开发第二篇_第4张图片

所以condef.java里面也定义了这部分文件类型的常量。

既然已经判断出了apdu命令是要我们创建文件,同时又判断出了要创建的是什么文件,接下来就简单了,就是直接调用几大文件数据结构的接口去创建相应的文件,同时,这里要判断一些异常的情况,也就是apdu命令有输入错误的情况,然后返回相应的异常给终端。比如:

		if(papdu.cla != (byte)0x80)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
		
		if(papdu.lc != (byte)0x07)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if(EPfile != null)//有文件了还重复创建则会报错
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		
		if(keyfile == null)//都还没密钥文件(必须先于任何其他文件创建)
			ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);

嗯,创建文件就是这么简单了,主要就是要理解文件系统里面的几大文件,同时看懂文件数据结构的几个java文件是怎么实现的。然后调用就很简单了。

最后,看看运行结果,由于终端要发送不少命令,所以可以把命令全部放到文件里面,然后直接在命令行中读取文件,然后就会在模拟器中执行文件里面的所有命令了。

首先,把命令脚本名称改一下,主要是改后缀名为jcsh:


然后运行之后再命令行中先输入:

/set-var path "文件路径(不包括文件名.jcsh)"

查找文件路径的快速方法(通用电脑小常识),查询文件属性,然后就可以看到文件所在的路径了。

输入上面那条路径命令之后,就再输入文件名(不包括后缀),比如这里是purse,然后回车就可以让模拟器执行命令脚本里面的所有命令了,注意,有时候脚本文件里面会有一些奇奇怪怪的未注释的中文字符,比如中文的空格,有很多软件开发工具都对这些非常敏感会导致执行脚本出错,我在执行脚本的时候就遇到了,解决办法是重新建一个txt文件,然后改下文件名,把脚本命令复制进来。

命令脚本内容:

文件系统创建—Java Card开发第二篇_第5张图片

运行结果:

文件系统创建—Java Card开发第二篇_第6张图片

可以看到都是No Error.也就把四大文件都给建立好了,后面的就是在文件里面进行添加密钥等等的操作了,这是后面学习篇的内容。值得注意的是,每次重新运行的时候,模拟器不会保存你之前创建的文件,毕竟只是个模拟器嘛。所以除非一直运行着保留着上面创建文件的状态,否则重新运行后应当重新建立文件。

你可能感兴趣的:(java,card)