Skip to content

Commit

Permalink
Merge pull request #118 from zk-passport/better-mock-passport-data
Browse files Browse the repository at this point in the history
Better mock passport data
  • Loading branch information
0xturboblitz authored Jun 2, 2024
2 parents 9ecdc4b + 9101f6a commit 57ec1c3
Show file tree
Hide file tree
Showing 8 changed files with 575 additions and 65 deletions.
12 changes: 10 additions & 2 deletions app/src/utils/nfcScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ const handleResponseIOS = async (
};
amplitude.track('Sig alg after conversion: ' + passportData.signatureAlgorithm);

console.log('passportData', JSON.stringify({
...passportData,
photoBase64: passportData.photoBase64.substring(0, 100) + '...'
}, null, 2));

console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
Expand All @@ -171,8 +176,6 @@ const handleResponseIOS = async (
console.log('encryptedDigest', [...passportData.encryptedDigest.slice(0, 10), '...']);
console.log("photoBase64", passportData.photoBase64.substring(0, 100) + '...')

// console.log('passportData', JSON.stringify(passportData, null, 2));

useUserStore.getState().registerPassportData(passportData)
useNavigationStore.getState().setStep(Steps.NFC_SCAN_COMPLETED);
} catch (e: any) {
Expand Down Expand Up @@ -224,6 +227,11 @@ const handleResponseAndroid = async (
};
amplitude.track('Sig alg after conversion: ' + passportData.signatureAlgorithm);

console.log('passportData', JSON.stringify({
...passportData,
photoBase64: passportData.photoBase64.substring(0, 100) + '...'
}, null, 2));

console.log('mrz', passportData.mrz);
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
console.log('pubKey', passportData.pubKey);
Expand Down
9 changes: 9 additions & 0 deletions circuits/scripts/genMockPassportData/README.md
Original file line number Diff line number Diff line change
@@ -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!
122 changes: 122 additions & 0 deletions circuits/scripts/genMockPassportData/rsassaPss_65537.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import assert from "assert";
import { PassportData } from "../../../common/src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, arraysAreEqual } from "../../../common/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_256 = [
[
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]
]
]
const signatureAlgorithm = 'rsassaPss'

export function genMockPassportData_rsassaPss_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));
sampleDataHashes_256.unshift([1, mrzHash]);
const concatenatedDataHashes = formatAndConcatenateDataHashes(
mrzHash,
sampleDataHashes_256 as [number, number[]][],
);

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, 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);

console.log("mrzHash", mrzHash);
assert(
arraysAreEqual(mrzHash, dataGroupHashes.slice(31, 31 + mrzHash.length)),
'mrzHash is at the right place in dataGroupHashes'
);

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";

assert(Buffer.isBuffer(rsa_public));
assert.strictEqual(typeof hash_algorithm, "string");
assert(Buffer.isBuffer(message));
assert(Buffer.isBuffer(signature));

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_rsassaPss_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));
215 changes: 215 additions & 0 deletions circuits/scripts/genMockPassportData/rsassaPss_65537_full_form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// 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 "../../../common/src/utils/types";
import { hash, assembleEContent, formatAndConcatenateDataHashes, formatMrz, arraysAreEqual } from "../../../common/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_256 = [
[
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]
]
]
const signatureAlgorithm = 'rsassaPss'

export function genMockPassportData_rsassaPss_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));
sampleDataHashes_256.unshift([1, mrzHash]);
const concatenatedDataHashes = formatAndConcatenateDataHashes(
mrzHash,
sampleDataHashes_256 as [number, number[]][],
);

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);

console.log("mrzHash", mrzHash);
assert(
arraysAreEqual(mrzHash, dataGroupHashes.slice(31, 31 + mrzHash.length)),
'mrzHash is at the right place in dataGroupHashes'
);

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_rsassaPss_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));

Loading

0 comments on commit 57ec1c3

Please sign in to comment.