"""Test for missing bsi_compliance_rules table scenario. This test covers the case where a database has the correct schema version but is missing the unified bsi_compliance_rules table (using old schema). """ import sqlite3 import tempfile from pathlib import Path import pytest from sslysze_scan.db.compliance import check_compliance from sslysze_scan.db.writer import write_scan_results def create_legacy_schema_db(db_path: str) -> None: """Create a database with schema version 6 but legacy BSI tables. This simulates the state where crypto_standards.db was copied but the unify_bsi_schema.py migration was not yet executed. """ conn = sqlite3.connect(db_path) cursor = conn.cursor() # Create schema_version table cursor.execute(""" CREATE TABLE schema_version ( version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL ) """) cursor.execute( "INSERT INTO schema_version (version, applied_at) VALUES (6, '2025-01-01')" ) # Create legacy BSI tables cursor.execute(""" CREATE TABLE bsi_tr_02102_2_tls ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, name TEXT NOT NULL, tls_version TEXT, valid_until INTEGER, reference TEXT, notes TEXT ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_4_ssh_kex ( id INTEGER PRIMARY KEY AUTOINCREMENT, key_exchange_method TEXT NOT NULL UNIQUE, spezifikation TEXT, verwendung TEXT, bemerkung TEXT ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_4_ssh_encryption ( id INTEGER PRIMARY KEY AUTOINCREMENT, verschluesselungsverfahren TEXT NOT NULL UNIQUE, spezifikation TEXT, verwendung TEXT, bemerkung TEXT ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_4_ssh_mac ( id INTEGER PRIMARY KEY AUTOINCREMENT, mac_verfahren TEXT NOT NULL UNIQUE, spezifikation TEXT, verwendung TEXT ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_4_ssh_auth ( id INTEGER PRIMARY KEY AUTOINCREMENT, signaturverfahren TEXT NOT NULL UNIQUE, spezifikation TEXT, verwendung TEXT, bemerkung TEXT ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_1_key_requirements ( id INTEGER PRIMARY KEY AUTOINCREMENT, algorithm_type TEXT NOT NULL, usage_context TEXT NOT NULL, min_key_length INTEGER NOT NULL, valid_until INTEGER, notes TEXT, UNIQUE(algorithm_type, usage_context) ) """) cursor.execute(""" CREATE TABLE bsi_tr_02102_1_hash_requirements ( id INTEGER PRIMARY KEY AUTOINCREMENT, algorithm TEXT NOT NULL UNIQUE, min_output_bits INTEGER, deprecated INTEGER DEFAULT 0, notes TEXT ) """) # Create IANA tables cursor.execute(""" CREATE TABLE iana_tls_cipher_suites ( value TEXT PRIMARY KEY, description TEXT NOT NULL, dtls_ok TEXT, recommended TEXT, reference TEXT ) """) cursor.execute(""" CREATE TABLE iana_tls_supported_groups ( value TEXT PRIMARY KEY, description TEXT NOT NULL, dtls_ok TEXT, recommended TEXT, reference TEXT ) """) cursor.execute(""" CREATE TABLE iana_ssh_kex_methods ( value TEXT PRIMARY KEY, description TEXT NOT NULL, recommended TEXT, reference TEXT ) """) cursor.execute(""" CREATE TABLE iana_ssh_encryption_algorithms ( value TEXT PRIMARY KEY, description TEXT NOT NULL, recommended TEXT, reference TEXT ) """) cursor.execute(""" CREATE TABLE iana_ssh_mac_algorithms ( value TEXT PRIMARY KEY, description TEXT NOT NULL, recommended TEXT, reference TEXT ) """) # Create scan tables cursor.execute(""" CREATE TABLE scans ( scan_id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, hostname TEXT NOT NULL, ports TEXT NOT NULL, scan_duration_seconds REAL ) """) cursor.execute(""" CREATE TABLE scanned_hosts ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, fqdn TEXT, ipv4 TEXT, ipv6 TEXT, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_cipher_suites ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, tls_version TEXT NOT NULL, cipher_suite_name TEXT NOT NULL, accepted BOOLEAN NOT NULL, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_supported_groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, group_name TEXT NOT NULL, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_certificates ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, position INTEGER NOT NULL, subject TEXT, issuer TEXT, valid_from TEXT, valid_until TEXT, key_type TEXT, key_bits INTEGER, signature_algorithm TEXT, serial_number TEXT, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_ssh_kex_methods ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, kex_method_name TEXT NOT NULL, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_ssh_encryption_algorithms ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, encryption_algorithm_name TEXT NOT NULL, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_ssh_mac_algorithms ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, mac_algorithm_name TEXT NOT NULL, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_ssh_host_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, host_key_algorithm TEXT NOT NULL, key_bits INTEGER, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) cursor.execute(""" CREATE TABLE scan_compliance_status ( id INTEGER PRIMARY KEY AUTOINCREMENT, scan_id INTEGER NOT NULL, port INTEGER NOT NULL, timestamp TEXT NOT NULL, check_type TEXT NOT NULL, item_name TEXT NOT NULL, iana_value TEXT, iana_recommended TEXT, bsi_approved INTEGER, bsi_valid_until INTEGER, passed INTEGER NOT NULL, severity TEXT, details TEXT, FOREIGN KEY (scan_id) REFERENCES scans(scan_id) ) """) # Add some test data to legacy tables cursor.execute(""" INSERT INTO bsi_tr_02102_2_tls (category, name, tls_version, valid_until) VALUES ('cipher_suite', 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256', '1.2', 2031) """) cursor.execute(""" INSERT INTO bsi_tr_02102_4_ssh_kex (key_exchange_method, verwendung) VALUES ('diffie-hellman-group14-sha256', '2031+') """) cursor.execute(""" INSERT INTO iana_tls_cipher_suites (value, description, recommended) VALUES ('0x13,0x01', 'TLS_AES_128_GCM_SHA256', 'Y') """) cursor.execute(""" INSERT INTO bsi_tr_02102_1_key_requirements (algorithm_type, usage_context, min_key_length, valid_until) VALUES ('RSA', 'signature', 3000, NULL) """) conn.commit() conn.close() def test_check_compliance_with_missing_unified_table(): """Test that check_compliance fails with clear error when bsi_compliance_rules is missing.""" with tempfile.TemporaryDirectory() as tmpdir: db_path = str(Path(tmpdir) / "test.db") create_legacy_schema_db(db_path) # Verify bsi_compliance_rules doesn't exist yet conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='bsi_compliance_rules'" ) assert cursor.fetchone() is None conn.close() # Create a minimal scan result scan_results = { 443: { "cipher_suites": [ ("TLS 1.2", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", True) ], "supported_groups": ["secp256r1"], "certificates": [], } } # Write scan results should work from datetime import UTC, datetime scan_id = write_scan_results( db_path=db_path, hostname="example.com", ports=[443], scan_results=scan_results, scan_start_time=datetime.now(UTC), scan_duration=1.5, ) # Check compliance should fail with clear error about missing table with pytest.raises(sqlite3.Error) as exc_info: check_compliance(db_path, scan_id) error_msg = str(exc_info.value).lower() assert "bsi_compliance_rules" in error_msg or "no such table" in error_msg