add UserInfo screen

This commit is contained in:
turnoffthiscomputer
2024-12-06 16:27:28 +01:00
parent 073b051fec
commit 5fd38e9213
4 changed files with 129 additions and 7 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { NativeEventEmitter, NativeModules, Linking, Modal, Platform, Pressable, TouchableOpacity, ScrollView } from 'react-native';
import { YStack, XStack, Text, Button, Tabs, Sheet, Label, Fieldset, Input, Switch, H2, Image, useWindowDimensions, H4, H3, View, Separator } from 'tamagui'
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share, Eraser, ArrowRight, UserPlus, CalendarSearch, X, ShieldCheck } from '@tamagui/lucide-icons';
import { HelpCircle, IterationCw, VenetianMask, Cog, CheckCircle2, ChevronLeft, Share, Eraser, ArrowRight, UserPlus, CalendarSearch, X, ShieldCheck, Info } from '@tamagui/lucide-icons';
import Telegram from '../images/telegram.png'
import Github from '../images/github.png'
import Internet from "../images/internet.png"
@@ -39,6 +39,7 @@ import OPENPASSPORT_LOGO from '../images/openpassport.png'
import { countryCodes } from '../../../common/src/constants/constants';
import getCountryISO2 from "country-iso-3-to-2";
import { flag } from 'country-emoji';
import UserInfo from './UserInfo';
const emitter = (Platform.OS === 'android')
? new NativeEventEmitter(NativeModules.nativeModule)
@@ -203,6 +204,9 @@ const MainScreen: React.FC = () => {
else if (selectedTab === "valid") {
setSelectedTab("app");
}
else if (selectedTab === "userInfo") {
setSelectedTab("app");
}
}
useEffect(() => {
@@ -289,7 +293,7 @@ const MainScreen: React.FC = () => {
</Sheet>
<Sheet open={HelpIsOpen} onOpenChange={setHelpIsOpen} dismissOnSnapToBottom modal animation="medium" snapPoints={[76]}>
<Sheet open={HelpIsOpen} onOpenChange={setHelpIsOpen} dismissOnSnapToBottom modal animation="medium" snapPoints={[82]}>
<Sheet.Overlay />
<Sheet.Frame bg={bgWhite} borderTopLeftRadius="$9" borderTopRightRadius="$9" pt="$2" pb="$3">
<YStack p="$4" f={1} gap="$3">
@@ -330,9 +334,19 @@ const MainScreen: React.FC = () => {
<Dialog.Button onPress={() => handleContribute()} label="Contribute" />
</Dialog.Container>
<Separator mt="$5" borderColor={separatorColor} w="80%" alignSelf='center' />
<Fieldset mt="$4" gap="$4" horizontal alignSelf="center">
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="restart">
View passport infos
</Label>
<Button bg="white" jc="center" borderColor={borderColor} borderWidth={1.2} size="$3.5" ml="$2" onPress={() => {
setHelpIsOpen(false);
setSelectedTab("userInfo");
}}>
<Info color={textBlack} />
</Button>
</Fieldset>
<Fieldset horizontal mt="$3" alignSelf='center'>
<Fieldset horizontal mt="$2" alignSelf='center'>
<Label color={textBlack} width={225} justifyContent="flex-end" htmlFor="restart" >
Display other options
</Label>
@@ -344,7 +358,7 @@ const MainScreen: React.FC = () => {
{displayOtherOptions && (
<YStack gap="$2" mt="$3" ai="center">
<YStack gap="$2" mt="$2" ai="center">
<Fieldset gap="$4" horizontal>
<Label color={textBlack} width={200} justifyContent="flex-end" htmlFor="restart">
Rescan passport
@@ -801,6 +815,9 @@ const MainScreen: React.FC = () => {
<Tabs.Content value="wrong" f={1}>
<WrongProofScreen />
</Tabs.Content>
<Tabs.Content value="userInfo" f={1}>
<UserInfo />
</Tabs.Content>
</Tabs>
<XStack mt="$2.5" justifyContent='center' alignItems='center' gap="$1.5">
<ShieldCheck color={textBlack} size={12} />

View File

@@ -0,0 +1,89 @@
import React from 'react';
import { YStack, Text, XStack, Separator } from 'tamagui';
import useUserStore from '../stores/userStore';
import { textBlack, separatorColor } from '../utils/colors';
import { findSubarrayIndex } from '../../../common/src/utils/utils';
import { PassportData } from '../../../common/src/utils/types';
import { hash } from '../../../common/src/utils/utils';
import { parseCertificate } from '../../../common/src/utils/certificates/handleCertificate';
const UserInfo: React.FC = () => {
const { passportData } = useUserStore();
const { eContent, signedAttr, dg1Hash, dgPresents } = passportData as PassportData;
const dg1HashOffset = dg1Hash ? findSubarrayIndex(eContent, dg1Hash.map(byte => byte > 127 ? byte - 256 : byte)) : undefined;
const InfoRow = ({ label, value }: { label: string; value: string | number }) => (
<XStack py="$2" justifyContent="space-between">
<Text color={textBlack} fontSize="$5">{label}</Text>
<Text color={textBlack} fontSize="$5">{value}</Text>
</XStack>
);
function findHashSizeOfEContent(eContent: number[], signedAttr: number[]) {
const allHashes = ['sha512', 'sha384', 'sha256', 'sha1'];
for (const hashFunction of allHashes) {
const hashValue = hash(hashFunction, eContent);
const hashOffset = findSubarrayIndex(signedAttr, hashValue);
if (hashOffset !== -1) {
return { hashFunction, offset: hashOffset };
}
}
}
const { hashFunction: eContentHashFunction, offset: eContentHashOffset } = findHashSizeOfEContent(eContent, signedAttr) || { hashFunction: '', offset: 0 };
const dscHashFunction = parseCertificate(passportData?.dsc || '').hashFunction;
return (
<YStack f={1} p="$0" gap="$2" jc="flex-start" mt="$10">
<Text fontSize="$8" color={textBlack} mb="$4">Passport Data Info</Text>
<Separator borderColor={separatorColor} />
<InfoRow
label="Data Groups"
value={passportData?.dgPresents?.toString().split(',').map(item => item.replace('DG', '')).join(',') || 'None'}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="DG1 Hash Size"
value={`${passportData?.dg1Hash?.length || 0} ${passportData?.dg1Hash?.length === 32 ? '(sha256)' : passportData?.dg1Hash?.length === 20 ? '(sha1)' : passportData?.dg1Hash?.length === 48 ? '(sha384)' : passportData?.dg1Hash?.length === 64 ? '(sha512)' : ''}`}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="DG1 Hash Offset"
value={dg1HashOffset || 0}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Size"
value={passportData?.eContent?.length || 0}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Function"
value={eContentHashFunction}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Offset"
value={eContentHashOffset}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="Signed Attributes Size"
value={passportData?.signedAttr?.length || 0}
/>
<Separator borderColor={separatorColor} />
<InfoRow
label="Signed Attributes Hash Function"
value={dscHashFunction}
/>
</YStack>
);
};
export default UserInfo;

View File

@@ -148,6 +148,8 @@ const handleResponseIOS = async (
const parsed = JSON.parse(response);
const dgHashesObj = JSON.parse(parsed?.dataGroupHashes)
const dg1HashString = dgHashesObj?.DG1?.sodHash
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'))
const dg2HashString = dgHashesObj?.DG2?.sodHash
const dg2Hash = Array.from(Buffer.from(dg2HashString, 'hex'))
@@ -190,7 +192,9 @@ const handleResponseIOS = async (
const passportData = {
mrz,
dsc: pem,
dg2Hash,
dg2Hash: dg2Hash,
dg1Hash: dg1Hash,
dgPresents: parsed?.dataGroupsPresent,
eContent: concatenatedDataHashesArraySigned,
signedAttr: signedEContentArray,
encryptedDigest: encryptedDigestArray,
@@ -235,12 +239,22 @@ const handleResponseAndroid = async (
} = response;
const dgHashesObj = JSON.parse(dataGroupHashes);
const dg2Hash = dgHashesObj["2"]; // This will give you the DG2 hash
const dg1HashString = dgHashesObj["1"];
const dg1Hash = Array.from(Buffer.from(dg1HashString, 'hex'));
const dg2Hash = dgHashesObj["2"];
const pem = "-----BEGIN CERTIFICATE-----" + documentSigningCertificate + "-----END CERTIFICATE-----"
const dgPresents = Object.keys(dgHashesObj)
.map(key => parseInt(key))
.filter(num => !isNaN(num))
.sort((a, b) => a - b);
const passportData: PassportData = {
mrz: mrz.replace(/\n/g, ''),
dsc: pem,
dg2Hash,
dg1Hash,
dgPresents,
eContent: JSON.parse(encapContent),
signedAttr: JSON.parse(eContent),
encryptedDigest: JSON.parse(encryptedDigest),