diff --git a/build.rs b/build.rs index 3c856a55..b6899e9c 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ +// Purpose: Generate the scaffolding for the uniffi library. fn main() { - uniffi::generate_scaffolding("./src/c2pa.udl").unwrap(); + let _result = uniffi::generate_scaffolding("./src/c2pa.udl"); } diff --git a/c2pa/__init__.py b/c2pa/__init__.py index 13ac1aa2..013d42a0 100644 --- a/c2pa/__init__.py +++ b/c2pa/__init__.py @@ -11,8 +11,35 @@ # specific language governing permissions and limitations under # each license. -from .c2pa_api import Reader, Builder, create_signer, create_remote_signer, sign_ps256 -from .c2pa import Error, SigningAlg, CallbackSigner, sdk_version, version -from .c2pa.c2pa import _UniffiConverterTypeSigningAlg, _UniffiConverterTypeReader, _UniffiRustBuffer +from .c2pa_api import ( + Reader, + Builder, + create_signer, + create_remote_signer, + sign_ps256, + load_settings_file, +) +from .c2pa import Error, SigningAlg, CallbackSigner, sdk_version, version, load_settings +from .c2pa.c2pa import ( + _UniffiConverterTypeSigningAlg, + _UniffiConverterTypeReader, + _UniffiRustBuffer, +) -__all__ = ['Reader', 'Builder', 'CallbackSigner', 'create_signer', 'sign_ps256', 'Error', 'SigningAlg', 'sdk_version', 'version', 'create_remote_signer', '_UniffiConverterTypeSigningAlg', '_UniffiRustBuffer', '_UniffiConverterTypeReader'] +__all__ = [ + "Reader", + "Builder", + "CallbackSigner", + "create_signer", + "sign_ps256", + "Error", + "SigningAlg", + "sdk_version", + "version", + "load_settings", + "load_settings_file", + "create_remote_signer", + "_UniffiConverterTypeSigningAlg", + "_UniffiRustBuffer", + "_UniffiConverterTypeReader", +] diff --git a/c2pa/c2pa_api/__init__.py b/c2pa/c2pa_api/__init__.py index 99aab45b..52e45d98 100644 --- a/c2pa/c2pa_api/__init__.py +++ b/c2pa/c2pa_api/__init__.py @@ -1 +1 @@ -from .c2pa_api import * \ No newline at end of file +from .c2pa_api import * diff --git a/c2pa/c2pa_api/c2pa_api.py b/c2pa/c2pa_api/c2pa_api.py index f507616e..7d06f385 100644 --- a/c2pa/c2pa_api/c2pa_api.py +++ b/c2pa/c2pa_api/c2pa_api.py @@ -17,15 +17,14 @@ import tempfile PROJECT_PATH = os.getcwd() -SOURCE_PATH = os.path.join( - PROJECT_PATH,"target","python" -) +SOURCE_PATH = os.path.join(PROJECT_PATH, "target", "python") sys.path.append(SOURCE_PATH) import c2pa.c2pa as api # This module provides a simple Python API for the C2PA library. + # Reader is used to read a manifest store from a stream or file. # It performs full validation on the manifest store. # It also supports writing resources to a stream or file. @@ -37,7 +36,9 @@ class Reader(api.Reader): def __init__(self, format, stream, manifest_data=None): super().__init__() if manifest_data is not None: - self.from_manifest_data_and_stream(manifest_data, format, C2paStream(stream)) + self.from_manifest_data_and_stream( + manifest_data, format, C2paStream(stream) + ) else: self.from_stream(format, C2paStream(stream)) @@ -67,6 +68,7 @@ def resource_to_file(self, uri, path) -> None: with open(path, "wb") as file: return self.resource_to_stream(uri, file) + # The Builder is used to construct a new Manifest and add it to a stream or file. # The initial manifest is defined by a Manifest Definition dictionary. # It supports adding resources from a stream or file. @@ -130,7 +132,7 @@ def from_archive(cls, stream): super().from_archive(self, C2paStream(stream)) return self - def sign(self, signer, format, input, output = None): + def sign(self, signer, format, input, output=None): return super().sign(signer, format, C2paStream(input), C2paStream(output)) def sign_file(self, signer, sourcePath, outputPath): @@ -145,7 +147,7 @@ def __init__(self, stream): self.stream = stream def read_stream(self, length: int) -> bytes: - #print("Reading " + str(length) + " bytes") + # print("Reading " + str(length) + " bytes") return self.stream.read(length) def seek_stream(self, pos: int, mode: api.SeekMode) -> int: @@ -154,11 +156,11 @@ def seek_stream(self, pos: int, mode: api.SeekMode) -> int: whence = 1 elif mode is api.SeekMode.END: whence = 2 - #print("Seeking to " + str(pos) + " with whence " + str(whence)) + # print("Seeking to " + str(pos) + " with whence " + str(whence)) return self.stream.seek(pos, whence) def write_stream(self, data: str) -> int: - #print("Writing " + str(len(data)) + " bytes") + # print("Writing " + str(len(data)) + " bytes") return self.stream.write(data) def flush_stream(self) -> None: @@ -178,11 +180,12 @@ def __init__(self, callback): # Convenience class so we can just pass in a callback function -#class CallbackSigner(c2pa.CallbackSigner): +# class CallbackSigner(c2pa.CallbackSigner): # def __init__(self, callback, alg, certs, timestamp_url=None): # cb = SignerCallback(callback) # super().__init__(cb, alg, certs, timestamp_url) + # Creates a Signer given a callback and configuration values # It is used by the Builder class to sign the asset # @@ -196,6 +199,7 @@ def __init__(self, callback): def create_signer(callback, alg, certs, timestamp_url=None): return api.CallbackSigner(SignerCallback(callback), alg, certs, timestamp_url) + # Because we "share" SigningAlg enum in-between bindings, # seems we need to manually coerce the enum types, # like unffi itself does too @@ -218,29 +222,35 @@ def convert_to_alg(alg): case _: raise ValueError("Unsupported signing algorithm: " + str(alg)) + # Creates a special case signer that uses direct COSE handling # The callback signer should also define the signing algorithm to use # And a way to find out the needed reserve size def create_remote_signer(callback): return api.CallbackSigner.new_from_signer( - callback, - convert_to_alg(callback.alg()), - callback.reserve_size() + callback, convert_to_alg(callback.alg()), callback.reserve_size() ) + # Example of using openssl in an os shell to sign data using Ps256 # Note: the openssl command line tool must be installed for this to work def sign_ps256_shell(data: bytes, key_path: str) -> bytes: with tempfile.NamedTemporaryFile() as bytes: bytes.write(data) signature = tempfile.NamedTemporaryFile() - os.system("openssl dgst -sha256 -sign {} -out {} {}".format(key_path, signature.name, bytes.name)) + os.system( + "openssl dgst -sha256 -sign {} -out {} {}".format( + key_path, signature.name, bytes.name + ) + ) return signature.read() + # Example of using python crypto to sign data using openssl with Ps256 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding + def sign_ps256(data: bytes, key: bytes) -> bytes: private_key = serialization.load_pem_private_key( key, @@ -249,9 +259,17 @@ def sign_ps256(data: bytes, key: bytes) -> bytes: signature = private_key.sign( data, padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH + mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), - hashes.SHA256() + hashes.SHA256(), ) return signature + + +def load_settings_file(path: str, format=None): + with open(path, "r") as file: + if format is None: + # determine the format from the file extension + format = os.path.splitext(path)[1][1:] + settings = file.read() + api.load_settings(settings, format) diff --git a/src/c2pa.udl b/src/c2pa.udl index 37d3acbf..f624fcb5 100644 --- a/src/c2pa.udl +++ b/src/c2pa.udl @@ -2,6 +2,8 @@ namespace c2pa { string version(); string sdk_version(); + [Throws=Error] + void load_settings([ByRef] string format, [ByRef] string settings); }; [Error] diff --git a/src/lib.rs b/src/lib.rs index 50f4035e..3d9e7d9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use std::env; use std::sync::RwLock; -pub use c2pa::{Signer, SigningAlg}; +pub use c2pa::{Signer, SigningAlg, settings::load_settings_from_str}; /// these all need to be public so that the uniffi macro can see them mod error; @@ -45,6 +45,11 @@ pub fn sdk_version() -> String { ) } +pub fn load_settings(settings: &str, format: &str) -> Result<()> { + load_settings_from_str(settings, format)?; + Ok(()) +} + pub struct Reader { reader: RwLock, } @@ -102,6 +107,14 @@ impl Reader { } } + // pub fn validation_state(&self) -> Result { + // if let Ok(st) = self.reader.validation_state() { + // Ok(st.validation_state()) + // } else { + // Err(Error::RwLock) + // } + // } + pub fn resource_to_stream(&self, uri: &str, stream: &dyn Stream) -> Result { if let Ok(reader) = self.reader.try_read() { let mut stream = StreamAdapter::from(stream); diff --git a/tests/benchmark.py b/tests/benchmark.py index 7b1e7df6..8f64d4b9 100644 --- a/tests/benchmark.py +++ b/tests/benchmark.py @@ -1,43 +1,65 @@ -from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256 +# Copyright 2024 Adobe. All rights reserved. +# This file is licensed to you under the Apache License, +# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +# or the MIT license (http://opensource.org/licenses/MIT), +# at your option. +# Unless required by applicable law or agreed to in writing, +# this software is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +# implied. See the LICENSE-MIT and LICENSE-APACHE files for the +# specific language governing permissions and limitations under +# each license. + +from c2pa import ( + Builder, + Error, + Reader, + SigningAlg, + create_signer, + sdk_version, + sign_ps256, +) import os import io + PROJECT_PATH = os.getcwd() testPath = os.path.join(PROJECT_PATH, "tests", "fixtures", "C.jpg") manifestDefinition = { "claim_generator": "python_test", - "claim_generator_info": [{ - "name": "python_test", - "version": "0.0.1", - }], + "claim_generator_info": [ + { + "name": "python_test", + "version": "0.0.1", + } + ], "format": "image/jpeg", "title": "Python Test Image", "ingredients": [], "assertions": [ - { 'label': 'stds.schema-org.CreativeWork', - 'data': { - '@context': 'http://schema.org/', - '@type': 'CreativeWork', - 'author': [ - { '@type': 'Person', - 'name': 'Gavin Peacock' - } - ] + { + "label": "stds.schema-org.CreativeWork", + "data": { + "@context": "http://schema.org/", + "@type": "CreativeWork", + "author": [{"@type": "Person", "name": "Gavin Peacock"}], }, - 'kind': 'Json' + "kind": "Json", } - ] + ], } -private_key = open("tests/fixtures/ps256.pem","rb").read() +private_key = open("tests/fixtures/ps256.pem", "rb").read() + # Define a function that signs data with PS256 using a private key def sign(data: bytes) -> bytes: print("date len = ", len(data)) return sign_ps256(data, private_key) + # load the public keys from a pem file -certs = open("tests/fixtures/ps256.pub","rb").read() +certs = open("tests/fixtures/ps256.pub", "rb").read() # Create a local Ps256 signer with certs and a timestamp server signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") @@ -50,22 +72,27 @@ def sign(data: bytes) -> bytes: testPath = "tests/fixtures/c.jpg" outputPath = "target/python_out.jpg" + def test_files_build(): - # Delete the output file if it exists + # Delete the output file if it exists if os.path.exists(outputPath): os.remove(outputPath) builder.sign_file(signer, testPath, outputPath) + def test_streams_build(): - #with open(testPath, "rb") as file: + # with open(testPath, "rb") as file: output = io.BytesIO(bytearray()) builder.sign(signer, "image/jpeg", io.BytesIO(source), output) + def test_func(benchmark): benchmark(test_files_build) + def test_streams(benchmark): benchmark(test_streams_build) -#def test_signer(benchmark): -# benchmark(sign_ps256, data, private_key) \ No newline at end of file + +# def test_signer(benchmark): +# benchmark(sign_ps256, data, private_key) diff --git a/tests/fixtures/settings.toml b/tests/fixtures/settings.toml new file mode 100644 index 00000000..508377c6 --- /dev/null +++ b/tests/fixtures/settings.toml @@ -0,0 +1,229 @@ +# This sample c2pa settings file enables a trust list. +# In practice you should update the trust anchors from a remote source as needed. +# Many other settings are available, see the c2pa documentation for more information. +[verify] +trusted = true + +[trust] +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 +""" +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----- +MIIF3zCCA8egAwIBAgIUfPyUDhze4auMF066jChlB9aD2yIwDQYJKoZIhvcNAQEL +BQAwdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hl +cmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVT +VElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTI0MDczMTE5MDUwMVoXDTM0 +MDcyOTE5MDUwMVowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQH +DAlTb21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQL +DBBGT1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAkBSlOCwlWBgbqLxFu99ERwU23D/V7qBs7GsA +ZPaAvwCKf7FgVTpkzz6xsgArQU6MVo8n1tXUWWThB81xTXwqbWINP0pl5RnZKFxH +TmloE2VEMrEK3q4W6gqMjyiG+hPkwUK450WdJGkUkYi2rp6YF9YWJHv7YqYodz+u +mkIRcsczwRPDaJ7QA6pu3V4YlwrFXZu7jMHHMju02emNoiI8n7QZBJXpRr4C87jT +Ad+aNJQZ1DJ/S/QfiYpaXQ2xNH/Wq7zNXXIMs/LU0kUCggFIj+k6tmaYIAYKJR6o +dmV3anBTF8iSuAqcUXvM4IYMXSqMgzot3MYPYPdC+rj+trQ9bCPOkMAp5ySx8pYr +Upo79FOJvG8P9JzuFRsHBobYjtQqJnn6OczM69HVXCQn4H4tBpotASjT2gc6sHYv +a7YreKCbtFLpJhslNysIzVOxlnDbsugbq1gK8mAwG48ttX15ZUdX10MDTpna1FWu +Jnqa6K9NUfrvoW97ff9itca5NDRmm/K5AVA801NHFX1ApVty9lilt+DFDtaJd7zy +9w0+8U1sZ4+sc8moFRPqvEZZ3gdFtDtVjShcwdbqHZdSNU2lNbVCiycjLs/5EMRO +WfAxNZaKUreKGfOZkvQNqBhuebF3AfgmP6iP1qtO8aSilC1/43DjVRx3SZ1eecO6 +n0VGjgcCAwEAAaNjMGEwHQYDVR0OBBYEFBTOcmBU5xp7Jfn4Nzyw+kIc73yHMB8G +A1UdIwQYMBaAFBTOcmBU5xp7Jfn4Nzyw+kIc73yHMA8GA1UdEwEB/wQFMAMBAf8w +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCLexj0luEpQh/LEB14 +ARG/yQ8iqW2FMonQsobrDQSI4BhrQ4ak5I892MQX9xIoUpRAVp8GkJ/eXM6ChmXa +wMJSkfrPGIvES4TY2CtmXDNo0UmHD1GDfHKQ06FJtRJWpn9upT/9qTclTNtvwxQ8 +bKl/y7lrFsn+fQsKL2i5uoQ9nGpXG7WPirJEt9jcld2yylWSStTS4MXJIZSlALIA +mBTkbzEpzBOLHRRezdfoV4hyL/tWyiXa799436kO48KtwEzvYzC5cZ4bqvM5BXQf +6aiIYZT7VypFwJQtpTgnfrsjr2Y8q/+N7FoMpLfFO4eeqtwWPiP/47/lb9np/WQq +iO/yyIwYVwiqVG0AyzA5Z4pdke1t93y3UuhXgxevJ7GqGXuLCM0iMqFrAkPlLJzI +84THLJzFy+wEKH+/L1Zi94cHNj3WvablAMG5v/Kfr6k+KueNQzrY4jZrQPUEdxjv +xk/1hyZg+khAPVKRxhWeIr6/KIuQYu6kJeTqmXKafx5oHAS6OqcK7G1KbEa1bWMV +K0+GGwenJOzSTKWKtLO/6goBItGnhyQJCjwiBKOvcW5yfEVjLT+fJ7dkvlSzFMaM +OZIbev39n3rQTWb4ORq1HIX2JwNsEQX+gBv6aGjMT2a88QFS0TsAA5LtFl8xeVgt +xPd7wFhjRZHfuWb2cs63xjAGjQ== +-----END CERTIFICATE----- +""" \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py index c9758100..337a9651 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,49 +17,52 @@ import shutil import unittest -from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256, version +from c2pa import ( + Builder, + Error, + Reader, + SigningAlg, + create_signer, + sdk_version, + sign_ps256, + version, +) # a little helper function to get a value from a nested dictionary from functools import reduce import operator + def getitem(d, key): return reduce(operator.getitem, key, d) + # define the manifest we will use for testing manifest_def = { - "claim_generator_info": [{ - "name": "python test", - "version": "0.1" - }], + "claim_generator_info": [{"name": "python test", "version": "0.1"}], "title": "My Title", - "thumbnail": { - "format": "image/jpeg", - "identifier": "A.jpg" - }, + "thumbnail": {"format": "image/jpeg", "identifier": "A.jpg"}, "assertions": [ - { + { "label": "c2pa.training-mining", "data": { "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } + "c2pa.ai_generative_training": {"use": "notAllowed"}, + "c2pa.ai_inference": {"use": "notAllowed"}, + "c2pa.ai_training": {"use": "notAllowed"}, + "c2pa.data_mining": {"use": "notAllowed"}, } - } + }, } - ] + ], } ingredient_def = { "relationship": "parentOf", - "thumbnail": { - "identifier": "A.jpg", - "format": "image/jpeg" - } + "thumbnail": {"identifier": "A.jpg", "format": "image/jpeg"}, } + class TestC2paSdk(unittest.TestCase): def test_version(self): assert version() == "0.6.2" @@ -67,6 +70,7 @@ def test_version(self): def test_sdk_version(self): assert "c2pa-rs/" in sdk_version() + class TestReader(unittest.TestCase): def test_v2_read_cloud_manifest(self): reader = Reader.from_file("tests/fixtures/cloud.jpg") @@ -74,28 +78,40 @@ def test_v2_read_cloud_manifest(self): assert manifest is not None def test_v2_read(self): - #example of reading a manifest store from a file + # example of reading a manifest store from a file try: reader = Reader.from_file("tests/fixtures/C.jpg") manifest = reader.get_active_manifest() assert manifest is not None assert "make_test_images" in manifest["claim_generator"] - assert manifest["title"]== "C.jpg" + assert manifest["title"] == "C.jpg" assert manifest["format"] == "image/jpeg" # There should be no validation status errors assert manifest.get("validation_status") == None # read creative work assertion (author name) - assert getitem(manifest,("assertions",0,"label")) == "stds.schema-org.CreativeWork" - assert getitem(manifest,("assertions",0,"data","author",0,"name")) == "Adobe make_test" + assert ( + getitem(manifest, ("assertions", 0, "label")) + == "stds.schema-org.CreativeWork" + ) + assert ( + getitem(manifest, ("assertions", 0, "data", "author", 0, "name")) + == "Adobe make_test" + ) # read Actions assertion - assert getitem(manifest,("assertions",1,"label")) == "c2pa.actions" - assert getitem(manifest,("assertions",1,"data","actions",0,"action")) == "c2pa.created" + assert getitem(manifest, ("assertions", 1, "label")) == "c2pa.actions" + assert ( + getitem(manifest, ("assertions", 1, "data", "actions", 0, "action")) + == "c2pa.created" + ) # read signature info - assert getitem(manifest,("signature_info","issuer")) == "C2PA Test Signing Cert" + assert ( + getitem(manifest, ("signature_info", "issuer")) + == "C2PA Test Signing Cert" + ) # read thumbnail data from file - assert getitem(manifest,("thumbnail","format")) == "image/jpeg" + assert getitem(manifest, ("thumbnail", "format")) == "image/jpeg" # check the thumbnail data - uri = getitem(manifest,("thumbnail","identifier")) + uri = getitem(manifest, ("thumbnail", "identifier")) reader.resource_to_file(uri, "target/thumbnail_read_v2.jpg") except Exception as e: @@ -106,18 +122,24 @@ def test_reader_from_file_no_store(self): with pytest.raises(Error.ManifestNotFound) as err: reader = Reader.from_file("tests/fixtures/A.jpg") + class TestSignerr(unittest.TestCase): def test_v2_sign(self): # define a source folder for any assets we need to read data_dir = "tests/fixtures/" try: - key = open(data_dir + "ps256.pem", "rb").read() + with open(data_dir + "ps256.pem", "rb") as file: + key = file.read() + def sign(data: bytes) -> bytes: return sign_ps256(data, key) - certs = open(data_dir + "ps256.pub", "rb").read() + with open(data_dir + "ps256.pub", "rb") as file: + certs = file.read() # Create a local signer from a certificate pem file - signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + signer = create_signer( + sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com" + ) builder = Builder(manifest_def) @@ -125,15 +147,23 @@ def sign(data: bytes) -> bytes: builder.add_resource_file("A.jpg", data_dir + "A.jpg") - builder.to_archive(open("target/archive.zip", "wb")) + output_archive = "target/archive.zip" + if os.path.exists(output_archive): + os.remove(output_archive) + + with open(output_archive, "wb") as file: + builder.to_archive(file) - builder = Builder.from_archive(open("target/archive.zip", "rb")) + with open(output_archive, "rb") as file: + builder = Builder.from_archive(file) with tempfile.TemporaryDirectory() as output_dir: output_path = output_dir + "out.jpg" if os.path.exists(output_path): os.remove(output_path) - c2pa_data = builder.sign_file(signer, data_dir + "A.jpg", output_dir + "out.jpg") + c2pa_data = builder.sign_file( + signer, data_dir + "A.jpg", output_dir + "out.jpg" + ) assert len(c2pa_data) > 0 reader = Reader.from_file(output_dir + "out.jpg") @@ -142,8 +172,8 @@ def sign(data: bytes) -> bytes: manifest = manifest_store["manifests"][manifest_store["active_manifest"]] assert "python_test" in manifest["claim_generator"] # check custom title and format - assert manifest["title"]== "My Title" - assert manifest,["format"] == "image/jpeg" + assert manifest["title"] == "My Title" + assert manifest, ["format"] == "image/jpeg" # There should be no validation status errors assert manifest.get("validation_status") == None assert manifest["ingredients"][0]["relationship"] == "parentOf" @@ -156,13 +186,19 @@ def sign(data: bytes) -> bytes: def test_v2_sign_file_same(self): data_dir = "tests/fixtures/" try: - key = open(data_dir + "ps256.pem", "rb").read() + with open(data_dir + "ps256.pem", "rb") as file: + key = file.read() + def sign(data: bytes) -> bytes: return sign_ps256(data, key) - certs = open(data_dir + "ps256.pub", "rb").read() + with open(data_dir + "ps256.pub", "rb") as file: + certs = file.read() + # Create a local signer from a certificate pem file - signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + signer = create_signer( + sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com" + ) builder = Builder(manifest_def) @@ -179,13 +215,14 @@ def sign(data: bytes) -> bytes: manifest = reader.get_active_manifest() # check custom title and format - assert manifest["title"]== "My Title" + assert manifest["title"] == "My Title" assert manifest["format"] == "image/jpeg" # There should be no validation status errors assert manifest.get("validation_status") == None except Exception as e: print("Failed to sign manifest store: " + str(e)) - #exit(1) + # exit(1) + -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_unit_tests.py b/tests/test_unit_tests.py index d10ab197..37836141 100644 --- a/tests/test_unit_tests.py +++ b/tests/test_unit_tests.py @@ -17,12 +17,22 @@ import unittest from unittest.mock import mock_open, patch -from c2pa import Builder, Error, Reader, SigningAlg, create_signer, sdk_version, sign_ps256 +from c2pa import ( + Builder, + Error, + Reader, + SigningAlg, + create_signer, + sdk_version, + sign_ps256, + load_settings_file, +) PROJECT_PATH = os.getcwd() testPath = os.path.join(PROJECT_PATH, "tests", "fixtures", "C.jpg") + class TestC2paSdk(unittest.TestCase): def test_version(self): self.assertIn("0.6.2", sdk_version()) @@ -31,7 +41,7 @@ def test_version(self): class TestReader(unittest.TestCase): def test_stream_read(self): with open(testPath, "rb") as file: - reader = Reader("image/jpeg",file) + reader = Reader("image/jpeg", file) json = reader.json() self.assertIn("C.jpg", json) @@ -39,56 +49,67 @@ def test_stream_read_and_parse(self): with open(testPath, "rb") as file: reader = Reader("image/jpeg", file) manifest_store = json.loads(reader.json()) - title = manifest_store["manifests"][manifest_store["active_manifest"]]["title"] + title = manifest_store["manifests"][manifest_store["active_manifest"]][ + "title" + ] self.assertEqual(title, "C.jpg") def test_json_decode_err(self): with self.assertRaises(Error.Io): - manifest_store = Reader("image/jpeg","foo") + manifest_store = Reader("image/jpeg", "foo") def test_reader_bad_format(self): with self.assertRaises(Error.NotSupported): with open(testPath, "rb") as file: reader = Reader("badFormat", file) + def test_settings_trust(self): + load_settings_file("tests/fixtures/settings.toml") + with open(testPath, "rb") as file: + reader = Reader("image/jpeg", file) + json = reader.json() + self.assertIn("C.jpg", json) + class TestBuilder(unittest.TestCase): # Define a manifest as a dictionary manifestDefinition = { "claim_generator": "python_test", - "claim_generator_info": [{ - "name": "python_test", - "version": "0.0.1", - }], + "claim_generator_info": [ + { + "name": "python_test", + "version": "0.0.1", + } + ], "format": "image/jpeg", "title": "Python Test Image", "ingredients": [], "assertions": [ - { 'label': 'stds.schema-org.CreativeWork', - 'data': { - '@context': 'http://schema.org/', - '@type': 'CreativeWork', - 'author': [ - { '@type': 'Person', - 'name': 'Gavin Peacock' - } - ] + { + "label": "stds.schema-org.CreativeWork", + "data": { + "@context": "http://schema.org/", + "@type": "CreativeWork", + "author": [{"@type": "Person", "name": "Gavin Peacock"}], }, - 'kind': 'Json' + "kind": "Json", } - ] + ], } # Define a function that signs data with PS256 using a private key def sign(data: bytes) -> bytes: - key = open("tests/fixtures/ps256.pem","rb").read() - return sign_ps256(data, key) + with open("tests/fixtures/ps256.pem", "rb") as file: + key = file.read() + return sign_ps256(data, key) # load the public keys from a pem file - certs = open("tests/fixtures/ps256.pub","rb").read() + certs = open("tests/fixtures/ps256.pub", "rb").read() # Create a local Ps256 signer with certs and a timestamp server - signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + signer = create_signer( + sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com" + ) def test_streams_sign(self): with open(testPath, "rb") as file: @@ -127,5 +148,6 @@ def test_remote_sign(self): self.assertIn("Python Test", json_data) self.assertNotIn("validation_status", json_data) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/training.py b/tests/training.py index 392009c5..aa2828b1 100644 --- a/tests/training.py +++ b/tests/training.py @@ -20,67 +20,64 @@ # set up paths to the files we we are using PROJECT_PATH = os.getcwd() -testFile = os.path.join(PROJECT_PATH,"tests","fixtures","A.jpg") -pemFile = os.path.join(PROJECT_PATH,"tests","fixtures","es256_certs.pem") -keyFile = os.path.join(PROJECT_PATH,"tests","fixtures","es256_private.key") -testOutputFile = os.path.join(PROJECT_PATH,"target","dnt.jpg") +testFile = os.path.join(PROJECT_PATH, "tests", "fixtures", "A.jpg") +pemFile = os.path.join(PROJECT_PATH, "tests", "fixtures", "es256_certs.pem") +keyFile = os.path.join(PROJECT_PATH, "tests", "fixtures", "es256_private.key") +testOutputFile = os.path.join(PROJECT_PATH, "target", "dnt.jpg") # a little helper function to get a value from a nested dictionary from functools import reduce import operator + + def getitem(d, key): return reduce(operator.getitem, key, d) + print("version = " + version()) # first create an asset with a do not train assertion # define a manifest with the do not train assertion manifest_json = { - "claim_generator_info": [{ - "name": "python_test", - "version": "0.1" - }], + "claim_generator_info": [{"name": "python_test", "version": "0.1"}], "title": "Do Not Train Example", - "thumbnail": { - "format": "image/jpeg", - "identifier": "thumbnail" - }, + "thumbnail": {"format": "image/jpeg", "identifier": "thumbnail"}, "assertions": [ - { - "label": "c2pa.training-mining", - "data": { - "entries": { - "c2pa.ai_generative_training": { "use": "notAllowed" }, - "c2pa.ai_inference": { "use": "notAllowed" }, - "c2pa.ai_training": { "use": "notAllowed" }, - "c2pa.data_mining": { "use": "notAllowed" } + { + "label": "c2pa.training-mining", + "data": { + "entries": { + "c2pa.ai_generative_training": {"use": "notAllowed"}, + "c2pa.ai_inference": {"use": "notAllowed"}, + "c2pa.ai_training": {"use": "notAllowed"}, + "c2pa.data_mining": {"use": "notAllowed"}, + } + }, } - } - } - ] + ], } ingredient_json = { "title": "A.jpg", "relationship": "parentOf", - "thumbnail": { - "identifier": "thumbnail", - "format": "image/jpeg" - } + "thumbnail": {"identifier": "thumbnail", "format": "image/jpeg"}, } # V2 signing api try: # This could be implemented on a server using an HSM - key = open("tests/fixtures/ps256.pem","rb").read() + key = open("tests/fixtures/ps256.pem", "rb").read() + def sign(data: bytes) -> bytes: return sign_ps256(data, key) - certs = open("tests/fixtures/ps256.pub","rb").read() + certs = open("tests/fixtures/ps256.pub", "rb").read() # Create a signer from a certificate pem file - signer = create_signer(sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com") + signer = create_signer( + sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com" + ) builder = Builder(manifest_json) @@ -101,7 +98,7 @@ def sign(data: bytes) -> bytes: # now verify the asset and check the manifest for a do not train assertion -allowed = True # opt out model, assume training is ok if the assertion doesn't exist +allowed = True # opt out model, assume training is ok if the assertion doesn't exist try: reader = Reader.from_file(testOutputFile) manifest_store = json.loads(reader.json()) @@ -109,11 +106,14 @@ def sign(data: bytes) -> bytes: manifest = manifest_store["manifests"][manifest_store["active_manifest"]] for assertion in manifest["assertions"]: if assertion["label"] == "c2pa.training-mining": - if getitem(assertion, ("data","entries","c2pa.ai_training","use")) == "notAllowed": + if ( + getitem(assertion, ("data", "entries", "c2pa.ai_training", "use")) + == "notAllowed" + ): allowed = False # get the ingredient thumbnail - uri = getitem(manifest,("ingredients", 0, "thumbnail", "identifier")) + uri = getitem(manifest, ("ingredients", 0, "thumbnail", "identifier")) reader.resource_to_file(uri, "target/thumbnail_v2.jpg") except Exception as err: sys.exit(err)