diff --git a/README.md b/README.md index 18b4ce4..bf62e99 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Communicate Client-Server via Windows NamedPipe - [x] Add support for safe closing (0.9.0) - [x] Add support for other header settings (0.9.0) - [x] Add logging (0.9.1) -- [ ] Send data in chunks (if data is too large) (0.9.3) +- [x] Send data in chunks (if data is too large) (0.9.3) - [x] Add support for encryption (0.9.2) - - [x] simple (via char xor'ing; auto-pairing) - - [ ] password (via AES and PBKDF2) - - [ ] certificate (via RSA) + - [x] simple (via char xor'ing; auto-pairing) (0.9.2) + - [x] password (via AES and PBKDF2) (0.9.3) + - [ ] certificate (via RSA) - [ ] Add support for multiple clients diff --git a/examples/client_long_data.py b/examples/client_long_data.py new file mode 100644 index 0000000..84d8fc5 --- /dev/null +++ b/examples/client_long_data.py @@ -0,0 +1,10 @@ +from winConnect import WinConnectClient + +connector = WinConnectClient('test') + +i = b'i' * 1024 * 1024 +with connector as conn: + print(f"Sending {len(i)/1024}kb...") + conn.send_data(i) + data = conn.read_pipe() + print(f"({type(data)}) {data[:9]=}; ok={data == i}") diff --git a/examples/server_echo.py b/examples/server_echo.py index 48cbbe7..146a36f 100644 --- a/examples/server_echo.py +++ b/examples/server_echo.py @@ -1,10 +1,6 @@ from winConnect import WinConnectDaemon connector = WinConnectDaemon('test') -# Set header settings -# see: https://docs.python.org/3.13/library/struct.html#format-characters -# Default: ">H" - Big-endian unsigned short integer (header_size: 2 bytes, max_size: 65535) -connector.set_header_settings(">H") for data in connector.listen(): print(f"({type(data)}) {data=}") diff --git a/examples/with_crypto/client_password.py b/examples/with_crypto/client_password.py new file mode 100644 index 0000000..81faabe --- /dev/null +++ b/examples/with_crypto/client_password.py @@ -0,0 +1,27 @@ +import sys + +from loguru import logger + +from winConnect import WinConnectClient, crypto + +logger.remove() +logger.add(sys.stdout, level="DEBUG") + +crypt_mode = crypto.WinConnectCryptoPassword("test_password") + +connector = WinConnectClient('test') +connector.set_logger(logger) +connector.set_crypto(crypt_mode) + +def console(): + with connector as conn: + while True: + i = input(":> ") + if i == "exit": + break + conn.send_data(i) + data = conn.read_pipe() + print(f"({type(data)}) {data=}") + +if __name__ == '__main__': + console() diff --git a/examples/with_crypto/server_password.py b/examples/with_crypto/server_password.py new file mode 100644 index 0000000..3c155dd --- /dev/null +++ b/examples/with_crypto/server_password.py @@ -0,0 +1,21 @@ +import sys + +from loguru import logger + +from winConnect import WinConnectDaemon +from winConnect import crypto + +logger.remove() +logger.add(sys.stdout, level="DEBUG") + +crypt_mode = crypto.WinConnectCryptoPassword("test_password") + +connector = WinConnectDaemon('test') +connector.set_logger(logger) +connector.set_crypto(crypt_mode) + +for data in connector.listen(): + print(f"({type(data)}) {data=}") + if data is None and connector.closed: + break + connector.send_data(data) diff --git a/requirements.txt b/requirements.txt index b085198..c305067 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ # 12.03.2025 pywin32~=309 ormsgpack~=1.8.0 +pycryptodome~=3.21.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 0aba9d7..0954f54 100644 --- a/setup.py +++ b/setup.py @@ -13,10 +13,17 @@ packages = [_name, ] package_dir = {_name: _name} lib_path = here / _name -requires = [ - "pywin32==309", - "ormsgpack==1.8.0" -] +requires = { + "install_requires": [ + "pywin32==309", + "ormsgpack==1.8.0" + ], + "extra_packages": { + "crypto": [ + "pycryptodome==3.21.0" + ] + } +} # 'setup.py publish' shortcut. if sys.argv[-1] == 'publish': @@ -44,12 +51,13 @@ setup( package_data={'': ['LICENSE']}, package_dir=package_dir, include_package_data=True, - install_requires=requires, + **requires, license=about['__license__'], classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "Natural Language :: Russian", + "Topic :: Software Development :: Libraries", + "Natural Language :: English", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", diff --git a/winConnect/WinConnectBase.py b/winConnect/WinConnectBase.py index a6a7fb6..e3f1614 100644 --- a/winConnect/WinConnectBase.py +++ b/winConnect/WinConnectBase.py @@ -1,3 +1,4 @@ +import hashlib import json import logging import struct @@ -25,16 +26,17 @@ class WinConnectBase: default_encoding = 'utf-8' - read_max_buffer = SimpleConvertor.to_gb(3)-1 # Max size via chunked messages + read_max_buffer = SimpleConvertor.to_gb(3)-32 # Max size via chunked messages ormsgpack_options = ormsgpack.OPT_NON_STR_KEYS | ormsgpack.OPT_NAIVE_UTC | ormsgpack.OPT_PASSTHROUGH_TUPLE # ormsgpack options def __init__(self, pipe_name: str): self._log = logging.getLogger(f"WinConnect:{pipe_name}") - # _version: + # versions: # 1 - 0.9.1 - # 2 - 0.9.2+ (with crypto) - self._version = 2 + # 2 - 0.9.2 (with crypto) + # 3 - 0.9.3+ (with crypto+salt) + self._version = 3 self._pipe_name = r'\\.\pipe\{}'.format(pipe_name) self._pipe = None self._opened = False @@ -50,9 +52,9 @@ class WinConnectBase: self.__crypto = WinConnectCrypto() self.__crypto.set_crypto_class(WinConnectCryptoNone()) - # self._chunks = [] - - self._lock = threading.Lock() + self._pipe_lock = threading.Lock() + self._read_lock = threading.Lock() + self._write_lock = threading.Lock() def set_crypto(self, crypto): if self._connected: @@ -67,9 +69,13 @@ class WinConnectBase: self.__crypto.set_logger(logger) def _calc_body_max_size(self): - # Max size of body: 2 ** (8 * header_size) - 1 - header_size - 1 + # Max size of body: struct_range - header_size - crypt_fix - action_and_data # - header_size; X byte for header_size - self._body_max_size = SimpleConvertor.struct_range(self._header_format)[1] - self._header_size + # - crypt_fix; 32 byte for crypto fix (internal data) + # - action_and_data; 8 byte for action and data (internal data) act:typ: = 8 byte + self._body_max_size = SimpleConvertor.struct_range(self._header_format)[1] - self._header_size - 32 - 8 + if self._body_max_size-64 < 0: + raise exceptions.WinConnectBaseException("Header size is too small") def set_header_settings(self, fmt): if self._connected: @@ -101,122 +107,205 @@ class WinConnectBase: def closed(self): return not self._connected - def _open_pipe(self): ... - - def __pack_data(self, action, data) -> (bytes, bytes): - data_type = "msg" - data = ormsgpack.packb(data, option=self.ormsgpack_options) - compressed_data = zlib.compress(data) - return data_type.encode(self.encoding) + b":" + action + b":" + compressed_data - - def __unpack_data(self, data: bytes) -> (str, Any): - data_type, action_data = self.__parse_message(data) - if data_type != b"msg": - self._send_error(WinConnectErrors.UNKNOWN_DATA_TYPE, f"Unknown data type '{data_type}'") - raise exceptions.WinConnectBadDataTypeException('Is client using correct lib? Unknown data type') - action, data = self.__parse_message(action_data) - decompressed_data = zlib.decompress(data) - deserialized_data = ormsgpack.unpackb(decompressed_data) - return action, deserialized_data - @staticmethod def __parse_message(message: bytes): return message.split(b":", 1) - def _read_message(self) -> (str, Any): - with self._lock: - _hfmt, _hsize = self.__header_settings + def _open_pipe(self): ... + + def __handle_send_data(self, action, data) -> bytes: + t = type(data) + if t == bytes or t == bytearray: + data_type = b"raw" + ready_data = bytes(data) + else: + data_type = b"msg" + ready_data = ormsgpack.packb(data, option=self.ormsgpack_options) + return data_type + b":" + action + b":" + zlib.compress(ready_data) + + def __handle_receive_data_type(self, data): + data_type, action_data = self.__parse_message(data) + action, data = self.__parse_message(action_data) + data = zlib.decompress(data) + match data_type: + case b"raw": + ready_data = data + case b"msg": + ready_data = ormsgpack.unpackb(data) + case _: + self._send_error(WinConnectErrors.UNKNOWN_DATA_TYPE, f"Unknown data type '{data_type}'") + raise exceptions.WinConnectBadDataTypeException('Is client using correct lib? Unknown data type') + return action, ready_data + + def __raw_read(self, size): + with self._pipe_lock: try: - _, header = win32file.ReadFile(self._pipe, self._header_size) + _, data = win32file.ReadFile(self._pipe, size) + return data except pywintypes.error as e: if e.winerror == 109: exc = exceptions.WinConnectConnectionClosedException("Connection closed") exc.real_exc = e raise exc raise e + + def __read_and_decrypt(self, size): + data = self.__raw_read(size) + if self._inited: + data = self.__crypto.decrypt(data) + return data + + def _read_message(self) -> (str, Any): + with self._read_lock: + _hfmt, _hsize = self.__header_settings + # Read header + header = self.__raw_read(_hsize) if not header: - return b"" + self._send_error(WinConnectErrors.BAD_HEADER, f"No header received") + self.close() if len(header) != _hsize and self._inited: self._send_error(WinConnectErrors.BAD_HEADER, f"Bad header size. Expected: {_hsize}, got: {len(header)}") self.close() message_size = struct.unpack(_hfmt, header)[0] - if message_size > self._body_max_size or message_size > self.read_max_buffer: + if message_size > self._body_max_size: self._send_error(WinConnectErrors.BODY_TOO_BIG, f"Body is too big. Max size: {self._body_max_size}kb") self.close() if not self._connected: return None, None - _, data = win32file.ReadFile(self._pipe, message_size) - if self._inited: - data = self.__crypto.decrypt(data) - action, data = self.__unpack_data(data) + # Read body + data = self.__read_and_decrypt(message_size) + action, data = self.__handle_receive_data_type(data) self._log.debug(f"[{self._pipe_name}] Received message: {action=} {data=}") return action, data - def _send_message(self, action: str, data: Any): - action = action.encode(self.encoding) - with self._lock: + def __raw_write(self, packet): + with self._pipe_lock: if self.closed: raise exceptions.WinConnectSessionClosedException("Session is closed") - packed_data = self.__pack_data(action, data) + win32file.WriteFile(self._pipe, packet) + + def _send_message(self, action: str, data: Any): + with self._write_lock: + action = action.encode(self.encoding) + packed_data = self.__handle_send_data(action, data) if self._inited: packed_data = self.__crypto.encrypt(packed_data) + message_size = len(packed_data) if message_size > self._body_max_size: - raise ValueError('Message is too big') - # Если размер сообщения больше размера read_header_size, то ошибка - if message_size > 2 ** (8 * self._header_size): - raise ValueError('Message is too big') - _hfmt, _ = self.__header_settings - header = struct.pack(_hfmt, message_size) - packet = header + packed_data - self._log.debug(f"[{self._pipe_name}] Sending message: {action=} {data=}; {packet=}") - win32file.WriteFile(self._pipe, packet) + raise exceptions.WinConnectBaseException('Message is too big') + + self._log.debug(f"[{self._pipe_name}] Sending message: {action=} {data=}; {message_size} {packed_data=}") + # Send header + self.__raw_write(struct.pack(self.__header_settings[0], message_size)) + # Send body + self.__raw_write(packed_data) def _send_error(self, error: WinConnectErrors, error_message: str = None): e = {"error": True, "code": error.value, "message": error.name, "description": error_message} - self._send_message("error", e) + self._send_message("err", e) + + def __read_chunked_message(self, data_info: bytes): + self._log.debug(f"[{self._pipe_name}] Receive long message. Reading in chunks...") + chunk_size = self._body_max_size - 32 + cdata_sha256, cdata_len = data_info[:32], int(data_info[32:]) + if cdata_len > self.read_max_buffer: + self._send_error(WinConnectErrors.BODY_TOO_BIG, f"Body is too big. Max size: {self.read_max_buffer}kb") + self.close() + _buffer = b"" + + with self._read_lock: + for i in range(0, cdata_len, chunk_size): + _buffer += self.__read_and_decrypt(chunk_size) + + if cdata_sha256 != hashlib.sha256(_buffer).digest(): + self._send_error(WinConnectErrors.BAD_DATA, f"Data is corrupted") + + return zlib.decompress(_buffer) + + def __send_chunked_message(self, data: bytes): + self._log.debug(f"[{self._pipe_name}] Long message. Sending in chunks...") + chunk_size = self._body_max_size - 32 + cdata = zlib.compress(data) + + cdata_len = len(cdata) + if cdata_len > self.read_max_buffer: + raise exceptions.WinConnectBaseException(f'Message is too big. Change WinConnectBase.read_max_buffer. Now is: {self.read_max_buffer/1024}kb') + cdata_sha256 = hashlib.sha256(cdata).digest() + self._send_message("dtc", cdata_sha256 + str(cdata_len).encode(self.encoding)) + + with self._write_lock: + for i in range(0, cdata_len, chunk_size): + _encrypted = self.__crypto.encrypt(cdata[i:i + chunk_size]) + self.__raw_write(_encrypted) def _parse_action(self, action, data: Any) -> (bool, Any): - # return: (internal_command, data) + # return: (internal_action, data) if not self._connected: return match action: - case b"command": + case b"cmd": # Command return True, self._parse_command(data) - case b"data": + case b"dtn": # Data normal return False, data - case b"error": + case b"dtc": # Data chunked + return False, self.__read_chunked_message(data) + case b"err": return False, WinConnectError(data['code'], data['message']) case _: return self._send_error(WinConnectErrors.UNKNOWN_ACTION, f"Unknown action '{action}'") def _parse_command(self, data: bytes): + _blank_settings = { + 'version': None, + 'encoding': None, + 'header_size': None, + 'header_format': None, + 'max_buffer': None, + 'crypto': None + } command, data = self.__parse_message(data) match command: case b'get_session_settings': self._log.debug(f"[{self._pipe_name}] Received get_session_settings from {data}") - settings = { - 'version': self._version, - 'encoding': self.default_encoding, - 'header_size': self._header_size, - 'header_format': self._header_format, - 'max_buffer': self.read_max_buffer, - "crypto": self.__crypto.get_info() - } - session_settings = f"set_session_settings:{json.dumps(settings)}".encode(self.init_encoding) - self._send_message("command", session_settings) + _blank_settings['version'] = self._version + _blank_settings['encoding'] = self._session_encoding + _blank_settings['header_size'] = self._header_size + _blank_settings['header_format'] = self._header_format + _blank_settings['max_buffer'] = self.read_max_buffer + _blank_settings['crypto'] = self.__crypto.crypt_name + session_settings = f"set_session_settings:{len(self.__crypto.crypt_salt)}:{json.dumps(_blank_settings)}".encode(self.encoding) + self.__crypto.crypt_salt + self._send_message("cmd", session_settings) return True case b'set_session_settings': + self._log.debug(f"[{self._pipe_name}] Received session settings.") + len_salt, data_salt = self.__parse_message(data) + len_salt = int(len_salt) + if len_salt > 0: + data, salt = data_salt[:-len_salt], data_salt[-len_salt:] + else: + data, salt = data_salt, b'' + + if salt != self.__crypto.crypt_salt: + self._log.debug(f"[{self._pipe_name}] Updating salt") + self.__crypto.set_salt(salt) + try: settings = json.loads(data.decode(self.init_encoding)) except json.JSONDecodeError as e: self._send_error(WinConnectErrors.BAD_DATA, f"JSONDecodeError: {e}") return self.close() - if settings.get('version') != self._version: + + if _blank_settings.keys() != settings.keys(): + self._log.error(f"{WinConnectErrors.BAD_SETTINGS}") + self._send_error(WinConnectErrors.BAD_SETTINGS, f"Setting have wrong structure") + return self.close() + + if settings['version'] != self._version: self._log.error(f"{WinConnectErrors.BAD_VERSION}") self._send_error(WinConnectErrors.BAD_VERSION, f"Version mismatch") return self.close() - if settings.get('crypto') != self.__crypto.get_info(): + if settings['crypto'] != self.__crypto.crypt_name: self._log.error(f"{WinConnectErrors.BAD_CRYPTO}") self._send_error(WinConnectErrors.BAD_CRYPTO, f"Crypto mismatch") return self.close() @@ -228,6 +317,7 @@ class WinConnectBase: case b"session_ready": self._inited = True return True + case b"close": self.close() return True @@ -238,15 +328,17 @@ class WinConnectBase: action, data = self._read_message() if not self._connected: return - if action != b"command": + if action != b"cmd": return self._send_error(WinConnectErrors.BAD_DATA, "Unknown data type") if not self._parse_command(data): return self._send_error(WinConnectErrors.INIT_FIRST, "Server need to init session first") - self._send_message("command", b"session_ready:") + self._send_message("cmd", b"session_ready:") self._parse_action(*self._read_message()) def send_data(self, data): - self._send_message("data", data) + if len(data) > self._body_max_size: + return self.__send_chunked_message(data) + self._send_message("dtn", data) def _close_session(self): ... diff --git a/winConnect/WinConnectClient.py b/winConnect/WinConnectClient.py index 6a41eb8..173f56e 100644 --- a/winConnect/WinConnectClient.py +++ b/winConnect/WinConnectClient.py @@ -39,13 +39,13 @@ class WinConnectClient(WinConnectBase): raise e def _init(self, program_name="NoName"): - self._send_message("command", b"get_session_settings:" + program_name.encode(self.encoding)) + self._send_message("cmd", b"get_session_settings:" + program_name.encode(self.encoding)) self._init_session() def _close_session(self): """Send close command to server""" if not self.closed: - self._send_message("command", b"close:") + self._send_message("cmd", b"close:") def __check_pipe(self): if not self._opened: diff --git a/winConnect/__meta__.py b/winConnect/__meta__.py index 4cc7390..66470fd 100644 --- a/winConnect/__meta__.py +++ b/winConnect/__meta__.py @@ -3,8 +3,8 @@ __title__ = 'winConnect' __description__ = 'Communicate Client-Server via Windows NamedPipe.' __url__ = 'https://github.com/SantaSpeen/winConnect' -__version__ = '0.9.2' -__build__ = 39 +__version__ = '0.9.3' +__build__ = 82 __author__ = 'SantaSpeen' __author_email__ = 'admin@anidev.ru' __license__ = "MIT" diff --git a/winConnect/crypto/WinConnectCrypto.py b/winConnect/crypto/WinConnectCrypto.py index 03a33f9..652038c 100644 --- a/winConnect/crypto/WinConnectCrypto.py +++ b/winConnect/crypto/WinConnectCrypto.py @@ -15,15 +15,26 @@ def test_crypto_class(crypto_class: "WinConnectCryptoBase"): class WinConnectCrypto: def __init__(self): - self.__crypto_class = None + self.__crypto_class: WinConnectCryptoBase = None self.__log_prefix = None self._log = logging.getLogger("WinConnectCrypto") + @property + def crypt_name(self) -> str: + return self.__crypto_class.__class__.__name__ + + @property + def crypt_salt(self) -> bytes: + return self.__crypto_class.salt + + def set_salt(self, salt: bytes): + self.__crypto_class.salt = salt + def set_crypto_class(self, crypto_class: "WinConnectCryptoBase"): - self._log.debug(f"{self.__log_prefix}Updating crypto class. {self.get_info()} -> {crypto_class.__class__.__name__}") + self._log.debug(f"{self.__log_prefix}Updating crypto class. {self.crypt_name} -> {crypto_class.__class__.__name__}") test_crypto_class(crypto_class) self.__crypto_class = crypto_class - self.__log_prefix = f"[{self.get_info()}] " + self.__log_prefix = f"[{self.crypt_name}] " self._log.debug(f"{self.__log_prefix}Crypto class updated.") def set_logger(self, logger): @@ -38,9 +49,6 @@ class WinConnectCrypto: self._log.debug(f"{self.__log_prefix}Crypto class loaded") return True - def get_info(self): - return self.__crypto_class.__class__.__name__ - def encrypt(self, data: bytes) -> bytes: return self.__crypto_class.encrypt(data) diff --git a/winConnect/crypto/__init__.py b/winConnect/crypto/__init__.py index 079eca7..7ada28e 100644 --- a/winConnect/crypto/__init__.py +++ b/winConnect/crypto/__init__.py @@ -3,6 +3,6 @@ from .crypto_classes import ( WinConnectCryptoBase, WinConnectCryptoNone, WinConnectCryptoSimple, - # WinConnectCryptoPassword, + WinConnectCryptoPassword, # WinConnectCryptoCert ) diff --git a/winConnect/crypto/crypto_class_base.py b/winConnect/crypto/crypto_class_base.py index baab3d1..044101f 100644 --- a/winConnect/crypto/crypto_class_base.py +++ b/winConnect/crypto/crypto_class_base.py @@ -1,5 +1,11 @@ class WinConnectCryptoBase: + @property + def salt(self) -> bytes: + return b"" + @salt.setter + def salt(self, value): ... + def encrypt(self, data: bytes) -> bytes: ... def decrypt(self, data: bytes) -> bytes: ... diff --git a/winConnect/crypto/crypto_classes.py b/winConnect/crypto/crypto_classes.py index 1579b55..6b552f5 100644 --- a/winConnect/crypto/crypto_classes.py +++ b/winConnect/crypto/crypto_classes.py @@ -1,8 +1,18 @@ +import os import random +from pathlib import Path from .crypto_class_base import WinConnectCryptoBase from winConnect.exceptions import WinConnectCryptoSimpleBadHeaderException +_pip_crypto = True +try: + from Crypto.Cipher import AES + from Crypto.Protocol.KDF import PBKDF2 + from Crypto.Cipher import PKCS1_OAEP +except ImportError: + _pip_crypto = False + class WinConnectCryptoNone(WinConnectCryptoBase): def __init__(self): ... @@ -19,18 +29,16 @@ class WinConnectCryptoSimple(WinConnectCryptoBase): shift_key = random.randint(100, 749) key = random.randint(5, 250) encrypted_text = bytearray() - header = f"wccs{shift_key}{key+shift_key}:" - for char in header: - encrypted_text.append(ord(char)) + header = f"wccs{shift_key}{key+shift_key}:".encode() for char in data: encrypted_text.append(char ^ key) - return bytes(encrypted_text) + return header + bytes(encrypted_text) def decrypt(self, data: bytes) -> bytes: try: header, content = data.split(b":", 1) if header[:4] != b"wccs": - raise WinConnectCryptoSimpleBadHeaderException("Bad header in message.") + raise WinConnectCryptoSimpleBadHeaderException(f"Bad header in message: {header[:4].decode()}") except ValueError: raise WinConnectCryptoSimpleBadHeaderException("No header in message.") shift_key = int(header[4:7]) @@ -44,27 +52,57 @@ class WinConnectCryptoSimple(WinConnectCryptoBase): class WinConnectCryptoPassword(WinConnectCryptoBase): def __init__(self, password: str): - pass + if not _pip_crypto: + raise ImportError("Crypto library not installed. Install with 'pip install winConnect[crypto]'") + self.password = password + self.__salt = os.urandom(16) + self.__key = PBKDF2(password, self.__salt, dkLen=32, count=100000) + + @property + def salt(self): + return self.__salt + + @salt.setter + def salt(self, value): + self.__salt = value + self.__key = PBKDF2(self.password, self.__salt, dkLen=32, count=100000) def encrypt(self, data: bytes) -> bytes: - pass + iv = os.urandom(16) # Генерируем IV + header = b"wccp" + iv + cipher = AES.new(self.__key, AES.MODE_CBC, iv) + pad_len = 16 - len(data) % 16 + padded_data = data + bytes([pad_len] * pad_len) + return header + cipher.encrypt(padded_data) # Шифруем def decrypt(self, data: bytes) -> bytes: - pass + try: + header, iv, content = data[:4], data[4:20], data[20:] + if header[:4] != b"wccp": + raise WinConnectCryptoSimpleBadHeaderException(f"Bad header in message: {header.decode()}") + except ValueError: + raise WinConnectCryptoSimpleBadHeaderException("No header in message.") -class WinConnectCryptoCert(WinConnectCryptoBase): - def __init__(self, cert_file: str): - pass - - def _open_cert(self): - pass - - def load(self) -> None: - self._open_cert() - - def encrypt(self, data: bytes) -> bytes: - pass - - def decrypt(self, data: bytes) -> bytes: - pass + cipher = AES.new(self.__key, AES.MODE_CBC, iv) + decrypted = cipher.decrypt(content) + pad_len = decrypted[-1] # Убираем PKCS7 padding + return decrypted[:-pad_len] +# class WinConnectCryptoCert(WinConnectCryptoBase): +# def __init__(self, cert_file: str): +# if not _pip_crypto: +# raise ImportError("Crypto library not installed. Install with 'pip install winConnect[crypto]'") +# self.cert_file = Path(cert_file) +# +# def _open_cert(self): +# pass +# +# def load(self) -> None: +# self._open_cert() +# +# def encrypt(self, data: bytes) -> bytes: +# pass +# +# def decrypt(self, data: bytes) -> bytes: +# pass +# diff --git a/winConnect/errors.py b/winConnect/errors.py index 377a194..a11580f 100644 --- a/winConnect/errors.py +++ b/winConnect/errors.py @@ -14,7 +14,7 @@ class WinConnectErrors(Enum): BAD_DATA = 50 BAD_VERSION = 51 BAD_HEADER = 52 - BAD_BODY = 53 + BAD_SETTINGS = 53 BAD_CRYPTO = 54 BODY_TOO_BIG = 60