Se lee correctamente el token de la tarjeta

This commit is contained in:
2026-04-13 11:35:10 +02:00
parent 6ef2477a49
commit 5905e79357

View 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
}
}