38 Commits

Author SHA1 Message Date
SantaSpeen d5a0e4d633 Merge pull request #4 from SantaSpeen/dev
Dev
2025-03-15 02:50:55 +03:00
SantaSpeen b2ba454d07 ???? 2025-03-15 02:50:30 +03:00
SantaSpeen bb6a11e5fa Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	requirements.txt
2025-03-15 02:46:13 +03:00
SantaSpeen c5a79777e6 Merge pull request #3 from SantaSpeen/dev
Dev
2025-03-15 02:28:12 +03:00
SantaSpeen bad58d4521 fastfix 2025-03-14 16:03:18 +03:00
SantaSpeen 94bf268bab [>] json > orjson 2025-03-14 16:01:22 +03:00
SantaSpeen 53776f9278 .idea 2025-03-14 15:56:27 +03:00
SantaSpeen d124548527 [~] Optimize imports 2025-03-14 15:56:18 +03:00
SantaSpeen caa14e0fe5 update req 2025-03-14 15:55:05 +03:00
SantaSpeen 9b85ccf002 [~] change == 2025-03-13 23:45:58 +03:00
SantaSpeen 2b0c038e9c Merge pull request #2 from SantaSpeen/dev
Update 0.9.3; New protocol version: 3
2025-03-13 19:56:34 +03:00
SantaSpeen 038a3837fb [-] WinConnectCryptoCert 2025-03-13 19:52:59 +03:00
SantaSpeen 68724650d2 Update TODOs 2025-03-13 19:52:10 +03:00
SantaSpeen a66896a4a1 Bump build 2025-03-13 19:51:41 +03:00
SantaSpeen 9545c6f4c3 Add example for LOOOOOOOONG data (chunked) 2025-03-13 19:50:37 +03:00
SantaSpeen ac6ed4aea3 [!] Fix salt..
[+] Chunked ready
[+] sha256 check
2025-03-13 19:50:12 +03:00
SantaSpeen 7e12d64c05 Chunk needs fixing 2025-03-13 19:36:38 +03:00
SantaSpeen 4a320155fe Remove header setting) 2025-03-13 18:25:40 +03:00
SantaSpeen 91f882b494 Update TODOs 2025-03-13 17:59:16 +03:00
SantaSpeen c0e6c9cc63 Bump version 0.9.3 2025-03-13 17:58:34 +03:00
SantaSpeen b473fe3fa3 [!+] WinConnectCryptoPassword
[~] Change init_sess algo (+salt)
2025-03-13 17:57:58 +03:00
SantaSpeen d8a4d1682c [!] Update protocol: v3 (with crypto+salt)
[~] WinConnectCryptoPassword
[+] WinConnectCryptoBase.salt (+setter)
[~] Optimize WinConnectCryptoSimple.encrypt
[+] pycryptodome
2025-03-13 17:02:41 +03:00
SantaSpeen d937479aa8 Add examples with password 2025-03-13 16:49:54 +03:00
SantaSpeen 7274aa159c Update classifiers 2025-03-13 15:04:05 +03:00
SantaSpeen 7ca5b9b775 Merge pull request #1 from SantaSpeen/dev
Update 0.9.2; New protocol version: 2
2025-03-13 14:09:21 +03:00
SantaSpeen 605b46cb72 Add examples with crypto 2025-03-13 14:07:05 +03:00
SantaSpeen af66d240a1 Add examples with crypto 2025-03-13 13:54:40 +03:00
SantaSpeen d074273fd8 Bump version 0.9.2 2025-03-13 13:51:45 +03:00
SantaSpeen d088e6fae7 [+] Support for crypto
[~] Change init algo
2025-03-13 13:51:24 +03:00
SantaSpeen 54335f1cfb [+] Crypto exc and errors 2025-03-13 13:49:57 +03:00
SantaSpeen 98c25ca909 [~] minor update 2025-03-13 13:49:46 +03:00
SantaSpeen 169290aa83 [+] crypto module 2025-03-13 13:48:46 +03:00
SantaSpeen 3a11141b21 [+] WinConnectCrypto 2025-03-13 13:48:07 +03:00
SantaSpeen 4e350bcc53 bones
[+] WinConnectCryptoPassword
[+] WinConnectCryptoCert
2025-03-13 13:47:48 +03:00
SantaSpeen 7978e4f2ed [+] WinConnectCryptoBase (and WinConnectCryptoNone)
[+] WinConnectCryptoSimple
2025-03-13 13:47:11 +03:00
SantaSpeen e0f3ee31ed Update TODOs 2025-03-13 13:43:25 +03:00
SantaSpeen 9f57667bd0 rename 2025-03-13 11:38:11 +03:00
SantaSpeen db575bd235 Update TODOs 2025-03-12 16:54:11 +03:00
21 changed files with 564 additions and 111 deletions
+1 -1
View File
@@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Python 3.13 (winConnect)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (winConnect)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (winConnect)" project-jdk-type="Python SDK" />
</project>
+10 -7
View File
@@ -1,14 +1,17 @@
# winConnect (Windows Only)
# winConnect
Communicate Client-Server via Windows NamedPipe
## ToDo:
- [x] Add support for sending and receiving data
- [x] Add support for other header settings
- [x] Add support for safe closing
- [x] Add logging
- [ ] Particular client to client communication (via chunks(?))
- [ ] Add support for encryption
- [x] Add support for sending and receiving data (0.1.0)
- [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)
- [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) (0.9.2)
- [x] password (via AES and PBKDF2) (0.9.3)
- [ ] certificate (via RSA)
- [ ] Add support for multiple clients
+10
View File
@@ -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,11 +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: ">L"
# >L - Big-endian long integer (header_size: 4 bytes, max_size: 4294967295)
connector.set_header_settings(">L")
for data in connector.listen():
print(f"({type(data)}) {data=}")
+27
View File
@@ -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()
+27
View File
@@ -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.WinConnectCryptoSimple()
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()
+21
View File
@@ -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)
+21
View File
@@ -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.WinConnectCryptoSimple()
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)
+4 -2
View File
@@ -1,4 +1,6 @@
# python 3.13
# 12.03.2025
pywin32~=309
ormsgpack~=1.8.0
pywin32==309
ormsgpack==1.8.0
orjson==3.10.15
pycryptodome==3.21.0
+15 -7
View File
@@ -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",
+194 -74
View File
@@ -1,4 +1,5 @@
import json
import hashlib
import orjson
import logging
import struct
import threading
@@ -9,9 +10,11 @@ import ormsgpack
import pywintypes
import win32file
from winConnect.errors import WinConnectErrors, WinConnectClientError
from winConnect import exceptions
from winConnect.utils import SimpleConvertor
from .crypto.WinConnectCrypto import WinConnectCrypto
from .crypto.crypto_classes import WinConnectCryptoNone
from .errors import WinConnectErrors, WinConnectError
from . import exceptions
from .utils import SimpleConvertor
# header: len(data) in struct.pack via header_format
# data: action:data
@@ -19,17 +22,21 @@ from winConnect.utils import SimpleConvertor
class WinConnectBase:
init_encoding = 'utf-8'
init_header_format = ">L" # Format for reading header (big-endian, unsigned long; 4 bytes)
init_header_format = ">H" # Format for reading header (big-endian, unsigned long; 4 bytes)
default_encoding = 'utf-8'
read_max_buffer = SimpleConvertor.to_gb(4) # Max size of buffer for message
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}")
self._version = 1
# versions:
# 1 - 0.9.1
# 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
@@ -42,17 +49,33 @@ class WinConnectBase:
self._inited = False
self._session_encoding = self.init_encoding
self._parts_buffer = None # Buffer for parts of message (If message is too big)
self.__crypto = WinConnectCrypto()
self.__crypto.set_crypto_class(WinConnectCryptoNone())
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:
raise exceptions.WinConnectConnectionAlreadyOpenException("Can't change crypto while session is active")
self.__crypto.set_crypto_class(crypto)
if not self.__crypto.test_and_load():
raise exceptions.WinConnectCryptoException("Crypto failed test")
def set_logger(self, logger):
logger.debug(f"[{self._pipe_name}] Update logger")
self._log = logger
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:
@@ -84,123 +107,217 @@ 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)
action, data = self.__unpack_data(data)
# self._log.debug(f"[{self._pipe_name}] Received message: {action=} {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)
# self._log.debug(f"[{self._pipe_name}] Sending message: {action=} {data=}")
win32file.WriteFile(self._pipe, header)
win32file.WriteFile(self._pipe, packed_data)
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":
return False, WinConnectClientError(data['code'], data['message'])
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
}
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)}:".encode(self.encoding) + orjson.dumps(_blank_settings) + 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:
settings = orjson.loads(data.decode(self.init_encoding))
except orjson.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['crypto'] != self.__crypto.crypt_name:
self._log.error(f"{WinConnectErrors.BAD_CRYPTO}")
self._send_error(WinConnectErrors.BAD_CRYPTO, f"Crypto mismatch")
return self.close()
self._session_encoding = settings.get('encoding', self.default_encoding)
self._header_size = settings.get('header_size', self._header_size)
self._header_format = settings.get('header_format', self._header_format)
self.read_max_buffer = settings.get('max_buffer', self.read_max_buffer)
self._send_message("command", b"ready:")
return True
case b"ready":
case b"session_ready":
self._inited = True
return True
case b"close":
self.close()
return True
@@ -211,14 +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._inited = True
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): ...
+5 -5
View File
@@ -1,8 +1,8 @@
import pywintypes
import win32file
from winConnect.WinConnectBase import WinConnectBase
from winConnect.exceptions import WinConnectConnectionNoPipeException
from .WinConnectBase import WinConnectBase
from .exceptions import WinConnectConnectionNoPipeException
class WinConnectClient(WinConnectBase):
@@ -30,7 +30,7 @@ class WinConnectClient(WinConnectBase):
)
self._opened = True
self._connected = True
self._log.debug(f"Pipe '{self._pipe_name}' opened")
self._log.debug(f"[{self._pipe_name}] Pipe opened")
except pywintypes.error as e:
if e.winerror == 2:
exc = WinConnectConnectionNoPipeException(f"Error while opening pipe: Pipe not found")
@@ -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:
+8 -7
View File
@@ -1,16 +1,13 @@
import win32pipe
from winConnect.WinConnectBase import WinConnectBase
from winConnect.utils import SimpleConvertor
from .WinConnectBase import WinConnectBase
class WinConnectDaemon(WinConnectBase):
# see: https://mhammond.github.io/pywin32/win32pipe__CreateNamedPipe_meth.html
pipe_openMode = win32pipe.PIPE_ACCESS_DUPLEX # Open mode (read/write)
pipe_pipeMode = win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT # Pipe mode (message type, message read mode, blocking mode)
pipe_nMaxInstances = 2 # Max number of instances
pipe_nOutBufferSize = SimpleConvertor.to_kb(64) # Max size of output buffer
pipe_nInBufferSize = SimpleConvertor.to_kb(64) # Max size of input buffer
pipe_nMaxInstances = 1 # Max number of instances
pipe_nDefaultTimeOut = 0 # ~ ms
pipe_sa = None # Security attributes
@@ -19,13 +16,17 @@ class WinConnectDaemon(WinConnectBase):
self.run = True
def _open_pipe(self):
pipe_nOutBufferSize, pipe_nInBufferSize = self._body_max_size+20, self._body_max_size+20
self._log.debug(f"[{self._pipe_name}] Creating pipe. "
f"Settings: {self.pipe_openMode=}, {self.pipe_pipeMode=}, {self.pipe_nMaxInstances=}, "
f"{pipe_nOutBufferSize=}, {pipe_nInBufferSize=}, {self.pipe_nDefaultTimeOut=}, {self.pipe_sa=}")
self._pipe = win32pipe.CreateNamedPipe(
self._pipe_name,
self.pipe_openMode,
self.pipe_pipeMode,
self.pipe_nMaxInstances,
self.pipe_nOutBufferSize,
self.pipe_nInBufferSize,
pipe_nOutBufferSize,
pipe_nInBufferSize,
self.pipe_nDefaultTimeOut,
self.pipe_sa
)
+2
View File
@@ -1,4 +1,6 @@
from .WinConnectDaemon import WinConnectDaemon
from .WinConnectClient import WinConnectClient
from . import crypto
from .__meta__ import *
+2 -2
View File
@@ -3,8 +3,8 @@
__title__ = 'winConnect'
__description__ = 'Communicate Client-Server via Windows NamedPipe.'
__url__ = 'https://github.com/SantaSpeen/winConnect'
__version__ = '0.9.1'
__build__ = 16
__version__ = '0.9.3'
__build__ = 82
__author__ = 'SantaSpeen'
__author_email__ = 'admin@anidev.ru'
__license__ = "MIT"
+56
View File
@@ -0,0 +1,56 @@
import logging
from .crypto_class_base import WinConnectCryptoBase
from winConnect.exceptions import (
WinConnectCryptoBadModeException,
WinConnectCryptoException,
)
def test_crypto_class(crypto_class: "WinConnectCryptoBase"):
if not isinstance(crypto_class, WinConnectCryptoBase):
raise WinConnectCryptoBadModeException("crypto_class must be a subclass of WinConnectCryptoBase")
if not crypto_class.test():
raise WinConnectCryptoException("crypto_class failed test (test_bytes != decrypt_bytes)")
class WinConnectCrypto:
def __init__(self):
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.crypt_name} -> {crypto_class.__class__.__name__}")
test_crypto_class(crypto_class)
self.__crypto_class = crypto_class
self.__log_prefix = f"[{self.crypt_name}] "
self._log.debug(f"{self.__log_prefix}Crypto class updated.")
def set_logger(self, logger):
logger.debug(f"{self.__log_prefix}Setting logger")
self._log = logger
def test_and_load(self) -> bool:
self._log.debug(f"{self.__log_prefix}Testing and loading crypto class")
if not self.__crypto_class.test():
return False
self.__crypto_class.load()
self._log.debug(f"{self.__log_prefix}Crypto class loaded")
return True
def encrypt(self, data: bytes) -> bytes:
return self.__crypto_class.encrypt(data)
def decrypt(self, data: bytes) -> bytes:
return self.__crypto_class.decrypt(data)
+8
View File
@@ -0,0 +1,8 @@
from .WinConnectCrypto import WinConnectCrypto
from .crypto_classes import (
WinConnectCryptoBase,
WinConnectCryptoNone,
WinConnectCryptoSimple,
WinConnectCryptoPassword,
# WinConnectCryptoCert
)
+20
View File
@@ -0,0 +1,20 @@
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: ...
def test(self, test_bytes: bytes = b"test_string") -> bool:
encrypted = self.encrypt(test_bytes)
decrypted = self.decrypt(encrypted)
if decrypted != test_bytes:
return False
return True
def load(self) -> None: ...
def unload(self) -> None: ...
+107
View File
@@ -0,0 +1,107 @@
import os
import random
from winConnect.exceptions import WinConnectCryptoSimpleBadHeaderException
from .crypto_class_base import WinConnectCryptoBase
_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): ...
def encrypt(self, data: bytes) -> bytes:
return data
def decrypt(self, data: bytes) -> bytes:
return data
class WinConnectCryptoSimple(WinConnectCryptoBase):
def __init__(self):
pass
def encrypt(self, data: bytes) -> bytes:
shift_key = random.randint(100, 749)
key = random.randint(5, 250)
encrypted_text = bytearray()
header = f"wccs{shift_key}{key+shift_key}:".encode()
for char in data:
encrypted_text.append(char ^ key)
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(f"Bad header in message: {header[:4].decode()}")
except ValueError:
raise WinConnectCryptoSimpleBadHeaderException("No header in message.")
shift_key = int(header[4:7])
key = int(header[7:]) - shift_key
decrypted_text = bytearray()
for char in content:
decrypted_text.append(char ^ key)
return bytes(decrypted_text)
class WinConnectCryptoPassword(WinConnectCryptoBase):
def __init__(self, password: str):
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:
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:
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.")
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
#
+3 -1
View File
@@ -14,11 +14,13 @@ class WinConnectErrors(Enum):
BAD_DATA = 50
BAD_VERSION = 51
BAD_HEADER = 52
BAD_SETTINGS = 53
BAD_CRYPTO = 54
BODY_TOO_BIG = 60
@dataclass
class WinConnectClientError:
class WinConnectError:
code: WinConnectErrors
message: str
+23
View File
@@ -31,3 +31,26 @@ class WinConnectSessionAlreadyActiveException(WinConnectBaseException): ...
class WinConnectSessionClosedException(WinConnectBaseException): ...
# Crypto
class WinConnectCryptoException(WinConnectBaseException): ...
class WinConnectCryptoBadModeException(WinConnectCryptoException): ...
## Simple
class WinConnectCryptoSimpleBadHeaderException(WinConnectCryptoException): ...
## key
class WinConnectCryptoKeyRequiredException(WinConnectCryptoException): ...
class WinConnectCryptoKeyInvalidException(WinConnectCryptoException): ...
## cert
class WinConnectCryptoCertificationRequiredException(WinConnectCryptoException): ...
class WinConnectCryptoCertificationNotFoundException(WinConnectCryptoException): ...
class WinConnectCryptoCertificationInvalidException(WinConnectCryptoException): ...