From 972a7b5896a6047ea43a86db87820ab474d898ff Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 22 Jan 2024 17:14:50 -0500 Subject: [PATCH] verification: add test_verify_tz_aware (#10229) * verification: add test_verify_tz_aware Signed-off-by: William Woodruff * py_to_datetime handles tzinfo, add test Signed-off-by: William Woodruff * Update src/rust/src/x509/common.rs Co-authored-by: Alex Gaynor * x509/common: coverage for the coverage god Signed-off-by: William Woodruff --------- Signed-off-by: William Woodruff Co-authored-by: Alex Gaynor --- src/rust/src/x509/common.rs | 21 ++++++++---- tests/x509/verification/test_verification.py | 35 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 42d08823430e..a941f50b928c 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -503,13 +503,22 @@ pub(crate) fn py_to_datetime( py: pyo3::Python<'_>, val: &pyo3::PyAny, ) -> pyo3::PyResult { + // We treat naive datetimes as UTC times, while aware datetimes get + // normalized to UTC before conversion. + let val_utc = if val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { + val + } else { + let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; + val.call_method1(pyo3::intern!(py, "astimezone"), (utc,))? + }; + Ok(asn1::DateTime::new( - val.getattr(pyo3::intern!(py, "year"))?.extract()?, - val.getattr(pyo3::intern!(py, "month"))?.extract()?, - val.getattr(pyo3::intern!(py, "day"))?.extract()?, - val.getattr(pyo3::intern!(py, "hour"))?.extract()?, - val.getattr(pyo3::intern!(py, "minute"))?.extract()?, - val.getattr(pyo3::intern!(py, "second"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "year"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "month"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "day"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "hour"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "minute"))?.extract()?, + val_utc.getattr(pyo3::intern!(py, "second"))?.extract()?, ) .unwrap()) } diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index d4b0bc07d606..8c2be7054227 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -103,3 +103,38 @@ def test_store_rejects_empty_list(self): def test_store_rejects_non_certificates(self): with pytest.raises(TypeError): Store(["not a cert"]) # type: ignore[list-item] + + +class TestServerVerifier: + @pytest.mark.parametrize( + ("validation_time", "valid"), + [ + # 03:15:02 UTC+2, or 1 second before expiry in UTC + ("2018-11-16T03:15:02+02:00", True), + # 00:15:04 UTC-1, or 1 second after expiry in UTC + ("2018-11-16T00:15:04-01:00", False), + ], + ) + def test_verify_tz_aware(self, validation_time, valid): + # expires 2018-11-16 01:15:03 UTC + leaf = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + + store = Store([leaf]) + + builder = PolicyBuilder().store(store) + builder = builder.time( + datetime.datetime.fromisoformat(validation_time) + ) + verifier = builder.build_server_verifier(DNSName("cryptography.io")) + + if valid: + assert verifier.verify(leaf, []) == [leaf] + else: + with pytest.raises( + x509.verification.VerificationError, + match="cert is not valid at validation time", + ): + verifier.verify(leaf, [])