mirror of
https://github.com/selfxyz/self.git
synced 2026-04-05 03:00:53 -04:00
implement multi-hash circuits for passport verification
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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' };
|
||||
}
|
||||
}
|
||||
51
common/src/utils/circuitsName.ts
Normal file
51
common/src/utils/circuitsName.ts
Normal 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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()),
|
||||
|
||||
161
common/src/utils/parsePassportData.ts
Normal file
161
common/src/utils/parsePassportData.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user