From b6fe6e9a2efee198d2173987100ef2f2e71f9c2a Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Date: Tue, 17 Oct 2023 10:55:06 +0530 Subject: [PATCH] perf(INJI-458): cache fixed result of custom keystore presence Each call over the RN bridge can take significant user-visible time, and since the result is used multiple times and cannot change for a device in runtime, it can be computed once stored for later use. Signed-off-by: Harsh Vardhan --- .talismanrc | 14 ++++++++++++- App.tsx | 4 ++-- machines/QrLoginMachine.ts | 6 +++--- .../EsignetMosipVCItemMachine.ts | 8 +++---- .../EsignetMosipVCItemMachine.typegen.ts | 2 +- .../ExistingMosipVCItemMachine.ts | 8 +++---- .../ExistingMosipVCItemMachine.typegen.ts | 1 + machines/issuersMachine.ts | 11 ++++------ machines/issuersMachine.typegen.ts | 6 ++++++ machines/settings.ts | 4 ++-- machines/store.ts | 8 +++---- shared/cryptoutil/cryptoUtil.ts | 21 ++++++++++++++----- shared/storage.ts | 4 ++-- shared/telemetry/TelemetryUtils.js | 4 ++-- 14 files changed, 64 insertions(+), 37 deletions(-) diff --git a/.talismanrc b/.talismanrc index 6d24056494..0bdc637091 100644 --- a/.talismanrc +++ b/.talismanrc @@ -25,6 +25,12 @@ fileignoreconfig: checksum: 18af825821bc95e1056050623b804a5a8e7435b9e3383916a5d63024eeba9553 - filename: screens/WelcomeScreenController.ts checksum: d8fe74404c80bf435459f4d20427a661fb622f0ee9f754345616abd346b98d14 +- filename: machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts + checksum: b02bd62a768257e835d035cab0b01480f35035a0dfa0ffd18bcadf282f1fab84 +- filename: machines/store.ts + checksum: 71a3e3972b176c7c3f3a6b4c59b92f0d1856241ac884b9e27ff5ab81b0a71bcd +- filename: shared/cryptoutil/cryptoUtil.ts + checksum: 439e238178fbd6a8c113127f408a5fae3b6075a84ba13c3c37a72a653d5733f5 - filename: shared/telemetry/TelemetryUtils.js checksum: 9a61cd59a3718adf1f14faf3024fec66a3295ef373878a878a28e5cb1287afaa - filename: machines/store.ts @@ -32,4 +38,10 @@ fileignoreconfig: - filename: shared/fileStorage.ts checksum: 07cb337dc1d5b0f0eef56270ac4f4f589260ee5e490183c024cf98a2aeafb139 - filename: shared/storage.ts - checksum: c8d874aa373bdf526bf59192139822f56915e702ef673bac4e0d7549b0fea3d0 \ No newline at end of file + checksum: c8d874aa373bdf526bf59192139822f56915e702ef673bac4e0d7549b0fea3d0 + checksum: f9711b617b986af9bb733a31373e49494667ef07b74988fbf09688cb50ca73bd +- filename: machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts + checksum: 3ac59db154914fec7770e14b0451f21f98a0fe8bb0b0243e800ae3aac8a2941d +- filename: machines/issuersMachine.ts + checksum: ae05c4d0d2a65e480156014b324a454269e00dba47414c00c757f81b4fc27cef +version: "" diff --git a/App.tsx b/App.tsx index 19c358e4db..c32f32f28e 100644 --- a/App.tsx +++ b/App.tsx @@ -18,7 +18,7 @@ import {Alert} from 'react-native'; import {configureTelemetry} from './shared/telemetry/TelemetryUtils'; import {MessageOverlay} from './components/MessageOverlay'; import SecureKeystore from 'react-native-secure-keystore'; -import {isCustomSecureKeystore} from './shared/cryptoutil/cryptoUtil'; +import {isCustomKeystore} from './shared/cryptoutil/cryptoUtil'; import i18n from './i18n'; import './shared/flipperConfig'; @@ -91,7 +91,7 @@ const AppInitialization: React.FC = () => { const {t} = useTranslation('common'); useEffect(() => { - if (isCustomSecureKeystore()) { + if (isCustomKeystore) { SecureKeystore.updatePopup( t('biometricPopup.title'), t('biometricPopup.description'), diff --git a/machines/QrLoginMachine.ts b/machines/QrLoginMachine.ts index 34cc66be6a..2a1529f771 100644 --- a/machines/QrLoginMachine.ts +++ b/machines/QrLoginMachine.ts @@ -12,7 +12,7 @@ import {MY_VCS_STORE_KEY, ESIGNET_BASE_URL} from '../shared/constants'; import {StoreEvents} from './store'; import {linkTransactionResponse, VC} from '../types/VC/ExistingMosipVC/vc'; import {request} from '../shared/request'; -import {getJwt, isCustomSecureKeystore} from '../shared/cryptoutil/cryptoUtil'; +import {getJwt, isCustomKeystore} from '../shared/cryptoutil/cryptoUtil'; import { getBindingCertificateConstant, getPrivateKey, @@ -363,7 +363,7 @@ export const qrLoginMachine = sendAuthenticate: async context => { let privateKey; const individualId = context.selectedVc.vcMetadata.id; - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { privateKey = await getPrivateKey( context.selectedVc.walletBindingResponse?.walletBindingId, ); @@ -397,7 +397,7 @@ export const qrLoginMachine = sendConsent: async context => { let privateKey; const individualId = context.selectedVc.vcMetadata.id; - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { privateKey = await getPrivateKey( context.selectedVc.walletBindingResponse?.walletBindingId, ); diff --git a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts index 52a6af0ccf..07e7eea152 100644 --- a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts +++ b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.ts @@ -5,7 +5,7 @@ import {VCMetadata} from '../../../shared/VCMetadata'; import {VC} from '../../../types/VC/ExistingMosipVC/vc'; import { generateKeys, - isCustomSecureKeystore, + isCustomKeystore, WalletBindingResponse, } from '../../../shared/cryptoutil/cryptoUtil'; import {log} from 'xstate/lib/actions'; @@ -632,7 +632,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( ), setPublicKey: assign({ publicKey: (context, event) => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return (event.data as KeyPair).public; } return event.data as string; @@ -788,7 +788,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( return walletResponse; }, generateKeyPair: async context => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return await generateKeys(); } const isBiometricsEnabled = SecureKeystore.hasBiometricsEnabled(); @@ -825,7 +825,7 @@ export const EsignetMosipVCItemMachine = model.createMachine( return vc != null; }, - isCustomSecureKeystore: () => isCustomSecureKeystore(), + isCustomSecureKeystore: () => isCustomKeystore, }, }, ); diff --git a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts index 75db6f0fab..dd13500fca 100644 --- a/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts +++ b/machines/VCItemMachine/EsignetMosipVCItem/EsignetMosipVCItemMachine.typegen.ts @@ -128,7 +128,7 @@ export interface Typegen0 { sendWalletBindingSuccess: | 'done.invoke.vc-item-openid4vci.kebabPopUp.addingWalletBindingId:invocation[0]' | 'done.invoke.vc-item-openid4vci.kebabPopUp.updatingPrivateKey:invocation[0]'; - setContext: 'STORE_RESPONSE'; + setContext: 'GET_VC_RESPONSE' | 'STORE_RESPONSE'; setGeneratedOn: 'GET_VC_RESPONSE'; setOtp: 'INPUT_OTP'; setPinCard: 'PIN_CARD'; diff --git a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts index ff239ccab5..3a70295514 100644 --- a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts +++ b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.ts @@ -15,7 +15,7 @@ import {verifyCredential} from '../../../shared/vcjs/verifyCredential'; import {log} from 'xstate/lib/actions'; import { generateKeys, - isCustomSecureKeystore, + isCustomKeystore, WalletBindingResponse, } from '../../../shared/cryptoutil/cryptoUtil'; import {KeyPair} from 'react-native-rsa-native'; @@ -839,7 +839,7 @@ export const ExistingMosipVCItemMachine = ), setPublicKey: assign({ publicKey: (context, event) => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return (event.data as KeyPair).public; } return event.data as string; @@ -1235,7 +1235,7 @@ export const ExistingMosipVCItemMachine = }, generateKeyPair: async context => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return await generateKeys(); } const isBiometricsEnabled = SecureKeystore.hasBiometricsEnabled(); @@ -1410,7 +1410,7 @@ export const ExistingMosipVCItemMachine = return context.isVerified; }, - isCustomSecureKeystore: () => isCustomSecureKeystore(), + isCustomSecureKeystore: () => isCustomKeystore, }, }, ); diff --git a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts index ba48f5e9ec..c7d56d4579 100644 --- a/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts +++ b/machines/VCItemMachine/ExistingMosipVCItem/ExistingMosipVCItemMachine.typegen.ts @@ -219,6 +219,7 @@ export interface Typegen0 { resetWalletBindingSuccess: 'DISMISS'; revokeVID: 'done.invoke.vc-item.requestingRevoke:invocation[0]'; sendTamperedVc: 'TAMPERED_VC'; + sendTelemetryEvents: 'STORE_RESPONSE'; sendVcUpdated: 'PIN_CARD'; sendWalletBindingSuccess: | 'done.invoke.vc-item.kebabPopUp.addingWalletBindingId:invocation[0]' diff --git a/machines/issuersMachine.ts b/machines/issuersMachine.ts index b6c9b49857..7498910f4c 100644 --- a/machines/issuersMachine.ts +++ b/machines/issuersMachine.ts @@ -5,10 +5,7 @@ import {MY_VCS_STORE_KEY} from '../shared/constants'; import {StoreEvents} from './store'; import {AppServices} from '../shared/GlobalContext'; import NetInfo from '@react-native-community/netinfo'; -import { - generateKeys, - isCustomSecureKeystore, -} from '../shared/cryptoutil/cryptoUtil'; +import {generateKeys, isCustomKeystore} from '../shared/cryptoutil/cryptoUtil'; import SecureKeystore from 'react-native-secure-keystore'; import {KeyPair} from 'react-native-rsa-native'; import {ActivityLogEvents} from './activityLog'; @@ -449,7 +446,7 @@ export const IssuersMachine = model.createMachine( }), setPublicKey: assign({ publicKey: (_, event) => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return (event.data as KeyPair).public; } return event.data as string; @@ -524,7 +521,7 @@ export const IssuersMachine = model.createMachine( return await authorize(context.selectedIssuer); }, generateKeyPair: async context => { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return await generateKeys(); } const isBiometricsEnabled = SecureKeystore.hasBiometricsEnabled(); @@ -569,7 +566,7 @@ export const IssuersMachine = model.createMachine( ); }, shouldFetchIssuersAgain: context => context.issuers.length === 0, - isCustomSecureKeystore: () => isCustomSecureKeystore(), + isCustomSecureKeystore: () => isCustomKeystore, }, }, ); diff --git a/machines/issuersMachine.typegen.ts b/machines/issuersMachine.typegen.ts index 9d9bb364e6..63aa7dc121 100644 --- a/machines/issuersMachine.typegen.ts +++ b/machines/issuersMachine.typegen.ts @@ -58,6 +58,10 @@ export interface Typegen0 { type: 'error.platform.issuersMachine.performAuthorization:invocation[0]'; data: unknown; }; + 'error.platform.issuersMachine.verifyingCredential:invocation[0]': { + type: 'error.platform.issuersMachine.verifyingCredential:invocation[0]'; + data: unknown; + }; 'xstate.init': {type: 'xstate.init'}; }; invokeSrcNameMap: { @@ -89,6 +93,8 @@ export interface Typegen0 { | 'error.platform.issuersMachine.downloadCredentials:invocation[0]' | 'error.platform.issuersMachine.downloadIssuerConfig:invocation[0]' | 'error.platform.issuersMachine.performAuthorization:invocation[0]'; + sendErrorEndEvent: 'error.platform.issuersMachine.verifyingCredential:invocation[0]'; + sendSuccessEndEvent: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]'; setCredentialWrapper: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]'; setError: | 'error.platform.issuersMachine.displayIssuers:invocation[0]' diff --git a/machines/settings.ts b/machines/settings.ts index 76cc855f9e..8f82875b4c 100644 --- a/machines/settings.ts +++ b/machines/settings.ts @@ -17,7 +17,7 @@ import getAllConfigurations, { import Storage from '../shared/storage'; import ShortUniqueId from 'short-unique-id'; import {__AppId} from '../shared/GlobalVariables'; -import {isCustomSecureKeystore} from '../shared/cryptoutil/cryptoUtil'; +import {isCustomKeystore} from '../shared/cryptoutil/cryptoUtil'; const model = createModel( { @@ -274,7 +274,7 @@ function generateAppId() { } function deviceSupportsHardwareKeystore() { - return isIOS() ? true : isCustomSecureKeystore(); + return isIOS() ? true : isCustomKeystore; } type State = StateFrom; diff --git a/machines/store.ts b/machines/store.ts index 7ca566bac9..61f10a01a1 100644 --- a/machines/store.ts +++ b/machines/store.ts @@ -22,7 +22,7 @@ import { ENCRYPTION_ID, encryptJson, HMAC_ALIAS, - isCustomSecureKeystore, + isCustomKeystore, } from '../shared/cryptoutil/cryptoUtil'; import {VCMetadata} from '../shared/VCMetadata'; import FileStorage, {getFilePath} from '../shared/fileStorage'; @@ -82,7 +82,7 @@ export const storeMachine = events: {} as EventFrom, }, id: 'store', - initial: !isCustomSecureKeystore() + initial: !isCustomKeystore ? 'gettingEncryptionKey' : 'checkEncryptionKey', states: { @@ -441,7 +441,7 @@ export const storeMachine = generateEncryptionKey: () => async callback => { const randomBytes = await generateSecureRandom(32); const randomBytesString = binaryToBase64(randomBytes); - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { const hasSetCredentials = await Keychain.setGenericPassword( ENCRYPTION_ID, randomBytesString, @@ -475,7 +475,7 @@ export const storeMachine = }, guards: { - isCustomSecureKeystore: () => isCustomSecureKeystore(), + isCustomSecureKeystore: () => isCustomKeystore, }, }, ); diff --git a/shared/cryptoutil/cryptoUtil.ts b/shared/cryptoutil/cryptoUtil.ts index a5019c1e32..423089ffb0 100644 --- a/shared/cryptoutil/cryptoUtil.ts +++ b/shared/cryptoutil/cryptoUtil.ts @@ -18,6 +18,11 @@ export function generateKeys(): Promise { return Promise.resolve(RSA.generateKeys(4096)); } +/** + * isCustomKeystore is a cached check of existence of a hardware keystore. + */ +export const isCustomKeystore = isCustomSecureKeystore(); + export async function getJwt( privateKey: string, individualId: string, @@ -70,7 +75,7 @@ export async function createSignature( ) { let signature64; - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { const key = forge.pki.privateKeyFromPem(privateKey); const md = forge.md.sha256.create(); md.update(preHash, 'utf8'); @@ -98,7 +103,13 @@ export function encodeB64(str: string) { return replaceCharactersInB64(encodedB64); } -export function isCustomSecureKeystore() { +/** + * 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; } @@ -112,7 +123,7 @@ export interface WalletBindingResponse { export async function clear() { try { console.log('clearing entire storage'); - if (isCustomSecureKeystore()) { + if (isCustomKeystore) { SecureKeystore.clearKeys(); } await Storage.clear(); @@ -131,7 +142,7 @@ export async function encryptJson( return JSON.stringify(data); } - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return CryptoJS.AES.encrypt(data, encryptionKey).toString(); } return await SecureKeystore.encryptData(ENCRYPTION_ID, data); @@ -147,7 +158,7 @@ export async function decryptJson( return JSON.parse(encryptedData); } - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return CryptoJS.AES.decrypt(encryptedData, encryptionKey).toString( CryptoJS.enc.Utf8, ); diff --git a/shared/storage.ts b/shared/storage.ts index 07ddfe161c..e784623edf 100644 --- a/shared/storage.ts +++ b/shared/storage.ts @@ -11,7 +11,7 @@ import { decryptJson, encryptJson, HMAC_ALIAS, - isCustomSecureKeystore, + isCustomKeystore, } from './cryptoutil/cryptoUtil'; import {VCMetadata} from './VCMetadata'; import {ENOENT, getItem} from '../machines/store'; @@ -30,7 +30,7 @@ async function generateHmac( encryptionKey: string, data: string, ): Promise { - if (!isCustomSecureKeystore()) { + if (!isCustomKeystore) { return CryptoJS.HmacSHA256(encryptionKey, data).toString(); } return await SecureKeystore.generateHmacSha(HMAC_ALIAS, data); diff --git a/shared/telemetry/TelemetryUtils.js b/shared/telemetry/TelemetryUtils.js index 333bb79b1a..3d80134d28 100644 --- a/shared/telemetry/TelemetryUtils.js +++ b/shared/telemetry/TelemetryUtils.js @@ -11,7 +11,7 @@ import { } from '../GlobalVariables'; import {OBSRV_HOST} from 'react-native-dotenv'; import DeviceInfo from 'react-native-device-info'; -import {isCustomSecureKeystore} from '../cryptoutil/cryptoUtil'; +import {isCustomKeystore} from '../cryptoutil/cryptoUtil'; import * as RNLocalize from 'react-native-localize'; export function sendStartEvent(data) { @@ -124,7 +124,7 @@ export function getAppInfoEventData() { osName: DeviceInfo.getSystemName(), osVersion: DeviceInfo.getSystemVersion(), osApiLevel: Platform.Version.toString(), - isHardwareKeystoreSupported: isCustomSecureKeystore(), + isHardwareKeystoreSupported: isCustomKeystore, dateTime: new Date().getTime(), zone: RNLocalize.getTimeZone(), offset: new Date().getTimezoneOffset() * 60 * 1000,