Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Passport MetaData screen #256

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions app/src/screens/MainScreen.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -203,6 +204,9 @@ const MainScreen: React.FC = () => {
else if (selectedTab === "valid") {
setSelectedTab("app");
}
else if (selectedTab === "userInfo") {
setSelectedTab("app");
}
}

useEffect(() => {
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand All @@ -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
Expand Down Expand Up @@ -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} />
Expand Down
89 changes: 89 additions & 0 deletions app/src/screens/UserInfo.tsx
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 16 additions & 2 deletions app/src/utils/nfcScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions common/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type PassportData = {
mrz: string;
dg1Hash?: number[];
dg2Hash?: number[];
dgPresents?: any[];
dsc: string;
eContent: number[];
signedAttr: number[];
Expand Down
Loading