From fb8824124f03f3c0f42e0df0193976935abda2aa Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:22:38 +0200 Subject: [PATCH 1/8] refactor(pcsc): Improve PCSCWatcher error handling and restart logic - Refactor the PCSCWatcher class to improve error handling and restart logic. - Add a maximum limit for the number of start method calls and restart attempts. - Implement delays between restart attempts to prevent excessive resource usage. - Reset the startCalls counter and restartAttempts counter on successful connection. --- packages/server/src/pcsc/card_watcher.ts | 2 +- packages/server/src/pcsc/pcsc_watcher.ts | 94 ++++++++++++++------- packages/server/src/services/card_reader.ts | 2 +- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/packages/server/src/pcsc/card_watcher.ts b/packages/server/src/pcsc/card_watcher.ts index ac92d53c..64e42005 100644 --- a/packages/server/src/pcsc/card_watcher.ts +++ b/packages/server/src/pcsc/card_watcher.ts @@ -40,7 +40,7 @@ export class CardWatcher extends core.EventLogEmitter { this.options.opensc = options.opensc; } this.config = new CardConfig(options); - this.watcher = new PCSCWatcher(); + this.watcher = PCSCWatcher.singleton; this.watcher .on("info", (level, source, message, data) => { this.emit("info", level, source, message, data); diff --git a/packages/server/src/pcsc/pcsc_watcher.ts b/packages/server/src/pcsc/pcsc_watcher.ts index 2ff87c59..ac5d0fa5 100644 --- a/packages/server/src/pcsc/pcsc_watcher.ts +++ b/packages/server/src/pcsc/pcsc_watcher.ts @@ -10,31 +10,70 @@ export interface PCSCWatcherEvent { export class PCSCWatcher extends core.EventLogEmitter { + public static singleton = new PCSCWatcher(); + public source = "pcsc"; public readers: CardReader[] = []; protected pcsc: PCSCLite | null = null; - private startCalls = 0; // Track the number of start method calls - - constructor() { + /** + * Track the number of start method calls + */ + private startCalls = 0; + /** + * Track the number of restart attempts after warning + */ + private restartAttempts = 0; + + /** + * Maximum number of initial start attempts + */ + private static readonly MAX_START_CALLS = 3; + /** + * Maximum number of restart attempts after warning + */ + private static readonly MAX_RESTART_ATTEMPTS = 10; + + private static readonly START_DELAY = 1e3; // 1 second + private static readonly RESTART_DELAY = 1e4; // 10 seconds + // private static readonly RESTART_DELAY = 600000; // 10 minutes + + private constructor() { super(); } public start(): this { - if (this.startCalls > 3) { // Adjust the maximum recursion limit as needed + this.log("info", "Start PCSC listening"); + this._start(); + return this; + } + + private _start(): void { + console.log("Counters", this.startCalls, this.restartAttempts); + if (this.startCalls >= PCSCWatcher.MAX_START_CALLS) { + // Exceeded maximum start calls + this.emit("error", new WebCryptoLocalError(WebCryptoLocalError.CODE.PCSC_CANNOT_START)); this.log("warn", "PCSC start calls limit reached. Restarting PCSC"); + this.startCalls = 0; + this.restartAttempts += 1; + + if (this.restartAttempts >= PCSCWatcher.MAX_RESTART_ATTEMPTS) { + // Exceeded maximum restart attempts, stop trying + this.log("warn", "Maximum restart attempts reached. Stopping PCSC reconnection attempts."); + return; + } + // Wait and restart pcsc again setTimeout(() => { - this.start(); - }, 1e5); - return this; + this.log("info", "Retrying PCSC start"); + this._start(); + }, PCSCWatcher.RESTART_DELAY); + return; } - this.log("info", "Start PCSC listening"); this.startCalls += 1; // Increment the start call counter - console.log(`PCSC startCalls: ${this.startCalls}`); try { this.pcsc = pcsc(); @@ -42,14 +81,12 @@ export class PCSCWatcher extends core.EventLogEmitter { this.emit("error", err); // Restart only if the start call counter is within the limit - // See https://github.com/PeculiarVentures/webcrypto-local/issues/284 - if (this.startCalls <= 3) { + if (this.startCalls <= PCSCWatcher.MAX_START_CALLS) { this.pcsc?.removeAllListeners(); - // PCSCLite closes session on PCSC error. For that case we need to restart it with small delay. - // See https://github.com/PeculiarVentures/fortify/issues/421 + // PCSCLite closes session on PCSC error. Restart with a small delay. setTimeout(() => { - this.start() - }, 1e3); + this.start(); + }, PCSCWatcher.START_DELAY); } }); this.pcsc.on("reader", (reader) => { @@ -63,14 +100,13 @@ export class PCSCWatcher extends core.EventLogEmitter { this.emit("error", err); }); reader.on("status", (status) => { - // console.log("----name:'%s' atr:%s reader_state:%s state:%s", reader.name, status.atr.toString("hex"), reader.state, status.state); - // check what has changed + // Check what has changed const changes = (reader.state || 0) ^ status.state; if (changes) { if ((changes & reader.SCARD_STATE_EMPTY) && (status.state & reader.SCARD_STATE_EMPTY)) { - // card removed + // Card removed if (atr) { - // don't fire event if 'atr' wasn't set + // Don't fire event if 'atr' wasn't set const event: PCSCWatcherEvent = { reader, atr, @@ -79,7 +115,7 @@ export class PCSCWatcher extends core.EventLogEmitter { atr = null; } } else if ((changes & reader.SCARD_STATE_PRESENT) && (status.state & reader.SCARD_STATE_PRESENT)) { - // card insert + // Card inserted const event: PCSCWatcherEvent = { reader, }; @@ -91,7 +127,7 @@ export class PCSCWatcher extends core.EventLogEmitter { reader: reader.name, atr: atr?.toString("hex") || "unknown", }); - // Delay for lib loading + // Delay for library loading setTimeout(() => { this.emit("insert", event); }, 1e3); @@ -100,10 +136,8 @@ export class PCSCWatcher extends core.EventLogEmitter { }); reader.on("end", () => { - // console.log("Reader", this.name, "removed"); if (atr) { - // don't fire event if 'atr' wasn't set - + // Don't fire event if 'atr' wasn't set this.log("info", "Token was removed from the reader", { reader: reader.name, atr: atr?.toString("hex") || "unknown", @@ -117,17 +151,17 @@ export class PCSCWatcher extends core.EventLogEmitter { atr = null; } }); - - // Reset startCalls counter on successful connection - this.startCalls = 0; }); + + // Reset startCalls counter and restartAttempts on successful connection + this.log("info", "PCSC connected successfully"); + this.startCalls = 0; + this.restartAttempts = 0; } catch (err) { - this.emit("error", new WebCryptoLocalError(WebCryptoLocalError.CODE.PCSC_CANNOT_START)); setTimeout(() => { - this.start(); + this._start(); }, 1e3); } - return this; } public stop() { diff --git a/packages/server/src/services/card_reader.ts b/packages/server/src/services/card_reader.ts index 4eaad8de..8149230d 100644 --- a/packages/server/src/services/card_reader.ts +++ b/packages/server/src/services/card_reader.ts @@ -9,7 +9,7 @@ import { Service } from "./service"; export class CardReaderService extends Service { constructor(server: Server) { - super(server, new PCSCWatcher(), [ + super(server, PCSCWatcher.singleton, [ proto.CardReaderGetReadersActionProto, ]); From 7dc6679820a7f09d4a958d7b35a18e46e492dd71 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:24:33 +0200 Subject: [PATCH 2/8] refactor(server): Validate pkcs11 path before slot iteration This update ensures that the pkcs11 module path is validated before iterating over slots. This change prevents duplicate log messages that were previously caused by invalid paths. --- packages/server/src/provider.ts | 47 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/server/src/provider.ts b/packages/server/src/provider.ts index e035dd1c..e898154d 100755 --- a/packages/server/src/provider.ts +++ b/packages/server/src/provider.ts @@ -162,31 +162,31 @@ export class LocalProvider extends core.EventLogEmitter { //#region Add providers from config list this.config.providers = this.config.providers || []; for (const prov of this.config.providers) { - prov.slots = prov.slots || [0]; - for (const slot of prov.slots) { - if (fs.existsSync(prov.lib)) { - try { - const crypto = new Pkcs11Crypto({ - library: prov.lib, - libraryParameters: prov.libraryParameters, - slot, - readWrite: true, - }); - if (prov.config) { - this.log("info", "Use ConfigTemplateBuilder", prov.config); - crypto.templateBuilder = new ConfigTemplateBuilder(prov.config); - } else { - this.log("info", "Use default TemplateBuilder"); + if (fs.existsSync(prov.lib)) { + prov.slots = prov.slots || [0]; + for (const slot of prov.slots) { + try { + const crypto = new Pkcs11Crypto({ + library: prov.lib, + libraryParameters: prov.libraryParameters, + slot, + readWrite: true, + }); + if (prov.config) { + this.log("info", "Use ConfigTemplateBuilder", prov.config); + crypto.templateBuilder = new ConfigTemplateBuilder(prov.config); + } else { + this.log("info", "Use default TemplateBuilder"); + } + this.addProvider(crypto, { + name: prov.name, + }); + } catch (err) { + this.emit("error", new WebCryptoLocalError(WebCryptoLocalError.CODE.PROVIDER_INIT, `Provider:open Cannot load PKCS#11 library by path ${prov.lib}. ${stringifyError(err)}`)); } - this.addProvider(crypto, { - name: prov.name, - }); - } catch (err) { - this.emit("error", new WebCryptoLocalError(WebCryptoLocalError.CODE.PROVIDER_INIT, `Provider:open Cannot load PKCS#11 library by path ${prov.lib}. ${stringifyError(err)}`)); - } - } else { - this.log("info", `File ${prov.lib} does not exist`, { action: "open" }); } + } else { + this.log("info", `File ${prov.lib} does not exist`, { action: "open" }); } } //#endregion @@ -195,7 +195,6 @@ export class LocalProvider extends core.EventLogEmitter { if (this.cards) { this.cards .on("error", (err) => { - this.emit("error", err); return this.emit("token", { added: [], removed: [], From 08e72e20e40df253a549ba2bd85447fb188a9f4e Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:28:28 +0200 Subject: [PATCH 3/8] refactor(server): Disable CardReaderService in LocalServer constructor --- packages/server/src/server.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 793b1e9e..abf7fb92 100755 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -44,13 +44,16 @@ export class LocalServer extends core.EventLogEmitter { this.server = new Server(options); if (!options.disablePCSC) { - this.cardReader = new CardReaderService(this.server) - .on("info", (level, source, message, data) => { - this.emit("info", level, source, message, data); - }) - .on("error", (e) => { - this.emit("error", e); - }); + // The CardReaderService is disabled because it is not used on the client side, + // but it duplicates log entries for PCSCWatcher. + + // this.cardReader = new CardReaderService(this.server) + // .on("info", (level, source, message, data) => { + // this.emit("info", level, source, message, data); + // }) + // .on("error", (e) => { + // this.emit("error", e); + // }); } else { // Disable PCSC for provider too options.config.disablePCSC = true; From 51c1c4e00ccd1f79fb25955be46b318cd5a9ddaf Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:29:11 +0200 Subject: [PATCH 4/8] chore(scripts): Run `listen` after setting the event listeners This change ensures that the `listen` method is called after setting the event listeners. Previously, some logs triggered during startup were not being recorded in the log. --- scripts/server.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/server.ts b/scripts/server.ts index 02638c3b..5ad21241 100644 --- a/scripts/server.ts +++ b/scripts/server.ts @@ -90,7 +90,6 @@ async function main() { }; new server.LocalServer(options) - .listen("127.0.0.1:31337") .on("listening", (e: any) => { console.log("Started at 127.0.0.1:31337"); }) @@ -108,6 +107,7 @@ async function main() { console.log("Identity changed"); }) .on("error", (e: Error) => { + console.log("Error:"); console.error(e); }) .on("notify", (p: any) => { @@ -143,7 +143,8 @@ async function main() { }) .on("close", (e: any) => { console.log("Close:", e.remoteAddress); - }); + }) + .listen("127.0.0.1:31337"); } main(); From a5bfe83cc54521d1985d0baa0b0677dbccd92ec6 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:33:36 +0200 Subject: [PATCH 5/8] refactor(pcsc): Remove console.log statement in PCSCWatcher --- packages/server/src/pcsc/pcsc_watcher.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/pcsc/pcsc_watcher.ts b/packages/server/src/pcsc/pcsc_watcher.ts index ac5d0fa5..65036cd4 100644 --- a/packages/server/src/pcsc/pcsc_watcher.ts +++ b/packages/server/src/pcsc/pcsc_watcher.ts @@ -50,7 +50,6 @@ export class PCSCWatcher extends core.EventLogEmitter { } private _start(): void { - console.log("Counters", this.startCalls, this.restartAttempts); if (this.startCalls >= PCSCWatcher.MAX_START_CALLS) { // Exceeded maximum start calls this.emit("error", new WebCryptoLocalError(WebCryptoLocalError.CODE.PCSC_CANNOT_START)); From 666ff5bbc43ca98002a2b7bfd62bf4354e106d00 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:44:28 +0200 Subject: [PATCH 6/8] refactor(pcsc): Increase maximum restart attempts in PCSCWatcher --- packages/server/src/pcsc/pcsc_watcher.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/server/src/pcsc/pcsc_watcher.ts b/packages/server/src/pcsc/pcsc_watcher.ts index 65036cd4..9572c7f8 100644 --- a/packages/server/src/pcsc/pcsc_watcher.ts +++ b/packages/server/src/pcsc/pcsc_watcher.ts @@ -33,11 +33,10 @@ export class PCSCWatcher extends core.EventLogEmitter { /** * Maximum number of restart attempts after warning */ - private static readonly MAX_RESTART_ATTEMPTS = 10; + private static readonly MAX_RESTART_ATTEMPTS = 12; private static readonly START_DELAY = 1e3; // 1 second - private static readonly RESTART_DELAY = 1e4; // 10 seconds - // private static readonly RESTART_DELAY = 600000; // 10 minutes + private static readonly RESTART_DELAY = 300000; // 5 minutes private constructor() { super(); From eaf8e286b209bfa53e18e65fd7912c6d6250c6b5 Mon Sep 17 00:00:00 2001 From: microshine Date: Tue, 17 Sep 2024 10:56:00 +0200 Subject: [PATCH 7/8] chore(scripts): Remove console.log statement --- scripts/server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/server.ts b/scripts/server.ts index 5ad21241..a7dfe992 100644 --- a/scripts/server.ts +++ b/scripts/server.ts @@ -107,7 +107,6 @@ async function main() { console.log("Identity changed"); }) .on("error", (e: Error) => { - console.log("Error:"); console.error(e); }) .on("notify", (p: any) => { From 7c167fde9ac537ee0e9189b510810e87750ab07e Mon Sep 17 00:00:00 2001 From: Kharya1337 Date: Tue, 17 Sep 2024 13:36:12 +0300 Subject: [PATCH 8/8] chore(cards): add support for IDPrime 940C and IDPrime 3940B FIDO --- packages/cards/lib/card.json | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/cards/lib/card.json b/packages/cards/lib/card.json index 273b95bc..cd094db4 100644 --- a/packages/cards/lib/card.json +++ b/packages/cards/lib/card.json @@ -1,6 +1,6 @@ { "$schema": "./card.schema.json", - "version": "1.1.18", + "version": "1.1.20", "cards": [ { "atr": "3BD518FF8191FE1FC38073C821100A", @@ -378,6 +378,16 @@ "name": "ID Prime 940", "driver": "39b3d7a3662c4b48bb120d008dd18648" }, + { + "atr": "3B7F96000080318065B085050039120FFE829000", + "name": "ID Prime 940C", + "driver": "39b3d7a3662c4b48bb120d008dd18648" + }, + { + "atr": "3BFF9600008131FE4380318065B0855956FB120FFC82900002", + "name": "ID Prime 3940B FIDO", + "driver": "39b3d7a3662c4b48bb120d008dd18648" + }, { "atr": "3b7f96000080318065b084565110120ffe829000", "name": "Gemalto IDBridge CT30", @@ -493,7 +503,13 @@ } }, "file": { - "windows": "%WINDIR/System32/eTPKCS11.dll", + "windows": { + "x64": [ + "%WINDIR/System32/eTPKCS11.dll", + "%PROGRAMFILES/SafeNet/Authentication/SAC/x64/IDPrimePKCS1164.dll" + ], + "x86": "%WINDIR/System32/eTPKCS11.dll" + }, "osx": "/usr/local/lib/libeTPkcs11.dylib", "linux": "/usr/lib/libeTPkcs11.so" }