Socket recv() abstracted away. Add 1 second delay after receiving complete records. Support zip content type. Version bump 1.0.3

This commit is contained in:
themighty1
2015-09-11 17:14:35 +02:00
parent 8788783dec
commit b86fd2c255
9 changed files with 182 additions and 191 deletions

1
bootstrap.js vendored
View File

@@ -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");

View File

@@ -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});
};

View File

@@ -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();
};

View File

@@ -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
View 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;
}
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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>

View File

@@ -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",