UNPKG

@trap_stevo/verilink

Version:

Encrypted from the start. Trusted to the end. This client-side protocol redefines secure communication — forging a direct bridge to zero-trust architecture through encrypted sessions, intelligent attestation, and seamless claim validation. Engineered for

175 lines (174 loc) 6.4 kB
"use strict"; function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } const crypto = require("crypto"); const axios = require("axios"); var _VeriLink_brand = /*#__PURE__*/new WeakSet(); class VeriLink { constructor(options = {}) { _classPrivateMethodInitSpec(this, _VeriLink_brand); this.persistSessionKey = options.persistSessionKey || false; this.persistSessionID = options.persistSessionID || false; this.mutator = typeof options.mutator === "string" ? new Uint8Array(Buffer.from(options.mutator)) : options.mutator || new Uint8Array(Buffer.from("vlk")); this.userAgent = options.userAgent || "verilink-node"; this.deviceID = options.deviceID || null; this.vaultSGN = options.vaultSGN || "vlx"; this.linkSGN = options.linkSGN || "vli"; this.serverURL = options.serverURL || ""; this.algorithm = "aes-256-gcm"; this.ivLength = 12; if (options.sessionKey) { this.setKey(options.sessionKey); } else { this.sessionKey = _assertClassBrand(_VeriLink_brand, this, _loadOrGenerateSessionKey).call(this); } if (options.sessionID) { this.sessionID = options.sessionID; } else { this.sessionID = _assertClassBrand(_VeriLink_brand, this, _loadOrGenerateSessionID).call(this); } } setServer(url) { this.serverURL = url; } setKey(key) { if (typeof key === "string") { this.sessionKey = Buffer.from(key, "base64"); } else if (Buffer.isBuffer(key)) { this.sessionKey = key; } else { throw new Error("Invalid session key data (Base64 string or Buffer)"); } } async pair(pairingURL = "/device/pair") { const sessionKey = this.sessionKey.toString("base64"); const sessionID = this.sessionID; const response = await axios.post(this.serverURL + pairingURL, { sessionKey }, { headers: { "x-vlink-session-id": sessionID } }); return response.data; } async send(method, path, data = {}, headers = {}, options = { fullResponse: false }) { const methodUpper = method.toUpperCase(); const url = this.serverURL + path; const link = _assertClassBrand(_VeriLink_brand, this, _generateLink).call(this); const vlinkID = _assertClassBrand(_VeriLink_brand, this, _hmac).call(this, this.sessionKey, link); const encrypted = _assertClassBrand(_VeriLink_brand, this, _encrypt).call(this, data); const claim = _assertClassBrand(_VeriLink_brand, this, _signClaim).call(this); const config = { method, url, headers: { "Content-Type": "application/json", "x-vlink-claim-timestamp": claim.timestamp, "x-vlink-session-id": this.sessionID, "x-vlink-device": claim.deviceID, "x-vlink-claim": claim.signature, "x-vlink-id": vlinkID, "x-vlink": link, ...headers } }; if (methodUpper === "GET") { config.params = { encrypted }; } else { config.data = { encrypted }; } const response = await axios(config); if (response.data?.encrypted) { const decrypted = _assertClassBrand(_VeriLink_brand, this, _decrypt).call(this, response.data.encrypted); return options.fullResponse ? { headers: response.headers, status: response.status, data: decrypted, response } : decrypted; } return options.fullResponse ? { headers: response.headers, status: response.status, data: response.data, response } : response.data; } mutateForStorage(buffer) { const bytes = new Uint8Array(buffer); const mutated = bytes.map((b, i) => b ^ this.mutator[i % this.mutator.length]); return Buffer.from(mutated).toString("base64"); } unmutateFromStorage(base64) { const bytes = Buffer.from(base64, "base64"); const restored = bytes.map((b, i) => b ^ this.mutator[i % this.mutator.length]); return Buffer.from(restored); } getMutatorFingerprint() { return crypto.createHash("sha256").update(this.mutator).digest("hex").slice(0, 12); } getSession() { return { sessionKey: this.sessionKey.toString("base64"), fingerprint: this.getMutatorFingerprint(), sessionID: this.sessionID }; } } function _encrypt(data) { const iv = crypto.randomBytes(this.ivLength); const cipher = crypto.createCipheriv(this.algorithm, this.sessionKey, iv); const json = JSON.stringify(data); const encrypted = Buffer.concat([cipher.update(json, "utf8"), cipher.final()]); const tag = cipher.getAuthTag(); const full = Buffer.concat([iv, tag, encrypted]); return full.toString("base64"); } function _decrypt(b64) { const buffer = Buffer.from(b64, "base64"); const ciphertext = buffer.slice(28); const tag = buffer.slice(12, 28); const iv = buffer.slice(0, 12); const decipher = crypto.createDecipheriv(this.algorithm, this.sessionKey, iv); decipher.setAuthTag(tag); const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return JSON.parse(decrypted.toString("utf8")); } function _signClaim() { const timestamp = Date.now(); const payload = { deviceID: this.deviceID || null, timestamp, userAgent: this.userAgent }; return { signature: _assertClassBrand(_VeriLink_brand, this, _hmac).call(this, this.sessionKey, JSON.stringify(payload)), timestamp, deviceID: payload.deviceID }; } function _hmac(key, msg) { return crypto.createHmac("sha256", key).update(msg).digest("base64"); } function _generateLink(len = 16) { return crypto.randomBytes(len).toString("hex"); } function _loadOrGenerateSessionKey() { const key = crypto.randomBytes(32); return key; } function _loadOrGenerateSessionID() { return `vl-${crypto.randomUUID?.() || _assertClassBrand(_VeriLink_brand, this, _fallbackUUID).call(this)}`; } function _fallbackUUID() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.randomBytes(1)[0] & 15 >> c / 4).toString(16)); } module.exports = VeriLink;