Skip to content

Commit 0b2a753

Browse files
authored
asn1: Add support for NULL (pyca#14311)
Signed-off-by: Facundo Tuesca <facundo.tuesca@trailofbits.com>
1 parent 138e837 commit 0b2a753

File tree

9 files changed

+60
-6
lines changed

9 files changed

+60
-6
lines changed

src/cryptography/hazmat/asn1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
GeneralizedTime,
1010
IA5String,
1111
Implicit,
12+
Null,
1213
PrintableString,
1314
Size,
1415
UtcTime,
@@ -25,6 +26,7 @@
2526
"GeneralizedTime",
2627
"IA5String",
2728
"Implicit",
29+
"Null",
2830
"PrintableString",
2931
"Size",
3032
"UtcTime",

src/cryptography/hazmat/asn1/asn1.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,3 +365,4 @@ class Default(typing.Generic[U]):
365365
UtcTime = declarative_asn1.UtcTime
366366
GeneralizedTime = declarative_asn1.GeneralizedTime
367367
BitString = declarative_asn1.BitString
368+
Null = declarative_asn1.Null

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ class BitString:
103103
def __eq__(self, other: object) -> bool: ...
104104
def as_bytes(self) -> bytes: ...
105105
def padding_bits(self) -> int: ...
106+
107+
class Null:
108+
def __new__(cls) -> Null: ...
109+
def __repr__(self) -> str: ...
110+
def __eq__(self, other: object) -> bool: ...

src/rust/src/declarative_asn1/decode.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use pyo3::types::{PyAnyMethods, PyListMethods};
88
use crate::asn1::big_byte_slice_to_py_int;
99
use crate::declarative_asn1::types::{
1010
check_size_constraint, is_tag_valid_for_type, is_tag_valid_for_variant, AnnotatedType,
11-
Annotation, BitString, Encoding, GeneralizedTime, IA5String, PrintableString, Type, UtcTime,
12-
Variant,
11+
Annotation, BitString, Encoding, GeneralizedTime, IA5String, Null, PrintableString, Type,
12+
UtcTime, Variant,
1313
};
1414
use crate::error::CryptographyError;
1515

@@ -161,6 +161,15 @@ fn decode_bitstring<'a>(
161161
)?)
162162
}
163163

164+
fn decode_null<'a>(
165+
py: pyo3::Python<'a>,
166+
parser: &mut Parser<'a>,
167+
encoding: &Option<pyo3::Py<Encoding>>,
168+
) -> ParseResult<pyo3::Bound<'a, Null>> {
169+
read_value::<asn1::Null>(parser, encoding)?;
170+
Ok(pyo3::Bound::new(py, Null {})?)
171+
}
172+
164173
// Utility function to handle explicit encoding when parsing
165174
// CHOICE fields.
166175
fn decode_choice_with_encoding<'a>(
@@ -302,6 +311,7 @@ pub(crate) fn decode_annotated_type<'a>(
302311
Type::UtcTime() => decode_utc_time(py, parser, encoding)?.into_any(),
303312
Type::GeneralizedTime() => decode_generalized_time(py, parser, encoding)?.into_any(),
304313
Type::BitString() => decode_bitstring(py, parser, annotation)?.into_any(),
314+
Type::Null() => decode_null(py, parser, encoding)?.into_any(),
305315
};
306316

307317
match &ann_type.annotation.get().default {

src/rust/src/declarative_asn1/encode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
262262
.map_err(|_| asn1::WriteError::AllocationError)?;
263263
write_value(writer, &bitstring, encoding)
264264
}
265+
Type::Null() => write_value(writer, &(), encoding),
265266
}
266267
}
267268
}

src/rust/src/declarative_asn1/types.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ pub enum Type {
5151
GeneralizedTime(),
5252
/// BIT STRING (`bytes`)
5353
BitString(),
54+
/// NULL
55+
Null(),
5456
}
5557

5658
/// A type that we know how to encode/decode, along with any
@@ -378,6 +380,21 @@ impl BitString {
378380
}
379381
}
380382

383+
#[pyo3::pyclass(frozen, eq, module = "cryptography.hazmat.bindings._rust.asn1")]
384+
#[derive(PartialEq)]
385+
pub struct Null;
386+
387+
#[pyo3::pymethods]
388+
impl Null {
389+
#[new]
390+
fn new() -> Self {
391+
Null
392+
}
393+
pub fn __repr__(&self) -> String {
394+
"Null()".to_string()
395+
}
396+
}
397+
381398
/// Utility function for converting builtin Python types
382399
/// to their Rust `Type` equivalent.
383400
#[pyo3::pyfunction]
@@ -405,6 +422,8 @@ pub fn non_root_python_to_rust<'p>(
405422
Type::GeneralizedTime().into_pyobject(py)
406423
} else if class.is(BitString::type_object(py)) {
407424
Type::BitString().into_pyobject(py)
425+
} else if class.is(Null::type_object(py)) {
426+
Type::Null().into_pyobject(py)
408427
} else {
409428
Err(pyo3::exceptions::PyTypeError::new_err(format!(
410429
"cannot handle type: {class:?}"
@@ -523,6 +542,7 @@ pub(crate) fn is_tag_valid_for_type(
523542
check_tag_with_encoding(asn1::GeneralizedTime::TAG, encoding, tag)
524543
}
525544
Type::BitString() => check_tag_with_encoding(asn1::BitString::TAG, encoding, tag),
545+
Type::Null() => check_tag_with_encoding(asn1::Null::TAG, encoding, tag),
526546
}
527547
}
528548

src/rust/src/lib.rs

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

tests/hazmat/asn1/test_api.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def test_invalid_bitstring(self) -> None:
148148
# Padding bits have to be zero
149149
asn1.BitString(data=b"\x01\x02\x03", padding_bits=2)
150150

151+
def test_repr_null(self) -> None:
152+
assert repr(asn1.Null()) == "Null()"
153+
151154

152155
class TestSequenceAPI:
153156
def test_fail_unsupported_field(self) -> None:

tests/hazmat/asn1/test_serialization.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,11 @@ def test_fail_bitstring(self) -> None:
301301
asn1.decode_der(asn1.BitString, b"\x03\x02\x08\x00")
302302

303303

304+
class TestNull:
305+
def test_ok_null(self) -> None:
306+
assert_roundtrips([(asn1.Null(), b"\x05\x00")])
307+
308+
304309
class TestSequence:
305310
def test_ok_sequence_single_field(self) -> None:
306311
@asn1.sequence
@@ -467,7 +472,8 @@ class Example:
467472
h: typing.Union[asn1.BitString, None]
468473
i: typing.Union[asn1.IA5String, None]
469474
j: typing.Union[x509.ObjectIdentifier, None]
470-
k: Annotated[typing.Union[str, None], asn1.Implicit(0)]
475+
k: typing.Union[asn1.Null, None]
476+
z: Annotated[typing.Union[str, None], asn1.Implicit(0)]
471477
only_field_present: Annotated[
472478
typing.Union[str, None], asn1.Implicit(1)
473479
]
@@ -487,6 +493,7 @@ class Example:
487493
i=None,
488494
j=None,
489495
k=None,
496+
z=None,
490497
only_field_present="a",
491498
),
492499
b"\x30\x03\x81\x01a",
@@ -537,7 +544,11 @@ class Example:
537544
j: Annotated[
538545
typing.Union[int, bool], asn1.Default(3), asn1.Explicit(0)
539546
]
540-
k: Annotated[str, asn1.Default("a"), asn1.Implicit(0)]
547+
k: Annotated[
548+
asn1.Null,
549+
asn1.Default(asn1.Null()),
550+
]
551+
z: Annotated[str, asn1.Default("a"), asn1.Implicit(0)]
541552
only_field_present: Annotated[
542553
str, asn1.Default("a"), asn1.Implicit(1)
543554
]
@@ -556,7 +567,8 @@ class Example:
556567
h=asn1.IA5String("a"),
557568
i=default_oid,
558569
j=3,
559-
k="a",
570+
k=asn1.Null(),
571+
z="a",
560572
only_field_present="b",
561573
),
562574
b"\x30\x03\x81\x01b",

0 commit comments

Comments
 (0)