Skip to content

Commit

Permalink
add passportMetaData feature
Browse files Browse the repository at this point in the history
  • Loading branch information
remicolin committed Dec 28, 2024
1 parent 22bc01e commit 9a0330a
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 60 deletions.
68 changes: 14 additions & 54 deletions app/src/screens/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ 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';
import { parsePassportData } from '../utils/parsePassportData';

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 passportMetaData = passportData ? parsePassportData(passportData) : null;

const InfoRow = ({ label, value }: { label: string; value: string | number }) => (
<XStack py="$2" justifyContent="space-between">
Expand All @@ -19,69 +15,33 @@ const UserInfo: React.FC = () => {
</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'}
/>
<InfoRow label="Data Groups" value={passportMetaData?.dataGroups || '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)' : ''}`}
/>
<InfoRow label="DG1 Hash Function" value={passportMetaData?.dg1HashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="DG1 Hash Offset"
value={dg1HashOffset || 0}
/>

<InfoRow label="DG1 Hash Offset" value={passportMetaData?.dg1HashOffset || 'None'} />
<Separator borderColor={separatorColor} />

<InfoRow
label="eContent Size"
value={passportData?.eContent?.length || 0}
/>
<InfoRow label="eContent Size" value={passportMetaData?.eContentSize || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Function"
value={eContentHashFunction}
/>

<InfoRow label="eContent Hash Function" value={passportMetaData?.eContentHashFunction || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="eContent Hash Offset"
value={eContentHashOffset}
/>

<InfoRow label="eContent Hash Offset" value={passportMetaData?.eContentHashOffset || 'None'} />
<Separator borderColor={separatorColor} />

<InfoRow
label="Signed Attributes Size"
value={passportData?.signedAttr?.length || 0}
/>
<InfoRow label="Signed Attributes Size" value={passportMetaData?.signedAttrSize || 'None'} />
<Separator borderColor={separatorColor} />
<InfoRow
label="Signed Attributes Hash Function"
value={dscHashFunction}
/>

<InfoRow label="Signed Attributes Hash Function" value={passportMetaData?.signedAttrHashFunction || 'None'} />
</YStack>
);
};
Expand Down
83 changes: 83 additions & 0 deletions app/src/utils/parsePassportData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { PassportData } from '../../../common/src/utils/types';
import { findSubarrayIndex, formatMrz, hash } from '../../../common/src/utils/utils';
import { parseCertificate } from '../../../common/src/utils/certificates/handleCertificate';

export interface PassportMetadata {
dataGroups: string;
dg1HashFunction: string;
dg1HashOffset: number;
eContentSize: number;
eContentHashFunction: string;
eContentHashOffset: number;
signedAttrSize: number;
signedAttrHashFunction: string;
countryCode?: string;
}

export 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 };
}
}
return { hashFunction: 'unknown', offset: -1 };
}

export function findDG1HashInEContent(mrz: string, eContent: number[]): { hash: number[], hashFunction: string } | null {
const hashFunctions = ['sha512', 'sha384', 'sha256', 'sha1'];
const formattedMrz = formatMrz(mrz);

for (const hashFunction of hashFunctions) {
const hashValue = hash(hashFunction, formattedMrz);
const hashOffset = findSubarrayIndex(eContent, hashValue);

if (hashOffset !== -1) {
return { hash: hashValue, hashFunction };
}
}
return null;
}

export function getCountryCodeFromMrz(mrz: string): string {
return mrz.substring(2, 5);
}

export function parsePassportData(passportData: PassportData): PassportMetadata {
// Extract DG1 hash info
const dg1HashInfo = passportData.mrz ?
findDG1HashInEContent(passportData.mrz, passportData.eContent) :
null;

// Use extracted DG1 hash if found, otherwise use provided dg1Hash
const dg1Hash = dg1HashInfo?.hash || passportData.dg1Hash;
const dg1HashFunction = dg1HashInfo?.hashFunction || 'unknown';

const dg1HashOffset = dg1Hash
? findSubarrayIndex(
passportData.eContent,
dg1Hash.map(byte => byte > 127 ? byte - 256 : byte)
)
: 0;

const { hashFunction: eContentHashFunction, offset: eContentHashOffset } =
findHashSizeOfEContent(passportData.eContent, passportData.signedAttr);

const dscHashFunction = passportData.dsc ?
parseCertificate(passportData.dsc).hashFunction :
'unknown';

return {
dataGroups: passportData.dgPresents?.toString().split(',').map(item => item.replace('DG', '')).join(',') || 'None',
dg1HashFunction,
dg1HashOffset,
eContentSize: passportData.eContent?.length || 0,
eContentHashFunction,
eContentHashOffset,
signedAttrSize: passportData.signedAttr?.length || 0,
signedAttrHashFunction: dscHashFunction,
countryCode: passportData.mrz ? getCountryCodeFromMrz(passportData.mrz) : undefined
};
}
25 changes: 20 additions & 5 deletions common/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,23 @@ export function generateMerkleProof(imt: LeanIMT, _index: number, maxDepth: numb
return { merkleProofSiblings, merkleProofIndices, depthForThisOne };
}

export function findSubarrayIndex(arr: any[], subarray: any[]): number {
return arr.findIndex((_, index) => subarray.every((element, i) => element === arr[index + i]));
export function findSubarrayIndex(arr: number[], subArr: number[]): number {
if (!arr || !Array.isArray(arr) || !subArr || !Array.isArray(subArr)) {
console.warn('Invalid input to findSubarrayIndex:', { arr, subArr });
return -1;
}

if (subArr.length === 0) {
return -1;
}

if (subArr.length > arr.length) {
return -1;
}

return arr.findIndex((_, i) =>
subArr.every((val, j) => arr[i + j] === val)
);
}

export function extractRSFromSignature(signatureBytes: number[]): { r: string; s: string } {
Expand Down Expand Up @@ -479,9 +494,9 @@ function checkStringLength(str: string) {
function stringToBigInt(str: string): bigint {
return BigInt(
'1' +
Array.from(str)
.map((char) => char.charCodeAt(0).toString().padStart(3, '0'))
.join('')
Array.from(str)
.map((char) => char.charCodeAt(0).toString().padStart(3, '0'))
.join('')
);
}

Expand Down
3 changes: 2 additions & 1 deletion registry/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
.env.local
outputs/
outputs/
src/passport_data/passport_data/
55 changes: 55 additions & 0 deletions registry/src/passport_data/parse_passport_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from 'fs';
import path from 'path';
import { PassportData } from '../../../common/src/utils/types';
import { parsePassportData } from '../../../app/src/utils/parsePassportData';

function parsePassportFile(filePath: string) {
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const passportData = JSON.parse(fileContent) as PassportData;

const info = parsePassportData(passportData);

// Print the results
console.log(`\nProcessing file: ${path.basename(filePath)}`);
console.log('----------------------------------------');
if (info.countryCode) console.log(`Country Code: ${info.countryCode}`);
console.log(`Data Groups: ${info.dataGroups}`);
console.log(`DG1 Hash Function: ${info.dg1HashFunction}`);
console.log(`DG1 Hash Offset: ${info.dg1HashOffset}`);
console.log(`eContent Size: ${info.eContentSize}`);
console.log(`eContent Hash Function: ${info.eContentHashFunction}`);
console.log(`eContent Hash Offset: ${info.eContentHashOffset}`);
console.log(`Signed Attributes Size: ${info.signedAttrSize}`);
console.log(`Signed Attributes Hash Function: ${info.signedAttrHashFunction}`);

} catch (error) {
console.error(`Error processing file ${filePath}:`, error);
}
}

function main() {
const directoryPath = path.join(__dirname, 'passport_data');
console.log(directoryPath);

try {
const files = fs.readdirSync(directoryPath);
const jsonFiles = files.filter(file => file.endsWith('.json'));

if (jsonFiles.length === 0) {
console.log('No JSON files found in the passport_data directory');
return;
}

jsonFiles.forEach(file => {
const filePath = path.join(directoryPath, file);
parsePassportFile(filePath);
});

} catch (error) {
console.error('Error reading directory:', error);
}
}

// Execute the script
main();

0 comments on commit 9a0330a

Please sign in to comment.