From a6ed9031f5ac25b61a1ab77247ccd95f76c978d7 Mon Sep 17 00:00:00 2001 From: KiruthikaJeyashankar Date: Wed, 6 Nov 2024 18:55:27 +0530 Subject: [PATCH] [INJIMOB-2160] use pixelpass for processing mmdoc data for rendering (#1660) * [INJIMOB-2160] use pixelpass for processing mmdoc data for rendering Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] fix history not showing properly post download Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] add processing VC logic for iOS Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] modify selectCredential in VCItemSelectors to return selectCredential Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - remove debug logs Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - mark prop credentialWrapper as mandatory Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - optimize imports Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - remove unused functions / code block Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - format code Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] modify pixelpass module to get toJSON api from pixelpass class Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor - optimize imports Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] show keytype for mso_mdoc format VCs Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] use id in mso_mdoc VC as unique VC ID Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] refactor getDisplayId method Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] update ci-client & pixelpass version Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] add runtime asset to gitignore Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] remove unused var Signed-off-by: KiruthikaJeyashankar * [INJIMOB-2160] bypass verification for mock VCs This is done since mock VCs are not verifiable as of now. Co-Authored by: BalachandarG Signed-off-by: KiruthikaJeyashankar --------- Signed-off-by: KiruthikaJeyashankar --- .gitignore | 3 + android/app/build.gradle | 2 +- .../mosip/residentapp/RNPixelpassModule.java | 23 ++++- components/QrCodeOverlay.tsx | 3 +- components/VC/VCItemController.tsx | 2 +- components/VC/Views/VCCardView.tsx | 18 +++- components/VC/Views/VCDetailView.tsx | 55 ++++++----- components/VC/common/VCProcessor.ts | 26 +++++ components/VC/common/VCUtils.tsx | 9 +- ios/Inji.xcodeproj/project.pbxproj | 32 +++--- .../xcshareddata/swiftpm/Package.resolved | 10 +- ios/RNPixelpassModule.m | 3 + ios/RNPixelpassModule.swift | 10 ++ machines/Issuers/IssuersActions.ts | 20 +++- machines/Issuers/IssuersService.ts | 30 ++++-- .../VCItemMachine/VCItemSelectors.ts | 6 +- .../VCMetaMachine/vc.d.ts | 5 +- machines/bleShare/request/selectors.ts | 9 +- screens/Home/ViewVcModal.tsx | 24 ++++- screens/Home/ViewVcModalController.ts | 2 +- screens/Request/ReceiveVcScreen.tsx | 20 +++- screens/Scan/SendVPScreen.tsx | 2 +- screens/Scan/SendVcScreenController.ts | 15 ++- shared/Utils.ts | 4 + shared/VCMetadata.ts | 58 ++++++++--- shared/openId4VCI/Utils.ts | 99 ++++++++++--------- shared/storage.ts | 1 - 27 files changed, 338 insertions(+), 153 deletions(-) create mode 100644 components/VC/common/VCProcessor.ts diff --git a/.gitignore b/.gitignore index 52b60ed9..6e06dc40 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,6 @@ injitest/target/ ## Unit test .jest/cache + +## runtime bundles / assets +android/app/src/main/assets/faceModel.tflite \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 8f64577a..d43fd613 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -265,7 +265,7 @@ dependencies { implementation("io.mosip:inji-openID4VP:0.1.0-SNAPSHOT") implementation("com.facebook.react:react-android") implementation 'com.facebook.soloader:soloader:0.10.1+' - implementation("io.mosip:pixelpass:0.2.1") + implementation("io.mosip:pixelpass-aar:0.6.0-SNAPSHOT") implementation("io.mosip:secure-keystore:0.3.0-SNAPSHOT") implementation("io.mosip:tuvali:0.5.1") implementation("io.mosip:inji-vci-client:0.2.0-SNAPSHOT") diff --git a/android/app/src/main/java/io/mosip/residentapp/RNPixelpassModule.java b/android/app/src/main/java/io/mosip/residentapp/RNPixelpassModule.java index 8f6678b0..7ec9a404 100644 --- a/android/app/src/main/java/io/mosip/residentapp/RNPixelpassModule.java +++ b/android/app/src/main/java/io/mosip/residentapp/RNPixelpassModule.java @@ -1,16 +1,19 @@ package io.mosip.residentapp; + import io.mosip.pixelpass.PixelPass; +import io.mosip.pixelpass.cbor.Utils; + import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Promise; public class RNPixelpassModule extends ReactContextBaseJavaModule { - private PixelPass pixelPass; + private final PixelPass pixelPass; + public RNPixelpassModule(ReactApplicationContext reactContext) { super(reactContext); - pixelPass = new PixelPass(); - + pixelPass = new PixelPass(); } @Override @@ -31,10 +34,20 @@ public class RNPixelpassModule extends ReactContextBaseJavaModule { @ReactMethod public void generateQRData(String data, String header, Promise promise) { try { - String qrData=pixelPass.generateQRData(data,header); + String qrData = pixelPass.generateQRData(data, header); promise.resolve(qrData); } catch (Exception e) { - promise.reject("ERROR_GENERATING_QR", "Failed to generate QR Data: " + e.toString()); + promise.reject("ERROR_GENERATING_QR", "Failed to generate QR Data: " + e); + } + } + + @ReactMethod + public void decodeBase64UrlEncodedCBORData(String data, Promise promise) { + try { + Object decodedData = pixelPass.toJson(data); + promise.resolve(decodedData.toString()); + } catch (Exception e) { + promise.reject("ERROR_DECODING_DATA", "Failed to decode Data: " + e); } } } diff --git a/components/QrCodeOverlay.tsx b/components/QrCodeOverlay.tsx index 1714cb24..02ad3f1c 100644 --- a/components/QrCodeOverlay.tsx +++ b/components/QrCodeOverlay.tsx @@ -32,8 +32,9 @@ export const QrCodeOverlay: React.FC = props => { throw new Error('No key data found'); } } catch { + const {credential} = props.verifiableCredential; qrData = await RNPixelpassModule.generateQRData( - JSON.stringify(props.verifiableCredential), + JSON.stringify(credential), '', ); await RNSecureKeystoreModule.storeData(props.meta.id, qrData); diff --git a/components/VC/VCItemController.tsx b/components/VC/VCItemController.tsx index 2f3254d9..aadc5ed7 100644 --- a/components/VC/VCItemController.tsx +++ b/components/VC/VCItemController.tsx @@ -5,8 +5,8 @@ import { selectGeneratedOn, selectKebabPopUp, selectWalletBindingResponse, - selectCredential, selectVerifiableCredentialData, + selectCredential, } from '../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors'; import {useInterpret, useSelector} from '@xstate/react'; import {VCItemProps} from './Views/VCCardView'; diff --git a/components/VC/Views/VCCardView.tsx b/components/VC/Views/VCCardView.tsx index 8ffdec8e..6c2963d0 100644 --- a/components/VC/Views/VCCardView.tsx +++ b/components/VC/Views/VCCardView.tsx @@ -14,6 +14,7 @@ import {CARD_VIEW_DEFAULT_FIELDS, isVCLoaded} from '../common/VCUtils'; import {VCItemMachine} from '../../../machines/VerifiableCredential/VCItemMachine/VCItemMachine'; import {useTranslation} from 'react-i18next'; import {Copilot} from '../../ui/Copilot'; +import {VCProcessor} from '../common/VCProcessor'; export const VCCardView: React.FC = props => { const controller = useVcItemController(props); @@ -30,10 +31,23 @@ export const VCCardView: React.FC = props => { controller.UPDATE_VC_METADATA(props.vcMetadata); }, [props.vcMetadata]); - const vc = props.isDownloading ? null : controller.credential; - const [fields, setFields] = useState([]); const [wellknown, setWellknown] = useState(null); + const [vc, setVc] = useState(null); + + useEffect(() => { + async function loadVc() { + if (!props.isDownloading) { + const processedData = await VCProcessor.processForRendering( + controller.credential, + controller.verifiableCredentialData.format, + ); + setVc(processedData); + } + } + + loadVc(); + }, [props.isDownloading, controller.credential]); useEffect(() => { const { diff --git a/components/VC/Views/VCDetailView.tsx b/components/VC/Views/VCDetailView.tsx index b4813b50..c7cb2ab2 100644 --- a/components/VC/Views/VCDetailView.tsx +++ b/components/VC/Views/VCDetailView.tsx @@ -3,6 +3,7 @@ import {useTranslation} from 'react-i18next'; import {Image, ImageBackground, View} from 'react-native'; import { Credential, + CredentialWrapper, VerifiableCredential, VerifiableCredentialData, WalletBindingResponse, @@ -40,7 +41,7 @@ const getProfileImage = (face: any) => { }; export const VCDetailView: React.FC = props => { - const {t, i18n} = useTranslation('VcDetails'); + const {t} = useTranslation('VcDetails'); const logo = props.verifiableCredentialData.issuerLogo; const face = props.verifiableCredentialData.face; const verifiableCredential = props.credential; @@ -94,7 +95,9 @@ export const VCDetailView: React.FC = props => { {getProfileImage(face)} = props => { )} - {shouldShowHrLine(verifiableCredential) && ( - <> - - - {fieldItemIterator( + <> + + + {shouldShowHrLine(verifiableCredential) && + fieldItemIterator( DETAIL_VIEW_BOTTOM_SECTION_FIELDS, verifiableCredential, props.wellknown, props, )} - - - - )} + + + @@ -246,6 +248,7 @@ export interface VCItemDetailsProps { credential: VerifiableCredential | Credential; verifiableCredentialData: VerifiableCredentialData; walletBindingResponse?: WalletBindingResponse; + credentialWrapper: CredentialWrapper; onBinding?: () => void; activeTab?: Number; vcHasImage: boolean; diff --git a/components/VC/common/VCProcessor.ts b/components/VC/common/VCProcessor.ts new file mode 100644 index 00000000..8dd9124b --- /dev/null +++ b/components/VC/common/VCProcessor.ts @@ -0,0 +1,26 @@ +import {NativeModules} from 'react-native'; +import {VerifiableCredential} from '../../../machines/VerifiableCredential/VCMetaMachine/vc'; +import {VCFormat} from '../../../shared/VCFormat'; +import {getVerifiableCredential} from '../../../machines/VerifiableCredential/VCItemMachine/VCItemSelectors'; +import {parseJSON} from '../../../shared/Utils'; + +const {RNPixelpassModule} = NativeModules; + +export class VCProcessor { + static async processForRendering( + vcData: VerifiableCredential, + vcFormat: String, + ): Promise { + if (vcFormat === VCFormat.mso_mdoc) { + if (vcData.processedCredential) { + return vcData.processedCredential; + } + const decodedString = + await RNPixelpassModule.decodeBase64UrlEncodedCBORData( + vcData.credential.toString(), + ); + return parseJSON(decodedString); + } + return getVerifiableCredential(vcData); + } +} diff --git a/components/VC/common/VCUtils.tsx b/components/VC/common/VCUtils.tsx index 7d057228..98af9430 100644 --- a/components/VC/common/VCUtils.tsx +++ b/components/VC/common/VCUtils.tsx @@ -232,7 +232,7 @@ export const fieldItemIterator = ( }; export const isVCLoaded = ( - verifiableCredential: Credential, + verifiableCredential: Credential | null, fields: string[], ) => { return verifiableCredential != null && fields.length > 0; @@ -258,7 +258,11 @@ export const getIdType = ( wellknown: CredentialTypes | IssuerWellknownResponse, credentialConfigurationId: string | undefined = undefined, ): string => { - if (wellknown && wellknown?.display) { + if ( + wellknown && + wellknown['credential_configurations_supported'] === undefined && + wellknown?.display + ) { const idTypeObj = wellknown.display.map((displayProps: any) => { return {language: displayProps.locale, value: displayProps.name}; }); @@ -266,7 +270,6 @@ export const getIdType = ( } else if (wellknown && Object.keys(wellknown).length > 0) { let supportedCredentialsWellknown; wellknown = parseJSON(wellknown) as unknown as Object[]; - if (!!!wellknown['credential_configurations_supported']) { return i18n.t('VcDetails:nationalCard'); } diff --git a/ios/Inji.xcodeproj/project.pbxproj b/ios/Inji.xcodeproj/project.pbxproj index f2ab6453..ca16bebd 100644 --- a/ios/Inji.xcodeproj/project.pbxproj +++ b/ios/Inji.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 1E6875E92CA554E80086D870 /* OpenID4VP in Frameworks */ = {isa = PBXBuildFile; productRef = 1E6875E82CA554E80086D870 /* OpenID4VP */; }; 1E6875EB2CA554FD0086D870 /* RNOpenID4VPModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EA2CA554FD0086D870 /* RNOpenID4VPModule.m */; }; 1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E6875EC2CA5550F0086D870 /* RNOpenID4VPModule.swift */; }; + 34873E472CD8DAF3004DE734 /* VCIClient in Frameworks */ = {isa = PBXBuildFile; productRef = 34873E462CD8DAF3004DE734 /* VCIClient */; }; + 34873E4D2CD8DD11004DE734 /* pixelpass in Frameworks */ = {isa = PBXBuildFile; productRef = 34873E4C2CD8DD11004DE734 /* pixelpass */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; 73295844242A4AD3AA52D0BE /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = D98B96A488E54CBDB286B26F /* noop-file.swift */; }; 96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Inji.a */; }; @@ -29,12 +31,10 @@ 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 */; }; E86208152C0335C5007C3E24 /* RNVCIClientModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86208142C0335C5007C3E24 /* RNVCIClientModule.swift */; }; E86208172C0335EC007C3E24 /* RNVCIClientModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E86208162C0335EC007C3E24 /* RNVCIClientModule.m */; }; - E8AF2B9D2C0D93E800E775F6 /* VCIClient in Frameworks */ = {isa = PBXBuildFile; productRef = E8AF2B9C2C0D93E800E775F6 /* VCIClient */; }; FD8D20B92BBAACDF009AD01C /* Fontisto.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FD8D20A62BBAACDF009AD01C /* Fontisto.ttf */; }; FD8D20BA2BBAACDF009AD01C /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FD8D20A72BBAACDF009AD01C /* Octicons.ttf */; }; FD8D20BB2BBAACDF009AD01C /* Feather.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FD8D20A82BBAACDF009AD01C /* Feather.ttf */; }; @@ -113,10 +113,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E8AF2B9D2C0D93E800E775F6 /* VCIClient in Frameworks */, + 34873E472CD8DAF3004DE734 /* VCIClient in Frameworks */, 9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */, 1E6875E92CA554E80086D870 /* OpenID4VP in Frameworks */, - 9CE34B1F2BFE03E4001AF414 /* pixelpass in Frameworks */, + 34873E4D2CD8DD11004DE734 /* pixelpass in Frameworks */, 9C7CDF492C89802C00243A9A /* securekeystore in Frameworks */, 96905EF65AED1B983A6B3ABC /* libPods-Inji.a in Frameworks */, ); @@ -281,11 +281,11 @@ ); name = Inji; packageProductDependencies = ( - 9CE34B1E2BFE03E4001AF414 /* pixelpass */, - E8AF2B9C2C0D93E800E775F6 /* VCIClient */, 9C4850422C3E5873002ECBD5 /* ios-tuvali-library */, 9C7CDF482C89802C00243A9A /* securekeystore */, 1E6875E82CA554E80086D870 /* OpenID4VP */, + 34873E462CD8DAF3004DE734 /* VCIClient */, + 34873E4C2CD8DD11004DE734 /* pixelpass */, ); productName = Inji; productReference = 13B07F961A680F5B00A75B9A /* Inji.app */; @@ -314,8 +314,8 @@ ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( - 9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */, - E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */, + 34873E4B2CD8DD11004DE734 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */, + 34873E452CD8DAF3004DE734 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */, 9C4850412C3E5873002ECBD5 /* XCRemoteSwiftPackageReference "tuvali-ios-swift" */, 9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */, 1E6875E72CA554E80086D870 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */, @@ -787,17 +787,17 @@ kind = branch; }; }; - 9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */ = { + 34873E452CD8DAF3004DE734 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mosip/pixelpass-ios-swift/"; + repositoryURL = "https://github.com/mosip/inji-vci-client-ios-swift"; requirement = { branch = develop; kind = branch; }; }; - E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */ = { + 34873E4B2CD8DD11004DE734 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mosip/inji-vci-client-ios-swift.git"; + repositoryURL = "https://github.com/mosip/pixelpass-ios-swift"; requirement = { branch = develop; kind = branch; @@ -821,14 +821,14 @@ package = 9C7CDF472C89802C00243A9A /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */; productName = securekeystore; }; - 9CE34B1E2BFE03E4001AF414 /* pixelpass */ = { + 34873E4C2CD8DD11004DE734 /* pixelpass */ = { isa = XCSwiftPackageProductDependency; - package = 9CE34B1D2BFE03E4001AF414 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */; + package = 34873E4B2CD8DD11004DE734 /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */; productName = pixelpass; }; - E8AF2B9C2C0D93E800E775F6 /* VCIClient */ = { + 34873E462CD8DAF3004DE734 /* VCIClient */ = { isa = XCSwiftPackageProductDependency; - package = E86208242C039895007C3E24 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */; + package = 34873E452CD8DAF3004DE734 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */; productName = VCIClient; }; /* End XCSwiftPackageProductDependency section */ diff --git a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved index 652048a7..448371d7 100644 --- a/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "f9795fd8da608010a3959d5ff512f9c3efa9ac32efccde3511dfd65f1d22ce33", + "originHash" : "178f6c7c607eeb08b99a4966015d08339500de64791888a2e79d6b7afae53659", "pins" : [ { "identity" : "base45-swift", @@ -40,19 +40,19 @@ { "identity" : "inji-vci-client-ios-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/mosip/inji-vci-client-ios-swift.git", + "location" : "https://github.com/mosip/inji-vci-client-ios-swift", "state" : { "branch" : "develop", - "revision" : "0ff87e2eb1e6f078be8a004214cfaed8aae7fa93" + "revision" : "5fe56728106cffc8eff47e43437f288307c9d91b" } }, { "identity" : "pixelpass-ios-swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/mosip/pixelpass-ios-swift/", + "location" : "https://github.com/mosip/pixelpass-ios-swift", "state" : { "branch" : "develop", - "revision" : "170f3489e77940212f471ee1cb9f3ab392039a45" + "revision" : "7111692893287015599e4e0297bcade6a5375527" } }, { diff --git a/ios/RNPixelpassModule.m b/ios/RNPixelpassModule.m index acb03505..b1c5d6fc 100644 --- a/ios/RNPixelpassModule.m +++ b/ios/RNPixelpassModule.m @@ -11,6 +11,9 @@ RCT_EXTERN_METHOD(decode:(NSString *)parameter RCT_EXTERN_METHOD(generateQRData:(NSString *)data header:(NSString *)header resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(decodeBase64UrlEncodedCBORData:(NSString *)data + resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + @end diff --git a/ios/RNPixelpassModule.swift b/ios/RNPixelpassModule.swift index 86e9666f..2e971d08 100644 --- a/ios/RNPixelpassModule.swift +++ b/ios/RNPixelpassModule.swift @@ -27,6 +27,16 @@ class RNPixelpassModule: NSObject, RCTBridgeModule { reject("E_NO_IMAGE", "Unable to generate QR data", nil) } } + + @objc + func decodeBase64UrlEncodedCBORData(_ data: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + do { + let decodedData = try PixelPass().toJson(base64UrlEncodedCborEncodedString: data) + resolve(decodedData) + } catch { + reject("ERROR_DECODING", "Unable to decode data", nil) + } + } @objc static func requiresMainQueueSetup() -> Bool { diff --git a/machines/Issuers/IssuersActions.ts b/machines/Issuers/IssuersActions.ts index 2807bd2e..ba0f685c 100644 --- a/machines/Issuers/IssuersActions.ts +++ b/machines/Issuers/IssuersActions.ts @@ -7,7 +7,6 @@ import { MY_VCS_STORE_KEY, NETWORK_REQUEST_FAILED, REQUEST_TIMEOUT, - TECHNICAL_ERROR, isIOS, } from '../../shared/constants'; import {assign, send} from 'xstate'; @@ -168,10 +167,21 @@ export const IssuersActions = (model: any) => { storeVerifiableCredentialData: send( (context: any) => { - const vcMeatadata = getVCMetadata(context, context.keyType); - return StoreEvents.SET(vcMeatadata.getVcKey(), { - ...context.credentialWrapper, - vcMetadata: vcMeatadata, + const vcMetadata = getVCMetadata(context, context.keyType); + const { + verifiableCredential: { + processedCredential, + ...filteredVerifiableCredential + }, + ...rest + } = context.credentialWrapper; + const storableData = { + ...rest, + verifiableCredential: filteredVerifiableCredential, + }; + return StoreEvents.SET(vcMetadata.getVcKey(), { + ...storableData, + vcMetadata: vcMetadata, }); }, { diff --git a/machines/Issuers/IssuersService.ts b/machines/Issuers/IssuersService.ts index 43c42cc2..28c4b54c 100644 --- a/machines/Issuers/IssuersService.ts +++ b/machines/Issuers/IssuersService.ts @@ -15,13 +15,18 @@ import { generateKeyPair, } from '../../shared/cryptoutil/cryptoUtil'; import {NativeModules} from 'react-native'; -import {verifyCredential} from '../../shared/vcjs/verifyCredential'; +import { + VerificationErrorMessage, + VerificationErrorType, + verifyCredential, +} from '../../shared/vcjs/verifyCredential'; import { getImpressionEventData, sendImpressionEvent, } from '../../shared/telemetry/TelemetryUtils'; import {TelemetryConstants} from '../../shared/telemetry/TelemetryConstants'; import {VciClient} from '../../shared/vciClient/VciClient'; +import {isMockVC} from '../../shared/Utils'; export const IssuersService = () => { return { @@ -75,7 +80,7 @@ export const IssuersService = () => { ); console.info(`VC download via ${context.selectedIssuerId} is successful`); - return updateCredentialInformation(context, credential); + return await updateCredentialInformation(context, credential); }, invokeAuthorization: async (context: any) => { sendImpressionEvent( @@ -119,12 +124,21 @@ export const IssuersService = () => { }, verifyCredential: async (context: any) => { - const verificationResult = await verifyCredential( - context.verifiableCredential?.credential, - context.selectedCredentialType.format, - ); - if (!verificationResult.isVerified) { - throw new Error(verificationResult.verificationErrorCode); + //TODO: Remove bypassing verification of mock VCs once mock VCs are verifiable + if (!isMockVC(context.selectedIssuerId)) { + const verificationResult = await verifyCredential( + context.verifiableCredential?.credential, + context.selectedCredentialType.format, + ); + if (!verificationResult.isVerified) { + throw new Error(verificationResult.verificationErrorCode); + } + } else { + return { + isVerified: true, + verificationMessage: VerificationErrorMessage.NO_ERROR, + verificationErrorCode: VerificationErrorType.NO_ERROR, + }; } }, }; diff --git a/machines/VerifiableCredential/VCItemMachine/VCItemSelectors.ts b/machines/VerifiableCredential/VCItemMachine/VCItemSelectors.ts index 1dcd0ede..a3c306cb 100644 --- a/machines/VerifiableCredential/VCItemMachine/VCItemSelectors.ts +++ b/machines/VerifiableCredential/VCItemMachine/VCItemSelectors.ts @@ -7,6 +7,8 @@ import { VerifiableCredential, VerifiableCredentialData, } from '../VCMetaMachine/vc'; +import {VCFormat} from '../../../shared/VCFormat'; +import {VCProcessor} from '../../../components/VC/common/VCProcessor'; type State = StateFrom; @@ -36,8 +38,8 @@ export function getVerifiableCredential( return verifiableCredential?.credential || verifiableCredential; } -export function selectCredential(state: State): Credential { - return getVerifiableCredential(state.context.verifiableCredential); +export function selectCredential(state: State): VerifiableCredential { + return state.context.verifiableCredential; } export function selectVerifiableCredentialData( diff --git a/machines/VerifiableCredential/VCMetaMachine/vc.d.ts b/machines/VerifiableCredential/VCMetaMachine/vc.d.ts index f86c233e..0d1e1ef7 100644 --- a/machines/VerifiableCredential/VCMetaMachine/vc.d.ts +++ b/machines/VerifiableCredential/VCMetaMachine/vc.d.ts @@ -43,7 +43,7 @@ export interface CredentialSubject { type VCContext = (string | Record)[]; -export interface Credential { +export type Credential = { credentialConfigurationId: any; '@context': VCContext; credentialSubject: CredentialSubject; @@ -58,11 +58,12 @@ export interface Credential { verificationMethod: string; }; type: string[]; -} +} | string export interface VerifiableCredential { issuerLogo: logoType; credential: Credential; + processedCredential?: object; wellKnown: string; credentialConfigurationId: string; } diff --git a/machines/bleShare/request/selectors.ts b/machines/bleShare/request/selectors.ts index ca4a2ad7..ac971f51 100644 --- a/machines/bleShare/request/selectors.ts +++ b/machines/bleShare/request/selectors.ts @@ -2,7 +2,7 @@ import {StateFrom} from 'xstate'; import {requestMachine} from './requestMachine'; import {VCMetadata} from '../../../shared/VCMetadata'; import {getMosipLogo} from '../../../components/VC/common/VCUtils'; -import {Credential} from '../../VerifiableCredential/VCMetaMachine/vc'; +import {VerifiableCredential} from '../../VerifiableCredential/VCMetaMachine/vc'; type State = StateFrom; @@ -10,11 +10,8 @@ export function selectSenderInfo(state: State) { return state.context.senderInfo; } -export function selectCredential(state: State): Credential { - return ( - state.context.incomingVc?.verifiableCredential?.credential || - state.context.incomingVc?.verifiableCredential - ); +export function selectCredential(state: State): VerifiableCredential { + return state.context.incomingVc?.verifiableCredential; } export function selectVerifiableCredentialData(state: State) { diff --git a/screens/Home/ViewVcModal.tsx b/screens/Home/ViewVcModal.tsx index 4a59da22..b8f3daba 100644 --- a/screens/Home/ViewVcModal.tsx +++ b/screens/Home/ViewVcModal.tsx @@ -33,12 +33,29 @@ import { BannerNotification, BannerStatus, } from '../../components/BannerNotification'; +import {VCProcessor} from '../../components/VC/common/VCProcessor'; export const ViewVcModal: React.FC = props => { const {t} = useTranslation('ViewVcModal'); const controller = useViewVcModal(props); const profileImage = controller.verifiableCredentialData.face; const verificationStatus = controller.verificationStatus; + const [verifiableCredential, setVerifiableCredential] = useState(null); + + useEffect(() => { + async function processVC() { + if (controller.credential) { + const vcData = await VCProcessor.processForRendering( + controller.credential, + controller.verifiableCredentialData.format, + ); + setVerifiableCredential(vcData); + } + } + + processVC(); + }, [controller.credential]); + useEffect(() => { if (controller.isVerificationInProgress) { controller.SHOW_VERIFICATION_STATUS_BANNER(); @@ -85,7 +102,7 @@ export const ViewVcModal: React.FC = props => { } /> - {isVCLoaded(controller.credential, fields) ? ( + {isVCLoaded(verifiableCredential, fields) ? ( props.vcItemActor.send('KEBAB_POPUP')} accessible={false}> @@ -143,13 +160,14 @@ export const ViewVcModal: React.FC = props => { /> )} - {!isVCLoaded(controller.credential, fields) ? ( + {!isVCLoaded(verifiableCredential, fields) ? ( ) : ( { const {t} = useTranslation('ReceiveVcScreen'); @@ -23,6 +24,22 @@ export const ReceiveVcScreen: React.FC = () => { const verifiableCredentialData = controller.verifiableCredentialData; const profileImage = verifiableCredentialData.face; + const [credential, setCredential] = useState(null); + + useEffect(() => { + async function processVC() { + if (controller.credential) { + const vcData = await VCProcessor.processForRendering( + controller.credential, + verifiableCredentialData.vcMetadata.format, + ); + setCredential(vcData); + } + } + + processVC(); + }, [controller.credential]); + useEffect(() => { getDetailedViewFields( verifiableCredentialData?.issuer, @@ -54,7 +71,8 @@ export const ReceiveVcScreen: React.FC = () => { (vcRef: ActorRefFrom) => { setSelectedIndex(index); - const {serviceRefs, wellknownResponse, ...vcData} = - vcRef.getSnapshot().context; + const { + serviceRefs, + wellknownResponse, + verifiableCredential: { + processedCredential, + ...filteredVerifiableCredential + }, + ...rest + } = vcRef.getSnapshot().context; + const vcData = { + ...rest, + verifiableCredential: filteredVerifiableCredential, + }; scanService.send( ScanEvents.SELECT_VC(vcData, VCShareFlowType.SIMPLE_SHARE), ); diff --git a/shared/Utils.ts b/shared/Utils.ts index 754453d6..49ce4fa7 100644 --- a/shared/Utils.ts +++ b/shared/Utils.ts @@ -35,6 +35,10 @@ export const isMosipVC = (issuer: string) => { return issuer === Issuers.Mosip || issuer === Issuers.MosipOtp; }; +export const isMockVC = (issuer: string) => { + return issuer.toLowerCase().startsWith('mock'); +}; + export const parseJSON = (input: any) => { let result = null; try { diff --git a/shared/VCMetadata.ts b/shared/VCMetadata.ts index 41fa552b..6c13e1e0 100644 --- a/shared/VCMetadata.ts +++ b/shared/VCMetadata.ts @@ -4,7 +4,7 @@ import { VcIdType, VerifiableCredential, } from '../machines/VerifiableCredential/VCMetaMachine/vc'; -import {CredentialIdForMsoMdoc, Protocols} from './openId4VCI/Utils'; +import {Protocols} from './openId4VCI/Utils'; import {getMosipIdentifier} from './commonUtil'; import {VCFormat} from './VCFormat'; @@ -67,7 +67,7 @@ export class VCMetadata { ? vc.displayId : vc.vcMetadata ? vc.vcMetadata.displayId - : getDisplayId(vc.verifiableCredential), + : getDisplayId(vc.verifiableCredential, vc.format), downloadKeyType: vc.downloadKeyType, }); } @@ -119,27 +119,53 @@ export const getVCMetadata = (context: object, keyType: string) => { id: `${credentialId} + '_' + ${issuer}`, timestamp: context.timestamp ?? '', isVerified: context.vcMetadata.isVerified ?? false, - displayId: getDisplayId(context.verifiableCredential), - format: context.credentialWrapper.format, + displayId: getDisplayId( + context['verifiableCredential'] as VerifiableCredential, + context['credentialWrapper'].format, + ), + format: context['credentialWrapper'].format, downloadKeyType: keyType, }); }; const getDisplayId = ( verifiableCredential: VerifiableCredential | Credential, + format: string, ) => { - if (verifiableCredential?.credential) { - if (verifiableCredential.credential?.credentialSubject) { - return ( - verifiableCredential.credential?.credentialSubject?.policyNumber || - getMosipIdentifier(verifiableCredential.credential.credentialSubject) - ); - } else { - return CredentialIdForMsoMdoc(verifiableCredential); + try { + if (format === VCFormat.mso_mdoc) { + const namespaces = + (verifiableCredential as VerifiableCredential)?.processedCredential?.[ + 'issuerSigned' + ]['nameSpaces'] ?? {}; + + let displayId: string | undefined; + for (const namespace in namespaces) { + displayId = namespaces[namespace].find( + (element: object) => + element['elementIdentifier'] === 'document_number', + ).elementValue; + if (!!displayId) break; + } + + if (!!displayId) return displayId; + console.error('error in id getting ', 'Id not found for the credential'); + throw new Error('Id not found for the credential'); } + if (verifiableCredential?.credential) { + if (verifiableCredential.credential?.credentialSubject) { + return ( + verifiableCredential.credential?.credentialSubject?.policyNumber || + getMosipIdentifier(verifiableCredential.credential.credentialSubject) + ); + } + } + return ( + verifiableCredential?.credentialSubject?.policyNumber || + getMosipIdentifier(verifiableCredential.credentialSubject) + ); + } catch (error) { + console.error('Error getting the display Id - ', error); + return null; } - return ( - verifiableCredential?.credentialSubject?.policyNumber || - getMosipIdentifier(verifiableCredential.credentialSubject) - ); }; diff --git a/shared/openId4VCI/Utils.ts b/shared/openId4VCI/Utils.ts index d75a3e20..bd98ca0a 100644 --- a/shared/openId4VCI/Utils.ts +++ b/shared/openId4VCI/Utils.ts @@ -26,6 +26,7 @@ import {KeyTypes} from '../cryptoutil/KeyTypes'; import {VCFormat} from '../VCFormat'; import {UnsupportedVcFormat} from '../error/UnsupportedVCFormat'; import {VCMetadata} from '../VCMetadata'; +import {VCProcessor} from '../../components/VC/common/VCProcessor'; export const Protocols = { OpenId4VCI: 'OpenId4VCI', @@ -62,49 +63,64 @@ export const isActivationNeeded = (issuer: string) => { export const Issuers_Key_Ref = 'OpenId4VCI_KeyPair'; -export const getIdentifier = (context, credential: VerifiableCredential) => { - const credentialIdentifier = credential.credential.id; - if (credentialIdentifier === undefined) { - return ( - context.selectedIssuer.credential_issuer + - ':' + - context.selectedIssuer.protocol + - ':' + - CredentialIdForMsoMdoc(credential) - ); - } else { - const credId = credentialIdentifier.startsWith('did') - ? credentialIdentifier.split(':') - : credentialIdentifier.split('/'); - return ( - context.selectedIssuer.credential_issuer + - ':' + - context.selectedIssuer.protocol + - ':' + - credId[credId.length - 1] - ); - } -}; - -export const updateCredentialInformation = ( +export const getIdentifier = ( context, credential: VerifiableCredential, -): CredentialWrapper => { + format: string, +) => { + let credentialIdentifier = ''; + if (format === VCFormat.mso_mdoc) { + credentialIdentifier = credential?.processedCredential?.['id'] ?? ''; + } else if (typeof credential.credential !== 'string') { + credentialIdentifier = credential.credential.id; + } + const credId = + credentialIdentifier.startsWith('did') || + credentialIdentifier.startsWith('urn:') + ? credentialIdentifier.split(':') + : credentialIdentifier.split('/'); + return ( + context.selectedIssuer.credential_issuer + + ':' + + context.selectedIssuer.protocol + + ':' + + credId[credId.length - 1] + ); +}; + +export const updateCredentialInformation = async ( + context, + credential: VerifiableCredential, +): Promise => { + let processedCredential; + if (context.selectedCredentialType.format === VCFormat.mso_mdoc) { + processedCredential = await VCProcessor.processForRendering( + credential, + context.selectedCredentialType.format, + ); + } + const verifiableCredential = { + ...credential, + wellKnown: context.selectedIssuer['wellknown_endpoint'], + credentialConfigurationId: context.selectedCredentialType.id, + issuerLogo: getDisplayObjectForCurrentLanguage( + context.selectedIssuer.display, + )?.logo, + processedCredential, + }; return { - verifiableCredential: { - ...credential, - wellKnown: context.selectedIssuer['wellknown_endpoint'], - credentialConfigurationId: context.selectedCredentialType.id, - issuerLogo: getDisplayObjectForCurrentLanguage( - context.selectedIssuer.display, - )?.logo, - }, + verifiableCredential, format: context.selectedCredentialType.format, - identifier: getIdentifier(context, credential), + identifier: getIdentifier( + context, + verifiableCredential, + context.selectedCredentialType.format, + ), generatedOn: new Date(), - vcMetadata: - {...context.vcMetadata, format: context.selectedCredentialType.format} || - {}, + vcMetadata: { + ...context.vcMetadata, + format: context.selectedCredentialType.format, + }, }; }; @@ -259,13 +275,6 @@ export enum ErrorMessage { CREDENTIAL_TYPE_DOWNLOAD_FAILURE = 'credentialTypeListDownloadFailure', } -export function CredentialIdForMsoMdoc(credential: VerifiableCredential) { - return credential.credential['issuerSigned']['nameSpaces'][ - 'org.iso.18013.5.1' - ].find(element => element.elementIdentifier === 'document_number') - .elementValue; -} - export async function constructProofJWT( publicKey: any, privateKey: any, diff --git a/shared/storage.ts b/shared/storage.ts index 113c40bf..6a78f331 100644 --- a/shared/storage.ts +++ b/shared/storage.ts @@ -34,7 +34,6 @@ import fileStorage from './fileStorage'; import {DocumentDirectoryPath, ReadDirItem} from 'react-native-fs'; import {verifyCredential} from './vcjs/verifyCredential'; import {Credential} from '../machines/VerifiableCredential/VCMetaMachine/vc'; -import {isMosipVC} from './Utils'; export const MMKV = new MMKVLoader().initialize(); const {RNSecureKeystoreModule} = NativeModules;