DLMS-SPODES 0.87.17__py3-none-any.whl → 0.88.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DLMS_SPODES/Values/EN/__init__.py +1 -1
- DLMS_SPODES/Values/EN/actors.py +8 -8
- DLMS_SPODES/Values/EN/relation_to_obis_names.py +387 -387
- DLMS_SPODES/Values/RU/__init__.py +1 -1
- DLMS_SPODES/Values/RU/actors.py +8 -8
- DLMS_SPODES/Values/RU/relation_to_obis_names.py +396 -396
- DLMS_SPODES/__init__.py +6 -6
- DLMS_SPODES/configEN.ini +126 -126
- DLMS_SPODES/config_parser.py +53 -53
- DLMS_SPODES/cosem_interface_classes/Overview/__init__.py +0 -0
- DLMS_SPODES/cosem_interface_classes/Overview/class_id.py +107 -0
- DLMS_SPODES/cosem_interface_classes/__class_init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/__init__.py +3 -2
- DLMS_SPODES/cosem_interface_classes/activity_calendar.py +210 -254
- DLMS_SPODES/cosem_interface_classes/arbitrator.py +78 -105
- DLMS_SPODES/cosem_interface_classes/association_ln/abstract.py +50 -34
- DLMS_SPODES/cosem_interface_classes/association_ln/authentication_mechanism_name.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/mechanism_id.py +25 -25
- DLMS_SPODES/cosem_interface_classes/association_ln/method.py +5 -5
- DLMS_SPODES/cosem_interface_classes/association_ln/ver0.py +440 -485
- DLMS_SPODES/cosem_interface_classes/association_ln/ver1.py +126 -133
- DLMS_SPODES/cosem_interface_classes/association_ln/ver2.py +30 -36
- DLMS_SPODES/cosem_interface_classes/association_ln/ver3.py +3 -4
- DLMS_SPODES/cosem_interface_classes/association_sn/ver0.py +14 -12
- DLMS_SPODES/cosem_interface_classes/clock.py +81 -131
- DLMS_SPODES/cosem_interface_classes/collection.py +2106 -2122
- DLMS_SPODES/cosem_interface_classes/cosem_interface_class.py +525 -583
- DLMS_SPODES/cosem_interface_classes/data.py +12 -21
- DLMS_SPODES/cosem_interface_classes/demand_register/ver0.py +32 -59
- DLMS_SPODES/cosem_interface_classes/disconnect_control.py +56 -74
- DLMS_SPODES/cosem_interface_classes/extended_register.py +18 -27
- DLMS_SPODES/cosem_interface_classes/gprs_modem_setup.py +33 -43
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver0.py +78 -103
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver1.py +42 -40
- DLMS_SPODES/cosem_interface_classes/gsm_diagnostic/ver2.py +6 -9
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver0.py +11 -11
- DLMS_SPODES/cosem_interface_classes/iec_hdlc_setup/ver1.py +27 -53
- DLMS_SPODES/cosem_interface_classes/iec_local_port_setup.py +9 -11
- DLMS_SPODES/cosem_interface_classes/image_transfer/image_transfer_status.py +15 -15
- DLMS_SPODES/cosem_interface_classes/image_transfer/ver0.py +54 -126
- DLMS_SPODES/cosem_interface_classes/implementations/__init__.py +3 -3
- DLMS_SPODES/cosem_interface_classes/implementations/arbitrator.py +19 -19
- DLMS_SPODES/cosem_interface_classes/implementations/data.py +491 -487
- DLMS_SPODES/cosem_interface_classes/implementations/profile_generic.py +85 -83
- DLMS_SPODES/cosem_interface_classes/ipv4_setup.py +42 -72
- DLMS_SPODES/cosem_interface_classes/limiter.py +77 -111
- DLMS_SPODES/cosem_interface_classes/ln_pattern.py +334 -333
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver0.py +51 -65
- DLMS_SPODES/cosem_interface_classes/modem_configuration/ver1.py +27 -39
- DLMS_SPODES/cosem_interface_classes/ntp_setup/ver0.py +48 -67
- DLMS_SPODES/cosem_interface_classes/obis.py +28 -23
- DLMS_SPODES/cosem_interface_classes/overview.py +198 -197
- DLMS_SPODES/cosem_interface_classes/parameter.py +548 -547
- DLMS_SPODES/cosem_interface_classes/parameters.py +172 -172
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver0.py +90 -133
- DLMS_SPODES/cosem_interface_classes/profile_generic/ver1.py +268 -277
- DLMS_SPODES/cosem_interface_classes/push_setup/ver0.py +13 -12
- DLMS_SPODES/cosem_interface_classes/push_setup/ver1.py +9 -10
- DLMS_SPODES/cosem_interface_classes/push_setup/ver2.py +124 -166
- DLMS_SPODES/cosem_interface_classes/register.py +18 -45
- DLMS_SPODES/cosem_interface_classes/register_activation/ver0.py +45 -80
- DLMS_SPODES/cosem_interface_classes/register_monitor.py +33 -46
- DLMS_SPODES/cosem_interface_classes/reports.py +72 -70
- DLMS_SPODES/cosem_interface_classes/schedule.py +88 -176
- DLMS_SPODES/cosem_interface_classes/script_table.py +54 -87
- DLMS_SPODES/cosem_interface_classes/security_setup/ver0.py +45 -68
- DLMS_SPODES/cosem_interface_classes/security_setup/ver1.py +122 -158
- DLMS_SPODES/cosem_interface_classes/single_action_schedule.py +34 -50
- DLMS_SPODES/cosem_interface_classes/special_days_table.py +54 -84
- DLMS_SPODES/cosem_interface_classes/tcp_udp_setup.py +20 -42
- DLMS_SPODES/cosem_pdu.py +93 -93
- DLMS_SPODES/enums.py +625 -625
- DLMS_SPODES/exceptions.py +106 -106
- DLMS_SPODES/firmwares.py +99 -99
- DLMS_SPODES/hdlc/frame.py +875 -875
- DLMS_SPODES/hdlc/sub_layer.py +54 -54
- DLMS_SPODES/literals.py +17 -17
- DLMS_SPODES/obis/__init__.py +1 -1
- DLMS_SPODES/obis/media_id.py +931 -931
- DLMS_SPODES/pardata.py +22 -22
- DLMS_SPODES/pdu_enums.py +98 -98
- DLMS_SPODES/relation_to_OBIS.py +463 -465
- DLMS_SPODES/settings.py +551 -551
- DLMS_SPODES/types/choices.py +140 -142
- DLMS_SPODES/types/common_data_types.py +2379 -2401
- DLMS_SPODES/types/cosem_service_types.py +109 -109
- DLMS_SPODES/types/implementations/arrays.py +25 -25
- DLMS_SPODES/types/implementations/bitstrings.py +97 -97
- DLMS_SPODES/types/implementations/double_long_usingneds.py +35 -35
- DLMS_SPODES/types/implementations/enums.py +57 -57
- DLMS_SPODES/types/implementations/integers.py +12 -11
- DLMS_SPODES/types/implementations/long_unsigneds.py +127 -127
- DLMS_SPODES/types/implementations/octet_string.py +11 -11
- DLMS_SPODES/types/implementations/structs.py +64 -64
- DLMS_SPODES/types/type_alias.py +74 -0
- DLMS_SPODES/types/useful_types.py +627 -677
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/METADATA +30 -30
- dlms_spodes-0.88.1.dist-info/RECORD +118 -0
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/WHEEL +1 -1
- DLMS_SPODES/cosem_interface_classes/a_parameter.py +0 -20
- DLMS_SPODES/cosem_interface_classes/attr_indexes.py +0 -12
- dlms_spodes-0.87.17.dist-info/RECORD +0 -117
- {dlms_spodes-0.87.17.dist-info → dlms_spodes-0.88.1.dist-info}/top_level.txt +0 -0
DLMS_SPODES/exceptions.py
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
from enum import IntEnum
|
|
2
|
-
from .enums import Transmit, Application
|
|
3
|
-
from semver import Version as SemVer
|
|
4
|
-
from . import pdu_enums as pdu
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TomlKeyError(Exception):
|
|
8
|
-
"""for handle exceptions of toml"""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DLMSException(Exception):
|
|
12
|
-
""" Common InterTechElectric exceptions class """
|
|
13
|
-
error: Transmit | Application = Transmit.UNKNOWN
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class AssociationResultError(DLMSException):
|
|
17
|
-
""" Result of the proposed AA establishment and eventually the reason of the rejection of the association establishment request, as it is specified in ISO/IEC 8650-1.
|
|
18
|
-
When no diagnostics are included, a null value is assigned to the result-source-diagnostics field. IEC62056-53 2004 7.3.3 The AARQ and AARE APDUs """
|
|
19
|
-
error = Transmit.NO_ACCESS
|
|
20
|
-
__match_args_ = ('result_source_diagnostics', )
|
|
21
|
-
|
|
22
|
-
# TODO: make arg right type
|
|
23
|
-
def __init__(self, result_source_diagnostics: IntEnum):
|
|
24
|
-
Exception.__init__(self, F'Connection is {result_source_diagnostics.name}')
|
|
25
|
-
self.result_source_diagnostics = result_source_diagnostics
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class Timeout(DLMSException):
|
|
29
|
-
""" timeout during connection or exchange """
|
|
30
|
-
error = Transmit.TIMEOUT
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class NoPort(DLMSException):
|
|
34
|
-
""" not found port """
|
|
35
|
-
error = Transmit.NO_PORT
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Abort(DLMSException):
|
|
39
|
-
""" manual interrupt """
|
|
40
|
-
error = Transmit.ABORT
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class ITEConnection(DLMSException):
|
|
44
|
-
""""""
|
|
45
|
-
error = Transmit.UNKNOWN
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class NoTransport(ITEConnection):
|
|
49
|
-
""""""
|
|
50
|
-
error = Transmit.NO_TRANSPORT
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class ITEApplication(DLMSException):
|
|
54
|
-
""""""
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class NoObject(ITEApplication):
|
|
58
|
-
""" object missing in collection """
|
|
59
|
-
error = Application.MISSING_OBJ
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class IDError(ITEApplication):
|
|
63
|
-
""""""
|
|
64
|
-
error = Application.ID_ERROR
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class EmptyObj(ITEApplication):
|
|
68
|
-
""" emtpy field in DLMS object """
|
|
69
|
-
error = Application.EMPTY_OBJ
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class NoConfig(ITEApplication):
|
|
73
|
-
""" configuration for device not founded """
|
|
74
|
-
error = Application.NO_CONFIG
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class TypeErr(ITEApplication):
|
|
78
|
-
""" unknown device type """
|
|
79
|
-
error = Application.TYPE_ERROR
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class VersionError(ITEApplication):
|
|
83
|
-
""" Version error """
|
|
84
|
-
error = Application.VERSION_ERROR
|
|
85
|
-
|
|
86
|
-
def __init__(self, error_version: SemVer, additional: str = 'device'):
|
|
87
|
-
Exception.__init__(self, F'Unsupported {additional} version: {error_version}')
|
|
88
|
-
self.version = error_version
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class ResultError(ITEApplication):
|
|
92
|
-
""" DLMS COSEMpdu_GB83.asn error """
|
|
93
|
-
error = Application.RESULT_ERROR
|
|
94
|
-
|
|
95
|
-
def __init__(self, error: pdu.DataAccessResult | pdu.ActionResult):
|
|
96
|
-
Exception.__init__(self, error)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class UnknownError(DLMSException):
|
|
100
|
-
""" for unknown errors """
|
|
101
|
-
error = Transmit.UNKNOWN
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
class NeedUpdate(ITEApplication):
|
|
105
|
-
"""error until there is no action"""
|
|
106
|
-
error = Application.VERSION_ERROR
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from .enums import Transmit, Application
|
|
3
|
+
from semver import Version as SemVer
|
|
4
|
+
from . import pdu_enums as pdu
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TomlKeyError(Exception):
|
|
8
|
+
"""for handle exceptions of toml"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DLMSException(Exception):
|
|
12
|
+
""" Common InterTechElectric exceptions class """
|
|
13
|
+
error: Transmit | Application = Transmit.UNKNOWN
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AssociationResultError(DLMSException):
|
|
17
|
+
""" Result of the proposed AA establishment and eventually the reason of the rejection of the association establishment request, as it is specified in ISO/IEC 8650-1.
|
|
18
|
+
When no diagnostics are included, a null value is assigned to the result-source-diagnostics field. IEC62056-53 2004 7.3.3 The AARQ and AARE APDUs """
|
|
19
|
+
error = Transmit.NO_ACCESS
|
|
20
|
+
__match_args_ = ('result_source_diagnostics', )
|
|
21
|
+
|
|
22
|
+
# TODO: make arg right type
|
|
23
|
+
def __init__(self, result_source_diagnostics: IntEnum):
|
|
24
|
+
Exception.__init__(self, F'Connection is {result_source_diagnostics.name}')
|
|
25
|
+
self.result_source_diagnostics = result_source_diagnostics
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Timeout(DLMSException):
|
|
29
|
+
""" timeout during connection or exchange """
|
|
30
|
+
error = Transmit.TIMEOUT
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NoPort(DLMSException):
|
|
34
|
+
""" not found port """
|
|
35
|
+
error = Transmit.NO_PORT
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Abort(DLMSException):
|
|
39
|
+
""" manual interrupt """
|
|
40
|
+
error = Transmit.ABORT
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ITEConnection(DLMSException):
|
|
44
|
+
""""""
|
|
45
|
+
error = Transmit.UNKNOWN
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NoTransport(ITEConnection):
|
|
49
|
+
""""""
|
|
50
|
+
error = Transmit.NO_TRANSPORT
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ITEApplication(DLMSException):
|
|
54
|
+
""""""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NoObject(ITEApplication):
|
|
58
|
+
""" object missing in collection """
|
|
59
|
+
error = Application.MISSING_OBJ
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class IDError(ITEApplication):
|
|
63
|
+
""""""
|
|
64
|
+
error = Application.ID_ERROR
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class EmptyObj(ITEApplication):
|
|
68
|
+
""" emtpy field in DLMS object """
|
|
69
|
+
error = Application.EMPTY_OBJ
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NoConfig(ITEApplication):
|
|
73
|
+
""" configuration for device not founded """
|
|
74
|
+
error = Application.NO_CONFIG
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TypeErr(ITEApplication):
|
|
78
|
+
""" unknown device type """
|
|
79
|
+
error = Application.TYPE_ERROR
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VersionError(ITEApplication):
|
|
83
|
+
""" Version error """
|
|
84
|
+
error = Application.VERSION_ERROR
|
|
85
|
+
|
|
86
|
+
def __init__(self, error_version: SemVer, additional: str = 'device'):
|
|
87
|
+
Exception.__init__(self, F'Unsupported {additional} version: {error_version}')
|
|
88
|
+
self.version = error_version
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ResultError(ITEApplication):
|
|
92
|
+
""" DLMS COSEMpdu_GB83.asn error """
|
|
93
|
+
error = Application.RESULT_ERROR
|
|
94
|
+
|
|
95
|
+
def __init__(self, error: pdu.DataAccessResult | pdu.ActionResult):
|
|
96
|
+
Exception.__init__(self, error)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class UnknownError(DLMSException):
|
|
100
|
+
""" for unknown errors """
|
|
101
|
+
error = Transmit.UNKNOWN
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NeedUpdate(ITEApplication):
|
|
105
|
+
"""error until there is no action"""
|
|
106
|
+
error = Application.VERSION_ERROR
|
DLMS_SPODES/firmwares.py
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
from functools import lru_cache
|
|
2
|
-
from typing import Optional
|
|
3
|
-
import pickle
|
|
4
|
-
import hashlib
|
|
5
|
-
import os
|
|
6
|
-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
7
|
-
from cryptography.hazmat.primitives import padding
|
|
8
|
-
from cryptography.hazmat.backends import default_backend
|
|
9
|
-
from .settings import settings
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def init_cipher(key: bytes) -> tuple[Cipher, bytes]:
|
|
13
|
-
"""Initialize AES-CBC cipher with random IV"""
|
|
14
|
-
iv = os.urandom(16)
|
|
15
|
-
cipher = Cipher(
|
|
16
|
-
algorithms.AES(key),
|
|
17
|
-
modes.CBC(iv),
|
|
18
|
-
backend=default_backend()
|
|
19
|
-
)
|
|
20
|
-
return cipher, iv
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
|
|
24
|
-
"""Decrypt data with hash verification: dec(dummy + data + sha256)"""
|
|
25
|
-
cipher = Cipher(
|
|
26
|
-
algorithms.AES(key),
|
|
27
|
-
modes.CBC(iv),
|
|
28
|
-
backend=default_backend()
|
|
29
|
-
)
|
|
30
|
-
decryptor = cipher.decryptor()
|
|
31
|
-
# Decrypt and remove padding
|
|
32
|
-
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
|
33
|
-
unpadder = padding.PKCS7(128).unpadder()
|
|
34
|
-
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
|
|
35
|
-
# Verify hash
|
|
36
|
-
data, data_hash = plaintext[16:-32], plaintext[-32:]
|
|
37
|
-
if hashlib.sha256(data).digest() == data_hash:
|
|
38
|
-
return data
|
|
39
|
-
else:
|
|
40
|
-
raise ValueError('Invalid password or corrupted data')
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
|
|
44
|
-
"""Encrypt data with hash: ciphertext = enc(dummy + plaintext + sha256)"""
|
|
45
|
-
# Prepare data (16 dummy bytes + data + hash)
|
|
46
|
-
plaintext = bytes(16) + data + hashlib.sha256(data).digest()
|
|
47
|
-
|
|
48
|
-
# Add padding
|
|
49
|
-
padder = padding.PKCS7(128).padder()
|
|
50
|
-
padded_plaintext = padder.update(plaintext) + padder.finalize()
|
|
51
|
-
|
|
52
|
-
# Encrypt
|
|
53
|
-
cipher = Cipher(
|
|
54
|
-
algorithms.AES(key),
|
|
55
|
-
modes.CBC(iv),
|
|
56
|
-
backend=default_backend()
|
|
57
|
-
)
|
|
58
|
-
encryptor = cipher.encryptor()
|
|
59
|
-
return encryptor.update(padded_plaintext) + encryptor.finalize()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@lru_cache(maxsize=10)
|
|
63
|
-
def get_firmware(man: bytes) -> Optional[tuple[
|
|
64
|
-
dict[tuple[tuple[int, int, int], str], bytes],
|
|
65
|
-
dict[tuple[int, str], bytes]
|
|
66
|
-
]]:
|
|
67
|
-
for firmware in settings.firmwares:
|
|
68
|
-
if firmware.man.encode() == man:
|
|
69
|
-
match firmware.key.codec:
|
|
70
|
-
case "ascii":
|
|
71
|
-
cipher_key = firmware.key.value.encode("ascii")
|
|
72
|
-
case "hex":
|
|
73
|
-
cipher_key = bytes.fromhex(firmware.key.value)
|
|
74
|
-
case _:
|
|
75
|
-
raise ValueError(f"in get firmware, unknown firmware.key.codec={firmware.key.codec}")
|
|
76
|
-
new_firmwares = {}
|
|
77
|
-
new_boots = {}
|
|
78
|
-
with open(firmware.path, 'rb') as file:
|
|
79
|
-
try:
|
|
80
|
-
name, firmwares_, boots_ = pickle.load(file)
|
|
81
|
-
load_name, version = name.split('_')
|
|
82
|
-
|
|
83
|
-
if load_name == "CryptoFirmware":
|
|
84
|
-
iv = os.urandom(16)
|
|
85
|
-
for it in firmwares_:
|
|
86
|
-
decryption = decrypt(cipher_key, iv, firmwares_[it])
|
|
87
|
-
new_firmwares[it] = decryption
|
|
88
|
-
for it in boots_:
|
|
89
|
-
decryption = decrypt(cipher_key, iv, boots_[it])
|
|
90
|
-
new_boots[it] = decryption
|
|
91
|
-
else:
|
|
92
|
-
raise ValueError(f"Wrong firmware.path={firmware.path}")
|
|
93
|
-
except KeyError as e:
|
|
94
|
-
raise ValueError(f"Decoding error firmware.path={firmware.path}, {e}")
|
|
95
|
-
except ValueError as e:
|
|
96
|
-
raise ValueError(f"Decoding error firmware.path={firmware.path}, {e}")
|
|
97
|
-
except Exception as e:
|
|
98
|
-
raise ValueError(f"unknown error: {e}")
|
|
99
|
-
return new_firmwares, new_boots
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import pickle
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
7
|
+
from cryptography.hazmat.primitives import padding
|
|
8
|
+
from cryptography.hazmat.backends import default_backend
|
|
9
|
+
from .settings import settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def init_cipher(key: bytes) -> tuple[Cipher, bytes]:
|
|
13
|
+
"""Initialize AES-CBC cipher with random IV"""
|
|
14
|
+
iv = os.urandom(16)
|
|
15
|
+
cipher = Cipher(
|
|
16
|
+
algorithms.AES(key),
|
|
17
|
+
modes.CBC(iv),
|
|
18
|
+
backend=default_backend()
|
|
19
|
+
)
|
|
20
|
+
return cipher, iv
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
|
|
24
|
+
"""Decrypt data with hash verification: dec(dummy + data + sha256)"""
|
|
25
|
+
cipher = Cipher(
|
|
26
|
+
algorithms.AES(key),
|
|
27
|
+
modes.CBC(iv),
|
|
28
|
+
backend=default_backend()
|
|
29
|
+
)
|
|
30
|
+
decryptor = cipher.decryptor()
|
|
31
|
+
# Decrypt and remove padding
|
|
32
|
+
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
|
33
|
+
unpadder = padding.PKCS7(128).unpadder()
|
|
34
|
+
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
|
|
35
|
+
# Verify hash
|
|
36
|
+
data, data_hash = plaintext[16:-32], plaintext[-32:]
|
|
37
|
+
if hashlib.sha256(data).digest() == data_hash:
|
|
38
|
+
return data
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError('Invalid password or corrupted data')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
|
|
44
|
+
"""Encrypt data with hash: ciphertext = enc(dummy + plaintext + sha256)"""
|
|
45
|
+
# Prepare data (16 dummy bytes + data + hash)
|
|
46
|
+
plaintext = bytes(16) + data + hashlib.sha256(data).digest()
|
|
47
|
+
|
|
48
|
+
# Add padding
|
|
49
|
+
padder = padding.PKCS7(128).padder()
|
|
50
|
+
padded_plaintext = padder.update(plaintext) + padder.finalize()
|
|
51
|
+
|
|
52
|
+
# Encrypt
|
|
53
|
+
cipher = Cipher(
|
|
54
|
+
algorithms.AES(key),
|
|
55
|
+
modes.CBC(iv),
|
|
56
|
+
backend=default_backend()
|
|
57
|
+
)
|
|
58
|
+
encryptor = cipher.encryptor()
|
|
59
|
+
return encryptor.update(padded_plaintext) + encryptor.finalize()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@lru_cache(maxsize=10)
|
|
63
|
+
def get_firmware(man: bytes) -> Optional[tuple[
|
|
64
|
+
dict[tuple[tuple[int, int, int], str], bytes],
|
|
65
|
+
dict[tuple[int, str], bytes]
|
|
66
|
+
]]:
|
|
67
|
+
for firmware in settings.firmwares:
|
|
68
|
+
if firmware.man.encode() == man:
|
|
69
|
+
match firmware.key.codec:
|
|
70
|
+
case "ascii":
|
|
71
|
+
cipher_key = firmware.key.value.encode("ascii")
|
|
72
|
+
case "hex":
|
|
73
|
+
cipher_key = bytes.fromhex(firmware.key.value)
|
|
74
|
+
case _:
|
|
75
|
+
raise ValueError(f"in get firmware, unknown firmware.key.codec={firmware.key.codec}")
|
|
76
|
+
new_firmwares = {}
|
|
77
|
+
new_boots = {}
|
|
78
|
+
with open(firmware.path, 'rb') as file:
|
|
79
|
+
try:
|
|
80
|
+
name, firmwares_, boots_ = pickle.load(file)
|
|
81
|
+
load_name, version = name.split('_')
|
|
82
|
+
|
|
83
|
+
if load_name == "CryptoFirmware":
|
|
84
|
+
iv = os.urandom(16)
|
|
85
|
+
for it in firmwares_:
|
|
86
|
+
decryption = decrypt(cipher_key, iv, firmwares_[it])
|
|
87
|
+
new_firmwares[it] = decryption
|
|
88
|
+
for it in boots_:
|
|
89
|
+
decryption = decrypt(cipher_key, iv, boots_[it])
|
|
90
|
+
new_boots[it] = decryption
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError(f"Wrong firmware.path={firmware.path}")
|
|
93
|
+
except KeyError as e:
|
|
94
|
+
raise ValueError(f"Decoding error firmware.path={firmware.path}, {e}")
|
|
95
|
+
except ValueError as e:
|
|
96
|
+
raise ValueError(f"Decoding error firmware.path={firmware.path}, {e}")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
raise ValueError(f"unknown error: {e}")
|
|
99
|
+
return new_firmwares, new_boots
|