UNPKG

coralpay-node-sdk

Version:
356 lines (310 loc) 10.2 kB
/** * Created by osemeodigie on 13/05/2018. * objective: building to scale * * @package CoralPayPGPLibrary */ const fs = require("fs"); const GPG = require("gpg"); const misc = require("locutus/php/misc"); /** * GPG status output file descriptor. The status file descriptor outputs * detailed information for many GPG commands. See the second section of * the file <b>doc/DETAILS</b> in the * {@link http://www.gnupg.org/download/ GPG package} for a detailed * description of GPG's status output. */ const FD_STATUS = 3; /** * Command input file descriptor. This is used for methods requiring * passphrases. */ const FD_COMMAND = 4; const defaultArguments = [ // '--status-fd ' + FD_STATUS, // '--command-fd ' + FD_COMMAND, "--exit-on-status-write-error", "--no-permission-warning", "--no-secmem-warning", "--no-tty", "--no-default-keyring", // ignored if keying files are not specified "--no-options", // prevent creation of ~/.gnupg directory ]; /** * Used to import a key into the GPG keychain. * * @param {*} keyContentToImport * @param {*} importOptions */ const importKeys = async (keyContentToImport, importOptions = {}) => { const argOptions = importOptions || { debug: false, }; return new Promise((resolve, reject) => { GPG.importKey(keyContentToImport, function (importError, result, fingerprint) { if (!importError) { if (argOptions.debug != undefined && argOptions.debug) { console.log(`\n\nSuccesfully imported the key!!! \n\nImported Key Data: \n${result} \n\n`); } const keyId = fingerprint != null && fingerprint != undefined ? fingerprint : null; return resolve(keyId); } if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nError occurred while importing key!\n`); } return reject(new Error(`Error occurred while importing key!`)); }); }); }; exports.importKeys = importKeys; /** * This method is used to encrypt the request to be sent to Cgate. * * @param {*} plainMessage - the plain request to encrypt * @param {*} keyIdForPublicKey - the Long/Short ID for the imported public key * @param {*} encryptOptions - options to control how the output is generated */ const _encryptRequest = async (plainMessage, keyIdForPublicKey, encryptOptions = {}) => { const argOptions = encryptOptions || { homedir: null, armor: true, debug: false, format: "binary", // binary , hex or armor showVersion: false, }; if (keyIdForPublicKey == undefined || keyIdForPublicKey == null) { if (argOptions.debug != undefined && argOptions.debug) { console.log(`Error: Key Not Found with Id`); } throw new Error(`Error: Key Not Found with Id`); return; } let optionArgs = [ "--default-key", keyIdForPublicKey, "--recipient", keyIdForPublicKey, "--trust-model", "always", // so we don't get "no assurance this key belongs to the given user" ]; if (argOptions.homedir != undefined && argOptions.homedir != null && argOptions.homedir) { optionArgs.push("--homedir").push(argOptions.homedir); } if ( (argOptions.armor != undefined && argOptions.armor) || (argOptions.format != undefined && argOptions.format.toLowerCase() === "armor") ) { optionArgs.push("--armor"); } // Add the default options optionArgs = optionArgs.concat(defaultArguments); if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nCalling GPG with options: \n${JSON.stringify(optionArgs)}\n`); } return new Promise((resolve, reject) => { GPG.encrypt(plainMessage, optionArgs, function (error, encryptedBuffer) { if (error) { if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nError occurred while encrypting request with GPG. Error: \n${error}\n`); } reject(error); } console.log("Encrypted Buffer ==>", encryptedBuffer); const outputString = argOptions.format != undefined && argOptions.format.toLowerCase() === "hex" ? encryptedBuffer.toString("hex") : encryptedBuffer.toString(); if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nSuccessfully Encrypted With GPG: \n\n${outputString}\n`); } resolve(outputString); }); }); }; exports.encryptRequest = _encryptRequest; /** * This method is used to decrypt the response that is received from Cgate. * * @param {*} encryptedResponse - the encrypted response to decrypt * @param {*} keyIdForPrivateKey - the Long/Short ID for the imported public key * @param {*} passphrase - the passphrase associated with the Private key * @param {*} decryptOptions - options to control how the decryption process flows */ const _decryptRequest = async (encryptedResponse, keyIdForPrivateKey, passphrase, decryptOptions = {}) => { const argOptions = decryptOptions || { homedir: null, armor: true, debug: false, format: "binary", // plain, hex, or armor showVersion: false, }; if (keyIdForPrivateKey == undefined || keyIdForPrivateKey == null) { if (argOptions.debug != undefined && argOptions.debug) { console.log(`Error: Key Not Found with Id`); } throw new Error(`Error: Key Not Found with Id`); return; } let optionArgs = [ // "--skip-verify", "--ignore-mdc-error", // "--no-use-agent", // '--options /dev/null', // ignore any saved options //'--pinentry-mode loopback', // Since 2.1.13 we can use "loopback mode" instead of gpg-agent "--default-key", keyIdForPrivateKey, "-u", keyIdForPrivateKey, "--trust-model", "always", // so we don't get "no assurance this key belongs to the given user" // 'echo your_password | gpg --batch --yes --passphrase-fd 0', "--batch", // '--quiet', "--yes", //"--pinentry-mode", //"loopback", // '--passphrase-file <(echo password)', //'--passphrase', passphrase ]; if (passphrase) { optionArgs.push("--batch", "--pinentry-mode", "loopback"); optionArgs.push("--passphrase", passphrase); } if (argOptions.homedir != undefined && argOptions.homedir != null && argOptions.homedir) { optionArgs.push("--homedir").push(argOptions.homedir); } if ( (argOptions.armor != undefined && argOptions.armor) || (argOptions.format != undefined && argOptions.format.toLowerCase() === "armor") ) { optionArgs.push("--armor"); } // Add the default options optionArgs = optionArgs.concat(defaultArguments); if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nCalling GPG with options: \n${JSON.stringify(optionArgs)}\n${optionArgs.join(" ")}`); } const binaryEncryptedResponse = Buffer.from(encryptedResponse, "hex").toString("binary"); const armoredBinMessage = enarmor(binaryEncryptedResponse, "PGP MESSAGE"); console.log(armoredBinMessage); return new Promise((resolve, reject) => { GPG.decrypt(armoredBinMessage, optionArgs, function (error, decryptedBuffer) { if (error) { if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nError occurred while decrypting response with GPG.\n${error}\n`); } reject(error); } console.log("Decrypted Buffer ==>", decryptedBuffer); const outputString = argOptions.format != undefined && argOptions.format.toLowerCase() === "hex" ? decryptedBuffer.toString("hex") : decryptedBuffer.toString(); if (argOptions.debug != undefined && argOptions.debug) { console.log(`\nSuccessfully Decrypted With GPG: \n\n${outputString}\n`); } resolve(outputString); }); }); }; exports.decryptRequest = _decryptRequest; /** * Utility function to convert Uint8 array to a binary string. * * @param {*} u8Array */ const convertUint8ArrayToBinaryString = (u8Array) => { var i, len = u8Array.length, binaryString = ""; for (i = 0; i < len; i++) { binaryString += String.fromCharCode(u8Array[i]); } return binaryString; }; /** * Utility function to convert from a binary string to an Uint8 array. * * @param {*} binaryString */ const convertBinaryStringToUint8Array = (binaryString) => { var i, len = binaryString.length, u8_array = new Uint8Array(len); for (var i = 0; i < len; i++) { u8_array[i] = binaryString.charCodeAt(i); } return u8_array; }; /** * Ord function * @param {*} str */ const ord = (str) => { return str.charCodeAt(0); }; /** * @see http://tools.ietf.org/html/rfc4880#section-6.2 */ const header = (marker) => { return "-----BEGIN " + marker.toUpperCase() + "-----"; }; /** * @see http://tools.ietf.org/html/rfc4880#section-6.2 */ const footer = (marker) => { return "-----END " + marker.toUpperCase() + "-----"; }; /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6.1 */ const crc24 = (data) => { crc = 0x00b704ce; for (i = 0; i < data.length; i += 1) { crc ^= (ord(data[i]) & 255) << 16; for (j = 0; j < 8; j += 1) { crc <<= 1; if (crc & 0x01000000) { crc ^= 0x01864cfb; } } } return crc & 0x00ffffff; }; const wordwrap = (word, width, breakChar, shouldCut) => { breakChar = breakChar || "n"; width = width || 76; shouldCut = shouldCut || false; if (!word) { return word; } const wordLength = word.length; let startIndex = 0; let wrappedWordLines = []; while (startIndex < wordLength) { const acceptableWidth = startIndex + width > wordLength ? wordLength - startIndex : width; wrappedWordLines.push(word.substr(startIndex, acceptableWidth)); startIndex += width; } return wrappedWordLines.join(breakChar); }; /** * @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6.2 * @see http://tools.ietf.org/html/rfc2045 */ const enarmor = (data, marker = "MESSAGE", headers = {}) => { text = header(marker) + "\n"; const headerKeys = Object.keys(headers); for (i = 0; i < headerKeys.length; i += 1) { text += headerKeys[i] + ": " + headers[headerKeys[i]] + "\n"; } const base64Data = Buffer.from(data, "binary").toString("base64"); const packDataToUInt32BE = misc.pack("N", crc24(data)); // pack line here const base64PackedData = Buffer.from(packDataToUInt32BE.substr(1), "binary").toString("base64"); text += "\n" + wordwrap(base64Data, 76, "\n", true); text += "\n" + "=" + base64PackedData + "\n"; text += footer(marker) + "\n"; return text; }; exports.enarmor = enarmor;