mirror of
https://github.com/tlsnotary/PageSigner.git
synced 2026-01-09 14:48:07 -05:00
Merge pull request #17 from tlsnotary/socket_fix
Socket recv() abstracted away. Add 1 second delay after receiving com…
This commit is contained in:
1
bootstrap.js
vendored
1
bootstrap.js
vendored
@@ -187,6 +187,7 @@ function startup(data, reason) AddonManager.getAddonByID(data.id, function(addon
|
||||
function loadjs(){
|
||||
jsloaded = true;
|
||||
var addon = thisaddon;
|
||||
include(addon, "socket.js");
|
||||
include(addon, "firefox/button.js");
|
||||
include(addon, "tlsn_utils.js");
|
||||
include(addon, "oracles.js");
|
||||
|
||||
@@ -425,98 +425,53 @@ function Socket(name, port){
|
||||
this.name = name;
|
||||
this.port = port;
|
||||
this.uid = Math.random().toString(36).slice(-10);
|
||||
this.buffer = [];
|
||||
this.recv_timeout = 20*1000;
|
||||
}
|
||||
//inherit the base class
|
||||
Socket.prototype = Object.create(AbstractSocket.prototype);
|
||||
Socket.prototype.constructor = Socket;
|
||||
|
||||
Socket.prototype.connect = function(){
|
||||
var that = this;
|
||||
var is_open = false;
|
||||
var uid = this.uid;
|
||||
return new Promise(function(resolve, reject) {
|
||||
chrome.runtime.sendMessage(appId,
|
||||
{'command':'connect',
|
||||
'args':{'name':that.name, 'port':that.port},
|
||||
'uid':uid},
|
||||
'uid':that.uid},
|
||||
function(response){
|
||||
console.log('in connect response', response);
|
||||
clearInterval(timer);
|
||||
if (response.retval === 'success'){
|
||||
is_open = true;
|
||||
//endless data fetching loop for the lifetime of this Socket
|
||||
var fetch = function(){
|
||||
chrome.runtime.sendMessage(appId, {'command':'recv', 'uid':that.uid}, function(response){
|
||||
console.log('fetched some data', response.data.length, that.uid);
|
||||
that.buffer = [].concat(that.buffer, response.data);
|
||||
setTimeout(function(){fetch()}, 100);
|
||||
});
|
||||
};
|
||||
fetch();
|
||||
resolve('ready');
|
||||
}
|
||||
reject(response.retval);
|
||||
|
||||
});
|
||||
//dont wait too loong
|
||||
var timer;
|
||||
var startTime = new Date().getTime();
|
||||
var check = function(){
|
||||
var now = new Date().getTime();
|
||||
if (( (now - startTime) / 1000) >= 20){
|
||||
clearInterval(timer);
|
||||
reject('connect: socket timed out');
|
||||
return;
|
||||
}
|
||||
if (!is_open){
|
||||
console.log('connect: Another timeout');
|
||||
return;
|
||||
}
|
||||
clearInterval(timer);
|
||||
console.log('connect: promise resolved');
|
||||
resolve('ready');
|
||||
};
|
||||
timer = setInterval(check, 100);
|
||||
//dont wait for connect for too long
|
||||
var timer = setTimeout(function(){
|
||||
reject('connect: socket timed out');
|
||||
}, 1000*20);
|
||||
});
|
||||
};
|
||||
Socket.prototype.send = function(data_in){
|
||||
//Transform number array into ArrayBuffer
|
||||
var ab = new ArrayBuffer(data_in.length);
|
||||
var dv = new DataView(ab);
|
||||
for(var i=0; i < data_in.length; i++){
|
||||
dv.setUint8(i, data_in[i]);
|
||||
}
|
||||
chrome.runtime.sendMessage(appId,
|
||||
{'command':'send',
|
||||
'args':{'data':data_in},
|
||||
'uid':this.uid});
|
||||
};
|
||||
Socket.prototype.recv = function(){
|
||||
var uid = this.uid;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var startTime = new Date().getTime();
|
||||
var complete_records = [];
|
||||
var buf = [];
|
||||
var cancelled = false;
|
||||
var timer = setTimeout(function(){
|
||||
reject('recv: socket timed out');
|
||||
cancelled = true;
|
||||
}, 20*1000);
|
||||
|
||||
var check = function(){
|
||||
if (cancelled) return;
|
||||
chrome.runtime.sendMessage(appId, {'command':'recv', 'uid':uid}, function(response){
|
||||
if (cancelled) return;
|
||||
if (response.data.length > 0){
|
||||
buf = [].concat(buf, response.data);
|
||||
var rv = check_complete_records(buf);
|
||||
complete_records = [].concat(complete_records, rv.comprecs);
|
||||
if (! rv.is_complete){
|
||||
console.log("check_complete_records failed");
|
||||
buf = rv.incomprecs;
|
||||
setTimeout(function(){check()}, 100);
|
||||
return;
|
||||
}
|
||||
clearTimeout(timer);
|
||||
console.log('recv promise resolved');
|
||||
resolve(complete_records);
|
||||
return;
|
||||
}
|
||||
console.log('Another timeout in recv');
|
||||
setTimeout(function(){check()}, 100);
|
||||
});
|
||||
};
|
||||
check();
|
||||
});
|
||||
};
|
||||
Socket.prototype.close = function(){
|
||||
chrome.runtime.sendMessage(appId, {'command':'close'});
|
||||
console.log('closing socket', this.uid);
|
||||
chrome.runtime.sendMessage(appId,
|
||||
{'command':'close', 'uid':this.uid});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -775,105 +775,44 @@ function Socket(name, port){
|
||||
this.name = name;
|
||||
this.port = port;
|
||||
this.sckt = null;
|
||||
this.is_open = false;
|
||||
this.buffer = [];
|
||||
this.recv_timeout = 20*1000;
|
||||
}
|
||||
//inherit the base class
|
||||
Socket.prototype = Object.create(AbstractSocket.prototype);
|
||||
Socket.prototype.constructor = Socket;
|
||||
|
||||
Socket.prototype.connect = function(){
|
||||
//TCPSocket doesnt like to be wrapped in a Promise. We work around by making the
|
||||
//promise resolve when .is_open is triggered
|
||||
var TCPSocket = Components.classes["@mozilla.org/tcp-socket;1"].createInstance(Components.interfaces.nsIDOMTCPSocket);
|
||||
this.sckt = TCPSocket.open(this.name, this.port, {binaryType:"arraybuffer"});
|
||||
var that = this; //inside .ondata/open etc this is lost
|
||||
this.sckt.ondata = function(event){
|
||||
//transform ArrayBuffer into number array
|
||||
var view = new DataView(event.data);
|
||||
var int_array = [];
|
||||
for(var i=0; i < view.byteLength; i++){
|
||||
int_array.push(view.getUint8(i));
|
||||
}
|
||||
console.log('ondata got bytes:', view.byteLength);
|
||||
that.buffer = [].concat(that.buffer, int_array);
|
||||
};
|
||||
this.sckt.onopen = function() {
|
||||
that.is_open = true;
|
||||
console.log('onopen');
|
||||
};
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var timer;
|
||||
var startTime = new Date().getTime();
|
||||
var check = function(){
|
||||
var now = new Date().getTime();
|
||||
if (( (now - startTime) / 1000) >= 20){
|
||||
clearInterval(timer);
|
||||
reject('socket timed out');
|
||||
return;
|
||||
}
|
||||
if (!that.is_open){
|
||||
console.log('Another timeout');
|
||||
return;
|
||||
}
|
||||
clearInterval(timer);
|
||||
console.log('promise resolved');
|
||||
resolve('ready');
|
||||
};
|
||||
timer = setInterval(check, 100);
|
||||
});
|
||||
|
||||
};
|
||||
Socket.prototype.send = function(data_in){
|
||||
//Transform number array into ArrayBuffer
|
||||
var sock = this.sckt;
|
||||
var ab = new ArrayBuffer(data_in.length);
|
||||
var dv = new DataView(ab);
|
||||
for(var i=0; i < data_in.length; i++){
|
||||
dv.setUint8(i, data_in[i]);
|
||||
}
|
||||
sock.send(ab, 0, ab.byteLength);
|
||||
}
|
||||
Socket.prototype.recv = function(is_handshake){
|
||||
if (typeof(is_handshake) === "undefined"){
|
||||
is_handshake = false;
|
||||
}
|
||||
var that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
console.log('in recv promise');
|
||||
var timer;
|
||||
var startTime = new Date().getTime();
|
||||
var tmp_buf = [];
|
||||
|
||||
var complete_records = [];
|
||||
var buf = [];
|
||||
//keep checking until either timeout or enough data gathered
|
||||
var check_recv = function(){
|
||||
var now = new Date().getTime();
|
||||
if (( (now - startTime) / 1000) >= 20){
|
||||
clearInterval(timer);
|
||||
console.log('rejecting');
|
||||
reject('socket timed out');
|
||||
return;
|
||||
}
|
||||
if (that.buffer.length === 0){
|
||||
console.log('Another timeout in recv');
|
||||
return;
|
||||
}
|
||||
buf = [].concat(buf, that.buffer);
|
||||
that.buffer = [];
|
||||
var rv = check_complete_records(buf);
|
||||
complete_records = [].concat(complete_records, rv.comprecs);
|
||||
if (! rv.is_complete){
|
||||
console.log("check_complete_records failed");
|
||||
buf = rv.incomprecs;
|
||||
return;
|
||||
}
|
||||
//else
|
||||
clearInterval(timer);
|
||||
console.log('promise resolved');
|
||||
resolve(complete_records);
|
||||
var TCPSocket = Components.classes["@mozilla.org/tcp-socket;1"].createInstance(Components.interfaces.nsIDOMTCPSocket);
|
||||
that.sckt = TCPSocket.open(that.name, that.port, {binaryType:"arraybuffer"});
|
||||
that.sckt.ondata = function(event){
|
||||
var int_array = ab2ba(event.data)
|
||||
console.log('ondata got bytes:', int_array.length);
|
||||
that.buffer = [].concat(that.buffer, int_array);
|
||||
};
|
||||
timer = setInterval(check_recv, 100);
|
||||
//dont wait for connect for too long
|
||||
var timer = setTimeout(function(){
|
||||
reject('connect: socket timed out');
|
||||
}, 1000*20)
|
||||
|
||||
that.sckt.onopen = function() {
|
||||
clearInterval(timer);
|
||||
console.log('onopen');
|
||||
resolve('ready');
|
||||
};
|
||||
that.sckt.onerror = function(event) {
|
||||
clearInterval(timer);
|
||||
console.log('onerror', event.data);
|
||||
reject(event.data);
|
||||
};
|
||||
});
|
||||
};
|
||||
Socket.prototype.send = function(data_in){
|
||||
var ab = ba2ab(data_in);
|
||||
this.sckt.send(ab, 0, ab.byteLength);
|
||||
};
|
||||
Socket.prototype.close = function(){
|
||||
this.sckt.close();
|
||||
};
|
||||
|
||||
@@ -306,6 +306,10 @@ function writeDatafile(data_with_headers, session_dir){
|
||||
type = 'pdf';
|
||||
break;
|
||||
}
|
||||
else if (header_lines[i].search("zip") > -1){
|
||||
type = 'zip';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type === "html"){
|
||||
@@ -675,6 +679,8 @@ function verifyCert(chain){
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//This must be at the bottom, otherwise we'd have to define each function
|
||||
//before it gets used.
|
||||
browser_specific_init();
|
||||
|
||||
107
content/socket.js
Normal file
107
content/socket.js
Normal file
@@ -0,0 +1,107 @@
|
||||
//The only way to determine if the server is done sending data is to check that the receiving
|
||||
//buffer has nothing but complete TLS records i.e. that there is no incomplete TLS records
|
||||
//However it was observed that in cases when getting e.g. zip files, some servers first send HTTP header as one
|
||||
//TLS record followed by the body as another record(s)
|
||||
//That's why after receiving a complete TLS record we wait to get some more data
|
||||
//This extra waiting must not be done for the handshake messages to avoid adding latency and having the handshake
|
||||
//dropped by the server
|
||||
function AbstractSocket(){};
|
||||
AbstractSocket.prototype.recv = function(is_handshake){
|
||||
if (typeof(is_handshake) === 'undefined'){
|
||||
is_handshake = false;
|
||||
}
|
||||
var that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var startTime = new Date().getTime();
|
||||
var complete_records = [];
|
||||
var buf = [];
|
||||
var resolved = false;
|
||||
|
||||
var timer = setTimeout(function(){
|
||||
reject('recv: socket timed out');
|
||||
resolved = true;
|
||||
}, that.recv_timeout);
|
||||
|
||||
var check = function(){
|
||||
//console.log('check()ing for more data', uid);
|
||||
if (resolved) {
|
||||
console.log('returning because resolved');
|
||||
return;
|
||||
}
|
||||
if (that.buffer.length === 0) {
|
||||
setTimeout(function(){check()}, 100);
|
||||
return;
|
||||
}
|
||||
console.log('new data in check', that.buffer.length);
|
||||
//else got new data
|
||||
buf = [].concat(buf, that.buffer);
|
||||
that.buffer = [];
|
||||
var rv = check_complete_records(buf);
|
||||
complete_records = [].concat(complete_records, rv.comprecs);
|
||||
if (! rv.is_complete){
|
||||
console.log("check_complete_records failed", that.uid);
|
||||
buf = rv.incomprecs;
|
||||
setTimeout(function(){check()}, 100);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
function finished_receiving(){
|
||||
clearTimeout(timer);
|
||||
console.log('recv promise resolving', that.uid);
|
||||
resolved = true;
|
||||
resolve(complete_records);
|
||||
};
|
||||
|
||||
console.log("got complete records", that.uid);
|
||||
if (is_handshake){
|
||||
finished_receiving();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
console.log("in recv waiting for an extra second", that.uid);
|
||||
buf = [];
|
||||
//give the server another second to send more data
|
||||
setTimeout(function(){
|
||||
if (that.buffer.length === 0){
|
||||
finished_receiving();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
console.log('more data received after waiting for a second', that.uid);
|
||||
check();
|
||||
}
|
||||
},1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
check();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function check_complete_records(d){
|
||||
/*'''Given a response d from a server,
|
||||
we want to know if its contents represents
|
||||
a complete set of records, however many.'''
|
||||
*/
|
||||
var complete_records = [];
|
||||
var incomplete_records = [];
|
||||
|
||||
while(d){
|
||||
if (d.length < 5){
|
||||
return {'is_complete':false, 'comprecs':complete_records, 'incomprecs':d};
|
||||
}
|
||||
var l = ba2int(d.slice(3,5));
|
||||
if (d.length < (l+5)){
|
||||
return {'is_complete':false, 'comprecs':complete_records, 'incomprecs':d};
|
||||
}
|
||||
else if(d.length === (l+5)){
|
||||
return {'is_complete':true, 'comprecs':[].concat(complete_records, d)};
|
||||
}
|
||||
else {
|
||||
complete_records = [].concat(complete_records, d.slice(0, l+5));
|
||||
d = d.slice(l+5);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,34 +64,6 @@ function get_cs(cs){
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function check_complete_records(d){
|
||||
/*'''Given a response d from a server,
|
||||
we want to know if its contents represents
|
||||
a complete set of records, however many.'''
|
||||
*/
|
||||
var complete_records = [];
|
||||
var incomplete_records = [];
|
||||
|
||||
while(d){
|
||||
var l = ba2int(d.slice(3,5));
|
||||
if (d.length < (l+5)){
|
||||
return {'is_complete':false, 'comprecs':complete_records, 'incomprecs':d};
|
||||
}
|
||||
else if(d.length === (l+5)){
|
||||
return {'is_complete':true, 'comprecs':[].concat(complete_records, d)};
|
||||
}
|
||||
else {
|
||||
complete_records = [].concat(complete_records, d.slice(0, l+5));
|
||||
d = d.slice(l+5);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function send_and_recv(command, data, expected_response){
|
||||
return new Promise(function(resolve, reject) {
|
||||
var req = get_xhr();
|
||||
|
||||
@@ -9,6 +9,16 @@ function ab2ba(ab){
|
||||
}
|
||||
|
||||
|
||||
function ba2ab(ba){
|
||||
var ab = new ArrayBuffer(ba.length);
|
||||
var dv = new DataView(ab);
|
||||
for(var i=0; i < ba.length; i++){
|
||||
dv.setUint8(i, ba[i]);
|
||||
}
|
||||
return ab;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ba2ua(ba){
|
||||
var ua = new Uint8Array(ba.length);
|
||||
@@ -71,7 +81,7 @@ function hex2ba(str){
|
||||
return ba;
|
||||
}
|
||||
|
||||
//Turn a max 4 byte array into an int.
|
||||
//Turn a max 4 byte array (big-endian) into an int.
|
||||
function ba2int( x ){
|
||||
assert(x.length <= 8, "Cannot convert bytearray larger than 8 bytes");
|
||||
var retval = 0;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>pagesigner@tlsnotary</em:id>
|
||||
<em:version>1.0.2</em:version>
|
||||
<em:version>1.0.3</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>true</em:unpack>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
"name": "PageSigner",
|
||||
"description": "PageSigner - a cryptographically secure webpage screenshot tool",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"author": "TLSNotary Group",
|
||||
|
||||
"permissions": [
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
"background": {
|
||||
"scripts": [
|
||||
"content/socket.js",
|
||||
"content/chrome/chrome_specific.js",
|
||||
"content/tlsn_utils.js",
|
||||
"content/oracles.js",
|
||||
|
||||
Reference in New Issue
Block a user