UNPKG

node-webcrypto-p11

Version:
1,408 lines (1,387 loc) 120 kB
/*! * The MIT License (MIT) * * Copyright (c) 2020 Peculiar Ventures, LLC * * See the full version of the license https://github.com/PeculiarVentures/node-webcrypto-p11/blob/master/LICENSE */ 'use strict'; var tslib = require('tslib'); var core = require('webcrypto-core'); var graphene = require('graphene-pk11'); var pkcs11 = require('pkcs11js'); var pvtsutils = require('pvtsutils'); var x509 = require('@peculiar/x509'); var asn1Schema = require('@peculiar/asn1-schema'); var asnX509 = require('@peculiar/asn1-x509'); var crypto = require('crypto'); var jsonSchema = require('@peculiar/json-schema'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var core__namespace = /*#__PURE__*/_interopNamespaceDefault(core); var graphene__namespace = /*#__PURE__*/_interopNamespaceDefault(graphene); var pkcs11__namespace = /*#__PURE__*/_interopNamespaceDefault(pkcs11); var pvtsutils__namespace = /*#__PURE__*/_interopNamespaceDefault(pvtsutils); var x509__namespace = /*#__PURE__*/_interopNamespaceDefault(x509); var asn1Schema__namespace = /*#__PURE__*/_interopNamespaceDefault(asn1Schema); var asnX509__namespace = /*#__PURE__*/_interopNamespaceDefault(asnX509); var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto); var jsonSchema__namespace = /*#__PURE__*/_interopNamespaceDefault(jsonSchema); class CryptoKey extends core__namespace.CryptoKey { static defaultKeyAlgorithm() { const alg = { label: "", name: "", sensitive: false, token: false, }; return alg; } static getID(p11Key) { let name; switch (p11Key.class) { case graphene__namespace.ObjectClass.PRIVATE_KEY: name = "private"; break; case graphene__namespace.ObjectClass.PUBLIC_KEY: name = "public"; break; case graphene__namespace.ObjectClass.SECRET_KEY: name = "secret"; break; default: throw new Error(`Unsupported Object type '${graphene__namespace.ObjectClass[p11Key.class]}'`); } return `${name}-${p11Key.handle.toString("hex")}-${p11Key.id.toString("hex")}`; } get key() { return this.p11Object.toType(); } constructor(key, alg, usages) { super(); this.type = "secret"; this.extractable = false; this.usages = []; this.p11Object = key; switch (key.class) { case graphene__namespace.ObjectClass.PUBLIC_KEY: this.initPublicKey(key.toType()); break; case graphene__namespace.ObjectClass.PRIVATE_KEY: this.initPrivateKey(key.toType()); break; case graphene__namespace.ObjectClass.SECRET_KEY: this.initSecretKey(key.toType()); break; default: throw new core__namespace.CryptoError(`Wrong incoming session object '${graphene__namespace.ObjectClass[key.class]}'`); } const { name, ...defaultAlg } = CryptoKey.defaultKeyAlgorithm(); this.algorithm = { ...alg, ...defaultAlg }; this.id = CryptoKey.getID(key); if (usages) { this.usages = usages; } try { this.algorithm.label = key.label; } catch { } try { this.algorithm.token = key.token; } catch { } try { if (key instanceof graphene__namespace.PrivateKey || key instanceof graphene__namespace.SecretKey) { this.algorithm.sensitive = key.get("sensitive"); } } catch { } this.onAssign(); } toJSON() { return { algorithm: this.algorithm, type: this.type, usages: this.usages, extractable: this.extractable, }; } initPrivateKey(key) { this.p11Object = key; this.type = "private"; this.alwaysAuthenticate = key.alwaysAuthenticate; try { this.extractable = key.extractable; } catch { this.extractable = false; } this.usages = []; if (key.decrypt) { this.usages.push("decrypt"); } if (key.derive) { this.usages.push("deriveKey"); this.usages.push("deriveBits"); } if (key.sign) { this.usages.push("sign"); } if (key.unwrap) { this.usages.push("unwrapKey"); } } initPublicKey(key) { this.p11Object = key; this.type = "public"; this.extractable = true; if (key.encrypt) { this.usages.push("encrypt"); } if (key.verify) { this.usages.push("verify"); } if (key.wrap) { this.usages.push("wrapKey"); } } initSecretKey(key) { this.p11Object = key; this.type = "secret"; try { this.extractable = key.extractable; } catch { this.extractable = false; } if (key.sign) { this.usages.push("sign"); } if (key.verify) { this.usages.push("verify"); } if (key.encrypt) { this.usages.push("encrypt"); } if (key.decrypt) { this.usages.push("decrypt"); } if (key.wrap) { this.usages.push("wrapKey"); } if (key.unwrap) { this.usages.push("unwrapKey"); } if (key.derive) { this.usages.push("deriveKey"); this.usages.push("deriveBits"); } } onAssign() { } } class Assert { static isSession(data) { if (!(data instanceof graphene__namespace.Session)) { throw new TypeError("PKCS#11 session is not initialized"); } } static isModule(data) { if (!(data instanceof graphene__namespace.Module)) { throw new TypeError("PKCS#11 module is not initialized"); } } static isCryptoKey(data) { if (!(data instanceof CryptoKey)) { throw new TypeError("Object is not an instance of PKCS#11 CryptoKey"); } } static requiredParameter(parameter, parameterName) { if (!parameter) { throw new Error(`Absent mandatory parameter "${parameterName}"`); } } } class Pkcs11Object { static assertStorage(obj) { if (!obj) { throw new TypeError("PKCS#11 object is empty"); } } constructor(object) { this.p11Object = object; } } class CryptoCertificate extends Pkcs11Object { static getID(p11Object) { let type; let id; if (p11Object instanceof graphene__namespace.Data) { type = "request"; id = p11Object.objectId; } else if (p11Object instanceof graphene__namespace.X509Certificate) { type = "x509"; id = p11Object.id; } if (!type || !id) { throw new Error("Unsupported PKCS#11 object"); } return `${type}-${p11Object.handle.toString("hex")}-${id.toString("hex")}`; } get id() { Pkcs11Object.assertStorage(this.p11Object); return CryptoCertificate.getID(this.p11Object); } get token() { try { Pkcs11Object.assertStorage(this.p11Object); return this.p11Object.token; } catch { } return false; } get sensitive() { return false; } get label() { try { Pkcs11Object.assertStorage(this.p11Object); return this.p11Object.label; } catch { } return ""; } constructor(crypto) { super(); this.type = "x509"; this.crypto = crypto; } async computeID() { let id = this.publicKey.p11Object.id; const indexes = await this.crypto.keyStorage.keys(); if (!indexes.some(o => o.split("-")[2] === id.toString("hex"))) { let certKeyRaw; try { certKeyRaw = await this.crypto.subtle.exportKey("spki", this.publicKey); } catch { return id; } for (const index of indexes) { const [type] = index.split("-"); if (type !== "public") { continue; } let keyRaw; try { const key = await this.crypto.keyStorage.getItem(index); keyRaw = await this.crypto.subtle.exportKey("spki", key); if (pvtsutils__namespace.BufferSourceConverter.isEqual(keyRaw, certKeyRaw)) { id = key.p11Object.id; break; } } catch { continue; } } } return id; } } class ParserError extends Error { constructor(message) { super(message); this.name = "ParserError"; } } class X509CertificateRequest extends CryptoCertificate { constructor() { super(...arguments); this.type = "request"; } get subjectName() { var _a; return (_a = this.getData()) === null || _a === void 0 ? void 0 : _a.subject; } get value() { Pkcs11Object.assertStorage(this.p11Object); return new Uint8Array(this.p11Object.value).buffer; } async importCert(data, algorithm, keyUsages) { const array = new Uint8Array(data).buffer; this.parse(array); const { token, label, sensitive, ...keyAlg } = algorithm; this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto); const id = await this.computeID(); const template = this.crypto.templateBuilder.build({ action: "import", type: "request", attributes: { id, label: algorithm.label || "X509 Request", token: !!(algorithm.token), }, }); template.value = Buffer.from(data); this.p11Object = this.crypto.session.create(template).toType(); } async exportCert() { return this.value; } toJSON() { return { publicKey: this.publicKey.toJSON(), subjectName: this.subjectName, type: this.type, value: pvtsutils__namespace.Convert.ToBase64Url(this.value), }; } async exportKey(algorithm, usages) { if (!this.publicKey) { const publicKeyID = this.id.replace(/\w+-\w+-/i, ""); const keyIndexes = await this.crypto.keyStorage.keys(); for (const keyIndex of keyIndexes) { const parts = keyIndex.split("-"); if (parts[0] === "public" && parts[2] === publicKeyID) { if (algorithm && usages) { this.publicKey = await this.crypto.keyStorage.getItem(keyIndex, algorithm, true, usages); } else { this.publicKey = await this.crypto.keyStorage.getItem(keyIndex); } break; } } if (!this.publicKey) { if (algorithm && usages) { this.publicKey = await this.getData().publicKey.export(algorithm, usages, this.crypto); } else { this.publicKey = await this.getData().publicKey.export(this.crypto); } } } return this.publicKey; } parse(data) { try { this.csr = new x509__namespace.Pkcs10CertificateRequest(data); } catch { throw new ParserError("Cannot parse PKCS10 certificate request"); } } getData() { if (!this.csr) { this.parse(this.value); } return this.csr; } } class X509Certificate extends CryptoCertificate { constructor() { super(...arguments); this.type = "x509"; } get serialNumber() { return this.getData().serialNumber; } get notBefore() { return this.getData().notBefore; } get notAfter() { return this.getData().notAfter; } get issuerName() { return this.getData().issuer; } get subjectName() { return this.getData().subject; } get value() { Pkcs11Object.assertStorage(this.p11Object); return new Uint8Array(this.p11Object.value).buffer; } async importCert(data, algorithm, keyUsages) { const array = new Uint8Array(data); this.parse(array.buffer); const { token, label, sensitive, ...keyAlg } = algorithm; this.publicKey = await this.getData().publicKey.export(keyAlg, keyUsages, this.crypto); const id = await this.computeID(); const certLabel = this.getName(); const template = this.crypto.templateBuilder.build({ action: "import", type: "x509", attributes: { id, label: algorithm.label || certLabel, token: !!(algorithm.token), }, }); template.value = Buffer.from(data); const asn = asn1Schema__namespace.AsnConvert.parse(data, asnX509__namespace.Certificate); template.serial = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn1Schema__namespace.AsnIntegerArrayBufferConverter.toASN(asn.tbsCertificate.serialNumber))); template.subject = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn.tbsCertificate.subject)); template.issuer = Buffer.from(asn1Schema__namespace.AsnConvert.serialize(asn.tbsCertificate.issuer)); this.p11Object = this.crypto.session.create(template).toType(); } async exportCert() { return this.value; } toJSON() { return { publicKey: this.publicKey.toJSON(), notBefore: this.notBefore, notAfter: this.notAfter, subjectName: this.subjectName, issuerName: this.issuerName, serialNumber: this.serialNumber, type: this.type, value: pvtsutils__namespace.Convert.ToBase64Url(this.value), }; } async exportKey(algorithm, usages) { if (!this.publicKey) { const publicKeyID = this.id.replace(/\w+-\w+-/i, ""); const keyIndexes = await this.crypto.keyStorage.keys(); for (const keyIndex of keyIndexes) { const parts = keyIndex.split("-"); if (parts[0] === "public" && parts[2] === publicKeyID) { if (algorithm && usages) { this.publicKey = await this.crypto.keyStorage.getItem(keyIndex, algorithm, usages); } else { this.publicKey = await this.crypto.keyStorage.getItem(keyIndex); } break; } } if (!this.publicKey) { if (algorithm && usages) { this.publicKey = await this.getData().publicKey.export(algorithm, usages, this.crypto); } else { this.publicKey = await this.getData().publicKey.export(this.crypto); } } } return this.publicKey; } parse(data) { try { this.x509 = new x509__namespace.X509Certificate(data); } catch { throw new ParserError("Cannot parse X509 certificate"); } } getData() { if (!this.x509) { this.parse(this.value); } return this.x509; } getName() { const name = new x509__namespace.Name(this.subjectName).toJSON(); for (const item of name) { const commonName = item.CN; if (commonName && commonName.length > 0) { return commonName[0]; } } return this.subjectName; } } const TEMPLATES = [ { class: graphene__namespace.ObjectClass.CERTIFICATE, certType: graphene__namespace.CertificateType.X_509, token: true }, { class: graphene__namespace.ObjectClass.DATA, token: true, label: "X509 Request" }, ]; class CertificateStorage { constructor(crypto) { this.crypto = crypto; } async getValue(key) { const storageObject = this.getItemById(key); if (storageObject instanceof graphene__namespace.X509Certificate) { const x509Object = storageObject.toType(); const x509 = new X509Certificate(this.crypto); x509.p11Object = x509Object; return x509.exportCert(); } else if (storageObject instanceof graphene__namespace.Data) { const x509Object = storageObject.toType(); const x509request = new X509CertificateRequest(this.crypto); x509request.p11Object = x509Object; return x509request.exportCert(); } return null; } async indexOf(item) { var _a; if (item instanceof CryptoCertificate && ((_a = item.p11Object) === null || _a === void 0 ? void 0 : _a.token)) { return CryptoCertificate.getID(item.p11Object); } return null; } async keys() { const keys = []; TEMPLATES.forEach((template) => { this.crypto.session.find(template, (obj) => { const item = obj.toType(); const id = CryptoCertificate.getID(item); keys.push(id); }); }); return keys; } async clear() { const objects = []; TEMPLATES.forEach((template) => { this.crypto.session.find(template, (obj) => { objects.push(obj); }); }); objects.forEach((obj) => { obj.destroy(); }); } async hasItem(item) { const sessionObject = this.getItemById(item.id); return !!sessionObject; } async getItem(index, algorithm, usages) { const storageObject = this.getItemById(index); if (storageObject instanceof graphene__namespace.X509Certificate) { const x509Object = storageObject.toType(); const x509 = new X509Certificate(this.crypto); x509.p11Object = x509Object; if (algorithm && usages) { await x509.exportKey(algorithm, usages); } else { await x509.exportKey(); } return x509; } else if (storageObject instanceof graphene__namespace.Data) { const x509Object = storageObject.toType(); const x509request = new X509CertificateRequest(this.crypto); x509request.p11Object = x509Object; if (algorithm && usages) { await x509request.exportKey(algorithm, usages); } else { await x509request.exportKey(); } return x509request; } else { return null; } } async removeItem(key) { const sessionObject = this.getItemById(key); if (sessionObject) { sessionObject.destroy(); } } async setItem(data) { if (!(data instanceof CryptoCertificate)) { throw new Error("Incoming data is not PKCS#11 CryptoCertificate"); } Pkcs11Object.assertStorage(data.p11Object); if (!data.p11Object.token) { const template = this.crypto.templateBuilder.build({ action: "copy", type: data.type, attributes: { token: true, } }); const obj = this.crypto.session.copy(data.p11Object, template); return CryptoCertificate.getID(obj.toType()); } else { return data.id; } } async exportCert(format, cert) { switch (format) { case "pem": { throw Error("PEM format is not implemented"); } case "raw": { return cert.exportCert(); } default: throw new Error(`Unsupported format in use ${format}`); } } async importCert(format, data, algorithm, usages) { let rawData; let rawType = null; switch (format) { case "pem": if (typeof data !== "string") { throw new TypeError("data: Is not type string"); } if (core__namespace.PemConverter.isCertificate(data)) { rawType = "x509"; } else if (core__namespace.PemConverter.isCertificateRequest(data)) { rawType = "request"; } else { throw new core__namespace.OperationError("data: Is not correct PEM data. Must be Certificate or Certificate Request"); } rawData = core__namespace.PemConverter.toArrayBuffer(data); break; case "raw": if (!pvtsutils__namespace.BufferSourceConverter.isBufferSource(data)) { throw new TypeError("data: Is not type ArrayBuffer or ArrayBufferView"); } rawData = pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data); break; default: throw new TypeError("format: Is invalid value. Must be 'raw', 'pem'"); } switch (rawType) { case "x509": { const x509 = new X509Certificate(this.crypto); await x509.importCert(Buffer.from(rawData), algorithm, usages); return x509; } case "request": { const request = new X509CertificateRequest(this.crypto); await request.importCert(Buffer.from(rawData), algorithm, usages); return request; } default: { try { const x509 = new X509Certificate(this.crypto); await x509.importCert(Buffer.from(rawData), algorithm, usages); return x509; } catch (e) { if (!(e instanceof ParserError)) { throw e; } } try { const request = new X509CertificateRequest(this.crypto); await request.importCert(Buffer.from(rawData), algorithm, usages); return request; } catch (e) { if (!(e instanceof ParserError)) { throw e; } } throw new core__namespace.OperationError("Cannot parse Certificate or Certificate Request from incoming ASN1"); } } } getItemById(id) { let object = null; TEMPLATES.forEach((template) => { this.crypto.session.find(template, (obj) => { const item = obj.toType(); if (id === CryptoCertificate.getID(item)) { object = item; return false; } }); }); return object; } } const ID_DIGEST = "SHA-1"; function GUID() { return crypto__namespace.randomBytes(20); } function b64UrlDecode(b64url) { return Buffer.from(pvtsutils__namespace.Convert.FromBase64Url(b64url)); } function prepareData(data) { return Buffer.from(pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data)); } function isHashedAlgorithm(data) { return data instanceof Object && "name" in data && "hash" in data; } function isCryptoKeyPair(data) { return data instanceof Object && "privateKey" in data && "publicKey" in data; } function prepareAlgorithm(algorithm) { if (typeof algorithm === "string") { return { name: algorithm, }; } if (isHashedAlgorithm(algorithm)) { const preparedAlgorithm = { ...algorithm }; preparedAlgorithm.hash = prepareAlgorithm(algorithm.hash); return preparedAlgorithm; } return { ...algorithm }; } function digest(algorithm, data) { const hash = crypto__namespace.createHash(algorithm.replace("-", "")); hash.update(prepareData(Buffer.from(pvtsutils__namespace.BufferSourceConverter.toArrayBuffer(data)))); return hash.digest(); } function calculateProviderID(slot) { const str = slot.manufacturerID + slot.slotDescription + slot.getToken().serialNumber + slot.handle.toString("hex"); return digest(ID_DIGEST, Buffer.from(str)).toString("hex"); } function getProviderInfo(slot) { const slots = slot.module.getSlots(true); let index = -1; for (let i = 0; i < slots.length; i++) { if (slots.items(i).handle.equals(slot.handle)) { index = i; break; } } const token = slot.getToken(); const provider = { id: calculateProviderID(slot), slot: index, name: token.label, reader: slot.slotDescription, serialNumber: slot.getToken().serialNumber, algorithms: [], isRemovable: !!(slot.flags & graphene__namespace.SlotFlag.REMOVABLE_DEVICE), isHardware: !!(slot.flags & graphene__namespace.SlotFlag.HW_SLOT), }; const algorithms = slot.getMechanisms(); for (let i = 0; i < algorithms.length; i++) { const algorithm = algorithms.tryGetItem(i); if (!algorithm) { continue; } let algName = ""; switch (algorithm.name) { case "SHA_1": algName = "SHA-1"; break; case "SHA256": algName = "SHA-256"; break; case "SHA384": algName = "SHA-384"; break; case "SHA512": algName = "SHA-512"; break; case "RSA_PKCS": case "SHA1_RSA_PKCS": case "SHA256_RSA_PKCS": case "SHA384_RSA_PKCS": case "SHA512_RSA_PKCS": algName = "RSASSA-PKCS1-v1_5"; break; case "SHA1_RSA_PSS": case "SHA256_RSA_PSS": case "SHA384_RSA_PSS": case "SHA512_RSA_PSS": algName = "RSA-PSS"; break; case "SHA1_RSA_PKCS_PSS": case "SHA256_RSA_PKCS_PSS": case "SHA384_RSA_PKCS_PSS": case "SHA512_RSA_PKCS_PSS": algName = "RSA-PSS"; break; case "RSA_PKCS_OAEP": algName = "RSA-OAEP"; break; case "ECDSA": case "ECDSA_SHA1": case "ECDSA_SHA256": case "ECDSA_SHA384": case "ECDSA_SHA512": algName = "ECDSA"; break; case "ECDH1_DERIVE": algName = "ECDH"; break; case "AES_CBC_PAD": algName = "AES-CBC"; break; case "AES_ECB": case "AES_ECB_PAD": algName = "AES-ECB"; break; case "AES_GCM_PAD": algName = "AES-GCM"; break; case "AES_KEY_WRAP_PAD": algName = "AES-KW"; break; } if (algName && !provider.algorithms.some((alg) => alg === algName)) { provider.algorithms.push(algName); } } return provider; } async function alwaysAuthenticate(key, container, operation) { if (key.key instanceof graphene__namespace.PrivateKey && key.key.alwaysAuthenticate) { if (!container.onAlwaysAuthenticate) { throw new core__namespace.CryptoError("Crypto key requires re-authentication, but Crypto doesn't have 'onAlwaysAuthenticate' method"); } const pin = await container.onAlwaysAuthenticate(key, container, operation); if (pin) { container.session.login(pin, graphene__namespace.UserType.CONTEXT_SPECIFIC); } } } class AesCryptoKey extends CryptoKey { onAssign() { this.algorithm.length = this.key.get("valueLen") << 3; } } class AesCrypto { constructor(container) { this.container = container; } async generateKey(algorithm, extractable, keyUsages) { return new Promise((resolve, reject) => { const template = this.container.templateBuilder.build({ action: "generate", type: "secret", attributes: { id: GUID(), label: algorithm.label || `AES-${algorithm.length}`, token: algorithm.token, sensitive: algorithm.sensitive, extractable, usages: keyUsages, }, }); template.keyType = graphene__namespace.KeyType.AES; template.valueLen = algorithm.length >> 3; this.container.session.generateKey(graphene__namespace.KeyGenMechanism.AES, template, (err, aesKey) => { try { if (err) { reject(new core__namespace.CryptoError(`Aes: Can not generate new key\n${err.message}`)); } else { if (!aesKey) { throw new Error("Cannot get key from callback function"); } resolve(new AesCryptoKey(aesKey, algorithm)); } } catch (e) { reject(e); } }); }); } async exportKey(format, key) { const template = key.key.getAttribute({ value: null, valueLen: null }); switch (format.toLowerCase()) { case "jwk": { const aes = /AES-(\w+)/.exec(key.algorithm.name)[1]; const jwk = { kty: "oct", k: pvtsutils__namespace.Convert.ToBase64Url(template.value), alg: `A${template.valueLen * 8}${aes}`, ext: true, key_ops: key.usages, }; return jwk; } case "raw": return new Uint8Array(template.value).buffer; default: throw new core__namespace.OperationError("format: Must be 'jwk' or 'raw'"); } } async importKey(format, keyData, algorithm, extractable, keyUsages) { const formatLower = format.toLowerCase(); if (formatLower !== "jwk" && formatLower !== "raw") { throw new core__namespace.OperationError("format: Must be 'jwk' or 'raw'"); } if (formatLower === "jwk") { const jwk = keyData; if (!jwk.k) { throw new core__namespace.OperationError("jwk.k: Cannot get required property"); } keyData = pvtsutils__namespace.Convert.FromBase64Url(jwk.k); } const value = keyData; if (value.byteLength !== 16 && value.byteLength !== 24 && value.byteLength !== 32) { throw new core__namespace.OperationError("keyData: Is wrong key length"); } const aesAlg = { ...AesCryptoKey.defaultKeyAlgorithm(), ...algorithm, length: value.byteLength * 8, }; const template = this.container.templateBuilder.build({ action: "import", type: "secret", attributes: { id: GUID(), label: algorithm.label || `AES-${aesAlg.length}`, token: algorithm.token, sensitive: algorithm.sensitive, extractable, usages: keyUsages, }, }); template.keyType = graphene__namespace.KeyType.AES; template.value = Buffer.from(value); const sessionObject = this.container.session.create(template); const key = new AesCryptoKey(sessionObject.toType(), aesAlg); return key; } async encrypt(padding, algorithm, key, data) { if (padding) { const blockLength = 16; const mod = blockLength - (data.byteLength % blockLength); const pad = Buffer.alloc(mod); pad.fill(mod); data = Buffer.concat([Buffer.from(data), pad]); } return new Promise((resolve, reject) => { const enc = Buffer.alloc(this.getOutputBufferSize(key.algorithm, true, data.byteLength)); const mechanism = this.wc2pk11(algorithm); this.container.session.createCipher(mechanism, key.key) .once(Buffer.from(data), enc, (err, data2) => { if (err) { reject(err); } else { resolve(new Uint8Array(data2).buffer); } }); }); } async decrypt(padding, algorithm, key, data) { const dec = await new Promise((resolve, reject) => { const buf = Buffer.alloc(this.getOutputBufferSize(key.algorithm, false, data.length)); const mechanism = this.wc2pk11(algorithm); this.container.session.createDecipher(mechanism, key.key) .once(Buffer.from(data), buf, (err, data2) => { if (err) { reject(err); } else { resolve(data2); } }); }); if (padding) { const paddingLength = dec[dec.length - 1]; const res = new Uint8Array(dec.slice(0, dec.length - paddingLength)); return res.buffer; } else { return new Uint8Array(dec).buffer; } } isAesGCM(algorithm) { return algorithm.name.toUpperCase() === "AES-GCM"; } isAesCBC(algorithm) { return algorithm.name.toUpperCase() === "AES-CBC"; } isAesECB(algorithm) { return algorithm.name.toUpperCase() === "AES-ECB"; } wc2pk11(algorithm) { const session = this.container.session; if (this.isAesGCM(algorithm)) { const aad = algorithm.additionalData ? prepareData(algorithm.additionalData) : undefined; let AesGcmParamsClass = graphene__namespace.AesGcmParams; if (session.slot.module.cryptokiVersion.major >= 2 && session.slot.module.cryptokiVersion.minor >= 40) { AesGcmParamsClass = graphene__namespace.AesGcm240Params; } const params = new AesGcmParamsClass(prepareData(algorithm.iv), aad, algorithm.tagLength || 128); return { name: "AES_GCM", params }; } else if (this.isAesCBC(algorithm)) { return { name: "AES_CBC_PAD", params: prepareData(algorithm.iv) }; } else if (this.isAesECB(algorithm)) { return { name: "AES_ECB", params: null }; } else { throw new core__namespace.OperationError("Unrecognized algorithm name"); } } getOutputBufferSize(keyAlg, enc, dataSize) { const len = keyAlg.length >> 3; if (enc) { return (Math.ceil(dataSize / len) * len) + len; } else { return dataSize; } } } class AesCbcProvider extends core__namespace.AesCbcProvider { constructor(container) { super(); this.container = container; this.crypto = new AesCrypto(container); } async onGenerateKey(algorithm, extractable, keyUsages) { const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages); return key; } async onEncrypt(algorithm, key, data) { return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data)); } async onExportKey(format, key) { return this.crypto.exportKey(format, key); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); Assert.isCryptoKey(key); } } class AesEcbProvider extends core__namespace.ProviderCrypto { constructor(container) { super(); this.container = container; this.name = "AES-ECB"; this.usages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"]; this.crypto = new AesCrypto(container); } async onGenerateKey(algorithm, extractable, keyUsages) { const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages); return key; } async onEncrypt(algorithm, key, data) { return this.crypto.encrypt(true, algorithm, key, new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return this.crypto.decrypt(true, algorithm, key, new Uint8Array(data)); } async onExportKey(format, key) { return this.crypto.exportKey(format, key); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(key instanceof CryptoKey)) { throw new TypeError("key: Is not a PKCS11 CryptoKey"); } } } class AesGcmProvider extends core__namespace.AesGcmProvider { constructor(container) { super(); this.container = container; this.crypto = new AesCrypto(container); } async onGenerateKey(algorithm, extractable, keyUsages) { const key = await this.crypto.generateKey({ ...algorithm, name: this.name }, extractable, keyUsages); return key; } async onEncrypt(algorithm, key, data) { return this.crypto.encrypt(false, algorithm, key, new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return this.crypto.decrypt(false, algorithm, key, new Uint8Array(data)); } async onExportKey(format, key) { return this.crypto.exportKey(format, key); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { return this.crypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(key instanceof CryptoKey)) { throw new TypeError("key: Is not a PKCS11 CryptoKey"); } } } class EcCryptoKey extends CryptoKey { onAssign() { if (!this.algorithm.namedCurve) { try { const paramsECDSA = asn1Schema__namespace.AsnConvert.parse(this.key.get("paramsECDSA"), core__namespace.asn1.ObjectIdentifier); try { const pointEC = core__namespace.EcCurves.get(paramsECDSA.value); this.algorithm.namedCurve = pointEC.name; } catch { this.algorithm.namedCurve = paramsECDSA.value; } } catch { } } } } const id_ecPublicKey = "1.2.840.10045.2.1"; class EcCrypto { constructor(container) { this.container = container; this.publicKeyUsages = ["verify"]; this.privateKeyUsages = ["sign", "deriveKey", "deriveBits"]; } async generateKey(algorithm, extractable, keyUsages) { return new Promise((resolve, reject) => { const attrs = { id: GUID(), label: algorithm.label, token: algorithm.token, sensitive: algorithm.sensitive, extractable, usages: keyUsages, }; if (algorithm.alwaysAuthenticate) { attrs.alwaysAuthenticate = true; } const privateTemplate = this.createTemplate({ action: "generate", type: "private", attributes: attrs, }); const publicTemplate = this.createTemplate({ action: "generate", type: "public", attributes: attrs, }); publicTemplate.paramsEC = Buffer.from(core__namespace.EcCurves.get(algorithm.namedCurve).raw); this.container.session.generateKeyPair(graphene__namespace.KeyGenMechanism.EC, publicTemplate, privateTemplate, (err, keys) => { try { if (err) { reject(err); } else { if (!keys) { throw new Error("Cannot get keys from callback function"); } const wcKeyPair = { privateKey: new EcCryptoKey(keys.privateKey, algorithm), publicKey: new EcCryptoKey(keys.publicKey, algorithm), }; resolve(wcKeyPair); } } catch (e) { reject(e); } }); }); } async exportKey(format, key) { switch (format.toLowerCase()) { case "jwk": { if (key.type === "private") { return this.exportJwkPrivateKey(key); } else { return this.exportJwkPublicKey(key); } } case "pkcs8": { const jwk = await this.exportJwkPrivateKey(key); return this.jwk2pkcs(jwk); } case "spki": { const jwk = await this.exportJwkPublicKey(key); return this.jwk2spki(jwk); } case "raw": { const jwk = await this.exportJwkPublicKey(key); if (key.algorithm.namedCurve === "X25519") { return pvtsutils__namespace.Convert.FromBase64Url(jwk.x); } else { const publicKey = jsonSchema__namespace.JsonParser.fromJSON(jwk, { targetSchema: core__namespace.asn1.EcPublicKey }); return publicKey.value; } } default: throw new core__namespace.OperationError("format: Must be 'jwk', 'raw', pkcs8' or 'spki'"); } } async importKey(format, keyData, algorithm, extractable, keyUsages) { switch (format.toLowerCase()) { case "jwk": { const jwk = keyData; if (jwk.d) { return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages); } else { return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages); } } case "spki": { const jwk = this.spki2jwk(keyData); return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages); } case "pkcs8": { const jwk = this.pkcs2jwk(keyData); return this.importJwkPrivateKey(jwk, algorithm, extractable, keyUsages); } case "raw": { const curve = core__namespace.EcCurves.get(algorithm.namedCurve); const ecPoint = core__namespace.EcUtils.decodePoint(keyData, curve.size); const jwk = { kty: "EC", crv: algorithm.namedCurve, x: pvtsutils__namespace.Convert.ToBase64Url(ecPoint.x), }; if (ecPoint.y) { jwk.y = pvtsutils__namespace.Convert.ToBase64Url(ecPoint.y); } return this.importJwkPublicKey(jwk, algorithm, extractable, keyUsages); } default: throw new core__namespace.OperationError("format: Must be 'jwk', 'raw', 'pkcs8' or 'spki'"); } } getAlgorithm(p11AlgorithmName) { const mechanisms = this.container.session.slot.getMechanisms(); let EC; for (let i = 0; i < mechanisms.length; i++) { const mechanism = mechanisms.tryGetItem(i); if (mechanism && (mechanism.name === p11AlgorithmName || mechanism.name === "ECDSA")) { EC = mechanism.name; } } if (!EC) { throw new Error(`Cannot get PKCS11 EC mechanism by name '${p11AlgorithmName}'`); } return EC; } prepareData(hashAlgorithm, data) { return digest(hashAlgorithm.replace("-", ""), data); } importJwkPrivateKey(jwk, algorithm, extractable, keyUsages) { const template = this.createTemplate({ action: "import", type: "private", attributes: { id: GUID(), token: algorithm.token, sensitive: algorithm.sensitive, label: algorithm.label, alwaysAuthenticate: algorithm.alwaysAuthenticate, extractable, usages: keyUsages, }, }); template.paramsEC = Buffer.from(core__namespace.EcCurves.get(algorithm.namedCurve).raw); template.value = b64UrlDecode(jwk.d); const p11key = this.container.session.create(template).toType(); return new EcCryptoKey(p11key, algorithm); } importJwkPublicKey(jwk, algorithm, extractable, keyUsages) { const namedCurve = core__namespace.EcCurves.get(algorithm.namedCurve); const template = this.createTemplate({ action: "import", type: "public", attributes: { id: GUID(), token: algorithm.token, label: algorithm.label, extractable, usages: keyUsages, } }); template.paramsEC = Buffer.from(namedCurve.raw); let pointEc; if (namedCurve.name === "curve25519") { pointEc = b64UrlDecode(jwk.x); } else { const point = core__namespace.EcUtils.encodePoint({ x: b64UrlDecode(jwk.x), y: b64UrlDecode(jwk.y) }, namedCurve.size); const derPoint = asn1Schema__namespace.AsnConvert.serialize(new asn1Schema__namespace.OctetString(point)); pointEc = Buffer.from(derPoint); } template.pointEC = pointEc; const p11key = this.container.session.create(template).toType(); return new EcCryptoKey(p11key, algorithm); } exportJwkPublicKey(key) { const pkey = key.key.getAttribute({ pointEC: null, }); const curve = core__namespace.EcCurves.get(key.algorithm.namedCurve); const p11PointEC = pkey.pointEC; if (!p11PointEC) { throw new Error("Cannot get required ECPoint attribute"); } const derEcPoint = asn1Schema__namespace.AsnConvert.parse(p11PointEC, asn1Schema__namespace.OctetString); const ecPoint = core__namespace.EcUtils.decodePoint(derEcPoint, curve.size); const jwk = { kty: "EC", crv: key.algorithm.namedCurve, ext: true, key_ops: key.usages, x: pvtsutils__namespace.Convert.ToBase64Url(ecPoint.x), }; if (curve.name !== "curve25519") { jwk.y = pvtsutils__namespace.Convert.ToBase64Url(ecPoint.y); } return jwk; } async exportJwkPrivateKey(key) { const pkey = key.key.getAttribute({ value: null, }); const jwk = { kty: "EC", crv: key.algorithm.namedCurve, ext: true, key_ops: key.usages, d: pvtsutils__namespace.Convert.ToBase64Url(pkey.value), }; return jwk; } createTemplate(params) { const template = this.container.templateBuilder.build({ ...params, attributes: { ...params.attributes, label: params.attributes.label || "EC", }, }); template.keyType = graphene__namespace.KeyType.EC; return template; } spki2jwk(raw) { const keyInfo = asn1Schema__namespace.AsnParser.parse(raw, core__namespace.asn1.PublicKeyInfo); if (keyInfo.publicKeyAlgorithm.algorithm !== id_ecPublicKey) { throw new Error("SPKI is not EC public key"); } const namedCurveId = asn1Schema__namespace.AsnParser.parse(keyInfo.publicKeyAlgorithm.paramet