Skip to content

Latest commit

 

History

History
205 lines (164 loc) · 8.82 KB

usage.md

File metadata and controls

205 lines (164 loc) · 8.82 KB

Usage

How to build a manifest, sign an asset and read one back — from Laravel, or from plain PHP with any PSR-18 client. Start with the quickstart in the README if you have not signed anything yet.

Installation and configuration

The Quickstart covers the short version. This section is the full set of knobs.

composer require provemark/content-credentials

For the latest unreleased changes, require the main branch:

composer require provemark/content-credentials:dev-main

In Laravel the service provider and ContentCredentials facade are registered automatically (package auto-discovery). Publish the config if you want to tweak it:

php artisan vendor:publish --tag=content-credentials-config

Set the service location and shared secret in your .env:

CONTENTAUTH_SERVICE_URL=http://localhost:3000
CONTENTAUTH_API_KEY=your-shared-secret
# Optional HTTP timeouts (seconds) for the signing-service calls:
CONTENTAUTH_TIMEOUT=10
CONTENTAUTH_CONNECT_TIMEOUT=5
# Optional cap (bytes) on a service response the client will buffer (default 96 MiB):
CONTENTAUTH_MAX_RESPONSE_BYTES=100663296

These timeouts apply to the HTTP client this package builds for you. If you bind your own PSR-18 client into the container, that client owns its timeouts — PSR-18 has no timeout API, so the package cannot set one on a client it did not construct.

Usage (Laravel)

use Provemark\ContentCredentials\Core\Manifest\ManifestBuilder;
use Provemark\ContentCredentials\Core\Manifest\MediaType;
use Provemark\ContentCredentials\Core\Signing\Asset;
use Provemark\ContentCredentials\Laravel\ContentCredentials;

$bytes = file_get_contents('image.png');

// 1. Describe the asset as AI-generated (EU AI Act Art. 50 marking).
$manifest = ManifestBuilder::forAiGenerated(MediaType::Png)
    ->withSoftwareAgent('ACME GenAI Image Model', '3.1.0')
    ->withClaimGenerator(config('app.name'), '1.0.0')
    ->build();

// 2. Sign it via the service.
$signed = ContentCredentials::sign(new Asset($bytes, MediaType::Png), $manifest);
file_put_contents('signed.png', $signed->bytes); // never re-encode these bytes

// 3. Read the credential back.
$report = ContentCredentials::read(new Asset($signed->bytes, MediaType::Png));

// The marking, verified: true only if the signature also checked out.
$report->isVerifiedAiGenerated(); // true

// The first question, and the one that decides whether the rest mean anything:
// an asset with no Content Credentials reads as an empty report rather than an
// error, and every accessor below then answers false or empty.
$report->hasManifest();          // false for an unsigned or stripped asset

// The individual pieces. Note isAiGenerated() reports what the manifest
// CLAIMS — it does not imply the signature verified, so gate on
// isSignatureValid() before acting on it.
$report->isSignatureValid();     // true — integrity verdict
$report->isAiGenerated();        // true — the claim
$report->digitalSourceTypes();   // ['http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia']
$report->softwareAgents();       // [SoftwareAgent('ACME GenAI Image Model', '3.1.0')] — generated by what
$report->involvesGenerativeAi(); // true — wider than isAiGenerated(); see docs/marking.md
$report->signer()?->issuer;      // e.g. "C2PA Test Signing Cert"
$report->hasTimestamp();         // true when signed with a trusted timestamp (see "Going to production")
$report->isTrusted();            // true only when the service verified against a trust list
$report->declaredSpecVersion();  // e.g. '2.4.0' — which C2PA rules the generator says it followed, or null

Claims versus verdicts. isAiGenerated(), signer(), digitalSourceTypes(), softwareAgents() and declaredSpecVersion() describe what a manifest asserts; they answer for a tampered or unverifiable manifest too. isSignatureValid() and isTrusted() are the verdicts. isVerifiedAiGenerated() combines the marking with the signature verdict, so the safe check is also the short one to write.

Which system generated it

softwareAgents() answers the question that follows "is this AI-generated": generated by what. It returns Core\Manifest\SoftwareAgent objects — the same type the builder takes — with a name and an optional version.

foreach ($report->softwareAgents() as $agent) {
    echo $agent->name;      // 'ACME GenAI Image Model'
    echo $agent->version;   // '3.1.0', or null when the manifest names none
}

Both halves of the Article 50 marking are readable this way: the digitalSourceType says what kind of source, the software agent says what produced it, and a compliant marking carries both.

Three details worth knowing before you build on it. The list is distinct and in first-appearance order, and the same name at a different version counts as a different agent — a version change is usually the thing worth noticing. Manifests are untrusted input, so nothing throws: an entry whose name is not a string is skipped entirely, while a non-string version drops only the version, because a name read correctly is worth keeping. And an asset with no manifest, or none naming an agent, gives an empty list rather than an error.

What the file was made from

Everything above describes the active manifest. When another tool re-signs a file, for example a CMS module that signs every upload, it puts its own manifest on top and the original becomes an ingredient. The AI marking is then in the ingredient, not in the active manifest, and digitalSourceTypes() is empty. ingredients() reads them (SPEC-045):

foreach ($report->ingredients() as $ingredient) {
    $ingredient->relationship();        // 'parentOf', 'componentOf', … verbatim
    $ingredient->hasManifest();         // false when the ingredient carries none
    $ingredient->digitalSourceTypes();  // that ingredient's own claim
    $ingredient->isTrusted();           // its own verdict, see below
    $ingredient->ingredients();         // and what *it* was made from
}

Check each ingredient's own isTrusted(); the report's does not cover it. Only the active manifest's signer can make a report trusted, and what the re-signing tool recorded about an ingredient is left out of that verdict. A file whose AI claim was signed by an unknown certificate can read as trusted once a trusted tool re-signs it.

An ingredient is trusted only when all of these hold:

  • everything above it is trusted: the report itself for the active manifest's ingredients, the parent ingredient for theirs. Without this, an image signed with a homemade certificate could carry a genuine, trusted AI manifest grafted underneath as its "parent", and only the untrusted signer would vouch for the link;
  • the reader's own validation of it found a trusted signer and no fault, and its claim signature was validated, by the reader or by the tool that recorded it;
  • no tool that wrote the file recorded a fault, or a status code this library does not know, anywhere in it. A reader leaves recorded statuses out of its own validation, so such a record may have hidden a real fault from view. (A record entry without a readable code is ignored here: it cannot match a reader's status, so it cannot hide one.)

Without trust anchors, no ingredient is trusted.

What to do with a trusted ingredient's claim is still your decision: whether a parentOf ingredient's type describes the file, and how far down to look. The walk is bounded against hostile files: a cycle stops it, and so do depth 8 and 64 ingredients in total. A cut-off is silent — the ingredients beyond it are simply absent, which reads the same as having none — so treat an empty list as "none reported", not as proof that there are none.

Usage (plain PHP / any framework)

Core depends only on PSR interfaces — inject any PSR-18 client and PSR-17 factories:

use Provemark\ContentCredentials\Core\Manifest\ManifestBuilder;
use Provemark\ContentCredentials\Core\Manifest\MediaType;
use Provemark\ContentCredentials\Core\Signing\Asset;
use Provemark\ContentCredentials\Core\Signing\SigningServiceConfig;
use Provemark\ContentCredentials\Core\Signing\SigningServiceSigner;
use GuzzleHttp\Client;                       // any PSR-18 client
use Nyholm\Psr7\Factory\Psr17Factory;        // any PSR-17 factory

$factory = new Psr17Factory();
$signer = new SigningServiceSigner(
    new Client(),
    $factory,
    $factory,
    new SigningServiceConfig('http://localhost:3000', getenv('CONTENTAUTH_API_KEY')),
);

$manifest = ManifestBuilder::forAiGenerated(MediaType::Jpeg)
    ->withSoftwareAgent('ACME GenAI Image Model', '3.1.0')
    ->build();

$signed = $signer->sign(new Asset(file_get_contents('in.jpg'), MediaType::Jpeg), $manifest);
file_put_contents('out.jpg', $signed->bytes);

Reading works the same way with SigningServiceReader → ManifestReport.