Merge pull request #310 from zk-passport/fix/change-lower-dependency

Fix/change lower dependency
This commit is contained in:
turnoffthiscomputer
2025-01-16 22:43:01 +01:00
committed by GitHub
8 changed files with 188 additions and 122 deletions

View File

@@ -0,0 +1,13 @@
pragma circom 2.1.9;
include "../../../utils/crypto/signature/rsa/verifyRsa65537Pkcs1v1_5.circom";
template VerifyRsaPkcs1v1_5Tester() {
signal input signature[35];
signal input modulus[35];
signal input message[35];
VerifyRsa65537Pkcs1v1_5(120,35,224)(signature, modulus, message);
}
component main = VerifyRsaPkcs1v1_5Tester();

View File

@@ -3,10 +3,12 @@ pragma circom 2.1.9;
include "circomlib/circuits/bitify.circom";
// PKCS1v1.5 Padding Scheme
// Reference: https://datatracker.ietf.org/doc/html/rfc8017#section-9.2
// 0x00 || 0x01 || PS || 0x00 || OID || Hash
// PS is a sequence of 0xFF bytes that is padded so that the data to be signed matches the length of the key.
// OID is the object identifier for the hash function used.
// For SHA1, the OID is 0x3021300906052b0e03021a05000414
// For SHA224, the OID is 0x302d300d06096086480165030402040500041c
// For SHA256, the OID is 0x3031300d060960864801650304020105000420
// For SHA384, the OID is 0x3041300d060960864801650304020205000430
// For SHA512, the OID is 0x3051300d060960864801650304020305000440
@@ -105,6 +107,9 @@ function getOID(HASH_SIZE) {
if (HASH_SIZE == 160) {
return 0x3021300906052b0e03021a05000414;
}
if (HASH_SIZE == 224) {
return 0x302d300d06096086480165030402040500041c;
}
if (HASH_SIZE == 256) {
return 0x3031300d060960864801650304020105000420;
}
@@ -125,6 +130,9 @@ function getOIDSize(HASH_SIZE) {
if (HASH_SIZE == 160) {
return 120;
}
if (HASH_SIZE == 224) {
return 152;
}
if (HASH_SIZE == 256) {
return 152;
}

View File

@@ -1,6 +1,6 @@
pragma circom 2.1.9;
include "@zk-email/circuits/lib/fp.circom";
include "@zk-email/circuits/lib/bigint.circom";
include "./pkcs1v1_5Padding.circom";
include "../FpPowMod.circom";

View File

@@ -33,6 +33,11 @@ export const generateMockRsaPkcs1v1_5Inputs = (signatureAlgorithm: SignatureAlgo
signAlgorithm = signatureAlgorithm.includes('sha256') ? 'sha256' : 'sha512';
publicExponent = 65537;
break;
case 'rsa_sha224_65537_2048':
modulusLength = 2048;
signAlgorithm = 'sha224';
publicExponent = 65537;
break;
default:
throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm}`);
}

View File

@@ -3,7 +3,7 @@ import { describe, it } from 'mocha';
import path from 'path';
import { generateMockRsaPkcs1v1_5Inputs } from './generateMockInputsInCircuits';
import { SignatureAlgorithm } from '../../../common/src/utils/types';
import { expect } from 'chai';
describe('VerifyRsaPkcs1v1_5 Circuit Test', function () {
this.timeout(0);
/** Some tests are disabled to avoid overloading the CI/CD pipeline - the commented rsa verifications will however be tested in prove.test.ts and dsc.test.ts **/
@@ -14,6 +14,7 @@ describe('VerifyRsaPkcs1v1_5 Circuit Test', function () {
'rsa_sha256_65537_3072',
'rsa_sha256_65537_4096',
'rsa_sha512_65537_4096',
'rsa_sha224_65537_2048',
];
rsaAlgorithms.forEach((algorithm) => {
@@ -41,5 +42,49 @@ describe('VerifyRsaPkcs1v1_5 Circuit Test', function () {
// Check constraints
await circuit.checkConstraints(witness);
});
it('Should fail to verify RSA signature with invalid signature', async function () {
const { signature, modulus, message } = generateMockRsaPkcs1v1_5Inputs(algorithm);
const invalidSignature = signature.map((byte: string) => String((parseInt(byte) + 1) % 256));
const circuit = await wasmTester(
path.join(__dirname, `../../circuits/tests/utils/rsa/test_${algorithm}.circom`),
{
include: ['node_modules', './node_modules/@zk-kit/binary-merkle-root.circom/src'],
}
);
try {
await circuit.calculateWitness({
signature: invalidSignature,
modulus,
message,
});
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
it('Should fail to verify RSA signature with invalid message', async function () {
const { signature, modulus, message } = generateMockRsaPkcs1v1_5Inputs(algorithm);
const invalidMessage = message.map((byte: string) => String((parseInt(byte) + 1) % 256));
const circuit = await wasmTester(
path.join(__dirname, `../../circuits/tests/utils/rsa/test_${algorithm}.circom`),
{
include: ['node_modules', './node_modules/@zk-kit/binary-merkle-root.circom/src'],
}
);
try {
await circuit.calculateWitness({
signature,
modulus,
message: invalidMessage,
});
} catch (error) {
expect(error.message).to.include('Assert Failed');
}
});
});
});

View File

@@ -223,8 +223,8 @@ export function generateCircuitInputsProve(
const dg1PaddingFunction =
passportMetadata.dg1HashFunction === 'sha1' ||
passportMetadata.dg1HashFunction === 'sha224' ||
passportMetadata.dg1HashFunction === 'sha256'
passportMetadata.dg1HashFunction === 'sha224' ||
passportMetadata.dg1HashFunction === 'sha256'
? shaPad
: sha384_512Pad;
@@ -235,8 +235,8 @@ export function generateCircuitInputsProve(
const eContentPaddingFunction =
passportMetadata.eContentHashFunction === 'sha1' ||
passportMetadata.eContentHashFunction === 'sha224' ||
passportMetadata.eContentHashFunction === 'sha256'
passportMetadata.eContentHashFunction === 'sha224' ||
passportMetadata.eContentHashFunction === 'sha256'
? shaPad
: sha384_512Pad;
const [signedAttrPadded, signedAttrPaddedLen] = eContentPaddingFunction(

View File

@@ -2,153 +2,147 @@ import { PassportData } from '../../../common/src/utils/types';
import { findSubarrayIndex, formatMrz, getHashLen, hash } from './utils';
import { parseCertificateSimple } from './certificate_parsing/parseCertificateSimple';
import {
CertificateData,
PublicKeyDetailsECDSA,
PublicKeyDetailsRSA,
PublicKeyDetailsRSAPSS,
CertificateData,
PublicKeyDetailsECDSA,
PublicKeyDetailsRSA,
PublicKeyDetailsRSAPSS,
} from './certificate_parsing/dataStructure';
import { hashAlgos } from '../constants/constants';
import { brutforceSignatureAlgorithm } from './brutForcePassportSignature';
import { DscCertificateMetaData, parseDscCertificateData } from './parseDscCertificateData';
export interface PassportMetadata {
dataGroups: string;
dg1HashFunction: string;
dg1HashOffset: number;
dgPaddingBytes: number;
eContentSize: number;
eContentHashFunction: string;
eContentHashOffset: number;
signedAttrSize: number;
signedAttrHashFunction: string;
signatureAlgorithm: string;
saltLength: number;
curveOrExponent: string;
signatureAlgorithmBits: number;
countryCode: string;
cscaFound: boolean;
cscaHashFunction: string;
cscaSignature: string;
cscaSaltLength: number;
cscaCurveOrExponent: string;
cscaSignatureAlgorithmBits: number;
dsc: string;
dataGroups: string;
dg1HashFunction: string;
dg1HashOffset: number;
dgPaddingBytes: number;
eContentSize: number;
eContentHashFunction: string;
eContentHashOffset: number;
signedAttrSize: number;
signedAttrHashFunction: string;
signatureAlgorithm: string;
saltLength: number;
curveOrExponent: string;
signatureAlgorithmBits: number;
countryCode: string;
cscaFound: boolean;
cscaHashFunction: string;
cscaSignature: string;
cscaSaltLength: number;
cscaCurveOrExponent: string;
cscaSignatureAlgorithmBits: number;
dsc: string;
}
function findHashSizeOfEContent(eContent: number[], signedAttr: number[]) {
for (const hashFunction of hashAlgos) {
const hashValue = hash(hashFunction, eContent);
const hashOffset = findSubarrayIndex(signedAttr, hashValue as number[]);
if (hashOffset !== -1) {
return { hashFunction, offset: hashOffset };
}
for (const hashFunction of hashAlgos) {
const hashValue = hash(hashFunction, eContent);
const hashOffset = findSubarrayIndex(signedAttr, hashValue as number[]);
if (hashOffset !== -1) {
return { hashFunction, offset: hashOffset };
}
return { hashFunction: 'unknown', offset: -1 };
}
return { hashFunction: 'unknown', offset: -1 };
}
function findDG1HashInEContent(
mrz: string,
eContent: number[]
mrz: string,
eContent: number[]
): { hash: number[]; hashFunction: string; offset: number } | null {
const formattedMrz = formatMrz(mrz);
const formattedMrz = formatMrz(mrz);
for (const hashFunction of hashAlgos) {
const hashValue = hash(hashFunction, formattedMrz);
const normalizedHash = (hashValue as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const hashOffset = findSubarrayIndex(eContent, normalizedHash);
for (const hashFunction of hashAlgos) {
const hashValue = hash(hashFunction, formattedMrz);
const normalizedHash = (hashValue as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const hashOffset = findSubarrayIndex(eContent, normalizedHash);
if (hashOffset !== -1) {
return { hash: hashValue as number[], hashFunction, offset: hashOffset };
}
if (hashOffset !== -1) {
return { hash: hashValue as number[], hashFunction, offset: hashOffset };
}
return null;
}
return null;
}
function getDgPaddingBytes(passportData: PassportData, dg1HashFunction: string): number {
const formattedMrz = formatMrz(passportData.mrz);
const hashValue = hash(dg1HashFunction, formattedMrz);
const normalizedHash = (hashValue as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const dg1HashOffset = findSubarrayIndex(passportData.eContent, normalizedHash);
const dg2Hash = passportData.dg2Hash;
const normalizedDg2Hash = (dg2Hash as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const dg2HashOffset = findSubarrayIndex(passportData.eContent, normalizedDg2Hash);
return dg2HashOffset - dg1HashOffset - getHashLen(dg1HashFunction);
const formattedMrz = formatMrz(passportData.mrz);
const hashValue = hash(dg1HashFunction, formattedMrz);
const normalizedHash = (hashValue as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const dg1HashOffset = findSubarrayIndex(passportData.eContent, normalizedHash);
const dg2Hash = passportData.dg2Hash;
const normalizedDg2Hash = (dg2Hash as number[]).map((byte) => (byte > 127 ? byte - 256 : byte));
const dg2HashOffset = findSubarrayIndex(passportData.eContent, normalizedDg2Hash);
return dg2HashOffset - dg1HashOffset - getHashLen(dg1HashFunction);
}
export function getCountryCodeFromMrz(mrz: string): string {
return mrz.substring(2, 5);
return mrz.substring(2, 5);
}
export function getCurveOrExponent(certData: CertificateData): string {
if (certData.signatureAlgorithm === 'rsapss' || certData.signatureAlgorithm === 'rsa') {
return (certData.publicKeyDetails as PublicKeyDetailsRSA).exponent;
}
return (certData.publicKeyDetails as PublicKeyDetailsECDSA).curve;
if (certData.signatureAlgorithm === 'rsapss' || certData.signatureAlgorithm === 'rsa') {
return (certData.publicKeyDetails as PublicKeyDetailsRSA).exponent;
}
return (certData.publicKeyDetails as PublicKeyDetailsECDSA).curve;
}
export function parsePassportData(passportData: PassportData): PassportMetadata {
const dg1HashInfo = passportData.mrz
? findDG1HashInEContent(passportData.mrz, passportData.eContent)
: null;
const dg1HashInfo = passportData.mrz
? findDG1HashInEContent(passportData.mrz, passportData.eContent)
: null;
const dg1HashFunction = dg1HashInfo?.hashFunction || 'unknown';
const dg1HashOffset = dg1HashInfo?.offset || 0;
let dgPaddingBytes = -1;
try {
dgPaddingBytes = getDgPaddingBytes(passportData, dg1HashFunction);
} catch (error) {
console.error('Error getting DG padding bytes:', error);
}
const { hashFunction: eContentHashFunction, offset: eContentHashOffset } = findHashSizeOfEContent(
passportData.eContent,
passportData.signedAttr
);
const dg1HashFunction = dg1HashInfo?.hashFunction || 'unknown';
const dg1HashOffset = dg1HashInfo?.offset || 0;
let dgPaddingBytes = -1;
try {
dgPaddingBytes = getDgPaddingBytes(passportData, dg1HashFunction);
} catch (error) {
console.error('Error getting DG padding bytes:', error);
}
const { hashFunction: eContentHashFunction, offset: eContentHashOffset } = findHashSizeOfEContent(
passportData.eContent,
passportData.signedAttr
);
const brutForcedPublicKeyDetails = brutforceSignatureAlgorithm(passportData);
const brutForcedPublicKeyDetails = brutforceSignatureAlgorithm(passportData);
let parsedDsc = null;
let dscSignatureAlgorithmBits = 0;
let parsedDsc = null;
let dscSignatureAlgorithmBits = 0;
let brutForcedPublicKeyDetailsDsc: DscCertificateMetaData;
let brutForcedPublicKeyDetailsDsc: DscCertificateMetaData;
if (passportData.dsc) {
parsedDsc = parseCertificateSimple(passportData.dsc);
dscSignatureAlgorithmBits = parseInt(parsedDsc.publicKeyDetails?.bits || '0');
if (passportData.dsc) {
parsedDsc = parseCertificateSimple(passportData.dsc);
dscSignatureAlgorithmBits = parseInt(parsedDsc.publicKeyDetails?.bits || '0');
brutForcedPublicKeyDetailsDsc = parseDscCertificateData(parsedDsc)
brutForcedPublicKeyDetailsDsc = parseDscCertificateData(parsedDsc);
}
}
return {
dataGroups:
passportData.dgPresents
?.toString()
.split(',')
.map((item) => item.replace('DG', ''))
.join(',') || 'None',
dg1HashFunction,
dg1HashOffset,
dgPaddingBytes,
eContentSize: passportData.eContent?.length || 0,
eContentHashFunction,
eContentHashOffset,
signedAttrSize: passportData.signedAttr?.length || 0,
signedAttrHashFunction: brutForcedPublicKeyDetails.hashAlgorithm,
signatureAlgorithm: brutForcedPublicKeyDetails.signatureAlgorithm,
saltLength: brutForcedPublicKeyDetails.saltLength,
curveOrExponent: parsedDsc ? getCurveOrExponent(parsedDsc) : 'unknown',
signatureAlgorithmBits: dscSignatureAlgorithmBits,
countryCode: passportData.mrz ? getCountryCodeFromMrz(passportData.mrz) : 'unknown',
cscaFound: brutForcedPublicKeyDetailsDsc.cscaFound,
cscaHashFunction: brutForcedPublicKeyDetailsDsc.cscaHashAlgorithm,
cscaSignature: brutForcedPublicKeyDetailsDsc.cscaSignatureAlgorithm,
cscaSaltLength: brutForcedPublicKeyDetailsDsc.cscaSaltLength,
cscaCurveOrExponent: brutForcedPublicKeyDetailsDsc.cscaCurveOrExponent,
cscaSignatureAlgorithmBits: brutForcedPublicKeyDetailsDsc.cscaSignatureAlgorithmBits,
dsc: passportData.dsc,
};
}
return {
dataGroups:
passportData.dgPresents
?.toString()
.split(',')
.map((item) => item.replace('DG', ''))
.join(',') || 'None',
dg1HashFunction,
dg1HashOffset,
dgPaddingBytes,
eContentSize: passportData.eContent?.length || 0,
eContentHashFunction,
eContentHashOffset,
signedAttrSize: passportData.signedAttr?.length || 0,
signedAttrHashFunction: brutForcedPublicKeyDetails.hashAlgorithm,
signatureAlgorithm: brutForcedPublicKeyDetails.signatureAlgorithm,
saltLength: brutForcedPublicKeyDetails.saltLength,
curveOrExponent: parsedDsc ? getCurveOrExponent(parsedDsc) : 'unknown',
signatureAlgorithmBits: dscSignatureAlgorithmBits,
countryCode: passportData.mrz ? getCountryCodeFromMrz(passportData.mrz) : 'unknown',
cscaFound: brutForcedPublicKeyDetailsDsc.cscaFound,
cscaHashFunction: brutForcedPublicKeyDetailsDsc.cscaHashAlgorithm,
cscaSignature: brutForcedPublicKeyDetailsDsc.cscaSignatureAlgorithm,
cscaSaltLength: brutForcedPublicKeyDetailsDsc.cscaSaltLength,
cscaCurveOrExponent: brutForcedPublicKeyDetailsDsc.cscaCurveOrExponent,
cscaSignatureAlgorithmBits: brutForcedPublicKeyDetailsDsc.cscaSignatureAlgorithmBits,
dsc: passportData.dsc,
};
}

View File

@@ -31,6 +31,7 @@ export type SignatureAlgorithm =
| 'rsa_sha256_65537_3072'
| 'rsa_sha256_65537_4096'
| 'rsa_sha512_65537_4096'
| 'rsa_sha224_65537_2048'
| 'rsapss_sha256_65537_3072'
| 'rsapss_sha256_65537_4096'
| 'rsapss_sha256_3_2048'
@@ -46,7 +47,7 @@ export type SignatureAlgorithm =
| 'ecdsa_sha1_brainpoolP224r1_224'
| 'ecdsa_sha224_brainpoolP224r1_224'
| 'ecdsa_sha256_brainpoolP224r1_224'
| 'ecdsa_sha512_brainpoolP512r1_512'
| 'ecdsa_sha512_brainpoolP512r1_512';
export type Proof = {
proof: {