shorless 0.8.6__tar.gz

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.
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: shorless
3
+ Version: 0.8.6
4
+ Summary: Scanner DevSecOps Post-Quantique (PQC) & Générateur CBOM CycloneDX 1.6
5
+ Author-email: Shorless Security <contact@shorless.io>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://shorless.io
8
+ Project-URL: Documentation, https://shorless.io/scanner-documentation.html
9
+ Project-URL: Repository, https://github.com/Shorless/scanner
10
+ Keywords: pqc,post-quantum,cbom,cyclonedx,devsecops,cryptography,ml-kem
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: typer>=0.9.0
14
+ Requires-Dist: rich>=13.0.0
15
+
16
+ # ⚡ Shorless — Post-Quantum (PQC) DevSecOps Scanner & CycloneDX 1.6 CBOM Generator
17
+
18
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.8.6-10B981.svg)](https://pypi.org/project/shorless/)
19
+ [![License](https://img.shields.io/badge/license-Apache%202.0-06B6D4.svg)](https://opensource.org/licenses/Apache-2.0)
20
+ [![CycloneDX 1.6](https://img.shields.io/badge/CBOM-CycloneDX%201.6-A855F7.svg)](https://cyclonedx.org/capabilities/cbom/)
21
+
22
+ **Shorless** (`shorless`) est un scanner statique (AST), d'infrastructure (Nginx, Docker) et réseau (`--check-tls`) Open Source conçu pour aider les équipes DevSecOps de PME et d'ETI à cartographier leur dette cryptographique (RSA, ECDSA, SHA-1, OpenSSL < 3.2, TLS non hybridé) et à préparer leur transition vers la cryptographie post-quantique (**ANSSI** / **NIST FIPS 203 ML-KEM** & **FIPS 204 ML-DSA**).
23
+
24
+ 🔗 **Site officiel & Sandbox interactif :** [https://shorless.io](https://shorless.io)
25
+ 📘 **Documentation complète du scanner :** [https://shorless.io/scanner-documentation.html](https://shorless.io/scanner-documentation.html)
26
+
27
+ ---
28
+
29
+ ## 🆕 Nouveautés de la version v0.8.6 (Support Dockerfile & Inspection TLS Live (--check-tls))
30
+
31
+ - Ajout du scan des Dockerfiles et détection OpenSSL < 3.2
32
+ - Ajout de l'option --check-tls pour auditer un serveur TLS en direct
33
+ - Export unifié Code + Infra + Réseau dans le CBOM CycloneDX 1.6
34
+
35
+ ---
36
+
37
+ ## 🚀 Installation & Utilisation Rapide (PyPI / pipx)
38
+
39
+ ```bash
40
+ # 1. Audit complet Code (AST) + Infra (Nginx / Dockerfile) + Export CBOM CycloneDX 1.6
41
+ pipx run shorless . --cbom cbom.json
42
+
43
+ # 2. Audit incluant l'inspection en direct d'un serveur TLS en production
44
+ shorless . --check-tls https://shorless.io --cbom cbom.json
45
+
46
+ # 3. Mode bloqueur CI/CD (renvoie exit code 1 si un risque critique face à Shor est trouvé)
47
+ shorless . --cbom cbom.json --ci
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 📄 Licence
53
+
54
+ Distribué sous licence **Apache 2.0**. © 2026 Shorless Security.
@@ -0,0 +1,39 @@
1
+ # ⚡ Shorless — Post-Quantum (PQC) DevSecOps Scanner & CycloneDX 1.6 CBOM Generator
2
+
3
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.8.6-10B981.svg)](https://pypi.org/project/shorless/)
4
+ [![License](https://img.shields.io/badge/license-Apache%202.0-06B6D4.svg)](https://opensource.org/licenses/Apache-2.0)
5
+ [![CycloneDX 1.6](https://img.shields.io/badge/CBOM-CycloneDX%201.6-A855F7.svg)](https://cyclonedx.org/capabilities/cbom/)
6
+
7
+ **Shorless** (`shorless`) est un scanner statique (AST), d'infrastructure (Nginx, Docker) et réseau (`--check-tls`) Open Source conçu pour aider les équipes DevSecOps de PME et d'ETI à cartographier leur dette cryptographique (RSA, ECDSA, SHA-1, OpenSSL < 3.2, TLS non hybridé) et à préparer leur transition vers la cryptographie post-quantique (**ANSSI** / **NIST FIPS 203 ML-KEM** & **FIPS 204 ML-DSA**).
8
+
9
+ 🔗 **Site officiel & Sandbox interactif :** [https://shorless.io](https://shorless.io)
10
+ 📘 **Documentation complète du scanner :** [https://shorless.io/scanner-documentation.html](https://shorless.io/scanner-documentation.html)
11
+
12
+ ---
13
+
14
+ ## 🆕 Nouveautés de la version v0.8.6 (Support Dockerfile & Inspection TLS Live (--check-tls))
15
+
16
+ - Ajout du scan des Dockerfiles et détection OpenSSL < 3.2
17
+ - Ajout de l'option --check-tls pour auditer un serveur TLS en direct
18
+ - Export unifié Code + Infra + Réseau dans le CBOM CycloneDX 1.6
19
+
20
+ ---
21
+
22
+ ## 🚀 Installation & Utilisation Rapide (PyPI / pipx)
23
+
24
+ ```bash
25
+ # 1. Audit complet Code (AST) + Infra (Nginx / Dockerfile) + Export CBOM CycloneDX 1.6
26
+ pipx run shorless . --cbom cbom.json
27
+
28
+ # 2. Audit incluant l'inspection en direct d'un serveur TLS en production
29
+ shorless . --check-tls https://shorless.io --cbom cbom.json
30
+
31
+ # 3. Mode bloqueur CI/CD (renvoie exit code 1 si un risque critique face à Shor est trouvé)
32
+ shorless . --cbom cbom.json --ci
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 📄 Licence
38
+
39
+ Distribué sous licence **Apache 2.0**. © 2026 Shorless Security.
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "shorless"
7
+ version = "0.8.6"
8
+ description = "Scanner DevSecOps Post-Quantique (PQC) & Générateur CBOM CycloneDX 1.6"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [
13
+ { name = "Shorless Security", email = "contact@shorless.io" }
14
+ ]
15
+ keywords = ["pqc", "post-quantum", "cbom", "cyclonedx", "devsecops", "cryptography", "ml-kem"]
16
+ dependencies = [
17
+ "typer>=0.9.0",
18
+ "rich>=13.0.0"
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://shorless.io"
23
+ Documentation = "https://shorless.io/scanner-documentation.html"
24
+ Repository = "https://github.com/Shorless/scanner"
25
+
26
+ [project.scripts]
27
+ shorless = "shorless_scanner:app"
28
+
29
+ [tool.setuptools]
30
+ py-modules = ["shorless_scanner"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: shorless
3
+ Version: 0.8.6
4
+ Summary: Scanner DevSecOps Post-Quantique (PQC) & Générateur CBOM CycloneDX 1.6
5
+ Author-email: Shorless Security <contact@shorless.io>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://shorless.io
8
+ Project-URL: Documentation, https://shorless.io/scanner-documentation.html
9
+ Project-URL: Repository, https://github.com/Shorless/scanner
10
+ Keywords: pqc,post-quantum,cbom,cyclonedx,devsecops,cryptography,ml-kem
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: typer>=0.9.0
14
+ Requires-Dist: rich>=13.0.0
15
+
16
+ # ⚡ Shorless — Post-Quantum (PQC) DevSecOps Scanner & CycloneDX 1.6 CBOM Generator
17
+
18
+ [![PyPI version](https://img.shields.io/badge/pypi-v0.8.6-10B981.svg)](https://pypi.org/project/shorless/)
19
+ [![License](https://img.shields.io/badge/license-Apache%202.0-06B6D4.svg)](https://opensource.org/licenses/Apache-2.0)
20
+ [![CycloneDX 1.6](https://img.shields.io/badge/CBOM-CycloneDX%201.6-A855F7.svg)](https://cyclonedx.org/capabilities/cbom/)
21
+
22
+ **Shorless** (`shorless`) est un scanner statique (AST), d'infrastructure (Nginx, Docker) et réseau (`--check-tls`) Open Source conçu pour aider les équipes DevSecOps de PME et d'ETI à cartographier leur dette cryptographique (RSA, ECDSA, SHA-1, OpenSSL < 3.2, TLS non hybridé) et à préparer leur transition vers la cryptographie post-quantique (**ANSSI** / **NIST FIPS 203 ML-KEM** & **FIPS 204 ML-DSA**).
23
+
24
+ 🔗 **Site officiel & Sandbox interactif :** [https://shorless.io](https://shorless.io)
25
+ 📘 **Documentation complète du scanner :** [https://shorless.io/scanner-documentation.html](https://shorless.io/scanner-documentation.html)
26
+
27
+ ---
28
+
29
+ ## 🆕 Nouveautés de la version v0.8.6 (Support Dockerfile & Inspection TLS Live (--check-tls))
30
+
31
+ - Ajout du scan des Dockerfiles et détection OpenSSL < 3.2
32
+ - Ajout de l'option --check-tls pour auditer un serveur TLS en direct
33
+ - Export unifié Code + Infra + Réseau dans le CBOM CycloneDX 1.6
34
+
35
+ ---
36
+
37
+ ## 🚀 Installation & Utilisation Rapide (PyPI / pipx)
38
+
39
+ ```bash
40
+ # 1. Audit complet Code (AST) + Infra (Nginx / Dockerfile) + Export CBOM CycloneDX 1.6
41
+ pipx run shorless . --cbom cbom.json
42
+
43
+ # 2. Audit incluant l'inspection en direct d'un serveur TLS en production
44
+ shorless . --check-tls https://shorless.io --cbom cbom.json
45
+
46
+ # 3. Mode bloqueur CI/CD (renvoie exit code 1 si un risque critique face à Shor est trouvé)
47
+ shorless . --cbom cbom.json --ci
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 📄 Licence
53
+
54
+ Distribué sous licence **Apache 2.0**. © 2026 Shorless Security.
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ shorless_scanner.py
4
+ shorless.egg-info/PKG-INFO
5
+ shorless.egg-info/SOURCES.txt
6
+ shorless.egg-info/dependency_links.txt
7
+ shorless.egg-info/entry_points.txt
8
+ shorless.egg-info/requires.txt
9
+ shorless.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shorless = shorless_scanner:app
@@ -0,0 +1,2 @@
1
+ typer>=0.9.0
2
+ rich>=13.0.0
@@ -0,0 +1 @@
1
+ shorless_scanner
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ ================================================================================
5
+ SHORLESS PQC & CBOM SCANNER (shorless_scanner.py)
6
+ ================================================================================
7
+ Scanner DevSecOps Open Source (Apache 2.0) de préparation Post-Quantique :
8
+ 1. Analyse statique AST Python (RSA, ECDSA P-256, SHA-1, MD5).
9
+ 2. Inspection des configurations TLS Nginx (.conf) et courbes hybrides ML-KEM.
10
+ 3. Inspection des Dockerfiles (Dockerfile, Dockerfile.*, *.dockerfile) et versions OpenSSL.
11
+ 4. Inspection en direct des terminaisons TLS distantes (--check-tls https://...).
12
+ 5. Génération native de l'inventaire CBOM au standard OWASP CycloneDX 1.6 (cbom.json).
13
+ ================================================================================
14
+ """
15
+
16
+ import ast
17
+ import json
18
+ import re
19
+ import shutil
20
+ import socket
21
+ import ssl
22
+ import subprocess
23
+ import uuid
24
+ from datetime import datetime, timezone
25
+ from pathlib import Path
26
+ from urllib.parse import urlparse
27
+ import typer
28
+ from rich.console import Console
29
+ from rich.table import Table
30
+ from rich.panel import Panel
31
+
32
+ app = typer.Typer(help="Scanner DevSecOps de préparation Post-Quantique (PQC) & Générateur CBOM CycloneDX 1.6")
33
+ console = Console()
34
+
35
+ # Base de connaissances PQC (Règles NIST FIPS 203/204 & ANSSI)
36
+ PQC_RULES = {
37
+ "generate_private_key": {
38
+ "algo": "RSA / ECC (Asymétrique)",
39
+ "risk": "CRITIQUE",
40
+ "reason": "Vulnérable à l'algorithme de Shor (Harvest Now, Decrypt Later).",
41
+ "remediation": "Migrer vers une approche hybride (ex: X25519 + ML-KEM-768) ou signatures ML-DSA."
42
+ },
43
+ "SECP256R1": {
44
+ "algo": "ECDSA / ECDH (P-256)",
45
+ "risk": "CRITIQUE",
46
+ "reason": "Courbe elliptique classique cassable par un ordinateur quantique cryptographiquement pertinent.",
47
+ "remediation": "Utiliser liboqs / OpenSSL 3.2+ avec un provider OQS pour hybrider la courbe."
48
+ },
49
+ "SECP384R1": {
50
+ "algo": "ECDSA / ECDH (P-384)",
51
+ "risk": "CRITIQUE",
52
+ "reason": "Courbe elliptique classique vulnérable à l'algorithme de Shor.",
53
+ "remediation": "Hybrider l'échange de clés avec ML-KEM-768 ou ML-KEM-1024 (NIST FIPS 203)."
54
+ },
55
+ "SHA1": {
56
+ "algo": "SHA-1 (Hachage)",
57
+ "risk": "ÉLEVÉ",
58
+ "reason": "Obsolète (collisions classiques + affaiblissement par l'algorithme de Grover).",
59
+ "remediation": "Remplacer par SHA-256, SHA-384 ou SHA-3."
60
+ },
61
+ "MD5": {
62
+ "algo": "MD5 (Hachage)",
63
+ "risk": "ÉLEVÉ",
64
+ "reason": "Totalement compromis.",
65
+ "remediation": "Remplacer par SHA-256 ou SHA-3."
66
+ }
67
+ }
68
+
69
+
70
+ class PythonCryptoVisitor(ast.NodeVisitor):
71
+ """Analyse l'arbre syntaxique (AST) du code Python pour détecter les appels cryptographiques."""
72
+ def __init__(self, filepath: str):
73
+ self.filepath = filepath
74
+ self.findings = []
75
+
76
+ def visit_Call(self, node: ast.Call):
77
+ func_name = ""
78
+ if isinstance(node.func, ast.Attribute):
79
+ func_name = node.func.attr
80
+ elif isinstance(node.func, ast.Name):
81
+ func_name = node.func.id
82
+
83
+ if func_name in PQC_RULES:
84
+ rule = PQC_RULES[func_name]
85
+ self.findings.append({
86
+ "file": self.filepath,
87
+ "line": node.lineno,
88
+ "type": "Code Python (AST)",
89
+ "detected": func_name,
90
+ "algo": rule["algo"],
91
+ "risk": rule["risk"],
92
+ "remediation": rule["remediation"]
93
+ })
94
+ self.generic_visit(node)
95
+
96
+
97
+ def scan_nginx_config(filepath: Path) -> list:
98
+ """Scanne les fichiers de configuration Nginx pour vérifier la présence de TLS hybride PQC."""
99
+ findings = []
100
+ content = filepath.read_text(encoding="utf-8", errors="ignore")
101
+ lines = content.splitlines()
102
+
103
+ has_ssl = False
104
+ has_pqc_curves = False
105
+
106
+ for i, line in enumerate(lines, start=1):
107
+ clean_line = line.strip()
108
+ if clean_line.startswith("#"):
109
+ continue
110
+ if "ssl_protocols" in clean_line:
111
+ has_ssl = True
112
+ if "TLSv1.3" not in clean_line:
113
+ findings.append({
114
+ "file": str(filepath),
115
+ "line": i,
116
+ "type": "Infra (Nginx)",
117
+ "detected": clean_line,
118
+ "algo": "TLS < 1.3",
119
+ "risk": "CRITIQUE",
120
+ "remediation": "Activer TLSv1.3 (prérequis obligatoire pour l'échange de clés post-quantique ML-KEM)."
121
+ })
122
+ if "ssl_ecdh_curve" in clean_line:
123
+ if "MLKEM" in clean_line.upper() or "KYBER" in clean_line.upper():
124
+ has_pqc_curves = True
125
+
126
+ if has_ssl and not has_pqc_curves:
127
+ findings.append({
128
+ "file": str(filepath),
129
+ "line": 1,
130
+ "type": "Infra (Nginx)",
131
+ "detected": "Absence de courbe hybride PQC",
132
+ "algo": "Échange de clés ECDHE classique",
133
+ "risk": "MOYEN",
134
+ "remediation": "Ajouter 'ssl_ecdh_curve X25519MLKEM768:X25519;' (Supporté dès OpenSSL 3.2+ / Nginx récent)."
135
+ })
136
+ return findings
137
+
138
+
139
+ def scan_dockerfile(filepath: Path) -> list:
140
+ """Scanne les Dockerfiles pour détecter les images ou versions d'OpenSSL incompatibles avec la PQC."""
141
+ findings = []
142
+ content = filepath.read_text(encoding="utf-8", errors="ignore")
143
+ lines = content.splitlines()
144
+
145
+ legacy_base_patterns = (
146
+ "buster", "stretch", "bullseye", "ubuntu:18.04", "ubuntu:20.04",
147
+ "alpine:3.12", "alpine:3.13", "alpine:3.14", "alpine:3.15", "alpine:3.16", "alpine:3.17", "alpine:3.18"
148
+ )
149
+
150
+ for i, line in enumerate(lines, start=1):
151
+ clean_line = line.strip()
152
+ if not clean_line or clean_line.startswith("#"):
153
+ continue
154
+
155
+ lower_line = clean_line.lower()
156
+
157
+ # 1. Vérification de l'image de base (FROM)
158
+ if lower_line.startswith("from "):
159
+ if any(pat in lower_line for pat in legacy_base_patterns):
160
+ findings.append({
161
+ "file": str(filepath),
162
+ "line": i,
163
+ "type": "Conteneur (Docker)",
164
+ "detected": clean_line,
165
+ "algo": "Image OS Legacy (OpenSSL < 3.2)",
166
+ "risk": "ÉLEVÉ",
167
+ "remediation": "Migrer vers une image de base récente (ex: debian:trixie, ubuntu:24.04, alpine:3.20+) embarquant OpenSSL 3.2+."
168
+ })
169
+
170
+ # 2. Vérification d'installation explicite d'OpenSSL obsolète (1.1.x, 1.0.x, 3.0.x)
171
+ if "openssl" in lower_line and any(ver in lower_line for ver in ("1.1.", "1.0.", "3.0.", "libssl1.1")):
172
+ findings.append({
173
+ "file": str(filepath),
174
+ "line": i,
175
+ "type": "Conteneur (Docker)",
176
+ "detected": clean_line,
177
+ "algo": "OpenSSL Obsolète (Incompatible PQC)",
178
+ "risk": "CRITIQUE",
179
+ "remediation": "Mettre à niveau vers OpenSSL 3.2+ (ou 3.5+) avec oqs-provider pour supporter ML-KEM (FIPS 203) et ML-DSA (FIPS 204)."
180
+ })
181
+
182
+ # 3. Détection de génération de clés RSA/ECDSA en ligne de commande dans le Dockerfile
183
+ if "openssl genrsa" in lower_line or "ssh-keygen -t rsa" in lower_line or "ecparam" in lower_line:
184
+ findings.append({
185
+ "file": str(filepath),
186
+ "line": i,
187
+ "type": "Conteneur (Docker)",
188
+ "detected": clean_line,
189
+ "algo": "Génération de clé RSA/ECC dans l'image",
190
+ "risk": "CRITIQUE",
191
+ "remediation": "Ne pas générer de clés asymétriques classiques dans le build Docker ; utiliser des certificats hybrides injectés au runtime."
192
+ })
193
+
194
+ return findings
195
+
196
+
197
+ def scan_live_tls_endpoint(target_url: str) -> list:
198
+ """Inspecte un endpoint TLS en direct pour vérifier TLS 1.3, le certificat et l'hybridation PQC."""
199
+ findings = []
200
+
201
+ if "://" not in target_url:
202
+ target_url = f"https://{target_url}"
203
+ parsed = urlparse(target_url)
204
+ hostname = parsed.hostname or target_url
205
+ port = parsed.port or 443
206
+ endpoint_label = f"{hostname}:{port}"
207
+
208
+ console.print(f"[cyan]🌐 Inspection TLS en direct sur :[/cyan] [bold white]{endpoint_label}[/bold white]")
209
+
210
+ tls_version = None
211
+ cipher_name = None
212
+ try:
213
+ ctx = ssl.create_default_context()
214
+ with socket.create_connection((hostname, port), timeout=6) as sock:
215
+ with ctx.wrap_socket(sock, server_hostname=hostname) as ssock:
216
+ tls_version = ssock.version()
217
+ cipher_info = ssock.cipher()
218
+ cipher_name = cipher_info[0] if cipher_info else "Inconnu"
219
+ except Exception as e:
220
+ findings.append({
221
+ "file": endpoint_label,
222
+ "line": port,
223
+ "type": "Réseau (TLS Live)",
224
+ "detected": f"Erreur de connexion : {e}",
225
+ "algo": "Endpoint TLS Injoignable / Invalide",
226
+ "risk": "ÉLEVÉ",
227
+ "remediation": "Vérifier l'accessibilité réseau, le pare-feu et la validité de la chaîne de certificats X.509."
228
+ })
229
+ return findings
230
+
231
+ if tls_version != "TLSv1.3":
232
+ findings.append({
233
+ "file": endpoint_label,
234
+ "line": port,
235
+ "type": "Réseau (TLS Live)",
236
+ "detected": f"Protocole négocié : {tls_version} ({cipher_name})",
237
+ "algo": f"{tls_version} (< TLS 1.3)",
238
+ "risk": "CRITIQUE",
239
+ "remediation": "Activer TLSv1.3 sur le reverse-proxy / Load Balancer (requis pour l'échange de clés hybride ML-KEM)."
240
+ })
241
+
242
+ openssl_bin = shutil.which("openssl")
243
+ negotiated_group = ""
244
+ cert_sig_algo = ""
245
+
246
+ if openssl_bin:
247
+ try:
248
+ proc = subprocess.run(
249
+ [openssl_bin, "s_client", "-connect", endpoint_label, "-servername", hostname, "-brief"],
250
+ input="",
251
+ capture_output=True,
252
+ text=True,
253
+ encoding="utf-8",
254
+ errors="ignore",
255
+ timeout=6
256
+ )
257
+ output = (proc.stdout or "") + "\n" + (proc.stderr or "")
258
+ for line in output.splitlines():
259
+ low = line.lower()
260
+ if "Server Temp Key" in line or "Peer Temp Key" in line or "Negotiated TLS1.3 group" in line:
261
+ negotiated_group = line.strip()
262
+ elif "peer signing digest" in low or "signature type" in low:
263
+ cert_sig_algo = line.strip()
264
+ except Exception:
265
+ pass
266
+
267
+ is_pqc_hybrid = any(k in negotiated_group.upper() for k in ("MLKEM", "KYBER"))
268
+ if not is_pqc_hybrid:
269
+ detected_kem = negotiated_group if negotiated_group else f"Handshake {tls_version} ({cipher_name}) sans KEM hybride"
270
+ findings.append({
271
+ "file": endpoint_label,
272
+ "line": port,
273
+ "type": "Réseau (TLS Live)",
274
+ "detected": detected_kem,
275
+ "algo": "Échange de clés TLS non hybridé (ECDHE classique)",
276
+ "risk": "MOYEN",
277
+ "remediation": "Activer la courbe hybride 'X25519MLKEM768:X25519' (OpenSSL 3.2+ / Nginx / Cloudflare) contre Harvest Now, Decrypt Later."
278
+ })
279
+ else:
280
+ console.print(f"[bold green]🔒 Échange de clés hybride Post-Quantique actif sur {endpoint_label} ({negotiated_group})[/bold green]")
281
+
282
+ findings.append({
283
+ "file": endpoint_label,
284
+ "line": port,
285
+ "type": "Certificat (X.509)",
286
+ "detected": cert_sig_algo or f"Certificat leaf sur {cipher_name}",
287
+ "algo": "Certificat X.509 Asymétrique Classique (RSA/ECDSA)",
288
+ "risk": "MOYEN",
289
+ "remediation": "Inventorié dans le CBOM. Planifier la migration PKI vers des certificats hybrides ou ML-DSA (FIPS 204) d'ici 2028-2030."
290
+ })
291
+
292
+ return findings
293
+
294
+
295
+ def generate_cyclonedx_cbom(findings: list, output_path: str):
296
+ """Génère un fichier CBOM (Cryptography Bill of Materials) au format standard CycloneDX 1.6."""
297
+ cbom = {
298
+ "bomFormat": "CycloneDX",
299
+ "specVersion": "1.6",
300
+ "serialNumber": f"urn:uuid:{uuid.uuid4()}",
301
+ "version": 1,
302
+ "metadata": {
303
+ "timestamp": datetime.now(timezone.utc).isoformat(),
304
+ "tools": [{"vendor": "Shorless", "name": "shorless-scanner", "version": "0.8.6"}]
305
+ },
306
+ "components": []
307
+ }
308
+
309
+ for idx, f in enumerate(findings):
310
+ cbom["components"].append({
311
+ "type": "cryptographic-asset",
312
+ "bom-ref": f"crypto-asset-{idx}",
313
+ "name": f["algo"],
314
+ "evidence": {
315
+ "occurrences": [{"location": f["file"], "line": f["line"]}]
316
+ },
317
+ "cryptoProperties": {
318
+ "assetType": "algorithm",
319
+ "quantumSafe": "false",
320
+ "remediation": f["remediation"]
321
+ }
322
+ })
323
+
324
+ Path(output_path).write_text(json.dumps(cbom, indent=2, ensure_ascii=False), encoding="utf-8")
325
+
326
+
327
+ @app.command()
328
+ def scan(
329
+ target_dir: str = typer.Argument(".", help="Dossier du projet à scanner"),
330
+ cbom_out: str = typer.Option("cbom.json", "--cbom", help="Fichier de sortie CycloneDX CBOM"),
331
+ check_tls: str = typer.Option(None, "--check-tls", help="URL ou domaine:port à inspecter en direct (ex: https://shorless.io)"),
332
+ fail_on_critical: bool = typer.Option(False, "--ci", help="Renvoie un code d'erreur (1) pour bloquer la CI/CD")
333
+ ):
334
+ """Lance un audit de vulnérabilité Post-Quantique sur le code, l'infra (Nginx/Docker) et les endpoints TLS."""
335
+ console.print(Panel.fit("[bold cyan]⚡ Shorless PQC-Scanner - Audit DevSecOps Post-Quantique[/bold cyan]"))
336
+
337
+ path = Path(target_dir)
338
+ all_findings = []
339
+
340
+ if path.exists():
341
+ # 1. Scan des fichiers Python (.py)
342
+ for py_file in path.rglob("*.py"):
343
+ if py_file.name in ("shorless_scanner.py", "pqc_scanner.py", "release_shorless.py", "cron_generate_article.py"):
344
+ continue
345
+ try:
346
+ tree = ast.parse(py_file.read_text(encoding="utf-8", errors="ignore"))
347
+ visitor = PythonCryptoVisitor(str(py_file))
348
+ visitor.visit(tree)
349
+ all_findings.extend(visitor.findings)
350
+ except SyntaxError:
351
+ pass
352
+
353
+ # 2. Scan des configurations Nginx (.conf)
354
+ for conf_file in path.rglob("*.conf"):
355
+ all_findings.extend(scan_nginx_config(conf_file))
356
+
357
+ # 3. Scan des Dockerfiles (Dockerfile, Dockerfile.*, *.dockerfile)
358
+ for item in path.rglob("*"):
359
+ if item.is_file() and (
360
+ item.name == "Dockerfile"
361
+ or item.name.startswith("Dockerfile.")
362
+ or item.name.lower().endswith(".dockerfile")
363
+ ):
364
+ all_findings.extend(scan_dockerfile(item))
365
+
366
+ # 4. Scan TLS en direct si --check-tls est fourni
367
+ if check_tls:
368
+ all_findings.extend(scan_live_tls_endpoint(check_tls))
369
+
370
+ # 5. Affichage des résultats dans le terminal
371
+ if not all_findings:
372
+ console.print("[bold green]✅ Aucune vulnérabilité post-quantique détectée ![/bold green]")
373
+ raise typer.Exit(0)
374
+
375
+ table = Table(title=f"Inventaire Cryptographique & Risques Quantiques ({len(all_findings)} alertes)")
376
+ table.add_column("Fichier / Endpoint", style="cyan")
377
+ table.add_column("Source", style="magenta")
378
+ table.add_column("Actif / Algorithme détecté", style="white")
379
+ table.add_column("Risque PQC", style="bold")
380
+ table.add_column("Plan de remédiation (Auto-PR disponible en SaaS)", style="green")
381
+
382
+ critical_count = 0
383
+ for f in all_findings:
384
+ risk_color = "red" if f["risk"] == "CRITIQUE" else "yellow"
385
+ if f["risk"] == "CRITIQUE":
386
+ critical_count += 1
387
+ table.add_row(
388
+ f"{f['file']}:{f['line']}",
389
+ f["type"],
390
+ f["algo"],
391
+ f"[{risk_color}]{f['risk']}[/{risk_color}]",
392
+ f["remediation"]
393
+ )
394
+
395
+ console.print(table)
396
+
397
+ # 6. Export du CBOM CycloneDX 1.6
398
+ generate_cyclonedx_cbom(all_findings, cbom_out)
399
+ console.print(f"\n📄 [bold]CBOM CycloneDX 1.6 généré avec succès :[/bold] [underline]{cbom_out}[/underline]")
400
+
401
+ # 7. Mode bloqueur CI/CD (GitHub Actions / GitLab CI)
402
+ if fail_on_critical and critical_count > 0:
403
+ console.print(f"\n[bold red]❌ Pipeline CI bloquée : {critical_count} vulnérabilité(s) critique(s) face au risque quantique.[/bold red]")
404
+ raise typer.Exit(code=1)
405
+
406
+
407
+ if __name__ == "__main__":
408
+ app()