UNPKG

@tiplink/api

Version:

Api for creating and sending TipLinks

249 lines (248 loc) 11.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.encodeUTF8 = exports.decodeUTF8 = exports.decryptDataUsingDH = exports.decryptMessage = exports.encryptMessage = exports.generateNonce = exports.decryptPrivateKey = exports.encryptPublicKey = exports.generatePublicPrivateKey = exports.decrypt = exports.encrypt = exports.generateKey = exports.generateRandomSalt = exports.DEFAULT_TIPLINK_KEYLENGTH = void 0; const tweetnacl_1 = __importDefault(require("tweetnacl")); const tweetnacl_util_1 = require("tweetnacl-util"); Object.defineProperty(exports, "decodeUTF8", { enumerable: true, get: function () { return tweetnacl_util_1.decodeUTF8; } }); Object.defineProperty(exports, "encodeUTF8", { enumerable: true, get: function () { return tweetnacl_util_1.encodeUTF8; } }); exports.DEFAULT_TIPLINK_KEYLENGTH = 12; const crypto_1 = require("crypto"); const crypto = crypto_1.webcrypto; const bs58_1 = __importDefault(require("bs58")); /* Convert a string into an ArrayBuffer from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String */ function stringToArrayBuffer(str) { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; } // Crypto encrypting links in db and apikeys const KEY_LENGTH_BYTES = 256; const SALT_LENGTH_BYTES = 12; const MODULUS_LENGTH = 4096; const DIGEST_ALGO = "SHA-512"; const HASH_ALGO = "AES-GCM"; // Alternatives 'AES-CTR' 'AES-CBC' 'AES-GCM' const HASH_ALGO_WTIH_PUBLIC_PRIVATE = "RSA-OAEP"; const EXPORT_HASH_ALGO = "A256GCM"; // const EXPORT_PUBLIC_PRIVATE_ALGO = "RSA-OAEP-512"; const EXPORT_FORMAT = "jwk"; const EXPORT_PUBLIC_FORMAT = "spki"; const EXPORT_PRIVATE_FORMAT = "pkcs8"; const generateRandomSalt = () => { return Buffer.from(crypto.getRandomValues(new Uint8Array(SALT_LENGTH_BYTES))).toString("hex"); }; exports.generateRandomSalt = generateRandomSalt; const generateKey = () => __awaiter(void 0, void 0, void 0, function* () { const key = yield crypto.subtle.generateKey({ name: HASH_ALGO, length: KEY_LENGTH_BYTES, }, true, ["encrypt", "decrypt"]); // @ts-ignore return exportKey(key); }); exports.generateKey = generateKey; const exportKey = (key) => __awaiter(void 0, void 0, void 0, function* () { // @ts-ignore const exportableKey = yield crypto.subtle.exportKey(EXPORT_FORMAT, key); // if (exportableKey.alg === EXPORT_HASH_ALGO) { if (exportableKey.k === undefined) { throw Error("Export Key Error"); } return exportableKey.k; // } }); const importKey = (key) => { const jwkKey = { alg: EXPORT_HASH_ALGO, k: key, ext: true, key_ops: ["encrypt", "decrypt"], kty: "oct", }; return crypto.subtle.importKey("jwk", jwkKey, { name: HASH_ALGO, length: KEY_LENGTH_BYTES, }, true, ["encrypt", "decrypt"]); }; const encrypt = (data, key, salt) => __awaiter(void 0, void 0, void 0, function* () { const ec = new TextEncoder(); const iv = Uint8Array.from(Buffer.from(salt, "hex")); const encKey = yield importKey(key); const ciphertext = yield crypto.subtle.encrypt({ name: HASH_ALGO, iv: iv, }, encKey, ec.encode(data)); return Buffer.from(ciphertext).toString("hex"); }); exports.encrypt = encrypt; const decrypt = (data, key, salt) => __awaiter(void 0, void 0, void 0, function* () { const ciphertext = Uint8Array.from(Buffer.from(data, "hex")); const iv = Uint8Array.from(Buffer.from(salt, "hex")); const decKey = yield importKey(key); const dec = new TextDecoder(); const plaintext = yield crypto.subtle.decrypt({ name: HASH_ALGO, iv: iv, }, decKey, ciphertext); return dec.decode(plaintext); }); exports.decrypt = decrypt; const generatePublicPrivateKey = () => __awaiter(void 0, void 0, void 0, function* () { const { publicKey, privateKey } = yield crypto.subtle.generateKey({ name: HASH_ALGO_WTIH_PUBLIC_PRIVATE, modulusLength: MODULUS_LENGTH, publicExponent: new Uint8Array([1, 0, 1]), hash: DIGEST_ALGO, }, true, ["encrypt", "decrypt"]); return { // @ts-ignore publicKey: yield exportPublicKey(publicKey), // @ts-ignore privateKey: yield exportPrivateKey(privateKey), }; }); exports.generatePublicPrivateKey = generatePublicPrivateKey; const exportPublicKey = (publicKey) => __awaiter(void 0, void 0, void 0, function* () { // @ts-ignore const exportableKey = yield crypto.subtle.exportKey(EXPORT_PUBLIC_FORMAT, publicKey); // TODO type check this? return btoa( // @ts-ignore String.fromCharCode.apply(null, new Uint8Array(exportableKey))); // return Buffer.from(exportableKey).toString('hex'); }); const importPublicKey = (publicKey) => __awaiter(void 0, void 0, void 0, function* () { // const arrBuf = Uint8Array.from(Buffer.from(publicKey, 'hex')); const arrBuf = stringToArrayBuffer(atob(publicKey)); return crypto.subtle.importKey(EXPORT_PUBLIC_FORMAT, arrBuf, { name: HASH_ALGO_WTIH_PUBLIC_PRIVATE, // modulusLength: MODULUS_LENGTH, // publicExponent: new Uint8Array([1, 0, 1]), hash: DIGEST_ALGO, }, true, ["encrypt"]); }); const exportPrivateKey = (privateKey) => __awaiter(void 0, void 0, void 0, function* () { // @ts-ignore const exportableKey = yield crypto.subtle.exportKey(EXPORT_PRIVATE_FORMAT, privateKey); // TODO type check this? return btoa( // @ts-ignore String.fromCharCode.apply(null, new Uint8Array(exportableKey))); // return Buffer.from(exportableKey).toString('hex'); }); const importPrivateKey = (privateKey) => __awaiter(void 0, void 0, void 0, function* () { const arrBuf = stringToArrayBuffer(atob(privateKey)); return crypto.subtle.importKey(EXPORT_PRIVATE_FORMAT, arrBuf, { name: HASH_ALGO_WTIH_PUBLIC_PRIVATE, // modulusLength: MODULUS_LENGTH, // publicExponent: new Uint8Array([1, 0, 1]), hash: DIGEST_ALGO, }, true, ["decrypt"]); }); const encryptPublicKey = (data, publicKey) => __awaiter(void 0, void 0, void 0, function* () { const ec = new TextEncoder(); const encKey = yield importPublicKey(publicKey); const ciphertext = yield crypto.subtle.encrypt({ name: HASH_ALGO_WTIH_PUBLIC_PRIVATE, }, encKey, ec.encode(data)); return Buffer.from(ciphertext).toString("hex"); }); exports.encryptPublicKey = encryptPublicKey; const decryptPrivateKey = (data, privateKey) => __awaiter(void 0, void 0, void 0, function* () { const ciphertext = Uint8Array.from(Buffer.from(data, "hex")); const decKey = yield importPrivateKey(privateKey); const dec = new TextDecoder(); const plaintext = yield crypto.subtle.decrypt({ name: HASH_ALGO_WTIH_PUBLIC_PRIVATE, // iv: iv, }, decKey, ciphertext); return dec.decode(plaintext); }); exports.decryptPrivateKey = decryptPrivateKey; const generateNonce = () => __awaiter(void 0, void 0, void 0, function* () { return Buffer.from(tweetnacl_1.default.randomBytes(24)).toString("hex"); }); exports.generateNonce = generateNonce; const encryptMessage = (keypair, nonce, message) => __awaiter(void 0, void 0, void 0, function* () { const nonceArray = Uint8Array.from(Buffer.from(nonce, "hex")); return tweetnacl_1.default.secretbox((0, tweetnacl_util_1.decodeUTF8)(message), nonceArray, keypair.secretKey.slice(32)); }); exports.encryptMessage = encryptMessage; const decryptMessage = (keypair, nonce, encryptedMessage) => __awaiter(void 0, void 0, void 0, function* () { const nonceArray = Uint8Array.from(Buffer.from(nonce, "hex")); const encryptedMessageArray = Uint8Array.from(Buffer.from(encryptedMessage, "hex")); // const encryptedMessageArray = stringToArrayBuffer(window.atob(encryptedMessage)); const res = tweetnacl_1.default.secretbox.open(encryptedMessageArray, nonceArray, keypair.secretKey.slice(32)); if (res === null) { return ""; } else { return (0, tweetnacl_util_1.encodeUTF8)(res); } }); exports.decryptMessage = decryptMessage; // encrypted using symmetric key encryption generated from a Diffie-Hellman key exchange // https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange' // export const encryptDataUsingDH = ( // dataToEncrypt: { [key: string]: any }, // recipientEncryptionPubKey: Uint8Array | string // bs58 encoded string or nacl box public key (not solana keypair) // ) => { // // nacl box keypair (not solana wallet keypair) // const keypair = nacl.box.keyPair(); // const nonce = nacl.randomBytes(24); // const dataBuffer = Buffer.from(JSON.stringify(dataToEncrypt)); // const recipientPubKey = // typeof recipientEncryptionPubKey === "string" // ? bs58.decode(recipientEncryptionPubKey) // : recipientEncryptionPubKey; // const encryptedData = nacl.box( // dataBuffer, // nonce, // recipientPubKey, // keypair.secretKey // ); // return { // nonceUint8Arr: nonce, // nonceBs58: bs58.encode(nonce), // encryptedDataUint8Arr: encryptedData, // encryptedDataBs58: bs58.encode(encryptedData), // encryptionKeypairToHold: keypair, // }; // }; const decryptDataUsingDH = (dataToDecrypt, // string must be bs58 encoded nonce, // string must be bs58 encoded senderEncryptionPubKey, // string must be bs58 encoded encryptionKeypair) => { const encryptedData = typeof dataToDecrypt === "string" ? bs58_1.default.decode(dataToDecrypt) : dataToDecrypt; const encryptionNonce = typeof nonce == "string" ? bs58_1.default.decode(nonce) : nonce; const senderPubKey = typeof senderEncryptionPubKey === "string" ? bs58_1.default.decode(senderEncryptionPubKey) : senderEncryptionPubKey; const decryptedData = tweetnacl_1.default.box.open(encryptedData, encryptionNonce, senderPubKey, encryptionKeypair.secretKey); if (!decryptedData) return; const decryptonDataJson = JSON.parse(Buffer.from(decryptedData).toString("utf8")); // json.parse returns "any" typed json so ok to silence eslint error // eslint-disable-next-line @typescript-eslint/no-unsafe-return return decryptonDataJson; }; exports.decryptDataUsingDH = decryptDataUsingDH;