feature: IANA update

This commit is contained in:
Heiko
2025-12-19 20:10:39 +01:00
parent f038d6a3fc
commit 753c582010
27 changed files with 1923 additions and 419 deletions

View File

@@ -0,0 +1,69 @@
<?xml version='1.0' encoding='UTF-8'?>
<registry xmlns="http://www.iana.org/assignments" id="ikev2-parameters">
<title>Internet Key Exchange Version 2 (IKEv2) Parameters</title>
<created>2005-01-18</created>
<updated>2025-12-03</updated>
<registry id="ikev2-parameters-5">
<title>Transform Type 1 - Encryption Algorithm Transform IDs</title>
<record>
<value>12</value>
<description>ENCR_AES_CBC</description>
<esp>Y</esp>
<ikev2>Y</ikev2>
<xref type="rfc" data="rfc3602"/>
</record>
<record>
<value>20</value>
<description>ENCR_AES_GCM_16</description>
<esp>Y</esp>
<ikev2>Y</ikev2>
<xref type="rfc" data="rfc4106"/>
</record>
<record>
<value>28</value>
<description>ENCR_CHACHA20_POLY1305</description>
<esp>Y</esp>
<ikev2>Y</ikev2>
<xref type="rfc" data="rfc7634"/>
</record>
</registry>
<registry id="ikev2-parameters-8">
<title>Transform Type 4 - Diffie-Hellman Group Transform IDs</title>
<record>
<value>14</value>
<description>2048-bit MODP Group</description>
<status>RECOMMENDED</status>
<xref type="rfc" data="rfc3526"/>
</record>
<record>
<value>19</value>
<description>256-bit random ECP group</description>
<status>RECOMMENDED</status>
<xref type="rfc" data="rfc5903"/>
</record>
<record>
<value>31</value>
<description>Curve25519</description>
<status>RECOMMENDED</status>
<xref type="rfc" data="rfc8031"/>
</record>
</registry>
<registry id="ikev2-parameters-12">
<title>IKEv2 Authentication Method</title>
<record>
<value>1</value>
<description>RSA Digital Signature</description>
<status>DEPRECATED</status>
<xref type="rfc" data="rfc7427"/>
</record>
<record>
<value>14</value>
<description>Digital Signature</description>
<status>RECOMMENDED</status>
<xref type="rfc" data="rfc7427"/>
</record>
</registry>
</registry>

View File

@@ -0,0 +1,96 @@
<?xml version='1.0' encoding='UTF-8'?>
<registry xmlns="http://www.iana.org/assignments" id="tls-parameters">
<title>Transport Layer Security (TLS) Parameters</title>
<category>Transport Layer Security (TLS)</category>
<created>2005-08-23</created>
<updated>2025-12-03</updated>
<registry id="tls-parameters-4">
<title>TLS Cipher Suites</title>
<record>
<value>0x13,0x01</value>
<description>TLS_AES_128_GCM_SHA256</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8446"/>
</record>
<record>
<value>0x13,0x02</value>
<description>TLS_AES_256_GCM_SHA384</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8446"/>
</record>
<record>
<value>0x00,0x9C</value>
<description>TLS_RSA_WITH_AES_128_GCM_SHA256</description>
<dtls>Y</dtls>
<rec>N</rec>
<xref type="rfc" data="rfc5288"/>
</record>
<record>
<value>0x00,0x2F</value>
<description>TLS_RSA_WITH_AES_128_CBC_SHA</description>
<dtls>Y</dtls>
<rec>N</rec>
<xref type="rfc" data="rfc5246"/>
</record>
<record>
<value>0x00,0x0A</value>
<description>TLS_RSA_WITH_3DES_EDE_CBC_SHA</description>
<dtls>Y</dtls>
<rec>N</rec>
<xref type="rfc" data="rfc5246"/>
</record>
</registry>
<registry id="tls-parameters-8">
<title>TLS Supported Groups</title>
<record>
<value>23</value>
<description>secp256r1</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8422"/>
</record>
<record>
<value>24</value>
<description>secp384r1</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8422"/>
</record>
<record>
<value>29</value>
<description>x25519</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8446"/>
</record>
</registry>
<registry id="tls-signaturescheme">
<title>TLS SignatureScheme</title>
<record>
<value>0x0403</value>
<description>ecdsa_secp256r1_sha256</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8446"/>
</record>
<record>
<value>0x0804</value>
<description>rsa_pss_rsae_sha256</description>
<dtls>Y</dtls>
<rec>Y</rec>
<xref type="rfc" data="rfc8446"/>
</record>
<record>
<value>0x0401</value>
<description>rsa_pkcs1_sha256</description>
<dtls>Y</dtls>
<rec>N</rec>
<xref type="rfc" data="rfc8446"/>
</record>
</registry>
</registry>

View File

@@ -1,73 +0,0 @@
"""Tests for compliance checking functionality."""
from datetime import datetime
class TestComplianceChecks:
"""Tests for compliance validation logic."""
def test_check_bsi_validity(self) -> None:
"""Test BSI cipher suite validity checking."""
# Valid BSI-approved cipher suite (not expired)
cipher_suite_valid = {
"name": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"iana_recommended": "N",
"bsi_approved": True,
"bsi_valid_until": "2029",
}
# Check that current year is before 2029
current_year = datetime.now().year
assert current_year < 2029, "Test assumes current year < 2029"
# BSI-approved and valid should be compliant
assert cipher_suite_valid["bsi_approved"] is True
assert int(cipher_suite_valid["bsi_valid_until"]) > current_year
# Expired BSI-approved cipher suite
cipher_suite_expired = {
"name": "TLS_OLD_CIPHER",
"iana_recommended": "N",
"bsi_approved": True,
"bsi_valid_until": "2020",
}
# BSI-approved but expired should not be compliant
assert cipher_suite_expired["bsi_approved"] is True
assert int(cipher_suite_expired["bsi_valid_until"]) < current_year
# No BSI data
cipher_suite_no_bsi = {
"name": "TLS_CHACHA20_POLY1305_SHA256",
"iana_recommended": "Y",
"bsi_approved": False,
"bsi_valid_until": None,
}
# Without BSI approval, compliance depends on IANA
assert cipher_suite_no_bsi["bsi_approved"] is False
def test_check_iana_recommendation(self) -> None:
"""Test IANA recommendation checking."""
# IANA recommended cipher suite
cipher_suite_recommended = {
"name": "TLS_AES_256_GCM_SHA384",
"iana_recommended": "Y",
"bsi_approved": True,
"bsi_valid_until": "2031",
}
assert cipher_suite_recommended["iana_recommended"] == "Y"
# IANA not recommended cipher suite
cipher_suite_not_recommended = {
"name": "TLS_RSA_WITH_AES_128_CBC_SHA",
"iana_recommended": "N",
"bsi_approved": False,
"bsi_valid_until": None,
}
assert cipher_suite_not_recommended["iana_recommended"] == "N"
# No IANA data (should default to non-compliant)
cipher_suite_no_iana = {
"name": "TLS_UNKNOWN_CIPHER",
"iana_recommended": None,
"bsi_approved": False,
"bsi_valid_until": None,
}
assert cipher_suite_no_iana["iana_recommended"] is None

202
tests/test_iana_parse.py Normal file
View File

@@ -0,0 +1,202 @@
"""Tests for IANA XML parsing functionality."""
import xml.etree.ElementTree as ET
import pytest
from sslysze_scan.iana_parser import (
extract_field_value,
find_registry,
get_element_text,
is_unassigned,
parse_xml_with_namespace_support,
process_xref_elements,
)
class TestParseXmlWithNamespace:
"""Tests for XML parsing with namespace detection."""
def test_parse_tls_parameters_with_namespace(self) -> None:
"""Test parsing TLS parameters XML with IANA namespace."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
assert root is not None
assert ns is not None
assert "iana" in ns
assert ns["iana"] == "http://www.iana.org/assignments"
def test_parse_nonexistent_file(self) -> None:
"""Test that parsing nonexistent file raises FileNotFoundError."""
with pytest.raises(FileNotFoundError):
parse_xml_with_namespace_support("nonexistent.xml")
class TestFindRegistry:
"""Tests for finding registry by ID."""
def test_find_cipher_suites_registry(self) -> None:
"""Test finding TLS cipher suites registry."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
assert registry is not None
assert registry.get("id") == "tls-parameters-4"
def test_find_nonexistent_registry(self) -> None:
"""Test that finding nonexistent registry raises ValueError."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
with pytest.raises(ValueError, match="Registry .* nicht gefunden"):
find_registry(root, "nonexistent-registry", ns)
class TestGetElementText:
"""Tests for element text extraction."""
def test_get_element_text_with_namespace(self) -> None:
"""Test extracting text from element with namespace."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
value = get_element_text(first_record, "value", ns)
assert value == "0x13,0x01"
description = get_element_text(first_record, "description", ns)
assert description == "TLS_AES_128_GCM_SHA256"
def test_get_element_text_nonexistent(self) -> None:
"""Test that nonexistent element returns empty string."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
result = get_element_text(first_record, "nonexistent", ns)
assert result == ""
class TestProcessXrefElements:
"""Tests for xref element processing."""
def test_process_single_xref(self) -> None:
"""Test processing single xref element."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
xref_str = process_xref_elements(first_record, ns)
assert "rfc:rfc8446" in xref_str
def test_process_no_xref(self) -> None:
"""Test processing record without xref elements."""
xml_str = """
<record xmlns="http://www.iana.org/assignments">
<value>0x13,0x01</value>
<description>Test</description>
</record>
"""
record = ET.fromstring(xml_str)
ns = {"iana": "http://www.iana.org/assignments"}
xref_str = process_xref_elements(record, ns)
assert xref_str == ""
class TestExtractFieldValue:
"""Tests for field value extraction."""
def test_extract_recommended_field(self) -> None:
"""Test extracting Recommended field."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
rec = extract_field_value(first_record, "Recommended", ns)
assert rec == "Y"
def test_extract_rfc_draft_field(self) -> None:
"""Test extracting RFC/Draft field via xref processing."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
rfc_draft = extract_field_value(first_record, "RFC/Draft", ns)
assert "rfc:rfc8446" in rfc_draft
class TestExtractUpdatedDate:
"""Tests for extracting updated date from XML."""
def test_extract_updated_from_tls_xml(self) -> None:
"""Test extracting updated date from TLS parameters XML."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
with open(xml_path, encoding="utf-8") as f:
xml_content = f.read()
lines = xml_content.split("\n")[:10]
updated_line = [line for line in lines if "<updated>" in line]
assert len(updated_line) == 1
assert "2025-12-03" in updated_line[0]
class TestIsUnassigned:
"""Tests for unassigned entry detection."""
def test_is_unassigned_numeric_range(self) -> None:
"""Test detection of numeric range values."""
xml_str = """
<record xmlns="http://www.iana.org/assignments">
<value>42-255</value>
<description>Unassigned</description>
</record>
"""
record = ET.fromstring(xml_str)
ns = {"iana": "http://www.iana.org/assignments"}
assert is_unassigned(record, ns) is True
def test_is_unassigned_hex_range(self) -> None:
"""Test detection of hex range values."""
xml_str = """
<record xmlns="http://www.iana.org/assignments">
<value>0x0000-0x0200</value>
<description>Reserved for backward compatibility</description>
</record>
"""
record = ET.fromstring(xml_str)
ns = {"iana": "http://www.iana.org/assignments"}
assert is_unassigned(record, ns) is True
def test_is_unassigned_false(self) -> None:
"""Test that assigned entries return False."""
xml_path = "tests/fixtures/iana_xml/tls-parameters-minimal.xml"
root, ns = parse_xml_with_namespace_support(xml_path)
registry = find_registry(root, "tls-parameters-4", ns)
records = registry.findall("iana:record", ns)
first_record = records[0]
assert is_unassigned(first_record, ns) is False

285
tests/test_iana_update.py Normal file
View File

@@ -0,0 +1,285 @@
"""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 .* nicht gefunden"):
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"]

View File

@@ -0,0 +1,232 @@
"""Tests for IANA data validators."""
import sqlite3
import pytest
from sslysze_scan.iana_validator import (
ValidationError,
get_min_rows,
normalize_header,
validate_cipher_suite_row,
validate_headers,
validate_ikev2_row,
validate_registry_data,
validate_signature_schemes_row,
validate_supported_groups_row,
)
class TestNormalizeHeader:
"""Tests for header normalization."""
def test_normalize_combined(self) -> None:
"""Test combined normalization."""
assert normalize_header("RFC/Draft") == "rfc_draft"
assert normalize_header("Recommended") == "recommended"
class TestValidateHeaders:
"""Tests for header validation against database schema."""
def test_validate_headers_matching(self, test_db_path: str) -> None:
"""Test that correct headers pass validation."""
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Description", "DTLS", "Recommended", "RFC/Draft"]
validate_headers("iana_tls_cipher_suites", headers, conn)
conn.close()
def test_validate_headers_mismatch_count(self, test_db_path: str) -> None:
"""Test that wrong number of columns raises error."""
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Description"]
with pytest.raises(ValidationError, match="Column count mismatch"):
validate_headers("iana_tls_cipher_suites", headers, conn)
conn.close()
def test_validate_headers_mismatch_name(self, test_db_path: str) -> None:
"""Test that wrong column name raises error."""
conn = sqlite3.connect(test_db_path)
headers = ["Value", "Name", "DTLS", "Recommended", "RFC/Draft"]
with pytest.raises(ValidationError, match="Column .* mismatch"):
validate_headers("iana_tls_cipher_suites", headers, conn)
conn.close()
class TestCipherSuiteValidation:
"""Tests for cipher suite data validation."""
def test_valid_cipher_suite(self) -> None:
"""Test that valid cipher suite passes."""
row = {
"value": "0x13,0x01",
"description": "TLS_AES_128_GCM_SHA256",
"dtls": "Y",
"recommended": "Y",
"rfc_draft": "rfc: rfc8446",
}
validate_cipher_suite_row(row)
def test_missing_required_field(self) -> None:
"""Test that missing value field raises error."""
row = {"description": "TLS_AES_128_GCM_SHA256"}
with pytest.raises(ValidationError, match="Missing required field"):
validate_cipher_suite_row(row)
def test_invalid_value_format(self) -> None:
"""Test that invalid value format raises error."""
row = {
"value": "1301",
"description": "TLS_AES_128_GCM_SHA256",
}
with pytest.raises(ValidationError, match="Invalid value format"):
validate_cipher_suite_row(row)
def test_invalid_recommended_value(self) -> None:
"""Test that invalid Recommended value raises error."""
row = {
"value": "0x13,0x01",
"description": "TLS_AES_128_GCM_SHA256",
"recommended": "X",
}
with pytest.raises(ValidationError, match="Invalid Recommended value"):
validate_cipher_suite_row(row)
def test_empty_recommended_valid(self) -> None:
"""Test that empty Recommended field is valid."""
row = {
"value": "0x13,0x01",
"description": "TLS_AES_128_GCM_SHA256",
"recommended": "",
}
validate_cipher_suite_row(row)
class TestSupportedGroupsValidation:
"""Tests for supported groups data validation."""
def test_valid_supported_group(self) -> None:
"""Test that valid supported group passes."""
row = {
"value": "23",
"description": "secp256r1",
"recommended": "Y",
}
validate_supported_groups_row(row)
def test_invalid_value_non_numeric(self) -> None:
"""Test that non-numeric value raises error."""
row = {"value": "0x17", "description": "secp256r1"}
with pytest.raises(ValidationError, match="Value must be numeric"):
validate_supported_groups_row(row)
class TestSignatureSchemesValidation:
"""Tests for signature schemes data validation."""
def test_valid_signature_scheme(self) -> None:
"""Test that valid signature scheme passes."""
row = {
"value": "0x0403",
"description": "ecdsa_secp256r1_sha256",
"recommended": "Y",
}
validate_signature_schemes_row(row)
def test_invalid_value_format(self) -> None:
"""Test that invalid value format raises error."""
row = {"value": "0403", "description": "ecdsa_secp256r1_sha256"}
with pytest.raises(ValidationError, match="Invalid value format"):
validate_signature_schemes_row(row)
class TestIKEv2Validation:
"""Tests for IKEv2 data validation."""
def test_valid_ikev2_row(self) -> None:
"""Test that valid IKEv2 row passes."""
row = {
"value": "12",
"description": "ENCR_AES_CBC",
"esp": "Y",
"ikev2": "Y",
}
validate_ikev2_row(row)
def test_invalid_value_non_numeric(self) -> None:
"""Test that non-numeric value raises error."""
row = {"value": "0x0C", "description": "ENCR_AES_CBC"}
with pytest.raises(ValidationError, match="Value must be numeric"):
validate_ikev2_row(row)
class TestGetMinRows:
"""Tests for minimum row count lookup."""
def test_get_min_rows_unknown_table(self) -> None:
"""Test that unknown tables return default minimum."""
assert get_min_rows("iana_unknown_table") == 5
class TestValidateRegistryData:
"""Tests for complete registry data validation."""
def test_validate_registry_sufficient_rows(self) -> None:
"""Test that sufficient rows pass validation."""
rows = [
{
"value": f"0x13,0x{i:02x}",
"description": f"Cipher_{i}",
"dtls": "Y",
"recommended": "Y",
"rfc_draft": "rfc: rfc8446",
}
for i in range(60)
]
validate_registry_data("iana_tls_cipher_suites", rows)
def test_validate_registry_insufficient_rows(self) -> None:
"""Test that insufficient rows raise error."""
rows = [
{
"value": "0x13,0x01",
"description": "Cipher_1",
"dtls": "Y",
"recommended": "Y",
"rfc_draft": "rfc: rfc8446",
}
]
with pytest.raises(ValidationError, match="Insufficient data"):
validate_registry_data("iana_tls_cipher_suites", rows)
def test_validate_registry_invalid_row(self) -> None:
"""Test that invalid row in dataset raises error."""
rows = [
{
"value": f"0x13,0x{i:02x}",
"description": f"Cipher_{i}",
"dtls": "Y",
"recommended": "Y",
"rfc_draft": "rfc: rfc8446",
}
for i in range(60)
]
rows[30]["value"] = "invalid"
with pytest.raises(ValidationError, match="Row 31"):
validate_registry_data("iana_tls_cipher_suites", rows)
def test_validate_registry_no_validator(self) -> None:
"""Test that tables without validator pass basic validation."""
rows = [{"value": "1", "description": f"Item_{i}"} for i in range(10)]
validate_registry_data("iana_tls_alerts", rows)

View File

@@ -5,21 +5,10 @@ from typing import Any
from sslysze_scan.reporter.template_utils import (
build_template_context,
format_tls_version,
generate_report_id,
)
class TestFormatTlsVersion:
"""Tests for format_tls_version function."""
def test_format_tls_version_all_versions(self) -> None:
"""Test formatting all known TLS versions."""
versions = ["1.0", "1.1", "1.2", "1.3", "ssl_3.0", "unknown"]
expected = ["TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3", "SSL 3.0", "unknown"]
assert [format_tls_version(v) for v in versions] == expected
class TestGenerateReportId:
"""Tests for generate_report_id function."""