atomicshop 3.3.0__py3-none-any.whl → 3.3.2__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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '3.3.0'
4
+ __version__ = '3.3.2'
@@ -36,6 +36,8 @@ class ArgparseWrapper:
36
36
  # formatter_class=RawTextHelpFormatter: shows raw text and not the default argparse text parsing.
37
37
  self.parser = argparse.ArgumentParser(description=self.description_full,
38
38
  usage=self.usage_variable,
39
+ # usage=argparse.SUPPRESS # Remove usage line from the beginning.
40
+ # add_help=False, # Remove help line from the end.
39
41
  formatter_class=RawTextHelpFormatter)
40
42
 
41
43
  def add_arguments(self) -> None:
@@ -68,7 +68,8 @@ def is_certificate_in_store(
68
68
  by_cert_issuer: bool = True,
69
69
  by_cert_thumbprint: bool = True,
70
70
  issuer_name: str = None,
71
- store_location: str = "ROOT"
71
+ store_location: str = "ROOT",
72
+ print_kwargs: dict = None
72
73
  ) -> tuple[bool, list]:
73
74
  """
74
75
  The function will check if the CA certificate is installed in the Windows certificate Trusted Root store.
@@ -89,6 +90,7 @@ def is_certificate_in_store(
89
90
  certificates with the same issuer name.
90
91
  :param issuer_name: string, issuer name to search for. You can search by certificate or by issuer name.
91
92
  :param store_location: string, store location to search in. Default is "ROOT".
93
+ :param print_kwargs: dict, print_api kwargs.
92
94
  :return: tuple(bool - True if certificate is installed and False if not, list of certificates found)
93
95
  """
94
96
 
@@ -102,7 +104,7 @@ def is_certificate_in_store(
102
104
 
103
105
  if certificate:
104
106
  # Make sure the certificate is x509.Certificate object.
105
- certificate_x509 = cryptographyw.convert_object_to_x509(certificate)
107
+ certificate_x509 = cryptographyw.convert_object_to_x509(certificate, print_kwargs=print_kwargs)
106
108
  # Get the certificate thumbprint.
107
109
  provided_thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(certificate_x509)
108
110
  provided_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(certificate_x509)
@@ -117,7 +119,12 @@ def is_certificate_in_store(
117
119
  result_found_list: list = []
118
120
  found: bool = False
119
121
  for cert, encoding, trust in ssl.enum_certificates(store_location):
122
+ # Some certificates in the store can have zero or negative serial number.
123
+ # We will skip them, since they're deprecated by the cryptography library.
120
124
  store_certificate = cryptographyw.convert_object_to_x509(cert)
125
+ if not store_certificate:
126
+ continue
127
+
121
128
  store_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(store_certificate)
122
129
  store_thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(store_certificate)
123
130
 
atomicshop/filesystem.py CHANGED
@@ -288,7 +288,11 @@ def remove_file(file_path: str, **kwargs) -> bool:
288
288
  return False
289
289
 
290
290
 
291
- def remove_directory(directory_path: str, force_readonly: bool = False, print_kwargs: dict = None) -> bool:
291
+ def remove_directory(
292
+ directory_path: str,
293
+ force_readonly: bool = False,
294
+ print_kwargs: dict = None
295
+ ) -> bool:
292
296
  """
293
297
  Remove directory if it exists.
294
298
 
@@ -93,7 +93,10 @@ def import_engines_configs(print_kwargs: dict) -> int:
93
93
  for engine_config_path in engine_config_path_list:
94
94
  # Initialize engine.
95
95
  current_module: initialize_engines.ModuleCategory = initialize_engines.ModuleCategory(config_static.MainConfig.SCRIPT_DIRECTORY)
96
- current_module.fill_engine_fields_from_config(engine_config_path.path, print_kwargs=print_kwargs or {})
96
+ result_code, error = current_module.fill_engine_fields_from_config(engine_config_path.path, print_kwargs=print_kwargs or {})
97
+ if result_code != 0:
98
+ print_api(f"Error reading engine config file: {engine_config_path.path}\n{error}", color='red')
99
+ return result_code
97
100
  result_code, error = current_module.initialize_engine(print_kwargs=print_kwargs or {})
98
101
  if result_code != 0:
99
102
  print_api(f"Error initializing engine from directory: {Path(engine_config_path.path).parent}\n{error}", color='red')
@@ -45,7 +45,7 @@ class ModuleCategory:
45
45
  self,
46
46
  engine_config_file_path: str,
47
47
  print_kwargs: dict = None
48
- ):
48
+ ) -> tuple[int, str]:
49
49
  # Read the configuration file of the engine.
50
50
  configuration_data = tomls.read_toml_file(engine_config_file_path, **(print_kwargs or {}))
51
51
 
@@ -82,7 +82,11 @@ class ModuleCategory:
82
82
 
83
83
  for domain_index, domain_port_string in enumerate(self.domain_list):
84
84
  # Splitting the domain and port
85
- domain, port = domain_port_string.split(':')
85
+ if ':' in domain_port_string:
86
+ domain, port = domain_port_string.split(':')
87
+ else:
88
+ error_string: str = f"No [domain:port] pair found in: {domain_port_string}"
89
+ return 1, error_string
86
90
 
87
91
  self.domain_target_dict[domain] = {'ip': None, 'port': port}
88
92
 
@@ -96,6 +100,8 @@ class ModuleCategory:
96
100
  for subdomain, file_name in self.mtls.items():
97
101
  self.mtls[subdomain] = f'{engine_directory_path}{os.sep}{file_name}'
98
102
 
103
+ return 0, ''
104
+
99
105
  def initialize_engine(
100
106
  self,
101
107
  reference_general: bool = False,
@@ -320,7 +320,7 @@ def mitm_server(config_file_path: str, script_version: str):
320
320
  return 1
321
321
 
322
322
  # Import the configuration file.
323
- result = config_static.load_config(config_file_path)
323
+ result = config_static.load_config(config_file_path, print_kwargs=dict(stdout=False))
324
324
  if result != 0:
325
325
  return result
326
326
 
@@ -392,49 +392,22 @@ def mitm_server(config_file_path: str, script_version: str):
392
392
  # Logging Startup information.
393
393
  startup_output(system_logger, script_version)
394
394
 
395
- print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue')
396
-
397
395
  multiprocess_list: list[multiprocessing.Process] = list()
398
396
  # noinspection PyTypeHints
399
397
  is_ready_multiprocessing_event_list: list[multiprocessing.Event] = list()
400
398
 
401
- # === Initialize DNS module ====================================================================================
402
- if config_static.DNSServer.enable:
403
- is_dns_process_ready: multiprocessing.Event = multiprocessing.Event()
404
- is_ready_multiprocessing_event_list.append(is_dns_process_ready)
405
-
406
- dns_server_kwargs: dict = dict(
407
- listening_address=config_static.DNSServer.listening_address,
408
- log_directory_path=config_static.LogRec.logs_path,
409
- backupCount_log_files_x_days=config_static.LogRec.store_logs_for_x_days,
410
- forwarding_dns_service_ipv4=config_static.DNSServer.forwarding_dns_service_ipv4,
411
- forwarding_dns_service_port=config_static.DNSServer.forwarding_dns_service_port,
412
- resolve_by_engine=(
413
- config_static.DNSServer.resolve_by_engine, config_static.ENGINES_LIST),
414
- resolve_regular_pass_thru=config_static.DNSServer.resolve_regular_pass_thru,
415
- resolve_all_domains_to_ipv4=(
416
- config_static.DNSServer.resolve_all_domains_to_ipv4_enable, config_static.DNSServer.target_ipv4),
417
- offline_mode=config_static.MainConfig.offline,
418
- cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
419
- logging_queue=NETWORK_LOGGER_QUEUE,
420
- logger_name=network_logger_name,
421
- is_ready_multiprocessing=is_dns_process_ready
422
- )
423
-
424
- dns_process = multiprocessing.Process(
425
- target=dns_server.start_dns_server_multiprocessing_worker,
426
- kwargs=dns_server_kwargs,
427
- name="dns_server",
428
- daemon=True
429
- )
430
- dns_process.start()
431
-
432
- multiprocess_list.append(dns_process)
433
-
434
-
435
- # === EOF Initialize DNS module ================================================================================
436
399
  # === Initialize TCP Server ====================================================================================
437
400
  if config_static.TCPServer.enable:
401
+ # Get the default network adapter configuration and set the one from config.
402
+ # We set the virtual IPs in the network adapter here, so the server multiprocessing processes can listen on them.
403
+ setting_result: int = _add_virtual_ips_set_default_dns_gateway(system_logger)
404
+ if setting_result != 0:
405
+ print_api.print_api("Failed to set the default DNS gateway.", error_type=True, color="red",
406
+ logger=system_logger)
407
+ # Wait for the message to be printed and saved to file.
408
+ time.sleep(1)
409
+ return setting_result
410
+
438
411
  # Start statistics CSV Queue listener and the logger.
439
412
  _ = statistics_csv.StatisticsCSVWriter(
440
413
  directory_path=config_static.LogRec.logs_path,
@@ -515,9 +488,11 @@ def mitm_server(config_file_path: str, script_version: str):
515
488
  exceptions_logger_name=EXCEPTIONS_CSV_LOGGER_NAME,
516
489
  exceptions_logger_queue=EXCEPTIONS_CSV_LOGGER_QUEUE,
517
490
  forwarding_dns_service_ipv4_list___only_for_localhost=[config_static.DNSServer.forwarding_dns_service_ipv4],
518
- skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST
491
+ skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST,
492
+ print_kwargs=dict(stdout=False)
519
493
  )
520
494
 
495
+ # noinspection PyTypeHints
521
496
  is_tcp_process_ready: multiprocessing.Event = multiprocessing.Event()
522
497
  is_ready_multiprocessing_event_list.append(is_tcp_process_ready)
523
498
 
@@ -541,25 +516,52 @@ def mitm_server(config_file_path: str, script_version: str):
541
516
  recs_archiver_thread.start()
542
517
 
543
518
  # Check that all the multiprocesses are ready.
544
- for event in is_ready_multiprocessing_event_list:
545
- if not event.wait(timeout=30):
546
- print_api.print_api("One of the processes didn't start in time.", error_type=True, color="red",
547
- logger=system_logger)
548
- # Wait for the message to be printed and saved to file.
549
- time.sleep(1)
550
- return 1
519
+ if not _wait_for_events(is_ready_multiprocessing_event_list, timeout=30, system_logger=system_logger):
520
+ return 1
521
+ # === EOF Initialize TCP Server ====================================================================================
551
522
 
552
- # Get the default network adapter configuration and set the one from config.
553
- dns_setting_result: int = _set_default_dns_gateway(system_logger)
554
- if dns_setting_result != 0:
555
- print_api.print_api("Failed to set the default DNS gateway.", error_type=True, color="red",
556
- logger=system_logger)
557
- # Wait for the message to be printed and saved to file.
558
- time.sleep(1)
559
- return dns_setting_result
523
+ # === Initialize DNS module ========================================================================================
524
+ if config_static.DNSServer.enable:
525
+ # noinspection PyTypeHints
526
+ is_dns_process_ready: multiprocessing.Event = multiprocessing.Event()
527
+
528
+ dns_server_kwargs: dict = dict(
529
+ listening_address=config_static.DNSServer.listening_address,
530
+ log_directory_path=config_static.LogRec.logs_path,
531
+ backupCount_log_files_x_days=config_static.LogRec.store_logs_for_x_days,
532
+ forwarding_dns_service_ipv4=config_static.DNSServer.forwarding_dns_service_ipv4,
533
+ forwarding_dns_service_port=config_static.DNSServer.forwarding_dns_service_port,
534
+ resolve_by_engine=(
535
+ config_static.DNSServer.resolve_by_engine, config_static.ENGINES_LIST),
536
+ resolve_regular_pass_thru=config_static.DNSServer.resolve_regular_pass_thru,
537
+ resolve_all_domains_to_ipv4=(
538
+ config_static.DNSServer.resolve_all_domains_to_ipv4_enable, config_static.DNSServer.target_ipv4),
539
+ offline_mode=config_static.MainConfig.offline,
540
+ cache_timeout_minutes=config_static.DNSServer.cache_timeout_minutes,
541
+ logging_queue=NETWORK_LOGGER_QUEUE,
542
+ logger_name=network_logger_name,
543
+ is_ready_multiprocessing=is_dns_process_ready
544
+ )
545
+
546
+ dns_process = multiprocessing.Process(
547
+ target=dns_server.start_dns_server_multiprocessing_worker,
548
+ kwargs=dns_server_kwargs,
549
+ name="dns_server",
550
+ daemon=True
551
+ )
552
+ dns_process.start()
553
+
554
+ multiprocess_list.append(dns_process)
555
+
556
+ # Check that the multiprocess is ready.
557
+ if not _wait_for_events([is_dns_process_ready], timeout=30, system_logger=system_logger):
558
+ return 1
559
+ # === EOF Initialize DNS module ====================================================================================
560
560
 
561
561
  if config_static.DNSServer.enable or config_static.TCPServer.enable:
562
562
  print_api.print_api("The Server is Ready for Operation!", color="green", logger=system_logger)
563
+ print_api.print_api("Press [Ctrl]+[C] to stop.", color='blue', logger=system_logger)
564
+
563
565
  # Get al the queue listener processes (basically this is not necessary, since they're 'daemons', but this is a good practice).
564
566
  multiprocess_list.extend(loggingw.get_listener_processes())
565
567
 
@@ -577,6 +579,7 @@ def mitm_server(config_file_path: str, script_version: str):
577
579
  time.sleep(1)
578
580
 
579
581
 
582
+ # noinspection PyTypeHints
580
583
  def _create_tcp_server_process(
581
584
  socket_wrapper_kwargs: dict,
582
585
  config_file_path: str,
@@ -628,7 +631,33 @@ def _create_tcp_server_process(
628
631
  sys.exit(0)
629
632
 
630
633
 
631
- def _set_default_dns_gateway(system_logger: logging.Logger) -> int:
634
+ # noinspection PyTypeHints
635
+ def _wait_for_events(
636
+ events: list[multiprocessing.Event],
637
+ timeout: int = 30,
638
+ system_logger: logging.Logger = None
639
+ ) -> bool:
640
+ """
641
+ Wait for all events in the list to be set.
642
+
643
+ :param events: List of multiprocessing.Event objects.
644
+ :param timeout: Maximum time to wait for all events to be set.
645
+ :return: True if all events are set, False if timeout occurs.
646
+ """
647
+
648
+ # Check that all the multiprocesses are ready.
649
+ for event in events:
650
+ if not event.wait(timeout=timeout):
651
+ print_api.print_api("One of the processes didn't start in time.", error_type=True, color="red",
652
+ logger=system_logger)
653
+ # Wait for the message to be printed and saved to file.
654
+ time.sleep(1)
655
+ return False
656
+
657
+ return True
658
+
659
+
660
+ def _add_virtual_ips_set_default_dns_gateway(system_logger: logging.Logger) -> int:
632
661
  """
633
662
  The function reads the current DNS gateway setting and sets the new one.
634
663
 
@@ -198,6 +198,7 @@ class CertificateAuthority(object):
198
198
 
199
199
  cert.set_issuer(cert.get_subject())
200
200
  cert.set_pubkey(key)
201
+
201
202
  cert.add_extensions([
202
203
  crypto.X509Extension(b"basicConstraints",
203
204
  True,
@@ -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(certificate):
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,7 +37,8 @@ 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
- :return: certificate in x509 object of 'cryptography' module.
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.
@@ -40,7 +46,7 @@ def convert_object_to_x509(certificate):
40
46
  if not os.path.isfile(certificate):
41
47
  raise FileNotFoundError(f'File not found: {certificate}')
42
48
  # Import the certificate from the path.
43
- certificate = file_io.read_file(certificate, file_mode='rb')
49
+ certificate = file_io.read_file(certificate, file_mode='rb', **(print_kwargs or {}))
44
50
 
45
51
  # Check if 'certificate' is a bytes object and PEM format.
46
52
  # We're checking if it starts with '-----BEGIN ' since the pem certificate can include PRIVATE KEY and will be
@@ -82,14 +88,27 @@ def convert_pem_to_x509_object(certificate: Union[str, bytes]) -> x509.Certifica
82
88
  return x509.load_pem_x509_certificate(certificate)
83
89
 
84
90
 
85
- def convert_der_to_x509_object(certificate: bytes) -> x509.Certificate:
91
+ def convert_der_to_x509_object(certificate: bytes) -> x509.Certificate | None:
86
92
  """Convert DER certificate from socket to x509 object.
87
93
 
88
94
  :param certificate: bytes, certificate to convert.
89
95
  :return: certificate in x509 object of 'cryptography' module.
90
96
  """
91
97
 
92
- return x509.load_der_x509_certificate(certificate)
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
93
112
 
94
113
 
95
114
  def convert_x509_object_to_pem_bytes(certificate) -> bytes:
@@ -8,7 +8,7 @@ from tempfile import gettempdir
8
8
  # noinspection PyPackageRequirements
9
9
  from playwright.sync_api import sync_playwright, Error
10
10
  # Stealth options for playwright. External.
11
- from playwright_stealth import stealth_sync
11
+ # from playwright_stealth import stealth_sync
12
12
 
13
13
  from ...keyboard_press import send_alt_tab
14
14
  from ... import filesystem, print_api
@@ -150,7 +150,7 @@ class PlaywrightEngine:
150
150
  self.page = self.browser.new_page()
151
151
 
152
152
  # Making playwright stealthier with less footprint of automation.
153
- stealth_sync(self.page)
153
+ # stealth_sync(self.page)
154
154
 
155
155
  def close_browser(self) -> None:
156
156
  self.page.close()
@@ -1,4 +1,4 @@
1
- import random
1
+ from secrets import randbits
2
2
  from OpenSSL import crypto
3
3
 
4
4
  from .. import certificates
@@ -121,7 +121,14 @@ def generate_server_certificate_empty(
121
121
  """
122
122
 
123
123
  cert = crypto.X509()
124
- cert.set_serial_number(random.randint(0, 2 ** 64 - 1))
124
+
125
+ # RFC 5280: serial must be positive and non-zero
126
+ # 1 … 2⁶⁴-1
127
+ cert.set_serial_number(randbits(64))
128
+ current_serial = cert.get_serial_number()
129
+ if current_serial <= 0:
130
+ raise RuntimeError(f"Refusing to create a certificate with non-positive serial: {str(current_serial)}")
131
+
125
132
  cert.get_subject().CN = certname
126
133
 
127
134
  cert.set_version(2)
@@ -393,29 +393,37 @@ class DnsServer:
393
393
  is_ready_multiprocessing.set()
394
394
 
395
395
  while True:
396
+ forward_to_tcp_server = False # reset every request
397
+
396
398
  # Needed this logging line when DNS was separate process.
397
399
  # self.logger.info("Waiting to receive new requests...")
398
400
 
399
- # noinspection PyBroadException
400
401
  try:
401
402
  client_data, client_address = main_socket_object.recvfrom(self.buffer_size_receive)
402
403
  client_data: bytes
403
404
  client_address: tuple
404
405
  except ConnectionResetError:
406
+ client_address = (str(), int())
405
407
  traceback_string = tracebacks.get_as_string(one_line=True)
406
408
  # This error happens when the client closes the connection before the server.
407
409
  # This is not an error for a DNS Server, but we'll log it anyway only with the full DNS logger.
408
410
  message = (f"Error: to receive DNS request, An existing connection was forcibly closed | "
409
411
  f"{traceback_string}")
412
+ # print_api(message, logger=self.logger, logger_method='critical', traceback_string=True)
410
413
  self.dns_statistics_csv_writer.write_error(
411
414
  dns_type='request', client_address=client_address, error_message=message)
412
- pass
413
415
  continue
414
- except Exception:
415
- message = "Unknown Exception: to receive DNS request"
416
- print_api(
417
- message, logger=self.logger, logger_method='critical', traceback_string=True)
418
- pass
416
+ except KeyboardInterrupt:
417
+ # message = "KeyboardInterrupt: Stopping DNS Server..."
418
+ # print_api(message, logger=self.logger, logger_method='info')
419
+ # self.logger.info(message)
420
+ # Stop the server
421
+ break
422
+ except Exception as e:
423
+ message = f"Unknown Exception to receive DNS request: {str(e)}"
424
+ print_api(message, logger=self.logger, logger_method='critical', traceback_string=True)
425
+ self.dns_statistics_csv_writer.write_error(
426
+ dns_type='request', client_address=client_address, error_message=message)
419
427
  continue
420
428
 
421
429
  # noinspection PyBroadException
@@ -1,5 +1,6 @@
1
1
  import ssl
2
2
  from dataclasses import dataclass
3
+ from typing import Callable, Any
3
4
 
4
5
  from ..loggingw import loggingw
5
6
  from ...domains import get_domain_without_first_subdomain_if_no_subdomain_return_as_is
@@ -31,7 +32,7 @@ class SNISetup:
31
32
  default_server_certificate_name: str,
32
33
  default_server_certificate_directory: str,
33
34
  default_certificate_domain_list: list,
34
- sni_custom_callback_function: callable,
35
+ sni_custom_callback_function: Callable[..., Any],
35
36
  sni_use_default_callback_function: bool,
36
37
  sni_use_default_callback_function_extended: bool,
37
38
  sni_add_new_domains_to_default_server_certificate: bool,
@@ -54,7 +55,7 @@ class SNISetup:
54
55
  self.default_server_certificate_name = default_server_certificate_name
55
56
  self.default_server_certificate_directory = default_server_certificate_directory
56
57
  self.default_certificate_domain_list = default_certificate_domain_list
57
- self.sni_custom_callback_function: callable = sni_custom_callback_function
58
+ self.sni_custom_callback_function: Callable[..., Any] = sni_custom_callback_function
58
59
  self.sni_use_default_callback_function: bool = sni_use_default_callback_function
59
60
  self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
60
61
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -259,20 +260,23 @@ class SNIHandler:
259
260
  # If DNS server is enabled we'll get the domain from dns server.
260
261
  if self.domain_from_dns_server:
261
262
  self.sni_received_parameters.destination_name = self.domain_from_dns_server
262
- message = \
263
- f"SNI Handler: No SNI was passed, using domain from DNS Server: {self.domain_from_dns_server}"
263
+ message = (
264
+ f"SNI Passed: False\n"
265
+ f"SNI Handler: No SNI was passed, using domain from DNS Server: {self.domain_from_dns_server}")
264
266
  print_api(message, color="yellow", **(print_kwargs or {}))
265
267
  # If DNS server is disabled, the domain from dns server will be empty.
266
268
  else:
267
- message = f"SNI Handler: No SNI was passed, No domain passed from DNS Server. " \
268
- f"Service name will be 'None'."
269
+ message = (
270
+ f"SNI Passed: False\n"
271
+ f"SNI Handler: No SNI was passed, No domain passed from DNS Server. Service name will be 'None'.")
269
272
  print_api(message, color="yellow", **(print_kwargs or {}))
270
273
 
271
274
  # Setting "server_hostname" as a domain.
272
275
  self.sni_received_parameters.ssl_socket.server_hostname = self.sni_received_parameters.destination_name
273
- message = \
274
- f"SNI Handler: port {self.sni_received_parameters.ssl_socket.getsockname()[1]}: " \
275
- f"Incoming connection for [{self.sni_received_parameters.ssl_socket.server_hostname}]"
276
+ message = (
277
+ f"SNI Passed: True\n"
278
+ f"SNI Handler: port {self.sni_received_parameters.ssl_socket.getsockname()[1]}: "
279
+ f"Incoming connection for [{self.sni_received_parameters.ssl_socket.server_hostname}]")
276
280
  print_api(message, **(print_kwargs or {}))
277
281
  except Exception as exception_object:
278
282
  message = f"SNI Handler: Undocumented exception general settings section: {exception_object}"
@@ -1,9 +1,11 @@
1
1
  import multiprocessing
2
2
  import threading
3
3
  import select
4
- from typing import Literal, Union
4
+ from typing import Literal, Union, Callable, Any
5
5
  from pathlib import Path
6
6
  import socket
7
+ import shutil
8
+ import os
7
9
 
8
10
  from ...mitm import initialize_engines
9
11
  from ..psutilw import psutil_networks
@@ -47,7 +49,7 @@ class SocketWrapper:
47
49
  default_server_certificate_name: str = None,
48
50
  default_certificate_domain_list: list = None,
49
51
  default_server_certificate_directory: str = None,
50
- sni_custom_callback_function: callable = None,
52
+ sni_custom_callback_function: Callable[..., Any] = None,
51
53
  sni_use_default_callback_function: bool = False,
52
54
  sni_use_default_callback_function_extended: bool = False,
53
55
  sni_add_new_domains_to_default_server_certificate: bool = False,
@@ -76,9 +78,7 @@ class SocketWrapper:
76
78
  statistics_logger_queue: multiprocessing.Queue = None,
77
79
  exceptions_logger_name: str = 'SocketWrapperExceptions',
78
80
  exceptions_logger_queue: multiprocessing.Queue = None,
79
- no_engine_usage_enable: bool = False,
80
- no_engines_listening_address_list: list[str] = None,
81
- engines_list: list[initialize_engines.ModuleCategory] = None
81
+ print_kwargs: dict = None,
82
82
  ):
83
83
  """
84
84
  Socket Wrapper class that will be used to create sockets, listen on them, accept connections and send them to
@@ -171,22 +171,7 @@ class SocketWrapper:
171
171
  :param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
172
172
  :param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
173
173
  multiprocessing. You need to start the logger listener in the main process to handle the queue.
174
- :param no_engine_usage_enable: boolean, if True, 'engines_list' will be used to listen on the addresses,
175
- but the "no_engines_listening_address_list" parameter will be used instead.
176
- :param no_engines_listening_address_list: list, of ips+ports that will be listened on.
177
- Example: ['0.0.0.0:443', '0.0.0.0:80']
178
- :param engines_list: list, of engines that will be used to process the requests. Structure of engine_config.toml:
179
- [engine]
180
- "domains" = ["example.com"]
181
-
182
- [mtls]
183
- # "subdomain.domain.com" = "file_name_in_current_dir.pem"
184
-
185
- [no_sni]
186
- #get_from_dns = 1 # Blocking, the accept function will wait until the domain is received from DNS.
187
- #get_from_engine = 0
188
- #try_to_get_from_dns_on_empty_get_from_engine = 0 # Non-blocking, on empty DNS server queue, accept() will connect to the domain from below.
189
- #"domain" = "example.com"
174
+ :param print_kwargs: dict, additional arguments to pass to the print function.
190
175
  """
191
176
 
192
177
  self.ip_address: str = ip_address
@@ -202,7 +187,7 @@ class SocketWrapper:
202
187
  self.default_server_certificate_name: str = default_server_certificate_name
203
188
  self.default_certificate_domain_list: list = default_certificate_domain_list
204
189
  self.default_server_certificate_directory: str = default_server_certificate_directory
205
- self.sni_custom_callback_function: callable = sni_custom_callback_function
190
+ self.sni_custom_callback_function: Callable[..., Any] = sni_custom_callback_function
206
191
  self.sni_use_default_callback_function: bool = sni_use_default_callback_function
207
192
  self.sni_use_default_callback_function_extended: bool = sni_use_default_callback_function_extended
208
193
  self.sni_add_new_domains_to_default_server_certificate: bool = sni_add_new_domains_to_default_server_certificate
@@ -221,9 +206,7 @@ class SocketWrapper:
221
206
  self.ssh_script_to_execute = ssh_script_to_execute
222
207
  self.forwarding_dns_service_ipv4_list___only_for_localhost = (
223
208
  forwarding_dns_service_ipv4_list___only_for_localhost)
224
- # self.no_engine_usage_enable: bool = no_engine_usage_enable
225
- # self.no_engines_listening_address_list: list[str] = no_engines_listening_address_list
226
- # self.engines_list: list[initialize_engines.ModuleCategory] = engines_list
209
+ self.print_kwargs: dict = print_kwargs
227
210
 
228
211
  self.socket_object = None
229
212
 
@@ -313,13 +296,6 @@ class SocketWrapper:
313
296
  "You can't set both [sni_use_default_callback_function = True] and [sni_custom_callback_function]."
314
297
  raise SocketWrapperConfigurationValuesError(message)
315
298
 
316
- # if self.no_engine_usage_enable and not self.no_engines_listening_address_list:
317
- # message = "You set [no_engine_usage_enable = True], but you didn't set [no_engines_listening_address_list]."
318
- # raise SocketWrapperConfigurationValuesError(message)
319
- # elif not self.no_engine_usage_enable and not self.engines_list:
320
- # message = "You set [no_engine_usage_enable = False], but you didn't set [engines_list]."
321
- # raise SocketWrapperConfigurationValuesError(message)
322
-
323
299
  try:
324
300
  booleans.is_only_1_true_in_list(
325
301
  booleans_list_of_tuples=[
@@ -392,6 +368,13 @@ class SocketWrapper:
392
368
  pem_file_path=self.ca_certificate_filepath,
393
369
  crt_file_path=self.ca_certificate_crt_filepath)
394
370
 
371
+ # If someone removed the CA certificate file manually, and now it was created, we also need to
372
+ # clear the cached certificates.
373
+ shutil.rmtree(self.sni_server_certificates_cache_directory)
374
+ os.makedirs(self.sni_server_certificates_cache_directory, exist_ok=True)
375
+ print_api("Removed cached server certificates.", logger=self.logger)
376
+
377
+
395
378
  if self.install_ca_certificate_to_root_store:
396
379
  if not self.ca_certificate_filepath:
397
380
  message = "You set [install_ca_certificate_to_root_store = True],\n" \
@@ -411,7 +394,8 @@ class SocketWrapper:
411
394
  # If there is only one certificate with the same name, check if it is the same certificate.
412
395
  elif is_installed_by_name and len(certificate_list_by_name) == 1:
413
396
  is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
414
- certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True)
397
+ certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
398
+ print_kwargs=self.print_kwargs)
415
399
  # If the certificate is not the same, delete it.
416
400
  if not is_installed_by_file:
417
401
  if not permissions.is_admin():
@@ -427,7 +411,9 @@ class SocketWrapper:
427
411
  if self.install_ca_certificate_to_root_store:
428
412
  # Install CA certificate to the root store if it is not installed.
429
413
  is_installed_by_file, certificate_list_by_file = certificates.is_certificate_in_store(
430
- certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True)
414
+ certificate=self.ca_certificate_filepath, by_cert_thumbprint=True, by_cert_issuer=True,
415
+ print_kwargs=self.print_kwargs
416
+ )
431
417
  if not is_installed_by_file:
432
418
  if not permissions.is_admin():
433
419
  raise SocketWrapperConfigurationValuesError(
@@ -448,7 +434,7 @@ class SocketWrapper:
448
434
 
449
435
  def start_listening_socket(
450
436
  self,
451
- callable_function: callable,
437
+ callable_function: Callable[..., Any],
452
438
  callable_args: tuple = ()
453
439
  ):
454
440
  """
@@ -478,7 +464,7 @@ class SocketWrapper:
478
464
  def listening_socket_loop(
479
465
  self,
480
466
  listening_socket_object: socket.socket,
481
- callable_function: callable,
467
+ callable_function: Callable[..., Any],
482
468
  callable_args=()
483
469
  ):
484
470
  """
@@ -688,7 +674,7 @@ class SocketWrapper:
688
674
 
689
675
 
690
676
  def before_socket_thread_worker(
691
- callable_function: callable,
677
+ callable_function: Callable[..., Any],
692
678
  callable_args: tuple,
693
679
  exceptions_logger: loggingw.ExceptionCsvLogger = None
694
680
  ):
@@ -1,32 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: atomicshop
3
- Version: 3.3.0
3
+ Version: 3.3.2
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
- License: MIT License
7
-
8
- Copyright (c) 2023 Bugsec, Denis Kras
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
6
+ License-Expression: MIT
27
7
  Project-URL: Homepage, https://github.com/BugSec-Official/atomicshop
28
8
  Classifier: Intended Audience :: Developers
29
- Classifier: License :: OSI Approved :: MIT License
30
9
  Classifier: Programming Language :: Python :: 3
31
10
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
11
  Requires-Python: >=3.10
@@ -38,7 +17,7 @@ Requires-Dist: cryptography
38
17
  Requires-Dist: dnslib
39
18
  Requires-Dist: dnspython
40
19
  Requires-Dist: docker
41
- Requires-Dist: flask-socketio
20
+ Requires-Dist: flask_socketio
42
21
  Requires-Dist: google-api-python-client
43
22
  Requires-Dist: google-generativeai
44
23
  Requires-Dist: icmplib
@@ -49,16 +28,16 @@ Requires-Dist: pandas
49
28
  Requires-Dist: paramiko
50
29
  Requires-Dist: pefile
51
30
  Requires-Dist: playwright
52
- Requires-Dist: playwright-stealth ==1.0.6
53
31
  Requires-Dist: protobuf
54
32
  Requires-Dist: psutil
55
- Requires-Dist: py7zr ==0.22.0
33
+ Requires-Dist: py7zr==0.22.0
56
34
  Requires-Dist: pyautogui
57
35
  Requires-Dist: pymongo
58
36
  Requires-Dist: pyopenssl
59
37
  Requires-Dist: python-bidi
60
38
  Requires-Dist: python-docx
61
39
  Requires-Dist: python-magic
40
+ Requires-Dist: pywin32; platform_system == "Windows"
62
41
  Requires-Dist: reportlab
63
42
  Requires-Dist: setuptools
64
43
  Requires-Dist: SoundCard
@@ -66,7 +45,7 @@ Requires-Dist: soundfile
66
45
  Requires-Dist: SpeechRecognition
67
46
  Requires-Dist: tldextract
68
47
  Requires-Dist: websockets
69
- Requires-Dist: pywin32 ; platform_system == "Windows"
48
+ Dynamic: license-file
70
49
 
71
50
  <h1 align="center">Atomic Workshop</h1>
72
51
 
@@ -1,9 +1,9 @@
1
- atomicshop/__init__.py,sha256=s0WHDabOH-FxPC97KYDUGsgwzxunVIi9wt09wSWNIfY,122
1
+ atomicshop/__init__.py,sha256=8Ad5ESRJJbXiqt8GJU6Ro7MSdYdMqA0sQPjFqY8RsUU,122
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
5
5
  atomicshop/appointment_management.py,sha256=BsYH_PClTGLVazcuNjt30--hpXKYjSmHp1R1iQbM4Hc,7330
6
- atomicshop/certificates.py,sha256=MEGj7t3Nt2CHE7yzXrvFTLCOKZG9tJ6Ok5JC2BsFRis,7603
6
+ atomicshop/certificates.py,sha256=c9Z3VQvf7T5OD4sPHRzzfZO1M2gRzyRwpMLiMR06B0w,7942
7
7
  atomicshop/command_line_processing.py,sha256=u5yT9Ger_cu7ni5ID0VFlRbVD46ARHeNC9tRM-_YXrQ,1038
8
8
  atomicshop/config_init.py,sha256=G6_f25zPxyPlht5jJC4ZDrrQiuJfjHiuPd22VBjd9Cg,2683
9
9
  atomicshop/console_output.py,sha256=AOSJjrRryE97PAGtgDL03IBtWSi02aNol8noDnW3k6M,4667
@@ -14,7 +14,7 @@ atomicshop/dns.py,sha256=XB0tijVi1bxWLWKV9hPzpt75jK4SrGbZCV5VJbiiQ74,7185
14
14
  atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
15
15
  atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
16
16
  atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
17
- atomicshop/filesystem.py,sha256=owVG49AtC0-6KCYd3bORwTS44xj4NlfktD70QpIQMrM,68030
17
+ atomicshop/filesystem.py,sha256=tCPRA6SQiJJkFL3BnPCoVhPCkvrkI2C4RyuSKzuxo1w,68060
18
18
  atomicshop/functions.py,sha256=pK8hoCE9z61PtWCxQJsda7YAphrLH1wxU5x-1QJP-sY,499
19
19
  atomicshop/get_process_list.py,sha256=8cxb7gKe9sl4R6H2yMi8J6oe-RkonTvCdKjRFqi-Fs4,6075
20
20
  atomicshop/get_process_name_cmd_dll.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
@@ -91,7 +91,7 @@ atomicshop/archiver/shutils.py,sha256=BomnK7zT-nQXA1z0i2R2aTv8eu88wPx7tf2HtOdbmE
91
91
  atomicshop/archiver/zips.py,sha256=0Z_1MWs7YRiCBVpyaG8llnzRguHSO4R51KDMN3FJZt8,16984
92
92
  atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  atomicshop/basics/ansi_escape_codes.py,sha256=uGVRW01v2O052701sOfAdCaM_GLF_cu_jrssuYiPbzA,3216
94
- atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
94
+ atomicshop/basics/argparse_template.py,sha256=NRz63v2jr4tHcAGvqW1okYsGRt6n0u8FXuMQQT3dHPw,6071
95
95
  atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
96
96
  atomicshop/basics/bytes_arrays.py,sha256=xfFW9CBQyzf0iXNWpx-EfrxtN3-tiKzuczPzOb6cOdU,7190
97
97
  atomicshop/basics/classes.py,sha256=dBSQktjc1GmphpD_qOGYNDl-mQcmwg8OkhSqtXPfHEU,15301
@@ -137,10 +137,10 @@ atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
137
  atomicshop/mitm/config_static.py,sha256=yNlELenRvLvW37dl2zXstts5zLklay9xF7sVu_AytKM,8839
138
138
  atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
139
139
  atomicshop/mitm/connection_thread_worker.py,sha256=50RP7De2t0WlUk4Ywmv6B63GwvtvoJ9mULo9Q3b-zcY,32992
140
- atomicshop/mitm/import_config.py,sha256=gnm_eVkBg3pmLf_iZL_JiMWute2WVLH_AbAb-TCZiHY,18159
141
- atomicshop/mitm/initialize_engines.py,sha256=dO8u2pffixLDN_evZy0yJpnjmTx_J23cLnoHVLj5qbY,10800
140
+ atomicshop/mitm/import_config.py,sha256=J3ZLF28AsKu1h76iRV_sCM52g0oh4dwDGddZl_XcJsU,18351
141
+ atomicshop/mitm/initialize_engines.py,sha256=qzz5jzh_lKC03bI1w5ebngVXo1K-RV3poAyW-nObyqo,11042
142
142
  atomicshop/mitm/message.py,sha256=CDhhm4BTuZE7oNZCjvIZ4BuPOW4MuIzQLOg91hJaxDI,3065
143
- atomicshop/mitm/mitm_main.py,sha256=9NvPM6gUbNjHAj2hY3800kM3nHxSEMgn8MprjV6LoVw,37101
143
+ atomicshop/mitm/mitm_main.py,sha256=lHk66kai66W44y4Zu7z4eWRA2sZOwvuGHRzNjPHy31k,38291
144
144
  atomicshop/mitm/recs_files.py,sha256=tv8XFhYZMkBv4DauvpiAdPgvSo0Bcm1CghnmwO7dx8M,5018
145
145
  atomicshop/mitm/shared_functions.py,sha256=0lzeyINd44sVEfFbahJxQmz6KAMWbYrW5ou3UYfItvw,1777
146
146
  atomicshop/mitm/statistic_analyzer.py,sha256=5_sAYGX2Xunzo_pS2W5WijNCwr_BlGJbbOO462y_wN4,27533
@@ -195,7 +195,7 @@ atomicshop/wrappers/_process_wrapper_curl.py,sha256=XkZZXYl7D0Q6UfdWqy-18AvpU0yV
195
195
  atomicshop/wrappers/_process_wrapper_tar.py,sha256=WUMZFKNrlG4nJP9tWZ51W7BR1j_pIjsjgyAStmWjRGs,655
196
196
  atomicshop/wrappers/astw.py,sha256=VkYfkfyc_PJLIOxByT6L7B8uUmKY6-I8XGZl4t_z828,4239
197
197
  atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK_tJuSgU,22800
198
- atomicshop/wrappers/cryptographyw.py,sha256=LfzTnwvJE03G6WZryOOf43VKhhnyMakzHpn8DPPCoy4,13252
198
+ atomicshop/wrappers/cryptographyw.py,sha256=QEUpDn8vUvMg3ADz6-4oC2kbDNC_woDlw7C0zU7qFVM,14233
199
199
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
200
200
  atomicshop/wrappers/githubw.py,sha256=DrFF_oN-rulPQV1iKgVzZadCjuYuCC5eKAjZp_3YD0g,23476
201
201
  atomicshop/wrappers/msiw.py,sha256=GQLqud72nfex3kvO1bJSruNriCYTYX1_G1gSf1MPkIA,6118
@@ -204,11 +204,11 @@ atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc
204
204
  atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
205
205
  atomicshop/wrappers/pipw.py,sha256=mu4jnHkSaYNfpBiLZKMZxEX_E2LqW5BVthMZkblPB_c,1317
206
206
  atomicshop/wrappers/process_wrapper_pbtk.py,sha256=ycPmBRnv627RWks6N8OhxJQe8Gu3h3Vwj-4HswPOw0k,599
207
- atomicshop/wrappers/pyopensslw.py,sha256=OBWxA6EJ2vU_Qlf4M8m6ilcG3hyYB4yB0EsXUf7NhEU,6804
207
+ atomicshop/wrappers/pyopensslw.py,sha256=pklvXEbi0fHu5n1eRKKHEDLN_nsIqCTXv5Lv0bzReTo,7071
208
208
  atomicshop/wrappers/sysmonw.py,sha256=CdawuWuy_uUi3ALCm6lKP7pSyKeTk1MXyzOaTMbBSO8,5346
209
209
  atomicshop/wrappers/ubuntu_terminal.py,sha256=vGEBznz_EKzjhsFAXzefep3SwdP2toYPxWkagOjME3I,12328
210
210
  atomicshop/wrappers/wslw.py,sha256=2Z7X0j5M2hoRZjbHfm_vqwNXZeptsdkNCdhdcM_S9vo,6998
211
- atomicshop/wrappers/certauthw/certauth.py,sha256=hKedW0DOWlEigSNm8wu4SqHkCQsGJ1tJfH7s4nr3Bk0,12223
211
+ atomicshop/wrappers/certauthw/certauth.py,sha256=qLo593_MLU8VfbhYoNQ3J5BvtZuE71aFQROysEt6_dk,12225
212
212
  atomicshop/wrappers/certauthw/certauthw.py,sha256=4WvhjANI7Kzqrr_nKmtA8Kf7B6rute_5wfP65gwQrjw,8082
213
213
  atomicshop/wrappers/ctyping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
214
214
  atomicshop/wrappers/ctyping/file_details_winapi.py,sha256=dmdnCMwx4GgVEctiUIyniVShAoXmv_bZu_o81C450PY,2810
@@ -284,7 +284,7 @@ atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
284
284
  atomicshop/wrappers/playwrightw/_tryouts.py,sha256=l1BLkFsiIMNlgv7nfZd1XGEvXQkIQkIcg48__9OaC00,4920
285
285
  atomicshop/wrappers/playwrightw/base.py,sha256=WeRpx8otdXuKSr-BjY-uCJTze21kbPpfitoOjKQz5-g,9818
286
286
  atomicshop/wrappers/playwrightw/combos.py,sha256=215y7wugyyBrFK9_0WutnMXsF1jqJ2PLm9eHEa2PMz0,7145
287
- atomicshop/wrappers/playwrightw/engine.py,sha256=fapOeaqSMNoxJucTvhzp30cKfKNSVe45drhZJcJ-fOk,15191
287
+ atomicshop/wrappers/playwrightw/engine.py,sha256=sw8TfQBNn-v0kMYDtmaWAA0wXndM57-4TMteWMw0jnA,15195
288
288
  atomicshop/wrappers/playwrightw/infra.py,sha256=ncp62l6CgAgLz4lqBtKEWZO_6ES8cqnbzJBov-tlpBo,97
289
289
  atomicshop/wrappers/playwrightw/javascript.py,sha256=_bW7CAtm0Y8IHYrAalg5HpPFnk6juiqicmkvZ9dITaA,1235
290
290
  atomicshop/wrappers/playwrightw/keyboard.py,sha256=zN3YddGO-qUkn6C0CRVFejP4cTuaUwXLDNFhFREjERY,422
@@ -323,23 +323,23 @@ atomicshop/wrappers/socketw/accepter.py,sha256=4I9ORugRDvwaqSzm_gWSjZnRwQGY8hDTl
323
323
  atomicshop/wrappers/socketw/base.py,sha256=EcosGkD8VzgBY3GeIHDSG29ThQfXwg3-GQPmBTAqTdw,3048
324
324
  atomicshop/wrappers/socketw/certificator.py,sha256=mtWPJ_ew3OSwt0-1W4jaoco1VIY4NRCrMv3mDUxb_Cc,12418
325
325
  atomicshop/wrappers/socketw/creator.py,sha256=LGI4gcgJ47thx6f96rjwjPz3CsTAIv6VxWFY4EyUF2E,13667
326
- atomicshop/wrappers/socketw/dns_server.py,sha256=oGlCZh_z2GwiPH2ZxLGsnlTV64y4IA-Gk6C50wfcqsc,54540
326
+ atomicshop/wrappers/socketw/dns_server.py,sha256=iQym3LmrfUfBwrFmuSHdp_DgeUOueNpEKa_TPYQv0r4,55147
327
327
  atomicshop/wrappers/socketw/exception_wrapper.py,sha256=qW_1CKyPgGlsIt7_jusKkMV4A4hih4bX324u0PLnoO8,7382
328
328
  atomicshop/wrappers/socketw/get_process.py,sha256=aJC-_qFUv3NgWCSUzDI72E4z8_-VTZE9NVZ0CwUoNlM,5698
329
329
  atomicshop/wrappers/socketw/receiver.py,sha256=9B3MvcDqr4C3x2fsnjG5SQognd1wRqsBgikxZa0wXG8,8243
330
330
  atomicshop/wrappers/socketw/sender.py,sha256=aX_K8l_rHjd5AWb8bi5mt8-YTkMYVRDB6DnPqK_XDUE,4754
331
- atomicshop/wrappers/socketw/sni.py,sha256=q-F-R1KtE94g8WGrR3QHgi-otXZJUPBprEwQqnY80_A,17639
331
+ atomicshop/wrappers/socketw/sni.py,sha256=PT50BFOHN5X0ZIyQRHA_Rl4z_lUbkv0em7Chl0viUlw,17819
332
332
  atomicshop/wrappers/socketw/socket_client.py,sha256=McBd3DeCy787oDGCEMUEP2awWy3vdkPqr9w-aFh2fBM,22502
333
333
  atomicshop/wrappers/socketw/socket_server_tester.py,sha256=Qobmh4XV8ZxLUaw-eW4ESKAbeSLecCKn2OWFzMhadk0,6420
334
- atomicshop/wrappers/socketw/socket_wrapper.py,sha256=NRr64RHrKPG049MplW7_eTtpZzJxBc2Nx_XNfuwP_Hk,42197
334
+ atomicshop/wrappers/socketw/socket_wrapper.py,sha256=u_v0pjMMrgsdtI0iPPddiLe2wXnBoqTgNM9Y3zjGD4U,41013
335
335
  atomicshop/wrappers/socketw/ssl_base.py,sha256=62-hPm7zla1rh3m_WvDnXqKH-sDUTdiRptD8STCkgdk,2313
336
336
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=_gA8bMX6Sw_UCXKi2y9wNAwlqifgExgDGfQIa9pFxQA,5543
337
337
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
338
338
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
339
339
  atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
340
- atomicshop-3.3.0.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
- atomicshop-3.3.0.dist-info/METADATA,sha256=rKYvbtY-GzA-SEwXN_mTpfVAQTqtHvbOC3OwCFSrL0c,10602
342
- atomicshop-3.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
343
- atomicshop-3.3.0.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
344
- atomicshop-3.3.0.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
345
- atomicshop-3.3.0.dist-info/RECORD,,
340
+ atomicshop-3.3.2.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
341
+ atomicshop-3.3.2.dist-info/METADATA,sha256=0dnZPwzfNiqMA9qqJbgCwPT4OgNUkV1HWL9ZZhZ5YXY,9288
342
+ atomicshop-3.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
343
+ atomicshop-3.3.2.dist-info/entry_points.txt,sha256=SJEgEP0KoFtfxuGwe5tOzKfXkjR9Dv6YYug33KNYxyY,69
344
+ atomicshop-3.3.2.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
345
+ atomicshop-3.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5