refactor circuits repo, clean circuits tests, implement new certificates scripts, fix all tests from circuits repo

This commit is contained in:
turnoffthiscomputer
2024-07-14 15:44:55 +02:00
parent 6ed8e0d5a1
commit 464f6d21ce
39 changed files with 3031 additions and 1820 deletions

View File

@@ -0,0 +1,9 @@
# How to generate mock passport data based on your real data?
- Build the app and scan your passport to log your passport data.
- Copy one of the files of this folder and paste your passport data.
- Adapt the `verify` function to verify it. Once this is done, adapt the `genMockPassportData` to generate a mock one.
- Once the mock passport data generated is verified correctly by the same `verify` function that verifies yours, you're all set!
- Run the script to generate a mock passport data and add it to `common/src/utils/mockPassportData.ts`
- Do a PR
- DM us to collect your bounty!

View File

@@ -0,0 +1,629 @@
{
"mrz": "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02",
"signatureAlgorithm": "sha256WithRSASSAPSS",
"pubKey": {
"modulus": "24462187253413274681146293990014601117483150253485750502784042435672184694412963307122026240846907391312882376801424642119473345751861224453041335405750030091821974208795494089279845074559882616814677854700627123408815125641207116387150180075958682953326415376187334908428885389819481874887447587379894859906385655519567588675165038354987379327125622417796020195813417774532495071662150990707566936780047952622227454986438290561518433120591444215515025611804148686981931860883842745085825036432179109865379231910853752492302597965268640127145219080386320748404798990484447108886155687702517815916586904444799519356177",
"exponent": "65537"
},
"dataGroupHashes": [
-114,
-118,
-62,
50,
66,
9,
-21,
-98,
-47,
-62,
-12,
-83,
77,
111,
103,
117,
-85,
-35,
-96,
21,
-11,
-19,
-27,
-99,
-119,
62,
-62,
-36,
-103,
117,
-80,
-33,
31,
-123,
108,
84,
-98,
102,
70,
11,
-91,
-81,
-60,
12,
-55,
-126,
25,
-125,
46,
125,
-100,
-62,
28,
23,
55,
-123,
-99,
-92,
-121,
-120,
-36,
78,
-66,
82,
-76,
-21,
-34,
33,
79,
50,
-104,
-120,
-114,
35,
116,
-32,
6,
-14,
-100,
-115,
-128,
-8,
10,
61,
98,
86,
-8,
45,
-49,
-46,
90,
-24,
-81,
38,
0,
-62,
104,
108,
-19,
-10,
97,
-26,
116,
-58,
69,
110,
26,
87,
17,
89,
110,
-57,
108,
-6,
36,
21,
39,
87,
110,
102,
-6,
-43,
-82,
-125,
-85,
-82,
-120,
-101,
87,
-112,
111,
15,
-104,
127,
85,
25,
-102,
81,
20,
58,
51,
75,
-63,
116,
-22,
0,
60,
30,
29,
30,
-73,
-115,
72,
-9,
-1,
-53,
100,
124,
41,
-22,
106,
78,
31,
11,
114,
-119,
-19,
17,
92,
71,
-122,
47,
62,
78,
-67,
-23,
-55,
-42,
53,
4,
47,
-67,
-55,
-123,
6,
121,
34,
-125,
64,
-114,
91,
-34,
-46,
-63,
62,
-34,
104,
82,
36,
41,
-118,
-3,
70,
15,
-108,
-48,
-100,
45,
105,
-85,
-15,
-61,
-71,
43,
-39,
-94,
-110,
-55,
-34,
89,
-18,
38,
76,
123,
-40,
13,
51,
-29,
72,
-11,
59,
-63,
-18,
-90,
103,
49,
23,
-92,
-85,
-68,
-62,
-59,
-100,
-69,
-7,
28,
-58,
95,
69,
15,
-74,
56,
54,
38
],
"eContent": [
49,
102,
48,
21,
6,
9,
42,
-122,
72,
-122,
-9,
13,
1,
9,
3,
49,
8,
6,
6,
103,
-127,
8,
1,
1,
1,
48,
28,
6,
9,
42,
-122,
72,
-122,
-9,
13,
1,
9,
5,
49,
15,
23,
13,
49,
57,
49,
50,
49,
54,
49,
55,
50,
50,
51,
56,
90,
48,
47,
6,
9,
42,
-122,
72,
-122,
-9,
13,
1,
9,
4,
49,
34,
4,
32,
-122,
-17,
-90,
-50,
-85,
-36,
1,
-60,
98,
23,
122,
117,
-121,
-30,
85,
120,
53,
83,
-125,
-57,
-15,
70,
39,
-114,
64,
123,
39,
-78,
-76,
-122,
-50,
-17
],
"encryptedDigest": [
87,
20,
96,
-119,
-78,
21,
117,
-35,
91,
41,
86,
-58,
-21,
72,
-36,
58,
-79,
-29,
74,
-103,
-98,
-46,
-58,
-84,
110,
29,
-89,
-15,
127,
121,
92,
125,
106,
81,
-16,
-96,
-9,
112,
-33,
-36,
-52,
-114,
-14,
-80,
-61,
-59,
33,
-104,
120,
-8,
8,
-29,
-56,
14,
-79,
-122,
60,
-23,
-100,
72,
51,
-31,
-68,
-70,
37,
18,
-91,
47,
-19,
-40,
-4,
-59,
-66,
88,
103,
32,
-108,
-77,
-15,
-44,
-100,
13,
46,
-45,
-41,
115,
-18,
110,
-3,
12,
-17,
85,
111,
-51,
12,
6,
114,
0,
92,
58,
-26,
-13,
-40,
34,
43,
11,
87,
-72,
-1,
-119,
-86,
13,
79,
-1,
-72,
16,
-89,
109,
79,
-99,
-26,
-110,
-17,
79,
-2,
-37,
-59,
-61,
-3,
-50,
57,
121,
-10,
-19,
106,
-76,
-43,
-33,
-68,
-1,
5,
22,
-5,
-39,
-13,
-10,
73,
33,
28,
-48,
-24,
-119,
-49,
127,
-115,
-99,
59,
10,
-62,
-47,
-33,
99,
54,
112,
116,
62,
99,
68,
-87,
-78,
104,
-84,
-105,
61,
104,
-56,
-11,
-56,
-102,
-39,
-70,
81,
-58,
-7,
98,
30,
29,
-116,
6,
-127,
-103,
-5,
98,
-64,
-83,
84,
-4,
100,
70,
112,
29,
114,
109,
-3,
-72,
-13,
-4,
-10,
-14,
-82,
14,
-38,
112,
-112,
15,
-10,
-101,
87,
23,
77,
-31,
101,
121,
-6,
-125,
117,
126,
103,
100,
10,
95,
-90,
-103,
-92,
-118,
32,
124,
-115,
-116,
4,
73,
-15,
60,
-16,
-23,
110,
30,
-122,
42,
-28,
57,
-59,
-60,
118,
53,
122
],
"photoBase64": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}

View File

@@ -0,0 +1,94 @@
import { readFileSync, writeFileSync } from "fs";
import { PassportData } from "../../src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, hexToDecimal, arraysAreEqual, findSubarrayIndex } from "../../src/utils/utils";
import * as forge from 'node-forge';
import { assert } from "console";
import { mock_dsc_key_sha1_rsa_4096 } from "../../src/constants/mockCertificates";
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
const sampleDataHashes = [
[
2,
[-66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, -8]
],
[
3,
[0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6]
],
[
14,
[76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59]
]
] as [number, number[]][]
const signatureAlgorithm = 'sha1WithRSAEncryption'
const hashLen = 20
export function genMockPassportData_sha1WithRSAEncryption_65537(): PassportData {
const mrzHash = hash(signatureAlgorithm, formatMrz(sampleMRZ));
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
31 // could have been different
);
const eContent = assembleEContent(hash(signatureAlgorithm, concatenatedDataHashes));
const privKey = forge.pki.privateKeyFromPem(mock_dsc_key_sha1_rsa_4096);
const modulus = privKey.n.toString(16);
const md = forge.md.sha1.create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
const signature = privKey.sign(md)
const signatureBytes = Array.from(signature, (c: string) => c.charCodeAt(0));
return {
mrz: sampleMRZ,
signatureAlgorithm: signatureAlgorithm,
pubKey: {
modulus: hexToDecimal(modulus),
exponent: '65537',
},
dataGroupHashes: concatenatedDataHashes,
eContent: eContent,
encryptedDigest: signatureBytes,
photoBase64: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}
}
function verify(passportData: PassportData): boolean {
const { mrz, signatureAlgorithm, pubKey, dataGroupHashes, eContent, encryptedDigest } = passportData;
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(signatureAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(dataGroupHashes, mrzHash)
console.log('dg1HashOffset', dg1HashOffset);
assert(dg1HashOffset !== -1, 'MRZ hash index not found in dataGroupHashes');
const concatHash = hash(signatureAlgorithm, dataGroupHashes)
assert(
arraysAreEqual(
concatHash,
eContent.slice(eContent.length - concatHash.length)
),
'concatHash is not at the right place in eContent'
);
const modulus = new forge.jsbn.BigInteger(pubKey.modulus, 10);
const exponent = new forge.jsbn.BigInteger(pubKey.exponent, 10);
const rsaPublicKey = forge.pki.rsa.setPublicKey(modulus, exponent);
const md = forge.md.sha1.create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
const signature = Buffer.from(encryptedDigest).toString(
'binary',
);
return rsaPublicKey.verify(md.digest().bytes(), signature);
}
const mockPassportData = genMockPassportData_sha1WithRSAEncryption_65537();
console.log("Passport Data:", JSON.stringify(mockPassportData, null, 2));
console.log("Signature valid:", verify(mockPassportData));
writeFileSync(__dirname + '/passportData.json', JSON.stringify(mockPassportData, null, 2));

View File

@@ -0,0 +1,107 @@
import assert from "assert";
import { PassportData } from "../../src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, hexToDecimal, arraysAreEqual, findSubarrayIndex } from "../../src/utils/utils";
import * as forge from 'node-forge';
import { writeFileSync, readFileSync } from "fs";
import { mock_dsc_key_sha256_rsa_4096 } from "../../src/constants/mockCertificates";
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
const sampleDataHashes = [
[
2,
[-66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, -8, 10, 61, 98, 86, -8, 45, -49, -46, 90, -24, -81, 38]
],
[
3,
[0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6, 36, 21, 39, 87, 110, 102, -6, -43, -82, -125, -85, -82]
],
[
11,
[-120, -101, 87, -112, 111, 15, -104, 127, 85, 25, -102, 81, 20, 58, 51, 75, -63, 116, -22, 0, 60, 30, 29, 30, -73, -115, 72, -9, -1, -53, 100, 124]
],
[
12,
[41, -22, 106, 78, 31, 11, 114, -119, -19, 17, 92, 71, -122, 47, 62, 78, -67, -23, -55, -42, 53, 4, 47, -67, -55, -123, 6, 121, 34, -125, 64, -114]
],
[
13,
[91, -34, -46, -63, 62, -34, 104, 82, 36, 41, -118, -3, 70, 15, -108, -48, -100, 45, 105, -85, -15, -61, -71, 43, -39, -94, -110, -55, -34, 89, -18, 38]
],
[
14,
[76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59, -100, -69, -7, 28, -58, 95, 69, 15, -74, 56, 54, 38]
]
] as [number, number[]][]
const signatureAlgorithm = 'sha256WithRSAEncryption'
const hashLen = 32
export function genMockPassportData_sha256WithRSAEncryption_65537(): PassportData {
const mrzHash = hash(signatureAlgorithm, formatMrz(sampleMRZ));
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
31
);
const eContent = assembleEContent(hash(signatureAlgorithm, concatenatedDataHashes));
const privKey = forge.pki.privateKeyFromPem(mock_dsc_key_sha256_rsa_4096);
const modulus = privKey.n.toString(16);
const md = forge.md.sha256.create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
const signature = privKey.sign(md)
const signatureBytes = Array.from(signature, (c: string) => c.charCodeAt(0));
return {
mrz: sampleMRZ,
signatureAlgorithm: signatureAlgorithm,
pubKey: {
modulus: hexToDecimal(modulus),
exponent: '65537',
},
dataGroupHashes: concatenatedDataHashes,
eContent: eContent,
encryptedDigest: signatureBytes,
photoBase64: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}
}
function verify(passportData: PassportData): boolean {
const { mrz, signatureAlgorithm, pubKey, dataGroupHashes, eContent, encryptedDigest } = passportData;
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(signatureAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(dataGroupHashes, mrzHash)
console.log('dg1HashOffset', dg1HashOffset);
assert(dg1HashOffset !== -1, 'MRZ hash index not found in dataGroupHashes');
const concatHash = hash(signatureAlgorithm, dataGroupHashes)
assert(
arraysAreEqual(
concatHash,
eContent.slice(eContent.length - hashLen)
),
'concatHash is not at the right place in eContent'
);
const modulus = new forge.jsbn.BigInteger(pubKey.modulus, 10);
const exponent = new forge.jsbn.BigInteger(pubKey.exponent, 10);
const rsaPublicKey = forge.pki.rsa.setPublicKey(modulus, exponent);
const md = forge.md.sha256.create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
const signature = Buffer.from(encryptedDigest).toString(
'binary',
);
return rsaPublicKey.verify(md.digest().bytes(), signature);
}
const mockPassportData = genMockPassportData_sha256WithRSAEncryption_65537();
console.log("Passport Data:", JSON.stringify(mockPassportData, null, 2));
console.log("Signature valid:", verify(mockPassportData));
writeFileSync(__dirname + '/passportData.json', JSON.stringify(mockPassportData, null, 2));

View File

@@ -0,0 +1,127 @@
import assert from "assert";
import { PassportData } from "../../src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, arraysAreEqual, findSubarrayIndex } from "../../src/utils/utils";
import * as forge from 'node-forge';
import crypto from 'crypto';
import { readFileSync, writeFileSync } from "fs";
import { mock_dsc_key_sha256_rsapss_2048 } from "../../src/constants/mockCertificates";
import { mock_dsc_sha256_rsapss_2048 } from "../../src/constants/mockCertificates";
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
const sampleDataHashes = [
[
2,
[-66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, -8, 10, 61, 98, 86, -8, 45, -49, -46, 90, -24, -81, 38]
],
[
3,
[0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6, 36, 21, 39, 87, 110, 102, -6, -43, -82, -125, -85, -82]
],
[
11,
[-120, -101, 87, -112, 111, 15, -104, 127, 85, 25, -102, 81, 20, 58, 51, 75, -63, 116, -22, 0, 60, 30, 29, 30, -73, -115, 72, -9, -1, -53, 100, 124]
],
[
12,
[41, -22, 106, 78, 31, 11, 114, -119, -19, 17, 92, 71, -122, 47, 62, 78, -67, -23, -55, -42, 53, 4, 47, -67, -55, -123, 6, 121, 34, -125, 64, -114]
],
[
13,
[91, -34, -46, -63, 62, -34, 104, 82, 36, 41, -118, -3, 70, 15, -108, -48, -100, 45, 105, -85, -15, -61, -71, 43, -39, -94, -110, -55, -34, 89, -18, 38]
],
[
14,
[76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59, -100, -69, -7, 28, -58, 95, 69, 15, -74, 56, 54, 38]
]
] as [number, number[]][]
const signatureAlgorithm = 'sha256WithRSASSAPSS'
const hashLen = 32
export function genMockPassportData_sha256WithRSASSAPSS_65537(): PassportData {
const privateKeyPem = forge.pki.privateKeyFromPem(mock_dsc_key_sha256_rsapss_2048);
const privateKeyPemString = forge.pki.privateKeyToPem(privateKeyPem);
const certificate = forge.pki.certificateFromPem(mock_dsc_sha256_rsapss_2048);
const publicKey = certificate.publicKey as forge.pki.rsa.PublicKey;
const modulus = (publicKey as any).n.toString(10);
const exponent = (publicKey as any).e.toString(10);
const salt = Buffer.from('dee959c7e06411361420ff80185ed57f3e6776afdee959c7e064113614201420', 'hex');
const mrzHash = hash(signatureAlgorithm, formatMrz(sampleMRZ));
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
30
);
const eContent = assembleEContent(hash(signatureAlgorithm, concatenatedDataHashes));
const my_message = Buffer.from(eContent);
const hash_algorithm = 'sha256';
const private_key = {
key: privateKeyPemString,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: salt.length,
};
const signature = crypto.sign(hash_algorithm, my_message, private_key);
const signatureArray = Array.from(signature, byte => byte < 128 ? byte : byte - 256);
return {
mrz: sampleMRZ,
signatureAlgorithm: signatureAlgorithm,
pubKey: {
modulus: modulus,
exponent: exponent,
},
dataGroupHashes: concatenatedDataHashes,
eContent: eContent,
encryptedDigest: signatureArray,
photoBase64: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}
}
function verify(passportData: PassportData): boolean {
const { mrz, signatureAlgorithm, pubKey, dataGroupHashes, eContent, encryptedDigest } = passportData;
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(signatureAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(dataGroupHashes, mrzHash)
console.log('dg1HashOffset', dg1HashOffset);
assert(dg1HashOffset !== -1, 'MRZ hash index not found in dataGroupHashes');
const concatHash = hash(signatureAlgorithm, dataGroupHashes)
assert(
arraysAreEqual(
concatHash,
eContent.slice(eContent.length - concatHash.length)
),
'concatHash is not at the right place in eContent'
);
const modulus = new forge.jsbn.BigInteger(pubKey.modulus, 10);
const exponent = new forge.jsbn.BigInteger(pubKey.exponent, 10);
const publicKey = forge.pki.setRsaPublicKey(modulus, exponent);
const pem = forge.pki.publicKeyToPem(publicKey);
const rsa_public = Buffer.from(pem);
const message = Buffer.from(eContent);
const signature = Buffer.from(encryptedDigest);
const hash_algorithm = "sha256";
const public_key = {
key: rsa_public,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 32,
};
const isVerified = crypto.verify(hash_algorithm, message, public_key, signature);
return isVerified;
}
const mockPassportData = genMockPassportData_sha256WithRSASSAPSS_65537();
console.log("Passport Data:", JSON.stringify(mockPassportData, null, 2));
console.log("Signature valid:", verify(mockPassportData));
writeFileSync(__dirname + '/passportData.json', JSON.stringify(mockPassportData, null, 2));

View File

@@ -0,0 +1,221 @@
// this file shows the fully explicit form of RSASSA-PSS, based on https://github.com/shigeki/ohtsu_rsa_pss_js/.
// It can be useful to implement the circuit
// It is currently broken so the line that errors is commented
import assert from "assert";
import { PassportData } from "../../src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, arraysAreEqual, findSubarrayIndex } from "../../src/utils/utils";
import * as forge from 'node-forge';
import crypto from 'crypto';
import { writeFileSync } from "fs";
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
const sampleDataHashes = [
[
2,
[-66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, -8, 10, 61, 98, 86, -8, 45, -49, -46, 90, -24, -81, 38]
],
[
3,
[0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6, 36, 21, 39, 87, 110, 102, -6, -43, -82, -125, -85, -82]
],
[
11,
[-120, -101, 87, -112, 111, 15, -104, 127, 85, 25, -102, 81, 20, 58, 51, 75, -63, 116, -22, 0, 60, 30, 29, 30, -73, -115, 72, -9, -1, -53, 100, 124]
],
[
12,
[41, -22, 106, 78, 31, 11, 114, -119, -19, 17, 92, 71, -122, 47, 62, 78, -67, -23, -55, -42, 53, 4, 47, -67, -55, -123, 6, 121, 34, -125, 64, -114]
],
[
13,
[91, -34, -46, -63, 62, -34, 104, 82, 36, 41, -118, -3, 70, 15, -108, -48, -100, 45, 105, -85, -15, -61, -71, 43, -39, -94, -110, -55, -34, 89, -18, 38]
],
[
14,
[76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59, -100, -69, -7, 28, -58, 95, 69, 15, -74, 56, 54, 38]
]
] as [number, number[]][]
const signatureAlgorithm = 'sha256WithRSASSAPSS'
const hashLen = 32
export function genMockPassportData_sha256WithRSASSAPSS_65537(): PassportData {
const keypair = forge.pki.rsa.generateKeyPair(2048);
const privateKeyPem = forge.pki.privateKeyToPem(keypair.privateKey);
const publicKey = keypair.publicKey;
const modulus = publicKey.n.toString(10);
const exponent = publicKey.e.toString(10);
const salt = Buffer.from('dee959c7e06411361420ff80185ed57f3e6776afdee959c7e064113614201420', 'hex');
const mrzHash = hash(signatureAlgorithm, formatMrz(sampleMRZ));
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
30
);
const eContent = assembleEContent(hash(signatureAlgorithm, concatenatedDataHashes));
const my_message = Buffer.from(eContent);
const sLen = 32;
const keylen = 2048;
const hash_algorithm = 'sha256';
const emBits = keylen - 1;
const emLen = Math.ceil(emBits / 8);
const hLen = 32;
console.log('my_message:', my_message);
const private_key = {
key: Buffer.from(privateKeyPem),
padding: crypto.constants.RSA_NO_PADDING
};
const padding1 = Buffer.alloc(8);
const hash1 = crypto.createHash(hash_algorithm);
hash1.update(my_message);
const mHash = hash1.digest();
if (emLen < hLen + sLen + 2) {
throw new Error('encoding error');
}
var padding2 = Buffer.alloc(emLen - sLen - hLen - 2);
const DB = Buffer.concat([padding2, Buffer.from('01', 'hex'), salt]);
const hash2 = crypto.createHash(hash_algorithm);
hash2.update(Buffer.concat([padding1, mHash, salt]));
const H = hash2.digest();
const dbMask = MGF1(H, emLen - hLen - 1, hLen, hash_algorithm);
var maskedDB = BufferXOR(DB, dbMask);
var b = Buffer.concat([maskedDB, H, Buffer.from('bc', 'hex')]);
var signature = crypto.privateEncrypt(private_key, b);
const signatureArray = Array.from(signature, byte => byte < 128 ? byte : byte - 256);
// const signatureBytes = Array.from(signature, (c: string) => c.charCodeAt(0));
return {
mrz: sampleMRZ,
signatureAlgorithm: signatureAlgorithm,
pubKey: {
modulus: modulus,
exponent: exponent,
},
dataGroupHashes: concatenatedDataHashes,
eContent: eContent,
encryptedDigest: signatureArray,
photoBase64: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}
}
function verify(passportData: PassportData): boolean {
const { mrz, signatureAlgorithm, pubKey, dataGroupHashes, eContent, encryptedDigest } = passportData;
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(signatureAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(dataGroupHashes, mrzHash)
assert(dg1HashOffset !== -1, 'MRZ hash index not found in dataGroupHashes');
const concatHash = hash(signatureAlgorithm, dataGroupHashes)
assert(
arraysAreEqual(
concatHash,
eContent.slice(eContent.length - concatHash.length)
),
'concatHash is not at the right place in eContent'
);
const modulus = new forge.jsbn.BigInteger(pubKey.modulus, 10);
const exponent = new forge.jsbn.BigInteger(pubKey.exponent, 10);
const publicKey = forge.pki.setRsaPublicKey(modulus, exponent);
const pem = forge.pki.publicKeyToPem(publicKey);
const rsa_public = Buffer.from(pem);
const message = Buffer.from(eContent);
const sLen = 32;
const keylen = 2048;
const signature = Buffer.from(encryptedDigest);
const hash_algorithm = "sha256";
const hLen = 32;
assert(Buffer.isBuffer(rsa_public));
assert.strictEqual(typeof keylen, "number");
assert.strictEqual(typeof hash_algorithm, "string");
assert(Buffer.isBuffer(message));
assert.strictEqual(typeof sLen, "number");
assert(Buffer.isBuffer(signature));
const public_key = {
key: rsa_public,
padding: crypto.constants.RSA_NO_PADDING,
};
var m = crypto.publicDecrypt(public_key, signature);
const emBits = keylen - 1;
assert(hLen);
const emLen = Math.ceil(emBits / 8);
const hash1 = crypto.createHash(hash_algorithm);
hash1.update(message);
const mHash = hash1.digest();
console.log("emLen", emLen);
console.log("hLen", hLen);
console.log("sLen", sLen);
if (emLen < hLen + sLen + 2) throw new Error("inconsistent");
if (m[m.length - 1] !== 0xbc) throw new Error("inconsistent");
const maskedDB = m.slice(0, emLen - hLen - 1);
const H = m.slice(emLen - hLen - 1, emLen - 1);
// if ((maskedDB[0] & 0x80) !== 0x00) throw new Error("inconsistent");
const dbMask = MGF1(H, emLen - hLen - 1, hLen, hash_algorithm);
const DB = BufferXOR(maskedDB, dbMask);
DB[0] = DB[0] & 0x7f;
for (var i = 0; i < emLen - hLen - sLen - 2; i++) {
assert.strictEqual(DB[i], 0x00);
}
assert.strictEqual(DB[emLen - hLen - sLen - 2], 0x01);
const salt = DB.slice(-sLen);
const MDash = Buffer.concat([Buffer.alloc(8), mHash, salt]);
const hash2 = crypto.createHash(hash_algorithm);
hash2.update(MDash);
const HDash = hash2.digest();
return HDash.equals(H);
}
function MGF1(mgfSeed: Buffer, maskLen: number, hLen: number, hash_algorithm: string) {
if (maskLen > 0xffffffff * hLen) {
throw new Error("mask too long");
}
var T = [];
for (var i = 0; i <= Math.ceil(maskLen / hLen) - 1; i++) {
var C = Buffer.alloc(4);
C.writeUInt32BE(i);
const hash3 = crypto.createHash(hash_algorithm);
hash3.update(Buffer.concat([mgfSeed, C]));
T.push(hash3.digest());
}
return Buffer.concat(T).slice(0, maskLen);
}
function BufferXOR(a: Buffer, b: Buffer) {
assert(a.length === b.length, "Buffers must have the same length");
var c = Buffer.alloc(a.length);
for (var i = 0; i < a.length; i++) {
c[i] = a[i] ^ b[i];
}
return c;
}
const mockPassportData = genMockPassportData_sha256WithRSASSAPSS_65537();
console.log("Passport Data:", JSON.stringify(mockPassportData, null, 2));
console.log("Signature valid:", verify(mockPassportData));
writeFileSync(__dirname + '/passportData.json', JSON.stringify(mockPassportData, null, 2));

View File

@@ -0,0 +1,113 @@
import assert from "assert";
import { PassportData } from "../../src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, arraysAreEqual, findSubarrayIndex } from "../../src/utils/utils";
import * as forge from 'node-forge';
import { writeFileSync } from "fs";
import elliptic from 'elliptic';
import * as crypto from 'crypto';
const sampleMRZ = "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02"
const sampleDataHashes = [
[
2,
[-66, 82, -76, -21, -34, 33, 79, 50, -104, -120, -114, 35, 116, -32, 6, -14, -100, -115, -128, -8, 10, 61, 98, 86, -8, 45, -49, -46, 90, -24, -81, 38]
],
[
3,
[0, -62, 104, 108, -19, -10, 97, -26, 116, -58, 69, 110, 26, 87, 17, 89, 110, -57, 108, -6, 36, 21, 39, 87, 110, 102, -6, -43, -82, -125, -85, -82]
],
[
11,
[-120, -101, 87, -112, 111, 15, -104, 127, 85, 25, -102, 81, 20, 58, 51, 75, -63, 116, -22, 0, 60, 30, 29, 30, -73, -115, 72, -9, -1, -53, 100, 124]
],
[
12,
[41, -22, 106, 78, 31, 11, 114, -119, -19, 17, 92, 71, -122, 47, 62, 78, -67, -23, -55, -42, 53, 4, 47, -67, -55, -123, 6, 121, 34, -125, 64, -114]
],
[
13,
[91, -34, -46, -63, 62, -34, 104, 82, 36, 41, -118, -3, 70, 15, -108, -48, -100, 45, 105, -85, -15, -61, -71, 43, -39, -94, -110, -55, -34, 89, -18, 38]
],
[
14,
[76, 123, -40, 13, 51, -29, 72, -11, 59, -63, -18, -90, 103, 49, 23, -92, -85, -68, -62, -59, -100, -69, -7, 28, -58, 95, 69, 15, -74, 56, 54, 38]
]
] as [number, number[]][]
const signatureAlgorithm = 'SHA384withECDSA'
const hashLen = 46
export function genMockPassportData_SHA384withECDSA(): PassportData {
const mrzHash = hash(signatureAlgorithm, formatMrz(sampleMRZ));
sampleDataHashes.unshift([1, mrzHash]);
const concatenatedDataHashes = formatAndConcatenateDataHashes(
[[1, mrzHash], ...sampleDataHashes],
hashLen,
33
);
const eContent = assembleEContent(hash(signatureAlgorithm, concatenatedDataHashes));
const ec = new elliptic.ec('p384');
const keyPair = ec.genKeyPair();
const pubKey = keyPair.getPublic();
const md = forge.md.sha384.create();
md.update(forge.util.binary.raw.encode(new Uint8Array(eContent)));
const signature = keyPair.sign(md.digest().toHex(), 'hex');
const signatureBytes = Array.from(Buffer.from(signature.toDER(), 'hex'));
const Qx = pubKey.getX().toString(16);
const Qy = pubKey.getY().toString(16);
return {
mrz: sampleMRZ,
signatureAlgorithm: signatureAlgorithm,
pubKey: {
publicKeyQ: `(${Qx},${Qy},1,fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc)`
},
dataGroupHashes: concatenatedDataHashes,
eContent: eContent,
encryptedDigest: signatureBytes,
photoBase64: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}
}
function verify(passportData: PassportData): boolean {
const { mrz, signatureAlgorithm, pubKey, dataGroupHashes, eContent, encryptedDigest } = passportData;
const formattedMrz = formatMrz(mrz);
const mrzHash = hash(signatureAlgorithm, formattedMrz);
const dg1HashOffset = findSubarrayIndex(dataGroupHashes, mrzHash)
console.log('dg1HashOffset', dg1HashOffset);
assert(dg1HashOffset !== -1, 'MRZ hash index not found in dataGroupHashes');
const concatHash = hash(signatureAlgorithm, dataGroupHashes)
assert(
arraysAreEqual(
concatHash,
eContent.slice(eContent.length - concatHash.length)
),
'concatHash is not at the right place in eContent'
);
const cleanPublicKeyQ = pubKey.publicKeyQ.replace(/[()]/g, '').split(',');
const Qx = cleanPublicKeyQ[0];
const Qy = cleanPublicKeyQ[1];
const ec = new elliptic.ec('p384');
const key = ec.keyFromPublic({ x: Qx, y: Qy }, 'hex');
const messageBuffer = Buffer.from(eContent);
const msgHash = crypto.createHash('sha384').update(messageBuffer).digest();
const signature = Buffer.from(encryptedDigest).toString('hex');
const isValid = key.verify(msgHash, signature);
return isValid;
}
const mockPassportData = genMockPassportData_SHA384withECDSA();
console.log("Passport Data:", JSON.stringify(mockPassportData, null, 2));
console.log("Signature valid:", verify(mockPassportData));
writeFileSync(__dirname + '/passportData.json', JSON.stringify(mockPassportData, null, 2));