Global Platform

[TOC]

术语表

术语 英文 解释
EMV Europay, MasterCard and Visa
ISD Issuer Secure Domains 发卡方安全域
SD Secure Domains 安全域
DM SD
DES Data Encryption Standard 数据加密标准
DEK Data Encryption Key 数据加密密钥
TDES Triple DES 3DES 算法
MAC Message Authentication Code 消息认证码
C-MAC Command Message Authentication Code
KMC DES Master Key for Personalization Session Keys
ICV Initial Chaining Vecto 初始向量
CM CardManger
SCP Secure Channel Protocol 安全通道协议
AID Application Identifier 应用标识符
APDU Application Protocol Data Unit 应用协议数据单元
CLA Class byte of the command message 命令消息中的类字节
INS Instruction byte of the command message
P1 Reference control parameter 1 引用控制参数1
P2 Reference control parameter 2 引用控制参数2
Lc Exact length of data in a case 3 or case 4 command
Le Maximum length of data expected in response to a case 2 or case 4 command
SW Status Word 状态字
SW1 Status Word One
SW2 Status Word Two
TLV Tag Length Value
OPEN GlobalPlatform envisequenguochengceronment
ENC Encryption 加密

打开安全通道

打开安全通道主要包括三步

  • 选中 ISD
  • INITIALIZE UPDATE 完成对卡的认证
  • EXTERNAL AUTHENTICATION 完成卡内 对卡外实体的认证

Select ISD

不同规范的卡ISD 不同, 我们使用的是GP211 ISD的AID 是a000000151000000 Select类型的APDU的数据段中需要指定ISD AID,这里有一个小技巧 如果不知道ISD AID情况可以发送APDU指令中不带数据段,就会默认选中发卡发安全域.

INITIALIZE UPDATE

在安全通道的显式发起期间, INITIALIZE UPDATE 命令用于在卡和主机之间传送卡和会话数据。这个命令开始一个安全通道会话的发起。
在当前安全通道的任何时候,都可以将 INITIALIZE UPDATE 命令发送给卡以发起一个新的安全通道会话。

发送APDU消息格式

INITIALZE UPDATE 编码表

编码 解释
CLA 0x80
INS 0x50 INITIALIZE UPDATE
P1 xx
P2 0x00
Lc 0x08 host challenge 长度
Data xxxxx host challenge
Le 0x00 返回长度任意或者说是最大255

这里主要要介绍 host challenge 这是一个8 bytes的 随机数,在每次会话中是唯一, 卡收到 host challenge 后用 静态秘钥 和自己的 card challenge sequence counter 加密生成 card cryptogram (卡的鉴别码) 然后返回给卡外实体(28 byte ).

相应消息

响应消息中包含下列数据的串联(没有分隔符):

名字 长度
秘钥推导数据 10 bytes
秘钥信息 2 bytes
sequence counter 2 bytes
card challenge 6 bytes
card cryptogram 8 bytes
  • 密钥派生数据一般由后端系统用来派生卡静态密钥.(这个暂时还没用到)
  • 秘钥信息里面包含了秘钥的版本号和scpVersion 标识符.
  • sequence counter 是卡内一个递增的计数器,他用于创建会话秘钥.
  • card challenge 是卡内部生成的随机数.
  • 卡认证码 这个用于卡外实体认证卡的合法性.

首先介绍下sequence counter


安全域将为每一个安全通道基本密钥或共享相同密钥版本号的(一组)安
全通道密钥管理一个序列计数器。
当且仅当安全通道(隐式或显式发起)的第一个 C-MAC 被验证为有效时,序列计数器才加 1。
它在命令具体处理前增加(即校验 C-MAC 之后,执行具体命令之前)。在安全通道密钥
的创建或更新时,序列计数器重置为 0。当安全通道密钥集包含多于一个密钥时,密钥集中任意一
个密钥创建或更新,序列计算器重置为 0。
注意:如果 C-MAC 不是安全通道中第一个 C-MAC,或者当一个安全通道会话已经开始并且
C-MAC 无效,序列计数器不会更新。从这个方面来说,序列计数器保持了到目前为止有效的安全通
道会话的个数,经历过的相应的安全消息密钥集。当达到最大值时,序列计数器将不会重置为 0。
不要求卡支持超过 32767 的计数器值。


再介绍下card cryptomgram, host cryptomgram , 生成规则. 这两个鉴别码完成了双向认证. 由于sequence
counter 是由安全域维护的一个计数序列, 且在每次会话中唯一,所以可以用这两bytes 为基础,然后填充协议要求的数据,然后推导出本地会话过程中的过程秘钥信息, 推导依赖与 静态秘钥,协议填充数据 , 加密算法

  • 首先卡外实体和卡有相同的对称秘钥信息(S-MAC , S-ENC , DNC)
  • 协议填充数据
    • 生成安全通道 C-MAC 会话密钥:需要用到以下数据,安全通道基本密钥或者 MAC 密钥(S-MAC),一个值为‘0101’的会话密钥派生数据. | 0101 | sequence counter | 00(12 bytes) |
    • 生成安全通道 R-MAC 会话密钥:需要用到以下数据,安全通道基本密钥或者 MAC 密钥(S-MAC),一个值为‘0102’的会话密钥派生数据 . | 0102 | sequence counter | 00(12 bytes) |
    • 生成安全通道加密会话密钥:需要用到以下数据,安全通道基本密钥或者加密密钥(S-ENC),一个值为‘0182’的会话密钥派生数据. | 0182 | sequence counter | 00(12 bytes) |
    • 生成安全通道数据加密会话密钥:需要用到以下数据,安全通道基本密钥或者数据加密密钥(DEK),一个值为‘0181’的会话密钥派生数据 . | 0181 | sequence counter | 00(12 bytes) |
  • 加密算法 推导过程秘钥 Session-ENC Session-MAC Session-DEK, 这里用的是3DES算法 初始ICV 为8 bytes 0x00数据, 模式是CBC模式,由于这里加密的数据是8的倍数 所以指定nopadding. 加密秘钥为相应的静态秘钥( 由于3DES 算法的加密秘钥是24 bytes 而相应的静态秘钥为16 bytes 这里做法是把 24 bytes 最后8 bytes 用静态秘钥的前8 bytes 填充),具体的实现细节如下.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    KeySet sessionKeys = new KeySet();
    try {
    byte[] derivationData = new byte[16];
    derivationData[2] = seq1;
    derivationData[3] = seq2;
    byte[] constantMAC = new byte[] {
    (byte) 0x01, (byte) 0x01
    };
    System.arraycopy(constantMAC, 0, derivationData, 0, 2);
    ICipher cipher = ICipher.Factory.getImplementation(
    ICipher.DESEDE_CBC_NOPADDING, GPUtil.getKey(
    staticKeys.keys[1], 24), new byte[8]);
    // Session-MAC
    sessionKeys.keys[1] = cipher.encrypt(derivationData);

推导过程秘钥的示例图如下:

  • card cryptomgram 生成校验, 需要用到卡外产生host challenge, 卡返回的sequence counter , card challenge和规范要求的填充数据, 这里用的的是mac_3des 算法(组合算法),用到的加密秘钥是 Session-ENC 具体实现看算法具体的注释.

card cryptomgram 生成校验过程(可以推算卡内也是这样算出来的, 只有具有相同的静态秘钥,按照相应的协议,才能生成通样的鉴别数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ByteArrayOutputStream bo = new ByteArrayOutputStream();
try {
// 卡外实体的host challenge
bo.write(rand);
// 这里获取 key information 和 card challege
bo.write(result, 12, 8);
} catch (IOException ioe) {
}
// 也就是说卡通过mac_3des算法 根据host challenge 和card callenge sequence counter
// 来生成 card cryptogram 此时加密算法icv 为8 byte 全00 数据
// 加密数据是 |host challenge(8 bytes)| + | sequence counter (2 bytes)|+| card challenge (6 bytes)| +|8000000000000000( 8 bytes)|
// 签名方法采用 S-ENC 会话密钥和全部为二进制 0 的 ICV 作用于这 24 字节数据块,
// 8 字节的签名结果就是卡的鉴别密码
byte[] myCryptogram = GPUtil.mac_3des(sessionKeys.keys[0], GPUtil
.pad80(bo.toByteArray()), new byte[8]);
byte[] cardCryptogram = new byte[8];
// 卡的鉴别密码(Card cryptogram)
System.arraycopy(result, 20, cardCryptogram, 0, 8);
// 这里认证卡是合法的, 不合法则抛出异常
if (!Arrays.equals(cardCryptogram, myCryptogram)) {
throw new CardException("Card cryptogram invalid.");
}

mac_3des 算法
这里初始向量cv 8 bytes 全 0x00 数据, key 是过程秘钥然后按照之上的规则扩展24 bytes , 加密数据是|host challenge(8 bytes)| + | sequenguochengce counter (2 bytes)|+| card challenge (6 bytes)| +|8000000000000000( 8 bytes)|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static byte[] mac_3des(byte[] key, byte[] text, int offset, int length,
byte[] cv) throws CardException {
if (length == -1)
length = text.length - offset;
try {
ICipher cipher = ICipher.Factory.getImplementation(
ICipher.DESEDE_CBC_NOPADDING, getKey(key, 24), cv);
byte[] result = new byte[8];
byte[] res = cipher.encrypt(text, offset, length);
// 把生成的密文的后 8 bytes copy 到前 8 bytes 其实 card cryptogram 是生成密文24 bytes 的后 8 bytes
System.arraycopy(res, res.length - 8, result, 0, 8);
return result;
} catch (Exception e) {
throw new CardException("MAC computation failed.");
}
}

  • host cryptogram 生成用来认证卡外实体的合法性, 具体细节基本上和 card cryptogram 生成类似, 只不过加密数据有所变化,这里只是颠倒了随机数 | sequence counter (2 bytes)|+| card challenge (6 bytes)|+|host challenge(8 bytes) | +|8000000000000000( 8 bytes)|
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    try {
    bo.reset();
    // sequence counter + card challenge
    bo.write(result, 12, 8);
    // host challenge
    bo.write(rand);
    } catch (IOException ioe) {
    }
    // 加密数据是| sequence counter (2 bytes)|+| card challenge (6 bytes)|+|host challenge(8 bytes) | +|8000000000000000( 8 bytes)|
    byte[] authData = GPUtil.mac_3des(sessionKeys.keys[0], GPUtil.pad80(bo
    .toByteArray()), new byte[8]);

接下来就进入了卡认证卡外实体的过程.

EXTERNAL AUTHENTICATE

本认证过程主要对指令做了C-MAC 签名 保证数据的不被篡改, 这里涉及加密算法的 和 ICV 以及明文组装.这个 C-MAC 是卡外实体对要发送到卡内的 APDU 指令(包括 APDU 指令的头,以及命令消息中的数据段,但不包括 Le)进行 MAC 运算后生成的。

ICV 用于链接 APDU 命令以保证这些命令顺序的完整性;。对于第一个成功执行 C-MAC 校验之后的命令来说,它们在进行 C-MAC 运算时所采用的 ICV 是前一个命令的 C-MAC 值.
为了反映在命令消息中存在 C-MAC 值,APDU 命令的头应该按照以下方式进行修改:

  • 在命令消息的长度字段(Lc)中应该加 8 以表明命令消息字段中存在 C-MAC
  • 命令类型(Cla)应该被修改以表明该 APDU 命令中包含了安全消息。这是通过设置命令类型字节的第 3 位来完成的。对于本规范中定义个所有 APDU 命令来说,包含后安全消息的 APDU 命令的命令类型都应该是‘0x84’。C-MAC 的生成和校验不影响命令类型中包含的逻辑通道的相关信息:在生成和校验 C-MAC 时,这里一直假定逻辑通道号为 0。C-MAC 被附加在 APDU 命令消息的最末端,这个 APDU 命令消息不包含填充数据,但是包含修改后的 APDU 命令头。
  • 在未修改过的 APDU 命令上进行 C-MAC 生成:在执行 C-MAC 计算之前,应该先对 APDU命令进行填充,在 C-MAC 生成之后修改 APDU 命令头。
  • 在修改后的 APDU 命令上进行 C-MAC 生成:在执行 C-MAC 运算之前,先进行 APDU命令的填充和 APDU 命令头的修改。

卡里为了校验卡外实体发来C-MAC,必须使用和卡外实体相同的数据填充机制,使用相同的ICV,使用相同的C-MAC会话密钥来对这个C-MAC进行校验。这个被成功校验的C-MAC值应该被保留,以作为下一个命令C-MAC校验时的ICV。无论这个APDU命令是否成功完成,卡内从来都不应该丢掉前一个命令中成功校验的C-MAC值

数据组装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
private CommandAPDU wrap(CommandAPDU command) throws CardException {
try {
//rmac = false
if (rmac) {
rMac.reset();
rMac.write(clearBits((byte) command.getCLA(), (byte) 0x07));
rMac.write(command.getINS());
rMac.write(command.getP1());
rMac.write(command.getP2());
if (command.getNc() >= 0) {
rMac.write(command.getNc());
rMac.write(command.getData());
}
}
// mac = true enc = false
if (!mac && !enc) {
return command;
}
int origCLA = command.getCLA();
int newCLA = origCLA;
int origINS = command.getINS();
int origP1 = command.getP1();
int origP2 = command.getP2();
byte[] origData = command.getData();java
int origLc = command.getNc();
int newLc = origLc;
byte[] newData = null;
int le = command.getNe();
ByteArrayOutputStream t = new ByteArrayOutputStream();
int maxLen = 255;
if (mac)
maxLen -= 8;
if (enc)
maxLen -= 8;
// mac 校验APDU 的data 长度应该小于等于247 为了追加MAC 到data 中 MAC 为8 bytes
if (origLc > maxLen) {
throw new CardException("APDU too long for wrapping. LC: " + origLc + " Max" + maxLen);
}
if (mac) {
if (icv == null) {
// 第一次 icv 为8 bytes 全0x00 数据
icv = new byte[8];
} else if (icvEnc) {
ICipher c = null;
if (scp == 1) {
c = ICipher.Factory.getImplementation(
ICipher.DESEDE_ECB_NOPADDING, GPUtil
.getKey(sessionKeys.keys[1], 24));
} else {
c = ICipher.Factory.getImplementation(
ICipher.DES_ECB_NOPADDING, GPUtil.getKey(
sessionKeys.keys[1], 8));
}
// 对icv 进行mac 运算
icv = c.encrypt(icv);
}
// preAPDU = true
if (preAPDU) {
// 修正CLA 和 Lc
newCLA = setBits((byte) newCLA, (byte) 0x04);
newLc = newLc + 8;
}
t.write(newCLA);
t.write(origINS);
t.write(origP1);
t.write(origP2);
t.write(newLc);
t.write(origData);
if (scp == 1) {
icv = GPUtil.mac_3des(sessionKeys.keys[1], GPUtil
.pad80(t.toByteArray()), icv);
} else {
// 生成新的icv
icv = GPUtil.mac_des_3des(sessionKeys.keys[1], GPUtil
.pad80(t.toByteArray()), icv);
}
// postAPDU = false ;
if (postAPDU) {
newCLA = setBits((byte) newCLA, (byte) 0x04);
newLc = newLc + 8;
}
t.reset();
newData = origData;
}
// enc = false origLc = 8
if (enc && origLc > 0) {
if (scp == 1) {
t.write(origLc);
t.write(origData);
if (t.size() % 8 != 0) {
byte[] x = GPUtil.pad80(t.toByteArray());
t.reset();
t.write(x);
}
} else {
t.write(GPUtil.pad80(origData));
}
newLc += t.size() - origData.length;
ICipher c = ICipher.Factory.getImplementation(
ICipher.DESEDE_CBC_NOPADDING, GPUtil.getKey(
sessionKeys.keys[0], 24), new byte[8]);
newData = c.encrypt(t.toByteArray());
t.reset();
}
t.write(newCLA);
t.write(origINS);
t.write(origP1);
t.write(origP2);
if (newLc > 0) {
t.write(newLc);
t.write(newData);
}
if (mac) {
t.write(icv);
}
if (le > 0) {
t.write(le);
}
CommandAPDU wrapped = new CommandAPDU(t.toByteArray());
return wrapped;
} catch (CardException ce) {
throw ce;
} catch (Exception e) {
throw new CardException("APDU wrapping failed.", e);
}
}

装载安装Applet

装载Applet 涉及的APDU 指令比较多,而且需要很多装载参数, 用到INSTLL( for load or install ) LOAD, 装载的前提是先打开安全通道,也是之上的过程,之后要获取Applet相关信息,这个信心可以从Apllet的cap文件获取,然后就是分段上传数据到卡,最后更改cap文件带的Applet的状态为selectable, 所以这里一切前提就是先是从cap 文件里面获取相关信息.

流程如下:

cap 分析

cap 文件其实是一个jar包,可以这么理解相当与java的jar包,里面还包括部分标记信息,这个信息有的是用来调试,,有的是为了作为安装使用,这里只是简单分析cap格式和获取想关信息,后期有所需求在继续深入分析.

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中的实际地址,调用实际地址中存储的数据。

    注:多字节数据总是按照大端(big-endian)顺序存放。

INSTALL[for load] 所需要的数据信息

下表描述了 INSTALL[for load]命令的数据字段:

装载文件数据块 Hash 和装载 Token 对于委托管理是必须的。如果装载文件包含一个或多个 DAP
块,那么装载文件数据块 Hash 是强制的。在其它情况下,装载文件数据块 Hash 是可选的并且是可
以被卡验证的。
装载文件 AID 和装载参数将与装载文件数据块(如果有的话)中包含的信息是一致的。

我们可以cap中获取 装载文件 AID。可以从cap里面的header.cap 文件里面分析获取, 其实这里还需要计算cap文件 里面相关文件的hash,其实是计算cap里面一组文件拼接后的HASH

Header.cap 格式

Header组件中包含了该CAP文件的基本信息,其中包括最重要的文件版本信息和包AID值。版本信息用以判断JAVA卡是否支持对该文件的解析。AID是CAP文件的唯一识别,单张JAVA智能卡上不支持装载相同AID的CAP文件。

1
2
3
4
5
6
7
8
9
10
header_component {
u1 tag
u2 size
u4 magic // 必须为0xDECAFFE
u1 minor_version
u1 major_version
u1 flags // ACC_INT - 0x01; ACC_EXPORT - 0x02; ACC_APPLET - 0x04
package_info package
package_name_info package_name
}

其中CAP File Package Flags:

Flags value
ACC_INT 0x01
ACC_EXPORTACC_EXPORT 0x02
ACC_EXPORT 0x04
1
2
3
4
5
6
7
8
9
10
11
package_info {
u1 minor_version
u1 major_version
u1 AID_length
u1 AID[AID_length]
}
package_name_info {
u1 name_length // 当包内没有定义任何remote interfaces或者remote classes的时候值可以为0
u1 name[name_length]
}

以 HelloWorld.cap 文件中的Header.cap中的信息为例:

0×01 | 0×00 0×13 | 0xDE 0xCA 0xFF 0xED | 0×01 | 0×02 | 0×04 |0×00 0×01 | 0×09 | 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01

  • tag: 0×01 COMPONENT_Header
  • size: 0×13 = 19 除去tag和size后的大小
  • magic: 0xDECAFFED 用以标识Java卡CAP文件格式,若一个文件不是以0xDECAFFED开头则肯定不是JavaCard CAP文件,因为它不符合规范
  • minor_version: 0×01 次版本号
  • major_version: 0×02 主版本号, 该CAP文件格式的版本号是02.01;如果CAP文件的版本号超出了Java卡虚拟机所能够处理的有效范围,Java卡虚拟机将不会处理该CAP文件
  • flag: 0×04 包中没有用到int类型、CAP文件中没有Export组件、CAP文件中有Applet组件。
  • package_info:
    • minor_version: 0×00
    • major_version: 0×01
    • AID_length: 0×09
    • AID: 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01

获取包AID java 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
byte[] header = capComponents.get("Header");
int i = 0;
// header[0] should be 1;
i++;
// header[1] should be 0;
i++;
// header[2] should be remaining length
i++;
// header[3, 4, 5, 6] should be magic
i += 4;
// header[7, 8] should be cap file version
i += 2;
// header[9] should be flags
i++;
// header[10,11] should be package version
i += 2;
// header[12] should be the length of AID
int len = header[i++];
packageAID = new AID(header, i, len);

Applet.cap 分析

Applet组件用以记录CAP文件中Applet的基本信息。CAP文件中的Applet可以有多个,且相互独立。 这里主要为了获取应用的AID,为INSTALL [ for make selectable ] 做准备,更改应用状态.

1
2
3
4
5
6
7
8
9
applet_component {
u1 tag
u2 size
u1 count // 包中applet的个数
{ u1 AID_length
u1 AID[AID_length]
u2 install_method_offset
} applets[count]
}

以 HelloWorld.cap 中的 Applet.cap为例:

0×03 | 0×00 0x0E | 0×01 | 0x0A 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01 0×01 0×00 0×14

  • tag: 03 COMPONENT_Applet
  • size: 0x0E = 14
  • count: 01 包中有一个applet
  • applets[]:
    • AID_length: 0x0A = 10
    • AID[]: 0xA0 0×00 0×00 0×00 0×62 0×03 0×01 0x0C 0×01 0×01 该applet的AID
    • install_method_offset: 0×14 即当前Applet中的install()在Method组件info[]中的偏移。其中,Install()方法是一个静态方法,用以创建当前Applet的类实例,赋予应用软件生命周期开始执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java 实现
``` java
byte[] applet = capComponents.get("Applet");
if(applet != null) {
i = 0;
// applet[0] should be 3;
i++;
// applet[1] should be 0;
i++;
// applet[2] should be remaining length
i++;
// header[3] should be number of applets
int num = applet[i++];
for (int j = 0; j < num; j++) {
len = applet[i++];
appletAIDs.add(new AID(applet, i, len));
i += len + 2;
}

拼INSTALL [for load ]指令

install for load 发起装载请求 上面基本介绍过APDU 带的数据段,这里只用确定P1 P2 就可以拼接APDU 发起load 请求.
P1 = 0x02
P2 = 0x00

相关过程代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 装载文件数据块 Hash 和装载 Token 对于委托管理是必须的。如果装载文件包含一个或多个 DAP
// 块,那么装载文件数据块 Hash 是强制的。在其它情况下,装载文件数据块 Hash 是可选的并且是可
// 以被卡验证的。
// useHash = false ;// 委托安装的时候为true
byte[] hash = useHash ? cap.getLoadFileDataHash(includeDebug)
: new byte[0];
// includeDebug = false
int len = cap.getCodeLength(includeDebug);
// loadParam = true ; // 装载参数 暂时还没弄懂
byte[] loadParams = loadParam ? new byte[] {
(byte) 0xEF, 0x04,
(byte) 0xC6, 0x02, (byte) ((len & 0xFF00) >> 8),
(byte) (len & 0xFF)
} : new byte[0];
ByteArrayOutputStream bo = new ByteArrayOutputStream();
try {
bo.write(cap.getPackageAID().getLength());
bo.write(cap.getPackageAID().getBytes());
android.util.Log.d("Load", "Load --> PackageAID" + ByteHelper.toHexString(cap.getPackageAID().getBytes())
+ " length" + cap.getPackageAID().getLength());
bo.write(sdAID.getLength());
bo.write(sdAID.getBytes());
android.util.Log.d("Load", "Load --> CM_AID" + ByteHelper.toHexString(sdAID.getBytes()) + " Length: "
+ sdAID.getLength());
bo.write(hash.length);
bo.write(hash);
android.util.Log.d("Load", "Load --> hash" + ByteHelper.toHexString(hash) + " Length: " + hash.length);
bo.write(loadParams.length);
bo.write(loadParams);
bo.write(0); // 装在token 长度
android.util.Log.d("Load", "Load --> Load Paras -->" + ByteHelper.toHexString(loadParams) + " length"
+ loadParams.length);
} catch (IOException ioe) {
}
CommandAPDU installForLoad = new CommandAPDU(CLA_GP, INSTALL, 0x02,
0x00, bo.toByteArray());
ResponseAPDU response = transmit(installForLoad);
notifyExchangedAPDU(installForLoad, response);

一系列的LOAD APDU 指令

为装载一个装载文件的 LOAD 命令的数据字段中传输的装载文件的结构
多个 LOAD 命令可以用来传输一个装载文件到卡中。这个装载文件被分成多个便于传输的小块。
每一个 LOAD 命令将从 ̳00 开始进行编号。LOAD 命令的编号是连续的,每次增加 1。装载文件的最后一块时将通知卡。
当接收到装载文件的最后一块后,卡将执行先于 LOAD 命令的对装载文件必要的内部处理和在INSTALL[for load]命令中标识的任何附加的处理。

LOAD APDU
代码 含义
CLA 0x80或 ̳0x84
INS ̳0xE8 LOAD
P1 xx 引用控制参数 P1
P2 xx 块号
Lc xx 数据字段的长度
Data xxxxx 装载数据(和 MAC 如果存在)
Le 0x00 qi

引用控制参数P1

下表描述了引用控制参数 P1 的编码,该参数用于指示命令消息中包含的块是否是最后一块。

load 过程实现

1
2
3
4
5
6
7
8
9
10
11
12
List<byte[]> blocks = cap.getLoadBlocks(includeDebug,
separateComponents, blockSize);
for (int i = 0; i < blocks.size(); i++) {
CommandAPDU load = new CommandAPDU(CLA_GP, LOAD, (i == blocks.size() - 1) ? 0x80 : 0x00, (byte) i, blocks.get(i));
response = transmit(load);
notifyExchangedAPDU(load, response);
sw = (short) response.getSW();
if (sw != SW_NO_ERROR) {
throw new GPLoadException(sw, "Load failed, SW: "
+ GPUtil.swToString(sw));
}

其中涉及到如何把cap文件里面的数据组装和分组,这个到看实现 这里就先不写了

拼INSTALL [for install] APDU指令

下表描述了 INSTALL[for install]命令的数据字段

装载文件数据块 Hash 和装载 Token 对于委托管理是必须的。如果装载文件包含一个或多个 DAP
块,那么装载文件数据块 Hash 是强制的。在其它情况下,装载文件数据块 Hash 是可选的并且是可
以被卡验证的。
装载文件 AID 和装载参数将与装载文件数据块(如果有的话)中包含的信息是一致的。

相关java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void installAndMakeSelecatable(AID packageAID, AID appletAID,
AID instanceAID, byte privileges, byte[] installParams,
byte[] installToken) throws GPMakeSelectableException,
CardException {
if (installParams == null) {
installParams = new byte[] {
(byte) 0xC9, 0x00
};
}
if (instanceAID == null) {
instanceAID = appletAID;
}
if (installToken == null) {
installToken = new byte[0];
}
ByteArrayOutputStream bo = new ByteArrayOutputStream();
try {
bo.write(packageAID.getLength());
bo.write(packageAID.getBytes());
bo.write(appletAID.getLength());
bo.write(appletAID.getBytes());
bo.write(instanceAID.getLength());
bo.write(instanceAID.getBytes());
bo.write(1);
bo.write(privileges);
bo.write(installParams.length);
bo.write(installParams);
bo.write(installToken.length);
bo.write(installToken);
} catch (IOException ioe) {
}
CommandAPDU install = new CommandAPDU(CLA_GP, INSTALL, 0x0C, 0x00, bo
.toByteArray());
ResponseAPDU response = transmit(install);
notifyExchangedAPDU(install, response);
short sw = (short) response.getSW();
if (sw != SW_NO_ERROR) {
throw new GPMakeSelectableException(sw,
"Install for Install and make selectable failed, SW: "
+ GPUtil.swToString(sw));
}
}