Skip to content

Commit 6558003

Browse files
authored
fix: correct CNSA 2.0 attribution, OpenSSL min version, and timeout types (#4)
- DEFAULT_TIMEOUT: int → float (10.0) across constants.py, checker.py, tls_utils.py; remove spurious int() cast in assessor.py - OPENSSL_MIN_VERSION: (3, 0) → (3, 5) — OpenSSL 3.0-3.4 have no native PQC hybrid group support; error message and verdict.py reason updated - X25519MLKEM768 standard: "CNSA 2.0" → "BSI TR-02102-2" (X25519 is 128-bit security, below CNSA 2.0's P-384 minimum) - SecP256r1MLKEM768 standard: "CNSA 2.0" → "draft-ietf-tls-mlkem" (P-256 is 128-bit security, below CNSA 2.0 threshold) - mlkem768x25519-sha256 SSH standard: add "BSI TR-02102-4" (2024) - Add test_returns_false_when_version_34 to test_constants.py (246 tests) - Bump version to 0.6.1
1 parent 676fb40 commit 6558003

11 files changed

Lines changed: 73 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,41 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
1111

1212
---
1313

14+
## [0.6.1] — 2026-06-24
15+
16+
### Fixed
17+
- `constants.py`: `DEFAULT_TIMEOUT` type corrected from `int` to `float` (`10.0`),
18+
consistent with the platform-wide convention (`timeout` is always `float`).
19+
- `constants.py`: `OPENSSL_MIN_VERSION` raised from `(3, 0)` to `(3, 5)` — OpenSSL
20+
3.0–3.4 have no native PQC hybrid group support; `X25519MLKEM768`,
21+
`SecP256r1MLKEM768`, and `SecP384r1MLKEM1024` require OpenSSL ≥ 3.5.
22+
Error message updated to reflect the new minimum and name the affected groups.
23+
- `constants.py` (`PQC_GROUPS`): `X25519MLKEM768` standard corrected from
24+
`"CNSA 2.0"` to `"BSI TR-02102-2"` — X25519 is 128-bit security, below CNSA
25+
2.0's P-384 minimum; BSI TR-02102-2 (2024) explicitly approves this hybrid.
26+
`SecP256r1MLKEM768` standard corrected from `"CNSA 2.0"` to
27+
`"draft-ietf-tls-mlkem"` — P-256 is 128-bit security, below the CNSA 2.0
28+
threshold. Only `SecP384r1MLKEM1024` (P-384 + ML-KEM-1024) qualifies for
29+
CNSA 2.0, unchanged.
30+
- `constants.py` (`SSH_PQC_GROUPS`): `mlkem768x25519-sha256` standard updated to
31+
include `"BSI TR-02102-4"` — BSI TR-02102-4 (2024) explicitly recommends
32+
this X25519+ML-KEM-768 hybrid for SSH.
33+
- `assessor.py`: removed spurious `int()` cast on `timeout` before passing to
34+
`check_tls()` — callers may supply a fractional timeout and the cast was
35+
silently truncating it.
36+
- `checker.py`, `tls_utils.py`: `timeout` parameter type annotation corrected
37+
from `int` to `float` to match the call chain and platform convention.
38+
- `verdict.py`: failure reason for unrecognised key exchange updated from
39+
"requires OpenSSL >= 3.0" to "requires OpenSSL >= 3.5".
40+
- `README.md`: "OpenSSL ≥ 3.0" requirement updated to "OpenSSL ≥ 3.5".
41+
42+
### Added
43+
- `tests/test_constants.py`: new test `test_returns_false_when_version_34`
44+
verifying that OpenSSL 3.4 (no native PQC group support) is correctly rejected
45+
with an informative error message (246 tests total).
46+
47+
---
48+
1449
## [0.6.0] — 2026-06-19
1550

1651
### Added
@@ -199,7 +234,8 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
199234

200235
---
201236

202-
[Unreleased]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.6.0...HEAD
237+
[Unreleased]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.6.1...HEAD
238+
[0.6.1]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.6.0...v0.6.1
203239
[0.6.0]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.5.2...v0.6.0
204240
[0.5.2]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.5.1...v0.5.2
205241
[0.5.1]: https://github.com/NC3-TestingPlatform/quantumvalidator/compare/v0.5.0...v0.5.1

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ $ quantumvalidator check cloudflare.com
1212
```
1313

1414
![Python](https://img.shields.io/badge/python-%3E%3D3.11-blue)
15-
![Tests](https://img.shields.io/badge/tests-245%20passing-brightgreen)
15+
![Tests](https://img.shields.io/badge/tests-246%20passing-brightgreen)
1616
![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
1717
![License](https://img.shields.io/badge/license-GPLv3-lightgrey)
1818

@@ -79,7 +79,7 @@ non-NIST-selected algorithm.
7979
## Requirements
8080

8181
- **Python** ≥ 3.11
82-
- **OpenSSL** ≥ 3.0 binary on PATH — required for PQC group negotiation
82+
- **OpenSSL** ≥ 3.5 binary on PATH — required for native PQC hybrid group negotiation
8383
- Debian/Ubuntu: `apt install openssl`
8484
- macOS: `brew install openssl`
8585
- `rich` ≥ 13.7 and `typer` ≥ 0.12 (installed automatically via pip)
@@ -297,7 +297,7 @@ pytest tests/test_tls_utils.py
297297
pytest tests/test_assessor.py::TestAssessHttps -v
298298
```
299299

300-
The test suite has **245 tests** and maintains **100% statement coverage**.
300+
The test suite has **246 tests** and maintains **100% statement coverage**.
301301

302302
All network I/O (`openssl s_client` subprocess) is mocked at the `probe_tls` boundary —
303303
no test touches a real server or the internet.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "quantumvalidator"
7-
version = "0.6.0"
7+
version = "0.6.1"
88
description = "Quantum-safe cryptography validator — TLS, STARTTLS, and SSH post-quantum readiness assessment"
99
readme = "README.md"
1010
requires-python = ">=3.11"

quantumvalidator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
try:
66
__version__ = version("quantumvalidator")
77
except PackageNotFoundError: # pragma: no cover
8-
__version__ = "0.6.0"
8+
__version__ = "0.6.1"
99

1010
import logging as _logging
1111

quantumvalidator/assessor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def assess(
5454

5555
logger.info("Starting assessment for %s:%d", target, effective_port)
5656

57-
result = check_tls(target, effective_port, timeout=int(timeout))
57+
result = check_tls(target, effective_port, timeout=timeout)
5858

5959
if not result.ok:
6060
checks: list[CheckResult] = [

quantumvalidator/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from quantumvalidator.tls_utils import TLSProbeResult, probe_tls
1111

1212

13-
def check_tls(host: str, port: int, timeout: int = 10) -> TLSProbeResult:
13+
def check_tls(host: str, port: int, timeout: float = 10.0) -> TLSProbeResult:
1414
"""Probe *host*:*port* for PQC key exchange support.
1515
1616
The underlying probe auto-detects STARTTLS mode (smtp/imap/pop3/ftp/lmtp/

quantumvalidator/constants.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ class GroupInfo:
2929
"X25519MLKEM768": GroupInfo(
3030
name="X25519MLKEM768",
3131
iana_codepoint="0x11ec",
32-
standard="CNSA 2.0, BSI TR-02102-2",
32+
# X25519 is 128-bit security — below CNSA 2.0's P-384 minimum.
33+
# BSI TR-02102-2 explicitly approves X25519+ML-KEM-768 hybrid.
34+
standard="BSI TR-02102-2",
3335
safe=True,
3436
),
3537
"SecP256r1MLKEM768": GroupInfo(
3638
name="SecP256r1MLKEM768",
3739
iana_codepoint="0x11eb",
38-
standard="CNSA 2.0",
40+
# P-256 is 128-bit security — below CNSA 2.0's P-384 minimum.
41+
standard="draft-ietf-tls-mlkem",
3942
safe=True,
4043
),
4144
"SecP384r1MLKEM1024": GroupInfo(
@@ -78,7 +81,8 @@ class GroupInfo:
7881
"mlkem768x25519-sha256": GroupInfo(
7982
name="mlkem768x25519-sha256",
8083
iana_codepoint=None,
81-
standard="NIST FIPS 203",
84+
# BSI TR-02102-4 (2024) recommends X25519+ML-KEM-768 hybrid for SSH.
85+
standard="NIST FIPS 203, BSI TR-02102-4",
8286
safe=True,
8387
),
8488
}
@@ -87,11 +91,11 @@ class GroupInfo:
8791
name for name, g in SSH_PQC_GROUPS.items() if g.safe
8892
)
8993

90-
DEFAULT_TIMEOUT: int = 10
94+
DEFAULT_TIMEOUT: float = 10.0
9195
DEFAULT_PORT_HTTPS: int = 443
9296

9397
OPENSSL_BINARY: str = "openssl"
94-
OPENSSL_MIN_VERSION: tuple[int, int] = (3, 0)
98+
OPENSSL_MIN_VERSION: tuple[int, int] = (3, 5)
9599

96100

97101
@functools.lru_cache(maxsize=1)
@@ -124,7 +128,8 @@ def check_openssl() -> tuple[bool, str]:
124128
return False, (
125129
f"OpenSSL {major}.{minor} detected; "
126130
f"requires >= {OPENSSL_MIN_VERSION[0]}.{OPENSSL_MIN_VERSION[1]} "
127-
"for PQC hybrid groups. "
131+
"for native PQC hybrid group support "
132+
"(X25519MLKEM768 / SecP256r1MLKEM768 / SecP384r1MLKEM1024). "
128133
"Upgrade OpenSSL (e.g. apt install openssl)."
129134
)
130135
except Exception as exc: # pragma: no cover

quantumvalidator/tls_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def probe_raw(
146146
def probe_tls(
147147
host: str,
148148
port: int,
149-
timeout: int = 10,
149+
timeout: float = 10.0,
150150
) -> TLSProbeResult:
151151
"""Probe *host*:*port* and return the negotiated TLS group.
152152

quantumvalidator/verdict.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def build_checks(
9898
value=negotiated_group,
9999
reason=(
100100
f"No PQC hybrid group negotiated; got {negotiated_group or 'none'}. "
101-
"Enable X25519MLKEM768 on the server (requires OpenSSL >= 3.0)."
101+
"Enable X25519MLKEM768 on the server (requires OpenSSL >= 3.5)."
102102
),
103103
standard="CNSA 2.0, BSI TR-02102-2",
104104
))

tests/test_constants.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ def test_returns_false_when_version_too_old(self, monkeypatch):
6666
assert ok is False
6767
assert "1.1" in msg
6868

69+
def test_returns_false_when_version_34(self, monkeypatch):
70+
# OpenSSL 3.4 has no native PQC group support; minimum is 3.5.
71+
check_openssl.cache_clear()
72+
monkeypatch.setattr(
73+
"quantumvalidator.constants.shutil.which",
74+
lambda name: f"/usr/bin/{name}",
75+
)
76+
monkeypatch.setattr(
77+
"quantumvalidator.constants._sp.run",
78+
lambda *a, **kw: MockRun(stdout="OpenSSL 3.4.0 22 Oct 2024\n"),
79+
)
80+
ok, msg = check_openssl()
81+
assert ok is False
82+
assert "3.4" in msg
83+
6984
def test_version_check_exception_skipped(self, monkeypatch):
7085
check_openssl.cache_clear()
7186
monkeypatch.setattr(

0 commit comments

Comments
 (0)