Open clementh59 opened 8 months ago
Hi Clément,
What signing algorithm are you using? Unfortunately the JavaScript SDK doesn't support Ed25519 at the moment due to WebCrypto's lack of support for it, so I'm wondering if this is causing the issue.
It's es256. Please note that I am not generating this image. I took an image sample from truepic.
"signature": {
"alg": "es256",
"issuer": "Truepic",
"time": "2023-03-20T14:29:27+00:00"
}
Also, I can verify it there: https://contentcredentials.org/verify. I'm not sure which library is used there, but it seems to accept it.
Hi @clementh59, what version of c2pa
do you have installed?
Hi @emensch, that's 0.11.12
for c2pa-wc and 0.17.5
for c2pa
@clementh59 Verify uses the open source c2pa-js under the hood. I made a minimal repro case in CodeSandbox showing the image loading from your server and showing the claim info.
What browser/version are you trying this on?
Thanks a lot for the answer. The issue probably comes from our end then. I am on Chrome, but that's probably not linked to my browser/version as all the people that tried our service (which is a chrome extension) had the same issue.
We'll investigate on our end to try to find the root of the issue.
Thanks.
@clementh59 if you'd like to post sample code—specifically anything about reading c2pa data and passing it to the web components—we might be able to help debug. There are a couple potential pitfalls that we don't do a great job of detailing in our docs at the moment 🙂
@emensch @dkozma Here is the code I tried:
const sampleImage = 'https://truepic.com/wp-content/uploads/2023/03/transparency_original-capture.jpg';
(async () => {
const libraryUrl = './lib-temp/c2pa.esm.js';
// Initialize the c2pa-js SDK
const { createC2pa } = await import(libraryUrl);
const c2pa = await createC2pa({
wasmSrc: './lib-temp/toolkit_bg.wasm',
workerSrc: './lib-temp/c2pa.worker.min.js',
});
// Read in our sample image and get a manifest store
try {
const { manifestStore } = await c2pa.read(sampleImage);
console.log('errors from site reading: ', manifestStore.validationStatus);
} catch (err) {
console.error('Error reading image:', err);
}
})();
This code works in a very basic web app, where I don't have any validation error. However, the same code, with the same libraries (0.17.6) does not work in a very basic chrome extension (it says one of the signature mismatches).
We'll continue to investigate on our side, but if you have any idea of what could be the issue, that would be super helpful.
Thanks a lot
It seems to come from this function that returns a validation error when fetching the manifest store data:
It might be the case only when it is run in a special environment (i.e a chrome extension). What is surprising is that it is only the case for a small set of images, most of them are validated without any issue.
@clementh59 I'm not sure if your browser extension also runs in a Firefox or Safari environment but if so I'm curious as to if the same problem exists in those browsers, or if this is localized to Chrome.
Good point. It only runs on Chrome for now, so I can't help on that point, sorry
No problem - let us try to replicate on a minimal browser extension environment and see if there is anything we can find.
:white_check_mark: Jira issue https://jira.corp.adobe.com/browse/CAI-5271 is successfully created for this GitHub issue.
@clementh59 Are you running the c2pa-js code in a content script or a background script in your extension?
@dkozma None of them was working, we had to run it from a sandbox. You can find our implementation here: https://github.com/digimarc-corp/c2pa-content-credentials-extension/blob/main/sandbox.js
Feel free to ask any questions or help regarding the integration into an extension, I am happy to help!
Hi @dkozma,
I created a minimalist extension that showcases the bug: https://github.com/digimarc-corp/c2pa-content-credentials-extension/tree/minimalist-extension
Here are the steps to have it work: npm i git clone https://github.com/contentauth/c2pa-js mv c2pa-js c2pa cd c2pa Rush install Rush build
Then, load the extension in chrome (https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked) Click on the extension icon and turn it on. Open an image (e.g. https://truepic.com/wp-content/uploads/2023/03/transparency_original-capture.jpg) and check the console, you should see the validation status.
I hope it will make the debug as easy as possible on your side! :)
Feel free to ask if you have any questions. I'm happy to jump on a call if you think it could be valuable.
Thanks, this is very helpful - will give this a try Monday or Tuesday.
@clementh59 Still looking into this. Will update when I have more - the minimal repro example you provided definitely makes things easier.
Hi @dkozma any news on the issue? Also do let us know if we can do anything to help, anything you'd like us to explore. The extension is gaining quite a bit of momentum and we are eager to make sure it validates things correctly.
Hi @dkozma,
We continued to dig on our side to make it as easy as possible for you to debug it and here are some information that might be useful:
ps256
signatures are validated, but not those with es256
signatures. For an image that contains two iterations of a manifest, one with a ps256 signature and one with an es256 signature, the first is validated but not the second one (https://truepic.com/wp-content/uploads/2023/03/transparency_original-capture.jpg).For you to debug, it might be useful to have actual data, so here it is. That's the data I get inside verify_cose_async
in cose_validator.rs
:
log::info!("Current alg: {:?}", alg);
// build result structure
let mut result = ValidationInfo::default();
log::info!("Result: {:?}", result);
// get the cert chain
let certs = get_sign_certs(&sign1)?;
log::info!("Certs: {:?}", certs);
sign1.payload = Some(data.clone()); // restore payload
log::info!("signature payload: {:?}", sign1.payload);
let tbs = sig_structure_data(
coset::SignatureContext::CoseSign1,
p_header,
None,
&additional_data,
sign1.payload.as_ref().unwrap_or(&vec![]),
); // get "to be signed" bytes
log::info!("TBS: {:?}", tbs);
[...]
log::info!("result2: {:?}", result);
Ok(result)
I provided a JSON with all the values printed so that you can reproduce it in your environment easily: logs.json
Also, here is the validation log:
[
"LogItem"{
"label":"self#jumbf=/c2pa/adobe:urn:uuid:0ca37023-3dc5-4fee-8541-299998fb6b70/c2pa.signature",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1148",
"description":"claim signature valid",
"err_val":"None",
"validation_status":"Some(""claimSignature.validated"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.jpeg",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/c2pa.thumbnail.ingredient.jpeg",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/c2pa.thumbnail.ingredient.jpeg",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/c2pa.ingredient",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/c2pa.ingredient",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/stds.schema-org.CreativeWork",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/stds.schema-org.CreativeWork",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/c2pa.actions",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/c2pa.actions",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=c2pa.assertions/c2pa.hash.data",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=c2pa.assertions/c2pa.hash.data",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/adobe:urn:uuid:0ca37023-3dc5-4fee-8541-299998fb6b70/c2pa.assertions/c2pa.hash.data",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1311",
"description":"data hash valid",
"err_val":"None",
"validation_status":"Some(""assertion.dataHash.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.signature",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1139",
"description":"claim signature is not valid",
"err_val":"Some(""CoseSignature"")",
"validation_status":"Some(""claimSignature.mismatch"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.libc2pa",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.libc2pa",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif__1",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif__1",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif__2",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/stds.exif__2",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/c2pa.thumbnail.claim.jpeg",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/c2pa.thumbnail.claim.jpeg",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.custom.odometry",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.custom.odometry",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.custom.blur",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/com.truepic.custom.blur",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
},
"LogItem"{
"label":"self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/c2pa.hash.data",
"file":"/Users/clement-hecquet-evt/Documents/clement/tempo/c2pa-rs/sdk/src/claim.rs",
"function":"verify_internal",
"line":"1235",
"description":"hashed uri matched: self#jumbf=/c2pa/Truepic Lens SDK libc2pa vv1.0.0.182:urn:uuid:d6adaa79-0708-4b7a-9af0-10c7c42a21e5/c2pa.assertions/c2pa.hash.data",
"err_val":"None",
"validation_status":"Some(""assertion.hashedURI.match"")"
}
]
It seems that only the COSE signature is problematic.
I hope it helps!
@clementh59 Thank you for digging into this - I'll go over this with the Rust team shortly.
~As far as the issues you are seeing in cose_validator
above, do you see it happening from within JavaScript in the extension environment as well as in a web environment or is it just in the extension environment?~
Edit: Actually, I see the discrepancy in running in web via extension, and lines up with what you mentioned:
In a browser/web context, the full chain gets validated properly
In the extension code, it validates the first ps256 certificate, then stops at the es256
Continuing to look into this...
@clementh59 Looking into this further, it looks like SubtleCrypto.importKey() cannot be found during verification:
I'm not sure if this is an issue with the extension running this in a web worker - I tried calling these functions manually in the extension console and they seem to work, so I'm not sure what is failing in this context.
@dkozma I tried to call this function:
async function importKey(rawKey) {
try {
const key = await window.crypto.subtle.importKey(
"raw", // format of the key to import
rawKey, // the key in ArrayBuffer format
{ // algorithm the key will be used with
name: "AES-CBC",
},
false, // whether the key is extractable
["encrypt", "decrypt"] // usages the key can be used for
);
console.log("Key imported successfully:", key);
return key;
} catch (error) {
console.error("Error importing key:", error);
}
}
In the same function as I call the c2pa verifications and the function runs correctly. So the issue doesn't seem to be linked to the fact that it is being executed in a web worker...
Hi @dkozma, we finally found a workaround for not using the library in a sandbox, which means we are not impacted by this bug anymore. Thanks anyway for the time you dedicated to this!
Thanks for being persistent. We are a small team an do not have access to all possible build targets and platforms.
Hi,
When validating the manifest of this image: https://truepic.com/wp-content/uploads/2023/03/transparency_original-capture.jpg with the library, I get this issue:
However, when validating with c2patool, it accepts the signature:
Do you know where it could come from?
Thanks, Clément