@trustchex/react-native-sdk 1.381.0 → 1.464.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -13
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
- package/ios/Camera/TrustchexCameraView.swift +10 -13
- package/ios/MLKit/MLKitModule.swift +1 -1
- package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
- package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -32
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +22 -4
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +126 -27
- package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +678 -0
- package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
- package/lib/module/Screens/Static/ResultScreen.js +154 -34
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +59 -51
- package/lib/module/Shared/Animations/recording.json +1 -0
- package/lib/module/Shared/Animations/video-call.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +231 -67
- package/lib/module/Shared/Components/EIDScanner.js +213 -112
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +77 -39
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
- package/lib/module/Shared/Components/NavigationManager.js +39 -19
- package/lib/module/Shared/Contexts/AppContext.js +1 -0
- package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
- package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
- package/lib/module/Shared/EIDReader/bacKey.js +16 -2
- package/lib/module/Shared/EIDReader/eidReader.js +354 -13
- package/lib/module/Shared/EIDReader/eidService.js +25 -1
- package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
- package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
- package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
- package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
- package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
- package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
- package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
- package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
- package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
- package/lib/module/Shared/Libs/SignalingClient.js +128 -0
- package/lib/module/Shared/Libs/analytics.utils.js +8 -0
- package/lib/module/Shared/Libs/contains.js +1 -40
- package/lib/module/Shared/Libs/country-display.utils.js +34 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
- package/lib/module/Shared/Libs/demo.utils.js +8 -0
- package/lib/module/Shared/Libs/http-client.js +9 -0
- package/lib/module/Shared/Libs/mrz.utils.js +3 -2
- package/lib/module/Shared/Libs/promise.utils.js +16 -2
- package/lib/module/Shared/Libs/status-bar.utils.js +23 -0
- package/lib/module/Shared/Services/DataUploadService.js +294 -0
- package/lib/module/Shared/Services/VideoSessionService.js +156 -0
- package/lib/module/Shared/Services/WebRTCService.js +510 -0
- package/lib/module/Shared/Types/analytics.types.js +4 -0
- package/lib/module/Translation/Resources/en.js +61 -2
- package/lib/module/Translation/Resources/tr.js +61 -2
- package/lib/module/Trustchex.js +64 -20
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
- package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +4 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +13 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +60 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +60 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +35 -5
- package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
- package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
- package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +58 -35
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +27 -4
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +156 -27
- package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
- package/src/Screens/Dynamic/VideoCallScreen.tsx +766 -0
- package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
- package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
- package/src/Screens/Static/ResultScreen.tsx +235 -48
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +67 -72
- package/src/Shared/Animations/recording.json +1 -0
- package/src/Shared/Animations/video-call.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +252 -51
- package/src/Shared/Components/EIDScanner.tsx +223 -116
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
- package/src/Shared/Components/IdentityDocumentCamera.tsx +224 -188
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
- package/src/Shared/Components/NavigationManager.tsx +41 -19
- package/src/Shared/Contexts/AppContext.ts +2 -0
- package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
- package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
- package/src/Shared/EIDReader/bacKey.ts +24 -8
- package/src/Shared/EIDReader/eidReader.ts +398 -12
- package/src/Shared/EIDReader/eidService.ts +49 -1
- package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
- package/src/Shared/EIDReader/paceInfo.ts +159 -0
- package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
- package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
- package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
- package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
- package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
- package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
- package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
- package/src/Shared/Libs/SignalingClient.ts +189 -0
- package/src/Shared/Libs/analytics.utils.ts +8 -0
- package/src/Shared/Libs/contains.ts +0 -53
- package/src/Shared/Libs/country-display.utils.ts +55 -0
- package/src/Shared/Libs/crypto.utils.ts +2 -2
- package/src/Shared/Libs/deeplink.utils.ts +12 -1
- package/src/Shared/Libs/demo.utils.ts +10 -0
- package/src/Shared/Libs/http-client.ts +19 -1
- package/src/Shared/Libs/mrz.utils.ts +3 -2
- package/src/Shared/Libs/promise.utils.ts +16 -2
- package/src/Shared/Libs/status-bar.utils.ts +21 -0
- package/src/Shared/Services/DataUploadService.ts +395 -0
- package/src/Shared/Services/VideoSessionService.ts +190 -0
- package/src/Shared/Services/WebRTCService.ts +636 -0
- package/src/Shared/Types/analytics.types.ts +4 -0
- package/src/Shared/Types/identificationInfo.ts +16 -1
- package/src/Translation/Resources/en.ts +88 -3
- package/src/Translation/Resources/tr.ts +89 -3
- package/src/Trustchex.tsx +65 -19
- package/src/version.ts +1 -1
|
@@ -10,10 +10,14 @@ import i18n from "../../Translation/index.js";
|
|
|
10
10
|
import StyledButton from "./StyledButton.js";
|
|
11
11
|
import { analyticsService } from "../Services/AnalyticsService.js";
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// Navigation lock - use a ref-like pattern so unmounting resets state.
|
|
14
|
+
// The module-level object is shared across all instances but the
|
|
15
|
+
// cleanup effect in the component resets it on unmount.
|
|
14
16
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
const navigationLock = {
|
|
18
|
+
isNavigating: false,
|
|
19
|
+
lastNavigationTime: 0
|
|
20
|
+
};
|
|
17
21
|
const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
18
22
|
canSkipStep
|
|
19
23
|
}, ref) => {
|
|
@@ -29,18 +33,20 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
29
33
|
CONTRACT_ACCEPTANCE: 'ContractAcceptanceScreen',
|
|
30
34
|
IDENTITY_DOCUMENT_SCAN: 'IdentityDocumentScanningScreen',
|
|
31
35
|
IDENTITY_DOCUMENT_EID_SCAN: 'IdentityDocumentEIDScanningScreen',
|
|
32
|
-
LIVENESS_CHECK: 'LivenessDetectionScreen'
|
|
36
|
+
LIVENESS_CHECK: 'LivenessDetectionScreen',
|
|
37
|
+
VERBAL_CONSENT: 'VerbalConsentScreen',
|
|
38
|
+
VIDEO_CALL: 'VideoCallScreen'
|
|
33
39
|
},
|
|
34
40
|
RESULT: 'ResultScreen'
|
|
35
41
|
};
|
|
36
42
|
React.useEffect(() => {
|
|
37
43
|
const handleFocus = () => {
|
|
38
|
-
isNavigating = false;
|
|
44
|
+
navigationLock.isNavigating = false;
|
|
39
45
|
};
|
|
40
46
|
navigation.addListener('focus', handleFocus);
|
|
41
47
|
return () => {
|
|
42
48
|
navigation.removeListener('focus', handleFocus);
|
|
43
|
-
isNavigating = false;
|
|
49
|
+
navigationLock.isNavigating = false;
|
|
44
50
|
};
|
|
45
51
|
}, [navigation]);
|
|
46
52
|
const getNextRoute = useCallback((workflowSteps, currentWorkFlowStep) => {
|
|
@@ -69,16 +75,22 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
69
75
|
if (nextStep.type === 'LIVENESS_CHECK') {
|
|
70
76
|
return routes.DYNAMIC_ROUTES.LIVENESS_CHECK;
|
|
71
77
|
}
|
|
78
|
+
if (nextStep.type === 'VIDEO_CALL') {
|
|
79
|
+
return routes.DYNAMIC_ROUTES.VIDEO_CALL;
|
|
80
|
+
}
|
|
81
|
+
if (nextStep.type === 'VERBAL_CONSENT') {
|
|
82
|
+
return routes.DYNAMIC_ROUTES.VERBAL_CONSENT;
|
|
83
|
+
}
|
|
72
84
|
return routes.VERIFICATION_SESSION_CHECK;
|
|
73
|
-
}, [appContext, routes.VERIFICATION_SESSION_CHECK, routes.DYNAMIC_ROUTES.CONTRACT_ACCEPTANCE, routes.DYNAMIC_ROUTES.
|
|
85
|
+
}, [appContext, routes.VERIFICATION_SESSION_CHECK, routes.RESULT, routes.DYNAMIC_ROUTES.CONTRACT_ACCEPTANCE, routes.DYNAMIC_ROUTES.IDENTITY_DOCUMENT_SCAN, routes.DYNAMIC_ROUTES.IDENTITY_DOCUMENT_EID_SCAN, routes.DYNAMIC_ROUTES.LIVENESS_CHECK, routes.DYNAMIC_ROUTES.VIDEO_CALL, routes.DYNAMIC_ROUTES.VERBAL_CONSENT]);
|
|
74
86
|
const goToNextRoute = useCallback(() => {
|
|
75
87
|
const currentTime = Date.now();
|
|
76
88
|
const minTimeBetweenNavigation = 1000;
|
|
77
|
-
if (isNavigating || currentTime - lastNavigationTime < minTimeBetweenNavigation) {
|
|
89
|
+
if (navigationLock.isNavigating || currentTime - navigationLock.lastNavigationTime < minTimeBetweenNavigation) {
|
|
78
90
|
return;
|
|
79
91
|
}
|
|
80
|
-
isNavigating = true;
|
|
81
|
-
lastNavigationTime = currentTime;
|
|
92
|
+
navigationLock.isNavigating = true;
|
|
93
|
+
navigationLock.lastNavigationTime = currentTime;
|
|
82
94
|
try {
|
|
83
95
|
const nextRoute = getNextRoute(appContext.workflowSteps, appContext.currentWorkflowStep);
|
|
84
96
|
navigation.dispatch(CommonActions.navigate({
|
|
@@ -88,15 +100,15 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
88
100
|
}
|
|
89
101
|
}));
|
|
90
102
|
} catch (error) {
|
|
91
|
-
isNavigating = false;
|
|
103
|
+
navigationLock.isNavigating = false;
|
|
92
104
|
throw error;
|
|
93
105
|
}
|
|
94
106
|
setTimeout(() => {
|
|
95
|
-
isNavigating = false;
|
|
107
|
+
navigationLock.isNavigating = false;
|
|
96
108
|
}, 1000);
|
|
97
109
|
}, [getNextRoute, appContext.workflowSteps, appContext.currentWorkflowStep, navigation]);
|
|
98
110
|
const goToNextRouteWithAlert = useCallback(() => {
|
|
99
|
-
if (isNavigating) {
|
|
111
|
+
if (navigationLock.isNavigating) {
|
|
100
112
|
return;
|
|
101
113
|
}
|
|
102
114
|
Alert.alert(t('general.warning'), t('navigationManager.skipStepWarning'), [{
|
|
@@ -108,10 +120,10 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
108
120
|
}]);
|
|
109
121
|
}, [goToNextRoute, t]);
|
|
110
122
|
const reset = useCallback(() => {
|
|
111
|
-
if (isNavigating) {
|
|
123
|
+
if (navigationLock.isNavigating) {
|
|
112
124
|
return;
|
|
113
125
|
}
|
|
114
|
-
isNavigating = true;
|
|
126
|
+
navigationLock.isNavigating = true;
|
|
115
127
|
try {
|
|
116
128
|
// Preserve demo session state when resetting
|
|
117
129
|
const wasDemoSession = appContext.isDemoSession;
|
|
@@ -124,8 +136,15 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
124
136
|
contractIds: [],
|
|
125
137
|
deviceInfo: ''
|
|
126
138
|
},
|
|
127
|
-
locale: appContext.locale || i18n.language
|
|
139
|
+
locale: appContext.locale || i18n.language,
|
|
140
|
+
// Explicitly reset collected data fields
|
|
141
|
+
scannedDocument: undefined,
|
|
142
|
+
livenessDetection: undefined,
|
|
143
|
+
authToken: undefined,
|
|
144
|
+
videoSessionId: undefined
|
|
128
145
|
};
|
|
146
|
+
|
|
147
|
+
// Reset branding to defaults while preserving any custom values
|
|
129
148
|
appContext.branding = {
|
|
130
149
|
logoUrl: appContext.branding?.logoUrl || '',
|
|
131
150
|
primaryColor: appContext.branding?.primaryColor || '#000000',
|
|
@@ -138,6 +157,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
138
157
|
appContext.setIsDemoSession?.(false);
|
|
139
158
|
analyticsService.setDemoSession(false);
|
|
140
159
|
}
|
|
160
|
+
appContext.isTestVideoSession = false;
|
|
141
161
|
navigation.dispatch(CommonActions.reset({
|
|
142
162
|
index: 0,
|
|
143
163
|
routes: [{
|
|
@@ -145,17 +165,17 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
145
165
|
}]
|
|
146
166
|
}));
|
|
147
167
|
} catch (error) {
|
|
148
|
-
isNavigating = false;
|
|
168
|
+
navigationLock.isNavigating = false;
|
|
149
169
|
throw error;
|
|
150
170
|
}
|
|
151
171
|
setTimeout(() => {
|
|
152
|
-
isNavigating = false;
|
|
172
|
+
navigationLock.isNavigating = false;
|
|
153
173
|
}, 1000);
|
|
154
174
|
}, [appContext, navigation, routes.VERIFICATION_SESSION_CHECK]);
|
|
155
175
|
usePreventRemove(true, ({
|
|
156
176
|
data
|
|
157
177
|
}) => {
|
|
158
|
-
if (data
|
|
178
|
+
if (data?.action?.type === 'RESET') {
|
|
159
179
|
navigation.dispatch(data.action);
|
|
160
180
|
}
|
|
161
181
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { SecureMessagingWrapper } from "./secureMessagingWrapper.js";
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
5
|
+
import { aesEncryptCBC, aesDecryptCBC, aesEncryptECB, aesCMAC } from "./utils/aesCrypto.utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AES-based secure messaging wrapper for PACE.
|
|
9
|
+
*
|
|
10
|
+
* Uses AES-CBC for encryption and AES-CMAC for MAC computation,
|
|
11
|
+
* as specified in ICAO Doc 9303 Part 11.
|
|
12
|
+
*/
|
|
13
|
+
export class AESSecureMessagingWrapper extends SecureMessagingWrapper {
|
|
14
|
+
constructor(ksEnc, ksMac, maxTransceiveLength, shouldCheckMAC, ssc) {
|
|
15
|
+
super(ksEnc, ksMac, maxTransceiveLength, shouldCheckMAC, ssc);
|
|
16
|
+
}
|
|
17
|
+
getType() {
|
|
18
|
+
return 'AES';
|
|
19
|
+
}
|
|
20
|
+
getPadLength() {
|
|
21
|
+
return 16;
|
|
22
|
+
}
|
|
23
|
+
getIV() {
|
|
24
|
+
// AES IV is E(ksEnc, SSC)
|
|
25
|
+
const sscHex = Buffer.from(this.getEncodedSendSequenceCounter()).toString('hex');
|
|
26
|
+
const ivHex = aesEncryptECB(sscHex, this.getEncryptionKey());
|
|
27
|
+
return Array.from(Buffer.from(ivHex, 'hex'));
|
|
28
|
+
}
|
|
29
|
+
encrypt(dataHex, keyHex) {
|
|
30
|
+
const ivHex = Buffer.from(this.getIV()).toString('hex');
|
|
31
|
+
return aesEncryptCBC(dataHex, keyHex, ivHex);
|
|
32
|
+
}
|
|
33
|
+
decrypt(dataHex, keyHex) {
|
|
34
|
+
const ivHex = Buffer.from(this.getIV()).toString('hex');
|
|
35
|
+
return aesDecryptCBC(dataHex, keyHex, ivHex);
|
|
36
|
+
}
|
|
37
|
+
computeMAC(dataHex, keyHex) {
|
|
38
|
+
return aesCMAC(dataHex, keyHex);
|
|
39
|
+
}
|
|
40
|
+
getEncodedSendSequenceCounter() {
|
|
41
|
+
// AES uses 16-byte SSC (128-bit counter)
|
|
42
|
+
const ssc = this.getSendSequenceCounter();
|
|
43
|
+
const bytes = new Uint8Array(16);
|
|
44
|
+
let remaining = ssc;
|
|
45
|
+
for (let i = 15; i >= 0; i--) {
|
|
46
|
+
bytes[i] = Number(remaining & 0xffn);
|
|
47
|
+
remaining >>= 8n;
|
|
48
|
+
}
|
|
49
|
+
return bytes;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -69,13 +69,27 @@ export class BACKey {
|
|
|
69
69
|
*/
|
|
70
70
|
getKey() {
|
|
71
71
|
try {
|
|
72
|
-
|
|
73
|
-
return cryptoUtils.computeKeySeed(mrz, 'SHA-1', true);
|
|
72
|
+
return cryptoUtils.computeKeySeed(this.getMRZString(), 'SHA-1', true);
|
|
74
73
|
} catch (gse) {
|
|
75
74
|
throw new Error('Unexpected exception');
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Returns the key seed for PACE, which is the full (non-truncated) SHA-1 hash
|
|
80
|
+
* of the MRZ info. PACE uses the full 20-byte hash unlike BAC which truncates to 16.
|
|
81
|
+
*/
|
|
82
|
+
getKeySeedForPACE() {
|
|
83
|
+
try {
|
|
84
|
+
return cryptoUtils.computeKeySeed(this.getMRZString(), 'SHA-1', false);
|
|
85
|
+
} catch (gse) {
|
|
86
|
+
throw new Error('Unexpected exception');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
getMRZString() {
|
|
90
|
+
return this.documentNumber + MRZInfo.checkDigit(this.documentNumber, false) + this.dateOfBirth + MRZInfo.checkDigit(this.dateOfBirth, false) + this.dateOfExpiry + MRZInfo.checkDigit(this.dateOfExpiry, false);
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
/**
|
|
80
94
|
* Sets the document number.
|
|
81
95
|
*
|
|
@@ -1,35 +1,374 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { BACKey } from "./bacKey.js";
|
|
4
|
+
import { PACEKeySpec } from "./paceKeySpec.js";
|
|
4
5
|
import { NFCManagerCardService } from "./nfcManagerCardService.js";
|
|
5
6
|
import { EIDService } from "./eidService.js";
|
|
6
7
|
import { DG1File } from "./lds/icao/dg1File.js";
|
|
7
8
|
import { DG2File } from "./lds/icao/dg2File.js";
|
|
8
9
|
import { Buffer } from 'buffer';
|
|
9
10
|
import { InputStream } from "./java/inputStream.js";
|
|
11
|
+
|
|
12
|
+
// --- Minimal PNG encoder (pure JS, no native deps) ---
|
|
13
|
+
// Uses deflate stored (uncompressed) blocks for O(n) encoding speed.
|
|
14
|
+
// A naive DCT-based JPEG encoder would be O(n²) and freeze the JS thread
|
|
15
|
+
// on real passport photos (400×500+ pixels).
|
|
16
|
+
|
|
17
|
+
/** Standard CRC32 lookup table */
|
|
18
|
+
const CRC_TABLE = new Uint32Array(256);
|
|
19
|
+
for (let n = 0; n < 256; n++) {
|
|
20
|
+
let c = n;
|
|
21
|
+
for (let k = 0; k < 8; k++) {
|
|
22
|
+
c = c & 1 ? 0xedb88320 ^ c >>> 1 : c >>> 1;
|
|
23
|
+
}
|
|
24
|
+
CRC_TABLE[n] = c;
|
|
25
|
+
}
|
|
26
|
+
function crc32(data) {
|
|
27
|
+
let crc = 0xffffffff;
|
|
28
|
+
for (let i = 0; i < data.length; i++) {
|
|
29
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ crc >>> 8;
|
|
30
|
+
}
|
|
31
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
32
|
+
}
|
|
33
|
+
function adler32(data) {
|
|
34
|
+
let a = 1;
|
|
35
|
+
let b = 0;
|
|
36
|
+
for (let i = 0; i < data.length; i++) {
|
|
37
|
+
a = (a + data[i]) % 65521;
|
|
38
|
+
b = (b + a) % 65521;
|
|
39
|
+
}
|
|
40
|
+
return (b << 16 | a) >>> 0;
|
|
41
|
+
}
|
|
42
|
+
function writeU32BE(arr, offset, val) {
|
|
43
|
+
arr[offset] = val >>> 24 & 0xff;
|
|
44
|
+
arr[offset + 1] = val >>> 16 & 0xff;
|
|
45
|
+
arr[offset + 2] = val >>> 8 & 0xff;
|
|
46
|
+
arr[offset + 3] = val & 0xff;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Encode raw RGB/RGBA pixels as an uncompressed PNG.
|
|
51
|
+
* Uses deflate stored blocks (no compression) for maximum compatibility.
|
|
52
|
+
*/
|
|
53
|
+
function pixelsToPng(pixels, width, height, channels) {
|
|
54
|
+
// Build raw scanlines: filter byte (0) + RGB data per row
|
|
55
|
+
const rawRowLen = 1 + width * 3; // filter byte + RGB
|
|
56
|
+
const rawData = new Uint8Array(rawRowLen * height);
|
|
57
|
+
for (let y = 0; y < height; y++) {
|
|
58
|
+
const rowOff = y * rawRowLen;
|
|
59
|
+
rawData[rowOff] = 0; // filter: None
|
|
60
|
+
for (let x = 0; x < width; x++) {
|
|
61
|
+
const srcIdx = (y * width + x) * channels;
|
|
62
|
+
const dstIdx = rowOff + 1 + x * 3;
|
|
63
|
+
rawData[dstIdx] = pixels[srcIdx]; // R
|
|
64
|
+
rawData[dstIdx + 1] = pixels[srcIdx + 1]; // G
|
|
65
|
+
rawData[dstIdx + 2] = pixels[srcIdx + 2]; // B
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Wrap raw data in zlib stored (uncompressed) format
|
|
70
|
+
const MAX_BLOCK = 65535;
|
|
71
|
+
const numBlocks = Math.ceil(rawData.length / MAX_BLOCK) || 1;
|
|
72
|
+
const zlibSize = 2 + rawData.length + numBlocks * 5 + 4;
|
|
73
|
+
const zlib = new Uint8Array(zlibSize);
|
|
74
|
+
let zOff = 0;
|
|
75
|
+
// zlib header: CMF=0x78 (deflate, 32K window), FLG=0x01 (check bits, no dict, level 0)
|
|
76
|
+
zlib[zOff++] = 0x78;
|
|
77
|
+
zlib[zOff++] = 0x01;
|
|
78
|
+
let remaining = rawData.length;
|
|
79
|
+
let srcOff = 0;
|
|
80
|
+
while (remaining > 0) {
|
|
81
|
+
const blockLen = Math.min(remaining, MAX_BLOCK);
|
|
82
|
+
const isFinal = remaining <= MAX_BLOCK ? 1 : 0;
|
|
83
|
+
zlib[zOff++] = isFinal; // BFINAL + BTYPE=00 (stored)
|
|
84
|
+
zlib[zOff++] = blockLen & 0xff;
|
|
85
|
+
zlib[zOff++] = blockLen >>> 8 & 0xff;
|
|
86
|
+
zlib[zOff++] = ~blockLen & 0xff;
|
|
87
|
+
zlib[zOff++] = ~blockLen >>> 8 & 0xff;
|
|
88
|
+
zlib.set(rawData.subarray(srcOff, srcOff + blockLen), zOff);
|
|
89
|
+
zOff += blockLen;
|
|
90
|
+
srcOff += blockLen;
|
|
91
|
+
remaining -= blockLen;
|
|
92
|
+
}
|
|
93
|
+
// Adler32 of uncompressed data
|
|
94
|
+
const adl = adler32(rawData);
|
|
95
|
+
writeU32BE(zlib, zOff, adl);
|
|
96
|
+
zOff += 4;
|
|
97
|
+
const zlibData = zlib.subarray(0, zOff);
|
|
98
|
+
|
|
99
|
+
// Build PNG chunks
|
|
100
|
+
const PNG_SIG = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
101
|
+
function makeChunk(type, data) {
|
|
102
|
+
const chunk = new Uint8Array(4 + 4 + data.length + 4);
|
|
103
|
+
writeU32BE(chunk, 0, data.length);
|
|
104
|
+
chunk[4] = type.charCodeAt(0);
|
|
105
|
+
chunk[5] = type.charCodeAt(1);
|
|
106
|
+
chunk[6] = type.charCodeAt(2);
|
|
107
|
+
chunk[7] = type.charCodeAt(3);
|
|
108
|
+
chunk.set(data, 8);
|
|
109
|
+
const crcInput = chunk.subarray(4, 8 + data.length);
|
|
110
|
+
writeU32BE(chunk, 8 + data.length, crc32(crcInput));
|
|
111
|
+
return chunk;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// IHDR: width, height, bit depth 8, color type 2 (RGB)
|
|
115
|
+
const ihdrData = new Uint8Array(13);
|
|
116
|
+
writeU32BE(ihdrData, 0, width);
|
|
117
|
+
writeU32BE(ihdrData, 4, height);
|
|
118
|
+
ihdrData[8] = 8; // bit depth
|
|
119
|
+
ihdrData[9] = 2; // color type: RGB
|
|
120
|
+
ihdrData[10] = 0; // compression
|
|
121
|
+
ihdrData[11] = 0; // filter
|
|
122
|
+
ihdrData[12] = 0; // interlace
|
|
123
|
+
|
|
124
|
+
const ihdrChunk = makeChunk('IHDR', ihdrData);
|
|
125
|
+
const idatChunk = makeChunk('IDAT', zlibData);
|
|
126
|
+
const iendChunk = makeChunk('IEND', new Uint8Array(0));
|
|
127
|
+
const png = new Uint8Array(PNG_SIG.length + ihdrChunk.length + idatChunk.length + iendChunk.length);
|
|
128
|
+
let off = 0;
|
|
129
|
+
png.set(PNG_SIG, off);
|
|
130
|
+
off += PNG_SIG.length;
|
|
131
|
+
png.set(ihdrChunk, off);
|
|
132
|
+
off += ihdrChunk.length;
|
|
133
|
+
png.set(idatChunk, off);
|
|
134
|
+
off += idatChunk.length;
|
|
135
|
+
png.set(iendChunk, off);
|
|
136
|
+
return png;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* If the image is JPEG 2000, decode and convert to PNG so React Native can display it.
|
|
141
|
+
* Returns { base64, mimeType } with the converted image, or the original if not JP2.
|
|
142
|
+
*/
|
|
143
|
+
function convertJP2IfNeeded(imageBuffer, mimeType) {
|
|
144
|
+
if (mimeType !== 'image/jp2') {
|
|
145
|
+
return {
|
|
146
|
+
base64: imageBuffer.toString('base64'),
|
|
147
|
+
mimeType
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
console.debug('[EID] Face image is JP2, attempting conversion…');
|
|
151
|
+
try {
|
|
152
|
+
// Lazy-load jpeg2000 to avoid crashing if it fails to import
|
|
153
|
+
const {
|
|
154
|
+
JpxImage
|
|
155
|
+
} = require('jpeg2000');
|
|
156
|
+
const jpx = new JpxImage();
|
|
157
|
+
jpx.parse(imageBuffer);
|
|
158
|
+
const tile = jpx.tiles[0];
|
|
159
|
+
console.debug(`[EID] JP2 decoded: ${tile.width}x${tile.height}, ${jpx.componentsCount} channels, ${tile.items.length} bytes`);
|
|
160
|
+
const pngBytes = pixelsToPng(tile.items, tile.width, tile.height, jpx.componentsCount);
|
|
161
|
+
const pngBase64 = Buffer.from(pngBytes).toString('base64');
|
|
162
|
+
console.debug(`[EID] Converted JP2 → PNG (${pngBytes.length} bytes, ${pngBase64.length} base64 chars)`);
|
|
163
|
+
return {
|
|
164
|
+
base64: pngBase64,
|
|
165
|
+
mimeType: 'image/png'
|
|
166
|
+
};
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.debug('[EID] JP2 conversion failed:', err);
|
|
169
|
+
return {
|
|
170
|
+
base64: imageBuffer.toString('base64'),
|
|
171
|
+
mimeType
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Try to parse PACE OID and parameter ID from EF.CardAccess data.
|
|
178
|
+
* Returns null if no PACE info is found.
|
|
179
|
+
*/
|
|
180
|
+
function parsePACEInfoFromCardAccess(data) {
|
|
181
|
+
try {
|
|
182
|
+
// EF.CardAccess is a SET of SecurityInfo sequences (ASN.1 DER)
|
|
183
|
+
// Each SecurityInfo: SEQUENCE { OID, INTEGER (version), optional INTEGER (parameterId) }
|
|
184
|
+
let offset = 0;
|
|
185
|
+
if (data[offset] === 0x31) {
|
|
186
|
+
// SET tag
|
|
187
|
+
offset++;
|
|
188
|
+
const setLen = readASN1Length(data, offset);
|
|
189
|
+
offset = setLen.nextOffset;
|
|
190
|
+
}
|
|
191
|
+
while (offset < data.length) {
|
|
192
|
+
if (data[offset] !== 0x30) break; // SEQUENCE tag
|
|
193
|
+
offset++;
|
|
194
|
+
const seqLen = readASN1Length(data, offset);
|
|
195
|
+
const seqEnd = seqLen.nextOffset + seqLen.length;
|
|
196
|
+
offset = seqLen.nextOffset;
|
|
197
|
+
|
|
198
|
+
// Read OID
|
|
199
|
+
if (data[offset] !== 0x06) {
|
|
200
|
+
offset = seqEnd;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
offset++;
|
|
204
|
+
const oidLen = readASN1Length(data, offset);
|
|
205
|
+
offset = oidLen.nextOffset;
|
|
206
|
+
const oidBytes = data.subarray(offset, offset + oidLen.length);
|
|
207
|
+
const oid = decodeOID(oidBytes);
|
|
208
|
+
offset += oidLen.length;
|
|
209
|
+
|
|
210
|
+
// Check if this is a PACE OID
|
|
211
|
+
if (oid.startsWith('0.4.0.127.0.7.2.2.4.')) {
|
|
212
|
+
// Read version (INTEGER)
|
|
213
|
+
let parameterId = 13; // default brainpoolP256r1
|
|
214
|
+
if (offset < seqEnd && data[offset] === 0x02) {
|
|
215
|
+
offset++;
|
|
216
|
+
const intLen = readASN1Length(data, offset);
|
|
217
|
+
offset = intLen.nextOffset + intLen.length;
|
|
218
|
+
}
|
|
219
|
+
// Read optional parameterId (INTEGER)
|
|
220
|
+
if (offset < seqEnd && data[offset] === 0x02) {
|
|
221
|
+
offset++;
|
|
222
|
+
const intLen = readASN1Length(data, offset);
|
|
223
|
+
offset = intLen.nextOffset;
|
|
224
|
+
parameterId = 0;
|
|
225
|
+
for (let i = 0; i < intLen.length; i++) {
|
|
226
|
+
parameterId = parameterId << 8 | data[offset + i];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
oid,
|
|
231
|
+
parameterId
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
offset = seqEnd;
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
// Failed to parse, PACE not available
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function readASN1Length(data, offset) {
|
|
242
|
+
const firstByte = data[offset];
|
|
243
|
+
if ((firstByte & 0x80) === 0) {
|
|
244
|
+
return {
|
|
245
|
+
length: firstByte,
|
|
246
|
+
nextOffset: offset + 1
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const numBytes = firstByte & 0x7f;
|
|
250
|
+
let length = 0;
|
|
251
|
+
for (let i = 0; i < numBytes; i++) {
|
|
252
|
+
length = length << 8 | data[offset + 1 + i];
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
length,
|
|
256
|
+
nextOffset: offset + 1 + numBytes
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function decodeOID(bytes) {
|
|
260
|
+
const components = [];
|
|
261
|
+
components.push(Math.floor(bytes[0] / 40));
|
|
262
|
+
components.push(bytes[0] % 40);
|
|
263
|
+
let value = 0;
|
|
264
|
+
for (let i = 1; i < bytes.length; i++) {
|
|
265
|
+
value = value << 7 | bytes[i] & 0x7f;
|
|
266
|
+
if ((bytes[i] & 0x80) === 0) {
|
|
267
|
+
components.push(value);
|
|
268
|
+
value = 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return components.join('.');
|
|
272
|
+
}
|
|
10
273
|
const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCallback) => {
|
|
11
274
|
let progress = 0;
|
|
12
275
|
const nfcManagerCardService = new NFCManagerCardService();
|
|
13
276
|
const passportService = new EIDService(nfcManagerCardService, EIDService.NORMAL_MAX_TRANSCEIVE_LENGTH, EIDService.DEFAULT_MAX_BLOCKSIZE, false, true);
|
|
14
277
|
try {
|
|
15
278
|
await passportService.open();
|
|
279
|
+
progress = 1;
|
|
280
|
+
if (progressCallback) {
|
|
281
|
+
progressCallback(progress);
|
|
282
|
+
}
|
|
16
283
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
284
|
+
// Try to read EF.CardAccess before selecting applet to check for PACE.
|
|
285
|
+
// Explicitly SELECT MF first so the SFI=0x1C context is correct,
|
|
286
|
+
// regardless of which application the chip activates on power-on.
|
|
287
|
+
let hasPACESucceeded = false;
|
|
288
|
+
try {
|
|
289
|
+
await passportService.sendSelectMF();
|
|
290
|
+
console.debug('[EID] SELECT MF OK');
|
|
291
|
+
} catch (mfErr) {
|
|
292
|
+
const mfMsg = mfErr instanceof Error ? mfErr.message : String(mfErr);
|
|
293
|
+
console.debug(`[EID] SELECT MF failed (${mfMsg}), continuing with SFI read anyway`);
|
|
294
|
+
}
|
|
295
|
+
progress = 2;
|
|
20
296
|
if (progressCallback) {
|
|
21
297
|
progressCallback(progress);
|
|
22
298
|
}
|
|
23
299
|
|
|
24
|
-
//
|
|
300
|
+
// Read EF.CardAccess — kept in a separate try/catch so we can distinguish
|
|
301
|
+
// "file read failure" from "PACE protocol failure" in the logs.
|
|
302
|
+
let cardAccessBuf = null;
|
|
25
303
|
try {
|
|
26
|
-
const
|
|
27
|
-
await
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
304
|
+
const cardAccessStream = passportService.getInputStream(EIDService.EF_CARD_ACCESS);
|
|
305
|
+
await cardAccessStream.init();
|
|
306
|
+
const cardAccessLen = cardAccessStream.getLength();
|
|
307
|
+
const buf = Buffer.alloc(cardAccessLen);
|
|
308
|
+
for (let i = 0; i < cardAccessLen; i++) {
|
|
309
|
+
buf[i] = await cardAccessStream.read();
|
|
310
|
+
}
|
|
311
|
+
cardAccessBuf = buf;
|
|
312
|
+
console.debug(`[EID] EF.CardAccess read OK: ${cardAccessLen} bytes = ${cardAccessBuf.toString('hex')}`);
|
|
313
|
+
} catch (readErr) {
|
|
314
|
+
const readMsg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
315
|
+
console.debug(`[EID] EF.CardAccess read FAILED: ${readMsg} — falling back to BAC`);
|
|
316
|
+
}
|
|
317
|
+
progress = 3;
|
|
318
|
+
if (progressCallback) {
|
|
319
|
+
progressCallback(progress);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// If EF.CardAccess was read successfully, attempt PACE.
|
|
323
|
+
if (cardAccessBuf !== null) {
|
|
324
|
+
try {
|
|
325
|
+
const paceInfo = parsePACEInfoFromCardAccess(cardAccessBuf);
|
|
326
|
+
if (paceInfo) {
|
|
327
|
+
console.debug(`[EID] PACE available: oid=${paceInfo.oid} parameterId=${paceInfo.parameterId}`);
|
|
328
|
+
// PACE is available - derive key from MRZ and perform PACE
|
|
329
|
+
const bacKey = new BACKey(documentNumber, dateOfBirth, dateOfExpiry);
|
|
330
|
+
const paceKey = PACEKeySpec.createMRZKey(bacKey.getKeySeedForPACE());
|
|
331
|
+
progress = 4;
|
|
332
|
+
if (progressCallback) {
|
|
333
|
+
progressCallback(progress);
|
|
334
|
+
}
|
|
335
|
+
await passportService.doPACE(paceKey, paceInfo.oid, paceInfo.parameterId, progressCallback);
|
|
336
|
+
hasPACESucceeded = true;
|
|
337
|
+
console.debug('[EID] PACE succeeded');
|
|
338
|
+
} else {
|
|
339
|
+
console.debug('[EID] EF.CardAccess read OK but no PACE SecurityInfo found — document does not support PACE, falling back to BAC');
|
|
340
|
+
}
|
|
341
|
+
} catch (paceError) {
|
|
342
|
+
// PACE protocol exchange failed — fall back to BAC
|
|
343
|
+
const msg = paceError instanceof Error ? paceError.message : String(paceError);
|
|
344
|
+
console.debug(`[EID] PACE protocol failed (${msg}), falling back to BAC`);
|
|
345
|
+
if (paceError instanceof Error && paceError.stack) {
|
|
346
|
+
console.debug('[EID] PACE error stack:', paceError.stack);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
progress = 9;
|
|
351
|
+
if (progressCallback) {
|
|
352
|
+
progressCallback(progress);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Select Applet for MRTD
|
|
356
|
+
await passportService.sendSelectApplet(hasPACESucceeded);
|
|
357
|
+
progress = 10;
|
|
358
|
+
if (progressCallback) {
|
|
359
|
+
progressCallback(progress);
|
|
360
|
+
}
|
|
361
|
+
if (!hasPACESucceeded) {
|
|
362
|
+
// Check if EF_COM is available
|
|
363
|
+
try {
|
|
364
|
+
const efComStream = passportService.getInputStream(EIDService.EF_COM);
|
|
365
|
+
await efComStream.init();
|
|
366
|
+
await efComStream.read();
|
|
367
|
+
} catch (error) {
|
|
368
|
+
// EF_COM not available -> try to do BAC
|
|
369
|
+
const bacKey = new BACKey(documentNumber, dateOfBirth, dateOfExpiry);
|
|
370
|
+
await passportService.doBAC(bacKey);
|
|
371
|
+
}
|
|
33
372
|
}
|
|
34
373
|
progress = 20;
|
|
35
374
|
if (progressCallback) {
|
|
@@ -69,8 +408,10 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
69
408
|
const imageInputStream = await faceImageInfo.getImageInputStream();
|
|
70
409
|
const buffer = Buffer.alloc(imageLength);
|
|
71
410
|
await imageInputStream.readBytesWithOffset(buffer, 0, imageLength);
|
|
72
|
-
|
|
73
|
-
|
|
411
|
+
const rawMimeType = faceImageInfo.getMimeType();
|
|
412
|
+
const converted = convertJP2IfNeeded(buffer, rawMimeType);
|
|
413
|
+
imageAsBase64 = converted.base64;
|
|
414
|
+
mimeType = converted.mimeType;
|
|
74
415
|
}
|
|
75
416
|
progress = 100;
|
|
76
417
|
if (progressCallback) {
|
|
@@ -6,6 +6,8 @@ import { DefaultFileSystem } from "./defaultFileSystem.js";
|
|
|
6
6
|
import { BACAPDUSender } from "./protocol/bacAPDUSender.js";
|
|
7
7
|
import { ReadBinaryAPDUSender } from "./protocol/readBinaryAPDUSender.js";
|
|
8
8
|
import { BACProtocol } from "./protocol/bacProtocol.js";
|
|
9
|
+
import { PACEAPDUSender } from "./protocol/paceAPDUSender.js";
|
|
10
|
+
import { PACEProtocol } from "./protocol/paceProtocol.js";
|
|
9
11
|
import { EID_CONSTANTS } from "./constants/eidConstants.js";
|
|
10
12
|
export class EIDService extends AbstractMRTDCardService {
|
|
11
13
|
/** Shared secret type for non-PACE key. */
|
|
@@ -175,7 +177,13 @@ export class EIDService extends AbstractMRTDCardService {
|
|
|
175
177
|
this.shouldCheckMAC = shouldCheckMAC;
|
|
176
178
|
this.isAppletSelected = false;
|
|
177
179
|
this.isOpen = false;
|
|
178
|
-
|
|
180
|
+
// EF.CardAccess and EF.CardSecurity live in the MF (Master File), outside
|
|
181
|
+
// the ICAO applet. They must be read via SFI-based READ BINARY (no SELECT
|
|
182
|
+
// needed). Without SFI, the implementation falls back to SELECT-by-FID
|
|
183
|
+
// which many passport chips reject at MF level before applet selection,
|
|
184
|
+
// causing PACE to be falsely reported as "not available".
|
|
185
|
+
const rootFidToSFI = new Map([[EID_CONSTANTS.EF_CARD_ACCESS, EID_CONSTANTS.SFI_CARD_ACCESS], [EID_CONSTANTS.EF_CARD_SECURITY, EID_CONSTANTS.SFI_CARD_SECURITY]]);
|
|
186
|
+
this.rootFileSystem = new DefaultFileSystem(this.readBinarySender, true, rootFidToSFI);
|
|
179
187
|
this.appletFileSystem = new DefaultFileSystem(this.readBinarySender, isSFIEnabled);
|
|
180
188
|
}
|
|
181
189
|
async open() {
|
|
@@ -209,6 +217,22 @@ export class EIDService extends AbstractMRTDCardService {
|
|
|
209
217
|
this.appletFileSystem.setWrapper(this.wrapper);
|
|
210
218
|
return bacResult;
|
|
211
219
|
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Performs the PACE protocol.
|
|
223
|
+
*
|
|
224
|
+
* @param accessKey the access key (MRZ or CAN based)
|
|
225
|
+
* @param oid the PACE OID string from EF.CardAccess
|
|
226
|
+
* @param parameterId the standard domain parameter ID (e.g. 13 for brainpoolP256r1)
|
|
227
|
+
*/
|
|
228
|
+
async doPACE(accessKey, oid, parameterId, progressCallback) {
|
|
229
|
+
const paceSender = new PACEAPDUSender(this.service);
|
|
230
|
+
const paceProtocol = new PACEProtocol(paceSender, this.wrapper, EIDService.NORMAL_MAX_TRANSCEIVE_LENGTH, this.maxTransceiveLengthForSecureMessaging, this.shouldCheckMAC);
|
|
231
|
+
const paceResult = await paceProtocol.doPACE(accessKey, oid, parameterId, progressCallback);
|
|
232
|
+
this.wrapper = paceResult.getWrapper();
|
|
233
|
+
this.appletFileSystem.setWrapper(this.wrapper);
|
|
234
|
+
return paceResult;
|
|
235
|
+
}
|
|
212
236
|
async close() {
|
|
213
237
|
try {
|
|
214
238
|
await this.service.close();
|