Validation Workflows
22 September 2026 at 12:02
Across all my posts about C2PA's flaws, there's one aspect that has been bothering me for years: you can upload the exact same picture to different C2PA validators and get conflicting results. It wasn't until I began flow-charting the validation process that I realized the scale of the problem.
In this blog entry, I'm going to compare the workflows used by TLS (required by HTTPS), Secure Evidence Attribution Label (SEAL), and C2PA. I'm specifically comparing TLS and SEAL to C2PA because (1) C2PA's proponents often compare C2PA's use of X.509 to TLS, and (2) C2PA's Chief Architect prompted me to come up with an alternative to C2PA, so I created SEAL.
TLS uses a very straightforward flowchart:
There can be differences between implementations. Browsers mainly differ in trust stores (Step 4) and revocation policies (Step 5). Those differences are small, publicly documented, and seldom change:
The typical use case today relies on DNS for storing the public key:
Following the alternative branch, when the public key is embedded in the SEAL record:
Whether you check DNS and then validate (3a/4a) or validate and then check DNS (3b/4b), passing both tests still means checking for revocation:
Every SEAL validator should handle this workflow the same way, so they should all get the same results under the same conditions. You might have noticed that I included the caveat "under the same conditions". If one validator can access DNS and another validator cannot (e.g., it's offline), then you may get different results. But even with this difference, the user can immediately tell why there is a difference. (Seeing that they cannot reach DNS because they are offline probably won't be a surprise to the user. And as mentioned in the previous blog entry, SEAL trusts DNS.)
C2PA's validation flow includes six steps where validators can make their own choices (Steps 2, 3, 6, 7, 9, and 10). Since every validator can do this differently, every validator can generate different and conflicting responses to the exact same media.
In this blog entry, I covered how the logic flows differ. So far, that's all theory. In the next blog entry, I'll use real-world examples to demonstrate how different C2PA validators generate different results for the exact same media.
In this blog entry, I'm going to compare the workflows used by TLS (required by HTTPS), Secure Evidence Attribution Label (SEAL), and C2PA. I'm specifically comparing TLS and SEAL to C2PA because (1) C2PA's proponents often compare C2PA's use of X.509 to TLS, and (2) C2PA's Chief Architect prompted me to come up with an alternative to C2PA, so I created SEAL.
TLS
Transport Layer Security (TLS) provides the framework for encrypting data over the internet. It uses a common container format called X.509, which holds the public keys and related meta information (issuer, subject, validity date range, etc.). Every web browser should handle the TLS information the same way, using the same logical flow.TLS uses a very straightforward flowchart:
- Is TLS present? If it isn't, then your connection is using plain old HTTP: unencrypted and untrusted. If it is present, then you're using HTTPS and can check the X.509 certificate information.
- Is the TLS certificate expired? An expired certificate should not be used. At minimum, it will generate a browser warning because the certificate is untrusted.
- Is the TLS certificate tied to the domain? Buried in the X.509 is a field that says "this certificate is associated with this domain name." For example, the certificate from my blog says that my certificate is associated with "hackerfactor.com" and "*.hackerfactor.com". If your browser sees my certificate associated with a different domain name, it will stop your access and inform you that the connection is insecure.

- Is the certificate linked to the trusted list? Your computer (and browsers) have a list of known trusted certificates, and the X.509 certificate defines a trust chain. This chain should link back to the trusted list. If it doesn't, then the certificate is untrusted, even if it came from the correct domain name.
- Is the certificate currently revoked? This doesn't just check the last certificate; it's designed to check every certificate in the chain. If any of them are revoked, then the certificate is explicitly invalid due to revocation. If it's not explicitly revoked, then it's valid, verified, authenticated to the domain, and chains to the trusted list. However, this has a soft-fail condition: if the revocation status cannot be determined, then it is treated as being "not revoked".
This "Is it revoked?" check is actually a little more complicated. For example, the revocation may include a date. If the current time (for HTTPS) is before the revocation date, then the certificate is still valid. This may not make too much sense for real-time web requests because the current time should always be after the revocation date. However, this is required for checking digital signatures at a later time. If it was signed before the revocation date, then it's valid. If it's signed after, then it's revoked and invalid.
There can be differences between implementations. Browsers mainly differ in trust stores (Step 4) and revocation policies (Step 5). Those differences are small, publicly documented, and seldom change:
- Browser differences: Mozilla, Apple, Microsoft, and Google each maintain their own trusted lists (aka root store or trust store), but they coordinate through the Common CA Database (CCADB). This way, trust stores overlap significantly. Operating systems often add a few roots of their own (such as Microsoft or Apple adding trusted roots for their own update services). Similarly, private enterprise and government networks may include custom internal CAs in the local trust stores. (The U.S. Department of Defense and federal networks are classic examples.)
- Revocation differences: Browsers can use different revocation methods, and these can change over time. For example, Chrome used to perform live revocation checking, but now uses downloaded lists (CRLSets). Firefox introduced its own downloaded-list mechanism, CRLite, in Firefox 137 (April 2025), and disabled OCSP for domain-validated certificates in Firefox 142 (August 2025).
SEAL
While SEAL doesn't use X.509 certificates, it does use a similar flow chart. However, the flow is a little complicated because of the optional embedded public keys.- Is SEAL present? If there is no SEAL record, then there is nothing to check.
- Is the public key in the SEAL record? This is an encoding option because post-quantum crypto keys can be too large for storing in DNS. Today's typical usage stores the public key in DNS. (If there is a public key then jump to 3b, otherwise continue to 3a.)
The typical use case today relies on DNS for storing the public key:
- Is the public key in DNS? Most of the time, you don't have the public key in the SEAL record, so you retrieve it from DNS. If DNS is unreachable or the record doesn't include the public key, then the data cannot be verified.
- Does it validate? You now have the public key from DNS. Does it validate the signature? If it doesn't, then the signature is invalid.
Following the alternative branch, when the public key is embedded in the SEAL record:
- Does it validate? Since you have the key, check if the signature validates. If it doesn't, then it's invalid. But if it does validate, then you haven't authenticated the signature yet.
- Is the public key's digest in the DNS? This check allows you to attribute the key to the signer. Without this, you have verification (the crypto worked) without authentication (you don't know who signed it).
Whether you check DNS and then validate (3a/4a) or validate and then check DNS (3b/4b), passing both tests still means checking for revocation:
- Is the key revoked? SEAL doesn't use a trust chain; there is only one revocation to check and it comes from the DNS record. As with TLS/X.509, there are actually a bunch of 'if'-conditions around date revocation and default revocation records, but the basic gist is "Is it revoked?" If it is revoked, then the signature is invalid due to revocation.
And if it isn't revoked? Then it's valid, with a verified signature and authenticated to the domain that holds the public key.
Every SEAL validator should handle this workflow the same way, so they should all get the same results under the same conditions. You might have noticed that I included the caveat "under the same conditions". If one validator can access DNS and another validator cannot (e.g., it's offline), then you may get different results. But even with this difference, the user can immediately tell why there is a difference. (Seeing that they cannot reach DNS because they are offline probably won't be a surprise to the user. And as mentioned in the previous blog entry, SEAL trusts DNS.)
C2PA
Compared to the other validation flows, C2PA is a long and complicated mess. It has 10 steps, 6 with optional implementations, and a major flow decision based on an optional signed timestamp (see Step 5).- Are Content Credentials present? Simple and straightforward; you cannot validate it if it doesn't exist.
- Is the certificate linked to the trusted list? The C2PA spec does not recommend any specific baseline root store. Instead, every instance (generator, signer, and validator) decides what root stores they want to include. There are currently many independent root stores, and most have no overlap:
- C2PA's Official Trust List: https://github.com/c2pa-org/conformance-public/tree/main/trust-list. (We'll ignore that GitHub probably isn't the safest place to store their official trust list. Too much risk of a GitHub compromise permitting unauthorized edits to the list.) This list is only for conforming products. (I probably need to write a completely separate blog detailing the problems with their conformance program.)
- C2PA Interim Trust List (ITL): https://github.com/contentauth/verify-site/tree/main/static/trust was officially frozen in January 2026, but has been updated a few times since then. (With the ITL, frozen doesn't really mean "no more changes". If you use the 'frozen' trust list from January 2026, then you can get different results compared to the 'frozen' list from March 2026.) The ITL is kept around for verifying signatures that were made before the official trust list was deployed.
- International Press Telecommunications Council (IPTC): https://trust.iptc.org/end-entity-list.pem. The IPTC maintains its own list that has no overlap with other lists.
- Sony: https://imagevalidation.authenticity.sony.net/trust/sony.pem is intended for Sony applications to validate Sony-signed documents. As far as I can tell, only Sony uses Sony's list.
I'm sure these are not the only C2PA trust lists; these are just the ones I currently know about.
The X.509 certificate contains details about the signer responsible for the cert. However, it does not specify which trust list should be used or where to get the trust list.
Asking "Is the signing certificate linked to the trusted list?" sounds like a simple yes/no question. However, it isn't. This is because every organization can use a different list! A user trying to validate the media should know which trust lists are being used and when they were last updated. If the validator includes multiple trust lists, then you also need to know the validator's rules for combining them. Unfortunately, validators don't generally provide this information.
While TLS does have a similar problem with trust list variations, the significant overlap between different platforms minimizes any differences. In contrast, different C2PA trust lists often have no overlap (e.g., the lists from IPTC, Sony, and C2PA are completely different), leading to very different results.
According to the W3C minutes, C2PA's Chief Architect Leonard Rosenthol was asked why they were using a separate trust list. His reply: "We've been trying to use the same model, just has different trust anchors." No, this really is not the same model. TLS and the significant overlap between trust store entries on different platforms are about interoperability; C2PA's vastly different trust stores with no required commonality are about enabling isolated walled gardens. - Is the signing certificate revoked? X.509 supports two different revocation methods:
- CRL: A Certificate Revocation List is a static list that you can download. It includes the cert serial number for every revoked certificate. In theory, the CRL can grow to be very large. In practice, they are typically tiny.
- OCSP: The Online Certificate Status Protocol is newer than CRL, but the CA/Browser Forum has made OCSP optional and requires CRLs. Some CAs and browsers have moved away from OCSP entirely: Let's Encrypt and Google Trust Services have stopped issuing OCSP responses for most certificates, Firefox is disabling OCSP checks in favor of CRLite, and Chrome doesn't rely on live OCSP checks by default. In effect, OCSP is becoming obsolete.
For CCADB-based lists, most of the certs have no defined revocation method. (On my Ubuntu system, 122 of the 146 certs had no defined revocation method.) This isn't a big problem for TLS since network connections are ephemeral. If you no longer trust a root, then the trust list managers will remove it from the list and your browser will stop trusting it after the next update.
On the other hand, C2PA uses static signing certificates, not ephemeral network connections. This introduces a lot of problems. For example:
- Revocation checking is optional and usually not implemented.
- CRLs are explicitly not supported. C2PA only supports OCSP. (This is an ironic choice: C2PA is relatively new, yet it requires a deprecated technology. It's also ironic given that C2PA claims to care about privacy, while industry groups are rejecting OCSP due to privacy concerns.)
- C2PA explicitly permits "OCSP Stapling", where the OCSP record is attached to the C2PA's manifest. But this is optional, typically not present, and has other problems (that really deserve a separate blog entry). OCSP Stapling is not a reliable solution.
With C2PA, revocation checking is optional and different validators can return different results based on how they query for revocation. But it gets a little worse: C2PA supports an optional signed timestamp. This means that a revoked signing certificate could appear un-revoked based on the timestamp. (See Steps 5-9.) - Is the signing cert currently expired? If the cert is not revoked (including unchecked or unavailable), then it is valid and trusted. Otherwise, you need to check for a signed timestamp (Step 5).
- Is there a signed timestamp? This is the point where C2PA's logic flow significantly deviates from TLS. If the signing certificate is expired or revoked, then a signed timestamp can still make it appear valid.
- Is the timestamp cert in the trusted list? To reach this step, we have an expired or revoked signing cert, but a signed timestamp is present. Do we trust the signed timestamp? This is similar to the "inconsistent lists" problem seen in Step 2, except that the C2PA and ITL use separate trusted lists for timestamp certificates. This allows validators to potentially mix-and-match. For example, they might permit a signing certificate from the IPTC trust list but the timestamp certificate from the "C2PA TSA Trust List" (which is different from the "C2PA Trust List").
- Is the timestamp certificate revoked before the timestamp date? As with Step 3, revocation checking is optional. Moreover, some validators seem to only check the last certificate in the chain for revocation, while others check the entire chain.
- Is the signed timestamp within the timestamp certificate's validity period? Every cert has a "Not Before" and "Not After" range. This is a sanity check step to make sure the trusted timestamp authority (TSA) used a date during the TSA cert's validity period. (A malicious TSA can still backdate or postdate the signature, but it must be within the "Not Before" and "Not After" range.)
- Is the signed timestamp within the signing cert's validity period and before any revocation? While Step 8 checked the signed time against the timestamp cert, this step checks the signed time against the signing cert. If the signed timestamp predates the signing cert's revocation date, then the cert is still valid. This permits the continued use of an expired or revoked signing cert (effectively un-expiring or un-revoking it). Unfortunately, I've seen some validators just check if the timestamp is valid, and not check if the revocation date predates the timestamp.
- Was the signing cert revoked? This is the final failure condition and triggers when the signing cert is invalid. Was it invalid due to the optional revocation checking (invalid and revoked) or expiration (invalid and expired)?
C2PA's validation flow includes six steps where validators can make their own choices (Steps 2, 3, 6, 7, 9, and 10). Since every validator can do this differently, every validator can generate different and conflicting responses to the exact same media.
- If a C2PA validator says you can trust the signature, there could always be another validator that says you cannot.
- If a validator says you cannot trust it, then you can probably shop around to find a validator that says it is trustworthy.
In this blog entry, I covered how the logic flows differ. So far, that's all theory. In the next blog entry, I'll use real-world examples to demonstrate how different C2PA validators generate different results for the exact same media.