Typescript - Completed native crypto implementation & fixed bugs

This commit is contained in:
Kiran Niranjan 2018-12-09 14:19:24 +05:30
parent 3f799f10cc
commit c17d5e8536
6 changed files with 184 additions and 28 deletions

View File

@ -119,6 +119,7 @@
"wdio-selenium-standalone-service": "0.0.12"
},
"dependencies": {
"@types/ffi": "0.2.1",
"archiver": "3.0.0",
"async.map": "0.5.2",
"async.mapseries": "0.5.2",

View File

@ -0,0 +1,136 @@
import { app } from 'electron';
import { Library } from 'ffi';
import * as path from 'path';
import { refType, types } from 'ref';
const execPath = path.dirname(app.getPath('exe'));
import { isDevEnv, isMac } from '../common/env';
import { logger } from '../common/logger';
const TAG_LENGTH = 16;
const arch = process.arch === 'ia32';
const winLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', 'library') : path.join(execPath, 'library');
const macLibraryPath = isDevEnv ? path.join(__dirname, '..', '..', 'library') : path.join(execPath, '..', 'library');
const cryptoLibPath = isMac ?
path.join(macLibraryPath, 'cryptoLib.dylib') :
(arch ? path.join(winLibraryPath, 'libsymphonysearch-x86.dll') : path.join(winLibraryPath, 'libsymphonysearch-x64.dll'));
const library = new Library((cryptoLibPath), {
AESEncryptGCM: [types.int32, [
refType(types.uchar),
types.int32,
refType(types.uchar),
types.int32,
refType(types.uchar),
refType(types.uchar),
types.uint32,
refType(types.uchar),
refType(types.uchar),
types.uint32,
]],
AESDecryptGCM: [types.int32, [
refType(types.uchar),
types.int32,
refType(types.uchar),
types.int32,
refType(types.uchar),
types.uint32,
refType(types.uchar),
refType(types.uchar),
types.uint32,
refType(types.uchar),
]],
getVersion: [types.CString, []],
});
export interface ICryptoLib {
AESGCMEncrypt: (name: string, base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
AESGCMDecrypt: (base64IV: string, base64AAD: string, base64Key: string, base64In: string) => string | null;
}
class CryptoLibrary implements ICryptoLib {
/**
* Encrypt / Decrypt
*
* @param name {string} Method name to execute
* @param base64IV {string}
* @param base64AAD {string}
* @param base64Key {string}
* @param base64In {string}
* @constructor
*/
public static EncryptDecrypt(name: string, base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
let base64Input = base64In;
if (!base64Input) {
base64Input = '';
}
const IV: Buffer = Buffer.from(base64IV, 'base64');
const AAD: Buffer = Buffer.from(base64AAD, 'base64');
const KEY: Buffer = Buffer.from(base64Key, 'base64');
const IN: Buffer = Buffer.from(base64Input, 'base64');
if (name === 'AESGCMEncrypt') {
const outPtr: Buffer = Buffer.alloc(IN.length);
const TAG: Buffer = Buffer.alloc(TAG_LENGTH);
const resultCode = library.AESEncryptGCM(IN, IN.length, AAD, AAD.length, KEY, IV, IV.length, outPtr, TAG, TAG_LENGTH);
if (resultCode < 0) {
logger.error(`AESEncryptGCM, Failed to encrypt with exit code ${resultCode}`);
}
const bufferArray = [outPtr, TAG];
return Buffer.concat(bufferArray).toString('base64');
}
if (name === 'AESGCMDecrypt') {
const cipherTextLen = IN.length - TAG_LENGTH;
const TAG = Buffer.from(IN.slice(IN.length - 16, IN.length));
const outPtr = Buffer.alloc(IN.length - TAG_LENGTH);
const resultCode = library.AESDecryptGCM(IN, cipherTextLen, AAD, AAD.length, TAG, TAG_LENGTH, KEY, IV, IV.length, outPtr);
if (resultCode < 0) {
logger.error(`AESDecryptGCM, Failed to decrypt with exit code ${resultCode}`);
}
return outPtr.toString('base64');
}
return null;
}
/**
* Encrypts the given data
*
* @param base64IV {string}
* @param base64AAD {string}
* @param base64Key {string}
* @param base64In {string}
* @constructor
*/
public AESGCMEncrypt(base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
return CryptoLibrary.EncryptDecrypt('AESGCMEncrypt', base64IV, base64AAD, base64Key, base64In);
}
/**
* Decrypts the give data
*
* @param base64IV {string}
* @param base64AAD {string}
* @param base64Key {string}
* @param base64In {string}
* @constructor
*/
public AESGCMDecrypt(base64IV: string, base64AAD: string, base64Key: string, base64In: string): string | null {
return CryptoLibrary.EncryptDecrypt('AESGCMDecrypt', base64IV, base64AAD, base64Key, base64In);
}
}
const cryptoLibrary = new CryptoLibrary();
export { cryptoLibrary };

View File

@ -69,6 +69,8 @@ export class WindowHandler {
private readonly config: IConfig;
// Window reference
private readonly windows: object;
private readonly isCustomTitleBarAndWindowOS: boolean;
private mainWindow: ICustomBrowserWindow | null;
private loadingWindow: Electron.BrowserWindow | null;
private aboutAppWindow: Electron.BrowserWindow | null;
@ -82,6 +84,8 @@ export class WindowHandler {
this.windows = {};
this.windowOpts = { ...this.getMainWindowOpts(), ...opts };
this.isAutoReload = false;
this.isCustomTitleBarAndWindowOS = isWindowsOS && this.config.isCustomTitleBar;
this.appMenu = null;
// Window references
this.mainWindow = null;
@ -127,7 +131,7 @@ export class WindowHandler {
if (!this.mainWindow || this.mainWindow.isDestroyed()) return;
// Injects custom title bar css into the webContents
if (isWindowsOS && this.mainWindow && this.config.isCustomTitleBar) {
if (this.mainWindow && this.isCustomTitleBarAndWindowOS) {
this.mainWindow.webContents.insertCSS(
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/title-bar.css'), 'utf8').toString(),
);
@ -136,7 +140,7 @@ export class WindowHandler {
this.mainWindow.webContents.insertCSS(
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
);
this.mainWindow.webContents.send('page-load');
this.mainWindow.webContents.send('page-load', { isWindowsOS });
this.appMenu = new AppMenu();
this.monitorWindowActions();
// Ready to show the window
@ -274,11 +278,9 @@ export class WindowHandler {
* Main window opts
*/
private getMainWindowOpts(): ICustomBrowserWindowConstructorOpts {
// config fields
const { isCustomTitleBar } = this.config;
return {
alwaysOnTop: false,
frame: !(isCustomTitleBar && isWindowsOS),
frame: !this.isCustomTitleBarAndWindowOS,
minHeight: 300,
minWidth: 300,
show: false,

View File

@ -37,10 +37,12 @@ const createAPI = () => {
createAPI();
// When the window is completely loaded
ipcRenderer.on('page-load', () => {
// injects custom window title bar
const titleBar = React.createElement(WindowsTitleBar);
ReactDOM.render(titleBar, document.body.appendChild(document.createElement( 'div' )));
ipcRenderer.on('page-load', (_event, { isWindowsOS }) => {
if (isWindowsOS) {
// injects custom window title bar
const titleBar = React.createElement(WindowsTitleBar);
ReactDOM.render(titleBar, document.body.appendChild(document.createElement( 'div' )));
}
// injects snack bar
const snackBar = React.createElement(SnackBar);

View File

@ -22,27 +22,12 @@ export default class SnackBar extends React.Component<{}, IState> {
this.removeSnackBar = this.removeSnackBar.bind(this);
}
/**
* Renders the custom title bar
*/
public render(): JSX.Element | undefined {
const { show } = this.state;
return show ? (
<div className='SnackBar SnackBar-show'>
<span >{i18n.t('Press ', SNACKBAR_NAMESPACE)()}</span>
<span className='SnackBar-esc'>{i18n.t('esc', SNACKBAR_NAMESPACE)()}</span>
<span >{i18n.t(' to exit full screen', SNACKBAR_NAMESPACE)()}</span>
</div>
) : <div>&nbsp;</div>;
}
public componentDidMount(): void {
ipcRenderer.on('window-enter-full-screen', this.showSnackBar);
ipcRenderer.on('window-leave-full-screen', this.removeSnackBar);
}
public componentWillMount(): void {
public componentWillUnmount(): void {
ipcRenderer.removeListener('window-enter-full-screen', this.showSnackBar);
ipcRenderer.removeListener('window-leave-full-screen', this.removeSnackBar);
}
@ -66,4 +51,19 @@ export default class SnackBar extends React.Component<{}, IState> {
clearTimeout(this.snackBarTimer);
}
}
/**
* Renders the custom title bar
*/
public render(): JSX.Element | null {
const { show } = this.state;
return show ? (
<div className='SnackBar SnackBar-show'>
<span >{i18n.t('Press ', SNACKBAR_NAMESPACE)()}</span>
<span className='SnackBar-esc'>{i18n.t('esc', SNACKBAR_NAMESPACE)()}</span>
<span >{i18n.t(' to exit full screen', SNACKBAR_NAMESPACE)()}</span>
</div>
) : <div />;
}
}

View File

@ -1,5 +1,6 @@
import { ipcRenderer } from 'electron';
import { ipcRenderer, remote } from 'electron';
import { ICryptoLib } from '../browser/crypto-library-handler';
import {
apiCmds,
apiName,
@ -35,10 +36,24 @@ const throttledSetLocale = throttle((locale) => {
});
}, 1000);
let cryptoLib: ICryptoLib | null;
try {
cryptoLib = remote.require('../browser/crypto-library-handler.js').cryptoLibrary;
} catch (e) {
cryptoLib = null;
// tslint:disable-next-line
console.warn('Failed to initialize Crypto Lib. You\'ll need to include the Crypto library. Contact the developers for more details');
}
export class SSFApi {
/**
* allows JS to register a activity detector that can be used by electron main process.
* Native encryption and decryption.
*/
public CryptoLib: ICryptoLib | null = cryptoLib; // tslint:disable-line
/**
* Allows JS to register a activity detector that can be used by electron main process.
*
* @param {Object} period - minimum user idle time in millisecond
* @param {Object} activityDetection - function that can be called accepting
@ -72,7 +87,7 @@ export class SSFApi {
}
/**
* sets the count on the tray icon to the given number.
* Sets the count on the tray icon to the given number.
*
* @param {number} count count to be displayed
* note: count of 0 will remove the displayed count.