vpn.email
Version:
vpn.email client
381 lines (380 loc) • 14.7 kB
JavaScript
;
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;
};