|
@@ -0,0 +1,420 @@
|
|
|
+Error.stackTraceLimit = Infinity;
|
|
|
+
|
|
|
+let urlPublicKeysMap = new Map();
|
|
|
+// Adapted code from https://stackoverflow.com/questions/36598638/generating-ecdh-keys-in-the-browser-through-webcryptoapi-instead-of-the-browseri
|
|
|
+const hex2Arr = str => {
|
|
|
+ if (!str) {
|
|
|
+ return new Uint8Array()
|
|
|
+ }
|
|
|
+ const arr = []
|
|
|
+ for (let i = 0, len = str.length; i < len; i+=2) {
|
|
|
+ arr.push(parseInt(str.substr(i, 2), 16))
|
|
|
+ }
|
|
|
+ return new Uint8Array(arr)
|
|
|
+}
|
|
|
+
|
|
|
+const buf2Hex = buf => {
|
|
|
+ return Array.from(new Uint8Array(buf))
|
|
|
+ .map(x => ('00' + x.toString(16)).slice(-2))
|
|
|
+ .join('')
|
|
|
+}
|
|
|
+
|
|
|
+function doVerify(msg1, sigval, pubkey) {
|
|
|
+ var curve = "secp256r1";
|
|
|
+ let sigalg = "SHA256withECDSA";
|
|
|
+ var sig = new KJUR.crypto.Signature({"alg": sigalg, "prov": "cryptojs/jsrsa"});
|
|
|
+ sig.init({xy: pubkey, curve: curve});
|
|
|
+ sig.updateHex(msg1); // updateHex for non-printable chars (updateString returns invalid signature); updateString if only printable chars.
|
|
|
+ var result = sig.verify(sigval);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+async function generateOwnKeypairAndDeriveKey(dh_ga_x, dh_ga_y)
|
|
|
+{
|
|
|
+ let alicePublicKey = hex2Arr('04' + dh_ga_x + dh_ga_y);
|
|
|
+ try {
|
|
|
+ const bobKey = await window.crypto.subtle.generateKey(
|
|
|
+ { name: 'ECDH', namedCurve: 'P-256' },
|
|
|
+ false, // no need to make Bob's private key exportable
|
|
|
+ ['deriveKey', 'deriveBits']);
|
|
|
+
|
|
|
+ const bobPublicKeyExported = await window.crypto.subtle.exportKey('raw', bobKey.publicKey);
|
|
|
+ const bobPublicKeyHex = buf2Hex(bobPublicKeyExported);
|
|
|
+
|
|
|
+ console.log(`Client's publicKey: ${bobPublicKeyHex}`);
|
|
|
+
|
|
|
+ const aliceKeyImported = await window.crypto.subtle.importKey(
|
|
|
+ 'raw',
|
|
|
+ alicePublicKey,
|
|
|
+ { name: 'ECDH', namedCurve: 'P-256'},
|
|
|
+ true,
|
|
|
+ []);
|
|
|
+
|
|
|
+ const sharedSecret = await window.crypto.subtle.deriveBits(
|
|
|
+ { name: 'ECDH', namedCurve: 'P-256', public: aliceKeyImported },
|
|
|
+ bobKey.privateKey,
|
|
|
+ 256);
|
|
|
+
|
|
|
+ const sharedSecretHex = buf2Hex(sharedSecret);
|
|
|
+ console.log(`sharedSecret: ${sharedSecretHex}`);
|
|
|
+
|
|
|
+ const hashOfSharedSecret = await window.crypto.subtle.digest("SHA-256", sharedSecret);
|
|
|
+ console.log("This is the hash" + buf2Hex(hashOfSharedSecret));
|
|
|
+
|
|
|
+ const ownPublicKeyArray=Array.from(hex2Arr(bobPublicKeyHex).slice(1));
|
|
|
+ const ownPublicKeyUnreadableString = ownPublicKeyArray.map(x => String.fromCharCode(x)).join('');
|
|
|
+ const ownPublicKeyBase64=btoa(ownPublicKeyUnreadableString);
|
|
|
+
|
|
|
+ return {mitigatorClientPublicKey: ownPublicKeyBase64, mitigatorDerivedKey: hashOfSharedSecret.slice(0, 16)};
|
|
|
+
|
|
|
+ // console.log(aes_key_handle);
|
|
|
+ // The code below is just for testing that indeed the hash was stored as the aes key. It will fail if the encryptable parameter in importKey (3rd one) is set to false.
|
|
|
+ // const aes_key = await window.crypto.subtle.exportKey(raw, aes_key_handle);
|
|
|
+ // console.log("This is the exported AES key: " + buf2Hex(aes_key));
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ console.log(err);
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function useDecryptorPublicKeyReturnOwnPublicKey(message) // sender
|
|
|
+{
|
|
|
+ // TODO: Lol how d'ya check the URL of the sender (sender["url"]) is the corresponding background JS page?
|
|
|
+ // Need to compare it to a URL that consists of the extension's ID..
|
|
|
+ // Background script only ever calls this when this is true, but still checking it anyway.
|
|
|
+ if(!("decryptorPublicKey" in message))
|
|
|
+ return {error: "could not find decryptorPublicKey"};
|
|
|
+
|
|
|
+ decryptorPublicKeyX=message["decryptorPublicKey"]["x"];
|
|
|
+ decryptorPublicKeyY=message["decryptorPublicKey"]["y"];
|
|
|
+ let returnValue = await generateOwnKeypairAndDeriveKey(decryptorPublicKeyX, decryptorPublicKeyY);
|
|
|
+ if(!("mitigatorClientPublicKey" in returnValue) || !("mitigatorDerivedKey" in returnValue))
|
|
|
+ return {error: "Oh no :( Could not generate the public key or the derived key"};
|
|
|
+ console.log("And this is the derived key.");
|
|
|
+ console.log(buf2Hex(returnValue.mitigatorDerivedKey));
|
|
|
+ return {"ownPublicKeyBase64": returnValue.mitigatorClientPublicKey, "derivedKey": returnValue.mitigatorDerivedKey};
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+function recursiveIncrementIV(iv, byteCounter, minByteCounter)
|
|
|
+{
|
|
|
+ if(iv[byteCounter] !== 0xff)
|
|
|
+ {
|
|
|
+ iv[byteCounter]++;
|
|
|
+ return {iv: iv};
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if(byteCounter === minByteCounter)
|
|
|
+ return {error: "Reached maximum number of messages. Plz regenerate a new key."};
|
|
|
+ else
|
|
|
+ return recursiveIncrementIV(iv, byteCounter-1, minByteCounter);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function incrementIV(iv)
|
|
|
+{
|
|
|
+ return recursiveIncrementIV(iv, 11, 8);
|
|
|
+}
|
|
|
+*/
|
|
|
+async function regenerateOwnPublicKey(input)
|
|
|
+{
|
|
|
+ if(!input.decryptorPublicKeyObj && !input.url)
|
|
|
+ {
|
|
|
+ console.log("No decryptor public key or URL: dont know which website to regenerate own public key, derived key for");
|
|
|
+ return {error: "No decryptor public key or URL: dont know which website to regenerate own public key, derived key for"};
|
|
|
+ }
|
|
|
+ let decryptorPublicKeyObj;
|
|
|
+
|
|
|
+ if(input.url)
|
|
|
+ {
|
|
|
+ returnValue=getDecryptorPublicKeyForUrl(input.url);
|
|
|
+ if(returnValue.error)
|
|
|
+ return returnValue;
|
|
|
+ decryptorPublicKeyObj=returnValue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ decryptorPublicKeyObj=input.decryptorPublicKeyObj;
|
|
|
+
|
|
|
+ returnValue=await useDecryptorPublicKeyReturnOwnPublicKey(decryptorPublicKeyObj);
|
|
|
+ console.log(returnValue);
|
|
|
+ if(!returnValue.ownPublicKeyBase64 || !returnValue.derivedKey)
|
|
|
+ {
|
|
|
+ let string3="Could not derive own public key or the derivedkey";
|
|
|
+ console.log(string3 + returnValue);
|
|
|
+ return {error: string3 + returnValue};
|
|
|
+ }
|
|
|
+
|
|
|
+ decryptorPublicKeyObj.ownPublicKey=returnValue.ownPublicKeyBase64;
|
|
|
+ decryptorPublicKeyObj.derivedKey = returnValue.derivedKey;
|
|
|
+ let iv=new Uint8Array(12);
|
|
|
+ window.crypto.getRandomValues(iv);
|
|
|
+ decryptorPublicKeyObj.iv = iv;
|
|
|
+ urlPublicKeysMap.set(input.url, decryptorPublicKeyObj);
|
|
|
+ return {success: "Successfully set own public key"};
|
|
|
+}
|
|
|
+
|
|
|
+async function processMitigatorPublicKeyHeader(e)
|
|
|
+{
|
|
|
+ const headers=e.responseHeaders;
|
|
|
+ const mitigatorHeader = headers.find(function(element) {
|
|
|
+ return element.name === "Mitigator-Public-Key";
|
|
|
+ });
|
|
|
+
|
|
|
+ if(mitigatorHeader === undefined)
|
|
|
+ {
|
|
|
+ const string="Either Mitigator is not supported or this function is called on headers for other objects fetched along with the webpage.";
|
|
|
+ console.log(string);
|
|
|
+ return {log: string};
|
|
|
+ }
|
|
|
+
|
|
|
+ let url;
|
|
|
+ if(e.documentUrl === undefined) // user opened a new tab.
|
|
|
+ url=e.url;
|
|
|
+ else
|
|
|
+ url=e.documentUrl;
|
|
|
+ url=url.split('/').slice(0, 3).join('/').concat('/');
|
|
|
+
|
|
|
+ //urlPublicKeysMap.set(url, undefined);
|
|
|
+
|
|
|
+ console.log("Here's the header value:");
|
|
|
+ console.log(mitigatorHeader["value"]);
|
|
|
+
|
|
|
+ let returnValue = verifyHeaderRetrieveDecryptorPublicKey(mitigatorHeader["value"]);
|
|
|
+ if (!returnValue.decryptorPublicKey)
|
|
|
+ {
|
|
|
+ console.log("Could not find decryptor public key:");
|
|
|
+ return {error: "Could not find decryptor public key:"};
|
|
|
+ }
|
|
|
+ const decryptorPublicKeyObj=returnValue;
|
|
|
+ urlPublicKeysMap.set(url, decryptorPublicKeyObj);
|
|
|
+
|
|
|
+ returnValue = await regenerateOwnPublicKey({decryptorPublicKeyObj: decryptorPublicKeyObj});
|
|
|
+ console.log(returnValue);
|
|
|
+}
|
|
|
+
|
|
|
+async function encryptFieldsForUrl(url, fields_array)
|
|
|
+{
|
|
|
+ let returnValue = getDerivedKeyIvForUrl(url);
|
|
|
+ if(returnValue.error)
|
|
|
+ {
|
|
|
+ console.log(returnValue);
|
|
|
+ return returnValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ //console.log("Encrypting");
|
|
|
+ const derivedKey=returnValue.derivedKey;
|
|
|
+ let iv=returnValue.iv;
|
|
|
+ let ciphertextArray = [];
|
|
|
+
|
|
|
+ const algo = { name: "AES-GCM", iv: iv };
|
|
|
+
|
|
|
+ const aes_key_handle = await window.crypto.subtle.importKey("raw", derivedKey,
|
|
|
+ algo,
|
|
|
+ false, // true, //whether the key is extractable (i.e. can be used in exportKey)
|
|
|
+ ["encrypt"] //can "encrypt", "decrypt", "wrapKey", or "unwrapKey"
|
|
|
+ );
|
|
|
+ // TODO: Error catching here
|
|
|
+
|
|
|
+ for (field of fields_array) {
|
|
|
+ let fieldAscii = new Uint8Array(field.length);
|
|
|
+ for (let i=0;i<field.length; i++)
|
|
|
+ {
|
|
|
+ fieldAscii[i] = field.charCodeAt(i);
|
|
|
+ }
|
|
|
+ console.log("About to encrypt this:");
|
|
|
+ console.log(field);
|
|
|
+ console.log("In ASCII:");
|
|
|
+ console.log(fieldAscii.toString());
|
|
|
+ try {
|
|
|
+ const ciphertextAndTag = await crypto.subtle.encrypt(algo, aes_key_handle, fieldAscii);
|
|
|
+ const ciphertextAndTagUint8 = new Uint8Array(ciphertextAndTag);
|
|
|
+ let ciphertext = ciphertextAndTagUint8.slice(0,-16);
|
|
|
+ let tag = ciphertextAndTagUint8.slice(-16);
|
|
|
+ let ciphertextIVTag = Array.of(...ciphertext, ...iv, ...tag);
|
|
|
+ console.log(ciphertextIVTag);
|
|
|
+ let ciphertextIVTagBase64 = btoa(ciphertextIVTag.map(x => String.fromCharCode(x)).join(''));
|
|
|
+ console.log(ciphertextIVTagBase64);
|
|
|
+ ciphertextArray.push(ciphertextIVTagBase64);
|
|
|
+ }
|
|
|
+ catch(err)
|
|
|
+ {
|
|
|
+ console.log(err);
|
|
|
+ console.log("Something went wrong when encrypting the fields.");
|
|
|
+ return {error: "Something went wrong when encrypting the fields."};
|
|
|
+ }
|
|
|
+ // To change the IV for each field.
|
|
|
+ window.crypto.getRandomValues(iv);
|
|
|
+ returnValue.iv = iv;
|
|
|
+ }
|
|
|
+ return {ciphertextFields: ciphertextArray};
|
|
|
+}
|
|
|
+
|
|
|
+// TODO: PUT THE CODE BELOW INTO ANOTHER FUNCTION, CALL IT AND THEN CALL THE CONTENT SCRIPT TO SET
|
|
|
+// DERIVED KEY, OWN PUBLIC KEY, WHICH THEN CALLS BACK TO SET ITS OWN PUBLIC KEY OVER HERE.
|
|
|
+function verifyHeaderRetrieveDecryptorPublicKey(mitigatorHeaderValue) {
|
|
|
+
|
|
|
+ console.log("Verifying header");
|
|
|
+ let decryptorLongTermPublicKey = "04e2e1449dece8b8cf759b2f52541a9ff28eee70449cfb67807079b04f6d2a10e36dd1f3735ebfc95e1a7a4162c6eede07a8969bd1ff5850008614325002a882c3";
|
|
|
+ //"04
|
|
|
+ //950b235cda836f8660885a9aabe5816fc8142c4912bd44f65c278c1749081061
|
|
|
+ //abad883ca934f8cc11c6c74205d2c9e55285a5696f075d1440a091e3bd6f0d81";
|
|
|
+ // this function does **not** return an array of bytes. It returns a *string* of potentially non-printable characters- it converts the array of bytes into their ASCII representation.
|
|
|
+ const decodedData = atob(mitigatorHeaderValue);
|
|
|
+
|
|
|
+ // converting it into an array of strings of hex representation of bytes.
|
|
|
+ const decodedDataArray = [];
|
|
|
+ for (let i = 0; i < decodedData.length; i++) {
|
|
|
+ decodedDataArray[i] = decodedData.charCodeAt(i).toString(16).padStart(2, '0');
|
|
|
+ }
|
|
|
+
|
|
|
+ const signature_r = decodedDataArray.slice(96,128).join('');
|
|
|
+ const signature_s = decodedDataArray.slice(128).join('');
|
|
|
+ let signature = "30440220";
|
|
|
+ signature=signature + signature_r + "0220" + signature_s;
|
|
|
+ const signature_data = decodedDataArray.slice(0,96).join('');
|
|
|
+
|
|
|
+ const result = doVerify(signature_data, signature, decryptorLongTermPublicKey);
|
|
|
+ if(result)
|
|
|
+ console.log("Yay dude it works wtf");
|
|
|
+ else
|
|
|
+ {
|
|
|
+ console.log("Damn, dude, it doesn't.");
|
|
|
+ // return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const verifier_mrenclave = decodedDataArray.slice(64).join('');
|
|
|
+ // TODO: Add verifier mrenclave checking code.
|
|
|
+ // Extracting public key components in a hex string format.
|
|
|
+ const public_key_x=decodedDataArray.slice(0,32).join('');
|
|
|
+ const public_key_y=decodedDataArray.slice(32,64).join('');
|
|
|
+
|
|
|
+ const urlDecryptorPublicKeyObject = {decryptorPublicKey: {x: public_key_x, y: public_key_y}};
|
|
|
+ return urlDecryptorPublicKeyObject;
|
|
|
+}
|
|
|
+
|
|
|
+function setMitigatorClientPublicKeyHeader(e) {
|
|
|
+ let headers = e.requestHeaders;
|
|
|
+ let returnValue = getOwnPublicKeyForUrl(e.url);
|
|
|
+ if(returnValue.ownPublicKey)
|
|
|
+ {
|
|
|
+ let clientHeaderObj={name: "mitigator-client-public-key", value:returnValue["ownPublicKey"] };
|
|
|
+ headers.push(clientHeaderObj);
|
|
|
+ console.log("Setting client's public key");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ console.log(returnValue);
|
|
|
+ return {requestHeaders: headers};
|
|
|
+}
|
|
|
+
|
|
|
+function getDecryptorPublicKeyForUrl(url)
|
|
|
+{
|
|
|
+ const searchUrl = url.split('/').slice(0, 3).join('/').concat('/');
|
|
|
+ const hasSearchUrl = urlPublicKeysMap.has(searchUrl);
|
|
|
+ let urlPublicKeyObj;
|
|
|
+ if(hasSearchUrl)
|
|
|
+ {
|
|
|
+ urlPublicKeyObj= urlPublicKeysMap.get(searchUrl);
|
|
|
+ return {decryptorPublicKey: urlPublicKeyObj["decryptorPublicKey"]};
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return {error: "Could not find this URL"};
|
|
|
+}
|
|
|
+/*
|
|
|
+function setOwnPublicKeyForUrl(url, ownPublicKeyBase64)
|
|
|
+{
|
|
|
+ const searchUrl = url.split('/').slice(0, 3).join('/').concat('/');
|
|
|
+ const hasSearchUrl = urlPublicKeysMap.has(searchUrl);
|
|
|
+ if(hasSearchUrl)
|
|
|
+ {
|
|
|
+ let urlPublicKeyObj= urlPublicKeysMap.get(searchUrl);
|
|
|
+ urlPublicKeyObj["ownPublicKey"]=ownPublicKeyBase64;
|
|
|
+ urlPublicKeysMap.set(searchUrl, urlPublicKeyObj);
|
|
|
+ return {success: "Successfully set own public key"};
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return { error: "Could not find this URL"};
|
|
|
+}
|
|
|
+*/
|
|
|
+function getOwnPublicKeyForUrl(url)
|
|
|
+{
|
|
|
+ const searchUrl = url.split('/').slice(0, 3).join('/').concat('/');
|
|
|
+ const hasSearchUrl = urlPublicKeysMap.has(searchUrl);
|
|
|
+ let returnValue;
|
|
|
+ if(hasSearchUrl)
|
|
|
+ {
|
|
|
+ returnValue=urlPublicKeysMap.get(searchUrl);
|
|
|
+ if(returnValue.ownPublicKey)
|
|
|
+ return {ownPublicKey:returnValue["ownPublicKey"]};
|
|
|
+ else {
|
|
|
+ return {error: "Own public key has not been set by content script yet."}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return {error: "Could not find this URL"};
|
|
|
+}
|
|
|
+
|
|
|
+function getDerivedKeyIvForUrl(url)
|
|
|
+{
|
|
|
+ const searchUrl = url.split('/').slice(0, 3).join('/').concat('/');
|
|
|
+ const hasSearchUrl = urlPublicKeysMap.has(searchUrl);
|
|
|
+ let returnValue;
|
|
|
+ if(hasSearchUrl)
|
|
|
+ {
|
|
|
+ returnValue=urlPublicKeysMap.get(searchUrl);
|
|
|
+ if(returnValue.derivedKey && returnValue.iv)
|
|
|
+ return {derivedKey:returnValue.derivedKey, iv:returnValue.iv};
|
|
|
+ else {
|
|
|
+ return {error: "Derived key, IV have not been set by content script yet."}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return {error: "Could not find this URL"};
|
|
|
+}
|
|
|
+// from the content script and reply back with the decryptor's public key or to set own public key.
|
|
|
+
|
|
|
+async function backgroundListener(message)
|
|
|
+{
|
|
|
+ // if("getDecryptorPublicKey" in message)
|
|
|
+ // return returnValue= getDecryptorPublicKeyForUrl(message["url"]);
|
|
|
+
|
|
|
+ // if("setOwnPublicKey" in message)
|
|
|
+ // return setOwnPublicKeyForUrl(message["url"], message["setOwnPublicKey"]);
|
|
|
+ let returnValue; let ownPublicKey;
|
|
|
+ if(!message.url)
|
|
|
+ {
|
|
|
+ returnValue={error: "No URL sent: cant check if Mitigator is supported on the content script site."};
|
|
|
+ console.log(returnValue.error);
|
|
|
+ return returnValue;
|
|
|
+ }
|
|
|
+ if(message.regenerateOwnPublicKey) // TODO: Set a home_url property in the url object and if the sent_url and home_url are different, then regenerate.
|
|
|
+ {
|
|
|
+ returnValue = await regenerateOwnPublicKey({url: message.url});
|
|
|
+ console.log(returnValue);
|
|
|
+ if(returnValue.error)
|
|
|
+ return returnValue;
|
|
|
+ }
|
|
|
+ if(message.fields)
|
|
|
+ returnValue=await encryptFieldsForUrl(message.url, message.fields);
|
|
|
+ if(message.getOwnPublicKey)
|
|
|
+ {
|
|
|
+ ownPublicKey=getOwnPublicKeyForUrl(message.url);
|
|
|
+ returnValue.ownPublicKey=ownPublicKey;
|
|
|
+ }
|
|
|
+ return returnValue;
|
|
|
+}
|
|
|
+
|
|
|
+browser.webRequest.onHeadersReceived.addListener(
|
|
|
+ processMitigatorPublicKeyHeader,
|
|
|
+ {urls: ["http://*/*"]},
|
|
|
+ ["responseHeaders"]
|
|
|
+);
|
|
|
+
|
|
|
+browser.webRequest.onBeforeSendHeaders.addListener(
|
|
|
+ setMitigatorClientPublicKeyHeader,
|
|
|
+ {urls: ["http://*/*"]},
|
|
|
+ ["blocking", "requestHeaders"]
|
|
|
+);
|
|
|
+
|
|
|
+browser.runtime.onMessage.addListener(backgroundListener);
|