"use strict";
/**
 * PKCS#11 manager — wraps pkcs11js to provide high-level token operations.
 *
 * Maps to the same operations as sign-docs/lib/services/pkcs11/pkcs11_manager.dart
 * using the pkcs11js Node.js native addon.
 */
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.Pkcs11Manager = void 0;
const pkcs11js = __importStar(require("pkcs11js"));
// PKCS#11 mechanism constants
const CKM_SHA256_RSA_PKCS = 0x00000040;
const CKM_SHA384_RSA_PKCS = 0x00000041;
const CKM_SHA512_RSA_PKCS = 0x00000042;
const CKM_RSA_PKCS = 0x00000001;
// Object class constants
const CKO_CERTIFICATE = 0x00000001;
const CKO_PRIVATE_KEY = 0x00000003;
// Attribute types
const CKA_CLASS = 0x00000000;
const CKA_TOKEN = 0x00000001;
const CKA_LABEL = 0x00000003;
const CKA_VALUE = 0x00000011;
const CKA_ID = 0x00000102;
const CKA_ISSUER = 0x00000081;
const CKA_SUBJECT = 0x00000101;
const CKA_SERIAL_NUMBER = 0x00000082;
// Session flags
const CKF_SERIAL_SESSION = 0x00000004;
const CKF_RW_SESSION = 0x00000002;
class Pkcs11Manager {
    pkcs11 = null;
    sessions = new Map(); // slotId -> sessionHandle
    _currentLibraryPath = null;
    get isInitialized() {
        return this.pkcs11 !== null;
    }
    get currentLibraryPath() {
        return this._currentLibraryPath;
    }
    /** Load a PKCS#11 middleware library. */
    loadMiddleware(libraryPath) {
        if (this.pkcs11 && this._currentLibraryPath === libraryPath)
            return;
        this.unloadMiddleware();
        const p = new pkcs11js.PKCS11();
        p.load(libraryPath);
        p.C_Initialize();
        this.pkcs11 = p;
        this._currentLibraryPath = libraryPath;
    }
    /** Unload the current middleware. */
    unloadMiddleware() {
        if (!this.pkcs11)
            return;
        // Close all sessions
        for (const [slotId] of this.sessions) {
            try {
                this.logout(slotId);
            }
            catch { /* ignore */ }
            try {
                this.closeSession(slotId);
            }
            catch { /* ignore */ }
        }
        this.sessions.clear();
        try {
            this.pkcs11.C_Finalize();
        }
        catch { /* ignore */ }
        this.pkcs11 = null;
        this._currentLibraryPath = null;
    }
    /** Get list of slots with tokens present. */
    getSlotList(tokenPresent = true) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        return this.pkcs11.C_GetSlotList(tokenPresent);
    }
    /** Get token info for a slot. */
    getTokenInfo(slotId, middlewareName) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const info = this.pkcs11.C_GetTokenInfo(slotId);
        const slotNum = slotId.readUInt32LE ? slotId.readUInt32LE(0) : 0;
        return {
            id: `${this._currentLibraryPath}_slot_${slotNum}`,
            label: (info.label ?? '').trim(),
            manufacturer: (info.manufacturerID ?? '').trim(),
            model: (info.model ?? '').trim(),
            serialNumber: (info.serialNumber ?? '').trim(),
            middlewarePath: this._currentLibraryPath,
            middlewareName,
            slotId: slotNum,
        };
    }
    /** Open a session on a slot. */
    openSession(slotId) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const session = this.pkcs11.C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION);
        const slotNum = slotId.readUInt32LE ? slotId.readUInt32LE(0) : 0;
        this.sessions.set(slotNum, session);
        return session;
    }
    /** Login with PIN. */
    login(slotId, pin) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const session = this.sessions.get(slotId);
        if (!session)
            throw new Error('No session open for slot');
        try {
            this.pkcs11.C_Login(session, 1 /* CKU_USER */, pin);
            return { valid: true };
        }
        catch (err) {
            const code = err.code ?? err.message ?? '';
            if (/CKR_PIN_INCORRECT/i.test(String(code))) {
                return { valid: false, error: 'PIN incorrect' };
            }
            if (/CKR_PIN_LOCKED/i.test(String(code))) {
                return { valid: false, attemptsRemaining: 0, error: 'Token locked' };
            }
            return { valid: false, error: String(code) };
        }
    }
    /** Logout from a session. */
    logout(slotId) {
        if (!this.pkcs11)
            return;
        const session = this.sessions.get(slotId);
        if (session) {
            try {
                this.pkcs11.C_Logout(session);
            }
            catch { /* ignore */ }
        }
    }
    /** Close a session. */
    closeSession(slotId) {
        if (!this.pkcs11)
            return;
        const session = this.sessions.get(slotId);
        if (session) {
            try {
                this.pkcs11.C_CloseSession(session);
            }
            catch { /* ignore */ }
            this.sessions.delete(slotId);
        }
    }
    /** Find all certificates on a token. */
    findCertificates(slotId) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const session = this.sessions.get(slotId);
        if (!session)
            throw new Error('No session open for slot');
        const template = [
            { type: CKA_CLASS, value: uintToBuffer(CKO_CERTIFICATE) },
            { type: CKA_TOKEN, value: Buffer.from([1]) },
        ];
        // Simpler: find objects with class=certificate
        this.pkcs11.C_FindObjectsInit(session, [
            { type: CKA_CLASS, value: uintToBuffer(CKO_CERTIFICATE) },
        ]);
        const certs = [];
        let handle;
        // eslint-disable-next-line no-cond-assign
        while ((handle = this.pkcs11.C_FindObjects(session)) && handle.length > 0) {
            try {
                const attrs = this.pkcs11.C_GetAttributeValue(session, handle, [
                    { type: CKA_LABEL },
                    { type: CKA_VALUE },
                    { type: CKA_ID },
                    { type: CKA_SUBJECT },
                    { type: CKA_ISSUER },
                ]);
                const label = bufToString(findAttr(attrs, CKA_LABEL));
                const derValue = findAttrBuf(attrs, CKA_VALUE);
                const id = findAttrBuf(attrs, CKA_ID);
                const subject = bufToString(findAttr(attrs, CKA_SUBJECT));
                const issuer = bufToString(findAttr(attrs, CKA_ISSUER));
                if (derValue && derValue.length > 0) {
                    const certId = id ? id.toString('hex') : label;
                    // Parse basic X.509 info from DER (simplified: extract CN from subject/issuer)
                    const subjectCN = extractCNFromDN(subject) ?? label;
                    const issuerCN = extractCNFromDN(issuer) ?? '';
                    certs.push({
                        id: certId,
                        label,
                        subjectCN,
                        issuerCN,
                        notBefore: '', // Would need full ASN.1 parse
                        notAfter: '',
                        derBase64: derValue.toString('base64'),
                    });
                }
            }
            catch {
                // Skip certs we can't read
            }
        }
        this.pkcs11.C_FindObjectsFinal(session);
        // Suppress unused template warning
        void template;
        return certs;
    }
    /** Find the private key handle matching a certificate ID. */
    findPrivateKeyForCertificate(slotId, certId) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const session = this.sessions.get(slotId);
        if (!session)
            throw new Error('No session open for slot');
        const idBuf = Buffer.from(certId, 'hex');
        this.pkcs11.C_FindObjectsInit(session, [
            { type: CKA_CLASS, value: uintToBuffer(CKO_PRIVATE_KEY) },
            { type: CKA_ID, value: idBuf },
        ]);
        const handle = this.pkcs11.C_FindObjects(session);
        this.pkcs11.C_FindObjectsFinal(session);
        return handle && handle.length > 0 ? handle : null;
    }
    /** Sign data using a private key. */
    sign(slotId, keyHandle, data, mechanism) {
        if (!this.pkcs11)
            throw new Error('Middleware not loaded');
        const session = this.sessions.get(slotId);
        if (!session)
            throw new Error('No session open for slot');
        this.pkcs11.C_SignInit(session, { mechanism }, keyHandle);
        return Buffer.from(this.pkcs11.C_Sign(session, data, Buffer.alloc(512)));
    }
    /** Convert algorithm name to PKCS#11 mechanism constant. */
    algorithmToMechanism(algorithm) {
        switch (algorithm.toUpperCase()) {
            case 'SHA256WITHRSA':
            case 'SHA256_RSA_PKCS':
                return CKM_SHA256_RSA_PKCS;
            case 'SHA384WITHRSA':
            case 'SHA384_RSA_PKCS':
                return CKM_SHA384_RSA_PKCS;
            case 'SHA512WITHRSA':
            case 'SHA512_RSA_PKCS':
                return CKM_SHA512_RSA_PKCS;
            case 'RSA_PKCS':
                return CKM_RSA_PKCS;
            default:
                return CKM_SHA256_RSA_PKCS;
        }
    }
}
exports.Pkcs11Manager = Pkcs11Manager;
// ── Helpers ────────────────────────────────────────────────
function uintToBuffer(value) {
    const buf = Buffer.alloc(4);
    buf.writeUInt32LE(value, 0);
    return buf;
}
function findAttr(attrs, type) {
    const attr = attrs.find(a => a.type === type);
    return attr?.value;
}
function findAttrBuf(attrs, type) {
    return findAttr(attrs, type);
}
function bufToString(buf) {
    if (!buf || buf.length === 0)
        return '';
    return buf.toString('utf8').replace(/\0/g, '').trim();
}
/** Extract CN= value from a DN string or raw buffer (simplified). */
function extractCNFromDN(value) {
    const match = value.match(/CN=([^,/]+)/i);
    return match ? match[1].trim() : null;
}
