Documentation
¶
Overview ¶
Package c2pa is a pure-Go, read-only reader for C2PA / Content Credentials (https://c2pa.org) provenance manifests embedded in media files.
It surfaces what a file CLAIMS about its provenance — the creating tool, title, declared media type, whether it declares AI-generated content, and the signer identity + signing time — by parsing the embedded JUMBF manifest (ISO 19566-5), CBOR-decoding the active manifest's claim and c2pa.actions assertion, and decoding the COSE_Sign1 signature envelope.
This is UNVERIFIED ¶
The reader is deliberately read-only: it does NOT validate the COSE cryptographic signature, and it does NOT check the signer's certificate chain against the C2PA trust list. Full validation requires the Rust c2pa-rs library via CGO, which this pure-Go package intentionally avoids.
Treat every field like EXIF or an email From header: accurate-as-recorded, not authenticated. SignedBy is who the file CLAIMS signed it, not a verified identity. A file with no manifest yields Info{Present:false}; absence of a signal (e.g. AIGenerated) does not prove its negation.
All parsing is best-effort and never panics: malformed or truncated input yields zero values rather than an error. Every input-scaled loop honours the supplied context.Context, so a cancelled call surrenders promptly.
Example ¶
Example reads the Content Credentials a JPEG claims, and surfaces the (unverified) creating tool, AI-generated flag, and signer identity.
package main
import (
"context"
"fmt"
"os"
"github.com/richardwooding/c2pa"
)
func main() {
f, err := os.Open("testdata/c2pa_signed.jpg")
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
info := c2pa.Read(context.Background(), c2pa.JPEG, f)
if !info.Present {
fmt.Println("no Content Credentials")
return
}
fmt.Println("title:", info.Title)
fmt.Println("ai-generated:", info.AIGenerated)
// SignedBy is the CLAIMED signer — not cryptographically verified.
fmt.Println("signed by:", info.SignedBy)
}
Output: title: CA.jpg ai-generated: false signed by: C2PA Signer
Index ¶
- Constants
- func WalkBoxes(ctx context.Context, jumbf []byte, fn func(label, tbox string, content []byte))
- type Container
- type Info
- type Severity
- type StatusCode
- type StatusEntry
- type ValidateOption
- func WithClock(now func() time.Time) ValidateOption
- func WithHTTPClient(client *http.Client) ValidateOption
- func WithMaxIngredientDepth(n int) ValidateOption
- func WithMaxScan(n int) ValidateOption
- func WithOnlineRevocation(enabled bool) ValidateOption
- func WithSigningTrust(pool *x509.CertPool) ValidateOption
- func WithTimestampTrust(pool *x509.CertPool) ValidateOption
- type ValidationResult
Examples ¶
Constants ¶
const MaxScan = 16 << 20
MaxScan caps how many leading bytes Read consumes looking for a manifest. C2PA manifests sit in the file header (before image data) and rarely exceed a few MB even with embedded thumbnails; past the cap Read gives up.
const ValidateMaxScan = 256 << 20
ValidateMaxScan caps how many leading bytes Validate consumes. Unlike Read's MaxScan (tuned for fast manifest discovery), validation must hash the whole asset for hard-binding checks, so the cap is larger. Assets beyond it cannot have their data hash verified — that is reported as an informational status, never a false mismatch.
Variables ¶
This section is empty.
Functions ¶
func WalkBoxes ¶
WalkBoxes recursively walks a JUMBF box tree, invoking fn(label, tbox, content) for every leaf box. label is the nearest enclosing superbox's jumd label, tbox is the 4-character box type, and content is the box payload. Nesting is capped at an internal depth limit so adversarial input cannot exhaust the stack; ctx is honoured at the top of every iteration.
This is a lower-level primitive — most callers want Read. It is exported for advanced use (e.g. surfacing assertions Read does not model).
Types ¶
type Container ¶
type Container string
Container identifies the carrier file format whose C2PA manifest to read.
type Info ¶
type Info struct {
// Present is true when a C2PA manifest was found and parsed.
Present bool
// ClaimGenerator is the tool that created/edited the asset (e.g.
// "Adobe Firefly", "make_test_images/0.33.1 c2pa-rs/0.33.1").
ClaimGenerator string
// Title is the claim's dc:title.
Title string
// Format is the claim's dc:format (declared media type).
Format string
// AIGenerated is true when a c2pa.actions assertion declares a
// digitalSourceType of trainedAlgorithmicMedia or
// compositeWithTrainedAlgorithmicMedia.
AIGenerated bool
// SignedBy is the COSE_Sign1 signer's leaf x509 certificate common name
// (Subject CN, falling back to the first Organization). UNVERIFIED — the
// certificate chain is not validated against the C2PA trust list.
SignedBy string
// SignedAt is the signing time from the RFC 3161 timestamp embedded in the
// signature (sigTst). Zero when absent. UNVERIFIED.
SignedAt time.Time
}
Info is the surfaced, CLAIMED, UNVERIFIED subset of a C2PA manifest. See the package doc: these are the file's assertions, not authenticated facts.
func Read ¶
Read reads up to MaxScan bytes from r and, for the given container, locates and parses the embedded JUMBF manifest. It returns a zero Info (Present=false) when there's no manifest. It never returns an error — provenance is best-effort metadata, surfaced like EXIF.
ctx is honoured at entry and inside the input-scaled scan loops, so a cancelled call surrenders promptly mid-scan rather than parsing a full adversarial header.
type Severity ¶ added in v0.2.0
type Severity int
Severity classifies a StatusCode as success, informational, or failure. Only failures flip ValidationResult.Valid to false.
const ( // SeverityInformational is advisory: the step ran but its outcome neither // proves nor disproves validity (e.g. revocation status unknown, an // unsupported-but-not-fatal feature, an absent optional timestamp). SeverityInformational Severity = iota // SeveritySuccess records a validation step that passed. SeveritySuccess // SeverityFailure records a validation step that failed. Any failure makes // the manifest invalid. SeverityFailure )
type StatusCode ¶ added in v0.2.0
type StatusCode string
StatusCode is a C2PA validation status code. The string values mirror the codes defined in the C2PA Technical Specification §15 (e.g. "claimSignature.validated", "signingCredential.untrusted") so a caller can match against them directly.
const ( StatusClaimSignatureValidated StatusCode = "claimSignature.validated" StatusSigningCredentialTrusted StatusCode = "signingCredential.trusted" StatusTimeStampValidated StatusCode = "timeStamp.validated" StatusAssertionHashedURIMatch StatusCode = "assertion.hashedURI.match" StatusAssertionDataHashMatch StatusCode = "assertion.dataHash.match" StatusAssertionBoxesHashMatch StatusCode = "assertion.boxesHash.match" StatusIngredientManifestValidated StatusCode = "ingredient.manifest.validated" )
Success status codes.
const ( StatusClaimMissing StatusCode = "claim.missing" StatusClaimRequiredMissing StatusCode = "claim.required.missing" StatusClaimMultiple StatusCode = "claim.multiple" StatusClaimSignatureMissing StatusCode = "claimSignature.missing" StatusClaimSignatureMismatch StatusCode = "claimSignature.mismatch" StatusSigningCredentialUntrusted StatusCode = "signingCredential.untrusted" StatusSigningCredentialInvalid StatusCode = "signingCredential.invalid" StatusSigningCredentialRevoked StatusCode = "signingCredential.revoked" StatusSigningCredentialExpired StatusCode = "signingCredential.expired" StatusTimeStampMismatch StatusCode = "timeStamp.mismatch" StatusTimeStampUntrusted StatusCode = "timeStamp.untrusted" StatusTimeStampOutsideValidity StatusCode = "timeStamp.outsideValidity" StatusAssertionHashedURIMismatch StatusCode = "assertion.hashedURI.mismatch" StatusAssertionDataHashMismatch StatusCode = "assertion.dataHash.mismatch" StatusAssertionBoxesHashMismatch StatusCode = "assertion.boxesHash.mismatch" StatusAssertionMissing StatusCode = "assertion.missing" StatusHardBindingMissing StatusCode = "hardBinding.missing" StatusAlgorithmUnsupported StatusCode = "algorithm.unsupported" StatusIngredientManifestMismatch StatusCode = "ingredient.manifest.mismatch" StatusGeneralError StatusCode = "general.error" )
Failure status codes.
const ( StatusRevocationUnknown StatusCode = "signingCredential.revocation.unknown" StatusTimeStampMissing StatusCode = "timeStamp.missing" StatusUnsupported StatusCode = "general.unsupported" )
Informational status codes.
func (StatusCode) Severity ¶ added in v0.2.0
func (c StatusCode) Severity() Severity
Severity returns the StatusCode's severity. Unknown codes are informational.
type StatusEntry ¶ added in v0.2.0
type StatusEntry struct {
Code StatusCode
Severity Severity
URI string
Explanation string
Err error
}
StatusEntry is one outcome from the validation pipeline: a C2PA status code, its severity, the JUMBF URI of the subject it concerns (best-effort), a human-readable explanation, and the underlying error for failures.
type ValidateOption ¶ added in v0.2.0
type ValidateOption func(*validateConfig)
ValidateOption configures a Validate call. See the With* constructors.
func WithClock ¶ added in v0.2.0
func WithClock(now func() time.Time) ValidateOption
WithClock overrides the time source used when no trusted timestamp pins the signing time (defaults to time.Now). Useful for deterministic tests.
func WithHTTPClient ¶ added in v0.2.0
func WithHTTPClient(client *http.Client) ValidateOption
WithHTTPClient sets the HTTP client used for OCSP/CRL fetches when online revocation is enabled (defaults to a client with a short timeout).
func WithMaxIngredientDepth ¶ added in v0.2.0
func WithMaxIngredientDepth(n int) ValidateOption
WithMaxIngredientDepth caps recursive ingredient/nested-manifest validation depth (default 16).
func WithMaxScan ¶ added in v0.2.0
func WithMaxScan(n int) ValidateOption
WithMaxScan overrides how many leading bytes Validate reads (default ValidateMaxScan).
func WithOnlineRevocation ¶ added in v0.2.0
func WithOnlineRevocation(enabled bool) ValidateOption
WithOnlineRevocation enables OCSP/CRL revocation checking, which makes network calls. It is off by default; when off, revocation is reported as an informational "unknown" status. Revocation is always soft-fail: a network or parse error is informational, never a validation failure.
func WithSigningTrust ¶ added in v0.2.0
func WithSigningTrust(pool *x509.CertPool) ValidateOption
WithSigningTrust overrides the embedded C2PA signing-anchor trust pool used to validate the claim signer's certificate chain.
func WithTimestampTrust ¶ added in v0.2.0
func WithTimestampTrust(pool *x509.CertPool) ValidateOption
WithTimestampTrust overrides the embedded C2PA timestamp-authority trust pool used to validate RFC 3161 timestamp tokens.
type ValidationResult ¶ added in v0.2.0
type ValidationResult struct {
// Valid is true iff Statuses contains no SeverityFailure entry.
Valid bool
// Info mirrors the surfaced fields Read would return for the same input.
Info Info
// Statuses is the ordered list of every validation outcome.
Statuses []StatusEntry
// ActiveManifestLabel is the active (last) manifest's JUMBF label.
ActiveManifestLabel string
// SignerChain is the COSE signer's certificate chain (leaf first) as
// presented in the manifest, populated once the chain is parsed.
SignerChain []*x509.Certificate
// SignedAt is the signing time from a verified RFC 3161 timestamp, or zero
// when no trusted timestamp was found.
SignedAt time.Time
}
ValidationResult is the outcome of Validate. Valid is true exactly when no SeverityFailure status was recorded (see the package docs on the roll-up rule). Statuses is the ordered accumulation of every success, informational, and failure status produced along the way.
func Validate ¶ added in v0.2.0
func Validate(ctx context.Context, container Container, r io.Reader, opts ...ValidateOption) ValidationResult
Validate reads up to ValidateMaxScan bytes from r and performs full C2PA validation of the embedded manifest: COSE signature verification, certificate chain + C2PA cert-profile validation against the (embedded or overridden) trust list, assertion and hard-binding hash verification, RFC 3161 timestamp verification, optional revocation checking, and recursive ingredient validation. It reports every outcome as a StatusEntry; ValidationResult.Valid is true exactly when no failure status was recorded.
Like Read, Validate never returns an error and never panics: malformed, truncated, or cancelled input is reported via failure statuses with Valid=false. It is the verified counterpart to Read's fast, unverified scan.
Example ¶
ExampleValidate verifies a JPEG's Content Credentials against the embedded C2PA trust list. The test fixture is signed by the c2pa-rs *test* PKI, so its signature verifies cryptographically but its signer does not chain to a production trust anchor — an honest "valid signature, untrusted signer" verdict. Pass WithSigningTrust / WithTimestampTrust to supply your own anchors.
package main
import (
"context"
"fmt"
"os"
"github.com/richardwooding/c2pa"
)
func main() {
f, err := os.Open("testdata/c2pa_signed.jpg")
if err != nil {
panic(err)
}
defer func() { _ = f.Close() }()
r := c2pa.Validate(context.Background(), c2pa.JPEG, f)
fmt.Println("valid:", r.Valid)
fmt.Println("signature verified:", r.Has(c2pa.StatusClaimSignatureValidated))
fmt.Println("signer trusted:", r.Has(c2pa.StatusSigningCredentialTrusted))
}
Output: valid: false signature verified: true signer trusted: false
func (ValidationResult) FirstFailure ¶ added in v0.2.0
func (r ValidationResult) FirstFailure() *StatusEntry
FirstFailure returns the first SeverityFailure status, or nil if none.
func (ValidationResult) Has ¶ added in v0.2.0
func (r ValidationResult) Has(code StatusCode) bool
Has reports whether any recorded status has the given code.