Verifying Assets

Overview

Verification is the process of inspecting a digital asset to find, validate, and report on any embedded C2PA manifests. This allows you to confirm an asset's provenance, check for tampering, and make informed decisions about its authenticity. This guide is for developers, platforms, and consumers who need a reliable way to check the history and origin of digital content.

What, Why, and How

  • What is Verification? Verification analyzes an asset's C2PA data, validates its cryptographic signatures against a managed trust list, and returns a detailed report. It answers the question: "Is this asset authentic, and where did it come from?"

  • Why Verify Assets? In a world where content can be easily manipulated, verification provides a crucial layer of trust. It helps you programmatically identify authentic content, detect unauthorized modifications, and display provenance information to your users, empowering them to evaluate content critically.

  • How to Get Started The process involves providing a reference to an asset and calling a single verify function in the Que SDK. The quickstart example below shows the simplest way to get a trust summary for an asset.

Quickstart

This example demonstrates the minimum code required to verify an asset. The default verification mode (summary) provides a high-level trust status.

import { Que } from "que-sdk";

const que = new Que({
  apiKeyAuth: process.env["QUE_API_KEY_AUTH"] ?? "",
});

const asset = {
  bucket: 'que-signed-assets',
  key: 'a1b2c3d4/signed-photo.jpg',
};

async function verifyAsset() {
  try {
    const result = await que.verifyAsset({
      asset,
      includeCertificates: true,
    });
    const report = JSON.parse(result.report);

    console.log('Verification successful. Report:', report);
  } catch (error) {
    console.error('Verification failed:', error);
  }
}

verifyAsset();
import os
import json
from que_media import Que

with Que(
    api_key_auth=os.getenv("QUE_API_KEY_AUTH", ""),
) as que:

    try:
        result = que.verify_asset(asset={
            "bucket": "que-signed-assets",
            "key": "a1b2c3d4/signed-photo.jpg"
        }, mode="summary", include_certificates=True)

        report = json.loads(result.report)
        print(f"Verification successful. Report: {report}")
    except Exception as e:
        print(f"Verification failed: {e}")
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"

	"github.com/que-platform/que-sdk-go"
)

func main() {
	client := quesdk.NewClient(os.Getenv("QUE_API_KEY"))

	asset := quesdk.AssetReference{
		Bucket: "que-signed-assets",
		Key:    "a1b2c3d4/signed-photo.jpg",
	}

	result, err := client.Verify(context.Background(), &quesdk.VerifyRequest{
		Asset: asset,
	})
	if err != nil {
		fmt.Printf("Verification failed: %v\n", err)
		return
	}

	var report map[string]interface{}
	json.Unmarshal([]byte(result.Report), &report)
	fmt.Printf("Verification successful. Report: %+v\n", report)
}
// The Que SDK for Rust is not yet available.
// The following is a conceptual example.
use que_sdk::{QueClient, types::{AssetReference, VerifyRequest}};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = QueClient::new(std::env::var("QUE_API_KEY")?);

    let asset = AssetReference::S3 {
        bucket: "que-signed-assets".to_string(),
        key: "a1b2c3d4/signed-photo.jpg".to_string(),
    };

    let request = VerifyRequest { asset, ..Default::default() };
    let result = client.verify(request).await?;
    let report: serde_json::Value = serde_json::from_str(&result.report)?;

    println!("Verification successful. Report: {}", report);
    Ok(())
}
package hello.world;

import java.lang.Exception;
import org.openapis.openapi.Que;
import org.openapis.openapi.models.components.*;
import org.openapis.openapi.models.errors.ProblemResponseException;
import org.openapis.openapi.models.operations.VerifyAssetResponse;

public class VerifyAssetExample {

    public static void main(String[] args) throws ProblemResponseException, ProblemResponseException, ProblemResponseException, Exception {

        Que sdk = Que.builder()
                .apiKeyAuth(System.getenv().getOrDefault("API_KEY_AUTH", ""))
            .build();

        VerifyRequest req = VerifyRequest.builder()
                .asset(AssetRefDto.of(S3.builder()
                    .bucket("que-signed-assets")
                    .key("a1b2c3d4/signed-photo.jpg")
                    .build()))
                .includeCertificates(true)
                .build();

        VerifyAssetResponse res = sdk.verifyAsset()
                .request(req)
                .call();

        if (res.verifyResponse().isPresent()) {
            System.out.println("Verification successful. Report: " + res.verifyResponse().get().report());
        }
    }
}
<?php
declare(strict_types=1);

require 'vendor/autoload.php';

use Que;
use Que\Models\Components;

$sdk = Que\Que::builder()
    ->setSecurity(getenv('QUE_API_KEY_AUTH') ?: '')
    ->build();

$verifyRequest = new Components\VerifyRequest(
    asset: new Components\S3(
        bucket: 'que-signed-assets',
        key: 'a1b2c3d4/signed-photo.jpg'
    ),
    mode: Components\VerifyRequestMode::Detailed
);

try {
    $response = $sdk->verifyAsset(
        request: $verifyRequest
    );

    if ($response->verifyResponse !== null) {
        $report = json_decode($response->verifyResponse->report);
        print_r($report);
    }
} catch (Exception $e) {
    echo 'Verification failed: ' . $e->getMessage();
}

Expected Response

A successful verification returns a JSON object containing a report field. This field is a JSON string that must be parsed to access the report details.

{
  "report": "{\"validationStatus\":[{\"code\":\"claim.signature.validated\",\"explanation\":\"Claim signature validated\"}],\"summary\":{\"isTrusted\":true,\"signer\":\"CN=Que Signing Service\"}}"
}

Configuring the Verification Request

You can customize the verification process to control the level of detail in the report and how different types of manifests are handled.

Verification Modes

Que offers several modes to control the depth of the verification report.

  • summary (Default): Returns a high-level pass/fail result, trust status, and basic signer information. This is the fastest option for simple validation.
  • info: Provides basic information about the manifest, its claims, and signing entities.
  • detailed: A comprehensive, forensic-level report of all assertions, claims, signatures, and validation steps.
  • tree: Renders a hierarchical view of the manifest's ingredient relationships, showing the full provenance chain.

Remote Manifests

Some assets may contain references to manifests stored externally rather than embedded in the file. By default, Que does not fetch these for security reasons. You can enable this behavior if needed.

Warning: Security Implications

Enabling remote manifest fetching can expose your application to security risks like Server-Side Request Forgery (SSRF) if the URLs are malicious. Only enable this feature if you trust the source of your assets.

For more details, see our guide on Remote Manifests.

Creator Identity (CAWG)

During verification, you can configure how Que handles CAWG identity assertions, such as making a valid creator identity mandatory for the verification to pass. See Creator Identity (CAWG) for configuration options.

Understanding the Verification Report

The report field in the response is a JSON string that your application must parse. The structure of the parsed object depends on the mode you requested.

A summary report contains two key fields:

  • validationStatus: An array of codes indicating which validation steps passed or failed. A claim.signature.validated code confirms the cryptographic signature is intact.
  • summary: An object containing the most important high-level result.
    • isTrusted: A boolean that is true if the asset was signed by a certificate in Que's managed Trust List. This is the primary indicator of authenticity.
    • signer: A string identifying the entity that signed the asset.

A false value for isTrusted does not necessarily mean the asset is malicious. It could mean it was signed by a self-signed certificate or a certificate from an unknown authority. The detailed codes in the report provide the context needed to make an informed decision.

For a full list of possible errors and report codes, see Errors and Troubleshooting.