vpn.email
Version:
vpn.email client
474 lines (387 loc) • 16.5 kB
text/typescript
import * as path from 'path';
import * as openpgp from 'openpgp';
import * as fs from 'fs';
import * as osenv from "osenv";
import * as os from 'os';
import * as event from 'events';
import * as Async from 'async';
import * as crypto from 'crypto'
import * as child_process from 'child_process';
import * as ramdonString from './randomString';
import * as testImapAccount from './testImapAccount';
import * as smtp from './smtpClass';
interface IpAddress_v4 {
localIpAddress:string;
mask:string;
mac:string;
routeIpAddress:string;
}
export class runCallBack {
private grep: child_process.ChildProcess = null;
constructor ( command: string, option: any, messageBack: ( message ) => void, private CallBack: ICallBack ) {
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 )
}
}
public stop () {
this.grep.kill ( 'SIGINT' )
}
}
export const uuid_generate = () => {
let lut: Array < string > = [];
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 ];
}
export const uuidFileName = () => {
const file = 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
export const saveConfig = ( session: ISockSession, CallBack: ICallBack ) => {
const key = session.keyPair;
const _session: ISockSession = {
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 => _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: ISockSession, CallBack: ICallBack ) => {
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 _Decryption ( data, key.privateKey, key.publicKey, ( err, initData ) => {
if ( err )
return CallBack ( err )
const ses:ISockSession = 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>
*/
export const checkInitFile = ( sockSession: ISockSession, CallBack: ICallBack ) => {
// 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' )
constructorViaKeyPair ( keyPair, err => {
// if vpn.email setup error delete all setup
if ( err ) {
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
}
export const deleteSystemPassword = ( session: ISockSession, CallBack: ICallBack ) => {
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: IKeyInformation = {
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: IKeyInformation, password: string ) => {
return keyPair.keyPasswordOK = keyPair.privateKey.decrypt ( password )
}
/**
*
*/
export const constructorViaKeyPair = ( keyPair: IKeyInformation, CallBack: ICallBack ) => {
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: IKeyInformation ) => {
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: IKeyInformation ) => {
let size = -1;
if ( key.privateKey.primaryKey.mpi.length ) {
size = ( key.privateKey.primaryKey.mpi [0].byteLength () * 8 );
}
return size;
}
const getCreateDate = ( key: IKeyInformation ) => {
return key.privateKey.primaryKey.created;
}
/**
* @param text {string}
* @param pubKey {string}
* @param privateKey {string} use signatures
* @param password {string} privateKey
* @param callBack {callback function}
*/
export const _encrypt = (text: string, publicKey, privateKey, callback) => {
const optionQQ = {
data: text,
publicKeys: publicKey,
privateKeys: privateKey
}
openpgp.encrypt ( optionQQ ).then (( m ) => {
callback( null, m.data );
}).catch (( err ) => {
callback ( err );
});
}
export const Encrypt = ( text: string, pubKey, privateKey, password: string,
CallBack: ( err?: Error, data?: string ) => void ) => {
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}
*/
export const Decryption = ( encryptMessage: string, password: string, privatePem: string, publicKey: string,
CallBack: ( err?: Error, data?: any ) => void ) => {
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,
// password:password if have will be ERROR as **** No symmetrically encrypted session key packet found.****
}
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 );
}
}
export const _Decryption = (encryptMessage: string, privateKey, publicKeys, CallBack: ICallBack) => {
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 );
})
}
export const defineSession = ( password: string , CallBack: ICallBack ) => {
const sockSession: ISockSession = {
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
}
checkInitFile ( sockSession, CallBack )
}
export const 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;
}