Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,116 @@
/*
CRS AID, TAGS and APDUs based on:
GlobalPlatform Card Technology Contactless Services Card Specification v2.2
– Amendment C Version 1.1.1
UICC APDUs based on:
ETSI TS 102 221 V8.2.0 (2009-06)
Smart Cards; UICC-Terminal interface; Physical and logical characteristics
(Release 8)
*/
/* globals SEUtils */
/* exported APDU, AID, TAG */
'use strict';
(function(exports) {
const AID = {
CRS: 'A00000015143525300',
UICC: 'A0000000871001FF33FFFF8901010100'
};
const APDU = {
// gets the response from SIM, can be used to retireve channel select
getResponse: { cla: 0x00, ins: 0xC0, p1: 0x00, p2: 0x00 },
readBinary: { cla: 0x00, ins: 0xB0, p1: 0x00, p2: 0x00 },
CRS: {
// Get applet version number and global update counter
getData: { cla: 0x80, ins: 0xCA, p1: 0x00, p2: 0xA5 },
// Get status of applets and Security Domains only.
// The ISD is ignored. Get first or only occurrence
getStatusAll1st: {
cla: 0x80, ins: 0xF2, p1: 0x40, p2: 0x00,
data: new Uint8Array([
0x4F, 0x00, // applet AID, empty here
0x5C, 0x03, // tag list to be returned
0x4F, // AID
0x9F, 0x70 // applet life cycle state - first byte;
// contactless activation state - second byte
])
},
// Get status of applets and Security Domains only.
// The ISD is ignored. Get next occurrence(s), if SW is 63h10h
getStatusAllNext: { cla: 0x80, ins: 0xF2, p1: 0x40, p2: 0x01,
data: new Uint8Array([0x4F, 0x00, 0x5C, 0x03, 0x4F, 0x9F, 0x70]) },
// Get status of applet identified with AID
getStatusAID: (aid) => {
return { cla: 0x80, ins: 0xF2, p1: 0x40, p2: 0x00,
data: SEUtils.joinUint8Arrays(
[0x4F, 0x0F], // applet AID
SEUtils.hexStringToByte(aid),
[0x5C, 0x04, // tag list
0x4F, // AID
0x9F, // applet lifecycle state
0x70, // contactless activation state
0x81 // selection priority
]
)
};
},
activateCLF: (aid) => {
return { cla: 0x80, ins: 0xF0, p1: 0x01, p2: 0x01,
data: SEUtils.joinUint8Arrays([0x4F, 0x0F],
SEUtils.hexStringToByte(aid))
};
},
deactivateCLF: (aid) => {
return { cla: 0x80, ins: 0xF0, p1: 0x01, p2: 0x00,
data: SEUtils.joinUint8Arrays([0x4F, 0x0F],
SEUtils.hexStringToByte(aid))
};
},
nfcActivate: { cla: 0x80, ins: 0xF0, p1: 0x04, p2: 0x80,
data: new Uint8Array([0x80, 0x01, 0x40]) },
nfcDeactivate: { cla: 0x80, ins: 0xF0, p1: 0x04, p2: 0x00,
data: new Uint8Array([0x80, 0x01, 0x40]) }
},
// PIN commands
UICC: {
getAttempts: (p2) => {
return { cla: 0x00, ins: 0x20, p1: 0x00, p2: p2 };
},
verify: (p2, pinBytes) => {
return { cla: 0x00, ins: 0x20, p1: 0x00, p2: p2, data: pinBytes };
},
enable: (p2, defPinBytes) => {
return { cla: 0x00, ins: 0x28, p1: 0x00, p2: p2, data: defPinBytes };
},
disable: (p2, defPinBytes) => {
return { cla: 0x00, ins: 0x26, p1: 0x00, p2: p2, data: defPinBytes };
},
change: (p2, curPinBytes, newPinBytes) => {
var data = SEUtils.joinUint8Arrays(curPinBytes, newPinBytes);
return { cla: 0x00, ins: 0x24, p1: 0x00, p2: p2, data: data };
},
unblock: (p2, pukBytes, defPinBytes) => {
var data = SEUtils.joinUint8Arrays(pukBytes, defPinBytes);
return { cla: 0x00, ins: 0x2C, p1: 0x00, p2: p2, data: data };
}
}
};
const TAG = {
CRS: {
APP_TEMPLATE: 0x61,
AID: 0x4F,
STATE: 0x9F70
},
};
exports.AID = AID;
exports.APDU = APDU;
exports.TAG = TAG;
}((typeof exports === 'undefined') ? window : exports));

View file

@ -0,0 +1,90 @@
'use strict';
/* exported SEUtils */
(function(exports) {
var SEUtils = {
byteToHexString: function byteToHexString(uint8arr) {
if (!uint8arr) {
return '';
}
var hexStr = '';
for (var i = 0; i < uint8arr.length; i++) {
var hex = (uint8arr[i] & 0xff).toString(16);
hex = (hex.length === 1) ? '0' + hex : hex;
hexStr += hex;
}
return hexStr.toUpperCase();
},
hexStringToByte: function hexStringToByte(str) {
var a = [];
for(var i = 0, len = str.length; i < len; i+=2) {
a.push(parseInt(str.substr(i,2),16));
}
return new Uint8Array(a);
},
byteToASCIIString: function byteToASCIIString(arr) {
var str = '';
var len = arr && arr.length ? arr.length : 0;
for(var i = 0; i < len; i++) {
str += arr[i] < 0x20 || arr > 0x7E ? '' : String.fromCharCode(arr[i]);
}
return str;
},
joinUint8Arrays: function joinUint8Arrays() {
var args = Array.prototype.slice.call(arguments);
var length = args.reduce(function(a, b) { return a + b.length; }, 0);
var out = new Uint8Array(length);
args.reduce(function(previousLen, buffer) {
out.set(buffer, previousLen);
return previousLen + buffer.length;
}, 0);
return out;
},
// no support for multibyte tag and constructed data object
parseSimpleTLV: function parseSimpleTLV(data) {
if(!data) {
return null;
}
var result = {};
for(var i = 0, len = data.length; i < len;) {
var tag = data[i];
var length = data[i+1];
var value = data.slice(i+2, i+2 + length);
result[tag] = value;
i += 2 + length;
}
return result;
},
promises: {
whilePromise: function(condition, action, init) {
return new Promise((resolve, reject) => {
var loop = (value) => {
if (condition(value)) {
return resolve(value);
}
return action(value).then(loop).catch(reject);
};
loop(init);
});
},
},
};
exports.SEUtils = SEUtils;
}((typeof exports === 'undefined') ? window : exports));

View file

@ -0,0 +1,187 @@
'use strict';
/* globals SEUtils, APDU, ERRORS, DEFAULTS */
/* exported simAccessManager */
(function(exports) {
const SIM_ACCESS_DISABLED = 'disabled';
const SIM_ACCESS_IDLE = 'idle';
const SIM_ACCESS_BUSY = 'busy';
var _simState = SIM_ACCESS_DISABLED;
var _seSession = null;
var _crsAid = DEFAULTS.CRS_AID;
var _uiccAid = DEFAULTS.UICC_AID;
var _pinP2 = DEFAULTS.PIN_P2;
var SIMAccessManager = {
set crsAid(value) {
_crsAid = value;
},
get crsAidBytes() {
return SEUtils.hexStringToByte(_crsAid);
},
set uiccAid(value) {
_uiccAid = value;
},
get uiccAidBytes() {
return SEUtils.hexStringToByte(_uiccAid);
},
set pinP2(value) {
_pinP2 = value;
},
get pinP2Bytes() {
return SEUtils.hexStringToByte(_pinP2)[0];
},
start: function start() {
if(!navigator.seManager) {
return Promise.reject(ERRORS.GLOBAL.NO_API);
}
return navigator.seManager.getSEReaders()
.then((readers) => {
var reader = readers.find(r => r.type.toLowerCase() === 'uicc' && r.isSEPresent);
if(!reader) {
return Promise.reject(ERRORS.SIM.NO_READER);
}
return reader.openSession();
})
.then((session) => {
_seSession = session;
_simState = SIM_ACCESS_IDLE;
})
.catch((e) => {
console.error(e);
_simState = SIM_ACCESS_DISABLED;
if(e !== ERRORS.SIM.NO_READER) {
e = ERRORS.SIM.ACCESS_FAILED;
}
return Promise.reject(e);
});
},
stop: function stop() {
return _seSession.closeAll()
.then(() => {
_seSession = null;
_simState = SIM_ACCESS_DISABLED;
})
.catch(e => this._cleanup(e, ERRORS.SIM.SIM_FAILURE));
},
_initSIMAccess: function _initSIMAccess() {
if(_simState !== SIM_ACCESS_IDLE) {
return Promise.reject(ERRORS.SIM.NOT_IDLE);
}
if(!_seSession.reader.isSEPresent) {
return Promise.reject(ERRORS.SIM.NO_READER);
}
_simState = SIM_ACCESS_BUSY;
return Promise.resolve();
},
_cleanup: function _cleanup(error, message) {
console.error(error);
_simState = SIM_ACCESS_IDLE;
if(error === ERRORS.SIM.NOT_IDLE || error === ERRORS.SIM.NO_READER) {
message = error;
}
return Promise.reject(message);
},
getAppletsData: function getAppletsData() {
return this._initSIMAccess()
.then(() => _seSession.openLogicalChannel(this.crsAidBytes))
.then((channel) => {
return channel.transmit(APDU.CRS.getStatusAll1st);
})
.then((resp) => {
var receivedData = new Uint8Array();
// we can get different responses, also propriatary
// using counter to not allow to try to query the SIM indifinately
var count = 0;
var condition = (resp) => (count++ > 10 || resp.sw1 === 0x90 && resp.sw2 === 0x00);
var action = (resp) => {
receivedData = SEUtils.joinUint8Arrays(receivedData, resp.data);
return resp.channel.transmit(APDU.CRS.getStatusAllNext);
};
return SEUtils.promises.whilePromise(condition, action, resp)
.then((lastResp) => {
lastResp.channel.close();
_simState = SIM_ACCESS_IDLE;
return SEUtils.joinUint8Arrays(receivedData, lastResp.data);
});
})
.catch(e => this._cleanup(e, ERRORS.SIM.APPLETS_FAILURE));
},
toggleCLF: function toggleCLF(on, aid) {
var apdu = on ? APDU.CRS.activateCLF(aid) : APDU.CRS.deactivateCLF(aid);
return this._initSIMAccess()
.then(() => _seSession.openLogicalChannel(this.crsAidBytes))
.then((channel) => channel.transmit(apdu))
.then((resp) => {
return resp.channel.close()
.then(() => {
_simState = SIM_ACCESS_IDLE;
return resp.sw1 === 0x90 && resp.sw2 === 0x00;
});
})
.catch(e => this._cleanup(e, ERRORS.SIM.SIM_FAILURE));
},
_execPinCommand: function _execPinCommand(type, pin1, pin2) {
return this._initSIMAccess()
.then(() => _seSession.openLogicalChannel(this.uiccAidBytes))
.then((channel) => {
var apdu = APDU.UICC[type](this.pinP2Bytes, pin1, pin2);
if(!apdu) {
return Promise.reject('PIN APDU type ' + type + ' not found');
}
return channel.transmit(apdu);
})
.then((resp) => {
return resp.channel.close().then(() => {
_simState = SIM_ACCESS_IDLE;
return SEUtils.byteToHexString([resp.sw1, resp.sw2]);
});
})
.catch(e => this._cleanup(e, ERRORS.SIM.PIN_FAILURE));
},
verifyPIN: function verifyPIN(pin) {
return this._execPinCommand('verify', pin);
},
enablePIN: function enablePIN(pin) {
return this._execPinCommand('enable', pin);
},
unblockPIN: function unblockPIN(puk, pin) {
return this._execPinCommand('unblock', puk, pin);
},
disablePIN: function disablePIN(pin) {
return this._execPinCommand('disable', pin);
},
changePIN: function changePIN(currentPin, newPin) {
return this._execPinCommand('change', currentPin, newPin);
},
};
exports.SIMAccess = SIMAccessManager;
}((typeof exports === 'undefined') ? window : exports));