mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-09 21:48:04 -05:00
* refactor(INJI-449): replace crypo-js with node-forge crypto-js has vulneraribitiles prior to version 4.2.0 for encryption / decryption & 4.x.x version is not compatible with our react native project For this reason we had to move to different library for encryption / decryption Co-authored-by: Sreenadh S <32409698+sree96@users.noreply.github.com> Signed-off-by: Kiruthika Jeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> * fix(INJI-449): secure-keystore warning popup shown on reload of app settings key which was stored in storage was not loaded into settings machine context correctly, which caused the bug - on reload settings related flows was falling back to initial setting. Co-authored-by: Sreenadh S <32409698+sree96@users.noreply.github.com> Signed-off-by: Kiruthika Jeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> * refactor(INJI-449): gitignore automation test results Signed-off-by: Kiruthika Jeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> * refactor(INJI-449): simplify usage of methods in node-forge Signed-off-by: Kiruthika Jeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> --------- Signed-off-by: Kiruthika Jeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com> Co-authored-by: Sreenadh S <32409698+sree96@users.noreply.github.com>
193 lines
5.8 KiB
TypeScript
193 lines
5.8 KiB
TypeScript
import {KeyPair, RSA} from 'react-native-rsa-native';
|
|
import forge from 'node-forge';
|
|
import {BIOMETRIC_CANCELLED, DEBUG_MODE_ENABLED, isIOS} from '../constants';
|
|
import SecureKeystore from 'react-native-secure-keystore';
|
|
import {BiometricCancellationError} from '../error/BiometricCancellationError';
|
|
import {EncryptedOutput} from './encryptedOutput';
|
|
|
|
// 5min
|
|
export const AUTH_TIMEOUT = 5 * 60;
|
|
export const ENCRYPTION_ID = 'c7c22a6c-9759-4605-ac88-46f4041d863d';
|
|
export const HMAC_ALIAS = '860cc320-4248-11ee-be56-0242ac120002';
|
|
//This key is used to request biometric at app open to reset auth timeout which is used by encryption key
|
|
export const DUMMY_KEY_FOR_BIOMETRIC_ALIAS =
|
|
'9a6cfc0e-4248-11ee-be56-0242ac120002';
|
|
|
|
export function generateKeys(): Promise<KeyPair> {
|
|
return Promise.resolve(RSA.generateKeys(4096));
|
|
}
|
|
|
|
/**
|
|
* isCustomKeystore is a cached check of existence of a hardware keystore.
|
|
*/
|
|
export const isHardwareKeystoreExists = isCustomSecureKeystore();
|
|
|
|
export async function getJWT(
|
|
header: object,
|
|
payLoad: object,
|
|
individualId: string,
|
|
privateKey: string,
|
|
) {
|
|
try {
|
|
const header64 = encodeB64(JSON.stringify(header));
|
|
const payLoad64 = encodeB64(JSON.stringify(payLoad));
|
|
const preHash = header64 + '.' + payLoad64;
|
|
const signature64 = await createSignature(
|
|
privateKey,
|
|
preHash,
|
|
individualId,
|
|
);
|
|
return header64 + '.' + payLoad64 + '.' + signature64;
|
|
} catch (e) {
|
|
console.log('Exception Occurred While Constructing JWT ', e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export async function createSignature(
|
|
privateKey: string,
|
|
preHash: string,
|
|
individualId: string,
|
|
) {
|
|
let signature64;
|
|
|
|
if (!isHardwareKeystoreExists) {
|
|
const key = forge.pki.privateKeyFromPem(privateKey);
|
|
const md = forge.md.sha256.create();
|
|
md.update(preHash, 'utf8');
|
|
|
|
const signature = key.sign(md);
|
|
return encodeB64(signature);
|
|
} else {
|
|
try {
|
|
signature64 = await SecureKeystore.sign(individualId, preHash);
|
|
} catch (error) {
|
|
console.error('Error in creating signature:', error);
|
|
if (error.toString().includes(BIOMETRIC_CANCELLED)) {
|
|
throw new BiometricCancellationError(error.toString());
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return replaceCharactersInB64(signature64);
|
|
}
|
|
}
|
|
|
|
function replaceCharactersInB64(encodedB64: string) {
|
|
return encodedB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
}
|
|
|
|
export function encodeB64(str: string) {
|
|
const encodedB64 = forge.util.encode64(str);
|
|
return replaceCharactersInB64(encodedB64);
|
|
}
|
|
|
|
/**
|
|
* DO NOT USE DIRECTLY and/or REPEATEDLY in application lifeycle.
|
|
*
|
|
* This can make a call to the Android native layer hence taking up more time,
|
|
* use the isCustomKeystore constant in the app lifeycle instead.
|
|
*/
|
|
function isCustomSecureKeystore() {
|
|
return !isIOS() ? SecureKeystore.deviceSupportsHardware() : false;
|
|
}
|
|
|
|
export interface WalletBindingResponse {
|
|
walletBindingId: string;
|
|
keyId: string;
|
|
thumbprint: string;
|
|
expireDateTime: string;
|
|
}
|
|
|
|
export async function encryptJson(
|
|
encryptionKey: string,
|
|
data: string,
|
|
): Promise<string> {
|
|
try {
|
|
// Disable Encryption in debug mode
|
|
if (DEBUG_MODE_ENABLED && __DEV__) {
|
|
return JSON.stringify(data);
|
|
}
|
|
|
|
if (!isHardwareKeystoreExists) {
|
|
return encryptWithForge(data, encryptionKey).toString();
|
|
}
|
|
return await SecureKeystore.encryptData(ENCRYPTION_ID, data);
|
|
} catch (error) {
|
|
console.error('error while encrypting:', error);
|
|
if (error.toString().includes(BIOMETRIC_CANCELLED)) {
|
|
throw new BiometricCancellationError(error.toString());
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function decryptJson(
|
|
encryptionKey: string,
|
|
encryptedData: string,
|
|
): Promise<string> {
|
|
try {
|
|
if (encryptedData === null || encryptedData === undefined) {
|
|
// to avoid crash in case of null or undefined
|
|
return '';
|
|
}
|
|
// Disable Encryption in debug mode
|
|
if (DEBUG_MODE_ENABLED && __DEV__) {
|
|
return JSON.parse(encryptedData);
|
|
}
|
|
|
|
if (!isHardwareKeystoreExists) {
|
|
return decryptWithForge(encryptedData, encryptionKey);
|
|
}
|
|
|
|
return await SecureKeystore.decryptData(ENCRYPTION_ID, encryptedData);
|
|
} catch (e) {
|
|
console.error('error decryptJson:', e);
|
|
|
|
if (e.toString().includes(BIOMETRIC_CANCELLED)) {
|
|
throw new BiometricCancellationError(e.toString());
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function encryptWithForge(text: string, key: string): EncryptedOutput {
|
|
//iv - initialization vector
|
|
const iv = forge.random.getBytesSync(16);
|
|
const salt = forge.random.getBytesSync(128);
|
|
const encryptionKey = forge.pkcs5.pbkdf2(key, salt, 4, 16);
|
|
const cipher = forge.cipher.createCipher('AES-CBC', encryptionKey);
|
|
cipher.start({iv: iv});
|
|
cipher.update(forge.util.createBuffer(text, 'utf8'));
|
|
cipher.finish();
|
|
var cipherText = forge.util.encode64(cipher.output.getBytes());
|
|
const encryptedData = new EncryptedOutput(
|
|
cipherText,
|
|
forge.util.encode64(iv),
|
|
forge.util.encode64(salt),
|
|
);
|
|
return encryptedData;
|
|
}
|
|
|
|
function decryptWithForge(encryptedData: string, key: string): string {
|
|
const encryptedOutput = EncryptedOutput.fromString(encryptedData);
|
|
const salt = forge.util.decode64(encryptedOutput.salt);
|
|
const encryptionKey = forge.pkcs5.pbkdf2(key, salt, 4, 16);
|
|
const decipher = forge.cipher.createDecipher('AES-CBC', encryptionKey);
|
|
decipher.start({iv: forge.util.decode64(encryptedOutput.iv)});
|
|
decipher.update(
|
|
forge.util.createBuffer(forge.util.decode64(encryptedOutput.encryptedData)),
|
|
);
|
|
decipher.finish();
|
|
const decryptedData = decipher.output.toString();
|
|
return decryptedData;
|
|
}
|
|
|
|
export function hmacSHA(encryptionKey: string, data: string) {
|
|
const hmac = forge.hmac.create();
|
|
hmac.start('sha256', encryptionKey);
|
|
hmac.update(data);
|
|
const resultBytes = hmac.digest().getBytes().toString();
|
|
return resultBytes;
|
|
}
|