@trustchex/react-native-sdk 1.409.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.
Files changed (156) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
  2. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +59 -1
  3. package/ios/Camera/TrustchexCameraView.swift +9 -1
  4. package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
  5. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +1 -4
  6. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +17 -4
  7. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +102 -23
  8. package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
  9. package/lib/module/Screens/Dynamic/VideoCallScreen.js +3 -1
  10. package/lib/module/Screens/Static/ResultScreen.js +128 -22
  11. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +8 -0
  12. package/lib/module/Shared/Animations/recording.json +1 -0
  13. package/lib/module/Shared/Components/DebugNavigationPanel.js +69 -71
  14. package/lib/module/Shared/Components/EIDScanner.js +212 -108
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
  16. package/lib/module/Shared/Components/IdentityDocumentCamera.js +53 -36
  17. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
  18. package/lib/module/Shared/Components/NavigationManager.js +24 -16
  19. package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
  20. package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
  21. package/lib/module/Shared/EIDReader/bacKey.js +16 -2
  22. package/lib/module/Shared/EIDReader/eidReader.js +354 -13
  23. package/lib/module/Shared/EIDReader/eidService.js +25 -1
  24. package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
  25. package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
  26. package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
  27. package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
  28. package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
  29. package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
  30. package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
  31. package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
  32. package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
  33. package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
  34. package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
  35. package/lib/module/Shared/Libs/analytics.utils.js +4 -0
  36. package/lib/module/Shared/Libs/contains.js +1 -40
  37. package/lib/module/Shared/Libs/country-display.utils.js +34 -0
  38. package/lib/module/Shared/Libs/demo.utils.js +8 -0
  39. package/lib/module/Shared/Libs/mrz.utils.js +3 -2
  40. package/lib/module/Shared/Libs/status-bar.utils.js +4 -2
  41. package/lib/module/Shared/Types/analytics.types.js +2 -0
  42. package/lib/module/Translation/Resources/en.js +41 -2
  43. package/lib/module/Translation/Resources/tr.js +41 -2
  44. package/lib/module/Trustchex.js +54 -20
  45. package/lib/module/version.js +1 -1
  46. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
  47. package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -0
  48. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  49. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  50. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  51. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts +3 -0
  52. package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
  53. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
  54. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  55. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  56. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  57. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  58. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
  60. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
  62. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  63. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  64. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
  65. package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
  66. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
  67. package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
  68. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
  69. package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
  70. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  71. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
  72. package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
  73. package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
  74. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
  75. package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
  76. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
  77. package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
  78. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
  79. package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
  80. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
  81. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
  82. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
  83. package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
  84. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
  85. package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
  86. package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
  87. package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
  88. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
  89. package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
  90. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  91. package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
  92. package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
  93. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
  94. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
  95. package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
  96. package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
  97. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  98. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  99. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -1
  100. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
  101. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  102. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +10 -1
  103. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  104. package/lib/typescript/src/Translation/Resources/en.d.ts +40 -1
  105. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  106. package/lib/typescript/src/Translation/Resources/tr.d.ts +40 -1
  107. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  108. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  109. package/lib/typescript/src/version.d.ts +1 -1
  110. package/package.json +7 -4
  111. package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
  112. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -4
  113. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +21 -4
  114. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +124 -23
  115. package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
  116. package/src/Screens/Dynamic/VideoCallScreen.tsx +3 -1
  117. package/src/Screens/Static/ResultScreen.tsx +183 -31
  118. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +9 -0
  119. package/src/Shared/Animations/recording.json +1 -0
  120. package/src/Shared/Components/DebugNavigationPanel.tsx +73 -48
  121. package/src/Shared/Components/EIDScanner.tsx +222 -111
  122. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
  123. package/src/Shared/Components/IdentityDocumentCamera.tsx +199 -184
  124. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
  125. package/src/Shared/Components/NavigationManager.tsx +27 -18
  126. package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
  127. package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
  128. package/src/Shared/EIDReader/bacKey.ts +24 -8
  129. package/src/Shared/EIDReader/eidReader.ts +398 -12
  130. package/src/Shared/EIDReader/eidService.ts +49 -1
  131. package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
  132. package/src/Shared/EIDReader/paceInfo.ts +159 -0
  133. package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
  134. package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
  135. package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
  136. package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
  137. package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
  138. package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
  139. package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
  140. package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
  141. package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
  142. package/src/Shared/Libs/analytics.utils.ts +4 -0
  143. package/src/Shared/Libs/contains.ts +0 -53
  144. package/src/Shared/Libs/country-display.utils.ts +55 -0
  145. package/src/Shared/Libs/crypto.utils.ts +2 -2
  146. package/src/Shared/Libs/demo.utils.ts +10 -0
  147. package/src/Shared/Libs/http-client.ts +12 -4
  148. package/src/Shared/Libs/mrz.utils.ts +3 -2
  149. package/src/Shared/Libs/status-bar.utils.ts +4 -2
  150. package/src/Shared/Services/VideoSessionService.ts +1 -1
  151. package/src/Shared/Types/analytics.types.ts +2 -0
  152. package/src/Shared/Types/identificationInfo.ts +11 -0
  153. package/src/Translation/Resources/en.ts +63 -3
  154. package/src/Translation/Resources/tr.ts +62 -3
  155. package/src/Trustchex.tsx +53 -17
  156. package/src/version.ts +1 -1
@@ -0,0 +1,62 @@
1
+ import type { AccessKeySpec } from '../accessKeySpec';
2
+ import type { SecureMessagingWrapper } from '../secureMessagingWrapper';
3
+ import type { MappingType } from '../paceInfo';
4
+
5
+ /**
6
+ * Result of PACE protocol execution.
7
+ */
8
+ export class PACEResult {
9
+ private paceKey: AccessKeySpec;
10
+ private mappingType: MappingType;
11
+ private agreementAlg: string;
12
+ private cipherAlg: string;
13
+ private digestAlg: string;
14
+ private keyLength: number;
15
+ private wrapper: SecureMessagingWrapper;
16
+
17
+ constructor(
18
+ paceKey: AccessKeySpec,
19
+ mappingType: MappingType,
20
+ agreementAlg: string,
21
+ cipherAlg: string,
22
+ digestAlg: string,
23
+ keyLength: number,
24
+ wrapper: SecureMessagingWrapper
25
+ ) {
26
+ this.paceKey = paceKey;
27
+ this.mappingType = mappingType;
28
+ this.agreementAlg = agreementAlg;
29
+ this.cipherAlg = cipherAlg;
30
+ this.digestAlg = digestAlg;
31
+ this.keyLength = keyLength;
32
+ this.wrapper = wrapper;
33
+ }
34
+
35
+ public getPACEKey(): AccessKeySpec {
36
+ return this.paceKey;
37
+ }
38
+
39
+ public getMappingType(): MappingType {
40
+ return this.mappingType;
41
+ }
42
+
43
+ public getAgreementAlg(): string {
44
+ return this.agreementAlg;
45
+ }
46
+
47
+ public getCipherAlg(): string {
48
+ return this.cipherAlg;
49
+ }
50
+
51
+ public getDigestAlg(): string {
52
+ return this.digestAlg;
53
+ }
54
+
55
+ public getKeyLength(): number {
56
+ return this.keyLength;
57
+ }
58
+
59
+ public getWrapper(): SecureMessagingWrapper {
60
+ return this.wrapper;
61
+ }
62
+ }
@@ -87,7 +87,7 @@ export abstract class SecureMessagingWrapper implements APDUWrapper {
87
87
  dataOutputStream.push(...paddedData);
88
88
  let cc2 = Uint8Array.from(
89
89
  Buffer.from(
90
- cryptoUtils.computeMac(
90
+ this.computeMAC(
91
91
  Buffer.from(dataOutputStream).toString('hex'),
92
92
  this.ksMac
93
93
  ),
@@ -116,6 +116,30 @@ export abstract class SecureMessagingWrapper implements APDUWrapper {
116
116
 
117
117
  protected abstract getEncodedSendSequenceCounter(): Uint8Array;
118
118
 
119
+ /**
120
+ * Encrypt data using the session encryption key.
121
+ * Subclasses override for AES vs 3DES.
122
+ */
123
+ protected encrypt(dataHex: string, keyHex: string): string {
124
+ return cryptoUtils.encryptWith3DES(dataHex, keyHex);
125
+ }
126
+
127
+ /**
128
+ * Decrypt data using the session encryption key.
129
+ * Subclasses override for AES vs 3DES.
130
+ */
131
+ protected decrypt(dataHex: string, keyHex: string): string {
132
+ return cryptoUtils.decryptWith3DES(dataHex, keyHex);
133
+ }
134
+
135
+ /**
136
+ * Compute MAC over data using the session MAC key.
137
+ * Subclasses override for AES-CMAC vs 3DES MAC.
138
+ */
139
+ protected computeMAC(dataHex: string, keyHex: string): string {
140
+ return cryptoUtils.computeMac(dataHex, keyHex);
141
+ }
142
+
119
143
  private wrapCommandAPDU(commandAPDU: CommandAPDU): CommandAPDU {
120
144
  const cla = commandAPDU.getCLA();
121
145
  const ins = commandAPDU.getINS();
@@ -149,10 +173,7 @@ export abstract class SecureMessagingWrapper implements APDUWrapper {
149
173
  this.getPadLength()
150
174
  );
151
175
  const ciphertext = Buffer.from(
152
- cryptoUtils.encryptWith3DES(
153
- Buffer.from(data).toString('hex'),
154
- this.ksEnc
155
- ),
176
+ this.encrypt(Buffer.from(data).toString('hex'), this.ksEnc),
156
177
  'hex'
157
178
  );
158
179
  output.push(hasDO85 ? 0x85 : 0x87);
@@ -180,7 +201,7 @@ export abstract class SecureMessagingWrapper implements APDUWrapper {
180
201
  );
181
202
 
182
203
  const cc = Buffer.from(
183
- cryptoUtils.computeMac(Buffer.from(n).toString('hex'), this.ksMac),
204
+ this.computeMAC(Buffer.from(n).toString('hex'), this.ksMac),
184
205
  'hex'
185
206
  );
186
207
  let ccLength = cc.length;
@@ -329,10 +350,7 @@ export abstract class SecureMessagingWrapper implements APDUWrapper {
329
350
  inputStream.readFully(ciphertext);
330
351
  const paddedData = Array.from(
331
352
  Buffer.from(
332
- cryptoUtils.decryptWith3DES(
333
- Buffer.from(ciphertext).toString('hex'),
334
- this.ksEnc
335
- ),
353
+ this.decrypt(Buffer.from(ciphertext).toString('hex'), this.ksEnc),
336
354
  'hex'
337
355
  )
338
356
  );
@@ -18,7 +18,8 @@ export class CommandAPDU {
18
18
  ne?: number
19
19
  ) {
20
20
  this.nc = dataLength || 0;
21
- this.ne = ne || 0;
21
+ // Java JMRTD uses -1 to mean "no Le field" (ne=0). Guard against negatives.
22
+ this.ne = ne == null || ne < 0 ? 0 : ne;
22
23
  this.apdu = this.buildAPDU(
23
24
  cla,
24
25
  ins,
@@ -88,7 +88,7 @@ export class TLVHelpers extends ASN1Constants {
88
88
  private static log(n: number, base: number): number {
89
89
  let result = 0;
90
90
  while (n > 0) {
91
- n = n / base;
91
+ n = Math.floor(n / base);
92
92
  result++;
93
93
  }
94
94
  return result;
@@ -6,7 +6,6 @@ import { Buffer } from 'buffer';
6
6
  import { TLVHelpers } from './tlv.helpers';
7
7
 
8
8
  class TLVUtil extends TLVHelpers {
9
-
10
9
  public static wrapDO(tag: number, data: number[]): Uint8Array {
11
10
  if (data == null) {
12
11
  throw new Error('Data to wrap is null');
@@ -49,7 +48,7 @@ class TLVUtil extends TLVHelpers {
49
48
  const actualTag = await tlvInputStream.readTag();
50
49
  if (actualTag !== expectedTag) {
51
50
  throw new Error(
52
- `Expected tag ${expectedTag.toString(16)}, found tag ${actualTag.toString(16)}`
51
+ `Expected TLV tag 0x${expectedTag.toString(16)}, found 0x${actualTag.toString(16)} in data: ${Buffer.from(wrappedData.slice(0, 16)).toString('hex')}`
53
52
  );
54
53
  }
55
54
 
@@ -61,8 +60,13 @@ class TLVUtil extends TLVHelpers {
61
60
  }
62
61
  return result;
63
62
  } catch (ioe) {
64
- // Never happens.
65
- throw new Error('Error reading from stream');
63
+ // Re-throw original error with context rather than masking it
64
+ if (ioe instanceof Error && ioe.message.includes('Expected TLV tag')) {
65
+ throw ioe;
66
+ }
67
+ throw new Error(
68
+ `TLV unwrap error (expected tag 0x${expectedTag.toString(16)}): ${ioe instanceof Error ? ioe.message : String(ioe)}`
69
+ );
66
70
  } finally {
67
71
  try {
68
72
  tlvInputStream.close();
@@ -72,7 +76,6 @@ class TLVUtil extends TLVHelpers {
72
76
  }
73
77
  }
74
78
  }
75
-
76
79
  }
77
80
 
78
81
  export default TLVUtil;
@@ -0,0 +1,217 @@
1
+ import CryptoJS from 'crypto-js';
2
+ import { Buffer } from 'buffer';
3
+ import 'react-native-get-random-values';
4
+
5
+ /**
6
+ * AES cryptographic utilities for PACE protocol.
7
+ */
8
+
9
+ /**
10
+ * Encrypt data using AES in CBC mode with zero IV.
11
+ */
12
+ export const aesEncryptCBC = (
13
+ dataHex: string,
14
+ keyHex: string,
15
+ ivHex: string = '00000000000000000000000000000000'
16
+ ): string => {
17
+ const data = CryptoJS.enc.Hex.parse(dataHex);
18
+ const key = CryptoJS.enc.Hex.parse(keyHex);
19
+ const iv = CryptoJS.enc.Hex.parse(ivHex);
20
+
21
+ const encrypted = CryptoJS.AES.encrypt(data, key, {
22
+ mode: CryptoJS.mode.CBC,
23
+ padding: CryptoJS.pad.NoPadding,
24
+ iv,
25
+ });
26
+
27
+ return encrypted.ciphertext.toString(CryptoJS.enc.Hex);
28
+ };
29
+
30
+ /**
31
+ * Decrypt data using AES in CBC mode with zero IV.
32
+ */
33
+ export const aesDecryptCBC = (
34
+ dataHex: string,
35
+ keyHex: string,
36
+ ivHex: string = '00000000000000000000000000000000'
37
+ ): string => {
38
+ const data = CryptoJS.enc.Hex.parse(dataHex);
39
+ const key = CryptoJS.enc.Hex.parse(keyHex);
40
+ const iv = CryptoJS.enc.Hex.parse(ivHex);
41
+
42
+ const cipherParams = CryptoJS.lib.CipherParams.create({
43
+ ciphertext: data,
44
+ });
45
+
46
+ const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
47
+ mode: CryptoJS.mode.CBC,
48
+ padding: CryptoJS.pad.NoPadding,
49
+ iv,
50
+ });
51
+
52
+ return decrypted.toString(CryptoJS.enc.Hex);
53
+ };
54
+
55
+ /**
56
+ * Encrypt a single block with AES in ECB mode (for SSC encryption).
57
+ */
58
+ export const aesEncryptECB = (dataHex: string, keyHex: string): string => {
59
+ const data = CryptoJS.enc.Hex.parse(dataHex);
60
+ const key = CryptoJS.enc.Hex.parse(keyHex);
61
+
62
+ const encrypted = CryptoJS.AES.encrypt(data, key, {
63
+ mode: CryptoJS.mode.ECB,
64
+ padding: CryptoJS.pad.NoPadding,
65
+ });
66
+
67
+ return encrypted.ciphertext.toString(CryptoJS.enc.Hex);
68
+ };
69
+
70
+ /**
71
+ * Compute AES CMAC (OMAC1) as defined in RFC 4493.
72
+ */
73
+ export const aesCMAC = (dataHex: string, keyHex: string): string => {
74
+ const key = Buffer.from(keyHex, 'hex');
75
+ const data = Buffer.from(dataHex, 'hex');
76
+ const blockSize = 16;
77
+
78
+ // Step 1: Generate subkeys
79
+ const zeroBlock = '00000000000000000000000000000000';
80
+ const L = Buffer.from(aesEncryptECB(zeroBlock, keyHex), 'hex');
81
+ const K1 = generateSubkey(L);
82
+ const K2 = generateSubkey(K1);
83
+
84
+ // Step 2: Determine if we need to pad
85
+ const n = data.length === 0 ? 1 : Math.ceil(data.length / blockSize);
86
+ const lastBlockComplete = data.length > 0 && data.length % blockSize === 0;
87
+
88
+ // Step 3: Process last block
89
+ const lastBlock = Buffer.alloc(blockSize);
90
+ if (lastBlockComplete) {
91
+ const start = (n - 1) * blockSize;
92
+ for (let i = 0; i < blockSize; i++) {
93
+ lastBlock[i] = data[start + i] ^ K1[i];
94
+ }
95
+ } else {
96
+ // Pad with 10*
97
+ const start = (n - 1) * blockSize;
98
+ const remaining = data.length - start;
99
+ const padded = Buffer.alloc(blockSize);
100
+ for (let i = 0; i < remaining; i++) {
101
+ padded[i] = data[start + i];
102
+ }
103
+ padded[remaining] = 0x80;
104
+ for (let i = 0; i < blockSize; i++) {
105
+ lastBlock[i] = padded[i] ^ K2[i];
106
+ }
107
+ }
108
+
109
+ // Step 4: CBC-MAC
110
+ let X = Buffer.alloc(blockSize);
111
+ for (let i = 0; i < n - 1; i++) {
112
+ const block = data.subarray(i * blockSize, (i + 1) * blockSize);
113
+ const Y = Buffer.alloc(blockSize);
114
+ for (let j = 0; j < blockSize; j++) {
115
+ Y[j] = X[j] ^ block[j];
116
+ }
117
+ X = Buffer.from(aesEncryptECB(Y.toString('hex'), keyHex), 'hex');
118
+ }
119
+
120
+ // Process last block
121
+ const Y = Buffer.alloc(blockSize);
122
+ for (let j = 0; j < blockSize; j++) {
123
+ Y[j] = X[j] ^ lastBlock[j];
124
+ }
125
+ const T = aesEncryptECB(Y.toString('hex'), keyHex);
126
+
127
+ return T;
128
+ };
129
+
130
+ function generateSubkey(input: Buffer): Buffer {
131
+ const blockSize = 16;
132
+ const Rb = Buffer.alloc(blockSize);
133
+ Rb[blockSize - 1] = 0x87;
134
+
135
+ // Left shift by 1
136
+ const output = Buffer.alloc(blockSize);
137
+ const msb = (input[0] & 0x80) !== 0;
138
+ for (let i = 0; i < blockSize - 1; i++) {
139
+ output[i] = ((input[i] << 1) | (input[i + 1] >> 7)) & 0xff;
140
+ }
141
+ output[blockSize - 1] = (input[blockSize - 1] << 1) & 0xff;
142
+
143
+ if (msb) {
144
+ for (let i = 0; i < blockSize; i++) {
145
+ output[i] ^= Rb[i];
146
+ }
147
+ }
148
+
149
+ return output;
150
+ }
151
+
152
+ /**
153
+ * Derive AES key from shared secret using KDF as per TR-SAC.
154
+ *
155
+ * @param sharedSecretHex the shared secret bytes as hex
156
+ * @param cipherAlg 'AES' or 'DESede'
157
+ * @param keyLength 128, 192, or 256
158
+ * @param mode 1 = ENC, 2 = MAC, 3 = PACE
159
+ */
160
+ export const deriveKey = (
161
+ sharedSecretHex: string,
162
+ cipherAlg: string,
163
+ keyLength: number,
164
+ mode: number
165
+ ): string => {
166
+ const input = sharedSecretHex + '000000' + mode.toString(16).padStart(2, '0');
167
+
168
+ let hash: string;
169
+ if (cipherAlg === 'AES' && keyLength > 128) {
170
+ // Use SHA-256 for AES-192 and AES-256
171
+ hash = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(input)).toString(
172
+ CryptoJS.enc.Hex
173
+ );
174
+ } else {
175
+ // Use SHA-1 for DESede and AES-128
176
+ hash = CryptoJS.SHA1(CryptoJS.enc.Hex.parse(input)).toString(
177
+ CryptoJS.enc.Hex
178
+ );
179
+ }
180
+
181
+ if (cipherAlg === 'DESede') {
182
+ // 3DES: 24-byte key (K1 || K2 || K1)
183
+ const ka = hash.substring(0, 16);
184
+ const kb = hash.substring(16, 32);
185
+ return ka + kb + ka;
186
+ } else {
187
+ // AES: return first keyLength/8 bytes
188
+ const keyBytes = keyLength / 4; // hex chars = keyLength / 8 * 2
189
+ return hash.substring(0, keyBytes);
190
+ }
191
+ };
192
+
193
+ /**
194
+ * Pad data using ISO 9797-1 method 2 with given block size.
195
+ */
196
+ export const padISO9797 = (data: Buffer, blockSize: number = 16): Buffer => {
197
+ const n = data.length;
198
+ const padLength = blockSize - (n % blockSize);
199
+ const out = Buffer.alloc(n + padLength);
200
+ data.copy(out);
201
+ out[n] = 0x80;
202
+ return out;
203
+ };
204
+
205
+ /**
206
+ * Unpad ISO 9797-1 method 2.
207
+ */
208
+ export const unpadISO9797 = (data: Buffer): Buffer => {
209
+ let i = data.length - 1;
210
+ while (i >= 0 && data[i] === 0x00) {
211
+ i--;
212
+ }
213
+ if (i >= 0 && data[i] === 0x80) {
214
+ return data.subarray(0, i);
215
+ }
216
+ return data;
217
+ };
@@ -289,6 +289,10 @@ const STEP_EVENT_MAP: Record<
289
289
  started: AnalyticsEventName.LIVENESS_CHECK_STARTED,
290
290
  completed: AnalyticsEventName.LIVENESS_CHECK_COMPLETED,
291
291
  },
292
+ verbal_consent: {
293
+ started: AnalyticsEventName.VERBAL_CONSENT_VIDEO_STARTED,
294
+ completed: AnalyticsEventName.VERBAL_CONSENT_VIDEO_COMPLETED,
295
+ },
292
296
  video_call: {
293
297
  started: AnalyticsEventName.VIDEO_CALL_STARTED,
294
298
  completed: AnalyticsEventName.VIDEO_CALL_COMPLETED,
@@ -4,56 +4,3 @@ export interface Rect {
4
4
  width: number;
5
5
  height: number;
6
6
  }
7
-
8
- interface Contains {
9
- outside: Rect;
10
- inside: Rect;
11
- }
12
-
13
- export function contains({ outside, inside }: Contains) {
14
- const outsideMaxX = outside.minX + outside.width;
15
- const insideMaxX = inside.minX + inside.width;
16
-
17
- const outsideMaxY = outside.minY + outside.height;
18
- const insideMaxY = inside.minY + inside.height;
19
-
20
- if (inside.minX < outside.minX) {
21
- return false;
22
- }
23
- if (insideMaxX > outsideMaxX) {
24
- return false;
25
- }
26
- if (inside.minY < outside.minY) {
27
- return false;
28
- }
29
- if (insideMaxY > outsideMaxY) {
30
- return false;
31
- }
32
-
33
- return true;
34
- }
35
-
36
- export function contains2({ outside, inside }: Contains) {
37
- const outsideMaxX = outside.minX + outside.width;
38
- const insideMaxX = inside.minX + inside.width;
39
-
40
- const outsideMaxY = outside.minY + outside.height;
41
- const insideMaxY = inside.minY + inside.height;
42
-
43
- const xIntersect = Math.max(
44
- 0,
45
- Math.min(insideMaxX, outsideMaxX) - Math.max(inside.minX, outside.minX)
46
- );
47
- const yIntersect = Math.max(
48
- 0,
49
- Math.min(insideMaxY, outsideMaxY) - Math.max(inside.minY, outside.minY)
50
- );
51
- const intersectArea = xIntersect * yIntersect;
52
-
53
- const insideArea = inside.width * inside.height;
54
- const outsideArea = outside.width * outside.height;
55
-
56
- const unionArea = insideArea + outsideArea - intersectArea;
57
-
58
- return unionArea === outsideArea;
59
- }
@@ -0,0 +1,55 @@
1
+ import { ISOCountry } from '../EIDReader/data/isoCountry';
2
+ import { UnicodeCountry } from '../EIDReader/data/unicodeCountry';
3
+
4
+ const COUNTRY_NAME_OVERRIDES: Record<string, string> = {
5
+ TUR: 'Türkiye',
6
+ };
7
+
8
+ const findCountryByAlpha3 = (alpha3Code: string) => {
9
+ return [...UnicodeCountry.VALUES, ...ISOCountry.VALUES].find(
10
+ (country) => country.toAlpha3Code() === alpha3Code
11
+ );
12
+ };
13
+
14
+ export const getLocalizedCountryName = (
15
+ alpha3Code?: string | null,
16
+ _language?: string | null
17
+ ): string => {
18
+ if (!alpha3Code) return '';
19
+
20
+ const normalizedCode = alpha3Code.trim().toUpperCase();
21
+ const overrideName = COUNTRY_NAME_OVERRIDES[normalizedCode];
22
+
23
+ if (overrideName) {
24
+ return overrideName;
25
+ }
26
+
27
+ const country = findCountryByAlpha3(normalizedCode);
28
+ if (!country) {
29
+ return normalizedCode;
30
+ }
31
+
32
+ if (
33
+ typeof Intl !== 'undefined' &&
34
+ 'Locale' in Intl &&
35
+ typeof Intl.Locale === 'function' &&
36
+ 'DisplayNames' in Intl &&
37
+ typeof Intl.DisplayNames === 'function'
38
+ ) {
39
+ const nativeLanguage = new Intl.Locale(
40
+ `und-${country.toAlpha2Code()}`
41
+ ).maximize().language;
42
+
43
+ if (nativeLanguage) {
44
+ const nativeName = new Intl.DisplayNames([nativeLanguage], {
45
+ type: 'region',
46
+ }).of(country.toAlpha2Code());
47
+
48
+ if (nativeName) {
49
+ return nativeName;
50
+ }
51
+ }
52
+ }
53
+
54
+ return country.getName();
55
+ };
@@ -9,8 +9,8 @@ const getSessionKey = async (apiUrl: string, sessionId: string) => {
9
9
  const clientPrivateKey = secp256k1.utils.randomPrivateKey();
10
10
  const clientPublicKey = secp256k1.getPublicKey(clientPrivateKey);
11
11
  const serverResponse = await httpClient.post<
12
- { serverPublicKey: String },
13
- { clientPublicKey: String }
12
+ { serverPublicKey: string },
13
+ { clientPublicKey: string }
14
14
  >(`${apiUrl}/verification-sessions/${sessionId}/auth`, {
15
15
  clientPublicKey: Buffer.from(clientPublicKey).toString('hex'),
16
16
  });
@@ -36,6 +36,16 @@ const demoSession = {
36
36
  },
37
37
  },
38
38
  },
39
+ {
40
+ type: 'VERBAL_CONSENT',
41
+ required: false,
42
+ data: {
43
+ voiceGuidanceActive: true,
44
+ verbalConsentTitle: 'Verbal Consent',
45
+ verbalConsentText:
46
+ 'I hereby provide my verbal consent for identity verification purposes. I confirm that all information provided is accurate and complete.',
47
+ },
48
+ },
39
49
  {
40
50
  type: 'IDENTITY_DOCUMENT_SCAN',
41
51
  required: false,
@@ -66,7 +66,10 @@ const request = async <TResponse, TBody>(
66
66
 
67
67
  console.log(`[HTTP] ${httpMethod} ${url}`);
68
68
  if (body) {
69
- console.log('[HTTP] Request body:', JSON.stringify(body).substring(0, 200) + '...');
69
+ console.log(
70
+ '[HTTP] Request body:',
71
+ JSON.stringify(body).substring(0, 200) + '...'
72
+ );
70
73
  }
71
74
 
72
75
  try {
@@ -80,14 +83,19 @@ const request = async <TResponse, TBody>(
80
83
 
81
84
  statusCode = response.status;
82
85
  success = response.ok;
83
- console.log(`[HTTP] Response status: ${statusCode} ${response.ok ? '✓' : '✗'}`);
86
+ console.log(
87
+ `[HTTP] Response status: ${statusCode} ${response.ok ? '✓' : '✗'}`
88
+ );
84
89
 
85
90
  let responseJson = null;
86
91
 
87
92
  try {
88
93
  responseJson = await response.json();
89
94
  if (responseJson) {
90
- console.log('[HTTP] Response body:', JSON.stringify(responseJson).substring(0, 200) + '...');
95
+ console.log(
96
+ '[HTTP] Response body:',
97
+ JSON.stringify(responseJson).substring(0, 200) + '...'
98
+ );
91
99
  }
92
100
  } catch (error) {
93
101
  // Invalid JSON response
@@ -214,7 +222,7 @@ const post = <TResponse, TBody>(
214
222
 
215
223
  const put = <TResponse, TBody>(
216
224
  url: string,
217
- body?: any,
225
+ body?: TBody,
218
226
  simulatedResponse?: TResponse
219
227
  ) => {
220
228
  return request<TResponse, TBody>('PUT', url, body, simulatedResponse);
@@ -114,8 +114,9 @@ const fixMRZ = (rawText: string): string => {
114
114
  // Must contain filler characters
115
115
  if (!/<+/.test(line)) return false;
116
116
 
117
- // Prioritize lines starting with I< (document code)
118
- if (line.startsWith('I<')) return true;
117
+ // Lines starting with a valid MRZ document code (ICAO 9303):
118
+ // I (ID cards), A (residence permits, admin docs), C (crew), P (passport), V (visa)
119
+ if (/^[IACPV][<A-Z]/.test(line)) return true;
119
120
 
120
121
  // Or lines with numbers and fillers (second line of TD1: birth date, expiry, etc.)
121
122
  if (/\d{5,}/.test(line) && /<{3,}/.test(line)) return true;
@@ -1,4 +1,4 @@
1
- import { StatusBar } from 'react-native';
1
+ import { Platform, StatusBar } from 'react-native';
2
2
  import { useEffect } from 'react';
3
3
 
4
4
  /**
@@ -6,7 +6,9 @@ import { useEffect } from 'react';
6
6
  */
7
7
  export const configureStatusBarForWhiteBackground = (): void => {
8
8
  StatusBar.setBarStyle('dark-content', true);
9
- StatusBar.setBackgroundColor('#ffffff', true);
9
+ if (Platform.OS === 'android') {
10
+ StatusBar.setBackgroundColor('#ffffff', true);
11
+ }
10
12
  };
11
13
 
12
14
  /**
@@ -127,7 +127,7 @@ export class VideoSessionService {
127
127
  console.log('[VideoSessionService] Creating SSE connection to:', url);
128
128
 
129
129
  const es = new EventSource(url);
130
- let heartbeatInterval: NodeJS.Timeout | null = null;
130
+ let heartbeatInterval: ReturnType<typeof setInterval> | null = null;
131
131
 
132
132
  es.addEventListener('open', () => {
133
133
  console.log('[VideoSessionService] Queue SSE connected');
@@ -82,6 +82,8 @@ export enum AnalyticsEventName {
82
82
  IDENTITY_DOCUMENT_EID_SCAN_COMPLETED = 'identity_document_eid_scan_completed',
83
83
  LIVENESS_CHECK_STARTED = 'liveness_check_started',
84
84
  LIVENESS_CHECK_COMPLETED = 'liveness_check_completed',
85
+ VERBAL_CONSENT_VIDEO_STARTED = 'verbal_consent_video_started',
86
+ VERBAL_CONSENT_VIDEO_COMPLETED = 'verbal_consent_video_completed',
85
87
  VIDEO_CALL_STARTED = 'video_call_started',
86
88
  VIDEO_CALL_COMPLETED = 'video_call_completed',
87
89
 
@@ -14,6 +14,14 @@ export interface IdentificationInfo {
14
14
  authToken?: string;
15
15
  videoSessionId?: string;
16
16
  mediaUploadedDuringVideoCall?: boolean;
17
+ verbalConsentVideos?: VerbalConsentVideo[];
18
+ }
19
+
20
+ export interface VerbalConsentVideo {
21
+ title: string;
22
+ text: string;
23
+ videoPath: string;
24
+ recordedSeconds?: number;
17
25
  }
18
26
 
19
27
  export interface ScannedIdentityDocument {
@@ -42,6 +50,7 @@ export interface WorkflowStep {
42
50
  | 'IDENTITY_DOCUMENT_SCAN'
43
51
  | 'IDENTITY_DOCUMENT_EID_SCAN'
44
52
  | 'LIVENESS_CHECK'
53
+ | 'VERBAL_CONSENT'
45
54
  | 'AML_CHECK'
46
55
  | 'VIDEO_CALL';
47
56
  data?: {
@@ -61,6 +70,8 @@ export interface WorkflowStep {
61
70
  allowedCountries?: 'TUR'[] | null;
62
71
  allowedLivenessInstructionTypes?: LivenessInstructionType[] | null;
63
72
  voiceGuidanceActive?: boolean;
73
+ verbalConsentTitle?: string | null;
74
+ verbalConsentText?: string | null;
64
75
  };
65
76
  required: boolean;
66
77
  }