Skip to content

Commit 19ec7f2

Browse files
committed
asn1: Add support for IA5String
Signed-off-by: Facundo Tuesca <[email protected]>
1 parent ed8c355 commit 19ec7f2

File tree

9 files changed

+102
-5
lines changed

9 files changed

+102
-5
lines changed

src/cryptography/hazmat/asn1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Default,
88
Explicit,
99
GeneralizedTime,
10+
IA5String,
1011
Implicit,
1112
PrintableString,
1213
Size,
@@ -21,6 +22,7 @@
2122
"Default",
2223
"Explicit",
2324
"GeneralizedTime",
25+
"IA5String",
2426
"Implicit",
2527
"PrintableString",
2628
"Size",

src/cryptography/hazmat/asn1/asn1.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ class Default(typing.Generic[U]):
238238
Size = declarative_asn1.Size
239239

240240
PrintableString = declarative_asn1.PrintableString
241+
IA5String = declarative_asn1.IA5String
241242
UtcTime = declarative_asn1.UtcTime
242243
GeneralizedTime = declarative_asn1.GeneralizedTime
243244
BitString = declarative_asn1.BitString

src/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ class PrintableString:
6666
def __eq__(self, other: object) -> bool: ...
6767
def as_str(self) -> str: ...
6868

69+
class IA5String:
70+
def __new__(cls, inner: str) -> IA5String: ...
71+
def __repr__(self) -> str: ...
72+
def __eq__(self, other: object) -> bool: ...
73+
def as_str(self) -> str: ...
74+
6975
class UtcTime:
7076
def __new__(cls, inner: datetime.datetime) -> UtcTime: ...
7177
def __repr__(self) -> str: ...

src/rust/src/declarative_asn1/decode.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use pyo3::types::PyListMethods;
88

99
use crate::asn1::big_byte_slice_to_py_int;
1010
use crate::declarative_asn1::types::{
11-
type_to_tag, AnnotatedType, BitString, Encoding, GeneralizedTime, PrintableString, Type,
12-
UtcTime,
11+
type_to_tag, AnnotatedType, BitString, Encoding, GeneralizedTime, IA5String, PrintableString,
12+
Type, UtcTime,
1313
};
1414
use crate::error::CryptographyError;
1515

@@ -77,6 +77,16 @@ fn decode_printable_string<'a>(
7777
Ok(pyo3::Bound::new(py, PrintableString { inner })?)
7878
}
7979

80+
fn decode_ia5_string<'a>(
81+
py: pyo3::Python<'a>,
82+
parser: &mut Parser<'a>,
83+
encoding: &Option<pyo3::Py<Encoding>>,
84+
) -> ParseResult<pyo3::Bound<'a, IA5String>> {
85+
let value = read_value::<asn1::IA5String<'a>>(parser, encoding)?.as_str();
86+
let inner = pyo3::types::PyString::new(py, value).unbind();
87+
Ok(pyo3::Bound::new(py, IA5String { inner })?)
88+
}
89+
8090
fn decode_utc_time<'a>(
8191
py: pyo3::Python<'a>,
8292
parser: &mut Parser<'a>,
@@ -216,6 +226,7 @@ pub(crate) fn decode_annotated_type<'a>(
216226
Type::PyBytes() => decode_pybytes(py, parser, encoding)?.into_any(),
217227
Type::PyStr() => decode_pystr(py, parser, encoding)?.into_any(),
218228
Type::PrintableString() => decode_printable_string(py, parser, encoding)?.into_any(),
229+
Type::IA5String() => decode_ia5_string(py, parser, encoding)?.into_any(),
219230
Type::UtcTime() => decode_utc_time(py, parser, encoding)?.into_any(),
220231
Type::GeneralizedTime() => decode_generalized_time(py, parser, encoding)?.into_any(),
221232
Type::BitString() => decode_bitstring(py, parser, encoding)?.into_any(),

src/rust/src/declarative_asn1/encode.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use pyo3::types::PyAnyMethods;
77
use pyo3::types::PyListMethods;
88

99
use crate::declarative_asn1::types::{
10-
AnnotatedType, AnnotatedTypeObject, BitString, Encoding, GeneralizedTime, PrintableString,
11-
Type, UtcTime,
10+
AnnotatedType, AnnotatedTypeObject, BitString, Encoding, GeneralizedTime, IA5String,
11+
PrintableString, Type, UtcTime,
1212
};
1313

1414
fn write_value<T: SimpleAsn1Writable>(
@@ -148,6 +148,19 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
148148
.ok_or(asn1::WriteError::AllocationError)?;
149149
write_value(writer, &printable_string, encoding)
150150
}
151+
Type::IA5String() => {
152+
let val: &pyo3::Bound<'_, IA5String> = value
153+
.cast()
154+
.map_err(|_| asn1::WriteError::AllocationError)?;
155+
let inner_str = val
156+
.get()
157+
.inner
158+
.to_cow(py)
159+
.map_err(|_| asn1::WriteError::AllocationError)?;
160+
let ia5_string: asn1::IA5String<'_> =
161+
asn1::IA5String::new(&inner_str).ok_or(asn1::WriteError::AllocationError)?;
162+
write_value(writer, &ia5_string, encoding)
163+
}
151164
Type::UtcTime() => {
152165
let val: &pyo3::Bound<'_, UtcTime> = value
153166
.cast()

src/rust/src/declarative_asn1/types.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5+
use asn1::IA5String as Asn1IA5String;
56
use asn1::PrintableString as Asn1PrintableString;
67
use asn1::SimpleAsn1Readable;
78
use asn1::UtcTime as Asn1UtcTime;
@@ -36,6 +37,8 @@ pub enum Type {
3637
PyStr(),
3738
/// PrintableString (`str`)
3839
PrintableString(),
40+
/// IA5String (`str`)
41+
IA5String(),
3942
/// UtcTime (`datetime`)
4043
UtcTime(),
4144
/// GeneralizedTime (`datetime`)
@@ -162,6 +165,39 @@ impl PrintableString {
162165
}
163166
}
164167

168+
#[derive(pyo3::FromPyObject)]
169+
#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.asn1")]
170+
pub struct IA5String {
171+
pub(crate) inner: pyo3::Py<pyo3::types::PyString>,
172+
}
173+
174+
#[pyo3::pymethods]
175+
impl IA5String {
176+
#[new]
177+
#[pyo3(signature = (inner,))]
178+
fn new(py: pyo3::Python<'_>, inner: pyo3::Py<pyo3::types::PyString>) -> pyo3::PyResult<Self> {
179+
if Asn1IA5String::new(&inner.to_cow(py)?).is_none() {
180+
return Err(pyo3::exceptions::PyValueError::new_err(format!(
181+
"invalid IA5String: {inner}"
182+
)));
183+
}
184+
185+
Ok(IA5String { inner })
186+
}
187+
188+
pub fn as_str(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyString>> {
189+
Ok(self.inner.clone_ref(py))
190+
}
191+
192+
fn __eq__(&self, py: pyo3::Python<'_>, other: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<bool> {
193+
(**self.inner.bind(py)).eq(other.inner.bind(py))
194+
}
195+
196+
pub fn __repr__(&self, py: pyo3::Python<'_>) -> pyo3::PyResult<String> {
197+
Ok(format!("IA5String({})", self.inner.bind(py).repr()?))
198+
}
199+
}
200+
165201
#[derive(pyo3::FromPyObject)]
166202
#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.asn1")]
167203
pub struct UtcTime {
@@ -310,6 +346,8 @@ pub fn non_root_python_to_rust<'p>(
310346
Type::PyBytes().into_pyobject(py)
311347
} else if class.is(PrintableString::type_object(py)) {
312348
Type::PrintableString().into_pyobject(py)
349+
} else if class.is(IA5String::type_object(py)) {
350+
Type::IA5String().into_pyobject(py)
313351
} else if class.is(UtcTime::type_object(py)) {
314352
Type::UtcTime().into_pyobject(py)
315353
} else if class.is(GeneralizedTime::type_object(py)) {
@@ -370,6 +408,7 @@ pub(crate) fn type_to_tag(t: &Type, encoding: &Option<pyo3::Py<Encoding>>) -> as
370408
Type::PyBytes() => <&[u8] as SimpleAsn1Readable>::TAG,
371409
Type::PyStr() => asn1::Utf8String::TAG,
372410
Type::PrintableString() => asn1::PrintableString::TAG,
411+
Type::IA5String() => asn1::IA5String::TAG,
373412
Type::UtcTime() => asn1::UtcTime::TAG,
374413
Type::GeneralizedTime() => asn1::GeneralizedTime::TAG,
375414
Type::BitString() => asn1::BitString::TAG,

src/rust/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ mod _rust {
152152
#[pymodule_export]
153153
use crate::declarative_asn1::types::{
154154
non_root_python_to_rust, AnnotatedType, Annotation, BitString, Encoding,
155-
GeneralizedTime, PrintableString, Size, Type, UtcTime,
155+
GeneralizedTime, IA5String, PrintableString, Size, Type, UtcTime,
156156
};
157157
}
158158

tests/hazmat/asn1/test_api.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ def test_invalid_printable_string(self) -> None:
3333
with pytest.raises(ValueError, match="invalid PrintableString: café"):
3434
asn1.PrintableString("café")
3535

36+
def test_repr_ia5_string(self) -> None:
37+
my_string = "MyString"
38+
assert repr(asn1.IA5String(my_string)) == f"IA5String({my_string!r})"
39+
40+
def test_ia5_string_as_str(self) -> None:
41+
my_string = "MyString"
42+
assert asn1.IA5String(my_string).as_str() == my_string
43+
44+
def test_invalid_ia5_string(self) -> None:
45+
with pytest.raises(ValueError, match="invalid IA5String: café"):
46+
asn1.IA5String("café")
47+
3648
def test_utc_time_as_datetime(self) -> None:
3749
dt = datetime.datetime(
3850
2000, 1, 1, 10, 10, 10, tzinfo=datetime.timezone.utc

tests/hazmat/asn1/test_serialization.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ def test_ok_printable_string(self) -> None:
127127
)
128128

129129

130+
class TestIA5String:
131+
def test_ok_ia5_string(self) -> None:
132+
assert_roundtrips(
133+
[
134+
(asn1.IA5String(""), b"\x16\x00"),
135+
(asn1.IA5String("hello"), b"\x16\x05hello"),
136+
(asn1.IA5String("Test User 1"), b"\x16\x0bTest User 1"),
137+
]
138+
)
139+
140+
130141
class TestUtcTime:
131142
def test_utc_time(self) -> None:
132143
assert_roundtrips(
@@ -538,6 +549,7 @@ class Example:
538549
f: typing.Union[asn1.GeneralizedTime, None]
539550
g: typing.Union[typing.List[int], None]
540551
h: typing.Union[asn1.BitString, None]
552+
i: typing.Union[asn1.IA5String, None]
541553

542554
assert_roundtrips(
543555
[
@@ -551,6 +563,7 @@ class Example:
551563
f=None,
552564
g=None,
553565
h=None,
566+
i=None,
554567
),
555568
b"\x30\x00",
556569
)

0 commit comments

Comments
 (0)