@abandonware/bleno
Version:
A Node.js module for implementing BLE (Bluetooth Low Energy) peripherals
164 lines (129 loc) • 4.69 kB
JavaScript
const debug = require('debug')('smp');
const { EventEmitter } = require('events');
const crypto = require('./crypto');
const mgmt = require('./mgmt');
const SMP_CID = 0x0006;
const SMP_PAIRING_REQUEST = 0x01;
const SMP_PAIRING_RESPONSE = 0x02;
const SMP_PAIRING_CONFIRM = 0x03;
const SMP_PAIRING_RANDOM = 0x04;
const SMP_PAIRING_FAILED = 0x05;
const SMP_ENCRYPT_INFO = 0x06;
const SMP_MASTER_IDENT = 0x07;
const SMP_UNSPECIFIED = 0x08;
class Smp extends EventEmitter {
constructor(aclStream, localAddressType, localAddress, remoteAddressType, remoteAddress) {
super();
this._aclStream = aclStream;
this._iat = Buffer.from([(remoteAddressType === 'random') ? 0x01 : 0x00]);
this._ia = Buffer.from(remoteAddress.split(':').reverse().join(''), 'hex');
this._rat = Buffer.from([(localAddressType === 'random') ? 0x01 : 0x00]);
this._ra = Buffer.from(localAddress.split(':').reverse().join(''), 'hex');
this._stk = null;
this._random = null;
this._diversifier = null;
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);
}
onAclStreamData(cid, data) {
if (cid !== SMP_CID) {
return;
}
const code = data.readUInt8(0);
if (SMP_PAIRING_REQUEST === code) {
this.handlePairingRequest(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);
}
}
onAclStreamEncryptChange(encrypted) {
if (encrypted) {
if (this._stk && this._diversifier && this._random) {
this.write(Buffer.concat([
Buffer.from([SMP_ENCRYPT_INFO]),
this._stk
]));
this.write(Buffer.concat([
Buffer.from([SMP_MASTER_IDENT]),
this._diversifier,
this._random
]));
}
}
}
onAclStreamLtkNegReply() {
this.write(Buffer.from([
SMP_PAIRING_FAILED,
SMP_UNSPECIFIED
]));
this.emit('fail');
}
onAclStreamEnd() {
this._aclStream.removeListener('data', this.onAclStreamDataBinded);
this._aclStream.removeListener('encryptChange', this.onAclStreamEncryptChangeBinded);
this._aclStream.removeListener('ltkNegReply', this.onAclStreamLtkNegReplyBinded);
this._aclStream.removeListener('end', this.onAclStreamEndBinded);
}
handlePairingRequest(data) {
this._preq = data;
this._pres = Buffer.from([
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);
}
handlePairingConfirm(data) {
this._pcnf = data;
this._tk = Buffer.from('00000000000000000000000000000000', 'hex');
this._r = crypto.r();
this.write(Buffer.concat([
Buffer.from([SMP_PAIRING_CONFIRM]),
crypto.c1(this._tk, this._r, this._pres, this._preq, this._iat, this._ia, this._rat, this._ra)
]));
}
handlePairingRandom(data) {
const r = data.slice(1);
const pcnf = Buffer.concat([
Buffer.from([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')) {
this._diversifier = Buffer.from('0000', 'hex');
this._random = Buffer.from('0000000000000000', 'hex');
this._stk = crypto.s1(this._tk, this._r, r);
mgmt.addLongTermKey(this._ia, this._iat, 0, 0, this._diversifier, this._random, this._stk);
this.write(Buffer.concat([
Buffer.from([SMP_PAIRING_RANDOM]),
this._r
]));
} else {
this.write(Buffer.from([
SMP_PAIRING_FAILED,
SMP_PAIRING_CONFIRM
]));
this.emit('fail');
}
}
handlePairingFailed(data) {
this.emit('fail');
}
write(data) {
this._aclStream.write(SMP_CID, data);
}
}
module.exports = Smp;