How do I use crypto.subtle.importKey to verify the signature sent by Admob SSV ?

The key to verify the signature is available at https://gstatic.com/admob/reward/verifier-keys.json. I can't get importKey to work with it.
3 Replies
Deno AI Helper
Deno AI Helper6mo ago
I wasn't able to find the answer to your question in the official Deno documentation. However, based on your message, I can provide some general advice on using crypto.subtle.importKey in the context of web cryptography. When using crypto.subtle.importKey to import a key for signature verification, you typically need to specify the format of the key, the key data, the algorithm the key will be used with, whether the key is extractable, and the key usages. Here's a generic example of how you might use crypto.subtle.importKey:
const keyData = /* ... your key data ... */;
const algorithm = {
name: "RSASSA-PKCS1-v1_5",
hash: { name: "SHA-256" },
};

crypto.subtle.importKey(
"jwk", // the format of the key data, e.g., "jwk" for JSON Web Key
keyData, // the key data
algorithm, // the algorithm the key will be used with
true, // whether the key is extractable
["verify"] // the key usages
)
.then(publicKey => {
// You can now use publicKey to verify signatures
})
.catch(error => {
console.error(error);
});
const keyData = /* ... your key data ... */;
const algorithm = {
name: "RSASSA-PKCS1-v1_5",
hash: { name: "SHA-256" },
};

crypto.subtle.importKey(
"jwk", // the format of the key data, e.g., "jwk" for JSON Web Key
keyData, // the key data
algorithm, // the algorithm the key will be used with
true, // whether the key is extractable
["verify"] // the key usages
)
.then(publicKey => {
// You can now use publicKey to verify signatures
})
.catch(error => {
console.error(error);
});
Please note that the above code is a general example and may not directly apply to your situation. You will need to adjust the key format, algorithm, and other parameters according to the actual key and signature algorithm used by Admob SSV. Since this advice is not based on the Deno documentation, please double-check the accuracy of the advice given, and ensure that it aligns with the specifics of the Admob SSV signature verification process and the format of the keys provided by the URL you mentioned.
pyrote
pyrote6mo ago
It looks like that JSON file is using a P-256 key. It matches what OpenSSL generates for prime256v1. So to load the key, this is what you would do.
import { decodeBase64 } from "jsr:@std/encoding/base64";

const VERIFIER_KEYS_URL = 'https://gstatic.com/admob/reward/verifier-keys.json';

const verifier_keys_response = await fetch(VERIFIER_KEYS_URL);

// parse the response as JSON.
const key_data = await verifier_keys_response.json();

// load the key data from the base64 property in the JSON data.
const key_buffer = decodeBase64(key_data.keys[0].base64);

const imported_key = await crypto.subtle.importKey(
'spki',
key_buffer,
{ name: 'ECDSA', namedCurve: 'P-256' },
false,
['verify']
);
import { decodeBase64 } from "jsr:@std/encoding/base64";

const VERIFIER_KEYS_URL = 'https://gstatic.com/admob/reward/verifier-keys.json';

const verifier_keys_response = await fetch(VERIFIER_KEYS_URL);

// parse the response as JSON.
const key_data = await verifier_keys_response.json();

// load the key data from the base64 property in the JSON data.
const key_buffer = decodeBase64(key_data.keys[0].base64);

const imported_key = await crypto.subtle.importKey(
'spki',
key_buffer,
{ name: 'ECDSA', namedCurve: 'P-256' },
false,
['verify']
);
I don't have any signatures to test it with so can't be sure. Google should really include the algorithm in that verifier-keys.json file.
TheCarpetMerchant
TheCarpetMerchantOP6mo ago
Thanks ! "spki" was what was wrong in my setup. I can create the key, but the verification fails. ´´´ async function verify(queryUrl: string) { const {signature, key_id} = queryString.parse(queryUrl); let queryParamsString = queryUrl; if (queryParamsString.indexOf('?') > -1) { queryParamsString = queryUrl.split('?')[1]; } const contentToVerify = queryParamsString.substring(0, queryParamsString.indexOf('signature') -1); const googleKeyRes = await axios.get('https://gstatic.com/admob/reward/verifier-keys.json'); const {keys} = googleKeyRes.data; const base64Decoded = base64.decodeBase64(keys.find((e) => e['keyId'] == key_id).base64); const key = await crypto.subtle.importKey('spki', base64Decoded, {name: "ECDSA", namedCurve: 'P-256'}, true, ["verify"]); const encoder = new TextEncoder() const s = encoder.encode(signature); // "MEQCIG8sJ33C2t7D-0PeLIbO2rxH7c78MS3G3P8-b_KBuQCUAiBUhq06AFVfpDTr8XPaBtSsAGUNZAQysKuJiVuly90g8A" const d = encoder.encode(contentToVerify); // "ad_network=5450213213286189855&ad_unit=1234567890&reward_amount=1&reward_item=Reward&timestamp=1718045362304&transaction_id=123456789&user_id=1" const result = await crypto.subtle.verify({name: "ECDSA", hash: "SHA-256"}, key, s, d); if (result) { return true; } else { throw new Error('Invalid Signature Supplied'); } }; ´´´ This is according to https://developers.google.com/admob/android/ssv, which says to have the query content ("ad_network=" etc) as the content that's signed. I'm probably doing the encoding wrong I guess but I can't figure out what's wrong about it. It defaults to UTF8, from what I read and the admob doc uses UTF8 as well.