From 7951efe46a687ba6376be67143b77e5d6aaaa8fb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 22 Jan 2024 13:54:05 -0500 Subject: [PATCH 1/4] verification: add test_verify_tz_aware Signed-off-by: William Woodruff --- tests/x509/verification/test_verification.py | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/x509/verification/test_verification.py b/tests/x509/verification/test_verification.py index d4b0bc07d606..608d81f54a5b 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -103,3 +103,27 @@ 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: + def test_verify_tz_aware(self): + # expires 2018-11-16 01:15:03 GMT + leaf = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + ) + + store = Store([leaf]) + + builder = PolicyBuilder().store(store) + # 00:15:03 in GMT+2, or 02:15:03 local time + builder = builder.time( + datetime.datetime.fromisoformat("2018-11-16T00:15:03+02:00") + ) + verifier = builder.build_server_verifier(DNSName("cryptography.io")) + + with pytest.raises( + x509.verification.VerificationError, + match="cert is not valid at validation time", + ): + verifier.verify(leaf, []) From 4a81f0e34b37070d6757692a658494eed6a43ba3 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 22 Jan 2024 14:56:48 -0500 Subject: [PATCH 2/4] py_to_datetime handles tzinfo, add test Signed-off-by: William Woodruff --- src/rust/src/x509/common.rs | 22 +++++++++++---- tests/x509/verification/test_verification.py | 29 ++++++++++++++------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 42d08823430e..cf62155b95ab 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -503,13 +503,23 @@ 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 = match val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { + true => val, + false => val.call_method1( + pyo3::intern!(py, "astimezone"), + (types::DATETIME_TIMEZONE_UTC.get(py)?,), + )?, + }; + 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 608d81f54a5b..8c2be7054227 100644 --- a/tests/x509/verification/test_verification.py +++ b/tests/x509/verification/test_verification.py @@ -106,8 +106,17 @@ def test_store_rejects_non_certificates(self): class TestServerVerifier: - def test_verify_tz_aware(self): - # expires 2018-11-16 01:15:03 GMT + @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, @@ -116,14 +125,16 @@ def test_verify_tz_aware(self): store = Store([leaf]) builder = PolicyBuilder().store(store) - # 00:15:03 in GMT+2, or 02:15:03 local time builder = builder.time( - datetime.datetime.fromisoformat("2018-11-16T00:15:03+02:00") + datetime.datetime.fromisoformat(validation_time) ) verifier = builder.build_server_verifier(DNSName("cryptography.io")) - with pytest.raises( - x509.verification.VerificationError, - match="cert is not valid at validation time", - ): - verifier.verify(leaf, []) + 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, []) From 9c799136a4c2bb54a7381aec4b76e43538b40f24 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 22 Jan 2024 16:48:39 -0500 Subject: [PATCH 3/4] Update src/rust/src/x509/common.rs Co-authored-by: Alex Gaynor --- src/rust/src/x509/common.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index cf62155b95ab..c646b92ad42a 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -505,12 +505,13 @@ pub(crate) fn py_to_datetime( ) -> pyo3::PyResult { // We treat naive datetimes as UTC times, while aware datetimes get // normalized to UTC before conversion. - let val_utc = match val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { - true => val, - false => val.call_method1( + let val_utc = if val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { + val + } else { + val.call_method1( pyo3::intern!(py, "astimezone"), (types::DATETIME_TIMEZONE_UTC.get(py)?,), - )?, + )? }; Ok(asn1::DateTime::new( From c69e9b015736b6b74f4fab288f7efb497ba4608c Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 22 Jan 2024 17:05:34 -0500 Subject: [PATCH 4/4] x509/common: coverage for the coverage god Signed-off-by: William Woodruff --- src/rust/src/x509/common.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index c646b92ad42a..a941f50b928c 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -508,10 +508,8 @@ pub(crate) fn py_to_datetime( let val_utc = if val.getattr(pyo3::intern!(py, "tzinfo"))?.is_none() { val } else { - val.call_method1( - pyo3::intern!(py, "astimezone"), - (types::DATETIME_TIMEZONE_UTC.get(py)?,), - )? + let utc = types::DATETIME_TIMEZONE_UTC.get(py)?; + val.call_method1(pyo3::intern!(py, "astimezone"), (utc,))? }; Ok(asn1::DateTime::new(