diff --git a/public/tools/tls-test/results.html b/public/tools/tls-test/results.html index dc4a863..9437ff2 100644 --- a/public/tools/tls-test/results.html +++ b/public/tools/tls-test/results.html @@ -56,6 +56,7 @@ + @@ -399,6 +400,30 @@ + {# -------- テスト履歴 -------- #} +
+
+

過去のテスト結果 (同一対象)

+ {% if history %} + + + + {% for h in history %} + + + + + + + {% endfor %} + +
実行日時ランクスコアリンク
{{ h.finished_at_display or '—' }}{{ h.rank or '—' }}{{ "%.0f"|format(h.score or 0) }}{{ h.id[:8] }}…
+ {% else %} +
[info]過去のテスト結果はありません
+ {% endif %} +
+
+ {# -------- JSON -------- #}
diff --git a/src/nercone_website/tools/tls_test/db.py b/src/nercone_website/tools/tls_test/db.py index 3d514a0..64d2925 100644 --- a/src/nercone_website/tools/tls_test/db.py +++ b/src/nercone_website/tools/tls_test/db.py @@ -58,6 +58,7 @@ class TlsTestDB: ); CREATE INDEX IF NOT EXISTS idx_tests_expires ON tests(expires_at); CREATE INDEX IF NOT EXISTS idx_tests_ip_created ON tests(client_ip, created_at); + CREATE INDEX IF NOT EXISTS idx_tests_target ON tests(target, created_at); CREATE TABLE IF NOT EXISTS test_progress ( test_id TEXT NOT NULL, @@ -185,6 +186,30 @@ class TlsTestDB: finally: conn.close() + def get_history_by_target(self, target: str, exclude_id: str | None = None, limit: int = 10) -> list[dict[str, Any]]: + conn = self._conn() + try: + cur = conn.cursor() + if exclude_id: + cur.execute( + "SELECT id, target, status, created_at, finished_at, rank, score, error_message" + " FROM tests WHERE target = ? AND id != ? AND status = 'done'" + " ORDER BY created_at DESC LIMIT ?", + (target, exclude_id, limit), + ) + else: + cur.execute( + "SELECT id, target, status, created_at, finished_at, rank, score, error_message" + " FROM tests WHERE target = ? AND status = 'done'" + " ORDER BY created_at DESC LIMIT ?", + (target, limit), + ) + rows = cur.fetchall() + cols = [c[0] for c in cur.description] + return [dict(zip(cols, r)) for r in rows] + finally: + conn.close() + def count_ip_in_window(self, client_ip: str, window_seconds: int) -> int: cutoff = int(time.time()) - window_seconds conn = self._conn() diff --git a/src/nercone_website/tools/tls_test/engine.py b/src/nercone_website/tools/tls_test/engine.py index 37c50e0..4c7e2b1 100644 --- a/src/nercone_website/tools/tls_test/engine.py +++ b/src/nercone_website/tools/tls_test/engine.py @@ -159,8 +159,12 @@ async def _gather(report: ReportProgress, finds: ReportFinding, result: ScanResu # SSL 2/3 → vulnerability findings; also record them as safety findings. if sslv2_supported: await emit(Finding("SSL/TLS Version", "SSL 2.0 supported", "SSLv2 は完全に破綻しています (DROWN)", "serious", 10, group=G_SAF)) + else: + await emit(Finding("SSL/TLS Version", "SSL 2.0 disabled", "", "good", 0, group=G_SAF)) if sslv3_supported: await emit(Finding("SSL/TLS Version", "SSL 3.0 supported", "POODLE 攻撃が可能", "serious", 10, group=G_SAF)) + else: + await emit(Finding("SSL/TLS Version", "SSL 3.0 disabled", "", "good", 0, group=G_SAF)) if version_support.get(C.TLS_1_0): await emit(Finding("SSL/TLS Version", "TLS 1.0 supported", "RFC 8996 で廃止済み", "notgood", 4, group=G_SAF)) else: diff --git a/src/nercone_website/tools/tls_test/views.py b/src/nercone_website/tools/tls_test/views.py index a7d7ef8..4dbd680 100644 --- a/src/nercone_website/tools/tls_test/views.py +++ b/src/nercone_website/tools/tls_test/views.py @@ -80,6 +80,15 @@ def tls_results_context(job: dict, test_id: str, request: Request, tls_test_db) if step not in seen_steps: log_entries += [{"kind": "finding", "finding": f} for f in fs] + target = (result.get("target") or job.get("target") or "").strip() + history = tls_test_db.get_history_by_target(target, exclude_id=test_id, limit=10) if target else [] + for h in history: + try: + ts = h.get("finished_at") or h.get("created_at") or 0 + h["finished_at_display"] = datetime.datetime.fromtimestamp(int(ts)).strftime("%Y-%m-%dT%H:%M:%S") if ts else "" + except Exception: + h["finished_at_display"] = "" + return { "test_id": test_id, "job": job, @@ -90,4 +99,5 @@ def tls_results_context(job: dict, test_id: str, request: Request, tls_test_db) "finished_at_display": finished_at_display, "share_url": str(request.url).split("#", 1)[0], "log_entries": log_entries, + "history": history, }