From 3a29727dc9aeb1188950270fe13f91b802be8915 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:40:18 +0500 Subject: [PATCH 01/69] ci(workflows): try to fix poetry build error --- .github/workflows/publish-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a943798c..c745a51f 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -88,14 +88,14 @@ jobs: - name: Semantic Version Release id: release - uses: python-semantic-release/python-semantic-release@v10.4.1 + uses: python-semantic-release/python-semantic-release@v8.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" - name: Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v10.4.1 + uses: python-semantic-release/publish-action@v8.3.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From 0cce39720dba5a2290533110d710b6e2ed68a604 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:48:16 +0500 Subject: [PATCH 02/69] ci(workflows): revert publish release action version --- .github/workflows/publish-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index c745a51f..17e4f256 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -95,7 +95,7 @@ jobs: git_committer_email: "actions@users.noreply.github.com" - name: Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v8.3.0 + uses: python-semantic-release/publish-action@10.4.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From cedcec0f029de36ebd79e595584333dd0aa7d7ce Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:56:42 +0500 Subject: [PATCH 03/69] ci(workflows): fix typo in version num --- .github/workflows/publish-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 17e4f256..dba563ff 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -95,7 +95,7 @@ jobs: git_committer_email: "actions@users.noreply.github.com" - name: Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@10.4.1 + uses: python-semantic-release/publish-action@v10.4.1 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} From a786643aa3f27f245b9536b5dd4ca9bf0f4eccf7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 20 Oct 2025 12:01:45 +0000 Subject: [PATCH 04/69] 0.1.0-rc.1 Automatically generated by python-semantic-release skip-checks: true --- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 312ad11a..7f8ac3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ +## v0.1.0-rc.1 (2025-10-20) + +### Documentation + +- **readme**: Add note on c2pie version + ([`3726a77`](https://github.com/TourmalineCore/c2pie/commit/3726a77c113c4dff2b344897af5f8b68521994dd)) + +- **readme**: Change latest version reference + ([`6409e05`](https://github.com/TourmalineCore/c2pie/commit/6409e05957922b98883326c6d8c78af0f8b1346e)) + +- **readme**: Remove note of package version in descr, change badge label + ([`a6efaf9`](https://github.com/TourmalineCore/c2pie/commit/a6efaf9f35ce21db4b041d97fd3fca8ad6d8445b)) + +### Features + +- **example-app**: Add ability to set c2pie version that should be used in example app + ([`c9c95eb`](https://github.com/TourmalineCore/c2pie/commit/c9c95eb4c50f756735d9c0c71ea65d673363da04)) + + ## v0.1.0-alpha.6 (2025-10-17) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index afe0e269..cd7de977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-alpha.6" +version = "0.1.0-rc.1" [[tool.poetry.source]] name = "PyPI" From a70b1947a67668e2db031f5e71ac7adac20723a7 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:11:35 +0500 Subject: [PATCH 05/69] ci(workflows): fix triggers --- .github/workflows/lint-and-test.yml | 5 +++-- .github/workflows/publish-package.yml | 2 +- .github/workflows/publish-release.yml | 2 -- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 85bba42f..9eb17929 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -2,8 +2,9 @@ name: Linting and Testing on: push: - branches: - - '**' + branches-ignore: + - master + - release/** paths-ignore: - '**.md' - 'docs/**' diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 869420af..3ef75772 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -2,7 +2,7 @@ name: Package Publishing on: release: - types: [created] + types: [created, published] permissions: contents: write diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index dba563ff..84092809 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -90,7 +90,6 @@ jobs: id: release uses: python-semantic-release/python-semantic-release@v8.3.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" @@ -98,5 +97,4 @@ jobs: uses: python-semantic-release/publish-action@v10.4.1 if: steps.release.outputs.released == 'true' with: - github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} \ No newline at end of file From 8449489f50f123edb3987ba1ce17513fd1cf8f4b Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:24:39 +0500 Subject: [PATCH 06/69] feat(main): add version option to c2pie command --- .github/workflows/publish-release.yml | 11 ++--------- README.md | 2 +- c2pie/main.py | 3 +++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 84092809..01e8d533 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -4,20 +4,16 @@ on: push: branches: - master - - release/** - create: - branches: - - release/** + - 'release/*' permissions: - contents: read + contents: write jobs: linting-and-testing: name: Linting and Testing uses: ./.github/workflows/lint-and-test.yml permissions: - contents: write packages: read release: @@ -28,9 +24,6 @@ jobs: group: ${{ github.workflow }}-release-${{ github.ref_name }} cancel-in-progress: false - permissions: - contents: write - steps: - name: Checkout Repository on Release Branch uses: actions/checkout@v4 diff --git a/README.md b/README.md index e864d6e9..27dce349 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![CI](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml/badge.svg)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![c2pa](https://img.shields.io/badge/c2pa-v1.4-seagreen.svg)](https://c2pa.org/) -[![coverage](https://img.shields.io/badge/coverage-92%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +[![coverage](https://img.shields.io/badge/coverage-91%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![latest](https://img.shields.io/pypi/v/c2pie?label=latest&colorB=fc8021)](https://pypi.org/project/c2pie/)
diff --git a/c2pie/main.py b/c2pie/main.py index 608c11ea..1446ab3f 100644 --- a/c2pie/main.py +++ b/c2pie/main.py @@ -1,4 +1,5 @@ import argparse +from importlib.metadata import version from pathlib import Path from c2pie.signing import sign_file @@ -15,6 +16,8 @@ def parse_arguments() -> argparse.Namespace: f"supported extensions are: {supported_extensions}.", ) + global_parser.add_argument("-V", "--version", action="version", version=f"c2pie {version('c2pie')}") + subparsers = global_parser.add_subparsers(title="subcommands", help="commands") sign_parser = subparsers.add_parser("sign", help="embed c2pa signature into a file") From 05da8f7a33fe400c84230a37e24d22656e04c35e Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:27:00 +0500 Subject: [PATCH 07/69] ci(workflows): fix permissions for linting and testing job --- .github/workflows/publish-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 01e8d533..ecf48353 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -15,6 +15,7 @@ jobs: uses: ./.github/workflows/lint-and-test.yml permissions: packages: read + contents: write release: name: Create and Publish Release From f5ecd25ca9fcf430c84184a044faf45231f6ebfa Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 21 Oct 2025 04:32:51 +0000 Subject: [PATCH 08/69] 0.1.0-rc.2 Automatically generated by python-semantic-release skip-checks: true --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f8ac3b7..baccaf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ +## v0.1.0-rc.2 (2025-10-21) + +### Features + +- **main**: Add version option to c2pie command + ([`8449489`](https://github.com/TourmalineCore/c2pie/commit/8449489f50f123edb3987ba1ce17513fd1cf8f4b)) + + ## v0.1.0-rc.1 (2025-10-20) ### Documentation diff --git a/pyproject.toml b/pyproject.toml index cd7de977..698fc6b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" [[tool.poetry.source]] name = "PyPI" From 2340f14d1be30dc77e91e687512e23e30b3b7a8d Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:42:16 +0500 Subject: [PATCH 09/69] ci(workflows): use gh_token again --- .github/workflows/publish-release.yml | 2 ++ CHANGELOG.md | 8 -------- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index ecf48353..6936630f 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -84,6 +84,7 @@ jobs: id: release uses: python-semantic-release/python-semantic-release@v8.3.0 with: + github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" git_committer_email: "actions@users.noreply.github.com" @@ -91,4 +92,5 @@ jobs: uses: python-semantic-release/publish-action@v10.4.1 if: steps.release.outputs.released == 'true' with: + github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index baccaf54..7f8ac3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,6 @@ -## v0.1.0-rc.2 (2025-10-21) - -### Features - -- **main**: Add version option to c2pie command - ([`8449489`](https://github.com/TourmalineCore/c2pie/commit/8449489f50f123edb3987ba1ce17513fd1cf8f4b)) - - ## v0.1.0-rc.1 (2025-10-20) ### Documentation diff --git a/pyproject.toml b/pyproject.toml index 698fc6b2..cd7de977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-rc.2" +version = "0.1.0-rc.1" [[tool.poetry.source]] name = "PyPI" From fdc800bc8724748d1164ac606d098b01a297d028 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 21 Oct 2025 04:47:27 +0000 Subject: [PATCH 10/69] 0.1.0-rc.2 Automatically generated by python-semantic-release skip-checks: true --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f8ac3b7..baccaf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ +## v0.1.0-rc.2 (2025-10-21) + +### Features + +- **main**: Add version option to c2pie command + ([`8449489`](https://github.com/TourmalineCore/c2pie/commit/8449489f50f123edb3987ba1ce17513fd1cf8f4b)) + + ## v0.1.0-rc.1 (2025-10-20) ### Documentation diff --git a/pyproject.toml b/pyproject.toml index cd7de977..698fc6b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" [[tool.poetry.source]] name = "PyPI" From caeb87f9715f6a48966831cdd8bd009f966ae2ee Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:46:01 +0500 Subject: [PATCH 11/69] chore(tests): rename test credentials files --- .env-example | 4 ++-- tests/credentials/{ps256.pub => certificate-chain.pub} | 0 tests/credentials/{ps256.pem => private-key.pem} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/credentials/{ps256.pub => certificate-chain.pub} (100%) rename tests/credentials/{ps256.pem => private-key.pem} (100%) diff --git a/.env-example b/.env-example index 132e8814..4811e686 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,4 @@ -C2PIE_KEY_FILEPATH=tests/credentials/ps256.pem -C2PIE_CERT_FILEPATH=tests/credentials/ps256.pub +C2PIE_KEY_FILEPATH=tests/credentials/private-key.pem +C2PIE_CERT_FILEPATH=tests/credentials/certificate-chain.pub TEST_PDF_PATH=tests/test_files/test_doc.pdf TEST_IMAGE_PATH=tests/test_files/test_image.jpg \ No newline at end of file diff --git a/tests/credentials/ps256.pub b/tests/credentials/certificate-chain.pub similarity index 100% rename from tests/credentials/ps256.pub rename to tests/credentials/certificate-chain.pub diff --git a/tests/credentials/ps256.pem b/tests/credentials/private-key.pem similarity index 100% rename from tests/credentials/ps256.pem rename to tests/credentials/private-key.pem From 2d74b9ddde6b952c854e5fac40751ff141ab6ad8 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:51:16 +0500 Subject: [PATCH 12/69] docs(readme): swap sections' places --- README.md | 267 +++++++++++++++++++++++++++--------------------------- 1 file changed, 135 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 27dce349..7e8822bd 100644 --- a/README.md +++ b/README.md @@ -63,81 +63,15 @@ For more detailed feature specification, please look at the [Features](#-feature # 🥧 Quick start -## Running example apps with Docker Compose - -For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. - ->[!IMPORTANT] -> Docker is essential for running example apps. - -Follow the steps: - -1. Clone the c2pie repository. - -2. Go to `example_app` directory: - ```bash - cd example_app - ``` - ->[!NOTE] ->By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. - -3. To test signing a JPG file, run: - ```bash - docker compose up c2pie-test-signing-jpg - ``` - - To test signing a PDF file, run: - ```bash - docker compose up c2pie-test-signing-pdf - ``` - - After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: - - ```bash - Successfully signed the file test_files/test_image.jpg! - The result was saved to test_files/signed_test_image.jpg. - c2patool_validation_results: - { - "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", - "manifests": { - "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9": { - "claim_generator": "c2pie", - ................ - }, - "validation_results": { - "activeManifest": { - "success": [ - { - "code": "claimSignature.insideValidity", - "url": "self#jumbf=/c2pa/urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9/c2pa.signature", - "explanation": "claim signature valid" - }, - ................ - }, - "validation_state": "Valid" - } - ``` - -You can also set up a Jupyter Lab environment and test c2pie there by running: -```bash -docker compose up c2pie-notebooks -``` - -After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. - -The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. - -
- -## Running from your own environment +## Running from your local environment using globally installed Python ### Prerequisites 1) Python environment. Currently supported Python versions: 3.9.2 - 3.14.0. -2) Private key and certificate chain pair. You can go to [Certificates](#-certificates) for instructions on how to generate one. - The repo contains pre-generated mock credentials in `tests/credentials`. You can use them for a quick start. +2) Private key and certificate chain pair. The repo contains pre-generated mock credentials in `tests/credentials`. You can either use them for a quick start.You can go to [Certificates](#-certificates) for instructions on how to generate one. + + 3) Key and certificate filepaths exported into the current environment with: ```bash @@ -151,7 +85,6 @@ The existing `notebooks` directory already contains an example notebook with com pip install c2pie ``` - ### Usage #### Command Line Interface @@ -201,6 +134,22 @@ The result was saved to tests/test_files/signed_test_doc.pdf. ### Validation +#### C2PA Verify Tool + +You can verify signed files using [Verify tool](https://contentcredentials.org/verify). + +Simply upload the file you'd like to verify. + +>[!IMPORTANT] +> Files embedded with self-signed certificates (like the ones this repository contains) **won't be verified**. +> +> You'll get the following message: +>``` +>The Content Credential issuer couldn’t be recognized. This file may not come from where it claims to. +>``` +> +>Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. + #### c2patool Output files can be validated with: @@ -233,21 +182,126 @@ c2patool_validation_results: } ``` -#### C2PA Verify Tool +
-You can also verify signed files using [Verify tool](https://contentcredentials.org/verify). +## Running example apps with Docker Compose -Simply upload the file you'd like to verify. +For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. >[!IMPORTANT] -> Files embedded with self-signed certificates (like the ones this repository contains) **won't be verified**. -> -> You'll get the following message: ->``` ->The Content Credential issuer couldn’t be recognized. This file may not come from where it claims to. ->``` +> Docker is essential for running example apps. + +Follow the steps: + +1. Clone the c2pie repository. + +2. Go to `example_app` directory: + ```bash + cd example_app + ``` + +>[!NOTE] +>By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. + +3. To test signing a JPG file, run: + ```bash + docker compose up c2pie-test-signing-jpg + ``` + + To test signing a PDF file, run: + ```bash + docker compose up c2pie-test-signing-pdf + ``` + + After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: + + ```bash + Successfully signed the file test_files/test_image.jpg! + The result was saved to test_files/signed_test_image.jpg. + c2patool_validation_results: + { + "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", + "manifests": { + "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9": { + "claim_generator": "c2pie", + ................ + }, + "validation_results": { + "activeManifest": { + "success": [ + { + "code": "claimSignature.insideValidity", + "url": "self#jumbf=/c2pa/urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9/c2pa.signature", + "explanation": "claim signature valid" + }, + ................ + }, + "validation_state": "Valid" + } + ``` + +You can also set up a Jupyter Lab environment and test c2pie there by running: +```bash +docker compose up c2pie-notebooks +``` + +After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. + +The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. + +
+ +# 🥧 Certificates + +Example certificate chain and key file are located in `tests/credentials`. + +>[!WARNING] +>This repository's credentials are suitable for development only! + +## Generating test credentials + +You can generate your own private key and certificate chain pair for testing the package by following these steps: + +1. Generate a private key: + ```bash + openssl genrsa -out private-key.pem 2048 + ``` + +2. Generate a Certificate Signing Request (CSR): + ```bash + openssl req -new \ + -key private-key.pem \ + -out csr.pem + ``` + + printf "RU\ntest\ntest\ntest\ntest\ntest\ntest\ntest\ntest\n"| openssl req -new -key private-key.pem -out csr.pem +3. Generate a Self-Signed Certificate: + ```bash + openssl x509 -req -days 365 \ + -in csr.pem \ + -signkey private-key.pem \ + -out certificate-chain.pem + ``` + +>[!IMPORTANT] +> Remember to update environment variables to use your newly generated credentials. > ->Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. + +>[!NOTE] +> You can change certificate's validity period with --days option at the last step. +> +>Certificate Signing Request file (*csr.pem*) can be deleted after the certificate has been generated. + + +## Getting credentials for production + +🔸 Use a real document‑signing certificate (RSA‑PSS or ECDSA per C2PA); + +🔸 Provide a leaf + intermediates bundle (no root); + +🔸 Configure trust anchors/allow‑lists in your validator environment. + +For detailed information on signing and certificates please explore the [corresponding section in the Content Authenticity Initiative (CAI) documentation](https://opensource.contentauthenticity.org/docs/signing/).
@@ -372,57 +426,6 @@ The library takes care of iterative sizing, so the `c2pa.hash.data` matches exac
-# 🥧 Certificates - -Example certificate and key are located in `tests/credentials`. - ->[!WARNING] ->This repository's credentials are suitable for development only! - -## Generating your own mock credentials - -You can generate your own mock credentials for testing and developing the package follow these steps: - -1. Generate a private key: - ```bash - openssl genrsa -out credentials/.pem 2048 - ``` - -2. Generate a Certificate Signing Request (CSR): - ```bash - openssl req -new \ - -key credentials/.pem \ - -out csr.pem - ``` - -3. Generate a Self-Signed Certificate: - ```bash - openssl x509 -req -days 365 \ - -in csr.pem \ - -signkey credentials/.pem \ - -out credentials/.pem - ``` ->[!IMPORTANT] -> Remember to update environment variables to use your newly generated credentials. - ->[!NOTE] -> You can change certificate's validity period with --days option at the last step. -> ->Certificate Signing Request file (*csr.pem*) can be deleted after the certificate has been generated. - - -## Getting credentials for production - -🔸 Use a real document‑signing certificate (RSA‑PSS or ECDSA per C2PA); - -🔸 Provide a leaf + intermediates bundle (no root); - -🔸 Configure trust anchors/allow‑lists in your validator environment. - -For detailed information on signing and certificates please explore the [corresponding section in the Content Authenticity Initiative (CAI) documentation](https://opensource.contentauthenticity.org/docs/signing/). - -
- # 🥧 Relevant links ∗ [CAI documentation](https://opensource.contentauthenticity.org/docs) From 58c2bcad5760846154ee115d82fcb247215dfde5 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:34:00 +0500 Subject: [PATCH 13/69] config: rename private key and certificate variables --- .env-example | 4 ++-- .github/workflows/lint-and-test.yml | 4 ++-- README.md | 17 ++++++++--------- c2pie/signing.py | 4 ++-- example_app/.example-app-env | 4 ++-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.env-example b/.env-example index 4811e686..7b2c8067 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,4 @@ -C2PIE_KEY_FILEPATH=tests/credentials/private-key.pem -C2PIE_CERT_FILEPATH=tests/credentials/certificate-chain.pub +C2PIE_PRIVATE_KEY_FILE=tests/credentials/private-key.pem +C2PIE_CERTIFICATE_CHAIN_FILE=tests/credentials/certificate-chain.pub TEST_PDF_PATH=tests/test_files/test_doc.pdf TEST_IMAGE_PATH=tests/test_files/test_image.jpg \ No newline at end of file diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 9eb17929..d8130082 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -176,8 +176,8 @@ jobs: --cov=c2pie \ -v env: - C2PIE_KEY_FILEPATH: ${{ vars.C2PIE_KEY_FILEPATH }} - C2PIE_CERT_FILEPATH: ${{ vars.C2PIE_CERT_FILEPATH }} + C2PIE_PRIVATE_KEY_FILE: ${{ vars.C2PIE_PRIVATE_KEY_FILE }} + C2PIE_CERTIFICATE_CHAIN_FILE: ${{ vars.C2PIE_CERTIFICATE_CHAIN_FILE }} - name: Upload coverage artifact if: (matrix.python-version == '3.12') && (matrix.os == 'ubuntu-latest') diff --git a/README.md b/README.md index 7e8822bd..3d99f7d4 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,18 @@ For more detailed feature specification, please look at the [Features](#-feature ## Table of Contents + [🥧 Quick start](#-quick-start) - + [Running example apps with Docker Compose](#running-example-apps-with-docker-compose) - + [Running from your own environment](#running-from-your-own-environment) + + [Running from your local environment using globally installed Python](#running-from-your-local-environment-using-globally-installed-python) + [Prerequisites](#prerequisites) + [Usage](#usage) + [Command Line Interface](#command-line-interface) + [Code](#code) + [Validation](#validation) - + [c2patool](#c2patool) + [C2PA Verify Tool](#c2pa-verify-tool) + + [c2patool](#c2patool) + + [Running example apps with Docker Compose](#running-example-apps-with-docker-compose) ++ [🥧 Certificates](#-certificates) + + [Generating test credentials](#generating-test-credentials) + + [Getting credentials for production](#getting-credentials-for-production) + [🥧 For developers](#-for-developers) + [First steps](#first-steps) + [Using Dev Containers](#using-dev-containers) @@ -52,9 +55,6 @@ For more detailed feature specification, please look at the [Features](#-feature + [🥧 Features](#-features) + [Workflow of test applications](#workflow-of-test-applications) + [Notes for PDF vs JPG/JPEG](#notes-for-pdf-vs-jpgjpeg) -+ [🥧 Certificates](#-certificates) - + [Generating your own mock credentials](#generating-your-own-mock-credentials) - + [Getting credentials for production](#getting-credentials-for-production) + [🥧 Relevant links](#-relevant-links) + [🥧 Contributing](#-contributing) + [🥧 License](#-license) @@ -75,8 +75,8 @@ For more detailed feature specification, please look at the [Features](#-feature 3) Key and certificate filepaths exported into the current environment with: ```bash - export C2PIE_KEY_FILEPATH= - export C2PIE_CERT_FILEPATH= + export C2PIE_PRIVATE_KEY_FILE= + export C2PIE_CERTIFICATE_CHAIN_FILE=certificate-chain.pub ``` 4) c2pie package installed in your current environment: @@ -274,7 +274,6 @@ You can generate your own private key and certificate chain pair for testing the -out csr.pem ``` - printf "RU\ntest\ntest\ntest\ntest\ntest\ntest\ntest\ntest\n"| openssl req -new -key private-key.pem -out csr.pem 3. Generate a Self-Signed Certificate: ```bash openssl x509 -req -days 365 \ diff --git a/c2pie/signing.py b/c2pie/signing.py index de5da794..5e04af07 100644 --- a/c2pie/signing.py +++ b/c2pie/signing.py @@ -93,11 +93,11 @@ def _load_certificates_and_key( key_path: str | None, certificates_path: str | None, ) -> tuple[bytes, bytes]: - key_path = key_path or os.getenv("C2PIE_KEY_FILEPATH") + key_path = key_path or os.getenv("C2PIE_PRIVATE_KEY_FILE") if not key_path: raise ValueError("Key filepath variable has not been set. Cannot sign the provided file.") - certificates_path = certificates_path or os.getenv("C2PIE_CERT_FILEPATH") + certificates_path = certificates_path or os.getenv("C2PIE_CERTIFICATE_CHAIN_FILE") if not certificates_path: raise ValueError("Certificate filepath variable has not been set. Cannot sign the provided file.") diff --git a/example_app/.example-app-env b/example_app/.example-app-env index f1be52ae..3ca5dd91 100644 --- a/example_app/.example-app-env +++ b/example_app/.example-app-env @@ -1,3 +1,3 @@ -C2PIE_KEY_FILEPATH=/example_app/credentials/ps256.pem -C2PIE_CERT_FILEPATH=/example_app/credentials/ps256.pub +C2PIE_PRIVATE_KEY_FILE=/example_app/credentials/ps256.pem +C2PIE_CERTIFICATE_CHAIN_FILE=/example_app/credentials/ps256.pub C2PIE_PACKAGE_VERSION=latest \ No newline at end of file From 3010b80b51cfe256c131733485d41e1fcae3ffd9 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:39:48 +0500 Subject: [PATCH 14/69] config: fix remaining redundant variables paths and names --- example_app/.example-app-env | 4 ++-- example_app/credentials/{ps256.pub => certificate-chain.pub} | 0 example_app/credentials/{ps256.pem => private-key.pem} | 0 tests/c2pa/claim_signature_test.py | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename example_app/credentials/{ps256.pub => certificate-chain.pub} (100%) rename example_app/credentials/{ps256.pem => private-key.pem} (100%) diff --git a/example_app/.example-app-env b/example_app/.example-app-env index 3ca5dd91..a2de6adf 100644 --- a/example_app/.example-app-env +++ b/example_app/.example-app-env @@ -1,3 +1,3 @@ -C2PIE_PRIVATE_KEY_FILE=/example_app/credentials/ps256.pem -C2PIE_CERTIFICATE_CHAIN_FILE=/example_app/credentials/ps256.pub +C2PIE_PRIVATE_KEY_FILE=/example_app/credentials/private-key.pem +C2PIE_CERTIFICATE_CHAIN_FILE=/example_app/credentials/certificate-chain.pub C2PIE_PACKAGE_VERSION=latest \ No newline at end of file diff --git a/example_app/credentials/ps256.pub b/example_app/credentials/certificate-chain.pub similarity index 100% rename from example_app/credentials/ps256.pub rename to example_app/credentials/certificate-chain.pub diff --git a/example_app/credentials/ps256.pem b/example_app/credentials/private-key.pem similarity index 100% rename from example_app/credentials/ps256.pem rename to example_app/credentials/private-key.pem diff --git a/tests/c2pa/claim_signature_test.py b/tests/c2pa/claim_signature_test.py index 31982675..b5549f7c 100644 --- a/tests/c2pa/claim_signature_test.py +++ b/tests/c2pa/claim_signature_test.py @@ -18,8 +18,8 @@ def test_create_claim_signature_with_empty_claim(): def test_create_claim_signature_with_non_empty_claim(): - key_filepath = "tests/credentials/ps256.pem" - cert_filepath = "tests/credentials/ps256.pub" + key_filepath = "tests/credentials/private-key.pem" + cert_filepath = "tests/credentials/certificate-chain.pub" with open(key_filepath, "rb") as f: key = f.read() From 3a62380fd6c5aa6f55414b12c18128b4d58a9ff9 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:47:49 +0500 Subject: [PATCH 15/69] style(readme): remove wrapping file paths in brackets --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d99f7d4..86cad074 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ For more detailed feature specification, please look at the [Features](#-feature 3) Key and certificate filepaths exported into the current environment with: ```bash - export C2PIE_PRIVATE_KEY_FILE= + export C2PIE_PRIVATE_KEY_FILE=private-key.pem export C2PIE_CERTIFICATE_CHAIN_FILE=certificate-chain.pub ``` @@ -91,13 +91,13 @@ For more detailed feature specification, please look at the [Features](#-feature You can run the following command to sign an input JPG or PDF file: ```python -c2pie sign --input_file +c2pie sign --input_file path/to/input_file ``` By default, signed file will be saved to the same directory as the input file with the *signed_* prefix. To explicitly set output path, use: ```python -c2pie sign --input_file --output_file +c2pie sign --input_file path/to/input_file --output_file path/to/output_file ``` If the file has been successfully signed, you'll see a message like this: From 9c5666a0bc8267d526b785916f500c7389e2a492 Mon Sep 17 00:00:00 2001 From: SupremeSoviet <112272101+SupremeSoviet@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:20:53 +0500 Subject: [PATCH 16/69] fix: make signature schema configurable * feat(signing): add draft schema loading * feat(signing): add schema validation * feat(signing): add signature customization --- c2pie/main.py | 10 +++++++ c2pie/signing.py | 77 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/c2pie/main.py b/c2pie/main.py index 1446ab3f..1576e0af 100644 --- a/c2pie/main.py +++ b/c2pie/main.py @@ -35,6 +35,14 @@ def parse_arguments() -> argparse.Namespace: default=None, help="optional path to save the signed file. If omitted, the program saves to 'signed_' + input_file.", ) + sign_parser.add_argument( + "-m", + "--manifest", + dest="schema_filepath", + type=Path, + default=None, + help="optional path to a the signature file. If omitted, the default signature is used.", + ) sign_parser.set_defaults(func=sign) return global_parser.parse_args() @@ -43,11 +51,13 @@ def parse_arguments() -> argparse.Namespace: def sign(arguments: argparse.Namespace) -> None: input_file_path = arguments.input_file output_file_path = arguments.output_file + schema_file_path = arguments.schema_filepath # sign the provided file sign_file( input_path=input_file_path, output_path=output_file_path, + schema_path=schema_file_path, ) diff --git a/c2pie/signing.py b/c2pie/signing.py index de5da794..f542296b 100644 --- a/c2pie/signing.py +++ b/c2pie/signing.py @@ -1,6 +1,7 @@ from __future__ import annotations import hashlib +import json import os from pathlib import Path from typing import Literal @@ -14,13 +15,70 @@ ) from c2pie.utils.content_types import C2PA_ContentTypes -creative_work_schema = { - "@context": "https://schema.org", - "@type": "CreativeWork", - "author": [{"@type": "Organization", "name": "Tourmaline Core"}], - "copyrightYear": "2026", - "copyrightHolder": "c2pie", -} + +def _read_schema_from_file(schema_filepath: Path) -> dict[str]: + try: + with open(schema_filepath) as schema_file: + schema = json.loads(schema_file.read()) + return schema + except Exception as ex: + raise ex + + +def _validate_schema(schema: dict[str]) -> None: + expected_items = { + "@context": "https://schema.org", + "@type": "CreativeWork", + } + + # check whether the @context and @type properties match the expected values + for property in ["@context", "@type"]: + value = schema.get(property, None) + if value != expected_items[property]: + raise ValueError( + "Schema must include the following properties with these values: " + '{@context: "https://schema.org", "@type": "CreativeWork"}' + ) + + # check that "copyrightYear" and "copyrightHolder" exist + for property in ["copyrightYear", "copyrightHolder"]: + value = schema.get(property, None) + if not value: + raise ValueError('Schema must include "copyrightYear" and "copyrightHolder"') + + # check that "author" is a non-empty list whose first item has "@type" and "name" + author = schema.get("author", None) + if not (isinstance(author, list) and author): + raise ValueError('author must be a non-empty list of objects with "@type" and "name"') + else: + author = author[0] + + author_type = author.get("@type", None) + author_name = author.get("name", None) + + if not author_type or not author_name: + raise ValueError('author[@"type"] and author[@"name"] must not be empty') + + if author_type != "Organization" and author_type != "Person": + raise ValueError('author["@type"] must be "Organization" or "Person"') + + +def _load_signature_schema(schema_path: str | Path | None) -> dict[str]: + default_schema = { + "@context": "https://schema.org", + "@type": "CreativeWork", + "author": [{"@type": "Organization", "name": "Tourmaline Core"}], + "copyrightYear": "2026", + "copyrightHolder": "c2pie", + } + + if schema_path is None: + return default_schema + + validated_schema_path = _validate_general_filepath(file_path=schema_path) + schema = _read_schema_from_file(schema_filepath=validated_schema_path) + _validate_schema(schema=schema) + return schema def _ensure_path_type_for_filepath(path: str | Path) -> Path: @@ -117,6 +175,7 @@ def sign_file( output_path: Path | str | None = None, key_path: str | None = None, certificates_path: str | None = None, + schema_path: str | None = None, ) -> None: key, certificates = _load_certificates_and_key( key_path=key_path, @@ -128,6 +187,8 @@ def sign_file( output_file_path=output_path, ) + schema = _load_signature_schema(schema_path=schema_path) + with open(input_path, "rb") as f: raw_bytes = f.read() @@ -140,7 +201,7 @@ def sign_file( creative_work_assertion = c2pie_GenerateAssertion( C2PA_AssertionTypes.creative_work, - creative_work_schema, + schema, ) hash_data_assertion = c2pie_GenerateHashDataAssertion( From ca7811be9389305b45b2b413466b916639c87b60 Mon Sep 17 00:00:00 2001 From: fpandyz <5389368+fpandyz@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:23:09 +0500 Subject: [PATCH 17/69] infra: add first part of draft workflow to test readme steps in Docker --- .github/workflows/test-readme-steps.yml | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/test-readme-steps.yml diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml new file mode 100644 index 00000000..0d6e2374 --- /dev/null +++ b/.github/workflows/test-readme-steps.yml @@ -0,0 +1,41 @@ +name: Execute Readme Steps + +on: + push: + branches: + - feature/* + +jobs: + execute-readme-steps: + runs-on: ubuntu-24.04 + container: python:3.9.2-buster + steps: + - name: Generate Certificates, Install c2pie, and Sign Image + run: | + openssl genrsa \ + -out ./private-key-filename.pem 2048 + + openssl req \ + -new \ + -key ./private-key-filename.pem \ + -subj "/C=PE/ST=Lima/L=Lima/O=Acme Inc. /OU=IT Department/CN=acme.com" \ + -out ./csr.pem + + openssl x509 \ + -req \ + -days 365 \ + -in ./csr.pem \ + -signkey ./private-key-filename.pem \ + -out ./certificate-filename.pem + + pip install c2pie + + wget https://github.com/TourmalineCore/c2pie/blob/master/example_app/test_files/test_image.jpg + + c2pie sign --input_file ./test_image.jpg + + - name: Upload Signed Image + uses: actions/upload-artifact@v4 + with: + name: signed_test_image + path: signed_test_image.jpg From 4603e3f4179812703e0a01ab0105c778f05b7c87 Mon Sep 17 00:00:00 2001 From: fpandyz <5389368+fpandyz@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:26:38 +0500 Subject: [PATCH 18/69] infra: add missing exports --- .github/workflows/test-readme-steps.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 0d6e2374..46bbc7ec 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -31,7 +31,10 @@ jobs: pip install c2pie wget https://github.com/TourmalineCore/c2pie/blob/master/example_app/test_files/test_image.jpg - + + export C2PIE_KEY_FILEPATH=./private-key-filename.pem + export C2PIE_CERT_FILEPATH=./certificate-filename.pem + c2pie sign --input_file ./test_image.jpg - name: Upload Signed Image From 9d27cf73ce3586d0906b4308d4569568fb42547c Mon Sep 17 00:00:00 2001 From: fpandyz <5389368+fpandyz@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:35:02 +0500 Subject: [PATCH 19/69] infra: add verification steps --- .github/workflows/test-readme-steps.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 46bbc7ec..30641dea 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -6,7 +6,7 @@ on: - feature/* jobs: - execute-readme-steps: + sign-image-steps: runs-on: ubuntu-24.04 container: python:3.9.2-buster steps: @@ -42,3 +42,19 @@ jobs: with: name: signed_test_image path: signed_test_image.jpg + + verify-image-steps: + needs: sign-image-steps + runs-on: ubuntu-24.04 + container: rust:1.90.0-bullseye + steps: + - name: Download Signed Image + uses: actions/download-artifact@v5 + with: + name: signed_test_image + + - name: Install c2patool and Verify Signed Image + run: | + cargo install c2patool + + c2patool signed_test_image.jpg From bec0f2f182c7e605576207e9aeb439b4c42dd3a9 Mon Sep 17 00:00:00 2001 From: fpandyz <5389368+fpandyz@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:53:39 +0500 Subject: [PATCH 20/69] infra: fix image URL --- .github/workflows/test-readme-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 30641dea..958ab6c7 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -30,7 +30,7 @@ jobs: pip install c2pie - wget https://github.com/TourmalineCore/c2pie/blob/master/example_app/test_files/test_image.jpg + wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_image.jpg export C2PIE_KEY_FILEPATH=./private-key-filename.pem export C2PIE_CERT_FILEPATH=./certificate-filename.pem From 1185185593b2aa738bdfd4994803f4d0ae927f0e Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:41:55 +0500 Subject: [PATCH 21/69] infra: use corrected certificate chain generation algorithm --- .github/workflows/test-readme-steps.yml | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 958ab6c7..fab36002 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -12,28 +12,29 @@ jobs: steps: - name: Generate Certificates, Install c2pie, and Sign Image run: | - openssl genrsa \ - -out ./private-key-filename.pem 2048 - - openssl req \ - -new \ - -key ./private-key-filename.pem \ - -subj "/C=PE/ST=Lima/L=Lima/O=Acme Inc. /OU=IT Department/CN=acme.com" \ - -out ./csr.pem - - openssl x509 \ - -req \ - -days 365 \ - -in ./csr.pem \ - -signkey ./private-key-filename.pem \ - -out ./certificate-filename.pem + openssl genpkey \ + -algorithm RSA-PSS \ + -pkeyopt rsa_keygen_bits:2048 + -pkeyopt rsa_pss_keygen_md:sha256 \ + -pkeyopt rsa_pss_keygen_mgf1_md:sha256 \ + -pkeyopt rsa_pss_keygen_saltlen:32 \ + -out private_key.key + + openssl req -new -x509 \ + -key private_key.key \ + -sha256 -days 825 \ + -subj "/C=US/ST=CA/L=Somewhere/O=C2PA Test Signing Cert/OU=FOR TESTING_ONLY/CN=C2PA PSS Signer/emailAddress=pie@example.com" \ + -addext "basicConstraints=critical,CA:false" \ + -addext "keyUsage=critical,digitalSignature,nonRepudiation" \ + -addext "extendedKeyUsage=critical,emailProtection" \ + -out certificate_chain.pem pip install c2pie wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_image.jpg - export C2PIE_KEY_FILEPATH=./private-key-filename.pem - export C2PIE_CERT_FILEPATH=./certificate-filename.pem + export C2PIE_PRIVATE_KEY_FILE=./private_key.key + export C2PIE_CERTIFICATE_CHAIN_FILE=./certificate_chain.pem c2pie sign --input_file ./test_image.jpg From 1eb336a0f457d027d5297db228a1aa6bcbbab651 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:48:53 +0500 Subject: [PATCH 22/69] infra: fix missing slash --- .github/workflows/test-readme-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index fab36002..a3c04a5e 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -14,7 +14,7 @@ jobs: run: | openssl genpkey \ -algorithm RSA-PSS \ - -pkeyopt rsa_keygen_bits:2048 + -pkeyopt rsa_keygen_bits:2048 \ -pkeyopt rsa_pss_keygen_md:sha256 \ -pkeyopt rsa_pss_keygen_mgf1_md:sha256 \ -pkeyopt rsa_pss_keygen_saltlen:32 \ From 539c1441a36f6e8ea6b49fb9e1bf80b62eff643b Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:49:18 +0500 Subject: [PATCH 23/69] infra: fix redundant action vars names --- .github/workflows/lint-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 85bba42f..fd3e6840 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -175,8 +175,8 @@ jobs: --cov=c2pie \ -v env: - C2PIE_KEY_FILEPATH: ${{ vars.C2PIE_KEY_FILEPATH }} - C2PIE_CERT_FILEPATH: ${{ vars.C2PIE_CERT_FILEPATH }} + C2PIE_KEY_FILEPATH: ${{ vars.C2PIE_PRIVATE_KEY_FILE }} + C2PIE_CERT_FILEPATH: ${{ vars.C2PIE_CERTIFICATE_CHAIN_FILE}} - name: Upload coverage artifact if: (matrix.python-version == '3.12') && (matrix.os == 'ubuntu-latest') From d61c3e3d45e9e9e49fbcdc37bda4b69afbdddaab Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:59:10 +0500 Subject: [PATCH 24/69] chore: merge renaming changes from "docs/update-readme" to fix workflow --- .env-example | 4 +- .github/workflows/lint-and-test.yml | 9 +- .github/workflows/publish-package.yml | 2 +- .github/workflows/publish-release.yml | 14 +- CHANGELOG.md | 27 ++ README.md | 288 +++++++++--------- c2pie/main.py | 3 + c2pie/signing.py | 4 +- example_app/.example-app-env | 4 +- .../{ps256.pub => certificate-chain.pub} | 0 .../{ps256.pem => private-key.pem} | 0 pyproject.toml | 2 +- tests/c2pa/claim_signature_test.py | 4 +- .../{ps256.pub => certificate-chain.pub} | 0 .../{ps256.pem => private-key.pem} | 0 15 files changed, 194 insertions(+), 167 deletions(-) rename example_app/credentials/{ps256.pub => certificate-chain.pub} (100%) rename example_app/credentials/{ps256.pem => private-key.pem} (100%) rename tests/credentials/{ps256.pub => certificate-chain.pub} (100%) rename tests/credentials/{ps256.pem => private-key.pem} (100%) diff --git a/.env-example b/.env-example index 132e8814..7b2c8067 100644 --- a/.env-example +++ b/.env-example @@ -1,4 +1,4 @@ -C2PIE_KEY_FILEPATH=tests/credentials/ps256.pem -C2PIE_CERT_FILEPATH=tests/credentials/ps256.pub +C2PIE_PRIVATE_KEY_FILE=tests/credentials/private-key.pem +C2PIE_CERTIFICATE_CHAIN_FILE=tests/credentials/certificate-chain.pub TEST_PDF_PATH=tests/test_files/test_doc.pdf TEST_IMAGE_PATH=tests/test_files/test_image.jpg \ No newline at end of file diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index fd3e6840..d8130082 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -2,8 +2,9 @@ name: Linting and Testing on: push: - branches: - - '**' + branches-ignore: + - master + - release/** paths-ignore: - '**.md' - 'docs/**' @@ -175,8 +176,8 @@ jobs: --cov=c2pie \ -v env: - C2PIE_KEY_FILEPATH: ${{ vars.C2PIE_PRIVATE_KEY_FILE }} - C2PIE_CERT_FILEPATH: ${{ vars.C2PIE_CERTIFICATE_CHAIN_FILE}} + C2PIE_PRIVATE_KEY_FILE: ${{ vars.C2PIE_PRIVATE_KEY_FILE }} + C2PIE_CERTIFICATE_CHAIN_FILE: ${{ vars.C2PIE_CERTIFICATE_CHAIN_FILE }} - name: Upload coverage artifact if: (matrix.python-version == '3.12') && (matrix.os == 'ubuntu-latest') diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 869420af..3ef75772 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -2,7 +2,7 @@ name: Package Publishing on: release: - types: [created] + types: [created, published] permissions: contents: write diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index a943798c..6936630f 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -4,21 +4,18 @@ on: push: branches: - master - - release/** - create: - branches: - - release/** + - 'release/*' permissions: - contents: read + contents: write jobs: linting-and-testing: name: Linting and Testing uses: ./.github/workflows/lint-and-test.yml permissions: - contents: write packages: read + contents: write release: name: Create and Publish Release @@ -28,9 +25,6 @@ jobs: group: ${{ github.workflow }}-release-${{ github.ref_name }} cancel-in-progress: false - permissions: - contents: write - steps: - name: Checkout Repository on Release Branch uses: actions/checkout@v4 @@ -88,7 +82,7 @@ jobs: - name: Semantic Version Release id: release - uses: python-semantic-release/python-semantic-release@v10.4.1 + uses: python-semantic-release/python-semantic-release@v8.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" diff --git a/CHANGELOG.md b/CHANGELOG.md index 312ad11a..baccaf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## v0.1.0-rc.2 (2025-10-21) + +### Features + +- **main**: Add version option to c2pie command + ([`8449489`](https://github.com/TourmalineCore/c2pie/commit/8449489f50f123edb3987ba1ce17513fd1cf8f4b)) + + +## v0.1.0-rc.1 (2025-10-20) + +### Documentation + +- **readme**: Add note on c2pie version + ([`3726a77`](https://github.com/TourmalineCore/c2pie/commit/3726a77c113c4dff2b344897af5f8b68521994dd)) + +- **readme**: Change latest version reference + ([`6409e05`](https://github.com/TourmalineCore/c2pie/commit/6409e05957922b98883326c6d8c78af0f8b1346e)) + +- **readme**: Remove note of package version in descr, change badge label + ([`a6efaf9`](https://github.com/TourmalineCore/c2pie/commit/a6efaf9f35ce21db4b041d97fd3fca8ad6d8445b)) + +### Features + +- **example-app**: Add ability to set c2pie version that should be used in example app + ([`c9c95eb`](https://github.com/TourmalineCore/c2pie/commit/c9c95eb4c50f756735d9c0c71ea65d673363da04)) + + ## v0.1.0-alpha.6 (2025-10-17) ### Bug Fixes diff --git a/README.md b/README.md index e864d6e9..86cad074 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![CI](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml/badge.svg)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![c2pa](https://img.shields.io/badge/c2pa-v1.4-seagreen.svg)](https://c2pa.org/) -[![coverage](https://img.shields.io/badge/coverage-92%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +[![coverage](https://img.shields.io/badge/coverage-91%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![latest](https://img.shields.io/pypi/v/c2pie?label=latest&colorB=fc8021)](https://pypi.org/project/c2pie/)
@@ -33,15 +33,18 @@ For more detailed feature specification, please look at the [Features](#-feature ## Table of Contents + [🥧 Quick start](#-quick-start) - + [Running example apps with Docker Compose](#running-example-apps-with-docker-compose) - + [Running from your own environment](#running-from-your-own-environment) + + [Running from your local environment using globally installed Python](#running-from-your-local-environment-using-globally-installed-python) + [Prerequisites](#prerequisites) + [Usage](#usage) + [Command Line Interface](#command-line-interface) + [Code](#code) + [Validation](#validation) - + [c2patool](#c2patool) + [C2PA Verify Tool](#c2pa-verify-tool) + + [c2patool](#c2patool) + + [Running example apps with Docker Compose](#running-example-apps-with-docker-compose) ++ [🥧 Certificates](#-certificates) + + [Generating test credentials](#generating-test-credentials) + + [Getting credentials for production](#getting-credentials-for-production) + [🥧 For developers](#-for-developers) + [First steps](#first-steps) + [Using Dev Containers](#using-dev-containers) @@ -52,9 +55,6 @@ For more detailed feature specification, please look at the [Features](#-feature + [🥧 Features](#-features) + [Workflow of test applications](#workflow-of-test-applications) + [Notes for PDF vs JPG/JPEG](#notes-for-pdf-vs-jpgjpeg) -+ [🥧 Certificates](#-certificates) - + [Generating your own mock credentials](#generating-your-own-mock-credentials) - + [Getting credentials for production](#getting-credentials-for-production) + [🥧 Relevant links](#-relevant-links) + [🥧 Contributing](#-contributing) + [🥧 License](#-license) @@ -63,86 +63,20 @@ For more detailed feature specification, please look at the [Features](#-feature # 🥧 Quick start -## Running example apps with Docker Compose - -For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. - ->[!IMPORTANT] -> Docker is essential for running example apps. - -Follow the steps: - -1. Clone the c2pie repository. - -2. Go to `example_app` directory: - ```bash - cd example_app - ``` - ->[!NOTE] ->By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. - -3. To test signing a JPG file, run: - ```bash - docker compose up c2pie-test-signing-jpg - ``` - - To test signing a PDF file, run: - ```bash - docker compose up c2pie-test-signing-pdf - ``` - - After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: - - ```bash - Successfully signed the file test_files/test_image.jpg! - The result was saved to test_files/signed_test_image.jpg. - c2patool_validation_results: - { - "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", - "manifests": { - "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9": { - "claim_generator": "c2pie", - ................ - }, - "validation_results": { - "activeManifest": { - "success": [ - { - "code": "claimSignature.insideValidity", - "url": "self#jumbf=/c2pa/urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9/c2pa.signature", - "explanation": "claim signature valid" - }, - ................ - }, - "validation_state": "Valid" - } - ``` - -You can also set up a Jupyter Lab environment and test c2pie there by running: -```bash -docker compose up c2pie-notebooks -``` - -After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. - -The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. - -
- -## Running from your own environment +## Running from your local environment using globally installed Python ### Prerequisites 1) Python environment. Currently supported Python versions: 3.9.2 - 3.14.0. -2) Private key and certificate chain pair. You can go to [Certificates](#-certificates) for instructions on how to generate one. - The repo contains pre-generated mock credentials in `tests/credentials`. You can use them for a quick start. +2) Private key and certificate chain pair. The repo contains pre-generated mock credentials in `tests/credentials`. You can either use them for a quick start.You can go to [Certificates](#-certificates) for instructions on how to generate one. + + 3) Key and certificate filepaths exported into the current environment with: ```bash - export C2PIE_KEY_FILEPATH= - export C2PIE_CERT_FILEPATH= + export C2PIE_PRIVATE_KEY_FILE=private-key.pem + export C2PIE_CERTIFICATE_CHAIN_FILE=certificate-chain.pub ``` 4) c2pie package installed in your current environment: @@ -151,20 +85,19 @@ The existing `notebooks` directory already contains an example notebook with com pip install c2pie ``` - ### Usage #### Command Line Interface You can run the following command to sign an input JPG or PDF file: ```python -c2pie sign --input_file +c2pie sign --input_file path/to/input_file ``` By default, signed file will be saved to the same directory as the input file with the *signed_* prefix. To explicitly set output path, use: ```python -c2pie sign --input_file --output_file +c2pie sign --input_file path/to/input_file --output_file path/to/output_file ``` If the file has been successfully signed, you'll see a message like this: @@ -201,6 +134,22 @@ The result was saved to tests/test_files/signed_test_doc.pdf. ### Validation +#### C2PA Verify Tool + +You can verify signed files using [Verify tool](https://contentcredentials.org/verify). + +Simply upload the file you'd like to verify. + +>[!IMPORTANT] +> Files embedded with self-signed certificates (like the ones this repository contains) **won't be verified**. +> +> You'll get the following message: +>``` +>The Content Credential issuer couldn’t be recognized. This file may not come from where it claims to. +>``` +> +>Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. + #### c2patool Output files can be validated with: @@ -233,21 +182,125 @@ c2patool_validation_results: } ``` -#### C2PA Verify Tool +
-You can also verify signed files using [Verify tool](https://contentcredentials.org/verify). +## Running example apps with Docker Compose -Simply upload the file you'd like to verify. +For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. >[!IMPORTANT] -> Files embedded with self-signed certificates (like the ones this repository contains) **won't be verified**. -> -> You'll get the following message: ->``` ->The Content Credential issuer couldn’t be recognized. This file may not come from where it claims to. ->``` +> Docker is essential for running example apps. + +Follow the steps: + +1. Clone the c2pie repository. + +2. Go to `example_app` directory: + ```bash + cd example_app + ``` + +>[!NOTE] +>By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. + +3. To test signing a JPG file, run: + ```bash + docker compose up c2pie-test-signing-jpg + ``` + + To test signing a PDF file, run: + ```bash + docker compose up c2pie-test-signing-pdf + ``` + + After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: + + ```bash + Successfully signed the file test_files/test_image.jpg! + The result was saved to test_files/signed_test_image.jpg. + c2patool_validation_results: + { + "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", + "manifests": { + "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9": { + "claim_generator": "c2pie", + ................ + }, + "validation_results": { + "activeManifest": { + "success": [ + { + "code": "claimSignature.insideValidity", + "url": "self#jumbf=/c2pa/urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9/c2pa.signature", + "explanation": "claim signature valid" + }, + ................ + }, + "validation_state": "Valid" + } + ``` + +You can also set up a Jupyter Lab environment and test c2pie there by running: +```bash +docker compose up c2pie-notebooks +``` + +After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. + +The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. + +
+ +# 🥧 Certificates + +Example certificate chain and key file are located in `tests/credentials`. + +>[!WARNING] +>This repository's credentials are suitable for development only! + +## Generating test credentials + +You can generate your own private key and certificate chain pair for testing the package by following these steps: + +1. Generate a private key: + ```bash + openssl genrsa -out private-key.pem 2048 + ``` + +2. Generate a Certificate Signing Request (CSR): + ```bash + openssl req -new \ + -key private-key.pem \ + -out csr.pem + ``` + +3. Generate a Self-Signed Certificate: + ```bash + openssl x509 -req -days 365 \ + -in csr.pem \ + -signkey private-key.pem \ + -out certificate-chain.pem + ``` + +>[!IMPORTANT] +> Remember to update environment variables to use your newly generated credentials. > ->Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. + +>[!NOTE] +> You can change certificate's validity period with --days option at the last step. +> +>Certificate Signing Request file (*csr.pem*) can be deleted after the certificate has been generated. + + +## Getting credentials for production + +🔸 Use a real document‑signing certificate (RSA‑PSS or ECDSA per C2PA); + +🔸 Provide a leaf + intermediates bundle (no root); + +🔸 Configure trust anchors/allow‑lists in your validator environment. + +For detailed information on signing and certificates please explore the [corresponding section in the Content Authenticity Initiative (CAI) documentation](https://opensource.contentauthenticity.org/docs/signing/).
@@ -372,57 +425,6 @@ The library takes care of iterative sizing, so the `c2pa.hash.data` matches exac
-# 🥧 Certificates - -Example certificate and key are located in `tests/credentials`. - ->[!WARNING] ->This repository's credentials are suitable for development only! - -## Generating your own mock credentials - -You can generate your own mock credentials for testing and developing the package follow these steps: - -1. Generate a private key: - ```bash - openssl genrsa -out credentials/.pem 2048 - ``` - -2. Generate a Certificate Signing Request (CSR): - ```bash - openssl req -new \ - -key credentials/.pem \ - -out csr.pem - ``` - -3. Generate a Self-Signed Certificate: - ```bash - openssl x509 -req -days 365 \ - -in csr.pem \ - -signkey credentials/.pem \ - -out credentials/.pem - ``` ->[!IMPORTANT] -> Remember to update environment variables to use your newly generated credentials. - ->[!NOTE] -> You can change certificate's validity period with --days option at the last step. -> ->Certificate Signing Request file (*csr.pem*) can be deleted after the certificate has been generated. - - -## Getting credentials for production - -🔸 Use a real document‑signing certificate (RSA‑PSS or ECDSA per C2PA); - -🔸 Provide a leaf + intermediates bundle (no root); - -🔸 Configure trust anchors/allow‑lists in your validator environment. - -For detailed information on signing and certificates please explore the [corresponding section in the Content Authenticity Initiative (CAI) documentation](https://opensource.contentauthenticity.org/docs/signing/). - -
- # 🥧 Relevant links ∗ [CAI documentation](https://opensource.contentauthenticity.org/docs) diff --git a/c2pie/main.py b/c2pie/main.py index 608c11ea..1446ab3f 100644 --- a/c2pie/main.py +++ b/c2pie/main.py @@ -1,4 +1,5 @@ import argparse +from importlib.metadata import version from pathlib import Path from c2pie.signing import sign_file @@ -15,6 +16,8 @@ def parse_arguments() -> argparse.Namespace: f"supported extensions are: {supported_extensions}.", ) + global_parser.add_argument("-V", "--version", action="version", version=f"c2pie {version('c2pie')}") + subparsers = global_parser.add_subparsers(title="subcommands", help="commands") sign_parser = subparsers.add_parser("sign", help="embed c2pa signature into a file") diff --git a/c2pie/signing.py b/c2pie/signing.py index de5da794..5e04af07 100644 --- a/c2pie/signing.py +++ b/c2pie/signing.py @@ -93,11 +93,11 @@ def _load_certificates_and_key( key_path: str | None, certificates_path: str | None, ) -> tuple[bytes, bytes]: - key_path = key_path or os.getenv("C2PIE_KEY_FILEPATH") + key_path = key_path or os.getenv("C2PIE_PRIVATE_KEY_FILE") if not key_path: raise ValueError("Key filepath variable has not been set. Cannot sign the provided file.") - certificates_path = certificates_path or os.getenv("C2PIE_CERT_FILEPATH") + certificates_path = certificates_path or os.getenv("C2PIE_CERTIFICATE_CHAIN_FILE") if not certificates_path: raise ValueError("Certificate filepath variable has not been set. Cannot sign the provided file.") diff --git a/example_app/.example-app-env b/example_app/.example-app-env index f1be52ae..a2de6adf 100644 --- a/example_app/.example-app-env +++ b/example_app/.example-app-env @@ -1,3 +1,3 @@ -C2PIE_KEY_FILEPATH=/example_app/credentials/ps256.pem -C2PIE_CERT_FILEPATH=/example_app/credentials/ps256.pub +C2PIE_PRIVATE_KEY_FILE=/example_app/credentials/private-key.pem +C2PIE_CERTIFICATE_CHAIN_FILE=/example_app/credentials/certificate-chain.pub C2PIE_PACKAGE_VERSION=latest \ No newline at end of file diff --git a/example_app/credentials/ps256.pub b/example_app/credentials/certificate-chain.pub similarity index 100% rename from example_app/credentials/ps256.pub rename to example_app/credentials/certificate-chain.pub diff --git a/example_app/credentials/ps256.pem b/example_app/credentials/private-key.pem similarity index 100% rename from example_app/credentials/ps256.pem rename to example_app/credentials/private-key.pem diff --git a/pyproject.toml b/pyproject.toml index afe0e269..698fc6b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-alpha.6" +version = "0.1.0-rc.2" [[tool.poetry.source]] name = "PyPI" diff --git a/tests/c2pa/claim_signature_test.py b/tests/c2pa/claim_signature_test.py index 31982675..b5549f7c 100644 --- a/tests/c2pa/claim_signature_test.py +++ b/tests/c2pa/claim_signature_test.py @@ -18,8 +18,8 @@ def test_create_claim_signature_with_empty_claim(): def test_create_claim_signature_with_non_empty_claim(): - key_filepath = "tests/credentials/ps256.pem" - cert_filepath = "tests/credentials/ps256.pub" + key_filepath = "tests/credentials/private-key.pem" + cert_filepath = "tests/credentials/certificate-chain.pub" with open(key_filepath, "rb") as f: key = f.read() diff --git a/tests/credentials/ps256.pub b/tests/credentials/certificate-chain.pub similarity index 100% rename from tests/credentials/ps256.pub rename to tests/credentials/certificate-chain.pub diff --git a/tests/credentials/ps256.pem b/tests/credentials/private-key.pem similarity index 100% rename from tests/credentials/ps256.pem rename to tests/credentials/private-key.pem From 9ebfa3c2acd50150c9d7501965362014b97ca129 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:08:39 +0500 Subject: [PATCH 25/69] docs(readme): add signing and validating with Docker containers --- README.md | 242 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 86cad074..7bc356d4 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,17 @@ For more detailed feature specification, please look at the [Features](#-feature ## Table of Contents + [🥧 Quick start](#-quick-start) + + [Running signing from a Docker container](#running-signing-from-a-docker-container) + [Running from your local environment using globally installed Python](#running-from-your-local-environment-using-globally-installed-python) + [Prerequisites](#prerequisites) + [Usage](#usage) + [Command Line Interface](#command-line-interface) + [Code](#code) - + [Validation](#validation) - + [C2PA Verify Tool](#c2pa-verify-tool) - + [c2patool](#c2patool) + [Running example apps with Docker Compose](#running-example-apps-with-docker-compose) + + [Validation](#validation) + + [C2PA Verify Tool](#c2pa-verify-tool) + + [c2patool](#c2patool) + + [Validating test image with a Docker container](#validating-test-image-with-a-docker-container) + [🥧 Certificates](#-certificates) + [Generating test credentials](#generating-test-credentials) + [Getting credentials for production](#getting-credentials-for-production) @@ -63,14 +65,69 @@ For more detailed feature specification, please look at the [Features](#-feature # 🥧 Quick start +## Running signing from a Docker container + +1) Run a Docker container from a Python image: +```bash +docker run --rm -it --entrypoint bash --name c2pie-test python:3.12 +``` + +2) Inside the container execute the following bash commands: + +```bash +# Generate private key and certificate chain: +openssl genpkey \ +-algorithm RSA-PSS \ +-pkeyopt rsa_keygen_bits:2048 \ +-pkeyopt rsa_pss_keygen_md:sha256 \ +-pkeyopt rsa_pss_keygen_mgf1_md:sha256 \ +-pkeyopt rsa_pss_keygen_saltlen:32 \ +-out private_key.key + +openssl req -new -x509 \ +-key private_key.key \ +-sha256 -days 825 \ +-subj "/C=US/ST=CA/L=Somewhere/O=C2PA Test Signing Cert/OU=FOR TESTING_ONLY/CN=C2PA PSS Signer/emailAddress=pie@example.com" \ +-addext "basicConstraints=critical,CA:false" \ +-addext "keyUsage=critical,digitalSignature,nonRepudiation" \ +-addext "extendedKeyUsage=critical,emailProtection" \ +-out certificate_chain.pem + +# Export created private key and certificate chain files into env variables: +export C2PIE_PRIVATE_KEY_FILE=./private_key.key +export C2PIE_CERTIFICATE_CHAIN_FILE=./certificate_chain.pem + +# Install package +pip install c2pie + +# Download test image from this repo +wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_image.jpg + +# Sign downloaded image +c2pie sign --input_file ./test_image.jpg +``` + +3) **In a separate terminal**, execute the following commands to copy the file from the container to the folder you're currently in: + +```bash +docker cp c2pie-test:signed_test_image.jpg . +``` + +>[!NOTE] +>You can use the `c2pie-test` container to experiment with other JPG/JPEG or PDF files. +> +>Once you exit the container, it will be deleted automatically. + +After being copied to host machine, signed files can then be validated using either of the methods from [Validation](#validation) section: [C2PA Verify Tool]((https://contentcredentials.org/verify)) or [c2patool](#validating-test-image-with-a-docker-container). + + ## Running from your local environment using globally installed Python ### Prerequisites -1) Python environment. Currently supported Python versions: 3.9.2 - 3.14.0. - -2) Private key and certificate chain pair. The repo contains pre-generated mock credentials in `tests/credentials`. You can either use them for a quick start.You can go to [Certificates](#-certificates) for instructions on how to generate one. +1) Python environment. Currently supported Python versions: 3.9.2 - 3.14.0. Make sure to [create and activate virtual environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/) to avoid installing packages globally and any errors caused by that. +2) Private key and certificate chain pair. The repo contains pre-generated mock credentials in `tests/credentials`. You can either download and use them for a quick start or go to [Certificates](#-certificates) for instructions on how to generate a similar key-certificate pair. 3) Key and certificate filepaths exported into the current environment with: @@ -132,9 +189,76 @@ Successfully signed the file tests/test_files/test_doc.pdf! The result was saved to tests/test_files/signed_test_doc.pdf. ``` -### Validation +## Running example apps with Docker Compose + +For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. + +>[!IMPORTANT] +> Docker is essential for running example apps. + +Follow the steps: + +1. Clone the c2pie repository. + +2. Go to `example_app` directory: + ```bash + cd example_app + ``` + +>[!NOTE] +>By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. -#### C2PA Verify Tool +3. To test signing a JPG file, run: + ```bash + docker compose up c2pie-test-signing-jpg + ``` + + To test signing a PDF file, run: + ```bash + docker compose up c2pie-test-signing-pdf + ``` + + After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: + + ```bash + Successfully signed the file test_files/test_image.jpg! + The result was saved to test_files/signed_test_image.jpg. + c2patool_validation_results: + { + "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", + "manifests": { + "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9": { + "claim_generator": "c2pie", + ................ + }, + "validation_results": { + "activeManifest": { + "success": [ + { + "code": "claimSignature.insideValidity", + "url": "self#jumbf=/c2pa/urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9/c2pa.signature", + "explanation": "claim signature valid" + }, + ................ + }, + "validation_state": "Valid" + } + ``` + +You can also set up a Jupyter Lab environment and test c2pie there by running: +```bash +docker compose up c2pie-notebooks +``` + +After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. + +The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. + +
+ +## Validation + +### C2PA Verify Tool You can verify signed files using [Verify tool](https://contentcredentials.org/verify). @@ -150,9 +274,16 @@ Simply upload the file you'd like to verify. > >Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. -#### c2patool +### c2patool -Output files can be validated with: +[c2patool](https://github.com/contentauth/c2pa-rs/tree/main/cli) is a command line tool for working with C2PA manifests and media assets (audio, image or video files) provided by the C2PA Rust Library. + +If you already have Rust, install c2patool with: +```bash +cargo install c2patool +``` + +To validate files with c2patool, run: ```bash c2patool path/to/your_output.jpg c2patool path/to/your_output.pdf @@ -182,42 +313,31 @@ c2patool_validation_results: } ``` -
- -## Running example apps with Docker Compose - -For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. +#### Validating test image with a Docker container ->[!IMPORTANT] -> Docker is essential for running example apps. - -Follow the steps: - -1. Clone the c2pie repository. - -2. Go to `example_app` directory: - ```bash - cd example_app - ``` +1. Run a container with Rust: +```bash +docker run --rm -it --entrypoint bash --name c2pie-validate rust:1.90.0-bullseye +``` ->[!NOTE] ->By default, example apps use the latest available stable c2pie version. If you'd like to test some particular version, you can change the value of `C2PIE_PACKAGE_VERSION` in `example_app/.example-app-env`. +2. Install c2patool in the container: +```bash +cargo install c2patool +``` -3. To test signing a JPG file, run: +1. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container)and copied to your working directory: + + **In a separate terminal**, copy it into the Rust container: ```bash - docker compose up c2pie-test-signing-jpg + docker cp ./signed_test_image.jpg c2pie-validate:signed_test_image.jpg ``` - - To test signing a PDF file, run: + Then validate the copied image with: ```bash - docker compose up c2pie-test-signing-pdf + c2patool signed_test_image.jpg ``` - After running either of these commands, you'll see a resulting signed file appear in `example_app/test_files` directory with a `signed-` prefix and a corresponding message with c2patool validation results in your terminal like this: - + If the validation was successful, you'll get an output similar to this: ```bash - Successfully signed the file test_files/test_image.jpg! - The result was saved to test_files/signed_test_image.jpg. c2patool_validation_results: { "active_manifest": "urn:uuid:f0ce8560b76342d1bb3085cfbe6cc5e9", @@ -240,14 +360,10 @@ Follow the steps: } ``` -You can also set up a Jupyter Lab environment and test c2pie there by running: -```bash -docker compose up c2pie-notebooks -``` - -After running this command you should be able to access Jupyter Lab at `localhost:8888` from your browser. - -The existing `notebooks` directory already contains an example notebook with commands to test signing functionality. +>[!NOTE] +>You can validate other files in the same `c2pie-validate` container. +> +>Once you exit, the container will be deleted automatically.
@@ -264,32 +380,32 @@ You can generate your own private key and certificate chain pair for testing the 1. Generate a private key: ```bash - openssl genrsa -out private-key.pem 2048 - ``` - -2. Generate a Certificate Signing Request (CSR): - ```bash - openssl req -new \ - -key private-key.pem \ - -out csr.pem + openssl genpkey \ + -algorithm RSA-PSS \ + -pkeyopt rsa_keygen_bits:2048 \ + -pkeyopt rsa_pss_keygen_md:sha256 \ + -pkeyopt rsa_pss_keygen_mgf1_md:sha256 \ + -pkeyopt rsa_pss_keygen_saltlen:32 \ + -out private_key.key ``` -3. Generate a Self-Signed Certificate: +2. Generate a Self-Signed Certificate: ```bash - openssl x509 -req -days 365 \ - -in csr.pem \ - -signkey private-key.pem \ - -out certificate-chain.pem + openssl req -new -x509 \ + -key private_key.key \ + -sha256 -days 825 \ + -subj "/C=US/ST=CA/L=Somewhere/O=C2PA Test Signing Cert/OU=FOR TESTING_ONLY/CN=C2PA PSS Signer/emailAddress=pie@example.com" \ + -addext "basicConstraints=critical,CA:false" \ + -addext "keyUsage=critical,digitalSignature,nonRepudiation" \ + -addext "extendedKeyUsage=critical,emailProtection" \ + -out certificate_chain.pem ``` >[!IMPORTANT] -> Remember to update environment variables to use your newly generated credentials. -> +> Remember to update environment variables `C2PIE_PRIVATE_KEY_FILE` and `C2PIE_CERTIFICATE_CHAIN_FILE` to use your newly generated key (`private_key.key`) and certificate chain (`certificate_chain.pem`) files. >[!NOTE] -> You can change certificate's validity period with --days option at the last step. -> ->Certificate Signing Request file (*csr.pem*) can be deleted after the certificate has been generated. +> You can change certificate's validity period with -days option at the last step. ## Getting credentials for production From 5f319995f12a9d292cd13e864e7a44a452ecbf6f Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:13:36 +0500 Subject: [PATCH 26/69] config(semantic-releases): add alpha prereleases group --- pyproject.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 698fc6b2..4ec04442 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-rc.2" +version = "0.1.0-alpha.7" [[tool.poetry.source]] name = "PyPI" @@ -110,3 +110,8 @@ match = "^(release)/.+" prerelease = true prerelease_token = "rc" +[tool.semantic_release.branches.alpha] +match = "^(feat|fix|docs)/.+" +prerelease = true +prerelease_token = "alpha" + From df5fac4b7998d621c6e9ab1893b18fb651b34c05 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:21:09 +0500 Subject: [PATCH 27/69] docs(readme): fix links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7bc356d4..20647adc 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ docker cp c2pie-test:signed_test_image.jpg . > >Once you exit the container, it will be deleted automatically. -After being copied to host machine, signed files can then be validated using either of the methods from [Validation](#validation) section: [C2PA Verify Tool]((https://contentcredentials.org/verify)) or [c2patool](#validating-test-image-with-a-docker-container). +After being copied to host machine, signed files can then be validated using either of the methods from [Validation](#validation) section: [C2PA Verify Tool](https://contentcredentials.org/verify) or [c2patool](#validating-test-image-with-a-docker-container). ## Running from your local environment using globally installed Python @@ -272,7 +272,7 @@ Simply upload the file you'd like to verify. >The Content Credential issuer couldn’t be recognized. This file may not come from where it claims to. >``` > ->Please proceed to [production credentials section](#-getting-credentials-for-production) to find out about generating verifiable credentials. +>Please proceed to [production credentials section](#getting-credentials-for-production) to find out about generating verifiable credentials. ### c2patool From f5fd29246aa2c7dc599a5d2415d1be480cd3e3d0 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:31:17 +0500 Subject: [PATCH 28/69] infra: use action for installing c2patool with automated caching --- .github/workflows/test-readme-steps.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index a3c04a5e..87cd4785 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -47,15 +47,17 @@ jobs: verify-image-steps: needs: sign-image-steps runs-on: ubuntu-24.04 - container: rust:1.90.0-bullseye steps: - name: Download Signed Image uses: actions/download-artifact@v5 with: name: signed_test_image + + - name: Install Rust toolchain and c2patool + uses: baptiste0928/cargo-install@v3 + with: + crate: c2patool - - name: Install c2patool and Verify Signed Image + - name: Verify Signed Image run: | - cargo install c2patool - c2patool signed_test_image.jpg From b5479820c157b4076d5c40fb3a94097410d5b1e1 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:36:38 +0500 Subject: [PATCH 29/69] docs(readme): add empty line dividers --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 20647adc..ff46c027 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ docker cp c2pie-test:signed_test_image.jpg . After being copied to host machine, signed files can then be validated using either of the methods from [Validation](#validation) section: [C2PA Verify Tool](https://contentcredentials.org/verify) or [c2patool](#validating-test-image-with-a-docker-container). +
## Running from your local environment using globally installed Python @@ -189,6 +190,8 @@ Successfully signed the file tests/test_files/test_doc.pdf! The result was saved to tests/test_files/signed_test_doc.pdf. ``` +
+ ## Running example apps with Docker Compose For a quick test of c2pie's functionality with pre-prepared environment, test files and credentials, you can run our example apps. From 1bab57bccdddf9b5a5178dcd6d5c7f3bf322b501 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:06:21 +0500 Subject: [PATCH 30/69] docs(readme): add note on customizing signature subject info --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff46c027..871e9eef 100644 --- a/README.md +++ b/README.md @@ -408,7 +408,11 @@ You can generate your own private key and certificate chain pair for testing the > Remember to update environment variables `C2PIE_PRIVATE_KEY_FILE` and `C2PIE_CERTIFICATE_CHAIN_FILE` to use your newly generated key (`private_key.key`) and certificate chain (`certificate_chain.pem`) files. >[!NOTE] -> You can change certificate's validity period with -days option at the last step. +> You can change certificate's validity period with `-days` option at the last step. +> +> `-subj` option allows to set signature info used to sign the certificate. You can change the values to fit your info. Here's what each field letter code stands for: +> +> **/C** - Counry Code, **/ST** - State or Province name, **/L** - Locality Name (e.g. city), **/O** - Organization Name, **/OU** - Organization Unit Name, **/CN** - Common Name (e.g. your name), **/emailAdress** - email adress ## Getting credentials for production From be8a7d492ecb1d656f318dadd1d610d8ac50a6d8 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:08:07 +0500 Subject: [PATCH 31/69] infra: add failing workflow if invalid signature --- .github/workflows/test-readme-steps.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 87cd4785..f67eeed5 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -58,6 +58,19 @@ jobs: with: crate: c2patool - - name: Verify Signed Image + - name: Validate Signed Image run: | - c2patool signed_test_image.jpg + echo "$(c2patool signed_test_image.jpg)" >> validation_results.json + + - name: Download jq and Get validation results + id: validation_check + run: | + sudo apt install jq + echo "validation_results=$(jq -r .validation_state validation_results.json)" >> $GITHUB_OUTPUT + + - name: Fail if validation failed + if: ${{ steps.validation_check.outputs.validation_results == 'Invalid' }} + run: | + echo "Invalid C2PA signature" + exit 1 + From 3695d706aae60a4acb075a404b669025c3548375 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:19:08 +0500 Subject: [PATCH 32/69] infra: add logging --- .github/workflows/test-readme-steps.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index f67eeed5..3798897c 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -60,6 +60,7 @@ jobs: - name: Validate Signed Image run: | + c2patool signed_test_image.jpg echo "$(c2patool signed_test_image.jpg)" >> validation_results.json - name: Download jq and Get validation results @@ -68,6 +69,10 @@ jobs: sudo apt install jq echo "validation_results=$(jq -r .validation_state validation_results.json)" >> $GITHUB_OUTPUT + - name: Debug validation check + run: | + echo "Validation result extracted from file: ${{ steps.validation_check.outputs.validation_results }}" + - name: Fail if validation failed if: ${{ steps.validation_check.outputs.validation_results == 'Invalid' }} run: | From 7c4a230e5d7be13918f5b5f6c642c7f4eaea4e3f Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:49:31 +0500 Subject: [PATCH 33/69] docs(readme): fix env vars filepaths to correspond with the example --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 871e9eef..e9a4a559 100644 --- a/README.md +++ b/README.md @@ -131,10 +131,10 @@ After being copied to host machine, signed files can then be validated using eit 2) Private key and certificate chain pair. The repo contains pre-generated mock credentials in `tests/credentials`. You can either download and use them for a quick start or go to [Certificates](#-certificates) for instructions on how to generate a similar key-certificate pair. -3) Key and certificate filepaths exported into the current environment with: +3) Key and certificate chain filepaths exported into the current environment with (pay attention to filenames): ```bash - export C2PIE_PRIVATE_KEY_FILE=private-key.pem - export C2PIE_CERTIFICATE_CHAIN_FILE=certificate-chain.pub + export C2PIE_PRIVATE_KEY_FILE=private_key.key + export C2PIE_CERTIFICATE_CHAIN_FILE=certificate_chain.pem ``` 4) c2pie package installed in your current environment: From 4b49d540ccceeba6661a1b80a1137ece12e35237 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:52:48 +0500 Subject: [PATCH 34/69] docs(readme): fix numbers in list --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9a4a559..d179e1b1 100644 --- a/README.md +++ b/README.md @@ -328,9 +328,9 @@ docker run --rm -it --entrypoint bash --name c2pie-validate rust:1.90.0-bullseye cargo install c2patool ``` -1. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container)and copied to your working directory: +3. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container)and copied to your working directory: - **In a separate terminal**, copy it into the Rust container: + **In a separate terminal**, copy the signed image into the Rust container: ```bash docker cp ./signed_test_image.jpg c2pie-validate:signed_test_image.jpg ``` From 2782d1d95afaccfa905e7aa5e24aea448dac65eb Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:10:07 +0500 Subject: [PATCH 35/69] docs(readme): change days amount in signing certificate command --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d179e1b1..19569120 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ openssl genpkey \ openssl req -new -x509 \ -key private_key.key \ --sha256 -days 825 \ +-sha256 -days 365 \ -subj "/C=US/ST=CA/L=Somewhere/O=C2PA Test Signing Cert/OU=FOR TESTING_ONLY/CN=C2PA PSS Signer/emailAddress=pie@example.com" \ -addext "basicConstraints=critical,CA:false" \ -addext "keyUsage=critical,digitalSignature,nonRepudiation" \ @@ -396,7 +396,7 @@ You can generate your own private key and certificate chain pair for testing the ```bash openssl req -new -x509 \ -key private_key.key \ - -sha256 -days 825 \ + -sha256 -days 365 \ -subj "/C=US/ST=CA/L=Somewhere/O=C2PA Test Signing Cert/OU=FOR TESTING_ONLY/CN=C2PA PSS Signer/emailAddress=pie@example.com" \ -addext "basicConstraints=critical,CA:false" \ -addext "keyUsage=critical,digitalSignature,nonRepudiation" \ From 80c408612a95f5aaf5b2dd7cd8475a1bc8f6456d Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:55:19 +0500 Subject: [PATCH 36/69] infra: add pdf signing and validation --- .github/workflows/test-readme-steps.yml | 52 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 3798897c..7f195cc6 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -6,12 +6,13 @@ on: - feature/* jobs: - sign-image-steps: + sign-files-steps: runs-on: ubuntu-24.04 container: python:3.9.2-buster steps: - - name: Generate Certificates, Install c2pie, and Sign Image + - name: Generate Certificates, Install c2pie, and Sign Image and PDF run: | + # Generate private key and certificate chain: openssl genpkey \ -algorithm RSA-PSS \ -pkeyopt rsa_keygen_bits:2048 \ @@ -29,23 +30,35 @@ jobs: -addext "extendedKeyUsage=critical,emailProtection" \ -out certificate_chain.pem - pip install c2pie - - wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_image.jpg - + # Export created private key and certificate chain files into env variables: export C2PIE_PRIVATE_KEY_FILE=./private_key.key export C2PIE_CERTIFICATE_CHAIN_FILE=./certificate_chain.pem + # Install package + pip install c2pie + + # Download test image and PDF from this repo + wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_image.jpg + wget https://raw.githubusercontent.com/TourmalineCore/c2pie/refs/heads/master/example_app/test_files/test_doc.pdf + + # Sign files c2pie sign --input_file ./test_image.jpg + c2pie sign --input_file ./test_doc.pdf - name: Upload Signed Image uses: actions/upload-artifact@v4 with: name: signed_test_image path: signed_test_image.jpg + + - name: Upload Signed PDF + uses: actions/upload-artifact@v4 + with: + name: signed_test_doc + path: signed_test_doc.pdf verify-image-steps: - needs: sign-image-steps + needs: sign-files-steps runs-on: ubuntu-24.04 steps: - name: Download Signed Image @@ -53,29 +66,38 @@ jobs: with: name: signed_test_image + - name: Download Signed PDF + uses: actions/download-artifact@v5 + with: + name: signed_test_doc + - name: Install Rust toolchain and c2patool uses: baptiste0928/cargo-install@v3 with: crate: c2patool - - name: Validate Signed Image + - name: Validate Signed Image and PDF run: | c2patool signed_test_image.jpg - echo "$(c2patool signed_test_image.jpg)" >> validation_results.json - + echo "$(c2patool signed_test_image.jpg)" >> image_validation_results.json + c2patool signed_test_doc.pdf + echo "$(c2patool signed_test_doc.pdf)" >> pdf_validation_results.json + - name: Download jq and Get validation results id: validation_check run: | sudo apt install jq - echo "validation_results=$(jq -r .validation_state validation_results.json)" >> $GITHUB_OUTPUT + echo "image_validation_results=$(jq -r .validation_state image_validation_results.json)" >> $GITHUB_OUTPUT + echo "pdf_validation_results=$(jq -r .validation_state pdf_validation_results.json)" >> $GITHUB_OUTPUT - name: Debug validation check run: | - echo "Validation result extracted from file: ${{ steps.validation_check.outputs.validation_results }}" - + echo "Image validation result extracted from file: ${{ steps.validation_check.outputs.image_validation_results }}" + echo "PDF validation result extracted from file: ${{ steps.validation_check.outputs.pdf_validation_results }}" + - name: Fail if validation failed - if: ${{ steps.validation_check.outputs.validation_results == 'Invalid' }} + if: ${{ steps.validation_check.outputs.image_validation_results == 'Invalid' || steps.validation_check.outputs.pdf_validation_results == 'Invalid' }} run: | - echo "Invalid C2PA signature" + echo "Invalid C2PA signature in one of the files. Check logs of the previous step." exit 1 From a896642edaabd947aa7152d182897ed69e212967 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:03:47 +0500 Subject: [PATCH 37/69] docs(readme): update python version for test container image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19569120..cef06745 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ For more detailed feature specification, please look at the [Features](#-feature 1) Run a Docker container from a Python image: ```bash -docker run --rm -it --entrypoint bash --name c2pie-test python:3.12 +docker run --rm -it --entrypoint bash --name c2pie-test python:3.14 ``` 2) Inside the container execute the following bash commands: From cf468f38f8722d6d8ab48fdd5e7d721b3af0d72b Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:09:25 +0500 Subject: [PATCH 38/69] docs: move contributing to its separate file --- CONTRIBUTING.md | 92 ++++++++++++++++++++++++++++++++++++++++++ README.md | 105 ------------------------------------------------ 2 files changed, 92 insertions(+), 105 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..699be799 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,92 @@ +## First steps + +To contribute to the c2pie package development, you can use one of the following approaches **after cloning the repository**. + +### General principles + +🔸 Use Conventional Commits (e.g., `feat:`, `fix:`, `style(ruff):`, `ci:`). + +🔸 Run `Lint and Format` task before committing. + +🔸 Add unit tests for new behavior. + +### Using Dev Containers +1. Make sure you have installed Docker and [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) extension for VS code. + +2. Open the repo in VS Code and Reopen in Container. The container installs Python, Poetry, the package in editable mode, and configures Ruff as a default formatter, which provides linting and formatting and enables auto-fixing files on save (see `.devcontainer/devcontainer.json`). + +### Using a Local Environment + +>[!NOTE] +>We strongly recommend using Dev Containers in order to automatically create an isolated Python environment with all dependencies installed, environment variables exported and some helpful development tools included. + + +1. Make sure the environment you're currently in has Python and Poetry installed and their versions meet the requirements of the project. You can verify that by running: + + ```bash + python --version + poetry --version + ``` + +2. Go to the repository's folder in terminal and run: + ```bash + poetry install + ``` + This will automatically create and activate a poetry shell with project's dependencies installed. + + +3. To run any Python command related to the project's dependencies, remember to add `poetry run` in front of the command. For example: + ```bash + poetry run c2pie sign --input_file tests/test_files/test_doc.pdf + + poetry run ruff check + ``` +>[!WARNING] +> Commands in further sections don't include `poetry run` by default as they are intended to be run from a Dev Container. Remember to add `poetry run`. + + +## Run test applications + +To run test applications, you need to fill out `TEST_PDF_PATH` and/or `TEST_IMAGE_PATH` in values in *.env*. Test scripts use these filepaths as input files for signing. + +Also make sure that you have test certificate chain and public key in `tests/credentials`. They should be there by default if you've cloned the repository. If needed, you can change their filepaths in *.env* as well. + + +You can test the signing workflow with the following VS Code tasks: + +🔸 `Run JPG test application` + +🔸 `Run PDF test application` + +## Run tests + +Run from terminal: +```bash +pytest +``` + +Or use the VC Code task `Run unit tests`. Note that the task excludes the e2e test. + +Or if you'd like to get info on test coverage, use: +```bash +pytest --cov +``` + +## Lint & format + +You can check if there are any issues to deal with them manually: + +```bash +ruff format --check . +ruff check . +``` + +Or check and automatically fix where possible: +```bash +ruff format . +ruff check . --fix +``` + +The latter option is also available via the VC Code task `Lint and Format` + +
\ No newline at end of file diff --git a/README.md b/README.md index cef06745..e1bd4202 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,10 @@ For more detailed feature specification, please look at the [Features](#-feature + [🥧 Certificates](#-certificates) + [Generating test credentials](#generating-test-credentials) + [Getting credentials for production](#getting-credentials-for-production) -+ [🥧 For developers](#-for-developers) - + [First steps](#first-steps) - + [Using Dev Containers](#using-dev-containers) - + [Using a Local Environment](#using-a-local-environment) - + [Run test applications](#run-test-applications) - + [Run tests](#run-tests) - + [Lint \& format](#lint--format) + [🥧 Features](#-features) + [Workflow of test applications](#workflow-of-test-applications) + [Notes for PDF vs JPG/JPEG](#notes-for-pdf-vs-jpgjpeg) + [🥧 Relevant links](#-relevant-links) -+ [🥧 Contributing](#-contributing) + [🥧 License](#-license)
@@ -427,93 +419,6 @@ For detailed information on signing and certificates please explore the [corresp
-# 🥧 For developers - -## First steps - -To contribute to the c2pie package development, you can use one of the following approaches **after cloning the repository**. - -### Using Dev Containers -1. Make sure you have installed Docker and [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) extension for VS code. - -2. Open the repo in VS Code and Reopen in Container. The container installs Python, Poetry, the package in editable mode, and configures Ruff as a default formatter, which provides linting and formatting and enables auto-fixing files on save (see `.devcontainer/devcontainer.json`). - -### Using a Local Environment - ->[!NOTE] ->We strongly recommend using Dev Containers in order to automatically create an isolated Python environment with all dependencies installed, environment variables exported and some helpful development tools included. - - -1. Make sure the environment you're currently in has Python and Poetry installed and their versions meet the requirements of the project. You can verify that by running: - - ```bash - python --version - poetry --version - ``` - -2. Go to the repository's folder in terminal and run: - ```bash - poetry install - ``` - This will automatically create and activate a poetry shell with project's dependencies installed. - - -3. To run any Python command related to the project's dependencies, remember to add `poetry run` in front of the command. For example: - ```bash - poetry run c2pie sign --input_file tests/test_files/test_doc.pdf - - poetry run ruff check - ``` ->[!WARNING] -> Commands in further sections don't include `poetry run` by default as they are intended to be run from a Dev Container. Remember to add `poetry run`. - - -## Run test applications - -To run test applications, you need to fill out `TEST_PDF_PATH` and/or `TEST_IMAGE_PATH` in values in *.env*. Test scripts use these filepaths as input files for signing. - -Also make sure that you have test certificate chain and public key in `tests/credentials`. They should be there by default if you've cloned the repository. If needed, you can change their filepaths in *.env* as well. - - -You can test the signing workflow with the following VS Code tasks: - -🔸 `Run JPG test application` - -🔸 `Run PDF test application` - -## Run tests - -Run from terminal: -```bash -pytest -``` - -Or use the VC Code task `Run unit tests`. Note that the task excludes the e2e test. - -Or if you'd like to get info on test coverage, use: -```bash -pytest --cov -``` - -## Lint & format - -You can check if there are any issues to deal with them manually: - -```bash -ruff format --check . -ruff check . -``` - -Or check and automatically fix where possible: -```bash -ruff format . -ruff check . --fix -``` - -The latter option is also available via the VC Code task `Lint and Format` - -
- # 🥧 Features 🔸 C2PA Claim (`c2pa.claim`) with canonical CBOR, `dc:format`, `alg`, and hashed‑URIs for assertions. @@ -559,16 +464,6 @@ The library takes care of iterative sizing, so the `c2pa.hash.data` matches exac
-# 🥧 Contributing - -🔸 Use Conventional Commits (e.g., `feat:`, `fix:`, `style(ruff):`, `ci:`). - -🔸 Run `Lint and Format` task before committing. - -🔸 Add unit tests for new behavior. - -
- # 🥧 License Apache License. See [c2pie repository's license](LICENSE) for more information. From b7a80ffef144c5f60a1e69bc4f5cabe4f3b6d7e3 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:15:20 +0500 Subject: [PATCH 39/69] docs(readme): add instructions for switching between terminals --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1bd4202..a6775f37 100644 --- a/README.md +++ b/README.md @@ -320,13 +320,13 @@ docker run --rm -it --entrypoint bash --name c2pie-validate rust:1.90.0-bullseye cargo install c2patool ``` -3. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container)and copied to your working directory: +3. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container) and copied to your working directory: **In a separate terminal**, copy the signed image into the Rust container: ```bash docker cp ./signed_test_image.jpg c2pie-validate:signed_test_image.jpg ``` - Then validate the copied image with: + Then **go back to the container's terminal** and validate the copied image with: ```bash c2patool signed_test_image.jpg ``` From 70633e58d3f4a654570e6cb83f18fdf4d72c0148 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 10:19:55 +0500 Subject: [PATCH 40/69] docs(readme): clarify file placement --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a6775f37..af5a3605 100644 --- a/README.md +++ b/README.md @@ -320,9 +320,10 @@ docker run --rm -it --entrypoint bash --name c2pie-validate rust:1.90.0-bullseye cargo install c2patool ``` -3. To test the imaged [previously signed using a Docker container](#running-signing-from-a-docker-container) and copied to your working directory: +3. To validate the image [previously signed using a Docker container](#running-signing-from-a-docker-container) and copied to your working directory: - **In a separate terminal**, copy the signed image into the Rust container: + **In a separate terminal**, copy the signed image into the Rust container. + > Make sure the signed file is in the same directory that you're running this `docker cp` from! ```bash docker cp ./signed_test_image.jpg c2pie-validate:signed_test_image.jpg ``` From 4d241ccd02fe46cb183ad5608256487e97d2381b Mon Sep 17 00:00:00 2001 From: fpandyz <5389368+fpandyz@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:39:02 +0500 Subject: [PATCH 41/69] infra: rename jobs in test-readme-steps.yml workflow --- .github/workflows/test-readme-steps.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-readme-steps.yml b/.github/workflows/test-readme-steps.yml index 7f195cc6..b49ce035 100644 --- a/.github/workflows/test-readme-steps.yml +++ b/.github/workflows/test-readme-steps.yml @@ -6,7 +6,7 @@ on: - feature/* jobs: - sign-files-steps: + sign-steps: runs-on: ubuntu-24.04 container: python:3.9.2-buster steps: @@ -57,8 +57,8 @@ jobs: name: signed_test_doc path: signed_test_doc.pdf - verify-image-steps: - needs: sign-files-steps + verify-steps: + needs: sign-steps runs-on: ubuntu-24.04 steps: - name: Download Signed Image From ca254343b110a5111b31539eea8a812dd45db5f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:51:38 +0000 Subject: [PATCH 42/69] docs(readme): bring test coverage score up to date --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af5a3605..a4df4efa 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![CI](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml/badge.svg)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![c2pa](https://img.shields.io/badge/c2pa-v1.4-seagreen.svg)](https://c2pa.org/) -[![coverage](https://img.shields.io/badge/coverage-91%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +[![coverage](https://img.shields.io/badge/coverage-87%25-olivedrab?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![latest](https://img.shields.io/pypi/v/c2pie?label=latest&colorB=fc8021)](https://pypi.org/project/c2pie/)
From 13746d5e9167341d8d4b883b87681b8b06a200d7 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:03:26 +0500 Subject: [PATCH 43/69] docs(readme): add notes on current package status --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a4df4efa..47b48e1a 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,19 @@
-**c2pie** is an open‑source Python library for constructing [C2PA](https://c2pa.org/) Content Credentials manifests that validate with [`c2patool`](https://github.com/contentauth/c2pa-rs) and other common C2PA consumers. +`c2pie` is an open‑source Python library for constructing [C2PA](https://c2pa.org/) Content Credentials manifests that validate with [`c2patool`](https://github.com/contentauth/c2pa-rs) and other common C2PA consumers. -The package supports building claims, assertions, and COSE signatures and embedding the manifest store into JPG/JPEG and PDF files. +As far as we know, c2pie is **the world's first Python package** to implement signing PDF files according to the C2PA Standard. + +The package supports building claims, assertions, and COSE signatures and embedding the manifest store into JPG/JPEG and PDF files. 🔸 **Supported file extensions**: `JPG`, `JPEG`, `PDF` 🔸 **Supported Python versions**: `3.9.2 - 3.14.0` -🔸 **C2PA Spec Version**: `1.4` +🔸 **Supported C2PA Spec Versions**: `1.4`. + +Support for C2PA 2.2 is planned for future releases. For more detailed feature specification, please look at the [Features](#-features) section. From 06c94a24a5c2c9ca1f44d91523c7f8b0832608fd Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 23 Oct 2025 08:11:30 +0000 Subject: [PATCH 44/69] 0.1.0-rc.3 Automatically generated by python-semantic-release skip-checks: true --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baccaf54..7e65daa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,52 @@ +## v0.1.0-rc.3 (2025-10-23) + +### Config + +- **semantic-releases**: Add alpha prereleases group + ([`5f31999`](https://github.com/TourmalineCore/c2pie/commit/5f319995f12a9d292cd13e864e7a44a452ecbf6f)) + +### Documentation + +- Move contributing to its separate file + ([`cf468f3`](https://github.com/TourmalineCore/c2pie/commit/cf468f38f8722d6d8ab48fdd5e7d721b3af0d72b)) + +- **readme**: Add empty line dividers + ([`b547982`](https://github.com/TourmalineCore/c2pie/commit/b5479820c157b4076d5c40fb3a94097410d5b1e1)) + +- **readme**: Add instructions for switching between terminals + ([`b7a80ff`](https://github.com/TourmalineCore/c2pie/commit/b7a80ffef144c5f60a1e69bc4f5cabe4f3b6d7e3)) + +- **readme**: Add note on customizing signature subject info + ([`1bab57b`](https://github.com/TourmalineCore/c2pie/commit/1bab57bccdddf9b5a5178dcd6d5c7f3bf322b501)) + +- **readme**: Add notes on current package status + ([`13746d5`](https://github.com/TourmalineCore/c2pie/commit/13746d5e9167341d8d4b883b87681b8b06a200d7)) + +- **readme**: Bring test coverage score up to date + ([`ca25434`](https://github.com/TourmalineCore/c2pie/commit/ca254343b110a5111b31539eea8a812dd45db5f8)) + +- **readme**: Change days amount in signing certificate command + ([`2782d1d`](https://github.com/TourmalineCore/c2pie/commit/2782d1d95afaccfa905e7aa5e24aea448dac65eb)) + +- **readme**: Clarify file placement + ([`70633e5`](https://github.com/TourmalineCore/c2pie/commit/70633e58d3f4a654570e6cb83f18fdf4d72c0148)) + +- **readme**: Fix env vars filepaths to correspond with the example + ([`7c4a230`](https://github.com/TourmalineCore/c2pie/commit/7c4a230e5d7be13918f5b5f6c642c7f4eaea4e3f)) + +- **readme**: Fix links + ([`df5fac4`](https://github.com/TourmalineCore/c2pie/commit/df5fac4b7998d621c6e9ab1893b18fb651b34c05)) + +- **readme**: Fix numbers in list + ([`4b49d54`](https://github.com/TourmalineCore/c2pie/commit/4b49d540ccceeba6661a1b80a1137ece12e35237)) + +- **readme**: Update python version for test container image + ([`a896642`](https://github.com/TourmalineCore/c2pie/commit/a896642edaabd947aa7152d182897ed69e212967)) + + ## v0.1.0-rc.2 (2025-10-21) ### Features diff --git a/pyproject.toml b/pyproject.toml index 4ec04442..9dd6c91d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "c2pie" -version = "0.1.0-alpha.7" +version = "0.1.0-rc.3" [[tool.poetry.source]] name = "PyPI" From 5bf4c395c14708331a9886c6234a284188a631b5 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:19:06 +0500 Subject: [PATCH 45/69] config(pyproject): bump development status --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9dd6c91d..85c5c346 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ authors = [ license = {file = 'LICENSE'} requires-python = '>=3.9.2' classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", From ae50ad7321ec35f8a222a3e7d146af0e2755d2b0 Mon Sep 17 00:00:00 2001 From: lasnown <125271962+lasnown@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:19:32 +0500 Subject: [PATCH 46/69] chore(changelog): empty changelog --- CHANGELOG.md | 287 --------------------------------------------------- 1 file changed, 287 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e65daa5..e69de29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,287 +0,0 @@ -# CHANGELOG - - - -## v0.1.0-rc.3 (2025-10-23) - -### Config - -- **semantic-releases**: Add alpha prereleases group - ([`5f31999`](https://github.com/TourmalineCore/c2pie/commit/5f319995f12a9d292cd13e864e7a44a452ecbf6f)) - -### Documentation - -- Move contributing to its separate file - ([`cf468f3`](https://github.com/TourmalineCore/c2pie/commit/cf468f38f8722d6d8ab48fdd5e7d721b3af0d72b)) - -- **readme**: Add empty line dividers - ([`b547982`](https://github.com/TourmalineCore/c2pie/commit/b5479820c157b4076d5c40fb3a94097410d5b1e1)) - -- **readme**: Add instructions for switching between terminals - ([`b7a80ff`](https://github.com/TourmalineCore/c2pie/commit/b7a80ffef144c5f60a1e69bc4f5cabe4f3b6d7e3)) - -- **readme**: Add note on customizing signature subject info - ([`1bab57b`](https://github.com/TourmalineCore/c2pie/commit/1bab57bccdddf9b5a5178dcd6d5c7f3bf322b501)) - -- **readme**: Add notes on current package status - ([`13746d5`](https://github.com/TourmalineCore/c2pie/commit/13746d5e9167341d8d4b883b87681b8b06a200d7)) - -- **readme**: Bring test coverage score up to date - ([`ca25434`](https://github.com/TourmalineCore/c2pie/commit/ca254343b110a5111b31539eea8a812dd45db5f8)) - -- **readme**: Change days amount in signing certificate command - ([`2782d1d`](https://github.com/TourmalineCore/c2pie/commit/2782d1d95afaccfa905e7aa5e24aea448dac65eb)) - -- **readme**: Clarify file placement - ([`70633e5`](https://github.com/TourmalineCore/c2pie/commit/70633e58d3f4a654570e6cb83f18fdf4d72c0148)) - -- **readme**: Fix env vars filepaths to correspond with the example - ([`7c4a230`](https://github.com/TourmalineCore/c2pie/commit/7c4a230e5d7be13918f5b5f6c642c7f4eaea4e3f)) - -- **readme**: Fix links - ([`df5fac4`](https://github.com/TourmalineCore/c2pie/commit/df5fac4b7998d621c6e9ab1893b18fb651b34c05)) - -- **readme**: Fix numbers in list - ([`4b49d54`](https://github.com/TourmalineCore/c2pie/commit/4b49d540ccceeba6661a1b80a1137ece12e35237)) - -- **readme**: Update python version for test container image - ([`a896642`](https://github.com/TourmalineCore/c2pie/commit/a896642edaabd947aa7152d182897ed69e212967)) - - -## v0.1.0-rc.2 (2025-10-21) - -### Features - -- **main**: Add version option to c2pie command - ([`8449489`](https://github.com/TourmalineCore/c2pie/commit/8449489f50f123edb3987ba1ce17513fd1cf8f4b)) - - -## v0.1.0-rc.1 (2025-10-20) - -### Documentation - -- **readme**: Add note on c2pie version - ([`3726a77`](https://github.com/TourmalineCore/c2pie/commit/3726a77c113c4dff2b344897af5f8b68521994dd)) - -- **readme**: Change latest version reference - ([`6409e05`](https://github.com/TourmalineCore/c2pie/commit/6409e05957922b98883326c6d8c78af0f8b1346e)) - -- **readme**: Remove note of package version in descr, change badge label - ([`a6efaf9`](https://github.com/TourmalineCore/c2pie/commit/a6efaf9f35ce21db4b041d97fd3fca8ad6d8445b)) - -### Features - -- **example-app**: Add ability to set c2pie version that should be used in example app - ([`c9c95eb`](https://github.com/TourmalineCore/c2pie/commit/c9c95eb4c50f756735d9c0c71ea65d673363da04)) - - -## v0.1.0-alpha.6 (2025-10-17) - -### Bug Fixes - -- **example-app**: Switch to installing c2pie as a pypi package - ([`aff1f38`](https://github.com/TourmalineCore/c2pie/commit/aff1f38e93549ca09ae9529af529c1d2eb5c27a6)) - -### Documentation - -- **readme**: Add latest version mentioning - ([`d78cd45`](https://github.com/TourmalineCore/c2pie/commit/d78cd45d77379e951dea7465e8c2bf5ea8ca1430)) - -### Features - -- **semantic-release**: Try to exclude semantic-release commits from changelog - ([`ebf5a54`](https://github.com/TourmalineCore/c2pie/commit/ebf5a549ee63480af4f619edaa88fefa088ad308)) - -- **semantic-release**: Try to exclude workflows commits from changelog - ([`7f196fb`](https://github.com/TourmalineCore/c2pie/commit/7f196fb51654c7115ca0260ffa791d6e675c5b75)) - - -## v0.1.0-alpha.5 (2025-10-16) - -### Bug Fixes - -- **semantic-releases**: Fix workflows not being ignored in changelog - ([`65c7432`](https://github.com/TourmalineCore/c2pie/commit/65c7432e4762074302c108f125cd2eb3c65276c3)) - -- **workflows**: Fix workflows still triggering on push at the same time as release - ([`9469203`](https://github.com/TourmalineCore/c2pie/commit/94692034e1a8c164ce53256a0d23c70ce3a4c227)) - - -## v0.1.0-alpha.4 (2025-10-16) - -### Bug Fixes - -- **workflows**: Add debug - ([`6c28056`](https://github.com/TourmalineCore/c2pie/commit/6c280564995d0794d321cd44a3c650e7ddd6d918)) - -- **workflows**: Add debuging and fix option for grep - ([`2b3c9ef`](https://github.com/TourmalineCore/c2pie/commit/2b3c9efd799329f63c7744a4f91a12bacac8e80f)) - -- **workflows**: Add processing url non-existence - ([`01a544f`](https://github.com/TourmalineCore/c2pie/commit/01a544faad87e0cdb13274f9e9d1f48e12585e38)) - -- **workflows**: Add sudo in download jq step - ([`fc01ea3`](https://github.com/TourmalineCore/c2pie/commit/fc01ea3b7c4309ce3efeaa519d6140a0e6a26ae4)) - -- **workflows**: Bring back env vars names - ([`15d0349`](https://github.com/TourmalineCore/c2pie/commit/15d0349ce0a6866e6a321527df084693478c5994)) - -- **workflows**: Bring back exporting vars - ([`55b5418`](https://github.com/TourmalineCore/c2pie/commit/55b5418371b84abb59a06bc2191b4c3694c3841e)) - -- **workflows**: Change way of accessing environment variables - ([`5b4e501`](https://github.com/TourmalineCore/c2pie/commit/5b4e5015dce34e2846232bc67f1efb52f7dad124)) - -- **workflows**: Convert str to int - ([`69b1f73`](https://github.com/TourmalineCore/c2pie/commit/69b1f73948395951e7e6b54842ac2aafd1aa7ea7)) - -- **workflows**: Correct syntax according to github actions rules - ([`041d51b`](https://github.com/TourmalineCore/c2pie/commit/041d51bef3f514c5bc061e382f5ac19ba5b9acd4)) - -- **workflows**: Export variables to github_env - ([`ee93cfd`](https://github.com/TourmalineCore/c2pie/commit/ee93cfd7dd302a59765ca94c368eb2e1bf7bf689)) - -- **workflows**: Fix combined coverage treated as dir - ([`6dc092a`](https://github.com/TourmalineCore/c2pie/commit/6dc092a257604e2ce0812adbb88785124876ef27)) - -- **workflows**: Fix finding current color - ([`7a28fe8`](https://github.com/TourmalineCore/c2pie/commit/7a28fe80e5504bd4100a33b37fe2cf8c1c8e69f5)) - -- **workflows**: Fix string replacement - ([`72bcdae`](https://github.com/TourmalineCore/c2pie/commit/72bcdaea479d1c9151c6d35a05f21ed7227cd529)) - -- **workflows**: Fix workflow permissions - ([`cf7fb7b`](https://github.com/TourmalineCore/c2pie/commit/cf7fb7b24d86316dbfa6b76405bb41b0743a0813)) - -- **workflows**: Remove escaping, add quotes to github env - ([`46a920a`](https://github.com/TourmalineCore/c2pie/commit/46a920a9f1328edc385e37e9ec1fb9551b20206e)) - -- **workflows**: Try replacing score with a python script - ([`5d3bb32`](https://github.com/TourmalineCore/c2pie/commit/5d3bb3208486451d2ca3c39893ebb9e503f4ccae)) - -- **workflows**: Use double quotes and fix sed - ([`2c1e8fd`](https://github.com/TourmalineCore/c2pie/commit/2c1e8fd7cb42e86368b69b1229354d2d3b4fe866)) - -### Documentation - -- **readme**: Change links to banner - ([`3ce2606`](https://github.com/TourmalineCore/c2pie/commit/3ce26060905d8acc0f13dcfa241dc3b379e4c95c)) - -- **readme**: Fix incorrect badge rendering - ([`95de4ec`](https://github.com/TourmalineCore/c2pie/commit/95de4ec92b3dd99fb8d9a4e1e4f0cb348048740c)) - -- **readme**: Fix invalid linking - ([`6aab921`](https://github.com/TourmalineCore/c2pie/commit/6aab9216c9341e0d9fadc86eaf664769172c30a1)) - -- **readme**: Fix not complete url - ([`24b768d`](https://github.com/TourmalineCore/c2pie/commit/24b768db20a749e120a5aa5cb8a2be874fecb614)) - -- **readme**: Fix url to badge - ([`f263901`](https://github.com/TourmalineCore/c2pie/commit/f2639018a511c28a5255d52a7a7a953d7e41a76f)) - -### Features - -- **workflows**: Add badge color replacement - ([`e249736`](https://github.com/TourmalineCore/c2pie/commit/e249736f15c0b3c0f8d8f3ec5510dd1ae657462a)) - -- **workflows**: Update coverage badge dynamically - ([`8554847`](https://github.com/TourmalineCore/c2pie/commit/85548474f7c415b7f811081af03e7d99ea3d90f9)) - -### Refactoring - -- **workflows**: Add comments to steps - ([`9ab3912`](https://github.com/TourmalineCore/c2pie/commit/9ab391257ef3c4b6fd2ae1b916ab7e14ec8a048b)) - -- **workflows**: Move git operations to a separate step - ([`267683b`](https://github.com/TourmalineCore/c2pie/commit/267683b384dbfbd94b29a80c5ca48e16cc17d4b5)) - -- **workflows**: Use better name for a job - ([`bb88ee3`](https://github.com/TourmalineCore/c2pie/commit/bb88ee33f4bb84a62366456149175636e4b8e4b5)) - - -## v0.1.0-alpha.3 (2025-10-14) - -### Features - -- **workflows**: Remove multi-platform build - ([`c7cb49e`](https://github.com/TourmalineCore/c2pie/commit/c7cb49e644aa93b40714d66598fe3dd290937510)) - - -## v0.1.0-alpha.2 (2025-10-14) - -### Bug Fixes - -- **c2pie**: Replace manifest label value with urn:uuid - ([`7249501`](https://github.com/TourmalineCore/c2pie/commit/7249501d8743747012620ffb4fc3b606d79e88d1)) - -- **tests**: Add sample jpeg - ([`ebd91d2`](https://github.com/TourmalineCore/c2pie/commit/ebd91d29757e272c0c7c1849ed120865802facc5)) - -- **workflows**: Add cargo-install cache-key inside rust cache action - ([`32069c8`](https://github.com/TourmalineCore/c2pie/commit/32069c899b30f0a250eb67e538b33d36e2d53aaf)) - -- **workflows**: Define bash as default shell - ([`bfda110`](https://github.com/TourmalineCore/c2pie/commit/bfda11072884019892e3a01049696be7476e3bcd)) - -- **workflows**: Fix coverage still skipping - ([`14a09d3`](https://github.com/TourmalineCore/c2pie/commit/14a09d35525a13c93845b9ab4e8518864720ad18)) - -- **workflows**: Fix poetry not found on windowa - ([`cb55887`](https://github.com/TourmalineCore/c2pie/commit/cb558870f894ea144681cf10059884c1e7d6a1da)) - -- **workflows**: Move run command to a separate step - ([`7d6bd96`](https://github.com/TourmalineCore/c2pie/commit/7d6bd960ce25f3431f8ff96e83186d7b410fc433)) - -- **workflows**: Move to github runners from self-hosted - ([`184d319`](https://github.com/TourmalineCore/c2pie/commit/184d3196d984622c5bb58600c2229cda52a58f28)) - -- **workflows**: Remove extra rust caching - ([`13e8e4c`](https://github.com/TourmalineCore/c2pie/commit/13e8e4c5a895f0c8ca5da165d1d22b169c3c7487)) - -- **workflows**: Remove space - ([`4585f3a`](https://github.com/TourmalineCore/c2pie/commit/4585f3adaea8d7000cfe3a187195ca9e920e05ff)) - -- **workflows**: Remove unnecessary condition - ([`e83be02`](https://github.com/TourmalineCore/c2pie/commit/e83be0246970737f260401366d0c3a028519cc47)) - -- **workflows**: Use another action version - ([`947edc5`](https://github.com/TourmalineCore/c2pie/commit/947edc5dd3cc1dfad64e887671690baeff47a3d0)) - -- **workflows**: Use cargo-install action instead of manual installation - ([`e23ce3e`](https://github.com/TourmalineCore/c2pie/commit/e23ce3e7a3aeb19678d908657a31402632928b09)) - -- **workflows**: Use updated lint and test workflow - ([`4cbfd4f`](https://github.com/TourmalineCore/c2pie/commit/4cbfd4ff2c316728b8e66391f85fcf58b42548ce)) - -### Documentation - -- **readme**: Add badge with c2pa version - ([`2b4c974`](https://github.com/TourmalineCore/c2pie/commit/2b4c974b1eac397c9576bb0c8d344cba3ef79572)) - -- **readme**: Add c2pa version and style quotes - ([`249fbb1`](https://github.com/TourmalineCore/c2pie/commit/249fbb1cb14fa2dc5e8ae6ab9fd3560f553eda47)) - -- **readme**: Add coverage badge - ([`128b3dc`](https://github.com/TourmalineCore/c2pie/commit/128b3dcafe91fe439ed20dce3ae307fde5b24feb)) - -- **readme**: Remove coverage badge - ([`924dc22`](https://github.com/TourmalineCore/c2pie/commit/924dc221b1a7408e1e8cc63900106bd9188d589f)) - -- **readme**: Replace manifest labels in validation example - ([`2b7e686`](https://github.com/TourmalineCore/c2pie/commit/2b7e686ef05e04a6ca7767511991cc36e2fb43bb)) - -### Features - -- **pyproject**: Disable build for sem-rel and update commit parsing - ([`5aa3122`](https://github.com/TourmalineCore/c2pie/commit/5aa31228bcd205c15c79581c9a66115945a7f179)) - -- **workflows**: Add support for multi-platform distribution - ([`85c002e`](https://github.com/TourmalineCore/c2pie/commit/85c002e63617629039e2600ecb4bec3a18e7560f)) - -- **workflows**: Make coverage job not skip if some tests weren't successful - ([`36f6904`](https://github.com/TourmalineCore/c2pie/commit/36f690408bdd3d73db5e996d228608f32d10c77f)) - - -## v0.1.0-alpha.1 (2025-10-10) - -- Initial Release From 61b4b7b282554ffacd756ca0257d6fb69aa26dca Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:31:04 -0700 Subject: [PATCH 47/69] fix(ci): #53: change poetry version into install poetry step so that fix the installation error in python 3.9 (#54) * fix(ci): #53: change poetry version into install poetry step so that fix the installation error in python 3.9 * fix(ci): #53: change poetry version into install poetry step in other jobs so that fix the installation error in python 3.9 --------- Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> --- .github/workflows/lint-and-test.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index d8130082..ec23c9d1 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -36,7 +36,9 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: - version: 'latest' + # If you use a version newer than 2.2.1, the Poetry installation will fail because, + # starting with version 2.3.0, Poetry no longer supports Python 3.9 + version: '2.2.1' virtualenvs-create: true virtualenvs-in-project: true @@ -88,7 +90,9 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: - version: 'latest' + # If you use a version newer than 2.2.1, the Poetry installation will fail because, + # starting with version 2.3.0, Poetry no longer supports Python 3.9 + version: '2.2.1' virtualenvs-create: true virtualenvs-in-project: true @@ -147,7 +151,9 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: - version: 'latest' + # If you use a version newer than 2.2.1, the Poetry installation will fail because, + # starting with version 2.3.0, Poetry no longer supports Python 3.9 + version: '2.2.1' virtualenvs-create: true virtualenvs-in-project: true From b676c92348939197d83689adb2cb94614551dbc6 Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Thu, 23 Apr 2026 04:24:32 -0700 Subject: [PATCH 48/69] fix(gitattributes): #51: correcting end of lines, change target of .gitattributes (#52) Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 8218efdb..94f480de 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -*.sh eol=lf \ No newline at end of file +* text=auto eol=lf \ No newline at end of file From 68ba654378bfad2a584fbcf2d587829fa6adf76e Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Wed, 6 May 2026 11:14:21 +0500 Subject: [PATCH 49/69] feat(actions-assertion): #55: add implementation of action assertion (#57) * feat(actions-assertion): #55: add implementation of actions assertion * test(actions-assertion): #55: add tests for Actions Assertion * fix: #55: convert line endings from CRLF to LF * docs(actions-assertion): #55: correct of the comment to reflect the facts * docs(readme): bring test coverage score up to date * refactor(actions-assertion): #55: move Actions assertion tests to separate test file so that to split the tests among yourselves * refactor(actions-assertion): #55: formatting correction * fix(actions-assertion): #55: fix a formatting-related error * refactor(actions-assertion): #55: simplification of the condition * feat(actions-assertion): #55: add a validation step for valid values in the Actions assertion constructor to prevent the creation of an unsupported action * docs(readme): bring test coverage score up to date --------- Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- c2pie/c2pa/assertion.py | 25 +++++++++ c2pie/interface.py | 9 ++- c2pie/signing.py | 7 ++- c2pie/utils/assertion_schemas.py | 7 +++ .../c2pa/assertions/actions_assertion_test.py | 56 +++++++++++++++++++ tests/c2pa/{ => assertions}/assertion_test.py | 0 6 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/c2pa/assertions/actions_assertion_test.py rename tests/c2pa/{ => assertions}/assertion_test.py (100%) diff --git a/c2pie/c2pa/assertion.py b/c2pie/c2pa/assertion.py index f48c7082..f049dd03 100644 --- a/c2pie/c2pa/assertion.py +++ b/c2pie/c2pa/assertion.py @@ -14,6 +14,8 @@ ) from c2pie.utils.content_types import jumbf_content_types +_ALLOWED_ACTIONS = ["c2pa.created", "c2pa.opened"] + class Assertion(SuperBox): """Universal assertion superbox (one content box).""" @@ -94,3 +96,26 @@ def set_hash_data_length(self, length: int) -> None: ) ] self.sync_payload() + + +class ActionsAssertion(Assertion): + """c2pa.actions.v2 assertion of actions on an asset.""" + + def __init__( + self, + action: str, + parameters: dict[str, list[dict[str, Any]]] | None = None, + ): + if action not in _ALLOWED_ACTIONS: + raise ValueError(f"Invalid action {action!r}. Must be one of: {_ALLOWED_ACTIONS}") + + schema: dict[str, Any] = { + "actions": [ + {"action": action}, + ], + } + + if parameters: + schema["actions"][0]["parameters"] = parameters + + super().__init__(C2PA_AssertionTypes.actions, schema) diff --git a/c2pie/interface.py b/c2pie/interface.py index 59255301..cb06c923 100644 --- a/c2pie/interface.py +++ b/c2pie/interface.py @@ -2,7 +2,7 @@ import uuid -from c2pie.c2pa.assertion import Assertion, HashDataAssertion +from c2pie.c2pa.assertion import ActionsAssertion, Assertion, HashDataAssertion from c2pie.c2pa.assertion_store import AssertionStore from c2pie.c2pa.claim import Claim from c2pie.c2pa.claim_signature import ClaimSignature @@ -23,6 +23,13 @@ def c2pie_GenerateHashDataAssertion(cai_offset: int, hashed_data: bytes) -> Hash return HashDataAssertion(cai_offset, hashed_data) +def c2pie_GenerateActionsAssertion( + action: str, + parameters: dict[str, list[dict[str, str]]] | None = None, +) -> ActionsAssertion: + return ActionsAssertion(action, parameters) + + def c2pie_GenerateManifest( assertions: list, private_key: bytes, diff --git a/c2pie/signing.py b/c2pie/signing.py index 95e6ccb3..89946daa 100644 --- a/c2pie/signing.py +++ b/c2pie/signing.py @@ -9,6 +9,7 @@ from c2pie.interface import ( C2PA_AssertionTypes, c2pie_EmplaceManifest, + c2pie_GenerateActionsAssertion, c2pie_GenerateAssertion, c2pie_GenerateHashDataAssertion, c2pie_GenerateManifest, @@ -208,7 +209,11 @@ def sign_file( cai_offset=cai_offset, hashed_data=hashlib.sha256(raw_bytes).digest() ) - assertions = [creative_work_assertion, hash_data_assertion] + # This section should be replaced with the content generation logic once the relevant + # functionality is available (example, action 'c2pa.opened' for Ingredient Assertion) + actions_assertion = c2pie_GenerateActionsAssertion(action="c2pa.created") + + assertions = [creative_work_assertion, hash_data_assertion, actions_assertion] manifest = c2pie_GenerateManifest( assertions=assertions, diff --git a/c2pie/utils/assertion_schemas.py b/c2pie/utils/assertion_schemas.py index 8740210d..2244fed3 100644 --- a/c2pie/utils/assertion_schemas.py +++ b/c2pie/utils/assertion_schemas.py @@ -11,6 +11,7 @@ class C2PA_AssertionTypes(enum.Enum): creative_work = 0 data_hash = 1 thumbnail = 2 + actions = 3 def json_to_bytes(json_object: dict[str, Any]) -> bytes: @@ -28,6 +29,8 @@ def get_assertion_content_type(assertion_type: C2PA_AssertionTypes) -> bytes: return jumbf_content_types["cbor"] elif assertion_type == C2PA_AssertionTypes.thumbnail: return jumbf_content_types["codestream"] + elif assertion_type == C2PA_AssertionTypes.actions: + return jumbf_content_types["cbor"] else: return b"" @@ -39,6 +42,8 @@ def get_assertion_content_box_type(assertion_type: C2PA_AssertionTypes) -> str: return b"cbor".hex() elif assertion_type == C2PA_AssertionTypes.thumbnail: return b"codestream".hex() # figure out which content type should be + elif assertion_type == C2PA_AssertionTypes.actions: + return b"cbor".hex() else: return b"".hex() @@ -50,5 +55,7 @@ def get_assertion_label(assertion_type: C2PA_AssertionTypes) -> str: return "c2pa.hash.data" elif assertion_type == C2PA_AssertionTypes.thumbnail: return "c2pa.thumbnail.claim.jpg" + elif assertion_type == C2PA_AssertionTypes.actions: + return "c2pa.actions.v2" else: return "" diff --git a/tests/c2pa/assertions/actions_assertion_test.py b/tests/c2pa/assertions/actions_assertion_test.py new file mode 100644 index 00000000..e8a3b1ad --- /dev/null +++ b/tests/c2pa/assertions/actions_assertion_test.py @@ -0,0 +1,56 @@ +from c2pie.c2pa.assertion import ActionsAssertion +from c2pie.utils.assertion_schemas import cbor_to_bytes +from c2pie.utils.content_types import jumbf_content_types + + +def test_actions_assertion_content_type_is_cbor(): + actions_assertion = ActionsAssertion(action="c2pa.created") + assert actions_assertion.get_content_type() == jumbf_content_types["cbor"] + + +def test_actions_assertion_label(): + actions_assertion = ActionsAssertion(action="c2pa.created") + assert actions_assertion.get_label() == "c2pa.actions.v2" + + +def test_actions_assertion_schema_fields(): + actions_assertion = ActionsAssertion(action="c2pa.created") + assert actions_assertion.schema["actions"][0]["action"] == "c2pa.created" + + +def test_actions_assertion_schema_has_no_parameters_by_default(): + actions_assertion = ActionsAssertion(action="c2pa.created") + assert "parameters" not in actions_assertion.schema["actions"][0] + + +def test_actions_assertion_schema_fields_with_parameters(): + expected_parameters = { + "ingredients": [ + { + "url": "self#jumbf=c2pa.assertions/c2pa.ingredient.v3", + "alg": "sha256", + "hash": "B-X0kqzQsdIOC9ZxU7M/FRaaLlQ8Ap1U95//TucEjkT=", + } + ] + } + + actions_assertion = ActionsAssertion(action="c2pa.opened", parameters=expected_parameters) + assert actions_assertion.schema["actions"][0]["action"] == "c2pa.opened" + assert actions_assertion.schema["actions"][0]["parameters"] == expected_parameters + + +def test_actions_assertion_serializes_as_cbor(): + actions_assertion = ActionsAssertion(action="c2pa.created") + + expected_payload = cbor_to_bytes( + { + "actions": [ + { + "action": "c2pa.created", + }, + ], + }, + ) + + assert len(actions_assertion.content_boxes) == 1 + assert actions_assertion.content_boxes[0].payload == expected_payload diff --git a/tests/c2pa/assertion_test.py b/tests/c2pa/assertions/assertion_test.py similarity index 100% rename from tests/c2pa/assertion_test.py rename to tests/c2pa/assertions/assertion_test.py From 88f9abefc181a6a10be9f9c76065b2f13891ee31 Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Wed, 6 May 2026 11:15:29 +0500 Subject: [PATCH 50/69] feat(hash-uri-map): #56: add generate hash uri map function (#58) * feat(hash-uri-map): #56: add implementation of generate hashed uri map function * test(hash-uri-map): #56: add tests for generate hashed uri map function * refactor(hash-uri-map): #56: formatting corrections --------- Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> --- c2pie/signing.py | 16 ++++++++++++++++ tests/c2pa/signing_test.py | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/c2pa/signing_test.py diff --git a/c2pie/signing.py b/c2pie/signing.py index 89946daa..e059c31f 100644 --- a/c2pie/signing.py +++ b/c2pie/signing.py @@ -171,6 +171,22 @@ def _load_certificates_and_key( return key, certificates +def _generate_hashed_uri_map( + url: str, + hash_value: bytes, + hash_algorithm: str | None = None, +) -> dict[str, str | bytes]: + result: dict[str, str | bytes] = { + "url": url, + "hash": hash_value, + } + + if hash_algorithm: + result["alg"] = hash_algorithm + + return result + + def sign_file( input_path: Path | str, output_path: Path | str | None = None, diff --git a/tests/c2pa/signing_test.py b/tests/c2pa/signing_test.py new file mode 100644 index 00000000..63522ecf --- /dev/null +++ b/tests/c2pa/signing_test.py @@ -0,0 +1,21 @@ +from c2pie.signing import _generate_hashed_uri_map + + +def test_generate_hashed_uri_map_required_fields(): + result = _generate_hashed_uri_map(url="self#jumbf=c2pa.assertions/c2pa.ingredient.v3", hash_value=b"\x01\x02\x03") + assert result["url"] == "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" + assert result["hash"] == b"\x01\x02\x03" + + +def test_generate_hashed_uri_map_no_alg_by_default(): + result = _generate_hashed_uri_map(url="self#jumbf=c2pa.assertions/c2pa.ingredient.v3", hash_value=b"\x01\x02\x03") + assert "alg" not in result + + +def test_generate_hashed_uri_map_with_alg(): + result = _generate_hashed_uri_map( + url="self#jumbf=c2pa.assertions/c2pa.ingredient.v3", + hash_value=b"\x01\x02\x03", + hash_algorithm="sha256", + ) + assert result["alg"] == "sha256" From 57430c592c067bc0bdd4e96f834cddb9ef19b1f1 Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Wed, 6 May 2026 11:15:44 +0500 Subject: [PATCH 51/69] fix: #62: fix for the library build auto update (#63) * fix: #62: update package versions so that the package can be linked to the source code * fix: #62: add the postCreateCommand to the devcontainer configuration to link the package to the project's source code * fix: #62: revert the changes and adjust the paths in the Dockerfile so that the package points to the correct directory --------- Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> --- .devcontainer/Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4eb9e100..3ee8aaac 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -16,9 +16,12 @@ ENV RUSTUP_HOME=/home/vscode/.rustup \ RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable && \ cargo install c2patool --locked -COPY . /c2pie/ - -WORKDIR c2pie +# If the configuration files are not copied, it will not +# be possible to install dependencies using poetry install +COPY . /workspaces/c2pie/ +WORKDIR /workspaces/c2pie/ +# --no-interaction - installing in non-interactive mode +# --no-ansi - disable ANSI codes RUN poetry config virtualenvs.create false \ && poetry lock && poetry install --no-interaction --no-ansi \ No newline at end of file From a4dbac589c7c48044532a6700ffa9c2868b10247 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 06:40:44 +0000 Subject: [PATCH 52/69] docs(readme): bring test coverage score up to date --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39ea1c3e..a7075320 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Linting and Testing](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml/badge.svg?branch=develop)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![c2pa](https://img.shields.io/badge/c2pa-v1.4-seagreen.svg)](https://c2pa.org/) -[![coverage](https://img.shields.io/badge/coverage-87%25-olivedrab?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +[![coverage](https://img.shields.io/badge/coverage-88%25-olivedrab?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![latest](https://img.shields.io/pypi/v/c2pie?label=latest&colorB=fc8021)](https://pypi.org/project/c2pie/)
From c8524ab154ee89c67bb83d5140c8e941cdd597a2 Mon Sep 17 00:00:00 2001 From: Artem Sheptunov <106321977+aasheptunov@users.noreply.github.com> Date: Wed, 6 May 2026 15:35:53 +0500 Subject: [PATCH 53/69] feat: #60: update the library to C2PA 2.4 (#61) * feat(actions-assertion): #55: add implementation of actions assertion * test(actions-assertion): #55: add tests for Actions Assertion * fix: #55: convert line endings from CRLF to LF * docs(actions-assertion): #55: correct of the comment to reflect the facts * docs(readme): bring test coverage score up to date * feat(claim): #60: update claim to C2PA v2.4 * refactor(creative-work): #60: removal of the CreativeWork assertion, as it has been deprecated in C2PA v2.4 * refactor(creative-work): #60: move Actions assertion tests to separate test file so that to split the tests among yourselves * fix: #60: correction of formatting errors * fix: #60: fix for an issue caused by differences in type definitions between Python versions 3.9 and 3.10+ * docs(readme): bring test coverage score up to date * feat(claim): #60: remove deprecated field and method; add a field containing the file name so that the validator displays the file name * feat(embedded-data-assertion): #60: replace the outdated codestream content type with embedded file; add Thumbnail and EmbeddedData assertions * feat(embedded-data-assertion): #60: add embedded-data assertion type * fix: #60: correction of formatting errors * test(embedded-data-assertion): #60: add tests for EmbeddedData assertion * test(assertion): #60: add tests for json_to_bytes function * test: #60: add a required parameter to the Claim constructor in tests; changing the value of the test variable to one that more closely reflects reality * test(interface): #60: add tests for interface functions * fix: #60: correction of formatting errors * refactor: #60: removal of the ability to provide an external c2pie schema, since the CreativeWork assertion has been removed * fix: #60: correction of formatting errors * refactor: #60: revision of the gathered_assertions addition condition * test: #60: add the missing test_ prefix to test cases * docs(readme): bring test coverage score up to date * reafctor: #60: changing the type of the received parameter to the correct one * refactor: #60: formatting corrections * fix: #60: fix a lint error by removing unused imports * feat(thumbnail-assertion): #60: add a separate class for the Thumbnail assertion * test: #60: add tests for thumbnail assertions; remove duplicate tests; move data hash assertion tests to a separate file * refactor: #60: formatting corrections --------- Co-authored-by: Artem Sheptunov <106321977+Infindery@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 + c2pie/c2pa/assertion.py | 80 +++++++++- c2pie/c2pa/claim.py | 87 ++++++----- c2pie/c2pa/manifest.py | 6 +- c2pie/interface.py | 25 ++- c2pie/main.py | 10 -- c2pie/signing.py | 85 +--------- c2pie/utils/assertion_schemas.py | 30 ++-- c2pie/utils/content_types.py | 2 +- tests/c2pa/assertion_store_test.py | 29 ++-- .../c2pa/assertions/actions_assertion_test.py | 7 +- tests/c2pa/assertions/assertion_test.py | 100 ++++++------ .../assertions/data_hash_assertion_test.py | 125 +++++++++++++++ .../embedded_data_assertion_test.py | 106 +++++++++++++ .../assertions/thumbnail_assertion_test.py | 121 +++++++++++++++ tests/c2pa/claim_signature_test.py | 42 ++--- tests/c2pa/claim_test.py | 73 ++++----- tests/c2pa/interface_test.py | 146 ++++++++++++++++++ 18 files changed, 792 insertions(+), 286 deletions(-) create mode 100644 tests/c2pa/assertions/data_hash_assertion_test.py create mode 100644 tests/c2pa/assertions/embedded_data_assertion_test.py create mode 100644 tests/c2pa/assertions/thumbnail_assertion_test.py create mode 100644 tests/c2pa/interface_test.py diff --git a/README.md b/README.md index a7075320..11ca4ec7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,11 @@ [![Linting and Testing](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml/badge.svg?branch=develop)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) [![c2pa](https://img.shields.io/badge/c2pa-v1.4-seagreen.svg)](https://c2pa.org/) +<<<<<<< feature/#60-update-the-library-to-c2pa-24 +[![coverage](https://img.shields.io/badge/coverage-91%25-forestgreen?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +======= [![coverage](https://img.shields.io/badge/coverage-88%25-olivedrab?logo=codecov&logoColor=ff9d1c)](https://github.com/TourmalineCore/c2pie/actions/workflows/lint-and-test.yml) +>>>>>>> develop [![latest](https://img.shields.io/pypi/v/c2pie?label=latest&colorB=fc8021)](https://pypi.org/project/c2pie/)
diff --git a/c2pie/c2pa/assertion.py b/c2pie/c2pa/assertion.py index f049dd03..e2dab536 100644 --- a/c2pie/c2pa/assertion.py +++ b/c2pie/c2pa/assertion.py @@ -24,28 +24,30 @@ def __init__( self, assertion_type: C2PA_AssertionTypes, schema: dict[str, Any], + content_boxes: list[ContentBox] | None = None, ): self.type = assertion_type self.schema = schema - payload = self.get_payload_from_schema() - box_type_hex = get_assertion_content_box_type(self.type) - content_box = ContentBox(box_type=box_type_hex, payload=payload) + if content_boxes is None: + payload = self.get_payload_from_schema() + box_type_hex = get_assertion_content_box_type(self.type) + content_boxes = [ContentBox(box_type=box_type_hex, payload=payload)] super().__init__( content_type=get_assertion_content_type(self.type), label=get_assertion_label(self.type), - content_boxes=[content_box], + content_boxes=content_boxes, ) def get_payload_from_schema(self) -> bytes: ctype = get_assertion_content_type(self.type) + if ctype == jumbf_content_types["json"]: return json_to_bytes(self.schema) - if ctype == jumbf_content_types["cbor"]: + elif ctype == jumbf_content_types["cbor"]: return cbor_to_bytes(self.schema) - if ctype == jumbf_content_types["codestream"]: - return self.schema.get("payload", b"") + return b"" def get_data_for_signing(self) -> bytes: @@ -119,3 +121,67 @@ def __init__( schema["actions"][0]["parameters"] = parameters super().__init__(C2PA_AssertionTypes.actions, schema) + + +class EmbeddedDataAssertion(Assertion): + """ + Embedded Data assertion, contains embedded data within the JUMBF Box. + + Can be used for the following assertions: + - c2pa.thumbnail.claim, + - c2pa.ingredient, + - c2pa.ingredient.thumbnail + - c2pa.embedded-data + + Structure: + JUMBF Super Box (jumb) + -> JUMBF Description Box (jumd) + -> Embedded File Description Box (bfdb) + -> Binary Data Box (bidb) + """ + + def __init__( + self, + media_type: str, + image_data: bytes, + assertion_type: C2PA_AssertionTypes = C2PA_AssertionTypes.embedded_data, + ): + # 0000 000x - Filename present? (0 - false, 1 - true) + # 0000 00x0 - What's inside bidb? (0 - binary data, 1 - URI) + # xxxx xx00- reserved + toggles_bytes = b"\x00" + + # IANA media type + null-terminate + media_type_bytes = media_type.encode("utf-8") + b"\x00" + + payload = toggles_bytes + media_type_bytes + + super().__init__( + assertion_type=assertion_type, + schema={}, + content_boxes=[ + ContentBox( + box_type=b"bfdb".hex(), # UUID Type of Embedded File Description Box + payload=payload, + ), + ContentBox( + box_type=b"bidb".hex(), # UUID Type of Binary Data Box + payload=image_data, + ), + ], + ) + + +class ThumbnailAssertion(EmbeddedDataAssertion): + """An assertion (c2pa.thumbnail.claim) containing an asset thumbnail""" + + def __init__( + self, + media_type: str, + image_data: bytes, + ): + super().__init__( + media_type=media_type, + image_data=image_data, + assertion_type=C2PA_AssertionTypes.thumbnail, + ) diff --git a/c2pie/c2pa/claim.py b/c2pie/c2pa/claim.py index cf9ff01a..a0d76131 100644 --- a/c2pie/c2pa/claim.py +++ b/c2pie/c2pa/claim.py @@ -11,29 +11,28 @@ from c2pie.jumbf_boxes.super_box import SuperBox from c2pie.utils.content_types import c2pa_content_types +_GATHERED_ASSERTIONS = {"c2pa.ingredient.v3", "c2pa.actions.v2"} + def _sha256(b: bytes) -> bytes: return hashlib.sha256(b).digest() class Claim(SuperBox): - """Claim (c2pa.claim) as a JUMBF superbox with one CBOR content box.""" + """Claim (c2pa.claim.v2) as a JUMBF superbox with one CBOR content box.""" def __init__( self, assertion_store: AssertionStore, - claim_generator: str = "c2pie", - manifest_label: str = f"urn:uuid:{uuid.uuid4().hex}", - dc_format: str = None, + manifest_label: str, + dc_title: str, ): - self.claim_generator = claim_generator self.manifest_label = manifest_label self.assertion_store = assertion_store - self.dc_format = dc_format + self.dc_title = dc_title self.claim_signature_label = f"self#jumbf=c2pa/{self.manifest_label}/c2pa.signature" - - self._instance_id = f"xmp:iid:{uuid.uuid4()}" + self.instance_id = f"xmp:iid:{uuid.uuid4()}" cbor_payload = self._build_cbor_payload() @@ -41,9 +40,10 @@ def __init__( box_type=b"cbor".hex(), payload=cbor_payload, ) + super().__init__( content_type=c2pa_content_types["claim"], - label="c2pa.claim", + label="c2pa.claim.v2", content_boxes=[content_box], ) @@ -62,26 +62,25 @@ def set_assertion_store(self, assertion_store) -> None: self.assertion_store = assertion_store self._rebuild_payload() - def set_format(self, dc_format: str | None) -> None: - self.dc_format = dc_format - self._rebuild_payload() - def _build_assertions_array(self) -> list[dict[str, Any]]: """ - Build the claim[‘assertions’] array from the current AssertionStore: + Build the claim["created_assertions"] and claim["gathered_assertions"] arrays from the current AssertionStore: - url: self#jumbf=/c2pa//c2pa.assertions/