homebridge-virtual-accessories
Version:
Virtual HomeKit accessories for Homebridge.
303 lines • 11.9 kB
JavaScript
/* eslint-disable brace-style */
import { createHash } from 'crypto';
/**
* TLV8
*
* https://github.com/kupa22/apple-homekey
*/
export class TLVUtils {
// Request
static OPERATION = 1;
static DEVICE_CREDENTIAL_REQUEST = 4;
static DEVICE_CREDENTIAL_RESPONSE = 5;
static READER_KEY_REQUEST = 6;
static READER_KEY_RESPONSE = 7;
static REQUESTS = [TLVUtils.DEVICE_CREDENTIAL_REQUEST, TLVUtils.READER_KEY_REQUEST];
// Operation value
static OPERATION_GET = 1;
static OPERATION_ADD = 2;
static OPERATION_REMOVE = 3;
static OPERATIONS = [TLVUtils.OPERATION_GET, TLVUtils.OPERATION_ADD, TLVUtils.OPERATION_REMOVE];
// Device Credential Request
static DEVICE_CREDENTIAL_REQUEST_KEY_TYPE = 1; // Add
static DEVICE_CREDENTIAL_REQUEST_DEVICE_CREDENTIAL_PUBLIC_KEY = 2; // Add
static DEVICE_CREDENTIAL_REQUEST_ISSUER_KEY_IDENTIFIER = 3; // Add
static DEVICE_CREDENTIAL_REQUEST_KEY_STATE = 4; // Add
static DEVICE_CREDENTIAL_REQUEST_KEY_IDENTIFIER = 5; // Remove? (not seen yet)
// Device Credential Response
static DEVICE_CREDENTIAL_RESPONSE_KEY_IDENTIFIER = 1; // Get? (not seen yet)
static DEVICE_CREDENTIAL_RESPONSE_ISSUER_KEY_IDENTIFIER = 2; // Add
static DEVICE_CREDENTIAL_RESPONSE_STATUS = 3; // Add, Remove
// Reader Key Request
static READER_KEY_REQUEST_KEY_TYPE = 1; // Add
static READER_KEY_REQUEST_READER_PRIVATE_KEY = 2; // Add
static READER_KEY_REQUEST_UNKNOWN = 3; // Add
static READER_KEY_REQUEST_KEY_IDENTIFIER = 4; // Remove
// Reader Key Response
static READER_KEY_RESPONSE_KEY_IDENTIFIER = 1; // Get? (not seen yet)
static READER_KEY_RESPONSE_STATUS = 2; // Add, Remove
// Key Types values
static KEY_TYPE_CURVE25519 = 1;
static KEY_TYPE_SECP256R1 = 2;
// Status values
static STATUS_SUCCESS = 0; // Add, Remove
static STATUS_OUT_OF_RESOURCES = 1; // Add, Remove
static STATUS_DUPLICATE = 2; // Add, Remove
static STATUS_DOES_NOT_EXIST = 3; // Add, Remove
static STATUS_NOT_SUPPORTED = 4; // Add, Remove
// Key State values
static KEY_STATE_INACTIVE = 0; // (not seen yet)
static KEY_STATE_ACTIVE = 1;
static parseTLVs(tlvString) {
let hexString = tlvString;
const tlvs = new Set();
while (hexString.length > 0) {
const valueLength = parseInt(hexString.substring(2, 4), 16);
const splitIndex = 4 + (valueLength * 2); // hex values are two characters
const tlvHexObject = hexString.substring(0, splitIndex);
const tlv = TLV.fromHexString(tlvHexObject);
tlvs.add(tlv);
hexString = hexString.substring(splitIndex);
}
return tlvs;
}
static toHexString(number) {
return number.toString(16).padStart(2, '0');
}
static getReaderIdentifier(readerPrivateKey) {
const KEY_IDENTIFIER = '6B65792D6964656E746966696572'; // hex encoding of "key-identifier"
const prefixBuffer = Buffer.from(KEY_IDENTIFIER, 'hex');
const readerPrivateKeyBuffer = Buffer.from(readerPrivateKey, 'hex');
const valueBuffer = Buffer.concat([prefixBuffer, readerPrivateKeyBuffer]);
const sha256hash = createHash('sha256').update(valueBuffer).digest();
const first8Bytes = sha256hash.subarray(0, 8);
return first8Bytes.toString('hex');
}
}
export class TLV {
type = 0;
length = 0;
value = 0;
static new(type, length, value) {
const tlv = new TLV();
tlv.type = type;
tlv.length = length;
tlv.value = value;
return tlv;
}
static fromHexString(tlvHexString) {
const type = tlvHexString.substring(0, 2);
const length = tlvHexString.substring(2, 4);
const value = tlvHexString.substring(4);
const tlv = new TLV();
tlv.type = parseInt(type, 16);
tlv.length = parseInt(length, 16);
if (value.length === 2) {
tlv.value = parseInt(value, 16);
}
else {
tlv.value = value;
}
return tlv;
}
constructor() { }
toHexString() {
const type = TLVUtils.toHexString(this.type);
const length = TLVUtils.toHexString(this.length);
let value = '';
if (typeof this.value === 'string') {
value = this.value;
}
else if (typeof this.value === 'number') {
value = TLVUtils.toHexString(this.value);
}
return `${type}${length}${value}`;
}
toFormattedHexString() {
const type = TLVUtils.toHexString(this.type);
const length = TLVUtils.toHexString(this.length);
let value = '';
if (typeof this.value === 'string') {
value = this.value;
}
else if (typeof this.value === 'number') {
value = TLVUtils.toHexString(this.value);
}
return `${type} ${length} ${value}`;
}
}
export class TLVRequest {
operation;
request;
requestPayload;
constructor(tlvString, log) {
const tlvs = TLVUtils.parseTLVs(tlvString);
tlvs.forEach(tlv => {
switch (tlv.type) {
case TLVUtils.OPERATION: {
this.operation = tlv;
break;
}
default: {
this.request = tlv;
}
}
});
if (this.request.type === TLVUtils.DEVICE_CREDENTIAL_REQUEST) {
this.requestPayload = new TLVDeviceCredentialRequest(this.request.value, log);
}
else if (this.request.type === TLVUtils.READER_KEY_REQUEST) {
this.requestPayload = new TLVReaderKeyRequest(this.request.value, log);
}
else {
log.error(`Invalid request type: ${this.request.type}`);
}
}
}
export class TLVDeviceCredentialRequest {
keyType; // Add
deviceCredentialPublicKey; // Add
issuerKeyIdentifier; // Add
keyState; // Add
keyIdentifier; // Remove? (not seen yet)
constructor(tlvString, log) {
const tlvs = TLVUtils.parseTLVs(tlvString);
tlvs.forEach(tlv => {
switch (tlv.type) {
case TLVUtils.DEVICE_CREDENTIAL_REQUEST_KEY_TYPE: {
this.keyType = tlv;
break;
}
case TLVUtils.DEVICE_CREDENTIAL_REQUEST_DEVICE_CREDENTIAL_PUBLIC_KEY: {
this.deviceCredentialPublicKey = tlv;
break;
}
case TLVUtils.DEVICE_CREDENTIAL_REQUEST_ISSUER_KEY_IDENTIFIER: {
this.issuerKeyIdentifier = tlv;
break;
}
case TLVUtils.DEVICE_CREDENTIAL_REQUEST_KEY_STATE: {
this.keyState = tlv;
break;
}
case TLVUtils.DEVICE_CREDENTIAL_REQUEST_KEY_IDENTIFIER: {
this.keyIdentifier = tlv;
break;
}
default: {
log.error(`Invalid TLVDeviceCredentialRequest sub tlv: ${tlv.type}`);
}
}
});
}
}
export class TLVDeviceCredentialResponse {
operation;
keyIdentifier; // Get? (not seen yet)
issuerKeyIdentifier; // Add
status; // Add, Remove
constructor() { }
toHexString() {
let responseValue = '';
if (this.operation === TLVUtils.OPERATION_GET) {
responseValue = this.keyIdentifier.toHexString();
}
else if (this.operation === TLVUtils.OPERATION_ADD) {
responseValue = this.issuerKeyIdentifier.toHexString() + this.status.toHexString();
}
else if (this.operation === TLVUtils.OPERATION_REMOVE) {
responseValue = this.status.toHexString();
}
const responseType = TLVUtils.toHexString(TLVUtils.READER_KEY_RESPONSE);
const responseLength = TLVUtils.toHexString(responseValue.length / 2); // hex values are two characters
return `${responseType}${responseLength}${responseValue}`;
}
static getResponseForGetOperation(keyIdentifier) {
const response = new TLVDeviceCredentialResponse();
response.keyIdentifier = TLV.new(TLVUtils.DEVICE_CREDENTIAL_RESPONSE_KEY_IDENTIFIER, keyIdentifier.length / 2, keyIdentifier);
response.operation = TLVUtils.OPERATION_GET;
return response;
}
static getResponseForAddOperation(issuerKeyIdentifier, status) {
const response = new TLVDeviceCredentialResponse();
response.issuerKeyIdentifier = TLV.new(TLVUtils.DEVICE_CREDENTIAL_RESPONSE_ISSUER_KEY_IDENTIFIER, issuerKeyIdentifier.length / 2, issuerKeyIdentifier);
response.status = TLV.new(TLVUtils.DEVICE_CREDENTIAL_RESPONSE_STATUS, 1, status);
response.operation = TLVUtils.OPERATION_ADD;
return response;
}
static getResponseForRemoveOperation(status) {
const response = new TLVDeviceCredentialResponse();
response.status = TLV.new(TLVUtils.DEVICE_CREDENTIAL_RESPONSE_STATUS, 1, status);
response.operation = TLVUtils.OPERATION_REMOVE;
return response;
}
}
export class TLVReaderKeyRequest {
keyType; // Add
readerPrivateKey; // Add
unknown; // Add
keyIdentifier; // Remove
constructor(tlvString, log) {
const tlvs = TLVUtils.parseTLVs(tlvString);
tlvs.forEach(tlv => {
switch (tlv.type) {
case TLVUtils.READER_KEY_REQUEST_KEY_TYPE: {
this.keyType = tlv;
break;
}
case TLVUtils.READER_KEY_REQUEST_READER_PRIVATE_KEY: {
this.readerPrivateKey = tlv;
break;
}
case TLVUtils.READER_KEY_REQUEST_UNKNOWN: {
this.unknown = tlv;
break;
}
case TLVUtils.READER_KEY_REQUEST_KEY_IDENTIFIER: {
this.keyIdentifier = tlv;
break;
}
default: {
log.error(`Invalid TLVReaderKeyRequest sub tlv: ${tlv.type}`);
}
}
});
}
}
export class TLVReaderKeyResponse {
operation;
keyIdentifier; // Get
status; // Add, Remove
constructor() { }
toHexString() {
let responseValue = '';
if (this.operation === TLVUtils.OPERATION_GET) {
responseValue = this.keyIdentifier.toHexString();
}
else if (this.operation === TLVUtils.OPERATION_ADD || this.operation === TLVUtils.OPERATION_REMOVE) {
responseValue = this.status.toHexString();
}
const responseType = TLVUtils.toHexString(TLVUtils.READER_KEY_RESPONSE);
const responseLength = TLVUtils.toHexString(responseValue.length / 2); // hex values are two characters
return `${responseType}${responseLength}${responseValue}`;
}
static getResponseForGetOperation(keyIdentifier) {
const response = new TLVReaderKeyResponse();
response.keyIdentifier = TLV.new(TLVUtils.READER_KEY_RESPONSE_KEY_IDENTIFIER, keyIdentifier.length / 2, keyIdentifier);
response.operation = TLVUtils.OPERATION_GET;
return response;
}
static getResponseForAddOperation(status) {
const response = new TLVReaderKeyResponse();
response.status = TLV.new(TLVUtils.READER_KEY_RESPONSE_STATUS, 1, status);
response.operation = TLVUtils.OPERATION_ADD;
return response;
}
static getResponseForRemoveOperation(status) {
const response = new TLVReaderKeyResponse();
response.status = TLV.new(TLVUtils.READER_KEY_RESPONSE_STATUS, 1, status);
response.operation = TLVUtils.OPERATION_REMOVE;
return response;
}
}
//# sourceMappingURL=tlv.js.map