atomicshop 2.3.11__py3-none-any.whl → 2.4.1__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 +1 -1
- atomicshop/basics/strings.py +17 -0
- atomicshop/certificates.py +78 -0
- atomicshop/file_io/jsons.py +39 -12
- atomicshop/hashing.py +10 -1
- atomicshop/ip_addresses.py +2 -1
- atomicshop/urls.py +0 -2
- atomicshop/wrappers/cryptographyw.py +97 -1
- atomicshop/wrappers/factw/fact_config.py +2 -0
- atomicshop/wrappers/factw/rest_binary_search.py +29 -0
- atomicshop/wrappers/factw/rest_file_object.py +37 -4
- atomicshop/wrappers/factw/rest_firmware.py +274 -6
- atomicshop/wrappers/factw/rest_router.py +19 -0
- atomicshop/wrappers/factw/rest_statistics.py +25 -0
- atomicshop/wrappers/factw/rest_status.py +1 -1
- {atomicshop-2.3.11.dist-info → atomicshop-2.4.1.dist-info}/METADATA +1 -1
- {atomicshop-2.3.11.dist-info → atomicshop-2.4.1.dist-info}/RECORD +20 -17
- {atomicshop-2.3.11.dist-info → atomicshop-2.4.1.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.3.11.dist-info → atomicshop-2.4.1.dist-info}/WHEEL +0 -0
- {atomicshop-2.3.11.dist-info → atomicshop-2.4.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
atomicshop/basics/strings.py
CHANGED
|
@@ -355,3 +355,20 @@ def check_if_suffix_is_in_string(string: str, suffix: str) -> bool:
|
|
|
355
355
|
"""
|
|
356
356
|
|
|
357
357
|
return string.endswith(suffix)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def convert_string_to_colon_separated(string: str, number_of_characters: int = 2) -> str:
|
|
361
|
+
"""
|
|
362
|
+
Function converts string to colon separated string.
|
|
363
|
+
:param string: string, to convert.
|
|
364
|
+
:param number_of_characters: integer, number of characters to separate.
|
|
365
|
+
|
|
366
|
+
Example:
|
|
367
|
+
convert_string_to_colon_separated('1234567890', 2)
|
|
368
|
+
Result:
|
|
369
|
+
'12:34:56:78:90'
|
|
370
|
+
|
|
371
|
+
:return: string.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
return ':'.join([string[i:i+number_of_characters] for i in range(0, len(string), number_of_characters)])
|
atomicshop/certificates.py
CHANGED
|
@@ -3,7 +3,85 @@ Site for checking OIDs:
|
|
|
3
3
|
https://oidref.com/1.3.6.1.5.5.7.3.1
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
import ssl
|
|
8
|
+
|
|
9
|
+
from .wrappers import cryptographyw
|
|
10
|
+
from .print_api import print_api
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# Valid for 3 years from now
|
|
7
14
|
# Max validity is 39 months:
|
|
8
15
|
# https://casecurity.org/2015/02/19/ssl-certificate-validity-periods-limited-to-39-months-starting-in-april/
|
|
9
16
|
SECONDS_NOT_AFTER_3_YEARS = 3 * 365 * 24 * 60 * 60
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_certificate_in_store(certificate, issuer_only: bool = False, thumbprint_only: bool = False):
|
|
20
|
+
"""
|
|
21
|
+
The function will check if the certificate is installed in the Windows certificate store.
|
|
22
|
+
|
|
23
|
+
:param certificate: x509 object, certificate to check.
|
|
24
|
+
:param issuer_only: bool, if True, will check only by the certificate issuer common name is installed in the store.
|
|
25
|
+
The problem that the issuer common name is not unique, so it can be installed multiple times.
|
|
26
|
+
:param thumbprint_only: bool, if True, will check only by the certificate thumbprint is installed in the store.
|
|
27
|
+
The problem that searching by the thumbprint will not tell you if there are multiple certificates with the same
|
|
28
|
+
issuer name.
|
|
29
|
+
:return: bool, True if certificate is installed, False if not.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Make sure the certificate is x509.Certificate object.
|
|
33
|
+
certificate = cryptographyw.convert_object_to_x509(certificate)
|
|
34
|
+
# Get the certificate thumbprint.
|
|
35
|
+
thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(certificate)
|
|
36
|
+
issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(certificate)
|
|
37
|
+
|
|
38
|
+
# for store in ["CA", "ROOT", "MY"]:
|
|
39
|
+
for cert, encoding, trust in ssl.enum_certificates("ROOT"):
|
|
40
|
+
store_certificate = cryptographyw.convert_object_to_x509(cert)
|
|
41
|
+
store_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(store_certificate)
|
|
42
|
+
store_thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(store_certificate)
|
|
43
|
+
|
|
44
|
+
if issuer_only:
|
|
45
|
+
if store_issuer_common_name == issuer_common_name:
|
|
46
|
+
return True, certificate
|
|
47
|
+
elif thumbprint_only:
|
|
48
|
+
if store_thumbprint == thumbprint:
|
|
49
|
+
return True, certificate
|
|
50
|
+
elif not issuer_only and not thumbprint_only:
|
|
51
|
+
if store_thumbprint == thumbprint and store_issuer_common_name == issuer_common_name:
|
|
52
|
+
return True, certificate
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_certificates_by_issuer_name(issuer_name: str, print_kwargs: dict = None):
|
|
56
|
+
"""
|
|
57
|
+
The function will return all certificates with the specified issuer name.
|
|
58
|
+
|
|
59
|
+
:param issuer_name: string, issuer name to search for.
|
|
60
|
+
:param print_kwargs: dict, that contains all the arguments for 'print_api' function.
|
|
61
|
+
|
|
62
|
+
:return: list, of certificates with the specified issuer name.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
if not print_kwargs:
|
|
66
|
+
print_kwargs = {}
|
|
67
|
+
|
|
68
|
+
certificates_list = []
|
|
69
|
+
|
|
70
|
+
for cert, encoding, trust in ssl.enum_certificates("ROOT"):
|
|
71
|
+
store_certificate = cryptographyw.convert_object_to_x509(cert)
|
|
72
|
+
store_issuer_common_name: str = cryptographyw.get_issuer_common_name_from_x509(store_certificate)
|
|
73
|
+
|
|
74
|
+
if store_issuer_common_name == issuer_name:
|
|
75
|
+
certificates_list.append(store_certificate)
|
|
76
|
+
|
|
77
|
+
if certificates_list:
|
|
78
|
+
for certificate_single in certificates_list:
|
|
79
|
+
issuer_name = cryptographyw.get_issuer_common_name_from_x509(certificate_single)
|
|
80
|
+
thumbprint = cryptographyw.get_sha1_thumbprint_from_x509(certificate_single)
|
|
81
|
+
message = f'Issuer name: {issuer_name} | Thumbprint: {thumbprint}'
|
|
82
|
+
print_api(message, **print_kwargs)
|
|
83
|
+
else:
|
|
84
|
+
message = f'No certificates with issuer name: {issuer_name}'
|
|
85
|
+
print_api(message, **print_kwargs)
|
|
86
|
+
|
|
87
|
+
return certificates_list
|
atomicshop/file_io/jsons.py
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import Union
|
|
2
3
|
|
|
3
4
|
from .file_io import read_file_decorator, write_file_decorator
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
# noinspection PyUnusedLocal
|
|
7
8
|
@read_file_decorator
|
|
8
|
-
def read_json_file(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def read_json_file(
|
|
10
|
+
file_path: str,
|
|
11
|
+
file_mode: str = 'r',
|
|
12
|
+
encoding=None,
|
|
13
|
+
file_object=None,
|
|
14
|
+
**kwargs
|
|
15
|
+
) -> dict:
|
|
13
16
|
"""
|
|
14
17
|
Read the json file and return its content as dictionary.
|
|
15
18
|
|
|
@@ -27,13 +30,15 @@ def read_json_file(file_path: str,
|
|
|
27
30
|
|
|
28
31
|
# noinspection PyUnusedLocal
|
|
29
32
|
@write_file_decorator
|
|
30
|
-
def write_json_file(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
def write_json_file(
|
|
34
|
+
json_content: Union[list, dict, str],
|
|
35
|
+
file_path: str,
|
|
36
|
+
file_mode: str = 'w',
|
|
37
|
+
indent=None,
|
|
38
|
+
use_default_indent=False,
|
|
39
|
+
file_object=None,
|
|
40
|
+
**kwargs
|
|
41
|
+
) -> None:
|
|
37
42
|
"""
|
|
38
43
|
Export list or dict to json file. If indent specified, the content will be beautified by the number of spaces
|
|
39
44
|
specified in 'indent' integer.
|
|
@@ -69,3 +74,25 @@ def write_json_file(json_content,
|
|
|
69
74
|
# If so, write it to file as regular string.
|
|
70
75
|
# Getting the 'file_object' from the 'write_file_decorator'.
|
|
71
76
|
file_object.write(json_content)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def convert_dict_to_json_string(
|
|
80
|
+
dict_or_list: Union[dict, list],
|
|
81
|
+
indent=None,
|
|
82
|
+
use_default_indent=False) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Convert dictionary or list of dictionaries to json formatted string.
|
|
85
|
+
|
|
86
|
+
:param dict_or_list: dictionary or list of dictionaries to convert.
|
|
87
|
+
:param indent: integer number of spaces for indentation.
|
|
88
|
+
If 'ident=0' new lines still will be created. The most compact is 'indent=None' (from documentation)
|
|
89
|
+
So, using default as 'None' and not something else.
|
|
90
|
+
:param use_default_indent: boolean. Default indent for 'json' format in many places is '2'. So, if you don't want
|
|
91
|
+
to set 'indent=2', just set this to 'True'.
|
|
92
|
+
:return: json formatted string.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
if use_default_indent:
|
|
96
|
+
indent = 2
|
|
97
|
+
|
|
98
|
+
return json.dumps(dict_or_list, indent=indent)
|
atomicshop/hashing.py
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import sys
|
|
3
|
+
from typing import Literal, Union
|
|
3
4
|
|
|
4
5
|
from . import web
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
def hash_bytes(bytes_object: bytes, hash_algo: str = 'sha256'):
|
|
8
|
+
# def hash_bytes(bytes_object: bytes, hash_algo: Union[Literal['sha256', 'md5', 'sha1'], str] = 'sha256') -> str:
|
|
9
|
+
def hash_bytes(bytes_object: bytes, hash_algo: str = 'sha256') -> str:
|
|
10
|
+
"""
|
|
11
|
+
The function will return hash of the bytes object with specified algorithm.
|
|
12
|
+
:param bytes_object: bytes object to hash.
|
|
13
|
+
:param hash_algo: string, file hashing algorithm. Default is 'sha256'. Basically the string can be any algorithm
|
|
14
|
+
that hashlib supports. Example: hashlib.sha256(), hashlib.md5(), hashlib.sha1()
|
|
15
|
+
:return: string, hash of the bytes object.
|
|
16
|
+
"""
|
|
8
17
|
# Equivalent to sha256 example: hashlib.sha256(bytes_object).hexdigest()
|
|
9
18
|
return getattr(hashlib, hash_algo)(bytes_object).hexdigest()
|
|
10
19
|
|
atomicshop/ip_addresses.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ipaddress
|
|
2
|
+
from typing import Union, Literal
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
def is_ip_address(string_value: str, ip_type:
|
|
5
|
+
def is_ip_address(string_value: str, ip_type: Union[Literal['ipv4', 'ipv6'], None] = None) -> bool:
|
|
5
6
|
"""
|
|
6
7
|
The function checks if the string is an IPv4 or IPv6 address.
|
|
7
8
|
|
atomicshop/urls.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import os
|
|
3
|
+
|
|
1
4
|
from ..print_api import print_api
|
|
5
|
+
from ..file_io import file_io
|
|
2
6
|
|
|
3
7
|
from cryptography import x509
|
|
8
|
+
from cryptography.x509 import Certificate
|
|
4
9
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
5
10
|
from cryptography.hazmat.primitives import serialization
|
|
6
11
|
from cryptography.hazmat.primitives import hashes
|
|
@@ -17,7 +22,50 @@ OID_TO_BUILDER_CLASS_EXTENSION_NAME: dict = {
|
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
|
|
20
|
-
def
|
|
25
|
+
def convert_object_to_x509(certificate):
|
|
26
|
+
"""Convert certificate to x509 object.
|
|
27
|
+
|
|
28
|
+
:param certificate: any object that can be converted to x509 object.
|
|
29
|
+
Supported types:
|
|
30
|
+
string that is path to file will be imported as bytes object abd converted to x509.Certificate
|
|
31
|
+
After check if it's PEM or DER format.
|
|
32
|
+
string that is PEM certificate will be converted to bytes, then x509.Certificate
|
|
33
|
+
bytes of PEM or DER will be converted to x509.Certificate.
|
|
34
|
+
x509.Certificate will be returned as is.
|
|
35
|
+
:return: certificate in x509 object of 'cryptography' module.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Check if 'certificate' is a string and a path.
|
|
39
|
+
if isinstance(certificate, str) and os.path.isfile(certificate):
|
|
40
|
+
# Import the certificate from the path.
|
|
41
|
+
certificate = file_io.read_file(certificate, file_mode='rb')
|
|
42
|
+
|
|
43
|
+
# Check if 'certificate' is a bytes object and PEM format.
|
|
44
|
+
# We're checking if it starts with '-----BEGIN ' since the pem certificate can include PRIVATE KEY and will be
|
|
45
|
+
# in the beginning of the file.
|
|
46
|
+
if (isinstance(certificate, bytes) and certificate.startswith(b'-----BEGIN ') and
|
|
47
|
+
b'-----BEGIN CERTIFICATE-----' in certificate):
|
|
48
|
+
# Convert the PEM certificate to x509 object.
|
|
49
|
+
certificate = convert_pem_to_x509_object(certificate)
|
|
50
|
+
# Check if 'certificate' is a bytes object and DER format.
|
|
51
|
+
elif isinstance(certificate, bytes) and certificate.startswith(b'\x30'):
|
|
52
|
+
# Convert the DER certificate to x509 object.
|
|
53
|
+
certificate = convert_der_to_x509_object(certificate)
|
|
54
|
+
# Check if 'certificate' is a string object and PEM format.
|
|
55
|
+
elif (isinstance(certificate, str) and certificate.startswith('-----BEGIN ') and
|
|
56
|
+
'-----BEGIN CERTIFICATE-----' in certificate):
|
|
57
|
+
# Convert the PEM certificate to x509 object.
|
|
58
|
+
certificate = convert_pem_to_x509_object(certificate)
|
|
59
|
+
# Check if 'certificate' is a x509 object.
|
|
60
|
+
elif isinstance(certificate, Certificate):
|
|
61
|
+
pass
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError(f'Unsupported certificate type: {type(certificate)}')
|
|
64
|
+
|
|
65
|
+
return certificate
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def convert_pem_to_x509_object(certificate: Union[str, bytes]) -> x509.Certificate:
|
|
21
69
|
"""Convert PEM certificate to x509 object.
|
|
22
70
|
|
|
23
71
|
:param certificate: string or bytes - certificate to convert.
|
|
@@ -51,6 +99,16 @@ def convert_x509_object_to_pem_bytes(certificate) -> bytes:
|
|
|
51
99
|
return certificate.public_bytes(serialization.Encoding.PEM)
|
|
52
100
|
|
|
53
101
|
|
|
102
|
+
def convert_x509_object_to_der_bytes(certificate) -> bytes:
|
|
103
|
+
"""Convert x509 object to DER certificate.
|
|
104
|
+
|
|
105
|
+
:param certificate: certificate in x509 object of 'cryptography' module.
|
|
106
|
+
:return: bytes of certificate in DER byte string format.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
return certificate.public_bytes(serialization.Encoding.DER)
|
|
110
|
+
|
|
111
|
+
|
|
54
112
|
def generate_private_key(public_exponent: int = 65537, bits: int = 2048):
|
|
55
113
|
private_key = rsa.generate_private_key(
|
|
56
114
|
public_exponent=public_exponent,
|
|
@@ -186,3 +244,41 @@ def _get_extensions_properties(certificate):
|
|
|
186
244
|
sub_keys = vars(ext._value)
|
|
187
245
|
|
|
188
246
|
print(f'{ext.oid._name}: {sub_keys}')
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_sha1_thumbprint_from_x509(certificate) -> str:
|
|
250
|
+
"""Get SHA1 thumbprint of the certificate.
|
|
251
|
+
|
|
252
|
+
:param certificate: certificate in x509 object of cryptography module.
|
|
253
|
+
:return: string, SHA1 thumbprint of the certificate.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
return certificate.fingerprint(hashes.SHA1()).hex()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_sha1_thumbprint_from_pem(pem_certificate: bytes) -> str:
|
|
260
|
+
"""Get SHA1 thumbprint of the certificate.
|
|
261
|
+
|
|
262
|
+
:param pem_certificate: bytes of PEM certificate.
|
|
263
|
+
:return: string, SHA1 thumbprint of the certificate.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
# Convert PEM certificate to x509 object.
|
|
267
|
+
certificate = convert_pem_to_x509_object(pem_certificate)
|
|
268
|
+
|
|
269
|
+
return get_sha1_thumbprint_from_x509(certificate)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def get_issuer_common_name_from_x509(certificate) -> str:
|
|
273
|
+
"""Get issuer common name from x509 certificate.
|
|
274
|
+
|
|
275
|
+
:param certificate: certificate in x509 object of cryptography module.
|
|
276
|
+
:return: string, issuer common name.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
issuer = certificate.issuer.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value
|
|
281
|
+
except IndexError:
|
|
282
|
+
issuer = certificate.issuer.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME)[0].value
|
|
283
|
+
|
|
284
|
+
return issuer
|
|
@@ -2,3 +2,5 @@ FACT_ADDRESS: str = 'http://localhost:5000'
|
|
|
2
2
|
FIRMWARE_ENDPOINT: str = '/rest/firmware'
|
|
3
3
|
FILE_OBJECT_ENDPOINT: str = '/rest/file_object'
|
|
4
4
|
STATUS_ENDPOINT: str = '/rest/status'
|
|
5
|
+
STATISTICS_ENDPOINT: str = '/rest/statistics'
|
|
6
|
+
BINARY_SEARCH_ENDPOINT: str = '/rest/binary_search'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# noinspection PyPackageRequirements
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
from . import fact_config
|
|
5
|
+
from ... print_api import print_api
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_string(string_to_search: str):
|
|
9
|
+
"""
|
|
10
|
+
Get the binaries by searching for a string.
|
|
11
|
+
:return:
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
yara_rule = {
|
|
15
|
+
"rule_file": "rule rulename {strings: $a = \"" + string_to_search + "\" condition: $a }"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.BINARY_SEARCH_ENDPOINT}'
|
|
19
|
+
response: requests.Response = requests.get(url, json=yara_rule)
|
|
20
|
+
|
|
21
|
+
# Check response status code.
|
|
22
|
+
if response.status_code == 200:
|
|
23
|
+
# Print response.
|
|
24
|
+
print_api(response.json())
|
|
25
|
+
else:
|
|
26
|
+
# Print error.
|
|
27
|
+
print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
|
|
28
|
+
|
|
29
|
+
return response
|
|
@@ -2,14 +2,34 @@
|
|
|
2
2
|
import requests
|
|
3
3
|
|
|
4
4
|
from . import fact_config, get_file_data
|
|
5
|
-
from ... print_api import print_status_of_list
|
|
5
|
+
from ... print_api import print_status_of_list, print_api
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def get_all_file_objects():
|
|
9
9
|
"""
|
|
10
|
-
|
|
10
|
+
Get all file_object UIDs from the database.
|
|
11
|
+
:return:
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FILE_OBJECT_ENDPOINT}'
|
|
15
|
+
response: requests.Response = requests.get(url)
|
|
16
|
+
|
|
17
|
+
# Check response status code.
|
|
18
|
+
if response.status_code == 200:
|
|
19
|
+
# Print response.
|
|
20
|
+
print_api(response.json())
|
|
21
|
+
else:
|
|
22
|
+
# Print error.
|
|
23
|
+
print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
|
|
24
|
+
|
|
25
|
+
return response
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_uid_data(uid: str):
|
|
29
|
+
"""
|
|
30
|
+
Get file_object data by UID.
|
|
11
31
|
:param uid: string, FACT UID.
|
|
12
|
-
:return:
|
|
32
|
+
:return:
|
|
13
33
|
"""
|
|
14
34
|
|
|
15
35
|
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FILE_OBJECT_ENDPOINT}/{uid}'
|
|
@@ -17,6 +37,19 @@ def is_uid_exist(uid: str):
|
|
|
17
37
|
|
|
18
38
|
# Check response status code.
|
|
19
39
|
if response.status_code == 200:
|
|
40
|
+
return response.json()
|
|
41
|
+
else:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_uid_exist(uid: str):
|
|
46
|
+
"""
|
|
47
|
+
Check if the specified FACT UID exists in the FILE_OBJECT database.
|
|
48
|
+
:param uid: string, FACT UID.
|
|
49
|
+
:return: boolean, True if exists, False if not.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
if get_uid_data(uid):
|
|
20
53
|
return True
|
|
21
54
|
else:
|
|
22
55
|
return False
|
|
@@ -2,11 +2,112 @@
|
|
|
2
2
|
import requests
|
|
3
3
|
import base64
|
|
4
4
|
import time
|
|
5
|
+
from typing import Union
|
|
6
|
+
import os
|
|
5
7
|
|
|
6
8
|
from . import fact_config, get_file_data, rest_file_object
|
|
7
|
-
from ...
|
|
8
|
-
from ...
|
|
9
|
-
from ... import
|
|
9
|
+
from ...print_api import print_api, print_status_of_list
|
|
10
|
+
from ...file_io import file_io, jsons, csvs
|
|
11
|
+
from ...basics import dicts
|
|
12
|
+
from ... import filesystem, ip_addresses
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_uid_list(
|
|
16
|
+
config_data: dict,
|
|
17
|
+
query: Union[dict, str] = None,
|
|
18
|
+
url_parameters: dict = None,
|
|
19
|
+
fetch_uid_data: bool = False
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Get firmware UIDs by query.
|
|
23
|
+
:param config_data: dict, of Parameters to pass for REST API.
|
|
24
|
+
If query is specified, this parameter is ignored.
|
|
25
|
+
:param query: string, query.
|
|
26
|
+
Example:
|
|
27
|
+
{$and: [{"device_name": "test"}, {"device_name": "test222"}]}
|
|
28
|
+
Info:
|
|
29
|
+
Return UIDs of all firmwares with device_name "test" and device_name "test222".
|
|
30
|
+
|
|
31
|
+
Example2:
|
|
32
|
+
{"vendor": "AVM"}
|
|
33
|
+
Info:
|
|
34
|
+
Return UIDs of all firmwares with vendor "AVM".
|
|
35
|
+
|
|
36
|
+
Operators:
|
|
37
|
+
$and: AND operator.
|
|
38
|
+
$or: OR operator.
|
|
39
|
+
$ne: NOT EQUAL operator.
|
|
40
|
+
$like: LIKE operator.
|
|
41
|
+
$in: IN operator.
|
|
42
|
+
<: LESS THAN operator.
|
|
43
|
+
$gt: GREATER THAN operator.
|
|
44
|
+
$exists: EXISTS operator.
|
|
45
|
+
$regex: REGEX operator.
|
|
46
|
+
$contains: CONTAINS operator.
|
|
47
|
+
Basically all the operators that are supported by the MongoDB.
|
|
48
|
+
|
|
49
|
+
:param url_parameters: dict, of URL parameters. Available parameters:
|
|
50
|
+
{
|
|
51
|
+
'limit': int - limit of results,
|
|
52
|
+
'offset': int - offset of results (paging),
|
|
53
|
+
'recursive': boolean - recursive search - only with query,
|
|
54
|
+
'inverted': boolean - inverted search - only with query and recursive
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:param fetch_uid_data: boolean, get data of the UIDs. This can take time, since each UID will be queried against the
|
|
58
|
+
database. Default is False.
|
|
59
|
+
:return: list, list of UIDs.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FIRMWARE_ENDPOINT}'
|
|
63
|
+
|
|
64
|
+
if query is None:
|
|
65
|
+
if 'requested_analysis_systems' in config_data:
|
|
66
|
+
dicts.remove_keys(config_data, ['requested_analysis_systems'])
|
|
67
|
+
|
|
68
|
+
query = config_data
|
|
69
|
+
|
|
70
|
+
if isinstance(query, dict):
|
|
71
|
+
query = jsons.convert_dict_to_json_string(query)
|
|
72
|
+
elif isinstance(query, str):
|
|
73
|
+
pass
|
|
74
|
+
else:
|
|
75
|
+
raise TypeError(f'Query must be dict or string, not {type(query)}')
|
|
76
|
+
|
|
77
|
+
# If there are parameters to add to the URL, add the '?' to the URL.
|
|
78
|
+
if url_parameters or query:
|
|
79
|
+
url = f'{url}?'
|
|
80
|
+
|
|
81
|
+
# Add parameters to the URL.
|
|
82
|
+
if url_parameters and query:
|
|
83
|
+
for key, value in url_parameters.items():
|
|
84
|
+
url = f'{url}{key}={str(value)}&'
|
|
85
|
+
url = f'{url}query={query}'
|
|
86
|
+
if url_parameters and not query:
|
|
87
|
+
for parameter_index, (key, value) in enumerate(url_parameters.items()):
|
|
88
|
+
url = f'{url}{key}={str(value)}'
|
|
89
|
+
if parameter_index < len(url_parameters) - 1:
|
|
90
|
+
url = f'{url}&'
|
|
91
|
+
elif query and not url_parameters:
|
|
92
|
+
url = f'{url}query={query}'
|
|
93
|
+
|
|
94
|
+
response: requests.Response = requests.get(url)
|
|
95
|
+
|
|
96
|
+
uids: list = list()
|
|
97
|
+
# Check response status code.
|
|
98
|
+
if response.status_code == 200:
|
|
99
|
+
uids: list = response.json()['uids']
|
|
100
|
+
# Print response.
|
|
101
|
+
# print_api(response.json())
|
|
102
|
+
print_api(f'Found {len(uids)} UIDs.')
|
|
103
|
+
else:
|
|
104
|
+
# Print error.
|
|
105
|
+
print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
|
|
106
|
+
|
|
107
|
+
if fetch_uid_data:
|
|
108
|
+
return get_uid_list_data(uids)
|
|
109
|
+
else:
|
|
110
|
+
return uids
|
|
10
111
|
|
|
11
112
|
|
|
12
113
|
def is_analysis_finished(uid: str) -> bool:
|
|
@@ -177,11 +278,11 @@ def upload_files(directory_path: str, json_data: dict):
|
|
|
177
278
|
return None
|
|
178
279
|
|
|
179
280
|
|
|
180
|
-
def
|
|
281
|
+
def get_uid_data(uid: str):
|
|
181
282
|
"""
|
|
182
|
-
|
|
283
|
+
Get firmware data by UID.
|
|
183
284
|
:param uid: string, FACT UID.
|
|
184
|
-
:return:
|
|
285
|
+
:return:
|
|
185
286
|
"""
|
|
186
287
|
|
|
187
288
|
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FIRMWARE_ENDPOINT}/{uid}'
|
|
@@ -189,6 +290,35 @@ def is_uid_exist(uid: str):
|
|
|
189
290
|
|
|
190
291
|
# Check response status code.
|
|
191
292
|
if response.status_code == 200:
|
|
293
|
+
return response.json()
|
|
294
|
+
else:
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_uid_list_data(uid_list: list):
|
|
299
|
+
"""
|
|
300
|
+
Get firmware data for each UID in the list.
|
|
301
|
+
:param uid_list: list, list of FACT UIDs.
|
|
302
|
+
:return:
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
uid_data_list: list = list()
|
|
306
|
+
for uid_index, uid in enumerate(uid_list):
|
|
307
|
+
print_status_of_list(
|
|
308
|
+
list_instance=uid_list, prefix_string='Getting UID Data: ', current_state=(uid_index + 1))
|
|
309
|
+
uid_data_list.append(get_uid_data(uid))
|
|
310
|
+
|
|
311
|
+
return uid_data_list
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def is_uid_exist(uid: str):
|
|
315
|
+
"""
|
|
316
|
+
Check if the specified FACT UID exists in the FIRMWARE database.
|
|
317
|
+
:param uid: string, FACT UID.
|
|
318
|
+
:return: boolean, True if exists, False if not.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
if get_uid_data(uid):
|
|
192
322
|
return True
|
|
193
323
|
else:
|
|
194
324
|
return False
|
|
@@ -245,3 +375,141 @@ def is_firmware_exist(directory_path: str, firmwares: list = None) -> list:
|
|
|
245
375
|
raise ValueError(message)
|
|
246
376
|
|
|
247
377
|
return firmwares
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def find_analysis_recursively(uid: str, object_path: str = str()):
|
|
381
|
+
"""
|
|
382
|
+
The function will find the analysis information like cve, OS type, etc. recursively and return it to
|
|
383
|
+
the asker firmware.
|
|
384
|
+
|
|
385
|
+
:param uid: string of the uid of the file_object.
|
|
386
|
+
:param object_path: string of the path of the file_object. Can be empty on the first iteration since it's the same
|
|
387
|
+
UID as the first asker function.
|
|
388
|
+
:return:
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
found_info: dict = dict()
|
|
392
|
+
|
|
393
|
+
# Get the data of the firmware.
|
|
394
|
+
file_data: dict = rest_file_object.get_uid_data(uid)
|
|
395
|
+
|
|
396
|
+
# Add current path to the object path to create a full path of the current object.
|
|
397
|
+
current_path: str = object_path + file_data['file_object']['meta_data']['hid']
|
|
398
|
+
print_api(f"Current File: {current_path}", print_end='\r')
|
|
399
|
+
|
|
400
|
+
cve_lookup_result: dict = file_data['file_object']['analysis']['cve_lookup']['result']
|
|
401
|
+
ip_and_uri_finder_result: dict = file_data['file_object']['analysis']['ip_and_uri_finder']['result']
|
|
402
|
+
software_components_result: dict = file_data['file_object']['analysis']['software_components']['result']
|
|
403
|
+
|
|
404
|
+
if cve_lookup_result and 'skipped' not in cve_lookup_result.keys():
|
|
405
|
+
if cve_lookup_result['cve_results']:
|
|
406
|
+
found_info['cve_lookup'] = file_data['file_object']['analysis']['cve_lookup']['result']
|
|
407
|
+
if ip_and_uri_finder_result and 'skipped' not in ip_and_uri_finder_result.keys():
|
|
408
|
+
if ip_and_uri_finder_result['ips_v4'] or ip_and_uri_finder_result['ips_v6'] or ip_and_uri_finder_result['uris']:
|
|
409
|
+
found_info['ip_and_uri_finder'] = file_data['file_object']['analysis']['ip_and_uri_finder']['result']
|
|
410
|
+
if software_components_result and 'skipped' not in software_components_result.keys():
|
|
411
|
+
found_info['software_components'] = file_data['file_object']['analysis']['software_components']['result']
|
|
412
|
+
|
|
413
|
+
if found_info:
|
|
414
|
+
found_info['path'] = current_path
|
|
415
|
+
found_files: list = [found_info]
|
|
416
|
+
else:
|
|
417
|
+
found_files: list = list()
|
|
418
|
+
|
|
419
|
+
for included_file_uid in file_data['file_object']['meta_data']['included_files']:
|
|
420
|
+
# Get the data of the included file.
|
|
421
|
+
included_found_files = find_analysis_recursively(included_file_uid, (
|
|
422
|
+
object_path + file_data['file_object']['meta_data']['hid']))
|
|
423
|
+
|
|
424
|
+
if included_found_files:
|
|
425
|
+
found_files.extend(included_found_files)
|
|
426
|
+
|
|
427
|
+
return found_files
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def save_firmware_uids_as_csv(
|
|
431
|
+
directory_path: str,
|
|
432
|
+
config_data: dict = None,
|
|
433
|
+
query: Union[dict, str] = None,
|
|
434
|
+
url_parameters: dict = None,
|
|
435
|
+
get_analysis_data: bool = False
|
|
436
|
+
):
|
|
437
|
+
"""
|
|
438
|
+
Save firmware UIDs as CSV file.
|
|
439
|
+
:param directory_path: string, path to save the CSV file.
|
|
440
|
+
:param config_data: check get_uid_list() for more info.
|
|
441
|
+
:param query: check get_uid_list() for more info.
|
|
442
|
+
:param url_parameters: check get_uid_list() for more info.
|
|
443
|
+
:param get_analysis_data: boolean. If 'True', the function will get the analysis data of each file that is found
|
|
444
|
+
in the firmware results. This is needed in order to determine if the firmware is vulnerable to CVEs, OS type,
|
|
445
|
+
etc. Default is 'False'.
|
|
446
|
+
NOTE: This can take a lot of time, since each internal file will be queried against the database.
|
|
447
|
+
|
|
448
|
+
:return:
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
uids: list = get_uid_list(
|
|
452
|
+
config_data=config_data, query=query, url_parameters=url_parameters, fetch_uid_data=True)
|
|
453
|
+
|
|
454
|
+
export_list: list = list()
|
|
455
|
+
for uid_index, uid in enumerate(uids):
|
|
456
|
+
print_status_of_list(
|
|
457
|
+
list_instance=uids, prefix_string='Checking UID for analysis items: ', current_state=(uid_index + 1),
|
|
458
|
+
same_line=False)
|
|
459
|
+
export_entry: dict = dict()
|
|
460
|
+
for key, value in uid['firmware']['meta_data'].items():
|
|
461
|
+
if key == 'included_files' or key == 'total_files_in_firmware':
|
|
462
|
+
continue
|
|
463
|
+
export_entry[key] = value
|
|
464
|
+
|
|
465
|
+
export_entry['mime'] = uid['firmware']['analysis']['file_type']['result']['mime']
|
|
466
|
+
export_entry['sha256'] = uid['firmware']['analysis']['file_hashes']['result']['sha256']
|
|
467
|
+
export_entry['uid'] = uid['request']['uid']
|
|
468
|
+
|
|
469
|
+
# Check for CVEs and other info recursively.
|
|
470
|
+
if get_analysis_data:
|
|
471
|
+
analysis_data_list = find_analysis_recursively(uid['request']['uid'])
|
|
472
|
+
else:
|
|
473
|
+
analysis_data_list = list()
|
|
474
|
+
|
|
475
|
+
export_entry['urls_ips']: list = list()
|
|
476
|
+
export_entry['cves']: list = list()
|
|
477
|
+
export_entry['software']: list = list()
|
|
478
|
+
|
|
479
|
+
for analysis_data in analysis_data_list:
|
|
480
|
+
if 'cve_lookup' in analysis_data:
|
|
481
|
+
for key, value in analysis_data['cve_lookup']['cve_results'].items():
|
|
482
|
+
export_entry['cves'] = [key, jsons.convert_dict_to_json_string(value)]
|
|
483
|
+
if 'software_components' in analysis_data:
|
|
484
|
+
for key, value in analysis_data['software_components'].items():
|
|
485
|
+
export_entry['software'] = [key, jsons.convert_dict_to_json_string(value['meta'])]
|
|
486
|
+
if 'ip_and_uri_finder' in analysis_data:
|
|
487
|
+
for key, value in analysis_data['ip_and_uri_finder'].items():
|
|
488
|
+
if not value:
|
|
489
|
+
continue
|
|
490
|
+
|
|
491
|
+
if key == 'ips_v4':
|
|
492
|
+
for ipv4s in analysis_data['ip_and_uri_finder']['ips_v4']:
|
|
493
|
+
for ip_address in ipv4s:
|
|
494
|
+
if ip_addresses.is_ip_address(ip_address, ip_type='ipv4'):
|
|
495
|
+
if ip_address not in export_entry['urls_ips']:
|
|
496
|
+
export_entry['urls_ips'].append(ip_address)
|
|
497
|
+
elif key == 'ips_v6':
|
|
498
|
+
for ipv6s in analysis_data['ip_and_uri_finder']['ips_v6']:
|
|
499
|
+
for ip_address in ipv6s:
|
|
500
|
+
if ip_addresses.is_ip_address(ip_address, ip_type='ipv6'):
|
|
501
|
+
if ip_address not in export_entry['urls_ips']:
|
|
502
|
+
export_entry['urls_ips'].append(ip_address)
|
|
503
|
+
elif key == 'uris':
|
|
504
|
+
for address in value:
|
|
505
|
+
if address not in export_entry['urls_ips']:
|
|
506
|
+
export_entry['urls_ips'].append(address)
|
|
507
|
+
|
|
508
|
+
export_list.append(export_entry)
|
|
509
|
+
# break
|
|
510
|
+
|
|
511
|
+
# Save UIDs as CSV file.
|
|
512
|
+
file_path = directory_path + os.sep + 'uids.csv'
|
|
513
|
+
csvs.write_list_to_csv(export_list, file_path)
|
|
514
|
+
|
|
515
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .import rest_firmware, rest_statistics, rest_binary_search
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def endpoint_router(config: dict):
|
|
5
|
+
"""
|
|
6
|
+
Route the endpoint.
|
|
7
|
+
:param config: dict, configuration dictionary.
|
|
8
|
+
:return:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
if config['method'] == 'upload_firmware':
|
|
12
|
+
rest_firmware.upload_files(config['firmwares_path'], config['data'])
|
|
13
|
+
elif config['method'] == 'firmware_csv':
|
|
14
|
+
rest_firmware.save_firmware_uids_as_csv(
|
|
15
|
+
directory_path=config['output_path'], config_data=config['data'], get_analysis_data=True)
|
|
16
|
+
elif config['method'] == 'get_statistics':
|
|
17
|
+
rest_statistics.get_statistics()
|
|
18
|
+
elif config['method'] == 'binary_search':
|
|
19
|
+
rest_binary_search.search_string(config['data']['vendor'])
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# noinspection PyPackageRequirements
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
from . import fact_config
|
|
5
|
+
from ... print_api import print_api
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_statistics():
|
|
9
|
+
"""
|
|
10
|
+
Get statistics of the FACT service.
|
|
11
|
+
:return:
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
url: str = f'{fact_config.FACT_ADDRESS}{fact_config.STATISTICS_ENDPOINT}'
|
|
15
|
+
response: requests.Response = requests.get(url)
|
|
16
|
+
|
|
17
|
+
# Check response status code.
|
|
18
|
+
if response.status_code == 200:
|
|
19
|
+
# Print response.
|
|
20
|
+
print_api(response.json())
|
|
21
|
+
else:
|
|
22
|
+
# Print error.
|
|
23
|
+
print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
|
|
24
|
+
|
|
25
|
+
return response
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=i3jQ-IHva0gZoh8TLttLwDYpRvGzWReWzwOG8PZkTyU,122
|
|
2
2
|
atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
|
|
3
3
|
atomicshop/appointment_management.py,sha256=N3wVGJgrqJfsj_lqiRfaL3FxMEe57by5Stzanh189mk,7263
|
|
4
4
|
atomicshop/archiver.py,sha256=E4dgAuh6ARtAWRW6Q0RdnMRMzsE_S1NjMiajHRIVG9s,5537
|
|
5
|
-
atomicshop/certificates.py,sha256=
|
|
5
|
+
atomicshop/certificates.py,sha256=J-cmd6Rpq3zZyzsOH-GcdqIXdg2UwM8_E9mg7XtUph8,3787
|
|
6
6
|
atomicshop/command_line_processing.py,sha256=u5yT9Ger_cu7ni5ID0VFlRbVD46ARHeNC9tRM-_YXrQ,1038
|
|
7
7
|
atomicshop/console_output.py,sha256=G-6jxnWooT1nJSaPxcCqIuw8S22R_0lOJcfrdovRhwE,1372
|
|
8
8
|
atomicshop/console_user_response.py,sha256=31HIy9QGXa7f-GVR8MzJauQ79E_ZqAeagF3Ks4GGdDU,3234
|
|
@@ -14,10 +14,10 @@ atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
|
|
|
14
14
|
atomicshop/filesystem.py,sha256=mS7o_o01HiC12G79z2_txvzM6YLNAWrfNC9IhOJRq6w,23684
|
|
15
15
|
atomicshop/functions.py,sha256=VqLjxAxhaxUr-Ad8P1cw9bZGdZpbtqfCaXQyHf3CM9g,509
|
|
16
16
|
atomicshop/github_wrapper.py,sha256=7pZkhliP4vdcdeVtbgTDEzBS3lUw3-mp5PMWUDA19V0,4347
|
|
17
|
-
atomicshop/hashing.py,sha256=
|
|
17
|
+
atomicshop/hashing.py,sha256=k_HXR7FnPUzLUKk8EiewJ_gLFBlWncZluiBwzplFMWs,3548
|
|
18
18
|
atomicshop/http_parse.py,sha256=nrf2rZcprLqtW8HVrV7TCZ1iTBcWRRy-mXIlAOzcaJs,9703
|
|
19
19
|
atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
|
|
20
|
-
atomicshop/ip_addresses.py,sha256=
|
|
20
|
+
atomicshop/ip_addresses.py,sha256=GBG9YXEqHItmGEgKwqatx28CbWislFI2ZI2xVHGIqO4,759
|
|
21
21
|
atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
|
|
22
22
|
atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
|
|
23
23
|
atomicshop/permissions.py,sha256=CYTDVOI0jh9ks0ZLnnOuPzppgCszFEc9-92DTkVTYi4,522
|
|
@@ -36,7 +36,7 @@ atomicshop/ssh_remote.py,sha256=Sas3nrQv8ardxR51t59xZZsYm8nvUcA7tMSqEDViRLk,1715
|
|
|
36
36
|
atomicshop/sys_functions.py,sha256=MTBxRve5bh58SPvhX3gMiGqHlSBuI_rdNN1NnnBBWqI,906
|
|
37
37
|
atomicshop/tempfiles.py,sha256=uq1ve2WlWehZ3NOTXJnpBBMt6HyCdBufqedF0HyzA6k,2517
|
|
38
38
|
atomicshop/timer.py,sha256=KxBBgVM8po6pUJDW8TgY1UXj0iiDmRmL5XDCq0VHAfU,1670
|
|
39
|
-
atomicshop/urls.py,sha256=
|
|
39
|
+
atomicshop/urls.py,sha256=CQl1j1kjEVDlAuYJqYD9XxPF1SUSgrmG8PjlcXNEKsQ,597
|
|
40
40
|
atomicshop/web.py,sha256=9cxzGhk16PU0daHi-mxSNH4r7LATDh527oN7fZ1dSMk,11003
|
|
41
41
|
atomicshop/addons/PlayWrightCodegen.cmd,sha256=Z5cnllsyXD4F1W2h-WLEnyFkg5nZy0-hTGHRWXVOuW4,173
|
|
42
42
|
atomicshop/addons/ScriptExecution.cmd,sha256=8iC-uHs9MX9qUD_C2M7n9Xw4MZvwOfxT8H5v3hluVps,93
|
|
@@ -68,7 +68,7 @@ atomicshop/basics/lists.py,sha256=ZyTjHyvkta-4_xCG1P-LFMENELmgAYlDdPq4hMRAOR8,25
|
|
|
68
68
|
atomicshop/basics/multiprocesses.py,sha256=GVeyF-r9Q6SAjpJ46eqkZQ4QUli554n-CAIE_W8pij8,958
|
|
69
69
|
atomicshop/basics/numbers.py,sha256=FRjAH1Thk3z3ayxq0Ik6Wh93ELDRk1geyDYTv8amZBE,165
|
|
70
70
|
atomicshop/basics/randoms.py,sha256=DmYLtnIhDK29tAQrGP1Nt-A-v8WC7WIEB8Edi-nk3N4,282
|
|
71
|
-
atomicshop/basics/strings.py,sha256=
|
|
71
|
+
atomicshop/basics/strings.py,sha256=fufYRPe-JUiwm_ENy7bmz-avQ6svAS0yEJ1Tuo3gXVA,13457
|
|
72
72
|
atomicshop/basics/threads.py,sha256=xvgdDJdmgN0wmmARoZ-H7Kvl1GOcEbvgaeGL4M3Hcx8,2819
|
|
73
73
|
atomicshop/basics/timeit_template.py,sha256=fYLrk-X_dhdVtnPU22tarrhhvlggeW6FdKCXM8zkX68,405
|
|
74
74
|
atomicshop/basics/tracebacks.py,sha256=cNfh_oAwF55kSIdqtv3boHZQIoQI8TajxkTnwJwpweI,535
|
|
@@ -78,7 +78,7 @@ atomicshop/etw/etw.py,sha256=xVJNbfCq4KgRfsDnul6CrIdAMl9xRBixZ-hUyqiB2g4,2403
|
|
|
78
78
|
atomicshop/file_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
79
|
atomicshop/file_io/csvs.py,sha256=4R4Kij8FmxNwXFjDtlF_A0flAk0Hj5nZKlEnqC5VxgQ,3125
|
|
80
80
|
atomicshop/file_io/file_io.py,sha256=kaRMSm8sNrnos1gqgAcPVkUXjXgZE-uW_STueqsFZyw,5657
|
|
81
|
-
atomicshop/file_io/jsons.py,sha256=
|
|
81
|
+
atomicshop/file_io/jsons.py,sha256=4xCnC6MfajLouXUFl2aVXUPvftQVf2eS5DgydPZHF_c,4170
|
|
82
82
|
atomicshop/file_io/tomls.py,sha256=T-K4l9FvkSN3eOBoAduPtlFkgW7JqUds6wDMRpZyG6U,1057
|
|
83
83
|
atomicshop/file_io/xlsxs.py,sha256=v_dyg9GD4LqgWi6wA1QuWRZ8zG4ZwB6Dz52ytdcmmmI,2184
|
|
84
84
|
atomicshop/file_io/xmls.py,sha256=sQvTKqUEAoMa9rxCvJJV2EiUE7UoTX6fa1REt-n6MVQ,1650
|
|
@@ -117,7 +117,7 @@ atomicshop/wrappers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
117
117
|
atomicshop/wrappers/_process_wrapper_curl.py,sha256=XkZZXYl7D0Q6UfdWqy-18AvpU0yVp9i2BVD2qRcXlkk,841
|
|
118
118
|
atomicshop/wrappers/_process_wrapper_tar.py,sha256=WUMZFKNrlG4nJP9tWZ51W7BR1j_pIjsjgyAStmWjRGs,655
|
|
119
119
|
atomicshop/wrappers/configparserw.py,sha256=Li2yb3SL8p1GMRRd_r9dNVYuEKGdlQRPIwAHBc5YF1A,17201
|
|
120
|
-
atomicshop/wrappers/cryptographyw.py,sha256=
|
|
120
|
+
atomicshop/wrappers/cryptographyw.py,sha256=H5NaHHDkr97QYhUrHFO9vY218u8k3N3Zgh6bQRnicUE,13140
|
|
121
121
|
atomicshop/wrappers/ffmpegw.py,sha256=YKptcdNQC1wyPLRLvc24fIPr4_rj8IHH01UUzt6XFO0,6039
|
|
122
122
|
atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc,1025
|
|
123
123
|
atomicshop/wrappers/process_wrapper_pbtk.py,sha256=rB3izJqJxs4cv6aNi1mryxrfNqHClH6GywmGSlyI_5Y,688
|
|
@@ -127,11 +127,14 @@ atomicshop/wrappers/certauthw/certauth.py,sha256=hKedW0DOWlEigSNm8wu4SqHkCQsGJ1t
|
|
|
127
127
|
atomicshop/wrappers/certauthw/certauthw.py,sha256=4WvhjANI7Kzqrr_nKmtA8Kf7B6rute_5wfP65gwQrjw,8082
|
|
128
128
|
atomicshop/wrappers/ctyping/process_winapi.py,sha256=QcXL-ETtlSSkoT8F7pYle97ubGWsjYp8cx8HxkVMgAc,2762
|
|
129
129
|
atomicshop/wrappers/factw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
130
|
-
atomicshop/wrappers/factw/fact_config.py,sha256=
|
|
130
|
+
atomicshop/wrappers/factw/fact_config.py,sha256=Cxxg46cAgsquoaJiHLEmGy9GxqXTQhO_53fP32KDQ78,276
|
|
131
131
|
atomicshop/wrappers/factw/get_file_data.py,sha256=ChKC0OjgjFlNubZQBwcGhRO3L2pccc27RLRlAMIUix4,1641
|
|
132
|
-
atomicshop/wrappers/factw/
|
|
133
|
-
atomicshop/wrappers/factw/
|
|
134
|
-
atomicshop/wrappers/factw/
|
|
132
|
+
atomicshop/wrappers/factw/rest_binary_search.py,sha256=k0O-fmBiFBQzCCyTjXdpSDKuegJPDJgZuE3GEmKK448,824
|
|
133
|
+
atomicshop/wrappers/factw/rest_file_object.py,sha256=4hk0XmwE1VVaiVoFTjnrRkmEQJPR3kWGEtj2ueex72Y,3216
|
|
134
|
+
atomicshop/wrappers/factw/rest_firmware.py,sha256=aE3laaM5oZJntqxbf_UPBL1yb_8d1VvicGxybMzOCVY,20383
|
|
135
|
+
atomicshop/wrappers/factw/rest_router.py,sha256=zrBDM66q9Q9BBuY8xFAL35IHmIOM5iPRuWFMn9lQ8QU,745
|
|
136
|
+
atomicshop/wrappers/factw/rest_statistics.py,sha256=0no4yDZ5GSJbb8Hl8ZMyTahvNV1o7tilXJ9ifl7v4j4,651
|
|
137
|
+
atomicshop/wrappers/factw/rest_status.py,sha256=iWpn6RpaQEKDavKTaTTsolCJ-HGzIEeNyQxVyMQRUYQ,639
|
|
135
138
|
atomicshop/wrappers/loggingw/checks.py,sha256=AGFsTsLxHQd1yAraa5popqLaGO9VM0KpcPGuSLn5ptU,719
|
|
136
139
|
atomicshop/wrappers/loggingw/formatters.py,sha256=mUtcJJfmhLNrwUVYShXTmdu40dBaJu4TS8FiuTXI7ys,7189
|
|
137
140
|
atomicshop/wrappers/loggingw/handlers.py,sha256=qm5Fbu8eDmlstMduUe5nKUlJU5IazFkSnQizz8Qt2os,5479
|
|
@@ -165,8 +168,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=VfNthyBvgI5tL9v3Qprh4
|
|
|
165
168
|
atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
|
|
166
169
|
atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
|
|
167
170
|
atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
|
|
168
|
-
atomicshop-2.
|
|
169
|
-
atomicshop-2.
|
|
170
|
-
atomicshop-2.
|
|
171
|
-
atomicshop-2.
|
|
172
|
-
atomicshop-2.
|
|
171
|
+
atomicshop-2.4.1.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
172
|
+
atomicshop-2.4.1.dist-info/METADATA,sha256=O2JGo-QiTkcZ4fi_FinqLRM_zdsGIgbOLUau2-l9JI8,9585
|
|
173
|
+
atomicshop-2.4.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
174
|
+
atomicshop-2.4.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
175
|
+
atomicshop-2.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|