From 03251eed234075bfada5f17ed081eeba16590f09 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 16:14:40 -0700 Subject: [PATCH 01/11] feat: Prepare 0.68.0 update --- c2pa-native-version.txt | 2 +- pyproject.toml | 2 +- tests/test_unit_tests.py | 101 +++++++++++++++++++----------- tests/test_unit_tests_threaded.py | 10 --- 4 files changed, 67 insertions(+), 48 deletions(-) diff --git a/c2pa-native-version.txt b/c2pa-native-version.txt index c9951732..1c50da98 100644 --- a/c2pa-native-version.txt +++ b/c2pa-native-version.txt @@ -1 +1 @@ -c2pa-v0.67.1 +c2pa-v0.68.0 diff --git a/pyproject.toml b/pyproject.toml index d4722df1..dc370403 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "c2pa-python" -version = "0.27.1" +version = "0.28.0" requires-python = ">=3.10" description = "Python bindings for the C2PA Content Authenticity Initiative (CAI) library" readme = { file = "README.md", content-type = "text/markdown" } diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 7bdb8897..2c36913c 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -40,7 +40,7 @@ class TestC2paSdk(unittest.TestCase): def test_sdk_version(self): - self.assertIn("0.67.1", sdk_version()) + self.assertIn("0.68.0", sdk_version()) class TestReader(unittest.TestCase): @@ -116,7 +116,8 @@ def test_stream_read_get_validation_state(self): reader = Reader("image/jpeg", file) validation_state = reader.get_validation_state() self.assertIsNotNone(validation_state) - self.assertEqual(validation_state, "Valid") + # Needs trust configuration to be set up to validate + # self.assertEqual(validation_state, "Valid") def test_stream_read_get_validation_results(self): with open(self.testPath, "rb") as file: @@ -924,7 +925,8 @@ def test_streams_sign_with_thumbnail_resource(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_streams_sign_with_es256_alg_v1_manifest(self): @@ -936,7 +938,8 @@ def test_streams_sign_with_es256_alg_v1_manifest(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Write buffer to file # output.seek(0) @@ -961,7 +964,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_existing_empty_file(self): reader = Reader("image/jpeg", target) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up... @@ -987,7 +991,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_new_dest_file(self): reader = Reader("image/jpeg", target) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up... @@ -1009,7 +1014,8 @@ def test_streams_sign_with_es256_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_streams_sign_with_es256_alg_2(self): @@ -1021,7 +1027,8 @@ def test_streams_sign_with_es256_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_with_ed25519_alg(self): @@ -1046,7 +1053,8 @@ def test_sign_with_ed25519_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_with_ed25519_alg_2(self): @@ -1071,7 +1079,8 @@ def test_sign_with_ed25519_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_with_ps256_alg(self): @@ -1096,7 +1105,8 @@ def test_sign_with_ps256_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_with_ps256_alg_2(self): @@ -1121,7 +1131,8 @@ def test_sign_with_ps256_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_archive_sign(self): @@ -1136,7 +1147,8 @@ def test_archive_sign(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) archive.close() output.close() @@ -1155,7 +1167,8 @@ def test_archive_sign_with_added_ingredient(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) archive.close() output.close() @@ -1186,7 +1199,6 @@ def test_remote_sign_using_returned_bytes(self): with Reader("image/jpeg", read_buffer, manifest_data) as reader: manifest_data = reader.json() self.assertIn("Python Test", manifest_data) - self.assertNotIn("validation_status", manifest_data) def test_remote_sign_using_returned_bytes_V2(self): with open(self.testPath, "rb") as file: @@ -1201,7 +1213,6 @@ def test_remote_sign_using_returned_bytes_V2(self): with Reader("image/jpeg", read_buffer, manifest_data) as reader: manifest_data = reader.json() self.assertIn("Python Test", manifest_data) - self.assertNotIn("validation_status", manifest_data) def test_sign_all_files(self): """Test signing all files in both fixtures directories""" @@ -1260,7 +1271,8 @@ def test_sign_all_files(self): reader = Reader(mime_type, output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) reader.close() output.close() except Error.NotSupported: @@ -1791,7 +1803,8 @@ def test_sign_single(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_mp4_video_file_single(self): @@ -1806,7 +1819,8 @@ def test_sign_mp4_video_file_single(self): reader = Reader("video/mp4", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_mov_video_file_single(self): @@ -1821,7 +1835,8 @@ def test_sign_mov_video_file_single(self): reader = Reader("mov", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() def test_sign_file_tmn_wip(self): @@ -1846,7 +1861,8 @@ def test_sign_file_tmn_wip(self): reader = Reader("image/jpeg", file) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up the temporary directory @@ -1874,7 +1890,8 @@ def test_sign_file_video(self): reader = Reader("video/mp4", file) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up the temporary directory @@ -1926,7 +1943,8 @@ def test_builder_sign_file_callback_signer_from_callback(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -1977,7 +1995,8 @@ def test_builder_sign_file_callback_signer_from_callback_V2(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -2030,7 +2049,8 @@ def ed25519_callback(data: bytes) -> bytes: reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) reader.close() output.close() @@ -2054,7 +2074,8 @@ def test_signing_manifest_v2(self): # Basic verification of the manifest self.assertIn("Python Test Image V2", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) output.close() @@ -2087,7 +2108,8 @@ def test_sign_file_mp4_video(self): reader = Reader("video/mp4", file) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up the temporary directory @@ -2115,13 +2137,15 @@ def test_sign_file_mov_video(self): reader = Reader("mov", file) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Verify also signed file using manifest bytes with Reader("mov", output_path, manifest_bytes) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up the temporary directory @@ -2149,13 +2173,15 @@ def test_sign_file_mov_video_V2(self): reader = Reader("mov", file) json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Verify also signed file using manifest bytes with Reader("mov", output_path, manifest_bytes) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) finally: # Clean up the temporary directory @@ -3503,7 +3529,6 @@ def test_sign_file_using_callback_signer_overloads(self): reader = Reader("image/jpeg", file) file_manifest_json = reader.json() self.assertIn("Python Test", file_manifest_json) - self.assertNotIn("validation_status", file_manifest_json) finally: shutil.rmtree(temp_dir) @@ -3674,7 +3699,8 @@ def test_sign_file_callback_signer(self): # Read the signed file and verify the manifest with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -3723,7 +3749,8 @@ def test_sign_file_callback_signer(self): # Read the signed file and verify the manifest with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -3769,7 +3796,8 @@ def test_sign_file_callback_signer_managed_single(self): with Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -3830,7 +3858,8 @@ def test_sign_file_callback_signer_managed_multiple_uses(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate + # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) diff --git a/tests/test_unit_tests_threaded.py b/tests/test_unit_tests_threaded.py index 9a00dd6a..14ef48fe 100644 --- a/tests/test_unit_tests_threaded.py +++ b/tests/test_unit_tests_threaded.py @@ -1429,10 +1429,6 @@ def sign_file(output_stream, manifest_def, thread_id): active_manifest1["title"], active_manifest2["title"]) - # Verify both outputs have valid signatures - self.assertNotIn("validation_status", manifest_store1) - self.assertNotIn("validation_status", manifest_store2) - # Clean up output1.close() output2.close() @@ -1511,12 +1507,6 @@ async def read_manifest(): self.assertTrue(author_found, "Author assertion not found in manifest") - # Verify no validation errors - self.assertNotIn( - "validation_status", - manifest_store, - "Manifest should not have validation errors") - except Exception as e: read_errors.append(f"Read error: {str(e)}") From 6f8b0c464ca52d02f4309b4164e7008e5dae6c96 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:09:51 -0700 Subject: [PATCH 02/11] fix: Load trust for some tests --- tests/__init__.py | 0 tests/test_settings.toml | 526 +++++++++++++++++++++++++++++++++++++++ tests/test_unit_tests.py | 409 +++++++++++++++++++++++++----- 3 files changed, 876 insertions(+), 59 deletions(-) delete mode 100644 tests/__init__.py create mode 100644 tests/test_settings.toml diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_settings.toml b/tests/test_settings.toml new file mode 100644 index 00000000..2fd664fc --- /dev/null +++ b/tests/test_settings.toml @@ -0,0 +1,526 @@ +# c2pa-rs Configuration File + +# Version information. +version = 1 + +# Trust settings for certificate validation. +# [trust] +# String to user-provided trust anchors (PEM format). +# user_anchors = "" +# String to system trust anchors (PEM format). +# trust_anchors = "" +[trust] +trust_anchors = """-----BEGIN CERTIFICATE----- +MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD +VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M +WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 +NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo +ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF +U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D +eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM +fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy +dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 +zGxQnM2hCA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG +BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 +zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 +mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj +YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 +OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD +nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd +5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf +iwNQm1DDcQPtPTnddOs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq +QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX +y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD +VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE +FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 +Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye +PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh +eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA +MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 +jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +xPd7wFhjRZHfuWb2cs63xjAGjQ== +-----END CERTIFICATE----- +""" +# String to trust configuration. +trust_config = """ +//id-kp-emailProtection +1.3.6.1.5.5.7.3.4 +//id-kp-documentSigning +1.3.6.1.5.5.7.3.36 +//id-kp-timeStamping +1.3.6.1.5.5.7.3.8 +//id-kp-OCSPSigning +1.3.6.1.5.5.7.3.9 +// MS C2PA Signing +1.3.6.1.4.1.311.76.59.1.9 +// c2pa-kp-claimSigning +1.3.6.1.4.1.62558.2.1 +""" + +# # Path to allowed certificate list (PEM format). +# allowed_list = "" + +# Verification settings. +[verify] +# Verify manifests after reading. +verify_after_reading = true +# Verify manifests after signing. +verify_after_sign = true +# Fetch remote manifests. +remote_manifest_fetch = true + +# Configuration for a signer. +# +# The signer can be retrieved via the `Settings::signer` function. +[signer] + +# A signer can be loaded in the API with the function `Settings::signer`. +[signer.local] +# Algorithm to use for signing. +alg = "ps256" +# Certificate used for signing (PEM format). +sign_cert = """-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +wvq03sA= +-----END CERTIFICATE----- +""" +# Private key used for signing (PEM format). +private_key = """-----BEGIN PRIVATE KEY----- +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 +G/dZR0Qj3j0T5F9GX719XpmzYbVFKIKPTsKF +-----END PRIVATE KEY----- +""" +# Time stamp authority URL for signing. +tsa_url = "http://timestamp.digicert.com" + +# # Alternatively, you can specify a remote signer, which is also loaded +# # via `Settings::signer`. +# # +# # Note that you may not specify both a local and remote signer at the same time. +# [signer.remote] +# # URL to the signer used for signing. +# # +# # A POST request with a byte stream will be sent to this URL. +# url = "https://www.google.com" +# # Algorithm to use for signing. +# alg = "ps256" +# # Certificate used for signing (PEM format). +# sign_cert = "" +# # Time stamp authority URL for signing. +# tsa_url = "" + +# Configuration for the `Builder`. +[builder] +# certificate_status_fetch = "all" +# certificate_status_should_override = true + +# Claim generator info list. +[builder.claim_generator_info] +# A human readable name. +name = "c2pa-rs testing" +# A human readable string of the product's version. +version = "1.0.0" +# The operating system the claim generator is running on. +#operating_system.name = "macOS" +# Or specify "auto" to infer the operating system automatically. +operating_system = "auto" +# Arbitrary fields can also be defined. +# +# By default, the SDK adds a field "org.cai.c2pa_rs" with the value +# being the current version of the SDK. +#some_other_field = "" + +# Actions assertion configuration. +[builder.actions] +# Signifies if all the actions that ever happened on a particular asset are specified +# or if some are missing. +#all_actions_included = true + +# A template to use as the base values for a particular action. +#[[builder.actions.templates]] +# The label of the action. +#action = "c2pa.edited" +# TODO: do we want to document these fields here or just include links to the docs +# rust docs or c2pa docs? +# The source type field is required for the c2pa.created action. +# +# For more information, see `c2pa::assertions::actions::source_type`. +#source_type = "http://c2pa.org/digitalsourcetype/empty" +# Description for the action +#description = "Some edit action." +# Arbitrary key/value pairs to store in the action. +#template_parameters = { "arbitrary_key" = true } +# A software agent has the same fields as a claim generator info. +#software_agent = { name = "My Service" } + +# Multiple templates can be specified. +#[[builder.actions.templates]] +#action = "c2pa.cropped" + +# TODO: document rest of fields for actions +# Actions to be added to every "Actions" assertion. +#[[builder.actions.actions]] +# The label of the action. +#action = "c2pa.drawing" + +# Similarly, multiple actions can be defined. +#382[[builder.actions.actions]] +#action = "c2pa.color_adjustments" + +# Settings for configuring how c2pa.created actions are auto created. +# +# This is a convenience setting and it can be disabled if the information +# is provided manually. +[builder.actions.auto_created_action] +# Whether to auto create the c2pa.created action. +enabled = true +# The source type field is required for the c2pa.created action. +# +# For more information, see `c2pa::assertions::actions::source_type`. +source_type = "http://c2pa.org/digitalsourcetype/empty" + +# Settings for configuring how c2pa.opened actions are auto created. +# +# This is a convenience setting and it can be disabled if the information +# is provided manually. +[builder.actions.auto_opened_action] +# Whether to auto create the c2pa.opened action. +enabled = true +# For more information, see `c2pa::assertions::actions::source_type`. +# +# Note this field is optional for the c2pa.opened action. +source_type = "http://c2pa.org/digitalsourcetype/empty" + +# Settings for configuring how c2pa.placed actions are auto created. +# +# This is a convenience setting and it can be disabled if the information +# is provided manually. +[builder.actions.auto_placed_action] +# Whether to auto create the c2pa.placed action. +enabled = true +# For more information, see `c2pa::assertions::actions::source_type`. +# +# Note this field is optional for the c2pa.placed action. +source_type = "http://c2pa.org/digitalsourcetype/empty" + +# Settings for automatic thumbnail generation. +[builder.thumbnail] +# Whether to enable automatic thumbnail generation. +enabled = true +# Whether to ignore errors when generating a thumbnail and continue signing. +ignore_errors = true +# The size of the longest edge of the thumbnail. +long_edge = 512 +# The output format of the thumbnail. +# +# If this field isn't specified, the thumbnail format will correspond to the +# input format. +#format = "png" +# Whether or not to prefer a smaller sized media format for the thumbnail. +# +# The "format" option takes precedence over this field. +# +# For instance, if the source input type is a PNG, but it doesn't have an alpha channel, +# the image will be converted to a JPEG of smaller size. +prefer_smallest_format = true +# The output quality of the thumbnail (low, medium, high). +quality = "medium" diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 2c36913c..5b45a820 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -23,6 +23,8 @@ import tempfile import shutil import ctypes +import toml +import threading # Suppress deprecation warnings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -38,6 +40,43 @@ INGREDIENT_TEST_FILE = os.path.join(FIXTURES_DIR, INGREDIENT_TEST_FILE_NAME) ALTERNATIVE_INGREDIENT_TEST_FILE = os.path.join(FIXTURES_DIR, "cloud.jpg") + +def load_test_settings_as_json(): + """ + Load the test_settings.toml file and return its content as JSON-compatible dict. + + Returns: + dict: The parsed TOML content as a Python dictionary (JSON-compatible). + + Raises: + FileNotFoundError: If test_settings.toml is not found. + toml.TomlDecodeError: If the TOML file is malformed. + """ + # Get the directory where this file is located + tests_dir = os.path.dirname(os.path.abspath(__file__)) + settings_path = os.path.join(tests_dir, 'test_settings.toml') + + # Load the TOML file + with open(settings_path, 'r') as f: + settings_data = toml.load(f) + + return settings_data + + +def load_test_settings_as_json_string(): + """ + Load the test_settings.toml file and return its content as a JSON string. + + Returns: + str: The parsed TOML content serialized as a JSON string. + + Raises: + FileNotFoundError: If test_settings.toml is not found. + toml.TomlDecodeError: If the TOML file is malformed. + """ + settings_data = load_test_settings_as_json() + return json.dumps(settings_data, indent=2) + class TestC2paSdk(unittest.TestCase): def test_sdk_version(self): self.assertIn("0.68.0", sdk_version()) @@ -116,8 +155,44 @@ def test_stream_read_get_validation_state(self): reader = Reader("image/jpeg", file) validation_state = reader.get_validation_state() self.assertIsNotNone(validation_state) - # Needs trust configuration to be set up to validate - # self.assertEqual(validation_state, "Valid") + # Needs trust configuration to be set up to validate as Trusted, otherwise manifest is Invalid + self.assertEqual(validation_state, "Invalid") + + def test_stream_read_get_validation_state_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def read_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + reader = Reader("image/jpeg", file) + validation_state = reader.get_validation_state() + result['validation_state'] = validation_state + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=read_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIsNotNone(result.get('validation_state')) + # With trust configuration loaded, manifest is Trusted + self.assertEqual(result.get('validation_state'), "Trusted") def test_stream_read_get_validation_results(self): with open(self.testPath, "rb") as file: @@ -925,8 +1000,6 @@ def test_streams_sign_with_thumbnail_resource(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) output.close() def test_streams_sign_with_es256_alg_v1_manifest(self): @@ -938,8 +1011,10 @@ def test_streams_sign_with_es256_alg_v1_manifest(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) # Write buffer to file # output.seek(0) @@ -964,8 +1039,10 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_existing_empty_file(self): reader = Reader("image/jpeg", target) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up... @@ -991,8 +1068,10 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_new_dest_file(self): reader = Reader("image/jpeg", target) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up... @@ -1014,8 +1093,10 @@ def test_streams_sign_with_es256_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_streams_sign_with_es256_alg_2(self): @@ -1027,10 +1108,60 @@ def test_streams_sign_with_es256_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() + def test_streams_sign_with_es256_alg_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + output = io.BytesIO(bytearray()) + builder.sign(self.signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader("image/jpeg", output) + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + output.close() + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + + def test_sign_with_ed25519_alg(self): with open(os.path.join(self.data_dir, "ed25519.pub"), "rb") as cert_file: certs = cert_file.read() @@ -1053,10 +1184,72 @@ def test_sign_with_ed25519_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() + def test_sign_with_ed25519_alg_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(os.path.join(self.data_dir, "ed25519.pub"), "rb") as cert_file: + certs = cert_file.read() + with open(os.path.join(self.data_dir, "ed25519.pem"), "rb") as key_file: + key = key_file.read() + + signer_info = C2paSignerInfo( + alg=b"ed25519", + sign_cert=certs, + private_key=key, + ta_url=b"http://timestamp.digicert.com" + ) + signer = Signer.from_info(signer_info) + + with open(self.testPath, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + output = io.BytesIO(bytearray()) + builder.sign(signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader("image/jpeg", output) + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + output.close() + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + def test_sign_with_ed25519_alg_2(self): with open(os.path.join(self.data_dir, "ed25519.pub"), "rb") as cert_file: certs = cert_file.read() @@ -1079,8 +1272,10 @@ def test_sign_with_ed25519_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_sign_with_ps256_alg(self): @@ -1105,8 +1300,10 @@ def test_sign_with_ps256_alg(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_sign_with_ps256_alg_2(self): @@ -1131,10 +1328,70 @@ def test_sign_with_ps256_alg_2(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate + # Needs trust configuration to be set up to validate as Trusted # self.assertNotIn("validation_status", json_data) output.close() + def test_sign_with_ps256_alg_2_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(os.path.join(self.data_dir, "ps256.pub"), "rb") as cert_file: + certs = cert_file.read() + with open(os.path.join(self.data_dir, "ps256.pem"), "rb") as key_file: + key = key_file.read() + + signer_info = C2paSignerInfo( + alg=b"ps256", + sign_cert=certs, + private_key=key, + ta_url=b"http://timestamp.digicert.com" + ) + signer = Signer.from_info(signer_info) + + with open(self.testPath2, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + output = io.BytesIO(bytearray()) + builder.sign(signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader("image/jpeg", output) + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + output.close() + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + def test_archive_sign(self): with open(self.testPath, "rb") as file: builder = Builder(self.manifestDefinition) @@ -1147,8 +1404,10 @@ def test_archive_sign(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) archive.close() output.close() @@ -1167,8 +1426,10 @@ def test_archive_sign_with_added_ingredient(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) archive.close() output.close() @@ -1271,8 +1532,10 @@ def test_sign_all_files(self): reader = Reader(mime_type, output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) reader.close() output.close() except Error.NotSupported: @@ -1803,8 +2066,10 @@ def test_sign_single(self): reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_sign_mp4_video_file_single(self): @@ -1819,8 +2084,10 @@ def test_sign_mp4_video_file_single(self): reader = Reader("video/mp4", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_sign_mov_video_file_single(self): @@ -1835,8 +2102,10 @@ def test_sign_mov_video_file_single(self): reader = Reader("mov", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() def test_sign_file_tmn_wip(self): @@ -1861,8 +2130,10 @@ def test_sign_file_tmn_wip(self): reader = Reader("image/jpeg", file) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up the temporary directory @@ -1890,8 +2161,10 @@ def test_sign_file_video(self): reader = Reader("video/mp4", file) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up the temporary directory @@ -1943,8 +2216,10 @@ def test_builder_sign_file_callback_signer_from_callback(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -1995,8 +2270,10 @@ def test_builder_sign_file_callback_signer_from_callback_V2(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm manifest_data = json.loads(json_data) @@ -2049,8 +2326,10 @@ def ed25519_callback(data: bytes) -> bytes: reader = Reader("image/jpeg", output) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) reader.close() output.close() @@ -2074,8 +2353,10 @@ def test_signing_manifest_v2(self): # Basic verification of the manifest self.assertIn("Python Test Image V2", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) output.close() @@ -2108,8 +2389,10 @@ def test_sign_file_mp4_video(self): reader = Reader("video/mp4", file) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up the temporary directory @@ -2137,15 +2420,19 @@ def test_sign_file_mov_video(self): reader = Reader("mov", file) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explciitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes with Reader("mov", output_path, manifest_bytes) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explciitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up the temporary directory @@ -2173,15 +2460,19 @@ def test_sign_file_mov_video_V2(self): reader = Reader("mov", file) json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes with Reader("mov", output_path, manifest_bytes) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate - # self.assertNotIn("validation_status", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # Or validation on red reports `signing certificate untrusted` + # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + self.assertIn("Invalid", json_data) finally: # Clean up the temporary directory @@ -3699,7 +3990,7 @@ def test_sign_file_callback_signer(self): # Read the signed file and verify the manifest with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() - # Needs trust configuration to be set up to validate + # Needs trust configuration to be set up to validate as Trusted # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm @@ -3749,7 +4040,7 @@ def test_sign_file_callback_signer(self): # Read the signed file and verify the manifest with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() - # Needs trust configuration to be set up to validate + # Needs trust configuration to be set up to validate as Trusted # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm @@ -3796,7 +4087,7 @@ def test_sign_file_callback_signer_managed_single(self): with Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate + # Needs trust configuration to be set up to validate as Trusted # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm @@ -3858,7 +4149,7 @@ def test_sign_file_callback_signer_managed_multiple_uses(self): with open(output_path, "rb") as file, Reader("image/jpeg", file) as reader: json_data = reader.json() self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate + # Needs trust configuration to be set up to validate as Trusted # self.assertNotIn("validation_status", json_data) # Parse the JSON and verify the signature algorithm From c08abf957a61eb655bcc93467a488881eefd3e44 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:23:33 -0700 Subject: [PATCH 03/11] fix: Trust settings --- tests/test_settings.toml | 526 -------------------------- tests/test_unit_tests.py | 283 ++++++++++---- tests/trust_config_test_settings.toml | 239 ++++++++++++ 3 files changed, 451 insertions(+), 597 deletions(-) delete mode 100644 tests/test_settings.toml create mode 100644 tests/trust_config_test_settings.toml diff --git a/tests/test_settings.toml b/tests/test_settings.toml deleted file mode 100644 index 2fd664fc..00000000 --- a/tests/test_settings.toml +++ /dev/null @@ -1,526 +0,0 @@ -# c2pa-rs Configuration File - -# Version information. -version = 1 - -# Trust settings for certificate validation. -# [trust] -# String to user-provided trust anchors (PEM format). -# user_anchors = "" -# String to system trust anchors (PEM format). -# trust_anchors = "" -[trust] -trust_anchors = """-----BEGIN CERTIFICATE----- -MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ -BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD -VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M -WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 -NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo -ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF -U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D -eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM -fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy -dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 -zGxQnM2hCA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG -BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 -zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 -mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj -YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 -OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD -nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd -5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf -iwNQm1DDcQPtPTnddOs= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq -QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX -y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD -VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE -FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 -Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye -PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh -eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA -MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 -jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 -xPd7wFhjRZHfuWb2cs63xjAGjQ== ------END CERTIFICATE----- -""" -# String to trust configuration. -trust_config = """ -//id-kp-emailProtection -1.3.6.1.5.5.7.3.4 -//id-kp-documentSigning -1.3.6.1.5.5.7.3.36 -//id-kp-timeStamping -1.3.6.1.5.5.7.3.8 -//id-kp-OCSPSigning -1.3.6.1.5.5.7.3.9 -// MS C2PA Signing -1.3.6.1.4.1.311.76.59.1.9 -// c2pa-kp-claimSigning -1.3.6.1.4.1.62558.2.1 -""" - -# # Path to allowed certificate list (PEM format). -# allowed_list = "" - -# Verification settings. -[verify] -# Verify manifests after reading. -verify_after_reading = true -# Verify manifests after signing. -verify_after_sign = true -# Fetch remote manifests. -remote_manifest_fetch = true - -# Configuration for a signer. -# -# The signer can be retrieved via the `Settings::signer` function. -[signer] - -# A signer can be loaded in the API with the function `Settings::signer`. -[signer.local] -# Algorithm to use for signing. -alg = "ps256" -# Certificate used for signing (PEM format). -sign_cert = """-----BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 -wvq03sA= ------END CERTIFICATE----- -""" -# Private key used for signing (PEM format). -private_key = """-----BEGIN PRIVATE KEY----- -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 -G/dZR0Qj3j0T5F9GX719XpmzYbVFKIKPTsKF ------END PRIVATE KEY----- -""" -# Time stamp authority URL for signing. -tsa_url = "http://timestamp.digicert.com" - -# # Alternatively, you can specify a remote signer, which is also loaded -# # via `Settings::signer`. -# # -# # Note that you may not specify both a local and remote signer at the same time. -# [signer.remote] -# # URL to the signer used for signing. -# # -# # A POST request with a byte stream will be sent to this URL. -# url = "https://www.google.com" -# # Algorithm to use for signing. -# alg = "ps256" -# # Certificate used for signing (PEM format). -# sign_cert = "" -# # Time stamp authority URL for signing. -# tsa_url = "" - -# Configuration for the `Builder`. -[builder] -# certificate_status_fetch = "all" -# certificate_status_should_override = true - -# Claim generator info list. -[builder.claim_generator_info] -# A human readable name. -name = "c2pa-rs testing" -# A human readable string of the product's version. -version = "1.0.0" -# The operating system the claim generator is running on. -#operating_system.name = "macOS" -# Or specify "auto" to infer the operating system automatically. -operating_system = "auto" -# Arbitrary fields can also be defined. -# -# By default, the SDK adds a field "org.cai.c2pa_rs" with the value -# being the current version of the SDK. -#some_other_field = "" - -# Actions assertion configuration. -[builder.actions] -# Signifies if all the actions that ever happened on a particular asset are specified -# or if some are missing. -#all_actions_included = true - -# A template to use as the base values for a particular action. -#[[builder.actions.templates]] -# The label of the action. -#action = "c2pa.edited" -# TODO: do we want to document these fields here or just include links to the docs -# rust docs or c2pa docs? -# The source type field is required for the c2pa.created action. -# -# For more information, see `c2pa::assertions::actions::source_type`. -#source_type = "http://c2pa.org/digitalsourcetype/empty" -# Description for the action -#description = "Some edit action." -# Arbitrary key/value pairs to store in the action. -#template_parameters = { "arbitrary_key" = true } -# A software agent has the same fields as a claim generator info. -#software_agent = { name = "My Service" } - -# Multiple templates can be specified. -#[[builder.actions.templates]] -#action = "c2pa.cropped" - -# TODO: document rest of fields for actions -# Actions to be added to every "Actions" assertion. -#[[builder.actions.actions]] -# The label of the action. -#action = "c2pa.drawing" - -# Similarly, multiple actions can be defined. -#382[[builder.actions.actions]] -#action = "c2pa.color_adjustments" - -# Settings for configuring how c2pa.created actions are auto created. -# -# This is a convenience setting and it can be disabled if the information -# is provided manually. -[builder.actions.auto_created_action] -# Whether to auto create the c2pa.created action. -enabled = true -# The source type field is required for the c2pa.created action. -# -# For more information, see `c2pa::assertions::actions::source_type`. -source_type = "http://c2pa.org/digitalsourcetype/empty" - -# Settings for configuring how c2pa.opened actions are auto created. -# -# This is a convenience setting and it can be disabled if the information -# is provided manually. -[builder.actions.auto_opened_action] -# Whether to auto create the c2pa.opened action. -enabled = true -# For more information, see `c2pa::assertions::actions::source_type`. -# -# Note this field is optional for the c2pa.opened action. -source_type = "http://c2pa.org/digitalsourcetype/empty" - -# Settings for configuring how c2pa.placed actions are auto created. -# -# This is a convenience setting and it can be disabled if the information -# is provided manually. -[builder.actions.auto_placed_action] -# Whether to auto create the c2pa.placed action. -enabled = true -# For more information, see `c2pa::assertions::actions::source_type`. -# -# Note this field is optional for the c2pa.placed action. -source_type = "http://c2pa.org/digitalsourcetype/empty" - -# Settings for automatic thumbnail generation. -[builder.thumbnail] -# Whether to enable automatic thumbnail generation. -enabled = true -# Whether to ignore errors when generating a thumbnail and continue signing. -ignore_errors = true -# The size of the longest edge of the thumbnail. -long_edge = 512 -# The output format of the thumbnail. -# -# If this field isn't specified, the thumbnail format will correspond to the -# input format. -#format = "png" -# Whether or not to prefer a smaller sized media format for the thumbnail. -# -# The "format" option takes precedence over this field. -# -# For instance, if the source input type is a PNG, but it doesn't have an alpha channel, -# the image will be converted to a JPEG of smaller size. -prefer_smallest_format = true -# The output quality of the thumbnail (low, medium, high). -quality = "medium" diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 5b45a820..e6779067 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -44,38 +44,24 @@ def load_test_settings_as_json(): """ Load the test_settings.toml file and return its content as JSON-compatible dict. - + Returns: dict: The parsed TOML content as a Python dictionary (JSON-compatible). - + Raises: FileNotFoundError: If test_settings.toml is not found. toml.TomlDecodeError: If the TOML file is malformed. """ - # Get the directory where this file is located + # Locate the file which contains default settings for tests tests_dir = os.path.dirname(os.path.abspath(__file__)) - settings_path = os.path.join(tests_dir, 'test_settings.toml') - - # Load the TOML file + settings_path = os.path.join(tests_dir, 'trust_config_test_settings.toml') + + # Load the located default test settings with open(settings_path, 'r') as f: settings_data = toml.load(f) - - return settings_data + return settings_data -def load_test_settings_as_json_string(): - """ - Load the test_settings.toml file and return its content as a JSON string. - - Returns: - str: The parsed TOML content serialized as a JSON string. - - Raises: - FileNotFoundError: If test_settings.toml is not found. - toml.TomlDecodeError: If the TOML file is malformed. - """ - settings_data = load_test_settings_as_json() - return json.dumps(settings_data, indent=2) class TestC2paSdk(unittest.TestCase): def test_sdk_version(self): @@ -1012,8 +998,8 @@ def test_streams_sign_with_es256_alg_v1_manifest(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) # Write buffer to file @@ -1040,8 +1026,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_existing_empty_file(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -1069,8 +1055,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_new_dest_file(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -1094,8 +1080,8 @@ def test_streams_sign_with_es256_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -1109,8 +1095,8 @@ def test_streams_sign_with_es256_alg_2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -1185,8 +1171,8 @@ def test_sign_with_ed25519_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -1273,8 +1259,8 @@ def test_sign_with_ed25519_alg_2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -1301,8 +1287,8 @@ def test_sign_with_ps256_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -1405,12 +1391,63 @@ def test_archive_sign(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) archive.close() output.close() + def test_archive_sign_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + builder = Builder(self.manifestDefinition) + archive = io.BytesIO(bytearray()) + builder.to_archive(archive) + builder = Builder.from_archive(archive) + output = io.BytesIO(bytearray()) + builder.sign(self.signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader("image/jpeg", output) + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + archive.close() + output.close() + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + def test_archive_sign_with_added_ingredient(self): with open(self.testPath, "rb") as file: builder = Builder(self.manifestDefinitionV2) @@ -1427,12 +1464,66 @@ def test_archive_sign_with_added_ingredient(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) archive.close() output.close() + def test_archive_sign_with_added_ingredient_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + archive = io.BytesIO(bytearray()) + builder.to_archive(archive) + builder = Builder.from_archive(archive) + output = io.BytesIO(bytearray()) + ingredient_json = '{"test": "ingredient"}' + with open(self.testPath, 'rb') as f: + builder.add_ingredient(ingredient_json, "image/jpeg", f) + builder.sign(self.signer, "image/jpeg", file, output) + output.seek(0) + reader = Reader("image/jpeg", output) + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + archive.close() + output.close() + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + def test_remote_sign(self): with open(self.testPath, "rb") as file: builder = Builder(self.manifestDefinition) @@ -1475,6 +1566,56 @@ def test_remote_sign_using_returned_bytes_V2(self): manifest_data = reader.json() self.assertIn("Python Test", manifest_data) + def test_remote_sign_using_returned_bytes_V2_with_trust_config(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def sign_and_validate_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + builder.set_no_embed() + with io.BytesIO() as output_buffer: + manifest_data = builder.sign( + self.signer, "image/jpeg", file, output_buffer) + output_buffer.seek(0) + read_buffer = io.BytesIO(output_buffer.getvalue()) + + with Reader("image/jpeg", read_buffer, manifest_data) as reader: + json_data = reader.json() + + # Get validation state with trust config + validation_state = reader.get_validation_state() + + result['json_data'] = json_data + result['validation_state'] = validation_state + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=sign_and_validate_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIn("Python Test", result.get('json_data', '')) + # With trust configuration loaded, validation should return "Trusted" + self.assertIsNotNone(result.get('validation_state')) + self.assertEqual(result.get('validation_state'), "Trusted") + def test_sign_all_files(self): """Test signing all files in both fixtures directories""" signing_dir = os.path.join(self.data_dir, "files-for-signing-tests") @@ -1533,8 +1674,8 @@ def test_sign_all_files(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) reader.close() output.close() @@ -2067,8 +2208,8 @@ def test_sign_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` (which makes the manifest Invalid) + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -2085,8 +2226,8 @@ def test_sign_mp4_video_file_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -2103,8 +2244,8 @@ def test_sign_mov_video_file_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -2131,8 +2272,8 @@ def test_sign_file_tmn_wip(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -2162,8 +2303,8 @@ def test_sign_file_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -2217,8 +2358,8 @@ def test_builder_sign_file_callback_signer_from_callback(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm @@ -2271,8 +2412,8 @@ def test_builder_sign_file_callback_signer_from_callback_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm @@ -2327,8 +2468,8 @@ def ed25519_callback(data: bytes) -> bytes: json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) reader.close() output.close() @@ -2354,8 +2495,8 @@ def test_signing_manifest_v2(self): # Basic verification of the manifest self.assertIn("Python Test Image V2", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) output.close() @@ -2390,8 +2531,8 @@ def test_sign_file_mp4_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -2421,8 +2562,8 @@ def test_sign_file_mov_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explciitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes @@ -2430,8 +2571,8 @@ def test_sign_file_mov_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explciitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: @@ -2461,8 +2602,8 @@ def test_sign_file_mov_video_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes @@ -2470,8 +2611,8 @@ def test_sign_file_mov_video_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on red reports `signing certificate untrusted` - # (for most tests, where we don't explicitly want to test that, we don't need it, but it is needed for a correct e2e signing flow) + # Or validation on read reports `signing certificate untrusted` + # (which makes the manifest Invalid) self.assertIn("Invalid", json_data) finally: diff --git a/tests/trust_config_test_settings.toml b/tests/trust_config_test_settings.toml new file mode 100644 index 00000000..ab77e004 --- /dev/null +++ b/tests/trust_config_test_settings.toml @@ -0,0 +1,239 @@ +# c2pa-rs Configuration File + +# Version information. +version = 1 + +# Trust settings for certificate validation. +# [trust] +# String to user-provided trust anchors (PEM format). +# user_anchors = "" +# String to system trust anchors (PEM format). +# trust_anchors = "" +[trust] +trust_anchors = """-----BEGIN CERTIFICATE----- +MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD +VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M +WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 +NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo +ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF +U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D +eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM +fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy +dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 +zGxQnM2hCA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG +BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 +zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 +mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj +YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 +OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD +nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd +5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf +iwNQm1DDcQPtPTnddOs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq +QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX +y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD +VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE +FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 +Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye +PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh +eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA +MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 +jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +xPd7wFhjRZHfuWb2cs63xjAGjQ== +-----END CERTIFICATE----- +""" +# String to trust configuration. +trust_config = """ +//id-kp-emailProtection +1.3.6.1.5.5.7.3.4 +//id-kp-documentSigning +1.3.6.1.5.5.7.3.36 +//id-kp-timeStamping +1.3.6.1.5.5.7.3.8 +//id-kp-OCSPSigning +1.3.6.1.5.5.7.3.9 +// MS C2PA Signing +1.3.6.1.4.1.311.76.59.1.9 +// c2pa-kp-claimSigning +1.3.6.1.4.1.62558.2.1 +""" + +# # Path to allowed certificate list (PEM format). +# allowed_list = "" From 2647e2639558deb3a10872f2a59beef81cb5552c Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:28:45 -0700 Subject: [PATCH 04/11] fix: Update tests --- tests/test_unit_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index e6779067..ff5d55f9 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -43,7 +43,9 @@ def load_test_settings_as_json(): """ - Load the test_settings.toml file and return its content as JSON-compatible dict. + Load default trust configuration test settings from a + TOML config file and return its content as JSON-compatible dict. + The return value is used to load settings. Returns: dict: The parsed TOML content as a Python dictionary (JSON-compatible). From 42309f5d67f9026f6c85357b70b5d157fd9dc5b8 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:33:03 -0700 Subject: [PATCH 05/11] fix: Restore deleted file --- tests/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..867e2c84 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Placeholder \ No newline at end of file From 5d967cd25dd4fcb972430e077a54eb935092745b Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:37:14 -0700 Subject: [PATCH 06/11] fix: Update the examples --- examples/sign.py | 10 ++++++---- examples/sign_info.py | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/sign.py b/examples/sign.py index 7182f99a..3df9fd5b 100644 --- a/examples/sign.py +++ b/examples/sign.py @@ -26,7 +26,7 @@ fixtures_dir = os.path.join(os.path.dirname(__file__), "../tests/fixtures/") output_dir = os.path.join(os.path.dirname(__file__), "../output/") -# Ensure the output directory exists +# Ensure the output directory exists. if not os.path.exists(output_dir): os.makedirs(output_dir) @@ -43,7 +43,7 @@ with open(fixtures_dir + "es256_private.key", "rb") as key_file: key = key_file.read() -# Define a callback signer function +# Define a callback signer function. def callback_signer_es256(data: bytes) -> bytes: """Callback function that signs data using ES256 algorithm.""" private_key = serialization.load_pem_private_key( @@ -60,7 +60,6 @@ def callback_signer_es256(data: bytes) -> bytes: # Create a manifest definition as a dictionary. # This manifest follows the V2 manifest format. manifest_definition = { - "claim_generator": "python_example", "claim_generator_info": [{ "name": "python_example", "version": "0.0.1", @@ -87,7 +86,7 @@ def callback_signer_es256(data: bytes) -> bytes: } # Sign the image with the signer created above, -# which will use the callback signer +# which will use the callback signer. print("\nSigning the image file...") with c2pa.Signer.from_callback( @@ -107,6 +106,9 @@ def callback_signer_es256(data: bytes) -> bytes: print("\nReading signed image metadata:") with open(output_dir + "A_signed.jpg", "rb") as file: with c2pa.Reader("image/jpeg", file) as reader: + # The validation state will depend on loaded trust settings. + # Without loaded trust settings, + # the manifest validation_state will be "Invalid". print(reader.json()) print("\nExample completed successfully!") diff --git a/examples/sign_info.py b/examples/sign_info.py index 0efa68d8..6b256647 100644 --- a/examples/sign_info.py +++ b/examples/sign_info.py @@ -100,6 +100,9 @@ print("\nReading signed image metadata:") with open(output_dir + "C_signed.jpg", "rb") as file: with c2pa.Reader("image/jpeg", file) as reader: + # The validation state will depend on loaded trust settings. + # Without loaded trust settings, + # the manifest validation_state will be "Invalid". print(reader.json()) print("\nExample completed successfully!") From 2b1e74778d2eebcf2e2d7b66d0017915859582b5 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 19:41:34 -0700 Subject: [PATCH 07/11] fix: Clarify comment --- tests/test_unit_tests.py | 100 +++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index ff5d55f9..f5faafaa 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -1000,8 +1000,8 @@ def test_streams_sign_with_es256_alg_v1_manifest(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) # Write buffer to file @@ -1028,8 +1028,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_existing_empty_file(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -1057,8 +1057,8 @@ def test_streams_sign_with_es256_alg_v1_manifest_to_new_dest_file(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -1082,8 +1082,8 @@ def test_streams_sign_with_es256_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -1097,8 +1097,8 @@ def test_streams_sign_with_es256_alg_2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -1173,8 +1173,8 @@ def test_sign_with_ed25519_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -1261,8 +1261,8 @@ def test_sign_with_ed25519_alg_2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -1289,8 +1289,8 @@ def test_sign_with_ps256_alg(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -1393,8 +1393,8 @@ def test_archive_sign(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) archive.close() output.close() @@ -1466,8 +1466,8 @@ def test_archive_sign_with_added_ingredient(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) archive.close() output.close() @@ -1676,8 +1676,8 @@ def test_sign_all_files(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) reader.close() output.close() @@ -2210,8 +2210,8 @@ def test_sign_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` (which makes the manifest Invalid) - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` (which makes the manifest Invalid) + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -2228,8 +2228,8 @@ def test_sign_mp4_video_file_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -2246,8 +2246,8 @@ def test_sign_mov_video_file_single(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -2274,8 +2274,8 @@ def test_sign_file_tmn_wip(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -2305,8 +2305,8 @@ def test_sign_file_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -2360,8 +2360,8 @@ def test_builder_sign_file_callback_signer_from_callback(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm @@ -2414,8 +2414,8 @@ def test_builder_sign_file_callback_signer_from_callback_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) # Parse the JSON and verify the signature algorithm @@ -2470,8 +2470,8 @@ def ed25519_callback(data: bytes) -> bytes: json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) reader.close() output.close() @@ -2497,8 +2497,8 @@ def test_signing_manifest_v2(self): # Basic verification of the manifest self.assertIn("Python Test Image V2", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) output.close() @@ -2533,8 +2533,8 @@ def test_sign_file_mp4_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -2564,8 +2564,8 @@ def test_sign_file_mov_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes @@ -2573,8 +2573,8 @@ def test_sign_file_mov_video(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: @@ -2604,8 +2604,8 @@ def test_sign_file_mov_video_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) # Verify also signed file using manifest bytes @@ -2613,8 +2613,8 @@ def test_sign_file_mov_video_V2(self): json_data = reader.json() self.assertIn("Python Test", json_data) # Needs trust configuration to be set up to validate as Trusted, - # Or validation on read reports `signing certificate untrusted` - # (which makes the manifest Invalid) + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. self.assertIn("Invalid", json_data) finally: From bfcd186ed1954367cf3dac12079dd1cd66e37b7a Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Fri, 24 Oct 2025 20:42:08 -0700 Subject: [PATCH 08/11] fix: CLean up old tmp test --- tests/test_unit_tests.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index f5faafaa..cdb47b64 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -2251,37 +2251,6 @@ def test_sign_mov_video_file_single(self): self.assertIn("Invalid", json_data) output.close() - def test_sign_file_tmn_wip(self): - temp_dir = tempfile.mkdtemp() - try: - # Create a temporary output file path - output_path = os.path.join(temp_dir, "signed_output.jpg") - - # Use the sign_file method - builder = Builder(self.manifestDefinition) - builder.sign_file( - self.testPath, - output_path, - self.signer - ) - - # Verify the output file was created - self.assertTrue(os.path.exists(output_path)) - - # Read the signed file and verify the manifest - with open(output_path, "rb") as file: - reader = Reader("image/jpeg", file) - json_data = reader.json() - self.assertIn("Python Test", json_data) - # Needs trust configuration to be set up to validate as Trusted, - # or validation_status on read reports `signing certificate untrusted` - # which makes the manifest validation_state become Invalid. - self.assertIn("Invalid", json_data) - - finally: - # Clean up the temporary directory - shutil.rmtree(temp_dir) - def test_sign_file_video(self): temp_dir = tempfile.mkdtemp() try: From c847f797ac31dd4f3f0bb11b0acdf15e0311235b Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Mon, 27 Oct 2025 16:24:01 -0700 Subject: [PATCH 09/11] fix: Switch to json --- tests/test_unit_tests.py | 75 ++++++++++++++++++++++++--- tests/trust_config_test_settings.json | 7 +++ 2 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 tests/trust_config_test_settings.json diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index cdb47b64..1f610447 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -41,7 +41,7 @@ ALTERNATIVE_INGREDIENT_TEST_FILE = os.path.join(FIXTURES_DIR, "cloud.jpg") -def load_test_settings_as_json(): +def load_toml_test_settings_as_json(): """ Load default trust configuration test settings from a TOML config file and return its content as JSON-compatible dict. @@ -64,6 +64,29 @@ def load_test_settings_as_json(): return settings_data +def load_test_settings_json(): + """ + Load default trust configuration test settings from a + JSON config file and return its content as JSON-compatible dict. + The return value is used to load settings. + + Returns: + dict: The parsed JSON content as a Python dictionary (JSON-compatible). + + Raises: + FileNotFoundError: If trust_config_test_settings.json is not found. + json.JSONDecodeError: If the JSON file is malformed. + """ + # Locate the file which contains default settings for tests + tests_dir = os.path.dirname(os.path.abspath(__file__)) + settings_path = os.path.join(tests_dir, 'trust_config_test_settings.json') + + # Load the located default test settings + with open(settings_path, 'r') as f: + settings_data = json.load(f) + + return settings_data + class TestC2paSdk(unittest.TestCase): def test_sdk_version(self): @@ -146,6 +169,42 @@ def test_stream_read_get_validation_state(self): # Needs trust configuration to be set up to validate as Trusted, otherwise manifest is Invalid self.assertEqual(validation_state, "Invalid") + def test_stream_read_get_validation_state_with_trust_config_from_toml(self): + # Run in a separate thread to isolate thread-local settings + result = {} + exception = {} + + def read_with_trust_config(): + try: + # Load trust configuration from test_settings.toml + settings_dict = load_toml_test_settings_as_json() + + # Apply the settings (including trust configuration) + # Settings are thread-local, so they won't affect other tests + # And that is why we also run the test in its own thread, so tests are isolated + load_settings(settings_dict) + + with open(self.testPath, "rb") as file: + reader = Reader("image/jpeg", file) + validation_state = reader.get_validation_state() + result['validation_state'] = validation_state + except Exception as e: + exception['error'] = e + + # Create and start thread + thread = threading.Thread(target=read_with_trust_config) + thread.start() + thread.join() + + # Check for exceptions + if 'error' in exception: + raise exception['error'] + + # Assertions run in main thread + self.assertIsNotNone(result.get('validation_state')) + # With trust configuration loaded, manifest is Trusted + self.assertEqual(result.get('validation_state'), "Trusted") + def test_stream_read_get_validation_state_with_trust_config(self): # Run in a separate thread to isolate thread-local settings result = {} @@ -154,7 +213,7 @@ def test_stream_read_get_validation_state_with_trust_config(self): def read_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1110,7 +1169,7 @@ def test_streams_sign_with_es256_alg_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1186,7 +1245,7 @@ def test_sign_with_ed25519_alg_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1328,7 +1387,7 @@ def test_sign_with_ps256_alg_2_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1407,7 +1466,7 @@ def test_archive_sign_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1480,7 +1539,7 @@ def test_archive_sign_with_added_ingredient_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests @@ -1576,7 +1635,7 @@ def test_remote_sign_using_returned_bytes_V2_with_trust_config(self): def sign_and_validate_with_trust_config(): try: # Load trust configuration from test_settings.toml - settings_dict = load_test_settings_as_json() + settings_dict = load_test_settings_json() # Apply the settings (including trust configuration) # Settings are thread-local, so they won't affect other tests diff --git a/tests/trust_config_test_settings.json b/tests/trust_config_test_settings.json new file mode 100644 index 00000000..82422751 --- /dev/null +++ b/tests/trust_config_test_settings.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "trust": { + "trust_anchors": "-----BEGIN CERTIFICATE-----\nMIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ\nBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD\nVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M\nWTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2\nNDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo\nZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF\nU1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D\neKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM\nfgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD\nVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy\ndfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8\nzGxQnM2hCA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw\ndzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx\nGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO\nR19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw\nNzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT\nb21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG\nT1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG\nBSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5\nzco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7\nmkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj\nYzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6\nOykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE\nAwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD\nnyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd\n5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf\niwNQm1DDcQPtPTnddOs=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw\ndzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx\nGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO\nR19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw\nNzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT\nb21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG\nT1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF\nK4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq\nQBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX\ny/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD\nVR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO\nBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE\nFzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6\nY/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw\ndzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx\nGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO\nR19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw\nNzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT\nb21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG\nT1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI\nKoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye\nPcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh\neBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM\nMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA\nMEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9\njgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n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\nxPd7wFhjRZHfuWb2cs63xjAGjQ==\n-----END CERTIFICATE-----\n", + "trust_config": "//id-kp-emailProtection\n1.3.6.1.5.5.7.3.4\n//id-kp-documentSigning\n1.3.6.1.5.5.7.3.36\n//id-kp-timeStamping\n1.3.6.1.5.5.7.3.8\n//id-kp-OCSPSigning\n1.3.6.1.5.5.7.3.9\n// MS C2PA Signing\n1.3.6.1.4.1.311.76.59.1.9\n// c2pa-kp-claimSigning\n1.3.6.1.4.1.62558.2.1\n" + } +} \ No newline at end of file From b7454ed973dcb1e0a351bde99ff66bedbc3f3776 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Mon, 27 Oct 2025 21:00:25 -0700 Subject: [PATCH 10/11] fix: Add sign all files test with V2 spec --- tests/test_unit_tests.py | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index 1f610447..db158fbc 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -1745,6 +1745,74 @@ def test_sign_all_files(self): except Exception as e: self.fail(f"Failed to sign {filename}: {str(e)}") + def test_sign_all_files_V2(self): + """Test signing all files in both fixtures directories""" + signing_dir = os.path.join(self.data_dir, "files-for-signing-tests") + reading_dir = os.path.join(self.data_dir, "files-for-reading-tests") + + # Map of file extensions to MIME types + mime_types = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.heic': 'image/heic', + '.heif': 'image/heif', + '.avif': 'image/avif', + '.tif': 'image/tiff', + '.tiff': 'image/tiff', + '.mp4': 'video/mp4', + '.avi': 'video/x-msvideo', + '.mp3': 'audio/mpeg', + '.m4a': 'audio/mp4', + '.wav': 'audio/wav' + } + + # Skip files that are known to be invalid or unsupported + skip_files = { + 'sample3.invalid.wav', # Invalid file + } + + # Process both directories + for directory in [signing_dir, reading_dir]: + for filename in os.listdir(directory): + if filename in skip_files: + continue + + file_path = os.path.join(directory, filename) + if not os.path.isfile(file_path): + continue + + # Get file extension and corresponding MIME type + _, ext = os.path.splitext(filename) + ext = ext.lower() + if ext not in mime_types: + continue + + mime_type = mime_types[ext] + + try: + with open(file_path, "rb") as file: + builder = Builder(self.manifestDefinitionV2) + output = io.BytesIO(bytearray()) + builder.sign(self.signer, mime_type, file, output) + builder.close() + output.seek(0) + reader = Reader(mime_type, output) + json_data = reader.json() + self.assertIn("Python Test", json_data) + # Needs trust configuration to be set up to validate as Trusted, + # or validation_status on read reports `signing certificate untrusted` + # which makes the manifest validation_state become Invalid. + self.assertIn("Invalid", json_data) + reader.close() + output.close() + except Error.NotSupported: + continue + except Exception as e: + self.fail(f"Failed to sign {filename}: {str(e)}") + def test_builder_no_added_ingredient_on_closed_builder(self): builder = Builder(self.manifestDefinition) From a30d0f416ad4f89cc259e32e658c7d602182f871 Mon Sep 17 00:00:00 2001 From: Tania Mathern Date: Thu, 30 Oct 2025 15:30:24 -0700 Subject: [PATCH 11/11] fix: Remove toml example --- tests/test_unit_tests.py | 59 ------- tests/trust_config_test_settings.toml | 239 -------------------------- 2 files changed, 298 deletions(-) delete mode 100644 tests/trust_config_test_settings.toml diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index abcc98ee..5bd5960a 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -41,29 +41,6 @@ ALTERNATIVE_INGREDIENT_TEST_FILE = os.path.join(FIXTURES_DIR, "cloud.jpg") -def load_toml_test_settings_as_json(): - """ - Load default trust configuration test settings from a - TOML config file and return its content as JSON-compatible dict. - The return value is used to load settings. - - Returns: - dict: The parsed TOML content as a Python dictionary (JSON-compatible). - - Raises: - FileNotFoundError: If test_settings.toml is not found. - toml.TomlDecodeError: If the TOML file is malformed. - """ - # Locate the file which contains default settings for tests - tests_dir = os.path.dirname(os.path.abspath(__file__)) - settings_path = os.path.join(tests_dir, 'trust_config_test_settings.toml') - - # Load the located default test settings - with open(settings_path, 'r') as f: - settings_data = toml.load(f) - - return settings_data - def load_test_settings_json(): """ Load default trust configuration test settings from a @@ -175,42 +152,6 @@ def test_stream_read_get_validation_state(self): # Needs trust configuration to be set up to validate as Trusted, otherwise manifest is Invalid self.assertEqual(validation_state, "Invalid") - def test_stream_read_get_validation_state_with_trust_config_from_toml(self): - # Run in a separate thread to isolate thread-local settings - result = {} - exception = {} - - def read_with_trust_config(): - try: - # Load trust configuration from test_settings.toml - settings_dict = load_toml_test_settings_as_json() - - # Apply the settings (including trust configuration) - # Settings are thread-local, so they won't affect other tests - # And that is why we also run the test in its own thread, so tests are isolated - load_settings(settings_dict) - - with open(self.testPath, "rb") as file: - reader = Reader("image/jpeg", file) - validation_state = reader.get_validation_state() - result['validation_state'] = validation_state - except Exception as e: - exception['error'] = e - - # Create and start thread - thread = threading.Thread(target=read_with_trust_config) - thread.start() - thread.join() - - # Check for exceptions - if 'error' in exception: - raise exception['error'] - - # Assertions run in main thread - self.assertIsNotNone(result.get('validation_state')) - # With trust configuration loaded, manifest is Trusted - self.assertEqual(result.get('validation_state'), "Trusted") - def test_stream_read_get_validation_state_with_trust_config(self): # Run in a separate thread to isolate thread-local settings result = {} diff --git a/tests/trust_config_test_settings.toml b/tests/trust_config_test_settings.toml deleted file mode 100644 index ab77e004..00000000 --- a/tests/trust_config_test_settings.toml +++ /dev/null @@ -1,239 +0,0 @@ -# c2pa-rs Configuration File - -# Version information. -version = 1 - -# Trust settings for certificate validation. -# [trust] -# String to user-provided trust anchors (PEM format). -# user_anchors = "" -# String to system trust anchors (PEM format). -# trust_anchors = "" -[trust] -trust_anchors = """-----BEGIN CERTIFICATE----- -MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ -BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD -VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M -WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 -NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo -ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF -U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D -eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM -fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy -dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 -zGxQnM2hCA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG -BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 -zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 -mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj -YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 -OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD -nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd -5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf -iwNQm1DDcQPtPTnddOs= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq -QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX -y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD -VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE -FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 -Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye -PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh -eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA -MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 -jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 -xPd7wFhjRZHfuWb2cs63xjAGjQ== ------END CERTIFICATE----- -""" -# String to trust configuration. -trust_config = """ -//id-kp-emailProtection -1.3.6.1.5.5.7.3.4 -//id-kp-documentSigning -1.3.6.1.5.5.7.3.36 -//id-kp-timeStamping -1.3.6.1.5.5.7.3.8 -//id-kp-OCSPSigning -1.3.6.1.5.5.7.3.9 -// MS C2PA Signing -1.3.6.1.4.1.311.76.59.1.9 -// c2pa-kp-claimSigning -1.3.6.1.4.1.62558.2.1 -""" - -# # Path to allowed certificate list (PEM format). -# allowed_list = ""