diff --git a/.commitlintrc.yml b/.commitlintrc.yml index a0b1e4587..09c1eb3e1 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -24,6 +24,7 @@ rules: - export_schema - make_test_images - sdk + - c2patool # Scope may be empty # (NOTE: Disabled for now while we work around diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb94ab35c..a9e2ca14f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -175,6 +175,30 @@ jobs: fail_ci_if_error: true verbose: true + cargo-check: + name: Default features build + if: | + github.event_name != 'pull_request' || + github.event.pull_request.author_association == 'COLLABORATOR' || + github.event.pull_request.author_association == 'MEMBER' || + github.event.pull_request.user.login == 'dependabot[bot]' || + contains(github.event.pull_request.labels.*.name, 'safe to test') + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + + - name: "`cargo check` with default features" + run: cargo check + tests-cross: name: Unit tests if: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5aadfbe5b..d0df02853 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -147,9 +147,11 @@ jobs: echo Will add nightly suffix $NIGHTLY_SUFFIX sed -i "s/^version = \"\\(.*\\)\"/version = \"\\1$NIGHTLY_SUFFIX\"/" sdk/Cargo.toml - + sed -i "s/path = \"..\/sdk\", version = \"\\(.*\\)\"/path = \"..\/sdk\", version = \"\\1$NIGHTLY_SUFFIX\"/" cli/Cargo.toml + cargo update -w - git add -f Cargo.lock + git add Cargo.lock + find . -name 'Cargo.toml' | xargs git add echo echo Proposed changes: diff --git a/.github/workflows/pr_title.yml b/.github/workflows/pr_title.yml index 1942ae6f6..f18696113 100644 --- a/.github/workflows/pr_title.yml +++ b/.github/workflows/pr_title.yml @@ -42,6 +42,7 @@ jobs: # these exact names: # # * sdk (The primary C2PA Rust SDK) + # * c2patool # * export_schema # * make_test_images # @@ -90,6 +91,16 @@ jobs: exit 0; fi + if echo "$PR_TITLE" | grep -E '^update: update '; then + echo "Exception / OK: Dependabot update pattern" + exit 0; + fi + + if echo "$PR_TITLE" | grep -E '^update: bump '; then + echo "Exception / OK: Dependabot update pattern" + exit 0; + fi + echo "Installing commitlint-rs. Please wait 30-40 seconds ..." cargo install --quiet commitlint-rs set -e diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea301ae25..4a1789881 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,9 @@ jobs: name: Release-plz runs-on: ubuntu-latest + outputs: + c2patool-release-tag: ${{ steps.sniff-c2patool-release-tag.outputs.tag }} + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -25,7 +28,8 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Run release-plz - uses: MarcoIeni/release-plz-action@v0.5.85 + id: release-plz + uses: MarcoIeni/release-plz-action@v0.5.86 env: GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_SECRET }} @@ -38,76 +42,81 @@ jobs: tail -n +2 |\ sed 's/origin\///' |\ xargs -I {} git push origin --delete {} - - - name: Identify c2patool release tag + + - name: Identify c2patool release + id: sniff-c2patool-release-tag run: | - echo "TO DO: Identify c2patool release tag, if any" - exit 1 - - # publish-c2patool-binaries: - # name: Publish c2patool binaries - # runs-on: ${{ matrix.os }} - # needs: release-plz - - # strategy: - # fail-fast: false - # matrix: - # os: [ macos-latest, ubuntu-latest, windows-latest ] - # rust_version: [ stable ] - # experimental: [ false ] - # include: - # - os: macos-latest - # artifact_name: c2patool_mac_universal.zip - # uploaded_asset_name: c2patool-${{ needs.repo-prep.outputs.new-tag }}-universal-apple-darwin.zip - # - os: ubuntu-latest - # artifact_name: c2patool_linux_intel.tar.gz - # uploaded_asset_name: c2patool-${{ needs.repo-prep.outputs.new-tag }}-x86_64-unknown-linux-gnu.tar.gz - # - os: windows-latest - # artifact_name: c2patool_win_intel.zip - # uploaded_asset_name: c2patool-${{ needs.repo-prep.outputs.new-tag }}-x86_64-pc-windows-msvc.zip - - # steps: - # - name: Checkout repository - # uses: actions/checkout@v4 - # with: - # ref: ${{ needs.repo-prep.outputs.commit-hash }} - - # - name: Install Rust toolchain - # uses: dtolnay/rust-toolchain@master - # with: - # toolchain: ${{ matrix.rust_version }} - # components: llvm-tools-preview - - # - name: Install cargo-sbom - # uses: baptiste0928/cargo-install@v3 - # with: - # crate: cargo-sbom - # version: '0.9.1' - - # - name: Cache Rust dependencies - # uses: Swatinem/rust-cache@v2 - - # - name: Run make release - # run: make release - - # - name: Upload binary to GitHub - # uses: svenstaro/upload-release-action@v1-release - # with: - # repo_token: ${{ secrets.GITHUB_TOKEN }} - # file: target/${{ matrix.artifact_name }} - # asset_name: ${{ matrix.uploaded_asset_name }} - # tag: ${{ needs.repo-prep.outputs.new-tag }} - # overwrite: true - - # - name: Generate SBOM - # run: cargo sbom > c2patool.${{ matrix.os }}.sbom.json - - # - name: Upload SBOM to Github - # uses: svenstaro/upload-release-action@v1-release - # with: - # repo_token: ${{ secrets.GITHUB_TOKEN }} - # file: c2patool.${{ matrix.os }}.sbom.json - # asset_name: c2patool-${{ needs.repo-prep.outputs.new-tag }}-sbom.json - # tag: ${{ needs.repo-prep.outputs.new-tag }} - # overwrite: true + echo tag=`git tag --contains HEAD | grep '^c2patool-'` >> "$GITHUB_OUTPUT" || true + + publish-c2patool-binaries: + name: Publish c2patool binaries + runs-on: ${{ matrix.os }} + needs: release-plz + + strategy: + fail-fast: false + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + rust_version: [ stable ] + experimental: [ false ] + include: + - os: macos-latest + artifact_name: c2patool_mac_universal.zip + uploaded_asset_name: c2patool-${{ needs.release-plz.outputs.c2patool-release-tag }}-universal-apple-darwin.zip + - os: ubuntu-latest + artifact_name: c2patool_linux_intel.tar.gz + uploaded_asset_name: c2patool-${{ needs.release-plz.outputs.c2patool-release-tag }}-x86_64-unknown-linux-gnu.tar.gz + - os: windows-latest + artifact_name: c2patool_win_intel.zip + uploaded_asset_name: c2patool-${{ needs.release-plz.outputs.c2patool-release-tag }}-x86_64-pc-windows-msvc.zip + + steps: + - name: Checkout repository + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: actions/checkout@v4 + + - name: Install Rust toolchain + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust_version }} + components: llvm-tools-preview + + - name: Install cargo-sbom + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: baptiste0928/cargo-install@v3 + with: + crate: cargo-sbom + version: '0.9.1' + + - name: Cache Rust dependencies + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: Swatinem/rust-cache@v2 + - name: Run make release + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + run: cd cli && make release + + - name: Upload binary to GitHub + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: svenstaro/upload-release-action@v1-release + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: target/${{ matrix.artifact_name }} + asset_name: ${{ matrix.uploaded_asset_name }} + tag: ${{ needs.release-plz.outputs.c2patool-release-tag }} + overwrite: true + + - name: Generate SBOM + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + run: cd cli && cargo sbom > c2patool.${{ matrix.os }}.sbom.json + + - name: Upload SBOM to Github + if: ${{ needs.release-plz.outputs.c2patool-release-tag }} + uses: svenstaro/upload-release-action@v1-release + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: cli/c2patool.${{ matrix.os }}.sbom.json + asset_name: c2patool-${{ needs.release-plz.outputs.c2patool-release-tag }}-sbom.json + tag: ${{ needs.release-plz.outputs.c2patool-release-tag }} + overwrite: true diff --git a/.gitignore b/.gitignore index cfcbfa31b..a43edfdfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ /target/ -Cargo.lock - **/*.rs.bk .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ee3dd3a..f61183a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,35 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm Since version 0.36.2, the format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.40.0](https://github.com/contentauth/c2pa-rs/compare/c2pa-v0.39.0...c2pa-v0.40.0) +_12 December 2024_ + +### Added + +* Add `RawSigner` trait to `c2pa-crypto` (derived from `c2pa::Signer`) (#716) +* Move time stamp code into c2pa-crypto (#696) +* Adds ValidationState support (#701) +* Introduce `DynamicAssertion` trait (#566) + +### Fixed + +* Compile `c2pa-crypto` with `cargo check` (#768) +* Verbose assertions for `is_none()` (#704) +* Remove `c2pa::Signer` dependency on `c2pa_crypto::TimeStampProvider` (#718) +* Add support for MP3 without ID3 header (#652) +* Treat Unicode-3.0 license as approved; unpin related dependencies (#693) +* Remote manifest fetch test was not using full path (#675) +* Fix #624 (edge cases when combining the box hashes) (#625) +* Fix #672, Callback is unsound (#674) +* Support "remote_manifest_fetch" verify setting (#667) + +### Updated dependencies + +* Bump chrono from 0.4.38 to 0.4.39 (#763) +* Bump asn1-rs from 0.5.2 to 0.6.2 (#724) +* Bump mockall requirement from 0.11.2 to 0.13.1 in /sdk (#685) +* Update zip requirement from 0.6.6 to 2.2.1 in /sdk (#698) + ## [0.39.0](https://github.com/contentauth/c2pa-rs/compare/c2pa-v0.38.0...c2pa-v0.39.0) _13 November 2024_ diff --git a/Cargo.lock b/Cargo.lock index f392c0d7a..f32635748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,15 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -179,49 +188,22 @@ dependencies = [ "term", ] -[[package]] -name = "asn1-rs" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" -dependencies = [ - "asn1-rs-derive 0.4.0", - "asn1-rs-impl 0.1.0", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", -] - [[package]] name = "asn1-rs" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive 0.5.1", - "asn1-rs-impl 0.2.0", + "asn1-rs-derive", + "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] -[[package]] -name = "asn1-rs-derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "asn1-rs-derive" version = "0.5.1" @@ -231,18 +213,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "synstructure 0.13.1", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "synstructure", ] [[package]] @@ -276,7 +247,7 @@ dependencies = [ "bstr", "doc-comment", "libc", - "predicates 3.1.2", + "predicates", "predicates-core", "predicates-tree", "wait-timeout", @@ -640,9 +611,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata", @@ -690,11 +661,11 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "c2pa" -version = "0.39.0" +version = "0.40.0" dependencies = [ "actix", "anyhow", - "asn1-rs 0.5.2", + "asn1-rs", "async-generic", "async-recursion", "async-trait", @@ -729,7 +700,7 @@ dependencies = [ "log", "lopdf", "memchr", - "mockall 0.11.4", + "mockall", "mp4", "openssl", "pem 3.0.4", @@ -756,7 +727,7 @@ dependencies = [ "sha2", "spki", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "treeline", "ureq", @@ -773,8 +744,9 @@ dependencies = [ [[package]] name = "c2pa-crypto" -version = "0.1.2" +version = "0.2.0" dependencies = [ + "actix", "async-generic", "async-trait", "base64 0.22.1", @@ -783,6 +755,7 @@ dependencies = [ "c2pa-status-tracker", "chrono", "ciborium", + "const-hex", "coset", "ecdsa", "ed25519-dalek", @@ -803,7 +776,7 @@ dependencies = [ "sha1", "sha2", "spki", - "thiserror", + "thiserror 1.0.69", "ureq", "url", "wasm-bindgen", @@ -817,11 +790,11 @@ dependencies = [ [[package]] name = "c2pa-status-tracker" -version = "0.1.0" +version = "0.2.0" [[package]] name = "c2patool" -version = "0.9.12" +version = "0.10.2" dependencies = [ "anyhow", "assert_cmd", @@ -832,10 +805,10 @@ dependencies = [ "glob", "httpmock", "log", - "mockall 0.13.1", + "mockall", "openssl", "pem 3.0.4", - "predicates 3.1.2", + "predicates", "reqwest", "serde", "serde_derive", @@ -851,9 +824,9 @@ version = "0.1.1" [[package]] name = "cc" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "shlex", ] @@ -866,9 +839,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -994,6 +967,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-hex" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1232,7 +1218,7 @@ version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs 0.6.2", + "asn1-rs", "displaydoc", "nom", "num-bigint", @@ -1250,6 +1236,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "difflib" version = "0.4.0" @@ -1361,6 +1358,7 @@ dependencies = [ "ed25519", "serde", "sha2", + "signature", "subtle", "zeroize", ] @@ -1503,15 +1501,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -2292,15 +2290,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.11.0" @@ -2339,9 +2328,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -2365,14 +2354,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a32280817bc6e0dbd9aa2abfe52b8fe7405bebc33995649525ecc13ececc3b59" dependencies = [ "nom", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "konst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65f00fb3910881e52bf0850ae2a82aea411488a557e1c02820ceaa60963dce3" +checksum = "298ddf99f06a97c1ecd0e910932662b7842855046234b0d0376d35d93add087f" dependencies = [ "const_panic", "konst_kernel", @@ -2381,9 +2370,9 @@ dependencies = [ [[package]] name = "konst_kernel" -version = "0.3.12" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599c1232f55c72c7fc378335a3efe1c878c92720838c8e6a4fd87784ef7764de" +checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" dependencies = [ "typewit", ] @@ -2445,9 +2434,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" @@ -2593,21 +2582,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mockall" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" -dependencies = [ - "cfg-if", - "downcast", - "fragile", - "lazy_static", - "mockall_derive 0.11.4", - "predicates 2.1.5", - "predicates-tree", -] - [[package]] name = "mockall" version = "0.13.1" @@ -2617,23 +2591,11 @@ dependencies = [ "cfg-if", "downcast", "fragile", - "mockall_derive 0.13.1", - "predicates 3.1.2", + "mockall_derive", + "predicates", "predicates-tree", ] -[[package]] -name = "mockall_derive" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "mockall_derive" version = "0.13.1" @@ -2657,7 +2619,7 @@ dependencies = [ "num-rational", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2789,7 +2751,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs 0.6.2", + "asn1-rs", ] [[package]] @@ -2967,20 +2929,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -2988,9 +2950,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -3001,9 +2963,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -3093,9 +3055,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3153,20 +3115,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp", - "itertools 0.10.5", - "normalize-line-endings", - "predicates-core", - "regex", -] - [[package]] name = "predicates" version = "3.1.2" @@ -3215,6 +3163,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "unarray", +] + [[package]] name = "quote" version = "1.0.37" @@ -3266,7 +3230,16 @@ version = "0.9.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a98fa0b8309344136abe6244130311e76997e546f76fae8054422a7539b43df7" dependencies = [ - "zerocopy 0.8.12", + "zerocopy 0.8.13", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", ] [[package]] @@ -3359,9 +3332,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -3374,7 +3347,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3565,22 +3538,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "log", "once_cell", @@ -3728,9 +3701,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -3776,9 +3749,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -4066,18 +4039,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.1" @@ -4152,7 +4113,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", ] [[package]] @@ -4166,6 +4136,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "time" version = "0.3.37" @@ -4267,9 +4248,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -4368,9 +4349,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typewit" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51dbd25812f740f45e2a9769f84711982e000483b13b73a8a1852e092abac8c" +checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0" dependencies = [ "typewit_proc_macros", ] @@ -4387,6 +4368,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -4413,9 +4400,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3193f92e105038f98ae68af40c008e3c94f2f046926e0f95e6c835dc6459bac8" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64 0.22.1", "flate2", @@ -4521,9 +4508,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -4532,13 +4519,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -4547,9 +4533,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -4560,9 +4546,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4570,9 +4556,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -4583,19 +4569,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ "js-sys", "minicov", - "once_cell", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -4604,9 +4589,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", @@ -4615,9 +4600,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -4845,7 +4830,7 @@ dependencies = [ "ring 0.16.20", "signature", "spki", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -4854,14 +4839,14 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs 0.6.2", + "asn1-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -4886,7 +4871,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "synstructure 0.13.1", + "synstructure", ] [[package]] @@ -4901,11 +4886,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e031087b26520ba76806365896f191416ce84873ed6c6910a9ab5fe0f98f8ed3" +checksum = "67914ab451f3bfd2e69e5e9d2ef3858484e7074d63f204fd166ec391b54de21d" dependencies = [ - "zerocopy-derive 0.8.12", + "zerocopy-derive 0.8.13", ] [[package]] @@ -4921,9 +4906,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568244125ba0fc91ae949b97f2852f82cb1a65c3327bd68e6edadd29e67cca26" +checksum = "7988d73a4303ca289df03316bc490e934accf371af6bc745393cf3c2c5c4f25d" dependencies = [ "proc-macro2", "quote", @@ -4948,7 +4933,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "synstructure 0.13.1", + "synstructure", ] [[package]] @@ -4981,13 +4966,17 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.6" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ - "byteorder", + "arbitrary", "crc32fast", "crossbeam-utils", + "displaydoc", + "indexmap 2.7.0", + "memchr", + "thiserror 2.0.6", ] [[package]] @@ -4998,9 +4987,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 2b4e5cf7c..8ff9340ea 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -4,7 +4,43 @@ All changes to this project are documented in this file. This project adheres to [Semantic Versioning](https://semver.org), except that – as is typical in the Rust community – the minimum supported Rust version may be increased without a major version increase. -Since version 0.9.13, the format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +Since version 0.10.0, the format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [0.10.2](https://github.com/contentauth/c2pa-rs/compare/c2patool-v0.10.1...c2patool-v0.10.2) +_12 December 2024_ + +### Fixed + +* No-op change to trigger new c2patool build +* Update makefile for c2patool's new location in c2pa-rs workspace + +## [0.10.1](https://github.com/contentauth/c2pa-rs/compare/c2patool-v0.10.0...c2patool-v0.10.1) +_12 December 2024_ + +### Fixed + +* No-op change to trigger new c2patool release + +## [0.10.0](https://github.com/contentauth/c2pa-rs/compare/c2patool-v0.9.12...c2patool-v0.10.0) +_12 December 2024_ + +### Added + +* Updates c2patool to use only the new Builder/Reader API (contentauth/c2patool#297) + +### Documented + +* Update Contributing guide, misc minor edits (contentauth/c2patool#296) + +### Fixed + +* Compile `c2pa-crypto` with `cargo check` (#768) + +### Other + +* Move c2patool source code into c2pa-rs repo (#723) +* Move profile settings to workspace Cargo.toml +* Enlarged description of c2pa command-line behavior (contentauth/c2patool[#285](https://github.com/contentauth/c2pa-rs/pull/285)) ## 0.9.12 _18 October 2024_ diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 2ee5ea9e9..d4b1c1066 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -2,10 +2,7 @@ name = "c2patool" default-run = "c2patool" -# Please do not manually edit `version`. Version updates will be generated -# automatically when c2patool is published. Remember to use (MINOR) or (MAJOR) -# tags in the PR title to trigger non-patch updates as needed. -version = "0.9.12" +version = "0.10.2" description = "Tool for displaying and creating C2PA manifests." authors = [ @@ -23,7 +20,7 @@ repository = "https://github.com/contentauth/c2pa-rs/tree/main/cli" [dependencies] anyhow = "1.0" atree = "0.5.2" -c2pa = { path = "../sdk", version = "0.39.0", features = [ +c2pa = { path = "../sdk", version = "0.40.0", features = [ "fetch_remote_manifests", "file_io", "add_thumbnails", diff --git a/cli/Makefile b/cli/Makefile index 478cb959b..b73138f22 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -31,13 +31,13 @@ fmt: # Creates a folder wtih c2patool bin, samples and readme c2patool-package: - rm -rf target/c2patool* - mkdir -p target/c2patool - mkdir -p target/c2patool/sample - cp target/release/c2patool target/c2patool/c2patool - cp README.md target/c2patool/README.md - cp sample/* target/c2patool/sample - cp CHANGELOG.md target/c2patool/CHANGELOG.md + rm -rf ../target/c2patool* + mkdir -p ../target/c2patool + mkdir -p ../target/c2patool/sample + cp ../target/release/c2patool ../target/c2patool/c2patool + cp README.md ../target/c2patool/README.md + cp sample/* ../target/c2patool/sample + cp CHANGELOG.md ../target/c2patool/CHANGELOG.md # These are for building the c2patool release bin on various platforms build-release-win: @@ -52,7 +52,7 @@ build-release-mac-x86: MACOSX_DEPLOYMENT_TARGET=10.15 cargo build --target=x86_64-apple-darwin --release build-release-mac-universal: build-release-mac-arm build-release-mac-x86 - lipo -create -output target/release/c2patool target/aarch64-apple-darwin/release/c2patool target/x86_64-apple-darwin/release/c2patool + lipo -create -output ../target/release/c2patool ../target/aarch64-apple-darwin/release/c2patool ../target/x86_64-apple-darwin/release/c2patool build-release-linux: cargo build --release @@ -60,13 +60,13 @@ build-release-linux: # Builds and packages a zip for c2patool for each platform ifeq ($(PLATFORM), mac) release: build-release-mac-universal c2patool-package - cd target && zip -r c2patool_mac_universal.zip c2patool && cd .. + cd ../target && zip -r c2patool_mac_universal.zip c2patool && cd .. endif ifeq ($(PLATFORM), win) release: build-release-win c2patool-package - cd target && 7z a -r c2patool_win_intel.zip c2patool && cd .. + cd ../target && 7z a -r c2patool_win_intel.zip c2patool && cd .. endif ifeq ($(PLATFORM), linux) release: build-release-linux c2patool-package - cd target && tar -czvf c2patool_linux_intel.tar.gz c2patool && cd .. + cd ../target && tar -czvf c2patool_linux_intel.tar.gz c2patool && cd .. endif diff --git a/cli/rustfmt.toml b/cli/rustfmt.toml deleted file mode 100644 index 1807a36a9..000000000 --- a/cli/rustfmt.toml +++ /dev/null @@ -1,16 +0,0 @@ -# IMPORTANT: PR validation uses nightly version of -# rustfmt. - -# Invoke by using: -# -# rustup toolchain add nightly -# cargo +nightly fmt - -blank_lines_upper_bound = 1 -edition = "2018" -format_code_in_doc_comments = true -group_imports = "StdExternalCrate" -hex_literal_case = "Lower" -imports_granularity = "Crate" -reorder_impl_items = true -unstable_features = true diff --git a/cli/src/info.rs b/cli/src/info.rs index 94303e2bc..9a9160d4d 100644 --- a/cli/src/info.rs +++ b/cli/src/info.rs @@ -15,7 +15,7 @@ use std::{io::Cursor, path::Path}; use anyhow::Result; use c2pa::{IngredientOptions, Reader}; -/// display additional C2PA information about the asset (not json formatted) +/// Display additional C2PA information about the asset (not JSON formatted). pub fn info(path: &Path) -> Result<()> { struct Options {} impl IngredientOptions for Options { diff --git a/cli/src/main.rs b/cli/src/main.rs index 910635f90..96db545d7 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -14,9 +14,10 @@ /// Tool to display and create C2PA manifests /// -/// A file path to an asset must be provided -/// If only the path is given, this will generate a summary report of any claims in that file -/// If a manifest definition json file is specified, the claim will be added to any existing claims +/// A file path to an asset must be provided. If only the path +/// is given, this will generate a summary report of any claims +/// in that file. If a manifest definition JSON file is specified, +/// the claim will be added to any existing claims. use std::{ fs::{create_dir_all, remove_dir_all, File}, io::Write, diff --git a/docs/usage.md b/docs/usage.md index 417857ed5..54ce6bfc0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -42,7 +42,6 @@ The Rust library crate provides the following capabilities: * `no_interleaved_io` forces fully-synchronous I/O; otherwise, the library uses threaded I/O for some operations to improve performance. * `fetch_remote_manifests` enables the verification step to retrieve externally referenced manifest stores. External manifests are only fetched if there is no embedded manifest store and no locally adjacent .c2pa manifest store file of the same name. * `json_schema` is used by `make schema` to produce a JSON schema document that represents the `ManifestStore` data structures. -* `psxxx_ocsp_stapling_experimental` enables a demonstration feature that attempts to fetch the OCSP data from the OCSP responders listed in the manifest signing certificate. The response becomes part of the manifest and is used to prove the certificate was not revoked at the time of signing. This is only implemented for PS256, PS384 and PS512 signatures and is intended as a demonstration. * `openssl_ffi_mutex` prevents multiple threads from accessing the C OpenSSL library simultaneously. (This library is not re-entrant.) In a multi-threaded process (such as Cargo's test runner), this can lead to unpredictable behavior. ### New API diff --git a/internal/crypto/CHANGELOG.md b/internal/crypto/CHANGELOG.md index 9fd3291d8..a0b4346d4 100644 --- a/internal/crypto/CHANGELOG.md +++ b/internal/crypto/CHANGELOG.md @@ -6,6 +6,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.2.0](https://github.com/contentauth/c2pa-rs/compare/c2pa-crypto-v0.1.2...c2pa-crypto-v0.2.0) +_12 December 2024_ + +### Added + +* Add `RawSigner` trait to `c2pa-crypto` (derived from `c2pa::Signer`) (#716) +* Move time stamp code into c2pa-crypto (#696) + +### Fixed + +* Compile `c2pa-crypto` with `cargo check` (#768) +* Verbose assertions for `is_none()` (#704) +* Remove `c2pa::Signer` dependency on `c2pa_crypto::TimeStampProvider` (#718) +* Treat Unicode-3.0 license as approved; unpin related dependencies (#693) + +### Updated dependencies + +* Bump chrono from 0.4.38 to 0.4.39 (#763) + ## [0.1.2](https://github.com/contentauth/c2pa-rs/compare/c2pa-crypto-v0.1.1...c2pa-crypto-v0.1.2) _24 October 2024_ diff --git a/internal/crypto/Cargo.toml b/internal/crypto/Cargo.toml index 06b3712f6..17dd7bc5b 100644 --- a/internal/crypto/Cargo.toml +++ b/internal/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c2pa-crypto" -version = "0.1.2" +version = "0.2.0" description = "Cryptography internals for c2pa-rs crate" authors = [ "Maurice Fisher ", @@ -35,8 +35,9 @@ async-trait = "0.1.77" base64 = "0.22.1" bcder = "0.7.3" bytes = "1.7.2" -c2pa-status-tracker = { path = "../status-tracker", version = "0.1.0" } +c2pa-status-tracker = { path = "../status-tracker", version = "0.2.0" } ciborium = "0.2.2" +const-hex = "1.14" coset = "0.3.1" getrandom = { version = "0.2.7", features = ["js"] } hex = "0.4.3" @@ -62,19 +63,19 @@ url = "2.5.3" normal = ["openssl"] # TEMPORARY: Remove after openssl transition complete. [dependencies.chrono] -version = "0.4.38" +version = "0.4.39" default-features = false features = ["wasmbind"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies.chrono] -version = "0.4.38" +version = "0.4.39" default-features = false features = ["now", "wasmbind"] [target.'cfg(target_arch = "wasm32")'.dependencies] async-trait = { version = "0.1.77" } ecdsa = "0.16.9" -ed25519-dalek = "2.1.1" +ed25519-dalek = { version = "2.1.1", features = ["alloc", "digest", "pem", "pkcs8"] } p256 = "0.13.2" p384 = "0.13.0" rsa = { version = "0.9.6", features = ["sha2"] } @@ -95,5 +96,8 @@ web-time = "1.1" getrandom = { version = "0.2.7", features = ["js"] } js-sys = "0.3.58" +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +actix = "0.13.1" + [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.31" diff --git a/internal/crypto/src/openssl/cert_chain.rs b/internal/crypto/src/openssl/cert_chain.rs new file mode 100644 index 000000000..f8442bfb3 --- /dev/null +++ b/internal/crypto/src/openssl/cert_chain.rs @@ -0,0 +1,48 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +#![allow(dead_code)] // TEMPORARY while refactoring + +use openssl::x509::X509; + +// Verify the certificate chain order. +// +// Return `true` if each cert in the chain can be verified as issued by the next +// issuer. +pub(crate) fn check_chain_order(certs: &[X509]) -> bool { + // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please + // don't make this pub or pub(crate) without finding a way to ensure that + // precondition. + + let mut iter = certs.iter().peekable(); + + while let Some(cert) = iter.next() { + let Some(next) = iter.peek() else { + break; + }; + + let Ok(pkey) = next.public_key() else { + return false; + }; + + let Ok(verified) = cert.verify(&pkey) else { + return false; + }; + + if !verified { + return false; + } + } + + true +} diff --git a/internal/crypto/src/openssl/mod.rs b/internal/crypto/src/openssl/mod.rs index 2b0ad9437..78fda28d4 100644 --- a/internal/crypto/src/openssl/mod.rs +++ b/internal/crypto/src/openssl/mod.rs @@ -18,7 +18,10 @@ //! //! [`openssl` native code library]: https://crates.io/crates/openssl +mod cert_chain; + mod ffi_mutex; pub use ffi_mutex::{OpenSslMutex, OpenSslMutexUnavailable}; +pub(crate) mod signers; pub mod validators; diff --git a/internal/crypto/src/openssl/signers/ecdsa_signer.rs b/internal/crypto/src/openssl/signers/ecdsa_signer.rs new file mode 100644 index 000000000..aa35635e4 --- /dev/null +++ b/internal/crypto/src/openssl/signers/ecdsa_signer.rs @@ -0,0 +1,140 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use openssl::{ + ec::EcKey, + hash::MessageDigest, + pkey::{PKey, Private}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + p1363::der_to_p1363, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +enum EcdsaSigningAlg { + Es256, + Es384, + Es512, +} + +/// Implements `Signer` trait using OpenSSL's implementation of +/// ECDSA encryption. +pub struct EcdsaSigner { + alg: EcdsaSigningAlg, + + cert_chain: Vec, + cert_chain_len: usize, + + private_key: EcKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl EcdsaSigner { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, + ) -> Result { + let alg = match alg { + SigningAlg::Es256 => EcdsaSigningAlg::Es256, + SigningAlg::Es384 => EcdsaSigningAlg::Es384, + SigningAlg::Es512 => EcdsaSigningAlg::Es512, + _ => { + return Err(RawSignerError::InternalError( + "EcdsaSigner should be used only for SigningAlg::Es***".to_string(), + )); + } + }; + + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + let private_key = EcKey::private_key_from_pem(private_key)?; + + Ok(EcdsaSigner { + alg, + cert_chain, + cert_chain_len, + private_key, + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for EcdsaSigner { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let private_key = PKey::from_ec_key(self.private_key.clone())?; + + let mut signer = match self.alg { + EcdsaSigningAlg::Es256 => Signer::new(MessageDigest::sha256(), &private_key)?, + EcdsaSigningAlg::Es384 => Signer::new(MessageDigest::sha384(), &private_key)?, + EcdsaSigningAlg::Es512 => Signer::new(MessageDigest::sha512(), &private_key)?, + }; + + signer.update(data)?; + + let der_sig = signer.sign_to_vec()?; + der_to_p1363(&der_sig, self.alg()) + } + + fn alg(&self) -> SigningAlg { + match self.alg { + EcdsaSigningAlg::Es256 => SigningAlg::Es256, + EcdsaSigningAlg::Es384 => SigningAlg::Es384, + EcdsaSigningAlg::Es512 => SigningAlg::Es512, + } + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } +} + +impl TimeStampProvider for EcdsaSigner { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/openssl/signers/ed25519_signer.rs b/internal/crypto/src/openssl/signers/ed25519_signer.rs new file mode 100644 index 000000000..63e6f574f --- /dev/null +++ b/internal/crypto/src/openssl/signers/ed25519_signer.rs @@ -0,0 +1,105 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use openssl::{ + pkey::{PKey, Private}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +/// Implements `RawSigner` trait using OpenSSL's implementation of +/// Edwards Curve encryption. +pub struct Ed25519Signer { + cert_chain: Vec, + cert_chain_len: usize, + + private_key: PKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl Ed25519Signer { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + time_stamp_service_url: Option, + ) -> Result { + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + let private_key = PKey::private_key_from_pem(private_key)?; + + Ok(Ed25519Signer { + cert_chain, + cert_chain_len, + + private_key, + + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for Ed25519Signer { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let mut signer = Signer::new_without_digest(&self.private_key)?; + + Ok(signer.sign_oneshot_to_vec(data)?) + } + + fn alg(&self) -> SigningAlg { + SigningAlg::Ed25519 + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } +} + +impl TimeStampProvider for Ed25519Signer { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/openssl/signers/mod.rs b/internal/crypto/src/openssl/signers/mod.rs new file mode 100644 index 000000000..639d0a1c5 --- /dev/null +++ b/internal/crypto/src/openssl/signers/mod.rs @@ -0,0 +1,67 @@ +// Copyright 2024 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +//! This module binds OpenSSL logic for generating raw signatures to this +//! crate's [`RawSigner`] trait. + +use crate::{ + raw_signature::{RawSigner, RawSignerError}, + SigningAlg, +}; + +mod ecdsa_signer; +mod ed25519_signer; +mod rsa_signer; + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +pub(crate) fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + match alg { + SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Ok(Box::new( + ecdsa_signer::EcdsaSigner::from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?, + )), + + SigningAlg::Ed25519 => Ok(Box::new( + ed25519_signer::Ed25519Signer::from_cert_chain_and_private_key( + cert_chain, + private_key, + time_stamp_service_url, + )?, + )), + + SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Ok(Box::new( + rsa_signer::RsaSigner::from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?, + )), + } +} diff --git a/internal/crypto/src/openssl/signers/rsa_signer.rs b/internal/crypto/src/openssl/signers/rsa_signer.rs new file mode 100644 index 000000000..98e58cd26 --- /dev/null +++ b/internal/crypto/src/openssl/signers/rsa_signer.rs @@ -0,0 +1,186 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private}, + rsa::{Rsa, RsaPrivateKeyBuilder}, + sign::Signer, + x509::X509, +}; + +use crate::{ + openssl::{cert_chain::check_chain_order, OpenSslMutex}, + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +enum RsaSigningAlg { + Ps256, + Ps384, + Ps512, +} + +/// Implements [`RawSigner`] trait using OpenSSL's implementation of SHA256 + +/// RSA encryption. +pub(crate) struct RsaSigner { + alg: RsaSigningAlg, + + cert_chain: Vec, + cert_chain_len: usize, + + private_key: PKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl RsaSigner { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, + ) -> Result { + let _openssl = OpenSslMutex::acquire()?; + + let cert_chain = X509::stack_from_pem(cert_chain)?; + let cert_chain_len = cert_chain.len(); + + if !check_chain_order(&cert_chain) { + return Err(RawSignerError::InvalidSigningCredentials( + "certificate chain in incorrect order".to_string(), + )); + } + + // Rebuild RSA keys to eliminate incompatible values. + let private_key = Rsa::private_key_from_pem(private_key)?; + + let n = private_key.n().to_owned()?; + let e = private_key.e().to_owned()?; + let d = private_key.d().to_owned()?; + let po = private_key.p(); + let qo = private_key.q(); + let dmp1o = private_key.dmp1(); + let dmq1o = private_key.dmq1(); + let iqmpo = private_key.iqmp(); + + let mut pk_builder = RsaPrivateKeyBuilder::new(n, e, d)?; + + if let Some(p) = po { + if let Some(q) = qo { + pk_builder = pk_builder.set_factors(p.to_owned()?, q.to_owned()?)?; + } + } + + if let Some(dmp1) = dmp1o { + if let Some(dmq1) = dmq1o { + if let Some(iqmp) = iqmpo { + pk_builder = pk_builder.set_crt_params( + dmp1.to_owned()?, + dmq1.to_owned()?, + iqmp.to_owned()?, + )?; + } + } + } + + let private_key = PKey::from_rsa(pk_builder.build())?; + + let alg: RsaSigningAlg = match alg { + SigningAlg::Ps256 => RsaSigningAlg::Ps256, + SigningAlg::Ps384 => RsaSigningAlg::Ps384, + SigningAlg::Ps512 => RsaSigningAlg::Ps512, + _ => { + return Err(RawSignerError::InternalError( + "RsaSigner should be used only for SigningAlg::Ps***".to_string(), + )); + } + }; + + Ok(RsaSigner { + alg, + cert_chain, + private_key, + cert_chain_len, + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for RsaSigner { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + let mut signer = match self.alg { + RsaSigningAlg::Ps256 => { + let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha256())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + + RsaSigningAlg::Ps384 => { + let mut signer = Signer::new(MessageDigest::sha384(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha384())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + + RsaSigningAlg::Ps512 => { + let mut signer = Signer::new(MessageDigest::sha512(), &self.private_key)?; + signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; + signer.set_rsa_mgf1_md(MessageDigest::sha512())?; + signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; + signer + } + }; + + Ok(signer.sign_oneshot_to_vec(data)?) + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + let _openssl = OpenSslMutex::acquire()?; + + self.cert_chain + .iter() + .map(|cert| { + cert.to_der() + .map_err(|e| RawSignerError::OpenSslError(e.to_string())) + }) + .collect() + } + + fn alg(&self) -> SigningAlg { + match self.alg { + RsaSigningAlg::Ps256 => SigningAlg::Ps256, + RsaSigningAlg::Ps384 => SigningAlg::Ps384, + RsaSigningAlg::Ps512 => SigningAlg::Ps512, + } + } +} + +impl TimeStampProvider for RsaSigner { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/p1363.rs b/internal/crypto/src/p1363.rs index 4f806d10a..838a4d80b 100644 --- a/internal/crypto/src/p1363.rs +++ b/internal/crypto/src/p1363.rs @@ -43,3 +43,62 @@ pub struct EcSigComps<'a> { pub r: &'a [u8], pub s: &'a [u8], } + +#[cfg(feature = "openssl")] +use crate::{raw_signature::RawSignerError, SigningAlg}; + +#[cfg(feature = "openssl")] +pub(crate) fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result, RawSignerError> { + // P1363 format: r | s + + let (_, p) = parse_ec_der_sig(data) + .map_err(|err| RawSignerError::InternalError(format!("invalid DER signature: {err}")))?; + + let mut r = const_hex::encode(p.r); + let mut s = const_hex::encode(p.s); + + let sig_len: usize = match alg { + SigningAlg::Es256 => 64, + SigningAlg::Es384 => 96, + SigningAlg::Es512 => 132, + _ => { + return Err(RawSignerError::InternalError( + "unsupported algorithm for der_to_p1363".to_string(), + )) + } + }; + + // Pad or truncate as needed. + let rp = if r.len() > sig_len { + let offset = r.len() - sig_len; + &r[offset..r.len()] + } else { + while r.len() != sig_len { + r.insert(0, '0'); + } + r.as_ref() + }; + + let sp = if s.len() > sig_len { + let offset = s.len() - sig_len; + &s[offset..s.len()] + } else { + while s.len() != sig_len { + s.insert(0, '0'); + } + s.as_ref() + }; + + if rp.len() != sig_len || rp.len() != sp.len() { + return Err(RawSignerError::InternalError( + "invalid signature components".to_string(), + )); + } + + // Merge r and s strings. + let new_sig = format!("{rp}{sp}"); + + // Convert back from hex string to byte array. + const_hex::decode(&new_sig) + .map_err(|e| RawSignerError::InternalError(format!("invalid signature components {e}"))) +} diff --git a/internal/crypto/src/raw_signature/mod.rs b/internal/crypto/src/raw_signature/mod.rs index 67e0510b5..b5a54e86e 100644 --- a/internal/crypto/src/raw_signature/mod.rs +++ b/internal/crypto/src/raw_signature/mod.rs @@ -13,6 +13,12 @@ //! Tools for working with raw signature algorithms. +pub(crate) mod signer; +pub use signer::{ + async_signer_from_cert_chain_and_private_key, signer_from_cert_chain_and_private_key, + AsyncRawSigner, RawSigner, RawSignerError, +}; + pub(crate) mod oids; mod validator; diff --git a/internal/crypto/src/raw_signature/signer.rs b/internal/crypto/src/raw_signature/signer.rs new file mode 100644 index 000000000..cca4f607d --- /dev/null +++ b/internal/crypto/src/raw_signature/signer.rs @@ -0,0 +1,269 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use async_trait::async_trait; +use thiserror::Error; + +use crate::{ + time_stamp::{AsyncTimeStampProvider, TimeStampError, TimeStampProvider}, + SigningAlg, +}; + +/// Implementations of the `RawSigner` trait generate a cryptographic signature +/// over an arbitrary byte array. +/// +/// If an implementation _can_ be asynchronous, that is preferred. +pub trait RawSigner: TimeStampProvider { + /// Return a raw signature over the original byte slice. + fn sign(&self, data: &[u8]) -> Result, RawSignerError>; + + /// Return the algorithm implemented by this signer. + fn alg(&self) -> SigningAlg; + + /// Return the signing certificate chain. + /// + /// Each certificate should be encoded in DER format and sequenced from + /// end-entity certificate to the outermost certificate authority. + fn cert_chain(&self) -> Result>, RawSignerError>; + + /// Return the size in bytes of the largest possible expected signature. + /// Signing will fail if the result of the [`sign`] function is larger + /// than this value. + /// + /// [`sign`]: Self::sign + fn reserve_size(&self) -> usize; + + /// Return an OCSP response for the signing certificate if available. + /// + /// By pre-querying the value for the signing certificate, the value can be + /// cached which will reduce load on the certificate authority, as + /// recommended by the C2PA spec. + fn ocsp_response(&self) -> Option> { + None + } +} + +/// Implementations of the `AsyncRawSigner` trait generate a cryptographic +/// signature over an arbitrary byte array. +/// +/// Use this trait only when the implementation must be asynchronous. +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait AsyncRawSigner: Sync + AsyncTimeStampProvider { + /// Return a raw signature over the original byte slice. + async fn sign(&self, data: Vec) -> Result, RawSignerError>; + + /// Return the algorithm implemented by this signer. + fn alg(&self) -> SigningAlg; + + /// Return the signing certificate chain. + /// + /// Each certificate should be encoded in DER format and sequenced from + /// end-entity certificate to the outermost certificate authority. + fn cert_chain(&self) -> Result>, RawSignerError>; + + /// Return the size in bytes of the largest possible expected signature. + /// Signing will fail if the result of the [`sign`] function is larger + /// than this value. + /// + /// [`sign`]: Self::sign + fn reserve_size(&self) -> usize; + + /// Return an OCSP response for the signing certificate if available. + /// + /// By pre-querying the value for the signing certificate, the value can be + /// cached which will reduce load on the certificate authority, as + /// recommended by the C2PA spec. + async fn ocsp_response(&self) -> Option> { + None + } +} + +/// Describes errors that can be identified when generating a raw signature. +#[derive(Debug, Eq, Error, PartialEq)] +#[non_exhaustive] +pub enum RawSignerError { + /// The signing credentials are invalid. + #[error("invalid signing credentials ({0})")] + InvalidSigningCredentials(String), + + /// An I/O error occurred. This typically happens when loading + /// public/private key material from files. + /// + /// NOTE: We do not directly capture the I/O error itself because it + /// lacks an `Eq` implementation. Instead we capture the error description. + #[error("I/O error ({0})")] + IoError(String), + + /// An error was reported by the OpenSSL native code. + /// + /// NOTE: We do not directly capture the OpenSSL error itself because it + /// lacks an `Eq` implementation. Instead we capture the error description. + #[cfg(feature = "openssl")] + #[error("an error was reported by OpenSSL native code: {0}")] + OpenSslError(String), + + /// The OpenSSL native code mutex could not be acquired. + #[cfg(feature = "openssl")] + #[error(transparent)] + OpenSslMutexUnavailable(#[from] crate::openssl::OpenSslMutexUnavailable), + + /// An unexpected internal error occured while requesting the time stamp + /// response. + #[error("internal error ({0})")] + InternalError(String), +} + +impl From for RawSignerError { + fn from(err: std::io::Error) -> Self { + Self::IoError(err.to_string()) + } +} + +#[cfg(feature = "openssl")] +impl From for RawSignerError { + fn from(err: openssl::error::ErrorStack) -> Self { + Self::OpenSslError(err.to_string()) + } +} + +#[cfg(target_arch = "wasm32")] +impl From for RawSignerError { + fn from(err: crate::webcrypto::WasmCryptoError) -> Self { + match err { + crate::webcrypto::WasmCryptoError::UnknownContext => { + Self::InternalError("unknown WASM context".to_string()) + } + crate::webcrypto::WasmCryptoError::NoCryptoAvailable => { + Self::InternalError("WASM crypto unavailable".to_string()) + } + } + } +} + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +#[allow(unused)] // arguments may or may not be used depending on crate features +pub fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + #[cfg(feature = "openssl")] + { + return crate::openssl::signers::signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + ); + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + { + return crate::webcrypto::signers::signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + ); + } + + Err(RawSignerError::InternalError(format!( + "unsupported algorithm: {alg}" + ))) +} + +/// Return a built-in [`AsyncRawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +#[allow(unused)] // arguments may or may not be used depending on crate features +pub fn async_signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + // TO DO: Preferentially use WASM-based signers, some of which are necessarily + // async. + + let sync_signer = signer_from_cert_chain_and_private_key( + cert_chain, + private_key, + alg, + time_stamp_service_url, + )?; + + Ok(Box::new(AsyncRawSignerWrapper(sync_signer))) +} + +struct AsyncRawSignerWrapper(Box); + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl AsyncRawSigner for AsyncRawSignerWrapper { + async fn sign(&self, data: Vec) -> Result, RawSignerError> { + self.0.sign(&data) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + self.0.cert_chain() + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + async fn ocsp_response(&self) -> Option> { + self.0.ocsp_response() + } +} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl AsyncTimeStampProvider for AsyncRawSignerWrapper { + fn time_stamp_service_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn time_stamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn time_stamp_request_body(&self, message: &[u8]) -> Result, TimeStampError> { + self.0.time_stamp_request_body(message) + } + + async fn send_time_stamp_request( + &self, + message: &[u8], + ) -> Option, TimeStampError>> { + self.0.send_time_stamp_request(message) + } +} diff --git a/internal/crypto/src/raw_signature/validator.rs b/internal/crypto/src/raw_signature/validator.rs index 089f2de57..63a5b44b4 100644 --- a/internal/crypto/src/raw_signature/validator.rs +++ b/internal/crypto/src/raw_signature/validator.rs @@ -50,6 +50,7 @@ pub fn validator_for_signing_alg(alg: SigningAlg) -> Option Result DateTime { } } +#[cfg(any(feature = "openssl", target_arch = "wasm32"))] fn validate_timestamp_sig( sig_alg: &bcder::Oid, hash_alg: &bcder::Oid, @@ -337,6 +342,8 @@ fn validate_timestamp_sig( tbs: &[u8], signing_key_der: &[u8], ) -> Result<(), TimeStampError> { + use crate::raw_signature::validator_for_sig_and_hash_algs; + let Some(validator) = validator_for_sig_and_hash_algs(sig_alg, hash_alg) else { return Err(TimeStampError::UnsupportedAlgorithm); }; diff --git a/internal/crypto/src/webcrypto/mod.rs b/internal/crypto/src/webcrypto/mod.rs index fb51e2d32..de9ae2b80 100644 --- a/internal/crypto/src/webcrypto/mod.rs +++ b/internal/crypto/src/webcrypto/mod.rs @@ -25,6 +25,7 @@ pub use async_validators::{ AsyncRawSignatureValidator, }; +pub(crate) mod signers; pub mod validators; mod window_or_worker; diff --git a/internal/crypto/src/webcrypto/signers/ed25519_signer.rs b/internal/crypto/src/webcrypto/signers/ed25519_signer.rs new file mode 100644 index 000000000..a7d3871a4 --- /dev/null +++ b/internal/crypto/src/webcrypto/signers/ed25519_signer.rs @@ -0,0 +1,101 @@ +// Copyright 2024 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use ed25519_dalek::{pkcs8::DecodePrivateKey, SigningKey}; +use x509_parser::{error::PEMError, pem::Pem}; + +use crate::{ + raw_signature::{RawSigner, RawSignerError}, + time_stamp::TimeStampProvider, + SigningAlg, +}; + +/// Implements `RawSigner` trait using `ed25519_dalek` crate's implementation of +/// Edwards Curve encryption. +pub struct Ed25519Signer { + #[allow(dead_code)] + cert_chain: Vec>, + cert_chain_len: usize, + + signing_key: SigningKey, + + time_stamp_service_url: Option, + time_stamp_size: usize, +} + +impl Ed25519Signer { + pub(crate) fn from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + time_stamp_service_url: Option, + ) -> Result { + let cert_chain = Pem::iter_from_buffer(cert_chain) + .map(|r| match r { + Ok(pem) => Ok(pem.contents), + Err(e) => Err(e), + }) + .collect::>, PEMError>>() + .map_err(|e| RawSignerError::InvalidSigningCredentials(e.to_string()))?; + + let cert_chain_len = cert_chain.len(); + + let private_key_pem = std::str::from_utf8(private_key).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid private key: {e}")) + })?; + + let signing_key = SigningKey::from_pkcs8_pem(private_key_pem).map_err(|e| { + RawSignerError::InvalidSigningCredentials(format!("invalid private key: {e}")) + })?; + + Ok(Ed25519Signer { + cert_chain, + cert_chain_len, + + signing_key, + + time_stamp_service_url, + time_stamp_size: 10000, + // TO DO: Call out to time stamp service to get actual time stamp and use that size? + }) + } +} + +impl RawSigner for Ed25519Signer { + fn sign(&self, data: &[u8]) -> Result, RawSignerError> { + use ed25519_dalek::Signer; + + Ok(self + .signing_key + .try_sign(data) + .map_err(|e| RawSignerError::InternalError(format!("signature error: {e}")))? + .to_vec()) + } + + fn alg(&self) -> SigningAlg { + SigningAlg::Ed25519 + } + + fn reserve_size(&self) -> usize { + 1024 + self.cert_chain_len + self.time_stamp_size + } + + fn cert_chain(&self) -> Result>, RawSignerError> { + Ok(self.cert_chain.clone()) + } +} + +impl TimeStampProvider for Ed25519Signer { + fn time_stamp_service_url(&self) -> Option { + self.time_stamp_service_url.clone() + } +} diff --git a/internal/crypto/src/webcrypto/signers/mod.rs b/internal/crypto/src/webcrypto/signers/mod.rs new file mode 100644 index 000000000..134f3d664 --- /dev/null +++ b/internal/crypto/src/webcrypto/signers/mod.rs @@ -0,0 +1,48 @@ +// Copyright 2024 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use crate::{ + raw_signature::{RawSigner, RawSignerError}, + SigningAlg, +}; + +mod ed25519_signer; + +/// Return a built-in [`RawSigner`] instance using the provided signing +/// certificate and private key. +/// +/// Which signers are available may vary depending on the platform and which +/// crate features were enabled. +/// +/// Returns `None` if the signing algorithm is unsupported. May return an `Err` +/// response if the certificate chain or private key are invalid. +pub(crate) fn signer_from_cert_chain_and_private_key( + cert_chain: &[u8], + private_key: &[u8], + alg: SigningAlg, + time_stamp_service_url: Option, +) -> Result, RawSignerError> { + match alg { + SigningAlg::Ed25519 => Ok(Box::new( + ed25519_signer::Ed25519Signer::from_cert_chain_and_private_key( + cert_chain, + private_key, + time_stamp_service_url, + )?, + )), + + _ => Err(RawSignerError::InternalError(format!( + "unsupported algorithm: {alg}" + ))), + } +} diff --git a/internal/status-tracker/CHANGELOG.md b/internal/status-tracker/CHANGELOG.md index 0b7dca1e4..a8b844038 100644 --- a/internal/status-tracker/CHANGELOG.md +++ b/internal/status-tracker/CHANGELOG.md @@ -6,6 +6,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.2.0](https://github.com/contentauth/c2pa-rs/compare/c2pa-status-tracker-v0.1.0...c2pa-status-tracker-v0.2.0) +_11 December 2024_ + +### Added + +* Move `validation_codes` from `c2pa-crypto` to `c2pa-status-tracker` + ## [0.1.0](https://github.com/contentauth/c2pa-rs/releases/tag/c2pa-status-tracker-v0.1.0) _13 November 2024_ diff --git a/internal/status-tracker/Cargo.toml b/internal/status-tracker/Cargo.toml index 1d6f015c9..5749c8742 100644 --- a/internal/status-tracker/Cargo.toml +++ b/internal/status-tracker/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c2pa-status-tracker" -version = "0.1.0" +version = "0.2.0" description = "Status tracking internals for c2pa-rs crate" authors = [ "Maurice Fisher ", diff --git a/internal/status-tracker/src/validation_codes.rs b/internal/status-tracker/src/validation_codes.rs index 466caca59..204c01eb0 100644 --- a/internal/status-tracker/src/validation_codes.rs +++ b/internal/status-tracker/src/validation_codes.rs @@ -293,7 +293,7 @@ pub const GENERAL_ERROR: &str = "general.error"; /// Returns `false` if the status code is a known C2PA failure status /// code or is unknown. /// -/// # Examples +/// ## Examples /// /// ``` /// use c2pa_status_tracker::validation_codes::*; diff --git a/make_test_images/Cargo.toml b/make_test_images/Cargo.toml index d1e9599ce..01aefa05d 100644 --- a/make_test_images/Cargo.toml +++ b/make_test_images/Cargo.toml @@ -20,7 +20,7 @@ image = { version = "0.25.2", default-features = false, features = [ "jpeg", "png", ] } -memchr = "2.7.1" +memchr = "2.7.4" nom = "7.1.3" regex = "1.5.6" serde = "1.0.197" diff --git a/release-plz.toml b/release-plz.toml index 58ed69220..fe0fe3e01 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -47,14 +47,17 @@ changelog_path = "./CHANGELOG.md" # of in the `sdk` folder (which would be the default). [[package]] -name = "c2pa-crypto" +name = "c2pa-status-tracker" [[package]] -name = "c2pa-status-tracker" +name = "c2pa-crypto" [[package]] name = "cawg-identity" +[[package]] +name = "c2patool" + [[package]] name = "export_schema" publish = false diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index a5f2f490a..212d56b49 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c2pa" -version = "0.39.0" +version = "0.40.0" description = "Rust SDK for C2PA (Coalition for Content Provenance and Authenticity) implementors" authors = [ "Maurice Fisher ", @@ -28,7 +28,6 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["v1_api"] add_thumbnails = ["image"] -psxxx_ocsp_stapling_experimental = [] file_io = ["openssl_sign"] serialize_thumbnails = [] no_interleaved_io = ["file_io"] @@ -64,7 +63,7 @@ required-features = ["unstable_api"] crate-type = ["lib"] [dependencies] -asn1-rs = "0.5.2" +asn1-rs = "0.6.2" async-generic = "1.1" async-recursion = "1.1.1" async-trait = { version = "0.1.77" } @@ -73,9 +72,9 @@ bcder = "0.7.3" bytes = "1.7.2" byteorder = { version = "1.4.3", default-features = false } byteordered = "0.6.0" -c2pa-crypto = { path = "../internal/crypto", version = "0.1.2" } -c2pa-status-tracker = { path = "../internal/status-tracker", version = "0.1.0" } -chrono = { version = "0.4.38", default-features = false, features = [ +c2pa-crypto = { path = "../internal/crypto", version = "0.2.0" } +c2pa-status-tracker = { path = "../internal/status-tracker", version = "0.2.0" } +chrono = { version = "0.4.39", default-features = false, features = [ "serde", "wasmbind", ] } @@ -100,7 +99,7 @@ jfifdump = "0.5.1" log = "0.4.8" lopdf = { version = "0.31.0", optional = true } lazy_static = "1.4.0" -memchr = "2.7.1" +memchr = "2.7.4" mp4 = "0.14.0" pem = "3.0.2" png_pong = "0.9.1" @@ -128,7 +127,7 @@ url = "2.5.3" uuid = { version = "1.10.0", features = ["serde", "v4", "js"] } x509-parser = "0.16.0" x509-certificate = "0.21.0" -zip = { version = "0.6.6", default-features = false } +zip = { version = "2.2.1", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ureq = "2.4.0" @@ -162,7 +161,7 @@ web-sys = { version = "0.3.58", features = [ [dev-dependencies] anyhow = "1.0.40" -mockall = "0.11.2" +mockall = "0.13.1" c2pa = { path = ".", features = [ "unstable_api", ] } # allow integration tests to use the new API diff --git a/sdk/examples/v2api.rs b/sdk/examples/v2api.rs index ac2dd503e..540292a6d 100644 --- a/sdk/examples/v2api.rs +++ b/sdk/examples/v2api.rs @@ -151,7 +151,7 @@ fn main() -> Result<()> { } println!("{}", reader.json()); - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); assert_eq!(reader.active_manifest().unwrap().title().unwrap(), title); Ok(()) diff --git a/sdk/src/assertions/actions.rs b/sdk/src/assertions/actions.rs index 0336f09ff..cc9d2fc5b 100644 --- a/sdk/src/assertions/actions.rs +++ b/sdk/src/assertions/actions.rs @@ -291,6 +291,7 @@ impl Action { } // Internal function to return any ingredients referenced by this action. + #[allow(dead_code)] // not used in some scenarios pub(crate) fn ingredient_ids(&mut self) -> Option> { match self.get_parameter(CAI_INGREDIENT_IDS) { Some(Value::Array(ids)) => { diff --git a/sdk/src/asset_handlers/c2pa_io.rs b/sdk/src/asset_handlers/c2pa_io.rs index 400122525..4dca3475e 100644 --- a/sdk/src/asset_handlers/c2pa_io.rs +++ b/sdk/src/asset_handlers/c2pa_io.rs @@ -146,13 +146,17 @@ pub mod tests { #![allow(clippy::expect_used)] #![allow(clippy::unwrap_used)] + use c2pa_crypto::SigningAlg; use c2pa_status_tracker::OneShotStatusTracker; use tempfile::tempdir; use super::{AssetIO, C2paIO, CAIReader, CAIWriter}; use crate::{ store::Store, - utils::test::{fixture_path, temp_dir_path, temp_signer}, + utils::{ + test::{fixture_path, temp_dir_path}, + test_signer::test_signer, + }, }; #[test] @@ -171,7 +175,7 @@ pub mod tests { let store = Store::load_from_asset(&temp_path, false, &mut OneShotStatusTracker::default()) .expect("loading store"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let manifest2 = store.to_jumbf(signer.as_ref()).expect("to_jumbf"); assert_eq!(&manifest, &manifest2); diff --git a/sdk/src/asset_handlers/gif_io.rs b/sdk/src/asset_handlers/gif_io.rs index b1c165418..8c5bba258 100644 --- a/sdk/src/asset_handlers/gif_io.rs +++ b/sdk/src/asset_handlers/gif_io.rs @@ -1459,7 +1459,7 @@ mod tests { let gif_io = GifIO {}; - assert!(gif_io.read_xmp(&mut stream).is_none()); + assert_eq!(gif_io.read_xmp(&mut stream), None); let mut output_stream1 = Cursor::new(Vec::with_capacity(SAMPLE1.len())); gif_io.embed_reference_to_stream( diff --git a/sdk/src/asset_handlers/mp3_io.rs b/sdk/src/asset_handlers/mp3_io.rs index 371f9e6c0..0fd28f955 100644 --- a/sdk/src/asset_handlers/mp3_io.rs +++ b/sdk/src/asset_handlers/mp3_io.rs @@ -592,7 +592,7 @@ pub mod tests { let mp3_io = Mp3IO::new("mp3"); let mut stream = File::open(fixture_path("sample1.mp3"))?; - assert!(mp3_io.read_xmp(&mut stream).is_none()); + assert_eq!(mp3_io.read_xmp(&mut stream), None); stream.rewind()?; let mut output_stream1 = Cursor::new(Vec::new()); diff --git a/sdk/src/asset_handlers/pdf.rs b/sdk/src/asset_handlers/pdf.rs index 96d290c89..370b1ee96 100644 --- a/sdk/src/asset_handlers/pdf.rs +++ b/sdk/src/asset_handlers/pdf.rs @@ -740,7 +740,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_read_xmp_on_pdf_with_none() { let pdf = Pdf::from_bytes(include_bytes!("../../tests/fixtures/basic-no-xmp.pdf")).unwrap(); - assert!(pdf.read_xmp().is_none()); + assert_eq!(pdf.read_xmp(), None); } #[cfg_attr(not(target_arch = "wasm32"), test)] diff --git a/sdk/src/asset_handlers/pdf_io.rs b/sdk/src/asset_handlers/pdf_io.rs index a56e35fb2..0210b611a 100644 --- a/sdk/src/asset_handlers/pdf_io.rs +++ b/sdk/src/asset_handlers/pdf_io.rs @@ -199,7 +199,7 @@ pub mod tests { mock_pdf.expect_read_xmp().returning(|| None); let pdf_io = PdfIO::new("pdf"); - assert!(pdf_io.read_xmp_from_pdf(mock_pdf).is_none()); + assert_eq!(pdf_io.read_xmp_from_pdf(mock_pdf), None); } #[test] diff --git a/sdk/src/builder.rs b/sdk/src/builder.rs index 0ea695456..49d5bfc65 100644 --- a/sdk/src/builder.rs +++ b/sdk/src/builder.rs @@ -24,7 +24,7 @@ use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::skip_serializing_none; use uuid::Uuid; -use zip::{write::FileOptions, ZipArchive, ZipWriter}; +use zip::{write::SimpleFileOptions, ZipArchive, ZipWriter}; use crate::{ assertion::AssertionDecodeError, @@ -453,7 +453,7 @@ impl Builder { { let mut zip = ZipWriter::new(stream); let options = - FileOptions::default().compression_method(zip::CompressionMethod::Stored); + SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); // write a version file zip.start_file("version.txt", options) .map_err(|e| Error::OtherError(Box::new(e)))?; @@ -1083,6 +1083,7 @@ mod tests { #![allow(clippy::unwrap_used)] use std::io::Cursor; + use c2pa_crypto::SigningAlg; use serde_json::json; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -1092,7 +1093,7 @@ mod tests { assertions::BoxHash, asset_handlers::jpeg_io::JpegIO, hash_stream_by_alg, - utils::test::{temp_signer, write_jpeg_placeholder_stream}, + utils::{test::write_jpeg_placeholder_stream, test_signer::test_signer}, Reader, }; @@ -1305,7 +1306,7 @@ mod tests { let mut _builder = Builder::from_archive(&mut zipped).unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder .sign(signer.as_ref(), format, &mut source, &mut dest) .unwrap(); @@ -1315,7 +1316,7 @@ mod tests { let manifest_store = Reader::from_stream(format, &mut dest).expect("from_bytes"); println!("{}", manifest_store); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); assert!(manifest_store.active_manifest().is_some()); let manifest = manifest_store.active_manifest().unwrap(); assert_eq!(manifest.title().unwrap(), "Test_Manifest"); @@ -1337,14 +1338,14 @@ mod tests { .unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder.sign_file(signer.as_ref(), source, &dest).unwrap(); // read and validate the signed manifest store let manifest_store = Reader::from_file(&dest).expect("from_bytes"); println!("{}", manifest_store); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); assert_eq!( manifest_store.active_manifest().unwrap().title().unwrap(), "Test_Manifest" @@ -1389,7 +1390,7 @@ mod tests { .unwrap(); // sign and write to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); builder .sign(signer.as_ref(), format, &mut source, &mut dest) .unwrap(); @@ -1401,7 +1402,7 @@ mod tests { println!("{}", manifest_store); if format != "c2pa" { // c2pa files will not validate since they have no associated asset - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); } assert_eq!( manifest_store.active_manifest().unwrap().title().unwrap(), @@ -1472,7 +1473,7 @@ mod tests { .unwrap(); // sign the ManifestStoreBuilder and write it to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let manifest_data = builder .sign(signer.as_ref(), "image/jpeg", &mut source, &mut dest) .unwrap(); @@ -1488,7 +1489,7 @@ mod tests { .expect("from_bytes"); println!("{}", reader.json()); - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); } #[test] @@ -1496,7 +1497,7 @@ mod tests { const CLOUD_IMAGE: &[u8] = include_bytes!("../tests/fixtures/cloud.jpg"); let mut input_stream = Cursor::new(CLOUD_IMAGE); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut builder = Builder::from_json(&simple_manifest()).unwrap(); @@ -1547,7 +1548,7 @@ mod tests { let reader = crate::Reader::from_stream("image/jpeg", output_stream).unwrap(); println!("{reader}"); #[cfg(not(target_arch = "wasm32"))] // skip this until we get wasm async signing working - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); } #[cfg_attr(not(target_arch = "wasm32"), actix::test)] @@ -1569,7 +1570,7 @@ mod tests { builder.add_assertion(labels::BOX_HASH, &box_hash).unwrap(); - let signer = crate::utils::test::temp_async_signer(); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ed25519); let manifest_bytes = builder .sign_box_hashed_embeddable_async(signer.as_ref(), "image/jpeg") @@ -1633,7 +1634,7 @@ mod tests { let mut builder = Builder::from_archive(&mut zipped).unwrap(); // sign the ManifestStoreBuilder and write it to the output stream - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _manifest_data = builder .sign(signer.as_ref(), "image/jpeg", &mut source, &mut dest) .unwrap(); @@ -1643,7 +1644,7 @@ mod tests { let reader = Reader::from_stream("image/jpeg", &mut dest).expect("from_bytes"); //println!("{}", reader); - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); assert_eq!( reader .active_manifest() @@ -1809,7 +1810,7 @@ mod tests { // convert buffer to cursor with Read/Write/Seek capability let mut input = Cursor::new(image.to_vec()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Embed a manifest using the signer. let mut output = Cursor::new(Vec::new()); builder diff --git a/sdk/src/callback_signer.rs b/sdk/src/callback_signer.rs index 494e0851a..52f952949 100644 --- a/sdk/src/callback_signer.rs +++ b/sdk/src/callback_signer.rs @@ -16,12 +16,10 @@ //! The `callback_signer` module provides a way to obtain a [`Signer`] or [`AsyncSigner`] //! using a callback and public signing certificates. +use async_trait::async_trait; use c2pa_crypto::SigningAlg; -use crate::{ - error::{Error, Result}, - AsyncSigner, Signer, -}; +use crate::{AsyncSigner, Error, Result, Signer}; /// Defines a callback function interface for a [`CallbackSigner`]. /// @@ -54,7 +52,6 @@ pub struct CallbackSigner { } unsafe impl Send for CallbackSigner {} - unsafe impl Sync for CallbackSigner {} impl CallbackSigner { @@ -66,6 +63,7 @@ impl CallbackSigner { { let certs = certs.into(); let reserve_size = 10000 + certs.len(); + Self { context: std::ptr::null(), callback: Box::new(callback), @@ -114,13 +112,14 @@ impl CallbackSigner { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; let signing_key = SigningKey::try_from(key_bytes).map_err(|e| Error::OtherError(Box::new(e)))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } } @@ -162,11 +161,8 @@ impl Signer for CallbackSigner { } } -use async_trait::async_trait; - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -// I'm not sure if this is useful since the callback is still synchronous. impl AsyncSigner for CallbackSigner { async fn sign(&self, data: Vec) -> Result> { (self.callback)(self.context, &data) diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs index a660f4e96..d8fd82007 100644 --- a/sdk/src/cose_sign.rs +++ b/sdk/src/cose_sign.rs @@ -38,7 +38,6 @@ use crate::{ cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp, }, trust_handler::TrustHandlerConfig, - utils::sig_utils::der_to_p1363, AsyncSigner, Error, Result, Signer, }; @@ -277,6 +276,63 @@ pub(crate) fn cose_sign( } } +fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result> { + // P1363 format: r | s + + let (_, p) = parse_ec_der_sig(data).map_err(|_err| Error::InvalidEcdsaSignature)?; + + let mut r = extfmt::Hexlify(p.r).to_string(); + let mut s = extfmt::Hexlify(p.s).to_string(); + + let sig_len: usize = match alg { + SigningAlg::Es256 => 64, + SigningAlg::Es384 => 96, + SigningAlg::Es512 => 132, + _ => return Err(Error::UnsupportedType), + }; + + // pad or truncate as needed + let rp = if r.len() > sig_len { + // truncate + let offset = r.len() - sig_len; + &r[offset..r.len()] + } else { + // pad + while r.len() != sig_len { + r.insert(0, '0'); + } + r.as_ref() + }; + + let sp = if s.len() > sig_len { + // truncate + let offset = s.len() - sig_len; + &s[offset..s.len()] + } else { + // pad + while s.len() != sig_len { + s.insert(0, '0'); + } + s.as_ref() + }; + + if rp.len() != sig_len || rp.len() != sp.len() { + return Err(Error::InvalidEcdsaSignature); + } + + // merge r and s strings + let mut new_sig = rp.to_string(); + new_sig.push_str(sp); + + // convert back from hex string to byte array + (0..new_sig.len()) + .step_by(2) + .map(|i| { + u8::from_str_radix(&new_sig[i..i + 2], 16).map_err(|_err| Error::InvalidEcdsaSignature) + }) + .collect() +} + #[async_generic(async_signature(signer: &dyn AsyncSigner, alg: SigningAlg))] fn build_protected_headers(signer: &dyn Signer, alg: SigningAlg) -> Result { let mut protected_h = match alg { @@ -448,9 +504,12 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result> { #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] + use c2pa_crypto::SigningAlg; use super::sign_claim; - use crate::{claim::Claim, utils::test::temp_signer}; + #[cfg(not(target_arch = "wasm32"))] + use crate::utils::test_signer::async_test_signer; + use crate::{claim::Claim, utils::test_signer::test_signer, Result, Signer}; #[test] fn test_sign_claim() { @@ -459,7 +518,7 @@ mod tests { let claim_bytes = claim.data().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let box_size = signer.reserve_size(); let cose_sign1 = sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap(); @@ -471,16 +530,16 @@ mod tests { #[cfg(feature = "openssl")] #[actix::test] async fn test_sign_claim_async() { - use crate::{ - cose_sign::sign_claim_async, openssl::AsyncSignerAdapter, AsyncSigner, SigningAlg, - }; + use c2pa_crypto::SigningAlg; + + use crate::{cose_sign::sign_claim_async, AsyncSigner}; let mut claim = Claim::new("extern_sign_test", Some("contentauth"), 1); claim.build().unwrap(); let claim_bytes = claim.data().unwrap(); - let signer = AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); let box_size = signer.reserve_size(); let cose_sign1 = sign_claim_async(&claim_bytes, &signer, box_size) @@ -498,8 +557,8 @@ mod tests { } } - impl crate::Signer for BogusSigner { - fn sign(&self, _data: &[u8]) -> crate::error::Result> { + impl Signer for BogusSigner { + fn sign(&self, _data: &[u8]) -> Result> { eprintln!("Canary, canary, please cause this deploy to fail!"); Ok(b"totally bogus signature".to_vec()) } @@ -508,7 +567,7 @@ mod tests { c2pa_crypto::SigningAlg::Ps256 } - fn certs(&self) -> crate::error::Result>> { + fn certs(&self) -> Result>> { let cert_vec: Vec = Vec::new(); let certs = vec![cert_vec]; Ok(certs) diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs index f316f5dce..0ad618fff 100644 --- a/sdk/src/cose_validator.rs +++ b/sdk/src/cose_validator.rs @@ -1384,11 +1384,12 @@ fn gt_to_datetime( #[cfg(feature = "openssl_sign")] #[cfg(test)] pub mod tests { + use c2pa_crypto::SigningAlg; use c2pa_status_tracker::DetailedStatusTracker; use sha2::digest::generic_array::sequence::Shorten; use super::*; - use crate::{openssl::temp_signer, signer::ConfigurableSigner, Signer, SigningAlg}; + use crate::{utils::test_signer::test_signer, Signer}; #[test] #[cfg(feature = "file_io")] @@ -1417,39 +1418,31 @@ pub mod tests { #[test] #[cfg(feature = "openssl_sign")] fn test_cert_algorithms() { - let cert_dir = crate::utils::test::fixture_path("certs"); let th = crate::openssl::OpenSSLTrustHandlerConfig::new(); let mut validation_log = DetailedStatusTracker::default(); - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let es256_cert = std::fs::read(cert_path).unwrap(); + let es256_cert = include_bytes!("../tests/fixtures/certs/es256.pub"); + let es384_cert = include_bytes!("../tests/fixtures/certs/es384.pub"); + let es512_cert = include_bytes!("../tests/fixtures/certs/es512.pub"); + let rsa_pss256_cert = include_bytes!("../tests/fixtures/certs/ps256.pub"); - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let es384_cert = std::fs::read(cert_path).unwrap(); - - let (_, cert_path) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let es512_cert = std::fs::read(cert_path).unwrap(); - - let (_, cert_path) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let rsa_pss256_cert = std::fs::read(cert_path).unwrap(); - - if let Ok(signcert) = openssl::x509::X509::from_pem(&es256_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es256_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&es384_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es384_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&es512_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(es512_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } - if let Ok(signcert) = openssl::x509::X509::from_pem(&rsa_pss256_cert) { + if let Ok(signcert) = openssl::x509::X509::from_pem(rsa_pss256_cert) { let der_bytes = signcert.to_der().unwrap(); assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } @@ -1466,7 +1459,7 @@ pub mod tests { let box_size = 10000; - let signer = crate::utils::test::temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let cose_bytes = crate::cose_sign::sign_claim(&claim_bytes, signer.as_ref(), box_size).unwrap(); @@ -1480,6 +1473,10 @@ pub mod tests { #[test] #[cfg(feature = "openssl_sign")] fn test_stapled_ocsp() { + use c2pa_crypto::raw_signature::{ + signer_from_cert_chain_and_private_key, RawSigner, RawSignerError, + }; + let mut validation_log = DetailedStatusTracker::default(); let mut claim = crate::claim::Claim::new("ocsp_sign_test", Some("contentauth"), 1); @@ -1491,13 +1488,9 @@ pub mod tests { let pem_key = include_bytes!("../tests/fixtures/certs/ps256.pem").to_vec(); let ocsp_rsp_data = include_bytes!("../tests/fixtures/ocsp_good.data"); - let signer = crate::openssl::RsaSigner::from_signcert_and_pkey( - &sign_cert, - &pem_key, - SigningAlg::Ps256, - None, - ) - .unwrap(); + let signer = + signer_from_cert_chain_and_private_key(&sign_cert, &pem_key, SigningAlg::Ps256, None) + .unwrap(); // create a test signer that supports stapling struct OcspSigner { @@ -1528,7 +1521,7 @@ pub mod tests { } let ocsp_signer = OcspSigner { - signer: Box::new(signer), + signer: Box::new(crate::signer::RawSignerWrapper(signer)), ocsp_rsp: ocsp_rsp_data.to_vec(), }; diff --git a/sdk/src/create_signer.rs b/sdk/src/create_signer.rs index 176cfdf07..67fb1887f 100644 --- a/sdk/src/create_signer.rs +++ b/sdk/src/create_signer.rs @@ -18,14 +18,9 @@ #[cfg(feature = "file_io")] use std::path::Path; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::signer_from_cert_chain_and_private_key, SigningAlg}; -use crate::{ - error::Result, - openssl::{EcSigner, EdSigner, RsaSigner}, - signer::ConfigurableSigner, - Signer, -}; +use crate::{error::Result, signer::RawSignerWrapper, Signer}; /// Creates a [`Signer`] instance using signing certificate and private key /// as byte slices. @@ -45,17 +40,9 @@ pub fn from_keys( alg: SigningAlg, tsa_url: Option, ) -> Result> { - Ok(match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Box::new( - RsaSigner::from_signcert_and_pkey(signcert, pkey, alg, tsa_url)?, - ), - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Box::new( - EcSigner::from_signcert_and_pkey(signcert, pkey, alg, tsa_url)?, - ), - SigningAlg::Ed25519 => Box::new(EdSigner::from_signcert_and_pkey( - signcert, pkey, alg, tsa_url, - )?), - }) + Ok(Box::new(RawSignerWrapper( + signer_from_cert_chain_and_private_key(signcert, pkey, alg, tsa_url)?, + ))) } /// Creates a [`Signer`] instance using signing certificate and @@ -74,18 +61,8 @@ pub fn from_files>( alg: SigningAlg, tsa_url: Option, ) -> Result> { - Ok(match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => Box::new( - RsaSigner::from_files(&signcert_path, &pkey_path, alg, tsa_url)?, - ), - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => Box::new( - EcSigner::from_files(&signcert_path, &pkey_path, alg, tsa_url)?, - ), - SigningAlg::Ed25519 => Box::new(EdSigner::from_files( - &signcert_path, - &pkey_path, - alg, - tsa_url, - )?), - }) + let cert_chain = std::fs::read(signcert_path)?; + let private_key = std::fs::read(pkey_path)?; + + from_keys(&cert_chain, &private_key, alg, tsa_url) } diff --git a/sdk/src/error.rs b/sdk/src/error.rs index e4d6429bd..0cd6cca08 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -312,6 +312,9 @@ pub enum Error { #[error(transparent)] RawSignatureValidationError(#[from] c2pa_crypto::raw_signature::RawSignatureValidationError), + + #[error(transparent)] + RawSignerError(#[from] c2pa_crypto::raw_signature::RawSignerError), } /// A specialized `Result` type for C2PA toolkit operations. diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs index 68a3659e4..cffb16cf2 100644 --- a/sdk/src/ingredient.rs +++ b/sdk/src/ingredient.rs @@ -1540,13 +1540,13 @@ mod tests { assert_eq!(&ingredient.title, title); assert_eq!(ingredient.format(), format); assert!(ingredient.manifest_data().is_some()); - assert!(ingredient.metadata().is_none()); + assert_eq!(ingredient.metadata(), None); #[cfg(target_arch = "wasm32")] web_sys::console::debug_2( &"ingredient_from_memory_async:".into(), &ingredient.to_string().into(), ); - assert!(ingredient.validation_status().is_none()); + assert_eq!(ingredient.validation_status(), None); } #[cfg_attr(not(target_arch = "wasm32"), test)] @@ -1563,8 +1563,8 @@ mod tests { assert_eq!(&ingredient.title, title); assert_eq!(ingredient.format(), format); assert!(ingredient.manifest_data().is_some()); - assert!(ingredient.metadata().is_none()); - assert!(ingredient.validation_status().is_none()); + assert_eq!(ingredient.metadata(), None); + assert_eq!(ingredient.validation_status(), None); } #[cfg_attr(not(target_arch = "wasm32"), actix::test)] @@ -1584,7 +1584,7 @@ mod tests { #[cfg(feature = "add_thumbnails")] assert!(ingredient.thumbnail().is_some()); assert!(ingredient.manifest_data().is_some()); - assert!(ingredient.metadata().is_none()); + assert_eq!(ingredient.metadata(), None); assert!(ingredient.validation_status().is_some()); assert_eq!( ingredient.validation_status().unwrap()[0].code(), @@ -1607,7 +1607,7 @@ mod tests { assert!(ingredient.provenance().is_some()); assert!(ingredient.provenance().unwrap().starts_with("https:")); assert!(ingredient.manifest_data().is_some()); - assert!(ingredient.validation_status().is_none()); + assert_eq!(ingredient.validation_status(), None); } #[allow(dead_code)] @@ -1629,7 +1629,7 @@ mod tests { .url() .unwrap() .starts_with("http")); - assert!(ingredient.manifest_data().is_none()); + assert_eq!(ingredient.manifest_data(), None); } #[cfg_attr(not(target_arch = "wasm32"), actix::test)] @@ -1650,7 +1650,7 @@ mod tests { &"ingredient_from_memory_async:".into(), &ingredient.to_string().into(), ); - assert!(ingredient.validation_status().is_none()); + assert_eq!(ingredient.validation_status(), None); assert!(ingredient.manifest_data().is_some()); assert!(ingredient.provenance().is_some()); } @@ -1695,7 +1695,7 @@ mod tests_file_io { assert!(ingredient.thumbnail().is_some()); assert_eq!(ingredient.thumbnail().unwrap().0, format); } else { - assert!(ingredient.thumbnail().is_none()); + assert_eq!(ingredient.thumbnail(), None); } } @@ -1711,8 +1711,8 @@ mod tests_file_io { println!("ingredient = {ingredient}"); assert_eq!(ingredient.title(), "Purple Square.psd"); assert_eq!(ingredient.format(), "image/vnd.adobe.photoshop"); - assert!(ingredient.thumbnail().is_none()); // should always be none - assert!(ingredient.manifest_data().is_none()); + assert_eq!(ingredient.thumbnail(), None); // should always be none + assert_eq!(ingredient.manifest_data(), None); } #[test] @@ -1732,7 +1732,7 @@ mod tests_file_io { .identifier .starts_with("self#jumbf=")); assert!(ingredient.manifest_data().is_some()); - assert!(ingredient.metadata().is_none()); + assert_eq!(ingredient.metadata(), None); } #[test] @@ -1746,9 +1746,9 @@ mod tests_file_io { assert_eq!(&ingredient.title, NO_MANIFEST_JPEG); assert_eq!(ingredient.format(), "image/jpeg"); test_thumbnail(&ingredient, "image/jpeg"); - assert!(ingredient.provenance().is_none()); - assert!(ingredient.manifest_data().is_none()); - assert!(ingredient.metadata().is_none()); + assert_eq!(ingredient.provenance(), None); + assert_eq!(ingredient.manifest_data(), None); + assert_eq!(ingredient.metadata(), None); assert!(ingredient.instance_id().starts_with("xmp.iid:")); #[cfg(feature = "add_thumbnails")] assert!(ingredient @@ -1784,8 +1784,8 @@ mod tests_file_io { assert_eq!(ingredient.format(), "image/jpeg"); assert_eq!(ingredient.hash(), Some("1234568abcdef")); assert_eq!(ingredient.thumbnail_ref().unwrap().format, "image/foo"); // always generated - assert!(ingredient.manifest_data().is_none()); - assert!(ingredient.metadata().is_none()); + assert_eq!(ingredient.manifest_data(), None); + assert_eq!(ingredient.metadata(), None); } #[test] @@ -1798,8 +1798,8 @@ mod tests_file_io { println!("ingredient = {ingredient}"); assert_eq!(ingredient.title(), "libpng-test.png"); test_thumbnail(&ingredient, "image/png"); - assert!(ingredient.provenance().is_none()); - assert!(ingredient.manifest_data.is_none()); + assert_eq!(ingredient.provenance(), None); + assert_eq!(ingredient.manifest_data, None); } #[test] @@ -1834,7 +1834,7 @@ mod tests_file_io { assert_eq!(ingredient.format(), "image/jpeg"); test_thumbnail(&ingredient, "image/jpeg"); assert!(ingredient.provenance().is_some()); - assert!(ingredient.manifest_data().is_none()); + assert_eq!(ingredient.manifest_data(), None); assert!(ingredient.validation_status().is_some()); assert_eq!( ingredient.validation_status().unwrap()[0].code(), @@ -1848,7 +1848,7 @@ mod tests_file_io { let ap = fixture_path("CIE-sig-CA.jpg"); let ingredient = Ingredient::from_file(ap).expect("from_file"); // println!("ingredient = {ingredient}"); - assert!(ingredient.validation_status().is_none()); + assert_eq!(ingredient.validation_status(), None); assert!(ingredient.manifest_data().is_some()); } @@ -1900,11 +1900,11 @@ mod tests_file_io { let mut ingredient = Ingredient::new("title", "format", "instance_id"); ingredient.resources.set_base_path(folder); - assert!(ingredient.thumbnail_ref().is_none()); + assert_eq!(ingredient.thumbnail_ref(), None); // assert!(ingredient // .set_manifest_data_ref(ResourceRef::new("image/jpg", "foo")) // .is_err()); - assert!(ingredient.manifest_data_ref().is_none()); + assert_eq!(ingredient.manifest_data_ref(), None); // verify we can set a reference assert!(ingredient .set_thumbnail_ref(ResourceRef::new("image/jpg", "C.jpg")) diff --git a/sdk/src/jumbf/labels.rs b/sdk/src/jumbf/labels.rs index c02cfc4cc..6bc17d697 100644 --- a/sdk/src/jumbf/labels.rs +++ b/sdk/src/jumbf/labels.rs @@ -327,7 +327,7 @@ pub mod tests { Some(assertion.to_string()), assertion_label_from_uri(&assertion_uri) ); - assert_eq!(None, assertion_label_from_uri(&absolute_uri)); + assert_eq!(assertion_label_from_uri(&absolute_uri), None); let assertion_relative = to_relative_uri(&assertion_uri); diff --git a/sdk/src/jumbf_io.rs b/sdk/src/jumbf_io.rs index 677ec9be7..7e11ae36e 100644 --- a/sdk/src/jumbf_io.rs +++ b/sdk/src/jumbf_io.rs @@ -349,10 +349,12 @@ pub mod tests { use std::io::Seek; + use c2pa_crypto::SigningAlg; + use super::*; use crate::{ asset_io::RemoteRefEmbedType, - utils::test::{create_test_store, temp_signer}, + utils::{test::create_test_store, test_signer::test_signer}, }; #[test] @@ -447,7 +449,7 @@ pub mod tests { fn test_jumbf(asset_type: &str, reader: &mut dyn CAIRead) { let mut writer = Cursor::new(Vec::new()); let store = create_test_store().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let jumbf = store.to_jumbf(&*signer).unwrap(); save_jumbf_to_stream(asset_type, reader, &mut writer, &jumbf).unwrap(); writer.set_position(0); diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs index 84a1d5b12..77e4c8fd9 100644 --- a/sdk/src/manifest.rs +++ b/sdk/src/manifest.rs @@ -1504,6 +1504,8 @@ pub(crate) mod tests { use std::io::Cursor; + #[cfg(any(feature = "file_io", target_arch = "wasm32"))] + use c2pa_crypto::SigningAlg; #[cfg(feature = "file_io")] use c2pa_status_tracker::{DetailedStatusTracker, StatusTracker}; #[cfg(feature = "file_io")] @@ -1520,7 +1522,8 @@ pub(crate) mod tests { ingredient::Ingredient, reader::Reader, store::Store, - utils::test::{static_test_uuid, temp_remote_signer, temp_signer, TEST_VC}, + utils::test::{static_test_uuid, temp_remote_signer, TEST_VC}, + utils::test_signer::{async_test_signer, test_signer}, Manifest, Result, }; #[cfg(feature = "file_io")] @@ -1601,7 +1604,7 @@ pub(crate) mod tests { let test_output = dir.path().join("wc_embed_test.jpg"); //embed a claim generated from this manifest - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _store = manifest .embed(&source_path, &test_output, signer.as_ref()) @@ -1612,7 +1615,7 @@ pub(crate) mod tests { if cfg!(feature = "add_thumbnails") { assert!(manifest.thumbnail().is_some()); } else { - assert!(manifest.thumbnail().is_none()); + assert_eq!(manifest.thumbnail(), None); } let ingredient = Ingredient::from_file(&test_output).expect("load_from_asset"); assert!(ingredient.active_manifest().is_some()); @@ -1772,7 +1775,7 @@ pub(crate) mod tests { ) .expect("add_assertion"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let c2pa_data = manifest .embed(&output, &output, signer.as_ref()) @@ -1797,7 +1800,7 @@ pub(crate) mod tests { .expect("add_redaction"); //embed a claim in output2 - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let _store2 = manifest2 .embed(&output2, &output2, signer.as_ref()) .expect("embed"); @@ -1816,7 +1819,7 @@ pub(crate) mod tests { let redacted_uri = &claim2.redactions().unwrap()[0]; let claim1 = store3.get_claim(&claim1_label).unwrap(); - assert!(claim1.get_claim_assertion(redacted_uri, 0).is_none()); + assert_eq!(claim1.get_claim_assertion(redacted_uri, 0), None); } #[test] @@ -1838,7 +1841,7 @@ pub(crate) mod tests { .add_assertion(&actions) .expect("add_assertion"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); parent_manifest .embed(&parent_output, &parent_output, signer.as_ref()) .expect("embed"); @@ -1895,8 +1898,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let async_signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(crate::SigningAlg::Ps256); + let async_signer = async_test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest @@ -1937,10 +1939,8 @@ pub(crate) mod tests { fn test_embed_user_label() { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let my_guid = static_test_uuid(); - - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest.set_label(my_guid); @@ -1965,7 +1965,7 @@ pub(crate) mod tests { let fp = format!("file:/{}", sidecar.to_str().unwrap()); let url = url::Url::parse(&fp).unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); manifest.set_label(static_test_uuid()); @@ -2106,7 +2106,8 @@ pub(crate) mod tests { )) .unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + let mut output = Cursor::new(Vec::new()); // Embed a manifest using the signer. manifest @@ -2128,7 +2129,7 @@ pub(crate) mod tests { #[cfg_attr(feature = "openssl_sign", actix::test)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] async fn test_embed_from_memory_async() { - use crate::{assertions::User, utils::test::temp_async_signer}; + use crate::assertions::User; let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); // convert buffer to cursor with Read/Write/Seek capability let mut stream = std::io::Cursor::new(image.to_vec()); @@ -2144,8 +2145,9 @@ pub(crate) mod tests { )) .unwrap(); - let signer = temp_async_signer(); + let signer = async_test_signer(SigningAlg::Ed25519); let mut output = Cursor::new(Vec::new()); + // Embed a manifest using the signer. manifest .embed_to_stream_async("jpeg", &mut stream, &mut output, signer.as_ref()) @@ -2173,7 +2175,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); let ingredient = @@ -2210,7 +2212,7 @@ pub(crate) mod tests { let fp = format!("file:/{}", sidecar.to_str().unwrap()); let url = url::Url::parse(&fp).unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let parent = Ingredient::from_file(fixture_path("XCA.jpg")).expect("getting parent"); let mut manifest = test_manifest(); @@ -2237,7 +2239,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = test_manifest(); let thumb_data = vec![1, 2, 3]; @@ -2399,7 +2401,8 @@ pub(crate) mod tests { // convert buffer to cursor with Read/Write/Seek capability let mut input = std::io::Cursor::new(image.to_vec()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let mut output = Cursor::new(Vec::new()); manifest @@ -2466,7 +2469,8 @@ pub(crate) mod tests { let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let output_image = manifest .embed_from_memory("jpeg", image, signer.as_ref()) @@ -2531,7 +2535,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_SMALL_JPEG); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json"); manifest.with_base_path(fixtures).expect("with_base"); @@ -2558,7 +2562,7 @@ pub(crate) mod tests { let temp_dir = tempdir().expect("temp dir"); let output = temp_fixture_path(&temp_dir, TEST_WEBP); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::from_json(MANIFEST_JSON).expect("from_json"); manifest.with_base_path(fixtures).expect("with_base"); @@ -2589,14 +2593,14 @@ pub(crate) mod tests { assert!(manifest .set_thumbnail_ref(ResourceRef::new("image/jpg", "foo")) .is_err()); - assert!(manifest.thumbnail_ref().is_none()); + assert_eq!(manifest.thumbnail_ref(), None); // verify we can set a references that do exist assert!(manifest .set_thumbnail_ref(ResourceRef::new("image/jpeg", "C.jpg")) .is_ok()); assert!(manifest.thumbnail_ref().is_some()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); manifest .embed(&output, &output, signer.as_ref()) .expect("embed"); @@ -2621,9 +2625,9 @@ pub(crate) mod tests { // verify there is a thumbnail ref assert!(manifest.thumbnail_ref().is_some()); // verify there is no thumbnail - assert!(manifest.thumbnail().is_none()); + assert_eq!(manifest.thumbnail(), None); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); manifest .embed(&output, &output, signer.as_ref()) .expect("embed"); @@ -2631,8 +2635,8 @@ pub(crate) mod tests { let manifest_store = Reader::from_file(&output).expect("from_file"); println!("{manifest_store}"); let active_manifest = manifest_store.active_manifest().unwrap(); - assert!(active_manifest.thumbnail_ref().is_none()); - assert!(active_manifest.thumbnail().is_none()); + assert_eq!(active_manifest.thumbnail_ref(), None); + assert_eq!(active_manifest.thumbnail(), None); } #[test] @@ -2652,9 +2656,11 @@ pub(crate) mod tests { let mut source = std::io::Cursor::new(vec![1, 2, 3]); let mut dest = std::io::Cursor::new(Vec::new()); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + let result = manifest.embed_to_stream("image/jpeg", &mut source, &mut dest, signer.as_ref()); + assert!(result.is_err()); assert!(result .unwrap_err() @@ -2668,7 +2674,7 @@ pub(crate) mod tests { fn test_data_hash_embeddable_manifest() { let ap = fixture_path("cloud.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let mut manifest = Manifest::new("claim_generator"); @@ -2717,7 +2723,7 @@ pub(crate) mod tests { let manifest_store = Reader::from_file(&output).expect("from_file"); println!("{manifest_store}"); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); } #[cfg(all(feature = "file_io", feature = "openssl_sign"))] @@ -2776,7 +2782,7 @@ pub(crate) mod tests { let manifest_store = Reader::from_file(&output).expect("from_file"); println!("{manifest_store}"); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); } #[test] @@ -2794,7 +2800,7 @@ pub(crate) mod tests { .add_labeled_assertion(crate::assertions::labels::BOX_HASH, &box_hash) .unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let embeddable = manifest .box_hash_embeddable_manifest(signer.as_ref(), None) @@ -2809,6 +2815,6 @@ pub(crate) mod tests { .unwrap(); println!("{reader}"); assert!(reader.active_manifest().is_some()); - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); } } diff --git a/sdk/src/manifest_store.rs b/sdk/src/manifest_store.rs index 5ac0d989f..3c072e632 100644 --- a/sdk/src/manifest_store.rs +++ b/sdk/src/manifest_store.rs @@ -628,7 +628,7 @@ mod tests { assert!(manifest_store.active_label().is_some()); assert!(manifest_store.get_active().is_some()); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.get_active().unwrap(); assert!(!manifest.ingredients().is_empty()); assert_eq!(manifest.issuer().unwrap(), "C2PA Test Signing Cert"); @@ -650,7 +650,7 @@ mod tests { assert!(manifest_store.active_label().is_some()); assert!(manifest_store.get_active().is_some()); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.get_active().unwrap(); assert!(!manifest.ingredients().is_empty()); assert_eq!(manifest.issuer().unwrap(), "C2PA Test Signing Cert"); @@ -668,7 +668,7 @@ mod tests { assert!(manifest_store.active_label().is_some()); assert!(manifest_store.get_active().is_some()); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.get_active().unwrap(); assert!(!manifest.ingredients().is_empty()); assert_eq!(manifest.issuer().unwrap(), "C2PA Test Signing Cert"); @@ -691,7 +691,7 @@ mod tests { .await .unwrap(); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); println!("{manifest_store}"); } @@ -710,7 +710,7 @@ mod tests { assert!(manifest_store.active_label().is_some()); assert!(manifest_store.get_active().is_some()); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.get_active().unwrap(); assert!(!manifest.ingredients().is_empty()); assert_eq!(manifest.issuer().unwrap(), "C2PA Test Signing Cert"); @@ -729,7 +729,7 @@ mod tests { assert!(manifest_store.active_label().is_some()); assert!(manifest_store.get_active().is_some()); assert!(!manifest_store.manifests().is_empty()); - assert!(manifest_store.validation_status().is_none()); + assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.get_active().unwrap(); assert!(!manifest.ingredients().is_empty()); assert_eq!(manifest.issuer().unwrap(), "C2PA Test Signing Cert"); diff --git a/sdk/src/openssl/ec_signer.rs b/sdk/src/openssl/ec_signer.rs deleted file mode 100644 index 136cf328e..000000000 --- a/sdk/src/openssl/ec_signer.rs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg}; -use openssl::{ - ec::EcKey, - hash::MessageDigest, - pkey::{PKey, Private}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{ - error::{Error, Result}, - signer::ConfigurableSigner, - utils::sig_utils::der_to_p1363, - Signer, -}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// ECDSA encryption. -pub struct EcSigner { - signcerts: Vec, - pkey: EcKey, - - certs_size: usize, - timestamp_size: usize, - - alg: SigningAlg, - tsa_url: Option, -} - -impl ConfigurableSigner for EcSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let certs_size = signcert.len(); - let pkey = EcKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?; - let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?; - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - Ok(EcSigner { - signcerts, - pkey, - certs_size, - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - alg, - tsa_url, - }) - } -} - -impl Signer for EcSigner { - fn sign(&self, data: &[u8]) -> Result> { - let _openssl = OpenSslMutex::acquire()?; - - let key = PKey::from_ec_key(self.pkey.clone()).map_err(Error::OpenSslError)?; - - let mut signer = match self.alg { - SigningAlg::Es256 => openssl::sign::Signer::new(MessageDigest::sha256(), &key)?, - SigningAlg::Es384 => openssl::sign::Signer::new(MessageDigest::sha384(), &key)?, - SigningAlg::Es512 => openssl::sign::Signer::new(MessageDigest::sha512(), &key)?, - _ => return Err(Error::UnsupportedType), - }; - - signer.update(data).map_err(Error::OpenSslError)?; - let der_sig = signer.sign_to_vec().map_err(Error::OpenSslError)?; - - der_to_p1363(&der_sig, self.alg) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(Error::OpenSslError)?; - certs.push(cert); - } - - Ok(certs) - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size // the Cose_Sign1 contains complete certs and timestamps so account for size - } -} - -#[cfg(test)] -#[cfg(feature = "file_io")] -mod tests { - #![allow(clippy::unwrap_used)] - - use super::*; - use crate::{openssl::temp_signer, utils::test::fixture_path}; - - #[test] - fn es256_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es256, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn es384_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es384, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn es512_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ec_signer(cert_dir, SigningAlg::Es512, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } -} diff --git a/sdk/src/openssl/ed_signer.rs b/sdk/src/openssl/ed_signer.rs deleted file mode 100644 index 21202eebf..000000000 --- a/sdk/src/openssl/ed_signer.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -use c2pa_crypto::{openssl::OpenSslMutex, SigningAlg}; -use openssl::{ - pkey::{PKey, Private}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{signer::ConfigurableSigner, Error, Result, Signer}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// Edwards Curve encryption. -pub struct EdSigner { - signcerts: Vec, - pkey: PKey, - - certs_size: usize, - timestamp_size: usize, - - alg: SigningAlg, - tsa_url: Option, -} - -impl ConfigurableSigner for EdSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let certs_size = signcert.len(); - let signcerts = X509::stack_from_pem(signcert).map_err(Error::OpenSslError)?; - let pkey = PKey::private_key_from_pem(pkey).map_err(Error::OpenSslError)?; - - if alg != SigningAlg::Ed25519 { - return Err(Error::UnsupportedType); // only ed25519 is supported by C2PA - } - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - Ok(EdSigner { - signcerts, - pkey, - certs_size, - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - alg, - tsa_url, - }) - } -} - -impl Signer for EdSigner { - fn sign(&self, data: &[u8]) -> Result> { - let _openssl = OpenSslMutex::acquire()?; - - let mut signer = - openssl::sign::Signer::new_without_digest(&self.pkey).map_err(Error::OpenSslError)?; - - let signed_data = signer.sign_oneshot_to_vec(data)?; - - Ok(signed_data) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(Error::OpenSslError)?; - certs.push(cert); - } - - Ok(certs) - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size // the Cose_Sign1 contains complete certs and timestamps so account for size - } -} - -#[cfg(test)] -#[cfg(feature = "file_io")] -mod tests { - #![allow(clippy::unwrap_used)] - use super::*; - use crate::{openssl::temp_signer, utils::test::fixture_path}; - - #[test] - fn ed25519_signer() { - let cert_dir = fixture_path("certs"); - - let (signer, _) = temp_signer::get_ed_signer(cert_dir, SigningAlg::Ed25519, None); - - let data = b"some sample content to sign"; - println!("data len = {}", data.len()); - - let signature = signer.sign(data).unwrap(); - println!("signature.len = {}", signature.len()); - assert!(signature.len() >= 64); - assert!(signature.len() <= signer.reserve_size()); - } -} diff --git a/sdk/src/openssl/mod.rs b/sdk/src/openssl/mod.rs index 13dbaeb83..43c7b4a8f 100644 --- a/sdk/src/openssl/mod.rs +++ b/sdk/src/openssl/mod.rs @@ -11,94 +11,10 @@ // specific language governing permissions and limitations under // each license. -#[cfg(feature = "openssl_sign")] -mod rsa_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use rsa_signer::RsaSigner; - -#[cfg(feature = "openssl_sign")] -mod ec_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use ec_signer::EcSigner; - -#[cfg(feature = "openssl_sign")] -mod ed_signer; -#[cfg(feature = "openssl_sign")] -pub(crate) use ed_signer::EdSigner; - #[cfg(feature = "openssl")] mod openssl_trust_handler; -#[cfg(test)] -pub(crate) mod temp_signer; #[cfg(feature = "openssl")] pub(crate) use openssl_trust_handler::verify_trust; #[cfg(feature = "openssl")] pub(crate) use openssl_trust_handler::OpenSSLTrustHandlerConfig; - -#[cfg(test)] -pub(crate) mod temp_signer_async; - -#[cfg(feature = "openssl")] -use openssl::x509::X509; -#[cfg(test)] -#[allow(unused_imports)] -#[cfg(feature = "openssl")] -pub(crate) use temp_signer_async::AsyncSignerAdapter; - -#[cfg(feature = "openssl")] -fn check_chain_order(certs: &[X509]) -> bool { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - { - if certs.len() > 1 { - for (i, c) in certs.iter().enumerate() { - if let Some(next_c) = certs.get(i + 1) { - if let Ok(pkey) = next_c.public_key() { - if let Ok(verified) = c.verify(&pkey) { - if !verified { - return false; - } - } else { - return false; - } - } else { - return false; - } - } - } - } - true - } -} - -#[cfg(not(feature = "openssl"))] -fn check_chain_order(certs: &[X509]) -> bool { - true -} - -#[cfg(feature = "openssl")] -#[allow(dead_code)] -fn check_chain_order_der(cert_ders: &[Vec]) -> bool { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - let mut certs: Vec = Vec::new(); - for cert_der in cert_ders { - if let Ok(cert) = X509::from_der(cert_der) { - certs.push(cert); - } else { - return false; - } - } - - check_chain_order(&certs) -} - -#[cfg(not(feature = "openssl"))] -fn check_chain_order_der(cert_ders: &[Vec]) -> bool { - true -} diff --git a/sdk/src/openssl/openssl_trust_handler.rs b/sdk/src/openssl/openssl_trust_handler.rs index b7ddee647..2e3b69d43 100644 --- a/sdk/src/openssl/openssl_trust_handler.rs +++ b/sdk/src/openssl/openssl_trust_handler.rs @@ -310,28 +310,23 @@ pub mod tests { use c2pa_crypto::SigningAlg; use super::*; - use crate::{ - openssl::temp_signer::{self}, - Signer, - }; + use crate::{utils::test_signer::test_signer, Signer}; #[test] fn test_trust_store() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); th.load_default_trust().unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -352,7 +347,6 @@ pub mod tests { #[test] fn test_broken_trust_chain() { - let cert_dir = crate::utils::test::fixture_path("certs"); let ta = include_bytes!("../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); let mut th = OpenSSLTrustHandlerConfig::new(); @@ -363,13 +357,13 @@ pub mod tests { th.load_trust_anchors_from_data(&mut reader).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -391,8 +385,6 @@ pub mod tests { #[test] fn test_allowed_list() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); @@ -405,13 +397,13 @@ pub mod tests { th.load_allowed_list(&mut allowed_list).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); @@ -432,8 +424,6 @@ pub mod tests { #[test] fn test_allowed_list_hashes() { - let cert_dir = crate::utils::test::fixture_path("certs"); - let mut th = OpenSSLTrustHandlerConfig::new(); th.clear(); @@ -446,13 +436,13 @@ pub mod tests { th.load_allowed_list(&mut allowed_list).unwrap(); // test all the certs - let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); - let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); - let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); - let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); - let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); - let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); - let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + let ps256 = test_signer(SigningAlg::Ps256); + let ps384 = test_signer(SigningAlg::Ps384); + let ps512 = test_signer(SigningAlg::Ps512); + let es256 = test_signer(SigningAlg::Es256); + let es384 = test_signer(SigningAlg::Es384); + let es512 = test_signer(SigningAlg::Es512); + let ed25519 = test_signer(SigningAlg::Ed25519); let ps256_certs = ps256.certs().unwrap(); let ps384_certs = ps384.certs().unwrap(); diff --git a/sdk/src/openssl/rsa_signer.rs b/sdk/src/openssl/rsa_signer.rs deleted file mode 100644 index ce19a5004..000000000 --- a/sdk/src/openssl/rsa_signer.rs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -use std::cell::Cell; - -use c2pa_crypto::{ - ocsp::OcspResponse, openssl::OpenSslMutex, time_stamp::TimeStampProvider, SigningAlg, -}; -use openssl::{ - hash::MessageDigest, - pkey::{PKey, Private}, - rsa::{Rsa, RsaPrivateKeyBuilder}, - x509::X509, -}; - -use super::check_chain_order; -use crate::{signer::ConfigurableSigner, Error, Result, Signer}; - -/// Implements `Signer` trait using OpenSSL's implementation of -/// SHA256 + RSA encryption. -pub struct RsaSigner { - signcerts: Vec, - pkey: PKey, - - certs_size: usize, - timestamp_size: usize, - ocsp_size: Cell, - - alg: SigningAlg, - tsa_url: Option, - ocsp_rsp: Cell, -} - -impl RsaSigner { - // Sample of OCSP stapling while signing. This code is only for demo purposes and not for - // production use since there is no caching in the SDK and fetching is expensive. This is behind the - // feature flag 'psxxx_ocsp_stapling_experimental' - fn update_ocsp(&self) { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - // do we need an update - let now = chrono::offset::Utc::now(); - - // is it time for an OCSP update - let ocsp_data = self.ocsp_rsp.take(); - let next_update = ocsp_data.next_update; - self.ocsp_rsp.set(ocsp_data); - if now > next_update { - #[cfg(feature = "psxxx_ocsp_stapling_experimental")] - { - if let Ok(certs) = self.certs_internal() { - if let Some(ocsp_rsp) = c2pa_crypto::ocsp::fetch_ocsp_response(&certs) { - self.ocsp_size.set(ocsp_rsp.len()); - let mut validation_log = - c2pa_status_tracker::DetailedStatusTracker::default(); - if let Ok(ocsp_response) = - OcspResponse::from_der_checked(&ocsp_rsp, None, &mut validation_log) - { - self.ocsp_rsp.set(ocsp_response); - } - } - } - } - } - } - - fn certs_internal(&self) -> Result>> { - // IMPORTANT: ffi_mutex::acquire() should have been called by calling fn. Please - // don't make this pub or pub(crate) without finding a way to ensure that - // precondition. - - let mut certs: Vec> = Vec::new(); - - for c in &self.signcerts { - let cert = c.to_der().map_err(wrap_openssl_err)?; - certs.push(cert); - } - - Ok(certs) - } -} - -impl ConfigurableSigner for RsaSigner { - fn from_signcert_and_pkey( - signcert: &[u8], - pkey: &[u8], - alg: SigningAlg, - tsa_url: Option, - ) -> Result { - let _openssl = OpenSslMutex::acquire()?; - - let signcerts = X509::stack_from_pem(signcert).map_err(wrap_openssl_err)?; - let rsa = Rsa::private_key_from_pem(pkey).map_err(wrap_openssl_err)?; - - // make sure cert chains are in order - if !check_chain_order(&signcerts) { - return Err(Error::BadParam( - "certificate chain is not in correct order".to_string(), - )); - } - - // rebuild RSA keys to eliminate incompatible values - let n = rsa.n().to_owned().map_err(wrap_openssl_err)?; - let e = rsa.e().to_owned().map_err(wrap_openssl_err)?; - let d = rsa.d().to_owned().map_err(wrap_openssl_err)?; - let po = rsa.p(); - let qo = rsa.q(); - let dmp1o = rsa.dmp1(); - let dmq1o = rsa.dmq1(); - let iqmpo = rsa.iqmp(); - let mut builder = RsaPrivateKeyBuilder::new(n, e, d).map_err(wrap_openssl_err)?; - - if let Some(p) = po { - if let Some(q) = qo { - builder = builder - .set_factors(p.to_owned()?, q.to_owned()?) - .map_err(wrap_openssl_err)?; - } - } - - if let Some(dmp1) = dmp1o { - if let Some(dmq1) = dmq1o { - if let Some(iqmp) = iqmpo { - builder = builder - .set_crt_params(dmp1.to_owned()?, dmq1.to_owned()?, iqmp.to_owned()?) - .map_err(wrap_openssl_err)?; - } - } - } - - let new_rsa = builder.build(); - - let pkey = PKey::from_rsa(new_rsa).map_err(wrap_openssl_err)?; - - let signer = RsaSigner { - signcerts, - pkey, - certs_size: signcert.len(), - timestamp_size: 10000, /* todo: call out to TSA to get actual timestamp and use that size */ - ocsp_size: Cell::new(0), - alg, - tsa_url, - ocsp_rsp: Cell::new(OcspResponse::default()), - }; - - // get OCSP if possible - signer.update_ocsp(); - - Ok(signer) - } -} - -impl Signer for RsaSigner { - fn sign(&self, data: &[u8]) -> Result> { - let mut signer = match self.alg { - SigningAlg::Ps256 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha256())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - SigningAlg::Ps384 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha384(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha384())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - SigningAlg::Ps512 => { - let mut signer = openssl::sign::Signer::new(MessageDigest::sha512(), &self.pkey) - .map_err(wrap_openssl_err)?; - - signer.set_rsa_padding(openssl::rsa::Padding::PKCS1_PSS)?; // use C2PA recommended padding - signer.set_rsa_mgf1_md(MessageDigest::sha512())?; - signer.set_rsa_pss_saltlen(openssl::sign::RsaPssSaltlen::DIGEST_LENGTH)?; - signer - } - // "rs256" => openssl::sign::Signer::new(MessageDigest::sha256(), &self.pkey) - // .map_err(wrap_openssl_err)?, - // "rs384" => openssl::sign::Signer::new(MessageDigest::sha384(), &self.pkey) - // .map_err(wrap_openssl_err)?, - // "rs512" => openssl::sign::Signer::new(MessageDigest::sha512(), &self.pkey) - // .map_err(wrap_openssl_err)?, - _ => return Err(Error::UnsupportedType), - }; - - let signed_data = signer.sign_oneshot_to_vec(data)?; - - // println!("sig: {}", Hexlify(&signed_data)); - - Ok(signed_data) - } - - fn reserve_size(&self) -> usize { - 1024 + self.certs_size + self.timestamp_size + self.ocsp_size.get() // the Cose_Sign1 contains complete certs, timestamps and ocsp so account for size - } - - fn certs(&self) -> Result>> { - let _openssl = OpenSslMutex::acquire()?; - self.certs_internal() - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn ocsp_val(&self) -> Option> { - let _openssl = OpenSslMutex::acquire().ok()?; - - // update OCSP if needed - self.update_ocsp(); - - let ocsp_data = self.ocsp_rsp.take(); - let ocsp_rsp = ocsp_data.ocsp_der.clone(); - self.ocsp_rsp.set(ocsp_data); - if !ocsp_rsp.is_empty() { - Some(ocsp_rsp) - } else { - None - } - } -} - -impl TimeStampProvider for RsaSigner { - fn time_stamp_service_url(&self) -> Option { - self.tsa_url.clone() - } -} - -fn wrap_openssl_err(err: openssl::error::ErrorStack) -> Error { - Error::OpenSslError(err) -} - -#[allow(unused_imports)] -#[allow(clippy::unwrap_used)] -#[cfg(test)] -mod tests { - - use super::*; - use crate::{ - utils::test::{fixture_path, temp_signer}, - Signer, SigningAlg, - }; - - #[test] - fn signer_from_files() { - let signer = temp_signer(); - let data = b"some sample content to sign"; - - let signature = signer.sign(data).unwrap(); - println!("signature len = {}", signature.len()); - assert!(signature.len() <= signer.reserve_size()); - } - - #[test] - fn sign_ps256() { - let cert_bytes = include_bytes!("../../tests/fixtures/temp_cert.data"); - let key_bytes = include_bytes!("../../tests/fixtures/temp_priv_key.data"); - - let signer = - RsaSigner::from_signcert_and_pkey(cert_bytes, key_bytes, SigningAlg::Ps256, None) - .unwrap(); - - let data = b"some sample content to sign"; - - let signature = signer.sign(data).unwrap(); - println!("signature len = {}", signature.len()); - assert!(signature.len() <= signer.reserve_size()); - } - - // #[test] - // fn sign_rs256() { - // let cert_bytes = include_bytes!("../../tests/fixtures/temp_cert.data"); - // let key_bytes = include_bytes!("../../tests/fixtures/temp_priv_key.data"); - - // let signer = - // RsaSigner::from_signcert_and_pkey(cert_bytes, key_bytes, "rs256".to_string(), None) - // .unwrap(); - - // let data = b"some sample content to sign"; - - // let signature = signer.sign(data).unwrap(); - // println!("signature len = {}", signature.len()); - // assert!(signature.len() <= signer.reserve_size()); - // } -} diff --git a/sdk/src/openssl/temp_signer.rs b/sdk/src/openssl/temp_signer.rs deleted file mode 100644 index 411615cdf..000000000 --- a/sdk/src/openssl/temp_signer.rs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -#![deny(missing_docs)] - -//! Temporary signing instances for testing purposes. -//! -//! This module contains functions to create self-signed certificates -//! and provision [`Signer`] instances for each of the supported signature -//! formats. -//! -//! Private-key and signing certificate pairs are created in a directory -//! provided by the caller. It is recommended to use a temporary directory -//! that is deleted upon completion of the test. (We recommend using -//! the [tempfile](https://crates.io/crates/tempfile) crate.) -//! -//! This module should be used only for testing purposes. - -// Since this module is intended for testing purposes, all of -// its functions are allowed to panic. -#![allow(clippy::panic)] -#![allow(clippy::unwrap_used)] - -#[cfg(feature = "file_io")] -use std::path::{Path, PathBuf}; - -#[cfg(feature = "file_io")] -use c2pa_crypto::SigningAlg; - -#[cfg(feature = "file_io")] -use crate::{ - openssl::{EcSigner, EdSigner, RsaSigner}, - signer::ConfigurableSigner, -}; - -/// Create an OpenSSL ES256 signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to receive the temporary -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be one of the `SigningAlg::Es*` variants. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_ec_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (EcSigner, PathBuf) { - match alg { - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => (), - _ => { - panic!("Unknown EC signer alg {alg:#?}"); - } - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - ( - EcSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} - -/// Create an OpenSSL ES256 signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to look for -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be `ed25519`. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_ed_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (EdSigner, PathBuf) { - if alg != SigningAlg::Ed25519 { - panic!("Unknown ED signer alg {alg:#?}"); - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - ( - EdSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} - -/// Create an OpenSSL SHA+RSA signer that can be used for testing purposes. -/// -/// # Arguments -/// -/// * `path` - A directory (which must already exist) to receive the temporary -/// private key / certificate pair. -/// * `alg` - A format for signing. Must be one of the `SignerAlg::Ps*` options. -/// * `tsa_url` - Optional URL for a timestamp authority. -/// -/// # Returns -/// -/// Returns a tuple of `(signer, sign_cert_path)` where `signer` is -/// the [`Signer`] instance and `sign_cert_path` is the path to the -/// signing certificate. -/// -/// # Panics -/// -/// Can panic if unable to invoke OpenSSL executable properly. -#[cfg(feature = "file_io")] -pub fn get_rsa_signer>( - path: P, - alg: SigningAlg, - tsa_url: Option, -) -> (RsaSigner, PathBuf) { - match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => (), - _ => { - panic!("Unknown RSA signer alg {alg:#?}"); - } - } - - let mut sign_cert_path = path.as_ref().to_path_buf(); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = path.as_ref().to_path_buf(); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - if !sign_cert_path.exists() || !pem_key_path.exists() { - panic!( - "path found: {}, {}", - sign_cert_path.display(), - pem_key_path.display() - ); - } - - ( - RsaSigner::from_files(&sign_cert_path, &pem_key_path, alg, tsa_url).unwrap(), - sign_cert_path, - ) -} diff --git a/sdk/src/openssl/temp_signer_async.rs b/sdk/src/openssl/temp_signer_async.rs deleted file mode 100644 index 4dc81fa2a..000000000 --- a/sdk/src/openssl/temp_signer_async.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2022 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -#![deny(missing_docs)] - -//! Temporary async signing instances for testing purposes. -//! -//! This is only a demonstration async Signer that is used to test -//! the asynchronous signing of claims. -//! This module should be used only for testing purposes. - -#[cfg(feature = "openssl_sign")] -use c2pa_crypto::SigningAlg; - -#[cfg(feature = "openssl_sign")] -fn get_local_signer(alg: SigningAlg) -> Box { - let cert_dir = crate::utils::test::fixture_path("certs"); - - match alg { - SigningAlg::Ps256 | SigningAlg::Ps384 | SigningAlg::Ps512 => { - let (s, _k) = super::temp_signer::get_rsa_signer(&cert_dir, alg, None); - Box::new(s) - } - SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => { - let (s, _k) = super::temp_signer::get_ec_signer(&cert_dir, alg, None); - Box::new(s) - } - SigningAlg::Ed25519 => { - let (s, _k) = super::temp_signer::get_ed_signer(&cert_dir, alg, None); - Box::new(s) - } - } -} - -#[cfg(feature = "openssl_sign")] -pub struct AsyncSignerAdapter { - alg: SigningAlg, - certs: Vec>, - reserve_size: usize, - tsa_url: Option, - ocsp_val: Option>, -} - -#[cfg(feature = "openssl_sign")] -impl AsyncSignerAdapter { - pub fn new(alg: SigningAlg) -> Self { - let signer = get_local_signer(alg); - - AsyncSignerAdapter { - alg, - certs: signer.certs().unwrap_or_default(), - reserve_size: signer.reserve_size(), - tsa_url: signer.time_authority_url(), - ocsp_val: signer.ocsp_val(), - } - } -} - -#[cfg(test)] -#[cfg(feature = "openssl_sign")] -#[async_trait::async_trait] -impl crate::AsyncSigner for AsyncSignerAdapter { - async fn sign(&self, data: Vec) -> crate::error::Result> { - let signer = get_local_signer(self.alg); - signer.sign(&data) - } - - fn alg(&self) -> SigningAlg { - self.alg - } - - fn certs(&self) -> crate::Result>> { - let mut output: Vec> = Vec::new(); - for v in &self.certs { - output.push(v.clone()); - } - Ok(output) - } - - fn reserve_size(&self) -> usize { - self.reserve_size - } - - fn time_authority_url(&self) -> Option { - self.tsa_url.clone() - } - - async fn ocsp_val(&self) -> Option> { - self.ocsp_val.clone() - } -} diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs index 6d6eff8f4..c5229d23b 100644 --- a/sdk/src/resource_store.rs +++ b/sdk/src/resource_store.rs @@ -404,8 +404,10 @@ mod tests { use std::io::Cursor; + use c2pa_crypto::SigningAlg; + use super::*; - use crate::{utils::test::temp_signer, Builder, Reader}; + use crate::{utils::test_signer::test_signer, Builder, Reader}; #[test] #[cfg(feature = "openssl_sign")] @@ -451,7 +453,8 @@ mod tests { let image = include_bytes!("../tests/fixtures/earth_apollo17.jpg"); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); + // Embed a manifest using the signer. let mut output_image = Cursor::new(Vec::new()); builder diff --git a/sdk/src/signer.rs b/sdk/src/signer.rs index cc42fa37a..72c37b436 100644 --- a/sdk/src/signer.rs +++ b/sdk/src/signer.rs @@ -12,7 +12,7 @@ // each license. use async_trait::async_trait; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::RawSigner, SigningAlg}; use crate::{DynamicAssertion, Result}; @@ -107,10 +107,8 @@ pub(crate) trait ConfigurableSigner: Signer + Sized { alg: SigningAlg, tsa_url: Option, ) -> Result { - use crate::Error; - - let signcert = std::fs::read(signcert_path).map_err(Error::IoError)?; - let pkey = std::fs::read(pkey_path).map_err(Error::IoError)?; + let signcert = std::fs::read(signcert_path).map_err(crate::Error::IoError)?; + let pkey = std::fs::read(pkey_path).map_err(crate::Error::IoError)?; Self::from_signcert_and_pkey(&signcert, &pkey, alg, tsa_url) } @@ -300,7 +298,7 @@ pub trait RemoteSigner: Sync { fn reserve_size(&self) -> usize; } -impl Signer for Box { +impl Signer for Box { fn sign(&self, data: &[u8]) -> Result> { (**self).sign(data) } @@ -328,4 +326,161 @@ impl Signer for Box { fn dynamic_assertions(&self) -> Vec> { (**self).dynamic_assertions() } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[async_trait] +impl AsyncSigner for Box { + async fn sign(&self, data: Vec) -> Result> { + (**self).sign(data).await + } + + fn alg(&self) -> SigningAlg { + (**self).alg() + } + + fn certs(&self) -> Result>> { + (**self).certs() + } + + fn reserve_size(&self) -> usize { + (**self).reserve_size() + } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message).await + } + + async fn ocsp_val(&self) -> Option> { + (**self).ocsp_val().await + } + + fn direct_cose_handling(&self) -> bool { + (**self).direct_cose_handling() + } + + fn dynamic_assertions(&self) -> Vec> { + (**self).dynamic_assertions() + } +} + +#[cfg(target_arch = "wasm32")] +#[async_trait(?Send)] +impl AsyncSigner for Box { + async fn sign(&self, data: Vec) -> Result> { + (**self).sign(data).await + } + + fn alg(&self) -> SigningAlg { + (**self).alg() + } + + fn certs(&self) -> Result>> { + (**self).certs() + } + + fn reserve_size(&self) -> usize { + (**self).reserve_size() + } + + fn time_authority_url(&self) -> Option { + (**self).time_authority_url() + } + + fn timestamp_request_headers(&self) -> Option> { + (**self).timestamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + (**self).timestamp_request_body(message) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + (**self).send_timestamp_request(message).await + } + + async fn ocsp_val(&self) -> Option> { + (**self).ocsp_val().await + } + + fn direct_cose_handling(&self) -> bool { + (**self).direct_cose_handling() + } + + fn dynamic_assertions(&self) -> Vec> { + (**self).dynamic_assertions() + } +} + +#[cfg_attr(target_arch = "wasm32", allow(dead_code))] +pub(crate) struct RawSignerWrapper(pub(crate) Box); + +impl Signer for RawSignerWrapper { + fn sign(&self, data: &[u8]) -> Result> { + self.0.sign(data).map_err(|e| e.into()) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn certs(&self) -> Result>> { + self.0.cert_chain().map_err(|e| e.into()) + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + fn ocsp_val(&self) -> Option> { + self.0.ocsp_response() + } + + fn time_authority_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn timestamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + self.0 + .time_stamp_request_body(message) + .map_err(|e| e.into()) + } + + fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + self.0 + .send_time_stamp_request(message) + .map(|r| r.map_err(|e| e.into())) + } } diff --git a/sdk/src/store.rs b/sdk/src/store.rs index 56a361b6f..00d85e696 100644 --- a/sdk/src/store.rs +++ b/sdk/src/store.rs @@ -523,7 +523,8 @@ impl Store { } else { if signer.direct_cose_handling() { // Let the signer do all the COSE processing and return the structured COSE data. - return signer.sign(claim_bytes.clone()).await; // do not verify remote signers (we never did) + return signer.sign(claim_bytes.clone()).await; + // do not verify remote signers (we never did) } else { cose_sign_async(signer, &claim_bytes, box_size, claim.version()).await } @@ -3705,9 +3706,10 @@ pub mod tests { hash_utils::Hasher, patch::patch_file, test::{ - create_test_claim, fixture_path, temp_dir_path, temp_fixture_path, temp_signer, + create_test_claim, fixture_path, temp_dir_path, temp_fixture_path, write_jpeg_placeholder_file, }, + test_signer::{async_test_signer, test_signer}, }, }; @@ -3758,7 +3760,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Test generate JUMBF // Get labels for label test @@ -3878,7 +3880,7 @@ pub mod tests { claimv2.add_claim_generator_info(cgi); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Test generate JUMBF // Get labels for label test @@ -3985,7 +3987,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4025,7 +4027,7 @@ pub mod tests { struct BadSigner {} - impl crate::Signer for BadSigner { + impl Signer for BadSigner { fn sign(&self, _data: &[u8]) -> Result> { Ok(b"not a valid signature".to_vec()) } @@ -4070,7 +4072,9 @@ pub mod tests { #[test] #[cfg(feature = "file_io")] fn test_sign_with_expired_cert() { - use crate::{openssl::RsaSigner, signer::ConfigurableSigner, SigningAlg}; + use c2pa_crypto::SigningAlg; + + use crate::create_signer; // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4084,7 +4088,7 @@ pub mod tests { let signcert_path = fixture_path("rsa-pss256_key-expired.pub"); let pkey_path = fixture_path("rsa-pss256-expired.pem"); let signer = - RsaSigner::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None).unwrap(); + create_signer::from_files(signcert_path, pkey_path, SigningAlg::Ps256, None).unwrap(); store.commit_claim(claim).unwrap(); @@ -4125,12 +4129,12 @@ pub mod tests { // original data should not be in file anymore check for first 1k let buf = fs::read(&op).unwrap(); - assert!(memmem::find(&buf, &original_jumbf[0..1024]).is_none()); + assert_eq!(memmem::find(&buf, &original_jumbf[0..1024]), None); } #[actix::test] async fn test_jumbf_generation_async() { - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4255,7 +4259,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4351,7 +4355,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits store.commit_claim(claim1).unwrap(); @@ -4424,7 +4428,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commmits store.commit_claim(claim1).unwrap(); @@ -4498,7 +4502,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4572,7 +4576,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4646,7 +4650,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4712,7 +4716,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4756,7 +4760,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4800,7 +4804,7 @@ pub mod tests { let claim1 = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -4926,7 +4930,7 @@ pub mod tests { fn test_verifiable_credentials() { use crate::utils::test::create_test_store; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -4964,7 +4968,7 @@ pub mod tests { fn test_data_box_creation() { use crate::utils::test::create_test_store; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -5019,7 +5023,7 @@ pub mod tests { fn test_update_manifest() { use crate::{hashed_uri::HashedUri, utils::test::create_test_store}; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("earth_apollo17.jpg"); @@ -5194,7 +5198,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. store.commit_claim(claim1).unwrap(); @@ -5224,7 +5228,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); let result: Vec = Vec::new(); let mut output_stream = Cursor::new(result); @@ -5286,7 +5290,7 @@ pub mod tests { claim.set_external_manifest(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); store.commit_claim(claim).unwrap(); @@ -5323,7 +5327,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5391,7 +5395,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5443,7 +5447,7 @@ pub mod tests { let mut claim = create_test_claim().unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // start with base url let fp = format!("file:/{}", sidecar.to_str().unwrap()); @@ -5499,7 +5503,7 @@ pub mod tests { // Create a new claim. let claim1 = create_test_claim().unwrap(); - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); store.commit_claim(claim1).unwrap(); @@ -5553,7 +5557,7 @@ pub mod tests { create_capture_claim(&mut claim_capture).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Move the claim to claims list. Note this is not real, the claims would have to be signed in between commits store.commit_claim(claim1).unwrap(); @@ -5623,7 +5627,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // get the embeddable manifest let em = store @@ -5709,7 +5713,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // get the embeddable manifest let em = store @@ -5772,12 +5776,12 @@ pub mod tests { #[cfg(feature = "file_io")] async fn test_datahash_embeddable_manifest_async() { // test adding to actual image - use std::io::SeekFrom; + let ap = fixture_path("cloud.jpg"); // Do we generate JUMBF? - let signer = crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = async_test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -5846,7 +5850,7 @@ pub mod tests { let ap = fixture_path("cloud.jpg"); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -5917,7 +5921,7 @@ pub mod tests { let mut hasher = Hasher::SHA256(Sha256::new()); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // Create claims store. let mut store = Store::new(); @@ -6023,7 +6027,7 @@ pub mod tests { fn test_placed_manifest() { use crate::jumbf::labels::to_normalized_uri; - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // test adding to actual image let ap = fixture_path("C.jpg"); @@ -6143,7 +6147,7 @@ pub mod tests { impl DynamicSigner { fn new() -> Self { - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); DynamicSigner { alg: signer.alg(), certs: signer.certs().unwrap_or_default(), @@ -6157,7 +6161,7 @@ pub mod tests { #[async_trait::async_trait] impl crate::AsyncSigner for DynamicSigner { async fn sign(&self, data: Vec) -> crate::error::Result> { - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); signer.sign(&data) } @@ -6270,7 +6274,7 @@ pub mod tests { store.commit_claim(claim).unwrap(); // Do we generate JUMBF? - let signer = temp_signer(); + let signer = test_signer(SigningAlg::Ps256); // add manifest based on let new_output_path = output_path.join(init_dir.file_name().unwrap()); diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs index e63b3a037..5c2887a31 100644 --- a/sdk/src/utils/mod.rs +++ b/sdk/src/utils/mod.rs @@ -19,7 +19,6 @@ pub(crate) mod merkle; pub(crate) mod mime; #[allow(dead_code)] // for wasm build pub(crate) mod patch; -pub(crate) mod sig_utils; #[cfg(feature = "add_thumbnails")] pub(crate) mod thumbnail; pub(crate) mod time_it; @@ -29,3 +28,6 @@ pub(crate) mod xmp_inmemory_utils; #[cfg(test)] #[allow(dead_code)] // for wasm build pub mod test; + +#[cfg(test)] +pub(crate) mod test_signer; diff --git a/sdk/src/utils/sig_utils.rs b/sdk/src/utils/sig_utils.rs deleted file mode 100644 index c5ef223a4..000000000 --- a/sdk/src/utils/sig_utils.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2024 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, -// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -// or the MIT license (http://opensource.org/licenses/MIT), -// at your option. - -// Unless required by applicable law or agreed to in writing, -// this software is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or -// implied. See the LICENSE-MIT and LICENSE-APACHE files for the -// specific language governing permissions and limitations under -// each license. - -use c2pa_crypto::{p1363::parse_ec_der_sig, SigningAlg}; - -use crate::{Error, Result}; - -pub(crate) fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Result> { - // P1363 format: r | s - - let (_, p) = parse_ec_der_sig(data).map_err(|_err| Error::InvalidEcdsaSignature)?; - - let mut r = extfmt::Hexlify(p.r).to_string(); - let mut s = extfmt::Hexlify(p.s).to_string(); - - let sig_len: usize = match alg { - SigningAlg::Es256 => 64, - SigningAlg::Es384 => 96, - SigningAlg::Es512 => 132, - _ => return Err(Error::UnsupportedType), - }; - - // pad or truncate as needed - let rp = if r.len() > sig_len { - // truncate - let offset = r.len() - sig_len; - &r[offset..r.len()] - } else { - // pad - while r.len() != sig_len { - r.insert(0, '0'); - } - r.as_ref() - }; - - let sp = if s.len() > sig_len { - // truncate - let offset = s.len() - sig_len; - &s[offset..s.len()] - } else { - // pad - while s.len() != sig_len { - s.insert(0, '0'); - } - s.as_ref() - }; - - if rp.len() != sig_len || rp.len() != sp.len() { - return Err(Error::InvalidEcdsaSignature); - } - - // merge r and s strings - let mut new_sig = rp.to_string(); - new_sig.push_str(sp); - - // convert back from hex string to byte array - (0..new_sig.len()) - .step_by(2) - .map(|i| { - u8::from_str_radix(&new_sig[i..i + 2], 16).map_err(|_err| Error::InvalidEcdsaSignature) - }) - .collect() -} diff --git a/sdk/src/utils/test.rs b/sdk/src/utils/test.rs index d3337510d..18d528f77 100644 --- a/sdk/src/utils/test.rs +++ b/sdk/src/utils/test.rs @@ -23,8 +23,6 @@ use std::{ use c2pa_crypto::SigningAlg; use tempfile::TempDir; -#[cfg(feature = "file_io")] -use crate::create_signer; use crate::{ assertions::{labels, Action, Actions, Ingredient, ReviewRating, SchemaDotOrg, Thumbnail}, asset_io::CAIReadWrite, @@ -33,12 +31,7 @@ use crate::{ jumbf_io::get_assetio_handler, salt::DefaultSalt, store::Store, - RemoteSigner, Result, Signer, -}; -#[cfg(feature = "openssl_sign")] -use crate::{ - openssl::{AsyncSignerAdapter, RsaSigner}, - signer::ConfigurableSigner, + AsyncSigner, RemoteSigner, Result, }; pub const TEST_SMALL_JPEG: &str = "earth_apollo17.jpg"; @@ -226,7 +219,7 @@ pub fn temp_fixture_path(temp_dir: &TempDir, file_name: &str) -> PathBuf { /// Can panic if the certs cannot be read. (This function should only /// be used as part of testing infrastructure.) #[cfg(feature = "file_io")] -pub fn temp_signer_file() -> RsaSigner { +pub fn temp_signer_file() -> Box { #![allow(clippy::expect_used)] let mut sign_cert_path = fixture_path("certs"); sign_cert_path.push("ps256"); @@ -236,7 +229,7 @@ pub fn temp_signer_file() -> RsaSigner { pem_key_path.push("ps256"); pem_key_path.set_extension("pem"); - RsaSigner::from_files(&sign_cert_path, &pem_key_path, SigningAlg::Ps256, None) + crate::create_signer::from_files(&sign_cert_path, &pem_key_path, SigningAlg::Ps256, None) .expect("get_temp_signer") } @@ -325,7 +318,7 @@ pub(crate) struct AsyncTestGoodSigner {} #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -impl crate::AsyncSigner for AsyncTestGoodSigner { +impl AsyncSigner for AsyncTestGoodSigner { async fn sign(&self, _data: Vec) -> Result> { Ok(b"not a valid signature".to_vec()) } @@ -350,79 +343,6 @@ impl crate::AsyncSigner for AsyncTestGoodSigner { } } -/// Create a [`Signer`] instance that can be used for testing purposes using ps256 alg. -/// -/// # Returns -/// -/// Returns a boxed [`Signer`] instance. -#[cfg(test)] -pub(crate) fn temp_signer() -> Box { - #[cfg(feature = "openssl_sign")] - { - #![allow(clippy::expect_used)] - let sign_cert = include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(); - let pem_key = include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(); - - let signer = RsaSigner::from_signcert_and_pkey( - &sign_cert, - &pem_key, - SigningAlg::Ps256, - None, // Some("http://timestamp.digicert.com".into()), - ) - .expect("get_temp_signer"); - - Box::new(signer) - } - - // todo: the will be a RustTLS signer shortly - #[cfg(not(feature = "openssl_sign"))] - { - Box::new(TestGoodSigner {}) - } -} - -#[cfg(any(target_arch = "wasm32", feature = "openssl_sign"))] -pub fn temp_async_signer() -> Box { - #[cfg(feature = "openssl_sign")] - { - Box::new(AsyncSignerAdapter::new(SigningAlg::Es256)) - } - - #[cfg(target_arch = "wasm32")] - { - let sign_cert = include_str!("../../tests/fixtures/certs/es256.pub"); - let pem_key = include_str!("../../tests/fixtures/certs/es256.pem"); - let signer = WebCryptoSigner::new("es256", sign_cert, pem_key); - Box::new(signer) - } -} - -/// Create a [`Signer`] instance for a specific algorithm that can be used for testing purposes. -/// -/// # Returns -/// -/// Returns a boxed [`Signer`] instance. -/// -/// # Panics -/// -/// Can panic if the certs cannot be read. (This function should only -/// be used as part of testing infrastructure.) -#[cfg(feature = "file_io")] -pub fn temp_signer_with_alg(alg: SigningAlg) -> Box { - #![allow(clippy::expect_used)] - // sign and embed into the target file - let mut sign_cert_path = fixture_path("certs"); - sign_cert_path.push(alg.to_string()); - sign_cert_path.set_extension("pub"); - - let mut pem_key_path = fixture_path("certs"); - pem_key_path.push(alg.to_string()); - pem_key_path.set_extension("pem"); - - create_signer::from_files(sign_cert_path.clone(), pem_key_path, alg, None) - .expect("get_temp_signer_with_alg") -} - struct TempRemoteSigner {} #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -431,8 +351,7 @@ impl crate::signer::RemoteSigner for TempRemoteSigner { async fn sign_remote(&self, claim_bytes: &[u8]) -> crate::error::Result> { #[cfg(feature = "openssl_sign")] { - let signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ps256); // this would happen on some remote server crate::cose_sign::cose_sign_async(&signer, claim_bytes, self.reserve_size(), 1).await @@ -577,17 +496,17 @@ struct TempAsyncRemoteSigner { #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] -impl crate::signer::AsyncSigner for TempAsyncRemoteSigner { +impl AsyncSigner for TempAsyncRemoteSigner { // this will not be called but requires an implementation async fn sign(&self, claim_bytes: Vec) -> Result> { #[cfg(feature = "openssl_sign")] { - let signer = - crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); + let signer = crate::utils::test_signer::async_test_signer(SigningAlg::Ps256); // this would happen on some remote server crate::cose_sign::cose_sign_async(&signer, &claim_bytes, self.reserve_size(), 1).await } + #[cfg(target_arch = "wasm32")] { let signer = crate::wasm::rsa_wasm_signer::RsaWasmSignerAsync::new(); diff --git a/sdk/src/utils/test_signer.rs b/sdk/src/utils/test_signer.rs new file mode 100644 index 000000000..c9d32a99d --- /dev/null +++ b/sdk/src/utils/test_signer.rs @@ -0,0 +1,146 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +#![allow(clippy::unwrap_used)] // This mod is only used in test code. + +use async_trait::async_trait; +use c2pa_crypto::{ + raw_signature::{ + async_signer_from_cert_chain_and_private_key, signer_from_cert_chain_and_private_key, + AsyncRawSigner, + }, + SigningAlg, +}; + +use crate::{signer::RawSignerWrapper, AsyncSigner, Result, Signer}; + +/// Creates a [`Signer`] instance for testing purposes using test credentials. +pub(crate) fn test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(RawSignerWrapper( + signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +/// Creates an [`AsyncSigner`] instance for testing purposes using test credentials. +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn async_test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(AsyncRawSignerWrapper( + async_signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +/// Creates an [`AsyncSigner`] instance for testing purposes using test credentials. +#[cfg(target_arch = "wasm32")] +pub(crate) fn async_test_signer(alg: SigningAlg) -> Box { + let (cert_chain, private_key) = cert_chain_and_private_key_for_alg(alg); + + Box::new(AsyncRawSignerWrapper( + async_signer_from_cert_chain_and_private_key(&cert_chain, &private_key, alg, None).unwrap(), + )) +} + +fn cert_chain_and_private_key_for_alg(alg: SigningAlg) -> (Vec, Vec) { + match alg { + SigningAlg::Ps256 => ( + include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(), + ), + + SigningAlg::Ps384 => ( + include_bytes!("../../tests/fixtures/certs/ps384.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps384.pem").to_vec(), + ), + + SigningAlg::Ps512 => ( + include_bytes!("../../tests/fixtures/certs/ps512.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ps512.pem").to_vec(), + ), + + SigningAlg::Es256 => ( + include_bytes!("../../tests/fixtures/certs/es256.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es256.pem").to_vec(), + ), + + SigningAlg::Es384 => ( + include_bytes!("../../tests/fixtures/certs/es384.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es384.pem").to_vec(), + ), + + SigningAlg::Es512 => ( + include_bytes!("../../tests/fixtures/certs/es512.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/es512.pem").to_vec(), + ), + + SigningAlg::Ed25519 => ( + include_bytes!("../../tests/fixtures/certs/ed25519.pub").to_vec(), + include_bytes!("../../tests/fixtures/certs/ed25519.pem").to_vec(), + ), + } +} + +#[cfg(not(target_arch = "wasm32"))] +struct AsyncRawSignerWrapper(Box); + +#[allow(dead_code)] // TEMPORARY: Not used on WASM +#[cfg(target_arch = "wasm32")] +struct AsyncRawSignerWrapper(Box); + +#[allow(dead_code)] // TEMPORARY: Not used on WASM +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl AsyncSigner for AsyncRawSignerWrapper { + async fn sign(&self, data: Vec) -> Result> { + self.0.sign(data).await.map_err(|e| e.into()) + } + + fn alg(&self) -> SigningAlg { + self.0.alg() + } + + fn certs(&self) -> Result>> { + self.0.cert_chain().map_err(|e| e.into()) + } + + fn reserve_size(&self) -> usize { + self.0.reserve_size() + } + + async fn ocsp_val(&self) -> Option> { + self.0.ocsp_response().await + } + + fn time_authority_url(&self) -> Option { + self.0.time_stamp_service_url() + } + + fn timestamp_request_headers(&self) -> Option> { + self.0.time_stamp_request_headers() + } + + fn timestamp_request_body(&self, message: &[u8]) -> Result> { + self.0 + .time_stamp_request_body(message) + .map_err(|e| e.into()) + } + + async fn send_timestamp_request(&self, message: &[u8]) -> Option>> { + self.0 + .send_time_stamp_request(message) + .await + .map(|r| r.map_err(|e| e.into())) + } +} diff --git a/sdk/src/wasm/rsa_wasm_signer.rs b/sdk/src/wasm/rsa_wasm_signer.rs index cb1cda0fa..4e3320539 100644 --- a/sdk/src/wasm/rsa_wasm_signer.rs +++ b/sdk/src/wasm/rsa_wasm_signer.rs @@ -311,10 +311,7 @@ mod tests { }; use super::*; - use crate::{ - utils::test::{fixture_path, temp_signer}, - Signer, - }; + use crate::{utils::test::fixture_path, Signer}; #[test] fn sign_ps256() { diff --git a/sdk/tests/common/test_signer.rs b/sdk/tests/common/test_signer.rs index c3bd1a2c1..f2ee408f6 100644 --- a/sdk/tests/common/test_signer.rs +++ b/sdk/tests/common/test_signer.rs @@ -12,7 +12,7 @@ // each license. use c2pa::CallbackSigner; -use c2pa_crypto::SigningAlg; +use c2pa_crypto::{raw_signature::RawSignerError, SigningAlg}; const CERTS: &[u8] = include_bytes!("../../tests/fixtures/certs/ed25519.pub"); const PRIVATE_KEY: &[u8] = include_bytes!("../../tests/fixtures/certs/ed25519.pem"); @@ -29,12 +29,13 @@ fn ed_sign(data: &[u8], private_key: &[u8]) -> c2pa::Result> { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; - let signing_key = - SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + let signing_key = SigningKey::try_from(key_bytes) + .map_err(|e| RawSignerError::InternalError(e.to_string()))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } diff --git a/sdk/tests/v2_api_integration.rs b/sdk/tests/v2_api_integration.rs index cbeebd0cc..768166cd5 100644 --- a/sdk/tests/v2_api_integration.rs +++ b/sdk/tests/v2_api_integration.rs @@ -15,7 +15,6 @@ // Isolate from wasm by wrapping in module. #[cfg(not(target_arch = "wasm32"))] // wasm doesn't support ed25519 yet mod integration_v2 { - use std::io::{Cursor, Seek}; use anyhow::Result; @@ -169,7 +168,7 @@ mod integration_v2 { } println!("{}", reader.json()); - assert!(reader.validation_status().is_none()); + assert_eq!(reader.validation_status(), None); assert_eq!(reader.active_manifest().unwrap().title().unwrap(), title); Ok(()) @@ -181,13 +180,14 @@ mod integration_v2 { // Parse the PEM data to get the private key let pem = parse(private_key).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // For Ed25519, the key is 32 bytes long, so we skip the first 16 bytes of the PEM data let key_bytes = &pem.contents()[16..]; let signing_key = SigningKey::try_from(key_bytes).map_err(|e| c2pa::Error::OtherError(Box::new(e)))?; + // Sign the data let signature: Signature = signing_key.sign(data); - Ok(signature.to_bytes().to_vec()) } }