Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 71 additions & 6 deletions src/rust/src/declarative_asn1/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,33 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
check_size_constraint(&annotation.size, || n_bits, "BIT STRING")?;
Ok(write_value(writer, &bitstring, encoding)?)
}
Type::Tlv() => Err(CryptographyError::Py(
pyo3::exceptions::PyNotImplementedError::new_err(
"TLV encoding currently not supported",
),
)),
Type::Tlv() => {
let tlv = value.cast::<super::types::Tlv>()?;
// The `Tlv` Python object stores the element's raw DER bytes
// (`full_data`) rather than a live `asn1::Tlv<'_>`, since the
// latter borrows its input and can't be held in a pyclass. To
// emit it we need an `asn1::Asn1Writable`, but `asn1::Tlv` has
// no public constructor, so we recover one by re-parsing
// `full_data`. This only reads the tag/length header (no
// allocation, no re-validation of the value) and is infallible
// in practice, since the bytes came from a successful decode.
let asn1_tlv =
asn1::parse_single::<asn1::Tlv<'_>>(tlv.get().full_data.as_bytes(py))?;
match encoding {
Some(e) => match e.get() {
// TLVs with implicit annotations are not supported
// (they are caught first at the Python level).
Encoding::Implicit(_) => Err(CryptographyError::Py(
pyo3::exceptions::PyValueError::new_err(
"invalid type definition: TLV/ANY fields cannot \
be implicitly encoded",
),
)),
Encoding::Explicit(n) => Ok(writer.write_explicit_element(&asn1_tlv, *n)?),
},
None => Ok(writer.write_element(&asn1_tlv)?),
}
}
Type::Null() => Ok(write_value(writer, &(), encoding)?),
Type::Certificate() => {
let val = value.cast::<crate::x509::certificate::Certificate>()?;
Expand Down Expand Up @@ -327,10 +349,53 @@ impl asn1::Asn1Writable for AnnotatedTypeObject<'_> {
#[cfg(test)]
mod tests {
use crate::declarative_asn1::types::{
AnnotatedType, AnnotatedTypeObject, Annotation, Encoding, Type, Variant,
AnnotatedType, AnnotatedTypeObject, Annotation, Encoding, Tlv, Type, Variant,
};
use asn1::Asn1Writable;
use pyo3::PyTypeInfo;

#[test]
// Needed for coverage of the `Encoding::Implicit` arm of the
// `Type::Tlv()` encoding case, since implicit TLV annotations are
// rejected first at the Python level and so never reach it.
fn test_encode_implicit_tlv() {
pyo3::Python::initialize();
pyo3::Python::attach(|py| {
// The DER encoding of the INTEGER `1`.
let full_data = pyo3::types::PyBytes::new(py, b"\x02\x01\x01").unbind();
let tag_bytes = pyo3::types::PyBytes::new(py, b"\x02").unbind();
let tlv = pyo3::Py::new(
py,
Tlv {
tag_bytes,
data_index: 2,
full_data,
},
)
.unwrap();

let annotation = Annotation {
default: None,
encoding: Some(pyo3::Py::new(py, Encoding::Implicit(0)).unwrap()),
size: None,
};
let ann_type = AnnotatedType {
inner: pyo3::Py::new(py, Type::Tlv()).unwrap(),
annotation: pyo3::Py::new(py, annotation).unwrap(),
};
let object = AnnotatedTypeObject {
annotated_type: &ann_type,
value: tlv.bind(py).clone().into_any(),
};

let result = asn1::write(|writer| object.write(writer));
assert!(result.is_err());
let error = result.unwrap_err();
assert!(format!("{error}")
.contains("invalid type definition: TLV/ANY fields cannot be implicitly encoded"));
});
}

#[test]
fn test_encode_implicit_choice() {
pyo3::Python::initialize();
Expand Down
28 changes: 21 additions & 7 deletions tests/hazmat/asn1/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,24 @@ class Example:
assert isinstance(decoded_example, Example)
assert decoded_example.foo == 9

def test_fail_encode_tlv(self) -> None:
tlv = asn1.decode_der(asn1.TLV, b"\x03\x02\x07\x40")
def test_ok_encode_tlv(self) -> None:
for original in [
b"\x03\x02\x07\x40",
b"\x02\x01\x01",
b"\x30\x03\x02\x01\x09",
b"\x05\x00",
]:
tlv = asn1.decode_der(asn1.TLV, original)
assert isinstance(tlv, asn1.TLV)
assert asn1.encode_der(tlv) == original

def test_ok_encode_tlv_roundtrip_from_value(self) -> None:
# Parse an arbitrary element with the TLV specifier, then
# serialize just that element back to its original DER.
original = asn1.encode_der(1)
tlv = asn1.decode_der(asn1.TLV, original)
assert isinstance(tlv, asn1.TLV)

with pytest.raises(
NotImplementedError, match="TLV encoding currently not supported"
):
asn1.encode_der(tlv)
assert asn1.encode_der(tlv) == original


class TestNull:
Expand Down Expand Up @@ -1054,6 +1064,10 @@ class Example:
assert decoded.bar.tag_bytes == b"\x02"
assert bytes(decoded.bar.data) == b"\x09"

# Re-encoding the EXPLICIT-tagged TLV fields produces the
# original DER.
assert asn1.encode_der(decoded) == encoded

def test_fail_sequence_with_tlv_with_explicit_annotation(
self,
) -> None:
Expand Down