diff --git a/.gitignore b/.gitignore index ebad8f8f..c678527b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.dccache +.dccache \ No newline at end of file diff --git a/docs/manifest/manifest-tasks.mdx b/docs/manifest/manifest-tasks.mdx deleted file mode 100644 index d24fb6a9..00000000 --- a/docs/manifest/manifest-tasks.mdx +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: tasks -title: Common tasks working with manifest data ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -## Reading a manifest - - - - - This is how to read a manifest using JavaScript. - - - - This is how to read a manifest using Python. - - - - This is how to read a manifest using C++. Do we want also want C? - - - - This is how to read a manifest using Node.js. - - - -## Getting resources from a manifest - - - - This is how to get resources from a manifest using JavaScript. - - - - This is how to get resources from a manifest using Python. - - - - This is how to get resources from a manifest using C++. - - - - This is how to get resources from a manifest using Node.js. - - - -## Building a manifest - - - - - This is how to build a manifest using Python. - - - - This is how to build a manifest using C++. - - - - This is how to build a manifest using Node.js. - - - -## Writing a manifest - - - - This is how to write a manifest using Python. - - - - This is how to write a manifest using C++. - - - - This is how to write a manifest using Node.js. - - - -## Signing a manifest - - - - This is how to sign a manifest using Python. - - - - This is how to sign a manifest using C++. - - - - This is how to sign a manifest using Node.js. - - diff --git a/docs/manifest/understanding.md b/docs/manifest/understanding.md index 8831d335..0aed839a 100644 --- a/docs/manifest/understanding.md +++ b/docs/manifest/understanding.md @@ -1,13 +1,11 @@ --- id: understanding-manifest -title: Working with manifests +title: Understanding manifests --- -## Understanding manifests - The concept of a _manifest_ is central to how Content Credentials work. -A collection of manifests (known as a _manifest store_) is attached to an asset. Each manifest contains information about the provenance of the asset. Creating or editing an asset using a C2PA-compliant device or tool (for example Adobe Photoshop) adds a new manifest to the manifest store. +A collection of manifests (known as a _manifest store_) is attached to an asset. Each manifest contains information about the provenance of the asset. Creating or editing an asset using a C2PA-compliant device or tool (for example, Adobe Photoshop) adds a new manifest to the manifest store. The manifests in the manifest store are not ordered, but the most-recently added manifest is the _active manifest_. The active manifest has content bindings that can be validated with the asset—that is, it's hashed with the asset to ensure its validity. @@ -21,6 +19,7 @@ A manifest store can be either: In addition, an asset can have a [external manifest store](https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_external_manifests), linked from the asset's metadata, as detailed in the [C2PA specification](https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_embedding_a_reference_to_an_external_manifest). This is sometimes referred to as a _remote manifest_. To determine if an asset has Content Credentials, the SDK checks for the presence of a manifest store in this order: + 1. In the asset metadata 1. In a sidebar file. 1. In a remote manifest. @@ -28,10 +27,10 @@ To determine if an asset has Content Credentials, the SDK checks for the presenc So, for example to see if `foo.jpg` has Content Credentials, the SDK first checks if there's a manifest store in the file itself, then looks for a sidecar file (`foo.c2pa` in the same directory), and finally looks in the asset's metadata for a reference to a remote manifest store. :::info -Currently, only Adobe has implemented a Content Credentials cloud service to provide access to remote manifest stores, but in theory anyone could do so to provide a publicly-accessible network location for manifests. +Currently, only Adobe has implemented a Content Credentials cloud service to provide access to remote manifest stores, but in theory anyone could do so to provide a publicly-accessible network location for manifests. ::: -## Binary versus JSON manifest formats +## Binary versus JSON manifest The manifest as described in the C2PA specification is a binary structure in JPEG universal metadata box format ([JUMBF](https://www.iso.org/standard/84635.html)) that can include JSON and binary data for things like encryption keys and thumbnail images. diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx new file mode 100644 index 00000000..58ac1d56 --- /dev/null +++ b/docs/tasks/build.mdx @@ -0,0 +1,48 @@ +--- +id: build +title: Adding and signing a manifest +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import JsBuild from './js/_js-build.md'; +import PythonBuild from './python/_python-build.md'; +import NodeBuild from './node/_node-build.md'; +import CppBuild from './cpp/_cpp-build.md'; +import RustBuild from './rust/_rust-build.md'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tasks/cpp/_cpp-build.md b/docs/tasks/cpp/_cpp-build.md new file mode 100644 index 00000000..ece961f5 --- /dev/null +++ b/docs/tasks/cpp/_cpp-build.md @@ -0,0 +1,43 @@ +This is an example of how to assign a manifest to an asset and sign the claim using C++: + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" + +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; + +const std::string manifest_json = R"{ + "claim_generator": "c2pa_c_test/0.1", + "claim_generator_info": [ + { + "name": "c2pa-c example", + "version": "0.1" + } + ], + "assertions": [ + { + "label": "cawg.training-mining", + "data": { + "entries": { + "cawg.ai_generative_training": { "use": "notAllowed" }, + "cawg.ai_inference": { "use": "notAllowed" }, + "cawg.ai_training": { "use": "notAllowed" }, + "cawg.data_mining": { "use": "notAllowed" } + } + } + } + ] + }; + +auto builder = Builder(manifest_json); +``` \ No newline at end of file diff --git a/docs/tasks/cpp/_cpp-get-resources.md b/docs/tasks/cpp/_cpp-get-resources.md new file mode 100644 index 00000000..60bb1591 --- /dev/null +++ b/docs/tasks/cpp/_cpp-get-resources.md @@ -0,0 +1,80 @@ +This is how to get resources from a manifest using C++. + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" +#include + +// this example uses nlohmann json for parsing the manifest +using json = nlohmann::json; +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; + +string read_text_file(const fs::path &path) +{ + ifstream file(path); + if (!file.is_open()) + { + throw runtime_error("Could not open file " + string(path)); + } + string contents((istreambuf_iterator(file)), istreambuf_iterator()); + file.close(); + return contents.data(); +} + +int main() +{ + fs::path manifest_path = current_dir / "../tests/fixtures/training.json"; + //fs::path certs_path = current_dir / "../tests/fixtures/es256_certs.pem"; + //fs::path image_path = current_dir / "../tests/fixtures/A.jpg"; + fs::path output_path = current_dir / "../target/example/training.jpg"; + fs::path thumbnail_path = current_dir / "../target/example/thumbnail.jpg"; + + try + { + // read the new manifest and display the JSON + auto reader = Reader(output_path); + + auto manifest_store_json = reader.json(); + cout << "The new manifest is " << manifest_store_json << endl; + + // get the active manifest + json manifest_store = json::parse(manifest_store_json); + if (manifest_store.contains("active_manifest")) + { + string active_manifest = manifest_store["active_manifest"]; + json &manifest = manifest_store["manifests"][active_manifest]; + + string identifer = manifest["thumbnail"]["identifier"]; + + reader.get_resource(identifer, thumbnail_path); + + cout << "thumbnail written to" << thumbnail_path << endl; + } + } + + catch (c2pa::Exception const &e) + { + cout << "C2PA Error: " << e.what() << endl; + } + + catch (runtime_error const &e) + { + cout << "setup error" << e.what() << endl; + } + + catch (json::parse_error const &e) + { + cout << "parse error " << e.what() << endl; + } +} +``` \ No newline at end of file diff --git a/docs/tasks/cpp/_cpp-read.md b/docs/tasks/cpp/_cpp-read.md new file mode 100644 index 00000000..e48e63f2 --- /dev/null +++ b/docs/tasks/cpp/_cpp-read.md @@ -0,0 +1,26 @@ + +Use the `read_file` function to read C2PA data from the specified file. This function examines the specified asset file for C2PA data and returns a JSON report if it finds any; it throws exceptions on errors. If there are validation errors, the report includes a `validation_status` field. + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include +#include "c2pa.hpp" +#include "test_signer.hpp" + +using namespace std; +namespace fs = std::filesystem; +using namespace c2pa; + +auto json_store = C2pa::read_file("work/media_file.jpg", "output/data_dir") +``` + +Where: + +- `work/media_file.jpg` is the asset file to read. +- `output/data_dir` is the optional path to data output directory; If provided, the function extracts any binary resources, such as thumbnails, icons, and C2PA data into that directory. These files are referenced by the identifier fields in the manifest store report. diff --git a/docs/tasks/cpp/_cpp-settings.md b/docs/tasks/cpp/_cpp-settings.md new file mode 100644 index 00000000..e59d0194 --- /dev/null +++ b/docs/tasks/cpp/_cpp-settings.md @@ -0,0 +1,56 @@ +```cpp +#include "c2pa.hpp" +// set settings to not generate thumbnails +c2pa::load_settings("{\"builder\": { \"thumbnail\":{\"enabled\": false}}}", "json"); +``` + +Load from a file: + +```cpp +#include +#include +#include +#include "c2pa.hpp" + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " /path/to/settings.json\n"; + return 1; + } + + const std::string settings_path = argv[1]; + + std::ifstream in(settings_path); + if (!in) { + std::cerr << "Failed to open settings file: " << settings_path << "\n"; + return 1; + } + + std::ostringstream buffer; + buffer << in.rdbuf(); + const std::string json = buffer.str(); + + try { + c2pa::load_settings(json, "json"); // format is "json" + std::cout << "Loaded settings successfully. c2pa version: " + << c2pa::version() << "\n"; + } catch (const c2pa::C2paException& e) { + std::cerr << "Failed to load settings: " << e.what() << "\n"; + return 1; + } + return 0; +} +``` + +Example `settings.json`: + +```json +{ + "builder": { + "thumbnail": { "enabled": false }, + "actions": { + "auto_placed_action": { "enabled": true } + } + } +} +``` \ No newline at end of file diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx new file mode 100644 index 00000000..217b67b2 --- /dev/null +++ b/docs/tasks/get-resources.mdx @@ -0,0 +1,49 @@ +--- +id: get-resources +title: Getting resources from a manifest +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import JSGetResources from './js/_js-get-resources.md'; +import PythonGetResources from './python/_python-get-resources.md'; +import NodeGetResources from './node/_node-get-resources.md'; +import CppGetResources from './cpp/_cpp-get-resources.md'; +import RustGetResources from './rust/_rust-get-resources.md'; + +Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tasks/index.md b/docs/tasks/index.md new file mode 100644 index 00000000..623f3976 --- /dev/null +++ b/docs/tasks/index.md @@ -0,0 +1,10 @@ +--- +title: Working with the SDK +--- + +At a high level, using the CAI open-source SDK is the same in every language, but how you accomplish each task is specific to the language you're using. + +- [Configuring SDK settings](./settings.mdx) +- [Reading manifest data](./read.mdx) +- [Getting resources from a manifest](./get-resources.mdx) +- [Adding a manifest to an asset and signing it](./build.mdx) diff --git a/docs/tasks/js/_js-build.md b/docs/tasks/js/_js-build.md new file mode 100644 index 00000000..34ae5165 --- /dev/null +++ b/docs/tasks/js/_js-build.md @@ -0,0 +1,95 @@ +Using client-side JavaScript, you can: +- Create and compose manifests using the JavaScript library manifest `builder` API. +- Perform signing in the browser if you have a private key available to the client (using WebCrypto, an imported key, or an ephemeral/test key). The web library provides a `Signer` interface you can implement to call into whatever signing capability you have in the browser. +- Produce a manifest store or sidecar (`.c2pa`) file entirely in the browser (so you can download a signed sidecar next to the asset). + +Signing Content Credentials requires an end-entity X.509 certificate that fits the C2PA trust model to produce publicly verifiable signatures. Getting and protecting that certificate/private key in a browser is risky and should not be done in production. + +::: warning +Never put production private keys into client code! +::: + +Embedding into binaries: While you can build and sign manifests client-side, embedding a signed manifest properly into binary file formats (JPEG/PNG/MP4, etc.) is functionality that server-side libraries (Node.js, Python, C/C++, and Rust) explicitly provide. + +Security & auditability: Browser-stored keys (or keys entered by users) are exposed to theft, malware, or cross-site scripting (XSS) attacks. For production signing, best practice is to use a remote signer (KMS/HSM) or server-side signing where keys are protected and signing operations are auditable. + +```js +import { createC2pa, type Signer } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +// 1) Create a Signer that calls your backend (which returns the signature bytes) +function createRemoteSigner(): Signer { + return { + alg: 'es256', + reserveSize: async () => 4096, // bytes to reserve for TSA/countersignature (tune as needed) + sign: async (toBeSigned: Uint8Array, _reserveSize: number) => { + const res = await fetch('/api/sign', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + alg: 'es256', + payload: Array.from(toBeSigned), + }), + }); + if (!res.ok) throw new Error('Signing failed'); + const sigBytes = new Uint8Array(await res.arrayBuffer()); + return sigBytes; + }, + }; +} + +async function run() { + // 2) Initialize the SDK + const c2pa = await createC2pa({ wasmSrc }); + + // 3) Fetch the asset to sign + const imgUrl = 'https://contentauth.github.io/example-assets/images/cloudscape-ACA-Cr.jpeg'; + const resp = await fetch(imgUrl); + const assetBlob = await resp.blob(); + + // 4) Build a simple manifest (add a created action and optional thumbnail) + const builder = await c2pa.builder.new(); + await builder.addAction({ + action: 'c2pa.created', + digitalSourceType: 'http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture', + }); + // Optional: include a thumbnail that represents the asset + await builder.setThumbnailFromBlob('image/jpeg', assetBlob); + + // 5) Sign and get a new asset with the manifest embedded + const signer = createRemoteSigner(); + const signedBytes = await builder.sign(signer, assetBlob.type, assetBlob); + + // 6) Use/save the signed asset + const signedBlob = new Blob([signedBytes], { type: assetBlob.type }); + const url = URL.createObjectURL(signedBlob); + // e.g., download + const a = document.createElement('a'); + a.href = url; + a.download = 'signed.jpg'; + a.click(); + URL.revokeObjectURL(url); + + // Cleanup + await builder.free(); + c2pa.dispose(); +} + +run().catch(console.error); +``` + +To retrieve manifest bytes alongside the signed asset: + +```js +const { asset, manifest } = await builder.signAndGetManifestBytes( + signer, + assetBlob.type, + assetBlob +); +// asset -> signed asset bytes +// manifest -> embedded manifest bytes +``` + +Notes: +- You provide the `Signer` used in the example above. In production, this object wraps a service/HSM that returns a proper signature for your algorithm (`es256`, `ps256`, `ed25519`, etc.). Set `reserveSize` to a value large enough for timestamps/countersignatures your signer adds. +- To attach a remote manifest instead of embedding, use `builder.setRemoteUrl(url)` and `builder.setNoEmbed(true)` before signing. \ No newline at end of file diff --git a/docs/tasks/js/_js-get-resources.md b/docs/tasks/js/_js-get-resources.md new file mode 100644 index 00000000..31851c36 --- /dev/null +++ b/docs/tasks/js/_js-get-resources.md @@ -0,0 +1,19 @@ +The example below shows how to get resources from manifest data using the JavaScript library. + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; +const c2pa = createC2pa({ wasmSrc }); + +const response = await fetch( + 'https://contentauth.github.io/example-assets/images/Firefly_tabby_cat.jpg' +); + +const blob = await response.blob(); +const reader = await c2pa.reader.fromBlob(blob.type, blob); + +... +``` + +More TBD. + diff --git a/docs/tasks/js/_js-read.md b/docs/tasks/js/_js-read.md new file mode 100644 index 00000000..36942302 --- /dev/null +++ b/docs/tasks/js/_js-read.md @@ -0,0 +1,25 @@ + +Once you've used [`createC2pa`](https://contentauth.github.io/c2pa-js/functions/_contentauth_c2pa-web.index.createC2pa.html) to create an instance of c2pa-web (for example in `c2pa` in this example), use `c2pa.reader.fromBlob()` to create a [Reader](https://contentauth.github.io/c2pa-js/interfaces/_contentauth_c2pa-web.index.Reader.html) for an asset. + +Then use Reader's [`manifestStore()` method](https://contentauth.github.io/c2pa-js/interfaces/_contentauth_c2pa-web.index.Reader.html#manifeststore) to read manifest data (if any) from the asset. + +For example: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; +const c2pa = createC2pa({ wasmSrc }); + +const response = await fetch( + 'https://contentauth.github.io/example-assets/images/Firefly_tabby_cat.jpg' +); + +const blob = await response.blob(); +const reader = await c2pa.reader.fromBlob(blob.type, blob); +const manifestStore = await reader.manifestStore(); + +console.log(manifestStore); + +// Free SDK objects when they are no longer needed to avoid memory leaks. +await reader.free(); +``` diff --git a/docs/tasks/js/_js-settings.md b/docs/tasks/js/_js-settings.md new file mode 100644 index 00000000..54108721 --- /dev/null +++ b/docs/tasks/js/_js-settings.md @@ -0,0 +1,59 @@ +Load settings as defined inline: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +// With Vite (or similar), this resolves to the hosted WASM binary URL: +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +const settings = { + // Turn trust verification on/off (defaults to true) + verify: { verifyTrust: true }, + + // Configure trust (PEM text, a URL, or an array of URLs) + trust: { + // Example: load system trust anchors from a URL (PEM file) + trustAnchors: 'https://example.com/trust_anchors.pem', + // Optional user anchors (also PEM text or URLs) + // userAnchors: '-----BEGIN CERTIFICATE-----\n...' + }, + + // Optional builder settings + // builder: { generateC2paArchive: true }, +}; + +const c2pa = await createC2pa({ wasmSrc, settings }); + +// Use the SDK (example: read an asset) +const res = await fetch('https://contentauth.github.io/example-assets/images/cloudscape-ACA-Cr.jpeg'); +const blob = await res.blob(); + +const reader = await c2pa.reader.fromBlob(blob.type, blob); +const manifestStore = await reader.manifestStore(); +console.log(manifestStore); + +await reader.free(); +c2pa.dispose(); +``` + +To load settings from a JSON file: + +```js +import { createC2pa } from '@contentauth/c2pa-web'; +import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url'; + +const settings = await fetch('/c2pa-settings.json').then(r => r.json()); +const c2pa = await createC2pa({ wasmSrc, settings }); +``` + +Where `c2pa-settings.json` is: + +```js +{ + "verify": { "verifyTrust": true }, + "trust": { + "trustAnchors": "https://example.com/trust_anchors.pem", + "userAnchors": "https://example.com/user_anchors.pem" + }, + "builder": { "generateC2paArchive": true } +} +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-build.md b/docs/tasks/node/_node-build.md new file mode 100644 index 00000000..6271fe8d --- /dev/null +++ b/docs/tasks/node/_node-build.md @@ -0,0 +1,171 @@ +This is an example of how to assign a manifest to an asset and sign the claim using Node.js: + +```ts +import { Builder } from '@contentauth/c2pa-node'; + +// Create a new builder +const builder = Builder.new(); + +// Create with custom settings +const settings = { + builder: { + generate_c2pa_archive: true + } +}; +const builder = Builder.new(settings); + +// Or create from an existing manifest definition +const builder = Builder.withJson(manifestDefinition); + +// Or create with both manifest and settings +const builder = Builder.withJson(manifestDefinition, settings); + +// Add assertions to the manifest +builder.addAssertion('c2pa.actions', actionsAssertion); + +// Add resources +await builder.addResource('resource://example', resourceAsset); + +// Sign the manifest +const manifest = builder.sign(signer, inputAsset, outputAsset); +``` + +Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. + +## Signing a stream + +If you have an asset file's data loaded into a stream, you can use it to sign the asset + +**NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . + +```ts +import { readFile } from 'node:fs/promises'; +import { createC2pa, createTestSigner } from 'c2pa-node'; + +// read an asset into a buffer +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +// build a manifest to use for signing +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +// create a signing function +async function sign(asset, manifest) { + const signer = await createTestSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +// sign +await sign(asset, manifest); +``` + +**Remote signing** + +If you have access to a web service that performs signing, you can use it to sign remotely; for example: + +```ts +import { readFile } from 'node:fs/promises'; +import { fetch, Headers } from 'node-fetch'; +import { createC2pa, SigningAlgorithm } from 'c2pa-node'; + +function createRemoteSigner() { + return { + type: 'remote', + async reserveSize() { + const url = `https://my.signing.service/box-size`; + const res = await fetch(url); + const data = (await res.json()) as { boxSize: number }; + return data.boxSize; + }, + async sign({ reserveSize, toBeSigned }) { + const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; + const res = await fetch(url, { + method: 'POST', + headers: new Headers({ + 'Content-Type': 'application/octet-stream', + }), + body: toBeSigned, + }); + return res.buffer(); + }, + }; +} + +async function sign(asset, manifest) { + const signer = createRemoteSigner(); + const c2pa = createC2pa({ + signer, + }); + + const { signedAsset, signedManifest } = await c2pa.sign({ + asset, + manifest, + }); +} + +const buffer = await readFile('to-be-signed.jpg'); +const asset: Asset = { buffer, mimeType: 'image/jpeg' }; + +const manifest = new ManifestBuilder( + { + claim_generator: 'my-app/1.0.0', + format: 'image/jpeg', + title: 'buffer_signer.jpg', + assertions: [ + { + label: 'c2pa.actions', + data: { + actions: [ + { + action: 'c2pa.created', + }, + ], + }, + }, + { + label: 'com.custom.my-assertion', + data: { + description: 'My custom test assertion', + version: '1.0.0', + }, + }, + ], + }, + { vendor: 'cai' }, +); + +await sign(asset, manifest); +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-get-resources.md b/docs/tasks/node/_node-get-resources.md new file mode 100644 index 00000000..68006e78 --- /dev/null +++ b/docs/tasks/node/_node-get-resources.md @@ -0,0 +1,10 @@ +The example below shows how to get resources from manifest data using the Node.js library. + +```js +import { createC2pa } from 'c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const c2pa = createC2pa(); + +// TBD +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-read.md b/docs/tasks/node/_node-read.md new file mode 100644 index 00000000..d6bceb76 --- /dev/null +++ b/docs/tasks/node/_node-read.md @@ -0,0 +1,52 @@ + +Use the `c2pa.read()` function to read a manifest; for example: + +```ts +// read-manifest.ts +import { Reader } from '@contentauth/c2pa-node'; + +async function main(): Promise { + const inputPath = process.argv[2]; + if (!inputPath) { + console.error('Usage: ts-node read-manifest.ts '); + process.exit(1); + } + + const reader = await Reader.fromAsset({ path: inputPath }); + if (!reader) { + console.log('No C2PA manifest found.'); + return; + } + + const manifestStore = reader.json(); + console.log(JSON.stringify(manifestStore, null, 2)); + + // If you only want the active manifest: + const active = reader.getActive(); + if (active) { + console.log('Active manifest label:', manifestStore.active_manifest); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); +``` + +Using a buffer + +```ts +import fs from 'node:fs/promises'; +import { Reader } from '@contentauth/c2pa-node'; + +async function readFromBuffer(filePath: string): Promise { + const buffer = await fs.readFile(filePath); + const reader = await Reader.fromAsset({ buffer, mimeType: 'jpeg' }); // adjust mimeType as needed + if (!reader) { + console.log('No C2PA manifest found.'); + return; + } + console.log(JSON.stringify(reader.json(), null, 2)); +} +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-settings.md b/docs/tasks/node/_node-settings.md new file mode 100644 index 00000000..459689c1 --- /dev/null +++ b/docs/tasks/node/_node-settings.md @@ -0,0 +1,51 @@ +```ts +import { + createTrustSettings, + createCawgTrustSettings, + createVerifySettings, + mergeSettings, + settingsToJson, + loadSettingsFromFile, + loadSettingsFromUrl +} from '@contentauth/c2pa-node'; + +// Create trust settings +const trustSettings = createTrustSettings({ + verifyTrustList: true, + userAnchors: "path/to/user-anchors.pem", + trustAnchors: "path/to/trust-anchors.pem", + allowedList: "path/to/allowed-list.pem" +}); + +// Create CAWG trust settings +const cawgTrustSettings = createCawgTrustSettings({ + verifyTrustList: true, + trustAnchors: "path/to/cawg-anchors.pem" +}); + +// Create verify settings +const verifySettings = createVerifySettings({ + verifyAfterReading: false, + verifyAfterSign: false, + verifyTrust: true, + verifyTimestampTrust: true, + ocspFetch: true, + remoteManifestFetch: true, + skipIngredientConflictResolution: false, + strictV1Validation: false +}); + +// Merge multiple settings +const combinedSettings = mergeSettings(trustSettings, verifySettings); + +// Convert settings to JSON string +const jsonString = settingsToJson(combinedSettings); + +// Load settings from file (JSON or TOML) +const fileSettings = await loadSettingsFromFile('./c2pa-settings.toml'); +const reader = await Reader.fromAsset(inputAsset, fileSettings); + +// Load settings from URL +const urlSettings = await loadSettingsFromUrl('https://example.com/c2pa-settings.json'); +const builder = Builder.new(urlSettings); +``` \ No newline at end of file diff --git a/docs/tasks/node/_node-wip.md b/docs/tasks/node/_node-wip.md new file mode 100644 index 00000000..ca2cd717 --- /dev/null +++ b/docs/tasks/node/_node-wip.md @@ -0,0 +1,3 @@ +:::note +The Node.js library is being revised. The documentation will be updated as soon as possible with the latest changes. +::: \ No newline at end of file diff --git a/docs/tasks/python/_python-build.md b/docs/tasks/python/_python-build.md new file mode 100644 index 00000000..b90cfb11 --- /dev/null +++ b/docs/tasks/python/_python-build.md @@ -0,0 +1,71 @@ + +This is an example of how to assign a manifest to an asset and sign the claim using Python. + +Use a `Builder` object to add a manifest to an asset. + +```python +# Import the C2PA Python package. +from c2pa import * + +# Import standard general-purpose packages. +import os +import io +import logging +import json +import base64 + +try: + # Define a function to sign the claim bytes. + # In this case we are using a pre-defined sign_ps256 method, passing in our private cert. + # Normally this cert would be kept safe in some other location. + def private_sign(data: bytes) -> bytes: + return sign_ps256(data, "tests/fixtures/ps256.pem") + + # Read our public certs into memory. + certs = open(data_dir + "ps256.pub", "rb").read() + + # Create a signer from the private signer, certs and a time stamp service URL. + signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + + # Create a builder add a thumbnail resource and an ingredient file. + builder = Builder(manifest_json) + + # Add the resource from a stream. + a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb") + builder.add_resource("image/jpeg", a_thumbnail_jpg_stream) + + # Add the resource from a file. + # The URI provided here, "thumbnail", must match an identifier in the manifest definition. + builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg") + + # Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail. + ingredient_json = { + "title": "A.jpg", + "relationship": "parentOf", # "parentOf", "componentOf" or "inputTo" + "thumbnail": { + "identifier": "thumbnail", + "format": "image/jpeg" + } + } + + # Add the ingredient from a stream. + a_jpg_stream = open("tests/fixtures/A.jpg", "rb") + builder.add_ingredient("image/jpeg", a_jpg_stream) + + # At this point archive or unarchive Builder to continue later. + # This example uses a bytearray for the archive stream. + # All ingredients and resources are saved in the archive. + archive = io.BytesIO(bytearray()) + builder.to_archive(archive) + archive.seek() + builder = builder.from_archive(archive) + + # Sign the builder with a stream and output it to a stream. + # This returns the binary manifest data that could be uploaded to cloud storage. + input_stream = open("tests/fixtures/A.jpg", "rb") + output_stream = open("target/out.jpg", "wb") + c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream) + +except Exception as err: + print(err) +``` \ No newline at end of file diff --git a/docs/tasks/python/_python-get-resources.md b/docs/tasks/python/_python-get-resources.md new file mode 100644 index 00000000..833d8660 --- /dev/null +++ b/docs/tasks/python/_python-get-resources.md @@ -0,0 +1,32 @@ + +The example below shows how to get resources from manifest data using the Python library. + +Retrieve binary resources such as thumbnails from the manifest data, use the `resource_to_stream` or `resource_to_file` methods using the associated `identifier` field values and a `uri`. + +_NOTE: Need to add example of using `reader.resource_to_stream()`._ + +```py +# Import the C2PA Python package. +from c2pa import * + +# Import standard general-purpose packages. +import os +import io +import logging +import json + +try: + # Create a reader from a file path. + reader = c2pa.Reader.from_file("path/to/media_file.jpg") + + # Get the active manifest. + manifest = reader.get_active_manifest() + if manifest != None: + + # get the uri to the manifest's thumbnail and write it to a file. + uri = manifest["thumbnail"]["identifier"] + reader.resource_to_file(uri, "thumbnail_v2.jpg") + +except Exception as err: + print(err) +``` \ No newline at end of file diff --git a/docs/tasks/python/_python-read.md b/docs/tasks/python/_python-read.md new file mode 100644 index 00000000..604c7831 --- /dev/null +++ b/docs/tasks/python/_python-read.md @@ -0,0 +1,48 @@ + +Use the `c2pa.Reader` object to read manifest data from a file or stream and perform validation on the manifest store. + +This example shows how to read a C2PA manifest embedded in a media file, and validate that it is trusted according to the official trust anchor certificate list. The output is printed as prettified JSON. + +```py +import sys +import c2pa +import urllib.request + +TRUST_ANCHORS_URL = "https://contentcredentials.org/trust/anchors.pem" + +def load_trust_anchors(): + try: + with urllib.request.urlopen(TRUST_ANCHORS_URL) as response: + anchors = response.read().decode('utf-8') + settings = { + "verify": { + "verify_cert_anchors": True + }, + "trust": { + "trust_anchors": anchors + } + } + c2pa.load_settings(settings) + except Exception as e: + print(f"Warning: Could not load trust anchors from {TRUST_ANCHORS_URL}: {e}") + + +def read_c2pa_data(media_path: str): + print(f"Reading {media_path}") + try: + with c2pa.Reader(media_path) as reader: + print(reader.detailed_json()) + except Exception as e: + print(f"Error reading C2PA data from {media_path}: {e}") + sys.exit(1) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + media_path = "tests/fixtures/cloud.jpg" + else: + media_path = sys.argv[1] + + load_trust_anchors() + read_c2pa_data(media_path) +``` \ No newline at end of file diff --git a/docs/tasks/python/_python-settings.md b/docs/tasks/python/_python-settings.md new file mode 100644 index 00000000..28af3472 --- /dev/null +++ b/docs/tasks/python/_python-settings.md @@ -0,0 +1,14 @@ +```py +try: + settings = { + "verify": { + "verify_cert_anchors": True + }, + "trust": { + "trust_anchors": "some url" + } + } + c2pa.load_settings(settings) +except Exception as e: + print(f"Exception loading settings: {e}") +``` \ No newline at end of file diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx new file mode 100644 index 00000000..0401b1aa --- /dev/null +++ b/docs/tasks/read.mdx @@ -0,0 +1,48 @@ +--- +id: read +title: Reading manifest data +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import JSRead from './js/_js-read.md'; +import PythonRead from './python/_python-read.md'; +import NodeRead from './node/_node-read.md'; +import CppRead from './cpp/_cpp-read.md'; +import RustRead from './rust/_rust-read.md'; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tasks/rust/_rust-build.md b/docs/tasks/rust/_rust-build.md new file mode 100644 index 00000000..6faadd69 --- /dev/null +++ b/docs/tasks/rust/_rust-build.md @@ -0,0 +1,57 @@ +This is an example of how to assign a manifest to an asset and sign the claim using Rust. + +This example is from [`c2pa-rs/sdk/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L88C5-L134C1): + +```rust +use std::io::{Cursor, Seek}; + +use anyhow::Result; +use c2pa::{ + crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, + Builder, CallbackSigner, Reader, +}; +use serde_json::json; + +let json = manifest_def(title, format); +let mut builder = Builder::from_json(&json)?; + +builder.add_ingredient_from_stream( + json!({ + "title": parent_name, + "relationship": "parentOf" + }) + .to_string(), + format, + &mut source, +)?; + +let thumb_uri = builder + .definition + .thumbnail + .as_ref() + .map(|t| t.identifier.clone()); + +// add a manifest thumbnail ( just reuse the image for now ) +if let Some(uri) = thumb_uri { + if !uri.starts_with("self#jumbf") { + source.rewind()?; + builder.add_resource(&uri, &mut source)?; + } +} + +// write the manifest builder to a zipped stream +let mut zipped = Cursor::new(Vec::new()); +builder.to_archive(&mut zipped)?; + +// unzip the manifest builder from the zipped stream +zipped.rewind()?; + +let ed_signer = + |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY); +let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS); + +let mut builder = Builder::from_archive(&mut zipped)?; +// sign the ManifestStoreBuilder and write it to the output stream +let mut dest = Cursor::new(Vec::new()); +builder.sign(&signer, format, &mut source, &mut dest)?; +``` diff --git a/docs/tasks/rust/_rust-get-resources.md b/docs/tasks/rust/_rust-get-resources.md new file mode 100644 index 00000000..ade19f45 --- /dev/null +++ b/docs/tasks/rust/_rust-get-resources.md @@ -0,0 +1,45 @@ +The example below shows how to get resources from manifest data using the Rust library. + +_NOTE: Need to clarify if/how these two code examples work together._ + +This is from [`resource_to_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.resource_to_stream) API doc: + +```rust +use c2pa::Reader; +#[cfg(feature = "file_io")] +{ + let stream = std::io::Cursor::new(Vec::new()); + let reader = Reader::from_file("path/to/file.jpg").unwrap(); + let manifest = reader.active_manifest().unwrap(); + let uri = &manifest.thumbnail_ref().unwrap().identifier; + let bytes_written = reader.resource_to_stream(uri, stream).unwrap(); +} +``` + +This is from [`c2pa-rs/examples/v2api.rs`](https://github.com/contentauth/c2pa-rs/blob/main/sdk/examples/v2api.rs#L138): + +```rust +use std::io::{Cursor, Seek}; + +use anyhow::Result; +use c2pa::{ + crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState, + Builder, CallbackSigner, Reader, +}; +use serde_json::json; + +let reader = Reader::from_stream(format, &mut dest)?; + +// extract a thumbnail image from the ManifestStore +let mut thumbnail = Cursor::new(Vec::new()); +if let Some(manifest) = reader.active_manifest() { + if let Some(thumbnail_ref) = manifest.thumbnail_ref() { + reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?; + println!( + "wrote thumbnail {} of size {}", + thumbnail_ref.format, + thumbnail.get_ref().len() + ); + } +} +``` \ No newline at end of file diff --git a/docs/tasks/rust/_rust-read.md b/docs/tasks/rust/_rust-read.md new file mode 100644 index 00000000..9521fd07 --- /dev/null +++ b/docs/tasks/rust/_rust-read.md @@ -0,0 +1,53 @@ + +Use the [`Reader`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html) struct to read manifest data from a file or stream. + +### Reading from a file + +Use [`from_file`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file) to read manifest data from a file: + +```rust +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); + +use c2pa::Reader; +let reader = Reader::from_file("path/to/file.jpg").unwrap(); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_file_async). + +### Reading from a stream + +Use [`from_stream`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream) to read manifest data from a stream: + +```rust +use std::{ + io::{Cursor, Write}, + process::{Command, Stdio}, +}; + +use anyhow::Result; +use c2pa::{settings::load_settings_from_str, Builder, CallbackSigner, Reader}; +use c2pa_crypto::raw_signature::SigningAlg; +use serde_json::json; + +const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg"); +const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub"); +const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem"); + +let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg")); +let reader = Reader::from_stream("image/jpeg", stream).unwrap(); +println!("{}", reader.json()); +``` + +There is also an asynchronous version of this method, [`from_stream_async`](https://docs.rs/c2pa/latest/c2pa/struct.Reader.html#method.from_stream_async). diff --git a/docs/tasks/rust/_rust-settings.md b/docs/tasks/rust/_rust-settings.md new file mode 100644 index 00000000..8effb800 --- /dev/null +++ b/docs/tasks/rust/_rust-settings.md @@ -0,0 +1,13 @@ +```rust +use c2pa::{Context, Builder, Result}; + +fn main() -> Result<()> { + // Create a Context with settings from a file + let context = Context::new() + .with_settings(include_str!("settings.json"))?; + + let builder = Builder::from_context(context); + // ... use builder + Ok(()) +} +``` \ No newline at end of file diff --git a/docs/tasks/settings.mdx b/docs/tasks/settings.mdx new file mode 100644 index 00000000..e0f62fbf --- /dev/null +++ b/docs/tasks/settings.mdx @@ -0,0 +1,49 @@ +--- +id: settings +title: Configuring settings +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import JSRead from './js/_js-settings.md'; +import PythonRead from './python/_python-settings.md'; +import NodeSettings from './node/_node-settings.md'; +import CppRead from './cpp/_cpp-settings.md'; +import RustRead from './rust/_rust-settings.md'; + + + + +Settings supports: `trust`, `cawgTrust`, `verify`, and `builder`. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docusaurus.config.js b/docusaurus.config.js index d4651312..a9e90f19 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -203,6 +203,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, + additionalLanguages: ['rust'], }, algolia: { // The application ID provided by Algolia diff --git a/sidebars.js b/sidebars.js index 9bdc569a..9ba65fba 100644 --- a/sidebars.js +++ b/sidebars.js @@ -24,7 +24,7 @@ const sidebars = { }, { type: 'category', - label: 'Working with manifests', + label: 'Understanding manifests', link: { type: 'doc', id: 'manifest/understanding-manifest' }, collapsed: true, items: [ @@ -103,6 +103,34 @@ const sidebars = { }, ], }, + { + type: 'category', + label: 'Working with the SDK', + link: { type: 'doc', id: 'tasks/index' }, + collapsed: true, + items: [ + { + type: 'doc', + id: 'tasks/settings', + label: 'Configuring settings', + }, + { + type: 'doc', + id: 'tasks/read', + label: 'Reading manifest data', + }, + { + type: 'doc', + id: 'tasks/get-resources', + label: 'Getting manifest resources', + }, + { + type: 'doc', + id: 'tasks/build', + label: 'Adding and signing a manifest', + }, + ], + }, { type: 'category', label: 'Signing and certificates', diff --git a/static/schemas/Builder.schema.json b/static/schemas/Builder.schema.json index 503545c0..5d7c99f1 100644 --- a/static/schemas/Builder.schema.json +++ b/static/schemas/Builder.schema.json @@ -6,20 +6,14 @@ "properties": { "claim_version": { "description": "The version of the claim. Defaults to 2.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint8", "minimum": 0, "maximum": 255 }, "vendor": { "description": "Optional prefix added to the generated Manifest Label\nThis is typically a reverse domain name.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_generator_info": { "description": "Claim Generator Info is always required with an entry", @@ -36,20 +30,14 @@ }, "metadata": { "description": "Optional manifest metadata. This will be deprecated in the future; not recommended to use.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssertionMetadata" } }, "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", @@ -90,27 +78,18 @@ }, "redactions": { "description": "A list of redactions - URIs to redacted assertions.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "label": { "description": "Allows you to pre-define the manifest label, which must be unique.\nNot intended for general use. If not set, it will be assigned automatically.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "remote_url": { "description": "Optional remote URL for the manifest", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "no_embed": { "description": "If true, the manifest store will not be embedded in the asset on sign", @@ -118,10 +97,7 @@ }, "base_path": { "description": "Base path to search for resources.", - "type": [ - "string", - "null" - ], + "type": ["string", "null"], "deprecated": true }, "intent": { @@ -145,10 +121,7 @@ } } }, - "required": [ - "no_embed", - "timestamp_manifest_labels" - ], + "required": ["no_embed", "timestamp_manifest_labels"], "$defs": { "ClaimGeneratorInfo": { "description": "Description of the claim generator, or the software used in generating the claim.\n\nThis structure is also used for actions softwareAgent", @@ -160,10 +133,7 @@ }, "version": { "description": "A human readable string of the product's version", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "icon": { "description": "hashed URI to the icon (either embedded or remote)", @@ -178,15 +148,10 @@ }, "operating_system": { "description": "A human readable string of the OS the claim generator is running on", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": true }, "UriOrResource": { @@ -213,33 +178,21 @@ }, "data_types": { "description": "More detailed data types as defined in the C2PA spec.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } }, "alg": { "description": "The algorithm used to hash the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "The hash of the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "format", - "identifier" - ] + "required": ["format", "identifier"] }, "AssetType": { "type": "object", @@ -248,15 +201,10 @@ "type": "string" }, "version": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "type" - ] + "required": ["type"] }, "HashedUri": { "description": "A `HashedUri` provides a reference to content available within the same\nmanifest store.\n\nThis is described in [§8.3, URI References], of the C2PA Technical\nSpecification.\n\n[§8.3, URI References]: https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_uri_references", @@ -268,10 +216,7 @@ }, "alg": { "description": "A string identifying the cryptographic hash algorithm used to compute\nthe hash", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "Byte string containing the hash value", @@ -284,20 +229,14 @@ } } }, - "required": [ - "url", - "hash" - ] + "required": ["url", "hash"] }, "AssertionMetadata": { "description": "The AssertionMetadata structure can be used as part of other assertions or on its own to reference others", "type": "object", "properties": { "reviewRatings": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ReviewRating" } @@ -333,10 +272,7 @@ ] }, "localizations": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "object", "additionalProperties": { @@ -368,10 +304,7 @@ "type": "string" }, "code": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "value": { "type": "integer", @@ -380,10 +313,7 @@ "maximum": 255 } }, - "required": [ - "explanation", - "value" - ] + "required": ["explanation", "value"] }, "DateT": { "type": "string" @@ -398,25 +328,17 @@ }, "details": { "description": "A human-readable string giving details about the source of the assertion data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "actors": { "description": "A list of [`Actor`]s associated with this source.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Actor" } } }, - "required": [ - "type" - ] + "required": ["type"] }, "Actor": { "description": "Identifies a person responsible for an action.", @@ -424,17 +346,11 @@ "properties": { "identifier": { "description": "An identifier for a human actor, used when the \"type\" is `humanEntry.identified`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "credentials": { "description": "List of references to W3C Verifiable Credentials.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/HashedUri" } @@ -454,24 +370,15 @@ }, "name": { "description": "A free-text string representing a human-readable name for the region which might be used in a user interface.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "identifier": { "description": "A free-text string representing a machine-readable, unique to this assertion, identifier for the region.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "type": { "description": "A value from a controlled vocabulary such as or an entity-specific\nvalue (e.g., com.litware.newType) that represents the type of thing(s) depicted by a region.\n\nNote this field serializes/deserializes into the name `type`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "role": { "description": "A value from our controlled vocabulary or an entity-specific value (e.g., com.litware.coolArea) that represents\nthe role of a region among other regions.", @@ -486,10 +393,7 @@ }, "description": { "description": "A free-text string.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Additional information about the asset.", @@ -503,9 +407,7 @@ ] } }, - "required": [ - "region" - ] + "required": ["region"] }, "Range": { "description": "A spatial, temporal, frame, or textual range describing the region of interest.", @@ -571,9 +473,7 @@ ] } }, - "required": [ - "type" - ] + "required": ["type"] }, "RangeType": { "description": "The type of range for the region of interest.", @@ -623,43 +523,27 @@ }, "width": { "description": "The width for rectangles or diameter for circles.\n\nThis field can be ignored for polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "height": { "description": "The height of a rectnagle.\n\nThis field can be ignored for circles and polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "inside": { "description": "If the range is inside the shape.\n\nThe default value is true.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "vertices": { "description": "The vertices of the polygon.\n\nThis field can be ignored for rectangles and circles.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Coordinate" } } }, - "required": [ - "type", - "unit", - "origin" - ] + "required": ["type", "unit", "origin"] }, "ShapeType": { "description": "The type of shape for the range.", @@ -711,10 +595,7 @@ "format": "double" } }, - "required": [ - "x", - "y" - ] + "required": ["x", "y"] }, "Time": { "description": "A temporal range representing a starting time to an ending time.", @@ -727,17 +608,11 @@ }, "start": { "description": "The start time or the start of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "end": { "description": "The end time or the end of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } } }, @@ -757,18 +632,12 @@ "properties": { "start": { "description": "The start of the frame or the end of the asset if not present.\n\nThe first frame/page starts at 0.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end of the frame inclusive or the end of the asset if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } } @@ -785,9 +654,7 @@ } } }, - "required": [ - "selectors" - ] + "required": ["selectors"] }, "TextSelectorRange": { "description": "One or two [`TextSelector`][TextSelector] identifiying the range to select.", @@ -809,9 +676,7 @@ ] } }, - "required": [ - "selector" - ] + "required": ["selector"] }, "TextSelector": { "description": "Selects a range of text via a fragment identifier.\n\nThis is modeled after the W3C Web Annotation selector model.", @@ -823,24 +688,16 @@ }, "start": { "description": "The start character offset or the start of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end character offset or the end of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } }, - "required": [ - "fragment" - ] + "required": ["fragment"] }, "Item": { "description": "Description of the boundaries of an identified range.", @@ -855,10 +712,7 @@ "type": "string" } }, - "required": [ - "identifier", - "value" - ] + "required": ["identifier", "value"] }, "Role": { "description": "A role describing the region.", @@ -916,38 +770,23 @@ "properties": { "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "document_id": { "description": "Document ID from `xmpMM:DocumentID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "instance_id": { "description": "Instance ID from `xmpMM:InstanceID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "provenance": { "description": "URI from `dcterms:provenance` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "thumbnail": { "description": "A thumbnail image capturing the visual state at the time of import.\n\nA tuple of thumbnail MIME format (for example `image/jpeg`) and binary bits of the image.", @@ -962,10 +801,7 @@ }, "hash": { "description": "An optional hash of the asset to prevent duplicates.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "relationship": { "description": "Set to `ParentOf` if this is the parent ingredient.\n\nThere can only be one parent ingredient in the ingredients.", @@ -974,17 +810,11 @@ }, "active_manifest": { "description": "The active manifest label (if one exists).\n\nIf this ingredient has a [`ManifestStore`],\nthis will hold the label of the active [`Manifest`].\n\n[`Manifest`]: crate::Manifest\n[`ManifestStore`]: crate::ManifestStore", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "validation_status": { "description": "Validation status (Ingredient v1 & v2)", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ValidationStatus" } @@ -1013,17 +843,11 @@ }, "description": { "description": "Additional description of the ingredient.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "informational_URI": { "description": "URI to an informational page about the ingredient or its data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Any additional [`Metadata`] as defined in the C2PA spec.\n\n[`Metadata`]: crate::Metadata", @@ -1038,10 +862,7 @@ }, "data_types": { "description": "Additional information about the data's type to the ingredient V2 structure.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } @@ -1059,16 +880,10 @@ }, "label": { "description": "The ingredient's label as assigned in the manifest.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "ocsp_responses": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ResourceRef" } @@ -1103,28 +918,17 @@ "type": "string" }, "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "explanation": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "success": { - "type": [ - "boolean", - "null" - ], + "type": ["boolean", "null"], "writeOnly": true } }, - "required": [ - "code" - ] + "required": ["code"] }, "ValidationResults": { "description": "A map of validation results for a manifest store.\n\nThe map contains the validation results for the active manifest and any ingredient deltas.\nIt is normal for there to be many", @@ -1143,10 +947,7 @@ }, "ingredientDeltas": { "description": "List of any changes/deltas between the current and previous validation results for each ingredient's\nmanifest. Present if the the ingredient is a C2PA asset.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/IngredientDeltaValidationResult" } @@ -1178,11 +979,7 @@ } } }, - "required": [ - "success", - "informational", - "failure" - ] + "required": ["success", "informational", "failure"] }, "IngredientDeltaValidationResult": { "description": "Represents any changes or deltas between the current and previous validation results for an ingredient's manifest.", @@ -1197,10 +994,7 @@ "$ref": "#/$defs/StatusCodes" } }, - "required": [ - "ingredientAssertionURI", - "validationDeltas" - ] + "required": ["ingredientAssertionURI", "validationDeltas"] }, "AssertionDefinition": { "description": "Defines an assertion that consists of a label that can be either\na C2PA-defined assertion label or a custom label in reverse domain format.", @@ -1230,26 +1024,16 @@ "type": "boolean" } }, - "required": [ - "label", - "data" - ] + "required": ["label", "data"] }, "AssertionData": { "description": "This allows the assertion to be expressed as CBOR or JSON.\nThe default is CBOR unless you specify that an assertion should be JSON.", - "anyOf": [ - true - ] + "anyOf": [true] }, "ManifestAssertionKind": { "description": "Assertions in C2PA can be stored in several formats", "type": "string", - "enum": [ - "Cbor", - "Json", - "Binary", - "Uri" - ] + "enum": ["Cbor", "Json", "Binary", "Uri"] }, "BuilderIntent": { "description": "Represents the type of builder flow being used.\n\nThis determines how the builder will be used, such as creating a new asset, opening an existing asset,\nor updating an existing asset.", @@ -1262,9 +1046,7 @@ "$ref": "#/$defs/DigitalSourceType" } }, - "required": [ - "create" - ], + "required": ["create"], "additionalProperties": false }, { @@ -1402,4 +1184,4 @@ ] } } -} \ No newline at end of file +} diff --git a/static/schemas/ManifestDefinition.schema.json b/static/schemas/ManifestDefinition.schema.json index 0579c226..6cefa44f 100644 --- a/static/schemas/ManifestDefinition.schema.json +++ b/static/schemas/ManifestDefinition.schema.json @@ -6,20 +6,14 @@ "properties": { "claim_version": { "description": "The version of the claim. Defaults to 2.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint8", "minimum": 0, "maximum": 255 }, "vendor": { "description": "Optional prefix added to the generated Manifest Label\nThis is typically a reverse domain name.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_generator_info": { "description": "Claim Generator Info is always required with an entry", @@ -36,20 +30,14 @@ }, "metadata": { "description": "Optional manifest metadata. This will be deprecated in the future; not recommended to use.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssertionMetadata" } }, "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", @@ -90,20 +78,14 @@ }, "redactions": { "description": "A list of redactions - URIs to redacted assertions.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "label": { "description": "Allows you to pre-define the manifest label, which must be unique.\nNot intended for general use. If not set, it will be assigned automatically.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, "$defs": { @@ -117,10 +99,7 @@ }, "version": { "description": "A human readable string of the product's version", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "icon": { "description": "hashed URI to the icon (either embedded or remote)", @@ -135,15 +114,10 @@ }, "operating_system": { "description": "A human readable string of the OS the claim generator is running on", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": true }, "UriOrResource": { @@ -170,33 +144,21 @@ }, "data_types": { "description": "More detailed data types as defined in the C2PA spec.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } }, "alg": { "description": "The algorithm used to hash the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "The hash of the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "format", - "identifier" - ] + "required": ["format", "identifier"] }, "AssetType": { "type": "object", @@ -205,15 +167,10 @@ "type": "string" }, "version": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "type" - ] + "required": ["type"] }, "HashedUri": { "description": "A `HashedUri` provides a reference to content available within the same\nmanifest store.\n\nThis is described in [§8.3, URI References], of the C2PA Technical\nSpecification.\n\n[§8.3, URI References]: https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_uri_references", @@ -225,10 +182,7 @@ }, "alg": { "description": "A string identifying the cryptographic hash algorithm used to compute\nthe hash", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "Byte string containing the hash value", @@ -241,20 +195,14 @@ } } }, - "required": [ - "url", - "hash" - ] + "required": ["url", "hash"] }, "AssertionMetadata": { "description": "The AssertionMetadata structure can be used as part of other assertions or on its own to reference others", "type": "object", "properties": { "reviewRatings": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ReviewRating" } @@ -290,10 +238,7 @@ ] }, "localizations": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "object", "additionalProperties": { @@ -325,10 +270,7 @@ "type": "string" }, "code": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "value": { "type": "integer", @@ -337,10 +279,7 @@ "maximum": 255 } }, - "required": [ - "explanation", - "value" - ] + "required": ["explanation", "value"] }, "DateT": { "type": "string" @@ -355,25 +294,17 @@ }, "details": { "description": "A human-readable string giving details about the source of the assertion data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "actors": { "description": "A list of [`Actor`]s associated with this source.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Actor" } } }, - "required": [ - "type" - ] + "required": ["type"] }, "Actor": { "description": "Identifies a person responsible for an action.", @@ -381,17 +312,11 @@ "properties": { "identifier": { "description": "An identifier for a human actor, used when the \"type\" is `humanEntry.identified`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "credentials": { "description": "List of references to W3C Verifiable Credentials.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/HashedUri" } @@ -411,24 +336,15 @@ }, "name": { "description": "A free-text string representing a human-readable name for the region which might be used in a user interface.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "identifier": { "description": "A free-text string representing a machine-readable, unique to this assertion, identifier for the region.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "type": { "description": "A value from a controlled vocabulary such as or an entity-specific\nvalue (e.g., com.litware.newType) that represents the type of thing(s) depicted by a region.\n\nNote this field serializes/deserializes into the name `type`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "role": { "description": "A value from our controlled vocabulary or an entity-specific value (e.g., com.litware.coolArea) that represents\nthe role of a region among other regions.", @@ -443,10 +359,7 @@ }, "description": { "description": "A free-text string.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Additional information about the asset.", @@ -460,9 +373,7 @@ ] } }, - "required": [ - "region" - ] + "required": ["region"] }, "Range": { "description": "A spatial, temporal, frame, or textual range describing the region of interest.", @@ -528,9 +439,7 @@ ] } }, - "required": [ - "type" - ] + "required": ["type"] }, "RangeType": { "description": "The type of range for the region of interest.", @@ -580,43 +489,27 @@ }, "width": { "description": "The width for rectangles or diameter for circles.\n\nThis field can be ignored for polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "height": { "description": "The height of a rectnagle.\n\nThis field can be ignored for circles and polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "inside": { "description": "If the range is inside the shape.\n\nThe default value is true.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "vertices": { "description": "The vertices of the polygon.\n\nThis field can be ignored for rectangles and circles.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Coordinate" } } }, - "required": [ - "type", - "unit", - "origin" - ] + "required": ["type", "unit", "origin"] }, "ShapeType": { "description": "The type of shape for the range.", @@ -668,10 +561,7 @@ "format": "double" } }, - "required": [ - "x", - "y" - ] + "required": ["x", "y"] }, "Time": { "description": "A temporal range representing a starting time to an ending time.", @@ -684,17 +574,11 @@ }, "start": { "description": "The start time or the start of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "end": { "description": "The end time or the end of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } } }, @@ -714,18 +598,12 @@ "properties": { "start": { "description": "The start of the frame or the end of the asset if not present.\n\nThe first frame/page starts at 0.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end of the frame inclusive or the end of the asset if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } } @@ -742,9 +620,7 @@ } } }, - "required": [ - "selectors" - ] + "required": ["selectors"] }, "TextSelectorRange": { "description": "One or two [`TextSelector`][TextSelector] identifiying the range to select.", @@ -766,9 +642,7 @@ ] } }, - "required": [ - "selector" - ] + "required": ["selector"] }, "TextSelector": { "description": "Selects a range of text via a fragment identifier.\n\nThis is modeled after the W3C Web Annotation selector model.", @@ -780,24 +654,16 @@ }, "start": { "description": "The start character offset or the start of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end character offset or the end of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } }, - "required": [ - "fragment" - ] + "required": ["fragment"] }, "Item": { "description": "Description of the boundaries of an identified range.", @@ -812,10 +678,7 @@ "type": "string" } }, - "required": [ - "identifier", - "value" - ] + "required": ["identifier", "value"] }, "Role": { "description": "A role describing the region.", @@ -873,38 +736,23 @@ "properties": { "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "document_id": { "description": "Document ID from `xmpMM:DocumentID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "instance_id": { "description": "Instance ID from `xmpMM:InstanceID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "provenance": { "description": "URI from `dcterms:provenance` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "thumbnail": { "description": "A thumbnail image capturing the visual state at the time of import.\n\nA tuple of thumbnail MIME format (for example `image/jpeg`) and binary bits of the image.", @@ -919,10 +767,7 @@ }, "hash": { "description": "An optional hash of the asset to prevent duplicates.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "relationship": { "description": "Set to `ParentOf` if this is the parent ingredient.\n\nThere can only be one parent ingredient in the ingredients.", @@ -931,17 +776,11 @@ }, "active_manifest": { "description": "The active manifest label (if one exists).\n\nIf this ingredient has a [`ManifestStore`],\nthis will hold the label of the active [`Manifest`].\n\n[`Manifest`]: crate::Manifest\n[`ManifestStore`]: crate::ManifestStore", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "validation_status": { "description": "Validation status (Ingredient v1 & v2)", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ValidationStatus" } @@ -970,17 +809,11 @@ }, "description": { "description": "Additional description of the ingredient.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "informational_URI": { "description": "URI to an informational page about the ingredient or its data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Any additional [`Metadata`] as defined in the C2PA spec.\n\n[`Metadata`]: crate::Metadata", @@ -995,10 +828,7 @@ }, "data_types": { "description": "Additional information about the data's type to the ingredient V2 structure.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } @@ -1016,16 +846,10 @@ }, "label": { "description": "The ingredient's label as assigned in the manifest.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "ocsp_responses": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ResourceRef" } @@ -1060,28 +884,17 @@ "type": "string" }, "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "explanation": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "success": { - "type": [ - "boolean", - "null" - ], + "type": ["boolean", "null"], "writeOnly": true } }, - "required": [ - "code" - ] + "required": ["code"] }, "ValidationResults": { "description": "A map of validation results for a manifest store.\n\nThe map contains the validation results for the active manifest and any ingredient deltas.\nIt is normal for there to be many", @@ -1100,10 +913,7 @@ }, "ingredientDeltas": { "description": "List of any changes/deltas between the current and previous validation results for each ingredient's\nmanifest. Present if the the ingredient is a C2PA asset.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/IngredientDeltaValidationResult" } @@ -1135,11 +945,7 @@ } } }, - "required": [ - "success", - "informational", - "failure" - ] + "required": ["success", "informational", "failure"] }, "IngredientDeltaValidationResult": { "description": "Represents any changes or deltas between the current and previous validation results for an ingredient's manifest.", @@ -1154,10 +960,7 @@ "$ref": "#/$defs/StatusCodes" } }, - "required": [ - "ingredientAssertionURI", - "validationDeltas" - ] + "required": ["ingredientAssertionURI", "validationDeltas"] }, "AssertionDefinition": { "description": "Defines an assertion that consists of a label that can be either\na C2PA-defined assertion label or a custom label in reverse domain format.", @@ -1187,26 +990,16 @@ "type": "boolean" } }, - "required": [ - "label", - "data" - ] + "required": ["label", "data"] }, "AssertionData": { "description": "This allows the assertion to be expressed as CBOR or JSON.\nThe default is CBOR unless you specify that an assertion should be JSON.", - "anyOf": [ - true - ] + "anyOf": [true] }, "ManifestAssertionKind": { "description": "Assertions in C2PA can be stored in several formats", "type": "string", - "enum": [ - "Cbor", - "Json", - "Binary", - "Uri" - ] + "enum": ["Cbor", "Json", "Binary", "Uri"] } } -} \ No newline at end of file +} diff --git a/static/schemas/Reader.schema.json b/static/schemas/Reader.schema.json index 13e5db58..cf19f21c 100644 --- a/static/schemas/Reader.schema.json +++ b/static/schemas/Reader.schema.json @@ -6,10 +6,7 @@ "properties": { "active_manifest": { "description": "A label for the active (most recent) manifest in the store", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "manifests": { "description": "A HashMap of Manifests", @@ -20,10 +17,7 @@ }, "validation_status": { "description": "ValidationStatus generated when loading the ManifestStore from an asset", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ValidationStatus" } @@ -51,9 +45,7 @@ ] } }, - "required": [ - "manifests" - ], + "required": ["manifests"], "$defs": { "Manifest": { "description": "A Manifest represents all the information in a c2pa manifest", @@ -61,51 +53,33 @@ "properties": { "vendor": { "description": "Optional prefix added to the generated Manifest label.\nThis is typically an internet domain name for the vendor (i.e. `adobe`).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_generator": { "description": "A User Agent formatted string identifying the software/hardware/system produced this claim\nSpaces are not allowed in names, versions can be specified with product/1.0 syntax.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_generator_info": { "description": "A list of claim generator info data identifying the software/hardware/system produced this claim.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ClaimGeneratorInfo" } }, "metadata": { "description": "A list of user metadata for this claim.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssertionMetadata" } }, "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "instance_id": { "description": "Instance ID from `xmpMM:InstanceID` in XMP metadata.", @@ -131,10 +105,7 @@ }, "credentials": { "description": "A List of verified credentials", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": true }, "assertions": { @@ -147,10 +118,7 @@ }, "redactions": { "description": "A list of redactions - URIs to a redacted assertions", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } @@ -167,17 +135,11 @@ ] }, "label": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_version": { "description": "The version of the claim, parsed from the claim label.\n\nFor example:\n- `c2pa.claim.v2` -> 2\n- `c2pa.claim` -> 1", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint8", "minimum": 0, "maximum": 255 @@ -194,10 +156,7 @@ }, "version": { "description": "A human readable string of the product's version", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "icon": { "description": "hashed URI to the icon (either embedded or remote)", @@ -212,15 +171,10 @@ }, "operating_system": { "description": "A human readable string of the OS the claim generator is running on", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": true }, "UriOrResource": { @@ -247,33 +201,21 @@ }, "data_types": { "description": "More detailed data types as defined in the C2PA spec.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } }, "alg": { "description": "The algorithm used to hash the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "The hash of the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "format", - "identifier" - ] + "required": ["format", "identifier"] }, "AssetType": { "type": "object", @@ -282,15 +224,10 @@ "type": "string" }, "version": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "type" - ] + "required": ["type"] }, "HashedUri": { "description": "A `HashedUri` provides a reference to content available within the same\nmanifest store.\n\nThis is described in [§8.3, URI References], of the C2PA Technical\nSpecification.\n\n[§8.3, URI References]: https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_uri_references", @@ -302,10 +239,7 @@ }, "alg": { "description": "A string identifying the cryptographic hash algorithm used to compute\nthe hash", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "Byte string containing the hash value", @@ -318,20 +252,14 @@ } } }, - "required": [ - "url", - "hash" - ] + "required": ["url", "hash"] }, "AssertionMetadata": { "description": "The AssertionMetadata structure can be used as part of other assertions or on its own to reference others", "type": "object", "properties": { "reviewRatings": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ReviewRating" } @@ -367,10 +295,7 @@ ] }, "localizations": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "object", "additionalProperties": { @@ -402,10 +327,7 @@ "type": "string" }, "code": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "value": { "type": "integer", @@ -414,10 +336,7 @@ "maximum": 255 } }, - "required": [ - "explanation", - "value" - ] + "required": ["explanation", "value"] }, "DateT": { "type": "string" @@ -432,25 +351,17 @@ }, "details": { "description": "A human-readable string giving details about the source of the assertion data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "actors": { "description": "A list of [`Actor`]s associated with this source.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Actor" } } }, - "required": [ - "type" - ] + "required": ["type"] }, "Actor": { "description": "Identifies a person responsible for an action.", @@ -458,17 +369,11 @@ "properties": { "identifier": { "description": "An identifier for a human actor, used when the \"type\" is `humanEntry.identified`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "credentials": { "description": "List of references to W3C Verifiable Credentials.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/HashedUri" } @@ -488,24 +393,15 @@ }, "name": { "description": "A free-text string representing a human-readable name for the region which might be used in a user interface.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "identifier": { "description": "A free-text string representing a machine-readable, unique to this assertion, identifier for the region.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "type": { "description": "A value from a controlled vocabulary such as or an entity-specific\nvalue (e.g., com.litware.newType) that represents the type of thing(s) depicted by a region.\n\nNote this field serializes/deserializes into the name `type`.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "role": { "description": "A value from our controlled vocabulary or an entity-specific value (e.g., com.litware.coolArea) that represents\nthe role of a region among other regions.", @@ -520,10 +416,7 @@ }, "description": { "description": "A free-text string.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Additional information about the asset.", @@ -537,9 +430,7 @@ ] } }, - "required": [ - "region" - ] + "required": ["region"] }, "Range": { "description": "A spatial, temporal, frame, or textual range describing the region of interest.", @@ -605,9 +496,7 @@ ] } }, - "required": [ - "type" - ] + "required": ["type"] }, "RangeType": { "description": "The type of range for the region of interest.", @@ -657,43 +546,27 @@ }, "width": { "description": "The width for rectangles or diameter for circles.\n\nThis field can be ignored for polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "height": { "description": "The height of a rectnagle.\n\nThis field can be ignored for circles and polygons.", - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "format": "double" }, "inside": { "description": "If the range is inside the shape.\n\nThe default value is true.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "vertices": { "description": "The vertices of the polygon.\n\nThis field can be ignored for rectangles and circles.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/Coordinate" } } }, - "required": [ - "type", - "unit", - "origin" - ] + "required": ["type", "unit", "origin"] }, "ShapeType": { "description": "The type of shape for the range.", @@ -745,10 +618,7 @@ "format": "double" } }, - "required": [ - "x", - "y" - ] + "required": ["x", "y"] }, "Time": { "description": "A temporal range representing a starting time to an ending time.", @@ -761,17 +631,11 @@ }, "start": { "description": "The start time or the start of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "end": { "description": "The end time or the end of the asset if not present.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } } }, @@ -791,18 +655,12 @@ "properties": { "start": { "description": "The start of the frame or the end of the asset if not present.\n\nThe first frame/page starts at 0.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end of the frame inclusive or the end of the asset if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } } @@ -819,9 +677,7 @@ } } }, - "required": [ - "selectors" - ] + "required": ["selectors"] }, "TextSelectorRange": { "description": "One or two [`TextSelector`][TextSelector] identifiying the range to select.", @@ -843,9 +699,7 @@ ] } }, - "required": [ - "selector" - ] + "required": ["selector"] }, "TextSelector": { "description": "Selects a range of text via a fragment identifier.\n\nThis is modeled after the W3C Web Annotation selector model.", @@ -857,24 +711,16 @@ }, "start": { "description": "The start character offset or the start of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" }, "end": { "description": "The end character offset or the end of the fragment if not present.", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "int32" } }, - "required": [ - "fragment" - ] + "required": ["fragment"] }, "Item": { "description": "Description of the boundaries of an identified range.", @@ -889,10 +735,7 @@ "type": "string" } }, - "required": [ - "identifier", - "value" - ] + "required": ["identifier", "value"] }, "Role": { "description": "A role describing the region.", @@ -950,38 +793,23 @@ "properties": { "title": { "description": "A human-readable title, generally source filename.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "format": { "description": "The format of the source file as a MIME type.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "document_id": { "description": "Document ID from `xmpMM:DocumentID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "instance_id": { "description": "Instance ID from `xmpMM:InstanceID` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "provenance": { "description": "URI from `dcterms:provenance` in XMP metadata.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "thumbnail": { "description": "A thumbnail image capturing the visual state at the time of import.\n\nA tuple of thumbnail MIME format (for example `image/jpeg`) and binary bits of the image.", @@ -996,10 +824,7 @@ }, "hash": { "description": "An optional hash of the asset to prevent duplicates.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "relationship": { "description": "Set to `ParentOf` if this is the parent ingredient.\n\nThere can only be one parent ingredient in the ingredients.", @@ -1008,17 +833,11 @@ }, "active_manifest": { "description": "The active manifest label (if one exists).\n\nIf this ingredient has a [`ManifestStore`],\nthis will hold the label of the active [`Manifest`].\n\n[`Manifest`]: crate::Manifest\n[`ManifestStore`]: crate::ManifestStore", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "validation_status": { "description": "Validation status (Ingredient v1 & v2)", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ValidationStatus" } @@ -1047,17 +866,11 @@ }, "description": { "description": "Additional description of the ingredient.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "informational_URI": { "description": "URI to an informational page about the ingredient or its data.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "metadata": { "description": "Any additional [`Metadata`] as defined in the C2PA spec.\n\n[`Metadata`]: crate::Metadata", @@ -1072,10 +885,7 @@ }, "data_types": { "description": "Additional information about the data's type to the ingredient V2 structure.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } @@ -1093,16 +903,10 @@ }, "label": { "description": "The ingredient's label as assigned in the manifest.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "ocsp_responses": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ResourceRef" } @@ -1137,28 +941,17 @@ "type": "string" }, "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "explanation": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "success": { - "type": [ - "boolean", - "null" - ], + "type": ["boolean", "null"], "writeOnly": true } }, - "required": [ - "code" - ] + "required": ["code"] }, "ValidationResults": { "description": "A map of validation results for a manifest store.\n\nThe map contains the validation results for the active manifest and any ingredient deltas.\nIt is normal for there to be many", @@ -1177,10 +970,7 @@ }, "ingredientDeltas": { "description": "List of any changes/deltas between the current and previous validation results for each ingredient's\nmanifest. Present if the the ingredient is a C2PA asset.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/IngredientDeltaValidationResult" } @@ -1212,11 +1002,7 @@ } } }, - "required": [ - "success", - "informational", - "failure" - ] + "required": ["success", "informational", "failure"] }, "IngredientDeltaValidationResult": { "description": "Represents any changes or deltas between the current and previous validation results for an ingredient's manifest.", @@ -1231,10 +1017,7 @@ "$ref": "#/$defs/StatusCodes" } }, - "required": [ - "ingredientAssertionURI", - "validationDeltas" - ] + "required": ["ingredientAssertionURI", "validationDeltas"] }, "ManifestAssertion": { "description": "A labeled container for an Assertion value in a Manifest", @@ -1250,10 +1033,7 @@ }, "instance": { "description": "There can be more than one assertion for any label", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0 }, @@ -1273,10 +1053,7 @@ "type": "boolean" } }, - "required": [ - "label", - "data" - ] + "required": ["label", "data"] }, "ManifestData": { "anyOf": [ @@ -1295,12 +1072,7 @@ "ManifestAssertionKind": { "description": "Assertions in C2PA can be stored in several formats", "type": "string", - "enum": [ - "Cbor", - "Json", - "Binary", - "Uri" - ] + "enum": ["Cbor", "Json", "Binary", "Uri"] }, "SignatureInfo": { "description": "Holds information about a signature", @@ -1319,38 +1091,23 @@ }, "issuer": { "description": "Human-readable issuing authority for this signature.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "common_name": { "description": "Human-readable for common name of this certificate.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "cert_serial_number": { "description": "The serial number of the certificate.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "time": { "description": "The time the signature was created.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "revocation_status": { "description": "Revocation status of the certificate.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] } } }, @@ -1415,4 +1172,4 @@ ] } } -} \ No newline at end of file +} diff --git a/static/schemas/Settings.schema.json b/static/schemas/Settings.schema.json index 00812c1e..e84bb901 100644 --- a/static/schemas/Settings.schema.json +++ b/static/schemas/Settings.schema.json @@ -53,14 +53,7 @@ ] } }, - "required": [ - "version", - "trust", - "cawg_trust", - "core", - "verify", - "builder" - ], + "required": ["version", "trust", "cawg_trust", "core", "verify", "builder"], "$defs": { "Trust": { "description": "Settings to configure the trust list.", @@ -72,36 +65,22 @@ }, "user_anchors": { "description": "List of additional user-provided trust anchor root certificates as a PEM bundle.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "trust_anchors": { "description": "List of default trust anchor root certificates as a PEM bundle.\n\nNormally this option contains the official C2PA-recognized trust anchors found here:\n", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "trust_config": { "description": "List of allowed extended key usage (EKU) object identifiers (OID) that\ncertificates must have.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "allowed_list": { "description": "List of explicitly allowed certificates as a PEM bundle.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "verify_trust_list" - ] + "required": ["verify_trust_list"] }, "Core": { "description": "Settings to configure core features.", @@ -109,10 +88,7 @@ "properties": { "merkle_tree_chunk_size_in_kb": { "description": "Size of the [`BmffHash`] merkle tree chunks in kilobytes.\n\nThis option is associated with the [`MerkleMap::fixed_block_size`] field.\n\nSee more information in the spec here:\n\n\n[`MerkleMap::fixed_block_size`]: crate::assertions::MerkleMap::fixed_block_size\n[`BmffHash`]: crate::assertions::BmffHash", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0 }, @@ -134,10 +110,7 @@ }, "allowed_network_hosts": { "description": "
\nThe CAWG identity assertion does not currently respect this setting.\nSee issue #1645.\n
\n\nList of host patterns that are allowed for network requests.\n\nEach pattern may include:\n- A scheme (e.g. `https://` or `http://`)\n- A hostname or IP address (e.g. `contentauthenticity.org` or `192.0.2.1`)\n - The hostname may contain a single leading wildcard (e.g. `*.contentauthenticity.org`)\n- An optional port (e.g. `contentauthenticity.org:443` or `192.0.2.1:8080`)\n\nMatching is case-insensitive. A wildcard pattern such as `*.contentauthenticity.org` matches\n`sub.contentauthenticity.org`, but does not match `contentauthenticity.org` or `fakecontentauthenticity.org`.\nIf a scheme is present in the pattern, only URIs using the same scheme are considered a match. If the scheme\nis omitted, any scheme is allowed as long as the host matches.\n\nThe behavior is as follows:\n- `None` (default) no filtering enabled.\n- `Some(vec)` where `vec` is empty, all traffic is blocked.\n- `Some(vec)` with at least one pattern, filtering enabled for only those patterns.\n\n# Examples\n\nPattern: `*.contentauthenticity.org`\n- Does match:\n - `https://sub.contentauthenticity.org`\n - `http://api.contentauthenticity.org`\n- Does **not** match:\n - `https://contentauthenticity.org` (no subdomain)\n - `https://sub.fakecontentauthenticity.org` (different host)\n\nPattern: `http://192.0.2.1:8080`\n- Does match:\n - `http://192.0.2.1:8080`\n- Does **not** match:\n - `https://192.0.2.1:8080` (scheme mismatch)\n - `http://192.0.2.1` (port omitted)\n - `http://192.0.2.2:8080` (different IP address)\n\nThese settings are applied by the SDK's HTTP resolvers to restrict network requests.\nWhen network requests occur depends on the operations being performed (reading manifests,\nvalidating credentials, timestamping, etc.).", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/HostPattern" } @@ -207,10 +180,7 @@ "properties": { "vendor": { "description": "The name of the vendor creating the content credential.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "claim_generator_info": { "description": "Claim generator info that is automatically added to the builder.\n\nNote that this information will prepend any claim generator info\nprovided explicitly to the builder.", @@ -244,10 +214,7 @@ }, "certificate_status_should_override": { "description": "Whether to only use [`CertificateStatus`] assertions to check certificate revocation status. If there\nis a stapled OCSP in the COSE claim of the manifest, it will be ignored. If [`Verify::ocsp_fetch`] is\nenabled, it will also be ignored.\n\nThe default value is false.\n\n[`CertificateStatus`]: crate::assertions::CertificateStatus\n[`Verify::ocsp_fetch`]: crate::settings::Verify::ocsp_fetch", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "intent": { "description": "The default [`BuilderIntent`] for the [`Builder`].\n\nSee [`BuilderIntent`] for more information.\n\n[`BuilderIntent`]: crate::BuilderIntent\n[`Builder`]: crate::Builder", @@ -262,31 +229,21 @@ }, "created_assertion_labels": { "description": "Assertions with a base label included in this list will be automatically marked as a created assertion.\nAssertions not in this list will be automatically marked as gathered.\n\nNote that the label should be a **base label**, not including the assertion version nor instance.\n\nSee more information on the difference between created vs gathered assertions in the spec here:\n", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "generate_c2pa_archive": { "description": "Whether to generate a C2PA archive (instead of zip) when writing the manifest builder.\nNow always defaults to true - the ability to disable it will be removed in the future.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "auto_timestamp_assertion": { "description": "Settings for configuring auto-generation of the [`TimeStamp`] assertion.\n\n[`TimeStamp`]: crate::assertions::TimeStamp", "$ref": "#/$defs/TimeStampSettings" } }, - "required": [ - "thumbnail", - "actions", - "auto_timestamp_assertion" - ] + "required": ["thumbnail", "actions", "auto_timestamp_assertion"] }, "ClaimGeneratorInfoSettings": { "description": "Settings for the claim generator info.", @@ -298,10 +255,7 @@ }, "version": { "description": "A human readable string of the product's version.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "icon": { "description": "Reference to an icon.", @@ -326,9 +280,7 @@ ] } }, - "required": [ - "name" - ], + "required": ["name"], "additionalProperties": true }, "ResourceRef": { @@ -345,33 +297,21 @@ }, "data_types": { "description": "More detailed data types as defined in the C2PA spec.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/AssetType" } }, "alg": { "description": "The algorithm used to hash the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "hash": { "description": "The hash of the resource (if applicable).", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "format", - "identifier" - ] + "required": ["format", "identifier"] }, "AssetType": { "type": "object", @@ -380,15 +320,10 @@ "type": "string" }, "version": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } }, - "required": [ - "type" - ] + "required": ["type"] }, "ClaimGeneratorInfoOperatingSystem": { "anyOf": [ @@ -504,17 +439,11 @@ "properties": { "all_actions_included": { "description": "Whether or not to set the [Actions::all_actions_included][crate::assertions::Actions::all_actions_included]\nfield.", - "type": [ - "boolean", - "null" - ] + "type": ["boolean", "null"] }, "templates": { "description": "Templates to be added to the [Actions::templates][crate::assertions::Actions::templates] field.", - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "$ref": "#/$defs/ActionTemplateSettings" } @@ -559,10 +488,7 @@ }, "software_agent_index": { "description": "0-based index into the softwareAgents array", - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "format": "uint", "minimum": 0 }, @@ -590,23 +516,15 @@ }, "description": { "description": "Description of the template.", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "template_parameters": { "description": "Additional parameters for the template", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "additionalProperties": true } }, - "required": [ - "action" - ] + "required": ["action"] }, "DigitalSourceType": { "description": "Description of the source of an asset.\n\nThe full list of possible digital source types are found below:\n\n", @@ -750,9 +668,7 @@ ] } }, - "required": [ - "enabled" - ] + "required": ["enabled"] }, "OcspFetchScope": { "description": "The scope of which manifests to fetch for OCSP.", @@ -780,9 +696,7 @@ "$ref": "#/$defs/DigitalSourceType" } }, - "required": [ - "create" - ], + "required": ["create"], "additionalProperties": false }, { @@ -814,11 +728,7 @@ "$ref": "#/$defs/TimeStampFetchScope" } }, - "required": [ - "enabled", - "skip_existing", - "fetch_scope" - ] + "required": ["enabled", "skip_existing", "fetch_scope"] }, "TimeStampFetchScope": { "description": "The scope of manifests to fetch timestamps for.\n\nSee [`TimeStampSettings`] for more information.", @@ -855,40 +765,25 @@ "type": "string" }, "tsa_url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "referenced_assertions": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "roles": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } } }, - "required": [ - "alg", - "sign_cert", - "private_key" - ] + "required": ["alg", "sign_cert", "private_key"] } }, - "required": [ - "local" - ], + "required": ["local"], "additionalProperties": false }, { @@ -908,40 +803,25 @@ "type": "string" }, "tsa_url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "referenced_assertions": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } }, "roles": { - "type": [ - "array", - "null" - ], + "type": ["array", "null"], "items": { "type": "string" } } }, - "required": [ - "url", - "alg", - "sign_cert" - ] + "required": ["url", "alg", "sign_cert"] } }, - "required": [ - "remote" - ], + "required": ["remote"], "additionalProperties": false } ] @@ -987,4 +867,4 @@ ] } } -} \ No newline at end of file +}