From c197315982efa1e296228126ebdce4b02a0fdc37 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Mar 2026 09:58:08 -0400 Subject: [PATCH 1/4] feat: allow partial verify settings --- js-src/Settings.spec.ts | 11 +++++++++++ js-src/Settings.ts | 32 +++++++++++++++++++------------- js-src/types.d.ts | 16 ++++++++-------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/js-src/Settings.spec.ts b/js-src/Settings.spec.ts index ac2f80f..043d318 100644 --- a/js-src/Settings.spec.ts +++ b/js-src/Settings.spec.ts @@ -71,6 +71,17 @@ describe("Settings", () => { expect(settings.verify?.ocsp_fetch).toBe(true); }); + it("creates verify settings with partial config", () => { + const settings = createVerifySettings({ + verifyAfterReading: false, + }); + + expect(settings.verify).toBeDefined(); + expect(settings.verify?.verify_after_reading).toBe(false); + expect(settings.verify?.verify_after_sign).toBeUndefined(); + expect(settings.verify?.verify_trust).toBeUndefined(); + }); + it("merges multiple settings", () => { const trustSettings = createTrustSettings({ verifyTrustList: true, diff --git a/js-src/Settings.ts b/js-src/Settings.ts index ba0d746..e7d221c 100644 --- a/js-src/Settings.ts +++ b/js-src/Settings.ts @@ -60,19 +60,25 @@ export function createCawgTrustSettings( export function createVerifySettings( verifyConfig: VerifyConfig, ): SettingsContext { - return { - verify: { - verify_after_reading: verifyConfig.verifyAfterReading, - verify_after_sign: verifyConfig.verifyAfterSign, - verify_trust: verifyConfig.verifyTrust, - verify_timestamp_trust: verifyConfig.verifyTimestampTrust, - ocsp_fetch: verifyConfig.ocspFetch, - remote_manifest_fetch: verifyConfig.remoteManifestFetch, - skip_ingredient_conflict_resolution: - verifyConfig.skipIngredientConflictResolution, - strict_v1_validation: verifyConfig.strictV1Validation, - }, - }; + const verify: NonNullable = {}; + if (verifyConfig.verifyAfterReading !== undefined) + verify.verify_after_reading = verifyConfig.verifyAfterReading; + if (verifyConfig.verifyAfterSign !== undefined) + verify.verify_after_sign = verifyConfig.verifyAfterSign; + if (verifyConfig.verifyTrust !== undefined) + verify.verify_trust = verifyConfig.verifyTrust; + if (verifyConfig.verifyTimestampTrust !== undefined) + verify.verify_timestamp_trust = verifyConfig.verifyTimestampTrust; + if (verifyConfig.ocspFetch !== undefined) + verify.ocsp_fetch = verifyConfig.ocspFetch; + if (verifyConfig.remoteManifestFetch !== undefined) + verify.remote_manifest_fetch = verifyConfig.remoteManifestFetch; + if (verifyConfig.skipIngredientConflictResolution !== undefined) + verify.skip_ingredient_conflict_resolution = + verifyConfig.skipIngredientConflictResolution; + if (verifyConfig.strictV1Validation !== undefined) + verify.strict_v1_validation = verifyConfig.strictV1Validation; + return { verify }; } /** diff --git a/js-src/types.d.ts b/js-src/types.d.ts index 06ef4f5..a08454b 100644 --- a/js-src/types.d.ts +++ b/js-src/types.d.ts @@ -453,21 +453,21 @@ export interface TrustConfig { */ export interface VerifyConfig { /** Whether to verify after reading a manifest */ - verifyAfterReading: boolean; + verifyAfterReading?: boolean; /** Whether to verify after signing a manifest */ - verifyAfterSign: boolean; + verifyAfterSign?: boolean; /** Whether to verify trust during validation */ - verifyTrust: boolean; + verifyTrust?: boolean; /** Whether to verify timestamp trust */ - verifyTimestampTrust: boolean; + verifyTimestampTrust?: boolean; /** Whether to fetch OCSP responses */ - ocspFetch: boolean; + ocspFetch?: boolean; /** Whether to fetch remote manifests */ - remoteManifestFetch: boolean; + remoteManifestFetch?: boolean; /** Whether to skip ingredient conflict resolution */ - skipIngredientConflictResolution: boolean; + skipIngredientConflictResolution?: boolean; /** Whether to use strict v1 validation */ - strictV1Validation: boolean; + strictV1Validation?: boolean; } /** From 2bf91feee4e94381ab87b2f9324160664fe7525b Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Mar 2026 12:37:57 -0400 Subject: [PATCH 2/4] chore: update c2pa crate --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7541b7..96c44e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,9 +495,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "c2pa" -version = "0.77.0" +version = "0.78.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6c3fcc525dcac6cde7d020cc4d3065b2d2042e48efc096baba97876b276f41" +checksum = "7cae87cdb2ae6070bf628f889d745797071b194ecb31c711b452617308075c5e" dependencies = [ "asn1-rs", "async-generic", diff --git a/Cargo.toml b/Cargo.toml index 8c92217..3603020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] [dependencies] async-trait = "0.1.77" ciborium = "0.2.2" -c2pa = { version = "0.77.0", default-features = false, features = ["file_io", "pdf", "fetch_remote_manifests", "add_thumbnails", "rust_native_crypto", "default_http"] } +c2pa = { version = "0.78.4", default-features = false, features = ["file_io", "pdf", "fetch_remote_manifests", "add_thumbnails", "rust_native_crypto", "default_http"] } futures = "0.3" image = "0.25.6" neon = { version = "1.0.0", default-features = false, features = [ From 1596383c0aaaedc4d7fbcfaf900525c49c330c7e Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Mar 2026 15:13:13 -0400 Subject: [PATCH 3/4] feat: match web SDK snakeCaseify --- js-src/Settings.spec.ts | 69 ++++++++++++++++------------------------- js-src/Settings.ts | 68 ++++++++++++++++------------------------ js-src/types.d.ts | 27 ++-------------- 3 files changed, 57 insertions(+), 107 deletions(-) diff --git a/js-src/Settings.spec.ts b/js-src/Settings.spec.ts index 043d318..b82db82 100644 --- a/js-src/Settings.spec.ts +++ b/js-src/Settings.spec.ts @@ -34,9 +34,9 @@ describe("Settings", () => { const settings = createTrustSettings(trustConfig); expect(settings.trust).toBeDefined(); - expect(settings.trust?.verify_trust_list).toBe(true); - expect(settings.trust?.user_anchors).toBe("test"); - expect(settings.trust?.allowed_list).toBe("allowed"); + expect(settings.trust?.verifyTrustList).toBe(true); + expect(settings.trust?.userAnchors).toBe("test"); + expect(settings.trust?.allowedList).toBe("allowed"); }); it("creates CAWG trust settings", () => { @@ -46,9 +46,9 @@ describe("Settings", () => { }; const settings = createCawgTrustSettings(trustConfig); - expect(settings.cawg_trust).toBeDefined(); - expect(settings.cawg_trust?.verify_trust_list).toBe(false); - expect(settings.cawg_trust?.trust_anchors).toBe("anchors"); + expect(settings.cawgTrust).toBeDefined(); + expect(settings.cawgTrust?.verifyTrustList).toBe(false); + expect(settings.cawgTrust?.trustAnchors).toBe("anchors"); }); it("creates verify settings", () => { @@ -65,10 +65,10 @@ describe("Settings", () => { const settings = createVerifySettings(verifyConfig); expect(settings.verify).toBeDefined(); - expect(settings.verify?.verify_after_reading).toBe(true); - expect(settings.verify?.verify_after_sign).toBe(false); - expect(settings.verify?.verify_trust).toBe(true); - expect(settings.verify?.ocsp_fetch).toBe(true); + expect(settings.verify?.verifyAfterReading).toBe(true); + expect(settings.verify?.verifyAfterSign).toBe(false); + expect(settings.verify?.verifyTrust).toBe(true); + expect(settings.verify?.ocspFetch).toBe(true); }); it("creates verify settings with partial config", () => { @@ -77,9 +77,9 @@ describe("Settings", () => { }); expect(settings.verify).toBeDefined(); - expect(settings.verify?.verify_after_reading).toBe(false); - expect(settings.verify?.verify_after_sign).toBeUndefined(); - expect(settings.verify?.verify_trust).toBeUndefined(); + expect(settings.verify?.verifyAfterReading).toBe(false); + expect(settings.verify?.verifyAfterSign).toBeUndefined(); + expect(settings.verify?.verifyTrust).toBeUndefined(); }); it("merges multiple settings", () => { @@ -102,11 +102,11 @@ describe("Settings", () => { const merged = mergeSettings(trustSettings, verifySettings); expect(merged.trust).toBeDefined(); expect(merged.verify).toBeDefined(); - expect(merged.trust?.verify_trust_list).toBe(true); - expect(merged.verify?.verify_after_reading).toBe(false); + expect(merged.trust?.verifyTrustList).toBe(true); + expect(merged.verify?.verifyAfterReading).toBe(false); }); - it("converts settings to JSON", () => { + it("converts settings to JSON with snake_case keys", () => { const settings = createVerifySettings({ verifyAfterReading: true, verifyAfterSign: true, @@ -122,7 +122,7 @@ describe("Settings", () => { expect(json).toContain("verify"); expect(json).toContain("verify_after_reading"); - // Should be parseable + // Should be parseable with snake_case keys const parsed = JSON.parse(json); expect(parsed.verify.verify_after_reading).toBe(true); }); @@ -130,17 +130,12 @@ describe("Settings", () => { it("does not include undefined values in trust settings JSON", () => { const trustConfig: TrustConfig = { verifyTrustList: true, - // userAnchors is undefined - // trustAnchors is undefined - // trustConfig is undefined - // allowedList is undefined }; const settings = createTrustSettings(trustConfig); const json = settingsToJson(settings); const parsed = JSON.parse(json); - // JSON.stringify automatically excludes undefined values expect(parsed.trust.verify_trust_list).toBe(true); expect("user_anchors" in parsed.trust).toBe(false); expect("trust_anchors" in parsed.trust).toBe(false); @@ -151,14 +146,12 @@ describe("Settings", () => { it("does not include undefined values in CAWG trust settings JSON", () => { const trustConfig: TrustConfig = { verifyTrustList: false, - // Only verifyTrustList is defined }; const settings = createCawgTrustSettings(trustConfig); const json = settingsToJson(settings); const parsed = JSON.parse(json); - // JSON.stringify automatically excludes undefined values expect(parsed.cawg_trust.verify_trust_list).toBe(false); expect("user_anchors" in parsed.cawg_trust).toBe(false); expect("trust_anchors" in parsed.cawg_trust).toBe(false); @@ -168,20 +161,12 @@ describe("Settings", () => { const verifyConfig: VerifyConfig = { verifyAfterReading: true, verifyAfterSign: false, - // Only first two are defined, rest are undefined - verifyTrust: undefined as any, - verifyTimestampTrust: undefined as any, - ocspFetch: undefined as any, - remoteManifestFetch: undefined as any, - skipIngredientConflictResolution: undefined as any, - strictV1Validation: undefined as any, }; const settings = createVerifySettings(verifyConfig); const json = settingsToJson(settings); const parsed = JSON.parse(json); - // JSON.stringify automatically excludes undefined values expect(parsed.verify.verify_after_reading).toBe(true); expect(parsed.verify.verify_after_sign).toBe(false); expect("verify_trust" in parsed.verify).toBe(false); @@ -193,17 +178,18 @@ describe("Settings", () => { it("does not include undefined values when merging settings", () => { const settings1: SettingsContext = { trust: { - verify_trust_list: true, - user_anchors: "test", + verifyTrustList: true, + userAnchors: "test", }, }; const settings2: SettingsContext = { trust: { - allowed_list: undefined, // Explicitly undefined + verifyTrustList: true, + allowedList: undefined, }, verify: { - verify_after_reading: false, + verifyAfterReading: false, }, }; @@ -211,7 +197,6 @@ describe("Settings", () => { const json = settingsToJson(merged); const parsed = JSON.parse(json); - // JSON.stringify automatically excludes undefined values expect(parsed.trust.verify_trust_list).toBe(true); expect(parsed.trust.user_anchors).toBe("test"); expect("allowed_list" in parsed.trust).toBe(false); @@ -232,15 +217,15 @@ describe("Settings", () => { const settings2: SettingsContext = { verify: { - verify_trust: true, // Override this value - ocsp_fetch: true, // Override this value + verifyTrust: true, + ocspFetch: true, }, }; const merged = mergeSettings(settings1, settings2); - expect(merged.verify?.verify_after_reading).toBe(true); // from settings1 - expect(merged.verify?.verify_trust).toBe(true); // overridden by settings2 - expect(merged.verify?.ocsp_fetch).toBe(true); // overridden by settings2 + expect(merged.verify?.verifyAfterReading).toBe(true); // from settings1 + expect(merged.verify?.verifyTrust).toBe(true); // overridden by settings2 + expect(merged.verify?.ocspFetch).toBe(true); // overridden by settings2 }); describe("loadSettingsFromFile", () => { diff --git a/js-src/Settings.ts b/js-src/Settings.ts index e7d221c..e817fc4 100644 --- a/js-src/Settings.ts +++ b/js-src/Settings.ts @@ -16,21 +16,32 @@ import fetch from "node-fetch"; import type { TrustConfig, VerifyConfig, SettingsContext } from "./types.d.ts"; +type SettingsObjectType = { + [k: string]: string | boolean | undefined | SettingsObjectType; +}; + +function snakeCaseify(object: SettingsObjectType): SettingsObjectType { + return Object.entries(object).reduce( + (result, [key, val]) => { + result[snakeCase(key)] = + typeof val === "object" && val !== null ? snakeCaseify(val) : val; + return result; + }, + {} as SettingsObjectType, + ); +} + +function snakeCase(str: string): string { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); +} + /** * Create a Settings object with trust configuration. * @param trustConfig The trust configuration * @returns Settings object that can be passed to Reader/Builder */ export function createTrustSettings(trustConfig: TrustConfig): SettingsContext { - return { - trust: { - verify_trust_list: trustConfig.verifyTrustList, - user_anchors: trustConfig.userAnchors, - trust_anchors: trustConfig.trustAnchors, - trust_config: trustConfig.trustConfig, - allowed_list: trustConfig.allowedList, - }, - }; + return { trust: { ...trustConfig } }; } /** @@ -41,15 +52,7 @@ export function createTrustSettings(trustConfig: TrustConfig): SettingsContext { export function createCawgTrustSettings( trustConfig: TrustConfig, ): SettingsContext { - return { - cawg_trust: { - verify_trust_list: trustConfig.verifyTrustList, - user_anchors: trustConfig.userAnchors, - trust_anchors: trustConfig.trustAnchors, - trust_config: trustConfig.trustConfig, - allowed_list: trustConfig.allowedList, - }, - }; + return { cawgTrust: { ...trustConfig } }; } /** @@ -60,25 +63,7 @@ export function createCawgTrustSettings( export function createVerifySettings( verifyConfig: VerifyConfig, ): SettingsContext { - const verify: NonNullable = {}; - if (verifyConfig.verifyAfterReading !== undefined) - verify.verify_after_reading = verifyConfig.verifyAfterReading; - if (verifyConfig.verifyAfterSign !== undefined) - verify.verify_after_sign = verifyConfig.verifyAfterSign; - if (verifyConfig.verifyTrust !== undefined) - verify.verify_trust = verifyConfig.verifyTrust; - if (verifyConfig.verifyTimestampTrust !== undefined) - verify.verify_timestamp_trust = verifyConfig.verifyTimestampTrust; - if (verifyConfig.ocspFetch !== undefined) - verify.ocsp_fetch = verifyConfig.ocspFetch; - if (verifyConfig.remoteManifestFetch !== undefined) - verify.remote_manifest_fetch = verifyConfig.remoteManifestFetch; - if (verifyConfig.skipIngredientConflictResolution !== undefined) - verify.skip_ingredient_conflict_resolution = - verifyConfig.skipIngredientConflictResolution; - if (verifyConfig.strictV1Validation !== undefined) - verify.strict_v1_validation = verifyConfig.strictV1Validation; - return { verify }; + return { verify: { ...verifyConfig } }; } /** @@ -94,8 +79,8 @@ export function mergeSettings(...settings: SettingsContext[]): SettingsContext { if (setting.trust) { merged.trust = { ...merged.trust, ...setting.trust }; } - if (setting.cawg_trust) { - merged.cawg_trust = { ...merged.cawg_trust, ...setting.cawg_trust }; + if (setting.cawgTrust) { + merged.cawgTrust = { ...merged.cawgTrust, ...setting.cawgTrust }; } if (setting.verify) { merged.verify = { ...merged.verify, ...setting.verify }; @@ -110,11 +95,12 @@ export function mergeSettings(...settings: SettingsContext[]): SettingsContext { /** * Convert a settings object to a JSON string. + * Converts camelCase keys to snake_case to match the c2pa-rs settings format. * @param settings The settings object - * @returns JSON string representation + * @returns JSON string representation with snake_case keys */ export function settingsToJson(settings: SettingsContext): string { - return JSON.stringify(settings); + return JSON.stringify(snakeCaseify(settings as SettingsObjectType)); } /** diff --git a/js-src/types.d.ts b/js-src/types.d.ts index a08454b..c82e9fb 100644 --- a/js-src/types.d.ts +++ b/js-src/types.d.ts @@ -477,32 +477,11 @@ export interface VerifyConfig { */ export interface SettingsContext { /** C2PA trust configuration */ - trust?: { - verify_trust_list?: boolean; - user_anchors?: string; - trust_anchors?: string; - trust_config?: string; - allowed_list?: string; - }; + trust?: TrustConfig; /** CAWG trust configuration */ - cawg_trust?: { - verify_trust_list?: boolean; - user_anchors?: string; - trust_anchors?: string; - trust_config?: string; - allowed_list?: string; - }; + cawgTrust?: TrustConfig; /** Verification configuration */ - verify?: { - verify_after_reading?: boolean; - verify_after_sign?: boolean; - verify_trust?: boolean; - verify_timestamp_trust?: boolean; - ocsp_fetch?: boolean; - remote_manifest_fetch?: boolean; - skip_ingredient_conflict_resolution?: boolean; - strict_v1_validation?: boolean; - }; + verify?: VerifyConfig; /** Builder configuration */ builder?: { thumbnail?: { From a1572962a9e62a98a478425486548ad6baaa3f8f Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Mar 2026 15:20:38 -0400 Subject: [PATCH 4/4] chore: add changeset --- .changeset/slick-ducks-stand.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slick-ducks-stand.md diff --git a/.changeset/slick-ducks-stand.md b/.changeset/slick-ducks-stand.md new file mode 100644 index 0000000..4d6640f --- /dev/null +++ b/.changeset/slick-ducks-stand.md @@ -0,0 +1,5 @@ +--- +"@contentauth/c2pa-node": patch +--- + +Allow partial verify settings