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
5 changes: 5 additions & 0 deletions .changeset/slick-ducks-stand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@contentauth/c2pa-node": patch
---

Allow partial verify settings
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
74 changes: 35 additions & 39 deletions js-src/Settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand All @@ -65,10 +65,21 @@ 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", () => {
const settings = createVerifySettings({
verifyAfterReading: false,
});

expect(settings.verify).toBeDefined();
expect(settings.verify?.verifyAfterReading).toBe(false);
expect(settings.verify?.verifyAfterSign).toBeUndefined();
expect(settings.verify?.verifyTrust).toBeUndefined();
});

it("merges multiple settings", () => {
Expand All @@ -91,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,
Expand All @@ -111,25 +122,20 @@ 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);
});

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);
Expand All @@ -140,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);
Expand All @@ -157,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);
Expand All @@ -182,25 +178,25 @@ 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,
},
};

const merged = mergeSettings(settings1, settings2);
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);
Expand All @@ -221,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", () => {
Expand Down
62 changes: 27 additions & 35 deletions js-src/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } };
}

/**
Expand All @@ -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 } };
}

/**
Expand All @@ -60,19 +63,7 @@ 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,
},
};
return { verify: { ...verifyConfig } };
}

/**
Expand All @@ -88,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 };
Expand All @@ -104,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));
}

/**
Expand Down
43 changes: 11 additions & 32 deletions js-src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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?: {
Expand Down
Loading