js-gp
Version:
implementation of globalplatform
571 lines (514 loc) • 20.7 kB
text/typescript
"use strict"
import * as buffer from 'buffer';
import * as crypto from 'crypto';
import { apducmd } from "./apducmd";
import * as admzip from 'adm-zip';
const GP_DEFAULT_KEY = new Buffer("404142434445464748494A4B4C4D4E4F", "hex");
const GP_DEFAULT_ICV = new Buffer("0000000000000000", "hex");
export enum GP_SECURITY_LEVEL {
NO_SECURITY = 0,
COMMAND_MAC = 1,
COMMAND_MAC_EN = 3
}
export interface IApdu {
transmit(command: Buffer): Promise<Buffer>;
}
export interface gp_key_info {
key_enc: Buffer;
key_mac: Buffer;
key_dek: Buffer;
key_ver: number;
}
export const defaultkey = {
key_enc: GP_DEFAULT_KEY,
key_mac: GP_DEFAULT_KEY,
key_dek: GP_DEFAULT_KEY,
key_ver: 0
};
export enum GP_PROTOCOL {
SCP_01 = 0,
SCP_02 = 1
}
export class jsgp {
private apdu: IApdu;
private key_enc: Buffer;
private key_mac: Buffer;
private key_dek: Buffer;
private session_key_enc: Buffer;
private session_key_mac: Buffer;
private session_key_dek: Buffer;
private security_level: number;
private command_mac: Buffer;
private apducmd: apducmd;
private protocol: GP_PROTOCOL;
private store_num: number;
private capcomponents: string[];
constructor(ap: IApdu) {
this.apdu = ap;
this.key_enc = GP_DEFAULT_KEY;
this.key_mac = GP_DEFAULT_KEY;
this.key_dek = GP_DEFAULT_KEY;
this.apducmd = new apducmd();
this.capcomponents = [];
this.capcomponents.push("Header.cap");
this.capcomponents.push("Directory.cap");
this.capcomponents.push("Import.cap");
this.capcomponents.push("Class.cap");
this.capcomponents.push("Method.cap");
this.capcomponents.push("StaticField.cap");
this.capcomponents.push("Export.cap");
this.capcomponents.push("ConstantPool.cap");
this.capcomponents.push("RefLocation.cap");
this.capcomponents.push("Descriptor.cap");
//this.capcomponents.push("Debug.cap");
}
public gp_security_ch_02(security_level: number, key: gp_key_info = defaultkey): Promise<Buffer> {
this.key_enc = key.key_enc;
this.key_mac = key.key_mac;
this.key_dek = key.key_dek;
let command = new Buffer(13);
command.writeUInt16BE(0x8050, 0);
command.writeInt8(security_level, 2);
command.write("00081122334455667788", 3, 20, "hex");
return this.apdu.transmit(command).then((initresp: Buffer) => {
if (initresp.length != 30)
return;
if (initresp.readUInt16BE(initresp.length - 2) != 0x9000)
return;
let cardkeydiver = initresp.slice(0, 10);
let cardkeyinfo = initresp.slice(10, 12);
let cardseqnum = initresp.readUInt16BE(12);
let cardchallenge = initresp.slice(14, 20);
let cardcrypto = initresp.slice(20, 28);
let data = new Buffer(16);
data.writeUInt16BE(cardseqnum, 2);
data.write("000000000000000000000000", 4, 14, "hex");
data.write("0182", 0, 4, "hex");
this.session_key_enc = this.crypt_des_cbc(data, this.key_enc);
data.write("0101", 0, 4, "hex");
this.session_key_mac = this.crypt_des_cbc(data, this.key_mac);
data.write("0181", 0, 4, "hex");
this.session_key_dek = this.crypt_des_cbc(data, this.key_dek);
data = new Buffer(24);
data.write("1122334455667788", 0, 16, "hex");
data.writeUInt16BE(cardseqnum, 8);
cardchallenge.copy(data, 10);
data.write("8000000000000000", 16, 16, "hex");
let hostcardcrypto = this.crypt_des_cbc(data, this.session_key_enc);
hostcardcrypto = hostcardcrypto.slice(hostcardcrypto.length - 8);
if (!hostcardcrypto.equals(cardcrypto))
return;
data.writeUInt16BE(cardseqnum, 0);
cardchallenge.copy(data, 2);
data.write("1122334455667788", 8, 16, "hex");
data.write("8000000000000000", 16, 16, "hex");
let hostcrypto = this.crypt_des_cbc(data, this.session_key_enc);
hostcrypto = hostcrypto.slice(hostcrypto.length - 8);
data = new Buffer(21);
data.writeUInt16BE(0x8482, 0);
data.writeUInt8(2, security_level);
data.writeUInt16BE(3, 0x010);
hostcrypto.copy(data, 5);
let mac = this.gp_data_mac(data.slice(0, 13));
mac.copy(data, 13);
return this.apdu.transmit(data).then((recv: Buffer) => {
if (recv.length != 2)
return;
if (recv.readUInt16BE(0) != 0x9000)
return;
this.security_level = security_level;
this.store_num = 0;
return recv;
});
});
}
public gp_select(p1p2: number, name: Buffer): Promise<Buffer> {
let command = new Buffer(name.length + 5);
command.writeUInt16BE(0x00A4, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(name.length, 4);
name.copy(command, 5);
return this.apdu.transmit(command);
}
public gp_select_aid(aid: Buffer): Promise<Buffer> {
return this.gp_select(0x0400, aid);
}
public gp_delete(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80E4, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_delete_aid(p1p2: number, aid: Buffer): Promise<Buffer> {
let data = new Buffer(aid.length + 2);
data.writeUInt8(0x4f, 0);
data.writeUInt8(aid.length, 1);
return this.gp_delete(p1p2, data);
}
public gp_manage_ch(p1p2: number): Promise<Buffer> {
let command = new Buffer(5);
command.writeUInt8(0x00, 0);
command.writeUInt8(0x70, 1);
command.writeUInt8(p1p2, 2);
command.writeUInt8(0, 4);
if ((p1p2 & 0xff00) == 0x8000)
command = command.slice(0, 4);
return this.apdu.transmit(command);
}
public gp_install(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80E6, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_install_ex(p1p2: number, aid1: Buffer, aid2: Buffer, aid3: Buffer, pri: number, para: Buffer, token: Buffer): Promise<Buffer> {
let command = new Buffer(265);
let offset = 0;
command.writeUInt16BE(0x80E6, 0);
command.writeUInt16BE(p1p2, 2);
// write start data field
offset = 5;
command.writeUInt8(aid1.length, offset);
offset += 1;
aid1.copy(command, offset);
offset += aid1.length;
command.writeUInt8(aid2.length, offset);
offset += 1;
aid2.copy(command, offset);
offset += aid2.length;
command.writeUInt8(aid3.length, offset);
offset += 1;
aid3.copy(command, offset);
offset += aid3.length;
if ((p1p2 & 0x0200) == 0x0000) {
if (pri > 0x010000) {
command.writeUInt8(0x03, offset);
offset += 1;
command.writeUInt8(pri >> 16, offset);
offset += 1;
command.writeUInt8(pri >> 8, offset);
offset += 1;
command.writeUInt8(pri >> 0, offset);
offset += 1;
}
else {
command.writeUInt8(0x01, offset);
offset += 1;
command.writeUInt8(pri, offset);
offset += 1;
}
}
command.writeUInt8(para.length, offset);
offset += 1;
para.copy(command, offset);
offset += para.length;
command.writeUInt8(token.length, offset);
offset += 1;
token.copy(command, offset);
offset += token.length;
// write lc
command.writeUInt8(4, offset - 5);
return this.gp_cmd_mac(command.slice(0, offset));
}
public gp_load(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80E8, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_storedata(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80E2, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public async gp_storedgi(p1: number, dgi: number, dgidata: Buffer): Promise<Buffer> {
let command = new Buffer(265);
let data = new Buffer(dgidata.length + 20);
let offset = 0;
let transoff = 0;
let translen = 0;
let recv: Buffer;
data.writeUInt16BE(dgi, offset);
offset += 1;
if (dgidata.length < 0xff) {
data.writeUInt8(dgidata.length, offset);
offset += 1;
}
else {
data.writeUInt8(0xff, offset);
offset += 1;
data.writeUInt16BE(dgidata.length, offset);
offset += 2;
}
dgidata.copy(data, offset);
offset += dgidata.length;
while (transoff < offset) {
translen = offset - transoff;
if (translen > 0xFE)
translen = 0xFE;
let cmddata = data.slice(transoff, transoff + translen);
if ((p1 & 0x60) == 0x60) {
let padded = this.data_padding_ex(cmddata);
cmddata = this.crypt_des_ecb(padded, this.session_key_dek);
}
command.writeUInt16BE(0x80E2, 0);
command.writeUInt8(p1, 2);
command.writeUInt8(this.store_num, 3);
command.writeUInt8(translen, 4);
cmddata.copy(command, 5);
recv = await this.gp_cmd_mac(command.slice(0, translen + 5));
if (recv.readUInt16BE(recv.length - 2) != 0x9000)
return recv;
transoff += translen;
this.store_num += 1;
}
return recv;
}
public gp_putkey(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80D8, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_getdata(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80CA, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_getstatus(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80F2, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_setstatus(p1p2: number, data: Buffer): Promise<Buffer> {
let command = new Buffer(data.length + 5);
command.writeUInt16BE(0x80F0, 0);
command.writeUInt16BE(p1p2, 2);
command.writeUInt8(data.length, 4);
data.copy(command, 5);
return this.gp_cmd_mac(command);
}
public gp_cap_data(capfile: string): Buffer {
let capdata: Buffer[] = [];
let zip = new admzip(capfile);
let zipEntries = zip.getEntries();
this.capcomponents.forEach(comp => {
zipEntries.forEach(zipEntry => {
if (this.strendswith(zipEntry.entryName, comp)) {
capdata.push(zipEntry.getCompressedData());
}
});
});
return Buffer.concat(capdata);
}
public gp_cap_pkg_aid(capfile: string): Buffer {
let pkgaid: Buffer;
let zip = new admzip(capfile);
let zipEntries = zip.getEntries();
this.capcomponents.forEach(comp => {
zipEntries.forEach(zipEntry => {
if (this.strendswith(zipEntry.entryName, "Header.cap")) {
pkgaid = zipEntry.getCompressedData();
pkgaid = pkgaid.slice(13);
}
});
});
return pkgaid;
}
public async gp_load_cap(capdata: Buffer): Promise<Buffer> {
let caplen = capdata.length;
let buff = new Buffer(caplen + 5);
let offset = 0;
let transoff = 0;
let translen = 0;
let recv: Buffer;
let load_num: number = 0;
if (caplen > 65535) {
buff.writeUInt16BE(0xc483, 0);
buff.writeUInt16BE(caplen >> 8, 2);
buff.writeUInt8(caplen, 4);
offset += 5;
}
else if (caplen > 255) {
buff.writeUInt16BE(0xc482, 0);
buff.writeUInt16BE(caplen, 2);
offset += 4;
}
else if (caplen > 127) {
buff.writeUInt16BE(0xc481, 0);
buff.writeUInt8(caplen, 2);
offset += 3;
}
else {
buff.writeUInt8(0xc4, 0);
buff.writeUInt8(caplen, 1);
offset += 2;
}
capdata.copy(buff, offset);
offset += caplen;
while (transoff < offset) {
translen = offset - transoff;
let P1 = 0x80;
if (translen > 230){
translen = 230;
P1 = 0x00;
}
let cmddata = buff.slice(transoff, transoff + translen);
recv = await this.gp_load((P1<<8)|(load_num&0xff), cmddata);
if (recv.readUInt16BE(recv.length - 2) != 0x9000)
return recv;
transoff += translen;
load_num += 1;
}
return recv;
}
public gp_cmd_mac(cmd: Buffer): Promise<Buffer> {
let command: Buffer;
this.apducmd.parse(cmd);
if (this.security_level == GP_SECURITY_LEVEL.NO_SECURITY) {
command = new Buffer(cmd.length);
this.apducmd.CLA &= 0xFB;
command.writeUInt8(this.apducmd.CLA, 0);
cmd.slice(1).copy(command, 1);
}
else if (this.security_level == GP_SECURITY_LEVEL.COMMAND_MAC) {
command = new Buffer(cmd.length + 16);
this.apducmd.CLA |= 0x04;
this.apducmd.LC += 8;
this.command_mac.copy(command, 0);
command.writeUInt8(this.apducmd.CLA, 8);
cmd.slice(1, 4).copy(command, 9);
command.writeUInt8(this.apducmd.LC, 12);
this.apducmd.DATA.copy(command, 13);
this.command_mac = this.gp_data_mac(command.slice(0, this.apducmd.DATA.length + 13));
this.command_mac.copy(command, this.apducmd.DATA.length + 13);
if (this.apducmd.LE != undefined)
command.writeUInt8(this.apducmd.LE, command[12] + 13)
command = command.slice(8);
}
else if (this.security_level == GP_SECURITY_LEVEL.COMMAND_MAC_EN) {
command = new Buffer(cmd.length + 24);
this.apducmd.CLA |= 0x04;
this.apducmd.LC += 8;
this.command_mac.copy(command, 0);
command.writeUInt8(this.apducmd.CLA, 8);
cmd.slice(1, 4).copy(command, 9);
command.writeUInt8(this.apducmd.LC, 12);
this.apducmd.DATA.copy(command, 13);
this.command_mac = this.gp_data_mac(command.slice(0, this.apducmd.DATA.length + 13));
let crypt = this.gp_data_encrypt(this.apducmd.DATA);
crypt.copy(command, 13);
this.command_mac.copy(command, crypt.length + 13);
command.writeUInt8(crypt.length + 8, 12);
if (this.apducmd.LE != undefined) {
command.writeUInt8(this.apducmd.LE, command[12] + 13)
command = command.slice(8, command[12] + 14);
}
else {
command = command.slice(8, command[12] + 13);
}
}
return this.apdu.transmit(command);
}
protected crypt_des_ecb(msg: Buffer, key: Buffer, en: boolean = true): Buffer {
let cipher;
let Keys = [];
let Result = [];
Keys.push(key);
if (key.length == 16)
Keys.push(key.subarray(0, 8));
if (en === true)
if (key.length == 8)
cipher = crypto.createCipheriv("des-ecb", Buffer.concat(Keys), "");
else
cipher = crypto.createCipheriv("des-ede3", Buffer.concat(Keys), "");
else
if (key.length == 8)
cipher = crypto.createDecipheriv("des-ecb", Buffer.concat(Keys), "");
else
cipher = crypto.createDecipheriv("des-ede3", Buffer.concat(Keys), "");
cipher.setAutoPadding(false);
Result.push(cipher.update(msg));
Result.push(cipher.final());
return Buffer.concat(Result);
}
protected crypt_des_cbc(msg: Buffer, key: Buffer, iv: Buffer = GP_DEFAULT_ICV, en: boolean = true): Buffer {
let cipher;
let Keys = [];
let Results = [];
Keys.push(key);
if (key.length == 16)
Keys.push(key.subarray(0, 8));
if (en === true)
if (key.length == 8)
cipher = crypto.createCipheriv("des-cbc", Buffer.concat(Keys), iv);
else
cipher = crypto.createCipheriv("des-ede3-cbc", Buffer.concat(Keys), iv);
else
if (key.length == 8)
cipher = crypto.createDecipheriv("des-cbc", Buffer.concat(Keys), iv);
else
cipher = crypto.createDecipheriv("des-ede3-cbc", Buffer.concat(Keys), iv);
cipher.setAutoPadding(false);
Results.push(cipher.update(msg));
Results.push(cipher.final());
return Buffer.concat(Results);
}
public gp_data_mac(data: Buffer): Buffer {
let mac0 = this.crypt_des_cbc(data, this.command_mac.slice(0, 8), GP_DEFAULT_ICV);
let mac1 = new Buffer(mac0.subarray(mac0.length - 16));
let mac2 = this.crypt_des_ecb(mac1, this.command_mac.slice(16, 16), false);
return this.crypt_des_ecb(mac2, this.command_mac.slice(0, 16));
}
public gp_data_encrypt(data: Buffer): Buffer {
let padded = this.data_padding(data);
return this.crypt_des_cbc(padded, this.session_key_enc);
}
public gp_sensitive_encrypt(data: Buffer): Buffer {
let padded = this.data_padding_ex(data);
return this.crypt_des_ecb(padded, this.session_key_dek);
}
protected data_padding(data: Buffer): Buffer {
let padlen = data.length % 8;
if (padlen == 0)
padlen = 8;
let result = new Buffer(data.length + padlen);
data.copy(result);
result.writeUInt8(0x80, data.length);
result.fill(0x00, data.length + 1);
return result;
}
protected data_padding_ex(data: Buffer): Buffer {
let padlen = data.length % 8;
if (padlen == 0)
return data;
let result = new Buffer(data.length + padlen);
data.copy(result);
result.writeUInt8(0x80, data.length);
result.fill(0x00, data.length + 1);
return result;
}
protected strendswith(s: string, t: string) {
return (s.lastIndexOf(t) == (s.length - t.length));
}
protected strstartswith(s: string, t: string) {
return (s.indexOf(t) == 0);
}
}