Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1 compatibility #4

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/lib/smartLockCommands/RequestConfigCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
153 changes: 44 additions & 109 deletions src/lib/smartLockPairer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;

Expand All @@ -121,149 +113,92 @@ 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.
case PairingState.REQ_CHALLENGE:
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;
Expand Down
9 changes: 1 addition & 8 deletions src/lib/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down