UNPKG

vpn.email

Version:
474 lines (387 loc) 16.5 kB
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; }