Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/core/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1530,6 +1530,30 @@ 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 === false)) {
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
? buildVerificationChecklist({
options,
Expand Down
Binary file not shown.
83 changes: 83 additions & 0 deletions tests/integration/batch_edoc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,89 @@ describeSensitive("Sensitive Samples Trusted List Checklist", () => {
}
});

// 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",
});
},
);

(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)", () => {
jest.setTimeout(30000);
verifyUntrustedIssuer(invalidSamplesDir);
});

const describeSensitiveInvalid = sensitiveInvalidSamplesDirExists ? describe : describe.skip;
describeSensitiveInvalid("Untrusted issuer verification (sensitive samples)", () => {
jest.setTimeout(30000);
verifyUntrustedIssuer(sensitiveInvalidSamplesDir);
});

describePublic("Public Samples Trusted List Checklist", () => {
const publicFiles = collectFiles(validSamplesDir, [".edoc", ".asice"]);

Expand Down
Loading