apdu.js
Summary
Implementation of ISO 7816-4 APDU processing
Class Summary
|
APDU |
Class implementing support for command and response APDUs
|
DataUnitAPDU |
Adapter class to decode APDUs for data unit handling
|
function APDU() {
if (arguments.length > 0) {
var arg = arguments[0];
if (arg instanceof ByteString) {
if (arguments.length != 1) {
throw new GPError("APDU", GPError.INVALID_ARGUMENTS, APDU.SW_GENERALERROR, "Only one argument of type ByteString expected");
}
this.fromByteString(arg);
} else {
if ((arguments.length < 4) || (arguments.length > 6)) {
throw new GPError("APDU", GPError.INVALID_ARGUMENTS, APDU.SW_GENERALERROR, "4 to 6 arguments expected");
}
for (var i = 0; i < 4; i++) {
if (typeof(arguments[i]) != "number") {
throw new GPError("APDU", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument must be of type Number");
}
}
this.cla = arguments[0];
this.ins = arguments[1];
this.p1 = arguments[2];
this.p2 = arguments[3];
var i = 4;
if (arguments.length > i) {
if (arguments[i] instanceof ByteString) {
this.cdata = arguments[i];
i++;
}
}
if (arguments.length > i) {
if (typeof(arguments[i]) != "number") {
throw new GPError("APDU", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument must be of type Number");
}
this.ne = arguments[i];
}
}
}
this.rapdu = null;
this.SW = APDU.SW_GENERALERROR;
}
APDU.INS_DEACTIVATE = 0x04;
APDU.INS_VERIFY = 0x20;
APDU.INS_MANAGE_SE = 0x22;
APDU.INS_CHANGE_REFERENCE_DATA = 0x24;
APDU.INS_PSO = 0x2A;
APDU.INS_RESET_RETRY_COUNTER = 0x2C;
APDU.INS_ACTIVATE = 0x44;
APDU.INS_GENERATE_KEY_PAIR = 0x46;
APDU.INS_EXTERNAL_AUTHENTICATE = 0x82;
APDU.INS_GET_CHALLENGE = 0x84;
APDU.INS_GENERAL_AUTHENTICATE = 0x86;
APDU.INS_COMPUTE_DIGITAL_SIGN = 0x9E;
APDU.INS_SELECT = 0xA4;
APDU.INS_READBINARY = 0xB0;
APDU.INS_READ_BINARY = 0xB0;
APDU.INS_READ_RECORD = 0xB2;
APDU.INS_VERIFY_CERTIFICATE = 0xBE;
APDU.INS_UPDATE_BINARY = 0xD6;
APDU.INS_TERMINATE = 0xE6;
APDU.SW_OK = 0x9000;
APDU.SW_TIMEOUT = 0x6401;
APDU.SW_OKMOREDATA = 0x6100;
APDU.SW_WARNING = 0x6200;
APDU.SW_WARNING1 = 0x6201;
APDU.SW_DATAINV = 0x6281;
APDU.SW_EOF = 0x6282;
APDU.SW_INVFILE = 0x6283;
APDU.SW_INVFORMAT = 0x6284;
APDU.SW_WARNINGNVCHG = 0x6300;
APDU.SW_WARNINGCOUNT = 0x63C0;
APDU.SW_WARNING0LEFT = 0x63C0;
APDU.SW_WARNING1LEFT = 0x63C1;
APDU.SW_WARNING2LEFT = 0x63C2;
APDU.SW_WARNING3LEFT = 0x63C3;
APDU.SW_EXECERR = 0x6400;
APDU.SW_MEMERR = 0x6501;
APDU.SW_MEMERRWRITE = 0x6581;
APDU.SW_WRONGLENGTH = 0x6700;
APDU.SW_CLANOTSUPPORTED = 0x6800;
APDU.SW_LCNOTSUPPORTED = 0x6881;
APDU.SW_SMNOTSUPPORTED = 0x6882;
APDU.SW_LASTCMDEXPECTED = 0x6883;
APDU.SW_CHAINNOTSUPPORTED = 0x6884;
APDU.SW_COMNOTALLOWED = 0x6900;
APDU.SW_COMINCOMPATIBLE = 0x6981;
APDU.SW_SECSTATNOTSAT = 0x6982;
APDU.SW_AUTHMETHLOCKED = 0x6983;
APDU.SW_REFDATANOTUSABLE = 0x6984;
APDU.SW_CONDOFUSENOTSAT = 0x6985;
APDU.SW_COMNOTALLOWNOEF = 0x6986;
APDU.SW_SMOBJMISSING = 0x6987;
APDU.SW_INCSMDATAOBJECT = 0x6988;
APDU.SW_INVPARA = 0x6A00;
APDU.SW_INVDATA = 0x6A80;
APDU.SW_FUNCNOTSUPPORTED = 0x6A81;
APDU.SW_NOAPPL = 0x6A82;
APDU.SW_FILENOTFOUND = 0x6A82;
APDU.SW_RECORDNOTFOUND = 0x6A83;
APDU.SW_OUTOFMEMORY = 0x6A84;
APDU.SW_INVLCTLV = 0x6A85;
APDU.SW_INVACC = 0x6A85;
APDU.SW_INCP1P2 = 0x6A86;
APDU.SW_INVLC = 0x6A87;
APDU.SW_RDNOTFOUND = 0x6A88;
APDU.SW_FILEEXISTS = 0x6A89;
APDU.SW_DFNAMEEXISTS = 0x6A8A;
APDU.SW_INVP1P2 = 0x6B00;
APDU.SW_INVLE = 0x6C00;
APDU.SW_INVINS = 0x6D00;
APDU.SW_INVCLA = 0x6E00;
APDU.SW_ACNOTSATISFIED = 0x9804;
APDU.SW_NOMORESTORAGE = 0x9210;
APDU.SW_GENERALERROR = 0x6F00;
APDU.prototype.fromByteString = function(bs) {
if (bs.length < 4) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_GENERALERROR, "Command APDU must be at least 4 bytes long");
}
this.cla = bs.byteAt(0);
this.ins = bs.byteAt(1);
this.p1 = bs.byteAt(2);
this.p2 = bs.byteAt(3);
if (bs.length > 4) {
var extended = false;
var i = 4;
var l = bs.length - i;
var n = bs.byteAt(i++);
l--;
if ((n == 0) && (l > 0)) {
extended = true;
if (l < 2) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Extended length APDU too short");
}
n = (bs.byteAt(i) << 8) + bs.byteAt(i + 1);
i += 2;
l -= 2;
}
if (l > 0) {
if (l < n) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Lc in APDU");
}
this.cdata = bs.bytes(i, n);
i += n;
l -= n;
if (l > 0) {
n = bs.byteAt(i++);
l--;
if (extended) {
if (l < 1) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Le in extended APDU");
}
n = (n << 8) + bs.byteAt(i++);
l--;
}
this.ne = (extended && (n == 0) ? 65536 : n);
}
} else {
this.ne = (extended && (n == 0) ? 65536 : n);
}
if (l > 0) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Too many bytes in APDU");
}
}
}
APDU.prototype.getCommandAPDU = function() {
var bb = new ByteBuffer();
bb.append(this.cla);
bb.append(this.ins);
bb.append(this.p1);
bb.append(this.p2);
var hasCData = (typeof(this.cdata) != "undefined");
var hasNe = (typeof(this.ne) != "undefined");
var extended = ((hasCData && this.cdata.length > 255) ||
(hasNe && this.ne > 256));
if (extended) {
bb.append(0);
}
if (hasCData && this.cdata.length > 0) {
if (extended) {
bb.append(this.cdata.length >> 8);
}
bb.append(this.cdata.length & 0xFF);
bb.append(this.cdata);
}
if (hasNe) {
if (extended) {
bb.append(this.ne >> 8);
}
bb.append(this.ne & 0xFF);
}
return bb.toByteString();
}
APDU.prototype.getResponseAPDU = function() {
var bb = new ByteBuffer();
if (this.rdata) {
bb.append(this.rdata);
}
bb.append(this.SW >> 8);
bb.append(this.SW & 0xFF);
return bb.toByteString();
}
APDU.prototype.getCLA = function() {
return this.cla;
}
APDU.prototype.isISO = function() {
return (this.cla & 0x80) == 0x00;
}
APDU.prototype.isChained = function() {
return (this.cla & 0x10) == 0x10;
}
APDU.prototype.isSecureMessaging = function() {
return (this.cla & 0x08) == 0x08;
}
APDU.prototype.isAuthenticatedHeader = function() {
return (this.cla & 0x0C) == 0x0C;
}
APDU.prototype.getINS = function() {
return this.ins;
}
APDU.prototype.getP1 = function() {
return this.p1;
}
APDU.prototype.getP2 = function() {
return this.p2;
}
APDU.prototype.setCData = function(cdata) {
this.cdata = cdata;
}
APDU.prototype.getCData = function() {
return this.cdata;
}
APDU.prototype.hasCData = function() {
return ((typeof(this.cdata) != "undefined") && (this.cdata != null));
}
APDU.prototype.getCDataAsTLVList = function() {
if (typeof(this.cdata) == "undefined") {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data in command APDU");
}
try {
var a = new TLVList(this.cdata, TLV.EMV);
}
catch(e) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid TLV data in command APDU");
}
return a;
}
APDU.prototype.getNe = function() {
return this.ne;
}
APDU.prototype.hasLe = function() {
return typeof(this.ne) != "undefined";
}
APDU.prototype.setSecureChannel = function(secureChannel) {
this.secureChannel = secureChannel;
}
APDU.prototype.getSecureChannel = function() {
return this.secureChannel;
}
APDU.prototype.hasSecureChannel = function() {
return (typeof(this.secureChannel) != "undefined") && (this.secureChannel != null);
}
APDU.prototype.wrap = function() {
if (this.hasSecureChannel()) {
this.secureChannel.wrap(this);
}
}
APDU.prototype.unwrap = function() {
if (this.hasSecureChannel()) {
this.secureChannel.unwrap(this);
}
}
APDU.prototype.setRData = function(data) {
this.rdata = data;
}
APDU.prototype.getRData = function() {
return this.rdata;
}
APDU.prototype.hasRData = function() {
return ((typeof(this.rdata) != "undefined") && (this.rdata != null));
}
APDU.prototype.setSW = function(sw) {
this.SW = sw;
}
APDU.prototype.getSW = function() {
return this.SW;
}
APDU.prototype.toString = function() {
return this.getCommandAPDU().toString(HEX) + " : " + this.getResponseAPDU().toString(HEX);
}
APDU.test = function() {
var a = new APDU(0x00, 0xA4, 0x00, 0x0C);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C");
var c = new APDU(b);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 0);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C00");
var c = new APDU(b);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 65536);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C000000");
var c = new APDU(b);
print(c);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C023F00");
var c = new APDU(b);
assert(a.toString() == c.toString());
var data = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX));
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C000100" + data);
var c = new APDU(b);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 0);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C023F0000");
var c = new APDU(b);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX), 0);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C000100" + data + "0000");
var c = new APDU(b);
assert(a.toString() == c.toString());
var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 65536);
print(a);
var b = a.getCommandAPDU();
assert(b.toString(HEX) == "00A4000C0000023F000000");
var c = new APDU(b);
assert(a.toString() == c.toString());
}
function DataUnitAPDU(apdu) {
this.apdu = apdu;
var p1 = apdu.getP1();
if ((this.apdu.getINS() & 1) == 0) {
if ((p1 & 0x80) == 0x80) {
this.offset = this.apdu.getP2();
this.sfi = p1 & 0x1F;
} else {
this.offset = (p1 << 8) + this.apdu.getP2();
}
this.data = apdu.getCData();
} else {
var p2 = apdu.getP2();
var fid = (p1 << 8) + p2;
if (((fid & 0xFFE0) == 0) && ((fid & 0x1F) >= 1) && ((fid & 0x1F) <= 30)) {
this.sfi = fid & 0x1F;
} else if (fid != 0) {
var bb = new ByteBuffer();
bb.append(p1);
bb.append(p2);
this.fid = bb.toByteString();
}
var a = this.apdu.getCDataAsTLVList();
if ((a.length < 1) || (a.length > 2)) {
throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, less than one or more than two elements in TLV");
}
var o = a.index(0);
if (o.getTag() != 0x54) {
throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, first tag must be '54' offset");
}
this.offset = o.getValue().toUnsigned();
if (a.length == 2) {
var o = a.index(1);
var t = o.getTag();
if ((t != 0x53) && (t != 0x73)) {
throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, second tag must be '53' or '73'");
}
this.data = o.getValue();
}
}
}
DataUnitAPDU.prototype.getSFI = function() {
if (typeof(this.sfi) == "undefined") {
return -1;
}
return this.sfi;
}
DataUnitAPDU.prototype.getFID = function() {
if (typeof(this.fid) == "undefined") {
return null;
}
return this.fid;
}
DataUnitAPDU.prototype.getOffset = function() {
return this.offset;
}
DataUnitAPDU.prototype.getCData = function() {
if (!this.hasCData()) {
throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data in command APDU");
}
return this.data;
}
DataUnitAPDU.prototype.hasCData = function() {
return ((typeof(this.data) != "undefined") && (this.data != null));
}
DataUnitAPDU.test = function() {
var apdu = new APDU(0x00, 0xB0, 0, 0, 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB0, 0x7F, 0xFF, 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0x7FFF);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB0, 0x80, 0, 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB0, 0x80, 0xFF, 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0xFF);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("540100", HEX), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("5401FF", HEX), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0xFF);
assert(!dh.hasCData());
var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("540401000000", HEX), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0x01000000);
assert(!dh.hasCData());
var data = new ByteString("1234", ASCII);
var apdu = new APDU(0x00, 0xD6, 0, 0, data);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD6, 0x7F, 0xFF, data);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0x7FFF);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD6, 0x80, 0, data);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD6, 0x80, 0xFF, data);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0xFF);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5401005304", HEX)).concat(data), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5401FF5304", HEX)).concat(data), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0xFF);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5404010000005304", HEX)).concat(data), 0);
var dh = new DataUnitAPDU(apdu);
assert(dh.getOffset() == 0x01000000);
assert(dh.hasCData());
assert(dh.getCData().equals(data));
}
Documentation generated by
JSDoc on Tue Sep 3 22:29:41 2013