Skip to content

Commit

Permalink
Merge pull request #309 from PeculiarVentures/pcsc-events-dedup
Browse files Browse the repository at this point in the history
Refactor PCSC Module: Enhance Error Handling and Restart Logic
  • Loading branch information
microshine authored Sep 17, 2024
2 parents 4cdb1a6 + eef9287 commit 89043da
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 67 deletions.
20 changes: 18 additions & 2 deletions packages/cards/lib/card.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "./card.schema.json",
"version": "1.1.19",
"version": "1.1.20",
"cards": [
{
"atr": "3B7F9600008031805843657274756D3031829000",
Expand Down Expand Up @@ -388,6 +388,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",
Expand Down Expand Up @@ -510,7 +520,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"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/pcsc/card_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
92 changes: 62 additions & 30 deletions packages/server/src/pcsc/pcsc_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,81 @@ 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 = 12;

private static readonly START_DELAY = 1e3; // 1 second
private static readonly RESTART_DELAY = 300000; // 5 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 {
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();
this.pcsc.on("error", (err) => {
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) => {
Expand All @@ -63,14 +98,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,
Expand All @@ -79,7 +113,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,
};
Expand All @@ -91,7 +125,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);
Expand All @@ -100,10 +134,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",
Expand All @@ -117,17 +149,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() {
Expand Down
47 changes: 23 additions & 24 deletions packages/server/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: [],
Expand Down
17 changes: 10 additions & 7 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/services/card_reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Service } from "./service";
export class CardReaderService extends Service<PCSCWatcher> {

constructor(server: Server) {
super(server, new PCSCWatcher(), [
super(server, PCSCWatcher.singleton, [
proto.CardReaderGetReadersActionProto,
]);

Expand Down
4 changes: 2 additions & 2 deletions scripts/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
})
Expand Down Expand Up @@ -143,7 +142,8 @@ async function main() {
})
.on("close", (e: any) => {
console.log("Close:", e.remoteAddress);
});
})
.listen("127.0.0.1:31337");
}

main();

0 comments on commit 89043da

Please sign in to comment.