diff --git a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi index e499eb5c846e..f24aaea1f3e9 100644 --- a/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi +++ b/src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -81,30 +81,35 @@ class PrintableString: def __new__(cls, inner: str) -> PrintableString: ... def __repr__(self) -> str: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def as_str(self) -> str: ... class IA5String: def __new__(cls, inner: str) -> IA5String: ... def __repr__(self) -> str: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def as_str(self) -> str: ... class UTCTime: def __new__(cls, inner: datetime.datetime) -> UTCTime: ... def __repr__(self) -> str: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def as_datetime(self) -> datetime.datetime: ... class GeneralizedTime: def __new__(cls, inner: datetime.datetime) -> GeneralizedTime: ... def __repr__(self) -> str: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def as_datetime(self) -> datetime.datetime: ... class BitString: def __new__(cls, data: bytes, padding_bits: int) -> BitString: ... def __repr__(self) -> str: ... def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... def as_bytes(self) -> bytes: ... def padding_bits(self) -> int: ... diff --git a/src/rust/src/declarative_asn1/types.rs b/src/rust/src/declarative_asn1/types.rs index 4efbe75a849d..7c9cb3afc26d 100644 --- a/src/rust/src/declarative_asn1/types.rs +++ b/src/rust/src/declarative_asn1/types.rs @@ -254,6 +254,10 @@ impl PrintableString { (**self.inner.bind(py)).eq(other.inner.bind(py)) } + fn __hash__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + (**self.inner.bind(py)).hash() + } + pub fn __repr__<'py>( &self, py: pyo3::Python<'py>, @@ -301,6 +305,10 @@ impl IA5String { (**self.inner.bind(py)).eq(other.inner.bind(py)) } + fn __hash__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + (**self.inner.bind(py)).hash() + } + pub fn __repr__<'py>( &self, py: pyo3::Python<'py>, @@ -359,6 +367,10 @@ impl UtcTime { (**self.inner.bind(py)).eq(other.inner.bind(py)) } + fn __hash__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + (**self.inner.bind(py)).hash() + } + pub fn __repr__<'py>( &self, py: pyo3::Python<'py>, @@ -404,6 +416,10 @@ impl GeneralizedTime { (**self.inner.bind(py)).eq(other.inner.bind(py)) } + fn __hash__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + (**self.inner.bind(py)).hash() + } + pub fn __repr__<'py>( &self, py: pyo3::Python<'py>, @@ -453,6 +469,12 @@ impl BitString { && self.padding_bits == other.padding_bits) } + fn __hash__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult { + (self.data.bind(py), self.padding_bits) + .into_pyobject(py)? + .hash() + } + pub fn __repr__<'py>( &self, py: pyo3::Python<'py>, diff --git a/tests/hazmat/asn1/test_api.py b/tests/hazmat/asn1/test_api.py index 63294768fad8..4eb2bc48b0e8 100644 --- a/tests/hazmat/asn1/test_api.py +++ b/tests/hazmat/asn1/test_api.py @@ -29,6 +29,14 @@ def test_invalid_printable_string(self) -> None: with pytest.raises(ValueError, match="invalid PrintableString: café"): asn1.PrintableString("café") + def test_hash_printable_string(self) -> None: + assert hash(asn1.PrintableString("MyString")) == hash( + asn1.PrintableString("MyString") + ) + assert hash(asn1.PrintableString("MyString")) != hash( + asn1.PrintableString("OtherString") + ) + def test_repr_ia5_string(self) -> None: my_string = "MyString" assert repr(asn1.IA5String(my_string)) == f"IA5String({my_string!r})" @@ -41,6 +49,14 @@ def test_invalid_ia5_string(self) -> None: with pytest.raises(ValueError, match="invalid IA5String: café"): asn1.IA5String("café") + def test_hash_ia5_string(self) -> None: + assert hash(asn1.IA5String("MyString")) == hash( + asn1.IA5String("MyString") + ) + assert hash(asn1.IA5String("MyString")) != hash( + asn1.IA5String("OtherString") + ) + def test_utc_time_as_datetime(self) -> None: dt = datetime.datetime( 2000, 1, 1, 10, 10, 10, tzinfo=datetime.timezone.utc @@ -53,6 +69,16 @@ def test_repr_utc_time(self) -> None: ) assert repr(asn1.UTCTime(dt)) == f"UTCTime({dt!r})" + def test_hash_utc_time(self) -> None: + dt = datetime.datetime( + 2000, 1, 1, 10, 10, 10, tzinfo=datetime.timezone.utc + ) + other_dt = datetime.datetime( + 2001, 1, 1, 10, 10, 10, tzinfo=datetime.timezone.utc + ) + assert hash(asn1.UTCTime(dt)) == hash(asn1.UTCTime(dt)) + assert hash(asn1.UTCTime(dt)) != hash(asn1.UTCTime(other_dt)) + def test_invalid_utc_time(self) -> None: with pytest.raises( ValueError, @@ -107,6 +133,18 @@ def test_repr_generalized_time(self) -> None: ) assert repr(asn1.GeneralizedTime(dt)) == f"GeneralizedTime({dt!r})" + def test_hash_generalized_time(self) -> None: + dt = datetime.datetime( + 2000, 1, 1, 10, 10, 10, 300000, tzinfo=datetime.timezone.utc + ) + other_dt = datetime.datetime( + 2001, 1, 1, 10, 10, 10, 300000, tzinfo=datetime.timezone.utc + ) + assert hash(asn1.GeneralizedTime(dt)) == hash(asn1.GeneralizedTime(dt)) + assert hash(asn1.GeneralizedTime(dt)) != hash( + asn1.GeneralizedTime(other_dt) + ) + def test_invalid_generalized_time(self) -> None: with pytest.raises( ValueError, @@ -129,6 +167,17 @@ def test_repr_bitstring(self) -> None: == f"BitString(data={data!r}, padding_bits=2)" ) + def test_hash_bitstring(self) -> None: + assert hash(asn1.BitString(b"\x01\x02\x30", 2)) == hash( + asn1.BitString(b"\x01\x02\x30", 2) + ) + assert hash(asn1.BitString(b"\x01\x02\x30", 2)) != hash( + asn1.BitString(b"\x01\x02\x40", 2) + ) + assert hash(asn1.BitString(b"\x01\x02\x30", 2)) != hash( + asn1.BitString(b"\x01\x02\x30", 3) + ) + def test_invalid_bitstring(self) -> None: with pytest.raises( ValueError,