Skip to content

Commit 6aa9e44

Browse files
authored
Merge pull request #7 from te-johan/install_mode
Add support for install mode
2 parents 5d5339d + e4a8b8f commit 6aa9e44

5 files changed

Lines changed: 52 additions & 19 deletions

File tree

osdp/_bus.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ def send_command(self, command: Command):
4444
else:
4545
log.warning("Device not found with address %s", command.address)
4646

47-
def add_device(self, address: int, use_crc: bool, use_secure_channel: bool) -> Device:
47+
def add_device(self, address: int, use_crc: bool, use_secure_channel: bool, master_key: bytes) -> Device:
4848
found_device = self._configured_devices.get(address)
4949
self._configured_devices_lock.acquire()
5050
if found_device is not None:
5151
self._configured_devices.pop(address)
52-
self._configured_devices[address] = Device(address, use_crc, use_secure_channel)
52+
self._configured_devices[address] = Device(address, use_crc, use_secure_channel, master_key)
5353
self._configured_devices_lock.release()
5454
return self._configured_devices[address]
5555

@@ -132,6 +132,9 @@ def process_reply(self, reply: Reply, device: Device):
132132
if device.validate_secure_channel_establishment(reply):
133133
log.debug("Secure session established.")
134134

135+
if device.reset_security_after_reply == True:
136+
device.reset_security()
137+
135138
if self._on_reply_received is not None:
136139
self._on_reply_received(reply)
137140

osdp/_command.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,5 @@ def custom_command_update(self, command_buffer: bytearray):
391391
pass
392392

393393
def keyset_data(self):
394-
header = []
395-
type = 0x01
396-
length = 0x10
397-
scbk = [0x41, 0x02, 0x31, 0x84, 0xF1, 0xA2, 0xDE, 0x7C, 0x32, 0x98, 0x01, 0xB8, 0x7B, 0x56, 0xB3, 0x60]
398-
header.append(type)
399-
header.append(length)
400-
return bytes(header + scbk)
394+
header = bytes([0x01, 0x10])
395+
return bytes(header + self.scbk)

osdp/_control_panel.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323

2424
class ControlPanel:
2525

26-
def __init__(self):
26+
def __init__(self, master_key: bytes = None):
2727
self._buses = {}
2828
self._reply_handlers = []
2929
self._reply_timeout = 5.0
30+
self._master_key = master_key
3031

3132
def start_connection(self, connection: OsdpConnection) -> UUID:
3233
bus = Bus(connection, self.on_reply_received)
@@ -100,7 +101,7 @@ def shutdown(self):
100101
def add_device(self, connection_id: UUID, address: int, use_crc: bool, use_secure_channel: bool):
101102
bus = self._buses.get(connection_id)
102103
if bus is not None:
103-
bus.add_device(address, use_crc, use_secure_channel)
104+
bus.add_device(address, use_crc, use_secure_channel, self._master_key)
104105

105106
def remove_device(self, connection_id: UUID, address: int):
106107
bus = self._buses.get(connection_id)

osdp/_device.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import queue
33
import datetime
44

5-
from ._types import Control
5+
from ._types import Control, SecurityBlockType
66
from ._command import (
7-
PollCommand, SecurityInitializationRequestCommand, ServerCryptogramCommand
7+
PollCommand, SecurityInitializationRequestCommand, ServerCryptogramCommand, KeySetCommand
88
)
99
from ._secure_channel import SecureChannel
1010

@@ -13,14 +13,15 @@
1313

1414
class Device(object):
1515

16-
def __init__(self, address: int, use_crc: bool, use_secure_channel: bool):
16+
def __init__(self, address: int, use_crc: bool, use_secure_channel: bool, master_key: bytes):
1717
self._use_secure_channel = use_secure_channel
1818
self.address = address
1919
self.message_control = Control(0, use_crc, use_secure_channel)
2020

2121
self._commands = queue.Queue()
22-
self._secure_channel = SecureChannel()
22+
self._secure_channel = SecureChannel(master_key)
2323
self._last_valid_reply = datetime.datetime.utcfromtimestamp(0)
24+
self.reset_security_after_reply = False
2425

2526
@property
2627
def is_security_established(self) -> bool:
@@ -40,6 +41,10 @@ def get_next_command_data(self):
4041
if self._use_secure_channel and not self._secure_channel.is_established:
4142
return ServerCryptogramCommand(self.address, self._secure_channel.server_cryptogram)
4243

44+
if self._use_secure_channel and self._secure_channel.is_scbkd:
45+
self.reset_security_after_reply = True
46+
return KeySetCommand(self.address, self._secure_channel.calculate_scbk())
47+
4348
if self._commands.empty():
4449
return PollCommand(self.address)
4550
else:
@@ -54,6 +59,9 @@ def valid_reply_has_been_received(self):
5459
self._last_valid_reply = datetime.datetime.now()
5560

5661
def initialize_secure_channel(self, reply):
62+
if reply.security_block_type == SecurityBlockType.SecureConnectionSequenceStep2.value:
63+
self._secure_channel.select_scbk(reply.secure_block_data[0])
64+
5765
reply_data = reply.extract_reply_data
5866
self._secure_channel.initialize(reply_data[:8], reply_data[8:16], reply_data[16:32])
5967

@@ -69,6 +77,7 @@ def generate_mac(self, message: bytes, is_command: bool):
6977
return self._secure_channel.generate_mac(message, is_command)
7078

7179
def reset_security(self):
80+
self.reset_security_after_reply = False
7281
self._secure_channel.reset()
7382

7483
def encrypt_data(self, data: bytes):

osdp/_secure_channel.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class SecureChannel:
1010
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
1111
])
1212

13-
def __init__(self):
13+
def __init__(self, master_key: bytes):
1414
self._cmac = None
1515
self._enc = None
1616
self._rmac = None
@@ -21,17 +21,26 @@ def __init__(self):
2121
self.server_cryptogram = None
2222
self.is_initialized = False
2323
self.is_established = False
24+
self.master_key = master_key
25+
self.is_scbkd = False
26+
self.cuid = None
27+
self.scbk = None
2428
self.reset()
2529

2630
def initialize(self, cuid: bytes, client_random_number: bytes, client_cryptogram: bytes):
31+
self.cuid = cuid
32+
if self.is_scbkd == True:
33+
self.scbk = self.default_secure_channel_key
34+
else:
35+
self.scbk = self.calculate_scbk()
2736
self._enc = self.generate_key(
2837
bytes([
2938
0x01, 0x82,
3039
self.server_random_number[0], self.server_random_number[1], self.server_random_number[2],
3140
self.server_random_number[3], self.server_random_number[4], self.server_random_number[5]
3241
]),
3342
bytes([0x00] * 8),
34-
self.default_secure_channel_key
43+
self.scbk
3544
)
3645

3746
if client_cryptogram != self.generate_key(self.server_random_number, client_random_number, self._enc):
@@ -44,7 +53,7 @@ def initialize(self, cuid: bytes, client_random_number: bytes, client_cryptogram
4453
self.server_random_number[3], self.server_random_number[4], self.server_random_number[5]
4554
]),
4655
bytes([0x00] * 8),
47-
self.default_secure_channel_key
56+
self.scbk
4857
)
4958
self._smac2 = self.generate_key(
5059
bytes([
@@ -54,7 +63,7 @@ def initialize(self, cuid: bytes, client_random_number: bytes, client_cryptogram
5463
self.server_random_number[4], self.server_random_number[5]
5564
]),
5665
bytes([0x00] * 8),
57-
self.default_secure_channel_key
66+
self.scbk
5867
)
5968
self.server_cryptogram = self.generate_key(
6069
client_random_number,
@@ -132,3 +141,19 @@ def reset(self):
132141
def generate_key(self, first: bytes, second: bytes, key: bytes) -> bytes:
133142
cipher = AES.new(key, AES.MODE_ECB)
134143
return cipher.encrypt(first + second)
144+
145+
def select_scbk(self, byte):
146+
if byte == 0x0:
147+
self.is_scbkd = True
148+
else:
149+
self.is_scbkd = False
150+
151+
def calculate_scbk(self):
152+
inv_cuid = bytes([(~b) & 0xFF for b in self.cuid])
153+
scbk = self.generate_key(
154+
self.cuid,
155+
inv_cuid,
156+
self.master_key
157+
)
158+
159+
return scbk

0 commit comments

Comments
 (0)