Se lee correctamente el token de la tarjeta
This commit is contained in:
266
src/services/NfcService-alt.ts
Normal file
266
src/services/NfcService-alt.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { logger } from "../main/LogService";
|
||||
import { NFC } from "nfc-pcsc";
|
||||
|
||||
function formatHexBuffer(buff: Buffer): string {
|
||||
console.log("LEN:", buff.length);
|
||||
const str = (
|
||||
buff
|
||||
.toString("hex")
|
||||
.replace(/(.{2})/g, "$1 ")
|
||||
.match(/.{1,48}/g) ?? [""]
|
||||
).join("\n");
|
||||
return str;
|
||||
}
|
||||
|
||||
function findIndexOfPattern(buff: Buffer, pattern: number[]): number {
|
||||
if (pattern.length === 0) return -1;
|
||||
|
||||
for (let i = 0; i <= buff.length - pattern.length; i++) {
|
||||
let coincidencia = true;
|
||||
|
||||
for (let j = 0; j < pattern.length; j++) {
|
||||
if (buff[i + j] !== pattern[j]) {
|
||||
coincidencia = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (coincidencia) return i; // Retorna el índice de inicio
|
||||
}
|
||||
|
||||
return -1; // Retorna -1 si no encuentra nada
|
||||
}
|
||||
|
||||
function decodeRecord(buff: Buffer): number {
|
||||
// Se suponde que el byte 0 del token es el inicio del PAN
|
||||
// https://www.eftlab.com/knowledge-base/complete-list-of-emv-nfc-tags
|
||||
const effectiveDate = [0x5f, 0x25];
|
||||
const pan = [0x5a];
|
||||
const panseq = [0x5f, 0x34];
|
||||
const usageControl = [0x9f, 0x07];
|
||||
const cdol = [0x8c];
|
||||
// ISO/IEC 7813
|
||||
const issuerData = [0x57];
|
||||
|
||||
if (buff.length < 164) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1. byte de inicio del PAN
|
||||
const inicio = findIndexOfPattern(buff, pan);
|
||||
|
||||
if (inicio == undefined) return 1;
|
||||
|
||||
// 2. 38 bytes desde ahi (offset de 2) por la instruccion
|
||||
const inicioPAN = inicio + pan.length;
|
||||
const finPan = inicioPAN + 1 + buff[inicioPAN];
|
||||
const fin = inicioPAN + 38; // 38 es supuestamente la longitud del public token
|
||||
//console.log("IDXPAN", inicioPAN, finPan, buff[inicioPAN]);
|
||||
|
||||
const completePAN = buff.subarray(inicioPAN, finPan);
|
||||
console.log("PAN:\n", formatHexBuffer(completePAN));
|
||||
const complete = buff.subarray(inicioPAN, fin);
|
||||
console.log("CODIGO:\n", formatHexBuffer(complete));
|
||||
|
||||
const inicioIDD = findIndexOfPattern(buff, issuerData);
|
||||
if (inicioIDD == undefined) return 1;
|
||||
const lenIDD = buff[inicioIDD + issuerData.length];
|
||||
const complpeteIDD = buff.subarray(
|
||||
inicioIDD,
|
||||
lenIDD + inicioIDD + issuerData.length + 1,
|
||||
);
|
||||
console.log("INICIOIDD", inicioIDD, lenIDD);
|
||||
console.log("IDD:", formatHexBuffer(complpeteIDD));
|
||||
|
||||
// Inmediatamente despues del PAN tiene el codigo 5f 34 (01) (00) que inica que
|
||||
// solo hay una tarjeta con este PAN pero en la imagen de treezor hay un separador
|
||||
// y la fecha de expiracion.
|
||||
|
||||
/**
|
||||
* Para leer el public token:
|
||||
*
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
function findPublicToken(buff: Buffer): string {
|
||||
/**
|
||||
57 - EMV
|
||||
13 - LEN (19)
|
||||
[3B] 54 69 23 - PAN BIN
|
||||
[5B] 08 79 39 81 68 - PAN REMANDER
|
||||
|
||||
[4B]
|
||||
d - separador
|
||||
29 09 - fecha de expiración
|
||||
22 1 - codigo de servicio
|
||||
|
||||
[7B]
|
||||
0 - separador
|
||||
00 00 06 1 - public token (7)
|
||||
97 5 - cvc
|
||||
00 - public token (2)
|
||||
f - padding
|
||||
*/
|
||||
const BYTE_INICIO_TOKEN = 12;
|
||||
const issuerData = [0x57];
|
||||
const emvOffset = issuerData.length + 1;
|
||||
const inicio = findIndexOfPattern(buff, issuerData);
|
||||
const finIndex = inicio + emvOffset + buff[inicio + issuerData.length];
|
||||
const issuerDataBuff = buff.subarray(inicio, finIndex);
|
||||
const inicioData = emvOffset; // EMV + LEN
|
||||
const inicioToken = inicioData + BYTE_INICIO_TOKEN;
|
||||
const tokenbuff = issuerDataBuff.subarray(inicioToken);
|
||||
const tokenstr = tokenbuff.toString("hex");
|
||||
// El primer cuarteto es un 0 de separacion el último es una f de padding
|
||||
const token = tokenstr.substring(1, 8) + tokenstr.substring(11, 13);
|
||||
console.log("Public token", tokenstr, " - ", token);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export type NfcEvent =
|
||||
| { type: "tag"; uid: string }
|
||||
| { type: "removed"; uid: string }
|
||||
| { type: "reader"; name: string }
|
||||
| { type: "error"; message: string };
|
||||
|
||||
// Estan hardcodeados pero en principio no deberia hacer falta más
|
||||
const commands = {
|
||||
app: [0x00, 0xa4, 0x04, 0x00, 0x05, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x00],
|
||||
options: [0x80, 0xa8, 0x00, 0x00, 0x02, 0x83, 0x00, 0x00],
|
||||
record: [0x00, 0xb2, 0x01, 0x14, 0x00],
|
||||
};
|
||||
/** PROVISIONAL */
|
||||
export class NfcServiceAlt {
|
||||
private nfc: NFC;
|
||||
private onEvent?: (event: NfcEvent) => void;
|
||||
private currentReaderName: string = "OFFLINE";
|
||||
|
||||
constructor(onEvent?: (event: NfcEvent) => void) {
|
||||
logger.info("NfcService: Constructor start");
|
||||
try {
|
||||
logger.info("NfcService: Creating PCSC instance...");
|
||||
this.nfc = new NFC(console);
|
||||
logger.info("NfcService: PCSC instance created.");
|
||||
} catch (err) {
|
||||
logger.error("NfcService: Failed to create PCSC instance", err);
|
||||
throw err;
|
||||
}
|
||||
this.onEvent = onEvent;
|
||||
this.init();
|
||||
logger.info("NfcService: Constructor end");
|
||||
}
|
||||
|
||||
public getReaderName(): string {
|
||||
return this.currentReaderName;
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
logger.info(
|
||||
"NfcService: Starting initialization and attaching listeners...",
|
||||
);
|
||||
this.nfc.on("reader", (reader) => {
|
||||
console.log(`Lector detectado: ${reader.name}`);
|
||||
logger.info(`NFC Reader detected: ${reader.name}`);
|
||||
reader.aid = "a0000000041010";
|
||||
|
||||
this.currentReaderName = reader.name;
|
||||
if (this.onEvent) {
|
||||
this.onEvent({ type: "reader", name: reader.name });
|
||||
}
|
||||
reader.autoProcessing = false;
|
||||
|
||||
reader.on("card", async (card) => {
|
||||
const send = async (
|
||||
cmd: number[],
|
||||
comment: string | null = null,
|
||||
responseMaxLength: number = 40,
|
||||
): Promise<Buffer<ArrayBufferLike>> => {
|
||||
const b = Buffer.from(cmd);
|
||||
/*
|
||||
console.log(
|
||||
(comment ? `[${comment}] ` : "") + `sending`,
|
||||
b,
|
||||
);
|
||||
*/
|
||||
const data = await reader.transmit(b, responseMaxLength);
|
||||
/*
|
||||
console.log(
|
||||
(comment ? `[${comment}] ` : "") + `received data \n`,
|
||||
formatHexBuffer(data),
|
||||
);
|
||||
*/
|
||||
return data;
|
||||
};
|
||||
|
||||
console.log(`Tarjeta detectada! UID: ${JSON.stringify(card)}`);
|
||||
logger.info(`NFC Tag detected: ${JSON.stringify(card)}`);
|
||||
|
||||
const res1 = await send(
|
||||
commands.app,
|
||||
"step 1 - select app",
|
||||
255,
|
||||
);
|
||||
const res2 = await send(
|
||||
commands.options,
|
||||
"step 2 - read options",
|
||||
255,
|
||||
);
|
||||
const res3 = await send(
|
||||
commands.record,
|
||||
"step 3 - read record 2 - 1",
|
||||
255,
|
||||
);
|
||||
|
||||
//decodeRecord(res3);
|
||||
const token = findPublicToken(res3);
|
||||
if (this.onEvent) {
|
||||
this.onEvent({ type: "tag", uid: token });
|
||||
}
|
||||
});
|
||||
|
||||
reader.on("card.off", async (card) => {
|
||||
console.log(`Tarjeta retirada: ${card.uid}`);
|
||||
logger.info(`NFC Tag removed: ${card.uid}`);
|
||||
if (this.onEvent) {
|
||||
this.onEvent({ type: "removed", uid: card.uid });
|
||||
}
|
||||
});
|
||||
|
||||
reader.on("error", (err: Error) => {
|
||||
console.error(`Error en el lector ${reader.name}:`, err);
|
||||
if (this.onEvent) {
|
||||
this.onEvent({
|
||||
type: "error",
|
||||
message: err.message || String(err),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
reader.on("end", () => {
|
||||
console.log(`Lector desconectado: ${reader.name}`);
|
||||
logger.info(`NFC Reader disconnected: ${reader.name}`);
|
||||
this.currentReaderName = "OFFLINE";
|
||||
if (this.onEvent) {
|
||||
this.onEvent({ type: "reader", name: "OFFLINE" });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.nfc.on("error", (err: Error) => {
|
||||
console.error("Error general de NFC:", err);
|
||||
if (this.onEvent) {
|
||||
this.onEvent({
|
||||
type: "error",
|
||||
message: err.message || String(err),
|
||||
});
|
||||
}
|
||||
});
|
||||
logger.info("NfcService: Initialization sequence complete.");
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
// La mayoría de los lectores se cierran solos al cerrar la app
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user