mirror of
https://github.com/SantaSpeen/winConnect.git
synced 2026-10-27 14:40:24 +00:00
Merge pull request #2 from SantaSpeen/dev
Update 0.9.3; New protocol version: 3
This commit is contained in:
15 files changed
+327
-120
No files matched your search
@@ -7,11 +7,11 @@ Communicate Client-Server via Windows NamedPipe
|
|||||||
- [x] Add support for safe closing (0.9.0)
|
- [x] Add support for safe closing (0.9.0)
|
||||||
- [x] Add support for other header settings (0.9.0)
|
- [x] Add support for other header settings (0.9.0)
|
||||||
- [x] Add logging (0.9.1)
|
- [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] Add support for encryption (0.9.2)
|
||||||
- [x] simple (via char xor'ing; auto-pairing)
|
- [x] simple (via char xor'ing; auto-pairing) (0.9.2)
|
||||||
- [ ] password (via AES and PBKDF2)
|
- [x] password (via AES and PBKDF2) (0.9.3)
|
||||||
- [ ] certificate (via RSA)
|
- [ ] certificate (via RSA)
|
||||||
- [ ] Add support for multiple clients
|
- [ ] Add support for multiple clients
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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}")
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
from winConnect import WinConnectDaemon
|
from winConnect import WinConnectDaemon
|
||||||
|
|
||||||
connector = WinConnectDaemon('test')
|
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():
|
for data in connector.listen():
|
||||||
print(f"({type(data)}) {data=}")
|
print(f"({type(data)}) {data=}")
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
# 12.03.2025
|
# 12.03.2025
|
||||||
pywin32~=309
|
pywin32~=309
|
||||||
ormsgpack~=1.8.0
|
ormsgpack~=1.8.0
|
||||||
|
pycryptodome~=3.21.0
|
||||||
@@ -13,10 +13,17 @@ packages = [_name, ]
|
|||||||
package_dir = {_name: _name}
|
package_dir = {_name: _name}
|
||||||
lib_path = here / _name
|
lib_path = here / _name
|
||||||
|
|
||||||
requires = [
|
requires = {
|
||||||
"pywin32==309",
|
"install_requires": [
|
||||||
"ormsgpack==1.8.0"
|
"pywin32==309",
|
||||||
]
|
"ormsgpack==1.8.0"
|
||||||
|
],
|
||||||
|
"extra_packages": {
|
||||||
|
"crypto": [
|
||||||
|
"pycryptodome==3.21.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# 'setup.py publish' shortcut.
|
# 'setup.py publish' shortcut.
|
||||||
if sys.argv[-1] == 'publish':
|
if sys.argv[-1] == 'publish':
|
||||||
@@ -44,12 +51,13 @@ setup(
|
|||||||
package_data={'': ['LICENSE']},
|
package_data={'': ['LICENSE']},
|
||||||
package_dir=package_dir,
|
package_dir=package_dir,
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=requires,
|
**requires,
|
||||||
license=about['__license__'],
|
license=about['__license__'],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Natural Language :: Russian",
|
"Topic :: Software Development :: Libraries",
|
||||||
|
"Natural Language :: English",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
|||||||
+162
-70
@@ -1,3 +1,4 @@
|
|||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
@@ -25,16 +26,17 @@ class WinConnectBase:
|
|||||||
|
|
||||||
default_encoding = 'utf-8'
|
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
|
ormsgpack_options = ormsgpack.OPT_NON_STR_KEYS | ormsgpack.OPT_NAIVE_UTC | ormsgpack.OPT_PASSTHROUGH_TUPLE # ormsgpack options
|
||||||
|
|
||||||
def __init__(self, pipe_name: str):
|
def __init__(self, pipe_name: str):
|
||||||
self._log = logging.getLogger(f"WinConnect:{pipe_name}")
|
self._log = logging.getLogger(f"WinConnect:{pipe_name}")
|
||||||
# _version:
|
# versions:
|
||||||
# 1 - 0.9.1
|
# 1 - 0.9.1
|
||||||
# 2 - 0.9.2+ (with crypto)
|
# 2 - 0.9.2 (with crypto)
|
||||||
self._version = 2
|
# 3 - 0.9.3+ (with crypto+salt)
|
||||||
|
self._version = 3
|
||||||
self._pipe_name = r'\\.\pipe\{}'.format(pipe_name)
|
self._pipe_name = r'\\.\pipe\{}'.format(pipe_name)
|
||||||
self._pipe = None
|
self._pipe = None
|
||||||
self._opened = False
|
self._opened = False
|
||||||
@@ -50,9 +52,9 @@ class WinConnectBase:
|
|||||||
self.__crypto = WinConnectCrypto()
|
self.__crypto = WinConnectCrypto()
|
||||||
self.__crypto.set_crypto_class(WinConnectCryptoNone())
|
self.__crypto.set_crypto_class(WinConnectCryptoNone())
|
||||||
|
|
||||||
# self._chunks = []
|
self._pipe_lock = threading.Lock()
|
||||||
|
self._read_lock = threading.Lock()
|
||||||
self._lock = threading.Lock()
|
self._write_lock = threading.Lock()
|
||||||
|
|
||||||
def set_crypto(self, crypto):
|
def set_crypto(self, crypto):
|
||||||
if self._connected:
|
if self._connected:
|
||||||
@@ -67,9 +69,13 @@ class WinConnectBase:
|
|||||||
self.__crypto.set_logger(logger)
|
self.__crypto.set_logger(logger)
|
||||||
|
|
||||||
def _calc_body_max_size(self):
|
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
|
# - 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):
|
def set_header_settings(self, fmt):
|
||||||
if self._connected:
|
if self._connected:
|
||||||
@@ -101,122 +107,205 @@ class WinConnectBase:
|
|||||||
def closed(self):
|
def closed(self):
|
||||||
return not self._connected
|
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
|
@staticmethod
|
||||||
def __parse_message(message: bytes):
|
def __parse_message(message: bytes):
|
||||||
return message.split(b":", 1)
|
return message.split(b":", 1)
|
||||||
|
|
||||||
def _read_message(self) -> (str, Any):
|
def _open_pipe(self): ...
|
||||||
with self._lock:
|
|
||||||
_hfmt, _hsize = self.__header_settings
|
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:
|
try:
|
||||||
_, header = win32file.ReadFile(self._pipe, self._header_size)
|
_, data = win32file.ReadFile(self._pipe, size)
|
||||||
|
return data
|
||||||
except pywintypes.error as e:
|
except pywintypes.error as e:
|
||||||
if e.winerror == 109:
|
if e.winerror == 109:
|
||||||
exc = exceptions.WinConnectConnectionClosedException("Connection closed")
|
exc = exceptions.WinConnectConnectionClosedException("Connection closed")
|
||||||
exc.real_exc = e
|
exc.real_exc = e
|
||||||
raise exc
|
raise exc
|
||||||
raise e
|
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:
|
if not header:
|
||||||
return b""
|
self._send_error(WinConnectErrors.BAD_HEADER, f"No header received")
|
||||||
|
self.close()
|
||||||
if len(header) != _hsize and self._inited:
|
if len(header) != _hsize and self._inited:
|
||||||
self._send_error(WinConnectErrors.BAD_HEADER, f"Bad header size. Expected: {_hsize}, got: {len(header)}")
|
self._send_error(WinConnectErrors.BAD_HEADER, f"Bad header size. Expected: {_hsize}, got: {len(header)}")
|
||||||
self.close()
|
self.close()
|
||||||
message_size = struct.unpack(_hfmt, header)[0]
|
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._send_error(WinConnectErrors.BODY_TOO_BIG, f"Body is too big. Max size: {self._body_max_size}kb")
|
||||||
self.close()
|
self.close()
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
return None, None
|
return None, None
|
||||||
_, data = win32file.ReadFile(self._pipe, message_size)
|
# Read body
|
||||||
if self._inited:
|
data = self.__read_and_decrypt(message_size)
|
||||||
data = self.__crypto.decrypt(data)
|
action, data = self.__handle_receive_data_type(data)
|
||||||
action, data = self.__unpack_data(data)
|
|
||||||
self._log.debug(f"[{self._pipe_name}] Received message: {action=} {data=}")
|
self._log.debug(f"[{self._pipe_name}] Received message: {action=} {data=}")
|
||||||
return action, data
|
return action, data
|
||||||
|
|
||||||
def _send_message(self, action: str, data: Any):
|
def __raw_write(self, packet):
|
||||||
action = action.encode(self.encoding)
|
with self._pipe_lock:
|
||||||
with self._lock:
|
|
||||||
if self.closed:
|
if self.closed:
|
||||||
raise exceptions.WinConnectSessionClosedException("Session is 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:
|
if self._inited:
|
||||||
packed_data = self.__crypto.encrypt(packed_data)
|
packed_data = self.__crypto.encrypt(packed_data)
|
||||||
|
|
||||||
message_size = len(packed_data)
|
message_size = len(packed_data)
|
||||||
if message_size > self._body_max_size:
|
if message_size > self._body_max_size:
|
||||||
raise ValueError('Message is too big')
|
raise exceptions.WinConnectBaseException('Message is too big')
|
||||||
# Если размер сообщения больше размера read_header_size, то ошибка
|
|
||||||
if message_size > 2 ** (8 * self._header_size):
|
self._log.debug(f"[{self._pipe_name}] Sending message: {action=} {data=}; {message_size} {packed_data=}")
|
||||||
raise ValueError('Message is too big')
|
# Send header
|
||||||
_hfmt, _ = self.__header_settings
|
self.__raw_write(struct.pack(self.__header_settings[0], message_size))
|
||||||
header = struct.pack(_hfmt, message_size)
|
# Send body
|
||||||
packet = header + packed_data
|
self.__raw_write(packed_data)
|
||||||
self._log.debug(f"[{self._pipe_name}] Sending message: {action=} {data=}; {packet=}")
|
|
||||||
win32file.WriteFile(self._pipe, packet)
|
|
||||||
|
|
||||||
def _send_error(self, error: WinConnectErrors, error_message: str = None):
|
def _send_error(self, error: WinConnectErrors, error_message: str = None):
|
||||||
e = {"error": True, "code": error.value, "message": error.name, "description": error_message}
|
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):
|
def _parse_action(self, action, data: Any) -> (bool, Any):
|
||||||
# return: (internal_command, data)
|
# return: (internal_action, data)
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
return
|
return
|
||||||
match action:
|
match action:
|
||||||
case b"command":
|
case b"cmd": # Command
|
||||||
return True, self._parse_command(data)
|
return True, self._parse_command(data)
|
||||||
case b"data":
|
case b"dtn": # Data normal
|
||||||
return False, data
|
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'])
|
return False, WinConnectError(data['code'], data['message'])
|
||||||
case _:
|
case _:
|
||||||
return self._send_error(WinConnectErrors.UNKNOWN_ACTION, f"Unknown action '{action}'")
|
return self._send_error(WinConnectErrors.UNKNOWN_ACTION, f"Unknown action '{action}'")
|
||||||
|
|
||||||
def _parse_command(self, data: bytes):
|
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)
|
command, data = self.__parse_message(data)
|
||||||
match command:
|
match command:
|
||||||
case b'get_session_settings':
|
case b'get_session_settings':
|
||||||
self._log.debug(f"[{self._pipe_name}] Received get_session_settings from {data}")
|
self._log.debug(f"[{self._pipe_name}] Received get_session_settings from {data}")
|
||||||
settings = {
|
_blank_settings['version'] = self._version
|
||||||
'version': self._version,
|
_blank_settings['encoding'] = self._session_encoding
|
||||||
'encoding': self.default_encoding,
|
_blank_settings['header_size'] = self._header_size
|
||||||
'header_size': self._header_size,
|
_blank_settings['header_format'] = self._header_format
|
||||||
'header_format': self._header_format,
|
_blank_settings['max_buffer'] = self.read_max_buffer
|
||||||
'max_buffer': self.read_max_buffer,
|
_blank_settings['crypto'] = self.__crypto.crypt_name
|
||||||
"crypto": self.__crypto.get_info()
|
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)
|
||||||
session_settings = f"set_session_settings:{json.dumps(settings)}".encode(self.init_encoding)
|
|
||||||
self._send_message("command", session_settings)
|
|
||||||
return True
|
return True
|
||||||
case b'set_session_settings':
|
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:
|
try:
|
||||||
settings = json.loads(data.decode(self.init_encoding))
|
settings = json.loads(data.decode(self.init_encoding))
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
self._send_error(WinConnectErrors.BAD_DATA, f"JSONDecodeError: {e}")
|
self._send_error(WinConnectErrors.BAD_DATA, f"JSONDecodeError: {e}")
|
||||||
return self.close()
|
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._log.error(f"{WinConnectErrors.BAD_VERSION}")
|
||||||
self._send_error(WinConnectErrors.BAD_VERSION, f"Version mismatch")
|
self._send_error(WinConnectErrors.BAD_VERSION, f"Version mismatch")
|
||||||
return self.close()
|
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._log.error(f"{WinConnectErrors.BAD_CRYPTO}")
|
||||||
self._send_error(WinConnectErrors.BAD_CRYPTO, f"Crypto mismatch")
|
self._send_error(WinConnectErrors.BAD_CRYPTO, f"Crypto mismatch")
|
||||||
return self.close()
|
return self.close()
|
||||||
@@ -228,6 +317,7 @@ class WinConnectBase:
|
|||||||
case b"session_ready":
|
case b"session_ready":
|
||||||
self._inited = True
|
self._inited = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
case b"close":
|
case b"close":
|
||||||
self.close()
|
self.close()
|
||||||
return True
|
return True
|
||||||
@@ -238,15 +328,17 @@ class WinConnectBase:
|
|||||||
action, data = self._read_message()
|
action, data = self._read_message()
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
return
|
return
|
||||||
if action != b"command":
|
if action != b"cmd":
|
||||||
return self._send_error(WinConnectErrors.BAD_DATA, "Unknown data type")
|
return self._send_error(WinConnectErrors.BAD_DATA, "Unknown data type")
|
||||||
if not self._parse_command(data):
|
if not self._parse_command(data):
|
||||||
return self._send_error(WinConnectErrors.INIT_FIRST, "Server need to init session first")
|
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())
|
self._parse_action(*self._read_message())
|
||||||
|
|
||||||
def send_data(self, data):
|
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): ...
|
def _close_session(self): ...
|
||||||
|
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ class WinConnectClient(WinConnectBase):
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
def _init(self, program_name="NoName"):
|
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()
|
self._init_session()
|
||||||
|
|
||||||
def _close_session(self):
|
def _close_session(self):
|
||||||
"""Send close command to server"""
|
"""Send close command to server"""
|
||||||
if not self.closed:
|
if not self.closed:
|
||||||
self._send_message("command", b"close:")
|
self._send_message("cmd", b"close:")
|
||||||
|
|
||||||
def __check_pipe(self):
|
def __check_pipe(self):
|
||||||
if not self._opened:
|
if not self._opened:
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
__title__ = 'winConnect'
|
__title__ = 'winConnect'
|
||||||
__description__ = 'Communicate Client-Server via Windows NamedPipe.'
|
__description__ = 'Communicate Client-Server via Windows NamedPipe.'
|
||||||
__url__ = 'https://github.com/SantaSpeen/winConnect'
|
__url__ = 'https://github.com/SantaSpeen/winConnect'
|
||||||
__version__ = '0.9.2'
|
__version__ = '0.9.3'
|
||||||
__build__ = 39
|
__build__ = 82
|
||||||
__author__ = 'SantaSpeen'
|
__author__ = 'SantaSpeen'
|
||||||
__author_email__ = 'admin@anidev.ru'
|
__author_email__ = 'admin@anidev.ru'
|
||||||
__license__ = "MIT"
|
__license__ = "MIT"
|
||||||
|
|||||||
@@ -15,15 +15,26 @@ def test_crypto_class(crypto_class: "WinConnectCryptoBase"):
|
|||||||
class WinConnectCrypto:
|
class WinConnectCrypto:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__crypto_class = None
|
self.__crypto_class: WinConnectCryptoBase = None
|
||||||
self.__log_prefix = None
|
self.__log_prefix = None
|
||||||
self._log = logging.getLogger("WinConnectCrypto")
|
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"):
|
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)
|
test_crypto_class(crypto_class)
|
||||||
self.__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.")
|
self._log.debug(f"{self.__log_prefix}Crypto class updated.")
|
||||||
|
|
||||||
def set_logger(self, logger):
|
def set_logger(self, logger):
|
||||||
@@ -38,9 +49,6 @@ class WinConnectCrypto:
|
|||||||
self._log.debug(f"{self.__log_prefix}Crypto class loaded")
|
self._log.debug(f"{self.__log_prefix}Crypto class loaded")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_info(self):
|
|
||||||
return self.__crypto_class.__class__.__name__
|
|
||||||
|
|
||||||
def encrypt(self, data: bytes) -> bytes:
|
def encrypt(self, data: bytes) -> bytes:
|
||||||
return self.__crypto_class.encrypt(data)
|
return self.__crypto_class.encrypt(data)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ from .crypto_classes import (
|
|||||||
WinConnectCryptoBase,
|
WinConnectCryptoBase,
|
||||||
WinConnectCryptoNone,
|
WinConnectCryptoNone,
|
||||||
WinConnectCryptoSimple,
|
WinConnectCryptoSimple,
|
||||||
# WinConnectCryptoPassword,
|
WinConnectCryptoPassword,
|
||||||
# WinConnectCryptoCert
|
# WinConnectCryptoCert
|
||||||
)
|
)
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
class WinConnectCryptoBase:
|
class WinConnectCryptoBase:
|
||||||
|
|
||||||
|
@property
|
||||||
|
def salt(self) -> bytes:
|
||||||
|
return b""
|
||||||
|
@salt.setter
|
||||||
|
def salt(self, value): ...
|
||||||
|
|
||||||
def encrypt(self, data: bytes) -> bytes: ...
|
def encrypt(self, data: bytes) -> bytes: ...
|
||||||
def decrypt(self, data: bytes) -> bytes: ...
|
def decrypt(self, data: bytes) -> bytes: ...
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
import os
|
||||||
import random
|
import random
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from .crypto_class_base import WinConnectCryptoBase
|
from .crypto_class_base import WinConnectCryptoBase
|
||||||
from winConnect.exceptions import WinConnectCryptoSimpleBadHeaderException
|
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):
|
class WinConnectCryptoNone(WinConnectCryptoBase):
|
||||||
def __init__(self): ...
|
def __init__(self): ...
|
||||||
@@ -19,18 +29,16 @@ class WinConnectCryptoSimple(WinConnectCryptoBase):
|
|||||||
shift_key = random.randint(100, 749)
|
shift_key = random.randint(100, 749)
|
||||||
key = random.randint(5, 250)
|
key = random.randint(5, 250)
|
||||||
encrypted_text = bytearray()
|
encrypted_text = bytearray()
|
||||||
header = f"wccs{shift_key}{key+shift_key}:"
|
header = f"wccs{shift_key}{key+shift_key}:".encode()
|
||||||
for char in header:
|
|
||||||
encrypted_text.append(ord(char))
|
|
||||||
for char in data:
|
for char in data:
|
||||||
encrypted_text.append(char ^ key)
|
encrypted_text.append(char ^ key)
|
||||||
return bytes(encrypted_text)
|
return header + bytes(encrypted_text)
|
||||||
|
|
||||||
def decrypt(self, data: bytes) -> bytes:
|
def decrypt(self, data: bytes) -> bytes:
|
||||||
try:
|
try:
|
||||||
header, content = data.split(b":", 1)
|
header, content = data.split(b":", 1)
|
||||||
if header[:4] != b"wccs":
|
if header[:4] != b"wccs":
|
||||||
raise WinConnectCryptoSimpleBadHeaderException("Bad header in message.")
|
raise WinConnectCryptoSimpleBadHeaderException(f"Bad header in message: {header[:4].decode()}")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise WinConnectCryptoSimpleBadHeaderException("No header in message.")
|
raise WinConnectCryptoSimpleBadHeaderException("No header in message.")
|
||||||
shift_key = int(header[4:7])
|
shift_key = int(header[4:7])
|
||||||
@@ -44,27 +52,57 @@ class WinConnectCryptoSimple(WinConnectCryptoBase):
|
|||||||
class WinConnectCryptoPassword(WinConnectCryptoBase):
|
class WinConnectCryptoPassword(WinConnectCryptoBase):
|
||||||
|
|
||||||
def __init__(self, password: str):
|
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:
|
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:
|
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):
|
cipher = AES.new(self.__key, AES.MODE_CBC, iv)
|
||||||
def __init__(self, cert_file: str):
|
decrypted = cipher.decrypt(content)
|
||||||
pass
|
pad_len = decrypted[-1] # Убираем PKCS7 padding
|
||||||
|
return decrypted[:-pad_len]
|
||||||
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
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
#
|
||||||
@@ -14,7 +14,7 @@ class WinConnectErrors(Enum):
|
|||||||
BAD_DATA = 50
|
BAD_DATA = 50
|
||||||
BAD_VERSION = 51
|
BAD_VERSION = 51
|
||||||
BAD_HEADER = 52
|
BAD_HEADER = 52
|
||||||
BAD_BODY = 53
|
BAD_SETTINGS = 53
|
||||||
BAD_CRYPTO = 54
|
BAD_CRYPTO = 54
|
||||||
|
|
||||||
BODY_TOO_BIG = 60
|
BODY_TOO_BIG = 60
|
||||||
|
|||||||
Reference in new issue
Block a user