Module: C2PA

Defined in:
lib/c2pa.rb,
lib/c2pa/error.rb,
lib/c2pa/config.rb,
lib/c2pa/actions.rb,
lib/c2pa/version.rb,
lib/c2pa/manifest.rb,
lib/c2pa/digital_source_types.rb

Defined Under Namespace

Modules: Actions, DigitalSourceTypes Classes: Config, Error, InvalidManifestError, InvalidSettingsError, Manifest, ReadError, SigningError

Constant Summary collapse

VALID_STATES =

Validation states that mean a signed asset is good.

"Trusted" is "Valid" plus a signing certificate that chains to a root in the active trust list. Accepting only "Valid" would reject exactly the production files that are most correct.

%w[Valid Trusted].freeze
VERSION =
"0.5.0"

Class Method Summary collapse

Class Method Details

.configure {|config| ... } ⇒ C2PA::Config

Configure how c2pa-rs validates.

Settings are global and take effect for subsequent calls. Signing already in flight on another thread continues against the settings it started with, since the underlying context is replaced rather than mutated.

Examples:

Trusting a private CA

C2PA.configure do |config|
  config.trust_anchors = "ca/root.pem"
end

An offline environment

C2PA.configure do |config|
  config.remote_manifest_fetch = false
  config.ocsp_fetch = false
end

Yield Parameters:

Returns:

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/c2pa.rb', line 38

def self.configure
  config = Config.new
  yield config if block_given?

  begin
    Native.configure(config.to_json)
  rescue RuntimeError => e
    raise InvalidSettingsError, e.message
  end

  config
end

.read(file:) ⇒ Hash

Read the C2PA manifest embedded in a signed file.

Examples:

manifest = C2PA.read(file: "photo_signed.jpg")
active = manifest["manifests"][manifest["active_manifest"]]
puts active["title"]

Parameters:

  • file (String)

    path to the signed file

Returns:

  • (Hash)

    parsed manifest JSON

Raises:



179
180
181
182
183
# File 'lib/c2pa.rb', line 179

def self.read(file:)
  JSON.parse(Native.read_file(file))
rescue RuntimeError => e
  raise ReadError, e.message
end

.read_buffer(data:, format: nil) ⇒ Hash

Read the C2PA manifest embedded in bytes held in memory.

c2pa-rs identifies most formats from the leading bytes and ignores the hint when the two disagree. The hint matters for formats with no signature to sniff, such as SVG, which cannot be read without it.

Parameters:

  • data (String)

    the asset, as a binary string

  • format (String, nil) (defaults to: nil)

    MIME type or extension, e.g. "image/svg+xml"

Returns:

  • (Hash)

    parsed manifest JSON

Raises:



162
163
164
165
166
167
# File 'lib/c2pa.rb', line 162

def self.read_buffer(data:, format: nil)
  data = binary!(data, "data")
  JSON.parse(Native.read_buffer(data, format))
rescue RuntimeError => e
  raise ReadError, e.message
end

.sdk_versionString

Return the version of the underlying c2pa-rs SDK.

Returns:

  • (String)


188
189
190
# File 'lib/c2pa.rb', line 188

def self.sdk_version
  Native.sdk_version
end

.sign(file:, output:, certificate:, key:, algorithm: "es256", manifest:, verify: true) ⇒ String

Sign a file with a C2PA manifest.

Examples:

manifest = C2PA::Manifest.new(title: "Sunset over the bay")
manifest.add_action(C2PA::Actions::CREATED)

C2PA.sign(
  file:        "photo.jpg",
  output:      "photo_signed.jpg",
  certificate: "cert.pem",
  key:         "key.pem",
  manifest:    manifest
)

Parameters:

  • file (String)

    path to the input file

  • output (String)

    path for the signed output file (must not already exist)

  • certificate (String)

    path to a PEM-encoded X.509 certificate (chain)

  • key (String)

    path to a PEM-encoded private key

  • algorithm (String) (defaults to: "es256")

    signing algorithm (default: "es256")

  • manifest (C2PA::Manifest)

    the manifest to embed

  • verify (Boolean) (defaults to: true)

    read the signed file back and confirm it validates (default: true)

Returns:

  • (String)

    the output path

Raises:



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/c2pa.rb', line 75

def self.sign(file:, output:, certificate:, key:, algorithm: "es256", manifest:, verify: true)
  manifest_json = manifest.to_json

  raise SigningError, "Source file not found: '#{file}'"             unless File.exist?(file)
  raise SigningError, "Certificate file not found: '#{certificate}'" unless File.exist?(certificate)
  raise SigningError, "Key file not found: '#{key}'"                 unless File.exist?(key)
  raise SigningError, "Output file already exists: '#{output}'"      if File.exist?(output)

  begin
    # to_json is the only thing genuinely required of a manifest, so an
    # object that provides just that still signs — as a creation.
    intent = manifest.respond_to?(:intent) ? manifest.intent&.to_s : nil
    files = manifest.respond_to?(:ingredient_files) ? manifest.ingredient_files : []
    ingredient_files = files.empty? ? nil : JSON.generate(files)
    Native.sign_file(file, output, certificate, key, algorithm, manifest_json,
                     intent, ingredient_files)
  rescue RuntimeError => e
    raise SigningError, e.message
  end

  verify_signed_output!(output) if verify

  output
end

.sign_buffer(data:, format:, certificate:, key:, algorithm: "es256", manifest:, verify: true) ⇒ String

Sign bytes held in memory, returning the signed bytes.

The counterpart to C2PA.sign for data you already have loaded, such as an upload. The format must be given, since there is no filename to infer it from. Input must be binary; a UTF-8-tagged string is rejected rather than transcoded, because that would corrupt the asset.

The input, the working copy and the result on both sides of the boundary are resident at once, so budget about four times the asset. Prefer C2PA.sign with paths for large video.

Examples:

signed = C2PA.sign_buffer(
  data:        File.binread("photo.jpg"),
  format:      "image/jpeg",
  certificate: "cert.pem",
  key:         "key.pem",
  manifest:    manifest
)

Parameters:

  • data (String)

    the asset, as a binary string

  • format (String)

    MIME type, e.g. "image/jpeg"

  • certificate (String)

    path to a PEM-encoded X.509 certificate chain

  • key (String)

    path to a PEM-encoded private key

  • algorithm (String) (defaults to: "es256")

    signing algorithm (default: "es256")

  • manifest (C2PA::Manifest)

    the manifest to embed

  • verify (Boolean) (defaults to: true)

    read the result back and confirm it validates

Returns:

  • (String)

    the signed asset, as a binary string

Raises:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/c2pa.rb', line 129

def self.sign_buffer(data:, format:, certificate:, key:, algorithm: "es256", manifest:, verify: true)
  manifest_json = manifest.to_json
  data = binary!(data, "data")

  raise SigningError, "Certificate file not found: '#{certificate}'" unless File.exist?(certificate)
  raise SigningError, "Key file not found: '#{key}'"                 unless File.exist?(key)

  signed =
    begin
      intent = manifest.respond_to?(:intent) ? manifest.intent&.to_s : nil
      files = manifest.respond_to?(:ingredient_files) ? manifest.ingredient_files : []
      ingredient_files = files.empty? ? nil : JSON.generate(files)
      Native.sign_buffer(data, format, certificate, key, algorithm, manifest_json,
                         intent, ingredient_files)
    rescue RuntimeError => e
      raise SigningError, e.message
    end

  verify_signed_buffer!(signed, format) if verify

  signed
end