Skip to content

Commit e4a8b8f

Browse files
author
Johan Carlsson
committed
Add support for install mode.
If a PD report to use SCBK-D in SCS_12, generate a SCBK using Master Key and set using the KEYSET command. Signed-off-by: Johan Carlsson <johan.carlsson@teenage.engineering>
1 parent a4aba3d commit e4a8b8f

3 files changed

Lines changed: 41 additions & 5 deletions

File tree

osdp/_bus.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/_device.py

Lines changed: 11 additions & 2 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

@@ -21,6 +21,7 @@ def __init__(self, address: int, use_crc: bool, use_secure_channel: bool, master
2121
self._commands = queue.Queue()
2222
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: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,25 @@ def __init__(self, master_key: bytes):
2222
self.is_initialized = False
2323
self.is_established = False
2424
self.master_key = master_key
25+
self.is_scbkd = False
26+
self.cuid = None
27+
self.scbk = None
2528
self.reset()
2629

2730
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()
2836
self._enc = self.generate_key(
2937
bytes([
3038
0x01, 0x82,
3139
self.server_random_number[0], self.server_random_number[1], self.server_random_number[2],
3240
self.server_random_number[3], self.server_random_number[4], self.server_random_number[5]
3341
]),
3442
bytes([0x00] * 8),
35-
self.default_secure_channel_key
43+
self.scbk
3644
)
3745

3846
if client_cryptogram != self.generate_key(self.server_random_number, client_random_number, self._enc):
@@ -45,7 +53,7 @@ def initialize(self, cuid: bytes, client_random_number: bytes, client_cryptogram
4553
self.server_random_number[3], self.server_random_number[4], self.server_random_number[5]
4654
]),
4755
bytes([0x00] * 8),
48-
self.default_secure_channel_key
56+
self.scbk
4957
)
5058
self._smac2 = self.generate_key(
5159
bytes([
@@ -55,7 +63,7 @@ def initialize(self, cuid: bytes, client_random_number: bytes, client_cryptogram
5563
self.server_random_number[4], self.server_random_number[5]
5664
]),
5765
bytes([0x00] * 8),
58-
self.default_secure_channel_key
66+
self.scbk
5967
)
6068
self.server_cryptogram = self.generate_key(
6169
client_random_number,
@@ -133,3 +141,19 @@ def reset(self):
133141
def generate_key(self, first: bytes, second: bytes, key: bytes) -> bytes:
134142
cipher = AES.new(key, AES.MODE_ECB)
135143
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)