Files
compliance-scan/tests/iana/test_iana_update.py
Heiko f60de7c2da Add SSH scan support with BSI TR-02102-4 compliance
- SSH scanning via ssh-audit (KEX, encryption, MAC, host keys)
- BSI TR-02102-4 and IANA compliance validation for SSH
- CSV/Markdown/reST reports for SSH results
- Unified compliance schema and database views
- Code optimization: modular query/writer architecture
2026-01-23 11:05:01 +01:00

325 lines
10 KiB
Python

"""Tests for IANA update functionality."""
import sqlite3
import pytest
from sslysze_scan.commands.update_iana import (
calculate_diff,
process_registry_with_validation,
)
from sslysze_scan.iana_validator import ValidationError
class TestCalculateDiff:
"""Tests for diff calculation between old and new data."""
def test_calculate_diff_no_changes(self) -> None:
"""Test diff calculation when data is unchanged."""
rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
("0x13,0x02", "TLS_AES_256_GCM_SHA384", "Y", "Y", "rfc8446"),
]
diff = calculate_diff(rows, rows)
assert len(diff["added"]) == 0
assert len(diff["deleted"]) == 0
assert len(diff["modified"]) == 0
def test_calculate_diff_added_rows(self) -> None:
"""Test diff calculation with added rows."""
old_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
]
new_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
("0x13,0x02", "TLS_AES_256_GCM_SHA384", "Y", "Y", "rfc8446"),
]
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 1
assert "0x13,0x02" in diff["added"]
assert len(diff["deleted"]) == 0
assert len(diff["modified"]) == 0
def test_calculate_diff_deleted_rows(self) -> None:
"""Test diff calculation with deleted rows."""
old_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
("0x13,0x02", "TLS_AES_256_GCM_SHA384", "Y", "Y", "rfc8446"),
]
new_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
]
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 0
assert len(diff["deleted"]) == 1
assert "0x13,0x02" in diff["deleted"]
assert len(diff["modified"]) == 0
def test_calculate_diff_modified_rows(self) -> None:
"""Test diff calculation with modified rows."""
old_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
]
new_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "N", "rfc8446"),
]
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 0
assert len(diff["deleted"]) == 0
assert len(diff["modified"]) == 1
assert "0x13,0x01" in diff["modified"]
def test_calculate_diff_mixed_changes(self) -> None:
"""Test diff calculation with mixed changes."""
old_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
("0x13,0x02", "TLS_AES_256_GCM_SHA384", "Y", "Y", "rfc8446"),
("0x00,0x9C", "TLS_RSA_WITH_AES_128_GCM_SHA256", "Y", "N", "rfc5288"),
]
new_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "N", "rfc8446"),
("0x13,0x03", "TLS_CHACHA20_POLY1305_SHA256", "Y", "Y", "rfc8446"),
("0x00,0x9C", "TLS_RSA_WITH_AES_128_GCM_SHA256", "Y", "N", "rfc5288"),
]
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 1
assert "0x13,0x03" in diff["added"]
assert len(diff["deleted"]) == 1
assert "0x13,0x02" in diff["deleted"]
assert len(diff["modified"]) == 1
assert "0x13,0x01" in diff["modified"]
class TestProcessRegistryWithValidation:
"""Tests for registry processing with validation."""
def test_process_valid_registry(self, test_db_path: str) -> None:
"""Test processing valid registry data."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
with open(xml_path, encoding="utf-8") as f:
xml_content = f.read()
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Description", "DTLS", "Recommended", "RFC/Draft"]
row_count, diff = process_registry_with_validation(
xml_content,
"tls-parameters-4",
"iana_tls_cipher_suites",
headers,
conn,
skip_min_rows_check=True,
)
assert row_count == 5
assert isinstance(diff, dict)
assert "added" in diff
assert "deleted" in diff
assert "modified" in diff
conn.close()
def test_process_registry_invalid_headers(self, test_db_path: str) -> None:
"""Test that invalid headers raise ValidationError."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
with open(xml_path, encoding="utf-8") as f:
xml_content = f.read()
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Name"]
with pytest.raises(ValidationError, match="Column count mismatch"):
process_registry_with_validation(
xml_content,
"tls-parameters-4",
"iana_tls_cipher_suites",
headers,
conn,
skip_min_rows_check=True,
)
conn.close()
def test_process_registry_nonexistent_registry_id(self, test_db_path: str) -> None:
"""Test that nonexistent registry ID raises ValueError."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
with open(xml_path, encoding="utf-8") as f:
xml_content = f.read()
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Description", "DTLS", "Recommended", "RFC/Draft"]
with pytest.raises(ValueError, match="Registry .* not found"):
process_registry_with_validation(
xml_content,
"nonexistent-registry",
"iana_tls_cipher_suites",
headers,
conn,
skip_min_rows_check=True,
)
conn.close()
class TestTransactionHandling:
"""Tests for database transaction handling."""
def test_transaction_rollback_preserves_data(self, test_db_path: str) -> None:
"""Test that rollback preserves original data."""
conn = sqlite3.connect(test_db_path)
cursor = conn.cursor()
original_count = cursor.execute(
"SELECT COUNT(*) FROM iana_tls_cipher_suites"
).fetchone()[0]
conn.execute("BEGIN TRANSACTION")
cursor.execute("DELETE FROM iana_tls_cipher_suites")
after_delete = cursor.execute(
"SELECT COUNT(*) FROM iana_tls_cipher_suites"
).fetchone()[0]
assert after_delete == 0
conn.rollback()
after_rollback = cursor.execute(
"SELECT COUNT(*) FROM iana_tls_cipher_suites"
).fetchone()[0]
assert after_rollback == original_count
conn.close()
def test_transaction_commit_persists_changes(self, test_db_path: str) -> None:
"""Test that commit persists changes."""
conn = sqlite3.connect(test_db_path)
cursor = conn.cursor()
conn.execute("BEGIN TRANSACTION")
cursor.execute(
"""
INSERT INTO iana_tls_cipher_suites
VALUES ('0xFF,0xFF', 'TEST_CIPHER', 'Y', 'N', 'test')
"""
)
conn.commit()
result = cursor.execute(
"""
SELECT COUNT(*) FROM iana_tls_cipher_suites
WHERE value = '0xFF,0xFF'
"""
).fetchone()[0]
assert result == 1
cursor.execute("DELETE FROM iana_tls_cipher_suites WHERE value = '0xFF,0xFF'")
conn.commit()
conn.close()
class TestDiffCalculationEdgeCases:
"""Tests for edge cases in diff calculation."""
def test_calculate_diff_empty_old_rows(self) -> None:
"""Test diff with empty old rows (initial import)."""
old_rows = []
new_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
("0x13,0x02", "TLS_AES_256_GCM_SHA384", "Y", "Y", "rfc8446"),
]
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 2
assert len(diff["deleted"]) == 0
assert len(diff["modified"]) == 0
def test_calculate_diff_empty_new_rows(self) -> None:
"""Test diff with empty new rows (complete deletion)."""
old_rows = [
("0x13,0x01", "TLS_AES_128_GCM_SHA256", "Y", "Y", "rfc8446"),
]
new_rows = []
diff = calculate_diff(old_rows, new_rows)
assert len(diff["added"]) == 0
assert len(diff["deleted"]) == 1
assert len(diff["modified"]) == 0
def test_calculate_diff_different_pk_index(self) -> None:
"""Test diff calculation with different primary key index."""
old_rows = [
("desc1", "0x01", "Y"),
("desc2", "0x02", "Y"),
]
new_rows = [
("desc1", "0x01", "N"),
("desc3", "0x03", "Y"),
]
diff = calculate_diff(old_rows, new_rows, pk_index=1)
assert "0x03" in diff["added"]
assert "0x02" in diff["deleted"]
assert "0x01" in diff["modified"]
class TestConsecutiveUpdates:
"""Tests for consecutive IANA updates."""
def test_consecutive_updates_show_no_changes(self, test_db_path: str) -> None:
"""Test that second update with same data shows no changes."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
with open(xml_path, encoding="utf-8") as f:
xml_content = f.read()
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Description", "DTLS", "Recommended", "RFC/Draft"]
row_count_1, diff_1 = process_registry_with_validation(
xml_content,
"tls-parameters-4",
"iana_tls_cipher_suites",
headers,
conn,
skip_min_rows_check=True,
)
row_count_2, diff_2 = process_registry_with_validation(
xml_content,
"tls-parameters-4",
"iana_tls_cipher_suites",
headers,
conn,
skip_min_rows_check=True,
)
assert row_count_1 == row_count_2
assert len(diff_2["added"]) == 0
assert len(diff_2["deleted"]) == 0
assert len(diff_2["modified"]) == 0
conn.close()