diff --git a/august.js b/august.js deleted file mode 100644 index cbf1e3fd82c621865e3c7005dde1bb523d0926e2..0000000000000000000000000000000000000000 --- a/august.js +++ /dev/null @@ -1,5 +0,0 @@ -var Lock = require('./lock'); - -module.exports = { - Lock: Lock -}; diff --git a/cli.js b/cli.js index edc51aa510bab3961ee08007e98328b21674bcac..b71489efd718fca6971bad72e0ed8b2d4d581a1b 100644 --- a/cli.js +++ b/cli.js @@ -1,6 +1,6 @@ #!/usr/bin/env node -var Lock = require('./lock'); +var Lock = require('./lib/lock'); var noble = require('noble'); var argv = require('yargs') diff --git a/index.js b/index.js new file mode 100644 index 0000000000000000000000000000000000000000..0c671a8b2b56166a6bbac13c11d83c78af675cf7 --- /dev/null +++ b/index.js @@ -0,0 +1,7 @@ +'use strict'; + +var Lock = require('./lib/lock'); + +module.exports = { + Lock: Lock +}; diff --git a/lock.js b/lib/lock.js similarity index 50% rename from lock.js rename to lib/lock.js index 30bd2d0a4442719ef6f151cdb9ad9e1a01cc234e..ee2c88e3628172bd50c39469de7da5de738f1024 100644 --- a/lock.js +++ b/lib/lock.js @@ -1,15 +1,6 @@ -var Promise = require('bluebird'); -var crypto = require('crypto'); -var debug = require('debug')('august'); -var events = require('events'); -var noble = require('noble'); -var util = require('util'); -var _ = require('underscore'); +'use strict'; -// promisification of noble -Promise.promisifyAll(require('noble/lib/characteristic').prototype); -Promise.promisifyAll(require('noble/lib/peripheral').prototype); -Promise.promisifyAll(require('noble/lib/service').prototype); +var LockSession = require('./lock_session'); // relevant UUIDs - w/ this library, must be lowercase and without hyphens const BLE_COMMAND_SERVICE = "bd4ac6100b4511e38ffd0800200c9a66"; @@ -28,119 +19,6 @@ function LockCommand() { return cmd; } -// Calculates the security checksum of a command buffer. -function securityChecksum(buffer) { - return (0 - (buffer.readUInt32LE(0x00) + buffer.readUInt32LE(0x04) + buffer.readUInt32LE(0x08))) >>> 0; -} - -/// -// LockSession - -function LockSession(writeCharacteristic, readCharacteristic, isSecure) { - if (!writeCharacteristic || !readCharacteristic) { - throw new Error('write and/or read characteristic not found'); - } - this._writeCharacteristic = writeCharacteristic; - this._readCharacteristic = readCharacteristic; - this._isSecure = isSecure; - return this; -} - -util.inherits(LockSession, events.EventEmitter); - -LockSession.prototype.setKey = function(key) { - var cipherSuite, iv; - if (this._isSecure) { - cipherSuite = 'aes-128-ecb'; - iv = ''; - } else { - cipherSuite = 'aes-128-cbc'; - iv = new Buffer(0x10); - iv.fill(0); - } - - this._encryptCipher = crypto.createCipheriv(cipherSuite, key, iv); - this._encryptCipher.setAutoPadding(false); - this._decryptCipher = crypto.createDecipheriv(cipherSuite, key, iv); - this._decryptCipher.setAutoPadding(false); -}; - -LockSession.prototype.start = function() { - // decrypt all reads, modifying the buffer in place - this._readCharacteristic.on('read', function(data, isNotify) { - if (!data) { - throw new Error('read returned no data'); - } - - debug('read data: ' + data.toString('hex')); - - if (this._decryptCipher) { - var cipherText = data.slice(0x00, 0x10); - var plainText = this._decryptCipher.update(cipherText); - plainText.copy(cipherText); - - debug('decrypted data: ' + data.toString('hex')); - } - - // the notification flag is not being set properly on OSX Yosemite, so just - // forcing it to true. - if (process.platform === 'darwin') { - isNotify = true; - } - - if (isNotify) { - this.emit('notification', data); - } - }.bind(this)); - - // enable notifications on the read characterestic - debug('enabling notifications on ' + this._readCharacteristic); - return this._readCharacteristic.notifyAsync(true); -}; - -LockSession.prototype.execute = function(command) { - // write the security checksum if on the secure channel - if (this._isSecure) { - var checksum = securityChecksum(command); - command.writeUInt32LE(checksum, 0x0c); - } - - debug((this._isSecure ? 'secure ' : '') + 'execute command: ' + command.toString('hex')); - - // NOTE: the last two bytes are not encrypted - // general idea seems to be that if the last byte of the command indicates an offline key offset (is non-zero), the command is "secure" and encrypted with the offline key - if (this._encryptCipher) { - var plainText = command.slice(0x00, 0x10); - var cipherText = this._encryptCipher.update(plainText); - cipherText.copy(plainText); - debug('execute command (encrypted): ' + command.toString('hex')); - } - - // register the notification event listener here, before issuing the write, as the - // response notification arrives before the write response. - var waitForNotification = new Promise(function(resolve) { - this.once('notification', resolve); - }.bind(this)); - - return this._writeCharacteristic.writeAsync(command, false).then(function() { - debug('write successful, waiting for notification...'); - return waitForNotification; - }).then(function(data) { - // perform some basic validation before passing it on - if (this._isSecure) { - if (securityChecksum(data) !== data.readUInt32LE(0x0c)) { - throw new Error("security checksum mismatch"); - } - } else { - if (data[0] !== 0xbb && data[0] !== 0xaa) { - throw new Error("unexpected magic in response"); - } - } - - return data; - }.bind(this)); -}; - /// // Lock object. diff --git a/lib/lock_session.js b/lib/lock_session.js new file mode 100644 index 0000000000000000000000000000000000000000..c81b42e3205ad2016b510c3fe474953f7d45ce4a --- /dev/null +++ b/lib/lock_session.js @@ -0,0 +1,129 @@ +'use strict'; + +var Promise = require('bluebird'); +var crypto = require('crypto'); +var debug = require('debug')('august'); +var events = require('events'); +var noble = require('noble'); +var util = require('util'); +var _ = require('underscore'); + +// promisification of noble +Promise.promisifyAll(require('noble/lib/characteristic').prototype); +Promise.promisifyAll(require('noble/lib/peripheral').prototype); +Promise.promisifyAll(require('noble/lib/service').prototype); + +// Calculates the security checksum of a command buffer. +function securityChecksum(buffer) { + return (0 - (buffer.readUInt32LE(0x00) + buffer.readUInt32LE(0x04) + buffer.readUInt32LE(0x08))) >>> 0; +} + +/// +// LockSession + +function LockSession(writeCharacteristic, readCharacteristic, isSecure) { + if (!writeCharacteristic || !readCharacteristic) { + throw new Error('write and/or read characteristic not found'); + } + this._writeCharacteristic = writeCharacteristic; + this._readCharacteristic = readCharacteristic; + this._isSecure = isSecure; + return this; +} + +util.inherits(LockSession, events.EventEmitter); + +LockSession.prototype.setKey = function(key) { + var cipherSuite, iv; + if (this._isSecure) { + cipherSuite = 'aes-128-ecb'; + iv = ''; + } else { + cipherSuite = 'aes-128-cbc'; + iv = new Buffer(0x10); + iv.fill(0); + } + + this._encryptCipher = crypto.createCipheriv(cipherSuite, key, iv); + this._encryptCipher.setAutoPadding(false); + this._decryptCipher = crypto.createDecipheriv(cipherSuite, key, iv); + this._decryptCipher.setAutoPadding(false); +}; + +LockSession.prototype.start = function() { + // decrypt all reads, modifying the buffer in place + this._readCharacteristic.on('read', function(data, isNotify) { + if (!data) { + throw new Error('read returned no data'); + } + + debug('read data: ' + data.toString('hex')); + + if (this._decryptCipher) { + var cipherText = data.slice(0x00, 0x10); + var plainText = this._decryptCipher.update(cipherText); + plainText.copy(cipherText); + + debug('decrypted data: ' + data.toString('hex')); + } + + // the notification flag is not being set properly on OSX Yosemite, so just + // forcing it to true. + if (process.platform === 'darwin') { + isNotify = true; + } + + if (isNotify) { + this.emit('notification', data); + } + }.bind(this)); + + // enable notifications on the read characterestic + debug('enabling notifications on ' + this._readCharacteristic); + return this._readCharacteristic.notifyAsync(true); +}; + +LockSession.prototype.execute = function(command) { + // write the security checksum if on the secure channel + if (this._isSecure) { + var checksum = securityChecksum(command); + command.writeUInt32LE(checksum, 0x0c); + } + + debug((this._isSecure ? 'secure ' : '') + 'execute command: ' + command.toString('hex')); + + // NOTE: the last two bytes are not encrypted + // general idea seems to be that if the last byte of the command indicates an offline key offset (is non-zero), the command is "secure" and encrypted with the offline key + if (this._encryptCipher) { + var plainText = command.slice(0x00, 0x10); + var cipherText = this._encryptCipher.update(plainText); + cipherText.copy(plainText); + debug('execute command (encrypted): ' + command.toString('hex')); + } + + // register the notification event listener here, before issuing the write, as the + // response notification arrives before the write response. + var waitForNotification = new Promise(function(resolve) { + this.once('notification', resolve); + }.bind(this)); + + return this._writeCharacteristic.writeAsync(command, false).then(function() { + debug('write successful, waiting for notification...'); + return waitForNotification; + }).then(function(data) { + // perform some basic validation before passing it on + if (this._isSecure) { + if (securityChecksum(data) !== data.readUInt32LE(0x0c)) { + throw new Error("security checksum mismatch"); + } + } else { + if (data[0] !== 0xbb && data[0] !== 0xaa) { + throw new Error("unexpected magic in response"); + } + } + + return data; + }.bind(this)); +}; + +module.exports = LockSession; diff --git a/tools/decode_capture.js b/tools/decode_capture.js index d9c5f54cbfbe4f67878b86dc3ee0f340c3313999..150d20b5b712029c07b57e7b5af1a0d95fdf63f6 100644 --- a/tools/decode_capture.js +++ b/tools/decode_capture.js @@ -1,3 +1,5 @@ +'use strict'; + // Decrypts a btsnoop_hci.log from an Android device (enabled in developer options.) // First use tshark to create a text log from a hci capture: diff --git a/tools/decrypt_preferences.js b/tools/decrypt_preferences.js index a1477380ac95211b96a24be13a7a5e6da2871358..f19cecded633221a3c6a1e6993f43ed2676fde50 100644 --- a/tools/decrypt_preferences.js +++ b/tools/decrypt_preferences.js @@ -1,3 +1,5 @@ +'use strict'; + var crypto = require('crypto'); var fs = require('fs');