From 6fe793baaa8a62707d2db4c81c1abf4d0eb656c8 Mon Sep 17 00:00:00 2001 From: Leonard Rosenthol Date: Fri, 5 Jun 2026 09:04:35 -0400 Subject: [PATCH 1/2] feat: add claim generator requirement extraction and RuleApplicability - Add RuleApplicability enum (CLAIM_GENERATOR, VALIDATOR, BOTH, UNSPECIFIED) to ValidationRule model so rules can declare who they target - Add parse_html_generation_rules() that scans all spec sections (not just Section 15) for SHALL/MUST statements directed at claim generators, returning GEN- prefixed ValidationRule objects - Update parse_html_spec() to include generation rules alongside validation rules - Update rules emitter to add generation_rules section grouped by spec area and by_applicability counts in summary - Add query_generation_rules MCP tool for filtering CG rules by area/severity - Add docs/claim-generator-requirements.md listing all 118 CG-directed requirements from C2PA 2.1, organized by spec area - Add tests for RuleApplicability round-trip and _infer_applicability logic Co-Authored-By: Claude Sonnet 4.6 --- docs/claim-generator-requirements.md | 513 +++++++++++++++++++++++++++ src/c2pa_kg/emitters/rules.py | 46 ++- src/c2pa_kg/models.py | 18 +- src/c2pa_kg/parsers/html_spec.py | 261 +++++++++++++- src/c2pa_kg/server/mcp_server.py | 61 ++++ tests/test_html_spec_parser.py | 63 +++- tests/test_models.py | 36 ++ 7 files changed, 987 insertions(+), 11 deletions(-) create mode 100644 docs/claim-generator-requirements.md diff --git a/docs/claim-generator-requirements.md b/docs/claim-generator-requirements.md new file mode 100644 index 0000000..f866e1f --- /dev/null +++ b/docs/claim-generator-requirements.md @@ -0,0 +1,513 @@ +# C2PA Claim Generator Requirements + +Normative requirements from the C2PA 2.1 specification directed at +claim generators (software that creates C2PA manifests). +Extracted from all specification sections. Organized by spec area. + +**Total requirements: 118** + +> Rules tagged `(validator too)` apply to both claim generators and validators. + +--- + +## Table of Contents + +- [Versioning](#versioning) — 1 rule +- [Assertions](#assertions) — 11 rules +- [Unique Identifiers](#unique-identifiers) — 4 rules +- [Binding to Content](#binding-to-content) — 2 rules +- [Claims](#claims) — 28 rules +- [Manifests](#manifests) — 11 rules +- [Cryptography](#cryptography) — 2 rules +- [Trust Model](#trust-model) — 7 rules +- [Standard Assertions](#standard-assertions) — 52 rules + +--- + +## Versioning + +*1 requirement* + +### 5.1. Compatibility + +**[GEN-VERSIO-0001]** `SHALL NOT` +In this specification, when a construct is marked as deprecated, that means that a claim generator shall not write that construct (or value), but that a validator should read it. + + +## Assertions + +*11 requirements* + +### 6.3. Versioning + +**[GEN-ASSERT-0002]** `SHALL` +Deprecated fields for C2PA standard assertions shall be indicated in Chapter 18, C2PA Standard Assertions. + +**[GEN-ASSERT-0004]** `SHALL` +In those situations where a non-backwards compatible change is required, instead of increasing the label’s version number, the assertion shall be given a new label. + +**[GEN-ASSERT-0001]** `SHALL NOT` +Existing fields shall not be removed. + +**[GEN-ASSERT-0003]** `SHALL NOT` +Claim generators shall not insert data into deprecated assertion fields when creating assertions. + +### 6.6. Assertion Store + +**[GEN-ASSERT-0005]** `SHALL` +The assertions and assertion store shall be stored as described in Section 11.1, “Use of JUMBF”; in particular, each assertion referenced in a claim’s created_assertions or gathered_assertions (but not redacted_assertions) shall be present in the assertion store located in the same C2PA Manifest as the claim. + +### 6.8. Redaction of Assertions + +**[GEN-ASSERT-0006]** `SHALL` +In addition, a record that something was removed shall be added to the claim in the form of a URI reference to the redaction assertion in the redacted_assertions field of the claim. + +**[GEN-ASSERT-0008]** `SHALL` +When redacting an ingredient assertion that references a C2PA Manifest, the associated manifest shall be removed from the C2PA Manifest Store if no other references to it remain after redacting. + +**[GEN-ASSERT-0009]** `SHALL` +Unless the redaction of the assertion also requires modification to the digital content, an update manifest shall be used to document the redaction as it makes a statement about the non-changes to the content. + +**[GEN-ASSERT-0011]** `SHALL` +They shall also not redact any hard binding to content assertion - either a c2pa.hash.data, c2pa.hash.boxes, c2pa.hash.collection.data, c2pa.hash.bmff.v2 (deprecated), or c2pa.hash.bmff.v3, as these assertions are necessary for determining the integrity of the asset. + +**[GEN-ASSERT-0010]** `SHALL NOT` +Claim generators shall not redact assertions with a label of c2pa.actions or c2pa.actions.v2 as this assertion type represents essential information in understanding the history of an asset. + +**[GEN-ASSERT-0007]** `SHOULD` +It is also strongly recommended that the claim generator should add a c2pa.redacted action assertion with a redacted field as described in Section 18.12.5, “Parameters”. + + +## Unique Identifiers + +*4 requirements* + +### 8.2. Versioning Manifests Due to Conflicts + +**[GEN-UNIQUE-0001]** `MUST` +In such a case, the modified version of the ingredient manifest needs to be copied into the asset’s C2PA Manifest Store, but must be re-labeled. + +**[GEN-UNIQUE-0002]** `SHALL` +If the current URN does not contain a "Claim Generator identifier string", then the claim generator shall append a :. + +**[GEN-UNIQUE-0003]** `SHALL` +In all cases, the claim generator shall append a : to the URN followed by a monotonically increasing integer, starting with 1, followed by an underscore (_) and then an integer from the list below representing the reason for the re-labeling. + +### 8.3. Identifying Non-C2PA Assets + +**[GEN-UNIQUE-0004]** `MAY` +When working with assets that do not contain a C2PA Manifest and do not contain embedded XMP, the claim generator may use any method of its choosing to provide it with a unique identifier. + + +## Binding to Content + +*2 requirements* + +### 9.3. Soft Bindings + +**[GEN-BINDIN-0001]** `SHALL NOT` +Because they serve a different purpose, a soft binding shall not be used as a hard binding. + +### 9.3.1. List of Allowed Soft Binding Algorithms + +**[GEN-BINDIN-0002]** `SHALL` +All soft bindings shall be generated using one of the algorithms listed in the soft binding algorithm list as supported by this specification. + + +## Claims + +*28 requirements* + +### 10.1. Overview + +**[GEN-CLAIMS-0001]** `SHOULD NOT` +Validators shall still accept this label (and associated claim-map), but claim generators should not produce such a claim. + +### 10.2.3.1. General + +**[GEN-CLAIMS-0002]** `SHALL` +Detailed information about the claim generator shall be present as the value of claim_generator_info. + +### 10.2.3.2. Generator Info Map + +**[GEN-CLAIMS-0003]** `MAY` +A claim generator may desire to provide a graphical representation of itself, referred here as an icon, to a Manifest Consumer that is presenting a user experience. + +### 10.3.2.1. Adding Assertions and Redactions + +**[GEN-CLAIMS-0004]** `SHALL` +The claim shall contain a created_assertions field and may contain a gathered_assertions field. + +**[GEN-CLAIMS-0005]** `SHALL` +In a standard or time-stamp manifest, the created_assertions field’s value shall include at least one assertion that represents a hard binding. + +**[GEN-CLAIMS-0006]** `SHALL` +If any assertions in ingredient claims are being redacted, their URI references shall be added to list which is the value of the redacted_assertions field. + +### 10.3.2.2. Adding Ingredients + +**[GEN-CLAIMS-0007]** `SHALL` +When an ingredient contains one or more C2PA manifests, those manifests shall be inserted into this asset’s C2PA Manifest Store to ensure that the provenance data is kept intact. + +**[GEN-CLAIMS-0008]** `SHALL` +If a manifest with the same unique identifier is already present in the C2PA Manifest Store, the two shall be compared (via hashing). + +**[GEN-CLAIMS-0009]** `SHALL` +If they are identical, the new manifest shall be ignored. + +**[GEN-CLAIMS-0010]** `SHALL` +If they are different, the new manifest shall be added to the store after changing its unique identifier to a new value as described in Chapter 8, Unique Identifiers. + +### 10.3.2.4. Signing a Claim + +**[GEN-CLAIMS-0011]** `SHALL` +For standard and update manifests, the payload field of Sig_structure shall be the serialized CBOR of the claim document, and shall use detached content mode. + +**[GEN-CLAIMS-0012]** `SHALL` +For time-stamp manifests, the payload field of Sig_structure shall be the value of the signature field of the COSE_Sign1_Tagged structure contained in the C2PA Claim Signature box of the C2PA Manifest of its parent ingredient, and shall use detached content mode. + +### 10.3.2.5.2. Choosing the Payload + +**[GEN-CLAIMS-0013]** `SHALL NOT` *(validator too)* +A claim generator shall not create one, but a validator shall process one if present. + +### 10.3.2.5.3. Obtaining the time-stamp + +**[GEN-CLAIMS-0014]** `SHALL` +All time-stamps shall be obtained as described in RFC 3161 with the following additional requirements: + +**[GEN-CLAIMS-0015]** `SHALL` +The MessageImprint of the TimeStampReq structure (RFC 3161, section 2.4.1) shall be computed by creating the ToBeSigned value in RFC 8152, section 4.4, with the following values for elements of Sig_structure: + +**[GEN-CLAIMS-0016]** `SHALL` +The context element shall be CounterSignature. + +**[GEN-CLAIMS-0017]** `SHALL` +The payload element shall be the value described by Section 10.3.2.5.2, “Choosing the Payload”. + +**[GEN-CLAIMS-0018]** `SHALL` +The certReq boolean of the TimeStampReq structure shall be asserted in the request to the TSA, to ensure its certificate chain is provided in the response. + +### 10.3.2.5.4. Storing the time-stamp + +**[GEN-CLAIMS-0019]** `SHALL` +If present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”. + +**[GEN-CLAIMS-0020]** `SHALL` +The content of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens. + +**[GEN-CLAIMS-0021]** `SHALL` +v2 time-stamps shall be stored in a COSE unprotected header whose label is the string sigTst2. + +**[GEN-CLAIMS-0022]** `SHALL` +When present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”. + +**[GEN-CLAIMS-0023]** `SHALL` +The content of value of the timeStampToken field of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens. + +**[GEN-CLAIMS-0024]** `SHALL` +If no time-stamps are included, then neither header (sigTst nor sigTst2) shall be present in the COSE unprotected header. + +### 10.3.2.6. Credential Revocation Information + +**[GEN-CLAIMS-0025]** `SHALL` +If credential revocation information is attached in this manner, a trusted time-stamp shall also be obtained after signing, as described in Section 10.3.2.5, “Time-stamps”. + +### 10.4.1. Create content bindings + +**[GEN-CLAIMS-0026]** `SHALL` +Claim generators shall ensure that changes to pad data (or any other excluded asset data) cannot change how the asset is interpreted. + +### 10.4.4. Going back and filling in + +**[GEN-CLAIMS-0028]** `SHOULD` +In this case, claim generators should use padding prior to assertion creation to ensure that the file layout need not change once the assertion has been finalized. + +**[GEN-CLAIMS-0027]** `MAY` +As such, the claim generator may no longer be able to change the file layout and/or offsets in a data hash assertion. + + +## Manifests + +*11 requirements* + +### 11.1.4.2. Manifest Store + +**[GEN-MANIFE-0001]** `SHALL` +The C2PA Manifest Store shall have a label of c2pa, a JUMBF type UUID of 63327061-0011-0010-8000-00AA00389B71 (c2pa) and shall contain one or more C2PA manifest superboxes, also known as C2PA Manifests. + +**[GEN-MANIFE-0002]** `SHALL` +Each C2PA Manifest shall contain the data created at the time a claim is issued including the C2PA Assertion Store, a C2PA Claim, and a C2PA Claim Signature. + +**[GEN-MANIFE-0003]** `SHALL` +The JUMBF type UUID for each C2PA Manifest shall be either 63326D61-0011-0010-8000-00AA00389B71 (c2ma), 6332636D-0011-0010-8000-00AA00389B71 (c2cm) or 6332756D-0011-0010-8000-00AA00389B71 (c2um) depending on the type of manifest. + +**[GEN-MANIFE-0004]** `SHALL` +The C2PA Manifest box shall be labelled with a urn:c2pa value computed as described in Unique Identifiers. + +### 11.1.4.3. Assertion Store + +**[GEN-MANIFE-0005]** `SHALL` +The C2PA Assertion Store is a superbox that shall have a label of c2pa.assertions and a JUMBF type UUID of 63326173-0011-0010-8000-00AA00389B71 (c2as). + +**[GEN-MANIFE-0006]** `SHALL` +It shall contain one or more JUMBF superboxes (called C2PA Assertion boxes) whose JUMBF type defines the BMFF type of the sub-boxes that contain the assertion data (ISO 19566-5:2023, Annex B). + +**[GEN-MANIFE-0007]** `SHALL` +These superboxes shall each have a label as defined in Standard Assertions. + +**[GEN-MANIFE-0008]** `SHALL NOT` +The C2PA Assertion Store shall not contain any JUMBF boxes or superboxes that are not JUMBF Content Boxes. + +### 11.1.4.5. Ingredient Storage + +**[GEN-MANIFE-0009]** `SHALL` +When a C2PA Manifest includes ingredient assertions, and an ingredient contains a C2PA Manifest, that C2PA Manifest shall be included to ensure that the provenance data is kept intact. + +### 11.2.2. Standard Manifests + +**[GEN-MANIFE-0010]** `SHALL NOT` +Manifest Consumers shall also accept standard C2PA Manifests specified with JUMBF type UUID 63326D64-0011-0010-8000-00AA00389B71 (c2md), but claim generators shall not create manifests with this JUMBF type UUID. + +### 11.2.5. Time-Stamp Manifests + +**[GEN-MANIFE-0011]** `SHALL` +A Time-Stamp Manifest shall contain only a single assertion, which is the c2pa.ingredient.v3 assertion that (a) includes an activeManifest field with a value that is the URI reference to that C2PA Manifest that is being updated and (b) has the value of parentOf for the relationship field. + + +## Cryptography + +*2 requirements* + +### 13.2.4. Signature Validation + +**[GEN-CRYPTO-0001]** `SHOULD` +When producing a signature, if the claim generator can also act as a validator, the claim generator should validate that the signing credential is acceptable according to Chapter 14, Trust Model and produce a warning if it is not. + +**[GEN-CRYPTO-0002]** `MAY` +The claim generator may still allow signing with that credential if so desired. + + +## Trust Model + +*7 requirements* + +### 14.4.3. Private Credential Storage + +**[GEN-TRUST_-0001]** `SHALL NOT` +If present, the private credential store shall only apply to validating signed C2PA manifests, and shall not apply to validating time-stamps. + +### 14.5. X.509 Certificates + +**[GEN-TRUST_-0002]** `SHALL` +Therefore, when creating the x5chain header as part of signing, the claim generator shall include the signer’s certificate and all intermediate certificate authorities in the header’s value. + +**[GEN-TRUST_-0005]** `SHALL` +Claim generators shall place this header only in the protected header bucket of the COSE signature as required above. + +**[GEN-TRUST_-0003]** `SHOULD` +Claim generators should use only the integer 33 as the label when inserting this header into a COSE signature. + +**[GEN-TRUST_-0004]** `SHOULD` +Claim generators may continue to write the string label x5chain but this behaviour is now deprecated and claim generators should be updated to use the integer label only. + +### 14.5.2. Certificate Revocation + +**[GEN-TRUST_-0007]** `SHALL NOT` +The claim generator shall not use Certificate Revocation Lists (CRLs, see RFC 5280). `` + +**[GEN-TRUST_-0006]** `SHOULD` +A claim generator should use the Online Certificate Status Protocol (OCSP, see RFC 6960) and OCSP stapling (as originally conceptualized in RFC 6066, Section 8, but implemented as described in this clause) to implement revocation. + + +## Standard Assertions + +*52 requirements* + +### 18.12.2. Mandatory presence of at least one actions assertion + +**[GEN-STANDA-0021]** `SHALL` +There shall be at least one actions assertion in every standard C2PA Manifest: + +**[GEN-STANDA-0022]** `SHALL` +If the asset was created de novo (for example, as a result of performing a File → New operation in a creative tool, capturing a photo or video, or generating the media by a generative AI model), then the actions array in the c2pa.actions assertion shall begin with a c2pa.created action as its first element. + +**[GEN-STANDA-0023]** `SHALL` +If the asset is created with no digital content, then there shall be no digitalSourceType value in conjunction with the c2pa.created action. + +**[GEN-STANDA-0024]** `SHALL` +For all other assets, a corresponding digitalSourceType value shall be recorded with the c2pa.created action, to indicate the nature of the asset at its inception. + +**[GEN-STANDA-0025]** `SHALL` +If the asset was created by opening an existing asset as a parentOf ingredient for editing, then the actions array in the c2pa.actions assertion shall begin with an action of c2pa.opened as its first element. + +**[GEN-STANDA-0026]** `SHALL` +The mandatory c2pa.created or c2pa.opened action shall be recorded in the first instance of the c2pa.actions.v2 assertion, that is, the one that does not have an instance label. + +**[GEN-STANDA-0027]** `SHALL` +Additionally, the c2pa.actions.v2 assertion that does not have an instance label shall be placed earlier than any other actions assertion in the created_assertions array in c2pa.claim.v2. + +### 18.12.3. Fields in the actions assertion + +**[GEN-STANDA-0028]** `SHALL` +If present, the reason field shall contain one of these standard values, or a custom value which conforms to the same syntax as entity-specific namespacing, for the rationale behind the action: + +**[GEN-STANDA-0029]** `SHALL` +When using a c2pa.redacted action, the reason field shall contain the rationale for the redaction. + +**[GEN-STANDA-0030]** `SHALL` +If included, the value of the when field shall be compliant with ISO 8601. + +**[GEN-STANDA-0031]** `SHALL` +When multiple softwareAgents are used, as described in Section 18.12.6.1.2, “SoftwareAgents”, then the softwareAgentIndex field shall be used to reference the softwareAgent by its 0-based index in the softwareAgents array. + +**[GEN-STANDA-0032]** `SHALL` +A given action shall only have one softwareAgent or softwareAgentIndex field. + +**[GEN-STANDA-0033]** `SHALL` +An action may include a digitalSourceType key, whose value shall be one of the terms defined by the IPTC or a C2PA specific value from the list below: + +### 18.13.12. Determining the need to copy or copy and re-label existing manifests + +**[GEN-STANDA-0037]** `SHALL` +To determine whether or not an existing manifest from the ingredient’s C2PA Manifest Store needs to be copied into the asset’s C2PA Manifest Store, the claim generator shall: + +**[GEN-STANDA-0040]** `SHALL` +The claim generator shall check if any assertions from either manifest were redacted (optionally utilizing the list of redactions compiled in the Performing explicit validation process). + +**[GEN-STANDA-0042]** `SHALL` +If all redactions were applied against the manifest from the ingredient’s Manifest Store, then the claim generator shall replace the manifest in the asset’s C2PA Manifest Store with the manifest from the ingredient’s C2PA Manifest Store. + +**[GEN-STANDA-0043]** `SHALL` +If different redactions were applied against both the C2PA Manifest from the ingredient’s C2PA Manifest Store and the asset’s C2PA Manifest Store, then the claim generator shall redact as many assertions as needed from the existing manifest in the asset’s C2PA Manifest Store to result in a union of the two sets of redactions. + +**[GEN-STANDA-0044]** `SHALL` +In all other cases, then the claim generator shall copy the manifest from the ingredient’s C2PA Manifest Store, re-label it with an updated URN per the process described in Unique Identifiers, and insert the re-labeled version into the asset’s C2PA Manifest Store. + +**[GEN-STANDA-0039]** `SHALL NOT` +If the hashes match, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store to the asset’s C2PA Manifest Store. + +**[GEN-STANDA-0041]** `SHALL NOT` +If all redactions were applied against the manifest already present in the asset’s C2PA Manifest Store, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store into the asset’s C2PA Manifest Store. + +**[GEN-STANDA-0038]** `MAY` +In case of validation failures, the claim generator may skip the rest of these steps if directed to do so (for example, via user input or via configuration). + +### 18.13.12.1.1. General + +**[GEN-STANDA-0045]** `SHALL` +In addition, when the ingredient assertion references a C2PA Manifest, the claim generator shall also act as a validator, performing validation of the ingredient as described in validation steps. + +### 18.13.12.1.2. V2 ingredient assertions (DEPRECATED) + +**[GEN-STANDA-0046]** `SHALL` +The code shall conform to the same syntax as entity-specific namespaces (e.g. com.litware.malformedFrobber) and the validationStatus object shall contain a success boolean. + +### 18.13.12.1.3. V3 ingredient assertions + +**[GEN-STANDA-0048]** `SHALL` +In a v3 ingredient assertion with an activeManifest field, the validationResults field shall contain a validation-results-map object which in turn contains: + +**[GEN-STANDA-0049]** `SHALL` +The delta validation results for an ingredient assertion shall contain the following: + +**[GEN-STANDA-0050]** `SHALL` +This status value comparison shall consider the status type (success, informational, or failure), code, and url, ignoring other fields. + +**[GEN-STANDA-0051]** `SHALL` +Each code is represented as a status-map object which shall contain a code field with the status code. + +**[GEN-STANDA-0052]** `SHALL` +The code shall conform to the same syntax as entity-specific namespaces (e.g. com.litware.malformedFrobber). + +**[GEN-STANDA-0047]** `SHALL NOT` +In a v3 ingredient assertion with no activeManifest field, the validationResults field shall not be present. + +### 18.13.3. Relationship + +**[GEN-STANDA-0034]** `SHALL` +When adding an ingredient assertion, a claim generator shall add a c2pa.actions assertion (see Section 18.12, “Actions”), if one does not already exist in the active manifest. + +### 18.13.5. Format + +**[GEN-STANDA-0035]** `SHALL` +It is recommended that a Claim Generator should provide this field and it shall contain a valid value. + +### 18.13.8.1. Standard Usage + +**[GEN-STANDA-0036]** `SHOULD` +Claim generators should take the size of this field into consideration when choosing whether to embed data. + +### 18.3.7.2. Localization Dictionary + +**[GEN-STANDA-0001]** `SHOULD` +In order for a Manifest Consumer to display human-readable information about these keys and values, the claim generator should provide the strings via this localization approach. + +### 18.5.1. Description + +**[GEN-STANDA-0002]** `SHALL NOT` +Claim generators shall not add this field to a data hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”. + +### 18.6.1. Description + +**[GEN-STANDA-0003]** `SHALL NOT` +Claim generators shall not add this field to a BMFF hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”. + +### 18.7.1. Description + +**[GEN-STANDA-0004]** `SHOULD` +A claim generator should use a general box hash assertion to verify the integrity, with a hard binding (i.e., cryptographic hash), of assets whose formats use a non-BMFF-based box format such as JPEG, PNG, or GIF. + +### 18.8.3. Fields + +**[GEN-STANDA-0005]** `SHALL` +A claim generator shall validate or sanitize the URIs before use, ensuring that neither . nor .. appear as part of the URI. + +### 18.9.1. Description + +**[GEN-STANDA-0006]** `SHALL NOT` +Claim generators shall not add this field to a soft binding assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”. + +**[GEN-STANDA-0007]** `SHALL NOT` +Claim generators shall not add this field to a soft binding assertion, and consumers should ignore the field when present. + +### 18.9.4. Soft Binding Algorithm List + +**[GEN-STANDA-0008]** `SHALL` +The alg field shall correspond to the alg field of an algorithm present in that list. + +**[GEN-STANDA-0010]** `SHALL` +The unique name of the algorithm is given in the alg field, and corresponds to the string that shall be used in the alg field a soft binding assertion that uses that algorithm. + +**[GEN-STANDA-0011]** `SHALL` +The name shall follow the namespacing requirements and represent the owner of the algorithm. + +**[GEN-STANDA-0012]** `SHALL` +If different versions of an algorithm are provided, then each shall have a separate entry in the Soft Binding Algorithm List. + +**[GEN-STANDA-0013]** `SHALL` +The type of the algorithm shall be either 'watermark' or 'fingerprint' to represent that the algorithm is an invisible watermark, or a fingerprint. + +**[GEN-STANDA-0015]** `SHALL` +The soft binding algorithm list entry shall contain a list of supported media types either as encodedMediaTypes or as decodedMediaTypes. + +**[GEN-STANDA-0016]** `SHALL` +The supported media types for decodedMediaTypes shall correspond to one more of the top-level IANA media types comprising of: "application", "audio", "image", "model", "text", "video". + +**[GEN-STANDA-0017]** `SHALL` +The supported media types for encodedMediaTypes shall correspond to one more of the registered IANA subtypes of a decodedMediaType listed in the preceding sentence. + +**[GEN-STANDA-0018]** `SHALL` +Additional information shall accompany each entry in the soft binding algorithm list, within the entryMetadata field. + +**[GEN-STANDA-0019]** `SHALL` +The contact details of the owner of the entry shall be provided as an email address (contact, required). + +**[GEN-STANDA-0020]** `SHALL` +An informational URL (informationalUrl, required) shall be provided that references a human readable page describing characteristics of the soft binding algorithm. + +**[GEN-STANDA-0009]** `SHALL NOT` +Entries in the soft binding algorithm list that have a deprecated field of true shall be considered deprecated and shall not be used to create soft binding assertions in manifests. + +**[GEN-STANDA-0014]** `SHALL NOT` +C2PA Manifests shall not be written using deprecated soft-bindings. diff --git a/src/c2pa_kg/emitters/rules.py b/src/c2pa_kg/emitters/rules.py index 9cf6f16..80933ed 100644 --- a/src/c2pa_kg/emitters/rules.py +++ b/src/c2pa_kg/emitters/rules.py @@ -12,6 +12,7 @@ from c2pa_kg.models import ( KnowledgeGraph, + RuleApplicability, RuleSeverity, StatusCode, ValidationPhase, @@ -49,6 +50,8 @@ def _rule_to_dict(rule: ValidationRule) -> dict[str, Any]: "phase": rule.phase.value, "spec_section": rule.spec_section, } + if rule.applicability != RuleApplicability.UNSPECIFIED: + d["applicability"] = rule.applicability.value if rule.condition: d["condition"] = rule.condition if rule.action: @@ -117,21 +120,49 @@ def _group_status_codes( return result +def _group_generation_rules_by_area( + rules: list[ValidationRule], +) -> dict[str, list[dict[str, Any]]]: + """Group GEN- rules by spec section area, sorted by severity.""" + gen_rules = [ + r for r in rules + if r.applicability in (RuleApplicability.CLAIM_GENERATOR, RuleApplicability.BOTH) + and r.rule_id.startswith("GEN-") + ] + + areas: dict[str, list[ValidationRule]] = {} + for rule in gen_rules: + # Derive area from spec_section: use text before first number or whole thing. + area = rule.spec_section or "General" + areas.setdefault(area, []).append(rule) + + result: dict[str, list[dict[str, Any]]] = {} + for area in sorted(areas): + sorted_rules = sorted(areas[area], key=lambda r: _SEVERITY_ORDER.get(r.severity, 99)) + result[area] = [_rule_to_dict(r) for r in sorted_rules] + + return result + + # --------------------------------------------------------------------------- # Summary statistics # --------------------------------------------------------------------------- def _build_summary(rules: list[ValidationRule]) -> dict[str, Any]: - """Build a summary dict of rule counts by phase and severity.""" + """Build a summary dict of rule counts by phase, severity, and applicability.""" by_phase: dict[str, int] = {} by_severity: dict[str, int] = {} + by_applicability: dict[str, int] = {} for rule in rules: by_phase[rule.phase.value] = by_phase.get(rule.phase.value, 0) + 1 by_severity[rule.severity.value] = by_severity.get(rule.severity.value, 0) + 1 + app = rule.applicability.value + by_applicability[app] = by_applicability.get(app, 0) + 1 return { "total": len(rules), "by_phase": by_phase, "by_severity": by_severity, + "by_applicability": by_applicability, } @@ -146,12 +177,17 @@ def emit_rules_json(kg: KnowledgeGraph, output_path: Path) -> None: { "version": "2.4", "rule_count": N, - "summary": { "total": N, "by_phase": {...}, "by_severity": {...} }, + "summary": { "total": N, "by_phase": {...}, "by_severity": {...}, + "by_applicability": {...} }, "phases": { "structural": [ ...rules... ], "cryptographic": [ ...rules... ], ... }, + "generation_rules": { + "": [ ...GEN- rules for claim generators... ], + ... + }, "status_codes": { "success": [ ...codes... ], "failure": [ ...codes... ], @@ -163,7 +199,10 @@ def emit_rules_json(kg: KnowledgeGraph, output_path: Path) -> None: kg: The knowledge graph containing rules and status codes. output_path: Destination .json file path. """ - phases = _group_rules_by_phase(kg.validation_rules) + # Separate validation rules (VAL-) from generation rules (GEN-) + val_rules = [r for r in kg.validation_rules if r.rule_id.startswith("VAL-")] + phases = _group_rules_by_phase(val_rules) + generation_rules = _group_generation_rules_by_area(kg.validation_rules) status_codes = _group_status_codes(kg.status_codes) summary = _build_summary(kg.validation_rules) @@ -172,6 +211,7 @@ def emit_rules_json(kg: KnowledgeGraph, output_path: Path) -> None: "rule_count": kg.rule_count, "summary": summary, "phases": phases, + "generation_rules": generation_rules, "status_codes": status_codes, } diff --git a/src/c2pa_kg/models.py b/src/c2pa_kg/models.py index 7984919..51db9cd 100644 --- a/src/c2pa_kg/models.py +++ b/src/c2pa_kg/models.py @@ -75,6 +75,15 @@ class ValidationPhase(Enum): CONTENT = "content" +class RuleApplicability(Enum): + """Who a normative rule is directed at.""" + + CLAIM_GENERATOR = "claim_generator" + VALIDATOR = "validator" + BOTH = "both" + UNSPECIFIED = "unspecified" + + class ChangeType(Enum): """Type of change between spec versions.""" @@ -194,9 +203,10 @@ class ValidationRule: referenced_entities: list[str] = field(default_factory=list) spec_section: str = "" source_text: str = "" + applicability: RuleApplicability = RuleApplicability.UNSPECIFIED def to_dict(self) -> dict: - return { + d: dict = { "rule_id": self.rule_id, "description": self.description, "severity": self.severity.value, @@ -207,6 +217,9 @@ def to_dict(self) -> dict: "spec_section": self.spec_section, "source_text": self.source_text, } + if self.applicability != RuleApplicability.UNSPECIFIED: + d["applicability"] = self.applicability.value + return d @dataclass @@ -513,6 +526,9 @@ def kg_from_dict(data: dict) -> KnowledgeGraph: referenced_entities=rud.get("referenced_entities", []), spec_section=rud.get("spec_section", ""), source_text=rud.get("source_text", ""), + applicability=RuleApplicability( + rud.get("applicability", "unspecified") + ), )) for en, ed in data.get("enum_types", {}).items(): diff --git a/src/c2pa_kg/parsers/html_spec.py b/src/c2pa_kg/parsers/html_spec.py index af9ffd8..4f90214 100644 --- a/src/c2pa_kg/parsers/html_spec.py +++ b/src/c2pa_kg/parsers/html_spec.py @@ -24,7 +24,7 @@ import re from pathlib import Path -from c2pa_kg.models import StatusCode, ValidationRule +from c2pa_kg.models import RuleApplicability, StatusCode, ValidationRule from c2pa_kg.parsers._normative import ( CAMEL_RE, detect_severity, @@ -270,6 +270,175 @@ def parse_html_status_codes(html_text: str) -> list[StatusCode]: return codes +# --------------------------------------------------------------------------- +# Claim-generator applicability detection +# --------------------------------------------------------------------------- + +# Explicit "claim generator shall/must" patterns. +_CG_EXPLICIT_RE = re.compile( + r"\bclaim\s+generators?\s+(?:shall|must|should|may)\b" + r"|\bclaim\s+generators?\s+(?:shall\s+not|must\s+not|should\s+not)\b", + re.IGNORECASE, +) + +# Sentence subject is a signer or creator entity. +_CG_SUBJECT_RE = re.compile( + r"^\s*(?:the\s+)?(?:claim\s+generator|signer|creator|manifest\s+creator)" + r"\s+(?:shall|must|should|may)\b", + re.IGNORECASE, +) + +# Explicit "a validator shall/must" — marks VALIDATOR applicability. +_VAL_EXPLICIT_RE = re.compile( + r"\b(?:a\s+)?validator\s+(?:shall|must)\b" + r"|\bwhen\s+validating\b" + r"|\bduring\s+validation\b", + re.IGNORECASE, +) + +# Section headings strongly associated with manifest creation / generation. +_CG_SECTION_KEYWORDS = frozenset( + [ + "creating a claim", + "claim fields", + "ingredient", + "assertion", + "assertions", + "binding to content", + "hard binding", + "soft binding", + "embedding", + "manifest store", + "adding ingredients", + "versioning", + "redaction", + "signing", + "time-stamp", + "timestamp", + "credential", + "claim generator", + ] +) + + +def _infer_applicability(sentence: str, section: str) -> RuleApplicability: + """Classify whether a rule targets claim generators, validators, or both.""" + s_lower = sentence.lower() + section_lower = section.lower() + + has_cg = bool(_CG_EXPLICIT_RE.search(sentence)) or bool( + _CG_SUBJECT_RE.match(sentence) + ) + has_val = bool(_VAL_EXPLICIT_RE.search(sentence)) + + if has_cg and has_val: + return RuleApplicability.BOTH + if has_cg: + return RuleApplicability.CLAIM_GENERATOR + if has_val: + return RuleApplicability.VALIDATOR + + # Check section context: if the section is about manifest construction and + # the rule has no explicit validator language, lean toward CG. + if any(kw in section_lower for kw in _CG_SECTION_KEYWORDS): + if not has_val and ("shall" in s_lower or "must" in s_lower): + # Passive / impersonal constructs in creation sections often imply CG. + return RuleApplicability.CLAIM_GENERATOR + + return RuleApplicability.UNSPECIFIED + + +# --------------------------------------------------------------------------- +# Full-spec section extraction +# --------------------------------------------------------------------------- + +# H2 section headers across the whole spec body. +_H2_HEADER_RE = re.compile( + r']*)?>(?:]*>)?\s*([\d.]*\s*.+?)', + re.DOTALL, +) + +# H3-H4 sub-section headers for finer-grained context. +_H3H4_HEADER_RE = re.compile( + r']*)?>(?:]*>)?\s*([\d.]*\s*.+?)', + re.DOTALL, +) + +# Map spec section number/name to a canonical spec area label. +# Number-prefixed patterns are checked first (most specific to least specific). +_SPEC_AREA_MAP: list[tuple[re.Pattern[str], str]] = [ + # Match by section number prefix (catches subsections like 18.12.2 etc.) + (re.compile(r"^18\.", re.IGNORECASE), "Standard Assertions"), + (re.compile(r"^10\.", re.IGNORECASE), "Claims"), + (re.compile(r"^11\.", re.IGNORECASE), "Manifests"), + (re.compile(r"^13\.", re.IGNORECASE), "Cryptography"), + (re.compile(r"^14\.", re.IGNORECASE), "Trust Model"), + (re.compile(r"^15\.", re.IGNORECASE), "Validation"), + (re.compile(r"^9\.", re.IGNORECASE), "Binding to Content"), + (re.compile(r"^8\.", re.IGNORECASE), "Unique Identifiers"), + (re.compile(r"^7\.", re.IGNORECASE), "Data Boxes"), + (re.compile(r"^6\.", re.IGNORECASE), "Assertions"), + (re.compile(r"^5\.", re.IGNORECASE), "Versioning"), + # Fallback: match by section title keywords (for H2 section titles without numbers) + (re.compile(r"standard.assertions", re.IGNORECASE), "Standard Assertions"), + (re.compile(r"\bclaims?\b", re.IGNORECASE), "Claims"), + (re.compile(r"\bmanifests?\b", re.IGNORECASE), "Manifests"), + (re.compile(r"cryptograph", re.IGNORECASE), "Cryptography"), + (re.compile(r"trust.model", re.IGNORECASE), "Trust Model"), + (re.compile(r"validation", re.IGNORECASE), "Validation"), + (re.compile(r"binding.to.content", re.IGNORECASE), "Binding to Content"), + (re.compile(r"unique.identifiers?", re.IGNORECASE), "Unique Identifiers"), + (re.compile(r"data.boxes", re.IGNORECASE), "Data Boxes"), + (re.compile(r"\bassertions?\b", re.IGNORECASE), "Assertions"), +] + + +def _spec_area_for_section(section_title: str) -> str: + """Map a section title to a high-level spec area name.""" + for pattern, area in _SPEC_AREA_MAP: + if pattern.search(section_title): + return area + return "General" + + +def _extract_spec_body_sections(html_text: str) -> list[tuple[str, str, str]]: + """Return (area, section_title, section_html) for each h2 section. + + Skips the Validation section (Section 15) since that is handled by + parse_html_validation_rules. Also skips preamble, appendices, and + non-normative sections. + """ + sections: list[tuple[str, str, str]] = [] + + h2_matches = list(_H2_HEADER_RE.finditer(html_text)) + for i, m in enumerate(h2_matches): + raw_title = _strip_tags(m.group(2)) + if not raw_title: + continue + # Skip validation section (handled separately), appendices, TOC, etc. + skip_keywords = [ + "validation", + "appendix", + "patent", + "table of contents", + "introduction", + "glossary", + "normative references", + "standard terms", + ] + if any(kw in raw_title.lower() for kw in skip_keywords): + continue + + area = _spec_area_for_section(raw_title) + + start = m.start() + end = h2_matches[i + 1].start() if i + 1 < len(h2_matches) else len(html_text) + section_html = html_text[start:end] + sections.append((area, raw_title, section_html)) + + return sections + + # --------------------------------------------------------------------------- # Validation rule extraction # --------------------------------------------------------------------------- @@ -449,6 +618,83 @@ def parse_html_validation_rules(html_text: str) -> list[ValidationRule]: return rules +def parse_html_generation_rules(html_text: str) -> list[ValidationRule]: + """Extract claim-generator-directed normative rules from all spec sections. + + Scans the entire specification (excluding Section 15, which is handled by + parse_html_validation_rules) for SHALL/MUST statements that target claim + generators or describe manifest construction requirements. + + Rules are tagged with applicability=CLAIM_GENERATOR (or BOTH when a + sentence also addresses validators). Rules with no discernible applicability + are omitted — this function is specifically for generation-time constraints. + + Returns ValidationRule objects with rule_id prefix "GEN-". + """ + sections = _extract_spec_body_sections(html_text) + + rules: list[ValidationRule] = [] + rule_counter: dict[str, int] = {} + + for area, section_title, section_html in sections: + cleaned = _clean_section(section_html) + sub_headers = _extract_section_headers(cleaned) + + text_blocks: list[tuple[int, str]] = [] + for m in _PARAGRAPH_TEXT_RE.finditer(cleaned): + text_blocks.append((m.start(), m.group(1))) + for m in _LIST_ITEM_RE.finditer(cleaned): + text_blocks.append((m.start(), m.group(1))) + text_blocks.sort(key=lambda x: x[0]) + + for offset, raw_html in text_blocks: + plain = _strip_tags(raw_html) + if not plain or len(plain) < 20: + continue + if not has_normative_keyword(plain): + continue + + sub_section = _section_at(offset, sub_headers) or section_title + block_entities = _extract_html_entities(raw_html, sub_section) + + for sentence in split_sentences(plain): + if not has_normative_keyword(sentence): + continue + if len(sentence) < 20: + continue + + applicability = _infer_applicability(sentence, sub_section) + if applicability not in ( + RuleApplicability.CLAIM_GENERATOR, + RuleApplicability.BOTH, + ): + continue + + severity = detect_severity(sentence) + phase = infer_phase(sub_section, sentence) + + area_key = area.upper().replace(" ", "_")[:6] + rule_counter[area_key] = rule_counter.get(area_key, 0) + 1 + rule_id = f"GEN-{area_key}-{rule_counter[area_key]:04d}" + + rules.append( + ValidationRule( + rule_id=rule_id, + description=sentence[:500], + severity=severity, + phase=phase, + condition="", + action="", + referenced_entities=block_entities[:10], + spec_section=sub_section, + source_text=sentence[:200], + applicability=applicability, + ) + ) + + return rules + + # --------------------------------------------------------------------------- # Public convenience API # --------------------------------------------------------------------------- @@ -459,18 +705,21 @@ def parse_html_spec( ) -> tuple[list[ValidationRule], list[StatusCode]]: """Parse a rendered C2PA spec HTML page. - Extracts both validation rules (from Section 15 prose) and status codes - (from the 3 standard tables). + Extracts validation rules (Section 15), generation rules (all other + sections, claim-generator-directed), and status codes. Args: html_text: Full HTML content of the C2PA specification page. Returns: - Tuple of (list[ValidationRule], list[StatusCode]). + Tuple of (list[ValidationRule], list[StatusCode]) where the rule list + includes both Section 15 validation rules and claim-generator + generation rules from all other sections. """ - rules = parse_html_validation_rules(html_text) + val_rules = parse_html_validation_rules(html_text) + gen_rules = parse_html_generation_rules(html_text) codes = parse_html_status_codes(html_text) - return rules, codes + return val_rules + gen_rules, codes def parse_html_spec_file( diff --git a/src/c2pa_kg/server/mcp_server.py b/src/c2pa_kg/server/mcp_server.py index ce9e218..750f3df 100644 --- a/src/c2pa_kg/server/mcp_server.py +++ b/src/c2pa_kg/server/mcp_server.py @@ -178,6 +178,67 @@ def query_validation_rules( {"version": version, "phase": phase, "rules": rules}, indent=2 ) + @mcp.tool( + name="query_generation_rules", + description=( + "Retrieve normative rules directed at claim generators (manifest " + "construction requirements) for a spec version, optionally filtered " + "by spec area (Assertions, Claims, Manifests, Binding to Content, " + "Cryptography, Standard Assertions, etc.) or by severity " + "(shall, must, should, may)." + ), + ) + def query_generation_rules( + version: str, + spec_area: str | None = None, + severity: str | None = None, + ) -> str: + """Get claim-generator-directed rules, optionally filtered. + + Args: + version: Spec version string (e.g. "2.4"). + spec_area: Optional spec area name to filter on (e.g. "Claims"). + Substring match, case-insensitive. + severity: Optional severity to filter on (e.g. "shall"). + + Returns: + JSON string with matching generation rules grouped by spec section. + """ + kg = _load_version(version) + all_rules: list[dict] = kg.get("validation_rules", []) + + gen_rules = [ + r for r in all_rules + if r.get("applicability") in ("claim_generator", "both") + ] + + if spec_area is not None: + area_lower = spec_area.lower() + gen_rules = [ + r for r in gen_rules + if area_lower in r.get("spec_section", "").lower() + ] + + if severity is not None: + gen_rules = [r for r in gen_rules if r.get("severity") == severity] + + # Group by spec section for readability + by_section: dict[str, list[dict]] = {} + for rule in gen_rules: + section = rule.get("spec_section", "General") + by_section.setdefault(section, []).append(rule) + + return json.dumps( + { + "version": version, + "spec_area": spec_area, + "severity": severity, + "total": len(gen_rules), + "by_section": by_section, + }, + indent=2, + ) + @mcp.tool( name="diff_versions", description=( diff --git a/tests/test_html_spec_parser.py b/tests/test_html_spec_parser.py index 6a910bb..0fe3540 100644 --- a/tests/test_html_spec_parser.py +++ b/tests/test_html_spec_parser.py @@ -12,8 +12,9 @@ import pytest -from c2pa_kg.models import RuleSeverity, ValidationPhase +from c2pa_kg.models import RuleApplicability, RuleSeverity, ValidationPhase from c2pa_kg.parsers.html_spec import ( + _infer_applicability, parse_html_spec, parse_html_status_codes, parse_html_validation_rules, @@ -210,3 +211,63 @@ def test_convenience_wrapper(self, html_text) -> None: rules, codes = parse_html_spec(html_text) assert len(rules) > 100 assert len(codes) > 100 + + def test_generation_rules_included(self, html_text) -> None: + rules, _ = parse_html_spec(html_text) + gen_rules = [r for r in rules if r.rule_id.startswith("GEN-")] + assert len(gen_rules) > 0, "Expected at least some GEN- rules" + + def test_generation_rules_have_applicability(self, html_text) -> None: + rules, _ = parse_html_spec(html_text) + gen_rules = [r for r in rules if r.rule_id.startswith("GEN-")] + for rule in gen_rules: + assert rule.applicability in ( + RuleApplicability.CLAIM_GENERATOR, + RuleApplicability.BOTH, + ), f"GEN- rule {rule.rule_id} has unexpected applicability {rule.applicability}" + + +# ----------------------------------------------------------------------- +# Applicability inference tests (no HTML file required) +# ----------------------------------------------------------------------- + + +class TestInferApplicability: + def test_explicit_claim_generator_shall(self) -> None: + result = _infer_applicability( + "Claim generators shall not redact the hard binding assertion.", "" + ) + assert result == RuleApplicability.CLAIM_GENERATOR + + def test_explicit_claim_generators_must(self) -> None: + result = _infer_applicability( + "Claim generators must include at least one assertion.", "" + ) + assert result == RuleApplicability.CLAIM_GENERATOR + + def test_explicit_validator(self) -> None: + result = _infer_applicability( + "A validator shall check the signature against the claim hash.", "" + ) + assert result == RuleApplicability.VALIDATOR + + def test_both_when_cg_and_validator(self) -> None: + result = _infer_applicability( + "A claim generator shall not create one, but a validator shall process one if present.", + "", + ) + assert result == RuleApplicability.BOTH + + def test_section_context_claim(self) -> None: + result = _infer_applicability( + "The created_assertions field shall be present.", + "Creating a Claim", + ) + assert result == RuleApplicability.CLAIM_GENERATOR + + def test_unrelated_sentence_unspecified(self) -> None: + result = _infer_applicability( + "The entity diagram shows the relationships between data structures.", + "Entity Diagram", + ) + assert result == RuleApplicability.UNSPECIFIED diff --git a/tests/test_models.py b/tests/test_models.py index a7b1c9a..6dc67c9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -11,6 +11,7 @@ PropertyType, Relationship, RelationshipType, + RuleApplicability, RuleSeverity, SpecVersion, StatusCode, @@ -334,3 +335,38 @@ def test_round_trip_status_codes(self) -> None: restored = kg_from_dict(kg.to_dict()) assert len(restored.status_codes) == 1 assert restored.status_codes[0].code == "claimSignature.validated" + + def test_round_trip_applicability(self) -> None: + kg = self._minimal_kg() + gen_rule = ValidationRule( + rule_id="GEN-CLAIM-0001", + description="Claim generators shall include a hard binding assertion.", + severity=RuleSeverity.SHALL, + phase=ValidationPhase.ASSERTION, + applicability=RuleApplicability.CLAIM_GENERATOR, + ) + kg.add_rule(gen_rule) + restored = kg_from_dict(kg.to_dict()) + gen = next(r for r in restored.validation_rules if r.rule_id == "GEN-CLAIM-0001") + assert gen.applicability == RuleApplicability.CLAIM_GENERATOR + + def test_applicability_unspecified_not_in_dict(self) -> None: + rule = ValidationRule( + rule_id="VAL-STRU-0001", + description="Must be valid.", + severity=RuleSeverity.MUST, + phase=ValidationPhase.STRUCTURAL, + ) + d = rule.to_dict() + assert "applicability" not in d + + def test_applicability_claim_generator_in_dict(self) -> None: + rule = ValidationRule( + rule_id="GEN-ASSE-0001", + description="Claim generators shall include a hard binding.", + severity=RuleSeverity.SHALL, + phase=ValidationPhase.ASSERTION, + applicability=RuleApplicability.CLAIM_GENERATOR, + ) + d = rule.to_dict() + assert d["applicability"] == "claim_generator" From d2d37b15295812a5b8080142688a0f979cd91566 Mon Sep 17 00:00:00 2001 From: Erik Svilich Date: Fri, 5 Jun 2026 16:46:27 +0000 Subject: [PATCH 2/2] fix: make claim generator rules merge-ready --- src/c2pa_kg/builders/ir_builder.py | 38 +- src/c2pa_kg/cli.py | 21 +- src/c2pa_kg/emitters/rules.py | 7 +- src/c2pa_kg/models.py | 4 + src/c2pa_kg/parsers/asciidoc.py | 37 +- src/c2pa_kg/parsers/html_spec.py | 90 +- src/c2pa_kg/server/mcp_server.py | 8 +- tests/test_cli.py | 7 + tests/test_emitters.py | 22 + tests/test_generation_rule_parser.py | 83 ++ tests/test_html_spec_parser.py | 3 +- tests/test_models.py | 14 + versions/2.4/metadata.json | 1770 +++++++++++++++++++++++++- versions/2.4/validation-rules.json | 1516 +++++++++++++++++++++- 14 files changed, 3534 insertions(+), 86 deletions(-) create mode 100644 tests/test_generation_rule_parser.py diff --git a/src/c2pa_kg/builders/ir_builder.py b/src/c2pa_kg/builders/ir_builder.py index 83a4f70..51523ab 100644 --- a/src/c2pa_kg/builders/ir_builder.py +++ b/src/c2pa_kg/builders/ir_builder.py @@ -27,7 +27,7 @@ ) from c2pa_kg.parsers.asciidoc import parse_assertion_docs, parse_validation_doc from c2pa_kg.parsers.cddl import parse_cddl_directory -from c2pa_kg.parsers.html_spec import parse_html_spec_file +from c2pa_kg.parsers.html_spec import parse_html_generation_rules, parse_html_spec_file from c2pa_kg.parsers.json_schema import parse_json_schema # --------------------------------------------------------------------------- @@ -506,9 +506,11 @@ def build_knowledge_graph( extracted schemas ZIP - must contain a cddl/ directory). version: SpecVersion metadata for the graph being built. html_spec: Path to a locally saved rendered HTML spec from - spec.c2pa.org. When provided, validation rules and status codes - are extracted from the HTML instead of the AsciiDoc source. This - allows building from the public site without specs-core access. + spec.c2pa.org. When provided alongside specs-core AsciiDoc, + claim-generator requirements are extracted from HTML and appended + to the AsciiDoc validation rules. If AsciiDoc validation sources + are unavailable, validation rules and status codes are extracted + from HTML as a fallback. Returns: Fully populated KnowledgeGraph. @@ -537,22 +539,30 @@ def build_knowledge_graph( # ------------------------------------------------------------------ # 3. Validation rules, status codes, assertion descriptions. - # Prefer HTML spec when provided; fall back to AsciiDoc source. + # Use AsciiDoc for validation/status codes when available: it preserves + # the established VAL-* rule IDs used by predicates. HTML is used to add + # claim-generator GEN-* requirements from non-validation sections. # ------------------------------------------------------------------ - if html_spec is not None and html_spec.is_file(): + validation_path = spec_source / _VALIDATION_SUBPATH + used_html_for_validation = False + if validation_path.is_file(): + rules, status_codes = parse_validation_doc(validation_path) + for rule in rules: + kg.add_rule(rule) + for code in status_codes: + kg.status_codes.append(code) + elif html_spec is not None and html_spec.is_file(): rules, status_codes = parse_html_spec_file(html_spec) + used_html_for_validation = True for rule in rules: kg.add_rule(rule) for code in status_codes: kg.status_codes.append(code) - else: - validation_path = spec_source / _VALIDATION_SUBPATH - if validation_path.is_file(): - rules, status_codes = parse_validation_doc(validation_path) - for rule in rules: - kg.add_rule(rule) - for code in status_codes: - kg.status_codes.append(code) + + if html_spec is not None and html_spec.is_file() and not used_html_for_validation: + html_text = html_spec.read_text(encoding="utf-8") + for rule in parse_html_generation_rules(html_text): + kg.add_rule(rule) assertions_dir = spec_source / _ASSERTIONS_SUBPATH if assertions_dir.is_dir(): diff --git a/src/c2pa_kg/cli.py b/src/c2pa_kg/cli.py index 831805f..dea3a8c 100644 --- a/src/c2pa_kg/cli.py +++ b/src/c2pa_kg/cli.py @@ -82,7 +82,20 @@ def cli() -> None: type=click.Path(file_okay=False, path_type=Path), help="Directory where output artifacts are written.", ) -def generate(spec_source: Path, version: str, output_dir: Path) -> None: +@click.option( + "--html-spec", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help=( + "Path to a rendered C2PA spec HTML page. When provided, validation " + "status codes and claim-generator requirements are extracted from HTML." + ), +) +def generate( + spec_source: Path, + version: str, + output_dir: Path, + html_spec: Path | None, +) -> None: """Build knowledge graph artifacts for a single spec version.""" try: spec_version: SpecVersion = get_version(version) @@ -103,7 +116,11 @@ def generate(spec_source: Path, version: str, output_dir: Path) -> None: ) from exc click.echo(f"Building knowledge graph for version {version} ...") - kg: KnowledgeGraph = build_knowledge_graph(spec_source, spec_version) + kg: KnowledgeGraph = build_knowledge_graph( + spec_source, + spec_version, + html_spec=html_spec, + ) version_dir = output_dir / version version_dir.mkdir(parents=True, exist_ok=True) diff --git a/src/c2pa_kg/emitters/rules.py b/src/c2pa_kg/emitters/rules.py index 80933ed..c1528f4 100644 --- a/src/c2pa_kg/emitters/rules.py +++ b/src/c2pa_kg/emitters/rules.py @@ -50,6 +50,8 @@ def _rule_to_dict(rule: ValidationRule) -> dict[str, Any]: "phase": rule.phase.value, "spec_section": rule.spec_section, } + if rule.spec_area: + d["spec_area"] = rule.spec_area if rule.applicability != RuleApplicability.UNSPECIFIED: d["applicability"] = rule.applicability.value if rule.condition: @@ -132,8 +134,7 @@ def _group_generation_rules_by_area( areas: dict[str, list[ValidationRule]] = {} for rule in gen_rules: - # Derive area from spec_section: use text before first number or whole thing. - area = rule.spec_section or "General" + area = rule.spec_area or rule.spec_section or "General" areas.setdefault(area, []).append(rule) result: dict[str, list[dict[str, Any]]] = {} @@ -185,7 +186,7 @@ def emit_rules_json(kg: KnowledgeGraph, output_path: Path) -> None: ... }, "generation_rules": { - "": [ ...GEN- rules for claim generators... ], + "": [ ...GEN- rules for claim generators... ], ... }, "status_codes": { diff --git a/src/c2pa_kg/models.py b/src/c2pa_kg/models.py index 51db9cd..1de2d7b 100644 --- a/src/c2pa_kg/models.py +++ b/src/c2pa_kg/models.py @@ -202,6 +202,7 @@ class ValidationRule: action: str = "" referenced_entities: list[str] = field(default_factory=list) spec_section: str = "" + spec_area: str = "" source_text: str = "" applicability: RuleApplicability = RuleApplicability.UNSPECIFIED @@ -217,6 +218,8 @@ def to_dict(self) -> dict: "spec_section": self.spec_section, "source_text": self.source_text, } + if self.spec_area: + d["spec_area"] = self.spec_area if self.applicability != RuleApplicability.UNSPECIFIED: d["applicability"] = self.applicability.value return d @@ -525,6 +528,7 @@ def kg_from_dict(data: dict) -> KnowledgeGraph: action=rud.get("action", ""), referenced_entities=rud.get("referenced_entities", []), spec_section=rud.get("spec_section", ""), + spec_area=rud.get("spec_area", ""), source_text=rud.get("source_text", ""), applicability=RuleApplicability( rud.get("applicability", "unspecified") diff --git a/src/c2pa_kg/parsers/asciidoc.py b/src/c2pa_kg/parsers/asciidoc.py index 5b7c801..9388618 100644 --- a/src/c2pa_kg/parsers/asciidoc.py +++ b/src/c2pa_kg/parsers/asciidoc.py @@ -76,6 +76,41 @@ def _section_at(offset: int, headers: list[tuple[int, int, str]]) -> str: _INFO_RE = re.compile(r"\binformational\b", re.IGNORECASE) _FAILURE_RE = re.compile(r"\bfailure\b", re.IGNORECASE) +_INCLUDE_RE = re.compile(r"^include::([^\[]+)\[[^\]]*\]\s*$", re.MULTILINE) + + +def _resolve_adoc_includes(path: Path, *, seen: frozenset[Path] = frozenset()) -> str: + """Read an AsciiDoc file and inline relative ``include::*.adoc[]`` files. + + The validation clause is split across partials, and the status-code tables + put their rows in included files. Non-AsciiDoc includes, such as CDDL source + snippets, are intentionally dropped from the parsed text: they are examples + or schemas, not normative prose for this parser. + """ + resolved = path.resolve() + if resolved in seen: + return "" + seen = seen | {resolved} + text = path.read_text(encoding="utf-8") + + def _replace(match: re.Match[str]) -> str: + include_target = match.group(1).strip() + include_path = (path.parent / include_target).resolve() + if include_path.suffix.lower() != ".adoc" or not include_path.is_file(): + return "" + return _resolve_adoc_includes(include_path, seen=seen) + + return _INCLUDE_RE.sub(_replace, text) + + +def _read_validation_adoc(validation_path: Path) -> str: + """Read Validation.adoc with validation partials and status-code annex.""" + text = _resolve_adoc_includes(validation_path) + annex = validation_path.with_name("ValidationCodes_Annex.adoc") + if annex.is_file(): + text = f"{text}\n\n{_resolve_adoc_includes(annex)}" + return text + def _category_from_context(text_before: str) -> str: """Determine status code category from the text preceding a table.""" @@ -232,7 +267,7 @@ def parse_validation_doc( Returns: Tuple of (list[ValidationRule], list[StatusCode]). """ - text = validation_path.read_text(encoding="utf-8") + text = _read_validation_adoc(validation_path) status_codes = _parse_status_code_tables(text) rules = _parse_normative_rules(text) diff --git a/src/c2pa_kg/parsers/html_spec.py b/src/c2pa_kg/parsers/html_spec.py index 4f90214..c4e46c6 100644 --- a/src/c2pa_kg/parsers/html_spec.py +++ b/src/c2pa_kg/parsers/html_spec.py @@ -274,50 +274,53 @@ def parse_html_status_codes(html_text: str) -> list[StatusCode]: # Claim-generator applicability detection # --------------------------------------------------------------------------- -# Explicit "claim generator shall/must" patterns. +# Explicit claim-generator actor patterns. These intentionally allow the +# normative verb to appear after a short conditional clause, e.g. +# "When a claim generator is performing validation, it should ...". _CG_EXPLICIT_RE = re.compile( - r"\bclaim\s+generators?\s+(?:shall|must|should|may)\b" - r"|\bclaim\s+generators?\s+(?:shall\s+not|must\s+not|should\s+not)\b", + r"\bclaim\s+generators?\b(?![’']s\b)[^.?!]{0,160}" + r"\b(?:shall\s+not|must\s+not|should\s+not|shall|must|should|may)\b", re.IGNORECASE, ) # Sentence subject is a signer or creator entity. _CG_SUBJECT_RE = re.compile( - r"^\s*(?:the\s+)?(?:claim\s+generator|signer|creator|manifest\s+creator)" - r"\s+(?:shall|must|should|may)\b", + r"^\s*(?:the\s+)?(?:signer|creator|manifest\s+creator)" + r"\s+(?:shall\s+not|must\s+not|should\s+not|shall|must|should|may)\b", re.IGNORECASE, ) -# Explicit "a validator shall/must" — marks VALIDATOR applicability. +# Explicit validator-directed patterns. Include plural validators and all RFC +# 2119 severities so mixed generator/validator sentences can be classified BOTH. _VAL_EXPLICIT_RE = re.compile( - r"\b(?:a\s+)?validator\s+(?:shall|must)\b" + r"\bvalidators?\b[^.?!]{0,160}" + r"\b(?:shall\s+not|must\s+not|should\s+not|shall|must|should|may)\b" r"|\bwhen\s+validating\b" r"|\bduring\s+validation\b", re.IGNORECASE, ) -# Section headings strongly associated with manifest creation / generation. -_CG_SECTION_KEYWORDS = frozenset( - [ - "creating a claim", - "claim fields", - "ingredient", - "assertion", - "assertions", - "binding to content", - "hard binding", - "soft binding", - "embedding", - "manifest store", - "adding ingredients", - "versioning", - "redaction", - "signing", - "time-stamp", - "timestamp", - "credential", - "claim generator", - ] +# Section headings where passive/impersonal requirements usually describe +# manifest construction steps performed by claim generators. Keep this narrow: +# broad headings like "Assertions", "Ingredients", or "Credentials" produce +# false positives for generic implementer or validator requirements. +_CG_CONSTRUCTION_SECTION_RE = re.compile( + r"creating\s+a\s+claim" + r"|adding\s+assertions?" + r"|adding\s+ingredients?" + r"|signing\s+a\s+claim" + r"|choosing\s+the\s+payload" + r"|obtaining\s+the\s+time-?stamp" + r"|storing\s+the\s+time-?stamp" + r"|credential\s+revocation\s+information" + r"|create\s+content\s+bindings?" + r"|going\s+back\s+and\s+filling\s+in" + r"|redaction\s+of\s+assertions?" + r"|versioning\s+manifests?\s+due\s+to\s+conflicts?" + r"|mandatory\s+presence\s+of\s+at\s+least\s+one\s+actions?\s+assertion" + r"|fields\s+in\s+the\s+actions?\s+assertion" + r"|determining\s+the\s+need\s+to\s+copy", + re.IGNORECASE, ) @@ -338,13 +341,12 @@ def _infer_applicability(sentence: str, section: str) -> RuleApplicability: if has_val: return RuleApplicability.VALIDATOR - # Check section context: if the section is about manifest construction and - # the rule has no explicit validator language, lean toward CG. - if any(kw in section_lower for kw in _CG_SECTION_KEYWORDS): + # Check section context: only tightly scoped construction procedure + # sections get passive / impersonal CG inference. Generic structural + # sections remain UNSPECIFIED unless they name the responsible actor. + if _CG_CONSTRUCTION_SECTION_RE.search(section_lower): if not has_val and ("shall" in s_lower or "must" in s_lower): - # Passive / impersonal constructs in creation sections often imply CG. return RuleApplicability.CLAIM_GENERATOR - return RuleApplicability.UNSPECIFIED @@ -401,6 +403,23 @@ def _spec_area_for_section(section_title: str) -> str: return "General" +_VERSION_HISTORY_SECTION_RE = re.compile( + r"^\d+(?:\.\d+)+\.\s+\d+\.\d+\s+-\s+", + re.IGNORECASE, +) + + +def _is_non_normative_generation_section(section_title: str) -> bool: + """Return True for change-log/history sections that quote normative words.""" + title_lower = section_title.lower() + if "version history" in title_lower: + return True + if "changes in this version" in title_lower: + return True + return bool(_VERSION_HISTORY_SECTION_RE.search(section_title)) + + + def _extract_spec_body_sections(html_text: str) -> list[tuple[str, str, str]]: """Return (area, section_title, section_html) for each h2 section. @@ -655,6 +674,8 @@ def parse_html_generation_rules(html_text: str) -> list[ValidationRule]: continue sub_section = _section_at(offset, sub_headers) or section_title + if _is_non_normative_generation_section(sub_section): + continue block_entities = _extract_html_entities(raw_html, sub_section) for sentence in split_sentences(plain): @@ -687,6 +708,7 @@ def parse_html_generation_rules(html_text: str) -> list[ValidationRule]: action="", referenced_entities=block_entities[:10], spec_section=sub_section, + spec_area=area, source_text=sentence[:200], applicability=applicability, ) diff --git a/src/c2pa_kg/server/mcp_server.py b/src/c2pa_kg/server/mcp_server.py index 750f3df..a4c07f4 100644 --- a/src/c2pa_kg/server/mcp_server.py +++ b/src/c2pa_kg/server/mcp_server.py @@ -209,18 +209,20 @@ def query_generation_rules( gen_rules = [ r for r in all_rules - if r.get("applicability") in ("claim_generator", "both") + if r.get("rule_id", "").startswith("GEN-") + and r.get("applicability") in ("claim_generator", "both") ] if spec_area is not None: area_lower = spec_area.lower() gen_rules = [ r for r in gen_rules - if area_lower in r.get("spec_section", "").lower() + if area_lower in r.get("spec_area", "").lower() ] if severity is not None: - gen_rules = [r for r in gen_rules if r.get("severity") == severity] + severity_key = severity.lower().replace(" ", "_").replace("-", "_") + gen_rules = [r for r in gen_rules if r.get("severity") == severity_key] # Group by spec section for readability by_section: dict[str, list[dict]] = {} diff --git a/tests/test_cli.py b/tests/test_cli.py index 9160ba7..1dd70e2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -69,6 +69,13 @@ def test_missing_required_options_gives_error(self) -> None: assert result.exit_code != 0 + def test_generate_help_mentions_html_spec(self) -> None: + runner = CliRunner() + result = runner.invoke(cli, ["generate", "--help"]) + assert result.exit_code == 0 + assert "--html-spec" in result.output + + class TestCliGroup: def test_help_exits_zero(self) -> None: runner = CliRunner() diff --git a/tests/test_emitters.py b/tests/test_emitters.py index 2a5e93c..24134ed 100644 --- a/tests/test_emitters.py +++ b/tests/test_emitters.py @@ -23,7 +23,11 @@ KnowledgeGraph, Property, PropertyType, + RuleApplicability, + RuleSeverity, SpecVersion, + ValidationPhase, + ValidationRule, ) # --------------------------------------------------------------------------- @@ -175,6 +179,24 @@ def test_rules_json_version_matches(self, sample_kg: KnowledgeGraph, tmp_output: data = json.loads(out.read_text(encoding="utf-8")) assert data["version"] == sample_kg.version.version + def test_generation_rules_grouped_by_spec_area( + self, sample_kg: KnowledgeGraph, tmp_output: Path + ) -> None: + sample_kg.add_rule(ValidationRule( + rule_id="GEN-CLAIMS-0001", + description="Claim generators shall include a hard binding.", + severity=RuleSeverity.SHALL, + phase=ValidationPhase.ASSERTION, + spec_section="10.3.2.1. Adding Assertions and Redactions", + spec_area="Claims", + applicability=RuleApplicability.CLAIM_GENERATOR, + )) + out = tmp_output / "validation-rules.json" + emit_rules_json(sample_kg, out) + data = json.loads(out.read_text(encoding="utf-8")) + assert "Claims" in data["generation_rules"] + assert data["generation_rules"]["Claims"][0]["spec_area"] == "Claims" + # --------------------------------------------------------------------------- # Changelog emitter diff --git a/tests/test_generation_rule_parser.py b/tests/test_generation_rule_parser.py new file mode 100644 index 0000000..56da673 --- /dev/null +++ b/tests/test_generation_rule_parser.py @@ -0,0 +1,83 @@ +"""Focused tests for claim-generator requirement extraction.""" + +from __future__ import annotations + +from c2pa_kg.models import RuleApplicability +from c2pa_kg.parsers.html_spec import _infer_applicability, parse_html_generation_rules + + +def test_conditional_singular_claim_generator_is_detected() -> None: + result = _infer_applicability( + "When a claim generator is performing ingredient validation, it should " + "add a specVersion key.", + "5.1. Compatibility", + ) + assert result == RuleApplicability.CLAIM_GENERATOR + + +def test_plural_validator_and_claim_generator_sentence_is_both() -> None: + result = _infer_applicability( + "Validators should still accept this label, but claim generators " + "should not produce such a claim.", + "10.1. Overview", + ) + assert result == RuleApplicability.BOTH + + +def test_generic_credential_validation_sentence_is_not_claim_generator() -> None: + result = _infer_applicability( + "If present, the private credential store shall only apply to validating " + "signed C2PA manifests, and shall not apply to validating time-stamps.", + "14.4.3. Private Credential Storage", + ) + assert result == RuleApplicability.UNSPECIFIED + + +def test_possessive_claim_generator_certificate_is_not_generator_actor() -> None: + result = _infer_applicability( + "To address the risk of the Claim Generator’s certificate getting " + "revoked, the validator should check that the certificate is still valid.", + "19.7.3. Verifiable Segment Info Validation", + ) + assert result == RuleApplicability.VALIDATOR + + +def test_passive_requirement_in_construction_section_is_claim_generator() -> None: + result = _infer_applicability( + "The created_assertions field shall be present.", + "10.3.2.1. Adding Assertions and Redactions", + ) + assert result == RuleApplicability.CLAIM_GENERATOR + + +def test_generation_rules_have_spec_area_and_skip_generic_false_positive() -> None: + html = ( + '' + '

10. Claims

' + '

' + '10.3.2.1. Adding Assertions and Redactions

' + '

' + 'The claim shall contain a created_assertions field.' + '

' + '

14. Trust Model

' + '

' + '14.4.3. Private Credential Storage

' + '

' + 'If present, the private credential store shall only apply to validating ' + 'signed C2PA manifests, and shall not apply to validating time-stamps.' + '

' + '

5. Versioning

' + '

5.3. Version History

' + '

5.3.1. 2.4 - April 2026

' + '

' + 'Only allows a single claim generator, which must be the signer.' + '

' + '' + ) + + rules = parse_html_generation_rules(html) + + assert len(rules) == 1 + assert rules[0].spec_area == "Claims" + assert rules[0].spec_section == "10.3.2.1. Adding Assertions and Redactions" + assert rules[0].applicability == RuleApplicability.CLAIM_GENERATOR diff --git a/tests/test_html_spec_parser.py b/tests/test_html_spec_parser.py index 0fe3540..16c2254 100644 --- a/tests/test_html_spec_parser.py +++ b/tests/test_html_spec_parser.py @@ -253,7 +253,8 @@ def test_explicit_validator(self) -> None: def test_both_when_cg_and_validator(self) -> None: result = _infer_applicability( - "A claim generator shall not create one, but a validator shall process one if present.", + "A claim generator shall not create one, but a validator shall " + "process one if present.", "", ) assert result == RuleApplicability.BOTH diff --git a/tests/test_models.py b/tests/test_models.py index 6dc67c9..451b4f6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -344,11 +344,13 @@ def test_round_trip_applicability(self) -> None: severity=RuleSeverity.SHALL, phase=ValidationPhase.ASSERTION, applicability=RuleApplicability.CLAIM_GENERATOR, + spec_area="Claims", ) kg.add_rule(gen_rule) restored = kg_from_dict(kg.to_dict()) gen = next(r for r in restored.validation_rules if r.rule_id == "GEN-CLAIM-0001") assert gen.applicability == RuleApplicability.CLAIM_GENERATOR + assert gen.spec_area == "Claims" def test_applicability_unspecified_not_in_dict(self) -> None: rule = ValidationRule( @@ -370,3 +372,15 @@ def test_applicability_claim_generator_in_dict(self) -> None: ) d = rule.to_dict() assert d["applicability"] == "claim_generator" + + def test_spec_area_in_dict_when_present(self) -> None: + rule = ValidationRule( + rule_id="GEN-STANDA-0001", + description="Claim generators shall include the required assertion.", + severity=RuleSeverity.SHALL, + phase=ValidationPhase.ASSERTION, + spec_area="Standard Assertions", + applicability=RuleApplicability.CLAIM_GENERATOR, + ) + d = rule.to_dict() + assert d["spec_area"] == "Standard Assertions" diff --git a/versions/2.4/metadata.json b/versions/2.4/metadata.json index dfa2f3b..a6fa8d1 100644 --- a/versions/2.4/metadata.json +++ b/versions/2.4/metadata.json @@ -5163,7 +5163,7 @@ "type": "string", "required": true, "cardinality": "1", - "description": "User-Agent string for the claim generator that created the claim (RFC 7231 \u00a75.5.3)" + "description": "User-Agent string for the claim generator that created the claim (RFC 7231 §5.5.3)" }, { "name": "claim_generator_info", @@ -12361,6 +12361,1760 @@ ], "spec_section": "Extras for ZIP", "source_text": "If the hash does not match, then the manifest shall be rejected with a failure code of assertion.collectionHash.mismatch." + }, + { + "rule_id": "GEN-VERSIO-0001", + "description": "In this specification, when a construct is marked as deprecated, that means that a claim generator shall not write that construct (or value), but that a validator should read it.", + "severity": "shall_not", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "TimeStampMap" + ], + "spec_section": "5.1. Compatibility", + "source_text": "In this specification, when a construct is marked as deprecated, that means that a claim generator shall not write that construct (or value), but that a validator should read it.", + "spec_area": "Versioning", + "applicability": "both" + }, + { + "rule_id": "GEN-VERSIO-0002", + "description": "To facilitate testing and diagnosing interoperability issues between claim generators and validators, a claim generator should declare which version of the specification it is using to generate the C2PA Manifest by providing a specVersion key in the claim_generator_info field of the claim.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "specVersion", + "claim_generator_info" + ], + "spec_section": "5.1. Compatibility", + "source_text": "To facilitate testing and diagnosing interoperability issues between claim generators and validators, a claim generator should declare which version of the specification it is using to generate the C2", + "spec_area": "Versioning", + "applicability": "both" + }, + { + "rule_id": "GEN-VERSIO-0003", + "description": "When a claim generator is performing ingredient validation, it should add a specVersion key to the validation-results-map to declare what version of the specification was used as the basis for the validation procedure.", + "severity": "should", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "specVersion", + "validation-results-map" + ], + "spec_section": "5.1. Compatibility", + "source_text": "When a claim generator is performing ingredient validation, it should add a specVersion key to the validation-results-map to declare what version of the specification was used as the basis for the val", + "spec_area": "Versioning", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0001", + "description": "Claim generators shall not insert data into deprecated assertion fields when creating assertions.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "6.3. Versioning", + "source_text": "Claim generators shall not insert data into deprecated assertion fields when creating assertions.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0002", + "description": "In addition, a record that something was removed shall be added to the claim in the form of a URI reference to the redacted assertion in the redacted_assertions field of the claim.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "redacted_assertions", + "c2pa.redacted", + "redacted" + ], + "spec_section": "6.8. Redaction of Assertions", + "source_text": "In addition, a record that something was removed shall be added to the claim in the form of a URI reference to the redacted assertion in the redacted_assertions field of the claim.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0003", + "description": "It is also strongly recommended that the claim generator should add a c2pa.redacted action with a redacted field as described in Section 18.15.4.7, “Parameters”.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "redacted_assertions", + "c2pa.redacted", + "redacted" + ], + "spec_section": "6.8. Redaction of Assertions", + "source_text": "It is also strongly recommended that the claim generator should add a c2pa.redacted action with a redacted field as described in Section 18.15.4.7, “Parameters”.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0004", + "description": "When redacting an ingredient assertion that references a C2PA Manifest, the associated manifest shall be removed from the C2PA Manifest Store if no other references to it remain after redacting.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "IngredientMap" + ], + "spec_section": "6.8. Redaction of Assertions", + "source_text": "When redacting an ingredient assertion that references a C2PA Manifest, the associated manifest shall be removed from the C2PA Manifest Store if no other references to it remain after redacting.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0005", + "description": "Claim generators shall not redact assertions with a label of c2pa.actions or c2pa.actions.v2 as this assertion type represents essential information in understanding the history of an asset.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.actions", + "c2pa.actions.v2" + ], + "spec_section": "6.8. Redaction of Assertions", + "source_text": "Claim generators shall not redact assertions with a label of c2pa.actions or c2pa.actions.v2 as this assertion type represents essential information in understanding the history of an asset.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-ASSERT-0006", + "description": "When creating an update manifest, the claim generator shall not redact the hard binding to content assertion that applies to the current asset.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.actions", + "c2pa.actions.v2" + ], + "spec_section": "6.8. Redaction of Assertions", + "source_text": "When creating an update manifest, the claim generator shall not redact the hard binding to content assertion that applies to the current asset.", + "spec_area": "Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0001", + "description": "When present, the \"Claim Generator identifier\" string shall consist of no more than 32 characters from the ASCII range (as per RFC 20), but which are not Control Characters (RFC 20, 5.2) or Graphic Characters (RFC 20, 5.3).", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "8.1. Uniquely Identifying C2PA Manifests and Assets", + "source_text": "When present, the \"Claim Generator identifier\" string shall consist of no more than 32 characters from the ASCII range (as per RFC 20), but which are not Control Characters (RFC 20, 5.2) or Graphic Ch", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0002", + "description": "In addition, when a \"Version and Reason\" string is present, a \"Claim Generator identifier\" string shall also be present but it may be empty.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "8.1. Uniquely Identifying C2PA Manifests and Assets", + "source_text": "In addition, when a \"Version and Reason\" string is present, a \"Claim Generator identifier\" string shall also be present but it may be empty.", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0003", + "description": "In such a case, the modified version of the ingredient manifest needs to be copied into the asset’s C2PA Manifest Store, and shall be re-labeled.", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim", + "IngredientMap" + ], + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "source_text": "In such a case, the modified version of the ingredient manifest needs to be copied into the asset’s C2PA Manifest Store, and shall be re-labeled.", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0004", + "description": "If the current URN does not contain a \"Claim Generator identifier string\", then the claim generator shall append a :.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "source_text": "If the current URN does not contain a \"Claim Generator identifier string\", then the claim generator shall append a :.", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0005", + "description": "In all cases, the claim generator shall append a : to the URN followed by a monotonically increasing integer, starting with 1, followed by an underscore (_) and then an integer from the list below representing the reason for the re-labeling.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "source_text": "In all cases, the claim generator shall append a : to the URN followed by a monotonically increasing integer, starting with 1, followed by an underscore (_) and then an integer from the list below rep", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-UNIQUE-0006", + "description": "When working with assets that do not contain a C2PA Manifest and do not contain embedded XMP, the claim generator may use any method of its choosing to provide it with a unique identifier.", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "8.3. Identifying Non-C2PA Assets", + "source_text": "When working with assets that do not contain a C2PA Manifest and do not contain embedded XMP, the claim generator may use any method of its choosing to provide it with a unique identifier.", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-BINDIN-0001", + "description": "However, a Claim Generator should, where possible, include asset metadata (i.e., metadata outside a C2PA Manifest such as EXIF or XMP) in the hard binding, in order to protect its integrity throughout the asset’s lifecycle.", + "severity": "should", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim", + "DataHashMap" + ], + "spec_section": "9.2.6. Binding Non-C2PA Asset Metadata", + "source_text": "However, a Claim Generator should, where possible, include asset metadata (i.e., metadata outside a C2PA Manifest such as EXIF or XMP) in the hard binding, in order to protect its integrity throughout", + "spec_area": "Binding to Content", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0001", + "description": "Validators should still accept this label (and associated claim-map), but claim generators shall not produce such a claim.", + "severity": "shall_not", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.claim", + "claim-map" + ], + "spec_section": "10.1. Overview", + "source_text": "Validators should still accept this label (and associated claim-map), but claim generators shall not produce such a claim.", + "spec_area": "Claims", + "applicability": "both" + }, + { + "rule_id": "GEN-CLAIMS-0002", + "description": "However, instead of setting dc:title, claim generators should create a metadata assertion containing the dc:title field.", + "severity": "should", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "10.2.2. Fields", + "source_text": "However, instead of setting dc:title, claim generators should create a metadata assertion containing the dc:title field.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0003", + "description": "Detailed information about the claim generator shall be present as the value of claim_generator_info.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim_generator_info" + ], + "spec_section": "10.2.3.1. General", + "source_text": "Detailed information about the claim generator shall be present as the value of claim_generator_info.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0004", + "description": "A claim generator may desire to provide a graphical representation of itself, referred here as an icon, to a Manifest Consumer that is presenting a user experience.", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "icon", + "c2pa.icon" + ], + "spec_section": "10.2.3.2. Generator Info Map", + "source_text": "A claim generator may desire to provide a graphical representation of itself, referred here as an icon, to a Manifest Consumer that is presenting a user experience.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0005", + "description": "The claim shall contain a created_assertions field and may contain a gathered_assertions field.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "created_assertions", + "gathered_assertions" + ], + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "source_text": "The claim shall contain a created_assertions field and may contain a gathered_assertions field.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0006", + "description": "In a standard manifest, the created_assertions field’s value shall include at least one assertion that represents a hard binding.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "created_assertions", + "gathered_assertions" + ], + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "source_text": "In a standard manifest, the created_assertions field’s value shall include at least one assertion that represents a hard binding.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0007", + "description": "If any assertions in ingredient claims are being redacted, their URI references shall be added to list which is the value of the redacted_assertions field.", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "redacted_assertions" + ], + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "source_text": "If any assertions in ingredient claims are being redacted, their URI references shall be added to list which is the value of the redacted_assertions field.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0008", + "description": "If a manifest with the same unique identifier is already present in the C2PA Manifest Store, the two shall be compared.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "IngredientMap" + ], + "spec_section": "10.3.2.2. Adding Ingredients", + "source_text": "If a manifest with the same unique identifier is already present in the C2PA Manifest Store, the two shall be compared.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0009", + "description": "If they are identical, the new manifest shall be ignored.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "IngredientMap" + ], + "spec_section": "10.3.2.2. Adding Ingredients", + "source_text": "If they are identical, the new manifest shall be ignored.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0010", + "description": "If they are different, the new manifest shall be added to the store after changing its unique identifier to a new value as described in Chapter 8, Unique Identifiers.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "IngredientMap" + ], + "spec_section": "10.3.2.2. Adding Ingredients", + "source_text": "If they are different, the new manifest shall be added to the store after changing its unique identifier to a new value as described in Chapter 8, Unique Identifiers.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0011", + "description": "If an ingredient’s manifest is remote, and the claim generator is unable to retrieve the manifest, it should use an error code of manifest.inaccessible to reflect that.", + "severity": "should", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest.inaccessible" + ], + "spec_section": "10.3.2.2. Adding Ingredients", + "source_text": "If an ingredient’s manifest is remote, and the claim generator is unable to retrieve the manifest, it should use an error code of manifest.inaccessible to reflect that.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0012", + "description": "For both types of manifests, standard and update, the payload field of Sig_structure shall be the serialized CBOR of the claim document, and shall use detached content mode.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "payload", + "Sig_structure" + ], + "spec_section": "10.3.2.4. Signing a Claim", + "source_text": "For both types of manifests, standard and update, the payload field of Sig_structure shall be the serialized CBOR of the claim document, and shall use detached content mode.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0013", + "description": "If possible, the claim generator should use a RFC 3161-compliant Time Stamping Authority (TSA) (RFC 3161) to obtain a trusted time-stamp proving that the signature itself actually existed at a certain date and time and incorporate that into the COSE_Sign1_Tagged structure as a countersignature.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "COSE_Sign1_Tagged" + ], + "spec_section": "10.3.2.5.1. Use of RFC 3161", + "source_text": "If possible, the claim generator should use a RFC 3161-compliant Time Stamping Authority (TSA) (RFC 3161) to obtain a trusted time-stamp proving that the signature itself actually existed at a certain", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0014", + "description": "A claim generator shall not create one, but a validator shall process one if present.", + "severity": "shall_not", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "payload", + "Sig_signature" + ], + "spec_section": "10.3.2.5.2. Choosing the Payload", + "source_text": "A claim generator shall not create one, but a validator shall process one if present.", + "spec_area": "Claims", + "applicability": "both" + }, + { + "rule_id": "GEN-CLAIMS-0015", + "description": "A \"v2 payload\" shall be used by claim generators performing a time-stamping operation.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "signature", + "COSE_Sign1_Tagged" + ], + "spec_section": "10.3.2.5.2. Choosing the Payload", + "source_text": "A \"v2 payload\" shall be used by claim generators performing a time-stamping operation.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0016", + "description": "All time-stamps shall be obtained as described in RFC 3161 with the following additional requirements:", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "TimeStampMap" + ], + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "source_text": "All time-stamps shall be obtained as described in RFC 3161 with the following additional requirements:", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0017", + "description": "The MessageImprint of the TimeStampReq structure (RFC 3161, section 2.4.1) shall be computed by creating the ToBeSigned value in RFC 8152, section 4.4, with the following values for elements of Sig_structure:", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "Sig_structure", + "MessageImprint", + "TimeStampReq", + "ToBeSigned" + ], + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "source_text": "The MessageImprint of the TimeStampReq structure (RFC 3161, section 2.4.1) shall be computed by creating the ToBeSigned value in RFC 8152, section 4.4, with the following values for elements of Sig_st", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0018", + "description": "The context element shall be CounterSignature.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "context", + "CounterSignature" + ], + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "source_text": "The context element shall be CounterSignature.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0019", + "description": "The payload element shall be the value described by Section 10.3.2.5.2, “Choosing the Payload”.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "payload" + ], + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "source_text": "The payload element shall be the value described by Section 10.3.2.5.2, “Choosing the Payload”.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0020", + "description": "The certReq boolean of the TimeStampReq structure shall be asserted in the request to the TSA, to ensure its certificate chain is provided in the response.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "certReq", + "TimeStampReq" + ], + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "source_text": "The certReq boolean of the TimeStampReq structure shall be asserted in the request to the TSA, to ensure its certificate chain is provided in the response.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0021", + "description": "If present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst", + "tstContainer", + "val", + "tstTokens", + "TimeStampResp" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "If present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0022", + "description": "The content of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst", + "tstContainer", + "val", + "tstTokens", + "TimeStampResp" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "The content of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0023", + "description": "v2 time-stamps shall be stored in a COSE unprotected header whose label is the string sigTst2.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "v2 time-stamps shall be stored in a COSE unprotected header whose label is the string sigTst2.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0024", + "description": "When present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "When present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0025", + "description": "The value of the timeStampToken field of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "The value of the timeStampToken field of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0026", + "description": "It shall be formatted as a DER-encoded RFC 3161 TimeStampToken wrapped in a CBOR byte string.", + "severity": "shall", + "phase": "timestamp", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "It shall be formatted as a DER-encoded RFC 3161 TimeStampToken wrapped in a CBOR byte string.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0027", + "description": "If no time-stamps are included, then neither header (sigTst nor sigTst2) shall be present in the COSE unprotected header.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "sigTst", + "sigTst2" + ], + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "source_text": "If no time-stamps are included, then neither header (sigTst nor sigTst2) shall be present in the COSE unprotected header.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0028", + "description": "If the signer’s credential supports querying its online credential status, and the credential contains a pointer to a service to provide time-stamped credential status information, the claim generator should query the service, capture the response, and store it in the manner described for credentials in the Trust Model.", + "severity": "should", + "phase": "trust", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "TimeStampMap" + ], + "spec_section": "10.3.2.6. Credential Revocation Information", + "source_text": "If the signer’s credential supports querying its online credential status, and the credential contains a pointer to a service to provide time-stamped credential status information, the claim generator", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0029", + "description": "If credential revocation information is attached in this manner, a trusted time-stamp shall also be obtained after signing, as described in Section 10.3.2.5, “Time-stamps”.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "TimeStampMap" + ], + "spec_section": "10.3.2.6. Credential Revocation Information", + "source_text": "If credential revocation information is attached in this manner, a trusted time-stamp shall also be obtained after signing, as described in Section 10.3.2.5, “Time-stamps”.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0030", + "description": "When creating a standard manifest, its claim shall include one or more content binding assertions in its list of assertions to ensure that the asset is tamper-evident.", + "severity": "shall", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest" + ], + "spec_section": "10.4.1. Create content bindings", + "source_text": "When creating a standard manifest, its claim shall include one or more content binding assertions in its list of assertions to ensure that the asset is tamper-evident.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0031", + "description": "When the size or location (or both) of the manifest in the asset is not known, then the start and length values in the data hash assertion shall both be zero and the size of the pad value should be large enough to accommodate writing in the values during the second pass.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "start", + "length", + "pad" + ], + "spec_section": "10.4.1. Create content bindings", + "source_text": "When the size or location (or both) of the manifest in the asset is not known, then the start and length values in the data hash assertion shall both be zero and the size of the pad value should be la", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0032", + "description": "The value of the pad key shall consist of all 0x00’s.", + "severity": "shall", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "start", + "length", + "pad" + ], + "spec_section": "10.4.1. Create content bindings", + "source_text": "The value of the pad key shall consist of all 0x00’s.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0033", + "description": "Claim generators shall ensure that changes to pad data (or any other excluded asset data) cannot change how the asset is interpreted.", + "severity": "shall", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "10.4.1. Create content bindings", + "source_text": "Claim generators shall ensure that changes to pad data (or any other excluded asset data) cannot change how the asset is interpreted.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0034", + "description": "If the serialized COSE_Sign1_Tagged structure exceeds the reserved size of the C2PA Claim Signature box, multiple step processing shall be repeated with a larger padding size chosen in Section 10.4.2, “Create a temporary Claim and Signature”.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "COSE_Sign1_Tagged" + ], + "spec_section": "10.4.4. Going back and filling in", + "source_text": "If the serialized COSE_Sign1_Tagged structure exceeds the reserved size of the C2PA Claim Signature box, multiple step processing shall be repeated with a larger padding size chosen in Section 10.4.2,", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0035", + "description": "As such, the claim generator may no longer be able to change the file layout and/or offsets in a data hash assertion.", + "severity": "may", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "10.4.4. Going back and filling in", + "source_text": "As such, the claim generator may no longer be able to change the file layout and/or offsets in a data hash assertion.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CLAIMS-0036", + "description": "In this case, claim generators should use padding prior to assertion creation to ensure that the file layout need not change once the assertion has been finalized.", + "severity": "should", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "10.4.4. Going back and filling in", + "source_text": "In this case, claim generators should use padding prior to assertion creation to ensure that the file layout need not change once the assertion has been finalized.", + "spec_area": "Claims", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-MANIFE-0001", + "description": "Manifest Consumers shall also accept standard C2PA Manifests specified with JUMBF type UUID 63326D64-0011-0010-8000-00AA00389B71 (c2md), but claim generators shall not create manifests with this JUMBF type UUID.", + "severity": "shall_not", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "11.2.2. Standard Manifests", + "source_text": "Manifest Consumers shall also accept standard C2PA Manifests specified with JUMBF type UUID 63326D64-0011-0010-8000-00AA00389B71 (c2md), but claim generators shall not create manifests with this JUMBF", + "spec_area": "Manifests", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CRYPTO-0001", + "description": "A claim generator may also wish to establish a \"claimed time of signing\" by adding an iat protected header, whose value is a NumericDate.", + "severity": "may", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "iat", + "NumericDate" + ], + "spec_section": "13.2.4. Adding a claimed time of signing", + "source_text": "A claim generator may also wish to establish a \"claimed time of signing\" by adding an iat protected header, whose value is a NumericDate.", + "spec_area": "Cryptography", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-CRYPTO-0002", + "description": "When producing a signature, if the claim generator can also act as a validator, the claim generator should validate that the signing credential is acceptable according to Chapter 14, Trust Model and produce a warning if it is not.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "13.2.5. Signature Validation", + "source_text": "When producing a signature, if the claim generator can also act as a validator, the claim generator should validate that the signing credential is acceptable according to Chapter 14, Trust Model and p", + "spec_area": "Cryptography", + "applicability": "both" + }, + { + "rule_id": "GEN-CRYPTO-0003", + "description": "The claim generator may still allow signing with that credential if so desired.", + "severity": "may", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "13.2.5. Signature Validation", + "source_text": "The claim generator may still allow signing with that credential if so desired.", + "spec_area": "Cryptography", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0001", + "description": "Therefore, when creating the x5chain header as part of signing, the claim generator shall include the signer’s certificate and all intermediate certificate authorities in the header’s value.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "x5chain" + ], + "spec_section": "14.5. X.509 Certificates", + "source_text": "Therefore, when creating the x5chain header as part of signing, the claim generator shall include the signer’s certificate and all intermediate certificate authorities in the header’s value.", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0002", + "description": "Claim generators should use only the integer 33 as the label when inserting this header into a COSE signature.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "x5chain" + ], + "spec_section": "14.5. X.509 Certificates", + "source_text": "Claim generators should use only the integer 33 as the label when inserting this header into a COSE signature.", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0003", + "description": "Claim generators may continue to write the string label x5chain but this behaviour is now deprecated and claim generators should be updated to use the integer label only.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "x5chain" + ], + "spec_section": "14.5. X.509 Certificates", + "source_text": "Claim generators may continue to write the string label x5chain but this behaviour is now deprecated and claim generators should be updated to use the integer label only.", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0004", + "description": "Claim generators shall place this header only in the protected header bucket of the COSE signature as required above.", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "x5chain" + ], + "spec_section": "14.5. X.509 Certificates", + "source_text": "Claim generators shall place this header only in the protected header bucket of the COSE signature as required above.", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0005", + "description": "A claim generator should use the Online Certificate Status Protocol (OCSP, RFC 6960) and OCSP stapling (as originally conceptualized in RFC 6066, Section 8, but implemented as described in this clause) to implement revocation.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "certificateInfo" + ], + "spec_section": "14.5.2. Certificate Revocation", + "source_text": "A claim generator should use the Online Certificate Status Protocol (OCSP, RFC 6960) and OCSP stapling (as originally conceptualized in RFC 6066, Section 8, but implemented as described in this clause", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0006", + "description": "The claim generator shall not use Certificate Revocation Lists (CRLs, RFC 5280).", + "severity": "shall_not", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "certificateInfo" + ], + "spec_section": "14.5.2. Certificate Revocation", + "source_text": "The claim generator shall not use Certificate Revocation Lists (CRLs, RFC 5280).", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0007", + "description": "Before signing a claim, if a signer’s certificate has the AIA extension, a claim generator should query the OCSP service indicated therein, capture the response, and store it in an element of the ocspVals array of the rVals header.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "ocspVals", + "rVals" + ], + "spec_section": "14.5.2. Certificate Revocation", + "source_text": "Before signing a claim, if a signer’s certificate has the AIA extension, a claim generator should query the OCSP service indicated therein, capture the response, and store it in an element of the ocsp", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-TRUST_-0008", + "description": "The claim generator should do the same for any intermediate CA certificates it includes with the claim signature.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "ocspVals", + "rVals" + ], + "spec_section": "14.5.2. Certificate Revocation", + "source_text": "The claim generator should do the same for any intermediate CA certificates it includes with the claim signature.", + "spec_area": "Trust Model", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0001", + "description": "In order for a Manifest Consumer to display human-readable information about these keys and values, the claim generator should provide the strings via this localization approach.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "com.litware" + ], + "spec_section": "18.3.9.2. Localization Dictionary", + "source_text": "In order for a Manifest Consumer to display human-readable information about these keys and values, the claim generator should provide the strings via this localization approach.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0002", + "description": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "severity": "shall", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.5.1. Description", + "source_text": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0003", + "description": "Claim generators shall not add this field to a data hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "url" + ], + "spec_section": "18.5.1. Description", + "source_text": "Claim generators shall not add this field to a data hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being ", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0004", + "description": "Claim generators shall not add this field to a BMFF hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "url" + ], + "spec_section": "18.6.1. Description", + "source_text": "Claim generators shall not add this field to a BMFF hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being ", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0005", + "description": "A claim generator should use a general box hash assertion to verify the integrity, with a hard binding (i.e., cryptographic hash), of assets whose formats use a non-BMFF-based box format such as JPEG, PNG, or GIF.", + "severity": "should", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "DataHashMap" + ], + "spec_section": "18.7.1. Description", + "source_text": "A claim generator should use a general box hash assertion to verify the integrity, with a hard binding (i.e., cryptographic hash), of assets whose formats use a non-BMFF-based box format such as JPEG,", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0006", + "description": "For boxes that have an excluded field with a value of true, the claim generator should include an accurate hash for compatibility with older validators that do not recognize the excluded field.", + "severity": "should", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "excluded", + "false", + "true" + ], + "spec_section": "18.7.1. Description", + "source_text": "For boxes that have an excluded field with a value of true, the claim generator should include an accurate hash for compatibility with older validators that do not recognize the excluded field.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0007", + "description": "If the claim generator is not concerned with backwards compatibility, it should write the binary string 00 (a single byte with a value of 0) for the hash.", + "severity": "should", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "excluded", + "false", + "true" + ], + "spec_section": "18.7.1. Description", + "source_text": "If the claim generator is not concerned with backwards compatibility, it should write the binary string 00 (a single byte with a value of 0) for the hash.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0008", + "description": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "severity": "shall", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.7.1. Description", + "source_text": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0009", + "description": "A claim generator shall validate or sanitize the URIs before use, ensuring that neither . nor .. appear as part of the URI.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "uri-hashed-data-map", + "uri" + ], + "spec_section": "18.8.3. Fields", + "source_text": "A claim generator shall validate or sanitize the URIs before use, ensuring that neither . nor .. appear as part of the URI.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0010", + "description": "If a claim generator will be providing a soft binding for the asset’s content, it shall be described using a soft binding assertion.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "claim", + "SoftBindingMap" + ], + "spec_section": "18.10.1. Description", + "source_text": "If a claim generator will be providing a soft binding for the asset’s content, it shall be described using a soft binding assertion.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0011", + "description": "Claim generators shall not add this field to a soft binding assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "url" + ], + "spec_section": "18.10.1. Description", + "source_text": "Claim generators shall not add this field to a soft binding assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content bei", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0012", + "description": "Claim generators shall not add this field to a soft binding assertion, and consumers should ignore the field when present.", + "severity": "shall_not", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "extent", + "scope", + "region" + ], + "spec_section": "18.10.1. Description", + "source_text": "Claim generators shall not add this field to a soft binding assertion, and consumers should ignore the field when present.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0013", + "description": "There shall be at least one actions assertion present in the created_assertions array of the Claim of a standard C2PA Manifest.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "created_assertions" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "There shall be at least one actions assertion present in the created_assertions array of the Claim of a standard C2PA Manifest.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0014", + "description": "If the asset was created de novo (for example, as a result of performing a File → New operation in a creative tool, capturing a photo or video, or generating the media by a generative AI model), then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shall have a c2pa.created action as its first element.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.created" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "If the asset was created de novo (for example, as a result of performing a File → New operation in a creative tool, capturing a photo or video, or generating the media by a generative AI model), then ", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0015", + "description": "For all assets, a corresponding digitalSourceType field, with an appropriate value, shall be recorded with the c2pa.created action, to indicate the nature of the asset at its inception.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "digitalSourceType", + "c2pa.created" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "For all assets, a corresponding digitalSourceType field, with an appropriate value, shall be recorded with the c2pa.created action, to indicate the nature of the asset at its inception.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0016", + "description": "If the asset is created with no digital content, then the digitalSourceType field shall have the value http://c2pa.org/digitalsourcetype/empty.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "digitalSourceType", + "c2pa.created" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "If the asset is created with no digital content, then the digitalSourceType field shall have the value http://c2pa.org/digitalsourcetype/empty.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0017", + "description": "If the asset was created by opening an existing asset as a parentOf ingredient for editing, then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shall have a c2pa.opened action as its first element.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "parentOf", + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.opened", + "c2pa.ingredient.v3", + "ingredients", + "parameters" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "If the asset was created by opening an existing asset as a parentOf ingredient for editing, then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shal", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0018", + "description": "The claim generator shall also add a corresponding c2pa.ingredient.v3 assertion for the ingredient that was opened, and include a hashed-uri reference to it in the value of the ingredients field of its parameters object.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "parentOf", + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.opened", + "c2pa.ingredient.v3", + "ingredients", + "parameters" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "The claim generator shall also add a corresponding c2pa.ingredient.v3 assertion for the ingredient that was opened, and include a hashed-uri reference to it in the value of the ingredients field of it", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0019", + "description": "The full set of actions assertions in a C2PA Manifest shall contain no more than one action whose type is either c2pa.created or c2pa.opened.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.created", + "c2pa.opened" + ], + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "source_text": "The full set of actions assertions in a C2PA Manifest shall contain no more than one action whose type is either c2pa.created or c2pa.opened.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0020", + "description": "If allActionsIncluded has a value of false, then the claim generator is stating that additional, unrecorded actions may have been performed.", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "actions-map-v2", + "allActionsIncluded" + ], + "spec_section": "18.15.3. All actions included", + "source_text": "If allActionsIncluded has a value of false, then the claim generator is stating that additional, unrecorded actions may have been performed.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0021", + "description": "In the situation where a claim generator is opening an asset strictly for the purposes of recording its c2pa.opened action and then immediately re-saving it without making any other changes, the claim generator shall set allActionsIncluded to true to assert that no other actions were performed on the asset.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.opened", + "allActionsIncluded" + ], + "spec_section": "18.15.3. All actions included", + "source_text": "In the situation where a claim generator is opening an asset strictly for the purposes of recording its c2pa.opened action and then immediately re-saving it without making any other changes, the claim", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0022", + "description": "A claim generator that performs the same action over and over, with the same parameters & settings, may use the multipleInstances field to indicate that the action was performed multiple times or not.", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "multipleInstances" + ], + "spec_section": "18.15.4.7. Parameters", + "source_text": "A claim generator that performs the same action over and over, with the same parameters & settings, may use the multipleInstances field to indicate that the action was performed multiple times or not.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0023", + "description": "When performing either a c2pa.opened or c2pa.placed action with an asset that does not contain a C2PA Manifest, the claim generator may use a soft binding lookup to find the C2PA Manifest for that asset.", + "severity": "may", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.opened", + "c2pa.placed", + "activeManifest", + "softBindingsMatched", + "softBindingAlgorithmsMatched" + ], + "spec_section": "18.15.10. Soft Binding Lookup", + "source_text": "When performing either a c2pa.opened or c2pa.placed action with an asset that does not contain a C2PA Manifest, the claim generator may use a soft binding lookup to find the C2PA Manifest for that ass", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0024", + "description": "If successful, the claim generator should add the located C2PA Manifest as the value of the activeManifest field in the ingredient assertion.", + "severity": "should", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.opened", + "c2pa.placed", + "activeManifest", + "softBindingsMatched", + "softBindingAlgorithmsMatched" + ], + "spec_section": "18.15.10. Soft Binding Lookup", + "source_text": "If successful, the claim generator should add the located C2PA Manifest as the value of the activeManifest field in the ingredient assertion.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0025", + "description": "When the claim generator provides the optional instanceID field of the ingredient assertion, then the value of the unique identifier shall be determined as specified by Section 8.3, “Identifying Non-C2PA Assets”.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "instanceID" + ], + "spec_section": "18.16.2. Establishing unique identifiers", + "source_text": "When the claim generator provides the optional instanceID field of the ingredient assertion, then the value of the unique identifier shall be determined as specified by Section 8.3, “Identifying Non-C", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0026", + "description": "When adding an ingredient assertion, a claim generator shall add a c2pa.actions assertion (see Section 18.15, “Actions”), if one does not already exist in the active manifest.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.actions", + "actions" + ], + "spec_section": "18.16.3. Relationship", + "source_text": "When adding an ingredient assertion, a claim generator shall add a c2pa.actions assertion (see Section 18.15, “Actions”), if one does not already exist in the active manifest.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0027", + "description": "However, claim generators should not set this value if the information is available in the ingredient’s active C2PA Manifest.", + "severity": "should_not", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.4. Title", + "source_text": "However, claim generators should not set this value if the information is available in the ingredient’s active C2PA Manifest.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0028", + "description": "It is recommended that a claim generator should provide this field and it shall contain a valid value.", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "18.16.5. Format", + "source_text": "It is recommended that a claim generator should provide this field and it shall contain a valid value.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0029", + "description": "If present, the content of this field should be produced by the claim generator, and should not contain user-provided information.", + "severity": "should_not", + "phase": "content", + "condition": "", + "action": "", + "referenced_entities": [ + "description" + ], + "spec_section": "18.16.7. Description field", + "source_text": "If present, the content of this field should be produced by the claim generator, and should not contain user-provided information.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0030", + "description": "Claim generators should take the size of this field into consideration when choosing whether to embed data.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.8.1. Standard Usage", + "source_text": "Claim generators should take the size of this field into consideration when choosing whether to embed data.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0031", + "description": "The claim generator should also copy into the asset’s C2PA Manifest Store any additional C2PA Manifests that were not validated, as well as any additional JUMBF boxes and superboxes appearing in the C2PA Manifest Store that are not recognized as C2PA Manifests.", + "severity": "should", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.11.1. General", + "source_text": "The claim generator should also copy into the asset’s C2PA Manifest Store any additional C2PA Manifests that were not validated, as well as any additional JUMBF boxes and superboxes appearing in the C", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0032", + "description": "To determine whether or not an existing manifest from the ingredient’s C2PA Manifest Store needs to be copied into the asset’s C2PA Manifest Store, the claim generator shall:", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "To determine whether or not an existing manifest from the ingredient’s C2PA Manifest Store needs to be copied into the asset’s C2PA Manifest Store, the claim generator shall:", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0033", + "description": "In case of validation failures, the claim generator may skip the rest of these steps if directed to do so (for example, via user input or via configuration).", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "In case of validation failures, the claim generator may skip the rest of these steps if directed to do so (for example, via user input or via configuration).", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0034", + "description": "If the hashes match, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store to the asset’s C2PA Manifest Store.", + "severity": "shall_not", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "If the hashes match, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store to the asset’s C2PA Manifest Store.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0035", + "description": "The claim generator shall check if any assertions from either manifest were redacted (optionally utilizing the list of redactions compiled in the Performing explicit validation process).", + "severity": "shall", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "The claim generator shall check if any assertions from either manifest were redacted (optionally utilizing the list of redactions compiled in the Performing explicit validation process).", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0036", + "description": "If all redactions were applied against the manifest already present in the asset’s C2PA Manifest Store, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store into the asset’s C2PA Manifest Store.", + "severity": "shall_not", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "If all redactions were applied against the manifest already present in the asset’s C2PA Manifest Store, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store i", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0037", + "description": "If all redactions were applied against the manifest from the ingredient’s Manifest Store, then the claim generator shall replace the manifest in the asset’s C2PA Manifest Store with the manifest from the ingredient’s C2PA Manifest Store.", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "If all redactions were applied against the manifest from the ingredient’s Manifest Store, then the claim generator shall replace the manifest in the asset’s C2PA Manifest Store with the manifest from ", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0038", + "description": "If different redactions were applied against both the C2PA Manifest from the ingredient’s C2PA Manifest Store and the asset’s C2PA Manifest Store, then the claim generator shall redact as many assertions as needed from the existing manifest in the asset’s C2PA Manifest Store to result in a union of the two sets of redactions.", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "If different redactions were applied against both the C2PA Manifest from the ingredient’s C2PA Manifest Store and the asset’s C2PA Manifest Store, then the claim generator shall redact as many asserti", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0039", + "description": "In all other cases, then the claim generator shall copy the manifest from the ingredient’s C2PA Manifest Store, re-label it with an updated URN per the process described in Unique Identifiers, and insert the re-labeled version into the asset’s C2PA Manifest Store.", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "manifest", + "claim" + ], + "spec_section": "18.16.12.1. Determining the need", + "source_text": "In all other cases, then the claim generator shall copy the manifest from the ingredient’s C2PA Manifest Store, re-label it with an updated URN per the process described in Unique Identifiers, and ins", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0040", + "description": "In addition, when the ingredient assertion references a C2PA Manifest, the claim generator shall also act as a validator, performing validation of the ingredient as described in validation steps.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "validationResults", + "validationStatus" + ], + "spec_section": "18.16.12.4.1. General", + "source_text": "In addition, when the ingredient assertion references a C2PA Manifest, the claim generator shall also act as a validator, performing validation of the ingredient as described in validation steps.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0041", + "description": "If the Claim Generator does include such a C2PA Manifest, then it shall include a softBindingsMatched field indicating true, and a softBindingAlgorithmsMatched field containing an array of strings (of soft binding algorithm names that were used to discover the ingredient C2PA Manifest).", + "severity": "shall", + "phase": "ingredient", + "condition": "", + "action": "", + "referenced_entities": [ + "parentOf", + "softBindingsMatched", + "softBindingAlgorithmsMatched", + "alg" + ], + "spec_section": "18.16.14. Soft Bindings", + "source_text": "If the Claim Generator does include such a C2PA Manifest, then it shall include a softBindingsMatched field indicating true, and a softBindingAlgorithmsMatched field containing an array of strings (of", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0042", + "description": "Claim generators shall include in this assertion only the specific metadata fields enumerated in Appendix B, Implementation Details for c2pa.metadata.", + "severity": "shall", + "phase": "assertion", + "condition": "", + "action": "", + "referenced_entities": [ + "c2pa.metadata" + ], + "spec_section": "18.17.3. The c2pa.metadata assertion", + "source_text": "Claim generators shall include in this assertion only the specific metadata fields enumerated in Appendix B, Implementation Details for c2pa.metadata.", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-STANDA-0043", + "description": "As described in Section 14.5.2, “Certificate Revocation”, the claim generator queries the OCSP service indicated by the signing certificate, captures the response, and shall store it the same binary format as used when it is stored as an element of the ocspVals array of the rVals header (see Example 3, “CDDL for rVals”).", + "severity": "shall", + "phase": "cryptographic", + "condition": "", + "action": "", + "referenced_entities": [ + "certificate-status-map", + "ocspVals", + "rVals" + ], + "spec_section": "18.19.3. Requirements", + "source_text": "As described in Section 14.5.2, “Certificate Revocation”, the claim generator queries the OCSP service indicated by the signing certificate, captures the response, and shall store it the same binary f", + "spec_area": "Standard Assertions", + "applicability": "claim_generator" + }, + { + "rule_id": "GEN-GENERA-0001", + "description": "Claim generators may derive additional per-segment validation values (e.g., anchor-point-based chaining values) from segment hashes and embed them in the Manifest.", + "severity": "may", + "phase": "structural", + "condition": "", + "action": "", + "referenced_entities": [ + "claim" + ], + "spec_section": "19.3.1. Description", + "source_text": "Claim generators may derive additional per-segment validation values (e.g., anchor-point-based chaining values) from segment hashes and embed them in the Manifest.", + "spec_area": "General", + "applicability": "claim_generator" } ], "enum_types": { @@ -13531,18 +15285,6 @@ "url_usage": "C2PA Assertion", "category": "failure" }, - { - "code": "assertion.alternativeContentRepresentation.hashMismatch", - "meaning": "The hash of the original preservation image content does not match the hash declared in the assertion.", - "url_usage": "C2PA Assertion", - "category": "failure" - }, - { - "code": "assertion.alternativeContentRepresentation.missing", - "meaning": "The original preservation image assertion, or its required content, could not be found or resolved.", - "url_usage": "C2PA Assertion", - "category": "failure" - }, { "code": "assertion.multipleHardBindings", "meaning": "The manifest has more than one hard binding assertion.", @@ -13825,7 +15567,7 @@ "stats": { "entity_count": 148, "relationship_count": 208, - "rule_count": 237, + "rule_count": 345, "enum_count": 37, "type_alias_count": 22, "status_code_count": 114, diff --git a/versions/2.4/validation-rules.json b/versions/2.4/validation-rules.json index 137584c..2d54150 100644 --- a/versions/2.4/validation-rules.json +++ b/versions/2.4/validation-rules.json @@ -1,24 +1,29 @@ { "version": "2.4", - "rule_count": 237, + "rule_count": 345, "summary": { - "total": 237, + "total": 345, "by_phase": { - "structural": 37, - "assertion": 118, - "cryptographic": 40, - "ingredient": 15, - "timestamp": 8, - "trust": 2, - "content": 17 + "structural": 69, + "assertion": 145, + "cryptographic": 56, + "ingredient": 27, + "timestamp": 18, + "trust": 3, + "content": 27 }, "by_severity": { - "may": 19, - "shall": 195, - "shall_not": 7, - "should": 13, - "should_not": 2, + "may": 29, + "shall": 255, + "shall_not": 21, + "should": 35, + "should_not": 4, "must": 1 + }, + "by_applicability": { + "unspecified": 237, + "both": 5, + "claim_generator": 103 } }, "phases": { @@ -2409,6 +2414,1489 @@ } ] }, + "generation_rules": { + "Assertions": [ + { + "rule_id": "GEN-ASSERT-0002", + "description": "In addition, a record that something was removed shall be added to the claim in the form of a URI reference to the redacted assertion in the redacted_assertions field of the claim.", + "severity": "shall", + "phase": "assertion", + "spec_section": "6.8. Redaction of Assertions", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "redacted_assertions", + "c2pa.redacted", + "redacted" + ] + }, + { + "rule_id": "GEN-ASSERT-0004", + "description": "When redacting an ingredient assertion that references a C2PA Manifest, the associated manifest shall be removed from the C2PA Manifest Store if no other references to it remain after redacting.", + "severity": "shall", + "phase": "assertion", + "spec_section": "6.8. Redaction of Assertions", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "IngredientMap" + ] + }, + { + "rule_id": "GEN-ASSERT-0001", + "description": "Claim generators shall not insert data into deprecated assertion fields when creating assertions.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "6.3. Versioning", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-ASSERT-0005", + "description": "Claim generators shall not redact assertions with a label of c2pa.actions or c2pa.actions.v2 as this assertion type represents essential information in understanding the history of an asset.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "6.8. Redaction of Assertions", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.actions", + "c2pa.actions.v2" + ] + }, + { + "rule_id": "GEN-ASSERT-0006", + "description": "When creating an update manifest, the claim generator shall not redact the hard binding to content assertion that applies to the current asset.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "6.8. Redaction of Assertions", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.actions", + "c2pa.actions.v2" + ] + }, + { + "rule_id": "GEN-ASSERT-0003", + "description": "It is also strongly recommended that the claim generator should add a c2pa.redacted action with a redacted field as described in Section 18.15.4.7, “Parameters”.", + "severity": "should", + "phase": "structural", + "spec_section": "6.8. Redaction of Assertions", + "spec_area": "Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "redacted_assertions", + "c2pa.redacted", + "redacted" + ] + } + ], + "Binding to Content": [ + { + "rule_id": "GEN-BINDIN-0001", + "description": "However, a Claim Generator should, where possible, include asset metadata (i.e., metadata outside a C2PA Manifest such as EXIF or XMP) in the hard binding, in order to protect its integrity throughout the asset’s lifecycle.", + "severity": "should", + "phase": "content", + "spec_section": "9.2.6. Binding Non-C2PA Asset Metadata", + "spec_area": "Binding to Content", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim", + "DataHashMap" + ], + "source_text": "However, a Claim Generator should, where possible, include asset metadata (i.e., metadata outside a C2PA Manifest such as EXIF or XMP) in the hard binding, in order to protect its integrity throughout" + } + ], + "Claims": [ + { + "rule_id": "GEN-CLAIMS-0003", + "description": "Detailed information about the claim generator shall be present as the value of claim_generator_info.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.2.3.1. General", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "claim_generator_info" + ] + }, + { + "rule_id": "GEN-CLAIMS-0005", + "description": "The claim shall contain a created_assertions field and may contain a gathered_assertions field.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "created_assertions", + "gathered_assertions" + ] + }, + { + "rule_id": "GEN-CLAIMS-0006", + "description": "In a standard manifest, the created_assertions field’s value shall include at least one assertion that represents a hard binding.", + "severity": "shall", + "phase": "assertion", + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "created_assertions", + "gathered_assertions" + ] + }, + { + "rule_id": "GEN-CLAIMS-0007", + "description": "If any assertions in ingredient claims are being redacted, their URI references shall be added to list which is the value of the redacted_assertions field.", + "severity": "shall", + "phase": "ingredient", + "spec_section": "10.3.2.1. Adding Assertions and Redactions", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "redacted_assertions" + ] + }, + { + "rule_id": "GEN-CLAIMS-0008", + "description": "If a manifest with the same unique identifier is already present in the C2PA Manifest Store, the two shall be compared.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.3.2.2. Adding Ingredients", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "IngredientMap" + ] + }, + { + "rule_id": "GEN-CLAIMS-0009", + "description": "If they are identical, the new manifest shall be ignored.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.3.2.2. Adding Ingredients", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "IngredientMap" + ] + }, + { + "rule_id": "GEN-CLAIMS-0010", + "description": "If they are different, the new manifest shall be added to the store after changing its unique identifier to a new value as described in Chapter 8, Unique Identifiers.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.3.2.2. Adding Ingredients", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "IngredientMap" + ] + }, + { + "rule_id": "GEN-CLAIMS-0012", + "description": "For both types of manifests, standard and update, the payload field of Sig_structure shall be the serialized CBOR of the claim document, and shall use detached content mode.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "10.3.2.4. Signing a Claim", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "payload", + "Sig_structure" + ] + }, + { + "rule_id": "GEN-CLAIMS-0015", + "description": "A \"v2 payload\" shall be used by claim generators performing a time-stamping operation.", + "severity": "shall", + "phase": "structural", + "spec_section": "10.3.2.5.2. Choosing the Payload", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "signature", + "COSE_Sign1_Tagged" + ] + }, + { + "rule_id": "GEN-CLAIMS-0016", + "description": "All time-stamps shall be obtained as described in RFC 3161 with the following additional requirements:", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "TimeStampMap" + ] + }, + { + "rule_id": "GEN-CLAIMS-0017", + "description": "The MessageImprint of the TimeStampReq structure (RFC 3161, section 2.4.1) shall be computed by creating the ToBeSigned value in RFC 8152, section 4.4, with the following values for elements of Sig_structure:", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "Sig_structure", + "MessageImprint", + "TimeStampReq", + "ToBeSigned" + ], + "source_text": "The MessageImprint of the TimeStampReq structure (RFC 3161, section 2.4.1) shall be computed by creating the ToBeSigned value in RFC 8152, section 4.4, with the following values for elements of Sig_st" + }, + { + "rule_id": "GEN-CLAIMS-0018", + "description": "The context element shall be CounterSignature.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "context", + "CounterSignature" + ] + }, + { + "rule_id": "GEN-CLAIMS-0019", + "description": "The payload element shall be the value described by Section 10.3.2.5.2, “Choosing the Payload”.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "payload" + ] + }, + { + "rule_id": "GEN-CLAIMS-0020", + "description": "The certReq boolean of the TimeStampReq structure shall be asserted in the request to the TSA, to ensure its certificate chain is provided in the response.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.3. Obtaining the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "certReq", + "TimeStampReq" + ] + }, + { + "rule_id": "GEN-CLAIMS-0021", + "description": "If present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst", + "tstContainer", + "val", + "tstTokens", + "TimeStampResp" + ] + }, + { + "rule_id": "GEN-CLAIMS-0022", + "description": "The content of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst", + "tstContainer", + "val", + "tstTokens", + "TimeStampResp" + ] + }, + { + "rule_id": "GEN-CLAIMS-0023", + "description": "v2 time-stamps shall be stored in a COSE unprotected header whose label is the string sigTst2.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ] + }, + { + "rule_id": "GEN-CLAIMS-0024", + "description": "When present, the value of this header shall be a tstContainer defined by Example 2, “CDDL for tstContainer”.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ] + }, + { + "rule_id": "GEN-CLAIMS-0025", + "description": "The value of the timeStampToken field of the TimeStampResp structure received in reply from the TSA shall be stored as the value of the val property of an element of tstTokens.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ] + }, + { + "rule_id": "GEN-CLAIMS-0026", + "description": "It shall be formatted as a DER-encoded RFC 3161 TimeStampToken wrapped in a CBOR byte string.", + "severity": "shall", + "phase": "timestamp", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst2", + "tstContainer", + "timeStampToken", + "val", + "tstTokens", + "TimeStampResp", + "TimeStampToken" + ] + }, + { + "rule_id": "GEN-CLAIMS-0027", + "description": "If no time-stamps are included, then neither header (sigTst nor sigTst2) shall be present in the COSE unprotected header.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "10.3.2.5.4. Storing the time-stamp", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "sigTst", + "sigTst2" + ] + }, + { + "rule_id": "GEN-CLAIMS-0029", + "description": "If credential revocation information is attached in this manner, a trusted time-stamp shall also be obtained after signing, as described in Section 10.3.2.5, “Time-stamps”.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "10.3.2.6. Credential Revocation Information", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "TimeStampMap" + ] + }, + { + "rule_id": "GEN-CLAIMS-0030", + "description": "When creating a standard manifest, its claim shall include one or more content binding assertions in its list of assertions to ensure that the asset is tamper-evident.", + "severity": "shall", + "phase": "content", + "spec_section": "10.4.1. Create content bindings", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest" + ] + }, + { + "rule_id": "GEN-CLAIMS-0031", + "description": "When the size or location (or both) of the manifest in the asset is not known, then the start and length values in the data hash assertion shall both be zero and the size of the pad value should be large enough to accommodate writing in the values during the second pass.", + "severity": "shall", + "phase": "assertion", + "spec_section": "10.4.1. Create content bindings", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "start", + "length", + "pad" + ], + "source_text": "When the size or location (or both) of the manifest in the asset is not known, then the start and length values in the data hash assertion shall both be zero and the size of the pad value should be la" + }, + { + "rule_id": "GEN-CLAIMS-0032", + "description": "The value of the pad key shall consist of all 0x00’s.", + "severity": "shall", + "phase": "content", + "spec_section": "10.4.1. Create content bindings", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "start", + "length", + "pad" + ] + }, + { + "rule_id": "GEN-CLAIMS-0033", + "description": "Claim generators shall ensure that changes to pad data (or any other excluded asset data) cannot change how the asset is interpreted.", + "severity": "shall", + "phase": "content", + "spec_section": "10.4.1. Create content bindings", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-CLAIMS-0034", + "description": "If the serialized COSE_Sign1_Tagged structure exceeds the reserved size of the C2PA Claim Signature box, multiple step processing shall be repeated with a larger padding size chosen in Section 10.4.2, “Create a temporary Claim and Signature”.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "10.4.4. Going back and filling in", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "COSE_Sign1_Tagged" + ], + "source_text": "If the serialized COSE_Sign1_Tagged structure exceeds the reserved size of the C2PA Claim Signature box, multiple step processing shall be repeated with a larger padding size chosen in Section 10.4.2," + }, + { + "rule_id": "GEN-CLAIMS-0001", + "description": "Validators should still accept this label (and associated claim-map), but claim generators shall not produce such a claim.", + "severity": "shall_not", + "phase": "structural", + "spec_section": "10.1. Overview", + "spec_area": "Claims", + "applicability": "both", + "referenced_entities": [ + "c2pa.claim", + "claim-map" + ] + }, + { + "rule_id": "GEN-CLAIMS-0014", + "description": "A claim generator shall not create one, but a validator shall process one if present.", + "severity": "shall_not", + "phase": "structural", + "spec_section": "10.3.2.5.2. Choosing the Payload", + "spec_area": "Claims", + "applicability": "both", + "referenced_entities": [ + "payload", + "Sig_signature" + ] + }, + { + "rule_id": "GEN-CLAIMS-0002", + "description": "However, instead of setting dc:title, claim generators should create a metadata assertion containing the dc:title field.", + "severity": "should", + "phase": "assertion", + "spec_section": "10.2.2. Fields", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-CLAIMS-0011", + "description": "If an ingredient’s manifest is remote, and the claim generator is unable to retrieve the manifest, it should use an error code of manifest.inaccessible to reflect that.", + "severity": "should", + "phase": "ingredient", + "spec_section": "10.3.2.2. Adding Ingredients", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest.inaccessible" + ] + }, + { + "rule_id": "GEN-CLAIMS-0013", + "description": "If possible, the claim generator should use a RFC 3161-compliant Time Stamping Authority (TSA) (RFC 3161) to obtain a trusted time-stamp proving that the signature itself actually existed at a certain date and time and incorporate that into the COSE_Sign1_Tagged structure as a countersignature.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "10.3.2.5.1. Use of RFC 3161", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "COSE_Sign1_Tagged" + ], + "source_text": "If possible, the claim generator should use a RFC 3161-compliant Time Stamping Authority (TSA) (RFC 3161) to obtain a trusted time-stamp proving that the signature itself actually existed at a certain" + }, + { + "rule_id": "GEN-CLAIMS-0028", + "description": "If the signer’s credential supports querying its online credential status, and the credential contains a pointer to a service to provide time-stamped credential status information, the claim generator should query the service, capture the response, and store it in the manner described for credentials in the Trust Model.", + "severity": "should", + "phase": "trust", + "spec_section": "10.3.2.6. Credential Revocation Information", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "TimeStampMap" + ], + "source_text": "If the signer’s credential supports querying its online credential status, and the credential contains a pointer to a service to provide time-stamped credential status information, the claim generator" + }, + { + "rule_id": "GEN-CLAIMS-0036", + "description": "In this case, claim generators should use padding prior to assertion creation to ensure that the file layout need not change once the assertion has been finalized.", + "severity": "should", + "phase": "assertion", + "spec_section": "10.4.4. Going back and filling in", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-CLAIMS-0004", + "description": "A claim generator may desire to provide a graphical representation of itself, referred here as an icon, to a Manifest Consumer that is presenting a user experience.", + "severity": "may", + "phase": "structural", + "spec_section": "10.2.3.2. Generator Info Map", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "icon", + "c2pa.icon" + ] + }, + { + "rule_id": "GEN-CLAIMS-0035", + "description": "As such, the claim generator may no longer be able to change the file layout and/or offsets in a data hash assertion.", + "severity": "may", + "phase": "assertion", + "spec_section": "10.4.4. Going back and filling in", + "spec_area": "Claims", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + } + ], + "Cryptography": [ + { + "rule_id": "GEN-CRYPTO-0002", + "description": "When producing a signature, if the claim generator can also act as a validator, the claim generator should validate that the signing credential is acceptable according to Chapter 14, Trust Model and produce a warning if it is not.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "13.2.5. Signature Validation", + "spec_area": "Cryptography", + "applicability": "both", + "referenced_entities": [ + "claim" + ], + "source_text": "When producing a signature, if the claim generator can also act as a validator, the claim generator should validate that the signing credential is acceptable according to Chapter 14, Trust Model and p" + }, + { + "rule_id": "GEN-CRYPTO-0001", + "description": "A claim generator may also wish to establish a \"claimed time of signing\" by adding an iat protected header, whose value is a NumericDate.", + "severity": "may", + "phase": "cryptographic", + "spec_section": "13.2.4. Adding a claimed time of signing", + "spec_area": "Cryptography", + "applicability": "claim_generator", + "referenced_entities": [ + "iat", + "NumericDate" + ] + }, + { + "rule_id": "GEN-CRYPTO-0003", + "description": "The claim generator may still allow signing with that credential if so desired.", + "severity": "may", + "phase": "cryptographic", + "spec_section": "13.2.5. Signature Validation", + "spec_area": "Cryptography", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + } + ], + "General": [ + { + "rule_id": "GEN-GENERA-0001", + "description": "Claim generators may derive additional per-segment validation values (e.g., anchor-point-based chaining values) from segment hashes and embed them in the Manifest.", + "severity": "may", + "phase": "structural", + "spec_section": "19.3.1. Description", + "spec_area": "General", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + } + ], + "Manifests": [ + { + "rule_id": "GEN-MANIFE-0001", + "description": "Manifest Consumers shall also accept standard C2PA Manifests specified with JUMBF type UUID 63326D64-0011-0010-8000-00AA00389B71 (c2md), but claim generators shall not create manifests with this JUMBF type UUID.", + "severity": "shall_not", + "phase": "structural", + "spec_section": "11.2.2. Standard Manifests", + "spec_area": "Manifests", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "Manifest Consumers shall also accept standard C2PA Manifests specified with JUMBF type UUID 63326D64-0011-0010-8000-00AA00389B71 (c2md), but claim generators shall not create manifests with this JUMBF" + } + ], + "Standard Assertions": [ + { + "rule_id": "GEN-STANDA-0002", + "description": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "severity": "shall", + "phase": "content", + "spec_section": "18.5.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0008", + "description": "Furthermore, the claim generator shall ensure the exclusion range only contains content from C2PA Manifest Store, or asset metadata (e.g., EXIF, IPTC metadata).", + "severity": "shall", + "phase": "content", + "spec_section": "18.7.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0009", + "description": "A claim generator shall validate or sanitize the URIs before use, ensuring that neither . nor .. appear as part of the URI.", + "severity": "shall", + "phase": "structural", + "spec_section": "18.8.3. Fields", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "uri-hashed-data-map", + "uri" + ] + }, + { + "rule_id": "GEN-STANDA-0010", + "description": "If a claim generator will be providing a soft binding for the asset’s content, it shall be described using a soft binding assertion.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.10.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "SoftBindingMap" + ] + }, + { + "rule_id": "GEN-STANDA-0013", + "description": "There shall be at least one actions assertion present in the created_assertions array of the Claim of a standard C2PA Manifest.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "created_assertions" + ] + }, + { + "rule_id": "GEN-STANDA-0014", + "description": "If the asset was created de novo (for example, as a result of performing a File → New operation in a creative tool, capturing a photo or video, or generating the media by a generative AI model), then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shall have a c2pa.created action as its first element.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.created" + ], + "source_text": "If the asset was created de novo (for example, as a result of performing a File → New operation in a creative tool, capturing a photo or video, or generating the media by a generative AI model), then " + }, + { + "rule_id": "GEN-STANDA-0015", + "description": "For all assets, a corresponding digitalSourceType field, with an appropriate value, shall be recorded with the c2pa.created action, to indicate the nature of the asset at its inception.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "digitalSourceType", + "c2pa.created" + ] + }, + { + "rule_id": "GEN-STANDA-0016", + "description": "If the asset is created with no digital content, then the digitalSourceType field shall have the value http://c2pa.org/digitalsourcetype/empty.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "digitalSourceType", + "c2pa.created" + ] + }, + { + "rule_id": "GEN-STANDA-0017", + "description": "If the asset was created by opening an existing asset as a parentOf ingredient for editing, then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shall have a c2pa.opened action as its first element.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "parentOf", + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.opened", + "c2pa.ingredient.v3", + "ingredients", + "parameters" + ], + "source_text": "If the asset was created by opening an existing asset as a parentOf ingredient for editing, then the actions array in the first c2pa.actions assertion in the created_assertions array of the Claim shal" + }, + { + "rule_id": "GEN-STANDA-0018", + "description": "The claim generator shall also add a corresponding c2pa.ingredient.v3 assertion for the ingredient that was opened, and include a hashed-uri reference to it in the value of the ingredients field of its parameters object.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "parentOf", + "actions", + "c2pa.actions", + "created_assertions", + "c2pa.opened", + "c2pa.ingredient.v3", + "ingredients", + "parameters" + ], + "source_text": "The claim generator shall also add a corresponding c2pa.ingredient.v3 assertion for the ingredient that was opened, and include a hashed-uri reference to it in the value of the ingredients field of it" + }, + { + "rule_id": "GEN-STANDA-0019", + "description": "The full set of actions assertions in a C2PA Manifest shall contain no more than one action whose type is either c2pa.created or c2pa.opened.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.15.2. Mandatory presence of at least one actions assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.created", + "c2pa.opened" + ] + }, + { + "rule_id": "GEN-STANDA-0021", + "description": "In the situation where a claim generator is opening an asset strictly for the purposes of recording its c2pa.opened action and then immediately re-saving it without making any other changes, the claim generator shall set allActionsIncluded to true to assert that no other actions were performed on the asset.", + "severity": "shall", + "phase": "structural", + "spec_section": "18.15.3. All actions included", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.opened", + "allActionsIncluded" + ], + "source_text": "In the situation where a claim generator is opening an asset strictly for the purposes of recording its c2pa.opened action and then immediately re-saving it without making any other changes, the claim" + }, + { + "rule_id": "GEN-STANDA-0025", + "description": "When the claim generator provides the optional instanceID field of the ingredient assertion, then the value of the unique identifier shall be determined as specified by Section 8.3, “Identifying Non-C2PA Assets”.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.16.2. Establishing unique identifiers", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "instanceID" + ], + "source_text": "When the claim generator provides the optional instanceID field of the ingredient assertion, then the value of the unique identifier shall be determined as specified by Section 8.3, “Identifying Non-C" + }, + { + "rule_id": "GEN-STANDA-0026", + "description": "When adding an ingredient assertion, a claim generator shall add a c2pa.actions assertion (see Section 18.15, “Actions”), if one does not already exist in the active manifest.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.16.3. Relationship", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.actions", + "actions" + ] + }, + { + "rule_id": "GEN-STANDA-0028", + "description": "It is recommended that a claim generator should provide this field and it shall contain a valid value.", + "severity": "shall", + "phase": "structural", + "spec_section": "18.16.5. Format", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0032", + "description": "To determine whether or not an existing manifest from the ingredient’s C2PA Manifest Store needs to be copied into the asset’s C2PA Manifest Store, the claim generator shall:", + "severity": "shall", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0035", + "description": "The claim generator shall check if any assertions from either manifest were redacted (optionally utilizing the list of redactions compiled in the Performing explicit validation process).", + "severity": "shall", + "phase": "structural", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0037", + "description": "If all redactions were applied against the manifest from the ingredient’s Manifest Store, then the claim generator shall replace the manifest in the asset’s C2PA Manifest Store with the manifest from the ingredient’s C2PA Manifest Store.", + "severity": "shall", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "If all redactions were applied against the manifest from the ingredient’s Manifest Store, then the claim generator shall replace the manifest in the asset’s C2PA Manifest Store with the manifest from " + }, + { + "rule_id": "GEN-STANDA-0038", + "description": "If different redactions were applied against both the C2PA Manifest from the ingredient’s C2PA Manifest Store and the asset’s C2PA Manifest Store, then the claim generator shall redact as many assertions as needed from the existing manifest in the asset’s C2PA Manifest Store to result in a union of the two sets of redactions.", + "severity": "shall", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "If different redactions were applied against both the C2PA Manifest from the ingredient’s C2PA Manifest Store and the asset’s C2PA Manifest Store, then the claim generator shall redact as many asserti" + }, + { + "rule_id": "GEN-STANDA-0039", + "description": "In all other cases, then the claim generator shall copy the manifest from the ingredient’s C2PA Manifest Store, re-label it with an updated URN per the process described in Unique Identifiers, and insert the re-labeled version into the asset’s C2PA Manifest Store.", + "severity": "shall", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "In all other cases, then the claim generator shall copy the manifest from the ingredient’s C2PA Manifest Store, re-label it with an updated URN per the process described in Unique Identifiers, and ins" + }, + { + "rule_id": "GEN-STANDA-0040", + "description": "In addition, when the ingredient assertion references a C2PA Manifest, the claim generator shall also act as a validator, performing validation of the ingredient as described in validation steps.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.16.12.4.1. General", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "validationResults", + "validationStatus" + ] + }, + { + "rule_id": "GEN-STANDA-0041", + "description": "If the Claim Generator does include such a C2PA Manifest, then it shall include a softBindingsMatched field indicating true, and a softBindingAlgorithmsMatched field containing an array of strings (of soft binding algorithm names that were used to discover the ingredient C2PA Manifest).", + "severity": "shall", + "phase": "ingredient", + "spec_section": "18.16.14. Soft Bindings", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "parentOf", + "softBindingsMatched", + "softBindingAlgorithmsMatched", + "alg" + ], + "source_text": "If the Claim Generator does include such a C2PA Manifest, then it shall include a softBindingsMatched field indicating true, and a softBindingAlgorithmsMatched field containing an array of strings (of" + }, + { + "rule_id": "GEN-STANDA-0042", + "description": "Claim generators shall include in this assertion only the specific metadata fields enumerated in Appendix B, Implementation Details for c2pa.metadata.", + "severity": "shall", + "phase": "assertion", + "spec_section": "18.17.3. The c2pa.metadata assertion", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.metadata" + ] + }, + { + "rule_id": "GEN-STANDA-0043", + "description": "As described in Section 14.5.2, “Certificate Revocation”, the claim generator queries the OCSP service indicated by the signing certificate, captures the response, and shall store it the same binary format as used when it is stored as an element of the ocspVals array of the rVals header (see Example 3, “CDDL for rVals”).", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "18.19.3. Requirements", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "certificate-status-map", + "ocspVals", + "rVals" + ], + "source_text": "As described in Section 14.5.2, “Certificate Revocation”, the claim generator queries the OCSP service indicated by the signing certificate, captures the response, and shall store it the same binary f" + }, + { + "rule_id": "GEN-STANDA-0003", + "description": "Claim generators shall not add this field to a data hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "18.5.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "url" + ], + "source_text": "Claim generators shall not add this field to a data hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being " + }, + { + "rule_id": "GEN-STANDA-0004", + "description": "Claim generators shall not add this field to a BMFF hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "18.6.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "url" + ], + "source_text": "Claim generators shall not add this field to a BMFF hash assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being " + }, + { + "rule_id": "GEN-STANDA-0011", + "description": "Claim generators shall not add this field to a soft binding assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content being validated as described in Section 15.10.3, “Assertion Validation”.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "18.10.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "url" + ], + "source_text": "Claim generators shall not add this field to a soft binding assertion, and consumers shall ignore the field when present, except this shall not affect inclusion of the field as part of the content bei" + }, + { + "rule_id": "GEN-STANDA-0012", + "description": "Claim generators shall not add this field to a soft binding assertion, and consumers should ignore the field when present.", + "severity": "shall_not", + "phase": "assertion", + "spec_section": "18.10.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "extent", + "scope", + "region" + ] + }, + { + "rule_id": "GEN-STANDA-0034", + "description": "If the hashes match, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store to the asset’s C2PA Manifest Store.", + "severity": "shall_not", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0036", + "description": "If all redactions were applied against the manifest already present in the asset’s C2PA Manifest Store, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store into the asset’s C2PA Manifest Store.", + "severity": "shall_not", + "phase": "ingredient", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "If all redactions were applied against the manifest already present in the asset’s C2PA Manifest Store, then the claim generator shall not copy the manifest from the ingredient’s C2PA Manifest Store i" + }, + { + "rule_id": "GEN-STANDA-0001", + "description": "In order for a Manifest Consumer to display human-readable information about these keys and values, the claim generator should provide the strings via this localization approach.", + "severity": "should", + "phase": "structural", + "spec_section": "18.3.9.2. Localization Dictionary", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "com.litware" + ] + }, + { + "rule_id": "GEN-STANDA-0005", + "description": "A claim generator should use a general box hash assertion to verify the integrity, with a hard binding (i.e., cryptographic hash), of assets whose formats use a non-BMFF-based box format such as JPEG, PNG, or GIF.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "18.7.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "DataHashMap" + ], + "source_text": "A claim generator should use a general box hash assertion to verify the integrity, with a hard binding (i.e., cryptographic hash), of assets whose formats use a non-BMFF-based box format such as JPEG," + }, + { + "rule_id": "GEN-STANDA-0006", + "description": "For boxes that have an excluded field with a value of true, the claim generator should include an accurate hash for compatibility with older validators that do not recognize the excluded field.", + "severity": "should", + "phase": "content", + "spec_section": "18.7.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "excluded", + "false", + "true" + ] + }, + { + "rule_id": "GEN-STANDA-0007", + "description": "If the claim generator is not concerned with backwards compatibility, it should write the binary string 00 (a single byte with a value of 0) for the hash.", + "severity": "should", + "phase": "content", + "spec_section": "18.7.1. Description", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "excluded", + "false", + "true" + ] + }, + { + "rule_id": "GEN-STANDA-0024", + "description": "If successful, the claim generator should add the located C2PA Manifest as the value of the activeManifest field in the ingredient assertion.", + "severity": "should", + "phase": "assertion", + "spec_section": "18.15.10. Soft Binding Lookup", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.opened", + "c2pa.placed", + "activeManifest", + "softBindingsMatched", + "softBindingAlgorithmsMatched" + ] + }, + { + "rule_id": "GEN-STANDA-0030", + "description": "Claim generators should take the size of this field into consideration when choosing whether to embed data.", + "severity": "should", + "phase": "structural", + "spec_section": "18.16.8.1. Standard Usage", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0031", + "description": "The claim generator should also copy into the asset’s C2PA Manifest Store any additional C2PA Manifests that were not validated, as well as any additional JUMBF boxes and superboxes appearing in the C2PA Manifest Store that are not recognized as C2PA Manifests.", + "severity": "should", + "phase": "structural", + "spec_section": "18.16.11.1. General", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ], + "source_text": "The claim generator should also copy into the asset’s C2PA Manifest Store any additional C2PA Manifests that were not validated, as well as any additional JUMBF boxes and superboxes appearing in the C" + }, + { + "rule_id": "GEN-STANDA-0027", + "description": "However, claim generators should not set this value if the information is available in the ingredient’s active C2PA Manifest.", + "severity": "should_not", + "phase": "ingredient", + "spec_section": "18.16.4. Title", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + }, + { + "rule_id": "GEN-STANDA-0029", + "description": "If present, the content of this field should be produced by the claim generator, and should not contain user-provided information.", + "severity": "should_not", + "phase": "content", + "spec_section": "18.16.7. Description field", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "description" + ] + }, + { + "rule_id": "GEN-STANDA-0020", + "description": "If allActionsIncluded has a value of false, then the claim generator is stating that additional, unrecorded actions may have been performed.", + "severity": "may", + "phase": "structural", + "spec_section": "18.15.3. All actions included", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "actions-map-v2", + "allActionsIncluded" + ] + }, + { + "rule_id": "GEN-STANDA-0022", + "description": "A claim generator that performs the same action over and over, with the same parameters & settings, may use the multipleInstances field to indicate that the action was performed multiple times or not.", + "severity": "may", + "phase": "structural", + "spec_section": "18.15.4.7. Parameters", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "multipleInstances" + ] + }, + { + "rule_id": "GEN-STANDA-0023", + "description": "When performing either a c2pa.opened or c2pa.placed action with an asset that does not contain a C2PA Manifest, the claim generator may use a soft binding lookup to find the C2PA Manifest for that asset.", + "severity": "may", + "phase": "content", + "spec_section": "18.15.10. Soft Binding Lookup", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "c2pa.opened", + "c2pa.placed", + "activeManifest", + "softBindingsMatched", + "softBindingAlgorithmsMatched" + ], + "source_text": "When performing either a c2pa.opened or c2pa.placed action with an asset that does not contain a C2PA Manifest, the claim generator may use a soft binding lookup to find the C2PA Manifest for that ass" + }, + { + "rule_id": "GEN-STANDA-0033", + "description": "In case of validation failures, the claim generator may skip the rest of these steps if directed to do so (for example, via user input or via configuration).", + "severity": "may", + "phase": "structural", + "spec_section": "18.16.12.1. Determining the need", + "spec_area": "Standard Assertions", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + } + ], + "Trust Model": [ + { + "rule_id": "GEN-TRUST_-0001", + "description": "Therefore, when creating the x5chain header as part of signing, the claim generator shall include the signer’s certificate and all intermediate certificate authorities in the header’s value.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "14.5. X.509 Certificates", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "x5chain" + ] + }, + { + "rule_id": "GEN-TRUST_-0004", + "description": "Claim generators shall place this header only in the protected header bucket of the COSE signature as required above.", + "severity": "shall", + "phase": "cryptographic", + "spec_section": "14.5. X.509 Certificates", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "x5chain" + ] + }, + { + "rule_id": "GEN-TRUST_-0006", + "description": "The claim generator shall not use Certificate Revocation Lists (CRLs, RFC 5280).", + "severity": "shall_not", + "phase": "structural", + "spec_section": "14.5.2. Certificate Revocation", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "certificateInfo" + ] + }, + { + "rule_id": "GEN-TRUST_-0002", + "description": "Claim generators should use only the integer 33 as the label when inserting this header into a COSE signature.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "14.5. X.509 Certificates", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "x5chain" + ] + }, + { + "rule_id": "GEN-TRUST_-0003", + "description": "Claim generators may continue to write the string label x5chain but this behaviour is now deprecated and claim generators should be updated to use the integer label only.", + "severity": "should", + "phase": "structural", + "spec_section": "14.5. X.509 Certificates", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "x5chain" + ] + }, + { + "rule_id": "GEN-TRUST_-0005", + "description": "A claim generator should use the Online Certificate Status Protocol (OCSP, RFC 6960) and OCSP stapling (as originally conceptualized in RFC 6066, Section 8, but implemented as described in this clause) to implement revocation.", + "severity": "should", + "phase": "structural", + "spec_section": "14.5.2. Certificate Revocation", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "claim", + "certificateInfo" + ], + "source_text": "A claim generator should use the Online Certificate Status Protocol (OCSP, RFC 6960) and OCSP stapling (as originally conceptualized in RFC 6066, Section 8, but implemented as described in this clause" + }, + { + "rule_id": "GEN-TRUST_-0007", + "description": "Before signing a claim, if a signer’s certificate has the AIA extension, a claim generator should query the OCSP service indicated therein, capture the response, and store it in an element of the ocspVals array of the rVals header.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "14.5.2. Certificate Revocation", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "ocspVals", + "rVals" + ], + "source_text": "Before signing a claim, if a signer’s certificate has the AIA extension, a claim generator should query the OCSP service indicated therein, capture the response, and store it in an element of the ocsp" + }, + { + "rule_id": "GEN-TRUST_-0008", + "description": "The claim generator should do the same for any intermediate CA certificates it includes with the claim signature.", + "severity": "should", + "phase": "cryptographic", + "spec_section": "14.5.2. Certificate Revocation", + "spec_area": "Trust Model", + "applicability": "claim_generator", + "referenced_entities": [ + "ocspVals", + "rVals" + ] + } + ], + "Unique Identifiers": [ + { + "rule_id": "GEN-UNIQUE-0001", + "description": "When present, the \"Claim Generator identifier\" string shall consist of no more than 32 characters from the ASCII range (as per RFC 20), but which are not Control Characters (RFC 20, 5.2) or Graphic Characters (RFC 20, 5.3).", + "severity": "shall", + "phase": "structural", + "spec_section": "8.1. Uniquely Identifying C2PA Manifests and Assets", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ], + "source_text": "When present, the \"Claim Generator identifier\" string shall consist of no more than 32 characters from the ASCII range (as per RFC 20), but which are not Control Characters (RFC 20, 5.2) or Graphic Ch" + }, + { + "rule_id": "GEN-UNIQUE-0002", + "description": "In addition, when a \"Version and Reason\" string is present, a \"Claim Generator identifier\" string shall also be present but it may be empty.", + "severity": "shall", + "phase": "structural", + "spec_section": "8.1. Uniquely Identifying C2PA Manifests and Assets", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-UNIQUE-0003", + "description": "In such a case, the modified version of the ingredient manifest needs to be copied into the asset’s C2PA Manifest Store, and shall be re-labeled.", + "severity": "shall", + "phase": "ingredient", + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim", + "IngredientMap" + ] + }, + { + "rule_id": "GEN-UNIQUE-0004", + "description": "If the current URN does not contain a \"Claim Generator identifier string\", then the claim generator shall append a :.", + "severity": "shall", + "phase": "structural", + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ] + }, + { + "rule_id": "GEN-UNIQUE-0005", + "description": "In all cases, the claim generator shall append a : to the URN followed by a monotonically increasing integer, starting with 1, followed by an underscore (_) and then an integer from the list below representing the reason for the re-labeling.", + "severity": "shall", + "phase": "structural", + "spec_section": "8.2. Versioning Manifests Due to Conflicts", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "claim" + ], + "source_text": "In all cases, the claim generator shall append a : to the URN followed by a monotonically increasing integer, starting with 1, followed by an underscore (_) and then an integer from the list below rep" + }, + { + "rule_id": "GEN-UNIQUE-0006", + "description": "When working with assets that do not contain a C2PA Manifest and do not contain embedded XMP, the claim generator may use any method of its choosing to provide it with a unique identifier.", + "severity": "may", + "phase": "structural", + "spec_section": "8.3. Identifying Non-C2PA Assets", + "spec_area": "Unique Identifiers", + "applicability": "claim_generator", + "referenced_entities": [ + "manifest", + "claim" + ] + } + ], + "Versioning": [ + { + "rule_id": "GEN-VERSIO-0001", + "description": "In this specification, when a construct is marked as deprecated, that means that a claim generator shall not write that construct (or value), but that a validator should read it.", + "severity": "shall_not", + "phase": "structural", + "spec_section": "5.1. Compatibility", + "spec_area": "Versioning", + "applicability": "both", + "referenced_entities": [ + "claim", + "TimeStampMap" + ] + }, + { + "rule_id": "GEN-VERSIO-0002", + "description": "To facilitate testing and diagnosing interoperability issues between claim generators and validators, a claim generator should declare which version of the specification it is using to generate the C2PA Manifest by providing a specVersion key in the claim_generator_info field of the claim.", + "severity": "should", + "phase": "structural", + "spec_section": "5.1. Compatibility", + "spec_area": "Versioning", + "applicability": "both", + "referenced_entities": [ + "specVersion", + "claim_generator_info" + ], + "source_text": "To facilitate testing and diagnosing interoperability issues between claim generators and validators, a claim generator should declare which version of the specification it is using to generate the C2" + }, + { + "rule_id": "GEN-VERSIO-0003", + "description": "When a claim generator is performing ingredient validation, it should add a specVersion key to the validation-results-map to declare what version of the specification was used as the basis for the validation procedure.", + "severity": "should", + "phase": "ingredient", + "spec_section": "5.1. Compatibility", + "spec_area": "Versioning", + "applicability": "claim_generator", + "referenced_entities": [ + "specVersion", + "validation-results-map" + ], + "source_text": "When a claim generator is performing ingredient validation, it should add a specVersion key to the validation-results-map to declare what version of the specification was used as the basis for the val" + } + ] + }, "status_codes": { "success": [ {