mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-09 14:48:07 -05:00
1141 lines
34 KiB
JavaScript
1141 lines
34 KiB
JavaScript
var mustVerifyCert = true; //set to false during debugging to be able to work with self-signed certs
|
|
var portPopup;
|
|
var portManager = null;
|
|
var notarization_in_progress = false;
|
|
var waiting_for_click = false;
|
|
var appId = null; //Chrome uses to send message to external Chrome app. Firefox uses it's own id
|
|
var popupError = null; //set to non-null when there is some error message that must be shown
|
|
//via the popup
|
|
var tabid = 0; //a signal that this is the main window when querying with .getViews()
|
|
|
|
|
|
function sendToPopup(data) {
|
|
if (is_chrome) {
|
|
chrome.runtime.sendMessage(data);
|
|
} else {
|
|
console.log('will postMessage ', data);
|
|
portPopup.postMessage(data);
|
|
}
|
|
}
|
|
|
|
|
|
function openChromeExtensions(){
|
|
chrome.tabs.query({url: 'chrome://extensions/*'},
|
|
function(tabs) {
|
|
if (tabs.length == 0) {
|
|
chrome.tabs.create({url: 'chrome://extensions'});
|
|
return;
|
|
}
|
|
chrome.tabs.update(tabs[0].id, {active: true});
|
|
});
|
|
}
|
|
|
|
|
|
//Pagesigner's popup has been clicked
|
|
async function popupProcess(){
|
|
if (notarization_in_progress) {
|
|
sendToPopup({
|
|
'destination': 'popup',
|
|
'message': 'notarization_in_progress'
|
|
});
|
|
return;
|
|
}
|
|
if (waiting_for_click) {
|
|
sendToPopup({
|
|
'destination': 'popup',
|
|
'message': 'waiting_for_click'
|
|
});
|
|
return;
|
|
}
|
|
if (!is_chrome) {
|
|
if (popupError) {
|
|
sendToPopup({
|
|
'destination': 'popup',
|
|
'message': 'popup error',
|
|
'data': popupError
|
|
});
|
|
popupError = null;
|
|
loadNormalIcon();
|
|
} else {
|
|
sendToPopup({
|
|
'destination': 'popup',
|
|
'message': 'show_menu'
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
//else{} the checks below are only for Chrome
|
|
//probe a non-existent port, if reject()ed with undefined, then the helper app is not running
|
|
try{
|
|
await new Socket('127.0.0.1', -1).connect();
|
|
}
|
|
catch(error){
|
|
if (error === undefined){
|
|
chrome.runtime.sendMessage({
|
|
'destination': 'popup',
|
|
'message': 'app_not_installed'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
if (popupError) {
|
|
chrome.runtime.sendMessage({
|
|
'destination': 'popup',
|
|
'message': 'popup error',
|
|
'data': popupError
|
|
});
|
|
popupError = null;
|
|
loadNormalIcon();
|
|
} else {
|
|
chrome.runtime.sendMessage({
|
|
'destination': 'popup',
|
|
'message': 'show_menu'
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
function openFilePicker(){
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var url = chrome.extension.getURL(prefix + 'content/viewer.html#filepicker');
|
|
//Chrome doesnt provide a straightforward was to associate a tab id with a window
|
|
//as a workaround, get all views
|
|
var myTabs = [];
|
|
for (let win of chrome.extension.getViews()){
|
|
if (win.is_file_picker){
|
|
//re-focus an already opened file picker
|
|
chrome.tabs.update(win.tabid, {active: true});
|
|
return;
|
|
}
|
|
myTabs.push(win.tabid)
|
|
}
|
|
console.log(myTabs)
|
|
|
|
chrome.tabs.create({url: url},
|
|
async function(t){
|
|
|
|
function check(){
|
|
|
|
console.log('checking if file picker is ready...')
|
|
setTimeout(async function(){
|
|
var myViews = chrome.extension.getViews();
|
|
//sometimes the View for the newly opened tab may not yet be available
|
|
//so we must wait a little longer
|
|
var isViewReady = false;
|
|
for (let win of myViews){
|
|
if (myTabs.includes(win.tabid)) continue;
|
|
//found a new tab
|
|
if (typeof(win.showFilePicker) == 'undefined') {
|
|
//viewer.js hasnt yet been loaded into the DOM
|
|
check();
|
|
return;
|
|
}
|
|
isViewReady = true;
|
|
win.showFilePicker()
|
|
win.tabid = t.id;
|
|
var is_testing = await getPref('testing')
|
|
if (is_testing) win.prepare_testing()
|
|
}
|
|
if (! isViewReady){
|
|
check();
|
|
}
|
|
}, 10)
|
|
|
|
}
|
|
check();
|
|
|
|
})
|
|
}
|
|
|
|
|
|
function openManager() {
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var url = chrome.extension.getURL(prefix + 'content/manager.html');
|
|
//re-focus tab if manager already open
|
|
for (let win of chrome.extension.getViews()){
|
|
if (! win.is_manager) continue;
|
|
chrome.tabs.update(win.tabid, {active: true});
|
|
return;
|
|
}
|
|
|
|
chrome.tabs.create({url: url},
|
|
function(t) {
|
|
|
|
function check(){
|
|
console.log('checking if manager tab is ready...')
|
|
setTimeout(async function(){
|
|
var isViewReady = false;
|
|
for (let win of chrome.extension.getViews()){
|
|
//sometimes the View for the newly opened tab may not yet be available
|
|
//so we must wait a little longer
|
|
if (! win.is_manager) continue;
|
|
//found manager window
|
|
isViewReady = true
|
|
win.tabid = t.id
|
|
var is_testing = await getPref('testing')
|
|
if (is_testing){
|
|
win.prepare_testing()
|
|
}
|
|
}
|
|
if (!isViewReady){
|
|
check();
|
|
}
|
|
}, 10)
|
|
}
|
|
check()
|
|
});
|
|
}
|
|
|
|
|
|
async function prepareNotarizing(after_click) {
|
|
if (!oracles_intact) {
|
|
sendAlert({
|
|
title: 'PageSigner error',
|
|
text: 'Cannot notarize because something is wrong with PageSigner server. Please try again later'
|
|
});
|
|
return;
|
|
}
|
|
|
|
var clickTimeout = null;
|
|
|
|
var active_tab = await new Promise(function(resolve, reject) {
|
|
chrome.tabs.query({active: true}, function(t) {
|
|
resolve(t[0]);
|
|
});
|
|
});
|
|
|
|
if (! active_tab.url.startsWith('https://')) {
|
|
sendAlert({
|
|
'title': 'PageSigner error',
|
|
'text': 'You can only notarize pages which start with https://'
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (after_click){
|
|
let prefix = is_chrome ? 'webextension/' : '';
|
|
let url = chrome.extension.getURL(prefix + 'content/arrow24.png');
|
|
chrome.browserAction.setIcon({path: url});
|
|
waiting_for_click = true;
|
|
clickTimeout = setTimeout(function() {
|
|
loadNormalIcon();
|
|
waiting_for_click = false;
|
|
sendAlert({
|
|
title: 'PageSigner error.',
|
|
text: 'You haven\'t clicked any https:// links in 30 seconds. Please try again. If this error persists it may mean that the website you are trying to notarize is not compatible with PageSigner.'
|
|
});
|
|
}, 30 * 1000);
|
|
}
|
|
|
|
var oBR_details;
|
|
var oBR_handler = function(details){
|
|
console.log('in onBeforeRequest', details)
|
|
chrome.webRequest.onBeforeRequest.removeListener(oBR_handler);
|
|
oBR_details = details;
|
|
}
|
|
chrome.webRequest.onBeforeRequest.addListener(
|
|
oBR_handler, {
|
|
urls: ["<all_urls>"],
|
|
tabId: active_tab.id,
|
|
types: ["main_frame", "xmlhttprequest"]
|
|
//types: ["main_frame", "sub_frame", "stylesheet", "script",
|
|
//"image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]
|
|
}, ["requestBody"]);
|
|
|
|
var oBSH_details;
|
|
var oBSH_handler = function(details){
|
|
console.log('in onBeforeSendHeaders', details)
|
|
chrome.webRequest.onBeforeSendHeaders.removeListener(oBSH_handler);
|
|
oBSH_details = details;
|
|
console.log(oBR_details, oBSH_details)
|
|
}
|
|
chrome.webRequest.onBeforeSendHeaders.addListener(
|
|
oBSH_handler, {
|
|
urls: ['<all_urls>'],
|
|
tabId: active_tab.id,
|
|
types: ["main_frame", "xmlhttprequest"]
|
|
}, ["requestHeaders", "extraHeaders"]);
|
|
|
|
|
|
//wait for the request to pass oBR and oBHS and reach onSendHeaders
|
|
await new Promise(function(resolve, reject) {
|
|
var oSH_handler = function(details){
|
|
console.log('in onSendHeaders')
|
|
chrome.webRequest.onSendHeaders.removeListener(oSH_handler);
|
|
resolve(details);
|
|
}
|
|
chrome.webRequest.onSendHeaders.addListener(
|
|
oSH_handler, {
|
|
urls: ['<all_urls>'],
|
|
tabId: active_tab.id,
|
|
types: ["main_frame", "xmlhttprequest"]
|
|
})
|
|
|
|
//if not Notarize After Click mode,
|
|
//reload current tab in order to trigger the HTTP request
|
|
if (!waiting_for_click) chrome.tabs.reload(active_tab.id);
|
|
//otherwise just wait for the user to click smth and trigger onBeforeRequest
|
|
})
|
|
|
|
if (waiting_for_click) {
|
|
clearTimeout(clickTimeout);
|
|
waiting_for_click = false;
|
|
}
|
|
|
|
if (oBR_details.url !== oBSH_details.url) return;
|
|
if (oBR_details.requestId !== oBSH_details.requestId) return;
|
|
if (oBR_details.method == 'POST') {
|
|
//POST payload is only available from onBeforeRequest
|
|
oBSH_details['requestBody'] = oBR_details.requestBody;
|
|
}
|
|
var rv = getHeaders(oBSH_details);
|
|
startNotarizing(rv.headers, rv.server, rv.port)
|
|
}
|
|
|
|
|
|
function getHeaders(obj) {
|
|
var x = obj.url.split('/');
|
|
var host = x[2].split(':')[0];
|
|
x.splice(0, 3);
|
|
var resource_url = x.join('/');
|
|
var headers = obj.method + " /" + resource_url + " HTTP/1.1" + "\r\n";
|
|
headers += "Host: " + host + "\r\n";
|
|
for (let h of obj.requestHeaders) {
|
|
//we dont want any "br" encoding
|
|
if (h.name == "Accept-Encoding") {
|
|
//h.value = 'gzip, deflate'
|
|
h.value = ''
|
|
}
|
|
headers += h.name + ": " + h.value + "\r\n";
|
|
}
|
|
if (obj.method == "GET") {
|
|
headers += "\r\n";
|
|
}
|
|
else if (obj.method == 'POST') {
|
|
if (obj.requestBody.raw != undefined) {
|
|
var content = ba2str(ab2ba(obj.requestBody.raw[0].bytes))
|
|
}
|
|
else{
|
|
var keys = Object.keys(obj.requestBody.formData);
|
|
var content = '';
|
|
for (var key of keys) {
|
|
content += key + '=' + obj.requestBody.formData[key] + '&';
|
|
}
|
|
//get rid of the last &
|
|
content = content.slice(0,-1)
|
|
//Chrome doesn't expose Content-Length which chokes nginx
|
|
headers += 'Content-Length: ' + parseInt(content.length) + '\r\n\r\n';
|
|
headers += content;
|
|
}
|
|
}
|
|
var port = 443;
|
|
if (obj.url.split(':').length === 3) {
|
|
//the port is explicitely provided in URL
|
|
port = parseInt(obj.url.split(':')[2].split('/')[0]);
|
|
}
|
|
return {
|
|
'headers': headers,
|
|
'server': host,
|
|
'port': port
|
|
};
|
|
}
|
|
|
|
|
|
async function getPGSG(sid){
|
|
var blob = await getSessionBlob(sid);
|
|
return blob.pgsg;
|
|
}
|
|
|
|
|
|
async function process_message(data) {
|
|
if (data.destination !== 'extension') return;
|
|
console.log('ext got msg', data);
|
|
switch (data.message){
|
|
case 'rename':
|
|
await renameSession(data.args.dir, data.args.newname);
|
|
var dataarray = await getAllSessions();
|
|
sendSessions(dataarray);
|
|
break;
|
|
case 'delete':
|
|
await deleteSession(data.args.dir);
|
|
var dataarray = await getAllSessions();
|
|
sendSessions(dataarray);
|
|
break;
|
|
case 'import':
|
|
verify_pgsg_and_show_data(data.args.data, true);
|
|
break;
|
|
case 'export':
|
|
let pgsg = await getPGSG(data.args.dir)
|
|
let value = await getSession(data.args.dir)
|
|
sendToManager({'pgsg':JSON.stringify(pgsg), 'name':value.sessionName}, 'export')
|
|
break;
|
|
case 'notarize':
|
|
prepareNotarizing(false);
|
|
break;
|
|
case 'notarizeAfter':
|
|
prepareNotarizing(true);
|
|
break;
|
|
case 'manage':
|
|
openManager();
|
|
break;
|
|
case 'refresh':
|
|
var dataarray = await getAllSessions();
|
|
sendSessions(dataarray);
|
|
break;
|
|
case 'openLink1':
|
|
chrome.tabs.create({url: 'https://www.tlsnotary.org'});
|
|
break;
|
|
case 'donate link':
|
|
chrome.tabs.create({url: 'https://www.tlsnotary.org/#Donate'});
|
|
break;
|
|
case 'viewdata':
|
|
openTab(data.args.dir);
|
|
break;
|
|
case 'viewraw':
|
|
viewRaw(data.args.dir);
|
|
break;
|
|
case 'file picker':
|
|
openFilePicker();
|
|
break;
|
|
case 'openInstallLink':
|
|
chrome.tabs.create({
|
|
url: 'https://chrome.google.com/webstore/detail/pagesigner-helper-app/oclohfdjoojomkfddjclanpogcnjhemd'});
|
|
break;
|
|
case 'openChromeExtensions':
|
|
openChromeExtensions();
|
|
break;
|
|
case 'popup active':
|
|
popupProcess()
|
|
break
|
|
}
|
|
}
|
|
|
|
|
|
//perform browser-specific init first
|
|
async function main() {
|
|
if (is_chrome) {
|
|
appId = "oclohfdjoojomkfddjclanpogcnjhemd"; //id of the helper app
|
|
chrome.runtime.onMessage.addListener(function(data) {
|
|
process_message(data);
|
|
});
|
|
} else {
|
|
appId = chrome.runtime.id;
|
|
//console.log('installing listener');
|
|
//Temporary kludge for FF53 to use Ports for communication
|
|
chrome.runtime.onConnect.addListener(function(port) {
|
|
console.log('chrome.runtime.onConnect.addListener with port', port);
|
|
if (port.name == 'popup-to-extension') {
|
|
portPopup = port;
|
|
console.log('in extension port connection from', port.name);
|
|
port.onMessage.addListener(function(data) {
|
|
console.log('in port listener, got', data);
|
|
process_message(data);
|
|
});
|
|
} else if (port.name == 'filepicker-to-extension') {
|
|
port.onMessage.addListener(function(data) {
|
|
console.log('in filepicker-to-extension got data', data);
|
|
if (data.destination !== 'extension') return;
|
|
if (data.message !== 'import') return;
|
|
verify_pgsg_and_show_data(data.args.data, true);
|
|
});
|
|
} else if (port.name == 'notification-to-extension') {
|
|
port.onMessage.addListener(function(data) {
|
|
console.log('in notification-to-extension got data', data);
|
|
if (data.destination !== 'extension') return;
|
|
if (data.message !== 'viewraw') return;
|
|
process_message(data);
|
|
});
|
|
} else if (port.name == 'manager-to-extension') {
|
|
portManager = port;
|
|
port.onMessage.addListener(function(data) {
|
|
console.log('in manager-to-extension got data', data);
|
|
if (data.destination !== 'extension') return;
|
|
process_message(data);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
init();
|
|
}
|
|
|
|
|
|
async function init() {
|
|
await init_db();
|
|
var alreadyConverted = await getPref('alreadyConverted');
|
|
if (alreadyConverted != true){
|
|
//will be run only once when Pagesigner 2.1.0 is released
|
|
//converts db's binary pgsgs into json
|
|
await addNewPreference('alreadyConverted', false)
|
|
await convert_db();
|
|
await setPref('alreadyConverted', true);
|
|
}
|
|
await parse_certs();
|
|
var is_verbose = await getPref('verbose');
|
|
if (is_verbose !== true && !is_chrome) {
|
|
//Firefox pollutes browser window, disable logging
|
|
console.log = function(){};
|
|
}
|
|
chosen_notary = oracles[Math.random() * (oracles.length) << 0];
|
|
var oracle_hash = ba2hex( await sha256( str2ba( JSON.stringify(chosen_notary) ) ) );
|
|
var vo = await getPref('verifiedOracles')
|
|
if (vo.includes(oracle_hash)) {
|
|
oracles_intact = true;
|
|
browser_init_finished = true;
|
|
} else {
|
|
//asynchronously check oracles and if the check fails, sets a global var
|
|
//which prevents notarization session from running
|
|
console.log('oracle not verified');
|
|
return check_oracle(chosen_notary)
|
|
.then(async function success() {
|
|
vo.push(oracle_hash)
|
|
await setPref('verifiedOracles', vo);
|
|
oracles_intact = true;
|
|
browser_init_finished = true;
|
|
})
|
|
.catch(function(err) {
|
|
console.log('caught error', err);
|
|
//query for a new oracle
|
|
//TODO fetch backup oracles list
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
//we can import chrome:// and file:// URL
|
|
async function import_resource(filename) {
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var path = chrome.extension.getURL(prefix + 'content/' + filename);
|
|
var resp = await fetch(path)
|
|
var data = await resp.text()
|
|
return data;
|
|
}
|
|
|
|
|
|
async function startNotarizing(headers, server, port) {
|
|
loadBusyIcon();
|
|
try{
|
|
let rv = await start_audit(server, port, headers);
|
|
let sessionId = await save_session(rv);
|
|
await showData(sessionId);
|
|
}
|
|
catch (err){
|
|
console.log('There was an error: ' + err);
|
|
if (err === "Server sent alert 2,40") {
|
|
sendAlert({
|
|
title: 'PageSigner error',
|
|
text: 'Pagesigner is not compatible with this website'
|
|
});
|
|
} else {
|
|
sendAlert({
|
|
title: 'PageSigner error',
|
|
text: err
|
|
});
|
|
}
|
|
}
|
|
loadNormalIcon();
|
|
}
|
|
|
|
|
|
|
|
|
|
async function save_session(args) {
|
|
|
|
assert(args.length === 11, "wrong args length");
|
|
var idx = -1
|
|
var server_certchain = args[idx+=1]
|
|
var rsa_sig = args[idx+=1]
|
|
var client_random = args[idx+=1]
|
|
var server_random = args[idx+=1]
|
|
var ec_pubkey_server = args[idx+=1]
|
|
var server_write_key = args[idx+=1]
|
|
var server_write_IV = args[idx+=1]
|
|
var encRecords = args[idx+=1]
|
|
var cleartext = args[idx+=1]
|
|
var notary_signature = args[idx+=1]
|
|
var time = args[idx+=1]
|
|
|
|
var pgsg_json = {}
|
|
pgsg_json["title"] = "PageSigner notarization file"
|
|
pgsg_json["version"] = 4
|
|
pgsg_json["certificate chain"] = {}
|
|
for (let [idx, cert] of server_certchain.entries()) {
|
|
let key = 'cert'+idx.toString()
|
|
pgsg_json["certificate chain"][key] = b64encode(cert)
|
|
}
|
|
pgsg_json["server RSA signature over EC pubkey"] = b64encode(rsa_sig)
|
|
pgsg_json['client random'] = b64encode(client_random)
|
|
pgsg_json['server random'] = b64encode(server_random)
|
|
pgsg_json["server EC pubkey"] = b64encode(ec_pubkey_server)
|
|
pgsg_json["server write key"] = b64encode(server_write_key)
|
|
pgsg_json["server write IV"] = b64encode(server_write_IV)
|
|
pgsg_json['encrypted records'] = {}
|
|
for (let [idx, rec] of encRecords.entries()) {
|
|
let key = 'rec'+idx.toString()
|
|
pgsg_json["encrypted records"][key] = b64encode(rec)
|
|
}
|
|
pgsg_json["notary signature"] = b64encode(notary_signature)
|
|
pgsg_json["notarization time"] = b64encode(time)
|
|
pgsg_json["notary name"] = chosen_notary.name
|
|
var pgsg = pgsg_json
|
|
|
|
var commonName = getCommonName(server_certchain[0]);
|
|
var creationTime = getTime();
|
|
await createNewSession (creationTime, commonName, chosen_notary.name, cleartext, pgsg, false);
|
|
return creationTime; //creationTime is also a session ID
|
|
}
|
|
|
|
|
|
async function showData (sid){
|
|
await openTab(sid);
|
|
var allSessions = await getAllSessions();
|
|
sendSessions(allSessions); //refresh manager
|
|
}
|
|
|
|
|
|
//convert an old binary-formatted pgsg into a json formatted one
|
|
function convertPgsg(data){
|
|
if (ba2str(data.slice(0,1)) == '{') {
|
|
//data is in json already nothing to convert
|
|
return data
|
|
}
|
|
var o = 0; //offset
|
|
if (ba2str(data.slice(o, o += 29)) !== "tlsnotary notarization file\n\n") {
|
|
throw ('wrong header');
|
|
}
|
|
if (data.slice(o, o += 2).toString() !== [0x00, 0x03].toString()) {
|
|
throw ('wrong version');
|
|
}
|
|
var client_random = data.slice(o, o += 32)
|
|
var server_random = data.slice(o, o += 32)
|
|
var chain_serialized_len = ba2int(data.slice(o, o += 3));
|
|
var chain_serialized = data.slice(o, o += chain_serialized_len);
|
|
var rsa_siglen = ba2int(data.slice(o, o += 2));
|
|
var rsa_sig = data.slice(o, o += rsa_siglen);
|
|
var ec_pubkey_server = data.slice(o, o += 65)
|
|
var ec_pubkey_client = data.slice(o, o += 65);
|
|
var server_reply_len = ba2int(data.slice(o, o += 4));
|
|
var server_response = data.slice(o, o += server_reply_len);
|
|
var notary_signature_len = ba2int(data.slice(o, o += 1));
|
|
var notary_signature = data.slice(o, o += notary_signature_len);
|
|
var ec_privkey = data.slice(o, o += 32);
|
|
var time = data.slice(o, o += 4);
|
|
assert(data.length === o, 'invalid .pgsg length');
|
|
|
|
o = 0;
|
|
var chain = [];
|
|
while (o < chain_serialized.length) {
|
|
var len = ba2int(chain_serialized.slice(o, o += 3));
|
|
var cert = chain_serialized.slice(o, o += len);
|
|
chain.push(cert);
|
|
}
|
|
|
|
var pgsg_json = {}
|
|
pgsg_json["title"] = "PageSigner notarization file"
|
|
pgsg_json["version"] = 3
|
|
pgsg_json["client random"] = b64encode(client_random)
|
|
pgsg_json["server random"] = b64encode(server_random)
|
|
pgsg_json["certificate chain"] = {}
|
|
for (let [idx, cert] of chain.entries()) {
|
|
let key = 'cert'+idx.toString()
|
|
pgsg_json["certificate chain"][key] = b64encode(cert)
|
|
}
|
|
pgsg_json["server RSA signature over EC pubkey"] = b64encode(rsa_sig)
|
|
pgsg_json["server EC pubkey"] = b64encode(ec_pubkey_server)
|
|
pgsg_json["client EC pubkey"] = b64encode(ec_pubkey_client)
|
|
pgsg_json["server response"] = b64encode(server_response)
|
|
pgsg_json["notary signature"] = b64encode(notary_signature)
|
|
pgsg_json["client EC privkey"] = b64encode(ec_privkey)
|
|
pgsg_json["notarization time"] = b64encode(time)
|
|
pgsg_json["notary name"] = "tlsnotarygroup8"
|
|
var pgsg = str2ba(JSON.stringify(pgsg_json))
|
|
return pgsg
|
|
}
|
|
|
|
|
|
async function verifyPgsg(json){
|
|
if (json['version'] == 3){
|
|
return await verifyPgsgV3(json);
|
|
}
|
|
else if (json['version'] == 4){
|
|
return await verifyPgsgV4(json);
|
|
}
|
|
else {
|
|
throw ('Unrecognized version of the imported pgsg file.')
|
|
}
|
|
}
|
|
|
|
|
|
//pgsg is in json
|
|
async function verifyPgsgV4(json) {
|
|
var server_write_key = b64decode(json['server write key'])
|
|
var server_write_IV = b64decode(json['server write IV'])
|
|
var chain = []
|
|
var certNumber = Object.keys(json['certificate chain']).length
|
|
for (var i=0; i<certNumber; i++){
|
|
let key = 'cert' + i.toString()
|
|
chain.push(b64decode(json['certificate chain'][key]))
|
|
}
|
|
var rsa_sig = b64decode(json['server RSA signature over EC pubkey'])
|
|
var client_random = b64decode(json['client random'])
|
|
var server_random = b64decode(json['server random'])
|
|
var ec_pubkey_server = b64decode(json['server EC pubkey'])
|
|
var encRecords = []
|
|
var encRecNumber = Object.keys(json['encrypted records']).length
|
|
for (var i=0; i<encRecNumber; i++){
|
|
let key = 'rec' + i.toString()
|
|
encRecords.push(b64decode(json['encrypted records'][key]))
|
|
}
|
|
var notary_signature = b64decode(json['notary signature'])
|
|
var time = b64decode(json['notarization time'])
|
|
var notaryName = json["notary name"]
|
|
var notary;
|
|
if (notaryName == oracle.name){
|
|
notary = oracle;
|
|
}
|
|
else{
|
|
var rv = await verifyOldOracle(notaryName);
|
|
if (rv.result == true){
|
|
notary = rv.oracle;
|
|
}
|
|
else{
|
|
throw('unrecognized oracle in the imported file')
|
|
}
|
|
}
|
|
|
|
var seconds = ba2int(time)
|
|
var date = new Date(seconds*1000)
|
|
var commonName = getCommonName(chain[0]);
|
|
var vcrv = await verifyChain(chain, date);
|
|
if (vcrv[0] != true) {
|
|
throw ('certificate verification failed');
|
|
}
|
|
var rv = await verifyECParamsSig(chain[0], ec_pubkey_server, rsa_sig, client_random, server_random)
|
|
if (rv != true){
|
|
throw ('EC parameters signature verification failed');
|
|
}
|
|
|
|
var commit_hash = await computeCommitHash(encRecords)
|
|
//check notary server signature
|
|
var signed_data_ba = await sha256([].concat(ec_pubkey_server, server_write_key, server_write_IV, commit_hash, time))
|
|
assert(await verifyNotarySig(notary_signature, notary.pubkeyPEM, signed_data_ba) == true)
|
|
//aesgcm decrypt the data
|
|
var cleartext = await decrypt_tls_responseV4 (encRecords, server_write_key, server_write_IV)
|
|
var dechunked = dechunk_http(ba2str(cleartext))
|
|
var ungzipped = gunzip_http(dechunked)
|
|
return [ungzipped, commonName, notaryName];
|
|
}
|
|
|
|
|
|
|
|
|
|
//pgsg is in json
|
|
async function verifyPgsgV3(json) {
|
|
var client_random = b64decode(json['client random'])
|
|
var server_random = b64decode(json['server random'])
|
|
var chain = []
|
|
var certNumber = Object.keys(json['certificate chain']).length
|
|
for (var i=0; i<certNumber; i++){
|
|
let key = 'cert' + i.toString()
|
|
chain.push(b64decode(json['certificate chain'][key]))
|
|
}
|
|
var rsa_sig = b64decode(json['server RSA signature over EC pubkey'])
|
|
var ec_pubkey_server = b64decode(json['server EC pubkey'])
|
|
var ec_pubkey_client = b64decode(json['client EC pubkey'])
|
|
var server_response = b64decode(json['server response'])
|
|
var notary_signature = b64decode(json['notary signature'])
|
|
var ec_privkey = b64decode(json['client EC privkey'])
|
|
var time = b64decode(json['notarization time'])
|
|
var notaryName = json["notary name"]
|
|
var notary;
|
|
if (notaryName == oracle.name){
|
|
notary = oracle;
|
|
}
|
|
else{
|
|
var rv = await verifyOldOracle(notaryName);
|
|
if (rv.result == true){
|
|
notary = rv.oracle;
|
|
}
|
|
else{
|
|
throw('unrecognized oracle in the imported file')
|
|
}
|
|
}
|
|
|
|
var seconds = ba2int(time)
|
|
var date = new Date(seconds*1000)
|
|
var commonName = getCommonName(chain[0]);
|
|
var vcrv = await verifyChain(chain, date);
|
|
if (vcrv[0] != true) {
|
|
throw ('certificate verification failed');
|
|
}
|
|
var rv = await verifyECParamsSig(chain[0], ec_pubkey_server, rsa_sig, client_random, server_random)
|
|
if (rv != true){
|
|
throw ('EC parameters signature verification failed');
|
|
}
|
|
|
|
//calculate pre-master secre
|
|
var ECpubkey_CryptoKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
ba2ab(ec_pubkey_server),
|
|
{name: 'ECDH', namedCurve:'P-256'},
|
|
true,
|
|
[]);
|
|
|
|
var ECprivkey_CryptoKey = await crypto.subtle.importKey(
|
|
"jwk",
|
|
{
|
|
"crv":"P-256",
|
|
"d": b64urlencode(ec_privkey),
|
|
"ext":true,
|
|
"key_ops":["deriveKey","deriveBits"],
|
|
"kty":"EC",
|
|
"x": b64urlencode(ec_pubkey_client.slice(1,33)),
|
|
"y": b64urlencode(ec_pubkey_client.slice(33,65))
|
|
},
|
|
{
|
|
name: "ECDH",
|
|
namedCurve: "P-256",
|
|
},
|
|
true,
|
|
["deriveBits"]
|
|
)
|
|
|
|
var keys = await getExpandedKeys (ECpubkey_CryptoKey, ECprivkey_CryptoKey,
|
|
client_random, server_random)
|
|
var server_write_key = keys[1]
|
|
var server_write_IV = keys[3]
|
|
console.log('server_write_key, server_write_IV', server_write_key, server_write_IV)
|
|
|
|
var commit_hash = await sha256(server_response)
|
|
//check notary server signature
|
|
var signed_data_ba = await sha256([].concat(ec_privkey, ec_pubkey_server, commit_hash, time))
|
|
assert(await verifyNotarySig(notary_signature, notary.pubkeyPEM, signed_data_ba) == true)
|
|
//aesgcm decrypt the data
|
|
var cleartext = await decrypt_tls_responseV3 (server_response, server_write_key, server_write_IV)
|
|
var dechunked = dechunk_http(ba2str(cleartext))
|
|
var ungzipped = gunzip_http(dechunked)
|
|
return [ungzipped, commonName, notaryName];
|
|
}
|
|
|
|
|
|
//imported_data is ba
|
|
async function verify_pgsg_and_show_data(imported_data, create) {
|
|
try {
|
|
//convert old binary format (if any) to json
|
|
imported_data = convertPgsg(imported_data)
|
|
var json = JSON.parse(ba2str(imported_data))
|
|
var a = await verifyPgsg(json);
|
|
} catch (e) {
|
|
sendAlert({
|
|
title: 'PageSigner failed to import file',
|
|
text: 'The error was: ' + e
|
|
});
|
|
return;
|
|
}
|
|
if (!create) return;
|
|
var cleartext = a[0];
|
|
var commonName = a[1];
|
|
var notaryName = a[2]
|
|
var creationTime = getTime();
|
|
await createNewSession (creationTime, commonName, notaryName, cleartext, json, true)
|
|
await openTab(creationTime);
|
|
var allSessions = await getAllSessions();
|
|
sendSessions(allSessions); //refresh manager
|
|
}
|
|
|
|
|
|
async function openTab(sid) {
|
|
var data = await getSession(sid);
|
|
var blob = await getSessionBlob(sid);
|
|
if (data === null) {throw('failed to get index', sid)}
|
|
var commonName = data.serverName
|
|
var cleartext = blob.cleartext
|
|
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var url = chrome.extension.getURL(prefix + 'content/viewer.html');
|
|
await chrome.webRequest.handlerBehaviorChanged(); //flush the in-memory cache
|
|
|
|
//reuse tab if viewer was already open because we were importing file
|
|
//this tab must be still active
|
|
var active_tab = await new Promise(function(resolve, reject) {
|
|
chrome.tabs.query({active: true}, function(t) {
|
|
resolve(t[0]);
|
|
});
|
|
})
|
|
//check if there is a file picker among our views
|
|
var isImportTab = false;
|
|
var myViews = chrome.extension.getViews()
|
|
for (let win of myViews){
|
|
if (win.tabid == active_tab.id && win.is_file_picker)
|
|
isImportTab = true;
|
|
}
|
|
|
|
//open a new tab and store the tabid inside a Window object
|
|
if (!isImportTab){
|
|
var myTabs = []
|
|
var myViews = chrome.extension.getViews();
|
|
for (let win of myViews ){
|
|
myTabs.push(win.tabid)
|
|
}
|
|
|
|
var newtab = await new Promise(function(resolve, reject) {
|
|
chrome.tabs.create({url: url},
|
|
function(t) {
|
|
resolve(t)})
|
|
});
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
|
|
function check(){
|
|
console.log('checking if tab is ready...')
|
|
setTimeout(async function(){
|
|
var myViews = chrome.extension.getViews();
|
|
//sometimes the View for the newly opened tab may not yet be available
|
|
//so we must wait a little longer
|
|
var isViewReady = false;
|
|
for (let win of myViews){
|
|
if (myTabs.includes(win.tabid)) continue;
|
|
//found a new viewer tab
|
|
if (typeof(win.main) == 'undefined') {
|
|
//viewer.js hasnt yet been loaded into the DOM
|
|
check();
|
|
return;
|
|
}
|
|
isViewReady = true;
|
|
win.tabid = newtab.id;
|
|
resolve();
|
|
}
|
|
if (! isViewReady){
|
|
check();
|
|
}
|
|
}, 10)
|
|
}
|
|
check();
|
|
|
|
});
|
|
}
|
|
|
|
//the tab is either an already opened import tab or a fully-loaded new viewer tab
|
|
//We already checked that the new viewer's tab DOM was loaded. Proceed to send the data
|
|
chrome.runtime.sendMessage({
|
|
destination: 'viewer',
|
|
data: cleartext,
|
|
sessionId: sid,
|
|
type: 'unknown',
|
|
serverName: commonName
|
|
});
|
|
}
|
|
|
|
|
|
async function viewRaw(sid) {
|
|
var data = await getSession(sid)
|
|
var blob = await getSessionBlob(sid)
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var url = chrome.extension.getURL(prefix + 'content/viewer.html');
|
|
//remember my Views which are already open
|
|
var myTabs = []
|
|
var myViews = chrome.extension.getViews();
|
|
for (let win of myViews ){
|
|
myTabs.push(win.tabid)
|
|
}
|
|
//open my tab
|
|
var newtab = await new Promise(function(resolve, reject) {
|
|
chrome.tabs.create({url: url},
|
|
function(t) {
|
|
resolve(t)})
|
|
});
|
|
//check that my tab's view is available and its DOM loaded
|
|
await new Promise(function(resolve, reject) {
|
|
|
|
function check(){
|
|
console.log('checking if raw viewer tab is ready...')
|
|
setTimeout(async function(){
|
|
var myViews = chrome.extension.getViews();
|
|
//sometimes the View for the newly opened tab may not yet be available
|
|
//so we must wait a little longer
|
|
var isViewReady = false;
|
|
for (let win of myViews){
|
|
if (myTabs.includes(win.tabid)) continue;
|
|
//found a new viewer tab
|
|
if (typeof(win.main) == 'undefined') {
|
|
//viewer.js hasnt yet been loaded into the DOM
|
|
check();
|
|
return;
|
|
}
|
|
isViewReady = true;
|
|
win.tabid = newtab.id;
|
|
resolve();
|
|
}
|
|
if (! isViewReady){
|
|
check();
|
|
}
|
|
}, 10)
|
|
}
|
|
check();
|
|
|
|
});
|
|
chrome.runtime.sendMessage({
|
|
destination: 'viewer',
|
|
data: blob.cleartext,
|
|
type: 'raw',
|
|
sessionId: sid,
|
|
serverName: data.serverName
|
|
});
|
|
|
|
}
|
|
|
|
|
|
function sendSessions(sessions) {
|
|
var rows = []
|
|
for (let session of sessions){
|
|
var verifier;
|
|
if (session.notaryName == undefined){
|
|
verifier = 'tlsnotarygroup8'
|
|
}
|
|
else {
|
|
verifier = session.notaryName;
|
|
}
|
|
rows.push({
|
|
'sessionName': session.sessionName,
|
|
'serverName': session.serverName,
|
|
'is_imported': session.is_imported,
|
|
'verifier': verifier,
|
|
'creationTime': session.creationTime,
|
|
});
|
|
}
|
|
sendToManager(rows);
|
|
}
|
|
|
|
|
|
function sendToManager(data, command) {
|
|
console.log('sending sendToManager ', data);
|
|
if (is_chrome) {
|
|
if (!command) command = 'payload'; //commands can be: payload, export
|
|
assert(['payload', 'export'].includes(command))
|
|
chrome.runtime.sendMessage({
|
|
'destination': 'manager',
|
|
'command': command,
|
|
'payload': data
|
|
});
|
|
} else {
|
|
console.log('will use portManager ', portManager);
|
|
//the manager may not have loaded yet
|
|
function do_send() {
|
|
console.log('do_send.count', do_send.count);
|
|
do_send.count++;
|
|
if (do_send.count > 30) return;
|
|
if (!portManager) { //null if manager was never active
|
|
setTimeout(do_send, 100);
|
|
} else {
|
|
portManager.postMessage({
|
|
'destination': 'manager',
|
|
'payload': data
|
|
});
|
|
}
|
|
}
|
|
do_send.count = 0;
|
|
do_send();
|
|
}
|
|
}
|
|
|
|
|
|
function sendAlert(alertData) {
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
//for some pages we cant inject js/css, use the ugly alert
|
|
function uglyAlert(alertData) {
|
|
var url = chrome.extension.getURL(prefix + 'content/icon_error.png');
|
|
chrome.browserAction.setIcon({
|
|
path: url
|
|
});
|
|
popupError = alertData;
|
|
}
|
|
|
|
chrome.tabs.query({active: true},
|
|
function(tabs) {
|
|
if (chrome.extension.lastError) {
|
|
uglyAlert(alertData);
|
|
return;
|
|
}
|
|
chrome.tabs.executeScript(tabs[0].id, {file: (prefix + 'content/sweetalert.min.js')},
|
|
function() {
|
|
if (chrome.extension.lastError) {
|
|
uglyAlert(alertData);
|
|
return;
|
|
}
|
|
chrome.tabs.insertCSS(tabs[0].id, {file: (prefix + 'content/sweetalert.css')},
|
|
function() {
|
|
if (chrome.extension.lastError) {
|
|
uglyAlert(alertData);
|
|
return;
|
|
}
|
|
chrome.tabs.executeScript(tabs[0].id, {code: "swal(" + JSON.stringify(alertData) + ")"});
|
|
if (chrome.extension.lastError) {
|
|
uglyAlert(alertData);
|
|
return;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
function loadBusyIcon() {
|
|
notarization_in_progress = true
|
|
|
|
var context=document.createElement('canvas').getContext('2d');
|
|
var start = new Date();
|
|
var lines = 16,
|
|
cW = 40,
|
|
cH = 40;
|
|
|
|
var interval = setInterval(function() {
|
|
if (!notarization_in_progress) {
|
|
clearInterval(interval)
|
|
return;
|
|
}
|
|
var rotation = parseInt(((new Date() - start) / 1000) * lines) / lines;
|
|
context.save();
|
|
context.clearRect(0, 0, cW, cH);
|
|
context.translate(cW / 2, cH / 2);
|
|
context.rotate(Math.PI * 2 * rotation);
|
|
for (var i = 0; i < lines; i++) {
|
|
context.beginPath();
|
|
context.rotate(Math.PI * 2 / lines);
|
|
context.moveTo(cW / 10, 0);
|
|
context.lineTo(cW / 4, 0);
|
|
context.lineWidth = cW / 30;
|
|
context.strokeStyle = 'rgba(0, 0, 0,' + i / lines + ')';
|
|
context.stroke();
|
|
}
|
|
|
|
var imageData = context.getImageData(10, 10, 19, 19);
|
|
chrome.browserAction.setIcon({
|
|
imageData: imageData
|
|
});
|
|
|
|
context.restore();
|
|
}, 1000 / 15);
|
|
|
|
}
|
|
|
|
|
|
function loadNormalIcon() {
|
|
var prefix = is_chrome ? 'webextension/' : '';
|
|
var url = chrome.extension.getURL(prefix + 'content/icon.png');
|
|
chrome.browserAction.setIcon({path: url});
|
|
notarization_in_progress = false;
|
|
}
|
|
|
|
|
|
//This must be at the bottom, otherwise we'd have to define each function
|
|
//before it gets used.
|
|
if (typeof(window) != 'undefined') {
|
|
//only run main() in browser environment
|
|
main();
|
|
}
|
|
|
|
if (typeof module !== 'undefined'){ //we are in node.js environment
|
|
module.exports={
|
|
save_session,
|
|
verifyPgsg
|
|
}
|
|
} |