[INJIMOB-1078] Merge remote-tracking branch 'upstream/develop' into injimob-1078-mso-mdoc-vc-support-copy

Signed-off-by: KiruthikaJeyashankar <81218987+KiruthikaJeyashankar@users.noreply.github.com>
This commit is contained in:
KiruthikaJeyashankar
2024-09-10 14:53:19 +05:30
55 changed files with 1842 additions and 709 deletions

View File

@@ -35,6 +35,14 @@ on:
options:
- orange
- purple
type:
description: 'Apk type'
required: true
default: 'release'
type: choice
options:
- release
- debug
jobs:
build-android:
@@ -45,6 +53,7 @@ jobs:
ESIGNET_HOST: ${{ inputs.esignetBackendServiceUrl }}
APPLICATION_THEME: ${{ inputs.theme }}
ALLOW_ENV_EDIT: ${{ inputs.allow_env_edit }}
APPLICATION_TYPE: ${{ inputs.type }}
KEYSTORE_ALIAS: androidbuildkey
KEYSTORE_PASSWORD: 'password'
SERVICE_LOCATION: '.'
@@ -53,4 +62,4 @@ jobs:
SCRIPT_NAME: './android-build.sh'
UPLOAD_TO_ACTIONS: 'true'
ANDROID_ARTIFACT_NAME: ${{ inputs.buildName }}
ANDROID_ARTIFACT_PATH: "android/app/build/outputs/apk/residentapp/release/Inji_universal.apk"
ANDROID_ARTIFACT_PATH: "android/app/build/outputs/apk/residentapp/${{ inputs.type }}/Inji_universal.apk"

View File

@@ -41,11 +41,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v3.1.0
- uses: actions/setup-node@v3
with:
node-version: '18.x'
node-version: '18.x'
- name: Cache npm dependencies
uses: actions/cache@v3.3.1
with:
@@ -57,7 +56,9 @@ jobs:
- name: Install npm dependencies
run: |
npm ci
- name: Check Java Version
run: |
java -version
- name: Generate Android keystore
run: |
echo "$ANDROID_KEYSTORE_FILE" > release.keystore.b64
@@ -84,7 +85,11 @@ jobs:
name: residentapp
path: android/app/build/outputs/apk/residentapp/release/Inji_universal.apk
retention-days: 10
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- name: Run UI Automation Tests on BrowserStack
run: |
chmod +x injitest/automation_trigger.sh
@@ -123,7 +128,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Cache npm dependencies
uses: actions/cache@v3.3.1
with:
@@ -161,6 +166,12 @@ jobs:
name: residentapp
path: ios/Inji.ipa
retention-days: 10
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- name: Run UI Automation Tests on BrowserStack
run: |

View File

@@ -265,9 +265,11 @@ android {
dependencies {
implementation("com.facebook.react:react-android")
implementation 'com.facebook.soloader:soloader:0.10.1+'
implementation("io.mosip:pixelpass:0.2.0-SNAPSHOT")
implementation("io.mosip:secure-keystore:0.2.0-SNAPSHOT")
implementation("io.mosip:tuvali:0.5.0-SNAPSHOT")
implementation("io.mosip:pixelpass:0.2.1")
implementation("io.mosip:secure-keystore:0.3.0-SNAPSHOT")
implementation("io.mosip:tuvali:0.5.1")
implementation("io.mosip:inji-vci-client:0.3.3-SNAPSHOT")
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
@@ -296,7 +298,6 @@ dependencies {
}
compileOnly project(':react-native-android-location-services-dialog-box')
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation("io.mosip:inji-vci-client:0.3.3-SNAPSHOT")
// {
// exclude group: "androidx.annotation", module: "annotation"
// }

View File

@@ -1,46 +1,87 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.mosip.residentapp" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"/>
</intent>
</queries>
<application tools:replace="usesCleartextTraffic" android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:usesCleartextTraffic="false">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="48.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@anonymous/inji"/>
<activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="io.mosip.residentapp"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.mosip.residentapp">
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="28" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application
android:name=".MainApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="false"
tools:replace="usesCleartextTraffic">
<meta-data
android:name="expo.modules.updates.ENABLED"
android:value="true" />
<meta-data
android:name="expo.modules.updates.EXPO_SDK_VERSION"
android:value="48.0.0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH"
android:value="ALWAYS" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS"
android:value="0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATE_URL"
android:value="https://exp.host/@anonymous/inji" />
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|locale|layoutDirection"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="wla-auth"
android:scheme="io.mosip.residentapp.inji" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -25,6 +25,7 @@ public class InjiPackage implements ReactPackage {
modules.add(new RNVersionModule());
modules.add(new RNWalletModule(new RNEventEmitter(reactApplicationContext), new Wallet(reactApplicationContext), reactApplicationContext));
modules.add(new RNVerifierModule(new RNEventEmitter(reactApplicationContext), new Verifier(reactApplicationContext), reactApplicationContext));
modules.add(new RNQrLoginIntentModule(reactApplicationContext));
return modules;
}

View File

@@ -0,0 +1,19 @@
package io.mosip.residentapp;
public class IntentData {
private String qrData = "";
private static IntentData intentData;
public static IntentData getInstance() {
if(intentData == null)
intentData = new IntentData();
return intentData;
}
public String getQrData() {
return qrData;
}
public void setQrData(String qrData) {
this.qrData = qrData;
}
}

View File

@@ -3,20 +3,19 @@ import expo.modules.ReactActivityDelegateWrapper;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
import expo.modules.ReactActivityDelegateWrapper;
import java.util.Objects;
/**
* IMPORTANT NOTE: The Android permission flow here works
@@ -43,6 +42,22 @@ public class MainActivity extends ReactActivity {
// This is required for expo-splash-screen.
setTheme(R.style.AppTheme);
super.onCreate(null);
Intent intent = getIntent();
readAndSetQRLoginIntentData(intent);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
readAndSetQRLoginIntentData(intent);
}
private void readAndSetQRLoginIntentData(Intent intent){
Uri data = intent.getData();
if(data != null && Objects.equals(data.getScheme(), "io.mosip.residentapp.inji")){
IntentData intentData = IntentData.getInstance();
intentData.setQrData(String.valueOf(data));
}
}
/**

View File

@@ -0,0 +1,38 @@
package io.mosip.residentapp;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class RNQrLoginIntentModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "QrLoginIntent";
}
RNQrLoginIntentModule(ReactApplicationContext context) {
super(context);
}
@ReactMethod
public void isQrLoginByDeepLink(Promise promise) {
try {
IntentData intentData = IntentData.getInstance();
promise.resolve(intentData.getQrData());
} catch (Exception e) {
promise.reject("E_UNKNOWN", e.getMessage());
}
}
@ReactMethod
public void resetQRLoginDeepLinkData(){
IntentData intentData = IntentData.getInstance();
intentData.setQrData("");
}
}

View File

@@ -4,6 +4,7 @@ import com.reactnativesecurekeystore.SecureKeystoreImpl;
import com.reactnativesecurekeystore.KeyGeneratorImpl;
import com.reactnativesecurekeystore.CipherBoxImpl;
import com.reactnativesecurekeystore.DeviceCapability;
import com.reactnativesecurekeystore.PreferencesImpl;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
@@ -13,6 +14,10 @@ import com.reactnativesecurekeystore.common.Util;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.Arguments;
import java.util.ArrayList;
import java.util.List;
public class RNSecureKeystoreModule extends ReactContextBaseJavaModule {
private final KeyGeneratorImpl keyGenerator = new KeyGeneratorImpl();
@@ -21,16 +26,17 @@ public class RNSecureKeystoreModule extends ReactContextBaseJavaModule {
private final SecureKeystoreImpl keystore;
private final DeviceCapability deviceCapability;
private final String logTag;
private final PreferencesImpl preferences;
public RNSecureKeystoreModule(ReactApplicationContext reactContext) {
super(reactContext);
this.biometrics = new Biometrics(reactContext);
this.keystore = new SecureKeystoreImpl(keyGenerator, cipherBox, biometrics);
this.biometrics = new Biometrics();
this.preferences = new PreferencesImpl(reactContext);
this.keystore = new SecureKeystoreImpl(keyGenerator, cipherBox, biometrics, preferences);
this.deviceCapability = new DeviceCapability(keystore, keyGenerator, biometrics);
this.logTag = Util.Companion.getLogTag(getClass().getSimpleName());
}
@Override
public String getName() {
return "RNSecureKeystoreModule";
@@ -57,8 +63,8 @@ public class RNSecureKeystoreModule extends ReactContextBaseJavaModule {
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String generateKeyPair(String alias, boolean isAuthRequired, Integer authTimeout) {
return keystore.generateKeyPair(alias, isAuthRequired, authTimeout);
public String generateKeyPair(String type, String alias, boolean isAuthRequired, Integer authTimeout) {
return keystore.generateKeyPair(type, alias, isAuthRequired, authTimeout);
}
@ReactMethod(isBlockingSynchronousMethod = true)
@@ -69,103 +75,109 @@ public class RNSecureKeystoreModule extends ReactContextBaseJavaModule {
@ReactMethod
public void encryptData(String alias, String data, Promise promise) {
Function1<String, Unit> successLambda = new Function1<String, Unit>() {
@Override
public Unit invoke(String encryptedText) {
promise.resolve(encryptedText);
return Unit.INSTANCE;
}
@Override
public Unit invoke(String encryptedText) {
promise.resolve(encryptedText);
return Unit.INSTANCE;
}
};
Function2<Integer, String, Unit> failureLambda = new Function2<Integer, String, Unit>() {
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(),message);
return Unit.INSTANCE;
}
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(), message);
return Unit.INSTANCE;
}
};
keystore.encryptData(
alias,
data,
successLambda,
failureLambda
);
alias,
data,
successLambda,
failureLambda,
getCurrentActivity());
}
@ReactMethod
public void decryptData(String alias, String encryptedText,Promise promise) {
Function1<String, Unit> successLambda = new Function1<String, Unit>() {
@Override
public Unit invoke(String data) {
promise.resolve(data);
return Unit.INSTANCE;
}
};
public void decryptData(String alias, String encryptedText, Promise promise) {
Function1<String, Unit> successLambda = new Function1<String, Unit>() {
@Override
public Unit invoke(String data) {
promise.resolve(data);
return Unit.INSTANCE;
}
};
Function2<Integer, String, Unit> failureLambda = new Function2<Integer, String, Unit>() {
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(),message);
return Unit.INSTANCE;
}
};
keystore.decryptData(alias, encryptedText, successLambda, failureLambda);
}
Function2<Integer, String, Unit> failureLambda = new Function2<Integer, String, Unit>() {
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(), message);
return Unit.INSTANCE;
}
};
keystore.decryptData(alias, encryptedText, successLambda, failureLambda, getCurrentActivity());
}
@ReactMethod
public void generateHmacSha(String alias, String data, Promise promise) {
Function1<String, Unit> successLambda = new Function1<String, Unit>() {
@Override
public Unit invoke(String sha) {
promise.resolve(sha);
return Unit.INSTANCE;
}
@Override
public Unit invoke(String sha) {
promise.resolve(sha);
return Unit.INSTANCE;
}
};
Function2<Integer, String, Unit> failureLambda = new Function2<Integer, String, Unit>() {
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(),message);
return Unit.INSTANCE;
}
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(), message);
return Unit.INSTANCE;
}
};
keystore.generateHmacSha(
alias,
data,
successLambda,
failureLambda
);
alias,
data,
successLambda,
failureLambda);
}
@ReactMethod
public void sign(String alias, String data, Promise promise) {
public void sign(String signAlgorithm, String alias, String data, Promise promise) {
String algorithm = "";
if ("RS256".equals(signAlgorithm))
algorithm = "SHA256withRSA";
else if ("ES256".equals(signAlgorithm))
algorithm = "SHA256withECDSA";
else {
promise.reject("", "Unsupported algorithm for signing");
}
Function1<String, Unit> successLambda = new Function1<String, Unit>() {
@Override
public Unit invoke(String signature) {
promise.resolve(signature);
return Unit.INSTANCE;
}
@Override
public Unit invoke(String signature) {
promise.resolve(signature);
return Unit.INSTANCE;
}
};
Function2<Integer, String, Unit> failureLambda = new Function2<Integer, String, Unit>() {
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(),message);
return Unit.INSTANCE;
}
@Override
public Unit invoke(Integer code, String message) {
promise.reject(code.toString(), message);
return Unit.INSTANCE;
}
};
keystore.sign(
alias,
data,
successLambda,
failureLambda
);
algorithm,
alias,
data,
successLambda,
failureLambda,
getCurrentActivity());
}
@ReactMethod(isBlockingSynchronousMethod = true)
@@ -175,6 +187,60 @@ public class RNSecureKeystoreModule extends ReactContextBaseJavaModule {
@ReactMethod(isBlockingSynchronousMethod = true)
public boolean hasBiometricsEnabled() {
return deviceCapability.hasBiometricsEnabled();
return deviceCapability.hasBiometricsEnabled(getCurrentActivity());
}
@ReactMethod
public void retrieveKey(String alias, Promise promise) {
try {
promise.resolve(keystore.retrieveKey(alias));
} catch (Exception e) {
promise.reject(e.getMessage());
}
}
@ReactMethod
public void storeGenericKey(String publicKey, String privateKey, String account, Promise promise) {
try {
keystore.storeGenericKey(publicKey, privateKey, account);
promise.resolve("Success");
} catch (Exception e) {
promise.reject(e.getMessage());
}
}
@ReactMethod
public void storeData(String key, String value, Promise promise) {
try {
keystore.storeGenericKey(value, "", key);
promise.resolve("success");
} catch (Exception e) {
promise.reject("Error occurred storing data");
}
}
@ReactMethod
public void retrieveGenericKey(String account, Promise promise) {
retrieveDataAndResolve(account, promise);
}
@ReactMethod
public void getData(String key, Promise promise) {
retrieveDataAndResolve(key, promise);
}
private void retrieveDataAndResolve(String key, Promise promise) {
try {
List<String> dataList = keystore.retrieveGenericKey(key);
WritableArray writableArray = Arguments.createArray();
for (String data : dataList) {
writableArray.pushString(data);
}
promise.resolve(writableArray);
} catch (Exception e) {
promise.reject(e.getMessage());
}
}
}

View File

@@ -4,7 +4,9 @@ MIMOTO_HOST = ENV["MIMOTO_HOST"]
ESIGNET_HOST = ENV["ESIGNET_HOST"]
APPLICATION_THEME = ENV["APPLICATION_THEME"]
RELEASE_KEYSTORE_ALIAS = ENV["RELEASE_KEYSTORE_ALIAS"]
DEBUG_KEYSTORE_ALIAS = ENV["DEBUG_KEYSTORE_ALIAS"]
RELEASE_KEYSTORE_PASSWORD = ENV["RELEASE_KEYSTORE_PASSWORD"]
DEBUG_KEYSTORE_PASSWORD = ENV["DEBUG_KEYSTORE_PASSWORD"]
PLAY_CONSOLE_RELEASE_DESCRIPTION = ENV["BUILD_DESCRIPTION"]
SLACK_URL = ENV["SLACK_WEBHOOK_URL"]
CREDENTIAL_REGISTRY_EDIT = ENV["CREDENTIAL_REGISTRY_EDIT"]
@@ -43,11 +45,16 @@ def generate_app_name()
end
end
desc "Verify Build for Android"
lane :android_build do
desc "Verify Release Build for Android"
lane :android_build_release do
gradle(task: "assembleResidentappRelease")
end
desc "Verify Debug Build for Android"
lane :android_build_debug do
gradle(task: "assembleResidentappDebug")
end
desc "Deploy an Internal testing version to the Google Play"
lane :android_build_internal do

View File

@@ -1,4 +1,7 @@
#As react-native/location npm package is old, We need to run this to map androidX source code
APPLICATION_TYPE=$1
(cd ../../ && npx jetify)
cd ..
@@ -7,4 +10,8 @@ yes | sudo gem install bundler
yes | sudo fastlane install_plugins
bundle exec fastlane android_build
if [ "$APPLICATION_TYPE" == "debug" ]; then
bundle exec fastlane android_build_debug
else
bundle exec fastlane android_build_release
fi

View File

@@ -6,7 +6,6 @@ import {useTranslation} from 'react-i18next';
import i18next, {i18n} from 'i18next';
import RNRestart from 'react-native-restart';
import {setItem} from '../machines/store';
import Keychain from 'react-native-keychain';
export const LanguageSelector: React.FC<LanguageSelectorProps> = props => {
const {i18n} = useTranslation();
@@ -30,8 +29,7 @@ export const LanguageSelector: React.FC<LanguageSelectorProps> = props => {
export const changeLanguage = async (i18n: i18n, language: string) => {
if (language !== i18n.language) {
await i18n.changeLanguage(language).then(async () => {
const existingCredentials = await Keychain.getGenericPassword();
await setItem('language', i18n.language, existingCredentials.password);
await setItem('language', i18n.language, '');
const isRTL = i18next.dir(language) === 'rtl';
if (isRTL !== I18nManager.isRTL) {
try {

View File

@@ -9,7 +9,6 @@ import testIDProps from '../shared/commonUtil';
import {SvgImage} from './ui/svg';
import {NativeModules} from 'react-native';
import {VerifiableCredential} from '../machines/VerifiableCredential/VCMetaMachine/vc';
import RNSecureKeyStore, {ACCESSIBLE} from 'react-native-secure-key-store';
import {DEFAULT_ECL, MAX_QR_DATA_LENGTH} from '../shared/constants';
import {VCMetadata} from '../shared/VCMetadata';
import {shareImageToAllSupportedApps} from '../shared/sharing/imageUtils';
@@ -21,19 +20,23 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = props => {
const [qrString, setQrString] = useState('');
const [qrError, setQrError] = useState(false);
const base64ImageType = 'data:image/png;base64,';
const {RNSecureKeystoreModule} = NativeModules;
async function getQRData(): Promise<string> {
let qrData: string;
try {
qrData = await RNSecureKeyStore.get(props.meta.id);
const keyData = await RNSecureKeystoreModule.getData(props.meta.id);
if (keyData[1] && keyData.length > 0) {
qrData = keyData[1];
} else {
throw new Error('No key data found');
}
} catch {
qrData = await RNPixelpassModule.generateQRData(
JSON.stringify(props.verifiableCredential),
'',
);
await RNSecureKeyStore.set(props.meta.id, qrData, {
accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY,
});
await RNSecureKeystoreModule.storeData(props.meta.id, qrData);
}
return qrData;
}

View File

@@ -11,7 +11,6 @@ import ta from './locales/tam.json';
import {iso6393To1} from 'iso-639-3';
import Keychain from 'react-native-keychain';
import {getItem} from './machines/store';
import {LocalizedField} from './machines/VerifiableCredential/VCMetaMachine/vc';
@@ -38,12 +37,7 @@ i18next
supportedLngs: Object.keys(SUPPORTED_LANGUAGES),
})
.then(async () => {
const existingCredentials = await Keychain.getGenericPassword();
const language = await getItem(
'language',
null,
existingCredentials.password,
);
const language = await getItem('language', null, '');
if (language !== i18next.language) {
i18next.changeLanguage(language);

View File

@@ -208,4 +208,4 @@
</plugin>
</plugins>
</build>
</project>
</project>

View File

@@ -23,6 +23,9 @@
9C4850502C3E59B5002ECBD5 /* RNEventMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */; };
9C4850512C3E59B5002ECBD5 /* RNVersionModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */; };
9C4850532C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */; };
9C7CDF3E2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */; };
9C7CDF432C7CC13500243A9A /* RNSecureKeystoreModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */; };
9C7CDF492C89802C00243A9A /* securekeystore in Frameworks */ = {isa = PBXBuildFile; productRef = 9C7CDF482C89802C00243A9A /* securekeystore */; };
9CE34B1F2BFE03E4001AF414 /* pixelpass in Frameworks */ = {isa = PBXBuildFile; productRef = 9CE34B1E2BFE03E4001AF414 /* pixelpass */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
@@ -70,6 +73,8 @@
9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventMapper.swift; path = Inji/RNEventMapper.swift; sourceTree = "<group>"; };
9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNVersionModule.m; path = Inji/RNVersionModule.m; sourceTree = "<group>"; };
9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventEmitterProtocol.swift; path = Inji/RNEventEmitterProtocol.swift; sourceTree = "<group>"; };
9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSecureKeystoreModule.swift; sourceTree = "<group>"; };
9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSecureKeystoreModule.m; sourceTree = "<group>"; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Inji/SplashScreen.storyboard; sourceTree = "<group>"; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
D98B96A488E54CBDB286B26F /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Inji/noop-file.swift"; sourceTree = "<group>"; };
@@ -106,6 +111,7 @@
E8AF2B9D2C0D93E800E775F6 /* VCIClient in Frameworks */,
9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */,
9CE34B1F2BFE03E4001AF414 /* pixelpass in Frameworks */,
9C7CDF492C89802C00243A9A /* securekeystore in Frameworks */,
96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -124,10 +130,12 @@
9C4850472C3E59B5002ECBD5 /* RNEventEmitter.swift */,
9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */,
9C4850492C3E59B5002ECBD5 /* RNEventMapper.swift */,
9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */,
9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */,
9C4850482C3E59B5002ECBD5 /* RNVersionModule.swift */,
9C4850462C3E59B5002ECBD5 /* RNWalletModule.m */,
9C4850442C3E59B5002ECBD5 /* RNWalletModule.swift */,
9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */,
9C0E86BA2BEE36C300E9F9F6 /* RNPixelpassModule.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
@@ -267,6 +275,7 @@
9CE34B1E2BFE03E4001AF414 /* pixelpass */,
E8AF2B9C2C0D93E800E775F6 /* VCIClient */,
9C4850422C3E5873002ECBD5 /* ios-tuvali-library */,
9C7CDF482C89802C00243A9A /* securekeystore */,
);
productName = Inji;
productReference = 13B07F961A680F5B00A75B9A /* Inji.app */;
@@ -298,6 +307,7 @@
9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */,
E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */,
9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */,
);
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
@@ -484,8 +494,10 @@
9C4850532C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
9C4850502C3E59B5002ECBD5 /* RNEventMapper.swift in Sources */,
9C7CDF432C7CC13500243A9A /* RNSecureKeystoreModule.m in Sources */,
E86208172C0335EC007C3E24 /* RNVCIClientModule.m in Sources */,
9C48504E2C3E59B5002ECBD5 /* RNEventEmitter.swift in Sources */,
9C7CDF3E2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift in Sources */,
9C48504B2C3E59B5002ECBD5 /* RNWalletModule.swift in Sources */,
9C48504D2C3E59B5002ECBD5 /* RNWalletModule.m in Sources */,
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
@@ -728,6 +740,14 @@
kind = branch;
};
};
9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/secure-keystore-ios-swift";
requirement = {
branch = develop;
kind = branch;
};
};
9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mosip/pixelpass-ios-swift/";
@@ -752,6 +772,11 @@
package = 9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */;
productName = "ios-tuvali-library";
};
9C7CDF482C89802C00243A9A /* securekeystore */ = {
isa = XCSwiftPackageProductDependency;
package = 9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */;
productName = securekeystore;
};
9CE34B1E2BFE03E4001AF414 /* pixelpass */ = {
isa = XCSwiftPackageProductDependency;
package = 9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */;

View File

@@ -1,5 +1,5 @@
{
"originHash" : "37844cf80fe1416dd9922e75c396bd616f0723f347f5649a1c0355a3ba3f3a82",
"originHash" : "23605762247a1c453627ce326361ce9a41806d86fd97ebda599994901cc7610b",
"pins" : [
{
"identity" : "base45-swift",
@@ -43,7 +43,25 @@
"location" : "https://github.com/mosip/pixelpass-ios-swift/",
"state" : {
"branch" : "develop",
"revision" : "0b716d5f6545b10de84c69edbf59565e66776ccd"
"revision" : "170f3489e77940212f471ee1cb9f3ab392039a45"
}
},
{
"identity" : "secure-keystore-ios-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mosip/secure-keystore-ios-swift",
"state" : {
"branch" : "develop",
"revision" : "497a8a3dc4a8658665c84bc8a36fa0e6bf252c09"
}
},
{
"identity" : "swiftcbor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/valpackett/SwiftCBOR",
"state" : {
"branch" : "master",
"revision" : "ec24382864e5ffc6d3915c0818745d5ab12545a8"
}
},
{

View File

@@ -431,6 +431,8 @@ PODS:
- react-native-cloud-storage (1.4.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-image-editor (4.2.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
@@ -452,8 +454,6 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-secure-key-store (2.0.10):
- React-Core
- react-native-spinkit (1.4.1):
- React
- React-perflogger (0.71.8)
@@ -560,8 +560,6 @@ PODS:
- RNGoogleSignin (10.1.1):
- GoogleSignIn (~> 7.0)
- React-Core
- RNKeychain (8.0.0):
- React-Core
- RNLocalize (3.0.2):
- React-Core
- RNPermissions (3.8.4):
@@ -651,6 +649,7 @@ DEPENDENCIES:
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-app-auth (from `../node_modules/react-native-app-auth`)
- react-native-cloud-storage (from `../node_modules/react-native-cloud-storage`)
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- "react-native-image-editor (from `../node_modules/@react-native-community/image-editor`)"
- react-native-location (from `../node_modules/react-native-location`)
- react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`)
@@ -658,7 +657,6 @@ DEPENDENCIES:
- react-native-restart (from `../node_modules/react-native-restart`)
- react-native-rsa-native (from `../node_modules/react-native-rsa-native`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-secure-key-store (from `../node_modules/react-native-secure-key-store`)
- react-native-spinkit (from `../node_modules/react-native-spinkit`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -682,7 +680,6 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- "RNGoogleSignin (from `../node_modules/@react-native-google-signin/google-signin`)"
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNPermissions (from `../node_modules/react-native-permissions`)
- RNScreens (from `../node_modules/react-native-screens`)
@@ -826,6 +823,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-app-auth"
react-native-cloud-storage:
:path: "../node_modules/react-native-cloud-storage"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-image-editor:
:path: "../node_modules/@react-native-community/image-editor"
react-native-location:
@@ -840,8 +839,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-rsa-native"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-secure-key-store:
:path: "../node_modules/react-native-secure-key-store"
react-native-spinkit:
:path: "../node_modules/react-native-spinkit"
React-perflogger:
@@ -888,8 +885,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNGoogleSignin:
:path: "../node_modules/@react-native-google-signin/google-signin"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNPermissions:
@@ -983,6 +978,7 @@ SPEC CHECKSUMS:
React-logger: 342f358b8decfbf8f272367f4eacf4b6154061be
react-native-app-auth: 1d12b6874a24152715a381d8e9149398ce7c2c95
react-native-cloud-storage: 4a4726995158d9d45bc6c4a27fd83ab4d0673632
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
react-native-image-editor: a60c74bc79f6101b7863c092aa53726e9e40a327
react-native-location: 5a40ec1cc6abf2f6d94df979f98ec76c3a415681
react-native-mmkv-storage: cfb6854594cfdc5f7383a9e464bb025417d1721c
@@ -990,7 +986,6 @@ SPEC CHECKSUMS:
react-native-restart: 45c8dca02491980f2958595333cbccd6877cb57e
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
react-native-spinkit: da294fd828216ad211fe36a5c14c1e09f09e62db
React-perflogger: d21f182895de9d1b077f8a3cd00011095c8c9100
React-RCTActionSheet: 0151f83ef92d2a7139bba7dfdbc8066632a6d47b
@@ -1014,7 +1009,6 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNGoogleSignin: aac5c1ec73422109dec1da770247a1e410dcc620
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4
RNPermissions: f1b49dd05fa9b83993cd05a9ee115247944d8f1a
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f

View File

@@ -0,0 +1,87 @@
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(RNSecureKeystoreModule, NSObject)
RCT_EXTERN_METHOD(generateKeyPair:(NSString *)type
isAuthRequired:(BOOL)isAuthRequired
authTimeout:(NSInteger)authTimeout
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(deleteKeyPair:(NSString *)tag
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(hasAlias:(NSString *)tag
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(sign:(NSString *)signAlgorithm
alias:(NSString *)alias
data:(NSString *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(storeGenericKey:(NSString *)publicKey
privateKey:(NSString *)privateKey
account:(NSString *)account
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(storeData:(NSString *)key
value:(NSString *)value
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(retrieveGenericKey:(NSString *)account
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(retrieveKey:(NSString *)alias
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getData:(NSString *)key
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(hasBiometricsEnabled:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(updatePopup:(NSString *)title
desc:(NSString *)desc)
RCT_EXTERN_METHOD(generateKey:(NSString *)alias
authRequired:(BOOL)authRequired
authTimeout:(NSInteger)authTimeout
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(encryptData:(NSString *)alias
data:(NSString *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(decryptData:(NSString *)alias
data:(NSString *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(generateHmacshaKey:(NSString *)alias
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(generateHmacSha:(NSString *)alias
data:(NSString *)data
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(clearKeys:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getJwk: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject)
@end

View File

@@ -0,0 +1,259 @@
import Foundation
import Security
import React
import securekeystore
@objc(RNSecureKeystoreModule)
class RNSecureKeystoreModule: NSObject,RCTBridgeModule {
static func moduleName() -> String! {
return "RNSecureKeystoreModule"
}
private var secureKeystore:SecureKeystoreProtocol
override init() {
self.secureKeystore=SecureKeystoreImpl()
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
@objc
func generateKeyPair(_ type: String, isAuthRequired: Bool, authTimeout: Int, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
var tag=""
if(type=="RS256")
{
tag="RSA"
}
else
{
tag="ECR1"
}
secureKeystore.generateKeyPair(type: tag, tag: tag, isAuthRequired: isAuthRequired, authTimeout: Int32(authTimeout), onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func deleteKeyPair(_ tag: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.deleteKeyPair(tag: tag,onSuccess: successLambda,onFailure: failureLambda)
}
@objc
func hasAlias(_ tag: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.hasAlias(tag: tag,onSuccess: successLambda,onFailure: failureLambda)
}
@objc
func sign(_ signAlgorithm: String, alias: String, data: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
var tag=""
if(alias=="RS256")
{
tag="RSA"
}
else
{
tag="ECR1"
}
secureKeystore.sign(signAlgorithm: tag,alias: tag,data: data,onSuccess: successLambda,onFailure: failureLambda)
}
@objc
func storeGenericKey(_ publickKey:String,privateKey:String, account: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.storeGenericKey(publicKey: publickKey, privateKey: privateKey, account: account, onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func storeData(_ key:String,value:String,resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.storeGenericKey(publicKey: value, privateKey: "", account: key, onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func retrieveGenericKey(_ account: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String?,String?) -> Void = { privateKey,publicKey in
let keyPair=[privateKey,publicKey]
resolve(keyPair)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.retrieveGenericKey(account: account,onSuccess: successLambda,onFailure: failureLambda)
}
@objc
func getData(_ key: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock)
{
let successLambda: (String?,String?) -> Void = { privateKey,publicKey in
let keyPair=[privateKey,publicKey]
resolve(keyPair)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.retrieveGenericKey(account: key,onSuccess: successLambda,onFailure: failureLambda)
}
@objc
func hasBiometricsEnabled(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let success = secureKeystore.hasBiometricsEnabled()
resolve(success)
}
@objc
func updatePopup(_ title: String, desc: String) {
secureKeystore.updatePopup(title: title, desc: desc)
}
@objc
func generateKey(_ alias: String, authRequired: Bool, authTimeout: Int, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.generateKey(alias: alias, authRequired: authRequired, authTimeout: Int32(authTimeout), onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func encryptData(_ alias: String, data: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String) -> Void = { encryptedData in
resolve(encryptedData)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.encryptData(alias: alias, data: data, onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func decryptData(_ alias: String, data: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String) -> Void = { decryptedData in
resolve(decryptedData)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.decryptData(alias: alias, data: data, onSuccess: successLambda, onFailure: failureLambda)
}
@available(iOS 13.0, *)
@objc
func generateHmacshaKey(_ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.generateHmacshaKey(alias: alias, onSuccess: successLambda, onFailure: failureLambda)
}
@available(iOS 13.0, *)
@objc
func generateHmacSha(_ alias: String, data: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (String?) -> Void = { hash in
resolve(hash)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.generateHmacSha(alias: alias, data: data, onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func clearKeys(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
let successLambda: (Bool) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.clearKeys(onSuccess: successLambda, onFailure: failureLambda)
}
@objc
func retrieveKey(_ alias:String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock){
let successLambda: (String) -> Void = { success in
resolve(success)
}
let failureLambda: (String, String) -> Void = { code, message in
reject(code, message, nil)
}
secureKeystore.retrieveKey(tag: alias, onSuccess: successLambda, onFailure: failureLambda)
}
}

View File

@@ -1,8 +1,14 @@
import {ErrorMessage, Issuers_Key_Ref} from '../../shared/openId4VCI/Utils';
import {
ErrorMessage,
Issuers_Key_Ref,
getKeyTypeFromWellknown,
selectCredentialRequestKey,
} from '../../shared/openId4VCI/Utils';
import {
MY_VCS_STORE_KEY,
NETWORK_REQUEST_FAILED,
REQUEST_TIMEOUT,
isIOS,
} from '../../shared/constants';
import {assign, send} from 'xstate';
import {StoreEvents} from '../store';
@@ -17,8 +23,10 @@ import {
sendImpressionEvent,
} from '../../shared/telemetry/TelemetryUtils';
import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import {KeyPair} from 'react-native-rsa-native';
import {NativeModules} from 'react-native';
import {KeyTypes} from '../../shared/cryptoutil/KeyTypes';
const {RNSecureKeystoreModule} = NativeModules;
export const IssuersActions = (model: any) => {
return {
setIsVerified: assign({
@@ -55,6 +63,15 @@ export const IssuersActions = (model: any) => {
}),
setSelectedCredentialType: model.assign({
selectedCredentialType: (_: any, event: any) => event.credType,
wellknownKeyTypes: (_: any, event: any) => {
const proofTypesSupported = event.credType.proof_types_supported;
if (proofTypesSupported?.jwt) {
return proofTypesSupported.jwt
.proof_signing_alg_values_supported as string[];
} else {
return [KeyTypes.RS256] as string[];
}
},
}),
setSupportedCredentialTypes: model.assign({
supportedCredentialTypes: (_: any, event: any) => event.data,
@@ -102,11 +119,11 @@ export const IssuersActions = (model: any) => {
}),
loadKeyPair: assign({
publicKey: (_, event: any) => event.response?.publicKey,
publicKey: (_, event: any) => event.data?.publicKey as string,
privateKey: (context: any, event: any) =>
event.response?.privateKey
? event.response.privateKey
: context.privateKey,
event.data?.privateKey
? event.data.privateKey
: (context.privateKey as string),
}),
getKeyPairFromStore: send(StoreEvents.GET(Issuers_Key_Ref), {
to: (context: any) => context.serviceRefs.store,
@@ -114,19 +131,22 @@ export const IssuersActions = (model: any) => {
sendBackupEvent: send(BackupEvents.DATA_BACKUP(true), {
to: (context: any) => context.serviceRefs.backup,
}),
storeKeyPair: send(
(context: any) => {
return StoreEvents.SET(Issuers_Key_Ref, {
publicKey: context.publicKey,
privateKey: context.privateKey,
});
},
{
to: context => context.serviceRefs.store,
},
),
storeKeyPair: async (context: any) => {
const keyType = context.keyType;
if ((keyType != 'ES256' && keyType != 'RS256') || isIOS())
await RNSecureKeystoreModule.storeGenericKey(
context.publicKey,
context.privateKey,
keyType,
);
},
storeVerifiableCredentialMeta: send(
context => StoreEvents.PREPEND(MY_VCS_STORE_KEY, getVCMetadata(context)),
context =>
StoreEvents.PREPEND(
MY_VCS_STORE_KEY,
getVCMetadata(context, context.keyType),
),
{
to: (context: any) => context.serviceRefs.store,
},
@@ -140,14 +160,14 @@ export const IssuersActions = (model: any) => {
},
setVCMetadata: assign({
vcMetadata: context => {
return getVCMetadata(context);
vcMetadata: (context: any) => {
return getVCMetadata(context, context.keyType);
},
}),
storeVerifiableCredentialData: send(
(context: any) => {
const vcMeatadata = getVCMetadata(context);
const vcMeatadata = getVCMetadata(context, context.keyType);
return StoreEvents.SET(vcMeatadata.getVcKey(), {
...context.credentialWrapper,
vcMetadata: vcMeatadata,
@@ -162,7 +182,7 @@ export const IssuersActions = (model: any) => {
context => {
return {
type: 'VC_ADDED',
vcMetadata: getVCMetadata(context),
vcMetadata: getVCMetadata(context, context.keyType),
};
},
{
@@ -174,7 +194,7 @@ export const IssuersActions = (model: any) => {
(context: any) => {
return {
type: 'VC_DOWNLOADED',
vcMetadata: getVCMetadata(context),
vcMetadata: getVCMetadata(context, context.keyType),
vc: context.credentialWrapper,
};
},
@@ -183,6 +203,13 @@ export const IssuersActions = (model: any) => {
},
),
setSelectedKey: model.assign({
keyType: (context: any) => {
const keyType = selectCredentialRequestKey(context.wellknownKeyTypes);
return keyType;
},
}),
setSelectedIssuers: model.assign({
selectedIssuer: (context: any, event: any) =>
context.issuers.find(issuer => issuer.credential_issuer === event.id),
@@ -217,19 +244,19 @@ export const IssuersActions = (model: any) => {
setPublicKey: assign({
publicKey: (_, event: any) => {
if (!isHardwareKeystoreExists) {
return (event.data as KeyPair).public;
return event.data.publicKey as string;
}
return event.data as string;
return event.data.publicKey as string;
},
}),
setPrivateKey: assign({
privateKey: (_, event: any) => (event.data as KeyPair).private,
privateKey: (_, event: any) => event.data.privateKey as string,
}),
logDownloaded: send(
context => {
const vcMetadata = getVCMetadata(context);
const vcMetadata = getVCMetadata(context, context.keyType);
return ActivityLogEvents.LOG_ACTIVITY(
{
_vcKey: vcMetadata.getVcKey(),

View File

@@ -11,7 +11,9 @@ export const IssuersGuards = () => {
(event.data as Error).message == VerificationErrorType.NETWORK_ERROR,
isSignedIn: (_: any, event: any) =>
(event.data as isSignedInResult).isSignedIn,
hasKeyPair: (context: any) => !!context.publicKey,
hasKeyPair: (context: any) => {
return !!context.publicKey;
},
isInternetConnected: (_: any, event: any) => !!event.data.isConnected,
isOIDCflowCancelled: (_: any, event: any) => {
// iOS & Android have different error strings for user cancelled flow

View File

@@ -94,7 +94,7 @@ export const IssuersMachine = model.createMachine(
invoke: {
src: 'downloadIssuerWellknown',
onDone: {
actions: 'updateIssuerFromWellknown',
actions: ['updateIssuerFromWellknown'],
target: 'downloadCredentialTypes',
},
onError: {
@@ -174,8 +174,9 @@ export const IssuersMachine = model.createMachine(
actions: [
'setTokenResponse',
'setLoadingReasonAsSettingUp',
'getKeyPairFromStore',
'setSelectedKey',
],
target: '.getKeyPairFromKeystore',
},
onError: [
{
@@ -210,26 +211,30 @@ export const IssuersMachine = model.createMachine(
},
initial: 'idle',
states: {
idle: {
on: {
STORE_RESPONSE: {
actions: 'loadKeyPair',
target: '#issuersMachine.checkKeyPair',
},
BIOMETRIC_CANCELLED: {
target: 'userCancelledBiometric',
},
STORE_ERROR: {
idle: {},
getKeyPairFromKeystore: {
invoke: {
src: 'getKeyPair',
onDone: {
actions: ['loadKeyPair'],
target: '#issuersMachine.checkKeyPair',
},
onError: [
{
cond: 'isBiometricCancelled',
target: 'userCancelledBiometric',
},
{
target: '#issuersMachine.checkKeyPair',
},
],
},
},
userCancelledBiometric: {
on: {
TRY_AGAIN: [
{
actions: ['getKeyPairFromStore'],
target: 'idle',
target: 'getKeyPairFromKeystore',
},
],
RESET_ERROR: {
@@ -242,12 +247,10 @@ export const IssuersMachine = model.createMachine(
},
checkKeyPair: {
description: 'checks whether key pair is generated',
entry: [
'setLoadingReasonAsDownloadingCredentials',
send('CHECK_KEY_PAIR'),
],
on: {
CHECK_KEY_PAIR: [
entry: ['setLoadingReasonAsDownloadingCredentials'],
invoke: {
src: 'getSelectedKey',
onDone: [
{
cond: 'hasKeyPair',
target: 'downloadCredentials',
@@ -256,6 +259,12 @@ export const IssuersMachine = model.createMachine(
target: 'generateKeyPair',
},
],
onError: [
{
target: 'selectingIssuer',
},
],
},
},
generateKeyPair: {
@@ -267,6 +276,7 @@ export const IssuersMachine = model.createMachine(
{
actions: [
'setPublicKey',
'setPrivateKey',
'setLoadingReasonAsDownloadingCredentials',
'storeKeyPair',
],
@@ -275,6 +285,7 @@ export const IssuersMachine = model.createMachine(
},
{
actions: [
// to be decided
'setPublicKey',
'setLoadingReasonAsDownloadingCredentials',
'setPrivateKey',

View File

@@ -8,6 +8,11 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.checkKeyPair:invocation[0]': {
type: 'done.invoke.issuersMachine.checkKeyPair:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.displayIssuers:invocation[0]': {
type: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
data: unknown;
@@ -33,6 +38,11 @@ export interface Typegen0 {
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]': {
type: 'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.issuersMachine.performAuthorization:invocation[0]': {
type: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
data: unknown;
@@ -68,6 +78,10 @@ export interface Typegen0 {
type: 'error.platform.issuersMachine.downloadIssuerWellknown:invocation[0]';
data: unknown;
};
'error.platform.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]': {
type: 'error.platform.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
data: unknown;
};
'error.platform.issuersMachine.performAuthorization:invocation[0]': {
type: 'error.platform.issuersMachine.performAuthorization:invocation[0]';
data: unknown;
@@ -85,13 +99,15 @@ export interface Typegen0 {
downloadIssuerWellknown: 'done.invoke.issuersMachine.downloadIssuerWellknown:invocation[0]';
downloadIssuersList: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
generateKeyPair: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
getKeyPair: 'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
getSelectedKey: 'done.invoke.issuersMachine.checkKeyPair:invocation[0]';
invokeAuthorization: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
isUserSignedAlready: 'done.invoke.issuersMachine.storing:invocation[0]';
verifyCredential: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
};
missingImplementations: {
actions:
| 'getKeyPairFromStore'
| 'downloadIssuerWellknown'
| 'loadKeyPair'
| 'logDownloaded'
| 'resetError'
@@ -104,6 +120,7 @@ export interface Typegen0 {
| 'sendErrorEndEvent'
| 'sendImpressionEvent'
| 'sendSuccessEndEvent'
| 'setCredentialTypeListDownloadFailureError'
| 'setCredentialWrapper'
| 'setError'
| 'setFetchWellknownError'
@@ -120,6 +137,7 @@ export interface Typegen0 {
| 'setSelectedCredentialType'
| 'setSelectedIssuerId'
| 'setSelectedIssuers'
| 'setSelectedKey'
| 'setSupportedCredentialTypes'
| 'setTokenResponse'
| 'setVCMetadata'
@@ -136,6 +154,7 @@ export interface Typegen0 {
| 'canSelectIssuerAgain'
| 'hasKeyPair'
| 'hasUserCancelledBiometric'
| 'isBiometricCancelled'
| 'isCustomSecureKeystore'
| 'isGenericError'
| 'isInternetConnected'
@@ -151,15 +170,15 @@ export interface Typegen0 {
| 'downloadIssuerWellknown'
| 'downloadIssuersList'
| 'generateKeyPair'
| 'getKeyPair'
| 'getSelectedKey'
| 'invokeAuthorization'
| 'isUserSignedAlready'
| 'verifyCredential';
};
eventsCausingActions: {
getKeyPairFromStore:
| 'TRY_AGAIN'
| 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
loadKeyPair: 'STORE_RESPONSE';
downloadIssuerWellknown: 'TRY_AGAIN';
loadKeyPair: 'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
logDownloaded:
| 'done.invoke.issuersMachine.verifyingCredential:invocation[0]'
| 'error.platform.issuersMachine.verifyingCredential:invocation[0]';
@@ -189,10 +208,10 @@ export interface Typegen0 {
sendErrorEndEvent: 'error.platform.issuersMachine.verifyingCredential:invocation[0]';
sendImpressionEvent: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
sendSuccessEndEvent: 'done.invoke.issuersMachine.verifyingCredential:invocation[0]';
setCredentialTypeListDownloadFailureError: 'error.platform.issuersMachine.downloadCredentialTypes:invocation[0]';
setCredentialWrapper: 'done.invoke.issuersMachine.downloadCredentials:invocation[0]';
setError:
| 'error.platform.issuersMachine.displayIssuers:invocation[0]'
| 'error.platform.issuersMachine.downloadCredentialTypes:invocation[0]'
| 'error.platform.issuersMachine.downloadCredentials:invocation[0]'
| 'error.platform.issuersMachine.performAuthorization:invocation[0]';
setFetchWellknownError: 'error.platform.issuersMachine.downloadIssuerWellknown:invocation[0]';
@@ -200,10 +219,10 @@ export interface Typegen0 {
setIssuers: 'done.invoke.issuersMachine.displayIssuers:invocation[0]';
setLoadingReasonAsDisplayIssuers: 'TRY_AGAIN';
setLoadingReasonAsDownloadingCredentials:
| 'STORE_ERROR'
| 'STORE_RESPONSE'
| 'TRY_AGAIN'
| 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
| 'done.invoke.issuersMachine.generateKeyPair:invocation[0]'
| 'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]'
| 'error.platform.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
setLoadingReasonAsSettingUp:
| 'SELECTED_ISSUER'
| 'TRY_AGAIN'
@@ -218,6 +237,7 @@ export interface Typegen0 {
setSelectedCredentialType: 'SELECTED_CREDENTIAL_TYPE';
setSelectedIssuerId: 'SELECTED_ISSUER';
setSelectedIssuers: 'SELECTED_ISSUER';
setSelectedKey: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
setSupportedCredentialTypes: 'done.invoke.issuersMachine.downloadCredentialTypes:invocation[0]';
setTokenResponse: 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
setVCMetadata:
@@ -243,8 +263,9 @@ export interface Typegen0 {
eventsCausingDelays: {};
eventsCausingGuards: {
canSelectIssuerAgain: 'TRY_AGAIN';
hasKeyPair: 'CHECK_KEY_PAIR';
hasKeyPair: 'done.invoke.issuersMachine.checkKeyPair:invocation[0]';
hasUserCancelledBiometric: 'error.platform.issuersMachine.downloadCredentials:invocation[0]';
isBiometricCancelled: 'error.platform.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
isCustomSecureKeystore: 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
isGenericError: 'error.platform.issuersMachine.downloadCredentials:invocation[0]';
isInternetConnected: 'done.invoke.checkInternet';
@@ -259,12 +280,18 @@ export interface Typegen0 {
| 'SELECTED_CREDENTIAL_TYPE'
| 'done.invoke.issuersMachine.downloadCredentialTypes:invocation[0]';
downloadCredential:
| 'CHECK_KEY_PAIR'
| 'done.invoke.issuersMachine.checkKeyPair:invocation[0]'
| 'done.invoke.issuersMachine.generateKeyPair:invocation[0]';
downloadCredentialTypes: 'done.invoke.issuersMachine.downloadIssuerWellknown:invocation[0]';
downloadIssuerWellknown: 'SELECTED_ISSUER' | 'TRY_AGAIN';
downloadIssuersList: 'CANCEL' | 'TRY_AGAIN' | 'xstate.init';
generateKeyPair: 'CHECK_KEY_PAIR';
generateKeyPair: 'done.invoke.issuersMachine.checkKeyPair:invocation[0]';
getKeyPair:
| 'TRY_AGAIN'
| 'done.invoke.issuersMachine.performAuthorization:invocation[0]';
getSelectedKey:
| 'done.invoke.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]'
| 'error.platform.issuersMachine.performAuthorization.getKeyPairFromKeystore:invocation[0]';
invokeAuthorization: 'done.invoke.checkInternet';
isUserSignedAlready:
| 'done.invoke.issuersMachine.verifyingCredential:invocation[0]'
@@ -286,6 +313,7 @@ export interface Typegen0 {
| 'handleVCVerificationFailure'
| 'idle'
| 'performAuthorization'
| 'performAuthorization.getKeyPairFromKeystore'
| 'performAuthorization.idle'
| 'performAuthorization.userCancelledBiometric'
| 'selectingCredentialType'
@@ -294,7 +322,10 @@ export interface Typegen0 {
| 'verifyingCredential'
| {
downloadCredentials?: 'idle' | 'userCancelledBiometric';
performAuthorization?: 'idle' | 'userCancelledBiometric';
performAuthorization?:
| 'getKeyPairFromKeystore'
| 'idle'
| 'userCancelledBiometric';
};
tags: never;
}

View File

@@ -24,9 +24,11 @@ export const IssuersModel = createModel(
credentialWrapper: {} as CredentialWrapper,
serviceRefs: {} as AppServices,
verificationErrorMessage: '',
publicKey: ``,
privateKey: ``,
publicKey: '',
privateKey: '',
vcMetadata: {} as VCMetadata,
keyType: 'RS256' as string,
wellknownKeyTypes: [] as string[],
},
{
events: IssuersEvents,

View File

@@ -5,14 +5,16 @@ import {
constructAuthorizationConfiguration,
constructIssuerMetaData,
constructProofJWT,
Issuers_Key_Ref,
getKeyTypeFromWellknown,
hasKeyPair,
updateCredentialInformation,
vcDownloadTimeout,
selectCredentialRequestKey,
} from '../../shared/openId4VCI/Utils';
import {authorize} from 'react-native-app-auth';
import {
generateKeys,
isHardwareKeystoreExists,
fetchKeyPair,
generateKeyPair,
} from '../../shared/cryptoutil/cryptoUtil';
import {NativeModules} from 'react-native';
import {
@@ -27,7 +29,6 @@ import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants';
import {isMosipVC} from '../../shared/Utils';
import {VciClient} from '../../shared/vciClient/VciClient';
const {RNSecureKeystoreModule} = NativeModules;
export const IssuersService = () => {
return {
isUserSignedAlready: () => async () => {
@@ -67,6 +68,7 @@ export const IssuersService = () => {
context.privateKey,
accessToken,
context.selectedIssuer,
context.keyType,
);
let credential = await VciClient.downloadCredential(
constructIssuerMetaData(
@@ -97,17 +99,21 @@ export const IssuersService = () => {
);
},
generateKeyPair: async () => {
if (!isHardwareKeystoreExists) {
return await generateKeys();
}
const isBiometricsEnabled = RNSecureKeystoreModule.hasBiometricsEnabled();
return RNSecureKeystoreModule.generateKeyPair(
Issuers_Key_Ref,
isBiometricsEnabled,
0,
);
generateKeyPair: async (context: any) => {
const keypair = await generateKeyPair(context.keyType);
return keypair;
},
getKeyPair: async (context: any) => {
if (!!(await fetchKeyPair(context.keyType)).publicKey) {
return await fetchKeyPair(context.keyType);
}
},
getSelectedKey: async (context: any) => {
return context.keyType;
},
verifyCredential: async (context: any) => {
//this issuer specific check has to be removed once vc validation is done.
if (isMosipVC(context.selectedIssuerId)) {

View File

@@ -4,6 +4,7 @@ import {ESIGNET_BASE_URL} from '../../shared/constants';
import {
isHardwareKeystoreExists,
getJWT,
fetchKeyPair,
} from '../../shared/cryptoutil/cryptoUtil';
import {getPrivateKey} from '../../shared/keystore/SecureKeystore';
@@ -26,16 +27,17 @@ export const QrLoginServices = {
sendAuthenticate: async context => {
let privateKey;
const individualId = context.selectedVc.vcMetadata.displayId;
const alias = context.selectedVc.vcMetadata.id;
const keyType = context.selectedVc.vcMetadata.downloadKeyType;
if (!isHardwareKeystoreExists) {
privateKey = await getPrivateKey(
context.selectedVc.walletBindingResponse?.walletBindingId,
);
}
const keyPair = await fetchKeyPair(keyType);
privateKey = keyPair.privateKey;
var config = await getAllConfigurations();
const jwtHeader = {
alg: 'RS256',
alg: keyType,
'x5t#S256': context.thumbprint,
};
@@ -47,7 +49,13 @@ export const QrLoginServices = {
exp: Math.floor(new Date().getTime() / 1000) + 18000,
};
const jwt = await getJWT(jwtHeader, jwtPayload, alias, privateKey);
const jwt = await getJWT(
jwtHeader,
jwtPayload,
keyType,
privateKey,
keyType,
);
const response = await request(
API_URLS.authenticate.method,
@@ -73,15 +81,17 @@ export const QrLoginServices = {
sendConsent: async context => {
let privateKey;
const alias = context.selectedVc.vcMetadata.id;
const keyType = context.selectedVc.vcMetadata.downloadKeyType;
if (!isHardwareKeystoreExists) {
privateKey = await getPrivateKey(
context.selectedVc.walletBindingResponse?.walletBindingId,
);
}
const keyPair = await fetchKeyPair(keyType);
privateKey = keyPair.privateKey;
const header = {
alg: 'RS256',
alg: keyType,
'x5t#S256': context.thumbprint,
};
const payload = {
@@ -91,7 +101,7 @@ export const QrLoginServices = {
permitted_authorized_scopes: context.authorizeScopes,
};
const JWT = await getJWT(header, payload, alias, privateKey);
const JWT = await getJWT(header, payload, keyType, privateKey, keyType);
const jwtComponents = JWT.split('.');
const detachedSignature = jwtComponents[0] + '.' + jwtComponents[2];

View File

@@ -375,14 +375,14 @@ export const VCItemActions = model => {
setPublicKey: assign({
publicKey: (_context, event) => {
if (!isHardwareKeystoreExists) {
return (event.data as KeyPair).public;
return event.data.publicKey as string;
}
return event.data as string;
return event.data.publicKey as string;
},
}),
setPrivateKey: assign({
privateKey: (_context, event) => (event.data as KeyPair).private,
privateKey: (_context, event) => event.data.privateKey as string,
}),
resetPrivateKey: assign({
privateKey: () => '',
@@ -403,7 +403,9 @@ export const VCItemActions = model => {
},
),
setOTP: model.assign({
OTP: (_, event) => event.OTP,
OTP: (_, event) => {
return event.OTP;
},
}),
unSetOTP: model.assign({OTP: () => ''}),

View File

@@ -15,6 +15,8 @@ export const VCItemGaurds = () => {
const vc = event.response;
return vc?.verifiableCredential != null;
},
hasKeyPair: (_, event) => !!event?.data?.publicKey,
isSignedIn: (_context, event) =>
(event.data as isSignedInResult).isSignedIn,

View File

@@ -284,16 +284,36 @@ export const VCItemMachine = model.createMachine(
},
addKeyPair: {
invoke: {
src: 'generateKeyPair',
src: 'fetchKeyPair',
onDone: [
{
cond: 'isCustomSecureKeystore',
target: 'addingWalletBindingId',
cond: 'hasKeyPair',
actions: ['setPublicKey'],
target: 'addingWalletBindingId',
},
{
target: 'addingWalletBindingId',
target: 'generateKeyPair',
},
],
onError: [
{
actions: [
'setErrorAsWalletBindingError',
'sendWalletBindingErrorEvent',
'logWalletBindingFailure',
],
target: 'showingWalletBindingError',
},
],
},
},
generateKeyPair: {
invoke: {
src: 'generateKeypairAndStore',
onDone: [
{
actions: ['setPublicKey', 'setPrivateKey'],
target: 'addingWalletBindingId',
},
],
onError: [
@@ -317,11 +337,6 @@ export const VCItemMachine = model.createMachine(
target: 'updatingContextVariables',
actions: ['setWalletBindingResponse'],
},
{
target: 'updatingPrivateKey',
/*The walletBindingResponse is used for conditional rendering in wallet binding. response and use it in updatingPrivateKey state*/
actions: ['setWalletBindingResponse'],
},
],
onError: [
{
@@ -452,10 +467,7 @@ export const VCItemMachine = model.createMachine(
onDone: [
{
cond: 'isSignedIn',
actions: [
'sendBackupEvent',
'refreshAllVcs',
],
actions: ['sendBackupEvent', 'refreshAllVcs'],
target: '#vc-item-machine.vcUtilitiesState.idle',
},
{

View File

@@ -1,142 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"": { type: "" };
"done.invoke.checkStatus": { type: "done.invoke.checkStatus"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.downloadCredential": { type: "done.invoke.downloadCredential"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]": { type: "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]": { type: "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform.checkStatus": { type: "error.platform.checkStatus"; data: unknown };
"error.platform.downloadCredential": { type: "error.platform.downloadCredential"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]": { type: "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]"; data: unknown };
"error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]": { type: "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]"; data: unknown };
"xstate.after(500)#vc-item-machine.verifyState.verifyingCredential": { type: "xstate.after(500)#vc-item-machine.verifyState.verifyingCredential" };
"xstate.init": { type: "xstate.init" };
"xstate.stop": { type: "xstate.stop" };
};
invokeSrcNameMap: {
"addWalletBindingId": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"checkDownloadExpiryLimit": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]";
"checkStatus": "done.invoke.checkStatus";
"downloadCredential": "done.invoke.downloadCredential";
"fetchIssuerWellknown": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]";
"generateKeyPair": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]";
"isUserSignedAlready": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
"loadDownloadLimitConfig": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"requestBindingOTP": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"updatePrivateKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"verifyCredential": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
};
missingImplementations: {
actions: "addVcToInProgressDownloads" | "closeViewVcModal" | "incrementDownloadCounter" | "logDownloaded" | "logRemovedVc" | "logWalletBindingFailure" | "logWalletBindingSuccess" | "refreshAllVcs" | "removeVcFromInProgressDownloads" | "removeVcItem" | "removeVcMetaDataFromStorage" | "removeVcMetaDataFromVcMachineContext" | "removeVerificationStatusFromVcMeta" | "requestVcContext" | "resetIsMachineInKebabPopupState" | "resetIsVerified" | "resetPrivateKey" | "resetVerificationStatus" | "sendActivationStartEvent" | "sendActivationSuccessEvent" | "sendBackupEvent" | "sendDownloadLimitExpire" | "sendDownloadingFailedToVcMeta" | "sendTelemetryEvents" | "sendUserCancelledActivationFailedEndEvent" | "sendVerificationError" | "sendVerificationStatusToVcMeta" | "sendWalletBindingErrorEvent" | "sendWalletBindingSuccess" | "setCommunicationDetails" | "setContext" | "setDownloadInterval" | "setErrorAsVerificationError" | "setErrorAsWalletBindingError" | "setIsVerified" | "setMaxDownloadCount" | "setOTP" | "setPinCard" | "setPrivateKey" | "setPublicKey" | "setThumbprintForWalletBindingId" | "setVcKey" | "setVcMetadata" | "setVerificationStatus" | "setWalletBindingResponse" | "showVerificationBannerStatus" | "storeContext" | "storeVcInContext" | "unSetBindingTransactionId" | "unSetError" | "unSetOTP" | "updateVcMetadata" | "updateWellknownResponse";
delays: never;
guards: "hasCredential" | "hasCredentialAndWellknown" | "isCustomSecureKeystore" | "isDownloadAllowed" | "isSignedIn" | "isVerificationPendingBecauseOfNetworkIssue";
services: "addWalletBindingId" | "checkDownloadExpiryLimit" | "checkStatus" | "downloadCredential" | "fetchIssuerWellknown" | "generateKeyPair" | "isUserSignedAlready" | "loadDownloadLimitConfig" | "requestBindingOTP" | "updatePrivateKey" | "verifyCredential";
};
eventsCausingActions: {
"addVcToInProgressDownloads": "GET_VC_RESPONSE";
"closeViewVcModal": "CLOSE_VC_MODAL" | "STORE_RESPONSE";
"incrementDownloadCounter": "POLL" | "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"logDownloaded": "STORE_RESPONSE";
"logRemovedVc": "STORE_RESPONSE" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]";
"logWalletBindingFailure": "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"logWalletBindingSuccess": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"refreshAllVcs": "STORE_RESPONSE" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]";
"removeVcFromInProgressDownloads": "STORE_RESPONSE" | "error.platform.downloadCredential" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]";
"removeVcItem": "CONFIRM";
"removeVcMetaDataFromStorage": "STORE_ERROR" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]";
"removeVcMetaDataFromVcMachineContext": "DISMISS";
"removeVerificationStatusFromVcMeta": "RESET_VERIFICATION_STATUS";
"requestVcContext": "DISMISS" | "REFRESH" | "STORE_ERROR" | "xstate.init";
"resetIsMachineInKebabPopupState": "" | "ADD_WALLET_BINDING_ID" | "CANCEL" | "CLOSE_VC_MODAL" | "DISMISS" | "REFRESH" | "REMOVE" | "SHOW_ACTIVITY" | "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "xstate.stop";
"resetIsVerified": "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"resetPrivateKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"resetVerificationStatus": "REMOVE_VERIFICATION_STATUS_BANNER" | "RESET_VERIFICATION_STATUS";
"sendActivationStartEvent": "CONFIRM";
"sendActivationSuccessEvent": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"sendBackupEvent": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
"sendDownloadLimitExpire": "FAILED" | "error.platform.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]";
"sendDownloadingFailedToVcMeta": "error.platform.downloadCredential";
"sendTelemetryEvents": "STORE_RESPONSE";
"sendUserCancelledActivationFailedEndEvent": "DISMISS";
"sendVerificationError": "STORE_RESPONSE";
"sendVerificationStatusToVcMeta": "STORE_RESPONSE";
"sendWalletBindingErrorEvent": "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"sendWalletBindingSuccess": "SHOW_BINDING_STATUS";
"setCommunicationDetails": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"setContext": "CREDENTIAL_DOWNLOADED" | "GET_VC_RESPONSE";
"setDownloadInterval": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"setErrorAsVerificationError": "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]";
"setErrorAsWalletBindingError": "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"setIsVerified": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"setMaxDownloadCount": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"setOTP": "INPUT_OTP";
"setPinCard": "PIN_CARD";
"setPrivateKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]";
"setPublicKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]";
"setThumbprintForWalletBindingId": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"setVcKey": "REMOVE";
"setVcMetadata": "UPDATE_VC_METADATA";
"setVerificationStatus": "SET_VERIFICATION_STATUS" | "SHOW_VERIFICATION_STATUS_BANNER" | "STORE_RESPONSE";
"setWalletBindingResponse": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"showVerificationBannerStatus": "SHOW_VERIFICATION_STATUS_BANNER" | "STORE_RESPONSE" | "xstate.after(500)#vc-item-machine.verifyState.verifyingCredential";
"storeContext": "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]" | "done.invoke.vc-item-machine.verifyState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
"storeVcInContext": "STORE_RESPONSE" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"unSetBindingTransactionId": "DISMISS";
"unSetError": "CANCEL" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.updatingPrivateKey:invocation[0]";
"unSetOTP": "DISMISS" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.requestingBindingOTP:invocation[0]";
"updateVcMetadata": "PIN_CARD" | "STORE_RESPONSE";
"updateWellknownResponse": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown:invocation[0]";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"hasCredential": "GET_VC_RESPONSE";
"hasCredentialAndWellknown": "GET_VC_RESPONSE";
"isCustomSecureKeystore": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"isDownloadAllowed": "POLL";
"isSignedIn": "done.invoke.vc-item-machine.vcUtilitiesState.kebabPopUp.triggerAutoBackup:invocation[0]" | "done.invoke.vc-item-machine.vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload:invocation[0]";
"isVerificationPendingBecauseOfNetworkIssue": "error.platform.vc-item-machine.vcUtilitiesState.verifyingCredential:invocation[0]" | "error.platform.vc-item-machine.verifyState.verifyingCredential:invocation[0]";
};
eventsCausingServices: {
"addWalletBindingId": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addKeyPair:invocation[0]";
"checkDownloadExpiryLimit": "POLL" | "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig:invocation[0]";
"checkStatus": "done.invoke.vc-item-machine.vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry:invocation[0]";
"downloadCredential": "DOWNLOAD_READY";
"fetchIssuerWellknown": "GET_VC_RESPONSE";
"generateKeyPair": "INPUT_OTP";
"isUserSignedAlready": "STORE_RESPONSE";
"loadDownloadLimitConfig": "GET_VC_RESPONSE" | "STORE_ERROR";
"requestBindingOTP": "CONFIRM" | "RESEND_OTP";
"updatePrivateKey": "done.invoke.vc-item-machine.vcUtilitiesState.walletBinding.addingWalletBindingId:invocation[0]";
"verifyCredential": "CREDENTIAL_DOWNLOADED" | "VERIFY";
};
matchesStates: "vcUtilitiesState" | "vcUtilitiesState.idle" | "vcUtilitiesState.kebabPopUp" | "vcUtilitiesState.kebabPopUp.idle" | "vcUtilitiesState.kebabPopUp.pinCard" | "vcUtilitiesState.kebabPopUp.removeWallet" | "vcUtilitiesState.kebabPopUp.removingVc" | "vcUtilitiesState.kebabPopUp.showActivities" | "vcUtilitiesState.kebabPopUp.triggerAutoBackup" | "vcUtilitiesState.loadVc" | "vcUtilitiesState.loadVc.loadVcFromContext" | "vcUtilitiesState.loadVc.loadVcFromContext.fetchWellknown" | "vcUtilitiesState.loadVc.loadVcFromContext.idle" | "vcUtilitiesState.loadVc.loadVcFromServer" | "vcUtilitiesState.loadVc.loadVcFromServer.checkingStatus" | "vcUtilitiesState.loadVc.loadVcFromServer.downloadingCredential" | "vcUtilitiesState.loadVc.loadVcFromServer.loadDownloadLimitConfig" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.idle" | "vcUtilitiesState.loadVc.loadVcFromServer.savingFailed.viewingVc" | "vcUtilitiesState.loadVc.loadVcFromServer.verifyingDownloadLimitExpiry" | "vcUtilitiesState.verifyingCredential" | "vcUtilitiesState.verifyingCredential.handleVCVerificationFailure" | "vcUtilitiesState.verifyingCredential.idle" | "vcUtilitiesState.verifyingCredential.triggerAutoBackupForVcDownload" | "vcUtilitiesState.walletBinding" | "vcUtilitiesState.walletBinding.acceptingBindingOTP" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.idle" | "vcUtilitiesState.walletBinding.acceptingBindingOTP.resendOTP" | "vcUtilitiesState.walletBinding.addKeyPair" | "vcUtilitiesState.walletBinding.addingWalletBindingId" | "vcUtilitiesState.walletBinding.requestingBindingOTP" | "vcUtilitiesState.walletBinding.showBindingWarning" | "vcUtilitiesState.walletBinding.showingWalletBindingError" | "vcUtilitiesState.walletBinding.updatingContextVariables" | "vcUtilitiesState.walletBinding.updatingPrivateKey" | "verifyState" | "verifyState.idle" | "verifyState.verificationCompleted" | "verifyState.verifyingCredential" | { "vcUtilitiesState"?: "idle" | "kebabPopUp" | "loadVc" | "verifyingCredential" | "walletBinding" | { "kebabPopUp"?: "idle" | "pinCard" | "removeWallet" | "removingVc" | "showActivities" | "triggerAutoBackup";
"loadVc"?: "loadVcFromContext" | "loadVcFromServer" | { "loadVcFromContext"?: "fetchWellknown" | "idle";
"loadVcFromServer"?: "checkingStatus" | "downloadingCredential" | "loadDownloadLimitConfig" | "savingFailed" | "verifyingDownloadLimitExpiry" | { "savingFailed"?: "idle" | "viewingVc"; }; };
"verifyingCredential"?: "handleVCVerificationFailure" | "idle" | "triggerAutoBackupForVcDownload";
"walletBinding"?: "acceptingBindingOTP" | "addKeyPair" | "addingWalletBindingId" | "requestingBindingOTP" | "showBindingWarning" | "showingWalletBindingError" | "updatingContextVariables" | "updatingPrivateKey" | { "acceptingBindingOTP"?: "idle" | "resendOTP"; }; };
"verifyState"?: "idle" | "verificationCompleted" | "verifyingCredential"; };
tags: never;
}

View File

@@ -1,25 +1,24 @@
import {NativeModules} from 'react-native';
import Cloud from '../../../shared/CloudBackupAndRestoreUtils';
import {VCMetadata} from '../../../shared/VCMetadata';
import getAllConfigurations, {
API_URLS,
CACHED_API,
DownloadProps,
} from '../../../shared/api';
import {
generateKeys,
isHardwareKeystoreExists,
fetchKeyPair,
generateKeyPair,
} from '../../../shared/cryptoutil/cryptoUtil';
import {
getBindingCertificateConstant,
savePrivateKey,
} from '../../../shared/keystore/SecureKeystore';
import {CredentialDownloadResponse, request} from '../../../shared/request';
import {WalletBindingResponse} from '../VCMetaMachine/vc';
import {verifyCredential} from '../../../shared/vcjs/verifyCredential';
import {getVerifiableCredential} from './VCItemSelectors';
import {getMatchingCredentialIssuerMetadata, getSelectedCredentialTypeDetails} from '../../../shared/openId4VCI/Utils';
import {
getMatchingCredentialIssuerMetadata,
getSelectedCredentialTypeDetails,
} from '../../../shared/openId4VCI/Utils';
import {getCredentialTypes} from '../../../components/VC/common/VCUtils';
import {isIOS} from '../../../shared/constants';
const {RNSecureKeystoreModule} = NativeModules;
export const VCItemServices = model => {
@@ -28,16 +27,6 @@ export const VCItemServices = model => {
return await Cloud.isSignedInAlready();
},
updatePrivateKey: async context => {
const hasSetPrivateKey: boolean = await savePrivateKey(
context.walletBindingResponse.walletBindingId,
context.privateKey,
);
if (!hasSetPrivateKey) {
throw new Error('Could not store private key in keystore.');
}
return '';
},
loadDownloadLimitConfig: async context => {
var resp = await getAllConfigurations();
const maxLimit: number = resp.vcDownloadMaxRetry;
@@ -89,17 +78,20 @@ export const VCItemServices = model => {
};
return walletResponse;
},
generateKeyPair: async context => {
if (!isHardwareKeystoreExists) {
return await generateKeys();
}
const isBiometricsEnabled = RNSecureKeystoreModule.hasBiometricsEnabled();
return RNSecureKeystoreModule.generateKeyPair(
VCMetadata.fromVC(context.vcMetadata).id,
isBiometricsEnabled,
0,
);
fetchKeyPair: async context => {
const keyType = context.vcMetadata?.downloadKeyType;
return await fetchKeyPair(keyType);
},
generateKeypairAndStore: async context => {
const keyType = context.vcMetadata?.downloadKeyType;
const keypair = await generateKeyPair(keyType);
if ((keyType != 'ES256' && keyType != 'RS256') || isIOS())
await RNSecureKeystoreModule.storeGenericKey(
keypair.publicKey as string,
keypair.privateKey as string,
keyType,
);
return keypair;
},
requestBindingOTP: async context => {
const response = await request(

View File

@@ -1,5 +1,5 @@
import NetInfo, {NetInfoStateType} from '@react-native-community/netinfo';
import {AppState, AppStateStatus} from 'react-native';
import {AppState, AppStateStatus, NativeModules} from 'react-native';
import {getDeviceId, getDeviceName} from 'react-native-device-info';
import {assign, EventFrom, send, spawn, StateFrom} from 'xstate';
import {createModel} from 'xstate/lib/model';
@@ -19,6 +19,7 @@ import {
changeEsignetUrl,
ESIGNET_BASE_URL,
isAndroid,
isIOS,
MIMOTO_BASE_URL,
SETTINGS_STORE_KEY,
} from '../shared/constants';
@@ -32,6 +33,12 @@ import {
createVcMetaMachine,
vcMetaMachine,
} from './VerifiableCredential/VCMetaMachine/VCMetaMachine';
import {
checkAllKeyPairs,
generateKeyPairsAndStore,
} from '../shared/cryptoutil/cryptoUtil';
const QrLoginIntent = NativeModules.QrLoginIntent;
const model = createModel(
{
@@ -40,6 +47,7 @@ const model = createModel(
isReadError: false,
isDecryptError: false,
isKeyInvalidateError: false,
linkCode: '',
},
{
events: {
@@ -56,6 +64,7 @@ const model = createModel(
APP_INFO_RECEIVED: (info: AppInfo) => ({info}),
STORE_RESPONSE: (response: unknown) => ({response}),
RESET_KEY_INVALIDATE_ERROR_DISMISS: () => ({}),
RESET_LINKCODE: () => ({}),
},
},
);
@@ -78,6 +87,9 @@ export const appMachine = model.createMachine(
DECRYPT_ERROR: {
actions: ['setIsDecryptError'],
},
RESET_LINKCODE: {
actions: ['resetLinkCode'],
},
DECRYPT_ERROR_DISMISS: {
actions: ['unsetIsDecryptError'],
},
@@ -103,13 +115,38 @@ export const appMachine = model.createMachine(
'unsetIsDecryptError',
'resetKeyInvalidateError',
],
target: 'services',
target: 'checkKeyPairs',
},
ERROR: {
actions: ['setIsReadError', 'updateKeyInvalidateError'],
},
},
},
checkKeyPairs: {
invoke: {
src: 'checkKeyPairs',
onDone: [
{
target: 'services',
},
],
onError: [
{
target: 'generateKeyPairs',
},
],
},
},
generateKeyPairs: {
invoke: {
src: 'generateKeyPairsAndStore',
onDone: [
{
target: 'checkKeyPairs',
},
],
},
},
services: {
entry: ['spawnServiceActors', 'logServiceEvents'],
on: {
@@ -166,6 +203,17 @@ export const appMachine = model.createMachine(
checking: {},
active: {
entry: ['forwardToServices'],
invoke: [
{
src: 'isQrLoginByDeepLink',
onDone: {
actions: ['setLinkCode'],
},
},
{
src: 'resetQRLoginDeepLinkData',
},
],
},
inactive: {
entry: ['forwardToServices'],
@@ -198,7 +246,17 @@ export const appMachine = model.createMachine(
},
{
actions: {
forwardToServices: pure((context, event) =>
setLinkCode: assign({
linkCode: (_, event) => {
if (event.data != '')
return new URL(event.data).searchParams.get('linkCode')!!;
return '';
},
}),
resetLinkCode: assign({
linkCode: '',
}),
forwardToSerices: pure((context, event) =>
Object.values(context.serviceRefs).map(serviceRef =>
send({...event, type: `APP_${event.type}`}, {to: serviceRef}),
),
@@ -345,6 +403,15 @@ export const appMachine = model.createMachine(
},
services: {
isQrLoginByDeepLink: () => async () => {
const data = await QrLoginIntent.isQrLoginByDeepLink();
//console.log('DeepLink: ', data);
return data;
},
resetQRLoginDeepLinkData: () => async () => {
return await QrLoginIntent.resetQRLoginDeepLinkData();
},
getAppInfo: () => async callback => {
const appInfo = {
deviceId: getDeviceId(),
@@ -397,6 +464,13 @@ export const appMachine = model.createMachine(
};
},
checkKeyPairs: async () => {
return await checkAllKeyPairs();
},
generateKeyPairsAndStore: async () => {
return await generateKeyPairsAndStore();
},
checkNetworkState: () => callback => {
return NetInfo.addEventListener(state => {
if (state.isConnected) {
@@ -447,3 +521,7 @@ export function selectIsDecryptError(state: State) {
export function selectIsKeyInvalidateError(state: State) {
return state.context.isKeyInvalidateError;
}
export function selectIsLinkCode(state: State) {
return state.context.linkCode;
}

View File

@@ -1,55 +0,0 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkFocusState": "done.invoke.app.ready.focus:invocation[0]";
"checkNetworkState": "done.invoke.app.ready.network:invocation[0]";
"getAppInfo": "done.invoke.app.init.info:invocation[0]";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"forwardToServices": "ACTIVE" | "INACTIVE" | "OFFLINE" | "ONLINE";
"loadCredentialRegistryHostFromStorage": "READY";
"loadCredentialRegistryInConstants": "STORE_RESPONSE";
"loadEsignetHostFromConstants": "STORE_RESPONSE";
"loadEsignetHostFromStorage": "READY";
"logServiceEvents": "READY";
"logStoreEvents": "KEY_INVALIDATE_ERROR" | "RESET_KEY_INVALIDATE_ERROR_DISMISS" | "xstate.init";
"requestDeviceInfo": "REQUEST_DEVICE_INFO";
"resetKeyInvalidateError": "READY" | "RESET_KEY_INVALIDATE_ERROR_DISMISS";
"setAppInfo": "APP_INFO_RECEIVED";
"setIsDecryptError": "DECRYPT_ERROR";
"setIsReadError": "ERROR";
"spawnServiceActors": "READY";
"spawnStoreActor": "KEY_INVALIDATE_ERROR" | "RESET_KEY_INVALIDATE_ERROR_DISMISS" | "xstate.init";
"unsetIsDecryptError": "DECRYPT_ERROR_DISMISS" | "READY";
"unsetIsReadError": "READY";
"updateKeyInvalidateError": "ERROR" | "KEY_INVALIDATE_ERROR";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
};
eventsCausingServices: {
"checkFocusState": "APP_INFO_RECEIVED";
"checkNetworkState": "APP_INFO_RECEIVED";
"getAppInfo": "STORE_RESPONSE";
};
matchesStates: "init" | "init.credentialRegistry" | "init.info" | "init.services" | "init.store" | "ready" | "ready.focus" | "ready.focus.active" | "ready.focus.checking" | "ready.focus.inactive" | "ready.network" | "ready.network.checking" | "ready.network.offline" | "ready.network.online" | "waiting" | { "init"?: "credentialRegistry" | "info" | "services" | "store";
"ready"?: "focus" | "network" | { "focus"?: "active" | "checking" | "inactive";
"network"?: "checking" | "offline" | "online"; }; };
tags: never;
}

View File

@@ -1,3 +1,5 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
@@ -28,11 +30,16 @@ export interface Typegen0 {
requestStoredContext: 'xstate.init';
setBiometrics: 'SETUP_BIOMETRICS';
setContext: 'STORE_RESPONSE';
setInitialDownloadDone: 'INITIAL_DOWNLOAD_DONE';
setIsToggleFromSettings: 'CHANGE_METHOD';
setLanguage: 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE';
setOnboardingDone: 'ONBOARDING_DONE';
setPasscode: 'SETUP_PASSCODE';
setPasscodeSalt: 'done.invoke.auth.introSlider:invocation[0]';
setTourGuide: 'SET_TOUR_GUIDE';
storeContext:
| 'INITIAL_DOWNLOAD_DONE'
| 'ONBOARDING_DONE'
| 'SETUP_BIOMETRICS'
| 'SETUP_PASSCODE'
| 'STORE_RESPONSE'

View File

@@ -46,12 +46,14 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
},
}),
resetLinkCode: model.assign({
linkcode: '',
}),
updateShowFaceAuthConsent: model.assign({
showFaceAuthConsent: (_, event) => {
return event.response || event.response === null;
},
}),
setShowFaceAuthConsent: model.assign({
showFaceAuthConsent: (_, event) => {
return !event.isDoNotAskAgainChecked;
@@ -230,6 +232,10 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => {
linkCode: (_, event) =>
new URL(event.params).searchParams.get('linkCode'),
}),
setLinkCodeFromDeepLink: assign({
linkCode: (_, event) => event.linkCode,
}),
setQuickShareData: assign({
quickShareData: (_, event) =>
JSON.parse(

View File

@@ -82,6 +82,20 @@ export const scanMachine =
cond: 'isMinimumStorageRequiredForAuditEntryReached',
target: 'restrictSharingVc',
},
{
target: 'qrLoginViaDeepLink',
},
],
},
},
qrLoginViaDeepLink: {
on: {
QRLOGIN_VIA_DEEP_LINK: [
{
actions: ['setChildRef', 'setLinkCodeFromDeepLink'],
cond: (_, event) => event.linkCode != '',
target: '#scan.showQrLogin',
},
{
target: 'startPermissionCheck',
},
@@ -362,7 +376,10 @@ export const scanMachine =
},
},
on: {
DISMISS: '#scan.checkFaceAuthConsent',
DISMISS: {
target: '#scan.checkFaceAuthConsent',
actions: ['resetLinkCode'],
},
},
initial: 'idle',
states: {
@@ -385,6 +402,7 @@ export const scanMachine =
getStartEventData(TelemetryConstants.FlowType.qrLogin),
),
],
exit: ['resetLinkCode'],
},
connecting: {
invoke: {

View File

@@ -18,6 +18,7 @@ export interface Typegen0 {
type: 'xstate.after(DESTROY_TIMEOUT)#scan.clearingConnection';
};
'xstate.init': {type: 'xstate.init'};
'xstate.stop': {type: 'xstate.stop'};
};
invokeSrcNameMap: {
checkBluetoothPermission: 'done.invoke.scan.checkBluetoothPermission.checking:invocation[0]';
@@ -55,6 +56,7 @@ export interface Typegen0 {
| 'removeLoggers'
| 'resetFaceCaptureBannerStatus'
| 'resetFlowType'
| 'resetLinkCode'
| 'resetSelectedVc'
| 'resetShowQuickShareSuccessBanner'
| 'sendBLEConnectionErrorEvent'
@@ -67,6 +69,7 @@ export interface Typegen0 {
| 'setChildRef'
| 'setFlowType'
| 'setLinkCode'
| 'setLinkCodeFromDeepLink'
| 'setQuickShareData'
| 'setReadyForBluetoothStateCheck'
| 'setReceiverInfo'
@@ -132,7 +135,10 @@ export interface Typegen0 {
| 'SCREEN_BLUR'
| 'STORE_RESPONSE'
| 'xstate.init';
resetFaceCaptureBannerStatus: 'ACCEPT_REQUEST' | 'CLOSE_BANNER';
resetFaceCaptureBannerStatus:
| 'ACCEPT_REQUEST'
| 'CLOSE_BANNER'
| 'STORE_RESPONSE';
resetFlowType:
| 'DISCONNECT'
| 'DISMISS'
@@ -141,6 +147,15 @@ export interface Typegen0 {
| 'RESET'
| 'SCREEN_BLUR'
| 'xstate.init';
resetLinkCode:
| 'BLE_ERROR'
| 'DISMISS'
| 'DISMISS_QUICK_SHARE_BANNER'
| 'RESET'
| 'SCREEN_BLUR'
| 'SCREEN_FOCUS'
| 'SELECT_VC'
| 'xstate.stop';
resetSelectedVc:
| 'DISCONNECT'
| 'DISMISS'
@@ -151,15 +166,16 @@ export interface Typegen0 {
| 'xstate.init';
resetShowQuickShareSuccessBanner: 'DISMISS' | 'DISMISS_QUICK_SHARE_BANNER';
sendBLEConnectionErrorEvent: 'BLE_ERROR';
sendScanData: 'SCAN';
sendScanData: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN';
sendVCShareFlowCancelEndEvent: 'CANCEL';
sendVCShareFlowTimeoutEndEvent: 'CANCEL' | 'RETRY';
sendVcShareSuccessEvent: 'VC_ACCEPTED';
sendVcSharingStartEvent: 'SCAN';
setBleError: 'BLE_ERROR';
setChildRef: 'STORE_RESPONSE';
setChildRef: 'QRLOGIN_VIA_DEEP_LINK' | 'STORE_RESPONSE';
setFlowType: 'SELECT_VC';
setLinkCode: 'SCAN';
setLinkCodeFromDeepLink: 'QRLOGIN_VIA_DEEP_LINK';
setQuickShareData: 'SCAN';
setReadyForBluetoothStateCheck: 'BLUETOOTH_PERMISSION_ENABLED';
setReceiverInfo: 'CONNECTED';
@@ -194,7 +210,7 @@ export interface Typegen0 {
uptoAndroid11: '' | 'START_PERMISSION_CHECK';
};
eventsCausingServices: {
QrLogin: 'SCAN';
QrLogin: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN';
checkBluetoothPermission:
| ''
| 'BLUETOOTH_STATE_DISABLED'
@@ -251,6 +267,7 @@ export interface Typegen0 {
| 'loadVCS.idle'
| 'loadVCS.navigatingToHome'
| 'nearByDevicesPermissionDenied'
| 'qrLoginViaDeepLink'
| 'recheckBluetoothState'
| 'recheckBluetoothState.checking'
| 'recheckBluetoothState.enabled'

View File

@@ -55,6 +55,7 @@ const ScanEvents = {
}),
ALLOWED: () => ({}),
DENIED: () => ({}),
QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}),
};
export const ScanModel = createModel(

View File

@@ -125,3 +125,7 @@ export function selectIsMinimumStorageRequiredForAuditEntryLimitReached(
export function selectIsFaceVerificationConsent(state: State) {
return state.matches('reviewing.faceVerificationConsent');
}
export function selectIsQrLoginViaDeepLink(state: State) {
return state.matches('qrLoginViaDeepLink');
}

View File

@@ -1,4 +1,3 @@
import * as Keychain from 'react-native-keychain';
import Storage, {MMKV} from '../shared/storage';
import binaryToBase64 from 'react-native/Libraries/Utilities/binaryToBase64';
import {
@@ -10,8 +9,7 @@ import {
StateFrom,
} from 'xstate';
import {createModel} from 'xstate/lib/model';
import {generateSecureRandom} from 'react-native-securerandom';
import {error, log} from 'xstate/lib/actions';
import {log} from 'xstate/lib/actions';
import {
isIOS,
MY_VCS_STORE_KEY,
@@ -37,7 +35,6 @@ import {
sendErrorEvent,
getErrorEventData,
} from '../shared/telemetry/TelemetryUtils';
import RNSecureKeyStore from 'react-native-secure-key-store';
import {Buffer} from 'buffer';
import {VC} from './VerifiableCredential/VCMetaMachine/vc';
@@ -100,13 +97,41 @@ export const storeMachine =
events: {} as EventFrom<typeof model>,
},
id: 'store',
initial: !isHardwareKeystoreExists
? 'gettingEncryptionKey'
: 'checkEncryptionKey',
initial: 'checkFreshInstall',
states: {
checkFreshInstall: {
invoke: {
src: 'checkFreshInstall',
},
on: {
READY: [
{
cond: 'hasData',
target: !isHardwareKeystoreExists
? 'gettingEncryptionKey'
: 'checkEncryptionKey',
},
{
target: 'clearIosKeys',
},
],
},
},
clearIosKeys: {
invoke: {
src: 'clearKeys',
onDone: [
{
target: !isHardwareKeystoreExists
? 'gettingEncryptionKey'
: 'checkEncryptionKey',
},
],
},
},
checkEncryptionKey: {
invoke: {
src: 'hasAndroidEncryptionKey',
src: 'hasEncryptionKey',
},
on: {
READY: {
@@ -298,15 +323,32 @@ export const storeMachine =
services: {
clear: () => clear(),
hasAndroidEncryptionKey: () => async callback => {
const hasSetCredentials =
RNSecureKeystoreModule.hasAlias(ENCRYPTION_ID);
clearKeys: () => async _ => {
if (isIOS()) {
const {RNSecureKeystoreModule} = NativeModules;
await RNSecureKeystoreModule.clearKeys();
}
return;
},
checkFreshInstall: () => async callback => {
const response = await getItem('auth', null, '');
callback(model.events.READY());
},
hasEncryptionKey: () => async callback => {
let hasSetCredentials;
try {
hasSetCredentials = await RNSecureKeystoreModule.hasAlias(
ENCRYPTION_ID,
);
} catch (e) {
hasSetCredentials = false;
}
if (hasSetCredentials) {
try {
const base64EncodedString =
Buffer.from('Dummy').toString('base64');
await RNSecureKeystoreModule.encryptData(
DUMMY_KEY_FOR_BIOMETRIC_ALIAS,
ENCRYPTION_ID,
base64EncodedString,
);
} catch (e) {
@@ -492,7 +534,7 @@ export const storeMachine =
if (isIOS()) {
RNSecureKeyStore.setResetOnAppUninstallTo(false);
}
const existingCredentials = await Keychain.getGenericPassword();
const existingCredentials = '';
if (existingCredentials) {
console.log('Credentials successfully loaded for user');
callback(model.events.KEY_RECEIVED(existingCredentials.password));
@@ -513,50 +555,30 @@ export const storeMachine =
}
},
generateEncryptionKey: () => async callback => {
const randomBytes = await generateSecureRandom(32);
const randomBytesString = binaryToBase64(randomBytes);
if (!isHardwareKeystoreExists) {
const hasSetCredentials = await Keychain.setGenericPassword(
ENCRYPTION_ID,
randomBytesString,
callback(
model.events.ERROR(
new Error('Could not generate keychain credentials.'),
),
);
if (hasSetCredentials) {
callback(model.events.KEY_RECEIVED(randomBytesString));
} else {
sendErrorEvent(
getErrorEventData(
TelemetryConstants.FlowType.fetchData,
'',
'Could not generate keychain credentials',
),
);
callback(
model.events.ERROR(
new Error('Could not generate keychain credentials.'),
),
);
}
} else {
const isBiometricsEnabled =
RNSecureKeystoreModule.hasBiometricsEnabled();
await RNSecureKeystoreModule.hasBiometricsEnabled();
await RNSecureKeystoreModule.generateKey(
ENCRYPTION_ID,
isBiometricsEnabled,
AUTH_TIMEOUT,
);
RNSecureKeystoreModule.generateHmacshaKey(HMAC_ALIAS);
RNSecureKeystoreModule.generateKey(
DUMMY_KEY_FOR_BIOMETRIC_ALIAS,
isBiometricsEnabled,
0,
);
callback(model.events.KEY_RECEIVED(''));
}
},
},
guards: {
hasData: (_, event: any) => {
return event.data !== null;
},
isCustomSecureKeystore: () => isHardwareKeystoreExists,
},
},

View File

@@ -1,48 +1,85 @@
// This file was automatically generated. Edits will be overwritten
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"done.invoke._store": { type: "done.invoke._store"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.store.resettingStorage:invocation[0]": { type: "done.invoke.store.resettingStorage:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform._store": { type: "error.platform._store"; data: unknown };
"xstate.init": { type: "xstate.init" };
};
invokeSrcNameMap: {
"checkStorageInitialisedOrNot": "done.invoke.store.checkStorageInitialisation:invocation[0]";
"clear": "done.invoke.store.resettingStorage:invocation[0]";
"generateEncryptionKey": "done.invoke.store.generatingEncryptionKey:invocation[0]";
"getEncryptionKey": "done.invoke.store.gettingEncryptionKey:invocation[0]";
"hasAndroidEncryptionKey": "done.invoke.store.checkEncryptionKey:invocation[0]";
"store": "done.invoke._store";
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
"forwardStoreRequest": "APPEND" | "CLEAR" | "EXPORT" | "FETCH_ALL_WELLKNOWN_CONFIG" | "GET" | "GET_VCS_DATA" | "PREPEND" | "REMOVE" | "REMOVE_ITEMS" | "REMOVE_VC_METADATA" | "RESTORE_BACKUP" | "SET" | "UPDATE";
"notifyParent": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]";
"setEncryptionKey": "KEY_RECEIVED";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"isCustomSecureKeystore": "KEY_RECEIVED";
};
eventsCausingServices: {
"checkStorageInitialisedOrNot": "ERROR";
"clear": "KEY_RECEIVED";
"generateEncryptionKey": "ERROR" | "IGNORE" | "READY";
"getEncryptionKey": "TRY_AGAIN";
"hasAndroidEncryptionKey": never;
"store": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]";
};
matchesStates: "checkEncryptionKey" | "checkStorageInitialisation" | "failedReadingKey" | "generatingEncryptionKey" | "gettingEncryptionKey" | "ready" | "resettingStorage";
tags: never;
}
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
'done.invoke._store': {
type: 'done.invoke._store';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'done.invoke.store.resettingStorage:invocation[0]': {
type: 'done.invoke.store.resettingStorage:invocation[0]';
data: unknown;
__tip: 'See the XState TS docs to learn how to strongly type this.';
};
'error.platform._store': {type: 'error.platform._store'; data: unknown};
'xstate.init': {type: 'xstate.init'};
};
invokeSrcNameMap: {
checkFreshInstall: 'done.invoke.store.checkFreshInstall:invocation[0]';
checkStorageInitialisedOrNot: 'done.invoke.store.checkStorageInitialisation:invocation[0]';
clear: 'done.invoke.store.resettingStorage:invocation[0]';
clearKeys: 'done.invoke.store.clearIosKeys:invocation[0]';
generateEncryptionKey: 'done.invoke.store.generatingEncryptionKey:invocation[0]';
getEncryptionKey: 'done.invoke.store.gettingEncryptionKey:invocation[0]';
hasEncryptionKey: 'done.invoke.store.checkEncryptionKey:invocation[0]';
store: 'done.invoke._store';
};
missingImplementations: {
actions: never;
delays: never;
guards: never;
services: never;
};
eventsCausingActions: {
forwardStoreRequest:
| 'APPEND'
| 'CLEAR'
| 'EXPORT'
| 'FETCH_ALL_WELLKNOWN_CONFIG'
| 'GET'
| 'GET_VCS_DATA'
| 'PREPEND'
| 'REMOVE'
| 'REMOVE_ITEMS'
| 'REMOVE_VC_METADATA'
| 'RESTORE_BACKUP'
| 'SET'
| 'UPDATE';
notifyParent:
| 'KEY_RECEIVED'
| 'READY'
| 'done.invoke.store.resettingStorage:invocation[0]';
setEncryptionKey: 'KEY_RECEIVED';
};
eventsCausingDelays: {};
eventsCausingGuards: {
hasData: 'READY';
isCustomSecureKeystore: 'KEY_RECEIVED';
};
eventsCausingServices: {
checkFreshInstall: 'xstate.init';
checkStorageInitialisedOrNot: 'ERROR';
clear: 'KEY_RECEIVED';
clearKeys: 'READY';
generateEncryptionKey: 'ERROR' | 'IGNORE' | 'READY';
getEncryptionKey: 'TRY_AGAIN';
hasEncryptionKey: never;
store:
| 'KEY_RECEIVED'
| 'READY'
| 'done.invoke.store.resettingStorage:invocation[0]';
};
matchesStates:
| 'checkEncryptionKey'
| 'checkFreshInstall'
| 'checkStorageInitialisation'
| 'clearIosKeys'
| 'failedReadingKey'
| 'generatingEncryptionKey'
| 'gettingEncryptionKey'
| 'ready'
| 'resettingStorage';
tags: never;
}

137
package-lock.json generated
View File

@@ -18,6 +18,8 @@
"@expo/metro-config": "~0.10.0",
"@invertase/react-native-apple-authentication": "^2.3.0",
"@iriscan/biometric-sdk-react-native": "0.2.6",
"@noble/hashes": "^1.4.0",
"@noble/secp256k1": "2.0.0",
"@react-native-clipboard/clipboard": "^1.10.0",
"@react-native-community/image-editor": "^4.2.0",
"@react-native-community/netinfo": "9.3.7",
@@ -28,6 +30,7 @@
"@react-navigation/native-stack": "^6.1.0",
"@robinbobin/react-native-google-drive-api-wrapper": "^1.2.4",
"@xstate/react": "^3.0.1",
"asn1.js": "^5.4.1",
"base45-web": "^1.0.2",
"base64url-universal": "^1.1.0",
"buffer": "^6.0.3",
@@ -70,8 +73,8 @@
"react-native-elements": "3.4.3",
"react-native-fs": "^2.18.0",
"react-native-gesture-handler": "~2.9.0",
"react-native-get-random-values": "^1.11.0",
"react-native-image-colors": "^2.4.0",
"react-native-keychain": "^8.0.0",
"react-native-linear-gradient": "^2.8.0",
"react-native-localize": "^3.0.2",
"react-native-location": "^2.5.0",
@@ -83,7 +86,6 @@
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.1",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-spinkit": "^1.5.1",
@@ -5851,6 +5853,30 @@
"eslint-scope": "5.1.1"
}
},
"node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/secp256k1": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz",
"integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -9685,6 +9711,18 @@
"resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz",
"integrity": "sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA=="
},
"node_modules/asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"license": "MIT",
"dependencies": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/asn1js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.3.2.tgz",
@@ -10437,6 +10475,12 @@
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="
},
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@@ -14321,6 +14365,7 @@
"version": "13.3.0",
"resolved": "https://registry.npmjs.org/expo-local-authentication/-/expo-local-authentication-13.3.0.tgz",
"integrity": "sha512-HZ2L9GOQGooV+6kT2wLrR42BlxczT4N18kPY6HF82S31/a/YHslgUUt1lmHNU64NJViSCOBaTeVCjh8t/BeNEA==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
},
@@ -14890,6 +14935,12 @@
"node": "> 0.1.90"
}
},
"node_modules/fast-base64-decode": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz",
"integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -22347,6 +22398,12 @@
"node": ">=4"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -25117,6 +25174,18 @@
"react-native": "*"
}
},
"node_modules/react-native-get-random-values": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz",
"integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==",
"license": "MIT",
"dependencies": {
"fast-base64-decode": "^1.0.0"
},
"peerDependencies": {
"react-native": ">=0.56"
}
},
"node_modules/react-native-gradle-plugin": {
"version": "0.71.19",
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.19.tgz",
@@ -25135,11 +25204,6 @@
"react-native": "*"
}
},
"node_modules/react-native-keychain": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-8.0.0.tgz",
"integrity": "sha512-c7Cs+YQN26UaQsRG1dmlXL7VL2ctnXwH/dl0IOMEQ7ZaL2NdN313YSAI8ZEZZjrVhNmPsyWEuvTFqWrdpItqQg=="
},
"node_modules/react-native-linear-gradient": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
@@ -25285,11 +25349,6 @@
"react-native": "*"
}
},
"node_modules/react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"node_modules/react-native-securerandom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",
@@ -34069,6 +34128,16 @@
"eslint-scope": "5.1.1"
}
},
"@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="
},
"@noble/secp256k1": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz",
"integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -36908,6 +36977,17 @@
"resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz",
"integrity": "sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA=="
},
"asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"asn1js": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.3.2.tgz",
@@ -37501,6 +37581,11 @@
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="
},
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@@ -40953,6 +41038,11 @@
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="
},
"fast-base64-decode": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz",
"integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q=="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -46517,6 +46607,11 @@
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -48604,6 +48699,14 @@
"prop-types": "^15.7.2"
}
},
"react-native-get-random-values": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz",
"integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==",
"requires": {
"fast-base64-decode": "^1.0.0"
}
},
"react-native-gradle-plugin": {
"version": "0.71.19",
"resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.19.tgz",
@@ -48617,11 +48720,6 @@
"node-vibrant": "3.1.6"
}
},
"react-native-keychain": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-8.0.0.tgz",
"integrity": "sha512-c7Cs+YQN26UaQsRG1dmlXL7VL2ctnXwH/dl0IOMEQ7ZaL2NdN313YSAI8ZEZZjrVhNmPsyWEuvTFqWrdpItqQg=="
},
"react-native-linear-gradient": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz",
@@ -48714,11 +48812,6 @@
"warn-once": "^0.1.0"
}
},
"react-native-secure-key-store": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-native-secure-key-store/-/react-native-secure-key-store-2.0.10.tgz",
"integrity": "sha512-K7aVlIGxyklnjhCidVexVgZF3LsgUD9GIxMy2NB/xkQsS9E2SJWkD/fJ56e25L2I6a9Mp1zuJrKnCtfBs1CvAw=="
},
"react-native-securerandom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-1.0.1.tgz",

View File

@@ -21,6 +21,8 @@
"@expo/metro-config": "~0.10.0",
"@invertase/react-native-apple-authentication": "^2.3.0",
"@iriscan/biometric-sdk-react-native": "0.2.6",
"@noble/hashes": "^1.4.0",
"@noble/secp256k1": "2.0.0",
"@react-native-clipboard/clipboard": "^1.10.0",
"@react-native-community/image-editor": "^4.2.0",
"@react-native-community/netinfo": "9.3.7",
@@ -31,6 +33,7 @@
"@react-navigation/native-stack": "^6.1.0",
"@robinbobin/react-native-google-drive-api-wrapper": "^1.2.4",
"@xstate/react": "^3.0.1",
"asn1.js": "^5.4.1",
"base45-web": "^1.0.2",
"base64url-universal": "^1.1.0",
"buffer": "^6.0.3",
@@ -73,8 +76,8 @@
"react-native-elements": "3.4.3",
"react-native-fs": "^2.18.0",
"react-native-gesture-handler": "~2.9.0",
"react-native-get-random-values": "^1.11.0",
"react-native-image-colors": "^2.4.0",
"react-native-keychain": "^8.0.0",
"react-native-linear-gradient": "^2.8.0",
"react-native-localize": "^3.0.2",
"react-native-location": "^2.5.0",
@@ -86,7 +89,6 @@
"react-native-rsa-native": "^2.0.5",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-secure-key-store": "^2.0.10",
"react-native-securerandom": "^1.0.1",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-spinkit": "^1.5.1",

View File

@@ -12,8 +12,8 @@ import {NotificationsScreen} from '../screens/NotificationsScreen';
import {SetupLanguageScreen} from '../screens/SetupLanguageScreen';
import {IntroSlidersScreen} from '../screens/Home/IntroSlidersScreen';
import {RequestLayout} from '../screens/Request/RequestLayout';
import {RequestStackParamList} from '../screens/Request/RequestLayoutController';
import {SplashScreen} from '../screens/SplashScreen';
import {RequestStackParamList} from './routesConstants';
export const baseRoutes: Screen[] = [
{

View File

@@ -1,4 +1,4 @@
import React, {useContext} from 'react';
import React, {useContext, useEffect} from 'react';
import {
BottomTabNavigationOptions,
createBottomTabNavigator,
@@ -17,9 +17,18 @@ import {CopilotProvider} from 'react-native-copilot';
import {View} from 'react-native';
import {CopilotTooltip} from '../components/CopilotTooltip';
import {Copilot} from '../components/ui/Copilot';
import {useSelector} from '@xstate/react';
import {selectIsLinkCode} from '../machines/app';
import {NavigationProp, useNavigation} from '@react-navigation/native';
import {BOTTOM_TAB_ROUTES, ScanStackParamList} from '../routes/routesConstants';
import {MainBottomTabParamList} from '../routes/routeTypes';
const {Navigator, Screen} = createBottomTabNavigator();
type ScanLayoutNavigation = NavigationProp<
ScanStackParamList & MainBottomTabParamList
>;
export const MainLayout: React.FC = () => {
const {t} = useTranslation('MainLayout');
@@ -31,6 +40,15 @@ export const MainLayout: React.FC = () => {
tabBarActiveTintColor: Theme.Colors.IconBg,
...Theme.BottomTabBarStyle,
};
const navigation = useNavigation<ScanLayoutNavigation>();
const linkCode = useSelector(appService, selectIsLinkCode);
useEffect(() => {
if (linkCode != '') {
navigation.navigate(BOTTOM_TAB_ROUTES.share);
}
}, [linkCode]);
return (
<CopilotProvider

View File

@@ -24,6 +24,7 @@ import {
selectIsFaceIdentityVerified,
selectCredential,
selectVerifiableCredentialData,
selectIsQrLoginViaDeepLink,
} from '../../machines/bleShare/scan/scanSelectors';
import {
selectBleError,
@@ -43,6 +44,7 @@ import {BOTTOM_TAB_ROUTES, SCAN_ROUTES} from '../../routes/routesConstants';
import {ScanStackParamList} from '../../routes/routesConstants';
import {VCShareFlowType} from '../../shared/Utils';
import {Theme} from '../../components/ui/styleUtils';
import {APP_EVENTS, selectIsLinkCode} from '../../machines/app';
type ScanLayoutNavigation = NavigationProp<
ScanStackParamList & MainBottomTabParamList
@@ -75,6 +77,10 @@ export function useScanLayout() {
scanService,
selectVerifiableCredentialData,
);
const isQrLoginViaDeepLink = useSelector(
scanService,
selectIsQrLoginViaDeepLink,
);
const locationError = {message: '', button: ''};
@@ -121,6 +127,7 @@ export function useScanLayout() {
scanService,
selectIsExchangingDeviceInfoTimeout,
);
const linkCode = useSelector(appService, selectIsLinkCode);
const isAccepted = useSelector(scanService, selectIsAccepted);
const isRejected = useSelector(scanService, selectIsRejected);
const isSent = useSelector(scanService, selectIsSent);
@@ -262,6 +269,9 @@ export function useScanLayout() {
if (isDone) {
changeTabBarVisible('flex');
navigation.navigate(BOTTOM_TAB_ROUTES.home);
} else if (isQrLoginViaDeepLink) {
scanService.send(ScanEvents.QRLOGIN_VIA_DEEP_LINK(linkCode));
appService.send(APP_EVENTS.RESET_LINKCODE());
} else if (
isReviewing &&
flowType === VCShareFlowType.SIMPLE_SHARE &&
@@ -284,6 +294,7 @@ export function useScanLayout() {
isBleError,
flowType,
isAccepted,
isQrLoginViaDeepLink,
]);
return {

View File

@@ -2,16 +2,11 @@ import {
GoogleSignin,
statusCodes,
} from '@react-native-google-signin/google-signin';
import RNSecureStorage, {ACCESSIBLE} from 'react-native-secure-key-store';
import {CloudStorage, CloudStorageScope} from 'react-native-cloud-storage';
import {GOOGLE_ANDROID_CLIENT_ID} from 'react-native-dotenv';
import {readFile, writeFile} from 'react-native-fs';
import {BackupDetails} from '../types/backup-and-restore/backup';
import {
AppleButton,
appleAuth,
} from '@invertase/react-native-apple-authentication';
import jwt_decode from 'jwt-decode';
import {appleAuth} from '@invertase/react-native-apple-authentication';
import {bytesToMB, sleep} from './commonUtil';
import {
IOS_SIGNIN_FAILED,
@@ -21,6 +16,7 @@ import {
} from './constants';
import fileStorage, {backupDirectoryPath, zipFilePath} from './fileStorage';
import {API} from './api';
import {NativeModules} from 'react-native';
class Cloud {
static status = {
@@ -133,6 +129,7 @@ class Cloud {
}
static async signIn(): Promise<SignInResult | IsIOSResult> {
const {RNSecureKeystoreModule} = NativeModules;
if (isIOS()) {
let profileInfo;
@@ -145,10 +142,9 @@ class Cloud {
const {email, nonce, identityToken, realUserStatus /* etc */} =
appleAuthRequestResponse;
profileInfo = {email: email, picture: null};
await RNSecureStorage.set(
await RNSecureKeystoreModule.storeData(
'userIdentifier',
JSON.stringify(appleAuthRequestResponse),
{accessible: ACCESSIBLE.WHEN_UNLOCKED},
);
return {status: this.status.SUCCESS, profileInfo: profileInfo};
@@ -203,11 +199,14 @@ class Cloud {
}
static async isSignedInAlready(): Promise<isSignedInResult> {
const {RNSecureKeystoreModule} = NativeModules;
try {
if (isIOS()) {
const isSignedIn = await CloudStorage.isCloudAvailable();
const userIdentifier = await RNSecureStorage.get('userIdentifier');
const userIdentifier = await RNSecureKeystoreModule.getData(
'userIdentifier',
)[0];
const userToken = JSON.parse(userIdentifier + '');
const user = userToken.user;
const email = userToken.email;

View File

@@ -24,6 +24,7 @@ export class VCMetadata {
displayId: string = '';
format: string = '';
downloadKeyType: string = '';
constructor({
idType = '',
requestId = '',
@@ -35,6 +36,7 @@ export class VCMetadata {
isVerified = false,
displayId = '',
format = '',
downloadKeyType = '',
} = {}) {
this.idType = idType;
this.requestId = requestId;
@@ -46,6 +48,7 @@ export class VCMetadata {
this.isVerified = isVerified;
this.displayId = displayId;
this.format = format;
this.downloadKeyType = downloadKeyType;
}
//TODO: Remove any typing and use appropriate typing
@@ -65,6 +68,7 @@ export class VCMetadata {
: vc.vcMetadata
? vc.vcMetadata.displayId
: getDisplayId(vc.verifiableCredential),
downloadKeyType: vc.downloadKeyType,
});
}
@@ -104,7 +108,7 @@ export function parseMetadatas(metadataStrings: object[]) {
return metadataStrings.map(o => new VCMetadata(o));
}
export const getVCMetadata = (context: object) => {
export const getVCMetadata = (context: object, keyType: string) => {
const [issuer, protocol, credentialId] =
context.credentialWrapper?.identifier.split(':');
@@ -117,6 +121,7 @@ export const getVCMetadata = (context: object) => {
isVerified: context.vcMetadata.isVerified ?? false,
displayId: getDisplayId(context.verifiableCredential),
format: context.credentialWrapper.format,
downloadKeyType: keyType,
});
};

View File

@@ -5,7 +5,6 @@ import {
COMMON_PROPS_KEY,
} from './constants';
import {INITIAL_CONFIG} from './InitialConfig';
import Keychain from 'react-native-keychain';
import {getItem, setItem} from '../machines/store';
import {faceMatchConfig} from './commonUtil';
import {configure} from '@iriscan/biometric-sdk-react-native';
@@ -193,19 +192,14 @@ async function generateCacheAPIFunctionWithCachePreference(
fetchCall: (...props: any[]) => any,
onErrorHardCodedValue?: any,
) {
const existingCredentials = await Keychain.getGenericPassword();
try {
const response = await getItem(
cacheKey,
null,
existingCredentials?.password,
);
const response = await getItem(cacheKey, null, '');
if (response) {
return response;
} else {
const response = await fetchCall();
setItem(cacheKey, response, existingCredentials?.password).then(() =>
setItem(cacheKey, response, '').then(() =>
console.log('Cached response for ' + cacheKey),
);
@@ -231,10 +225,9 @@ async function generateCacheAPIFunctionWithAPIPreference(
fetchCall: (...props: any[]) => any,
onErrorHardCodedValue?: any,
) {
const existingCredentials = await Keychain.getGenericPassword();
try {
const response = await fetchCall();
setItem(cacheKey, response, existingCredentials.password).then(() =>
setItem(cacheKey, response, '').then(() =>
console.log('Cached response for ' + cacheKey),
);
return response;
@@ -246,11 +239,7 @@ async function generateCacheAPIFunctionWithAPIPreference(
console.log(error);
const response = await getItem(
cacheKey,
null,
existingCredentials.password,
);
const response = await getItem(cacheKey, null, '');
if (response) {
return response;

View File

@@ -0,0 +1,6 @@
export enum KeyTypes {
RS256 = 'RS256',
ES256 = 'ES256',
ES256K = 'ES256K',
ED25519 = 'ED25519',
}

View File

@@ -1,27 +1,167 @@
import {KeyPair, RSA} from 'react-native-rsa-native';
import {RSA} from 'react-native-rsa-native';
import forge from 'node-forge';
import {BIOMETRIC_CANCELLED, DEBUG_MODE_ENABLED, isIOS} from '../constants';
import jose from 'node-jose';
import {
BIOMETRIC_CANCELLED,
DEBUG_MODE_ENABLED,
isAndroid,
isIOS,
} from '../constants';
import {NativeModules} from 'react-native';
import {BiometricCancellationError} from '../error/BiometricCancellationError';
import {EncryptedOutput} from './encryptedOutput';
import {Buffer} from 'buffer';
import base64url from 'base64url';
import {hmac} from '@noble/hashes/hmac';
import {sha256} from '@noble/hashes/sha256';
import 'react-native-get-random-values';
import * as secp from '@noble/secp256k1';
import base64 from 'react-native-base64';
import {KeyTypes} from './KeyTypes';
import convertDerToRsFormat from './signFormatConverter';
//polyfills setup
secp.etc.hmacSha256Sync = (k, ...m) =>
hmac(sha256, k, secp.etc.concatBytes(...m));
secp.etc.hmacSha256Async = (k, ...m) =>
Promise.resolve(secp.etc.hmacSha256Sync(k, ...m));
const {RNSecureKeystoreModule} = NativeModules;
// 5min
export const AUTH_TIMEOUT = 5 * 60;
export const ENCRYPTION_ID = 'c7c22a6c-9759-4605-ac88-46f4041d863d';
export const ENCRYPTION_ID = 'c7c22a6c-9759-4605-ac88-46f4041d863k';
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(2048));
export async function generateKeyPairRSA() {
if (isAndroid() && isHardwareKeystoreExists) {
const isBiometricsEnabled =
await RNSecureKeystoreModule.hasBiometricsEnabled();
return {
publicKey: await RNSecureKeystoreModule.generateKeyPair(
KeyTypes.RS256,
KeyTypes.RS256,
isBiometricsEnabled,
0,
),
privateKey: '',
};
}
const keyPair = await Promise.resolve(RSA.generateKeys(2048));
return {
publicKey: keyPair.public,
privateKey: keyPair.private,
};
}
export function generateKeyPairECK1() {
const privKey = secp.utils.randomPrivateKey();
const decoder = new TextDecoder();
const pubKey = secp.getPublicKey(privKey, false);
return {
publicKey: Buffer.from(pubKey).toString('base64'),
privateKey: Buffer.from(privKey).toString('base64'),
};
}
export async function generateKeyPairECR1() {
if (isAndroid()) {
const isBiometricsEnabled =
await RNSecureKeystoreModule.hasBiometricsEnabled();
return {
publicKey: await RNSecureKeystoreModule.generateKeyPair(
KeyTypes.ES256,
KeyTypes.ES256,
isBiometricsEnabled,
0,
),
privateKey: '',
};
}
const keystore = jose.JWK.createKeyStore();
const key = await keystore.generate('EC', 'P-256');
const jwkPublicKey = key.toJSON(); // Public key JWK
const jwkPrivateKey = key.toJSON(true); // Private key JWK (include private part)
return {
publicKey: JSON.stringify(jwkPublicKey),
privateKey: JSON.stringify(jwkPrivateKey),
};
}
export async function generateKeyPairED() {
return {
privateKey: '',
publicKey: '',
};
}
export async function generateKeyPair(keyType: any): Promise<any> {
switch (keyType) {
case KeyTypes.RS256:
return generateKeyPairRSA();
case KeyTypes.ES256:
return generateKeyPairECR1();
case KeyTypes.ES256K:
return generateKeyPairECK1();
case KeyTypes.ED25519:
return generateKeyPairED();
default:
break;
}
}
export async function checkAllKeyPairs() {
const RSAKey = await fetchKeyPair(KeyTypes.RS256);
const ECR1Key = await fetchKeyPair(KeyTypes.ES256);
const ECK1Key = await fetchKeyPair(KeyTypes.ES256K);
const EDKey = 'key';
if (
!(
!!RSAKey.publicKey &&
!!ECR1Key.publicKey &&
!!ECK1Key.publicKey &&
!!EDKey
)
)
throw Error('Keys not present');
}
export async function generateKeyPairsAndStore() {
const {RNSecureKeystoreModule} = NativeModules;
const RSAKeyPair = await generateKeyPair(KeyTypes.RS256);
const ECR1KeyPair = await generateKeyPair(KeyTypes.ES256);
const ECK1KeyPair = await generateKeyPair(KeyTypes.ES256K);
//const EDKeyPair = generateKeyPair(KeyTypes.ED25519);
await RNSecureKeystoreModule.storeGenericKey(
ECK1KeyPair.publicKey,
ECK1KeyPair.privateKey,
KeyTypes.ES256K,
);
// await RNSecureKeystoreModule.storeGenericKey(
// EDKeyPair.publicKey,
// EDKeyPair.privateKey,
// KeyTypes.ED25519,
// );
if (isIOS()) {
await RNSecureKeystoreModule.storeGenericKey(
RSAKeyPair.publicKey,
RSAKeyPair.privateKey,
KeyTypes.RS256,
);
await RNSecureKeystoreModule.storeGenericKey(
ECR1KeyPair.publicKey,
ECR1KeyPair.privateKey,
KeyTypes.ES256,
);
}
}
/**
* isCustomKeystore is a cached check of existence of a hardware keystore.
*/
const {RNSecureKeystoreModule} = NativeModules;
export const isHardwareKeystoreExists = isCustomSecureKeystore();
export async function getJWT(
@@ -29,12 +169,21 @@ export async function getJWT(
payLoad: object,
alias: string,
privateKey: string,
keyType: string,
) {
try {
const header64 = encodeB64(JSON.stringify(header));
const payLoad64 = encodeB64(JSON.stringify(payLoad));
const preHash = header64 + '.' + payLoad64;
const signature64 = await createSignature(privateKey, preHash, alias);
const signature64 = await createSignature(
privateKey,
alias,
preHash,
keyType,
header,
payLoad,
);
if (keyType == KeyTypes.ES256 && isIOS()) return signature64;
return header64 + '.' + payLoad64 + '.' + signature64;
} catch (e) {
console.error('Exception Occurred While Constructing JWT ', e);
@@ -43,32 +192,97 @@ export async function getJWT(
}
export async function createSignature(
privateKey: string,
preHash: string,
alias: string,
privateKey,
alias,
preHash,
keyType: string,
header,
payload,
) {
switch (keyType) {
case KeyTypes.RS256:
return createSignatureRSA(privateKey, preHash);
case KeyTypes.ES256:
return createSignatureECR1(privateKey, header, payload, preHash);
case KeyTypes.ES256K:
return createSignatureECK1(privateKey, preHash);
case KeyTypes.ED25519:
return createSignatureED(privateKey, preHash);
default:
break;
}
}
export async function createSignatureRSA(privateKey: string, preHash: 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);
throw Error;
} else {
try {
signature64 = await RNSecureKeystoreModule.sign(alias, preHash);
} catch (error) {
console.error('Error in creating signature:', error);
if (error.toString().includes(BIOMETRIC_CANCELLED)) {
throw new BiometricCancellationError(error.toString());
}
throw error;
}
if (isAndroid())
signature64 = await RNSecureKeystoreModule.sign(
KeyTypes.RS256,
KeyTypes.RS256,
preHash,
);
else {
const key = forge.pki.privateKeyFromPem(privateKey);
const md = forge.md.sha256.create();
md.update(preHash, 'utf8');
return replaceCharactersInB64(signature64);
const signature = key.sign(md);
signature64 = encodeB64(signature);
}
}
return replaceCharactersInB64(signature64);
}
export async function createSignatureECK1(privateKey, prehash) {
const sha = sha256(prehash);
const sign = await secp.signAsync(sha, privateKey, {lowS: false});
return base64url(Buffer.from(sign.toCompactRawBytes()));
}
export async function createSignatureED(privateKey, prehash) {
const sha = sha256(prehash);
const sign = await secp.signAsync(sha, privateKey);
return base64url(Buffer.from(sign.toCompactRawBytes()));
}
export async function createSignatureECR1(
privateKey,
header,
payload,
preHash,
) {
if (!isHardwareKeystoreExists) {
throw Error;
} else {
if (isAndroid()) {
let signature64 = RNSecureKeystoreModule.sign(
KeyTypes.ES256,
KeyTypes.ES256,
preHash,
);
const base64DeodedSignature = base64.decode(
signature64.replace(/\n/g, ''),
);
const derSignature = Uint8Array.from(base64DeodedSignature, char =>
char.charCodeAt(0),
);
signature64 = convertDerToRsFormat(derSignature);
return replaceCharactersInB64(signature64);
}
}
const key = await jose.JWK.asKey(JSON.parse(privateKey));
const signer = await jose.JWS.createSign(
{format: 'compact', fields: header},
{key, reference: false},
);
const jws = await signer.update(JSON.stringify(payload)).final();
return jws;
}
function replaceCharactersInB64(encodedB64: string) {
@@ -87,7 +301,7 @@ export function encodeB64(str: string) {
* use the isCustomKeystore constant in the app lifeycle instead.
*/
function isCustomSecureKeystore() {
return !isIOS() ? RNSecureKeystoreModule.deviceSupportsHardware() : false;
return isAndroid() ? RNSecureKeystoreModule.deviceSupportsHardware() : true;
}
export async function encryptJson(
@@ -134,11 +348,11 @@ export async function decryptJson(
if (!isHardwareKeystoreExists) {
return decryptWithForge(encryptedData, encryptionKey);
}
return await RNSecureKeystoreModule.decryptData(
const decryptedData = await RNSecureKeystoreModule.decryptData(
ENCRYPTION_ID,
encryptedData,
);
return isIOS() ? base64.decode(decryptedData) : decryptedData;
} catch (e) {
console.error('error decryptJson:', e);
@@ -188,3 +402,42 @@ export function hmacSHA(encryptionKey: string, data: string) {
const resultBytes = hmac.digest().getBytes().toString();
return resultBytes;
}
export async function fetchKeyPair(keyType: any) {
try {
const {RNSecureKeystoreModule} = NativeModules;
if (keyType == KeyTypes.RS256 || keyType == KeyTypes.ES256) {
if (isAndroid()) {
const publicKey = await RNSecureKeystoreModule.retrieveKey(keyType);
return {
publicKey: publicKey,
privateKey: '',
};
} else {
const keyPair = await RNSecureKeystoreModule.retrieveGenericKey(
keyType,
);
const publicKey = keyPair[0];
const privateKey = keyPair[1];
return {
publicKey: publicKey,
privateKey: privateKey,
};
}
} else {
const keyPair = await RNSecureKeystoreModule.retrieveGenericKey(keyType);
const publicKey = Buffer.from(keyPair[0], 'base64');
const privateKey = Buffer.from(keyPair[1], 'base64');
return {
publicKey: publicKey,
privateKey: privateKey,
};
}
} catch (e) {
console.error('error getting key', e);
return {
publicKey: '',
privateKey: '',
};
}
}

View File

@@ -0,0 +1,23 @@
import asn1 from 'asn1.js';
import {Buffer} from 'buffer';
const convertDerToRsFormat = derSignature => {
const ASN1Integer = asn1.define('ASN1Integer', function () {
this.int();
});
const ASN1Sequence = asn1.define('ASN1Sequence', function () {
this.seq().obj(this.key('r').int(), this.key('s').int());
});
const derBuffer = Buffer.from(derSignature);
const seq = ASN1Sequence.decode(derBuffer, 'der');
const r = seq.r.toArrayLike(Buffer, 'be', 32);
const s = seq.s.toArrayLike(Buffer, 'be', 32);
const rsBuffer = Buffer.concat([r, s]);
return rsBuffer.toString('base64');
};
export default convertDerToRsFormat;

View File

@@ -1,14 +1,5 @@
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
const bindingCertificate = '-bindingCertificate';
export async function savePrivateKey(id: string, privateKey: string) {
var result = await RNSecureKeyStore.set(id, privateKey, {
accessible: ACCESSIBLE.ALWAYS_THIS_DEVICE_ONLY,
});
return result;
}
export async function getPrivateKey(id: string) {
var result = await RNSecureKeyStore.get(id);
return result;

View File

@@ -4,7 +4,7 @@ import {isIOS} from '../constants';
import pem2jwk from 'simple-pem2jwk';
import {displayType, issuerType} from '../../machines/Issuers/IssuersMachine';
import getAllConfigurations, {CACHED_API} from '../api';
import base64url from 'base64url';
import i18next from 'i18next';
import {getJWT} from '../cryptoutil/cryptoUtil';
import i18n from '../../i18n';
@@ -23,7 +23,8 @@ import {getVerifiableCredential} from '../../machines/VerifiableCredential/VCIte
import {vcVerificationBannerDetails} from '../../components/BannerNotificationContainer';
import {getErrorEventData, sendErrorEvent} from '../telemetry/TelemetryUtils';
import {TelemetryConstants} from '../telemetry/TelemetryConstants';
import {NativeModules} from 'react-native';
import {KeyTypes} from '../cryptoutil/KeyTypes';
export const Protocols = {
OpenId4VCI: 'OpenId4VCI',
OTP: 'OTP',
@@ -160,31 +161,6 @@ export const constructAuthorizationConfiguration = (
};
};
export const getJWK = async publicKey => {
try {
let publicKeyJWKString;
let publicKeyJWK;
if (isIOS()) {
publicKeyJWKString = await jose.JWK.asKey(publicKey, 'pem');
publicKeyJWK = publicKeyJWKString.toJSON();
} else {
publicKeyJWK = await pem2jwk(publicKey);
}
return {
...publicKeyJWK,
alg: 'RS256',
use: 'sig',
};
} catch (e) {
console.error(
'Exception occurred while constructing JWK from PEM : ' +
publicKey +
' Exception is ',
e,
);
}
};
export const getSelectedCredentialTypeDetails = (
wellknown: any,
vcCredentialTypes: Object[],
@@ -357,10 +333,11 @@ export async function constructProofJWT(
privateKey: string,
accessToken: string,
selectedIssuer: issuerType,
keyType: string,
): Promise<string> {
const jwtHeader = {
alg: 'RS256',
jwk: await getJWK(publicKey),
alg: keyType,
jwk: await getJWK(publicKey, keyType),
typ: 'openid4vci-proof+jwt',
};
const decodedToken = jwtDecode(accessToken);
@@ -372,7 +349,90 @@ export async function constructProofJWT(
exp: Math.floor(new Date().getTime() / 1000) + 18000,
};
return await getJWT(jwtHeader, jwtPayload, Issuers_Key_Ref, privateKey);
return await getJWT(
jwtHeader,
jwtPayload,
Issuers_Key_Ref,
privateKey,
keyType,
);
}
export const getJWK = async (publicKey, keyType) => {
try {
let publicKeyJWK;
switch (keyType) {
case KeyTypes.RS256:
publicKeyJWK = await getJWKRSA(publicKey);
break;
case KeyTypes.ES256:
publicKeyJWK = await getJWKECR1(publicKey);
break;
case KeyTypes.ES256K:
publicKeyJWK = await getJWKECK1(publicKey);
break;
case KeyTypes.ED25519:
publicKeyJWK = await getJWKED(publicKey);
break;
default:
throw Error;
}
return {
...publicKeyJWK,
alg: keyType,
use: 'sig',
};
} catch (e) {
console.error(
'Exception occurred while constructing JWK from PEM : ' +
publicKey +
' Exception is ',
e,
);
}
};
async function getJWKRSA(publicKey): Promise<any> {
const publicKeyJWKString = await jose.JWK.asKey(publicKey, 'pem');
return publicKeyJWKString.toJSON();
}
function getJWKECR1(publicKey): any {
return JSON.parse(publicKey);
}
function getJWKECK1(publicKey): any {
const x = base64url(Buffer.from(publicKey.slice(1, 33))); // Skip the first byte (0x04) in the uncompressed public key
const y = base64url(Buffer.from(publicKey.slice(33)));
const jwk = {
kty: 'EC',
crv: 'secp256k1',
x: x,
y: y,
};
return jwk;
}
function getJWKED(publicKey): any {
throw new Error('Function not implemented.');
}
export async function hasKeyPair(keyType: any): Promise<boolean> {
const {RNSecureKeystoreModule} = NativeModules;
try {
return await RNSecureKeystoreModule.hasAlias(keyType);
} catch (e) {
console.warn('key not found');
return false;
}
}
export function selectCredentialRequestKey(keyTypes: string[]) {
const availableKeys = [
KeyTypes.RS256,
KeyTypes.ES256K,
KeyTypes.ES256,
KeyTypes.ED25519,
];
for (const key of availableKeys) {
if (keyTypes.includes(key)) return key;
}
throw Error;
}
export const constructIssuerMetaData = (