Commit ff7cc3db by Administrator

PSAM卡接入

parent acac892d
'use strict';
import {EventEmitter} from 'events';
import hexify from 'hexify';
import CmdApdu from './CmdApdu';
import ResApdu from './ResApdu';
//INS编码
const ins = {
//追加记录
APPEND_RECORD: 0xE2,
ENVELOPE: 0xC2,
ERASE_BINARY: 0x0E,
//外部认证
EXTERNAL_AUTHENTICATE: 0x82,
//产生随机数
GET_CHALLENGE: 0x84,
//
GET_DATA: 0xCA,
//取响应
GET_RESPONSE: 0xC0,
//内部认证
INTERNAL_AUTHENTICATE: 0x88,
MANAGE_CHANNEL: 0x70,
PUT_DATA: 0xDA,
//读二进制
READ_BINARY: 0xB0,
//读记录
READ_RECORD: 0xB2,
//选择文件
SELECT_FILE: 0xA4,
//修改二进制
UPDATE_BINARY: 0xD6,
//修改记录
UPDATE_RECORD: 0xDC,
//校验PIN
VERIFY: 0x20,
WRITE_BINARY: 0xD0,
WRITE_RECORD: 0xD2,
//导出母卡密钥
OUT_KEY: 0xF6
};
class Application extends EventEmitter {
constructor(card) {
super();
this.card = card;
}
send(cmdApdu) {
return this.card
.sendCmd(cmdApdu)
.then(resp => {
var response = new ResApdu(resp);
//console.log(`状态码: '${response.statusCode()}'`);
if (response.hasMoreBytesAvailable()) {
//console.log(`长度: '${response.data.length}' `);
return this.getResponse(response.numberOfBytesAvailable()).then((resp) => {
var resp = new ResApdu(resp);
return new ResApdu(response.getDataOnly() + resp.data);
});
} else if (response.isWrongLength()) {
cmdApdu.setLe(response.correctLength());
return this.send(cmdApdu).then((resp) => {
var resp = new ResApdu(resp);
return new ResApdu(response.getDataOnly() + resp.data);
});
}
return response;
});
};
/**
* 选择文件
*/
selectFile(bytes, p1, p2) {
var cmd = new CmdApdu({
cla: 0x00,
ins: ins.SELECT_FILE,
p1: p1 || 0x04,
p2: p2 || 0x00,
data: bytes
});
return this.send(cmd).then((response) => {
if (response.isOk()) {
this.emit('application-selected', {
application: hexify.toHexString(bytes)
});
}
return response;
});
};
/**
* 读取响应信息
*
*/
getResponse(length) {
return this.send(new CmdApdu({
cla: 0x00,
ins: ins.GET_RESPONSE,
p1: 0x00,
p2: 0x00,
le: length
}));
};
/**
* 获取母卡导出密钥
*/
getOutKey(data) {
if (typeof data === 'string') {
data = hexify.toByteArray(data);
}
var cmd = {
cla: 0x80,
ins: 0xF6,
p1: 0x07,
p2: 0x10,
lc: 0x08,
data: data
};
return this.send(new CmdApdu(cmd));
}
/**
* 获取随机数
*/
getChallenge() {
var cmd = {
cla: 0x00,
ins: 0x84,
p1: 0x00,
p2: 0x00,
le: 0x04
};
return this.send(new CmdApdu(cmd));
}
/**
* 读取记录
*/
readRecord(sfi, record) {
//console.log(`Application.readRecord, sfi='${sfi}', record=${record}`);
return this.send(new CmdApdu({
cla: 0x00,
ins: ins.READ_RECORD,
p1: record,
p2: (sfi << 3) + 4,
le: 0
}));
};
getData(p1, p2) {
return this.send(new CmdApdu({
cla: 0x00,
ins: ins.GET_DATA,
p1: p1,
p2: p2,
le: 0
}));
};
}
module.exports = Application;
\ No newline at end of file
'use strict';
/**
* <pre>
*
* 描述:
* 版本:1.0.0
* 日期:2019/01/16
* 作者:ningzp@junmp.com.cn
* <br>修改记录
* <br>修改日期 修改人 修改内容
*
* </pre>
*/
import {EventEmitter} from 'events';
import hexify from 'hexify';
import ResApdu from './ResApdu';
class Card extends EventEmitter {
constructor(device, attr, protocol) {
super();
this.device = device;
this.protocol = protocol;
this.attr = attr.toString('hex');
}
/**
* 获取PSAM卡序号
*/
getAttr() {
return this.attr;
}
toString() {
return `Card(attr:'${this.attr}')`;
}
/**
* 发送指令
*
* @param cmdApdu APDU指令
* @param callback 回调函数
*/
sendCmd(cmdApdu, callback) {
let buffer;
if (Array.isArray(cmdApdu)) {
buffer = new Buffer(cmdApdu);
} else if (typeof cmdApdu === 'string') {
buffer = new Buffer(hexify.toByteArray(cmdApdu));
} else if (Buffer.isBuffer(cmdApdu)) {
buffer = cmdApdu;
} else {
buffer = cmdApdu.toBuffer();
}
const protocol = this.protocol;
//指令发送事件
this.emit('cmd-send', {card: this, command: cmdApdu});
if (callback) {
//如果有回调接受
this.device.transmit(buffer, 0x102, protocol, (err, response) => {
//响应接受事件
this.emit('response-received', {
card: this,
command: cmdApdu,
response: new ResApdu(response)
})
});
} else {
// 定义默认回调接受
return new Promise((resolve, reject) => {
this.device.transmit(buffer, 0x102, protocol, (err, response) => {
if (err) {
reject(err);
} else {
this.emit('response-received', {
card: this,
command: cmdApdu,
response: new ResApdu(response)
});
resolve(response)
}
});
});
}
}
}
export default Card;
\ No newline at end of file
'use strict';
/**
* <pre>
* CLA INS P1 P2 Lc
*
* 描述:APDU指令包装
* 版本:1.0.0
* 日期:2019/01/16
* 作者:ningzp@junmp.com.cn
* <br>修改记录
* <br>修改日期 修改人 修改内容
*
* </pre>
*/
import {EventEmitter} from 'events';
import hexify from 'hexify';
class CmdApdu {
constructor(obj) {
if (obj.bytes) {
this.bytes = obj.bytes;
} else {
let size = obj.size;
let cla = obj.cla;
let ins = obj.ins;
let p1 = obj.p1;
let p2 = obj.p2;
let data = obj.data;
let le = obj.le || 0;
let lc;
// case 1
if (!size && !data && !le) {
size = 4;
}
// case 2
else if (!size && !data) {
//console.info('case 2');
size = 4 + 2;
}
// case 3
else if (!size && !le) {
//console.info('case 3');
size = data.length + 5 + 4;
//le = -1;
}
// case 4
else if (!size) {
//console.info('case 4');
size = data.length + 5 + 4;
}
// set data
if (data) {
lc = data.length;
} else {
//lc = 0;
}
this.bytes = [];
this.bytes.push(cla);
this.bytes.push(ins);
this.bytes.push(p1);
this.bytes.push(p2);
if (data) {
this.bytes.push(lc);
this.bytes = this.bytes.concat(data);
}
if(le){
this.bytes.push(le);
}
}
console.log(this.toString());
}
toString() {
return hexify.toHexString(this.bytes);
}
toByteArray() {
return this.bytes;
}
toBuffer() {
return new Buffer(this.bytes);
}
setLe(le) {
this.bytes.pop();
this.bytes.push(le);
}
}
export default CmdApdu;
\ No newline at end of file
'use strict';
/**
* <pre>
*
*
* 描述:设备接入类
* 版本:1.0.0
* 日期:2019/01/16
* 作者:ningzp@junmp.com.cn
* <br>修改记录
* <br>修改日期 修改人 修改内容
*
* </pre>
*/
import Card from './Card';
import {EventEmitter} from 'events';
class Device extends EventEmitter {
constructor(reader) {
super();
this.reader = reader;
this.name = reader.name;
this.card = null;
/**
* 卡片是否已插入
*/
const isCardInserted = (changes, reader, status) => {
return (changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT);
};
/**
* 卡片是否已拔出
*/
const isCardRemoved = (changes, reader, status) => {
return (changes & reader.SCARD_STATE_EMPTY) && (status.state & reader.SCARD_STATE_EMPTY);
};
/**
* 卡片已插入
*/
const cardInserted = (reader, status) => {
reader.connect({share_mode: 2}, (err, protocol) => {
if (err) {
this.emit('error', err);
} else {
this.card = new Card(this, status.atr, protocol);
//卡片已插入监听事件
this.emit('card-inserted', {device: this, card: this.card});
}
});
};
/**
* 卡片已拔出
*/
const cardRemoved = (reader) => {
const name = reader.name;
reader.disconnect(reader.SCARD_LEAVE_CARD, (err) => {
if (err) {
this.emit('error', err);
} else {
//卡片已拔出监听事件
this.emit('card-removed', {name, card: this.card});
this.card = null;
}
});
};
/**
* 状态触发事件
*/
reader.on('status', (status) => {
var changes = reader.state ^ status.state;
if (changes) {
if (isCardRemoved(changes, reader, status)) {
cardRemoved(reader);
} else if (isCardInserted(changes, reader, status)) {
cardInserted(reader, status);
}
}
});
}
/**
* 指令传输
*
* @param data 指令
* @param res_len 长度
* @param protocol 协议
* @param cb 回调
*/
transmit(data, res_len, protocol, cb) {
this.reader.transmit(data, res_len, protocol, cb);
}
getName() {
return this.name;
}
toString() {
return `${this.getName()}`;
}
}
export default Device;
\ No newline at end of file
'use strict';
/**
* <pre>
*
*
* 描述:
* 版本:1.0.0
* 日期:2019/01/16
* 作者:ningzp@junmp.com.cn
* <br>修改记录
* <br>修改日期 修改人 修改内容
*
* </pre>
*/
const pcsclite = require('@pokusew/pcsclite');
import {EventEmitter} from 'events';
import Device from './Device';
class Devices extends EventEmitter {
constructor() {
super();
this.pcsc = pcsclite();
this.devices = {};
this.pcsc.on('reader', (reader) => {
const device = new Device(reader);
this.devices[reader.name] = device;
//设备保活事件
this.emit('device-activated', {device, devices: this.listDevices()});
reader.on('end', () => {
delete this.devices[reader.name];
this.emit('device-deactivated', {device, devices: this.listDevices()});
});
reader.on('error', (error) => {
this.emit('error', {reader, error});
});
});
this.pcsc.on('error', (error) => {
this.emit('error', {error});
});
}
/**
* 设备激活状态
*/
onActivated() {
return new Promise((resolve, reject) => {
this.on('device-activated', event => resolve(event));
});
};
/**
* 设备非激活状态
*/
onDeactivated() {
return new Promise((resolve, reject) => {
this.on('device-deactivated', event => resolve(event));
});
};
/**
* 设备列表
*/
listDevices() {
return Object.keys(this.devices).map((k) => this.devices[k])
};
lookup(name) {
return this.devices[name];
};
toString() {
return `Devices('${this.listDevices()}')`;
}
}
module.exports = Devices;
\ No newline at end of file
'use strict';
/**
* <pre>
*
* 描述:封装APDU指令的响应
* 版本:1.0.0
* 日期:2019/01/16
* 作者:ningzp@junmp.com.cn
* <br>修改记录
* <br>修改日期 修改人 修改内容
*
* </pre>
*/
const statusCodes = {
'^9000$': '正常结束',
'^61(.{2})$': '正常结束,有XX个有效数据可取'
};
class ResApdu {
constructor(buffer) {
this.buffer = buffer;
this.data = buffer.toString('hex');
}
/**
* 获取响应的4位状态码
*/
getStatusCode() {
return this.data.substr(-4);
}
/**
* 获取响应的数据
*/
getDataOnly() {
return this.data.substr(0, this.data.length - 4);
}
/**
* 响应是否成功
*/
isOk() {
return this.getStatusCode() === '9000';
}
buffer() {
return this.buffer;
}
/**
* 验证61XX响应码是否还有数据需要读取
*/
hasMoreBytesAvailable() {
return this.data.substr(-4, 2) === '61';
}
/**
* 结合61XX返回byte的长度
* @see hasMoreBytesAvailable
*/
numberOfBytesAvailable() {
let hexLength = this.data.substr(-2, 2);
return parseInt(hexLength, 16);
}
/**
* 是否长度错误
*/
isWrongLength() {
return this.data.substr(-4, 2) === '6c';
}
/**
* 校正长度
*/
correctLength() {
let hexLength = this.data.substr(-2, 2);
return parseInt(hexLength, 16);
}
toString() {
return this.data.toString('hex');
}
}
export default ResApdu;
\ No newline at end of file
'use strict';
import Application from './Application';
import CmdApdu from './CmdApdu';
import ResApdu from './ResApdu';
import Devices from './Devices';
import Device from './Device';
import Card from './Card';
module.exports = {
Application,
CmdApdu,
ResApdu,
Devices,
Device,
Card
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论