UNPKG

homebridge-virtual-accessories

Version:
303 lines 11.9 kB
/* 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