UNPKG

js-gp

Version:

implementation of globalplatform

571 lines (514 loc) 20.7 kB
"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); } }