From 0f87a90597c5d7aa2b37f44e258e6f4c65e85cce Mon Sep 17 00:00:00 2001 From: bhumi46 Date: Tue, 25 Jun 2024 15:59:49 +0530 Subject: [PATCH 01/22] [DSD-5562] Signed-off-by: bhumi46 --- injitest/automation_trigger.sh | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/injitest/automation_trigger.sh b/injitest/automation_trigger.sh index 5c816c96..cb44e699 100755 --- a/injitest/automation_trigger.sh +++ b/injitest/automation_trigger.sh @@ -81,24 +81,32 @@ execute_ios_tests() { local username="$2" local access_key="$3" local test_type="$4" + local config_file="./iosConfig.yml" - cd injitest - # Update iosConfig.yml with the app_url obtained from BrowserStack - sed -i.bak '' "s|app:.*|app: $app_url|" iosConfig.yml - sed -i.bak '' "s|userName:.*|userName: $username|" iosConfig.yml - sed -i.bak '' "s|accessKey:.*|accessKey: $access_key|" iosConfig.yml + # Check if the configuration file exists + if [ ! -f "$config_file" ]; then + echo "Configuration file $config_file not found!" + exit 1 + fi + + # Update iosConfig.yml with the app_url, username, and access_key obtained from BrowserStack using awk + awk -v app_url="$app_url" -v username="$username" -v access_key="$access_key" ' + BEGIN { updated = 0 } + /^app:/ { $0 = "app: " app_url; updated++ } + /^userName:/ { $0 = "userName: " username; updated++ } + /^accessKey:/ { $0 = "accessKey: " access_key; updated++ } + { print } + END { if (updated < 3) exit 1 } + ' "$config_file" > "${config_file}.tmp" || { echo "Failed to update configuration file"; exit 1; } + + mv "${config_file}.tmp" "$config_file" + + echo "Configuration file updated:" # Run UI tests using Maven with the updated iosConfig.yml file and TestNG XML file based on the test type - mvn clean test -DtestngXmlFile="ios${test_type}.xml" -Dbrowserstack.config="iosConfig.yml" - rm iosConfig.yml.bak + mvn clean test -DtestngXmlFile="ios${test_type}.xml" -Dbrowserstack.config="$config_file" } -# Check if the correct number of arguments are passed -if [ "$#" -ne 4 ]; then - echo "Expected arguments: $@" - handle_error "Usage: $0 " -fi - # Assigning parameters to variables username=$1 From 308f7439c39495b63b67e89c948c0951738da62e Mon Sep 17 00:00:00 2001 From: bhumi46 Date: Tue, 25 Jun 2024 16:05:57 +0530 Subject: [PATCH 02/22] [DSD-5562] Signed-off-by: bhumi46 --- injitest/automation_trigger.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/injitest/automation_trigger.sh b/injitest/automation_trigger.sh index cb44e699..1220d83a 100755 --- a/injitest/automation_trigger.sh +++ b/injitest/automation_trigger.sh @@ -107,6 +107,11 @@ execute_ios_tests() { mvn clean test -DtestngXmlFile="ios${test_type}.xml" -Dbrowserstack.config="$config_file" } +# Check if the correct number of arguments are passed +if [ "$#" -ne 4 ]; then + echo "Expected arguments: $@" + handle_error "Usage: $0 " +fi # Assigning parameters to variables username=$1 From b43c8612cdd2a6035576a64bc2b3325a4d444b49 Mon Sep 17 00:00:00 2001 From: bhumi46 Date: Tue, 25 Jun 2024 18:29:14 +0530 Subject: [PATCH 03/22] [DSD-5562] Signed-off-by: bhumi46 --- injitest/automation_trigger.sh | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/injitest/automation_trigger.sh b/injitest/automation_trigger.sh index 1220d83a..80ac1a4a 100755 --- a/injitest/automation_trigger.sh +++ b/injitest/automation_trigger.sh @@ -81,32 +81,16 @@ execute_ios_tests() { local username="$2" local access_key="$3" local test_type="$4" - local config_file="./iosConfig.yml" - # Check if the configuration file exists - if [ ! -f "$config_file" ]; then - echo "Configuration file $config_file not found!" - exit 1 - fi - - # Update iosConfig.yml with the app_url, username, and access_key obtained from BrowserStack using awk - awk -v app_url="$app_url" -v username="$username" -v access_key="$access_key" ' - BEGIN { updated = 0 } - /^app:/ { $0 = "app: " app_url; updated++ } - /^userName:/ { $0 = "userName: " username; updated++ } - /^accessKey:/ { $0 = "accessKey: " access_key; updated++ } - { print } - END { if (updated < 3) exit 1 } - ' "$config_file" > "${config_file}.tmp" || { echo "Failed to update configuration file"; exit 1; } - - mv "${config_file}.tmp" "$config_file" - - echo "Configuration file updated:" + cd injitest + #Use the macOS-compatible commands with '' for sed commands + sed -i '' "s|app:.*|app: $app_url|" "iosConfig.yml" + sed -i '' "s|userName:.*|userName: $username|" "iosConfig.yml" + sed -i '' "s|accessKey:.*|accessKey: $access_key|" "iosConfig.yml" # Run UI tests using Maven with the updated iosConfig.yml file and TestNG XML file based on the test type - mvn clean test -DtestngXmlFile="ios${test_type}.xml" -Dbrowserstack.config="$config_file" + mvn clean test -DtestngXmlFile="ios${test_type}.xml" -Dbrowserstack.config="iosConfig.yml" } - # Check if the correct number of arguments are passed if [ "$#" -ne 4 ]; then echo "Expected arguments: $@" From 93d8c09674ae8cf6351256df33e08977021e4fc9 Mon Sep 17 00:00:00 2001 From: abhip2565 <74866247+abhip2565@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:27:11 +0530 Subject: [PATCH 04/22] [INJIMOB-1955] modify android artifacts version (#1595) Signed-off-by: Abhishek Paul --- android/app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ab120771..94b6b511 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -256,9 +256,10 @@ 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.0") + implementation("io.mosip:secure-keystore:0.2.0") + implementation("io.mosip:tuvali:0.5.0") + implementation("io.mosip:inji-vci-client:0.1.0") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; @@ -287,7 +288,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.2.0-SNAPSHOT" debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group: 'com.facebook.flipper' From 54b990e43b0e6c3e228c006abd3b7901c65ebdd6 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:24:36 +0530 Subject: [PATCH 05/22] [DSD-6142] Updated java21 env in workflow file Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 256a7664..3aa00bf8 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -42,6 +42,11 @@ jobs: steps: - uses: actions/checkout@v3.1.0 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '21' - uses: actions/setup-node@v3 with: node-version: '18.x' @@ -120,6 +125,11 @@ jobs: steps: - uses: actions/checkout@v3.1.0 + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '21' - uses: actions/setup-node@v3 with: node-version: '18.x' From 80ab71c33f7f46ff524a94606816734e0bdbbad5 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:33:50 +0530 Subject: [PATCH 06/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 3aa00bf8..5a424de7 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -50,7 +50,10 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '18.x' - + + - name: Compile with Maven + run: mvn clean compile -X + - name: Cache npm dependencies uses: actions/cache@v3.3.1 with: @@ -133,7 +136,10 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '18.x' - + + - name: Compile with Maven + run: mvn clean compile -X + - name: Cache npm dependencies uses: actions/cache@v3.3.1 with: From 2b53fe38c016aed41e09d98ade8ac8952f5cc7e0 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:51:37 +0530 Subject: [PATCH 07/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 5a424de7..5a6b5989 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -53,6 +53,7 @@ jobs: - name: Compile with Maven run: mvn clean compile -X + working-directory: /injitest/ - name: Cache npm dependencies uses: actions/cache@v3.3.1 From adce16d8f99af627664896341e545a7d4e932539 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:54:39 +0530 Subject: [PATCH 08/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 5a6b5989..d3592671 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -50,10 +50,11 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '18.x' - + - name: work dir + run: pwd - name: Compile with Maven run: mvn clean compile -X - working-directory: /injitest/ + working-directory: injitest/ - name: Cache npm dependencies uses: actions/cache@v3.3.1 From 52b8d4f92e81d0da90f1506a1ca9a8c575d2a5d2 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:06:39 +0530 Subject: [PATCH 09/22] [DSD-6142] Updated java21 env in workflow file and reverted compiler check Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index d3592671..20ef925f 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -47,14 +47,10 @@ jobs: with: distribution: 'temurin' java-version: '21' + - uses: actions/setup-node@v3 with: node-version: '18.x' - - name: work dir - run: pwd - - name: Compile with Maven - run: mvn clean compile -X - working-directory: injitest/ - name: Cache npm dependencies uses: actions/cache@v3.3.1 @@ -138,9 +134,6 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '18.x' - - - name: Compile with Maven - run: mvn clean compile -X - name: Cache npm dependencies uses: actions/cache@v3.3.1 From cba568e0f73f7693b95e94b2874808795817915d Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:31:48 +0530 Subject: [PATCH 10/22] Update pom.xml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- injitest/pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/injitest/pom.xml b/injitest/pom.xml index c9bd9304..8742a451 100644 --- a/injitest/pom.xml +++ b/injitest/pom.xml @@ -200,12 +200,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.11.0 - ${maven.compiler.source} - ${maven.compiler.target} + 21 + 21 - \ No newline at end of file + From c021c24cb26cea80e5082e817256efbef3a1173c Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:32:30 +0530 Subject: [PATCH 11/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 20ef925f..d16dc34f 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -42,11 +42,11 @@ jobs: steps: - uses: actions/checkout@v3.1.0 - - name: Set up JDK 21 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '21' + # - name: Set up JDK 21 + # uses: actions/setup-java@v3 + # with: + # distribution: 'Lemurian' + # java-version: '21' - uses: actions/setup-node@v3 with: From 81826090d84ec274cc0a0a26224321f5fd73f450 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:28:45 +0530 Subject: [PATCH 12/22] Update pom.xml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- injitest/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/injitest/pom.xml b/injitest/pom.xml index 8742a451..487b5273 100644 --- a/injitest/pom.xml +++ b/injitest/pom.xml @@ -200,7 +200,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.8.0 21 21 From bb5eb89e8772387c97babb12b26da79a6f63325b Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:31:07 +0530 Subject: [PATCH 13/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index d16dc34f..d32579a2 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -63,7 +63,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 From 9b7f6e7bd83962fd4ad181f7ffcfad92fd7e4f47 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:47:39 +0530 Subject: [PATCH 14/22] Update ui-automation.yml Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index d32579a2..88e7e0fa 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -92,7 +92,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 From 15b8f594970922ad1013473706bb59eba643d0cf Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:11:26 +0530 Subject: [PATCH 15/22] [DSD-6142] Updated java21 env in workflow file after build Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index 88e7e0fa..de3dde1f 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -41,17 +41,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.1.0 - # - name: Set up JDK 21 - # uses: actions/setup-java@v3 - # with: - # distribution: 'Lemurian' - # java-version: '21' - + - 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: @@ -132,11 +125,6 @@ jobs: steps: - uses: actions/checkout@v3.1.0 - - name: Set up JDK 21 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '21' - uses: actions/setup-node@v3 with: node-version: '18.x' From 0370f6c6d114978dbbd42cacb0bad244b817d1d6 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:14:44 +0530 Subject: [PATCH 16/22] [DSD-6142] reverted pom changes Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- injitest/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/injitest/pom.xml b/injitest/pom.xml index 487b5273..2514973e 100644 --- a/injitest/pom.xml +++ b/injitest/pom.xml @@ -200,10 +200,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.10.1 - 21 - 21 + ${maven.compiler.source} + ${maven.compiler.target} From 687fde59425fa063108e3f0f6d52e5824a896dc1 Mon Sep 17 00:00:00 2001 From: bhumi46 <111699703+bhumi46@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:53:58 +0530 Subject: [PATCH 17/22] [DSD-6142] Updated java21 env in workflow file after build for both android and ios Signed-off-by: bhumi46 <111699703+bhumi46@users.noreply.github.com> --- .github/workflows/ui-automation.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ui-automation.yml b/.github/workflows/ui-automation.yml index de3dde1f..d272c6c4 100644 --- a/.github/workflows/ui-automation.yml +++ b/.github/workflows/ui-automation.yml @@ -166,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: | From c29e46bf755b0a7496dea736e98dad373bb620aa Mon Sep 17 00:00:00 2001 From: adityankannan-tw <109274996+adityankannan-tw@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:33:31 +0530 Subject: [PATCH 18/22] [INJIMOB-1278]: add release/debug build support to android custom build (#1597) Signed-off-by: adityankannan-tw Co-authored-by: adityankannan-tw --- .github/workflows/android-custom-build.yml | 11 ++++++++++- android/fastlane/Fastfile | 9 +++++++-- android/scripts/android-build.sh | 9 ++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android-custom-build.yml b/.github/workflows/android-custom-build.yml index cd5df3e2..7d8afaf7 100644 --- a/.github/workflows/android-custom-build.yml +++ b/.github/workflows/android-custom-build.yml @@ -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" \ No newline at end of file + ANDROID_ARTIFACT_PATH: "android/app/build/outputs/apk/residentapp/${{ inputs.type }}/Inji_universal.apk" \ No newline at end of file diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 6bc489f3..f59c2fb1 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -43,11 +43,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 diff --git a/android/scripts/android-build.sh b/android/scripts/android-build.sh index a8fbf80d..9d23a947 100755 --- a/android/scripts/android-build.sh +++ b/android/scripts/android-build.sh @@ -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 \ No newline at end of file +if [ "$APPLICATION_TYPE" == "debug" ]; then + bundle exec fastlane android_build_debug +else + bundle exec fastlane android_build_release +fi \ No newline at end of file From a78b2d2ef3bc971e26e935415d490e1a9c6b7d4c Mon Sep 17 00:00:00 2001 From: Alka Prasad Date: Tue, 10 Sep 2024 10:09:06 +0530 Subject: [PATCH 19/22] [INJIMOB-1911]: add logic for QR login via deeplink (#1601) * [INJIMOB-1911]: add logic for QR login via deeplink Signed-off-by: Alka Prasad * [INJIMOB-1911]: bump up tuvali version Signed-off-by: Alka Prasad * [INJIMOB-1911]: bump up kotlin patch version Signed-off-by: Alka Prasad * [INJIMOB-1911]: rename the singleton variable name Signed-off-by: Alka Prasad * [INJIMOB-1911]: extract common code in a function Signed-off-by: Alka Prasad * [INJIMOB-1911]: refactor some logic and remove redundant code Signed-off-by: Alka Prasad --------- Signed-off-by: Alka Prasad --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 130 ++++++++++------ .../io/mosip/residentapp/InjiPackage.java | 1 + .../java/io/mosip/residentapp/IntentData.java | 19 +++ .../io/mosip/residentapp/MainActivity.java | 27 +++- .../residentapp/RNQrLoginIntentModule.java | 38 +++++ machines/app.ts | 44 +++++- machines/app.typegen.ts | 142 +++++++++++------- machines/bleShare/scan/scanActions.ts | 8 +- machines/bleShare/scan/scanMachine.ts | 20 ++- machines/bleShare/scan/scanMachine.typegen.ts | 25 ++- machines/bleShare/scan/scanModel.ts | 1 + machines/bleShare/scan/scanSelectors.ts | 4 + routes/index.ts | 2 +- screens/MainLayout.tsx | 20 ++- screens/Scan/ScanLayoutController.ts | 11 ++ 16 files changed, 379 insertions(+), 115 deletions(-) create mode 100644 android/app/src/main/java/io/mosip/residentapp/IntentData.java create mode 100644 android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java diff --git a/android/app/build.gradle b/android/app/build.gradle index 94b6b511..60b68715 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -258,7 +258,7 @@ dependencies { implementation 'com.facebook.soloader:soloader:0.10.1+' implementation("io.mosip:pixelpass:0.2.0") implementation("io.mosip:secure-keystore:0.2.0") - implementation("io.mosip:tuvali:0.5.0") + implementation("io.mosip:tuvali:0.5.1-SNAPSHOT") implementation("io.mosip:inji-vci-client:0.1.0") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a5a8d7ff..fc062777 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,46 +1,86 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java b/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java index 24addbcc..d0ec9bad 100644 --- a/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java +++ b/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java @@ -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; } diff --git a/android/app/src/main/java/io/mosip/residentapp/IntentData.java b/android/app/src/main/java/io/mosip/residentapp/IntentData.java new file mode 100644 index 00000000..b0684c50 --- /dev/null +++ b/android/app/src/main/java/io/mosip/residentapp/IntentData.java @@ -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; + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/io/mosip/residentapp/MainActivity.java b/android/app/src/main/java/io/mosip/residentapp/MainActivity.java index edd4eb8a..b69e716d 100644 --- a/android/app/src/main/java/io/mosip/residentapp/MainActivity.java +++ b/android/app/src/main/java/io/mosip/residentapp/MainActivity.java @@ -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)); + } } /** diff --git a/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java b/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java new file mode 100644 index 00000000..f471f55f --- /dev/null +++ b/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java @@ -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(""); + } + +} \ No newline at end of file diff --git a/machines/app.ts b/machines/app.ts index 7eea184f..53dfece8 100644 --- a/machines/app.ts +++ b/machines/app.ts @@ -32,6 +32,9 @@ import { createVcMetaMachine, vcMetaMachine, } from './VerifiableCredential/VCMetaMachine/VCMetaMachine'; +import {NativeModules} from 'react-native'; + +const QrLoginIntent = NativeModules.QrLoginIntent; const model = createModel( { @@ -40,6 +43,7 @@ const model = createModel( isReadError: false, isDecryptError: false, isKeyInvalidateError: false, + linkCode: '', }, { events: { @@ -56,6 +60,7 @@ const model = createModel( APP_INFO_RECEIVED: (info: AppInfo) => ({info}), STORE_RESPONSE: (response: unknown) => ({response}), RESET_KEY_INVALIDATE_ERROR_DISMISS: () => ({}), + RESET_LINKCODE: () => ({}), }, }, ); @@ -78,6 +83,9 @@ export const appMachine = model.createMachine( DECRYPT_ERROR: { actions: ['setIsDecryptError'], }, + RESET_LINKCODE: { + actions: ['resetLinkCode'], + }, DECRYPT_ERROR_DISMISS: { actions: ['unsetIsDecryptError'], }, @@ -166,6 +174,17 @@ export const appMachine = model.createMachine( checking: {}, active: { entry: ['forwardToServices'], + invoke: [ + { + src: 'isQrLoginByDeepLink', + onDone: { + actions: ['setLinkCode'], + }, + }, + { + src: 'resetQRLoginDeepLinkData', + }, + ], }, inactive: { entry: ['forwardToServices'], @@ -198,7 +217,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 +374,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(), @@ -447,3 +485,7 @@ export function selectIsDecryptError(state: State) { export function selectIsKeyInvalidateError(state: State) { return state.context.isKeyInvalidateError; } + +export function selectIsLinkCode(state: State) { + return state.context.linkCode; +} diff --git a/machines/app.typegen.ts b/machines/app.typegen.ts index 933e4337..66ac40f0 100644 --- a/machines/app.typegen.ts +++ b/machines/app.typegen.ts @@ -1,55 +1,89 @@ +// 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: { - "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; - } - \ No newline at end of file +export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + 'done.invoke.app.ready.focus.active:invocation[0]': { + type: 'done.invoke.app.ready.focus.active:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + '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]'; + isQrLoginByDeepLink: 'done.invoke.app.ready.focus.active:invocation[0]'; + resetQRLoginDeepLinkData: 'done.invoke.app.ready.focus.active:invocation[1]'; + }; + missingImplementations: { + actions: 'forwardToServices'; + 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'; + resetLinkCode: 'RESET_LINKCODE'; + setAppInfo: 'APP_INFO_RECEIVED'; + setIsDecryptError: 'DECRYPT_ERROR'; + setIsReadError: 'ERROR'; + setLinkCode: 'done.invoke.app.ready.focus.active:invocation[0]'; + 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'; + isQrLoginByDeepLink: 'ACTIVE'; + resetQRLoginDeepLinkData: 'ACTIVE'; + }; + 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; +} diff --git a/machines/bleShare/scan/scanActions.ts b/machines/bleShare/scan/scanActions.ts index b6e83c5b..dce95987 100644 --- a/machines/bleShare/scan/scanActions.ts +++ b/machines/bleShare/scan/scanActions.ts @@ -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( diff --git a/machines/bleShare/scan/scanMachine.ts b/machines/bleShare/scan/scanMachine.ts index e8d391d6..7fac41d2 100644 --- a/machines/bleShare/scan/scanMachine.ts +++ b/machines/bleShare/scan/scanMachine.ts @@ -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: { diff --git a/machines/bleShare/scan/scanMachine.typegen.ts b/machines/bleShare/scan/scanMachine.typegen.ts index eac4e373..1b39b24b 100644 --- a/machines/bleShare/scan/scanMachine.typegen.ts +++ b/machines/bleShare/scan/scanMachine.typegen.ts @@ -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' diff --git a/machines/bleShare/scan/scanModel.ts b/machines/bleShare/scan/scanModel.ts index 952c0bd9..0b3e27b1 100644 --- a/machines/bleShare/scan/scanModel.ts +++ b/machines/bleShare/scan/scanModel.ts @@ -55,6 +55,7 @@ const ScanEvents = { }), ALLOWED: () => ({}), DENIED: () => ({}), + QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}), }; export const ScanModel = createModel( diff --git a/machines/bleShare/scan/scanSelectors.ts b/machines/bleShare/scan/scanSelectors.ts index 64c77a00..60fd4a31 100644 --- a/machines/bleShare/scan/scanSelectors.ts +++ b/machines/bleShare/scan/scanSelectors.ts @@ -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'); +} diff --git a/routes/index.ts b/routes/index.ts index c4d105a8..66352212 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -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[] = [ { diff --git a/screens/MainLayout.tsx b/screens/MainLayout.tsx index 8364d7cf..1317e975 100644 --- a/screens/MainLayout.tsx +++ b/screens/MainLayout.tsx @@ -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(); + + const linkCode = useSelector(appService, selectIsLinkCode); + + useEffect(() => { + if (linkCode != '') { + navigation.navigate(BOTTOM_TAB_ROUTES.share); + } + }, [linkCode]); return ( Date: Tue, 10 Sep 2024 11:14:53 +0530 Subject: [PATCH 20/22] [INJIMOB-1278] - Debug build support for android custom build. (#1607) * [INJIMOB-1278]: add release/debug build support to android custom build Signed-off-by: adityankannan-tw * [INJIMOB-1278]: add release/debug build support to android custom build Signed-off-by: adityankannan-tw --------- Signed-off-by: adityankannan-tw Co-authored-by: adityankannan-tw --- android/fastlane/Fastfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index f59c2fb1..948828d4 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -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"] From c74e802468525a6b68337734fcaf0f519182e7b6 Mon Sep 17 00:00:00 2001 From: Alka Prasad Date: Tue, 10 Sep 2024 12:23:22 +0530 Subject: [PATCH 21/22] [INJIMOB: 1911] qrlogin via deeplink (#1606) * [INJIMOB-1911]: add logic for QR login via deeplink Signed-off-by: Alka Prasad * [INJIMOB-1911]: bump up tuvali version Signed-off-by: Alka Prasad * [INJIMOB-1911]: bump up kotlin patch version Signed-off-by: Alka Prasad * [INJIMOB-1911]: rename the singleton variable name Signed-off-by: Alka Prasad * [INJIMOB-1911]: extract common code in a function Signed-off-by: Alka Prasad * [INJIMOB-1911]: refactor some logic and remove redundant code Signed-off-by: Alka Prasad * [INJIMOB-1911]: add host name in the intend filter of qr-login Signed-off-by: Alka Prasad * [INJIMOB-1911]: update deeplink uri host name Signed-off-by: Alka Prasad --------- Signed-off-by: Alka Prasad Signed-off-by: Alka Prasad --- android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fc062777..4b70dcb9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -73,6 +73,7 @@ From 47a97c1783b976a76d54c7bddd86cb9c32eb8969 Mon Sep 17 00:00:00 2001 From: abhip2565 <74866247+abhip2565@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:18:52 +0530 Subject: [PATCH 22/22] [INJIMOB-1874] add ecr1,eck1 and ed key support (#1594) * [INJIMOB-1874] add ecr1,eck1 and ed key support Signed-off-by: Abhishek Paul * [INJIMOB-1874] refactor changes Signed-off-by: Abhishek Paul [INJIMOB-1874] refactor changes Signed-off-by: Abhishek Paul --------- Signed-off-by: Abhishek Paul --- android/app/build.gradle | 8 +- .../residentapp/RNSecureKeystoreModule.java | 208 ++++++++---- components/LanguageSelector.tsx | 4 +- components/QrCodeOverlay.tsx | 45 +-- i18n.ts | 4 +- ios/Inji.xcodeproj/project.pbxproj | 25 ++ .../xcshareddata/swiftpm/Package.resolved | 22 +- ios/Podfile.lock | 18 +- ios/RNSecureKeystoreModule.m | 87 +++++ ios/RNSecureKeystoreModule.swift | 259 +++++++++++++++ machines/Issuers/IssuersActions.ts | 81 +++-- machines/Issuers/IssuersGuards.ts | 4 +- machines/Issuers/IssuersMachine.ts | 56 ++-- machines/Issuers/IssuersMachine.typegen.ts | 34 +- machines/Issuers/IssuersModel.ts | 6 +- machines/Issuers/IssuersService.ts | 34 +- machines/QrLogin/QrLoginMachine.typegen.ts | 186 ++++------- machines/QrLogin/QrLoginServices.ts | 24 +- .../VCItemMachine/VCItemActions.ts | 10 +- .../VCItemMachine/VCItemGaurds.ts | 2 + .../VCItemMachine/VCItemMachine.ts | 40 ++- .../VCItemMachine/VCItemMachine.typegen.ts | 142 -------- .../VCItemMachine/VCItemServices.ts | 45 +-- machines/app.ts | 40 ++- machines/app.typegen.ts | 89 ----- machines/auth.ts | 2 +- machines/auth.typegen.ts | 114 +++---- machines/settings.typegen.ts | 121 +++---- machines/store.ts | 104 +++--- machines/store.typegen.ts | 17 +- package-lock.json | 137 ++++++-- package.json | 6 +- shared/CloudBackupAndRestoreUtils.ts | 17 +- shared/VCMetadata.ts | 8 +- shared/api.ts | 12 +- shared/cryptoutil/KeyTypes.ts | 7 + shared/cryptoutil/cryptoUtil.ts | 311 ++++++++++++++++-- shared/cryptoutil/signFormatConverter.ts | 26 ++ shared/keystore/SecureKeystore.ts | 9 +- shared/openId4VCI/Utils.ts | 120 +++++-- 40 files changed, 1588 insertions(+), 896 deletions(-) create mode 100644 ios/RNSecureKeystoreModule.m create mode 100644 ios/RNSecureKeystoreModule.swift create mode 100644 shared/cryptoutil/KeyTypes.ts create mode 100644 shared/cryptoutil/signFormatConverter.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 60b68715..d58e7a2d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -256,10 +256,10 @@ android { dependencies { implementation("com.facebook.react:react-android") implementation 'com.facebook.soloader:soloader:0.10.1+' - implementation("io.mosip:pixelpass:0.2.0") - implementation("io.mosip:secure-keystore:0.2.0") - implementation("io.mosip:tuvali:0.5.1-SNAPSHOT") - implementation("io.mosip:inji-vci-client:0.1.0") + 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.1.1" def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; diff --git a/android/app/src/main/java/io/mosip/residentapp/RNSecureKeystoreModule.java b/android/app/src/main/java/io/mosip/residentapp/RNSecureKeystoreModule.java index fb5091b6..4aba9cb4 100644 --- a/android/app/src/main/java/io/mosip/residentapp/RNSecureKeystoreModule.java +++ b/android/app/src/main/java/io/mosip/residentapp/RNSecureKeystoreModule.java @@ -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 successLambda = new Function1() { - @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 failureLambda = new Function2() { - @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 successLambda = new Function1() { - @Override - public Unit invoke(String data) { - promise.resolve(data); - return Unit.INSTANCE; - } - }; + public void decryptData(String alias, String encryptedText, Promise promise) { + Function1 successLambda = new Function1() { + @Override + public Unit invoke(String data) { + promise.resolve(data); + return Unit.INSTANCE; + } + }; - Function2 failureLambda = new Function2() { - @Override - public Unit invoke(Integer code, String message) { - promise.reject(code.toString(),message); - return Unit.INSTANCE; - } - }; - - keystore.decryptData(alias, encryptedText, successLambda, failureLambda); - } + Function2 failureLambda = new Function2() { + @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 successLambda = new Function1() { - @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 failureLambda = new Function2() { - @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 successLambda = new Function1() { - @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 failureLambda = new Function2() { - @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 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()); + } } } diff --git a/components/LanguageSelector.tsx b/components/LanguageSelector.tsx index e831ac40..670fa41e 100644 --- a/components/LanguageSelector.tsx +++ b/components/LanguageSelector.tsx @@ -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 = props => { const {i18n} = useTranslation(); @@ -30,8 +29,7 @@ export const LanguageSelector: React.FC = 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 { diff --git a/components/QrCodeOverlay.tsx b/components/QrCodeOverlay.tsx index 469a75c1..d532fbfa 100644 --- a/components/QrCodeOverlay.tsx +++ b/components/QrCodeOverlay.tsx @@ -1,39 +1,42 @@ -import React, {useEffect, useRef, useState} from 'react'; -import {Pressable, View} from 'react-native'; -import {Icon, Overlay} from 'react-native-elements'; -import {Centered, Column, Row, Text, Button} from './ui'; +import React, { useEffect, useRef, useState } from 'react'; +import { Pressable, View } from 'react-native'; +import { Icon, Overlay } from 'react-native-elements'; +import { Centered, Column, Row, Text, Button } from './ui'; import QRCode from 'react-native-qrcode-svg'; -import {Theme} from './ui/styleUtils'; -import {useTranslation} from 'react-i18next'; +import { Theme } from './ui/styleUtils'; +import { useTranslation } from 'react-i18next'; 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'; -import {ShareOptions} from 'react-native-share'; +import { SvgImage } from './ui/svg'; +import { NativeModules } from 'react-native'; +import { VerifiableCredential } from '../machines/VerifiableCredential/VCMetaMachine/vc'; +import { DEFAULT_ECL, MAX_QR_DATA_LENGTH } from '../shared/constants'; +import { VCMetadata } from '../shared/VCMetadata'; +import { shareImageToAllSupportedApps } from '../shared/sharing/imageUtils'; +import { ShareOptions } from 'react-native-share'; export const QrCodeOverlay: React.FC = props => { - const {RNPixelpassModule} = NativeModules; - const {t} = useTranslation('VcDetails'); + const { RNPixelpassModule } = NativeModules; + const { t } = useTranslation('VcDetails'); const [qrString, setQrString] = useState(''); const [qrError, setQrError] = useState(false); const base64ImageType = 'data:image/png;base64,'; + const { RNSecureKeystoreModule } = NativeModules async function getQRData(): Promise { 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; } @@ -102,7 +105,7 @@ export const QrCodeOverlay: React.FC = props => { + overlayStyle={{ padding: 1, borderRadius: 21 }}> { - const existingCredentials = await Keychain.getGenericPassword(); const language = await getItem( 'language', null, - existingCredentials.password, + "" ); if (language !== i18next.language) { diff --git a/ios/Inji.xcodeproj/project.pbxproj b/ios/Inji.xcodeproj/project.pbxproj index e4c236a0..e571f2d0 100644 --- a/ios/Inji.xcodeproj/project.pbxproj +++ b/ios/Inji.xcodeproj/project.pbxproj @@ -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 = ""; }; 9C48504A2C3E59B5002ECBD5 /* RNVersionModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNVersionModule.m; path = Inji/RNVersionModule.m; sourceTree = ""; }; 9C4850522C3E59E2002ECBD5 /* RNEventEmitterProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RNEventEmitterProtocol.swift; path = Inji/RNEventEmitterProtocol.swift; sourceTree = ""; }; + 9C7CDF3D2C7CBEDE00243A9A /* RNSecureKeystoreModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSecureKeystoreModule.swift; sourceTree = ""; }; + 9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSecureKeystoreModule.m; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Inji/SplashScreen.storyboard; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; 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 = ""; }; @@ -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" */; diff --git a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2fce5133..6fff5f31 100644 --- a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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" } }, { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 67ad3678..8d8547bd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/ios/RNSecureKeystoreModule.m b/ios/RNSecureKeystoreModule.m new file mode 100644 index 00000000..270e3f0b --- /dev/null +++ b/ios/RNSecureKeystoreModule.m @@ -0,0 +1,87 @@ +#import +#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 diff --git a/ios/RNSecureKeystoreModule.swift b/ios/RNSecureKeystoreModule.swift new file mode 100644 index 00000000..f6d73c63 --- /dev/null +++ b/ios/RNSecureKeystoreModule.swift @@ -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) + } + + + + +} diff --git a/machines/Issuers/IssuersActions.ts b/machines/Issuers/IssuersActions.ts index 7c29d15d..a3afd48e 100644 --- a/machines/Issuers/IssuersActions.ts +++ b/machines/Issuers/IssuersActions.ts @@ -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(), diff --git a/machines/Issuers/IssuersGuards.ts b/machines/Issuers/IssuersGuards.ts index 707bb358..270c5daa 100644 --- a/machines/Issuers/IssuersGuards.ts +++ b/machines/Issuers/IssuersGuards.ts @@ -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 diff --git a/machines/Issuers/IssuersMachine.ts b/machines/Issuers/IssuersMachine.ts index d7a5cefe..453b7297 100644 --- a/machines/Issuers/IssuersMachine.ts +++ b/machines/Issuers/IssuersMachine.ts @@ -94,7 +94,7 @@ export const IssuersMachine = model.createMachine( invoke: { src: 'downloadIssuerWellknown', onDone: { - actions: 'updateIssuerFromWellknown', + actions: ['updateIssuerFromWellknown'], target: 'downloadCredentialTypes', }, onError: { @@ -171,11 +171,8 @@ export const IssuersMachine = model.createMachine( invoke: { src: 'invokeAuthorization', onDone: { - actions: [ - 'setTokenResponse', - 'setLoadingReasonAsSettingUp', - 'getKeyPairFromStore', - ], + actions: ['setTokenResponse', 'setLoadingReasonAsSettingUp', 'setSelectedKey'], + target: '.getKeyPairFromKeystore', }, onError: [ { @@ -210,26 +207,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 +243,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 +255,12 @@ export const IssuersMachine = model.createMachine( target: 'generateKeyPair', }, ], + + onError: [ + { + target: 'selectingIssuer', + }, + ], }, }, generateKeyPair: { @@ -267,6 +272,7 @@ export const IssuersMachine = model.createMachine( { actions: [ 'setPublicKey', + 'setPrivateKey', 'setLoadingReasonAsDownloadingCredentials', 'storeKeyPair', ], @@ -274,7 +280,7 @@ export const IssuersMachine = model.createMachine( target: 'downloadCredentials', }, { - actions: [ + actions: [// to be decided 'setPublicKey', 'setLoadingReasonAsDownloadingCredentials', 'setPrivateKey', diff --git a/machines/Issuers/IssuersMachine.typegen.ts b/machines/Issuers/IssuersMachine.typegen.ts index c1041f3f..97316857 100644 --- a/machines/Issuers/IssuersMachine.typegen.ts +++ b/machines/Issuers/IssuersMachine.typegen.ts @@ -5,11 +5,13 @@ '@@xstate/typegen': true; internalEvents: { "done.invoke.checkInternet": { type: "done.invoke.checkInternet"; 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; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.downloadCredentialTypes:invocation[0]": { type: "done.invoke.issuersMachine.downloadCredentialTypes:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.downloadCredentials:invocation[0]": { type: "done.invoke.issuersMachine.downloadCredentials:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.downloadIssuerWellknown:invocation[0]": { type: "done.invoke.issuersMachine.downloadIssuerWellknown:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.generateKeyPair:invocation[0]": { type: "done.invoke.issuersMachine.generateKeyPair:invocation[0]"; 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; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.storing:invocation[0]": { type: "done.invoke.issuersMachine.storing:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; "done.invoke.issuersMachine.verifyingCredential:invocation[0]": { type: "done.invoke.issuersMachine.verifyingCredential:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; @@ -18,6 +20,7 @@ "error.platform.issuersMachine.downloadCredentialTypes:invocation[0]": { type: "error.platform.issuersMachine.downloadCredentialTypes:invocation[0]"; data: unknown }; "error.platform.issuersMachine.downloadCredentials:invocation[0]": { type: "error.platform.issuersMachine.downloadCredentials:invocation[0]"; data: unknown }; "error.platform.issuersMachine.downloadIssuerWellknown:invocation[0]": { 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 }; "error.platform.issuersMachine.verifyingCredential:invocation[0]": { type: "error.platform.issuersMachine.verifyingCredential:invocation[0]"; data: unknown }; "xstate.init": { type: "xstate.init" }; @@ -29,19 +32,21 @@ "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" | "loadKeyPair" | "logDownloaded" | "resetError" | "resetIsVerified" | "resetLoadingReason" | "resetSelectedCredentialType" | "resetVerificationErrorMessage" | "sendBackupEvent" | "sendDownloadingFailedToVcMeta" | "sendErrorEndEvent" | "sendImpressionEvent" | "sendSuccessEndEvent" | "setCredentialWrapper" | "setError" | "setFetchWellknownError" | "setIsVerified" | "setIssuers" | "setLoadingReasonAsDisplayIssuers" | "setLoadingReasonAsDownloadingCredentials" | "setLoadingReasonAsSettingUp" | "setMetadataInCredentialData" | "setNoInternet" | "setOIDCConfigError" | "setPrivateKey" | "setPublicKey" | "setSelectedCredentialType" | "setSelectedIssuerId" | "setSelectedIssuers" | "setSupportedCredentialTypes" | "setTokenResponse" | "setVCMetadata" | "setVerifiableCredential" | "storeKeyPair" | "storeVcMetaContext" | "storeVcsContext" | "storeVerifiableCredentialData" | "storeVerifiableCredentialMeta" | "updateIssuerFromWellknown" | "updateVerificationErrorMessage"; + actions: "downloadIssuerWellknown" | "loadKeyPair" | "logDownloaded" | "resetError" | "resetIsVerified" | "resetLoadingReason" | "resetSelectedCredentialType" | "resetVerificationErrorMessage" | "sendBackupEvent" | "sendDownloadingFailedToVcMeta" | "sendErrorEndEvent" | "sendImpressionEvent" | "sendSuccessEndEvent" | "setCredentialTypeListDownloadFailureError" | "setCredentialWrapper" | "setError" | "setFetchWellknownError" | "setIsVerified" | "setIssuers" | "setLoadingReasonAsDisplayIssuers" | "setLoadingReasonAsDownloadingCredentials" | "setLoadingReasonAsSettingUp" | "setMetadataInCredentialData" | "setNoInternet" | "setOIDCConfigError" | "setPrivateKey" | "setPublicKey" | "setSelectedCredentialType" | "setSelectedIssuerId" | "setSelectedIssuers" | "setSelectedKey" | "setSupportedCredentialTypes" | "setTokenResponse" | "setVCMetadata" | "setVerifiableCredential" | "storeKeyPair" | "storeVcMetaContext" | "storeVcsContext" | "storeVerifiableCredentialData" | "storeVerifiableCredentialMeta" | "updateIssuerFromWellknown" | "updateVerificationErrorMessage"; delays: never; - guards: "canSelectIssuerAgain" | "hasKeyPair" | "hasUserCancelledBiometric" | "isCustomSecureKeystore" | "isGenericError" | "isInternetConnected" | "isOIDCConfigError" | "isOIDCflowCancelled" | "isSignedIn" | "isVerificationPendingBecauseOfNetworkIssue" | "shouldFetchIssuersAgain"; - services: "checkInternet" | "downloadCredential" | "downloadCredentialTypes" | "downloadIssuerWellknown" | "downloadIssuersList" | "generateKeyPair" | "invokeAuthorization" | "isUserSignedAlready" | "verifyCredential"; + guards: "canSelectIssuerAgain" | "hasKeyPair" | "hasUserCancelledBiometric" | "isBiometricCancelled" | "isCustomSecureKeystore" | "isGenericError" | "isInternetConnected" | "isOIDCConfigError" | "isOIDCflowCancelled" | "isSignedIn" | "isVerificationPendingBecauseOfNetworkIssue" | "shouldFetchIssuersAgain"; + services: "checkInternet" | "downloadCredential" | "downloadCredentialTypes" | "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]"; "resetError": "RESET_ERROR" | "TRY_AGAIN" | "error.platform.issuersMachine.performAuthorization:invocation[0]"; "resetIsVerified": "error.platform.issuersMachine.verifyingCredential:invocation[0]"; @@ -53,13 +58,14 @@ "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]"; +"setError": "error.platform.issuersMachine.displayIssuers:invocation[0]" | "error.platform.issuersMachine.downloadCredentials:invocation[0]" | "error.platform.issuersMachine.performAuthorization:invocation[0]"; "setFetchWellknownError": "error.platform.issuersMachine.downloadIssuerWellknown:invocation[0]"; "setIsVerified": "done.invoke.issuersMachine.verifyingCredential:invocation[0]"; "setIssuers": "done.invoke.issuersMachine.displayIssuers:invocation[0]"; "setLoadingReasonAsDisplayIssuers": "TRY_AGAIN"; -"setLoadingReasonAsDownloadingCredentials": "STORE_ERROR" | "STORE_RESPONSE" | "TRY_AGAIN" | "done.invoke.issuersMachine.generateKeyPair:invocation[0]"; +"setLoadingReasonAsDownloadingCredentials": "TRY_AGAIN" | "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" | "done.invoke.issuersMachine.performAuthorization:invocation[0]"; "setMetadataInCredentialData": "done.invoke.issuersMachine.verifyingCredential:invocation[0]" | "error.platform.issuersMachine.verifyingCredential:invocation[0]"; "setNoInternet": "done.invoke.checkInternet"; @@ -69,6 +75,7 @@ "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": "done.invoke.issuersMachine.verifyingCredential:invocation[0]" | "error.platform.issuersMachine.verifyingCredential:invocation[0]"; @@ -86,8 +93,9 @@ }; 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"; @@ -99,17 +107,19 @@ }; eventsCausingServices: { "checkInternet": "SELECTED_CREDENTIAL_TYPE" | "done.invoke.issuersMachine.downloadCredentialTypes:invocation[0]"; -"downloadCredential": "CHECK_KEY_PAIR" | "done.invoke.issuersMachine.generateKeyPair:invocation[0]"; +"downloadCredential": "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]" | "error.platform.issuersMachine.verifyingCredential:invocation[0]"; "verifyCredential": "done.invoke.issuersMachine.downloadCredentials:invocation[0]"; }; - matchesStates: "checkInternet" | "checkKeyPair" | "displayIssuers" | "done" | "downloadCredentialTypes" | "downloadCredentials" | "downloadCredentials.idle" | "downloadCredentials.userCancelledBiometric" | "downloadIssuerWellknown" | "error" | "generateKeyPair" | "handleVCVerificationFailure" | "idle" | "performAuthorization" | "performAuthorization.idle" | "performAuthorization.userCancelledBiometric" | "selectingCredentialType" | "selectingIssuer" | "storing" | "verifyingCredential" | { "downloadCredentials"?: "idle" | "userCancelledBiometric"; -"performAuthorization"?: "idle" | "userCancelledBiometric"; }; + matchesStates: "checkInternet" | "checkKeyPair" | "displayIssuers" | "done" | "downloadCredentialTypes" | "downloadCredentials" | "downloadCredentials.idle" | "downloadCredentials.userCancelledBiometric" | "downloadIssuerWellknown" | "error" | "generateKeyPair" | "handleVCVerificationFailure" | "idle" | "performAuthorization" | "performAuthorization.getKeyPairFromKeystore" | "performAuthorization.idle" | "performAuthorization.userCancelledBiometric" | "selectingCredentialType" | "selectingIssuer" | "storing" | "verifyingCredential" | { "downloadCredentials"?: "idle" | "userCancelledBiometric"; +"performAuthorization"?: "getKeyPairFromKeystore" | "idle" | "userCancelledBiometric"; }; tags: never; } \ No newline at end of file diff --git a/machines/Issuers/IssuersModel.ts b/machines/Issuers/IssuersModel.ts index 5d6df08c..d8ae8786 100644 --- a/machines/Issuers/IssuersModel.ts +++ b/machines/Issuers/IssuersModel.ts @@ -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, diff --git a/machines/Issuers/IssuersService.ts b/machines/Issuers/IssuersService.ts index c2109e5d..53aa51c3 100644 --- a/machines/Issuers/IssuersService.ts +++ b/machines/Issuers/IssuersService.ts @@ -4,14 +4,16 @@ import NetInfo from '@react-native-community/netinfo'; import { constructAuthorizationConfiguration, 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 { @@ -26,7 +28,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 () => { @@ -74,6 +75,7 @@ export const IssuersService = () => { context.privateKey, accessToken, context.selectedIssuer, + context.keyType, ); let credential = await VciClient.downloadCredential( issuerMeta, @@ -100,17 +102,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.vcMetadata.issuer)) { diff --git a/machines/QrLogin/QrLoginMachine.typegen.ts b/machines/QrLogin/QrLoginMachine.typegen.ts index 0034358d..336f080e 100644 --- a/machines/QrLogin/QrLoginMachine.typegen.ts +++ b/machines/QrLogin/QrLoginMachine.typegen.ts @@ -1,124 +1,64 @@ -// This file was automatically generated. Edits will be overwritten -export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - 'done.invoke.QrLogin.linkTransaction:invocation[0]': { - type: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]': { - type: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'error.platform.QrLogin.linkTransaction:invocation[0]': { - type: 'error.platform.QrLogin.linkTransaction:invocation[0]'; - data: unknown; - }; - 'error.platform.QrLogin.sendingAuthenticate:invocation[0]': { - type: 'error.platform.QrLogin.sendingAuthenticate:invocation[0]'; - data: unknown; - }; - 'error.platform.QrLogin.sendingConsent:invocation[0]': { - type: 'error.platform.QrLogin.sendingConsent:invocation[0]'; - data: unknown; - }; - 'xstate.init': {type: 'xstate.init'}; - }; - invokeSrcNameMap: { - linkTransaction: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - sendAuthenticate: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]'; - sendConsent: 'done.invoke.QrLogin.sendingConsent:invocation[0]'; - }; - missingImplementations: { - actions: - | 'SetErrorMessage' - | 'expandLinkTransResp' - | 'forwardToParent' - | 'getFaceAuthConsent' - | 'loadMyVcs' - | 'loadThumbprint' - | 'resetFlowType' - | 'resetLinkTransactionId' - | 'resetSelectedVc' - | 'resetSelectedVoluntaryClaims' - | 'setClaims' - | 'setConsentClaims' - | 'setLinkedTransactionId' - | 'setMyVcs' - | 'setScanData' - | 'setSelectedVc' - | 'setShowFaceAuthConsent' - | 'setThumbprint' - | 'setlinkTransactionResponse' - | 'storeShowFaceAuthConsent' - | 'updateShowFaceAuthConsent'; - delays: never; - guards: - | 'isConsentAlreadyCaptured' - | 'isSimpleShareFlow' - | 'showFaceAuthConsentScreen'; - services: 'linkTransaction' | 'sendAuthenticate' | 'sendConsent'; - }; - eventsCausingActions: { - SetErrorMessage: - | 'error.platform.QrLogin.linkTransaction:invocation[0]' - | 'error.platform.QrLogin.sendingAuthenticate:invocation[0]' - | 'error.platform.QrLogin.sendingConsent:invocation[0]'; - expandLinkTransResp: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - forwardToParent: 'CANCEL' | 'DISMISS'; - getFaceAuthConsent: 'GET'; - loadMyVcs: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - loadThumbprint: 'FACE_VALID'; - resetFlowType: 'xstate.init'; - resetLinkTransactionId: 'GET'; - resetSelectedVc: 'xstate.init'; - resetSelectedVoluntaryClaims: 'GET'; - setClaims: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - setConsentClaims: 'TOGGLE_CONSENT_CLAIM'; - setLinkedTransactionId: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]'; - setMyVcs: 'STORE_RESPONSE'; - setScanData: 'GET'; - setSelectedVc: 'SELECT_VC'; - setShowFaceAuthConsent: 'FACE_VERIFICATION_CONSENT'; - setThumbprint: 'STORE_RESPONSE'; - setlinkTransactionResponse: 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - storeShowFaceAuthConsent: 'FACE_VERIFICATION_CONSENT'; - updateShowFaceAuthConsent: 'STORE_RESPONSE'; - }; - eventsCausingDelays: {}; - eventsCausingGuards: { - isConsentAlreadyCaptured: 'done.invoke.QrLogin.sendingAuthenticate:invocation[0]'; - isSimpleShareFlow: - | 'CANCEL' - | 'DISMISS' - | 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - showFaceAuthConsentScreen: - | 'VERIFY' - | 'done.invoke.QrLogin.linkTransaction:invocation[0]'; - }; - eventsCausingServices: { - linkTransaction: 'STORE_RESPONSE'; - sendAuthenticate: 'STORE_RESPONSE'; - sendConsent: 'CONFIRM'; - }; - matchesStates: - | 'ShowError' - | 'checkFaceAuthConsent' - | 'done' - | 'faceAuth' - | 'faceVerificationConsent' - | 'invalidIdentity' - | 'linkTransaction' - | 'loadMyVcs' - | 'loadingThumbprint' - | 'requestConsent' - | 'sendingAuthenticate' - | 'sendingConsent' - | 'showvcList' - | 'success' - | 'waitingForData'; - tags: never; -} + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "done.invoke.QrLogin.linkTransaction:invocation[0]": { type: "done.invoke.QrLogin.linkTransaction:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"done.invoke.QrLogin.sendingAuthenticate:invocation[0]": { type: "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"error.platform.QrLogin.linkTransaction:invocation[0]": { type: "error.platform.QrLogin.linkTransaction:invocation[0]"; data: unknown }; +"error.platform.QrLogin.sendingAuthenticate:invocation[0]": { type: "error.platform.QrLogin.sendingAuthenticate:invocation[0]"; data: unknown }; +"error.platform.QrLogin.sendingConsent:invocation[0]": { type: "error.platform.QrLogin.sendingConsent:invocation[0]"; data: unknown }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "linkTransaction": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"sendAuthenticate": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; +"sendConsent": "done.invoke.QrLogin.sendingConsent:invocation[0]"; + }; + missingImplementations: { + actions: "SetErrorMessage" | "expandLinkTransResp" | "forwardToParent" | "getFaceAuthConsent" | "loadMyVcs" | "loadThumbprint" | "resetFlowType" | "resetLinkTransactionId" | "resetSelectedVc" | "resetSelectedVoluntaryClaims" | "setClaims" | "setConsentClaims" | "setLinkedTransactionId" | "setMyVcs" | "setScanData" | "setSelectedVc" | "setShowFaceAuthConsent" | "setThumbprint" | "setlinkTransactionResponse" | "storeShowFaceAuthConsent" | "updateShowFaceAuthConsent"; + delays: never; + guards: "isConsentAlreadyCaptured" | "isSimpleShareFlow" | "showFaceAuthConsentScreen"; + services: "linkTransaction" | "sendAuthenticate" | "sendConsent"; + }; + eventsCausingActions: { + "SetErrorMessage": "error.platform.QrLogin.linkTransaction:invocation[0]" | "error.platform.QrLogin.sendingAuthenticate:invocation[0]" | "error.platform.QrLogin.sendingConsent:invocation[0]"; +"expandLinkTransResp": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"forwardToParent": "CANCEL" | "DISMISS"; +"getFaceAuthConsent": "GET"; +"loadMyVcs": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"loadThumbprint": "FACE_VALID"; +"resetFlowType": "xstate.init"; +"resetLinkTransactionId": "GET"; +"resetSelectedVc": "xstate.init"; +"resetSelectedVoluntaryClaims": "GET"; +"setClaims": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"setConsentClaims": "TOGGLE_CONSENT_CLAIM"; +"setLinkedTransactionId": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; +"setMyVcs": "STORE_RESPONSE"; +"setScanData": "GET"; +"setSelectedVc": "SELECT_VC"; +"setShowFaceAuthConsent": "FACE_VERIFICATION_CONSENT"; +"setThumbprint": "STORE_RESPONSE"; +"setlinkTransactionResponse": "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"storeShowFaceAuthConsent": "FACE_VERIFICATION_CONSENT"; +"updateShowFaceAuthConsent": "STORE_RESPONSE"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + "isConsentAlreadyCaptured": "done.invoke.QrLogin.sendingAuthenticate:invocation[0]"; +"isSimpleShareFlow": "CANCEL" | "DISMISS" | "done.invoke.QrLogin.linkTransaction:invocation[0]"; +"showFaceAuthConsentScreen": "VERIFY" | "done.invoke.QrLogin.linkTransaction:invocation[0]"; + }; + eventsCausingServices: { + "linkTransaction": "STORE_RESPONSE"; +"sendAuthenticate": "STORE_RESPONSE"; +"sendConsent": "CONFIRM"; + }; + matchesStates: "ShowError" | "checkFaceAuthConsent" | "done" | "faceAuth" | "faceVerificationConsent" | "invalidIdentity" | "linkTransaction" | "loadMyVcs" | "loadingThumbprint" | "requestConsent" | "sendingAuthenticate" | "sendingConsent" | "showvcList" | "success" | "waitingForData"; + tags: never; + } + \ No newline at end of file diff --git a/machines/QrLogin/QrLoginServices.ts b/machines/QrLogin/QrLoginServices.ts index 75ae65ab..96d3b8ba 100644 --- a/machines/QrLogin/QrLoginServices.ts +++ b/machines/QrLogin/QrLoginServices.ts @@ -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]; diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemActions.ts b/machines/VerifiableCredential/VCItemMachine/VCItemActions.ts index e96f7fe9..40ba805f 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemActions.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemActions.ts @@ -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: () => ''}), diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemGaurds.ts b/machines/VerifiableCredential/VCItemMachine/VCItemGaurds.ts index d8bb64c0..cc775a6d 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemGaurds.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemGaurds.ts @@ -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, diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemMachine.ts b/machines/VerifiableCredential/VCItemMachine/VCItemMachine.ts index 99111fc4..33cc4e71 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemMachine.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemMachine.ts @@ -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', - actions: ['setPublicKey', 'setPrivateKey'], + 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', }, { diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemMachine.typegen.ts b/machines/VerifiableCredential/VCItemMachine/VCItemMachine.typegen.ts index b39585e7..e69de29b 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemMachine.typegen.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemMachine.typegen.ts @@ -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; - } - \ No newline at end of file diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemServices.ts b/machines/VerifiableCredential/VCItemMachine/VCItemServices.ts index 35f8cc57..f3a0a8a5 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemServices.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemServices.ts @@ -1,25 +1,21 @@ 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 {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 +24,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 +75,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( diff --git a/machines/app.ts b/machines/app.ts index 53dfece8..0bf8ee63 100644 --- a/machines/app.ts +++ b/machines/app.ts @@ -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,7 +33,8 @@ import { createVcMetaMachine, vcMetaMachine, } from './VerifiableCredential/VCMetaMachine/VCMetaMachine'; -import {NativeModules} from 'react-native'; +import { checkAllKeyPairs, generateKeyPairsAndStore } from '../shared/cryptoutil/cryptoUtil'; + const QrLoginIntent = NativeModules.QrLoginIntent; @@ -111,13 +113,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: { @@ -435,6 +462,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) { diff --git a/machines/app.typegen.ts b/machines/app.typegen.ts index 66ac40f0..e69de29b 100644 --- a/machines/app.typegen.ts +++ b/machines/app.typegen.ts @@ -1,89 +0,0 @@ -// This file was automatically generated. Edits will be overwritten - -export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - 'done.invoke.app.ready.focus.active:invocation[0]': { - type: 'done.invoke.app.ready.focus.active:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - '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]'; - isQrLoginByDeepLink: 'done.invoke.app.ready.focus.active:invocation[0]'; - resetQRLoginDeepLinkData: 'done.invoke.app.ready.focus.active:invocation[1]'; - }; - missingImplementations: { - actions: 'forwardToServices'; - 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'; - resetLinkCode: 'RESET_LINKCODE'; - setAppInfo: 'APP_INFO_RECEIVED'; - setIsDecryptError: 'DECRYPT_ERROR'; - setIsReadError: 'ERROR'; - setLinkCode: 'done.invoke.app.ready.focus.active:invocation[0]'; - 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'; - isQrLoginByDeepLink: 'ACTIVE'; - resetQRLoginDeepLinkData: 'ACTIVE'; - }; - 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; -} diff --git a/machines/auth.ts b/machines/auth.ts index 9308ea79..f7b5a651 100644 --- a/machines/auth.ts +++ b/machines/auth.ts @@ -292,4 +292,4 @@ export function selectIsBiometricToggleFromSettings(state: State) { return state.context.toggleFromSettings; } return false; -} +} \ No newline at end of file diff --git a/machines/auth.typegen.ts b/machines/auth.typegen.ts index 33e6d900..9d3b9e72 100644 --- a/machines/auth.typegen.ts +++ b/machines/auth.typegen.ts @@ -1,63 +1,51 @@ -export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - '': {type: ''}; - 'done.invoke.auth.authorized:invocation[0]': { - type: 'done.invoke.auth.authorized:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'done.invoke.auth.introSlider:invocation[0]': { - type: 'done.invoke.auth.introSlider:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'xstate.init': {type: 'xstate.init'}; - }; - invokeSrcNameMap: { - downloadFaceSdkModel: 'done.invoke.auth.authorized:invocation[0]'; - generatePasscodeSalt: 'done.invoke.auth.introSlider:invocation[0]'; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - requestStoredContext: 'xstate.init'; - setBiometrics: 'SETUP_BIOMETRICS'; - setContext: 'STORE_RESPONSE'; - setIsToggleFromSettings: 'CHANGE_METHOD'; - setLanguage: 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE'; - setPasscode: 'SETUP_PASSCODE'; - setPasscodeSalt: 'done.invoke.auth.introSlider:invocation[0]'; - storeContext: - | 'SETUP_BIOMETRICS' - | 'SETUP_PASSCODE' - | 'STORE_RESPONSE' - | 'done.invoke.auth.authorized:invocation[0]' - | 'done.invoke.auth.introSlider:invocation[0]'; - }; - eventsCausingDelays: {}; - eventsCausingGuards: { - hasBiometricSet: ''; - hasData: 'STORE_RESPONSE'; - hasLanguageset: ''; - hasPasscodeSet: ''; - }; - eventsCausingServices: { - downloadFaceSdkModel: 'LOGIN' | 'SETUP_BIOMETRICS' | 'SETUP_PASSCODE'; - generatePasscodeSalt: 'SELECT'; - }; - matchesStates: - | 'authorized' - | 'checkingAuth' - | 'init' - | 'introSlider' - | 'languagesetup' - | 'savingDefaults' - | 'settingUp' - | 'unauthorized'; - tags: never; -} + + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "": { type: "" }; +"done.invoke.auth.authorized:invocation[0]": { type: "done.invoke.auth.authorized:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"done.invoke.auth.introSlider:invocation[0]": { type: "done.invoke.auth.introSlider:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "downloadFaceSdkModel": "done.invoke.auth.authorized:invocation[0]"; +"generatePasscodeSalt": "done.invoke.auth.introSlider:invocation[0]"; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + "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" | "done.invoke.auth.authorized:invocation[0]" | "done.invoke.auth.introSlider:invocation[0]"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + "hasBiometricSet": ""; +"hasData": "STORE_RESPONSE"; +"hasLanguageset": ""; +"hasPasscodeSet": ""; + }; + eventsCausingServices: { + "downloadFaceSdkModel": "LOGIN" | "SETUP_BIOMETRICS" | "SETUP_PASSCODE"; +"generatePasscodeSalt": "SELECT"; + }; + matchesStates: "authorized" | "checkingAuth" | "init" | "introSlider" | "languagesetup" | "savingDefaults" | "settingUp" | "unauthorized"; + tags: never; + } + \ No newline at end of file diff --git a/machines/settings.typegen.ts b/machines/settings.typegen.ts index 37a729a1..0be916ed 100644 --- a/machines/settings.typegen.ts +++ b/machines/settings.typegen.ts @@ -1,70 +1,53 @@ -// This file was automatically generated. Edits will be overwritten -export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - 'done.invoke.settings.resetInjiProps:invocation[0]': { - type: 'done.invoke.settings.resetInjiProps:invocation[0]'; - data: unknown; - __tip: 'See the XState TS docs to learn how to strongly type this.'; - }; - 'error.platform.settings.resetInjiProps:invocation[0]': { - type: 'error.platform.settings.resetInjiProps:invocation[0]'; - data: unknown; - }; - 'xstate.init': {type: 'xstate.init'}; - }; - invokeSrcNameMap: { - resetInjiProps: 'done.invoke.settings.resetInjiProps:invocation[0]'; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - requestStoredContext: 'xstate.init'; - resetCredentialRegistryResponse: 'CANCEL' | 'UPDATE_HOST'; - resetIsBiometricToggled: 'DISMISS'; - setBackupAndRestoreOptionExplored: 'SET_IS_BACKUP_AND_RESTORE_EXPLORED'; - setContext: 'STORE_RESPONSE'; - setIsBiometricToggled: 'TOGGLE_BIOMETRIC_UNLOCK'; - storeContext: - | 'ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS' - | 'SET_IS_BACKUP_AND_RESTORE_EXPLORED' - | 'SHOWN_ACCOUNT_SELECTION_CONFIRMATION' - | 'STORE_RESPONSE' - | 'TOGGLE_BIOMETRIC_UNLOCK' - | 'UPDATE_HOST' - | 'UPDATE_NAME' - | 'UPDATE_VC_LABEL' - | 'done.invoke.settings.resetInjiProps:invocation[0]'; - toggleBiometricUnlock: 'TOGGLE_BIOMETRIC_UNLOCK'; - updateCredentialRegistry: 'done.invoke.settings.resetInjiProps:invocation[0]'; - updateCredentialRegistryResponse: 'error.platform.settings.resetInjiProps:invocation[0]'; - updateCredentialRegistrySuccess: 'done.invoke.settings.resetInjiProps:invocation[0]'; - updateDefaults: 'STORE_RESPONSE'; - updateEsignetHostUrl: 'UPDATE_HOST'; - updateIsAccountSelectionConfirmationShown: 'SHOWN_ACCOUNT_SELECTION_CONFIRMATION'; - updateName: 'UPDATE_NAME'; - updatePartialDefaults: 'STORE_RESPONSE'; - updateUserShownWithHardwareKeystoreNotExists: 'ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS'; - updateVcLabel: 'UPDATE_VC_LABEL'; - }; - eventsCausingDelays: {}; - eventsCausingGuards: { - hasData: 'STORE_RESPONSE'; - hasPartialData: 'STORE_RESPONSE'; - }; - eventsCausingServices: { - resetInjiProps: 'UPDATE_HOST'; - }; - matchesStates: - | 'idle' - | 'init' - | 'resetInjiProps' - | 'showInjiTourGuide' - | 'storingDefaults'; - tags: never; -} + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "done.invoke.settings.resetInjiProps:invocation[0]": { type: "done.invoke.settings.resetInjiProps:invocation[0]"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"error.platform.settings.resetInjiProps:invocation[0]": { type: "error.platform.settings.resetInjiProps:invocation[0]"; data: unknown }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "resetInjiProps": "done.invoke.settings.resetInjiProps:invocation[0]"; + }; + missingImplementations: { + actions: never; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + "requestStoredContext": "xstate.init"; +"resetCredentialRegistryResponse": "CANCEL" | "UPDATE_HOST"; +"resetIsBiometricToggled": "DISMISS"; +"setBackupAndRestoreOptionExplored": "SET_IS_BACKUP_AND_RESTORE_EXPLORED"; +"setContext": "STORE_RESPONSE"; +"setIsBiometricToggled": "TOGGLE_BIOMETRIC_UNLOCK"; +"storeContext": "ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS" | "SET_IS_BACKUP_AND_RESTORE_EXPLORED" | "SHOWN_ACCOUNT_SELECTION_CONFIRMATION" | "STORE_RESPONSE" | "TOGGLE_BIOMETRIC_UNLOCK" | "UPDATE_HOST" | "UPDATE_NAME" | "UPDATE_VC_LABEL" | "done.invoke.settings.resetInjiProps:invocation[0]"; +"toggleBiometricUnlock": "TOGGLE_BIOMETRIC_UNLOCK"; +"updateCredentialRegistry": "done.invoke.settings.resetInjiProps:invocation[0]"; +"updateCredentialRegistryResponse": "error.platform.settings.resetInjiProps:invocation[0]"; +"updateCredentialRegistrySuccess": "done.invoke.settings.resetInjiProps:invocation[0]"; +"updateDefaults": "STORE_RESPONSE"; +"updateEsignetHostUrl": "UPDATE_HOST"; +"updateIsAccountSelectionConfirmationShown": "SHOWN_ACCOUNT_SELECTION_CONFIRMATION"; +"updateName": "UPDATE_NAME"; +"updatePartialDefaults": "STORE_RESPONSE"; +"updateUserShownWithHardwareKeystoreNotExists": "ACCEPT_HARDWARE_SUPPORT_NOT_EXISTS"; +"updateVcLabel": "UPDATE_VC_LABEL"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + "hasData": "STORE_RESPONSE"; +"hasPartialData": "STORE_RESPONSE"; + }; + eventsCausingServices: { + "resetInjiProps": "UPDATE_HOST"; + }; + matchesStates: "idle" | "init" | "resetInjiProps" | "showInjiTourGuide" | "storingDefaults"; + tags: never; + } + \ No newline at end of file diff --git a/machines/store.ts b/machines/store.ts index 915eb46a..6f7e89f4 100644 --- a/machines/store.ts +++ b/machines/store.ts @@ -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, }, 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, }, }, diff --git a/machines/store.typegen.ts b/machines/store.typegen.ts index 114c71c2..7acc497d 100644 --- a/machines/store.typegen.ts +++ b/machines/store.typegen.ts @@ -10,11 +10,13 @@ "xstate.init": { type: "xstate.init" }; }; invokeSrcNameMap: { - "checkStorageInitialisedOrNot": "done.invoke.store.checkStorageInitialisation:invocation[0]"; + "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]"; -"hasAndroidEncryptionKey": "done.invoke.store.checkEncryptionKey:invocation[0]"; +"hasEncryptionKey": "done.invoke.store.checkEncryptionKey:invocation[0]"; "store": "done.invoke._store"; }; missingImplementations: { @@ -32,17 +34,20 @@ }; eventsCausingGuards: { - "isCustomSecureKeystore": "KEY_RECEIVED"; + "hasData": "READY"; +"isCustomSecureKeystore": "KEY_RECEIVED"; }; eventsCausingServices: { - "checkStorageInitialisedOrNot": "ERROR"; + "checkFreshInstall": "xstate.init"; +"checkStorageInitialisedOrNot": "ERROR"; "clear": "KEY_RECEIVED"; +"clearKeys": "READY"; "generateEncryptionKey": "ERROR" | "IGNORE" | "READY"; "getEncryptionKey": "TRY_AGAIN"; -"hasAndroidEncryptionKey": never; +"hasEncryptionKey": never; "store": "KEY_RECEIVED" | "READY" | "done.invoke.store.resettingStorage:invocation[0]"; }; - matchesStates: "checkEncryptionKey" | "checkStorageInitialisation" | "failedReadingKey" | "generatingEncryptionKey" | "gettingEncryptionKey" | "ready" | "resettingStorage"; + matchesStates: "checkEncryptionKey" | "checkFreshInstall" | "checkStorageInitialisation" | "clearIosKeys" | "failedReadingKey" | "generatingEncryptionKey" | "gettingEncryptionKey" | "ready" | "resettingStorage"; tags: never; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 56108074..1749b148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 6de2182e..c0ab3663 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/shared/CloudBackupAndRestoreUtils.ts b/shared/CloudBackupAndRestoreUtils.ts index 040eb435..e2d4a1ee 100644 --- a/shared/CloudBackupAndRestoreUtils.ts +++ b/shared/CloudBackupAndRestoreUtils.ts @@ -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 { + 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 { + 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; diff --git a/shared/VCMetadata.ts b/shared/VCMetadata.ts index 6f525429..b59225fb 100644 --- a/shared/VCMetadata.ts +++ b/shared/VCMetadata.ts @@ -21,7 +21,7 @@ export class VCMetadata { timestamp?: string = ''; isVerified: boolean = false; displayId: string = ''; - + downloadKeyType: string = ''; constructor({ idType = '', requestId = '', @@ -32,6 +32,7 @@ export class VCMetadata { timestamp = '', isVerified = false, displayId = '', + downloadKeyType = '', } = {}) { this.idType = idType; this.requestId = requestId; @@ -42,6 +43,7 @@ export class VCMetadata { this.timestamp = timestamp; this.isVerified = isVerified; this.displayId = displayId; + this.downloadKeyType = downloadKeyType; } //TODO: Remove any typing and use appropriate typing @@ -60,6 +62,7 @@ export class VCMetadata { : vc.vcMetadata ? vc.vcMetadata.displayId : getDisplayId(vc.verifiableCredential), + downloadKeyType: vc.downloadKeyType, }); } @@ -99,7 +102,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(':'); @@ -111,6 +114,7 @@ export const getVCMetadata = (context: object) => { timestamp: context.timestamp ?? '', isVerified: context.vcMetadata.isVerified ?? false, displayId: getDisplayId(context.verifiableCredential), + downloadKeyType: keyType, }); }; diff --git a/shared/api.ts b/shared/api.ts index 41584a53..1c72dd0d 100644 --- a/shared/api.ts +++ b/shared/api.ts @@ -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,18 @@ async function generateCacheAPIFunctionWithCachePreference( fetchCall: (...props: any[]) => any, onErrorHardCodedValue?: any, ) { - const existingCredentials = await Keychain.getGenericPassword(); try { const response = await getItem( cacheKey, null, - existingCredentials?.password, + "" ); 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 +229,10 @@ 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; @@ -249,7 +247,7 @@ async function generateCacheAPIFunctionWithAPIPreference( const response = await getItem( cacheKey, null, - existingCredentials.password, + "" ); if (response) { diff --git a/shared/cryptoutil/KeyTypes.ts b/shared/cryptoutil/KeyTypes.ts new file mode 100644 index 00000000..2ecd4437 --- /dev/null +++ b/shared/cryptoutil/KeyTypes.ts @@ -0,0 +1,7 @@ +export enum KeyTypes { + RS256="RS256", + ES256="ES256", + ES256K="ES256K", + ED25519="ED25519" +} + diff --git a/shared/cryptoutil/cryptoUtil.ts b/shared/cryptoutil/cryptoUtil.ts index c7ea4adc..51fc7777 100644 --- a/shared/cryptoutil/cryptoUtil.ts +++ b/shared/cryptoutil/cryptoUtil.ts @@ -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 { - 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 { + 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: '', + }; + } +} diff --git a/shared/cryptoutil/signFormatConverter.ts b/shared/cryptoutil/signFormatConverter.ts new file mode 100644 index 00000000..77130104 --- /dev/null +++ b/shared/cryptoutil/signFormatConverter.ts @@ -0,0 +1,26 @@ +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; \ No newline at end of file diff --git a/shared/keystore/SecureKeystore.ts b/shared/keystore/SecureKeystore.ts index b4cd959d..a9484647 100644 --- a/shared/keystore/SecureKeystore.ts +++ b/shared/keystore/SecureKeystore.ts @@ -1,14 +1,7 @@ -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; diff --git a/shared/openId4VCI/Utils.ts b/shared/openId4VCI/Utils.ts index 31cd13b6..910695fb 100644 --- a/shared/openId4VCI/Utils.ts +++ b/shared/openId4VCI/Utils.ts @@ -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'; @@ -22,7 +22,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', @@ -142,31 +143,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[], @@ -284,10 +260,11 @@ export async function constructProofJWT( privateKey: string, accessToken: string, selectedIssuer: issuerType, + keyType: string, ): Promise { const jwtHeader = { - alg: 'RS256', - jwk: await getJWK(publicKey), + alg: keyType, + jwk: await getJWK(publicKey, keyType), typ: 'openid4vci-proof+jwt', }; const decodedToken = jwtDecode(accessToken); @@ -299,5 +276,88 @@ 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 { + 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 { + 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; }