UNPKG

vpn.email

Version:
381 lines (380 loc) 14.7 kB
"use strict"; const path = require("path"); const openpgp = require("openpgp"); const fs = require("fs"); const osenv = require("osenv"); const os = require("os"); const Async = require("async"); const crypto = require("crypto"); const child_process = require("child_process"); class runCallBack { constructor(command, option, messageBack, CallBack) { this.CallBack = CallBack; this.grep = null; const envString = JSON.stringify(process.env); const optionString = JSON.stringify(option); const env = (option && typeof option === 'object' && Object.keys(option).length) ? JSON.parse('{ "env": ' .concat(envString.substring(0, envString.length - 1) + ",") .concat(optionString.substring(1, optionString.length)) .concat('}')) : null; try { this.grep = child_process.fork(command, [], env); this.grep.on('message', msg => { if (messageBack && typeof messageBack === 'function') return messageBack(msg); console.log('grep.on message: ', msg); }); this.grep.on('close', (code, signal) => { console.log('runCallBack exit with code:', code, signal); if (code) { if (CallBack) CallBack(new Error('Exit with code:' + code)); return; } if (signal) { if (CallBack) CallBack(new Error('Exit with cancel')); return; } if (CallBack) CallBack(); }); } catch (ex) { if (ex.code === 'ENOENT') { console.log('runCallBack have not find command: ', command); } console.log('runCallBack catch ex:', ex); if (CallBack) CallBack(ex); } } stop() { this.grep.kill('SIGINT'); } } exports.runCallBack = runCallBack; exports.uuid_generate = () => { let lut = []; for (let i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); } var d0 = Math.random() * 0xffffffff | 0; var d1 = Math.random() * 0xffffffff | 0; var d2 = Math.random() * 0xffffffff | 0; var d3 = Math.random() * 0xffffffff | 0; return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' + lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' + lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; }; exports.uuidFileName = () => { const file = exports.uuid_generate().split('-'); return file.join('').substr(0, 2 + Math.random() * 30); }; const setupFolderName = '/Vpn.Email'; const localPath = path.parse(osenv.home() + setupFolderName); const lettersRegexp = /^[A-Za-z]+$/; const numberRegexp = /^[0-9]+$/; const SystemInitFilePath = path.parse(path.format(localPath) + '/initFile'); const FileErr = 'fileSystemError'; const cer_PrivfileName = path.parse(path.format(localPath) + '/systemCerPriv.key.pem'); const cer_PubfileName = path.parse(path.format(localPath) + '/systemCerPub.key.pem'); const EmailRegexp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; exports.saveConfig = (session, CallBack) => { const key = session.keyPair; const _session = { imapArray: session.imapArray, imapConnected: false, keyPair: null, keyPairPassword: null, sentEmail: null, vpnEmailPublicKey: null, newKeyPairProcessRunning: null, vpnEmailServerKeepConnected: null, newKeyProcessPid: null, vpnServerConnectData: session.vpnServerConnectData, dockerPublicKey: session.dockerPublicKey }; if (_session.imapArray && _session.imapArray.dummyData) _session.imapArray.dummyData = null; Async.waterfall([ next => exports._encrypt(JSON.stringify(_session), key.publicKey, key.privateKey, next), (data, next) => fs.writeFile(path.format(SystemInitFilePath), data, { encoding: 'utf8' }, next) ], CallBack); }; /** * read the config file with a password * @param password <string> */ const readConfig = (sockSession, CallBack) => { const pathname = path.format(SystemInitFilePath); // read INIT data from SystemInitFilePath fs.readFile(pathname, { encoding: 'utf8' }, (err, data) => { console.log('open System InitFilePath success!'); if (err) return CallBack(null, sockSession); const key = sockSession.keyPair; if (data && data.length && key.keyPasswordOK) { return exports._Decryption(data, key.privateKey, key.publicKey, (err, initData) => { if (err) return CallBack(err); const ses = JSON.parse(initData.data); sockSession.imapArray = ses.imapArray; sockSession.vpnServerConnectData = ses.vpnServerConnectData; CallBack(null, sockSession); }); } CallBack(null, sockSession); }); }; /** * check the file system ready or not * if have password, administratorEmail ... will check is it working if already init file, will make new key pair if haven't init * @param sockSession <ISockSession> * @param CallBack <function> */ exports.checkInitFile = (sockSession, CallBack) => { // check & make the system path const keyPair = sockSession.keyPair; Async.parallel([ next => fs.access(path.format(cer_PrivfileName), next), next => fs.access(path.format(cer_PubfileName), next) ], err => { if (err) return CallBack(null, sockSession); keyPair.privateKeyUTF8 = fs.readFileSync(path.format(cer_PrivfileName), 'utf8'); keyPair.publicKeyUTF8 = fs.readFileSync(path.format(cer_PubfileName), 'utf8'); exports.constructorViaKeyPair(keyPair, err => { // if vpn.email setup error delete all setup if (err) { exports.deleteSystemPassword(sockSession, CallBack); } if (sockSession.keyPairPassword && sockSession.keyPairPassword.length && checkPassword(keyPair, sockSession.keyPairPassword)) { return readConfig(sockSession, CallBack); } CallBack(null, sockSession); }); }); // already have key pair // Try use password to open PEM }; exports.deleteSystemPassword = (session, CallBack) => { session.keyPairPassword = null; session.imapArray = null; session.vpnServerConnectData = null; session.keyPair = generatePem(); Async.parallel([ next => fs.unlink(path.format(cer_PrivfileName), next), next => fs.unlink(path.format(cer_PubfileName), next), next => fs.unlink(path.format(SystemInitFilePath), next) ], (err) => { console.log('Async.parallel deleteSystemPassword success', err); CallBack(); }); }; const generatePem = () => { const ret = { userId: null, keyPassword: null, privateKeyUTF8: null, publicKeyUTF8: null, privateKey: null, publicKey: null, userName: null, email: null, keyBitLength: null, createDate: null, keyPasswordOK: false }; return (ret); }; /**------------------------------------------------------------------------------------------------------ * * check the private key password * input: * Password * ------------------------------------------------------------------------------------------------------*/ const checkPassword = (keyPair, password) => { return keyPair.keyPasswordOK = keyPair.privateKey.decrypt(password); }; /** * */ exports.constructorViaKeyPair = (keyPair, CallBack) => { let privateKey1 = openpgp.key.readArmored(keyPair.privateKeyUTF8); let pubvateKey1 = openpgp.key.readArmored(keyPair.privateKeyUTF8); if (privateKey1.err || pubvateKey1.err) { return CallBack(new Error('key pair error')); } keyPair.privateKey = privateKey1.keys[0]; keyPair.publicKey = pubvateKey1.keys; var user = keyPair.privateKey.users; if (user && user.length) { keyPair.userId = user[0].userId.userid; getInfoFromUserId(keyPair); } keyPair.keyBitLength = getBitLength(keyPair); keyPair.createDate = getCreateDate(keyPair); CallBack(null); }; /** * get key information * @param key <IKeyInformation> */ const getInfoFromUserId = (key) => { if (key.userId.length) { let temp = key.userId.split(' <'); let temp1 = temp[0].split(' ('); let temp2 = temp1.length > 1 ? temp1[1].split('||') : ''; key.email = temp.length > 1 ? temp[1].slice(0, temp[1].length - 1) : ''; key.userName = temp1[0]; } }; /** * get Bit Length * @param key <IKeyInformation> */ const getBitLength = (key) => { let size = -1; if (key.privateKey.primaryKey.mpi.length) { size = (key.privateKey.primaryKey.mpi[0].byteLength() * 8); } return size; }; const getCreateDate = (key) => { return key.privateKey.primaryKey.created; }; /** * @param text {string} * @param pubKey {string} * @param privateKey {string} use signatures * @param password {string} privateKey * @param callBack {callback function} */ exports._encrypt = (text, publicKey, privateKey, callback) => { const optionQQ = { data: text, publicKeys: publicKey, privateKeys: privateKey }; openpgp.encrypt(optionQQ).then((m) => { callback(null, m.data); }).catch((err) => { callback(err); }); }; exports.Encrypt = (text, pubKey, privateKey, password, CallBack) => { const publicKeys = (typeof pubKey === 'string') ? openpgp.key.readArmored(pubKey).keys : pubKey; const privateKeys = (typeof privateKey === 'string') ? openpgp.key.readArmored(privateKey).keys[0] : privateKey; if (!publicKeys || !privateKey || !privateKeys.decrypt) return CallBack(new Error('util Encrypt got ERROR: have not key pair information!')); if (password && password.length && !privateKeys.decrypt(password)) { return CallBack(new Error('password not matched')); } ; const optionQQ = { data: text, publicKeys: publicKeys, privateKeys: privateKeys, passwords: password }; openpgp.encrypt(optionQQ).then(m => { CallBack(null, m.data); }).catch(err => { CallBack(err); }); }; /** * @param encryptMessage {string} * @param password {string} private pem password * @param privatePem {string} private key * @param publicKey {string} public key for check signatures * @param fb {callBack function} */ exports.Decryption = (encryptMessage, password, privatePem, publicKey, CallBack) => { try { const privateKey = openpgp.key.readArmored(privatePem).keys[0]; const PublicKey = publicKey ? openpgp.key.readArmored(publicKey).keys : null; if (!privateKey.decrypt(password)) { return CallBack(new Error('password not matched')); } const DMessage = openpgp.message.readArmored(encryptMessage); const DecryptionOption = { message: DMessage, privateKey: privateKey, publicKeys: PublicKey, }; openpgp.decrypt(DecryptionOption).then(plaintext => { if (publicKey && publicKey.length && !plaintext.signatures[0].valid) return CallBack(new Error('Signatures error')); CallBack(null, plaintext); }).catch(err => { // failure CallBack(err); }); } catch (ex) { console.log('error =>', ex); CallBack(ex); } }; exports._Decryption = (encryptMessage, privateKey, publicKeys, CallBack) => { const DecryptionOption = { message: openpgp.message.readArmored(encryptMessage), privateKey: privateKey, publicKeys: publicKeys }; openpgp.decrypt(DecryptionOption).then((plaintext) => { return CallBack(null, plaintext); }).catch((err) => { // failure return CallBack(err); }); }; exports.defineSession = (password, CallBack) => { const sockSession = { keyPair: { createDate: null, email: null, keyPasswordOK: null, privateKey: null, privateKeyUTF8: null, userName: null, publicKey: null, publicKeyUTF8: null, userId: null, keyBitLength: null, keyPassword: password }, imapConnected: false, vpnEmailPublicKey: fs.readFileSync('BFC2ABE0.pub.asc', 'utf8'), imapArray: null, vpnEmailServerKeepConnected: false, vpnServerConnectData: null, keyPairPassword: password, sentEmail: null, newKeyPairProcessRunning: false, newKeyProcessPid: null, dockerPublicKey: null }; exports.checkInitFile(sockSession, CallBack); }; exports.machineuuid = () => { let _ret = os.totalmem() + os.cpus().map(function (cpu) { return cpu.model; }).join(":") + JSON.stringify(os.networkInterfaces()); const ret = crypto.createHash("md5").update(_ret).digest("HEX"); return ret; };