refactor circuits repo, add sha1 and rsapss dsc verification in circuits

This commit is contained in:
turnoffthiscomputer
2024-07-14 00:04:53 +02:00
parent 6df6ce1307
commit f99adb356b
58 changed files with 1405 additions and 776 deletions

File diff suppressed because one or more lines are too long

9
common/scripts/README.md Normal file
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,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));

View File

@@ -0,0 +1,474 @@
{
"mrz": "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02",
"signatureAlgorithm": "sha1WithRSAEncryption",
"pubKey": {
"modulus": "25825429057059192136108879878584777378654757240338449850333826035522788688006003098652086918620774520803490546755795078682140839966138109158244063837927200135790195224745239859057220329386561601721351696226057564841628462419818778146307352956712449575319326790354320318538693617555205343313027743984395735323775616464067940972666996572033472954611814220870194705592266623708066532169995623347732878333242475140240755230523341780618713394357686812647104201915704768910310961389728873036708721827192621939370755343467271300872482189554492692990185310969209988312747691889806959985764889980641836439721025225230179760027",
"exponent": "65537"
},
"dataGroupHashes": [
58,
-127,
-82,
12,
-127,
-122,
96,
100,
24,
117,
-114,
37,
-38,
115,
-43,
29,
31,
-120,
0,
55,
123,
95,
-5,
-51,
-36,
-14,
-55,
-81,
-64,
1,
50,
-96,
114,
-93,
25,
75,
70,
94,
85,
95,
40,
-11,
-101,
88,
-85,
-108,
-10,
-44,
104,
-62,
-117,
-66,
82,
-76,
-21,
-34,
33,
79,
50,
-104,
-120,
-114,
35,
116,
-32,
6,
-14,
-100,
-115,
-128,
-8,
0,
-62,
104,
108,
-19,
-10,
97,
-26,
116,
-58,
69,
110,
26,
87,
17,
89,
110,
-57,
108,
-6,
76,
123,
-40,
13,
51,
-29,
72,
-11,
59,
-63,
-18,
-90,
103,
49,
23,
-92,
-85,
-68,
-62,
-59
],
"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,
-44,
-111,
1,
94,
-94,
0,
-44,
-10,
92,
45,
51,
-75,
9,
107,
0,
93,
73,
104,
-127,
-71
],
"encryptedDigest": [
22,
247,
66,
163,
144,
4,
210,
177,
130,
237,
4,
44,
87,
28,
70,
24,
248,
5,
240,
162,
234,
93,
62,
124,
109,
165,
227,
92,
71,
199,
101,
98,
15,
132,
23,
191,
36,
158,
211,
56,
127,
72,
134,
133,
217,
210,
217,
34,
3,
77,
16,
139,
55,
25,
8,
83,
161,
2,
179,
201,
57,
19,
233,
67,
225,
54,
195,
226,
16,
22,
130,
96,
18,
206,
170,
208,
171,
126,
180,
137,
62,
195,
226,
206,
111,
82,
171,
199,
128,
147,
156,
156,
32,
5,
175,
187,
135,
227,
51,
0,
228,
175,
251,
2,
231,
85,
168,
160,
9,
133,
158,
175,
135,
42,
59,
13,
113,
211,
132,
92,
140,
106,
36,
191,
109,
213,
214,
57,
76,
35,
132,
176,
116,
130,
212,
101,
115,
28,
116,
203,
9,
98,
10,
68,
219,
128,
68,
110,
177,
175,
210,
80,
144,
65,
236,
128,
249,
219,
189,
216,
36,
234,
61,
75,
53,
101,
224,
126,
82,
207,
65,
182,
254,
133,
251,
103,
219,
207,
17,
172,
26,
139,
120,
184,
183,
176,
192,
102,
19,
184,
252,
146,
175,
169,
252,
3,
170,
141,
13,
136,
248,
94,
237,
158,
114,
149,
155,
241,
169,
161,
99,
239,
55,
30,
125,
219,
65,
43,
64,
12,
243,
138,
114,
150,
32,
165,
220,
91,
199,
176,
30,
156,
10,
199,
196,
152,
4,
194,
145,
163,
66,
212,
239,
164,
248,
242,
26,
51,
235,
101,
95,
133,
142,
189,
226,
195
],
"photoBase64": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}

View File

@@ -0,0 +1,128 @@
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";
const dsc_key = readFileSync('../common/src/mock_certificates/sha256_rsa_4096/mock_dsc.key', 'utf8');
const dsc_crt = readFileSync('../common/src/mock_certificates/sha256_rsa_4096/mock_dsc.crt', 'utf8');
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(dsc_key);
const privateKeyPemString = forge.pki.privateKeyToPem(privateKeyPem);
const certificate = forge.pki.certificateFromPem(dsc_crt);
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: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: salt.length,
};
const signature = crypto.sign(hash_algorithm, my_message, privateKeyPemString);
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,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";
const dsc_key = readFileSync('../common/src/mock_certificates/sha1_rsa_2048/mock_dsc.key', 'utf8');
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(dsc_key);
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,109 @@
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";
const dsc_key = readFileSync('../common/src/mock_certificates/sha256_rsa_4096/mock_dsc.key', 'utf8');
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 rsa = forge.pki.rsa;
//const privKey = rsa.generateKeyPair({ bits: 2048 }).privateKey;
const privKey = forge.pki.privateKeyFromPem(dsc_key);
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

@@ -1,19 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUVgXmIg3ZjJoGkQE2mwtKlcPI3bIwDQYJKoZIhvcNAQEF
BQAwFDESMBAGA1UEAwwJbW9ja19jc2NhMB4XDTI0MDYyODEzNDUzMFoXDTI1MDYy
ODEzNDUzMFowFDESMBAGA1UEAwwJbW9ja19jc2NhMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAmTkg9LzPdTsFgOL6uXHCGMylvo43XD5BpdION5nBFfx+
tKxC30AH41JAbJBb8kDY1FXQPfmE63ROfU0W9oMh8vu/n+yCJ22F4xqgOA012cXw
mEwa6A8dxeOqxv92dBdtN5XpcEne5FI1qzCCCRooQ5lMGikiGJiPVfXbIFPgVEv0
uW/bbmbNwmm3FZbWFKtotWIbfvLi14/9isk9Bgb1yYaiRhhr1AIyz6Whpsgxc+WE
GF8aOdmMzZWXouIyeaSr41R/PuXvOmkizg/YoJij4CDch42ofT/gtShP5k3uFa+G
NnmObx492pE7oEmtbJYi3ixuzx4tmnej+On40QBoSQIDAQABo1MwUTAdBgNVHQ4E
FgQUdTWxPLHwrq0uuqFF8jzGc1QPLOgwHwYDVR0jBBgwFoAUdTWxPLHwrq0uuqFF
8jzGc1QPLOgwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAlxot
U0kau1ukBqaaY8CrnxDYyW2WvxNLRSsT9M3I8V/XrDaok44qVl4yXg9p8dd7Pn8j
2ddnYk+tRBTm1I3sjdq/nVJ4soEqGdoxCQIKhKio8cJ26AOjuDv2j9mCZS6a4ptF
ZDpjuFA3Tuwaoxo5oygdk/0y1LX2ZLONn3moS/A/4mHwGID9jVBnwLsvYJNjHqBP
Dw7AiAaGUWC4IzBYE05PEz1VhcfFx9Gk0GhGqjB31da4J4QFq7uTz/WTuqQM7yoh
gy3qwxe+RisZnvsWHjrhmSyhVZJdUhqjvSnWF3LgJKux49VfRICsGbbWy8HXZzu0
ogX7sybD35QELYoB7A==
MIIC1zCCAb+gAwIBAgIUZipRXztCv63+iMkCt+ivpxwkAOAwDQYJKoZIhvcNAQEF
BQAwFDESMBAGA1UEAwwJTW9jayBDU0NBMB4XDTI0MDcxMjE3NDIxNVoXDTM0MDcx
MDE3NDIxNVowFDESMBAGA1UEAwwJTW9jayBDU0NBMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAvFuQiNhHOcXS8LMz8Tp76BIo6+ZZtythhcDaEVsroBWG
K63OqD89PTgg4d90To6TcMGXZXJZe+JifWsB5Fo+sUfwdOoWQ3hNKfWadKrkZs4x
dh/V05hJ4VAuaj9e2m937GQEsK9obG9MQnYZSlcODCYtIynM/IVmNr27I+s5fspP
0mZM+8X3mgjpQSwUob5zMkl8ZzskJau2oY6ImhI6fLNxAisS3wu/akpEVtKI3K8i
we99oPDO6xuHeuOQh0L6DbBdpYpF7jPOkah8zBQNfuENaCcjTxI2twrSN0YLuIsQ
fiEKbUq8DO5aRng9sws1DTNlf9AyvSPYfkizNtNHWwIDAQABoyEwHzAdBgNVHQ4E
FgQU16zJPs8xurwLQAoHiDOuUr+OmnQwDQYJKoZIhvcNAQEFBQADggEBAA6sMfoQ
1y42gg4/XNh6SOxHhT2z3u0lHq5+BxVORx5QcIi3rh1sgTocRBkl+4tzXFxDOzGt
RpVEOnM4VqbxgvuTLYPbZ3jnoS9S62w2if7xaOWexLO+3hsOtMIUrBfrfBax03/S
Tg9gkA98zvV1jbokPL23UXRWufv7L44HIFr3bLeikdOmpf6Lvp1ORiUXjbMi9o+c
ty+gWrxsV+825W3LGD/71DFSD4yS8wK9M9KLZb/21bt6tq4D/E3njnYbXID+1dTL
WC/4nCtzhd5n6cq1wKl6VnZ6bcyYb8MSQ6Kd6vbew1UnRae8KmFsjr2tJ50ESTj4
jQwzIZELkOP+EAE=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICWTCCAUECAQAwFDESMBAGA1UEAwwJTW9jayBDU0NBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAvFuQiNhHOcXS8LMz8Tp76BIo6+ZZtythhcDaEVsr
oBWGK63OqD89PTgg4d90To6TcMGXZXJZe+JifWsB5Fo+sUfwdOoWQ3hNKfWadKrk
Zs4xdh/V05hJ4VAuaj9e2m937GQEsK9obG9MQnYZSlcODCYtIynM/IVmNr27I+s5
fspP0mZM+8X3mgjpQSwUob5zMkl8ZzskJau2oY6ImhI6fLNxAisS3wu/akpEVtKI
3K8iwe99oPDO6xuHeuOQh0L6DbBdpYpF7jPOkah8zBQNfuENaCcjTxI2twrSN0YL
uIsQfiEKbUq8DO5aRng9sws1DTNlf9AyvSPYfkizNtNHWwIDAQABoAAwDQYJKoZI
hvcNAQELBQADggEBAK09cR+mHmiK10DSkVlnrZIk6sXO2jksPBBh2LGwE4CQSaBz
5C/XSQbXImkLWzkt2Kh2xYBCgjdoL8JNfsPYHWkjbN7OzOao0LM42a2o3mJZKixR
jcsWQLerrgXqhzP61gm+UVcZ77o5bfMtt1/52bIkJaBWd8Ka59wnTM75JQMVzM0y
R+dM7pSdvICzXvMc8NKZGOOROmAN3O317i5oFI89KgFQ0mhjjwdK7X8v9I7dhnHe
lI7ESWn/WCnIiq1tlDW3VyKzKul9nBFV2lDM1pHdfez0c9rAzry3Lz0NkM+yAtta
W4mf0Ybiz72mwkuY95Sk0+2GohR2BgYK3OjmVkw=
-----END CERTIFICATE REQUEST-----

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZOSD0vM91OwWA
4vq5ccIYzKW+jjdcPkGl0g43mcEV/H60rELfQAfjUkBskFvyQNjUVdA9+YTrdE59
TRb2gyHy+7+f7IInbYXjGqA4DTXZxfCYTBroDx3F46rG/3Z0F203lelwSd7kUjWr
MIIJGihDmUwaKSIYmI9V9dsgU+BUS/S5b9tuZs3CabcVltYUq2i1Yht+8uLXj/2K
yT0GBvXJhqJGGGvUAjLPpaGmyDFz5YQYXxo52YzNlZei4jJ5pKvjVH8+5e86aSLO
D9igmKPgINyHjah9P+C1KE/mTe4Vr4Y2eY5vHj3akTugSa1sliLeLG7PHi2ad6P4
6fjRAGhJAgMBAAECggEAA8T8pzIKb12Xz5wLPkWazcdMwronCUH0VZRMJSjHOpIz
GqDcG80sgdaKZHQC47wIluUIue+XVNS64MOv32RqO5sJ07EVjMtZsP9nkT1yStS0
bAYU9DV5U77GOv36VRXln1Q/hr5LKXDkJl12WNY6hvA2El+Fmdc4poJwBRTJ1upb
h+cGW/TBZXPirzRI1gicx4Qv++4Tv+GADpUAciOL8qXPklux5A8+cWCdOXHsb4sQ
rRILZ0INclZma1hmyMQYL5bfGqTpvNmoJbx4jiLuJTkIcIiCO0VR+A0Rd7l0bg/S
gzPuCK0/iSCB9QtctjVjsGF9XZut2xS/op8i0rz7UQKBgQDQ47Mp+dsHFLM3g/hw
s8loOcLDS0oRqTyg5NRWyAg6cuTTGs/RvEb77R2RTwok5l2+XcCR/WTc6f+cW3M/
XcDFaeXiStur1tnLr5d10twfdZE4C6xBujGAAt4pFUaerc76jb0HfUJDaANYlZIh
5p9nwuFGlYhFckBt4YuZek9omQKBgQC7x4R06PyIka7hrkUwLtDRMTgmi2OWP0Fv
rPQYLRKqL40Io/lI5VEpfQdgTI0k0YO92QsnvFqJBL+7qW3iPrqKAIMILiu37MNE
t65U4U7wzJRd7b2e33Fx6VWMOVbwvmEACoBTN+p8d/WJgg5FZ2wXo6GFWOQofOiI
0QhBkvdbMQKBgQCwjCI7Ef96ZpZqD1IE/w701gy0PoflkWG6omyMxBegPscDXR/U
JgrglbFtxJ3QPhtw74/4U2/R/ruZDPnEzppEv5IZxicPf1Eog8nBPWvp8tW/xq3a
B2IazVGy61Z6em/695ySHd3JVrmtPwz5Ng/4BI+dtngsKbompH/2QZen0QKBgGWZ
PuaOhMxTnn6G7vJx8hOtgMcCFgDd+sa0mmFWScoA7lrldWRmhrJe7pXA9YEdRx16
CJoYRBfslNyxgD2wWPd/7WWIrajXFpPgmhdczHxnBEOJ9VW3accLS3kSMSMSrqL6
C5J7J4ju0s/yqUwN+CMWKrdecBwj8SDNkJ0CKomRAoGAKUinXQmfvvJaSWe/dtBq
4Y97Q0vqFEZBDlE5WMYgnAVfnntdLfyMFcjvzVQksKrQWBIBCMboyhPABBdq7i9Y
X4g2czz9mQBgQdUoJ/Co2QBUaB3IqHnP7GbUuPjJ1kBv3xAh9MFBjdrD+sxfB0C7
NxT13TVPl8DrpE219OeBT8w=
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8W5CI2Ec5xdLw
szPxOnvoEijr5lm3K2GFwNoRWyugFYYrrc6oPz09OCDh33ROjpNwwZdlcll74mJ9
awHkWj6xR/B06hZDeE0p9Zp0quRmzjF2H9XTmEnhUC5qP17ab3fsZASwr2hsb0xC
dhlKVw4MJi0jKcz8hWY2vbsj6zl+yk/SZkz7xfeaCOlBLBShvnMySXxnOyQlq7ah
joiaEjp8s3ECKxLfC79qSkRW0ojcryLB732g8M7rG4d645CHQvoNsF2likXuM86R
qHzMFA1+4Q1oJyNPEja3CtI3Rgu4ixB+IQptSrwM7lpGeD2zCzUNM2V/0DK9I9h+
SLM200dbAgMBAAECggEACUarHgokq9QxuLfikXkzW/D1AfUUqwAga+Fc++jVA6u7
sU/8cu22Wtr2R5llFW9RFIb8KTtR69dwyUNpUvEc0Ec+MMvMb+MvTacyRZea6vMx
nWMg+SwxSz7TIllflvtRWRtt3bOtvVVX2JgPU8lowDFrHKW02iTJrV13p2Owr606
TUt0xq8xvnqWdmIl/xdGSgmb0PfdsWKLpwCVpJI6KZj2hQGffWhU/A3zNHNjGMfG
lRv3LEcqlsL/5+sYp5bOigk9fPMdrQ9eDJFvT5mas2mJt/YOUgefhy7quyyjuWEC
aT5QuYLEytCYrHKvdMowBznLjdcRtkDfYXOTtwtLfQKBgQDl+N7b28En4mD4Y9xE
N7pwQH+tjchtb/nSHmgXdvghiozBrvpg5tTujIPBJeZJdra6E8tNhSvafnsSPQbn
E3n2C3mqfUjR2/BVMYV9IQRhlxgvtMhLzsD2Oe7kbHl+aPiku3UZkWel+8CjeO4w
/3bzfHQB4jCPX0DjzcfEUvKSRwKBgQDRrPipyXH1idzuIVuuhrUZaA9hvawWd32K
IBAQCgILomL1IKcydoDJ71mCKvd3qVJd5u3zkbp+zQf+qs44Xofsvb1hZ5VIbqqA
APS53YOwvhV6J/yBHeNFvTPjynPNhH+Zd6h+N9FLezxttR7RnHxvdTz3sT6/JGnn
xkem8FR4TQKBgAbqlDFCi62aZlYyEqbOkkHb/rsGf5ei/X1vQqwp/M5tb0xH6enO
J2tBOJkvfZyKAHbaL4wWTDpXAXaL9VAvysQ2liMlf3IoTjMRuTUJMCuLMsQEiN2W
y0Dcfy3JgWHm4Et8A8qQD/DzAFkqQuegV0B9ZrPDzLgTIk13m+FwqTDzAoGBAKnU
u7jyhM6H8x4igNs1GxzQD51wfyHXeSTW4YvMP2njBtpbr3GNULW/m7V2TelriV6r
vNLLl847Dh21hcFcuCYGbY2m0+DOWmXB4Hdnt9IBtO2z069VYQ/Mt2nrQq8VgjDx
HdXWKj1EPWbzDFplsNiqkk5iD1vNB8cG8GSERzcFAoGAZprGJIfgEDkDX4lFpkkp
6NvUWxrZptebuVwm0hhPlXeb+p2myZtLUo/ID2S7iopb3eylSmZkOws0H54xsvc8
PguW+Vfc/bPqiejOPj+n2gKVw+1dBEsdGVV997fpCTXwNpjH9B6FJ4K4pEu0llcG
5q5M4KoybB6KWSeawgGrYZ4=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1 @@
4F01DA0921B4CC5D5073BCC7F5CACFD12A6A4883

View File

@@ -1,18 +1,18 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICWDCCAUACAQAwEzERMA8GA1UEAwwITW9jayBEU0MwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDNAbRAvBrCOUcQpP7wrJ5faC7qxNq14sBfP6K2G7Tp
j4BgeTkCgMAcOl4Ue1M+zhtYRo3pTnidLOvcy07lX1QMV1jwo/RLZYCVQoyqW/Lq
m8KvVXOOJrAzkdowcyOaOLxFyWIoaWj9ZSuPuzVntWA5n7u5MT1mYVO1sq27pXpp
1CwgDYifSLIcn3uKXGPy1CxlxUj/B3/3T0IrOEQkXqXJe7BMg1ijbjbJYoPK2Zoc
10vPbgdOXY/MwE6YxhpVo8Lzzq9E31kIq7Ovjrb6xT+kktSbXINX/3Htu8ELe6Ou
xAw+27CwaS/aCK/MJnHtRFzLyKvhsBf6BEq6ZgUSUfTVAgMBAAGgADANBgkqhkiG
9w0BAQsFAAOCAQEAXrunn244+vrEniiMFJ7si14YfKtnsE3goLAewViTfQHYkf/3
3v4rjYW0uWBjNVpgtLXAFcg2tl/xi0olngiITyj6f4CNWaoZdGTZ1HpeSAKKf/qR
jx8l09BnLp90xNTZOxjQNDGsnNs6Qx0D/jHEVk2ZeLy84clalIp7Y9VEvULR6uGt
PQrZSjNvzbseKA7UmFELak+6SotP9iG25UmaEB2V2XHZmMY5wiS9TjVqrSzpaTrt
B4A5aslqcpaWtXchX+Udg90c6aqFosAxfURbYW8Eq6FtU/PMbxZmIhdSW6tmmcCp
ee72V7kOyRp/GXAzlf2mjefkiOv0jbFlTpEBUg==
-----END CERTIFICATE REQUEST-----

View File

@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMk6l4jst9VsRv
1lLb8c88pqz6KJw932Tg11S2t9cB0r4bNPFjEiLZGfjWgIvGpO3Qj6lFjdDXUKZW
NgshJFt9It3fNVyzPU73/TCqyFIJCRw5Olv/Rbn5lfKUuSY/sK+0v7WdAq9zVhfl
rccL2LcsZ6gw4Is4ZTzS2lDHVRrjLyXEGO9OhKdbumVZsods6Gh3fb9jiQ9p0HDY
LphfgBWutl/CSEBxCMcnezebmwYU+/MQ0CCetSQQ6VOsM1s9y5S9ZJcA7mDPhUrC
YU7yPZnL0r2ciecq6TKJGJIzFxBcoRLqxAfxMoh/Ahrekw8c+ZMptgstUDYDsFQZ
ybt1QfebAgMBAAECggEAOj8ao+goOO4Q7PS9T8En65FbM7iHT7iArOGyTXnHXk4F
8ZIf3kDGKkEST+R2p57C1TxqChMoMH3WdjpZ7dw/rj/Uox464A+nIcV1a1KwxMDS
x9p9Yg+UtWLDmCOlJ8IjLNfemi8vDCwpjqvtPcq80IhM42mzSeHNMQ2WrUJmbuM1
0H8bFCRA1orqSYJz4tBVhlwtcEZFDstWgXZQ26kXkXcTtJVjfaJTAg6qwErsCUr8
hn6j3fzqOyhHqx2vHyvZns+ARchWHuMLQ5KLP16ijvDXuVHCMGv5FJT1CtUDG6n0
SY4nooaSviJoNJB2gg36KqUUwoGxMoGnreIAOIfH4QKBgQDmMXYfFGcBbWYRKX1D
ClaHfUlFR/7rZXhlVmKLgHMBR+OqCiqZbvorUbVAxpXw4IH+GNx695OqiQhLUOY7
yICpCt6Pq8gkfLYFQiXebevxfF60OB/NrbXICYtWyzqPAclBHauS4J9tDIYHfSJo
JD0MqJL37I0ctil0fNjmCVTJuwKBgQDjgwKxAvqvJX4En2+hrKG/aOmCXg5Wdn+3
wHvxu/3QRQJehbq/Qcz3MDXPATqOoH86SzFfjREMpsL3+FZYlN5cWK3siNDRq+Uz
O1JOPWB0ehMNCFX+UmaFdNfS1NB+oQ1QD+PVVRZUwDz5jdBKTXt4mtgUHbkYMFcs
xzVOecM7oQKBgQDKtWO/msfaiewH0PFtx/zK85lCWVQzFvgexSOilUqxJg1l/+3j
j8bdvuZOVJJVvF681EfUQZDX7DRRnrwgiUBDnD7BZhSpjOj9WHLgnQvZbBw5t/LJ
vN8HeOArXmwC4x/HJ8mfXg8GiLC+h4N1EfRw4UIK8VSSpBd9bTPT3wO7YQKBgHvE
FX6+JCmLt9ZD9bJ5+zPpmr409I/MvwXzTHngzt6x6+wHy+OnpB0EuQD3pidY4F2R
8jYMw62iOoYzLqXZtm1+QH9gnlixrmoEZ23wrbwvfJWmZ0GHCgan3ZDv/MwmSNu5
CTRQqWlJeuI3Z4xEQlsFwW6QDNU/1uyhIjqiuraBAoGACoHype8atLKYQsVMIqWE
t8+PeLJs1OKoJFCvTPaZ4uc5hj9q/Zwm1d2lXQHEys2fNnX04szAR4CqdMA+6vpE
pdoTmDwzpY1fjisWTZxuAKky1/MKoPx2vJefuC3P1EoLoIjOXxs8c1eIqmjtkvpr
AG9WTDrJOYamVzCLQ9mME/g=
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDNAbRAvBrCOUcQ
pP7wrJ5faC7qxNq14sBfP6K2G7Tpj4BgeTkCgMAcOl4Ue1M+zhtYRo3pTnidLOvc
y07lX1QMV1jwo/RLZYCVQoyqW/Lqm8KvVXOOJrAzkdowcyOaOLxFyWIoaWj9ZSuP
uzVntWA5n7u5MT1mYVO1sq27pXpp1CwgDYifSLIcn3uKXGPy1CxlxUj/B3/3T0Ir
OEQkXqXJe7BMg1ijbjbJYoPK2Zoc10vPbgdOXY/MwE6YxhpVo8Lzzq9E31kIq7Ov
jrb6xT+kktSbXINX/3Htu8ELe6OuxAw+27CwaS/aCK/MJnHtRFzLyKvhsBf6BEq6
ZgUSUfTVAgMBAAECggEAGlxMvEIzwqOw0qAUtAfOOYBhAAkeAmNMzzKUjowTgDDW
lhEgVoUNrtOGg2W0N5AzDe/MkaJoefVrwrFVzMKsQQX+RrseT4+WsBqlkcZO/wHW
T8tSF4Y8A/WOM8qqWktPUj3p5D5hpKucpVeyL0qwO9ihP9foCEEdjXCTTFyj1/WY
O7IdsKNDY+srTK73xWIEnyH+ffEKIaSu9wrntYASudllLO/PrxpxJ4M+/4/I5Cep
PzklQhEdv/5ler0uzHGc/RtVfibjpdEDVgsere9Nq+cXf0k4JrZZ8ySy3WHgAPWG
zWj3sduFW37Dsb/XvzuQ7oRg4v6lofaJSlaLxA2ExQKBgQD3eE75vjVQdTkKpFMQ
c//0nLRne09VbOqPHhpkyZC9FJJ+UkIRvlNbkBtHhhNTj/u6FnkaJGS3RONeDe0o
Q0fe16zY38q70HlulNo6h069ckhWrIA8qbrKntqxc2bVL5LDUoA/VmY5nk76L5Tu
7b22+ipcdij2ibU+r10T5X5/0wKBgQDUErJVj/dm52QI039VPyl1wMjGDeUvWZxs
8iT9FT58Kp/E/+fiZ8JbFqJyWSpEm5rZyK5P40USN1r/B2EdxHSyete52sko8Huh
Jku4DVStyIzq63d3gEpIqfb2rmMUlc707bNqdgOX4IZ8RYxFgnp2AB5X8e/LyHZR
nSFdADD3twKBgHw8PMGqGM/UmZdbGYxH//ZIeVT+FbzYGgVRYoS4oF4cJtMd7qdD
uktcVCWbDLbFsOUiBRf3r0xGXDLKoQW2iS05bz/NUdTp+xxlxAzgAsqexewlW5eY
yfqQt0+glL5vzKkcEOA7OsSwUgvNuWIdnkFu47dZZoUmawQWO48ShmgbAoGBAKlz
3wc7J8YTsfzTcfUdqPUr+8E1LSGuiq0Ktr96kBkKverdR93CZqv95ANWd82mQA8w
qQewY2pwzMbmkJUGevB585HP3dhWf5J+VRZVoInTq7WyPB1CZxi0pl7pbMXwBhPz
Mt8oOSrL/umhcLnBzjQnWBeRe7frD4+a7COxmW71AoGBAO0yhYXOeHpGTnHzDzTs
qu3rNhvQKy/8f25wvqn/xR5NfbcQv/Z63PgmDoq9VLrmeektwY4zTE9wO+n7WPQU
D2BiW2DQjX4OJsOPNs63+wCE71xj08ZxipFuxFWhbjtO/A82h6QMXYwfj9do1WGD
oouEG7wJwn8++cuiivyeTWf1
-----END PRIVATE KEY-----

View File

@@ -91,4 +91,26 @@ export function readCertificate(filePath: string): jsrsasign.X509 {
const certificate = new jsrsasign.X509();
certificate.readCertPEM(certPem);
return certificate;
}
export function getTBSCertificate(certificate: jsrsasign.X509): Buffer {
// console.log("Certificate:", certificate);
const certASN1 = certificate.getParam();
// console.log("certASN1:", certASN1);
if (!certASN1) {
console.error("Failed to get certificate parameters");
throw new Error("Invalid certificate structure");
}
// Extract the TBS part directly from the certificate's hex representation
const certHex = certificate.hex;
const tbsStartIndex = certHex.indexOf('30') + 2; // Start after the first sequence tag
const tbsLength = parseInt(certHex.substr(tbsStartIndex, 2), 16) * 2 + 2; // Length in bytes * 2 for hex + 2 for length field
const tbsHex = certHex.substr(tbsStartIndex - 2, tbsLength); // Include the sequence tag
// console.log("TBS Hex:", tbsHex);
return Buffer.from(tbsHex, 'hex');
}

View File

@@ -5,6 +5,7 @@ import { CSCA_AKI_MODULUS, CSCA_TREE_DEPTH } from "../constants/constants";
import { poseidon16, poseidon2, poseidon4 } from "poseidon-lite";
import { IMT } from "@zk-kit/imt";
import serialized_csca_tree from "../../pubkeys/serialized_csca_tree.json"
import { createHash } from "crypto";
export function findStartIndex(modulus: string, messagePadded: Uint8Array): number {
const modulusNumArray = [];
@@ -44,8 +45,12 @@ export function getCSCAInputs(dscSecret: string, dscCertificate: any, cscaCertif
if (devmod) {
console.log('DEV MODE');
//const csca_modulus_bigint = BigInt('0x' + csca_modulus);
console.log("certificate", cscaCertificate);
//console.log('csca_modulus_hex', cscaCertificate.getPublicKeyHex());
const rsaPublicKey = cscaCertificate.publicKey as forge.pki.rsa.PublicKey;
const csca_modulus = rsaPublicKey.n.toString(16).toLowerCase();
console.log('csca_modulus', csca_modulus);
csca_modulus_bigint = BigInt(`0x${csca_modulus}`);
csca_modulus_formatted = splitToWords(csca_modulus_bigint, BigInt(n_csca), BigInt(k_csca));
console.log('csca_modulus_formatted', csca_modulus_formatted);
@@ -105,6 +110,11 @@ export function getCSCAInputs(dscSecret: string, dscCertificate: any, cscaCertif
else if (signatureAlgorithm === '1.2.840.113549.1.1.11') { //sha256
[dsc_message_padded, dsc_messagePaddedLen] = sha256Pad(dsc_tbsCertificateUint8Array, max_cert_bytes);
}
else {
console.log("Signature algorithm not recognized", signatureAlgorithm);
[dsc_message_padded, dsc_messagePaddedLen] = sha256Pad(dsc_tbsCertificateUint8Array, max_cert_bytes);
}
const startIndex = findStartIndex(dsc_modulus, dsc_message_padded);
const startIndex_formatted = startIndex.toString();
const dsc_message_padded_formatted = Array.from(dsc_message_padded).map((x) => x.toString())
@@ -206,5 +216,18 @@ export function getCSCAModulusProof(leaf, n, k) {
return [tree.root, proof];
}
export function getTBSHash(cert: forge.pki.Certificate, hashAlgorithm: 'sha1' | 'sha256'): string[] {
const tbsCertAsn1 = forge.pki.certificateToAsn1(cert).value[0];
const tbsCertDer = forge.asn1.toDer(tbsCertAsn1 as any).getBytes();
const md = hashAlgorithm === 'sha256' ? forge.md.sha256.create() : forge.md.sha1.create();
md.update(tbsCertDer);
const tbsCertificateHash = md.digest();
const tbsCertificateHashString = tbsCertificateHash.data;
const tbsCertificateHashHex = Buffer.from(tbsCertificateHashString, 'binary').toString('hex');
const tbsCertificateHashBigint = BigInt(`0x${tbsCertificateHashHex}`);
console.log('tbsCertificateHashBigint', tbsCertificateHashBigint);
return splitToWords(tbsCertificateHashBigint, BigInt(64), BigInt(32));
}

View File

@@ -634,41 +634,41 @@ export const mockPassportData_sha1WithRSAEncryption_65537 = {
"mrz": "P<FRADUPONT<<ALPHONSE<HUGUES<ALBERT<<<<<<<<<24HB818324FRA0402111M3111115<<<<<<<<<<<<<<02",
"signatureAlgorithm": "sha1WithRSAEncryption",
"pubKey": {
"modulus": "22480969390802826527612289605322373709607796204484082125179082202842187594030746274096990975473157470353260434866140253775617322614317842584277684263273085398758828719210973208632246906520027587934588291311922909505699040207707335541543985272384000014886820430286901756121253512285729673528629390257652494355569005906835585173481884916905337685920438573395012275370654528706928278300240972594792647459747106552104398149544784541159688532413072278844759518831349873260668156132419311110193685220374735679580093984854335187305593318372186076577084719489194525709637470310818679179550246249200552494360645496955029908931",
"modulus": "25825429057059192136108879878584777378654757240338449850333826035522788688006003098652086918620774520803490546755795078682140839966138109158244063837927200135790195224745239859057220329386561601721351696226057564841628462419818778146307352956712449575319326790354320318538693617555205343313027743984395735323775616464067940972666996572033472954611814220870194705592266623708066532169995623347732878333242475140240755230523341780618713394357686812647104201915704768910310961389728873036708721827192621939370755343467271300872482189554492692990185310969209988312747691889806959985764889980641836439721025225230179760027",
"exponent": "65537"
},
"dataGroupHashes": [
-31,
-62,
104,
-64,
-48,
18,
27,
-5,
-7,
21,
125,
26,
-46,
-69,
-48,
30,
-72,
-105,
104,
58,
-127,
-82,
12,
-127,
-122,
96,
100,
24,
117,
-114,
37,
-38,
-53,
-28,
-75,
9,
105,
-62,
-46,
-40,
115,
-43,
29,
31,
-120,
0,
55,
123,
95,
-5,
-51,
-36,
-14,
-55,
-81,
-64,
72,
76,
1,
50,
-96,
114,
-93,
@@ -823,284 +823,284 @@ export const mockPassportData_sha1WithRSAEncryption_65537 = {
34,
4,
32,
-110,
20,
-102,
-44,
16,
-39,
107,
108,
84,
50,
-21,
-116,
-87,
-49,
5,
93,
-1,
58,
-111,
1,
94,
-94,
0,
-44,
-10,
92,
-28
45,
51,
-75,
9,
107,
0,
93,
73,
104,
-127,
-71
],
"encryptedDigest": [
67,
155,
237,
112,
248,
146,
216,
126,
42,
186,
213,
89,
48,
103,
191,
192,
32,
176,
46,
109,
111,
67,
91,
160,
195,
150,
60,
147,
128,
218,
108,
51,
142,
24,
201,
225,
218,
137,
57,
75,
102,
20,
66,
168,
4,
97,
54,
26,
195,
211,
62,
195,
80,
212,
18,
58,
142,
82,
186,
15,
175,
188,
201,
62,
203,
25,
98,
232,
212,
24,
166,
248,
52,
194,
23,
202,
253,
117,
248,
7,
38,
34,
6,
167,
61,
254,
213,
111,
35,
61,
97,
174,
91,
68,
128,
82,
90,
75,
125,
170,
7,
5,
38,
227,
175,
176,
1,
223,
90,
255,
205,
79,
29,
132,
151,
35,
92,
61,
48,
156,
40,
135,
179,
20,
250,
169,
0,
76,
115,
46,
253,
176,
244,
162,
245,
22,
247,
166,
51,
90,
99,
44,
197,
6,
63,
188,
4,
9,
245,
124,
114,
239,
29,
170,
153,
142,
169,
43,
85,
234,
203,
220,
110,
16,
109,
189,
63,
157,
249,
201,
109,
83,
251,
142,
35,
5,
211,
60,
126,
28,
28,
30,
205,
18,
237,
131,
129,
223,
63,
83,
61,
100,
85,
90,
167,
46,
180,
52,
145,
184,
97,
2,
111,
162,
47,
21,
106,
219,
170,
210,
33,
66,
163,
144,
138,
46,
108,
25,
11,
222,
64,
97,
21,
28,
113,
200,
170,
83,
156,
16,
31,
48,
4,
210,
158,
145,
129,
189,
133,
48,
174,
175,
182,
229,
3,
156,
166,
114,
195,
60,
219,
127,
8,
214,
77,
213,
245,
177,
130,
237,
4,
44,
87,
28,
70,
24,
248,
5,
240,
162,
234,
93,
62,
124,
109,
165,
227,
92,
71,
199,
101,
98,
15,
132,
23,
191,
36,
158,
211,
56,
127,
72,
134,
133,
217,
210,
217,
34,
3,
77,
16,
139,
55,
25,
8,
83,
161,
2,
179,
201,
57,
19,
233,
67,
225,
54,
195,
226,
16,
22,
130,
96,
18,
206,
170,
208,
171,
126,
180,
137,
62,
195,
226,
206,
111,
82,
171,
199,
128,
147,
186
156,
156,
32,
5,
175,
187,
135,
227,
51,
0,
228,
175,
251,
2,
231,
85,
168,
160,
9,
133,
158,
175,
135,
42,
59,
13,
113,
211,
132,
92,
140,
106,
36,
191,
109,
213,
214,
57,
76,
35,
132,
176,
116,
130,
212,
101,
115,
28,
116,
203,
9,
98,
10,
68,
219,
128,
68,
110,
177,
175,
210,
80,
144,
65,
236,
128,
249,
219,
189,
216,
36,
234,
61,
75,
53,
101,
224,
126,
82,
207,
65,
182,
254,
133,
251,
103,
219,
207,
17,
172,
26,
139,
120,
184,
183,
176,
192,
102,
19,
184,
252,
146,
175,
169,
252,
3,
170,
141,
13,
136,
248,
94,
237,
158,
114,
149,
155,
241,
169,
161,
99,
239,
55,
30,
125,
219,
65,
43,
64,
12,
243,
138,
114,
150,
32,
165,
220,
91,
199,
176,
30,
156,
10,
199,
196,
152,
4,
194,
145,
163,
66,
212,
239,
164,
248,
242,
26,
51,
235,
101,
95,
133,
142,
189,
226,
195
],
"photoBase64": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABjElEQVR42mL8//8/AyUYiBQYmIw3..."
}