implement multi-hash circuits for passport verification

This commit is contained in:
turnoffthiscomputer
2025-01-03 14:34:46 +01:00
parent 2a6803be0a
commit 478b3d604e
23 changed files with 397 additions and 325 deletions

View File

@@ -23,40 +23,41 @@ export const RPC_URL = 'https://opt-mainnet.g.alchemy.com/v2/Mjj_SdklUaCdR6EPfVK
export const DEVELOPMENT_MODE = true;
export const DEFAULT_MAJORITY = '18';
// export const MAX_PADDED_ECONTENT_LEN: Partial<
// Record<keyof typeof SignatureAlgorithmIndex, number>
// > = {
// rsa_sha256_65537_2048: 448,
// rsa_sha1_65537_2048: 320,
// rsapss_sha256_65537_2048: 384,
// rsapss_sha256_3_3072: 384,
// rsapss_sha256_65537_3072: 384,
// rsapss_sha256_65537_4096: 384,
// rsapss_sha256_3_4096: 384,
// rsapss_sha384_65537_3072: 384,
// ecdsa_sha1_secp256r1_256: 320,
// ecdsa_sha256_secp256r1_256: 384,
// ecdsa_sha384_secp384r1_384: 512,
// rsa_sha256_65537_3072: 384,
// rsa_sha256_3_2048: 384,
// };
const hashAlgos = ['sha1', 'sha256', 'sha384', 'sha512'];
export const MAX_PADDED_ECONTENT_LEN: Partial<
Record<keyof typeof SignatureAlgorithmIndex, number>
Record<typeof hashAlgos[number], number>
> = {
rsa_sha256_65537_2048: 384,
rsa_sha1_65537_2048: 320,
rsapss_sha256_65537_2048: 384,
rsapss_sha256_3_3072: 384,
rsapss_sha256_65537_3072: 384,
rsapss_sha256_65537_4096: 384,
rsapss_sha256_3_4096: 384,
rsapss_sha384_65537_3072: 384,
ecdsa_sha1_secp256r1_256: 320,
ecdsa_sha256_secp256r1_256: 384,
ecdsa_sha384_secp384r1_384: 512,
rsa_sha256_65537_3072: 384,
rsa_sha256_3_2048: 384,
sha1: 320,
sha256: 448,
sha384: 576,
sha512: 704,
};
export const MAX_PADDED_SIGNED_ATTR_LEN: Partial<
Record<keyof typeof SignatureAlgorithmIndex, number>
Record<typeof hashAlgos[number], number>
> = {
rsa_sha256_65537_2048: 192,
rsa_sha1_65537_2048: 192,
rsapss_sha256_65537_2048: 192,
rsapss_sha256_65537_3072: 192,
rsapss_sha256_65537_4096: 192,
rsapss_sha256_3_3072: 192,
rsapss_sha256_3_4096: 192,
rsapss_sha384_65537_3072: 256,
ecdsa_sha1_secp256r1_256: 192,
ecdsa_sha256_secp256r1_256: 192,
ecdsa_sha384_secp384r1_384: 192,
rsa_sha256_65537_3072: 192,
rsa_sha256_3_2048: 192,
sha1: 128,
sha256: 128,
sha384: 128,
sha512: 192,
};
export const MAX_CERT_BYTES: Partial<Record<keyof typeof SignatureAlgorithmIndex, number>> = {
@@ -119,10 +120,10 @@ export const circuitToSelectorMode = {
};
export const MAX_DATAHASHES_LEN = 320; // max formatted and concatenated datagroup hashes length in bytes
export const n_dsc = 64;
export const n_dsc = 120;
export const n_dsc_3072 = 96;
export const n_dsc_4096 = 120;
export const k_dsc = 32;
export const k_dsc = 35;
export const k_dsc_3072 = 32; //48;
export const k_dsc_4096 = 35;
export const n_csca = 120;

View File

@@ -1,12 +1,7 @@
import * as asn1js from "asn1js";
import { Certificate, RSAPublicKey, RSASSAPSSParams } from "pkijs";
import { extractHashFunction, getFriendlyName } from "./oids";
import { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, PublicKeyDetailsRSAPSS } from "./dataStructure";
import { getECDSACurveBits, identifyCurve, StandardCurve } from "./curves";
import { getIssuerCountryCode, getSubjectKeyIdentifier } from "./utils";
import fs from 'fs';
import { execSync } from 'child_process';
import { parseCertificateSimple } from "./parseCertificateSimple";
import { CertificateData } from "./dataStructure";
export function parseCertificate(pem: string, fileName: string): any {
let certificateData: CertificateData = {
id: '',
@@ -46,141 +41,3 @@ export function parseCertificate(pem: string, fileName: string): any {
}
}
function getParamsRSA(cert: Certificate): PublicKeyDetailsRSA {
const publicKeyValue = cert.subjectPublicKeyInfo.parsedKey as RSAPublicKey;
const modulusBytes = publicKeyValue.modulus.valueBlock.valueHexView;
const modulusHex = Buffer.from(modulusBytes).toString('hex');
const exponentBigInt = publicKeyValue.publicExponent.toBigInt();
const exponentDecimal = exponentBigInt.toString();
const actualBits = modulusBytes.length * 8;
return {
modulus: modulusHex,
exponent: exponentDecimal,
bits: actualBits.toString()
};
}
function getParamsRSAPSS(cert: Certificate): PublicKeyDetailsRSAPSS {
const { modulus, exponent, bits } = getParamsRSA(cert);
const sigAlgParams = cert.signatureAlgorithm.algorithmParams;
const pssParams = new RSASSAPSSParams({ schema: sigAlgParams });
const hashAlgorithm = getFriendlyName(pssParams.hashAlgorithm.algorithmId);
const mgf = getFriendlyName(pssParams.maskGenAlgorithm.algorithmId);
return {
modulus: modulus,
exponent: exponent,
bits: bits,
hashAlgorithm: hashAlgorithm,
mgf: mgf,
saltLength: pssParams.saltLength.toString()
};
}
function getParamsRSAPSS2(cert: Certificate): PublicKeyDetailsRSAPSS {
// Get the subjectPublicKey BitString
const spki = cert.subjectPublicKeyInfo;
const spkiValueHex = spki.subjectPublicKey.valueBlock.valueHexView;
// Parse the public key ASN.1 structure
const asn1PublicKey = asn1js.fromBER(spkiValueHex);
if (asn1PublicKey.offset === -1) {
throw new Error("Error parsing public key ASN.1 structure");
}
// The public key is an RSAPublicKey structure
const rsaPublicKey = new RSAPublicKey({ schema: asn1PublicKey.result });
const modulusBytes = rsaPublicKey.modulus.valueBlock.valueHexView;
const modulusHex = Buffer.from(modulusBytes).toString('hex');
const exponentBigInt = rsaPublicKey.publicExponent.toBigInt();
const exponentDecimal = exponentBigInt.toString();
const actualBits = modulusBytes.length * 8;
const sigAlgParams = cert.signatureAlgorithm.algorithmParams;
const pssParams = new RSASSAPSSParams({ schema: sigAlgParams });
const hashAlgorithm = getFriendlyName(pssParams.hashAlgorithm.algorithmId);
const mgf = getFriendlyName(pssParams.maskGenAlgorithm.algorithmId);
return {
modulus: modulusHex,
exponent: exponentDecimal,
bits: actualBits.toString(),
hashAlgorithm: hashAlgorithm,
mgf: mgf,
saltLength: pssParams.saltLength.toString()
};
}
export function getParamsECDSA(cert: Certificate): PublicKeyDetailsECDSA {
try {
const algorithmParams = cert.subjectPublicKeyInfo.algorithm.algorithmParams;
if (!algorithmParams) {
console.log('No algorithm params found');
return { curve: 'Unknown', params: {} as StandardCurve, bits: 'Unknown' };
}
const params = asn1js.fromBER(algorithmParams.valueBeforeDecodeView).result;
const valueBlock: any = params.valueBlock;
if (valueBlock.value && valueBlock.value.length >= 5) {
const curveParams: StandardCurve = {} as StandardCurve;
// Field ID (index 1)
const fieldId = valueBlock.value[1];
if (fieldId && fieldId.valueBlock && fieldId.valueBlock.value) {
const fieldType = fieldId.valueBlock.value[0];
const prime = fieldId.valueBlock.value[1];
//curveParams.fieldType = fieldType.valueBlock.toString();
curveParams.p = Buffer.from(prime.valueBlock.valueHexView).toString('hex');
}
// Curve Coefficients (index 2)
const curveCoefficients = valueBlock.value[2];
if (curveCoefficients && curveCoefficients.valueBlock && curveCoefficients.valueBlock.value) {
const a = curveCoefficients.valueBlock.value[0];
const b = curveCoefficients.valueBlock.value[1];
curveParams.a = Buffer.from(a.valueBlock.valueHexView).toString('hex');
curveParams.b = Buffer.from(b.valueBlock.valueHexView).toString('hex');
}
// Base Point G (index 3)
const basePoint = valueBlock.value[3];
if (basePoint && basePoint.valueBlock) {
curveParams.G = Buffer.from(basePoint.valueBlock.valueHexView).toString('hex');
}
// Order n (index 4)
const order = valueBlock.value[4];
if (order && order.valueBlock) {
curveParams.n = Buffer.from(order.valueBlock.valueHexView).toString('hex');
}
if (valueBlock.value.length >= 6) {
// Cofactor h (index 5)
const cofactor = valueBlock.value[5];
if (cofactor && cofactor.valueBlock) {
curveParams.h = Buffer.from(cofactor.valueBlock.valueHexView).toString('hex');
}
}
else {
curveParams.h = '01';
}
const identifiedCurve = identifyCurve(curveParams);
return { curve: identifiedCurve, params: curveParams, bits: getECDSACurveBits(identifiedCurve) };
} else {
if (valueBlock.value) {
console.log(valueBlock.value);
}
else {
console.log('No value block found');
}
}
} catch (error) {
console.error('Error parsing EC parameters:', error);
return { curve: 'Error', params: {} as StandardCurve, bits: 'Unknown' };
}
}

View File

@@ -0,0 +1,51 @@
import { PassportData } from "./types";
import { parsePassportData } from "./parsePassportData";
import { parseCertificateSimple } from "./certificate_parsing/parseCertificateSimple";
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSA, PublicKeyDetailsRSAPSS } from "./certificate_parsing/dataStructure";
export function getCircuitNameFromPassportData(passportData: PassportData) {
const passportMetadata = parsePassportData(passportData);
const parsedDsc = parseCertificateSimple(passportData.dsc);
const dgHashAlgo = passportMetadata.dg1HashFunction;
const eContentHashAlgo = passportMetadata.eContentHashFunction;
const signedAttrHashAlgo = passportMetadata.signedAttrHashFunction;
const sigAlg = passportMetadata.signatureAlgorithm;
if (parsedDsc.signatureAlgorithm === 'ecdsa') {
const curve = (parsedDsc.publicKeyDetails as PublicKeyDetailsECDSA).curve;
return `prove_${dgHashAlgo}_${eContentHashAlgo}_${signedAttrHashAlgo}_${sigAlg}_${curve}`;
}
else if (parsedDsc.signatureAlgorithm === 'rsa') {
const exponent = (parsedDsc.publicKeyDetails as PublicKeyDetailsRSA).exponent;
const bits = (parsedDsc.publicKeyDetails as PublicKeyDetailsRSA).bits;
if (parseInt(bits) <= 4096) {
return `prove_${dgHashAlgo}_${eContentHashAlgo}_${signedAttrHashAlgo}_${sigAlg}_${exponent}_${4096}`;
}
else {
throw new Error(`Unsupported key length: ${bits}`);
}
}
else if (parsedDsc.signatureAlgorithm === 'rsapss') {
const exponent = (parsedDsc.publicKeyDetails as PublicKeyDetailsRSA).exponent;
const saltLength = (parsedDsc.publicKeyDetails as PublicKeyDetailsRSAPSS).saltLength;
const bits = (parsedDsc.publicKeyDetails as PublicKeyDetailsRSAPSS).bits;
if (parseInt(bits) <= 4096) {
return `prove_${dgHashAlgo}_${eContentHashAlgo}_${signedAttrHashAlgo}_${sigAlg}_${exponent}_${saltLength}_${4096}`;
}
else {
throw new Error(`Unsupported key length: ${bits}`);
}
}
else {
throw new Error('Unsupported signature algorithm');
}
}
export function getCurveOrExponent(dsc: any) {
const parsedDsc = parseCertificateSimple(dsc);
if (parsedDsc.signatureAlgorithm === 'ecdsa') {
return (parsedDsc.publicKeyDetails as PublicKeyDetailsECDSA).curve;
}
return (parsedDsc.publicKeyDetails as PublicKeyDetailsRSA).exponent;
}

View File

@@ -1,5 +1,5 @@
import { PassportData } from './types';
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, getHashLen } from './utils';
import { hash, generateSignedAttr, formatAndConcatenateDataHashes, formatMrz, getHashLen } from './utils';
import * as forge from 'node-forge';
import * as asn1 from 'asn1js';
import elliptic from 'elliptic';
@@ -35,13 +35,37 @@ import {
mock_dsc_key_rsapss_65537_4096,
mock_dsc_sha256_rsapss_65537_4096,
} from '../constants/mockCertificates';
import { sampleDataHashes_small, sampleDataHashes_large } from '../constants/sampleDataHashes';
import { countryCodes } from '../constants/constants';
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
import { SignatureAlgorithm } from './types';
import { PublicKeyDetailsECDSA } from './certificate_parsing/dataStructure';
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSAPSS } from './certificate_parsing/dataStructure';
import { getCurveForElliptic } from './certificate_parsing/curves';
function generateRandomBytes(length: number): number[] {
// Generate numbers between -128 and 127 to match the existing signed byte format
return Array.from({ length }, () => Math.floor(Math.random() * 256) - 128);
}
function generateDataGroupHashes(mrzHash: number[], hashLen: number): [number, number[]][] {
// Generate hashes for DGs 2-15 (excluding some DGs that aren't typically used)
const dataGroups: [number, number[]][] = [
[1, mrzHash], // DG1 must be the MRZ hash
[2, generateRandomBytes(hashLen)],
[3, generateRandomBytes(hashLen)],
[4, generateRandomBytes(hashLen)],
[5, generateRandomBytes(hashLen)],
[7, generateRandomBytes(hashLen)],
[11, generateRandomBytes(hashLen)],
[12, generateRandomBytes(hashLen)],
[14, generateRandomBytes(hashLen)]
];
return dataGroups;
}
export function genMockPassportData(
dgHashAlgo: string,
eContentHashAlgo: string,
signatureType: SignatureAlgorithm,
nationality: keyof typeof countryCodes,
birthDate: string,
@@ -91,103 +115,91 @@ export function genMockPassportData(
let privateKeyPem: string;
let dsc: string;
let sampleDataHashes: [number, number[]][];
switch (signatureType) {
case 'rsa_sha1_65537_2048':
sampleDataHashes = sampleDataHashes_small;
privateKeyPem = mock_dsc_key_sha1_rsa_4096;
dsc = mock_dsc_sha1_rsa_4096;
break;
case 'rsa_sha256_65537_2048':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsa_4096;
dsc = mock_dsc_sha256_rsa_4096;
break;
case 'rsapss_sha256_65537_2048':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsapss_4096;
dsc = mock_dsc_sha256_rsapss_4096;
break;
case 'rsapss_sha256_3_4096':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsapss_3_4096;
dsc = mock_dsc_sha256_rsapss_3_4096;
break;
case 'rsapss_sha256_3_3072':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsapss_3_3072;
dsc = mock_dsc_sha256_rsapss_3_3072;
break;
case 'rsapss_sha384_65537_3072':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha384_rsapss_65537_3072;
dsc = mock_dsc_sha384_rsapss_65537_3072;
break;
case 'ecdsa_sha256_secp256r1_256':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_ecdsa;
dsc = mock_dsc_sha256_ecdsa;
break;
case 'ecdsa_sha1_secp256r1_256':
sampleDataHashes = sampleDataHashes_small;
privateKeyPem = mock_dsc_key_sha1_ecdsa;
dsc = mock_dsc_sha1_ecdsa;
break;
case 'ecdsa_sha384_secp384r1_384':
sampleDataHashes = sampleDataHashes_small;
privateKeyPem = mock_dsc_key_sha384_ecdsa;
dsc = mock_dsc_sha384_ecdsa;
break;
case 'ecdsa_sha256_brainpoolP256r1_256':
sampleDataHashes = sampleDataHashes_small;
privateKeyPem = mock_dsc_key_sha256_brainpoolP256r1;
dsc = mock_dsc_sha256_brainpoolP256r1;
break;
case 'rsa_sha256_3_2048':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsa_3_2048;
dsc = mock_dsc_sha256_rsa_3_2048;
break;
case 'rsa_sha256_65537_3072':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsa_65537_3072;
dsc = mock_dsc_sha256_rsa_65537_3072;
break;
case 'rsapss_sha256_65537_3072':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_sha256_rsapss_65537_3072;
dsc = mock_dsc_sha256_rsapss_65537_3072;
break;
case 'rsapss_sha256_65537_4096':
sampleDataHashes = sampleDataHashes_large;
privateKeyPem = mock_dsc_key_rsapss_65537_4096;
dsc = mock_dsc_sha256_rsapss_65537_4096;
break;
}
const parsedDsc = parseCertificateSimple(dsc);
const hashAlgorithm = parsedDsc.hashAlgorithm;
// Generate MRZ hash first
const mrzHash = hash(dgHashAlgo, formatMrz(mrz));
const mrzHash = hash(hashAlgorithm, formatMrz(mrz));
const hashLen = getHashLen(hashAlgorithm);
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
30
// Generate random hashes for other DGs, passing mrzHash for DG1
const dataGroupHashes = generateDataGroupHashes(mrzHash, getHashLen(dgHashAlgo));
const eContent = formatAndConcatenateDataHashes(
dataGroupHashes,
63
);
const eContent = assembleEContent(hash(hashAlgorithm, concatenatedDataHashes));
const signedAttr = generateSignedAttr(hash(eContentHashAlgo, eContent));
const signature = sign(privateKeyPem, dsc, eContent);
const signature = sign(privateKeyPem, dsc, signedAttr);
const signatureBytes = Array.from(signature, (byte) => (byte < 128 ? byte : byte - 256));
return {
dsc: dsc,
mrz: mrz,
dg2Hash: sampleDataHashes[0][1],
eContent: concatenatedDataHashes,
signedAttr: eContent,
dg2Hash: dataGroupHashes.find(([dgNum]) => dgNum === 2)?.[1] || [],
eContent: eContent,
signedAttr: signedAttr,
encryptedDigest: signatureBytes,
photoBase64: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIy3...',
mockUser: true,
@@ -205,7 +217,7 @@ function sign(privateKeyPem: string, dsc: string, eContent: number[]): number[]
const pss = forge.pss.create({
md: forge.md.sha256.create(),
mgf: forge.mgf.mgf1.create(forge.md.sha256.create()),
saltLength: 32,
saltLength: parseInt((publicKeyDetails as PublicKeyDetailsRSAPSS).saltLength),
});
const signatureBytes = privateKey.sign(md, pss);
return Array.from(signatureBytes, (c: string) => c.charCodeAt(0));
@@ -228,12 +240,12 @@ function sign(privateKeyPem: string, dsc: string, eContent: number[]): number[]
let md = forge.md[hashAlgorithm].create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
console.log('message to sign', md.digest().toHex());
// console.log('message to sign', md.digest().toHex());
const signature = keyPair.sign(md.digest().toHex(), 'hex');
console.log(Buffer.from(signature.toDER(), 'hex').toString('hex'));
// console.log(Buffer.from(signature.toDER(), 'hex').toString('hex'));
const signatureBytes = Array.from(Buffer.from(signature.toDER(), 'hex'));
console.log('sig', JSON.stringify(signatureBytes));
// console.log('sig', JSON.stringify(signatureBytes));
return signatureBytes;
} else {

View File

@@ -32,7 +32,7 @@ import { packBytes } from '../utils/utils';
import { SMT } from '@openpassport/zk-kit-smt';
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSA } from './certificate_parsing/dataStructure';
import { cp } from 'fs';
import { parsePassportData } from './parsePassportData';
export function generateCircuitInputsDisclose(
secret: string,
@@ -183,6 +183,7 @@ export function generateCircuitInputsProve(
user_identifier_type: 'uuid' | 'hex' | 'ascii' = DEFAULT_USER_ID_TYPE
) {
const { mrz, eContent, signedAttr, encryptedDigest, dsc, dg2Hash } = passportData;
const passportMetadata = parsePassportData(passportData);
const { signatureAlgorithm, hashAlgorithm, publicKeyDetails } = parseCertificateSimple(passportData.dsc);
let pubKey: any;
let signature: any;
@@ -215,19 +216,11 @@ export function generateCircuitInputsProve(
console.log('signatureAlgorithmFullName', signatureAlgorithmFullName);
const formattedMrz = formatMrz(mrz);
const dg1Hash = hash(hashAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(eContent, dg1Hash);
console.log('\x1b[90m%s\x1b[0m', 'dg1HashOffset', dg1HashOffset);
assert(dg1HashOffset !== -1, `DG1 hash ${dg1Hash} not found in eContent`);
const eContentHash = hash(hashAlgorithm, eContent);
const eContentHashOffset = findSubarrayIndex(signedAttr, eContentHash);
console.log('\x1b[90m%s\x1b[0m', 'eContentHashOffset', eContentHashOffset);
assert(eContentHashOffset !== -1, `eContent hash ${eContentHash} not found in signedAttr`);
if (eContent.length > MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]) {
console.error(
`Data hashes too long (${eContent.length} bytes). Max length is ${MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]} bytes.`
`eContent too long (${eContent.length} bytes). Max length is ${MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]} bytes.`
);
throw new Error(
`This length of datagroups (${eContent.length} bytes) is currently unsupported. Please contact us so we add support!`
@@ -237,12 +230,12 @@ export function generateCircuitInputsProve(
console.log('signatureAlgorithmFullName', signatureAlgorithmFullName);
const [eContentPadded, eContentLen] = shaPad(
new Uint8Array(eContent),
MAX_PADDED_ECONTENT_LEN[signatureAlgorithmFullName]
MAX_PADDED_ECONTENT_LEN[passportMetadata.dg1HashFunction]
);
const [signedAttrPadded, signedAttrPaddedLen] = shaPad(
new Uint8Array(signedAttr),
MAX_PADDED_SIGNED_ATTR_LEN[signatureAlgorithmFullName]
MAX_PADDED_SIGNED_ATTR_LEN[passportMetadata.eContentHashFunction]
);
const formattedMajority = majority.length === 1 ? `0${majority}` : majority;
@@ -261,13 +254,13 @@ export function generateCircuitInputsProve(
return {
selector_mode: formatInput(selector_mode),
dg1: formatInput(formattedMrz),
dg1_hash_offset: formatInput(dg1HashOffset),
dg1_hash_offset: formatInput(passportMetadata.dg1HashOffset),
dg2_hash: formatInput(formatDg2Hash(dg2Hash)),
eContent: Array.from(eContentPadded).map((x) => x.toString()),
eContent_padded_length: formatInput(eContentLen),
signed_attr: Array.from(signedAttrPadded).map((x) => x.toString()),
signed_attr_padded_length: formatInput(signedAttrPaddedLen),
signed_attr_econtent_hash_offset: formatInput(eContentHashOffset),
signed_attr_econtent_hash_offset: formatInput(passportMetadata.eContentHashOffset),
signature: signature,
pubKey: pubKey,
current_date: formatInput(getCurrentDateYYMMDD()),

View File

@@ -0,0 +1,161 @@
import { PassportData } from '../../../common/src/utils/types';
import { findSubarrayIndex, formatMrz, hash } from './utils';
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
import { CertificateData, PublicKeyDetailsECDSA, PublicKeyDetailsRSA, PublicKeyDetailsRSAPSS } from './certificate_parsing/dataStructure';
import { getCSCAFromSKI } from './csca';
export interface PassportMetadata {
dataGroups: string;
dg1HashFunction: string;
dg1HashOffset: number;
eContentSize: number;
eContentHashFunction: string;
eContentHashOffset: number;
signedAttrSize: number;
signedAttrHashFunction: string;
signatureAlgorithm: string;
signatureAlgorithmDetails: string;
curveOrExponent: string;
signatureAlgorithmBits: number;
countryCode: string;
cscaFound: boolean;
cscaHashFunction: string;
cscaSignature: string;
cscaSignatureAlgorithmDetails: string;
cscaCurveOrExponent: string;
cscaSignatureAlgorithmBits: number;
dsc: 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 getCurveOrExponent(certData: CertificateData): string {
if (certData.signatureAlgorithm === 'rsapss' || certData.signatureAlgorithm === 'rsa') {
return (certData.publicKeyDetails as PublicKeyDetailsRSA).exponent;
}
return (certData.publicKeyDetails as PublicKeyDetailsECDSA).curve;
}
export function getSimplePublicKeyDetails(certData: CertificateData): string {
interface SimplePublicKeyDetails {
exponent?: string;
curve?: string;
hashAlgorithm?: string;
saltLength?: string;
}
const simplePublicKeyDetails: SimplePublicKeyDetails = {};
if (certData.signatureAlgorithm === 'rsapss' || certData.signatureAlgorithm === 'rsa') {
simplePublicKeyDetails.exponent = (certData.publicKeyDetails as PublicKeyDetailsRSA).exponent;
if (certData.signatureAlgorithm === 'rsapss') {
simplePublicKeyDetails.hashAlgorithm = (certData.publicKeyDetails as PublicKeyDetailsRSAPSS).hashAlgorithm;
simplePublicKeyDetails.saltLength = (certData.publicKeyDetails as PublicKeyDetailsRSAPSS).saltLength;
}
}
else if (certData.signatureAlgorithm === 'ecdsa') {
simplePublicKeyDetails.curve = (certData.publicKeyDetails as PublicKeyDetailsECDSA).curve;
}
return JSON.stringify(simplePublicKeyDetails);
}
export function parsePassportData(passportData: PassportData): PassportMetadata {
const dg1HashInfo = passportData.mrz ?
findDG1HashInEContent(passportData.mrz, passportData.eContent) :
null;
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);
let parsedDsc = null;
let parsedCsca = null;
let csca = null;
let dscHashFunction = 'unknown';
let dscSignature = 'unknown';
let dscSignatureAlgorithmDetails = 'unknown';
let dscSignatureAlgorithmBits = 0;
let cscaHashFunction = 'unknown';
let cscaSignature = 'unknown';
let cscaSignatureAlgorithmDetails = 'unknown';
let cscaSignatureAlgorithmBits = 0;
if (passportData.dsc) {
parsedDsc = parseCertificateSimple(passportData.dsc);
dscHashFunction = parsedDsc.hashAlgorithm;
dscSignature = parsedDsc.signatureAlgorithm;
dscSignatureAlgorithmDetails = getSimplePublicKeyDetails(parsedDsc);
dscSignatureAlgorithmBits = parseInt(parsedDsc.publicKeyDetails?.bits || '0');
if (parsedDsc.authorityKeyIdentifier) {
csca = getCSCAFromSKI(parsedDsc.authorityKeyIdentifier, true);
if (csca) {
parsedCsca = parseCertificateSimple(csca);
cscaHashFunction = parsedCsca.hashAlgorithm;
cscaSignature = parsedCsca.signatureAlgorithm;
cscaSignatureAlgorithmDetails = getSimplePublicKeyDetails(parsedCsca);
cscaSignatureAlgorithmBits = parseInt(parsedCsca.publicKeyDetails?.bits || '0');
}
}
}
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,
signatureAlgorithm: dscSignature,
signatureAlgorithmDetails: dscSignatureAlgorithmDetails,
curveOrExponent: parsedDsc ? getCurveOrExponent(parsedDsc) : 'unknown',
signatureAlgorithmBits: dscSignatureAlgorithmBits,
countryCode: passportData.mrz ? getCountryCodeFromMrz(passportData.mrz) : 'unknown',
cscaFound: !!csca,
cscaHashFunction,
cscaSignature,
cscaSignatureAlgorithmDetails,
cscaCurveOrExponent: parsedCsca ? getCurveOrExponent(parsedCsca) : 'unknown',
cscaSignatureAlgorithmBits: cscaSignatureAlgorithmBits,
dsc: passportData.dsc
};
}

View File

@@ -13,23 +13,23 @@ export type PassportData = {
// Define the signature algorithm in "algorithm_hashfunction_domainPapameter_keyLength"
export type SignatureAlgorithm =
| 'rsa_sha1_65537_2048'
| 'rsa_sha256_65537_2048'
| 'rsapss_sha256_65537_2048'
| 'rsapss_sha256_3_4096'
| 'rsapss_sha256_3_3072'
| 'rsapss_sha384_65537_3072'
| 'rsapss_sha384_65537_4096'
| 'ecdsa_sha256_secp256r1_256'
| 'ecdsa_sha1_secp256r1_256'
| 'ecdsa_sha384_secp384r1_384'
| 'ecdsa_sha256_brainpoolP256r1_256'
| 'rsa_sha256_3_2048'
| 'rsa_sha256_65537_3072'
| 'rsa_sha256_65537_4096'
| 'rsa_sha512_65537_4096'
| 'rsapss_sha256_65537_3072'
| 'rsapss_sha256_65537_4096';
| 'rsa_sha1_65537_2048'
| 'rsa_sha256_65537_2048'
| 'rsapss_sha256_65537_2048'
| 'rsapss_sha256_3_4096'
| 'rsapss_sha256_3_3072'
| 'rsapss_sha384_65537_3072'
| 'rsapss_sha384_65537_4096'
| 'ecdsa_sha256_secp256r1_256'
| 'ecdsa_sha1_secp256r1_256'
| 'ecdsa_sha384_secp384r1_384'
| 'ecdsa_sha256_brainpoolP256r1_256'
| 'rsa_sha256_3_2048'
| 'rsa_sha256_65537_3072'
| 'rsa_sha256_65537_4096'
| 'rsa_sha512_65537_4096'
| 'rsapss_sha256_65537_3072'
| 'rsapss_sha256_65537_4096';
export type Proof = {
proof: {

View File

@@ -77,7 +77,6 @@ export function formatDg2Hash(dg2Hash: number[]) {
export function formatAndConcatenateDataHashes(
dataHashes: [number, number[]][],
hashLen: number,
dg1HashOffset: number
) {
// concatenating dataHashes :
@@ -159,7 +158,7 @@ export function formatAndConcatenateDataHashes(
return concat;
}
export function assembleEContent(messageDigest: number[]) {
export function generateSignedAttr(messageDigest: number[]) {
const constructedEContent = [];
// Detailed description is in private file r&d.ts for now

View File

@@ -3,42 +3,44 @@ import { describe, it } from 'mocha';
import { genMockPassportData } from '../src/utils/genMockPassportData';
import * as forge from 'node-forge';
import { PassportData, SignatureAlgorithm } from '../src/utils/types';
import { formatMrz, hash, arraysAreEqual, findSubarrayIndex } from '../src/utils/utils';
import * as asn1 from 'asn1js';
import { Certificate } from 'pkijs';
import elliptic from 'elliptic';
import { parseCertificateSimple } from '../src/utils/certificate_parsing/parseCertificateSimple';
import { getCurveForElliptic } from '../src/utils/certificate_parsing/curves';
import { PublicKeyDetailsECDSA, PublicKeyDetailsRSAPSS } from '../src/utils/certificate_parsing/dataStructure';
import { parsePassportData } from '../src/utils/parsePassportData';
const sigAlgs: SignatureAlgorithm[] = [
'rsa_sha1_65537_2048',
'rsa_sha256_65537_2048',
'rsapss_sha256_65537_2048',
'ecdsa_sha256_secp256r1_256',
'ecdsa_sha1_secp256r1_256',
// 'ecdsa_sha384_secp384r1_384',
const testCases = [
{ dgHashAlgo: 'sha1', eContentHashAlgo: 'sha1', sigAlg: 'rsa_sha1_65537_2048' },
{ dgHashAlgo: 'sha1', eContentHashAlgo: 'sha1', sigAlg: 'rsa_sha256_65537_2048' },
{ dgHashAlgo: 'sha256', eContentHashAlgo: 'sha256', sigAlg: 'rsapss_sha256_65537_2048' },
{ dgHashAlgo: 'sha256', eContentHashAlgo: 'sha256', sigAlg: 'ecdsa_sha256_secp256r1_256' },
{ dgHashAlgo: 'sha1', eContentHashAlgo: 'sha1', sigAlg: 'ecdsa_sha1_secp256r1_256' },
];
describe('Mock Passport Data Generator', function () {
this.timeout(0);
sigAlgs.forEach((sigAlg) => {
testCases.forEach(({ dgHashAlgo, eContentHashAlgo, sigAlg }) => {
it(`should generate valid passport data for ${sigAlg}`, () => {
const passportData = genMockPassportData(sigAlg, 'FRA', '000101', '300101');
const passportData = genMockPassportData(
dgHashAlgo,
eContentHashAlgo,
sigAlg as SignatureAlgorithm,
'FRA',
'000101',
'300101'
);
expect(passportData).to.exist;
expect(verify(passportData)).to.be.true;
expect(verify(passportData, dgHashAlgo, eContentHashAlgo)).to.be.true;
});
});
});
function verify(passportData: PassportData): boolean {
const { mrz, dsc, eContent, signedAttr, encryptedDigest } = passportData;
function verify(passportData: PassportData, dgHashAlgo: string, eContentHashAlgo: string): boolean {
const { dsc, eContent, signedAttr, encryptedDigest } = passportData;
const { signatureAlgorithm, hashAlgorithm, publicKeyDetails } = parseCertificateSimple(dsc);
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(hashAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(eContent, mrzHash);
assert(dg1HashOffset !== -1, 'MRZ hash index not found in eContent');
console.error(
'\x1b[32m',
'signatureAlgorithm',
@@ -51,11 +53,10 @@ function verify(passportData: PassportData): boolean {
signedAttr.length,
'\x1b[0m'
);
const concatHash = hash(hashAlgorithm, eContent);
assert(
arraysAreEqual(concatHash, signedAttr.slice(signedAttr.length - getHashLen(hashAlgorithm))),
'concatHash is not at the right place in signedAttr'
);
const passportMetaData = parsePassportData(passportData);
expect(passportMetaData.dg1HashFunction).to.equal(dgHashAlgo);
expect(passportMetaData.eContentHashFunction).to.equal(eContentHashAlgo);
if (signatureAlgorithm === 'ecdsa') {
const certBuffer = Buffer.from(