pcsc-mini
Version:
PC/SC (smart card) API bindings for Linux, MacOS, and Win32.
392 lines (391 loc) • 12.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CardStatusFlags = exports.CardState = exports.Card = exports.protocolString = exports.cardStatusString = exports.controlCode = exports.Protocol = exports.CardStatus = exports.CardMode = exports.CardDisposition = exports.attributes = exports.MAX_BUFFER_LEN = exports.MAX_ATR_LEN = void 0;
const pcsc = __importStar(require("./addon.node"));
exports.MAX_ATR_LEN = pcsc.MAX_ATR_LEN;
exports.MAX_BUFFER_LEN = pcsc.MAX_BUFFER_LEN;
exports.attributes = pcsc.attributes;
exports.CardDisposition = pcsc.CardDisposition;
exports.CardMode = pcsc.CardMode;
exports.CardStatus = pcsc.CardStatus;
exports.Protocol = pcsc.Protocol;
exports.controlCode = pcsc.controlCode;
exports.cardStatusString = pcsc.cardStatusString;
exports.protocolString = pcsc.protocolString;
/**
* Represents a connection to an inserted smart card.
*
* ## Example
* ```ts
* import * as pcsc from "pcsc-mini";
* const { CardDisposition, CardMode, ReaderStatus } = pcsc;
*
* const client = new pcsc.Client()
* .on("reader", onReader)
* .start();
*
* function onReader(reader: pcsc.Reader) {
* reader.on("change", async status => {
* if (!status.has(ReaderStatus.PRESENT)) return;
* if (status.hasAny(ReaderStatus.MUTE, ReaderStatus.IN_USE)) return;
*
* const card = await reader.connect(CardMode.EXCLUSIVE);
* console.log(`${await card.state()}`);
*
* const resTx = await card.transmit(
* Uint8Array.of(0xca, 0xfe, 0xf0, 0x0d)
* );
* console.log(resTx);
*
* const codeFeatures = pcsc.controlCode(3400);
* const resCtrl = await card.control(codeFeatures);
* console.log(resCtrl);
*
* await card.disconnect(CardDisposition.RESET);
* client.stop();
* process.exit(0);
* });
* }
* ```
*/
class Card {
#card;
constructor(card) {
this.#card = card;
}
/**
* Gets an attribute from the IFD Handler.
*
* @param id - ID of the requested attribute. {@link attributes.ids} provides
* a non-exhaustive list of available attribute IDs that may be useful.
*
* @param outputBuffer - Backing buffer for the response. If omitted, a new
* buffer will be allocated with enough space for the response.
*
* @returns The attribute value response.
* @throws {@link Err}
*/
attributeGet(id, outputBuffer) {
return this.#card.attributeGet(id, outputBuffer);
}
/**
* Sets an attribute of the IFD Handler.
*
* @param id - ID of the requested attribute. {@link attributes.ids} provides
* a non-exhaustive list of available attribute IDs that may be useful.
* @param value - The new byte data value of the attribute.
*
* @throws {@link Err}
*/
attributeSet(id, value) {
return this.#card.attributeSet(id, value);
}
/**
* Sends a command directly to the IFD Handler (reader driver).
*
* This is useful for creating client side reader drivers for functions like
* PIN pads, biometrics, or other extensions to the normal smart card reader
* that are not normally handled by PC/SC.
*
* ## Example:
* ```ts
* import * as pcsc from "pcsc-mini";
*
* const IOCTL_GET_FEATURE_REQUEST = pcsc.controlCode(3400);
*
* function getFeatures(card: pcsc.Card): Promise<Uint8Array> {
* return card.control(IOCTL_GET_FEATURE_REQUEST);
* }
* ```
*
* @param code - The card reader device control code. For cross platform
* compatibility, use {@link controlCode} to construct the control code.
* @param command - Control command data, if any.
* @param outputBuffer - Backing buffer for the response. If omitted, a new
* buffer will be allocated with enough space for the response.
*
* @returns The response data from the reader.
* @throws {@link Err}
*/
control(code, command, outputBuffer) {
return this.#card.control(code, command, outputBuffer);
}
/**
* Closes the connection to the card, rendering this instance invalid.
*
* @param then - Action to take after disconnection.
*
* @throws {@link Err}
*/
disconnect(then) {
return this.#card.disconnect(then);
}
/**
* The currently active card communication protocol.
*
* This is set when the card connection is first established and updated after
* {@link reconnect} calls. It will *not* be updated if/when a protocol is
* negotiated via {@link control}.
*
* @throws {@link Err}
*/
protocol() {
return this.#card.protocol();
}
/**
* Attempts to re-establish a previously reset connection.
*
* If connected in {@link CardMode.SHARED}, another process may reset the
* card, causing successive commands to return an error. In those cases,
* calling {@link reconnect} will restore the connection accordingly.
*
* > [!NOTE]
* > For the sake of portability across platforms, the reconnect request is
* > sent with the previously active protocol. To change protocols, first
* > {@link disconnect} and then establish a new connection.
*
* @param mode - Access mode for the restored connection.
* @param initAction - Action to take on the card when reconnecting.
* @returns - The active protocol after reconnection.
*
* @throws {@link Err}
*/
reconnect(mode, initAction) {
return this.#card.reconnect(mode, initAction);
}
/**
* The current state of the card.
* @throws {@link Err}
*/
async state() {
return new CardState(await this.#card.state());
}
/**
* Establishes a temporary {@link CardMode.EXCLUSIVE} connection to the card,
* if originally connected with {@link CardMode.SHARED}, creating a lock for
* issuing multiple commands without interruption from other processes.
*
* > [!IMPORTANT]
* > Must be followed up with a matching call to
* > {@link Transaction.end}() when done with the session.
*
* ## Example
* ```ts
* import * as pcsc from "pcsc-mini";
*
* async function longRunningProcess(card: pcsc.Card) {
* const txn = card.transaction();
*
* try {
* const r1 = await card.transmit(Uint8Array.of(0xca, 0xfe, 0xf0, 0x0d));
* const r2 = await card.transmit(Uint8Array.of(0xfa, 0xde, 0xfa, 0xce));
*
* return processResults(r1, r2);
* } catch (err) {
* console.error("Something went wrong:", err);
* } finally {
* txn.end(pcsc.CardDisposition.LEAVE);
* }
* }
* ```
*
* @returns A handle to the newly active transaction.
* @throws {@link Err}
*/
transaction() {
return this.#card.transaction();
}
/**
* Sends an [APDU](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit)
* to the card.
*
* ## Example
* ```ts
* import { Buffer } from "node:buffer";
* import * as pcsc from "pcsc-mini";
*
* async function getData(card: pcsc.Card) {
* const buf = new ArrayBuffer(pcsc.MAX_BUFFER_LEN);
*
* try {
* const res1 = await card.transmit(Uint8Array.of(0xca, 0xfe), buf);
* const foo = processRes1(res1);
*
* const res2 = await card.transmit(Buffer.of(0xf0, 0x0d), buf);
* const bar = processRes2(res2);
*
* return processResults(foo, bar);
* } catch (err) {
* console.error("Something went wrong:", err);
* return null;
* }
* }
* ```
*
* @param input - The APDU byte data.
* @param outputBuffer - Backing buffer for the response. If omitted, a new
* buffer will be allocated with enough space for the response.
*
* @returns The response data from the card.
* @throws {@link Err}
*/
transmit(input, outputBuffer) {
return this.#card.transmit(undefined, input, outputBuffer);
}
/**
* Sends an [APDU](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit)
* to the card with a specific protocol. Useful for {@link CardMode.DIRECT}
* connections, when a specific protocol has not yet been negotiated.
*
* ## Example
* ```ts
* import { Buffer } from "node:buffer";
* import * as pcsc from "pcsc-mini";
*
* async function getData(card: pcsc.Card) {
*
* try {
* const foo = await card.transmit(
* pcsc.Protocol.T1,
* Uint8Array.of(0xca, 0xfe),
* );
*
* const bar = await card.transmit(
* pcsc.Protocol.T1,
* Buffer.of(0xf0, 0x0d),
* );
*
* return processResults(foo, bar);
* } catch (err) {
* console.error("Something went wrong:", err);
* return null;
* }
* }
* ```
*
* @param input - The APDU byte data.
* @param outputBuffer - Backing buffer for the response. If omitted, a new
* buffer will be allocated with enough space for the response.
*
* @returns The response data from the card.
* @throws {@link Err}
*/
transmitProtocol(protocol, input, outputBuffer) {
return this.#card.transmit(protocol, input, outputBuffer);
}
}
exports.Card = Card;
/**
* The state of a connected smart card.
*/
class CardState {
/**
* The card [ATR](https://en.wikipedia.org/wiki/Answer_to_reset) value.
*/
atr;
/**
* The currently active protocol.
*/
protocol;
/**
* Name of the reader containing the card.
*/
readerName;
/**
* The current card status.
*/
status;
constructor(state) {
this.atr = state.atr;
this.protocol = state.protocol;
this.readerName = state.readerName;
this.status = new CardStatusFlags(state.status);
}
/**
* Human-friendly card info.
*/
toString() {
return (`[${this.readerName}]:\n` +
` Protocol: ${pcsc.protocolString(this.protocol)}\n` +
` Status: ${this.status}\n` +
` ATR: { ${byteString(this.atr)} }`);
}
}
exports.CardState = CardState;
/**
* Bit flag representation of a card's status.
*/
class CardStatusFlags {
/**
* The raw mask value of the status flags.
*/
raw;
constructor(raw) {
this.raw = raw;
}
/**
* `true` iff *all* the specified flags are set.
*/
has(...flags) {
let mask = 0;
for (const flag of flags)
mask |= flag;
return (this.raw & mask) === mask;
}
/**
* `true` if *any* of the specified flags are set.
*/
hasAny(...flags) {
let mask = 0;
for (const flag of flags)
mask |= flag;
return (this.raw & mask) !== 0;
}
/**
* Human-readable names of the enabled flags, for logging/debugging.
*/
toString() {
return (0, exports.cardStatusString)(this.raw);
}
}
exports.CardStatusFlags = CardStatusFlags;
function byteString(buf) {
const chunks = new Array(buf.length);
for (let i = 0; i < buf.length; i += 1) {
chunks[i] = buf[i].toString(16);
}
return chunks.join(", ");
}