From 69cdecd267c0b201469e294a00dd66d8193c6180 Mon Sep 17 00:00:00 2001 From: Martin Ziel Date: Tue, 1 Sep 2020 11:52:12 +0200 Subject: [PATCH 1/2] Fixed smartlock pairing by removing intermediate pairing steps --- src/lib/smartLockPairer.ts | 153 +++++++++++-------------------------- src/lib/states.ts | 9 +-- 2 files changed, 45 insertions(+), 117 deletions(-) diff --git a/src/lib/smartLockPairer.ts b/src/lib/smartLockPairer.ts index 3afcb1d..1c67bcd 100644 --- a/src/lib/smartLockPairer.ts +++ b/src/lib/smartLockPairer.ts @@ -9,13 +9,9 @@ export class SmartLockPairer extends Events.EventEmitter { private nukiPairingCharacteristic: import("noble").Characteristic; private state: PairingState = PairingState.IDLE; private config: NukiConfig; - private partialPayload: Buffer | null = null; private nonceABF: Uint8Array | null = null; private asBridge: boolean; - // The first packet should not be verified as it does not contain any CRC and is only partial. - private verifyCRC: boolean = false; - constructor(nukiPairingCharacteristic: import("noble").Characteristic, nukiConfig: NukiConfig, asBridge: boolean) { super(); @@ -71,10 +67,6 @@ export class SmartLockPairer extends Events.EventEmitter { } private validateCRC(data: Buffer): boolean { - if (this.partialPayload) { - data = Buffer.concat([this.partialPayload, data]); - } - if (!SmartLock.verifyCRC(data)) { let errorMessage = ErrorHandler.errorToMessage(GeneralError.BAD_CRC); @@ -111,7 +103,7 @@ export class SmartLockPairer extends Events.EventEmitter { private pairingDataReceived(payload: Buffer, isNotification: boolean): void { // Only check CRC if we should. - if (this.verifyCRC && !this.validateCRC(payload)) return; + if (!this.validateCRC(payload)) return; let data: Buffer; @@ -121,24 +113,14 @@ export class SmartLockPairer extends Events.EventEmitter { if (this.getCommandFromPayload(payload) != Command.PUBLIC_KEY) { this.printErrorMessage("Unexpected data received during REQ_PUB_KEY", payload); } else { - this.partialPayload = payload; - this.verifyCRC = true; - this.state = PairingState.REQ_PUB_KEY_FIN; - } - break; + this.config.credentials.slPublicKey = this.getDataFromPayload(payload); - // Smartlock has sent it's public key. We send ours now. - case PairingState.REQ_PUB_KEY_FIN: - this.config.credentials.slPublicKey = this.getDataFromPayload(Buffer.concat([this.partialPayload!, payload])); - this.partialPayload = null; + data = SmartLock.prepareCommand(Command.PUBLIC_KEY, new Buffer(this.config.credentials.publicKey)); - data = SmartLock.prepareCommand(Command.PUBLIC_KEY, new Buffer(this.config.credentials.publicKey)); - - this.writeData(data); - - this.verifyCRC = false; - this.state = PairingState.REQ_CHALLENGE; + this.writeData(data); + this.state = PairingState.REQ_CHALLENGE; + } break; // SmartLock has sent the first part of the challenge. @@ -146,124 +128,77 @@ export class SmartLockPairer extends Events.EventEmitter { if (this.getCommandFromPayload(payload) != Command.CHALLENGE) { this.printErrorMessage("Unexpected data received during REQ_CHALLENGE", payload); } else { - this.partialPayload = payload; - this.verifyCRC = true; - this.state = PairingState.REQ_CHALLENGE_FIN; - } - break; - - // Smartlock has sent the challenge. We calculate the authenticator and send it. - case PairingState.REQ_CHALLENGE_FIN: - let nonceK: Buffer = this.getDataFromPayload(Buffer.concat([this.partialPayload!, payload])); - this.partialPayload = null; + let nonceK: Buffer = this.getDataFromPayload(payload); - let r: Buffer = Buffer.concat([this.config.credentials.publicKey, this.config.credentials.slPublicKey, nonceK]); + let r: Buffer = Buffer.concat([this.config.credentials.publicKey, this.config.credentials.slPublicKey, nonceK]); - let authenticator: Buffer = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r).digest(); + let authenticator: Buffer = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r).digest(); - data = SmartLock.prepareCommand(Command.AUTH_AUTHENTICATOR, authenticator); + data = SmartLock.prepareCommand(Command.AUTH_AUTHENTICATOR, authenticator); - this.writeData(data); + this.writeData(data); - this.verifyCRC = false; - this.state = PairingState.REQ_CHALLENGE_AUTH; + this.state = PairingState.REQ_CHALLENGE_AUTH; + } break; - + // Smartlock has sent the first part of the second challenge. case PairingState.REQ_CHALLENGE_AUTH: if (this.getCommandFromPayload(payload) != Command.CHALLENGE) { this.printErrorMessage("Unexpected data received DURING REQ_CHALLENGE_AUTH", payload); } else { - this.partialPayload = payload; - this.verifyCRC = true; - this.state = PairingState.REQ_CHALLENGE_AUTH_FIN; - } - break; - - // Smartlock has sent the challenge. We calculate the authorization data and send it. - case PairingState.REQ_CHALLENGE_AUTH_FIN: - let nonceK2: Buffer = this.getDataFromPayload(Buffer.concat([this.partialPayload!, payload])); - this.partialPayload = null; + let nonceK2: Buffer = this.getDataFromPayload(payload); - let authData: Buffer = this.generateAuthorizationData(); + let authData: Buffer = this.generateAuthorizationData(); - this.nonceABF = SmartLock.generateNonce(32); + this.nonceABF = SmartLock.generateNonce(32); - let r2: Buffer = Buffer.concat([authData, this.nonceABF, nonceK2]); + let r2: Buffer = Buffer.concat([authData, this.nonceABF, nonceK2]); - let authenticator2: Buffer = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r2).digest(); + let authenticator2: Buffer = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r2).digest(); - data = Buffer.concat([authenticator2, authData, this.nonceABF]); + data = Buffer.concat([authenticator2, authData, this.nonceABF]); - data = SmartLock.prepareCommand(Command.AUTH_DATA, data); - - this.writeData(data); - - this.verifyCRC = false; - this.state = PairingState.REQ_AUTH_ID_A; + data = SmartLock.prepareCommand(Command.AUTH_DATA, data); + this.writeData(data); + + this.state = PairingState.REQ_AUTH_ID; + } break; //Smartlock has sent the first part of the authorization id - case PairingState.REQ_AUTH_ID_A: + case PairingState.REQ_AUTH_ID: if (this.getCommandFromPayload(payload) != Command.AUTH_ID) { this.printErrorMessage("Unexpected data received during REQ_AUTH_ID_A", payload); } else { - this.partialPayload = payload; - this.state = PairingState.REQ_AUTH_ID_B; - } - break; - - //Smartlock has sent the second part of the authorization id - case PairingState.REQ_AUTH_ID_B: - this.partialPayload = Buffer.concat([this.partialPayload!, payload]); - this.state = PairingState.REQ_AUTH_ID_C; - - break; - - //Smartlock has sent the third part of the authorization id - case PairingState.REQ_AUTH_ID_C: - this.partialPayload = Buffer.concat([this.partialPayload!, payload]); - this.state = PairingState.REQ_AUTH_ID_D; + let auth: Buffer = this.getDataFromPayload(payload); - break; - - //Smartlock has sent the fourth part of the authorization id - case PairingState.REQ_AUTH_ID_D: - this.partialPayload = Buffer.concat([this.partialPayload!, payload]); - this.verifyCRC = true; - this.state = PairingState.REQ_AUTH_ID_FIN; - - break; - //Smartlock has sent the fifth part of the authorization id - case PairingState.REQ_AUTH_ID_FIN: - let auth: Buffer = this.getDataFromPayload(Buffer.concat([this.partialPayload!, payload])); - this.partialPayload = null; + let authenticator3: Buffer = auth.slice(0, 32); + let authIdBuf: Buffer = auth.slice(32, 36); + this.config.authorizationId = authIdBuf.readUInt32LE(0); + this.config.slUUID = auth.slice(36, 52); + let nonceK3: Buffer = auth.slice(52, 84); - let authenticator3: Buffer = auth.slice(0, 32); - let authIdBuf: Buffer = auth.slice(32, 36); - this.config.authorizationId = authIdBuf.readUInt32LE(0); - this.config.slUUID = auth.slice(36, 52); - let nonceK3: Buffer = auth.slice(52, 84); + let r3: Buffer = Buffer.concat([authIdBuf, this.config.slUUID, nonceK3, this.nonceABF!]); - let r3: Buffer = Buffer.concat([authIdBuf, this.config.slUUID, nonceK3, this.nonceABF!]); + let cr = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r3).digest(); - let cr = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r3).digest(); + if (Buffer.compare(authenticator3, cr) !== 0) { + this.emit("pairingFailed", "The authenticator could not be verified."); + } else { + let r4 = Buffer.concat([authIdBuf, nonceK3]); + let authenticator4 = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r4).digest(); + data = SmartLock.prepareCommand(Command.AUTH_ID_CONFIRM, Buffer.concat([authenticator4, authIdBuf])); - if (Buffer.compare(authenticator3, cr) !== 0) { - this.emit("pairingFailed", "The authenticator could not be verified."); - } else { - let r4 = Buffer.concat([authIdBuf, nonceK3]); - let authenticator4 = crypto.createHmac('SHA256', this.config.credentials.sharedSecret!).update(r4).digest(); - data = SmartLock.prepareCommand(Command.AUTH_ID_CONFIRM, Buffer.concat([authenticator4, authIdBuf])); - - this.writeData(data); + this.writeData(data); - this.state = PairingState.REQ_AUTH_ID_CONFIRM; + this.state = PairingState.REQ_AUTH_ID_CONFIRM; + } } - break; + case PairingState.REQ_AUTH_ID_CONFIRM: if (this.getCommandFromPayload(payload) == Command.STATUS && this.getDataFromPayload(payload).readUInt8(0) == Status.COMPLETE) { this.state = PairingState.PAIRED; diff --git a/src/lib/states.ts b/src/lib/states.ts index 392e193..fd810b4 100644 --- a/src/lib/states.ts +++ b/src/lib/states.ts @@ -7,16 +7,9 @@ export enum PairingState { IDLE, FAILED, REQ_PUB_KEY, - REQ_PUB_KEY_FIN, REQ_CHALLENGE, - REQ_CHALLENGE_FIN, REQ_CHALLENGE_AUTH, - REQ_CHALLENGE_AUTH_FIN, - REQ_AUTH_ID_A, - REQ_AUTH_ID_B, - REQ_AUTH_ID_C, - REQ_AUTH_ID_D, - REQ_AUTH_ID_FIN, + REQ_AUTH_ID, REQ_AUTH_ID_CONFIRM, PAIRED } From 13a648ec0bd682eb372de5054fb4d0c57cc5368b Mon Sep 17 00:00:00 2001 From: Martin Ziel Date: Tue, 1 Sep 2020 11:54:48 +0200 Subject: [PATCH 2/2] Fixed compatibilty for the Nuki V1 --- src/lib/smartLockCommands/RequestConfigCommand.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/smartLockCommands/RequestConfigCommand.ts b/src/lib/smartLockCommands/RequestConfigCommand.ts index f1a79b2..f208a7f 100644 --- a/src/lib/smartLockCommands/RequestConfigCommand.ts +++ b/src/lib/smartLockCommands/RequestConfigCommand.ts @@ -52,8 +52,11 @@ export class RequestConfigCommand extends SmartLockCommand { let hardwareMinorVersion: number = payload.readUInt8(70); this._response.data.hardwareRevision = hardwareMajorVersion + "." + hardwareMinorVersion; - this._response.data.homeKitStatus = payload.readUInt8(71); - this._response.data.timeZoneId = payload.readUInt16LE(72); + //Additional fields added in API V2 only available on Nuki V2 + if(majorVersion > 1) { + this._response.data.homeKitStatus = payload.readUInt8(71); + this._response.data.timeZoneId = payload.readUInt16LE(72); + } this._complete = true; }