Embed signed manifest into image

Generate a minimal C2PA manifest, sign it (using a demo CSC signer) and embed the signed manifest into a JPEG image. The result is a signed image with embedded provenance information.

Inputs

  • Path to the JPEG image file.
  • Path to save the signed image.
  • Author name – Enter manually.
  • Author email – Enter manually (optional).
  • A certificate chain file (chain.pem) available locally required by the signer initialization.

Process

  1. Collect input values from the user (image path, output path, author details).
  2. Construct a minimal manifest as a JSON object containing the image metadata and author information.
  3. Load the image bytes from the input file.
  4. Load the certificate chain from chain.pem.
  5. Initialize a Builder with the manifest JSON and set up a signer (demo CSC signer in this case, which base64-encodes the hash).
  6. Use the builder to sign the manifest and embed it into the JPEG image.
  7. Save the signed image to the specified output path.

Output

  • A signed JPEG image file that contains the embedded manifest.
  • The manifest includes author information and provenance data, making the image verifiable by C2PA-compliant tools.

Python example

# This program takes a JPEG image, builds a C2PA manifest with author information, #...simulates signing the manifest using a dummy CSC signer (which just base64-...encodes #...the manifest hash instead of performing a real cryptographic signature), embeds the #...“signed” manifest into the image, and writes out the signed image to disk.    

#Dummy parts:  #demo_csc_signer is a placeholder and does not perform actual CSC signing.    

#In the production environment, this function calls the CSC API with your client certificate, private #...key, and PIN to obtain a legitimate signed hash (SAD) for the manifest.    

#While the program demonstrates the full flow of building a manifest, loading an #...image, and embedding a signature, the cryptographic signing step is mocked and #...must be replaced with actual credentials and CSC API calls in production.    

#For a working example to see how you can sign an image with a c2pa #...complaint manifest using DigiCert DTM CSC API, see next section.  

import io  
import json  
import base64  
import logging  
import getpass  
from c2pa import Builder, create_signer, SigningAlg    

# --- Configuration ---  
CERT_CHAIN_PATH = "chain.pem"  
SIGNING_ALG = SigningAlg.PS256  
TIMESTAMP_URL = "http://timestamp.digicert.com"    

# Load certificate chain  
with open(CERT_CHAIN_PATH, "rb") as f:      
    CERT_CHAIN = f.read() 
   
def log_step(msg: str):      
    print("\n" + "="*60 + "\n")      
    logging.info(msg)    

# --- Dummy CSC signer for demonstration ---  
def demo_csc_signer(data: bytes):      
    """Simulate signing: return a base64-encoded hash as a 'signed manifest'"""      
    hash_b64 = base64.b64encode(data).decode("utf-8")      
    print(f"Hash of manifest to be signed (base64): {hash_b64}")      
    # In production, call CSC APIs here      
    return hash_b64.encode("utf-8")  

def main():      
    logging.basicConfig(level=logging.INFO, format="%(message)s")            

    # --- Step 1: Inputs ---      
    log_step("Step 1: Enter required inputs")      
    image_path = input("Enter path to JPEG image: ").strip()      
    output_path = input("Enter output path for signed image: ").strip()      
    author_name = input("Enter author name: ").strip()      
    author_email = input("Enter author email (optional): ").strip()            

    # --- Step 2: Build manifest ---      
    log_step("Step 2: Build manifest")      
    manifest_dict = {          
        "title": image_path,          
        "format": "image/jpeg",          
        "claim_generator_info": [{"name": "Demo C2PA Builder", "version": "1.0.0"}],  
        "assertions": [              
            {                  
                "label": "stds.schema-org.CreativeWork",                  
                "data": {                      
                    "@context": "https://schema.org",                      
                    "@type": "CreativeWork",                      
                    "author": [{"@type": "Person", "name": author_name, "email": author_email}],                  
                },                  
                "kind": "Json",              
            }          
        ],      
    }      
    manifest_json = json.dumps(manifest_dict)  

    # --- Step 3: Load image ---      
    log_step("Step 3: Load image")      
    with open(image_path, "rb") as f:          
        img_bytes = f.read()            

    # --- Step 4: Create builder and signer ---      
    log_step("Step 4: Prepare builder and signer")      
    builder = Builder(manifest_json)      
    signer = create_signer(demo_csc_signer, SIGNING_ALG, CERT_CHAIN, TIMESTAMP_URL)            

    # --- Step 5: Embed signed manifest into image ---      
    log_step("Step 5: Embed signed manifest")      
    result = io.BytesIO()      
    builder.sign(signer, "image/jpeg", io.BytesIO(img_bytes), result)            

    # --- Step 6: Save signed image ---      
    log_step("Step 6: Save signed image")      
    with open(output_path, "wb") as f:          
        f.write(result.getvalue())            
    
    logging.info(f"✅ Signed image with embedded manifest saved to {output_path}")    

if __name__ == "__main__":      
    main()