-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #118 from zk-passport/better-mock-passport-data
Better mock passport data
- Loading branch information
Showing
8 changed files
with
575 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
122
circuits/scripts/genMockPassportData/rsassaPss_65537.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
215
circuits/scripts/genMockPassportData/rsassaPss_65537_full_form.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
|
Oops, something went wrong.