mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-10 07:07:57 -05:00
Merge pull request #16 from tlsnotary/firefox_verifycert
Firefox verifycert
This commit is contained in:
11
bootstrap.js
vendored
11
bootstrap.js
vendored
@@ -206,11 +206,22 @@ function loadjs(){
|
||||
include(addon, "jsbn2.js");
|
||||
include(addon, "pako.js");
|
||||
include(addon, "tlsn.js");
|
||||
include(addon, "notification_bar.js");
|
||||
include(addon, "testing/testing.js");
|
||||
include(addon, "testing/manager_test.js");
|
||||
include(addon, "verifychain/buffer.js2");
|
||||
include(addon, "verifychain/asn1.js2");
|
||||
include(addon, "verifychain/jsrsasign-latest-all-min.js2");
|
||||
include(addon, "verifychain/rootcertslist.js");
|
||||
include(addon, "verifychain/rootcerts.js");
|
||||
include(addon, "verifychain/verifychain.js");
|
||||
include(addon, "testdriver.js");
|
||||
}
|
||||
|
||||
|
||||
function shutdown(data, reason) {
|
||||
gBrowser.removeProgressListener(myListener);
|
||||
Services.obs.removeObserver(httpRequestBlocker, "http-on-modify-request");
|
||||
Services.ww.unregisterNotification(windowWatcher);
|
||||
eachWindow(unloadFromWindow);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@ var tabs = {};
|
||||
var appId = "oclohfdjoojomkfddjclanpogcnjhemd"; //id of the helper app
|
||||
var is_chrome = true;
|
||||
var fsRootPath; //path to local storage root, e.g. filesystem:chrome-extension://abcdabcd/persistent
|
||||
var manager_path; //manager.html which was copied into Downloads/ dir
|
||||
|
||||
|
||||
function getPref(value, type){
|
||||
function getPref(pref, type){
|
||||
return new Promise(function(resolve, reject) {
|
||||
chrome.storage.local.get(value, function(items){
|
||||
if (!items.hasOwnProperty('first')) resolve("undefined");
|
||||
resolve(items[value]);
|
||||
chrome.storage.local.get(pref, function(obj){
|
||||
if (Object.keys(obj).length === 0){
|
||||
resolve('undefined');
|
||||
return;
|
||||
}
|
||||
else {
|
||||
resolve(obj[pref]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -17,7 +22,9 @@ function getPref(value, type){
|
||||
|
||||
function setPref(pref, type, value){
|
||||
return new Promise(function(resolve, reject) {
|
||||
chrome.storage.local.set({pref:value}, function(){
|
||||
var obj = {};
|
||||
obj[pref] = value;
|
||||
chrome.storage.local.set(obj, function(){
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -25,23 +32,55 @@ function setPref(pref, type, value){
|
||||
|
||||
|
||||
function import_reliable_sites(){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState != 4)
|
||||
return;
|
||||
import_resource('pubkeys.txt')
|
||||
.then(function(text_ba){
|
||||
parse_reliable_sites(ba2str(text_ba));
|
||||
});
|
||||
}
|
||||
|
||||
if (xhr.responseText) {
|
||||
parse_reliable_sites(xhr.responseText);
|
||||
//we can import chrome:// and file:// URL
|
||||
function import_resource(filename, isFileURI){
|
||||
if (typeof(isFileURI) === 'undefined'){
|
||||
isFileURI = false;
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState != 4)
|
||||
return;
|
||||
|
||||
if (xhr.response) {
|
||||
resolve(ab2ba(xhr.response));
|
||||
}
|
||||
};
|
||||
var path = isFileURI ? filename : chrome.extension.getURL('content/'+toFilePath(filename));
|
||||
xhr.open('get', path, true);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//converts an array of file names into a string with correct slashes
|
||||
function toFilePath(pathArray){
|
||||
if (typeof(pathArray) === 'string') return pathArray;
|
||||
var expanded = '';
|
||||
for(var i=0; i < pathArray.length; i++){
|
||||
expanded += pathArray[i];
|
||||
//not trailing slash for last element
|
||||
if (i < (pathArray.length-1) ){
|
||||
expanded += '/';
|
||||
}
|
||||
};
|
||||
xhr.open('get', chrome.extension.getURL('content/pubkeys.txt'), true);
|
||||
xhr.send();
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
|
||||
function startListening(){
|
||||
console.log('tab listener started')
|
||||
chrome.webRequest.onSendHeaders.addListener(
|
||||
function(details) {
|
||||
console.log('in tab listener', details);
|
||||
if (details.type === "main_frame"){
|
||||
tabs[details.tabId] = details;
|
||||
}
|
||||
@@ -64,11 +103,10 @@ function getHeaders(){
|
||||
}
|
||||
var tab = tabs[t[0].id];
|
||||
var x = tab.url.split('/');
|
||||
var host = x[2];
|
||||
var host = x[2].split(':')[0];
|
||||
x.splice(0,3);
|
||||
var tab_url = x.join('/');
|
||||
var headers = '';
|
||||
headers += tab.method + " /" + tab_url + " HTTP/1.1" + "\r\n";
|
||||
var resource_url = x.join('/');
|
||||
var headers = tab.method + " /" + resource_url + " HTTP/1.1" + "\r\n";
|
||||
headers += "Host: " + host + "\r\n";
|
||||
for (var i = 0; i < tab.requestHeaders.length; i++){
|
||||
var h = tab.requestHeaders[i];
|
||||
@@ -77,7 +115,12 @@ function getHeaders(){
|
||||
if (tab.method == "GET"){
|
||||
headers += "\r\n";
|
||||
}
|
||||
resolve({'headers':headers, 'server':host});
|
||||
var port = 443;
|
||||
if (tab.url.split(':').length === 3){
|
||||
//the port is explicitely provided in URL
|
||||
port = parseInt(tab.url.split(':')[2].split('/')[0]);
|
||||
}
|
||||
resolve({'headers':headers, 'server':host, 'port':port});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -90,7 +133,7 @@ function loadBusyIcon(){
|
||||
|
||||
|
||||
function loadNormalIcon(){
|
||||
chrome.browserAction.setIcon({path:"content/icon128.png"});
|
||||
chrome.browserAction.setIcon({path:"icon.png"});
|
||||
chrome.browserAction.setPopup({popup:"content/chrome/popup.html"});
|
||||
}
|
||||
|
||||
@@ -99,9 +142,11 @@ function browser_specific_init(){
|
||||
window.webkitRequestFileSystem(window.PERSISTENT, 50*1024*1024, function(fs){
|
||||
fsRootPath = fs.root.toURL();
|
||||
});
|
||||
chrome.storage.local.get('valid_hashes', function(items){
|
||||
if (!items.hasOwnProperty('first')) return;
|
||||
valid_hashes = items.valid_hashes;
|
||||
getPref('valid_hashes')
|
||||
.then(function(hashes){
|
||||
if (hashes !== 'undefined'){
|
||||
valid_hashes = hashes;
|
||||
}
|
||||
});
|
||||
chrome.runtime.getPlatformInfo(function(p){
|
||||
if(p.os === "win"){
|
||||
@@ -109,11 +154,46 @@ function browser_specific_init(){
|
||||
}
|
||||
});
|
||||
//put icon into downloads dir. This is the icon for injected notification
|
||||
//put manager files also there so we could inject code to get the manager's DOM when testing
|
||||
//(Chrome forbids injecting into chrome-extension://* URIs but allows into file://* URIs)
|
||||
chrome.downloads.setShelfEnabled(false);
|
||||
setTimeout(function(){chrome.downloads.setShelfEnabled(true);}, 2000);
|
||||
chrome.downloads.download({url:chrome.extension.getURL("content/icon16.png"),
|
||||
conflictAction:'overwrite',
|
||||
filename:'pagesigner.tmp.dir/icon16.png'});
|
||||
var files_to_copy = ['icon16.png', 'manager.css', 'manager.html', 'manager.js2', 'sweetalert.css', 'sweetalert.min.js2', 'check.png', 'cross.png'];
|
||||
var copied_so_far = 0;
|
||||
for (var i=0; i < files_to_copy.length; i++){
|
||||
chrome.downloads.download(
|
||||
{url:chrome.extension.getURL('content/' + files_to_copy[i]),
|
||||
conflictAction:'overwrite',
|
||||
filename:'pagesigner.tmp.dir/' + files_to_copy[i]},
|
||||
function(downloadID){
|
||||
|
||||
var erase_when_download_completed = function(id){
|
||||
chrome.downloads.search({id:id}, function(item){
|
||||
if (item[0].state !== 'complete'){
|
||||
setTimeout(function(){
|
||||
erase_when_download_completed(id)
|
||||
}, 100);
|
||||
}
|
||||
else {
|
||||
if (item[0].filename.endsWith('manager.html')){
|
||||
var path = item[0].filename;
|
||||
if (os_win){
|
||||
path = '/' + encodeURI(path.replace(/\\/g, '/'));
|
||||
}
|
||||
manager_path = 'file://' + path;
|
||||
}
|
||||
//dont litter the Downloads menu
|
||||
chrome.downloads.erase({id:downloadID});
|
||||
copied_so_far++;
|
||||
if (copied_so_far === files_to_copy.length){
|
||||
chrome.downloads.setShelfEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
erase_when_download_completed(downloadID);
|
||||
});
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(function(data){
|
||||
if (data.destination !== 'extension') return;
|
||||
@@ -128,8 +208,14 @@ function browser_specific_init(){
|
||||
verify_tlsn_and_show_data(data.args.data, true);
|
||||
}
|
||||
else if (data.message === 'export'){
|
||||
chrome.downloads.download({url:fsRootPath+data.args.dir+'/pgsg.pgsg',
|
||||
if (testing){
|
||||
chrome.downloads.download({url:fsRootPath+data.args.dir+'/pgsg.pgsg',
|
||||
'saveAs':false, filename:'pagesigner.tmp.dir/' + data.args.file+'.pgsg'});
|
||||
}
|
||||
else {
|
||||
chrome.downloads.download({url:fsRootPath+data.args.dir+'/pgsg.pgsg',
|
||||
'saveAs':true, filename:data.args.file+'.pgsg'});
|
||||
}
|
||||
}
|
||||
else if (data.message === 'notarize'){
|
||||
startNotarizing();
|
||||
@@ -181,48 +267,7 @@ function browser_specific_init(){
|
||||
}
|
||||
|
||||
|
||||
function getModulus(cert){
|
||||
var c = Certificate.decode(new Buffer(cert), 'der');
|
||||
var pk = c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data;
|
||||
var pkba = ua2ba(pk);
|
||||
//expected modulus length 256, 384, 512
|
||||
var modlen = 256;
|
||||
if (pkba.length > 384) modlen = 384;
|
||||
if (pkba.length > 512) modlen = 512;
|
||||
var modulus = pkba.slice(pkba.length - modlen - 5, pkba.length -5);
|
||||
return modulus;
|
||||
}
|
||||
|
||||
function permutator(inputArr) {
|
||||
var results = [];
|
||||
|
||||
function permute(arr, memo) {
|
||||
var cur, memo = memo || [];
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
cur = arr.splice(i, 1);
|
||||
if (arr.length === 0) {
|
||||
results.push(memo.concat(cur));
|
||||
}
|
||||
permute(arr.slice(), memo.concat(cur));
|
||||
arr.splice(i, 0, cur[0]);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
return permute(inputArr);
|
||||
}
|
||||
|
||||
function verifyCert(chain){
|
||||
var chainperms = permutator(chain);
|
||||
for (var i=0; i < chainperms.length; i++){
|
||||
if (verifyCertChain(chainperms[i])){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function makeSessionDir(server, is_imported){
|
||||
@@ -361,12 +406,12 @@ function openTabs(sdir){
|
||||
chrome.tabs.reload(id, {bypassCache:true}, function(){
|
||||
//this callback triggers too early sometimes. Wait to make sure page reloaded
|
||||
setTimeout(function(){
|
||||
chrome.tabs.insertCSS(id, {file: 'content/chrome/injectbar.css'}, function(a){
|
||||
chrome.tabs.executeScript(id, {file: 'content/chrome/injectbar.js'}, function(a){
|
||||
chrome.tabs.executeScript(id, {code:
|
||||
'document.getElementById("domainName").textContent="' + commonName.toString() + '";' +
|
||||
'var sdir ="' + dirname.toString() + '";'});
|
||||
});
|
||||
chrome.tabs.executeScript(id, {file: 'content/notification_bar.js'}, function(a){
|
||||
chrome.tabs.executeScript(id, {code:
|
||||
'viewTabDocument = document;' +
|
||||
'install_bar();' +
|
||||
'document.getElementById("domainName").textContent="' + commonName.toString() + '";' +
|
||||
'document["pagesigner-session-dir"]="' + dirname.toString() + '";'});
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
@@ -376,18 +421,6 @@ function openTabs(sdir){
|
||||
}
|
||||
|
||||
|
||||
function getCommonName(cert){
|
||||
var c = Certificate.decode(new Buffer(cert), 'der');
|
||||
var fields = c.tbsCertificate.subject.value;
|
||||
for (var i=0; i < fields.length; i++){
|
||||
if (fields[i][0].type.toString() !== [2,5,4,3].toString()) continue;
|
||||
//first 2 bytes are DER-like metadata
|
||||
return ba2str(fields[i][0].value.slice(2));
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
|
||||
function Socket(name, port){
|
||||
this.name = name;
|
||||
this.port = port;
|
||||
@@ -467,7 +500,7 @@ Socket.prototype.recv = function(){
|
||||
if (! rv.is_complete){
|
||||
console.log("check_complete_records failed");
|
||||
buf = rv.incomprecs;
|
||||
setTimeout(check, 100);
|
||||
setTimeout(function(){check()}, 100);
|
||||
return;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
@@ -476,7 +509,7 @@ Socket.prototype.recv = function(){
|
||||
return;
|
||||
}
|
||||
console.log('Another timeout in recv');
|
||||
setTimeout(check, 100);
|
||||
setTimeout(function(){check()}, 100);
|
||||
});
|
||||
};
|
||||
check();
|
||||
@@ -492,16 +525,15 @@ function get_xhr(){
|
||||
}
|
||||
|
||||
function openManager(){
|
||||
var url = chrome.extension.getURL('content/manager.html');
|
||||
//re-focus tab if manager already open
|
||||
chrome.tabs.query({}, function(tabs){
|
||||
for(var i=0; i < tabs.length; i++){
|
||||
if (tabs[i].url.startsWith(url)){
|
||||
if (tabs[i].url === manager_path){
|
||||
chrome.tabs.update(tabs[i].id, {active:true});
|
||||
return;
|
||||
}
|
||||
}
|
||||
chrome.tabs.create({url:url});
|
||||
chrome.tabs.create({url:manager_path});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -527,8 +559,22 @@ function renamePGSG(dir, newname){
|
||||
|
||||
|
||||
function sendMessage(data){
|
||||
chrome.runtime.sendMessage({'destination':'manager',
|
||||
'data':data});
|
||||
//get the manager tab and inject the data into it
|
||||
chrome.tabs.query({}, function(tabs){
|
||||
for(var i=0; i < tabs.length; i++){
|
||||
if (tabs[i].url === manager_path){
|
||||
var jsonstring = JSON.stringify(data);
|
||||
chrome.tabs.executeScript(tabs[i].id, {code:
|
||||
'var idiv = document.getElementById("extension2manager");' +
|
||||
//seems like chrome unstringifies jsonstring, so we stringify it again
|
||||
'var json = JSON.stringify(' + jsonstring + ');' +
|
||||
'console.log("json is", json);' +
|
||||
'idiv.textContent = json;'+
|
||||
'idiv.click();'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -574,6 +620,8 @@ function getDirEntry(dirName){
|
||||
.then(function(rootDirEntry){
|
||||
rootDirEntry.getDirectory(dirName, {}, function(dirEntry){
|
||||
resolve(dirEntry);
|
||||
}, function(what){
|
||||
reject(what);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -650,7 +698,7 @@ function sendAlert(alertData){
|
||||
alert("You can only notarize pages which start with https://");
|
||||
return;
|
||||
}
|
||||
chrome.tabs.executeScript(tabs[0].id, {file:"content/sweetalert.min.js"}, function(){
|
||||
chrome.tabs.executeScript(tabs[0].id, {file:"content/sweetalert.min.js2"}, function(){
|
||||
chrome.tabs.insertCSS(tabs[0].id, {file:"content/sweetalert.css"}, function(){
|
||||
chrome.tabs.executeScript(tabs[0].id, {code:"swal("+ JSON.stringify(alertData) +")"});
|
||||
});
|
||||
|
||||
9
content/chrome/inject_into_manager.js
Normal file
9
content/chrome/inject_into_manager.js
Normal file
@@ -0,0 +1,9 @@
|
||||
console.log("injecting some code");
|
||||
document.addEventListener("hello", function(evt) {
|
||||
var data = evt.detail;
|
||||
console.log("got hello with", data);
|
||||
chrome.runtime.sendMessage(data);
|
||||
});
|
||||
|
||||
//this element is accessible by content scripts and by page javascript
|
||||
document.getElementById('content_script_injected_into_page').textContent = 'true';
|
||||
@@ -1,10 +0,0 @@
|
||||
.visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 2s linear;
|
||||
}
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
-webkit-transition: visibility 0s 2s, opacity 2s linear;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
var table = document.createElement("table");
|
||||
table.style.position = "fixed";
|
||||
table.style.top = "0px";
|
||||
table.style.left = "100px";
|
||||
table.style.background = "rgba(242, 241, 240, 0.9)";
|
||||
table.style.width = "80%";
|
||||
table.style.height = "32px";
|
||||
table.className = "hidden";
|
||||
var row = document.createElement("tr");
|
||||
var cell1 = document.createElement("td");
|
||||
var cell2 = document.createElement("td");
|
||||
var cell3 = document.createElement("td");
|
||||
cell3.style.align = "right";
|
||||
var img = document.createElement("img");
|
||||
img.src = "icon16.png";
|
||||
var text = document.createElement("text");
|
||||
text.textContent = "PageSigner successfully verified that the webpage below was received from ";
|
||||
var domain = document.createElement("text");
|
||||
domain.id = "domainName";
|
||||
var button = document.createElement("button");
|
||||
button.id = "viewRaw";
|
||||
button.textContent = "View raw data with HTTP headers";
|
||||
button.style.MozBorderRadius = "4px";
|
||||
button.style.WebkitBorderRadius = "4px";
|
||||
button.style.borderRadius = "4px";
|
||||
button.onclick = function(){
|
||||
chrome.runtime.sendMessage({destination:'extension', message:'viewraw', args:{dir:sdir}});
|
||||
}
|
||||
cell3.appendChild(button)
|
||||
cell2.appendChild(text);
|
||||
cell2.appendChild(domain);
|
||||
cell1.appendChild(img);
|
||||
row.appendChild(cell1);
|
||||
row.appendChild(cell2);
|
||||
row.appendChild(cell3);
|
||||
table.appendChild(row);
|
||||
document.body.appendChild(table);
|
||||
|
||||
setTimeout(function(){
|
||||
//make a transition to visible
|
||||
table.className = "visible";
|
||||
}, 0);
|
||||
@@ -46,7 +46,7 @@ img {
|
||||
|
||||
<table style="width:100%">
|
||||
<tr class="border_bottom">
|
||||
<td id="notarize"><img src="../icon.png"></img>Notarize this page</td>
|
||||
<td id="notarize"><img src="../../icon.png"></img>Notarize this page</td>
|
||||
</tr>
|
||||
<tr class="border_bottom">
|
||||
<td id="manage"><img src="../manage.png"></img>Manage files</td>
|
||||
@@ -55,7 +55,7 @@ img {
|
||||
<td id="import"><img src="../verify.png"></img>Import .pgsg file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="about"><img src="../icon.png"></img>About</td>
|
||||
<td id="about"><img src="../../icon.png"></img>About</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -30,3 +30,11 @@ document.getElementById("about").addEventListener("click",
|
||||
left:500})
|
||||
window.close();
|
||||
});
|
||||
|
||||
//if this file is opened in a tab during testing, it will have a hash appended to the URL
|
||||
setTimeout(function(){
|
||||
var hash = window.location.hash;
|
||||
if (hash === "#manage"){
|
||||
document.getElementById('manage').click();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
let prompts = Services.prompt;
|
||||
let prefs = Services.prefs;
|
||||
var testing_import_path; //used only in testing
|
||||
|
||||
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||
PREFS_BRANCH = Services.prefs.getBranch("extensions.pagesigner.button-position."),
|
||||
@@ -13,27 +14,30 @@ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||
|
||||
let main = {
|
||||
notarize: function() {
|
||||
if (testing){
|
||||
startTesting();
|
||||
}
|
||||
else {
|
||||
startNotarizing();
|
||||
}
|
||||
startNotarizing();
|
||||
},
|
||||
verify: function() {
|
||||
function importPath(path){
|
||||
OS.File.read(path)
|
||||
.then(function(imported_data){
|
||||
var data_ba = ua2ba(imported_data);
|
||||
verify_tlsn_and_show_data(data_ba, true);
|
||||
populateTable(); //TODO, why again? v_t_a_s_d already does that
|
||||
});
|
||||
};
|
||||
|
||||
if (testing){
|
||||
importPath(testing_import_path);
|
||||
return;
|
||||
}
|
||||
|
||||
const nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||
.createInstance(nsIFilePicker);
|
||||
fp.init(window, "Select the .pgsg file you want to import and verify", nsIFilePicker.modeOpen);
|
||||
var rv = fp.show();
|
||||
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
|
||||
var path = fp.file.path;
|
||||
OS.File.read(path).
|
||||
then(function(imported_data){
|
||||
var data_ba = ua2ba(imported_data);
|
||||
verify_tlsn_and_show_data(data_ba, true);
|
||||
populateTable();
|
||||
});
|
||||
importPath(fp.file.path);
|
||||
}
|
||||
},
|
||||
manage: function() {
|
||||
|
||||
@@ -20,7 +20,7 @@ var atob = win.atob;
|
||||
var JSON = win.JSON;
|
||||
var is_chrome = false;
|
||||
var fsRootPath; //path to pagesigner folder in FF profile dir
|
||||
|
||||
var manager_path; //manager.html which was copied into profile's pagesigner dir
|
||||
|
||||
function getPref(prefname, type){
|
||||
return new Promise(function(resolve, reject) {
|
||||
@@ -53,13 +53,64 @@ function setPref(prefname, type, value){
|
||||
|
||||
|
||||
function import_reliable_sites(){
|
||||
OS.File.read(OS.Path.join(OS.Constants.Path.profileDir,"extensions","pagesigner@tlsnotary","content","pubkeys.txt"), { encoding: "utf-8" }).
|
||||
then(function onSuccess(text) {
|
||||
parse_reliable_sites(text);
|
||||
import_resource('pubkeys.txt')
|
||||
.then(function(ba) {
|
||||
parse_reliable_sites(ba2str(ba));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//converts an array of file names into a string with correct slashes
|
||||
function toFilePath(pathArray){
|
||||
if (typeof(pathArray) === 'string') return pathArray;
|
||||
var expanded = '';
|
||||
for(var i=0; i < pathArray.length; i++){
|
||||
expanded = OS.Path.join(pathArray, dirName[i]);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
|
||||
//reads from addon content folder but also can read an arbitrary file://
|
||||
function import_resource(filename, isFileURI){
|
||||
if (typeof(isFileURI) === 'undefined'){
|
||||
isFileURI = false;
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
var path = 'content';
|
||||
if (typeof(filename) === 'string'){
|
||||
path += '/'+filename;
|
||||
}
|
||||
else {
|
||||
for (var i=0; i < filename.length; i++){
|
||||
path += '/'+filename[i];
|
||||
}
|
||||
}
|
||||
|
||||
path = isFileURI ? filename : thisaddon.getResourceURI(path).spec;
|
||||
var xhr = get_xhr();
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState != 4)
|
||||
return;
|
||||
|
||||
if (xhr.response) {
|
||||
resolve(ab2ba(xhr.response));
|
||||
}
|
||||
};
|
||||
xhr.open('get', path, true);
|
||||
xhr.send();
|
||||
|
||||
/*
|
||||
OS.File.read(OS.Path.fromFileURI(thisaddon.getResourceURI(path).spec))
|
||||
.then(function onSuccess(ba) {
|
||||
//returns Uint8Array which is compatible with our internal byte array
|
||||
resolve(ba);
|
||||
});
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function startListening(){
|
||||
//from now on, we will check the security status of all loaded tabs
|
||||
@@ -91,11 +142,10 @@ function getHeaders(){
|
||||
//passed tests, secure, grab headers, update status bar and start audit:
|
||||
var x = sanitized_url.split('/');
|
||||
x.splice(0,3);
|
||||
var tab_url = x.join('/');
|
||||
var resource_url = x.join('/');
|
||||
|
||||
var httpChannel = dict_of_httpchannels[sanitized_url];
|
||||
var headers = "";
|
||||
headers += httpChannel.requestMethod + " /" + tab_url + " HTTP/1.1" + "\r\n";
|
||||
var headers = httpChannel.requestMethod + " /" + resource_url + " HTTP/1.1" + "\r\n";
|
||||
httpChannel.visitRequestHeaders(function(header,value){
|
||||
headers += header +": " + value + "\r\n";});
|
||||
if (httpChannel.requestMethod == "GET"){
|
||||
@@ -114,8 +164,13 @@ function getHeaders(){
|
||||
//FF's uploaddata contains Content-Type and Content-Length headers + '\r\n\r\n' + http body
|
||||
headers += uploaddata;
|
||||
}
|
||||
var server = headers.split('\r\n')[1].split(':')[1].replace(/ /g,'');
|
||||
resolve({'headers':headers, 'server':server});
|
||||
var host = headers.split('\r\n')[1].split(':')[1].replace(/ /g,'');
|
||||
var port = 443;
|
||||
if (tab_url_full.split(':').length === 3){
|
||||
//the port is explicitely provided in URL
|
||||
port = parseInt(tab_url_full.split(':')[2].split('/')[0]);
|
||||
}
|
||||
resolve({'headers':headers, 'server':host, 'port':port});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,130 +194,186 @@ function browser_specific_init(){
|
||||
});
|
||||
|
||||
fsRootPath = OS.Path.join(OS.Constants.Path.profileDir, "pagesigner");
|
||||
manager_path = OS.Path.toFileURI(OS.Path.join(fsRootPath, "manager.html"));
|
||||
|
||||
//copy the manager file to local filesystem for security + takes care of some odd behaviour
|
||||
//when trying to add eventListener to chrome:// resources
|
||||
var contentDir = OS.Path.join(OS.Constants.Path.profileDir,"extensions","pagesigner@tlsnotary","content");
|
||||
var html = OS.Path.join(contentDir, "manager.html");
|
||||
var js = OS.Path.join(contentDir, "manager.js");
|
||||
var css = OS.Path.join(contentDir, "manager.css");
|
||||
var check = OS.Path.join(contentDir, "check.png");
|
||||
var cross = OS.Path.join(contentDir, "cross.png");
|
||||
var swalcss = OS.Path.join(contentDir, "sweetalert.css");
|
||||
var swaljs = OS.Path.join(contentDir, "sweetalert.min.js");
|
||||
var html = OS.Path.fromFileURI(thisaddon.getResourceURI('content/manager.html').spec);
|
||||
var js = OS.Path.fromFileURI(thisaddon.getResourceURI('content/manager.js2').spec);
|
||||
var css = OS.Path.fromFileURI(thisaddon.getResourceURI('content/manager.css').spec);
|
||||
var check = OS.Path.fromFileURI(thisaddon.getResourceURI('content/check.png').spec);
|
||||
var cross = OS.Path.fromFileURI(thisaddon.getResourceURI('content/cross.png').spec);
|
||||
var swalcss = OS.Path.fromFileURI(thisaddon.getResourceURI('content/sweetalert.css').spec);
|
||||
var swaljs = OS.Path.fromFileURI(thisaddon.getResourceURI('content/sweetalert.min.js2').spec);
|
||||
var icon = OS.Path.fromFileURI(thisaddon.getResourceURI('content/icon16.png').spec);
|
||||
|
||||
var dest_html = OS.Path.join(fsRootPath, "manager.html");
|
||||
var dest_js = OS.Path.join(fsRootPath, "manager.js");
|
||||
var dest_js = OS.Path.join(fsRootPath, "manager.js2");
|
||||
var dest_css = OS.Path.join(fsRootPath, "manager.css");
|
||||
var dest_check = OS.Path.join(fsRootPath, "check.png");
|
||||
var dest_cross = OS.Path.join(fsRootPath, "cross.png");
|
||||
var dest_swalcss = OS.Path.join(fsRootPath, "sweetalert.css");
|
||||
var dest_swaljs = OS.Path.join(fsRootPath, "sweetalert.min.js");
|
||||
var dest_swaljs = OS.Path.join(fsRootPath, "sweetalert.min.js2");
|
||||
var dest_icon = OS.Path.join(fsRootPath, "icon16.png");
|
||||
|
||||
OS.File.makeDir(fsRootPath, {ignoreExisting:true})
|
||||
.then(function(){
|
||||
OS.File.copy(html, dest_html);
|
||||
})
|
||||
.then(function(){
|
||||
return OS.File.copy(js, dest_js, {noOverwrite:true});
|
||||
return OS.File.copy(js, dest_js);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(css, dest_css, {noOverwrite:true});
|
||||
OS.File.copy(css, dest_css);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(check, dest_check, {noOverwrite:true});
|
||||
OS.File.copy(check, dest_check);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(cross, dest_cross, {noOverwrite:true});
|
||||
OS.File.copy(cross, dest_cross);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(swalcss, dest_swalcss, {noOverwrite:true});
|
||||
OS.File.copy(swalcss, dest_swalcss);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(swaljs, dest_swaljs, {noOverwrite:true});
|
||||
OS.File.copy(swaljs, dest_swaljs);
|
||||
})
|
||||
.then(function(){
|
||||
OS.File.copy(icon, dest_icon);
|
||||
});
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
var idiv;
|
||||
var listener;
|
||||
var d;
|
||||
function openManager(){
|
||||
//if manager is open, focus it
|
||||
var idiv, listener, managerDocument; //these must be global otherwise we'll get no events
|
||||
function openManager(is_loading){
|
||||
if (typeof(is_loading) === 'undefined'){
|
||||
is_loading = false;
|
||||
}
|
||||
var t;
|
||||
var was_manager_open = false;
|
||||
var tabs = gBrowser.tabs;
|
||||
for(var i=0; i < tabs.length; i++){
|
||||
var url = gBrowser.getBrowserForTab(tabs[i]).contentWindow.location.href;
|
||||
if (url.search('/pagesigner/manager.html') > -1){
|
||||
gBrowser.selectedTab = tabs[i];
|
||||
if (url == manager_path){
|
||||
t = tabs[i];
|
||||
if (! is_loading){
|
||||
//on Win7 i was getting 'load' event even when I clicked another tab
|
||||
//we want to select the tab only if manager was called from the menu
|
||||
gBrowser.selectedTab = t;
|
||||
}
|
||||
was_manager_open = true;
|
||||
}
|
||||
}
|
||||
if (was_manager_open && (gBrowser.getBrowserForTab(t).contentWindow.document === managerDocument)){
|
||||
console.log('ignoring the same managerDocument in tab');
|
||||
return;
|
||||
}
|
||||
|
||||
var promise;
|
||||
if (was_manager_open && !is_loading){
|
||||
//this may be a dangling manager from previous browser session
|
||||
//if so, then reload it
|
||||
if (gBrowser.getBrowserForTab(t).contentWindow.document !== managerDocument){
|
||||
console.log('detected a dangling manager tab');
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
else {
|
||||
console.log('focusing existing manager');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Promise.resolve() //dont want to indent the whole .then() section
|
||||
.then(function(){
|
||||
var uri = OS.Path.join(fsRootPath, "manager.html");
|
||||
var t = gBrowser.addTab(uri);
|
||||
gBrowser.selectedTab = t;
|
||||
else if (was_manager_open && is_loading){
|
||||
|
||||
var readyListener = function(e){
|
||||
//will trigger on reload (sometimes triggers twice but this should not affect us)
|
||||
d = gBrowser.getBrowserForTab(e.target).contentWindow.document;
|
||||
|
||||
var install_listener = function(d){
|
||||
listener = d.getElementById('manager2extension');
|
||||
idiv = d.getElementById('extension2manager');
|
||||
if (!listener){ //maybe the DOM hasnt yet loaded
|
||||
setTimeout(function(){
|
||||
install_listener(d);
|
||||
}, 100);
|
||||
return;
|
||||
console.log('reloading existing manager');
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
else if (!was_manager_open){
|
||||
promise = new Promise(function(resolve, reject) {
|
||||
console.log('opening a new manager');
|
||||
t = gBrowser.addTab(manager_path);
|
||||
gBrowser.selectedTab = t;
|
||||
function check_uri(){
|
||||
if (gBrowser.getBrowserForTab(t).contentWindow.location.href !== manager_path){
|
||||
console.log('data tab href not ready, waiting');
|
||||
setTimeout(function(){check_uri();}, 100);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
|
||||
var onEvent = function(){
|
||||
console.log('in click event');
|
||||
if (listener.textContent === '') return;//spurious click
|
||||
var data = JSON.parse(listener.textContent);
|
||||
listener.textContent = '';
|
||||
if (data.destination !== 'extension') return;
|
||||
if (data.message === 'refresh'){
|
||||
populateTable();
|
||||
}
|
||||
else if (data.message === 'export'){
|
||||
var path = OS.Path.join(fsRootPath, data.args.dir, 'pgsg.pgsg');
|
||||
console.log('saving full path', path);
|
||||
savePGSGFile(path, data.args.file);
|
||||
}
|
||||
else if (data.message === 'delete'){
|
||||
OS.File.removeDir(OS.Path.join(fsRootPath, data.args.dir))
|
||||
.then(function(){
|
||||
populateTable();
|
||||
});
|
||||
}
|
||||
else if (data.message === 'rename'){
|
||||
//to update dir's modtime, we remove the file and recreate it
|
||||
writeFile(data.args.dir, "meta", str2ba(data.args.newname), true)
|
||||
.then(function(){
|
||||
populateTable();
|
||||
});
|
||||
}
|
||||
else if (data.message === 'viewdata'){
|
||||
openTabs(data.args.dir);
|
||||
}
|
||||
else if (data.message === 'viewraw'){
|
||||
var path = OS.Path.join(fsRootPath, data.args.dir, 'raw.txt');
|
||||
gBrowser.selectedTab = gBrowser.addTab(path);
|
||||
}
|
||||
};
|
||||
listener.addEventListener('click', onEvent);
|
||||
onEvent(); //maybe the page asked for refresh before listener installed
|
||||
};
|
||||
|
||||
install_listener(d);
|
||||
check_uri();
|
||||
});
|
||||
}
|
||||
|
||||
promise
|
||||
.then(function(){
|
||||
//DOM may not be available immediately
|
||||
managerDocument = gBrowser.getBrowserForTab(t).contentWindow.document;
|
||||
return new Promise(function(resolve, reject) {
|
||||
function wait_for_DOM(){
|
||||
listener = managerDocument.getElementById('manager2extension');
|
||||
idiv = managerDocument.getElementById('extension2manager');
|
||||
if (listener && idiv){
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
console.log('manager DOM not ready yet, waiting');
|
||||
setTimeout(function(){wait_for_DOM()}, 100);
|
||||
}
|
||||
}
|
||||
wait_for_DOM();
|
||||
});
|
||||
})
|
||||
.then(function(){
|
||||
function onEvent(){
|
||||
console.log('in click event');
|
||||
if (listener.textContent === '') return;//spurious click
|
||||
var data = JSON.parse(listener.textContent);
|
||||
listener.textContent = '';
|
||||
if (data.destination !== 'extension') return;
|
||||
if (data.message === 'refresh'){
|
||||
populateTable();
|
||||
}
|
||||
else if (data.message === 'export'){
|
||||
var path = OS.Path.join(fsRootPath, data.args.dir, 'pgsg.pgsg');
|
||||
console.log('saving full path', path);
|
||||
savePGSGFile(path, data.args.file);
|
||||
}
|
||||
else if (data.message === 'delete'){
|
||||
OS.File.removeDir(OS.Path.join(fsRootPath, data.args.dir))
|
||||
.then(function(){
|
||||
populateTable();
|
||||
});
|
||||
}
|
||||
else if (data.message === 'rename'){
|
||||
//to update dir's modtime, we remove the file and recreate it
|
||||
writeFile(data.args.dir, "meta", str2ba(data.args.newname), true)
|
||||
.then(function(){
|
||||
populateTable();
|
||||
});
|
||||
}
|
||||
else if (data.message === 'viewdata'){
|
||||
var dir = OS.Path.join(fsRootPath, data.args.dir);
|
||||
openTabs(dir);
|
||||
}
|
||||
else if (data.message === 'viewraw'){
|
||||
var path = OS.Path.join(fsRootPath, data.args.dir, 'raw.txt');
|
||||
gBrowser.selectedTab = gBrowser.addTab(path);
|
||||
}
|
||||
};
|
||||
|
||||
t.addEventListener('load', readyListener);
|
||||
listener.addEventListener('click', onEvent);
|
||||
onEvent(); //maybe the page asked for refresh before listener installed
|
||||
|
||||
//add tab event listener which will trigger when user reloads the tab ie with F5
|
||||
function onLoadEvent(e){
|
||||
console.log('in tab load event handler');
|
||||
openManager(true);
|
||||
};
|
||||
if (!was_manager_open){
|
||||
//installed only once on first tab load
|
||||
t.addEventListener('load', onLoadEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -296,14 +407,17 @@ function getDirEntry(dirName){
|
||||
OS.File.stat(path)
|
||||
.then(function(stat){
|
||||
resolve(stat);
|
||||
})
|
||||
.catch(function(what){
|
||||
reject(what);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getName(obj){
|
||||
//XXX this is not x-platform
|
||||
return obj.path.split('/').pop();
|
||||
var delimiter = os_win ? '\\' : '/';
|
||||
return obj.path.split(delimiter).pop();
|
||||
}
|
||||
|
||||
|
||||
@@ -358,6 +472,24 @@ function sendMessage(data){
|
||||
|
||||
|
||||
function savePGSGFile(existing_path, name){
|
||||
var copyFile = function(src, dst){
|
||||
OS.File.copy(src, dst)
|
||||
.then(function(){
|
||||
console.log("File write OK");
|
||||
},
|
||||
function (e){
|
||||
console.log("Caught error writing file: "+e);
|
||||
});
|
||||
};
|
||||
|
||||
if (testing){
|
||||
var dldir = Cc["@mozilla.org/file/directory_service;1"].
|
||||
getService(Ci.nsIProperties).get("DfltDwnld", Ci.nsIFile).path;
|
||||
var dst = OS.Path.join(dldir, 'pagesigner.tmp.dir', name + '.pgsg');
|
||||
copyFile(existing_path, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
var nsIFilePicker = Ci.nsIFilePicker;
|
||||
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
fp.init(window, "Save PageSigner file As", nsIFilePicker.modeSave);
|
||||
@@ -366,17 +498,8 @@ function savePGSGFile(existing_path, name){
|
||||
fp.defaultString = name + ".pgsg";
|
||||
var rv = fp.show();
|
||||
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
|
||||
var path = fp.file.path;
|
||||
//write the file
|
||||
let promise = OS.File.copy(existing_path, fp.file.path);
|
||||
promise.then(function(){
|
||||
console.log("File write OK");
|
||||
},
|
||||
function (e){
|
||||
console.log("Caught error writing file: "+e);
|
||||
}
|
||||
);
|
||||
}
|
||||
copyFile(existing_path, fp.file.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -387,56 +510,6 @@ function showAboutInfo(){
|
||||
}
|
||||
|
||||
|
||||
//extracts modulus from PEM certificate
|
||||
function getModulus(cert){
|
||||
const nsIX509Cert = Ci.nsIX509Cert;
|
||||
const nsIX509CertDB = Ci.nsIX509CertDB;
|
||||
const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
|
||||
let certdb = Cc[nsX509CertDB].getService(nsIX509CertDB);
|
||||
let cert_obj = certdb.constructX509FromBase64(b64encode(cert));
|
||||
const nsASN1Tree = "@mozilla.org/security/nsASN1Tree;1";
|
||||
const nsIASN1Tree = Ci.nsIASN1Tree;
|
||||
var hexmodulus = "";
|
||||
|
||||
var certDumpTree = Cc[nsASN1Tree].createInstance(nsIASN1Tree);
|
||||
certDumpTree.loadASN1Structure(cert_obj.ASN1Structure);
|
||||
var modulus_str = certDumpTree.getDisplayData(12);
|
||||
if (! modulus_str.startsWith( "Modulus (" ) ){
|
||||
//most likely an ECC certificate
|
||||
alert ("Unfortunately this website is not compatible with PageSigner. (could not parse RSA certificate)");
|
||||
return;
|
||||
}
|
||||
var lines = modulus_str.split('\n');
|
||||
var line = "";
|
||||
for (var i = 1; i<lines.length; ++i){
|
||||
line = lines[i];
|
||||
//an empty line is where the pubkey part ends
|
||||
if (line === "") {break;}
|
||||
//remove all whitespaces (g is a global flag)
|
||||
hexmodulus += line.replace(/\s/g, '');
|
||||
}
|
||||
return hex2ba(hexmodulus);
|
||||
}
|
||||
|
||||
|
||||
//verify the certificate against Firefox's certdb
|
||||
function verifyCert(chain){
|
||||
const nsIX509Cert = Ci.nsIX509Cert;
|
||||
const nsIX509CertDB = Ci.nsIX509CertDB;
|
||||
const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
|
||||
let certdb = Cc[nsX509CertDB].getService(nsIX509CertDB);
|
||||
let cert_obj = certdb.constructX509FromBase64(b64encode(chain[0]));
|
||||
let a = {}, b = {};
|
||||
let retval = certdb.verifyCertNow(cert_obj, nsIX509Cert.CERT_USAGE_SSLServerWithStepUp, nsIX509CertDB.FLAG_LOCAL_ONLY, a, b);
|
||||
if (retval === 0){ //success
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function dumpSecurityInfo(channel,urldata) {
|
||||
// Do we have a valid channel argument?
|
||||
if (! channel instanceof Ci.nsIChannel) {
|
||||
@@ -478,23 +551,19 @@ var httpRequestBlocker = {
|
||||
notificationCallbacks = httpChannel.notificationCallbacks;
|
||||
}
|
||||
else if (httpChannel.loadGroup && httpChannel.loadGroup.notificationCallbacks) {
|
||||
notificationCallbacks = httpChannel.loadGroup.notificationCallbacks;
|
||||
notificationCallbacks = httpChannel.loadGroup.notificationCallbacks;
|
||||
}
|
||||
else {
|
||||
console.log('no notificationCallbacks');
|
||||
return;
|
||||
}
|
||||
var path = notificationCallbacks.getInterface(Components.interfaces.nsIDOMWindow).top.location.pathname;
|
||||
} catch (e){
|
||||
console.log('no interface');
|
||||
return; //xhr dont have any interface
|
||||
}
|
||||
if (block_urls.indexOf(path) > -1){
|
||||
console.log('found matching tab, blocking request', path);
|
||||
httpChannel.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
return;
|
||||
}
|
||||
console.log('not blocking request', path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -567,8 +636,10 @@ function makeSessionDir(server, is_imported){
|
||||
imported_str = "-IMPORTED";
|
||||
}
|
||||
var newdir = OS.Path.join(localDir, time+'-'+server+imported_str);
|
||||
OS.File.makeDir(newdir);
|
||||
resolve(newdir);
|
||||
OS.File.makeDir(newdir)
|
||||
.then(function(){
|
||||
resolve(newdir);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -590,23 +661,24 @@ function writeFile(dirName, fileName, data, is_update){
|
||||
}
|
||||
return new Promise(function(resolve, reject) {
|
||||
var path = OS.Path.join(fsRootPath, dirName, fileName);
|
||||
var promise;
|
||||
if (is_update){
|
||||
promise = OS.File.remove(path);
|
||||
}
|
||||
else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
var promise = is_update ? OS.File.remove(path) : Promise.resolve();
|
||||
promise
|
||||
.then(function(){
|
||||
return OS.File.open(path, {create:true});
|
||||
})
|
||||
.then(function(f){
|
||||
return f.close();
|
||||
})
|
||||
.then(function(){
|
||||
return OS.File.writeAtomic(path, ba2ua(data));
|
||||
})
|
||||
.then(function(){
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch(function(e){
|
||||
console.log('caught error in writeFile', e);
|
||||
alert('error in writeFile');
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -625,6 +697,8 @@ function openTabs(sdir){
|
||||
}
|
||||
|
||||
var commonName;
|
||||
var dataFileURI;
|
||||
var t;
|
||||
getFileContent(sdir, "metaDomainName")
|
||||
.then(function(data_ba){
|
||||
commonName = ba2str(data_ba);
|
||||
@@ -633,27 +707,70 @@ function openTabs(sdir){
|
||||
.then(function(data){
|
||||
var name = ba2str(data);
|
||||
var data_path = OS.Path.join(fsRootPath, sdir, name);
|
||||
dataFileURI = OS.Path.toFileURI(data_path);
|
||||
block_urls.push(data_path);
|
||||
var t = gBrowser.addTab(data_path);
|
||||
t = gBrowser.addTab(data_path);
|
||||
gBrowser.selectedTab = t;
|
||||
setTimeout(function(){
|
||||
gBrowser.getBrowserForTab(t).reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
|
||||
}, 500);
|
||||
install_notification(t, commonName, raw_path);
|
||||
//resolve when URI is ours
|
||||
console.log('opening data tab');
|
||||
return new Promise(function(resolve, reject) {
|
||||
function check_uri(){
|
||||
if (gBrowser.getBrowserForTab(t).contentWindow.location.href !== dataFileURI){
|
||||
console.log('data tab href not ready, waiting');
|
||||
setTimeout(function(){check_uri();}, 100);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
check_uri();
|
||||
});
|
||||
})
|
||||
.then(function(){
|
||||
//reload the tab and check URI
|
||||
console.log('reloading data tab');
|
||||
//set a token on old document object
|
||||
gBrowser.getBrowserForTab(t).contentWindow.document['pagesigner-before-reload'] = true;
|
||||
gBrowser.getBrowserForTab(t).reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
|
||||
//after reload FF marks previous document as [dead Object],
|
||||
return new Promise(function(resolve, reject) {
|
||||
function check_new_document(){
|
||||
var doc = gBrowser.getBrowserForTab(t).contentWindow.document;
|
||||
if (doc.hasOwnProperty('pagesigner-before-reload')){
|
||||
console.log('data tab href not ready, waiting');
|
||||
setTimeout(function(){check_new_document();}, 100);
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
check_new_document();
|
||||
});
|
||||
})
|
||||
.then(function(){
|
||||
viewTabDocument = gBrowser.getBrowserForTab(t).contentWindow.document;
|
||||
//even though .document is immediately available, its .body property may not be
|
||||
return new Promise(function(resolve, reject) {
|
||||
function wait_for_body(){
|
||||
if (viewTabDocument.body === null){
|
||||
console.log('body not available, waiting');
|
||||
setTimeout(function(){wait_for_body()}, 100);
|
||||
}
|
||||
else {
|
||||
console.log('viewTabDocument is ', viewTabDocument);
|
||||
console.log('body is ', viewTabDocument.body);
|
||||
install_bar();
|
||||
viewTabDocument.getElementById("domainName").textContent = commonName;
|
||||
viewTabDocument['pagesigner-session-dir'] = sdir;
|
||||
console.log('injected stuff into viewTabDocument');
|
||||
}
|
||||
};
|
||||
wait_for_body();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getCommonName(cert){
|
||||
const nsIX509Cert = Ci.nsIX509Cert;
|
||||
const nsIX509CertDB = Ci.nsIX509CertDB;
|
||||
const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
|
||||
let certdb = Cc[nsX509CertDB].getService(nsIX509CertDB);
|
||||
let cert_obj = certdb.constructX509FromBase64(b64encode(cert));
|
||||
return cert_obj.commonName;
|
||||
}
|
||||
|
||||
|
||||
function Socket(name, port){
|
||||
this.name = name;
|
||||
this.port = port;
|
||||
|
||||
BIN
content/icon.png
BIN
content/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 962 B |
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -6,7 +6,8 @@ var previous_session_start_time; // used to make sure user doesnt exceed rate li
|
||||
var chosen_notary;
|
||||
var tdict = {};
|
||||
var valid_hashes = [];
|
||||
var os_win = false; //is OS windows? used to fix / in paths
|
||||
var os_win = false; //is OS windows? used to fix / in paths
|
||||
var browser_init_finished = false; //signal to test script when it can start
|
||||
|
||||
|
||||
function init(){
|
||||
@@ -58,6 +59,7 @@ function init(){
|
||||
}
|
||||
import_reliable_sites();
|
||||
startListening();
|
||||
browser_init_finished = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,14 +116,12 @@ function parse_reliable_sites(text){
|
||||
if ( (extime - now) < 1000*60*60*24*90){
|
||||
continue;
|
||||
}
|
||||
reliable_sites.push( {'name':name, 'expires':expires, 'modulus':modulus} );
|
||||
reliable_sites.push( {'name':name, 'port':443, 'expires':expires, 'modulus':modulus} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//callback is used in testing to signal when this page's n10n finished
|
||||
function startNotarizing(callback){
|
||||
if (! oracles_intact){
|
||||
@@ -130,16 +130,17 @@ function startNotarizing(callback){
|
||||
}
|
||||
var modulus;
|
||||
var certsha256;
|
||||
var headers;
|
||||
var server;
|
||||
var headers, server, port, chain;
|
||||
getHeaders()
|
||||
.then(function(obj){
|
||||
headers = obj.headers;
|
||||
server = obj.server;
|
||||
port = obj.port;
|
||||
loadBusyIcon();
|
||||
return get_certificate(server);
|
||||
return get_certificate(server, port);
|
||||
})
|
||||
.then(function(chain){
|
||||
.then(function(certchain){
|
||||
chain = certchain;
|
||||
if (! verifyCert(chain)){
|
||||
sendAlert({title:"PageSigner error", text:"This website cannot be audited by PageSigner because it presented an untrusted certificate"});
|
||||
return;
|
||||
@@ -177,7 +178,7 @@ function startNotarizing(callback){
|
||||
});
|
||||
})
|
||||
.then(function(args){
|
||||
return start_audit(modulus, certsha256, server, headers, args[0], args[1], args[2]);
|
||||
return start_audit(modulus, certsha256, server, port, headers, args[0], args[1], args[2]);
|
||||
})
|
||||
.then(function(args2){
|
||||
return save_session_and_open_data(args2, server);
|
||||
@@ -530,7 +531,7 @@ function process_entries(pgsg_subdirs){
|
||||
}
|
||||
Promise.all(promises)
|
||||
.then(function(){
|
||||
console.log('about to sendTable', tdict);
|
||||
//console.log('about to sendTable', tdict);
|
||||
sendTable();
|
||||
})
|
||||
.catch(function(e){
|
||||
@@ -615,16 +616,65 @@ function fixWinPath(path){
|
||||
}
|
||||
|
||||
|
||||
//startsWith is a pretty recent method, let's implement it here if it's not provided
|
||||
//reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
|
||||
if (!String.prototype.startsWith) {
|
||||
String.prototype.startsWith = function(searchString, position) {
|
||||
position = position || 0;
|
||||
return this.lastIndexOf(searchString, position) === position;
|
||||
};
|
||||
function getModulus(cert){
|
||||
var c = Certificate.decode(new Buffer(cert), 'der');
|
||||
var pk = c.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey.data;
|
||||
var pkba = ua2ba(pk);
|
||||
//expected modulus length 256, 384, 512
|
||||
var modlen = 256;
|
||||
if (pkba.length > 384) modlen = 384;
|
||||
if (pkba.length > 512) modlen = 512;
|
||||
var modulus = pkba.slice(pkba.length - modlen - 5, pkba.length -5);
|
||||
return modulus;
|
||||
}
|
||||
|
||||
|
||||
function getCommonName(cert){
|
||||
var c = Certificate.decode(new Buffer(cert), 'der');
|
||||
var fields = c.tbsCertificate.subject.value;
|
||||
for (var i=0; i < fields.length; i++){
|
||||
if (fields[i][0].type.toString() !== [2,5,4,3].toString()) continue;
|
||||
//first 2 bytes are DER-like metadata
|
||||
return ba2str(fields[i][0].value.slice(2));
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
|
||||
function permutator(inputArr) {
|
||||
var results = [];
|
||||
|
||||
function permute(arr, memo) {
|
||||
var cur, memo = memo || [];
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
cur = arr.splice(i, 1);
|
||||
if (arr.length === 0) {
|
||||
results.push(memo.concat(cur));
|
||||
}
|
||||
permute(arr.slice(), memo.concat(cur));
|
||||
arr.splice(i, 0, cur[0]);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
return permute(inputArr);
|
||||
}
|
||||
|
||||
|
||||
function verifyCert(chain){
|
||||
var chainperms = permutator(chain);
|
||||
for (var i=0; i < chainperms.length; i++){
|
||||
if (verifyCertChain(chainperms[i])){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//This must be at the bottom, otherwise we'd have to define each function
|
||||
//before it gets used.
|
||||
browser_specific_init();
|
||||
|
||||
@@ -39,8 +39,8 @@ a {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script src="manager.js"></script>
|
||||
<script src="sweetalert.min.js"></script>
|
||||
<script src="manager.js2"></script>
|
||||
<script src="sweetalert.min.js2"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,25 +4,29 @@ if (navigator.userAgent.search('Chrome') > -1){
|
||||
}
|
||||
var idiv;
|
||||
|
||||
|
||||
function onload(){
|
||||
if (is_chrome){
|
||||
chrome.runtime.onMessage.addListener(
|
||||
function(request, sender, sendResponse) {
|
||||
if (request.destination !== 'manager') return;
|
||||
console.log('got request', request);
|
||||
process_data(request.data);
|
||||
});
|
||||
}
|
||||
else {
|
||||
idiv = document.getElementById('extension2manager');
|
||||
idiv.addEventListener('click', function(e){
|
||||
console.log('changed to', idiv.textContent);
|
||||
if (idiv.textContent === '') return; //spurious clicks can happen
|
||||
var data = JSON.parse(idiv.textContent);
|
||||
process_data(data);
|
||||
});
|
||||
}
|
||||
//These divs are used only in testing. We cant use a variable because Chrome
|
||||
//content scripts cant access vars, only DOM elements
|
||||
var div = document.createElement('div');
|
||||
div.id = 'content_script_injected_into_page';
|
||||
div.textContent = 'false';
|
||||
div.style.display = 'none';
|
||||
document.body.appendChild(div);
|
||||
|
||||
var div2 = document.createElement('div');
|
||||
div2.id = 'table_populated';
|
||||
div2.textContent = 'false';
|
||||
div2.style.display = 'none';
|
||||
document.body.appendChild(div2);
|
||||
//----------END OF TESTING DIVS----------
|
||||
|
||||
idiv = document.getElementById('extension2manager');
|
||||
idiv.addEventListener('click', function(e){
|
||||
console.log('changed to', idiv.textContent);
|
||||
if (idiv.textContent === '') return; //spurious clicks can happen
|
||||
var data = JSON.parse(idiv.textContent);
|
||||
process_data(data);
|
||||
});
|
||||
sendMessage({'destination':'extension', 'message':'refresh'});
|
||||
}
|
||||
|
||||
@@ -51,6 +55,8 @@ function process_data(rows){
|
||||
'verifier':r.verifier,
|
||||
'dir':r.dir});
|
||||
}
|
||||
document.getElementById('table_populated').textContent = 'true';
|
||||
table_populated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,13 +134,12 @@ function addRow(args){
|
||||
img.height = 30;
|
||||
img.width = 30;
|
||||
var label;
|
||||
var extrapath = is_chrome ? '/content/' : '';
|
||||
if (args.valid){
|
||||
img.src = extrapath + 'check.png';
|
||||
img.src = 'check.png';
|
||||
label = 'valid';
|
||||
}
|
||||
else {
|
||||
img.src = extrapath + 'cross.png';
|
||||
img.src = 'cross.png';
|
||||
label = 'invalid';
|
||||
}
|
||||
text = document.createElement("text");
|
||||
@@ -186,6 +191,7 @@ function doRename(t, oldname, dir){
|
||||
},
|
||||
function(new_name){
|
||||
if(!(isValid(new_name))){
|
||||
console.log('detected invalid name', new_name);
|
||||
//swal glitch - need a timeout
|
||||
setTimeout(function(){
|
||||
swal({title:"Invalid filename", text:'Please only use alphanumerical characters', type:'warning'});
|
||||
@@ -206,7 +212,18 @@ function doRename(t, oldname, dir){
|
||||
function sendMessage(msg){
|
||||
console.log('in sendMessage', msg);
|
||||
if (is_chrome){
|
||||
chrome.runtime.sendMessage(msg);
|
||||
var evt = document.createEvent("CustomEvent");
|
||||
evt.initCustomEvent("hello", true, true, msg);
|
||||
var dispatch_when_injected = function(){
|
||||
if (document.getElementById('content_script_injected_into_page').textContent !== 'true'){
|
||||
console.log('chrome has not yet injected, retrying');
|
||||
setTimeout(function(){dispatch_when_injected();}, 100);
|
||||
}
|
||||
else {
|
||||
document.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
dispatch_when_injected();
|
||||
}
|
||||
else {
|
||||
var json = JSON.stringify(msg);
|
||||
68
content/notification_bar.js
Normal file
68
content/notification_bar.js
Normal file
@@ -0,0 +1,68 @@
|
||||
var viewTabDocument;
|
||||
|
||||
function install_bar(){
|
||||
//when running from Firefox, the var gBrowser is available
|
||||
var using_chrome = typeof(gBrowser) === 'undefined';
|
||||
var using_firefox = typeof(gBrowser) !== 'undefined';
|
||||
var document = viewTabDocument;
|
||||
var table = document.createElement("table");
|
||||
table.style.position = "fixed";
|
||||
table.style.top = "0px";
|
||||
table.style.left = "100px";
|
||||
table.style.background = "rgba(242, 241, 240, 0.9)";
|
||||
table.style.width = "80%";
|
||||
table.style.height = "32px";
|
||||
table.style.visibility = 'hidden';
|
||||
table.style.opacity = '0';
|
||||
table.style.webkitTransition = 'visibility 0s 2s, opacity 2s linear';
|
||||
table.style.transition = 'visibility 0s 2s, opacity 2s linear';
|
||||
var row = document.createElement("tr");
|
||||
var cell1 = document.createElement("td");
|
||||
var cell2 = document.createElement("td");
|
||||
var cell3 = document.createElement("td");
|
||||
cell3.style.align = "right";
|
||||
var img = document.createElement("img");
|
||||
img.src = using_chrome ? "icon16.png" : "../icon16.png";
|
||||
var text = document.createElement("text");
|
||||
text.textContent = "PageSigner verified that this page was received from ";
|
||||
var domain = document.createElement("text");
|
||||
domain.id = "domainName";
|
||||
var sdir = document.createElement("text");
|
||||
sdir.id = "sdir";
|
||||
var button = document.createElement("button");
|
||||
button.id = "viewRaw";
|
||||
button.textContent = "View raw data with HTTP headers";
|
||||
button.style.MozBorderRadius = "4px";
|
||||
button.style.WebkitBorderRadius = "4px";
|
||||
button.style.borderRadius = "4px";
|
||||
button.onclick = function(){
|
||||
var dir = document['pagesigner-session-dir'];
|
||||
if (using_chrome){
|
||||
chrome.runtime.sendMessage({destination:'extension', message:'viewraw', args:{dir:dir}});
|
||||
}
|
||||
else if (using_firefox){
|
||||
var path = OS.Path.join(fsRootPath, dir, 'raw.txt');
|
||||
gBrowser.selectedTab = gBrowser.addTab(path);
|
||||
}
|
||||
};
|
||||
cell3.appendChild(button)
|
||||
cell2.appendChild(text);
|
||||
cell2.appendChild(domain);
|
||||
cell1.appendChild(img);
|
||||
row.appendChild(cell1);
|
||||
row.appendChild(cell2);
|
||||
row.appendChild(cell3);
|
||||
table.appendChild(row);
|
||||
table.appendChild(sdir);
|
||||
document.body.appendChild(table);
|
||||
document['pagesigner-session-dir'] = sdir;
|
||||
|
||||
setTimeout(function(){
|
||||
//make a transition to visible
|
||||
table.style.visibility = 'visible';
|
||||
table.style.opacity = '1';
|
||||
table.style.webkitTransition = 'opacity 2s linear';
|
||||
table.style.transition = 'opacity 2s linear';
|
||||
}, 0);
|
||||
|
||||
}
|
||||
1
content/testing/manager_test.js
Normal file
1
content/testing/manager_test.js
Normal file
@@ -0,0 +1 @@
|
||||
//Do not remove this empty file
|
||||
2
content/testing/testing.js
Normal file
2
content/testing/testing.js
Normal file
@@ -0,0 +1,2 @@
|
||||
//This is an empty file. Browsers expect to find this file in content/testing/testing.js
|
||||
//When running a testsuite, this file will be replaced
|
||||
@@ -140,7 +140,7 @@ function prepare_pms(modulus, tryno){
|
||||
var rs_choice = random_rs.name;
|
||||
console.log('random reliable site', rs_choice);
|
||||
var pms_session = new TLSNClientSession();
|
||||
pms_session.__init__({'server':rs_choice, 'ccs':53, 'tlsver':global_tlsver});
|
||||
pms_session.__init__({'server':rs_choice, 'port':random_rs.port, 'ccs':53, 'tlsver':global_tlsver});
|
||||
pms_session.server_modulus = random_rs.modulus;
|
||||
pms_session.sckt = new Socket(pms_session.server_name, pms_session.ssl_port);
|
||||
return pms_session.sckt.connect()
|
||||
@@ -167,7 +167,7 @@ function prepare_pms(modulus, tryno){
|
||||
#Change Cipher Spec record is returned by the server (we could
|
||||
#also check the server finished, but it isn't necessary)*/
|
||||
var record_to_find = new TLSRecord();
|
||||
record_to_find.__init__(chcis, [0x01], global_tlsver);
|
||||
record_to_find.__init__(chcis, [0x01], pms_session.tlsver);
|
||||
if (response.toString().indexOf(record_to_find.serialized.toString()) < 0){
|
||||
console.log("PMS trial failed, retrying. ("+response.toString()+")");
|
||||
throw("PMS trial failed");
|
||||
@@ -233,9 +233,9 @@ function decrypt_html(tlsn_session){
|
||||
}
|
||||
|
||||
|
||||
function get_certificate(server){
|
||||
function get_certificate(server, port){
|
||||
var probe_session = new TLSNClientSession();
|
||||
probe_session.__init__({'server':server, 'tlsver':global_tlsver});
|
||||
probe_session.__init__({'server':server, 'port':port, 'tlsver':global_tlsver});
|
||||
probe_session.sckt = new Socket(probe_session.server_name,probe_session.ssl_port);
|
||||
return probe_session.sckt.connect()
|
||||
.then(function(){
|
||||
@@ -250,9 +250,9 @@ function get_certificate(server){
|
||||
}
|
||||
|
||||
|
||||
function start_audit(modulus, certhash, name, headers, ee_secret, ee_pad_secret, rsapms2){
|
||||
function start_audit(modulus, certhash, name, port, headers, ee_secret, ee_pad_secret, rsapms2){
|
||||
var tlsn_session = new TLSNClientSession();
|
||||
tlsn_session.__init__({'server':name, 'tlsver':global_tlsver});
|
||||
tlsn_session.__init__({'server':name, 'port':port, 'tlsver':global_tlsver});
|
||||
tlsn_session.server_modulus = modulus;
|
||||
tlsn_session.server_mod_length = modulus.length;
|
||||
tlsn_session.auditee_secret = ee_secret;
|
||||
|
||||
BIN
icon.png
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 2.5 KiB |
@@ -5,7 +5,7 @@
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>pagesigner@tlsnotary</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:version>1.0.1</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>true</em:unpack>
|
||||
@@ -26,8 +26,7 @@
|
||||
<em:creator>TLSNotary Group</em:creator>
|
||||
<em:homepageURL>https://tlsnotary.org</em:homepageURL>
|
||||
<em:optionsURL>chrome://pagesigner/content/firefox/settings.xul</em:optionsURL>
|
||||
<em:iconURL>chrome://pagesigner/content/icon32.png</em:iconURL>
|
||||
<em:icon64URL>chrome://pagesigner/content/icon64.png</em:icon64URL>
|
||||
<em:icon64URL>icon.png</em:icon64URL>
|
||||
<em:optionsType>2</em:optionsType>
|
||||
</Description>
|
||||
</RDF>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
"name": "PageSigner",
|
||||
"description": "PageSigner - a cryptographically secure webpage screenshot tool",
|
||||
"version": "1.0",
|
||||
"version": "1.0.1",
|
||||
"author": "TLSNotary Group",
|
||||
|
||||
"permissions": [
|
||||
"background",
|
||||
"*://*/*", //need https to listen for requests and http to send stuff to oracle
|
||||
"file://*", //ideally we should limit acces only to pagesigner.tmp.dir but Chrome can't do that
|
||||
"file:///*pagesigner.tmp.dir*", //to inject notification bar when showing notarized html
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"activeTab",
|
||||
@@ -22,7 +22,7 @@
|
||||
],
|
||||
|
||||
"icons": {
|
||||
"128": "content/icon128.png" //chrome will scaled down as needed
|
||||
"64": "icon.png" //Chrome will scale down as needed
|
||||
},
|
||||
|
||||
//this is needed for asn1.js
|
||||
@@ -30,10 +30,17 @@
|
||||
|
||||
|
||||
"browser_action": {
|
||||
"default_icon": "content/icon128.png",
|
||||
"default_icon": "icon.png",
|
||||
"default_popup": "content/chrome/popup.html"
|
||||
},
|
||||
|
||||
"content_scripts":[
|
||||
{
|
||||
"matches":["file:///*pagesigner.tmp.dir/manager.html"],
|
||||
"js":["content/chrome/inject_into_manager.js"]
|
||||
}
|
||||
],
|
||||
|
||||
"background": {
|
||||
"scripts": [
|
||||
"content/chrome/chrome_specific.js",
|
||||
@@ -54,9 +61,10 @@
|
||||
"content/pako.js",
|
||||
"content/tlsn.js",
|
||||
"content/main.js",
|
||||
"content/verifychain/buffer.js",
|
||||
"content/verifychain/asn1.js",
|
||||
"content/verifychain/jsrsasign-latest-all-min.js",
|
||||
"content/testing/testing.js",
|
||||
"content/verifychain/buffer.js2",
|
||||
"content/verifychain/asn1.js2",
|
||||
"content/verifychain/jsrsasign-latest-all-min.js2",
|
||||
"content/verifychain/rootcertslist.js",
|
||||
"content/verifychain/rootcerts.js",
|
||||
"content/verifychain/verifychain.js"
|
||||
|
||||
Reference in New Issue
Block a user