ruby-c2pa 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/c2pa.rb CHANGED
@@ -2,10 +2,52 @@ require "json"
2
2
  require_relative "c2pa/version"
3
3
  require_relative "c2pa/error"
4
4
  require_relative "c2pa/actions"
5
+ require_relative "c2pa/digital_source_types"
6
+ require_relative "c2pa/config"
5
7
  require_relative "c2pa/manifest"
6
8
  require "c2pa/c2pa_native"
7
9
 
8
10
  module C2PA
11
+ # Validation states that mean a signed asset is good.
12
+ #
13
+ # "Trusted" is "Valid" plus a signing certificate that chains to a root in
14
+ # the active trust list. Accepting only "Valid" would reject exactly the
15
+ # production files that are most correct.
16
+ VALID_STATES = %w[Valid Trusted].freeze
17
+
18
+ # Configure how c2pa-rs validates.
19
+ #
20
+ # Settings are global and take effect for subsequent calls. Signing already
21
+ # in flight on another thread continues against the settings it started
22
+ # with, since the underlying context is replaced rather than mutated.
23
+ #
24
+ # @yieldparam config [C2PA::Config]
25
+ # @return [C2PA::Config] the configuration that was applied
26
+ # @raise [C2PA::InvalidSettingsError] if the settings are not usable
27
+ #
28
+ # @example Trusting a private CA
29
+ # C2PA.configure do |config|
30
+ # config.trust_anchors = "ca/root.pem"
31
+ # end
32
+ #
33
+ # @example An offline environment
34
+ # C2PA.configure do |config|
35
+ # config.remote_manifest_fetch = false
36
+ # config.ocsp_fetch = false
37
+ # end
38
+ def self.configure
39
+ config = Config.new
40
+ yield config if block_given?
41
+
42
+ begin
43
+ Native.configure(config.to_json)
44
+ rescue RuntimeError => e
45
+ raise InvalidSettingsError, e.message
46
+ end
47
+
48
+ config
49
+ end
50
+
9
51
  # Sign a file with a C2PA manifest.
10
52
  #
11
53
  # @param file [String] path to the input file
@@ -14,7 +56,10 @@ module C2PA
14
56
  # @param key [String] path to a PEM-encoded private key
15
57
  # @param algorithm [String] signing algorithm (default: "es256")
16
58
  # @param manifest [C2PA::Manifest] the manifest to embed
59
+ # @param verify [Boolean] read the signed file back and confirm it
60
+ # validates (default: true)
17
61
  # @return [String] the output path
62
+ # @raise [C2PA::SigningError] if signing fails, or if the signed file does not validate
18
63
  #
19
64
  # @example
20
65
  # manifest = C2PA::Manifest.new(title: "Sunset over the bay")
@@ -27,7 +72,7 @@ module C2PA
27
72
  # key: "key.pem",
28
73
  # manifest: manifest
29
74
  # )
30
- def self.sign(file:, output:, certificate:, key:, algorithm: "es256", manifest:)
75
+ def self.sign(file:, output:, certificate:, key:, algorithm: "es256", manifest:, verify: true)
31
76
  manifest_json = manifest.to_json
32
77
 
33
78
  raise SigningError, "Source file not found: '#{file}'" unless File.exist?(file)
@@ -35,9 +80,21 @@ module C2PA
35
80
  raise SigningError, "Key file not found: '#{key}'" unless File.exist?(key)
36
81
  raise SigningError, "Output file already exists: '#{output}'" if File.exist?(output)
37
82
 
38
- Native.sign_file(file, output, certificate, key, algorithm, manifest_json)
39
- rescue RuntimeError => e
40
- raise SigningError, e.message
83
+ begin
84
+ # to_json is the only thing genuinely required of a manifest, so an
85
+ # object that provides just that still signs — as a creation.
86
+ intent = manifest.respond_to?(:intent) ? manifest.intent&.to_s : nil
87
+ files = manifest.respond_to?(:ingredient_files) ? manifest.ingredient_files : []
88
+ ingredient_files = files.empty? ? nil : JSON.generate(files)
89
+ Native.sign_file(file, output, certificate, key, algorithm, manifest_json,
90
+ intent, ingredient_files)
91
+ rescue RuntimeError => e
92
+ raise SigningError, e.message
93
+ end
94
+
95
+ verify_signed_output!(output) if verify
96
+
97
+ output
41
98
  end
42
99
 
43
100
  # Read the C2PA manifest embedded in a signed file.
@@ -62,4 +119,48 @@ module C2PA
62
119
  def self.sdk_version
63
120
  Native.sdk_version
64
121
  end
122
+
123
+ # Read a freshly signed file back and confirm it actually validates.
124
+ #
125
+ # Signing reports success for manifests that verifiers reject. c2pa-rs
126
+ # applies its rules when reading, not when writing, so without this the gem
127
+ # hands back a path to a file that fails everywhere else — which is what
128
+ # released versions did, for years, silently.
129
+ #
130
+ # The rejected file is deleted rather than left behind, so a failed sign
131
+ # cannot leave something shippable on disk.
132
+ #
133
+ # @param output [String] path to the signed file
134
+ # @raise [C2PA::SigningError] if the file does not validate
135
+ def self.verify_signed_output!(output)
136
+ result =
137
+ begin
138
+ read(file: output)
139
+ rescue ReadError => e
140
+ discard(output)
141
+ raise SigningError, "signed file failed verification: could not read it back: #{e.message}"
142
+ end
143
+
144
+ state = result["validation_state"]
145
+ return if VALID_STATES.include?(state)
146
+
147
+ failures = Array(result.dig("validation_results", "activeManifest", "failure"))
148
+ .map { |failure| "#{failure["code"]} (#{failure["explanation"]})" }
149
+ detail = failures.empty? ? "no failure detail reported" : failures.uniq.join(", ")
150
+
151
+ discard(output)
152
+ raise SigningError,
153
+ "signed file failed verification: validation_state=#{state.inspect}, #{detail}. " \
154
+ "The output file has been removed. Pass verify: false to keep it for inspection."
155
+ end
156
+ private_class_method :verify_signed_output!
157
+
158
+ # Remove a file this library created and is about to reject.
159
+ def self.discard(path)
160
+ File.delete(path) if File.exist?(path)
161
+ rescue SystemCallError
162
+ # Leaving the file behind is better than masking the original failure.
163
+ nil
164
+ end
165
+ private_class_method :discard
65
166
  end
data/ruby-c2pa.gemspec CHANGED
@@ -8,10 +8,35 @@ Gem::Specification.new do |spec|
8
8
  spec.summary = "Ruby bindings for the c2pa content authenticity library"
9
9
  spec.description = "Embed and verify C2PA content provenance and authenticity credentials in images, video, and audio files. Ruby bindings for the official Rust c2pa-rs library."
10
10
  spec.license = "MIT"
11
- spec.homepage = "https://github.com/carlosrodriguez/ruby-c2pa"
12
- spec.metadata["source_code_uri"] = spec.homepage
11
+ spec.homepage = "https://github.com/eddorre/ruby-c2pa"
13
12
 
14
- spec.files = Dir["lib/**/*.rb", "ext/**/*.{rs,toml,rb}", "Rakefile", "*.gemspec", "LICENSE", "README.md"]
13
+ spec.metadata["source_code_uri"] = spec.homepage
14
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
15
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
16
+ spec.metadata["documentation_uri"] = "#{spec.homepage}/blob/main/README.md"
17
+
18
+ # Listed explicitly rather than globbed. "ext/**/*.rs" swept in whatever
19
+ # happened to be under ext/c2pa_native/target, so the package contents
20
+ # depended on what had been compiled on the machine that ran `gem build` —
21
+ # 1.3 MB of dependency build-script output shipped in 0.2.1.
22
+ #
23
+ # Cargo.lock is included deliberately. The extension is compiled at install
24
+ # time, so without it every installer resolves the dependency tree afresh.
25
+ # That is how 0.2.1 shipped against a broken atree and aborted the Ruby
26
+ # process on TIFF input.
27
+ spec.files = [
28
+ "LICENSE",
29
+ "README.md",
30
+ "CHANGELOG.md",
31
+ "CONTRIBUTING.md",
32
+ "Rakefile",
33
+ "ruby-c2pa.gemspec",
34
+ "ext/c2pa_native/Cargo.toml",
35
+ "ext/c2pa_native/Cargo.lock",
36
+ "ext/c2pa_native/extconf.rb",
37
+ *Dir["ext/c2pa_native/src/**/*.rs"],
38
+ *Dir["lib/**/*.rb"]
39
+ ].sort
15
40
  spec.require_paths = ["lib"]
16
41
  spec.extensions = ["ext/c2pa_native/extconf.rb"]
17
42
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-c2pa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Rodriguez
@@ -74,32 +74,31 @@ extensions:
74
74
  - ext/c2pa_native/extconf.rb
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - CHANGELOG.md
78
+ - CONTRIBUTING.md
77
79
  - LICENSE
78
80
  - README.md
79
81
  - Rakefile
82
+ - ext/c2pa_native/Cargo.lock
80
83
  - ext/c2pa_native/Cargo.toml
81
84
  - ext/c2pa_native/extconf.rb
82
85
  - ext/c2pa_native/src/lib.rs
83
- - ext/c2pa_native/target/release/build/oid-registry-c350c75504c969dc/out/oid_db.rs
84
- - ext/c2pa_native/target/release/build/pix-f303ab89d734032b/out/gamma_lut.rs
85
- - ext/c2pa_native/target/release/build/serde-2e9abb4d9d73cae4/out/private.rs
86
- - ext/c2pa_native/target/release/build/serde-f471616b462b0caf/out/private.rs
87
- - ext/c2pa_native/target/release/build/serde_core-2b94e9134dc44065/out/private.rs
88
- - ext/c2pa_native/target/release/build/serde_core-3dc945fa0ab21dd5/out/private.rs
89
- - ext/c2pa_native/target/release/build/thiserror-a1f4c63469c326b9/out/private.rs
90
- - ext/c2pa_native/target/release/build/typenum-4eb7d34b9fb695ef/out/tests.rs
91
- - ext/c2pa_native/target/release/build/typenum-b8de96984639a942/out/tests.rs
92
86
  - lib/c2pa.rb
93
87
  - lib/c2pa/actions.rb
88
+ - lib/c2pa/config.rb
89
+ - lib/c2pa/digital_source_types.rb
94
90
  - lib/c2pa/error.rb
95
91
  - lib/c2pa/manifest.rb
96
92
  - lib/c2pa/version.rb
97
93
  - ruby-c2pa.gemspec
98
- homepage: https://github.com/carlosrodriguez/ruby-c2pa
94
+ homepage: https://github.com/eddorre/ruby-c2pa
99
95
  licenses:
100
96
  - MIT
101
97
  metadata:
102
- source_code_uri: https://github.com/carlosrodriguez/ruby-c2pa
98
+ source_code_uri: https://github.com/eddorre/ruby-c2pa
99
+ bug_tracker_uri: https://github.com/eddorre/ruby-c2pa/issues
100
+ changelog_uri: https://github.com/eddorre/ruby-c2pa/blob/main/CHANGELOG.md
101
+ documentation_uri: https://github.com/eddorre/ruby-c2pa/blob/main/README.md
103
102
  rdoc_options: []
104
103
  require_paths:
105
104
  - lib