QueEngine is a high-level, configurable Rust library designed to be the core provenance and authenticity engine for the Que Platform. Its primary function is to provide a safe, robust, and developer-friendly interface for interacting with the C2PA (Coalition for Content Provenance and Authenticity) standard.
While the official c2pa-rs library is powerful, it is a low-level toolkit. QueEngine acts as an abstraction layer on top of it, providing:
-
Safe Defaults: Common operations like signing and verifying work out-of-the-box with secure, opinionated defaults.
-
Powerful Configuration: Exposes the full power of the underlying C2PA library through well-structured configuration objects, allowing advanced users to customize every aspect of the process.
-
Identity Assertions: Support for CAWG (Creator Assertions Working Group) X.509 identity assertions for enhanced creator verification (requires
cawgfeature). -
Clear Boundaries: A clean separation between pure domain logic and concrete implementation details (the "adapter" pattern).
-
Security & Stability: Manages thread-safety for global settings and provides robust error handling to prevent panics from crossing FFI boundaries.
-
Language SDKs (FFI): The
que-fficrate within this repository uses QueEngine to generate native bindings for Swift (iOS/macOS) and Kotlin (Android), enabling on-device, offline provenance operations.
The engine is split into two main concepts:
domain: This module contains pure Rust types, traits, and error definitions. It has no knowledge of C2PA or any other specific implementation. It defines what the engine can do (e.g.,ManifestEnginetrait).adapters: This module provides the concrete implementation. Thec2pa.rsadapter implements theManifestEnginetrait using thec2pa-rslibrary. This design allows for future adapters (e.g., for blockchain anchoring) to be added without changing the core domain.
The underlying c2pa-rs library relies on a global static configuration for certain settings (like trust validation). Modifying this from multiple threads is unsafe.
Assumption: To solve this, QueEngine uses a global Mutex (C2PA_SETTINGS_LOCK). Any function that needs to modify these global settings (like verify with a custom policy) will lock the mutex, apply the settings for the duration of the call, and then restore the baseline settings before releasing the lock. This serializes all settings-dependent C2PA calls, ensuring thread safety at the cost of some parallelism for those specific operations.
- HTTPS is enforced by default for all network URLs (timestamp authority, remote manifests). HTTP can be explicitly opted-in behind a feature and per-call flag.
- Remote manifest fetching is disabled by default; it can be explicitly opted-in behind a feature and per-call flag.
- DNS hardening prevents requests to private/loopback/link-local IP ranges, including resolutions via domain names (mitigates SSRF/DNS rebinding).
- Certificate chain inclusion in verification results is opt-in.
- No built-in or test certificates are bundled. You must bring your own certificates/keys for signing.
QueEngine follows a "secure by default" philosophy with sensible defaults that can be explicitly opted out of when needed.
All defaults are centralized in the EngineDefaults struct for consistency and maintainability. The secure_default() methods on all config structs use these centralized defaults.
| Feature | Default | Opt-in Method | Rationale |
|---|---|---|---|
| HTTPS enforcement | ✅ Enabled | Feature flag http_urls |
Security baseline |
| Remote manifests | ❌ Disabled | Feature flag remote_manifests + allow_remote_manifests: true |
Network security |
| HTTP URLs | ❌ Disabled | allow_insecure_remote_http: Some(true) |
SSL/TLS security |
| CAWG identity assertions | ❌ Disabled | Feature flag cawg + CawgIdentity config |
Identity verification |
| Memory limits | ✅ 128MB assets | Hard-coded (not configurable) | Resource exhaustion protection |
| Trust verification | ❌ Disabled | Provide TrustPolicyConfig |
Bring-your-own-trust |
| Certificate inclusion | ❌ Disabled | include_certificates: Some(true) |
Privacy protection |
| Embed manifests | ✅ Enabled | embed: false |
Standard C2PA behavior |
| Post-sign validation | ✅ Enabled | skip_post_sign_validation: true |
Quality assurance |
| File input method | AssetRef::Bytes |
AssetRef::Path, AssetRef::Stream |
Memory efficiency |
| Output method | OutputTarget::Memory |
OutputTarget::Path |
API convenience |
| Signing algorithm | SigAlg::Es256 |
SigAlg::Es384, Ps256, Ed25519 |
Compatibility |
| Verification mode | VerifyMode::Summary |
VerifyMode::Info, Detailed, Tree |
Performance |
| Timestamping | ❌ Disabled | Provide Timestamper |
Cost control |
QueEngine is heavily feature-gated to produce minimal binaries for different targets. This is critical for security and performance. For example, the enclave feature (for on-device signing) should never be compiled into the QueCloud server binary, and the kms feature (for cloud signing) should never be compiled into a device-side library.
See the Feature Flags section for a complete list.
Add QueEngine to your project's Cargo.toml.
[dependencies]
que-engine = { git = "https://github.com/QuePlatform/QueEngine", path = "crates/engine" }To include support for fragmented BMFF (MP4) files:
[dependencies]
que-engine = { git = "https://github.com/QuePlatform/QueEngine", path = "crates/engine", features = ["bmff"] }Add features you need to your dependency declaration:
c2pa(default): Enable the C2PA adapter.openssl(default): Use OpenSSL backend where applicable.bmff: Support fragmented BMFF signing helpers.cawg(opt-in): Enable CAWG (Creator Assertions Working Group) X.509 identity assertions for signing and verification. Defaults to reusing main signer certificates when enabled for enhanced creator verification.remote_manifests(opt-in): Allow fetching remote manifests during verification. Default is disabled.http_urls(opt-in): Allow HTTP (non-HTTPS) URLs for TSA/remote manifests. Default is disabled.
Example:
[dependencies]
que-engine = { git = "https://github.com/QuePlatform/QueEngine", features = ["bmff", "remote_manifests"] }QueEngine requires C2PA-compatible certificates for signing.
For development and testing, you can use the included test certificates:
- Certificate:
es256_certs.pem - Private key:
es256_private.pem
Note: These test certificates are for development only and will not be trusted in production environments.
For production use, you'll need your own C2PA-compatible certificates from an official Certificate Authority. The easiest way to get production-ready C2PA signing is using QueCloud, which provides a fully managed, cost-efficient service with built-in certificate management and secure key storage.
Alternatively, you can provide your own certificates via:
Signer::Local { cert_path, key_path }(URI format:local:/path/cert.pem,/path/key.pem)Signer::Env { cert_var, key_var }(URI format:env:CERT_ENV,KEY_ENVwhere env vars contain PEM content)
Example (env):
export CERT_PEM="$(cat /path/to/cert.pem)"
export KEY_PEM="$(cat /path/to/key.pem)"Use the secure defaults to get started quickly:
use que_engine::{C2paConfig, C2paVerificationConfig, AssetRef, SigAlg, Signer, EngineDefaults};
let signer: Signer = "env:CERT_PEM,KEY_PEM".parse().unwrap();
let sign_cfg = C2paConfig::secure_default(
AssetRef::Path("/path/to/input.jpg".into()),
signer,
EngineDefaults::SIGNING_ALGORITHM, // Uses centralized default (Es256)
);
let verify_cfg = C2paVerificationConfig::secure_default(
AssetRef::Path("/path/to/signed.jpg".into())
);
// All config structs have secure_default() methods that use EngineDefaults constants
let ingredient_cfg = IngredientConfig::secure_default(AssetRef::Path("/path/to/asset.jpg".into()));You can also reference individual defaults directly:
// Check what the default signing algorithm is
let alg = EngineDefaults::SIGNING_ALGORITHM; // SigAlg::Es256
// Use in your own configuration logic
let embed_manifests = EngineDefaults::EMBED_MANIFESTS; // true- API Reference: Explore the public functions available in the engine.
- Data Structures: Understand the configuration and result types used by the API.
Production-tuned limits to prevent memory exhaustion:
| Limit | Value | Purpose |
|---|---|---|
max_in_memory_asset_size |
128MB (default) | Prevents loading very large files into RAM |
max_in_memory_output_size |
128MB (default) | Prevents memory explosion from large signed assets |
max_stream_copy_size |
1GB (default) | Max size for stream-to-temp-file operations |
max_stream_read_timeout_secs |
300s (default) | Max time for stream operations |
These are now configurable per call via LimitsConfig in each config's limits field, while maintaining secure defaults.
When to use each AssetRef type:
- AssetRef::Bytes: Files < 128MB, API uploads, memory-resident data
- AssetRef::Stream: Files > 10MB, large files, memory-constrained servers
- AssetRef::Path: Local files, after secure URL fetching
For remote URLs (S3, HTTP endpoints):
- Validate first: Use
validate_external_http_url()before fetching - HEAD request: Check Content-Length and Content-Type before downloading
- Size limits: Enforce < 1GB content length
- MIME filtering: Only allow supported types (JPEG, PNG, MP4, PDF, etc.)
- Fetch to temp file: Store as
AssetRef::Pathfor processing - Timeout handling: < 5 minutes connect/read timeouts
Security policies:
- HTTPS only by default
- No redirects or limited redirects with re-validation
- DNS re-resolution with IP verification
- Allow only specific MIME types you support
Always provide content_type for streams when possible:
// Good: Explicit content type
AssetRef::Stream {
reader: stream,
content_type: Some("image/jpeg".to_string())
}
// Engine will attempt auto-detection if None, but explicit is betterMemory limit errors:
Config("in-memory asset too large")- File exceeds 128MBConfig("signed output too large to return in memory")- Output exceeds 128MBConfig("Stream size limit exceeded")- Stream copy exceeds 1GB
Handle these by:
- Using
AssetRef::Streamfor large inputs - Using
OutputTarget::Pathfor large outputs - Implementing streaming uploads in your API
QueEngine only supports the file formats officially supported by C2PA. The engine will automatically detect content types for supported formats, but for best results, provide explicit content_type when using AssetRef::Stream.
Supported formats include:
- Images: JPEG, PNG, GIF, WebP, HEIC, HEIF, AVIF, TIFF, SVG
- Video: MP4, MOV, AVI
- Audio: MP3, M4A, WAV
- Documents: PDF (read-only)
For the complete list of supported formats and their MIME types, see docs/TYPES.md.
QueEngine uses GitHub Actions to enforce that FFI bindings (Swift/Kotlin) are always up-to-date when a release is tagged.
The que-ffi crate generates Swift and Kotlin bindings from the Rust FFI layer. If these bindings are not regenerated before a release, the published SDKs may be out of sync with the engine code. This can cause runtime errors or missing functionality.
- A workflow (
.github/workflows/release-check.yml) runs only on tag pushes (e.g.,v1.2.0). - It:
- Builds the project in release mode.
- Runs the
build.shscript to regenerate Swift/Kotlin bindings. - Fails if the repository is “dirty” (i.e., if bindings changed but were not committed).
-
Before tagging a release, always run:
./build.sh git add bindings/ git commit -m "chore: update FFI bindings" -
Tag and push the release:
git tag v1.2.0 git push origin v1.2.0
-
If bindings were not updated, the CI job will fail with:
❌ Bindings are out of date for this release tag. Run ./build.sh locally, commit the changes, and re-tag the release.
This ensures that every release ships with correct, up-to-date FFI bindings.