Generating a Config Key Hash with the Safe Exam Browser and Node.js
Purpose
Ensure that students are entering the Safe Exam Browser (SEB) with the config file that you set and not a tampered configuration.
SEB can send you a hash that represents the config file the student used to enter the exam. On the server, you can reconstruct what this hash should look like — by comparing the 2, you can determine whether to allow the student access to the exam or not.
Prerequisites
- SEB (Windows: 2.4 or higher, iOS: 2.1.16 or higher, Mac: 2.1.5pre2 or higher)
- Node.js
Steps:
Step 1: Get your .seb file
The SEB file is simply a plist so here’s a basic template:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>originatorVersion</key> <string>SEB_Win_2.0RC</string> <key>startURL</key> <string>http://localhost:3000</string> <key>sendBrowserExamKey</key> <true /> </dict></plist>
You MUST set the sendBrowserExamKey
to true as it false by default. Setting it to true tells SEB to send the config key hash with every HTTP request.
An important note: the config key hash sent from SEB doesn’t include keys that have the default value e.g. the default value for allowApplicationLog
is false so whether or not you add<string>allowApplicationLog</string> <false />
to the .seb file, there will be no impact (i.e. it is only added if it is non-default).
Step 2: Remove the originator version
This value changes depending on the client so it is removed. So it becomes something like this:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>startURL</key> <string>http://localhost:3000</string> <key>sendBrowserExamKey</key> <true /> </dict></plist>
Step 3: Order alphabetically according to keys
As the order impacts the eventual hash value, the keys must be ordered alphabetically. So it becomes something like this:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <dict> <key>sendBrowserExamKey</key> <true /> <key>startURL</key> <string>http://localhost:3000</string> </dict></plist>
(sendBrowserExamKey
comes before startURL
)
Step 4: Convert to JSON
Becomes something like this:
{"sendBrowserExamKey": true, "startURL": "http://localhost:3000"}
Step 5: Convert to string and remove all whitespace
{"sendBrowserExamKey":true,"startURL":"http://localhost:3000"}
Step 6: Encode this with SHA-256
1c6b12783657260462dca8eec00afdc28296f5606d1f53d038b1e5617c122148
Step 7: Add the absolute URL to the beginning
http://localhost:30001c6b12783657260462dca8eec00afdc28296f5606d1f53d038b1e5617c122148
Step 8: Encode this with SHA-256
7e562e27df798faaa5efeb1464f86fe2b374a8b9ee5f398095527ce87ee91189
This should be the header that SEB passes you!
Doing this programmatically with Node.js
Simply construct a JSON object of what you expect the config file to be (Step 4) and continue on from there.
Here’s a simple example (I used sha.js for SHA256 encoding):
var shajs = require('sha.js');function getExpectedHash(expectedUrl) { // Ensure that these are alphabetised, originator version is removed and no default values are included
// You'll probably want to add more complex config here! const sebJson = { "sendBrowserExamKey": true, "startURL": expectedUrl }; // Convert to string and remove whitespace const sebJsonString = JSON.stringify(sebJson).replace(' ', ''); // Hash the expected config file const hashedSebJson = shajs('sha256').update(sebJsonString).digest('hex'); // Hash the url + hashed expected config file const expectedHash = shajs('sha256').update(url + hashedSebJson).digest('hex'); return expectedHash;}
And how you can use this function with Express:
router.get('/', function(req, res, next) { // Deduce the correct config key hash const expectedHash = getExpectedHash('someExpectedUrl'); const returnedHash = req.headers['x-safeexambrowser-configkeyhash'];
// Only allow if it is the correct config key hash const isCorrectFile = expectedHash === returnedHash; if (isCorrectFile) { // ALLOW ACCESS } else { // DON'T ALLOW ACCESS }});
Conclusion
There’s a simple example of how you can reconstruct the config key hash with Node.
A huge thanks to the team at SEB, it’s a super cool project!