[INJIMOB-3647] refactor: update isRevoked data type (#2149)

* [INJIMOB-3647] refactor: modify data type of isRevoked to EvaluationStatus

Type representing any possible value of EvaluationStatus.

- "TRUE" → Condition was evaluated and is positively true
- "FALSE" → Condition was evaluated and is definitively false
- "UNDETERMINED" → Condition could not be evaluated due to an error

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: modify data type of isRevoked to EvaluationStatus

Type representing any possible value of EvaluationStatus.

- "TRUE" → Condition was evaluated and is positively true
- "FALSE" → Condition was evaluated and is definitively false
- "UNDETERMINED" → Condition could not be evaluated due to an error

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: change statuslistVC type to record from string

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

# Conflicts:
#	shared/vcjs/verifyCredential.ts

* [INJIMOB-3647] refactor: update status revoke check to check for null status

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: VCMetadat constructor isRevoked param

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: rename EvaluationStatus to RevocationStatus

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3647] refactor: modify revocation status logs

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

---------

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
This commit is contained in:
KiruthikaJeyashankar
2025-12-01 11:28:11 +05:30
committed by GitHub
parent 0e667bd46d
commit 9457ad0d9f
18 changed files with 348 additions and 244 deletions

View File

@@ -64,7 +64,7 @@ fileignoreconfig:
- filename: assets/Finger_Print_Icon.svg
checksum: 776d4fe4fc4b54d185ccf97daf0511b9fe2c0e0f7c1a809047020e5e8a100db6
- filename: screens/MainLayout.tsx
checksum: dd31361997111c28461239e986112a30ee986e99432ac3016033508863b90ddd
checksum: 8909ef957c866221e864c6edaf93081af7f5968857200284bf7047631f525322
- filename: android/app/build.gradle
checksum: 8d5715e179a398518e6acff82c75b27077c9f893dc90b2972c77f9a09f10be95
- filename: .github/workflows/push-triggers.yml
@@ -270,7 +270,7 @@ fileignoreconfig:
- filename: machines/Issuers/IssuersService.ts
checksum: e3832dff27687abc28609d2b281e570b4b0017995b7cfb56627a6b96949c469a
- filename: screens/Home/ViewVcModal.tsx
checksum: cfb25d562185488432b76287c4ef93359c1c64d8e29f5755d4c0a726c1485442
checksum: 847d45d566b7fc86dd3ebbba74bf587399dd7754466e42ebdecd462a157705e9
- filename: injitest/src/main/resources/TestData.json
checksum: 1b5af14c96b456898259b4cb7a5607b006404cf0360274bdc204d7d065698e3c
- filename: injitest/src/test/java/androidTestCases/ActivateVcTest.java
@@ -385,7 +385,7 @@ fileignoreconfig:
- filename: android/app/src/main/java/io/mosip/residentapp/InjiOpenId4VPModule.java
checksum: 6b315164dca5de95c11e0dc8cbb480207b19c312b1c9135adc39ef74a1ff7e35
- filename: screens/Scan/SendVPScreenController.ts
checksum: f898ac7f1ecfa1df17e33b327d675f57debf2d5bd56052fc047dd03577354590
checksum: aa228c43a01e653b9da6ee354a39c942bec25848aa9631650611d1b5f85623d7
- filename: screens/Scan/SendVPScreen.tsx
checksum: de80cb9a932ed99e224438a8c373d117807101a39a440e97977654ef6935af6c
- filename: machines/openID4VP/openID4VPMachine.typegen.ts
@@ -416,4 +416,8 @@ fileignoreconfig:
checksum: 669e85d1c8ff526b97fa4ed4b8ed33a100eaba9f2f41bceccd75dc7a85a12103
- filename: screens/Home/MyVcsTab.tsx
checksum: 68ff83c5d9062fbc077d008956fa654540253c52ce68d7105c175c51562b3dc9
- filename: components/VC/common/VcStatustooTip.tsx
checksum: a48c88da719fadcb3d1aeb730a23735709c0c198351104203abd03445f6cc76f
- filename: screens/Settings/KeyManagementScreen.tsx
checksum: 6871fad16ecb5f019f502b2f7f715bc2a7b646ee08026b321aa1f8ce071dccc1
version: "1.0"

View File

@@ -59,7 +59,8 @@ const AppLayoutWrapper: React.FC = () => {
const authService = appService.children.get('auth');
const isAppSetupComplete = useSelector(authService, selectAppSetupComplete);
const [isDeepLinkOverlayVisible, setDeepLinkOverlayVisible] = useState(isDeepLinkFlow);
const [isDeepLinkOverlayVisible, setDeepLinkOverlayVisible] =
useState(isDeepLinkFlow);
useEffect(() => {
if (AppState.currentState === 'active') {

View File

@@ -1,10 +1,10 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { Column } from "../../ui";
import { Theme } from "../../ui/styleUtils";
import { Text } from "../../ui";
import { VC_STATUS_KEYS } from "./VCUtils";
import React from 'react';
import {useTranslation} from 'react-i18next';
import {View} from 'react-native';
import {Column} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {Text} from '../../ui';
import {VC_STATUS_KEYS} from './VCUtils';
export const StatusTooltipContent = () => {
const {t} = useTranslation('ViewVcModal');
@@ -13,13 +13,12 @@ export const StatusTooltipContent = () => {
<Column align="center" style={{marginTop: 20}}>
{VC_STATUS_KEYS.map(key => (
<View key={key} style={{marginBottom: 20}}>
<Text weight="semibold">{t(`statusToolTipContent.${key}.title`)}</Text>
<Text weight="semibold">
{t(`statusToolTipContent.${key}.title`)}
</Text>
<Text
weight="regular"
style={[
Theme.Styles.tooltipContentDescription,
{ marginTop: 3 },
]}>
style={[Theme.Styles.tooltipContentDescription, {marginTop: 3}]}>
{t(`statusToolTipContent.${key}.description`)}
</Text>
</View>
@@ -27,4 +26,3 @@ export const StatusTooltipContent = () => {
</Column>
);
};

View File

@@ -8,6 +8,7 @@ import {Row, Text} from './ui';
import {Theme} from './ui/styleUtils';
import {useTranslation} from 'react-i18next';
import {VCMetadata} from '../shared/VCMetadata';
import {RevocationStatus} from '../shared/vcVerifier/VcVerifier';
export const VCVerification: React.FC<VCVerificationProps> = ({
vcMetadata,
@@ -20,12 +21,15 @@ export const VCVerification: React.FC<VCVerificationProps> = ({
let statusIcon: JSX.Element;
if (vcMetadata.isVerified) {
if (vcMetadata.isRevoked) {
if (vcMetadata.isRevoked === RevocationStatus.TRUE) {
statusText = t('revoked');
statusIcon = <PendingIcon color="brown" />;
} else if (vcMetadata.isExpired) {
statusText = t('expired');
statusIcon = <PendingIcon color="red" />;
} else if (vcMetadata.isRevoked === RevocationStatus.UNDETERMINED) {
statusText = t('pending');
statusIcon = <PendingIcon color="orange" />;
} else {
statusText = t('valid');
statusIcon = <VerifiedIcon />;

View File

@@ -69,7 +69,10 @@ final class LdpStatusChecker {
var results: [String: CredentialStatusResult] = [:]
for entry in filteredEntries {
let purpose = (entry["statusPurpose"] as? String)?.lowercased() ?? ""
guard let purpose = (entry["statusPurpose"] as? String)?.lowercased(), !purpose.isEmpty else {
print("Warning: Skipping entry with missing statusPurpose")
continue
}
do {
let result = try await checkStatusEntry(entry: entry, purpose: purpose)
results[purpose] = result
@@ -175,7 +178,7 @@ final class LdpStatusChecker {
throw StatusCheckException(message: "statusMessage count mismatch", errorCode: .statusVerificationError)
}
print("Status message for purpose '\(purpose): $\(statusMessage)")
print("Status message for purpose '\(purpose): \(statusMessage)")
}
let bitSet = try decodeEncodedList(encodedList)

View File

@@ -1,5 +1,9 @@
import {assign, send} from 'xstate';
import {CommunicationDetails, UUID, VerificationStatus} from '../../../shared/Utils';
import {
CommunicationDetails,
UUID,
VerificationStatus,
} from '../../../shared/Utils';
import {StoreEvents} from '../../store';
import {VCMetadata} from '../../../shared/VCMetadata';
import {
@@ -31,6 +35,7 @@ import {VcMetaEvents} from '../VCMetaMachine/VCMetaMachine';
import {WalletBindingResponse} from '../VCMetaMachine/vc';
import {BannerStatusType} from '../../../components/BannerNotification';
import {VCActivityLog} from '../../../components/ActivityLogEvent';
import {RevocationStatus} from '../../../shared/vcVerifier/VcVerifier';
export const VCItemActions = model => {
return {
@@ -91,16 +96,21 @@ export const VCItemActions = model => {
sendReverificationSuccessToVcMeta: send(
(context: any) => ({
type: 'REVERIFY_VC_SUCCESS',
statusValue: context.vcMetadata.isRevoked
type:
context.vcMetadata.isRevoked === RevocationStatus.UNDETERMINED
? 'REVERIFY_VC_FAILED'
: 'REVERIFY_VC_SUCCESS',
statusValue:
context.vcMetadata.isRevoked === RevocationStatus.TRUE
? VerificationStatus.REVOKED
: context.vcMetadata.isExpired
? VerificationStatus.EXPIRED
: context.vcMetadata.isVerified
: context.vcMetadata.isVerified &&
context.vcMetadata.isRevoked === RevocationStatus.FALSE
? VerificationStatus.VALID
: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType
vcType: context.vcMetadata.credentialType,
}),
{
to: (context: any) => context.serviceRefs.vcMeta,
@@ -116,7 +126,7 @@ export const VCItemActions = model => {
type: 'REVERIFY_VC_FAILED',
statusValue: VerificationStatus.PENDING,
vcKey: context.vcMetadata.getVcKey(),
vcType: context.vcMetadata.credentialType
vcType: context.vcMetadata.credentialType,
}),
{
to: (context: any) => context.serviceRefs.vcMeta,

View File

@@ -31,7 +31,7 @@ export const baseRoutes: Screen[] = [
},
{
name: 'AuthView',
component:AuthWebViewScreen
component: AuthWebViewScreen,
},
{
name: 'Language',

View File

@@ -10,7 +10,7 @@ import {
import {authRoutes, baseRoutes} from '../routes';
import {useAppLayout} from './AppLayoutController';
import {StatusBar} from 'react-native';
import {GestureHandlerRootView} from "react-native-gesture-handler";
import {GestureHandlerRootView} from 'react-native-gesture-handler';
const {Navigator, Screen} = createNativeStackNavigator();
export const AppLayout: React.FC = () => {
@@ -28,7 +28,9 @@ export const AppLayout: React.FC = () => {
<GestureHandlerRootView>
<NavigationContainer ref={navigationRef}>
<StatusBar animated={true} barStyle="dark-content" />
<Navigator initialRouteName={baseRoutes[0].name} screenOptions={options}>
<Navigator
initialRouteName={baseRoutes[0].name}
screenOptions={options}>
{baseRoutes.map(route => (
<Screen key={route.name} {...route} />
))}

View File

@@ -40,7 +40,11 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
const controller = useViewVcModal(props);
const profileImage = controller.verifiableCredentialData.face;
const verificationStatus = controller.verificationStatus;
const verificationStatusMessage = controller.verificationStatus?.isRevoked ? "revoked" : controller.verificationStatus?.isExpired ? "expired" : controller.verificationStatus?.statusType;
const verificationStatusMessage = controller.verificationStatus?.isRevoked
? 'revoked'
: controller.verificationStatus?.isExpired
? 'expired'
: controller.verificationStatus?.statusType;
const [verifiableCredential, setVerifiableCredential] = useState(null);
const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null);
const [svgRendererError, setSvgRendererError] = useState<string[] | null>(
@@ -86,7 +90,8 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
setLoadingSvg(true);
const vcJsonString = JSON.stringify(controller.credential.credential);
const result = await VcRenderer.getInstance().generateCredentialDisplayContent(
const result =
await VcRenderer.getInstance().generateCredentialDisplayContent(
controller.verifiableCredentialData.format,
wellknown ?? null,
vcJsonString,
@@ -181,7 +186,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
<BannerNotification
type={verificationStatus?.statusType as BannerStatus}
message={t(`VcVerificationBanner:${verificationStatusMessage}`, {
vcDetails: `${verificationStatus?.vcType} ${verificationStatus?.vcNumber ?? ""}`,
vcDetails: `${verificationStatus?.vcType} ${
verificationStatus?.vcNumber ?? ''
}`,
})}
onClosePress={controller.RESET_VERIFICATION_STATUS}
key={'reVerificationInProgress'}
@@ -239,7 +246,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
/>
<MessageOverlay
isVisible={controller.isWalletBindingInProgress || controller.isReverifyingVc}
isVisible={
controller.isWalletBindingInProgress || controller.isReverifyingVc
}
title={t('inProgress')}
progress
/>

View File

@@ -11,10 +11,7 @@ import { HomeRouteProps } from '../../routes/routeTypes';
import {useIssuerScreenController} from './IssuerScreenController';
import {Loader} from '../../components/ui/Loader';
import ScanIcon from '../../assets/scanIcon.svg';
import {
isTranslationKeyFound,
removeWhiteSpace,
} from '../../shared/commonUtil';
import {isTranslationKeyFound, removeWhiteSpace} from '../../shared/commonUtil';
import {
ErrorMessage,
getDisplayObjectForCurrentLanguage,
@@ -50,7 +47,7 @@ export const IssuersScreen: React.FC<
const [search, setSearch] = useState('');
const [tapToSearch, setTapToSearch] = useState(false);
const [clearSearchIcon, setClearSearchIcon] = useState(false);
const showFullScreenError = controller.isError
const showFullScreenError = controller.isError;
const isVerificationFailed = controller.verificationErrorMessage !== '';
@@ -60,7 +57,6 @@ export const IssuersScreen: React.FC<
? t(translationKey)
: t('errors.verificationFailed.ERR_GENERIC');
useLayoutEffect(() => {
if (controller.loadingReason || showFullScreenError) {
props.navigation.setOptions({
@@ -78,7 +74,6 @@ export const IssuersScreen: React.FC<
),
});
}
}, [
controller.loadingReason,
controller.errorMessageType,
@@ -93,8 +88,10 @@ export const IssuersScreen: React.FC<
if (controller.isAuthEndpointToOpen) {
(props.navigation as any).navigate(AUTH_ROUTES.AuthView, {
authorizationURL: controller.authEndpount,
clientId: controller.selectedIssuer.client_id ?? "wallet",
redirectUri: controller.selectedIssuer.redirect_uri ?? "io.mosip.residentapp.inji://oauthredirect",
clientId: controller.selectedIssuer.client_id ?? 'wallet',
redirectUri:
controller.selectedIssuer.redirect_uri ??
'io.mosip.residentapp.inji://oauthredirect',
controller: controller,
});
}
@@ -203,7 +200,8 @@ export const IssuersScreen: React.FC<
return issuerTrustConsentComponent();
}
if (controller.isTxCodeRequested) {
return <TransactionCodeModal
return (
<TransactionCodeModal
visible={controller.isTxCodeRequested}
onDismiss={controller.CANCEL}
onVerify={controller.TX_CODE_RECEIVED}
@@ -211,6 +209,7 @@ export const IssuersScreen: React.FC<
description={controller.txCodeDisplayDetails.description}
length={controller.txCodeDisplayDetails.length}
/>
);
}
if (controller.isBiometricsCancelled) {
@@ -273,22 +272,27 @@ export const IssuersScreen: React.FC<
);
}
if (controller.isQrScanning) {
return qrScannerComponent();
}
function qrScannerComponent() {
return (
<Column crossAlign="center">
<QrScanner
onQrFound={controller.QR_CODE_SCANNED}
/>
<QrScanner onQrFound={controller.QR_CODE_SCANNED} />
</Column>
);
}
function issuerTrustConsentComponent() {
return <TrustModal isVisible={true} logo={controller.issuerLogo} name={controller.issuerName} onConfirm={controller.ON_CONSENT_GIVEN} onCancel={controller.CANCEL} />
return (
<TrustModal
isVisible={true}
logo={controller.issuerLogo}
name={controller.issuerName}
onConfirm={controller.ON_CONSENT_GIVEN}
onCancel={controller.CANCEL}
/>
);
}
return (
@@ -333,13 +337,20 @@ export const IssuersScreen: React.FC<
}}>
{t('description')}
</Text>
{search === '' && <View style={{ height: 85 }}><Issuer defaultLogo={ScanIcon} displayDetails={{
{search === '' && (
<View style={{height: 85}}>
<Issuer
defaultLogo={ScanIcon}
displayDetails={{
title: t('offerTitle'),
locale: i18n.language,
description: t('offerDescription'),
}} onPress={
controller.SCAN_CREDENTIAL_OFFER_QR_CODE
} testID={'credentalOfferButton'} /></View>}
}}
onPress={controller.SCAN_CREDENTIAL_OFFER_QR_CODE}
testID={'credentalOfferButton'}
/>
</View>
)}
<Column scroll style={Theme.IssuersScreenStyles.issuersContainer}>
{controller.issuers.length > 0 && (
@@ -368,5 +379,3 @@ export const IssuersScreen: React.FC<
</React.Fragment>
);
};

View File

@@ -53,7 +53,8 @@ export const ReceiveVcScreen: React.FC = () => {
setLoadingSvg(true);
const vcJsonString = JSON.stringify(controller.credential.credential);
const result = await VcRenderer.getInstance().generateCredentialDisplayContent(
const result =
await VcRenderer.getInstance().generateCredentialDisplayContent(
verifiableCredentialData.vcMetadata.format,
wellknown ?? null,
vcJsonString,

View File

@@ -65,9 +65,10 @@ export function useSendVPScreen() {
const navigation = useNavigation<MyVcsTabNavigation>();
const openID4VPService = scanService.getSnapshot().context.OpenId4VPRef;
// input descriptor id to VCs mapping
const [inputDescriptorIdToSelectedVcKeys, setInputDescriptorIdToSelectedVcKeys] = useState<Record<string, [string]>>(
{},
);
const [
inputDescriptorIdToSelectedVcKeys,
setInputDescriptorIdToSelectedVcKeys,
] = useState<Record<string, [string]>>({});
const hasLoggedErrorRef = useRef(false);
@@ -104,19 +105,24 @@ export function useSendVPScreen() {
const checkIfAllVCsHasImage = vcs => {
return Object.values(vcs)
.flatMap(vc => vc)
.every(vc => getFaceAttribute(vc.verifiableCredential,vc.format) != null);
.every(
vc => getFaceAttribute(vc.verifiableCredential, vc.format) != null,
);
};
const getSelectedVCs = (): Record<string, any[]> => {
let selectedVcsData: Record<string, any[]> = {}; // input_descriptor_id to VC[]
Object.entries(inputDescriptorIdToSelectedVcKeys).forEach(([inputDescriptorId, vcKeys]) => {
Object.entries(inputDescriptorIdToSelectedVcKeys).forEach(
([inputDescriptorId, vcKeys]) => {
vcKeys.forEach((vcKey: string) => {
const vcData = myVcs[vcKey];
selectedVcsData[inputDescriptorId] = selectedVcsData[inputDescriptorId] || [];
selectedVcsData[inputDescriptorId] =
selectedVcsData[inputDescriptorId] || [];
selectedVcsData[inputDescriptorId].push(vcData);
});
});
return selectedVcsData
},
);
return selectedVcsData;
};
const showConfirmationPopup = useSelector(
@@ -222,9 +228,18 @@ export function useSendVPScreen() {
showLoadingScreen: useSelector(openID4VPService, selectIsShowLoadingScreen),
vpVerifierName,
flowType: useSelector(openID4VPService, selectFlowType),
showTrustConsentModal: useSelector(openID4VPService,selectshowTrustConsentModal),
verifierNameInTrustModal: useSelector(openID4VPService, selectVerifierNameInTrustModal),
verifierLogoInTrustModal: useSelector(openID4VPService, selectVerifierLogoInTrustModal),
showTrustConsentModal: useSelector(
openID4VPService,
selectshowTrustConsentModal,
),
verifierNameInTrustModal: useSelector(
openID4VPService,
selectVerifierNameInTrustModal,
),
verifierLogoInTrustModal: useSelector(
openID4VPService,
selectVerifierLogoInTrustModal,
),
showConfirmationPopup,
isSelectingVCs,
checkIfAnyVCHasImage,
@@ -282,7 +297,13 @@ export function useSendVPScreen() {
(vcRef: ActorRefFrom<typeof VCItemMachine>) => {
let descriptorMappingToVCs = {...inputDescriptorIdToSelectedVcKeys};
const isVCSelected = Object.keys(inputDescriptorIdToSelectedVcKeys)?.includes(inputDescriptorId) && inputDescriptorIdToSelectedVcKeys[inputDescriptorId]?.includes(vcKey) ? false : true;
const isVCSelected =
Object.keys(inputDescriptorIdToSelectedVcKeys)?.includes(
inputDescriptorId,
) &&
inputDescriptorIdToSelectedVcKeys[inputDescriptorId]?.includes(vcKey)
? false
: true;
if (isVCSelected) {
if (descriptorMappingToVCs[inputDescriptorId]) {
if (!descriptorMappingToVCs[inputDescriptorId].includes(vcKey)) {
@@ -297,45 +318,59 @@ export function useSendVPScreen() {
descriptorMappingToVCs[inputDescriptorId] = descriptorMappingToVCs[
inputDescriptorId
].filter(key => key !== vcKey); // remove the vcKey from the array
if (descriptorMappingToVCs[inputDescriptorId].length === 0) { // if the array is empty, remove the input descriptor id
if (descriptorMappingToVCs[inputDescriptorId].length === 0) {
// if the array is empty, remove the input descriptor id
delete descriptorMappingToVCs[inputDescriptorId];
}
}
}
setInputDescriptorIdToSelectedVcKeys(descriptorMappingToVCs)
setInputDescriptorIdToSelectedVcKeys(descriptorMappingToVCs);
const {serviceRefs, wellknownResponse, ...vcData} =
vcRef.getSnapshot().context;
},
UNCHECK_ALL: () => {
setInputDescriptorIdToSelectedVcKeys({})
setInputDescriptorIdToSelectedVcKeys({});
},
CHECK_ALL: () => {
const updatedInputDescriptorToCredentialsMapping: Record<string, any[]> = {};
const updatedInputDescriptorToCredentialsMapping: Record<string, any[]> =
{};
Object.entries(vcsMatchingAuthRequest).map(([inputDescriptorId, vcs]) => {
updatedInputDescriptorToCredentialsMapping[inputDescriptorId] = [];
vcs.map(vcData => {
const vcKey = VCMetadata.fromVcMetadataString(
vcData.vcMetadata,
).getVcKey();
updatedInputDescriptorToCredentialsMapping[inputDescriptorId].push(vcKey);
updatedInputDescriptorToCredentialsMapping[inputDescriptorId].push(
vcKey,
);
});
});
setInputDescriptorIdToSelectedVcKeys({...updatedInputDescriptorToCredentialsMapping});
setInputDescriptorIdToSelectedVcKeys({
...updatedInputDescriptorToCredentialsMapping,
});
},
ACCEPT_REQUEST: (selectedDisclosuresByVc) => {
openID4VPService.send(OpenID4VPEvents.ACCEPT_REQUEST(getSelectedVCs(), selectedDisclosuresByVc));
ACCEPT_REQUEST: selectedDisclosuresByVc => {
openID4VPService.send(
OpenID4VPEvents.ACCEPT_REQUEST(
getSelectedVCs(),
selectedDisclosuresByVc,
),
);
},
VERIFIER_TRUST_CONSENT_GIVEN: () => {
openID4VPService.send(OpenID4VPEvents.VERIFIER_TRUST_CONSENT_GIVEN());
},
VERIFY_AND_ACCEPT_REQUEST: (selectedDisclosuresByVc) => {
VERIFY_AND_ACCEPT_REQUEST: selectedDisclosuresByVc => {
openID4VPService.send(
OpenID4VPEvents.VERIFY_AND_ACCEPT_REQUEST(getSelectedVCs(), selectedDisclosuresByVc),
OpenID4VPEvents.VERIFY_AND_ACCEPT_REQUEST(
getSelectedVCs(),
selectedDisclosuresByVc,
),
);
},
CANCEL,

View File

@@ -129,10 +129,7 @@ export const KeyManagementScreen: React.FC<KeyManagementScreenProps> = () => {
style={Theme.KeyManagementScreenStyle.heading}>
{t('header')}
</Text>
<HelpScreen
source={'keyManagement'}
triggerComponent={ HelpIcon() }
/>
<HelpScreen source={'keyManagement'} triggerComponent={HelpIcon()} />
</View>
<BannerNotificationContainer />
<View style={Theme.KeyManagementScreenStyle.copilotViewStyle}>

View File

@@ -13,10 +13,7 @@ import {ProfileInfo} from '../../shared/CloudBackupAndRestoreUtils';
import {useBackupScreen} from './BackupController';
import {BannerNotificationContainer} from '../../components/BannerNotificationContainer';
import {useBackupRestoreScreen} from '../Settings/BackupRestoreController';
import {
getAccountType,
getDriveName,
} from '../../shared/commonUtil';
import {getAccountType, getDriveName} from '../../shared/commonUtil';
import {HelpScreen} from '../../components/HelpScreen';
import {isIOS} from '../../shared/constants';
import {HelpIcon} from '../../components/ui/HelpIcon';

View File

@@ -9,6 +9,7 @@ import {getMosipIdentifier} from './commonUtil';
import {VCFormat} from './VCFormat';
import {isMosipVC, UUID} from './Utils';
import {getCredentialType} from '../components/VC/common/VCUtils';
import {RevocationStatus, RevocationStatusType} from './vcVerifier/VcVerifier';
const VC_KEY_PREFIX = 'VC';
const VC_ITEM_STORE_KEY_REGEX = '^VC_[a-zA-Z0-9_-]+$';
@@ -31,7 +32,7 @@ export class VCMetadata {
mosipIndividualId: string = '';
format: string = '';
isExpired: boolean = false;
isRevoked: boolean = false;
isRevoked: RevocationStatusType = RevocationStatus.FALSE;
downloadKeyType: string = '';
credentialType: string = '';
@@ -51,7 +52,7 @@ export class VCMetadata {
format = '',
downloadKeyType = '',
isExpired = false,
isRevoked = false,
isRevoked = RevocationStatus.FALSE,
credentialType = '',
issuerHost = '',
lastKnownStatusTimestamp = '',

View File

@@ -1,15 +1,31 @@
import {NativeModules} from 'react-native';
export const RevocationStatus = Object.freeze({
TRUE: 'TRUE',
FALSE: 'FALSE',
UNDETERMINED: 'UNDETERMINED',
} as const);
/**
* Type representing any possible value of RevocationStatus.
*
* - "TRUE" → Condition was evaluated and is positively true
* - "FALSE" → Condition was evaluated and is definitively false
* - "UNDETERMINED" → Condition could not be evaluated due to an error
*/
export type RevocationStatusType =
(typeof RevocationStatus)[keyof typeof RevocationStatus];
export type CredentialStatusResult = {
isValid: boolean;
error?: ErrorResult;
statusListVC?: string; // Available only in iOS
statusListVC?: Record<string, any>; // Available only in iOS
};
export type ErrorResult = {
code: string;
message: string;
}
};
export type VerificationSummaryResult = {
verificationStatus: boolean;

View File

@@ -4,14 +4,22 @@ import {RsaSignature2018} from '../../lib/jsonld-signatures/suites/rsa2018/RsaSi
import {Ed25519Signature2018} from '../../lib/jsonld-signatures/suites/ed255192018/Ed25519Signature2018';
import {AssertionProofPurpose} from '../../lib/jsonld-signatures/purposes/AssertionProofPurpose';
import {PublicKeyProofPurpose} from '../../lib/jsonld-signatures/purposes/PublicKeyProofPurpose';
import {Credential, VerifiableCredential,} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {
Credential,
VerifiableCredential,
} from '../../machines/VerifiableCredential/VCMetaMachine/vc';
import {getErrorEventData, sendErrorEvent} from '../telemetry/TelemetryUtils';
import {TelemetryConstants} from '../telemetry/TelemetryConstants';
import {getMosipIdentifier} from '../commonUtil';
import {NativeModules} from 'react-native';
import {isAndroid, isIOS} from '../constants';
import {VCFormat} from '../VCFormat';
import VCVerifier, {CredentialStatusResult, VerificationSummaryResult} from '../vcVerifier/VcVerifier';
import VCVerifier, {
CredentialStatusResult,
RevocationStatus,
RevocationStatusType,
VerificationSummaryResult,
} from '../vcVerifier/VcVerifier';
// FIXME: Ed25519Signature2018 not fully supported yet.
// Ed25519Signature2018 proof type check is not tested with its real credential
@@ -59,7 +67,8 @@ async function verifyCredentialForAndroid(
typeof verifiableCredential === 'string'
? verifiableCredential
: JSON.stringify(verifiableCredential);
const vcVerifierResult = await VCVerifier.getInstance().getVerificationSummary(
const vcVerifierResult =
await VCVerifier.getInstance().getVerificationSummary(
credentialString,
credentialFormat,
);
@@ -84,9 +93,10 @@ async function verifyCredentialForIos(
let verificationResponse: VerificationResult;
if (verifiableCredential.proof.type === ProofType.ED25519_2020) {
verificationResponse = createSuccessfulVerificationResult();
}
else{
const purpose = getPurposeFromProof(verifiableCredential.proof.proofPurpose);
} else {
const purpose = getPurposeFromProof(
verifiableCredential.proof.proofPurpose,
);
const suite = selectVerificationSuite(verifiableCredential.proof);
const vcjsOptions = {
purpose,
@@ -99,7 +109,6 @@ async function verifyCredentialForIos(
verificationResponse = handleResponse(result, verifiableCredential);
}
if (verificationResponse.isVerified) {
const statusArray = await VCVerifier.getInstance().getCredentialStatus(
verifiableCredential,
@@ -188,7 +197,9 @@ async function handleVcVerifierResponse(
verifiableCredential,
);
}
const isRevoked = await checkIsStatusRevoked(verificationResult.credentialStatus)
const isRevoked = await checkIsStatusRevoked(
verificationResult.credentialStatus,
);
return {
isVerified: verificationResult.verificationStatus,
verificationMessage: verificationResult.verificationMessage,
@@ -209,45 +220,51 @@ async function handleVcVerifierResponse(
}
}
const handleStatusListVCVerification = (status: CredentialStatusResult, type: "revoked" | "valid") => {
const handleStatusListVCVerification = (
status: CredentialStatusResult,
type: 'revoked' | 'valid',
) => {
const isValid = verifyStatusListVC(status.statusListVC);
if (!isValid) {
throw new Error(
`StatusListVC verification failed for ${type} entry ${status.error}`,
);
}
}
};
export async function checkIsStatusRevoked(
vcStatus: Record<string, CredentialStatusResult>,
): Promise<boolean> {
if (!Object.keys(vcStatus).length) return false;
): Promise<RevocationStatusType> {
if (!vcStatus || !Object.keys(vcStatus).length) return RevocationStatus.FALSE;
const revocationStatus = vcStatus["revocation"] as CredentialStatusResult;
if (!revocationStatus) return false;
const revocationStatus = vcStatus['revocation'] as CredentialStatusResult;
if (!revocationStatus) return RevocationStatus.FALSE;
const {isValid, error} = revocationStatus;
if (isValid) {
// Validate the valid statuses statusList VC for iOS
if (isIOS()) {
handleStatusListVCVerification(revocationStatus, "valid")
handleStatusListVCVerification(revocationStatus, 'valid');
}
return false
return RevocationStatus.FALSE;
}
console.error(`Credential is revoked. Error: ${error?.code}, Message: ${error?.message}`);
// if there is an error fetching revocation status itself, throw error (isValid = true, error = Error)
if (error) {
throw new Error(`Error fetching revocation status. Error: ${error.code}, Message: ${error.message}`);
console.error(
`Error fetching revocation status. Error: ${error.code}, Message: ${error.message}`,
);
return RevocationStatus.UNDETERMINED;
}
// There is no error fetching revocation status, but the status is invalid (isValid = false, error = undefined) - VC is revoked
// Validate the valid statuses statusList VC for iOS
if (isIOS()) {
handleStatusListVCVerification(revocationStatus, "revoked");
handleStatusListVCVerification(revocationStatus, 'revoked');
}
console.error(`Credential is revoked`);
// If revocation status is invalid, the credential is revoked
return true
return RevocationStatus.TRUE;
}
function createSuccessfulVerificationResult(): VerificationResult {
@@ -298,12 +315,12 @@ export interface VerificationResult {
isVerified: boolean;
verificationMessage: string;
verificationErrorCode: string;
isRevoked?: boolean;
isRevoked?: RevocationStatusType;
}
//TODO: Implement status list VC verification for iOS.
//Currently Digital Bazaar library does not support VC 2.0 status list VC verification.
function verifyStatusListVC(statusListVC: string | undefined) {
function verifyStatusListVC(statusListVC: Record<string, any> | undefined) {
return true;
}