From 7b0877a6c911ebc1848d8658c1722c7378772463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Je=CC=84kabsons?= Date: Tue, 17 Mar 2026 20:47:28 +0200 Subject: [PATCH 1/3] Return INDETERMINATE when signer issuer is not in trusted list When a trustListProvider is configured and the signer's issuer is not found (or not trusted at signing time), downgrade overall status from VALID to INDETERMINATE with an ISSUER_NOT_TRUSTED limitation. Add test-environment-sig-sample.edoc fixture with tests for both public and sensitive sample dirs. --- src/core/verification.ts | 21 ++++- .../test-environment-sig-sample.edoc | Bin 0 -> 47758 bytes tests/integration/batch_edoc.test.ts | 85 ++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/invalid_samples/test-environment-sig-sample.edoc diff --git a/src/core/verification.ts b/src/core/verification.ts index 415cce5..6792b5e 100644 --- a/src/core/verification.ts +++ b/src/core/verification.ts @@ -1455,7 +1455,7 @@ export async function verifySignature( options.verifyTimestamps === false || (timestampResult?.isValid ?? true); - const isValid = + let isValid = certResult.isValid && checksumResult.isValid && signatureResult.isValid && timestampValid; // Determine validation status and limitations @@ -1530,6 +1530,25 @@ export async function verifySignature( } } + // If everything else is valid but the issuer is not on the trusted list, + // downgrade from VALID to INDETERMINATE — crypto is sound but trust is unconfirmed + if ( + status === "VALID" && + options.trustListProvider && + trustListMatch && + (!trustListMatch.found || !trustListMatch.trustedAtTime) + ) { + status = "INDETERMINATE"; + isValid = false; + statusMessage = trustListMatch.found + ? "Signer issuer found in trusted list but was not trusted at signing time" + : "Signer issuer not found in trusted list"; + limitations.push({ + code: "ISSUER_NOT_TRUSTED", + description: statusMessage, + }); + } + const checklist = options.includeChecklist ? buildVerificationChecklist({ options, diff --git a/tests/fixtures/invalid_samples/test-environment-sig-sample.edoc b/tests/fixtures/invalid_samples/test-environment-sig-sample.edoc new file mode 100644 index 0000000000000000000000000000000000000000..ed07da1d57d89d8f4fb43ab309a62b7a6fc9db8f GIT binary patch literal 47758 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 f19{f}ug*Ozo^Sncw~&s { } }); +// Test that a document signed with a test/demo certificate (not in trusted list) +// gets INDETERMINATE status when trust list checking is enabled +const invalidSamplesDir = join(__dirname, "../fixtures/invalid_samples"); +const sensitiveInvalidSamplesDir = join(__dirname, "../fixtures/sensitive/invalid_samples"); +const invalidSamplesDirExists = existsSync(invalidSamplesDir); +const sensitiveInvalidSamplesDirExists = existsSync(sensitiveInvalidSamplesDir); + +const verifyUntrustedIssuer = (dir: string) => { + const testEnvFile = join(dir, "test-environment-sig-sample.edoc"); + const testEnvFileExists = existsSync(testEnvFile); + + (testEnvFileExists ? it : it.skip)( + "should return INDETERMINATE when issuer is not in trusted list", + async () => { + const edocBuffer = readFileSync(testEnvFile); + const container = parseEdoc(edocBuffer); + expect(container.signatures.length).toBeGreaterThan(0); + + const signature = container.signatures[0]; + const result = await verifySignature(signature, container.files, { + verifyTime: signature.signingTime, + checkRevocation: false, + includeChecklist: true, + trustListProvider, + }); + + // Crypto should be sound + expect(result.checksums.isValid).toBe(true); + expect(result.signature?.isValid).toBe(true); + + // But overall should be INDETERMINATE because issuer is not in trusted list + expect(result.isValid).toBe(false); + expect(result.status).toBe("INDETERMINATE"); + expect(result.trustListMatch?.found).toBe(false); + expect(result.limitations).toEqual( + expect.arrayContaining([expect.objectContaining({ code: "ISSUER_NOT_TRUSTED" })]), + ); + + // Verify checklist statuses + expect(getChecklistStatusMap(result)).toEqual({ + document_integrity: "pass", + signature_valid: "pass", + certificate_valid_at_signing_time: "pass", + timestamp_present: "pass", + timestamp_valid: "pass", + timestamp_authority_trusted_at_signing_time: "indeterminate", + certificate_not_revoked_at_signing_time: "skipped", + issuer_trusted_at_signing_time: "fail", + }); + + console.log( + `test-environment-sig-sample.edoc: ${result.status} - trustListMatch: ${JSON.stringify(result.trustListMatch)}`, + ); + }, + ); + + (testEnvFileExists ? it : it.skip)( + "should return VALID without trust list provider (crypto-only check)", + async () => { + const edocBuffer = readFileSync(testEnvFile); + const container = parseEdoc(edocBuffer); + const signature = container.signatures[0]; + + const result = await verifySignature(signature, container.files, { + verifyTime: signature.signingTime, + checkRevocation: false, + }); + + // Without trust list, crypto-valid signatures should be VALID + expect(result.isValid).toBe(true); + expect(result.status).toBe("VALID"); + }, + ); +}; + +const describeInvalid = invalidSamplesDirExists ? describe : describe.skip; +describeInvalid("Untrusted issuer verification (public samples)", () => { + verifyUntrustedIssuer(invalidSamplesDir); +}); + +const describeSensitiveInvalid = sensitiveInvalidSamplesDirExists ? describe : describe.skip; +describeSensitiveInvalid("Untrusted issuer verification (sensitive samples)", () => { + verifyUntrustedIssuer(sensitiveInvalidSamplesDir); +}); + describePublic("Public Samples Trusted List Checklist", () => { const publicFiles = collectFiles(validSamplesDir, [".edoc", ".asice"]); From 9c5555d9053d6b9558020b010bf3319723a1d102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Je=CC=84kabsons?= Date: Tue, 17 Mar 2026 21:04:06 +0200 Subject: [PATCH 2/3] Fix CI timeout and handle trust list provider errors Increase test timeout for untrusted issuer tests (trust list loading is slow on CI). Also downgrade to INDETERMINATE when the trust list provider throws an error, rather than silently returning VALID. --- src/core/verification.ts | 35 ++++++++++++++++------------ tests/integration/batch_edoc.test.ts | 2 ++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/core/verification.ts b/src/core/verification.ts index 6792b5e..395763f 100644 --- a/src/core/verification.ts +++ b/src/core/verification.ts @@ -1532,21 +1532,26 @@ export async function verifySignature( // If everything else is valid but the issuer is not on the trusted list, // downgrade from VALID to INDETERMINATE — crypto is sound but trust is unconfirmed - if ( - status === "VALID" && - options.trustListProvider && - trustListMatch && - (!trustListMatch.found || !trustListMatch.trustedAtTime) - ) { - status = "INDETERMINATE"; - isValid = false; - statusMessage = trustListMatch.found - ? "Signer issuer found in trusted list but was not trusted at signing time" - : "Signer issuer not found in trusted list"; - limitations.push({ - code: "ISSUER_NOT_TRUSTED", - description: statusMessage, - }); + if (status === "VALID" && options.trustListProvider) { + if (trustListMatch && (!trustListMatch.found || !trustListMatch.trustedAtTime)) { + status = "INDETERMINATE"; + isValid = false; + statusMessage = trustListMatch.found + ? "Signer issuer found in trusted list but was not trusted at signing time" + : "Signer issuer not found in trusted list"; + limitations.push({ + code: "ISSUER_NOT_TRUSTED", + description: statusMessage, + }); + } else if (!trustListMatch && trustListError) { + status = "INDETERMINATE"; + isValid = false; + statusMessage = "Trusted list check failed"; + limitations.push({ + code: "TRUST_LIST_CHECK_FAILED", + description: trustListError, + }); + } } const checklist = options.includeChecklist diff --git a/tests/integration/batch_edoc.test.ts b/tests/integration/batch_edoc.test.ts index 9336a2e..126d526 100644 --- a/tests/integration/batch_edoc.test.ts +++ b/tests/integration/batch_edoc.test.ts @@ -604,11 +604,13 @@ const verifyUntrustedIssuer = (dir: string) => { const describeInvalid = invalidSamplesDirExists ? describe : describe.skip; describeInvalid("Untrusted issuer verification (public samples)", () => { + jest.setTimeout(30000); verifyUntrustedIssuer(invalidSamplesDir); }); const describeSensitiveInvalid = sensitiveInvalidSamplesDirExists ? describe : describe.skip; describeSensitiveInvalid("Untrusted issuer verification (sensitive samples)", () => { + jest.setTimeout(30000); verifyUntrustedIssuer(sensitiveInvalidSamplesDir); }); From aa40d1a2f8eca374e5c1571b3670bb236ad2d177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Je=CC=84kabsons?= Date: Tue, 17 Mar 2026 21:06:19 +0200 Subject: [PATCH 3/3] Address review: explicit trustedAtTime check, remove debug console.log Use trustedAtTime === false instead of !trustedAtTime to avoid false-downgrading providers that return { found: true } without the trustedAtTime field. Remove stray console.log from test. --- src/core/verification.ts | 2 +- tests/integration/batch_edoc.test.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/core/verification.ts b/src/core/verification.ts index 395763f..1697eaf 100644 --- a/src/core/verification.ts +++ b/src/core/verification.ts @@ -1533,7 +1533,7 @@ export async function verifySignature( // If everything else is valid but the issuer is not on the trusted list, // downgrade from VALID to INDETERMINATE — crypto is sound but trust is unconfirmed if (status === "VALID" && options.trustListProvider) { - if (trustListMatch && (!trustListMatch.found || !trustListMatch.trustedAtTime)) { + if (trustListMatch && (!trustListMatch.found || trustListMatch.trustedAtTime === false)) { status = "INDETERMINATE"; isValid = false; statusMessage = trustListMatch.found diff --git a/tests/integration/batch_edoc.test.ts b/tests/integration/batch_edoc.test.ts index 126d526..d3be402 100644 --- a/tests/integration/batch_edoc.test.ts +++ b/tests/integration/batch_edoc.test.ts @@ -576,10 +576,6 @@ const verifyUntrustedIssuer = (dir: string) => { certificate_not_revoked_at_signing_time: "skipped", issuer_trusted_at_signing_time: "fail", }); - - console.log( - `test-environment-sig-sample.edoc: ${result.status} - trustListMatch: ${JSON.stringify(result.trustListMatch)}`, - ); }, );