From b92cd0e22d427abee3841ae8da75186e5b64c0a8 Mon Sep 17 00:00:00 2001 From: Julien Fraichot Date: Thu, 5 Dec 2024 17:28:32 +0100 Subject: [PATCH] feat(DataIntegrity): support challenge verification --- src/data/i18n.json | 6 +++++ src/index.ts | 7 +++++- src/inspectors/assertProofValidity.ts | 21 ++++++++++++++-- .../data-integrity-proof-support.test.ts | 24 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/data/i18n.json b/src/data/i18n.json index 46f2010..3b44023 100644 --- a/src/data/i18n.json +++ b/src/data/i18n.json @@ -4,6 +4,7 @@ "assertProofValidityPurposeVerifier": "Invalid proof purpose. Expected ${expectedProofPurpose} but received ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "The verification method ${proof.verificationMethod} is not allowed for the proof purpose ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "The proof is not authorized for this domain", + "assertProofValidityInvalidChallenge":"The proof's challenge does not match the verifier's challenge", "ensureHashesEqual": "Computed hash does not match remote hash", "ensureMerkleRootEqual": "Merkle root does not match remote hash.", "ensureValidReceipt": "The receipt is malformed. There was a problem navigating the merkle tree in the receipt.", @@ -23,6 +24,7 @@ "assertProofValidityPurposeVerifier": "Objectif de la signature invalide. Attendu: ${expectedProofPurpose} mais la signature a un objectif de ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "La méthode de vérification ${proof.verificationMethod} n'est pas autorisée pour l'objectif de verification: ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "La signature n'est pas autorisée pour ce domaine", + "assertProofValidityInvalidChallenge": "Le challenge de la signature ne correspond pas au challenge de vérification", "ensureHashesEqual": "Calcul du hash local différent du hash distant", "ensureMerkleRootEqual": "Le Merkle root ne correspond pas au hash distant", "ensureValidReceipt": "Erreur d'écriture du reçu. Un problème est survenu lors de la navigation de l'arbre Merkle du reçu.", @@ -42,6 +44,7 @@ "assertProofValidityPurposeVerifier": "Objectivo de verificación invalido. Se esperaba ${expectedProofPurpose} pero recibió ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "El método de verificación ${proof.verificationMethod} no está permitido para el objectivo de verificación ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "La prueba no está autorizada para este dominio", + "assertProofValidityInvalidChallenge": "El reto de la prueba no coincide con el reto de verificación", "ensureHashesEqual": "La cadena binaria calculada no corresponde con la cadena binaria remota", "ensureMerkleRootEqual": "La raíz Merkle no corresponde con la cadena binaria remota", "ensureValidReceipt": "El recibo está malformado. Hubo un problema navegando el árbol Merkle en el recibo", @@ -61,6 +64,7 @@ "assertProofValidityPurposeVerifier": "Invalid proof purpose. Expected ${expectedProofPurpose} but received ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "The verification method ${proof.verificationMethod} is not allowed for the proof purpose ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "The proof is not authorized for this domain", + "assertProofValidityInvalidChallenge": "The proof's challenge does not match the verifier's challenge", "ensureHashesEqual": "Il-hash ikkalkulat ma jikkorrispondix mar-remote hash", "ensureMerkleRootEqual": "Merkle root ma taqbilx mar-remote hash", "ensureValidReceipt": "L-irċevuta hija malformata. Kien hemm problema fin-navigazzjoni tal-merkle tree fl-irċevuta", @@ -80,6 +84,7 @@ "assertProofValidityPurposeVerifier": "Invalid proof purpose. Expected ${expectedProofPurpose} but received ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "The verification method ${proof.verificationMethod} is not allowed for the proof purpose ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "The proof is not authorized for this domain", + "assertProofValidityInvalidChallenge": "The proof's challenge does not match the verifier's challenge", "ensureHashesEqual": "L'hash calcolato non corrisponde all'hash remoto", "ensureMerkleRootEqual": "La radice di Merkle non corrisponde all'hash remoto.", "ensureValidReceipt": "La ricevuta è malformata. C'è stato un problema nella navigazione dell'albero di Merkle nella ricevuta.", @@ -99,6 +104,7 @@ "assertProofValidityPurposeVerifier": "Invalid proof purpose. Expected ${expectedProofPurpose} but received ${proof.proofPurpose}", "assertProofValidityPurposeIssuerKey": "The verification method ${proof.verificationMethod} is not allowed for the proof purpose ${expectedProofPurpose}", "assertProofValidityDomainVerifier": "The proof is not authorized for this domain", + "assertProofValidityInvalidChallenge": "The proof's challenge does not match the verifier's challenge", "ensureHashesEqual": "算出されたハッシュがリモートハッシュと一致しませんでした", "ensureMerkleRootEqual": "Merkle rootがリモートハッシュと一致しませんでした", "ensureValidReceipt": "レシートが異常です。レシート内のMerkle treeを辿る際に問題が発生しました。", diff --git a/src/index.ts b/src/index.ts index d34c746..f84ee1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,6 +38,7 @@ export interface MerkleProof2019API { // the purpose of proof that the verifier will be used for, defaults to assertionMethod proofPurpose?: string; domain?: string | string[]; + challenge?: string; } export interface MerkleProof2019VerificationResult { @@ -60,6 +61,7 @@ export class LDMerkleProof2019 extends LinkedDataProof { * using a context different from security-v2). * @param [document] {document} document used and signed by the MerkleProof2019 signature */ + public challenge: string; public domain: string[]; public type: string = 'MerkleProof2019'; public issuer: any = null; // TODO: define issuer type @@ -98,7 +100,8 @@ export class LDMerkleProof2019 extends LinkedDataProof { proof = null, options = {}, proofPurpose = 'assertionMethod', - domain = [] + domain = [], + challenge = '' }: MerkleProof2019API) { super({ type: 'MerkleProof2019' }); @@ -111,6 +114,7 @@ export class LDMerkleProof2019 extends LinkedDataProof { this.document = document; this.proofPurpose = proofPurpose; this.domain = Array.isArray(domain) ? domain : [domain]; + this.challenge = challenge; this.setProof(proof); this.setOptions(options); this.getChain(); @@ -245,6 +249,7 @@ export class LDMerkleProof2019 extends LinkedDataProof { () => assertProofValidity({ expectedProofPurpose: this.proofPurpose, expectedDomain: this.domain, + expectedChallenge: this.challenge, proof: this.proof, issuer: this.issuer }), diff --git a/src/inspectors/assertProofValidity.ts b/src/inspectors/assertProofValidity.ts index bbdfc07..28a8b23 100644 --- a/src/inspectors/assertProofValidity.ts +++ b/src/inspectors/assertProofValidity.ts @@ -22,9 +22,14 @@ function assertProofPurposeValidity ({ expectedProofPurpose, proof, issuer }): v .replace('${expectedProofPurpose}', expectedProofPurpose) ); } + + if (expectedProofPurpose === 'authentication' && !proof.domain) { + // TODO: return actual warning + console.warn('No domain found in proof, but it is recommended for authentication purposes'); + } } -function assertProofDomain ({ expectedDomain, proof }): void { +function assertProofDomain ({ expectedDomain, proof, expectedChallenge }): void { if (!expectedDomain.includes(proof.domain)) { throw new VerifierError('assertProofValidity', getText('errors', 'assertProofValidityDomainVerifier') @@ -34,11 +39,22 @@ function assertProofDomain ({ expectedDomain, proof }): void { .replace('${proof.domain}', proof.domain) ); } + + if (proof.domain && !proof.challenge) { + // TODO: return actual warning + console.warn('No challenge found in proof, but it is recommended for domain verification'); + } + + if (proof.challenge && proof.challenge !== expectedChallenge) { + throw new VerifierError('assertProofValidity', + getText('errors', 'assertProofValidityInvalidChallenge')); + } } interface AssertProofValidityAPI { expectedProofPurpose: string; expectedDomain: string[]; + expectedChallenge: string; proof: VCProof; issuer: any; } @@ -46,6 +62,7 @@ interface AssertProofValidityAPI { export default function assertProofValidity ({ expectedProofPurpose, expectedDomain, + expectedChallenge, proof, issuer }: AssertProofValidityAPI): boolean { @@ -54,7 +71,7 @@ export default function assertProofValidity ({ } if (proof.domain) { - assertProofDomain({ expectedDomain, proof }); + assertProofDomain({ expectedDomain, proof, expectedChallenge }); } return true; diff --git a/tests/verification/data-integrity-proof-support.test.ts b/tests/verification/data-integrity-proof-support.test.ts index 8f2d9ab..f37ca77 100644 --- a/tests/verification/data-integrity-proof-support.test.ts +++ b/tests/verification/data-integrity-proof-support.test.ts @@ -78,4 +78,28 @@ describe('given the document is signed following the DataIntegrityProof spec', f }); }); }); + + describe('given the challenge provided by the proof does not match the verifier\'s challenge', function () { + it('should throw an error', async function () { + const instance = new LDMerkleProof2019({ + document: fixture, + proof: { + ...fixture.proof, + domain: 'blockcerts.org', + challenge: 'another-challenge' + }, + proofPurpose: 'assertionMethod', + issuer: fixtureIssuerProfile, + domain: 'blockcerts.org', + challenge: 'challenge' + }); + + const result = await instance.verifyProof(); + expect(result).toEqual({ + verified: false, + verificationMethod: null, + error: 'The proof\'s challenge does not match the verifier\'s challenge' + }); + }); + }); });