Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,32 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **`C2paVerifierReader::readVerified()`** (SPEC-046) returns a `VerifiedRead`:
the `ManifestReport` and the verifier's own `VerificationReport` of one and
the same read, so the evidence a caller keeps
(`$read->verification->evidenceToJson()`) is the evidence of the verdict it
shows. `read()` is unchanged.
- **The reader takes an optional `Verifier`**, configured as the caller
chooses, such as one with a revocation source; without one it builds
`new Verifier` as before.
- **Trust settings may also be the verifier's `TrustSettings` object**, such as
`TrustSettings::fromTrustLists()`, so the evidence names the lists a verdict
was judged against. A settings document as a string still works.

### Changed

- **The verifier range moves to 0.10** (`^0.10`, `conflict` `<0.10 || >=0.11`,
SPEC-042 Amendment 11): the evidence document exists from verifier 0.10 on.
0.10.0 also carries a security review's fixes, six of them for files it
wrongly called `Trusted`.

### Documentation

- **What the verifier route does not pass on** (`docs/readers.md`). Verifier
0.9's evidence behind a verdict and its revocation source are reachable
only by calling the verifier directly; this package's reader passes no
source and reads no evidence.
- **The verifier route and its evidence** (`docs/readers.md`): what
`readVerified()` gives, and why calling the verifier a second time for the
evidence is not the same read.
- **0.19.6 said no verdict of this package's reader changes with verifier
0.9. One can:** a signer issued directly by a trust anchor, whose file
carries only its own certificate and a stapled `revoked`, is now `Invalid`
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@
"pestphp/pest": "^4.0",
"php-http/mock-client": "^1.6",
"phpstan/phpstan": "^2.0",
"provemark/c2pa-verifier": "^0.9",
"provemark/c2pa-verifier": "^0.10",
"vlucas/phpdotenv": "^5.6"
},
"conflict": {
"provemark/c2pa-verifier": "<0.9 || >=0.10"
"provemark/c2pa-verifier": "<0.10 || >=0.11"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 3 additions & 1 deletion docs/adr/ADR-0007-the-verifier-as-an-optional-reader.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ on purpose. On 2026-10-25 the floor moved to 0.8.2 (`<0.8.2 || >=0.9`,
SPEC-042 Amendment 9): 0.8.2 refuses two signatures that `c2patool` refuses
and 0.8.1 called `Valid` or `Trusted`, so 0.8.1 is left out on purpose.
On 2026-10-28 the range moved to `^0.9` (`<0.9 || >=0.10`, SPEC-042
Amendment 10): one minor, as measured.
Amendment 10): one minor, as measured. On 2026-10-29 it moved to `^0.10`
(`<0.10 || >=0.11`, SPEC-042 Amendment 11): `readVerified()` (SPEC-046)
hands on the evidence document verifier 0.10 introduced.
37 changes: 28 additions & 9 deletions docs/readers.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,15 +289,34 @@ else differs:
A temp directory that cannot be written is a `ReadFailedException`. What the
verifier itself allocates while parsing is not bounded here, and the service
path's 20 MB body cap does not apply, so bound uploads before they reach it.
- **What the verifier offers beyond its JSON report is not passed on.** This
reader takes the verifier's `toJson()` and builds the same `ManifestReport`
the other two readers give. Since verifier 0.9 its own report can also show
the evidence behind a verdict (the anchor and time each manifest was judged
at, the certificates, the time-stamps and OCSP responses found, which trust
lists were used), and a caller may hand it a revocation source so that a
signer is checked online. None of that reaches you through this package: it
passes no revocation source and reads no evidence. To use them, call the
verifier directly; its README lists both in its public contract.
- **The verifier's own report of the same read: `readVerified()`.** `read()`
gives the same `ManifestReport` the other two readers give. The verifier's
own report also holds the evidence behind a verdict (the anchor and time
each manifest was judged at, the certificates, the time-stamps and OCSP
responses found, which trust lists were used), and writes it down as a
document to keep. `readVerified($asset)` returns both, from one and the
same read (SPEC-046):

```php
use Provemark\C2paVerifier\Trust\TrustSettings;
use Provemark\C2paVerifier\Verifier\Verifier;

$reader = new C2paVerifierReader(
TrustSettings::fromTrustLists($signerPem, $tsaPem), // or a settings document as a string
new Verifier(revocationSource: $source), // optional: a verifier configured your way
);
$read = $reader->readVerified($asset);
$read->report; // the ManifestReport, as read() gives it
$read->verification->evidenceToJson(); // what that verdict rested on, to keep
```

Calling the verifier a second time for the evidence would be a second read,
at another moment: a revocation answer or the clock may differ, and the
evidence would not be that of the verdict shown. The evidence names the
trust lists only when they come as `TrustSettings::fromTrustLists()`; a
settings document gives none. Keeping the document, and recording which
file it belongs to, is yours to do. A refused or unreadable asset throws the
same `ReadFailedException` as `read()`.
- **A stapled `revoked` is believed in one case the service does not.** A
signer issued directly by a trust anchor, whose file carries only its own
certificate and a stapled OCSP response saying `revoked`, is `Invalid` here
Expand Down
24 changes: 23 additions & 1 deletion specs/SPEC-042-pure-php-reader-via-c2pa-verifier.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,28 @@ with 0.8.2. The new pin was seen red on the old range first. The release
that carries this is a patch. No criterion changes. AC9's test takes the
new pin.

## Amendment 11 (2026-10-29, approved by Maurice van Loon the same day)

The verifier released 0.10.0 on 2026-10-29, a minor: it adds
`VerificationReport::evidenceToArray()` and `evidenceToJson()` (its
SPEC-089) and carries the fixes of a security review of its whole code, six
of them for files it wrongly called `Trusted`. SPEC-046's `readVerified()`
hands that report on, so it needs 0.10.

**The range moves to 0.10**: `require-dev` is `"provemark/c2pa-verifier":
"^0.10"` and `conflict` is `"<0.10 || >=0.11"`, one minor as before.
`VerifierVersionRangeTest` pins it ("tests against the verifier that writes
the evidence down as a document"), in place of Amendment 10's pin.

Measured on 2026-10-29 with 0.10.0 installed, before any other change:
`composer check` (Pint, PHPStan, Deptrac, and 518 tests passed, 7 skipped)
with one failure, Amendment 10's pin, as it should; and the integration
tests that use the verifier (`VerifierReaderEquivalenceTest`,
`IngredientEquivalenceTest`, `RemoteManifestFetchTest` against the running
service: 52 passed, 4 skipped, as with 0.9.0). The new pin was seen red
on the old `composer.json` first. No criterion changes. AC9's test takes the
new pin.

## API sketch

```php
Expand Down Expand Up @@ -561,5 +583,5 @@ required); 11 verifier comparisons ran in each.
| AC6 | `tests/Unit/Reading/C2paVerifierReaderTest.php` :: "refuses trust settings the verifier refuses, at construction", "verifies without trust when no settings are given", "treats empty settings as no settings" | `src/Core/Reading/C2paVerifierReader.php` `buildSettings()`; `src/Core/Reading/Exception/TrustSettingsRejectedException.php` |
| AC7 | `tests/Unit/Reading/C2paVerifierReaderTest.php` :: "reports itself available when the verifier is installed", "names the package to install when the verifier is absent" | `src/Core/Reading/C2paVerifierReader.php` `isAvailable()`, `__construct()`; `src/Core/Reading/Exception/VerifierMissingException.php` |
| AC8 | `tests/Unit/Laravel/ReaderSelectionTest.php` :: "binds the pure-PHP reader when the mode is verifier", "reports verifier as the mode, configured and resolved", "passes the configured settings file to the pure-PHP reader", "treats an empty settings value as no trust, not as an error", "refuses a settings path that does not exist, when the reader is resolved", "refuses a settings file the verifier refuses, when the reader is resolved", "names verifier among the accepted modes when refusing", "does not resolve auto to the verifier, though it is installed" | `src/Laravel/ReaderFactory.php` `MODES`, `make()`, `verifierSettings()`; `config/content-credentials.php` `verifier_settings` |
| AC9 | `tests/Unit/VerifierVersionRangeTest.php` :: "refuses every verifier version outside the tested range", "tests against the verifier that reads revocation against the issuer its walk verified" (Amendment 10; Amendments 7 to 9's pins before it), "keeps the verifier out of require"; `tests/Integration/VerifierReaderEquivalenceTest.php` :: "agrees with the service reader through the verifier", "agrees with the in-process reader through the verifier" | `.github/workflows/ci.yml` steps "Assert the verifier comparison ran (SPEC-042)" and "Assert the verifier-extension comparison ran (SPEC-042)" (counted 0 on a skipped run, 11 on a real one); `composer.json` `require-dev`, `conflict` (ADR-0007 addendum); `docs/adr/ADR-0007-the-verifier-as-an-optional-reader.md` |
| AC9 | `tests/Unit/VerifierVersionRangeTest.php` :: "refuses every verifier version outside the tested range", "tests against the verifier that writes the evidence down as a document" (Amendment 11; Amendments 7 to 10's pins before it), "keeps the verifier out of require"; `tests/Integration/VerifierReaderEquivalenceTest.php` :: "agrees with the service reader through the verifier", "agrees with the in-process reader through the verifier" | `.github/workflows/ci.yml` steps "Assert the verifier comparison ran (SPEC-042)" and "Assert the verifier-extension comparison ran (SPEC-042)" (counted 0 on a skipped run, 11 on a real one); `composer.json` `require-dev`, `conflict` (ADR-0007 addendum); `docs/adr/ADR-0007-the-verifier-as-an-optional-reader.md` |
| AC10 | `tests/Unit/Reading/C2paVerifierReaderTest.php` :: "adds little to peak memory when reading a large asset", "fails with ReadFailedException when the copy cannot spill to disk" | `src/Core/Reading/C2paVerifierReader.php` `IN_MEMORY_BYTES`, `read()` (Amendment 3) |
Loading
Loading