atomicshop 2.15.11__py3-none-any.whl → 3.10.5__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.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -80
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +491 -182
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +1 -1
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
from typing import Union, Generator
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from websockets.server import ServerProtocol
|
|
5
|
+
from websockets.client import ClientProtocol
|
|
6
|
+
from websockets.extensions.permessage_deflate import PerMessageDeflate, ServerPerMessageDeflateFactory, ClientPerMessageDeflateFactory
|
|
7
|
+
from websockets.http11 import Request, Response
|
|
8
|
+
from websockets.frames import Frame, Opcode
|
|
9
|
+
from websockets.uri import parse_uri
|
|
10
|
+
from websockets.exceptions import InvalidHeaderValue
|
|
11
|
+
from websockets.protocol import OPEN
|
|
12
|
+
from websockets.streams import StreamReader
|
|
13
|
+
from websockets.exceptions import ProtocolError, PayloadTooBig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebsocketParseWrongOpcode(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_byte_http_response(
|
|
21
|
+
byte_http_request: Union[bytes, bytearray],
|
|
22
|
+
enable_logging: bool = False
|
|
23
|
+
) -> bytes:
|
|
24
|
+
"""
|
|
25
|
+
Create a byte HTTP response from a byte HTTP request.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
- byte_http_request (bytes, bytearray): The byte HTTP request.
|
|
29
|
+
- enable_logging (bool): Whether to enable logging.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
- bytes: The byte HTTP response.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Set up extensions
|
|
36
|
+
permessage_deflate_factory = ServerPerMessageDeflateFactory()
|
|
37
|
+
|
|
38
|
+
# Create the protocol instance
|
|
39
|
+
protocol = ServerProtocol(
|
|
40
|
+
extensions=[permessage_deflate_factory],
|
|
41
|
+
)
|
|
42
|
+
# At this state the protocol.state is State.CONNECTING
|
|
43
|
+
|
|
44
|
+
if enable_logging:
|
|
45
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
46
|
+
protocol.logger.setLevel(logging.DEBUG)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
protocol.receive_data(byte_http_request)
|
|
50
|
+
events = protocol.events_received()
|
|
51
|
+
event = events[0]
|
|
52
|
+
if isinstance(event, Request):
|
|
53
|
+
# Accept the handshake.
|
|
54
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
55
|
+
# Only after this state we can parse frames.
|
|
56
|
+
response = protocol.accept(event)
|
|
57
|
+
return response.serialize()
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError("The event is not a Request object.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class WebsocketFrameParser:
|
|
63
|
+
def __init__(self):
|
|
64
|
+
# Instantiate the permessage-deflate extension.
|
|
65
|
+
# If a frame uses 'deflate', then the 'permessage_deflate' should be the same object during parsing of
|
|
66
|
+
# several message on the same socket. Each time 'PerMessageDeflate' is initiated, the context changes
|
|
67
|
+
# and more than one message can't be parsed.
|
|
68
|
+
self.permessage_deflate_masked = PerMessageDeflate(
|
|
69
|
+
remote_no_context_takeover=False,
|
|
70
|
+
local_no_context_takeover=False,
|
|
71
|
+
remote_max_window_bits=15,
|
|
72
|
+
local_max_window_bits=15,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# We need separate instances for masked (frames from client) and unmasked (frames from server).
|
|
76
|
+
self.permessage_deflate_unmasked = PerMessageDeflate(
|
|
77
|
+
remote_no_context_takeover=False,
|
|
78
|
+
local_no_context_takeover=False,
|
|
79
|
+
remote_max_window_bits=15,
|
|
80
|
+
local_max_window_bits=15,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def parse_frame_bytes(
|
|
84
|
+
self,
|
|
85
|
+
data_bytes: bytes
|
|
86
|
+
):
|
|
87
|
+
# Define the read_exact function
|
|
88
|
+
def read_exact(n: int) -> Generator[None, None, bytes]:
|
|
89
|
+
return reader.read_exact(n)
|
|
90
|
+
|
|
91
|
+
# Helper function to run generator-based coroutines
|
|
92
|
+
def run_coroutine(coroutine):
|
|
93
|
+
try:
|
|
94
|
+
while True:
|
|
95
|
+
next(coroutine)
|
|
96
|
+
except StopIteration as e:
|
|
97
|
+
return e.value
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise e # Re-raise exceptions to be handled by the caller
|
|
100
|
+
|
|
101
|
+
# Function to parse frames
|
|
102
|
+
def parse_frame(mask: bool, deflate: bool):
|
|
103
|
+
try:
|
|
104
|
+
if mask:
|
|
105
|
+
# Decide whether to include permessage-deflate extension
|
|
106
|
+
extensions = [self.permessage_deflate_masked] if deflate else []
|
|
107
|
+
else:
|
|
108
|
+
extensions = [self.permessage_deflate_unmasked] if deflate else []
|
|
109
|
+
|
|
110
|
+
# Use Frame.parse to parse the frame
|
|
111
|
+
frame_parser = Frame.parse(
|
|
112
|
+
read_exact,
|
|
113
|
+
mask=mask, # Client frames are masked
|
|
114
|
+
max_size=None,
|
|
115
|
+
extensions=extensions
|
|
116
|
+
)
|
|
117
|
+
current_frame = run_coroutine(frame_parser)
|
|
118
|
+
except EOFError as e:
|
|
119
|
+
# Not enough data to parse a complete frame
|
|
120
|
+
raise e
|
|
121
|
+
except (ProtocolError, PayloadTooBig) as e:
|
|
122
|
+
print("Error parsing frame:", e)
|
|
123
|
+
raise e
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print("Error parsing frame:", e)
|
|
126
|
+
raise e
|
|
127
|
+
return current_frame
|
|
128
|
+
|
|
129
|
+
def process_frame(current_frame):
|
|
130
|
+
if current_frame.opcode == Opcode.TEXT:
|
|
131
|
+
message = current_frame.data.decode('utf-8', errors='replace')
|
|
132
|
+
return message, 'TEXT'
|
|
133
|
+
elif current_frame.opcode == Opcode.BINARY:
|
|
134
|
+
return current_frame.data, 'BINARY'
|
|
135
|
+
elif current_frame.opcode == Opcode.CLOSE:
|
|
136
|
+
return current_frame.data, 'CLOSE'
|
|
137
|
+
elif current_frame.opcode == Opcode.PING:
|
|
138
|
+
return current_frame.data, 'PING'
|
|
139
|
+
elif current_frame.opcode == Opcode.PONG:
|
|
140
|
+
return current_frame.data, 'PONG'
|
|
141
|
+
else:
|
|
142
|
+
raise WebsocketParseWrongOpcode("Received unknown frame with opcode:", current_frame.opcode)
|
|
143
|
+
|
|
144
|
+
# Create the StreamReader instance
|
|
145
|
+
reader = StreamReader()
|
|
146
|
+
|
|
147
|
+
masked = is_frame_masked(data_bytes)
|
|
148
|
+
deflated = is_frame_deflated(data_bytes)
|
|
149
|
+
|
|
150
|
+
# Feed the data into the reader
|
|
151
|
+
reader.feed_data(data_bytes)
|
|
152
|
+
|
|
153
|
+
# Parse and process frames
|
|
154
|
+
frame = parse_frame(masked, deflated)
|
|
155
|
+
parsed_frame, frame_opcode = process_frame(frame)
|
|
156
|
+
|
|
157
|
+
# This is basically not needed since we restart the 'reader = StreamReader()' each function execution.
|
|
158
|
+
# # After processing, reset the reader's buffer
|
|
159
|
+
# reader.buffer = b''
|
|
160
|
+
|
|
161
|
+
result: dict = {
|
|
162
|
+
'is_deflated': deflated,
|
|
163
|
+
'is_masked': masked,
|
|
164
|
+
'frame': parsed_frame,
|
|
165
|
+
'opcode': frame_opcode
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def create_websocket_frame(
|
|
172
|
+
data: Union[str, bytes, bytearray],
|
|
173
|
+
deflate: bool = False,
|
|
174
|
+
mask: bool = False,
|
|
175
|
+
opcode: int = None
|
|
176
|
+
) -> bytes:
|
|
177
|
+
"""
|
|
178
|
+
Create a WebSocket frame with the given data, optionally applying
|
|
179
|
+
permessage-deflate compression and masking.
|
|
180
|
+
|
|
181
|
+
Parameters:
|
|
182
|
+
- data (str, bytes, bytearray): The payload data.
|
|
183
|
+
If str, it will be encoded to bytes using UTF-8.
|
|
184
|
+
- deflate (bool): Whether to apply permessage-deflate compression.
|
|
185
|
+
- mask (bool): Whether to apply masking to the frame.
|
|
186
|
+
- opcode (int): The opcode of the frame. If not provided, it will be
|
|
187
|
+
determined based on the type of data.
|
|
188
|
+
Example:
|
|
189
|
+
from websockets.frames import Opcode
|
|
190
|
+
Opcode.TEXT, Opcode.BINARY, Opcode.CLOSE, Opcode.PING, Opcode.PONG.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
- bytes: The serialized WebSocket frame ready to be sent.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
# Determine the opcode if not provided
|
|
197
|
+
if opcode is None:
|
|
198
|
+
if isinstance(data, str):
|
|
199
|
+
opcode = Opcode.TEXT
|
|
200
|
+
elif isinstance(data, (bytes, bytearray)):
|
|
201
|
+
opcode = Opcode.BINARY
|
|
202
|
+
else:
|
|
203
|
+
raise TypeError("Data must be of type str, bytes, or bytearray.")
|
|
204
|
+
else:
|
|
205
|
+
if not isinstance(opcode, int):
|
|
206
|
+
raise TypeError("Opcode must be an integer.")
|
|
207
|
+
if not isinstance(data, (str, bytes, bytearray)):
|
|
208
|
+
raise TypeError("Data must be of type str, bytes, or bytearray.")
|
|
209
|
+
|
|
210
|
+
# Encode string data if necessary
|
|
211
|
+
if isinstance(data, str):
|
|
212
|
+
payload = data.encode('utf-8')
|
|
213
|
+
else:
|
|
214
|
+
payload = bytes(data)
|
|
215
|
+
|
|
216
|
+
# Create the Frame instance
|
|
217
|
+
frame = Frame(opcode=opcode, data=payload)
|
|
218
|
+
|
|
219
|
+
# Set up extensions if deflate is True
|
|
220
|
+
extensions = []
|
|
221
|
+
if deflate:
|
|
222
|
+
permessage_deflate = PerMessageDeflate(
|
|
223
|
+
remote_no_context_takeover=False,
|
|
224
|
+
local_no_context_takeover=False,
|
|
225
|
+
remote_max_window_bits=15,
|
|
226
|
+
local_max_window_bits=15,
|
|
227
|
+
)
|
|
228
|
+
extensions.append(permessage_deflate)
|
|
229
|
+
|
|
230
|
+
# Serialize the frame with the specified options
|
|
231
|
+
try:
|
|
232
|
+
frame_bytes = frame.serialize(
|
|
233
|
+
mask=mask,
|
|
234
|
+
extensions=extensions,
|
|
235
|
+
)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
raise RuntimeError(f"Error serializing frame: {e}")
|
|
238
|
+
|
|
239
|
+
return frame_bytes
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def is_frame_masked(frame_bytes: bytes):
|
|
243
|
+
"""
|
|
244
|
+
Determine whether a WebSocket frame is masked.
|
|
245
|
+
|
|
246
|
+
Parameters:
|
|
247
|
+
- frame_bytes (bytes): The raw bytes of the WebSocket frame.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
- bool: True if the frame is masked, False otherwise.
|
|
251
|
+
"""
|
|
252
|
+
if len(frame_bytes) < 2:
|
|
253
|
+
raise ValueError("Frame is too short to determine masking.")
|
|
254
|
+
|
|
255
|
+
# The second byte of the frame header contains the MASK bit
|
|
256
|
+
second_byte = frame_bytes[1]
|
|
257
|
+
|
|
258
|
+
# The MASK bit is the most significant bit (MSB) of the second byte
|
|
259
|
+
mask_bit = (second_byte & 0x80) != 0 # 0x80 is 1000 0000 in binary
|
|
260
|
+
|
|
261
|
+
return mask_bit
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def is_frame_deflated(frame_bytes):
|
|
265
|
+
"""
|
|
266
|
+
Determine whether a WebSocket frame is deflated (compressed).
|
|
267
|
+
|
|
268
|
+
Parameters:
|
|
269
|
+
- frame_bytes (bytes): The raw bytes of the WebSocket frame.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
- bool: True if the frame is deflated (compressed), False otherwise.
|
|
273
|
+
"""
|
|
274
|
+
if len(frame_bytes) < 1:
|
|
275
|
+
raise ValueError("Frame is too short to determine deflation status.")
|
|
276
|
+
|
|
277
|
+
# The first byte of the frame header contains the RSV1 bit
|
|
278
|
+
first_byte = frame_bytes[0]
|
|
279
|
+
|
|
280
|
+
# The RSV1 bit is the second most significant bit (bit 6)
|
|
281
|
+
rsv1 = (first_byte & 0x40) != 0 # 0x40 is 0100 0000 in binary
|
|
282
|
+
|
|
283
|
+
return rsv1
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class _WebsocketRequestParse:
|
|
287
|
+
"""
|
|
288
|
+
THIS IS ONLY FOR THE REFERENCE IT IS NOT CURRENTLY USED OR SHOULD BE USED.
|
|
289
|
+
Parse the websocket request and return the data
|
|
290
|
+
"""
|
|
291
|
+
def __init__(
|
|
292
|
+
self,
|
|
293
|
+
enable_logging: bool = False,
|
|
294
|
+
):
|
|
295
|
+
"""
|
|
296
|
+
Initialize the websocket parser.
|
|
297
|
+
|
|
298
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
299
|
+
"""
|
|
300
|
+
# noinspection PyTypeChecker
|
|
301
|
+
self.request_bytes: bytes = None
|
|
302
|
+
|
|
303
|
+
# Set up extensions
|
|
304
|
+
permessage_deflate_factory = ServerPerMessageDeflateFactory()
|
|
305
|
+
|
|
306
|
+
# Create the protocol instance
|
|
307
|
+
self.protocol = ServerProtocol(
|
|
308
|
+
extensions=[permessage_deflate_factory],
|
|
309
|
+
)
|
|
310
|
+
# At this state the protocol.state is State.CONNECTING
|
|
311
|
+
|
|
312
|
+
if enable_logging:
|
|
313
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
314
|
+
self.protocol.logger.setLevel(logging.DEBUG)
|
|
315
|
+
|
|
316
|
+
def parse(
|
|
317
|
+
self,
|
|
318
|
+
request_bytes: bytes
|
|
319
|
+
) -> Union[str, bytes, Request]:
|
|
320
|
+
"""
|
|
321
|
+
Parse the websocket request and return the data
|
|
322
|
+
|
|
323
|
+
:param request_bytes: bytes: The raw bytes of the websocket request.
|
|
324
|
+
:return: Request: The parsed request object.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
self.protocol.receive_data(request_bytes)
|
|
328
|
+
events = self.protocol.events_received()
|
|
329
|
+
for event in events:
|
|
330
|
+
if isinstance(event, Request):
|
|
331
|
+
# Accept the handshake.
|
|
332
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
333
|
+
# Only after this state we can parse frames.
|
|
334
|
+
response = self.protocol.accept(event)
|
|
335
|
+
self.protocol.send_response(response)
|
|
336
|
+
return event
|
|
337
|
+
elif isinstance(event, Frame):
|
|
338
|
+
frame = event
|
|
339
|
+
if frame.opcode == Opcode.TEXT:
|
|
340
|
+
message = frame.data.decode('utf-8')
|
|
341
|
+
return message
|
|
342
|
+
elif frame.opcode == Opcode.BINARY:
|
|
343
|
+
return frame.data
|
|
344
|
+
|
|
345
|
+
"""
|
|
346
|
+
# Handle control frames, these are here for the future references.
|
|
347
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
348
|
+
close_info = Close.parse(frame.data)
|
|
349
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
350
|
+
# Send a close frame in response if not already sent
|
|
351
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
352
|
+
self.protocol.send_close()
|
|
353
|
+
elif frame.opcode == Opcode.PING:
|
|
354
|
+
# Respond to ping with pong
|
|
355
|
+
self.protocol.send_pong(frame.data)
|
|
356
|
+
elif frame.opcode == Opcode.PONG:
|
|
357
|
+
print("Received pong")
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class _WebsocketResponseParse:
|
|
362
|
+
"""
|
|
363
|
+
THIS IS ONLY FOR THE REFERENCE IT IS NOT CURRENTLY USED OR SHOULD BE USED.
|
|
364
|
+
Parse the websocket response and return the data
|
|
365
|
+
"""
|
|
366
|
+
def __init__(
|
|
367
|
+
self,
|
|
368
|
+
enable_logging: bool = False,
|
|
369
|
+
):
|
|
370
|
+
"""
|
|
371
|
+
Initialize the websocket parser.
|
|
372
|
+
|
|
373
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
374
|
+
"""
|
|
375
|
+
# noinspection PyTypeChecker
|
|
376
|
+
self.response_bytes: bytes = None
|
|
377
|
+
|
|
378
|
+
# Set up extensions
|
|
379
|
+
permessage_deflate_factory = ClientPerMessageDeflateFactory()
|
|
380
|
+
|
|
381
|
+
# Parse the WebSocket URI.
|
|
382
|
+
# Since we're parsing the response, we don't need the URI, but the protocol object requires it.
|
|
383
|
+
# So we will just use a dummy URI.
|
|
384
|
+
wsuri = parse_uri('ws://example.com/websocket')
|
|
385
|
+
|
|
386
|
+
# Create the protocol instance
|
|
387
|
+
self.protocol = ClientProtocol(
|
|
388
|
+
wsuri,
|
|
389
|
+
extensions=[permessage_deflate_factory],
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
if enable_logging:
|
|
393
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
394
|
+
# self.protocol.logger.setLevel(logging.DEBUG)
|
|
395
|
+
self.protocol.debug = True
|
|
396
|
+
|
|
397
|
+
# Perform the handshake and emulate the connection and request sending.
|
|
398
|
+
request = self.protocol.connect()
|
|
399
|
+
self.protocol.send_request(request)
|
|
400
|
+
_ = self.protocol.data_to_send()
|
|
401
|
+
# At this state the protocol.state is State.CONNECTING
|
|
402
|
+
|
|
403
|
+
def parse(
|
|
404
|
+
self,
|
|
405
|
+
response_bytes: bytes
|
|
406
|
+
) -> Union[str, bytes, Response]:
|
|
407
|
+
"""
|
|
408
|
+
Parse the websocket response and return the data
|
|
409
|
+
|
|
410
|
+
:param response_bytes: bytes: The raw bytes of the websocket response.
|
|
411
|
+
:return: The parsed response.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
self.protocol.receive_data(response_bytes)
|
|
415
|
+
events = self.protocol.events_received()
|
|
416
|
+
for event in events:
|
|
417
|
+
if isinstance(event, Response):
|
|
418
|
+
# Accept the handshake.
|
|
419
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
420
|
+
# Only after this state we can parse frames.
|
|
421
|
+
try:
|
|
422
|
+
self.protocol.process_response(event)
|
|
423
|
+
except InvalidHeaderValue as e:
|
|
424
|
+
headers = event.headers
|
|
425
|
+
self.protocol.extensions = self.protocol.process_extensions(headers)
|
|
426
|
+
self.protocol.subprotocol = self.protocol.process_subprotocol(headers)
|
|
427
|
+
self.protocol.state = OPEN
|
|
428
|
+
return event
|
|
429
|
+
elif isinstance(event, Frame):
|
|
430
|
+
frame = event
|
|
431
|
+
if frame.opcode == Opcode.TEXT:
|
|
432
|
+
message = frame.data.decode('utf-8')
|
|
433
|
+
return message
|
|
434
|
+
elif frame.opcode == Opcode.BINARY:
|
|
435
|
+
return frame.data
|
|
436
|
+
|
|
437
|
+
"""
|
|
438
|
+
# Handle control frames, these are here for the future references.
|
|
439
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
440
|
+
close_info = Close.parse(frame.data)
|
|
441
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
442
|
+
# Send a close frame in response if not already sent
|
|
443
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
444
|
+
self.protocol.send_close()
|
|
445
|
+
elif frame.opcode == Opcode.PING:
|
|
446
|
+
# Respond to ping with pong
|
|
447
|
+
self.protocol.send_pong(frame.data)
|
|
448
|
+
elif frame.opcode == Opcode.PONG:
|
|
449
|
+
print("Received pong")
|
|
450
|
+
"""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Union
|
|
2
2
|
import os
|
|
3
|
+
import warnings
|
|
3
4
|
|
|
4
5
|
from ..print_api import print_api
|
|
5
6
|
from ..file_io import file_io
|
|
@@ -9,6 +10,7 @@ from cryptography.x509 import Certificate
|
|
|
9
10
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
10
11
|
from cryptography.hazmat.primitives import serialization
|
|
11
12
|
from cryptography.hazmat.primitives import hashes
|
|
13
|
+
from cryptography.utils import CryptographyDeprecationWarning
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
"""
|
|
@@ -22,7 +24,10 @@ OID_TO_BUILDER_CLASS_EXTENSION_NAME: dict = {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
def convert_object_to_x509(
|
|
27
|
+
def convert_object_to_x509(
|
|
28
|
+
certificate: Union[str, bytes, Certificate],
|
|
29
|
+
print_kwargs: dict = None
|
|
30
|
+
) -> x509.Certificate | None:
|
|
26
31
|
"""Convert certificate to x509 object.
|
|
27
32
|
|
|
28
33
|
:param certificate: any object that can be converted to x509 object.
|
|
@@ -32,13 +37,16 @@ def convert_object_to_x509(certificate):
|
|
|
32
37
|
string that is PEM certificate will be converted to bytes, then x509.Certificate
|
|
33
38
|
bytes of PEM or DER will be converted to x509.Certificate.
|
|
34
39
|
x509.Certificate will be returned as is.
|
|
35
|
-
:
|
|
40
|
+
:param print_kwargs: dict, additional arguments to pass to the print function.
|
|
41
|
+
:return: certificate in x509 object of 'cryptography' module. Or None if the certificate is not valid.
|
|
36
42
|
"""
|
|
37
43
|
|
|
38
44
|
# Check if 'certificate' is a string and a path.
|
|
39
|
-
if isinstance(certificate, str)
|
|
45
|
+
if isinstance(certificate, str):
|
|
46
|
+
if not os.path.isfile(certificate):
|
|
47
|
+
raise FileNotFoundError(f'File not found: {certificate}')
|
|
40
48
|
# Import the certificate from the path.
|
|
41
|
-
certificate = file_io.read_file(certificate, file_mode='rb')
|
|
49
|
+
certificate = file_io.read_file(certificate, file_mode='rb', **(print_kwargs or {}))
|
|
42
50
|
|
|
43
51
|
# Check if 'certificate' is a bytes object and PEM format.
|
|
44
52
|
# We're checking if it starts with '-----BEGIN ' since the pem certificate can include PRIVATE KEY and will be
|
|
@@ -80,14 +88,27 @@ def convert_pem_to_x509_object(certificate: Union[str, bytes]) -> x509.Certifica
|
|
|
80
88
|
return x509.load_pem_x509_certificate(certificate)
|
|
81
89
|
|
|
82
90
|
|
|
83
|
-
def convert_der_to_x509_object(certificate: bytes):
|
|
91
|
+
def convert_der_to_x509_object(certificate: bytes) -> x509.Certificate | None:
|
|
84
92
|
"""Convert DER certificate from socket to x509 object.
|
|
85
93
|
|
|
86
94
|
:param certificate: bytes, certificate to convert.
|
|
87
95
|
:return: certificate in x509 object of 'cryptography' module.
|
|
88
96
|
"""
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
# Some certificates in the store can have zero or negative serial number.
|
|
99
|
+
# We will skip them, since they're deprecated by the cryptography library.
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
with warnings.catch_warnings():
|
|
103
|
+
# Turn the deprecation warning into an exception we can trap.
|
|
104
|
+
warnings.filterwarnings("error", category=CryptographyDeprecationWarning)
|
|
105
|
+
converted_certificate = x509.load_der_x509_certificate(certificate)
|
|
106
|
+
except (CryptographyDeprecationWarning, ValueError):
|
|
107
|
+
return None # serial was 0/negative → skip
|
|
108
|
+
if converted_certificate.serial_number <= 0: # belt-and-braces
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
return converted_certificate
|
|
91
112
|
|
|
92
113
|
|
|
93
114
|
def convert_x509_object_to_pem_bytes(certificate) -> bytes:
|
|
@@ -151,8 +172,8 @@ def copy_extensions_from_old_cert_to_new_cert(
|
|
|
151
172
|
builder = x509.CertificateBuilder()
|
|
152
173
|
builder = builder.subject_name(certificate.subject)
|
|
153
174
|
builder = builder.issuer_name(certificate.issuer)
|
|
154
|
-
builder = builder.not_valid_before(certificate.
|
|
155
|
-
builder = builder.not_valid_after(certificate.
|
|
175
|
+
builder = builder.not_valid_before(certificate.not_valid_before_utc)
|
|
176
|
+
builder = builder.not_valid_after(certificate.not_valid_after_utc)
|
|
156
177
|
builder = builder.serial_number(certificate.serial_number)
|
|
157
178
|
|
|
158
179
|
# We're using the new private key that we will sign with the new certificate later.
|