Skip to content

ndurner/iiry

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IIRY app hero image

IIRY

Is It Really You?
A hackathon prototype for binding a German EUDI Wallet holder presentation to a JPEG.

Swift app and CLI Python service FastAPI iOS 17+ macOS 15 CLI C2PA profile CAWG VC+VP plus IIRY extension OpenID4VP Prototype

This hackathon MVP binds a German EUDI Wallet Verifiable Presentation to a JPEG. This can be useful, for example, to commit to a screenshot of a WhatsApp conversation. In the case of a request for something, this would give the receiver another signal whether or not the request is genuine.

While this may sound a lot like chat forensics, the security claim actually is attested image provenance:

This exact image is cryptographically bound to a fresh German EUDI Wallet holder presentation for the disclosed identity attributes, and the binding verified.

IIRY does not prove that a WhatsApp account belongs to the wallet holder, that the conversation content is true, or that a bank-transfer request by itself is legitimate. It proves image binding, credential presentation verification, holder-binding freshness, and verifier policy checks.

Quick Links

Demo Shape

Surface Role
iPhone app Captures or imports an image, shows the human challenge text, runs the wallet flow, and exports a signed C2PA JPEG using the protective .iiry extension.
Shared Swift core Implements nonce encoding, asset hashing, CAWG identity assertion encoding, JPEG/C2PA insertion, SD-JWT/OpenID4VP holder-binding checks, and validation.
macOS CLI Signs and verifies C2PA JPEGs through the same IIRYCore implementation used by the app.
FastAPI service Drives the OpenID4VP relying-party flow and returns wallet response material to the app.

Parts

  • ios/IIRY.swiftpm: SwiftUI iPhone app and the shared IIRYCore code.
  • cli/Sources/IIRYCLI: macOS CLI that uses the same IIRYCore package as the iPhone app.
  • service/iiry_service: FastAPI relying-party service for OpenID4VP / German EUDI Wallet callbacks.
  • docs/cawg-eudi-extension.md: lean CAWG extension proposal for OpenID4VP holder-bound EUDI presentations.

Core Idea

Content Credentials are the user-facing provenance experience built around the technical standards from the Coalition for Content Provenance and Authenticity (C2PA) for digital assets. The Creator Assertions Working Group (CAWG) adds identity assertions that can bind a credentialed actor to C2PA assertions.

CAWG's draft VC+VP identity assertion currently builds on the W3C standard for W3C Verifiable Credentials / Presentations. The German EUDI Wallet flow used here, however, returns an OpenID4VP presentation. That is not the same object shape as the CAWG draft's W3C VC/VP binding.

IIRY therefore uses an extension signature type:

io.github.ndurner.iiry.cawg.openid4vp.holder-binding.v3

The C2PA JPEG carries a literal cawg.identity assertion. Its signer_payload.sig_type is the IIRY namespace above, signer_payload.referenced_assertions includes the actual c2pa.hash.data hard-binding assertion, and its custom signature byte string carries canonical OpenID4VP evidence for the holder-bound wallet presentation.

The extension keeps the CAWG idea of an identity assertion whose signer_payload is presented to the credential holder and references the C2PA hard-binding assertion, but the holder proof is validated through OpenID4VP semantics:

  1. Verify the C2PA manifest and hard binding for the JPEG.
  2. Read the cawg.identity assertion with the IIRY OpenID4VP signature type.
  3. Decode the OpenID4VP nonce payload.
  4. Verify that the nonce contains both a digest of the CAWG signer_payload and a fresh random nonce.
  5. Verify the Wallet presentation holder binding over the same nonce.
  6. Verify issuer trust, disclosure integrity, audience, freshness, and policy.

The OpenID4VP nonce is originally a verifier transaction challenge. In holder-bound presentations it helps bind the proof to this verifier and this transaction, preventing presentation injection and replay. IIRY extends this to include the CAWG/C2PA binding: the Wallet signs a holder-binding proof over the OpenID4VP nonce, and that nonce contains the digest of the exact CAWG signer_payload embedded in cawg.identity.

This follows CAWG's signer-payload model closely, including the draft's signer_payload presentation and c2pa.hash.data interaction patterns so the hard-binding assertion can be known before the Wallet presentation is requested. The main deviation is that current IIRY uses OpenID4VP nonce for this commitment, not OpenID4VP transaction_data, and the CAWG signature byte string carries typed OpenID4VP evidence rather than a direct COSE signature over signer_payload.

Nonce Payload

The nonce passed in the OpenID4VP request is built like this:

base64url(deterministic-cbor([
  "io.github.ndurner.iiry.openid4vp-nonce.v3",
  random_256_bits,
  sha256(deterministic-cbor(cawg_identity.signer_payload))
]))

The human challenge text, for example:

Is it really you?
(2026-06-04 ABCD1234)

is intentionally not part of this nonce payload. It must be visible in the image itself and checked by the receiver. If the challenge is not captured in the image, IIRY can still prove that a wallet presentation was bound to those image bytes, but it cannot prove that the image answered the parent's latest challenge.

File Naming

The app exports a signed C2PA JPEG with a protective .iiry extension:

IIRY-Commitment-YYYY-MM-DD-ABCD1234.jpg.c2pa.cawg.iiry

Commitment is deliberate: the file contains a cryptographic commitment and evidence, not a blanket confirmation that the content is true. The .jpg segment keeps the ordinary image nature visible, .c2pa.cawg makes the technical choices visible during a no-slides demo, and the final .iiry extension discourages messenger pipelines from treating the file as an ordinary JPEG and stripping C2PA metadata. Detached JSON .iiry proof carriers are not an interchange format.

C2PA Status

The shared Swift core now implements a constrained IIRY JPEG/C2PA profile. It writes and reads JPEG APP11 C2PA/JUMBF segments, creates a c2pa.hash.data hard-binding assertion with exclusion ranges, embeds a CBOR cawg.identity assertion for the IIRY OpenID4VP signature type, writes a CBOR c2pa.claim.v2, and signs that claim as a detached COSE_Sign1 ES256 C2PA claim signature. The iPhone app and CLI use this same implementation.

For hackathon development the Swift core uses the same sample ES256 certificate/key material bundled with c2patool / c2pa-rs. That lets local reference tooling validate the C2PA mechanics, but it does not establish production signing-credential trust and must not be presented as a trusted Content Credential signer.

The Swift verifier used by both the iPhone app and CLI also verifies the embedded SD-JWT/OpenID4VP presentation: PID issuer JWT signature, disclosure hashes, holder key-binding signature, nonce, verifier audience, sd_hash, and issuer-chain trust against the German EUDIW sandbox PID provider trust list. For certificate-based relying-party deployments, the acceptable verifier audience is derived from the untracked RP access certificate at service/secrets/access-certificate.pem as x509_hash:base64url(sha256(access-certificate DER)). This follows the mock trust-list setup from the German EUDI Wallet Developer Guide / Blueprint and has only been tested with the German EUDIW Sandbox. It is not production PID issuer trust.

The CLI supports two verification modes for C2PA-bearing JPEGs:

  • iiry verify <image.jpg> --own verifies the Swift IIRY profile.
  • iiry verify <image.jpg> --c2patool asks local c2patool -d whether the asset satisfies the reference C2PA verifier.
  • iiry verify <image.jpg> --both runs both and exposes disagreement.
  • iiry verify <image.jpg> --own --service-base-url <url> verifies the wallet key-binding aud against redirect_uri:<url>.
  • iiry verify <image.jpg> --own --audience <aud> verifies the wallet key-binding aud against an explicit verifier identifier such as an x509_hash:... client ID.
  • iiry verify <image.jpg> --own --access-cert <cert.pem> derives the acceptable x509_hash:... audience from a local RP access certificate.
  • iiry verify <image.jpg> --c2patool --trust-c2pa-sample repeats the reference check while explicitly trusting the C2PA ES256 sample root anchor for local development only.

For IIRY-generated JPEGs, the expected default c2patool development result is that assertion.dataHash.match, assertion.hashedURI.match, and claimSignature.validated pass, while signingCredential.untrusted fails. With --trust-c2pa-sample, the C2PA layer should report validation_state: Trusted; this is still sample trust, not production trust. Separately, generic C2PA tooling can see the cawg.identity assertion but will not understand IIRY's OpenID4VP holder-binding semantics until that extension is standardized or explicitly supported.

The web service drives the OpenID4VP relying-party flow, verifies the wallet response during issuance, and returns the Wallet response material to the app. Received .iiry files are verified again in Swift.

The iOS Xcode project runs scripts/generate-verifier-policy.py before compiling IIRYCore. If service/secrets/access-certificate.pem exists locally, the script writes the corresponding non-secret x509_hash:... into ios/IIRY.swiftpm/Sources/IIRYCore/VerifierPolicy.generated.swift so the app can verify certificate-based wallet audiences without querying the service for policy. The access certificate itself remains ignored by Git. Regenerate the Swift policy after RP access certificate rotation.

Acknowledgements

IIRY is a prototype implementation built on public standards and reference ecosystems:

  • The C2PA/JUMBF/JPEG manifest work is implemented in Swift for this repository, guided by the Coalition for Content Provenance and Authenticity (C2PA) technical specification and checked for interoperability against c2patool.
  • The CAWG identity model and the proposed OpenID4VP extension build on the Creator Assertions Working Group draft VC+VP identity assertion.
  • The development-only ES256 certificate and private key embedded in IIRYCore are the sample testing material from the c2patool / c2pa-rs ecosystem. They are included only so the hackathon prototype can exercise C2PA signing mechanics and reference-tool verification; they must not be treated as production signing credentials.
  • The relying-party service follows the OpenID4VP and German EUDI Wallet sandbox flow. Production deployments need real relying-party key material, certificates, trust policy, and operational review.
  • The iOS app uses Apple platform frameworks including SwiftUI, PhotosUI, Uniform Type Identifiers, UIKit sharing, and CryptoKit. The service uses the Python FastAPI ecosystem listed in service/requirements.txt.

Web Service Serialization

The service can persist decrypted wallet presentation results for local CLI testing, but only when explicitly enabled:

IIRY_SERIALIZE_PRESENTATIONS=1

By default it stores sessions only and does not write serialized VP artifacts for reuse.

Build

Build and test the shared Swift core and macOS CLI:

swift test
swift run iiry --help

Run the service:

python3 -m venv .venv
. .venv/bin/activate
pip install -r service/requirements.txt
uvicorn iiry_service.app:app --app-dir service --reload --port 8110

The service expects RP key and German EUDI sandbox certificate material through environment variables or service/secrets/, matching the structure described in service/iiry_service/app.py.

References

About

Is It Really You - bringing EU state-issued digital identities to Content Credentials

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors