Skip to content

Commit 1a8aca1

Browse files
committed
feat: betterleaks parser and revamped cryptolyzer parser
1 parent 619da71 commit 1a8aca1

3 files changed

Lines changed: 159 additions & 99 deletions

File tree

backend/application/import_observations/parsers/betterleaks/__init__.py

Whitespace-only changes.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from json import dumps
2+
from typing import Any, Optional
3+
4+
from application.core.models import Branch, Observation, Product
5+
from application.core.types import Severity
6+
from application.import_observations.parsers.base_parser import (
7+
BaseFileParser,
8+
BaseParser,
9+
)
10+
from application.import_observations.types import Parser_Filetype, Parser_Type
11+
12+
13+
class BetterleaksParser(BaseParser, BaseFileParser):
14+
@classmethod
15+
def get_name(cls) -> str:
16+
return "Betterleaks"
17+
18+
@classmethod
19+
def get_filetype(cls) -> str:
20+
return Parser_Filetype.FILETYPE_JSON
21+
22+
@classmethod
23+
def get_type(cls) -> str:
24+
return Parser_Type.TYPE_SECRETS
25+
26+
def check_format(self, data: Any) -> bool:
27+
if not data or not isinstance(data, list) or len(data) == 0:
28+
return False
29+
30+
if data[0].get("RuleID") and data[0].get("Match") and data[0].get("Secret"):
31+
return True
32+
33+
return False
34+
35+
def get_observations( # pylint: disable=too-many-locals
36+
self, data: dict, product: Product, branch: Optional[Branch]
37+
) -> tuple[list[Observation], str]:
38+
observations = []
39+
40+
for entry in data:
41+
rule_id = entry.get("RuleID")
42+
description = entry.get("Description")
43+
start_line = entry.get("StartLine")
44+
end_line = entry.get("EndLine")
45+
match = entry.get("Match")
46+
secret = entry.get("Secret")
47+
file = entry.get("File")
48+
49+
url = entry.get("Attributes", {}).get("url")
50+
commit = entry.get("Attributes", {}).get("git.sha")
51+
date = entry.get("Attributes", {}).get("git.date")
52+
message = entry.get("Attributes", {}).get("git.message")
53+
54+
if match:
55+
if secret:
56+
match = match.replace(secret, "REDACTED")
57+
description += f"\n\n**Match:** `{match}`"
58+
59+
if commit:
60+
description += f"\n\n**Commit hash:** {commit}"
61+
if date:
62+
description += f"\n\n**Commit date:** {date}"
63+
if message:
64+
if message.find("\n") >= 0:
65+
message = message.split("\n")[0] + " ..."
66+
description += f"\n\n**Commit message:** {message}"
67+
68+
observation = Observation(
69+
title=rule_id,
70+
parser_severity=Severity.SEVERITY_MEDIUM,
71+
description=description,
72+
origin_source_file=file,
73+
origin_source_line_start=self.get_int_or_none(start_line),
74+
origin_source_line_end=self.get_int_or_none(end_line),
75+
origin_source_file_link=url,
76+
)
77+
78+
evidence = []
79+
evidence.append("Entry")
80+
81+
evidence_string = dumps(entry)
82+
if secret:
83+
secret = secret.replace("\r", "\\r").replace("\n", "\\n")
84+
evidence_string = evidence_string.replace(secret, "REDACTED")
85+
evidence.append(evidence_string)
86+
87+
observation.unsaved_evidences.append(evidence)
88+
89+
observations.append(observation)
90+
91+
return observations, self.get_name()

backend/application/import_observations/parsers/cryptolyzer/parser.py

Lines changed: 68 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,37 @@
1111

1212
# Recommended cipher suites, curves and signature algorithms according to German BSI as of 2023
1313
TLS12_RECOMMENDED_CIPHERS = [
14-
# "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
15-
# "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
16-
# "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
17-
# "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
18-
# "TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
19-
# "TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
20-
# "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
21-
# "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
22-
# "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
23-
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
24-
# "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
25-
# "TLS_DHE_DSS_WITH_AES_256_CBC_",
26-
# "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
27-
# "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
28-
# "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
29-
# "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
30-
# "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
31-
# "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
32-
# "TLS_DHE_RSA_WITH_AES_128_CCM",
33-
# "TLS_DHE_RSA_WITH_AES_256_CCM",
34-
# "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
35-
# "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
36-
# "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
37-
# "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
38-
# "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
39-
# "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
40-
# "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
41-
# "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
42-
# "TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
43-
# "TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
44-
# "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
45-
# "TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
46-
# "TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
47-
# "TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
48-
# "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
49-
# "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
50-
# "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
51-
# "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
52-
# "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
53-
# "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
54-
# "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
55-
# "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
56-
# "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
57-
# "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
58-
# "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
59-
# "TLS_DHE_PSK_WITH_AES_128_CCM",
60-
# "TLS_DHE_PSK_WITH_AES_256_CCM",
61-
# "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
62-
# "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
63-
# "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
64-
# "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
65-
14+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
15+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
6616
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
67-
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
6817
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
18+
"TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
19+
"TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
20+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
21+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
22+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
6923
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
24+
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
25+
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
26+
"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
27+
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
28+
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
29+
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
30+
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
31+
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
32+
"TLS_DHE_RSA_WITH_AES_128_CCM",
33+
"TLS_DHE_RSA_WITH_AES_256_CCM",
34+
"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
35+
"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
36+
"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
37+
"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
38+
"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
39+
"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
40+
"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
41+
"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
42+
"TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
43+
"TLS_DHE_PSK_WITH_AES_128_CCM",
44+
"TLS_DHE_PSK_WITH_AES_256_CCM",
7045
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
7146
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
7247
]
@@ -79,49 +54,49 @@
7954
]
8055

8156
RECOMMENDED_ELLIPTIC_CURVES = [
82-
# "brainpoolP256r1tls13",
83-
# "brainpoolP384r1tls13",
84-
# "brainpoolP512r1tls13",
85-
# "brainpoolP256r1",
86-
# "brainpoolP384r1",
87-
# "brainpoolP512r1",
57+
"BRAINPOOLP256R1TLS13",
58+
"BRAINPOOLP384R1TLS13",
59+
"BRAINPOOLP512R1TLS13",
60+
"BRAINPOOLP256R1",
61+
"BRAINPOOLP384R1",
62+
"BRAINPOOLP512R1",
8863
"SECP256R1",
8964
"PRIME256V1", # equivalent to secp256r1 according to RFC 4492
9065
"SECP384R1",
9166
"SECP521R1",
92-
# "ffdhe2048",
93-
# "ffdhe3072",
94-
# "ffdhe4096",
67+
"FFDHE3072",
68+
"FFDHE4096",
9569
"X25519_ML_KEM_768",
70+
"X25519_KYBER_768_R3",
9671
"X25519",
97-
9872
]
9973

10074
RECOMMENDED_SIGNATURE_ALGORITHMS = [
101-
"rsa_sha256",
102-
"rsa_sha384",
103-
"rsa_sha512",
104-
"dsa_sha256",
105-
"dsa_sha384",
106-
"dsa_sha512",
107-
"ecdsa_sha256",
108-
"ecdsa_sha384",
109-
"ecdsa_sha512",
110-
"rsa_pss_rsae_sha256",
111-
"rsa_pss_rsae_sha384",
112-
"rsa_pss_rsae_sha512",
113-
"rsa_pss_pss_sha256",
114-
"rsa_pss_pss_sha384",
115-
"rsa_pss_pss_sha512",
116-
"ecdsa_secp256r1_sha256",
117-
"ecdsa_secp384r1_sha384",
118-
"ecdsa_secp521r1_sha512",
119-
"ecdsa_brainpoolP256r1tls13_sha256",
120-
"ecdsa_brainpoolP384r1tls13_sha384",
121-
"ecdsa_brainpoolP512r1tls13_sha512",
75+
"RSA_SHA256",
76+
"RSA_SHA384",
77+
"RSA_SHA512",
78+
"DSA_SHA256",
79+
"DSA_SHA384",
80+
"DSA_SHA512",
81+
"ECDSA_SHA256",
82+
"ECDSA_SHA384",
83+
"ECDSA_SHA512",
84+
"RSA_PSS_RSAE_SHA256",
85+
"RSA_PSS_RSAE_SHA384",
86+
"RSA_PSS_RSAE_SHA512",
87+
"RSA_PSS_PSS_SHA256",
88+
"RSA_PSS_PSS_SHA384",
89+
"RSA_PSS_PSS_SHA512",
90+
"ECDSA_SECP256R1_SHA256",
91+
"ECDSA_SECP384R1_SHA384",
92+
"ECDSA_SECP521R1_SHA512",
93+
"ECDSA_BRAINPOOLP256R1TLS13_SHA256",
94+
"ECDSA_BRAINPOOLP384R1TLS13_SHA384",
95+
"ECDSA_BRAINPOOLP512R1TLS13_SHA512",
12296
]
12397

12498
BSI_LINK = "https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.pdf?__blob=publicationFile&v=5" # noqa: E501 pylint: disable=line-too-long
99+
TLS_REF_LINK = "https://docs.tlsref.org"
125100

126101

127102
class CryptoLyzerParser(BaseParser, BaseFileParser):
@@ -179,7 +154,7 @@ def check_weak_protocols(self, data: dict) -> Optional[Observation]:
179154
versions.remove("tls1_2")
180155
if "tls1_3" in versions:
181156
versions.remove("tls1_3")
182-
description = "**Weak protocols according to BSI recommendations:**\n* " + "\n* ".join(versions)
157+
description = "**Weak protocols:**\n* " + "\n* ".join(versions)
183158

184159
if not versions:
185160
return None
@@ -192,7 +167,7 @@ def check_weak_protocols(self, data: dict) -> Optional[Observation]:
192167
scanner=self.get_name(),
193168
)
194169

195-
observation.unsaved_references = [BSI_LINK]
170+
observation.unsaved_references = [BSI_LINK, TLS_REF_LINK]
196171

197172
evidence = []
198173
evidence.append("Result")
@@ -220,9 +195,7 @@ def check_ciphers(
220195

221196
if unrecommended_cipher_suites:
222197
endpoint_url = self.get_endpoint_url(cipher.get("target", {}))
223-
description = "**Unrecommended cipher suites according to BSI recommendations:**\n* " + "\n* ".join(
224-
unrecommended_cipher_suites
225-
)
198+
description = "**Unrecommended cipher suites:**\n* " + "\n* ".join(unrecommended_cipher_suites)
226199
observation = Observation(
227200
title="Unrecommended " + protocol_name + " cipher suites",
228201
description=description,
@@ -236,7 +209,7 @@ def check_ciphers(
236209
evidence.append(dumps(cipher))
237210
observation.unsaved_evidences.append(evidence)
238211

239-
observation.unsaved_references = [BSI_LINK]
212+
observation.unsaved_references = [BSI_LINK, TLS_REF_LINK]
240213

241214
return observation
242215

@@ -257,9 +230,7 @@ def check_curves(
257230
return None
258231

259232
endpoint_url = self.get_endpoint_url(curves.get("target", {}))
260-
description = "**Unrecommended elliptic curves according to BSI recommendations:**\n* " + "\n* ".join(
261-
unrecommended_curves
262-
)
233+
description = "**Unrecommended elliptic curves:**\n* " + "\n* ".join(unrecommended_curves)
263234
observation = Observation(
264235
title="Unrecommended elliptic curves",
265236
description=description,
@@ -273,7 +244,7 @@ def check_curves(
273244
evidence.append(dumps(curves))
274245
observation.unsaved_evidences.append(evidence)
275246

276-
observation.unsaved_references = [BSI_LINK]
247+
observation.unsaved_references = [BSI_LINK, TLS_REF_LINK]
277248

278249
return observation
279250

@@ -285,16 +256,14 @@ def check_signature_algorithms(
285256
unrecommended_signature_algorithms = []
286257
inner_signature_algorithms = signature_algorithms.get("sig_algos", {})
287258
for inner_signature_algorithm in inner_signature_algorithms:
288-
if inner_signature_algorithm.lower() not in RECOMMENDED_SIGNATURE_ALGORITHMS:
259+
if inner_signature_algorithm.upper() not in RECOMMENDED_SIGNATURE_ALGORITHMS:
289260
unrecommended_signature_algorithms.append(inner_signature_algorithm)
290261

291262
if not unrecommended_signature_algorithms:
292263
return None
293264

294265
endpoint_url = self.get_endpoint_url(signature_algorithms.get("target", {}))
295-
description = "**Unrecommended signature algorithms according to BSI recommendations:**\n* " + "\n* ".join(
296-
unrecommended_signature_algorithms
297-
)
266+
description = "**Unrecommended signature algorithms:**\n* " + "\n* ".join(unrecommended_signature_algorithms)
298267
observation = Observation(
299268
title="Unrecommended signature algorithms",
300269
description=description,
@@ -308,7 +277,7 @@ def check_signature_algorithms(
308277
evidence.append(dumps(signature_algorithms))
309278
observation.unsaved_evidences.append(evidence)
310279

311-
observation.unsaved_references = [BSI_LINK]
280+
observation.unsaved_references = [BSI_LINK, TLS_REF_LINK]
312281

313282
return observation
314283

0 commit comments

Comments
 (0)