diff --git a/src/main/index.ts b/src/main/index.ts index 965e3de..53b8175 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2,11 +2,10 @@ import { app, shell, BrowserWindow, ipcMain } from "electron"; import { join } from "path"; import { electronApp, optimizer, is } from "@electron-toolkit/utils"; import icon from "../../resources/icon.png?asset"; -import { NfcService } from "../services/NfcService"; import { labelReqHandler, printReqHandler } from "./handlers"; import { logger } from "./LogService"; import { createConnection } from "net"; -import { NfcServiceAlt } from "../services/NfcService-alt"; +import { NfcService } from "../services/NfcService"; // 1. Global Error Handling (CRITICAL: As early as possible) process.on("uncaughtException", (error) => { @@ -41,7 +40,7 @@ function createWindow(): void { }, }); - let nfcService: NfcServiceAlt | null = null; + let nfcService: NfcService | null = null; ipcMain.handle("nfc:getReaderName", () => { return nfcService ? nfcService.getReaderName() : "OFFLINE"; @@ -55,7 +54,7 @@ function createWindow(): void { // 2. Initialize NFC AFTER window is ready to show (DEFERRED INIT) logger.info("Deferred: Initializing NfcService..."); try { - nfcService = new NfcServiceAlt((event) => { + nfcService = new NfcService((event) => { if (!mainWindow.isDestroyed()) { mainWindow.webContents.send(`nfc:${event.type}`, event); } diff --git a/src/services/NfcService-alt.ts b/src/services/NfcService-alt.ts deleted file mode 100644 index e3d459b..0000000 --- a/src/services/NfcService-alt.ts +++ /dev/null @@ -1,266 +0,0 @@ -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> => { - 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 - } -} diff --git a/src/services/NfcService.ts b/src/services/NfcService.ts index d5afcc1..48c7984 100644 --- a/src/services/NfcService.ts +++ b/src/services/NfcService.ts @@ -1,7 +1,9 @@ -import PCSC, { Tag, Reader, KEYS } from "@tockawa/nfc-pcsc"; import { logger } from "../main/LogService"; +import { NFC } from "nfc-pcsc"; +// @ts-expect-error: . function formatHexBuffer(buff: Buffer): string { + console.log("LEN:", buff.length); const str = ( buff .toString("hex") @@ -11,17 +13,76 @@ function formatHexBuffer(buff: Buffer): string { 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 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 }; -// Extract the KEY_TYPE_A constant from the KEYS module, which represents the type A authentication key. -const { KEY_TYPE_A } = KEYS; - +// 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 NfcService { - private nfc: PCSC; + private nfc: NFC; private onEvent?: (event: NfcEvent) => void; private currentReaderName: string = "OFFLINE"; @@ -29,7 +90,7 @@ export class NfcService { logger.info("NfcService: Constructor start"); try { logger.info("NfcService: Creating PCSC instance..."); - this.nfc = new PCSC.default(); + this.nfc = new NFC(console); logger.info("NfcService: PCSC instance created."); } catch (err) { logger.error("NfcService: Failed to create PCSC instance", err); @@ -48,7 +109,7 @@ export class NfcService { logger.info( "NfcService: Starting initialization and attaching listeners...", ); - this.nfc.on("reader", (reader: Reader) => { + this.nfc.on("reader", (reader) => { console.log(`Lector detectado: ${reader.name}`); logger.info(`NFC Reader detected: ${reader.name}`); reader.aid = "a0000000041010"; @@ -57,25 +118,48 @@ export class NfcService { 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> => { + 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; + }; - reader.on("card", async (card: Tag) => { console.log(`Tarjeta detectada! UID: ${JSON.stringify(card)}`); - logger.info(`NFC Tag detected: ${card.uid}`); - const GET_OPTIONS = "80A8000002830000"; - const res = await reader.transmit( - Buffer.from(GET_OPTIONS, "hex"), - 18, + logger.info(`NFC Tag detected: ${JSON.stringify(card)}`); + + await send(commands.app, "step 1 - select app", 255); + await send(commands.options, "step 2 - read options", 255); + const res3 = await send( + commands.record, + "step 3 - read record 2 - 1", + 255, ); - console.log("PORCESSING OPTIONS", res.toString("hex")); + + //decodeRecord(res3); + const token = findPublicToken(res3); if (this.onEvent) { - this.onEvent({ type: "tag", uid: card.uid }); + this.onEvent({ type: "tag", uid: token }); } - //await reader.authenticate(2, KEY_TYPE_A, "A000000004"); - //const data = await reader.read(2, 16); - //console.log("bloque2", data.toString("utf8")); }); - reader.on("card.off", async (card: Tag) => { + reader.on("card.off", async (card) => { console.log(`Tarjeta retirada: ${card.uid}`); logger.info(`NFC Tag removed: ${card.uid}`); if (this.onEvent) {