@s89/ble-ancs
Version:
An Apple ANCS reciever from Linux. It is a combination of the Bleno, Noble and ANCS projects from Sandeep Mistry
370 lines (283 loc) • 9.96 kB
JavaScript
var debug = require('debug')('smp');
var events = require('events');
var util = require('util');
var crypto = require('./crypto');
var SMP_CID = 0x0006;
var SMP_PAIRING_REQUEST = 0x01;
var SMP_PAIRING_RESPONSE = 0x02;
var SMP_PAIRING_CONFIRM = 0x03;
var SMP_PAIRING_RANDOM = 0x04;
var SMP_PAIRING_FAILED = 0x05;
var SMP_ENCRYPT_INFO = 0x06;
var SMP_MASTER_IDENT = 0x07;
var LTK_INFO_SIZE = 36;
var MGMT_OP_LOAD_LONG_TERM_KEYS = 0x0013;
var Smp = function(aclStream, localAddressType, localAddress, remoteAddressType, remoteAddress) {
this._aclStream = aclStream;
this._ltkInfos = [];
//Bleno
this._iat = new Buffer([(remoteAddressType === 'random') ? 0x01 : 0x00]);
this._ia = new Buffer(remoteAddress.split(':').reverse().join(''), 'hex');
this._rat = new Buffer([(localAddressType === 'random') ? 0x01 : 0x00]);
this._ra = new Buffer(localAddress.split(':').reverse().join(''), 'hex');
/*
this._iat = new Buffer([(localAddressType === 'random') ? 0x01 : 0x00]);
this._ia = new Buffer(localAddress.split(':').reverse().join(''), 'hex');
this._rat = new Buffer([(remoteAddressType === 'random') ? 0x01 : 0x00]);
this._ra = new Buffer(remoteAddress.split(':').reverse().join(''), 'hex');
*/
this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
this.onAclStreamEncryptChangeBinded = this.onAclStreamEncryptChange.bind(this);
this.onAclStreamLtkNegReplyBinded = this.onAclStreamLtkNegReply.bind(this);
this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
this._aclStream.on('data', this.onAclStreamDataBinded);
this._aclStream.on('encryptChange', this.onAclStreamEncryptChangeBinded);
this._aclStream.on('ltkNegReply', this.onAclStreamLtkNegReplyBinded);
this._aclStream.on('end', this.onAclStreamEndBinded);
};
util.inherits(Smp, events.EventEmitter);
Smp.prototype.sendPairingRequest = function() {
console.log("Sending pairing request");
this._preq = new Buffer([
SMP_PAIRING_REQUEST,
0x03, //0x04, // IO capability: NoInputNoOutput
0x00, // OOB data: Authentication data not present
0x01, //0x05, // Authentication requirement: Bonding - No MITM
0x10, // Max encryption key size
0x00, //0x03, // Initiator key distribution: <none>
0x01 //0x03 // Responder key distribution: EncKey
]);
this.write(this._preq);
};
Smp.prototype.handlePairingRequest = function(data) {
this._preq = data;
console.log("Recieved a Pairing Request");
this._pres = new Buffer([
SMP_PAIRING_RESPONSE,
0x03, // IO capability: NoInputNoOutput
0x00, // OOB data: Authentication data not present
0x05, //0x01, // Authentication requirement: Bonding - No MITM
0x10, // Max encryption key size
0x03, //0x00, // Initiator key distribution: <none>
0x01 //0x01 // Responder key distribution: EncKey
]);
debug('\tShould be Sending: ' + this._pres.toString('hex'));
//this.write(this._pres);
};
/*
Smp.prototype.handlePairingRequest = function(data) {
this._preq = data;
console.log("Recieved a Pairing Request");
this._pres = new Buffer([
SMP_PAIRING_RESPONSE,
0x03, // IO capability: NoInputNoOutput
0x00, // OOB data: Authentication data not present
0x01, // Authentication requirement: Bonding - No MITM
0x10, // Max encryption key size
0x00, // Initiator key distribution: <none>
0x01 // Responder key distribution: EncKey
]);
this.write(this._pres);
};
*/
Smp.prototype.onAclStreamLtkNegReply = function() {
debug("SMP recieved LtkNegReply");
this.write(new Buffer([
SMP_PAIRING_FAILED,
SMP_UNSPECIFIED
]));
this.emit('fail');
};
Smp.prototype.onAclStreamData = function(cid, data) {
if (cid !== SMP_CID) {
return;
}
var code = data.readUInt8(0);
debug("SMP Data - code: 0x" + code.toString(16));
if (SMP_PAIRING_REQUEST === code) {
this.handlePairingRequest(data);
} else if (SMP_PAIRING_RESPONSE === code) {
this.handlePairingResponse(data);
} else if (SMP_PAIRING_CONFIRM === code) {
this.handlePairingConfirm(data);
} else if (SMP_PAIRING_RANDOM === code) {
this.handlePairingRandom(data);
} else if (SMP_PAIRING_FAILED === code) {
this.handlePairingFailed(data);
} else if (SMP_ENCRYPT_INFO === code) {
this.handleEncryptInfo(data);
} else if (SMP_MASTER_IDENT === code) {
this.handleMasterIdent(data);
}
};
Smp.prototype.onAclStreamEnd = function() {
this._aclStream.removeListener('data', this.onAclStreamDataBinded);
this._aclStream.removeListener('end', this.onAclStreamEndBinded);
this._aclStream.removeListener('encryptChange', this.onAclStreamEncryptChangeBinded);
this._aclStream.removeListener('ltkNegReply', this.onAclStreamLtkNegReplyBinded);
this.emit('end');
};
Smp.prototype.onAclStreamEncryptChange = function(encrypted) {
if (encrypted) {
if (this._stk && this._diversifier && this._random) {
this.write(Buffer.concat([
new Buffer([SMP_ENCRYPT_INFO]),
this._stk
]));
this.write(Buffer.concat([
new Buffer([SMP_MASTER_IDENT]),
this._diversifier,
this._random
]));
}
}
};
Smp.prototype.handlePairingResponse = function(data) {
this._pres = data;
debug("Recieved a Pairing Response");
debug('\t\tIO Capability: 0x' + data[1].toString(16));
debug('\t\tOOB Data: 0x' + data[2].toString(16));
debug('\t\tAuthentication Requirement: 0x' + data[3].toString(16));
debug('\t\tMax Encryption Size: 0x' + data[4].toString(16));
debug('\t\tInitiator Key: 0x' + data[5].toString(16));
debug('\t\tResponder Key: 0x' + data[6].toString(16));
this._tk = new Buffer('00000000000000000000000000000000', 'hex');
this._r = crypto.r();
/*
this.write(Buffer.concat([
new Buffer([SMP_PAIRING_CONFIRM]),
crypto.c1(this._tk, this._r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra)
]));*/
};
//Bleno
Smp.prototype.handlePairingConfirm = function(data) {
this._pcnf = data;
this._tk = new Buffer('00000000000000000000000000000000', 'hex');
this._r = crypto.r();
debug("Recieved a Pairing Confirm");
/*this.write(Buffer.concat([
new Buffer([SMP_PAIRING_CONFIRM]),
crypto.c1(this._tk, this._r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra)
]));*/
};
/*
//Noble
Smp.prototype.handlePairingConfirm = function(data) {
this._pcnf = data;
this.write(Buffer.concat([
new Buffer([SMP_PAIRING_RANDOM]),
this._r
]));
};*/
//Bleno
Smp.prototype.addLongTermKey = function(address, addressType, authenticated, master, ediv, rand, key) {
var ltkInfo = new Buffer(LTK_INFO_SIZE);
address.copy(ltkInfo, 0);
ltkInfo.writeUInt8(addressType.readUInt8(0) + 1, 6); // BDADDR_LE_PUBLIC = 0x01, BDADDR_LE_RANDOM 0x02, so add one
ltkInfo.writeUInt8(authenticated, 7);
ltkInfo.writeUInt8(master, 8);
ltkInfo.writeUInt8(key.length, 9);
ediv.copy(ltkInfo, 10);
rand.copy(ltkInfo, 12);
key.copy(ltkInfo, 20);
this._ltkInfos.push(ltkInfo);
this.loadLongTermKeys();
};
Smp.prototype.clearLongTermKeys = function() {
this._ltkInfos = [];
this.loadLongTermKeys();
};
Smp.prototype.loadLongTermKeys = function() {
var numLongTermKeys = this._ltkInfos.length;
var op = new Buffer(2 + numLongTermKeys * LTK_INFO_SIZE);
op.writeUInt16LE(numLongTermKeys, 0);
debug("Adding Long Term Keys")
for (var i = 0; i < numLongTermKeys; i++) {
debug('\t\t'+this._ltkInfos[i]);
this._ltkInfos[i].copy(op, 2 + i * LTK_INFO_SIZE);
}
//this.write(MGMT_OP_LOAD_LONG_TERM_KEYS, 0, op);
this.mgmtWrite(MGMT_OP_LOAD_LONG_TERM_KEYS, 0, op);
};
Smp.prototype.mgmtWrite = function(opcode, index, data) {
var length = 0;
if (data) {
length = data.length;
}
var pkt = new Buffer(6 + length);
pkt.writeUInt16LE(opcode, 0);
pkt.writeUInt16LE(index, 2);
pkt.writeUInt16LE(length, 4);
if (length) {
data.copy(pkt, 6);
}
debug('Mgmt writing -> ' + pkt.toString('hex'));
this.write(pkt);
};
Smp.prototype.handlePairingRandom = function(data) {
var r = data.slice(1);
debug("Handle Pairing Random: ");
/*
var pcnf = Buffer.concat([
new Buffer([SMP_PAIRING_CONFIRM]),
crypto.c1(this._tk, r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra)
]);
if (this._pcnf.toString('hex') === pcnf.toString('hex')) {
debug('\t\tRandom Worked: ');
this._diversifier = new Buffer('0000', 'hex');
this._random = new Buffer('0000000000000000', 'hex');
this._stk = crypto.s1(this._tk, this._r, r);
this.addLongTermKey(this._ia, this._iat, 0, 0, this._diversifier, this._random, this._stk);
this.write(Buffer.concat([
new Buffer([SMP_PAIRING_RANDOM]),
this._r
]));
} else {
debug('\t\tRandom failed: ');
this.write(new Buffer([
SMP_PAIRING_FAILED,
SMP_PAIRING_CONFIRM
]));
this.emit('fail');
}
*/
};
/*
//Noble
Smp.prototype.handlePairingRandom = function(data) {
var r = data.slice(1);
var pcnf = Buffer.concat([
new Buffer([SMP_PAIRING_CONFIRM]),
crypto.c1(this._tk, r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra)
]);
if (this._pcnf.toString('hex') === pcnf.toString('hex')) {
var stk = crypto.s1(this._tk, r, this._r);
this.emit('stk', stk);
} else {
this.write(new Buffer([
SMP_PAIRING_RANDOM,
SMP_PAIRING_CONFIRM
]));
this.emit('fail');
}
};
*/
Smp.prototype.handlePairingFailed = function(data) {
debug('Pairing Failed!');
debug('\t\tReason: ' + data[1].toString(16));
this.emit('fail');
};
Smp.prototype.handleEncryptInfo = function(data) {
var ltk = data.slice(1);
debug("Encrypt Info")
this.emit('ltk', ltk);
};
Smp.prototype.handleMasterIdent = function(data) {
var ediv = data.slice(1, 3);
var rand = data.slice(3);
this.emit('masterIdent', ediv, rand);
};
Smp.prototype.write = function(data) {
this._aclStream.write(SMP_CID, data);
};
module.exports = Smp;