Skip to content

Writing Modules

Michael Hardy edited this page Apr 29, 2017 · 7 revisions

Understanding how Modules are Loaded

When the server loads it imports hijackingprevention/rec.py to set up a class that receives API requests and another that stores client-side scripts. It then imports config.py which is responsible for importing all the modules in use.
Each module when imported calls methods registering its functions with the API receiver class and the client side storage class.

How Modules Work

Modules contain a Four important parts: A section of JavaScript that is delivered to the client for the purposes of collecting data. A function, called a hasher, that hashes the session data, before the user it came from is know, with a site-wide salt. A function, called a translator, that hashes user data with a unique salt. And a Comparer that compares session data to user data.

Writing Modules

Boiler Plate

At the start of the module you will need to import these modules:

import hijackingprevention.rec as rec #API receiver and client side data
from tornado import gen #used for Tornado coroutines

You will also need to have decided on the name of your data type. The name should not collide with any others (for this reason I would recommend prefacing it with your GitHub username) and should remain consistent throughout the module. For example for this tutorial I will be using michardy:fingerprint

Writing the Collection Function(s)

The collection function is some JavaScript that is served to the client and run when they view the web page. Before writing this you should determine how your function is best run. Should it be called asynchronously and call the send function when it is done or should it be called synchronously and have data submission handled for it?

Synchronous Collection

  1. In the case of synchronous collection, we write a function that returns an array with the name of our module's data type and the data we want to collect:
fxn = '''
function fingerprint(){
	var sr = window.screen.height.toString() + window.screen.width.toString() + window.screen.availHeight.toString() + window.screen.availWidth.toString() + navigator.hardwareConcurrency.toString();
	return(['michardy:fingerprint', sr]); //[Datatype, data]
}
'''

Note: In some cases, especially with asynchronous functions, it can be useful to write sub-functions that are called by the main one.

  1. And register it making sure to pass False as the last argument to signify that our function is not asynchronous:
rec.mods.add('michardy:fingerprint', fxn, False)

The add method takes three arguments:

  • A string containing the name of the datatype
  • A string containing the function
  • A Boolean saying if the function is asynchronous

Asynchronous Collection

  1. In the case of asynchronous collection, we write a function that calls the send function with our data:
fxn = '''
function fingerprint(){
	//Pretend this takes a long time
	//For a good example of a task that needs to be async see modules/keystroke_dynamics
	var sr = window.screen.height.toString() + window.screen.width.toString() + window.screen.availHeight.toString() + window.screen.availWidth.toString() + navigator.hardwareConcurrency.toString();
	hijackingPreventionSend('michardy:fingerprint', sr, hijackingPreventionCK, hijackingPreventionSID);
}
'''

Note: In some cases, especially with asynchronous functions, it can be useful to write sub-functions that are called by the main one.

The hijackingPreventionSend function takes four arguments:

  • The name of the datatype you are submitting
  • The data
  • The Client Key
  • The Client SID
  1. And register it making sure to pass True as the last argument to signify that our function is asynchronous:
rec.mods.add('michardy:fingerprint', fxn, False)

The add method takes three arguments:

  • A string containing the name of the datatype
  • A string containing the function
  • A Boolean saying if the function is asynchronous

Data Hashing Functions

Hasher

Hashers follow this format:

@gen.coroutine
def hasher(data, headers, salt):
	#Do stuff
	return('Output as string or bytes')

rec.rec.add_hasher('michardy:fingerprint', hasher)

The first declares the function a Tornado coroutine and the second sets up a function that will be passed data, HTTP headers in a dictionary, and a salt.

Translator

While translators are very similar they follow a slightly different format

@gen.coroutine
def translator(data):
	#Generate unique salt
	#Do stuff
	return('Output as string or bytes')

rec.rec.add_translator('michardy:fingerprint', translator)

Note: When generating the output of the translator I would recommend appending the salt to hash like Bcrypt does (or better yet using Bcrypt). If you do not do this your life will become unnecessarily hard when you write the comparer

Comparer

The comparer takes two arguments: the current session hash and the user hash. It returns a floating point number describing how well the hashes match.

@gen.coroutine
def comparer(ses_hash, usr_hash):
	#Do stuff
	return(score)


rec.rec.add_comparer('michardy:fingerprint', comparer, 1)

The add_comparer method takes three arguments: the name of the datatype, the comparer function, and the maximum possible score.

You can’t perform that action at this time.