diff --git a/.config/codespell_ignore.txt b/.config/codespell_ignore.txt index e5d65af4708..7faaa0e70a3 100644 --- a/.config/codespell_ignore.txt +++ b/.config/codespell_ignore.txt @@ -54,3 +54,5 @@ wan wanna webp widgits +UPER +uPER diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index dbf865f3ee6..128c6386261 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -28,15 +28,11 @@ Type, Union, cast, - TYPE_CHECKING, ) from typing import ( TypeVar, ) -if TYPE_CHECKING: - from scapy.asn1.ber import BERcodec_Object - try: from datetime import timezone except ImportError: @@ -110,35 +106,96 @@ class ASN1_Error(Scapy_Exception): class ASN1_Encoding_Error(ASN1_Error): - pass + def __init__(self, + msg, # type: str + encoded=None, # type: Any + remaining=b"" # type: bytes + ): + # type: (...) -> None + Scapy_Exception.__init__(self, msg) + self.remaining = remaining + self.encoded = encoded + + def __str__(self): + # type: () -> str + s = Scapy_Exception.__str__(self) + if self.encoded is not None: + if isinstance(self.encoded, ASN1_Object): + s += "\n### Already encoded ###\n%s" % self.encoded.strshow() + else: + s += "\n### Already encoded ###\n%r" % self.encoded + if self.remaining is not None: + s += "\n### Remaining ###\n%r" % self.remaining + return s class ASN1_Decoding_Error(ASN1_Error): - pass + def __init__(self, + msg, # type: str + decoded=None, # type: Any + remaining=b"" # type: bytes + ): + # type: (...) -> None + Scapy_Exception.__init__(self, msg) + self.remaining = remaining + self.decoded = decoded + + def __str__(self): + # type: () -> str + s = Scapy_Exception.__str__(self) + if self.decoded is not None: + if isinstance(self.decoded, ASN1_Object): + s += "\n### Already decoded ###\n%s" % self.decoded.strshow() + else: + s += "\n### Already decoded ###\n%r" % self.decoded + if self.remaining is not None: + s += "\n### Remaining ###\n%r" % self.remaining + return s class ASN1_BadTag_Decoding_Error(ASN1_Decoding_Error): pass +class ASN1Codec_metaclass(type): + def __new__(cls, + name, # type: str + bases, # type: Tuple[type, ...] + dct # type: Dict[str, Any] + ): + # type: (...) -> type + c = super(ASN1Codec_metaclass, cls).__new__(cls, name, bases, dct) + try: + c.tag.register(c.codec, c) # type: ignore + except Exception: + warning("Error registering %r for %r" % (c.tag, c.codec)) # type: ignore + return c + + class ASN1Codec(EnumElement): def register_stem(cls, stem): - # type: (Type[BERcodec_Object[Any]]) -> None + # type: (Type[Any]) -> None cls._stem = stem def register_tagging(cls, enc, dec): # type: (Any, Any) -> None - # Codec-level implicit/explicit tagging (BER/OER) or identity (UPER/PER). + # Codec-level implicit/explicit tagging (BER) or identity (OER/PER). cls._tagging_enc = enc cls._tagging_dec = dec def tagging_enc(cls, s, **kwargs): # type: (bytes, **Any) -> bytes - return cls._tagging_enc(s, **kwargs) # type: ignore + enc = getattr(cls, "_tagging_enc", None) + if enc is None: + return s + return cast(bytes, enc(s, **kwargs)) def tagging_dec(cls, s, **kwargs): # type: (bytes, **Any) -> Tuple[Optional[int], bytes] - return cls._tagging_dec(s, **kwargs) # type: ignore + dec = getattr(cls, "_tagging_dec", None) + if dec is None: + return None, s + return cast(Tuple[Optional[int], bytes], dec(s, **kwargs)) def dec(cls, s, context=None, _depth=0): # type: (bytes, Optional[Type[ASN1_Class]], int) -> ASN1_Object[Any] @@ -174,7 +231,7 @@ def __init__(self, key, # type: str value, # type: int context=None, # type: Optional[Type[ASN1_Class]] - codec=None # type: Optional[Dict[ASN1Codec, Type[BERcodec_Object[Any]]]] # noqa: E501 + codec=None # type: Optional[Dict[ASN1Codec, Type[Any]]] ): # type: (...) -> None EnumElement.__init__(self, key, value) @@ -199,11 +256,11 @@ def asn1_object(self, val): raise ASN1_Error("%r does not have any assigned ASN1 object" % self) def register(self, codecnum, codec): - # type: (ASN1Codec, Type[BERcodec_Object[Any]]) -> None + # type: (ASN1Codec, Type[Any]) -> None self._codec[codecnum] = codec def get_codec(self, codec): - # type: (Any) -> Type[BERcodec_Object[Any]] + # type: (Any) -> Type[Any] try: c = self._codec[codec] except KeyError: @@ -322,7 +379,7 @@ def __init__(self, val): def enc(self, codec): # type: (Any) -> bytes - return self.tag.get_codec(codec).enc(self.val) + return cast(bytes, self.tag.get_codec(codec).enc(self.val)) def __repr__(self): # type: () -> str diff --git a/scapy/asn1/ber.py b/scapy/asn1/ber.py index 2f675964992..40ded6a21ea 100644 --- a/scapy/asn1/ber.py +++ b/scapy/asn1/ber.py @@ -12,10 +12,10 @@ # Good read: https://luca.ntop.org/Teaching/Appunti/asn1.html from scapy.config import conf -from scapy.error import warning from scapy.compat import chb, bytes_encode from scapy.utils import binrepr, inet_aton, inet_ntoa from scapy.asn1.asn1 import ( + ASN1Codec_metaclass, ASN1Tag, ASN1_BADTAG, ASN1_BadTag_Decoding_Error, @@ -33,7 +33,6 @@ from typing import ( Any, AnyStr, - Dict, Generic, List, Optional, @@ -59,47 +58,11 @@ class BER_Exception(Exception): class BER_Encoding_Error(ASN1_Encoding_Error): - def __init__(self, - msg, # type: str - encoded=None, # type: Optional[Union[BERcodec_Object[Any], str]] # noqa: E501 - remaining=b"" # type: bytes - ): - # type: (...) -> None - Exception.__init__(self, msg) - self.remaining = remaining - self.encoded = encoded - - def __str__(self): - # type: () -> str - s = Exception.__str__(self) - if isinstance(self.encoded, ASN1_Object): - s += "\n### Already encoded ###\n%s" % self.encoded.strshow() - else: - s += "\n### Already encoded ###\n%r" % self.encoded - s += "\n### Remaining ###\n%r" % self.remaining - return s + pass class BER_Decoding_Error(ASN1_Decoding_Error): - def __init__(self, - msg, # type: str - decoded=None, # type: Optional[Any] - remaining=b"" # type: bytes - ): - # type: (...) -> None - Exception.__init__(self, msg) - self.remaining = remaining - self.decoded = decoded - - def __str__(self): - # type: () -> str - s = Exception.__str__(self) - if isinstance(self.decoded, ASN1_Object): - s += "\n### Already decoded ###\n%s" % self.decoded.strshow() - else: - s += "\n### Already decoded ###\n%r" % self.decoded - s += "\n### Remaining ###\n%r" % self.remaining - return s + pass class BER_BadTag_Decoding_Error(BER_Decoding_Error, @@ -164,7 +127,6 @@ def BER_num_dec(s, cls_id=0, max_pow=32): raise BER_Decoding_Error("BER_num_dec: got empty string", remaining=s) x = cls_id for i, c in enumerate(s): - c = c x <<= 7 x |= c & 0x7f if not c & 0x80: @@ -275,20 +237,8 @@ def BER_tagging_enc(s, implicit_tag=None, explicit_tag=None): # [ BER classes ] # -class BERcodec_metaclass(type): - def __new__(cls, - name, # type: str - bases, # type: Tuple[type, ...] - dct # type: Dict[str, Any] - ): - # type: (...) -> Type[BERcodec_Object[Any]] - c = cast('Type[BERcodec_Object[Any]]', - super(BERcodec_metaclass, cls).__new__(cls, name, bases, dct)) - try: - c.tag.register(c.codec, c) - except Exception: - warning("Error registering %r for %r" % (c.tag, c.codec)) - return c +class BERcodec_metaclass(ASN1Codec_metaclass): + pass _K = TypeVar('_K') @@ -412,13 +362,14 @@ def safedec(cls, @classmethod def enc(cls, s, size_len=0, **_kwargs): # type: (_K, Optional[int], **Any) -> bytes - # Ignore unknown kwargs so shared field._codec_kwargs() dicts (OER/UPER - # keys) do not TypeError on BER packets. + # Ignore unknown kwargs (field=/pkt=/constraint keys) so BER packets + # do not TypeError when shared field call sites pass them through. + size_len = 0 if size_len is None else int(size_len) if isinstance(s, (str, bytes)): return BERcodec_STRING.enc(s, size_len=size_len) else: try: - return BERcodec_INTEGER.enc(int(s), size_len=size_len) # type: ignore + return BERcodec_INTEGER.enc(int(s), size_len=size_len) # type: ignore # noqa: E501 except TypeError: raise TypeError("Trying to encode an invalid value !") @@ -437,6 +388,7 @@ class BERcodec_INTEGER(BERcodec_Object[int]): @classmethod def enc(cls, i, size_len=0, **_kwargs): # type: ignore[override] # type: (int, Optional[int], **Any) -> bytes + size_len = 0 if size_len is None else int(size_len) ls = [] while True: ls.append(i & 0xff) @@ -504,6 +456,7 @@ def do_dec(cls, @classmethod def enc(cls, _s, size_len=0, **_kwargs): # type: ignore[override] # type: (AnyStr, Optional[int], **Any) -> bytes + size_len = 0 if size_len is None else int(size_len) # /!\ this is DER encoding (bit strings are only zero-bit padded) s = bytes_encode(_s) if len(s) % 8 == 0: @@ -523,6 +476,7 @@ class BERcodec_STRING(BERcodec_Object[str]): @classmethod def enc(cls, _s, size_len=0, **_kwargs): # type: ignore[override] # type: (Union[str, bytes], Optional[int], **Any) -> bytes + size_len = 0 if size_len is None else int(size_len) s = bytes_encode(_s) # Be sure we are encoding bytes return chb(int(cls.tag)) + BER_len_enc(len(s), size=size_len) + s @@ -557,6 +511,7 @@ class BERcodec_OID(BERcodec_Object[bytes]): @classmethod def enc(cls, _oid, size_len=0, **_kwargs): # type: ignore[override] # type: (AnyStr, Optional[int], **Any) -> bytes + size_len = 0 if size_len is None else int(size_len) oid = bytes_encode(_oid) if oid: lst = [int(x) for x in oid.strip(b".").split(b".")] @@ -582,11 +537,12 @@ def do_dec(cls, l, s = BER_num_dec(s) lst.append(l) if lst: - # X.690 sect 8.19.4 - lst.insert(0, lst[0] // 40) - lst[1] %= 40 + from scapy.asn1.oid import oid_subidentifiers_to_dotted + oid = oid_subidentifiers_to_dotted(lst) + else: + oid = b"" return ( - cls.asn1_object(b".".join(str(k).encode('ascii') for k in lst)), + cls.asn1_object(oid), t, ) @@ -705,6 +661,7 @@ class BERcodec_IPADDRESS(BERcodec_STRING): @classmethod def enc(cls, ipaddr_ascii, size_len=0, **_kwargs): # type: ignore[override] # type: (str, Optional[int], **Any) -> bytes + size_len = 0 if size_len is None else int(size_len) try: s = inet_aton(ipaddr_ascii) except Exception: diff --git a/scapy/asn1/compound.py b/scapy/asn1/compound.py new file mode 100644 index 00000000000..ab7c56e71c1 --- /dev/null +++ b/scapy/asn1/compound.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""Shared helpers for ASN.1 compound-type encode/decode. + +Codec-specific SEQUENCE / CHOICE / SEQUENCE OF / PACKET hooks live in +``compound_ber``, ``compound_oer``, and ``compound_uper``. Those modules +are bound as methods on the encoder/decoder contexts in +``scapy.asn1.context``. +""" + +from typing import Any, Callable, List + + +def sequence_decode_children(field, pkt, presence, dissect): + # type: (Any, Any, List[bool], Callable[[Any], None]) -> None + from scapy.asn1fields import ASN1F_badsequence, ASN1F_optional + + opt_index = 0 + for obj in field.seq: + if isinstance(obj, ASN1F_optional): + if not presence[opt_index]: + obj.set_missing(pkt) + opt_index += 1 + continue + opt_index += 1 + try: + dissect(obj) + except ASN1F_badsequence: + break diff --git a/scapy/asn1/compound_ber.py b/scapy/asn1/compound_ber.py new file mode 100644 index 00000000000..3793bb8262d --- /dev/null +++ b/scapy/asn1/compound_ber.py @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""BER compound-type encode/decode hooks (SEQUENCE, CHOICE, SEQUENCE OF, PACKET). + +Context-first signatures so these functions can be bound on +``BER_Encoder`` / ``BER_Decoder``. OER reuses the PACKET hooks via +inheritance. +""" + +from typing import Any + +from scapy.asn1.asn1 import ( + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_Error, + ASN1_Object, +) + + +# ---- SEQUENCE ------------------------------------------------------------- + +def ber_sequence_encode_to(enc, field, pkt): + # type: (Any, Any, Any) -> None + # Encode children into a nested context, then wrap as one SEQUENCE TLV. + child_enc = type(enc)(codec=enc.codec) + for obj in field.seq: + obj.encode_to(pkt, child_enc) + enc.write(field.i2m(pkt, child_enc.finish())) + + +def ber_sequence_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + s = dec.remaining() + s = field._apply_tagging_dec(s, pkt, _fname=pkt.name) + from scapy.asn1.ber import BER_Decoding_Error + codec = field.ASN1_tag.get_codec(ASN1_Codecs.BER) + _i, s, remain = codec.check_type_check_len(s) + s = field._dissect_sequence_children(pkt, s) + if len(s) > 0: + raise BER_Decoding_Error( + "unexpected remainder in %s" % pkt.name, + remaining=s, + ) + dec.set_remainder(remain) + + +# ---- SEQUENCE OF ---------------------------------------------------------- + +def ber_sequence_of_encode_to(enc, field, pkt): + # type: (Any, Any, Any) -> None + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + s = val # type: Any + elif val is None: + s = b"" + elif field.holds_packets: + s = b"".join(bytes(i) for i in val) + else: + s = b"".join(field.fld.i2m(pkt, i) for i in val) + enc.write(field.i2m(pkt, s)) + + +def ber_sequence_of_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + s = dec.remaining() + s = field._apply_tagging_dec(s, pkt) + codec = field.ASN1_tag.get_codec(ASN1_Codecs.BER) + _i, s, remain = codec.check_type_check_len(s) + lst = [] + while s: + c, s = field._extract_packet(s, pkt) + if c: + lst.append(c) + field.set_val(pkt, lst) + dec.set_remainder(remain) + + +# ---- CHOICE ------------------------------------------------------------- + +def ber_choice_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, field.name) + if value is None: + s = b"" + else: + if isinstance(value, ASN1_Object): + s = value.enc(pkt.ASN1_codec) + else: + s = bytes(value) + if type(value) in field.pktchoices: + imp, exp = field.pktchoices[type(value)] + s = field._tagging_enc(pkt, s, implicit_tag=imp, explicit_tag=exp) + _imp, exp = field._tagging_tags(pkt) + enc.write(field._tagging_enc(pkt, s, explicit_tag=exp)) + + +def ber_choice_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.ber import BER_id_dec + from scapy.asn1fields import ASN1F_field + + s = dec.remaining() + if len(s) == 0: + raise ASN1_Error("ASN1F_CHOICE: got empty string") + s = field._apply_tagging_dec(s, pkt) + tag, _ = BER_id_dec(s) + if tag in field.choices: + choice = field.choices[tag] + elif field.flexible_tag: + choice = ASN1F_field + else: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + field.name, tag, list(field.choices.keys()) + ) + ) + if hasattr(choice, "ASN1_root"): + val, remain = field.extract_packet( + choice, s, _underlayer=pkt, _parent=pkt, + ) + elif isinstance(choice, type): + val, remain = choice(field.name, b"").m2i(pkt, s) + else: + val, remain = choice.m2i(pkt, s) + field.set_val(pkt, val) + dec.set_remainder(remain) + + +# ---- PACKET (nested ASN1_Packet; also used by OER) ------------------------ + +def ber_packet_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, field.name) + if value is None: + s = b"" + elif isinstance(value, bytes): + s = value + elif isinstance(value, ASN1_Object): + s = bytes(value.val) if value.val else b"" + else: + s = bytes(value) + from scapy.asn1packet import ASN1_Packet as _ASN1_Packet + if not isinstance(value, _ASN1_Packet): + enc.write(s) + return + imp, exp = field._tagging_tags(pkt) + enc.write(field._tagging_enc(pkt, s, implicit_tag=imp, explicit_tag=exp)) + + +def ber_packet_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + cls = (field.next_cls_cb(pkt) or field.cls) if field.next_cls_cb else field.cls + from scapy.asn1packet import ASN1_Packet as _ASN1_Packet + s = dec.remaining() + if not issubclass(cls, _ASN1_Packet): + val, remain = field.extract_packet( + cls, s, _underlayer=pkt, _parent=pkt, + ) + field.set_val(pkt, val) + dec.set_remainder(remain) + return + s = field._apply_tagging_dec( + s, pkt, + hidden_tag=cls.ASN1_root.ASN1_tag, + _fname=field.name, + ) + if not s: + field.set_val(pkt, None) + dec.set_remainder(s) + return + val, remain = field.extract_packet(cls, s, _underlayer=pkt, _parent=pkt) + field.set_val(pkt, val) + dec.set_remainder(remain) diff --git a/scapy/asn1/compound_oer.py b/scapy/asn1/compound_oer.py new file mode 100644 index 00000000000..958ea6afb59 --- /dev/null +++ b/scapy/asn1/compound_oer.py @@ -0,0 +1,181 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""OER compound-type encode/decode hooks (SEQUENCE, CHOICE, SEQUENCE OF). + +Context-first signatures so these functions can be bound on +``OER_Encoder`` / ``OER_Decoder``. PACKET encoding is inherited from BER. +""" + +from typing import Any, List, Tuple + +from scapy.asn1.asn1 import ( + ASN1_Class_UNIVERSAL, + ASN1_Error, + ASN1_Object, +) +from scapy.asn1.compound import sequence_decode_children + + +def read_oer_presence_bits(s, field): + # type: (bytes, Any) -> Tuple[List[bool], bytes] + from scapy.asn1.oer import OER_Decoding_Error, _OER_check_len + + number_of_optionals = len(field.optionals) + number_of_bits = ( + (1 if field.constraints.extensible else 0) + number_of_optionals + ) + if number_of_bits == 0: + return [], s + number_of_bytes = (number_of_bits + 7) // 8 + _OER_check_len("ASN1F_SEQUENCE", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + bits = [ + bool((value >> (8 * number_of_bytes - 1 - i)) & 1) + for i in range(number_of_bits) + ] + if field.constraints.extensible: + if bits[0]: + raise OER_Decoding_Error( + "ASN1F_SEQUENCE: extension additions are not supported", + remaining=s, + ) + bits = bits[1:] + return bits, s[number_of_bytes:] + + +def write_oer_presence_bits(bits): + # type: (List[int]) -> bytes + if not bits: + return b"" + number_of_bytes = (len(bits) + 7) // 8 + value = 0 + for bit in bits: + value = (value << 1) | bit + value <<= 8 * number_of_bytes - len(bits) + return value.to_bytes(number_of_bytes, "big") + + +# ---- SEQUENCE ------------------------------------------------------------- + +def oer_sequence_encode_to(enc, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_optional + + bits = [0] if field.constraints.extensible else [] + bits += [1 if opt.is_present(pkt) else 0 for opt in field.optionals] + enc.write(write_oer_presence_bits(bits)) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and not obj.is_present(pkt): + continue + obj.encode_to(pkt, enc) + + +def oer_sequence_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + s = dec.remaining() + s = field._apply_tagging_dec(s, pkt, _fname=pkt.name) + presence, s = read_oer_presence_bits(s, field) + child_dec = type(dec)(s) + sequence_decode_children( + field, pkt, presence, + lambda obj: obj.decode_from(pkt, child_dec), + ) + dec.set_remainder(child_dec.remaining()) + + +# ---- SEQUENCE OF ---------------------------------------------------------- + +def oer_sequence_of_encode_to(enc, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.oer import OER_unsigned_integer_enc + + val = getattr(pkt, field.name) + if isinstance(val, ASN1_Object) and val.tag == ASN1_Class_UNIVERSAL.RAW: + enc.write(field.i2m(pkt, val)) + return + items = val or [] + parts = [OER_unsigned_integer_enc(len(items))] + parts.extend( + bytes(item) if field.holds_packets else field.fld.i2m(pkt, item) + for item in items + ) + enc.write(field.i2m(pkt, b"".join(parts))) + + +def oer_sequence_of_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.oer import OER_unsigned_integer_dec + + s = field._apply_tagging_dec(dec.remaining(), pkt) + count, s = OER_unsigned_integer_dec(s) + lst = [] + for _ in range(count): + c, s = field._extract_packet(s, pkt) + if c: + lst.append(c) + field.set_val(pkt, lst) + dec.set_remainder(s) + + +# ---- CHOICE ------------------------------------------------------------- + +def oer_choice_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.oer import OER_tag_enc, OER_tag_parts + + if value is None: + value = getattr(pkt, field.name) + if value is None: + s = b"" + else: + if isinstance(value, ASN1_Object): + s = value.enc(pkt.ASN1_codec) + else: + s = bytes(value) + tag = field.alternative_tag(value) + if tag is not None: + tag_class, tag_number = OER_tag_parts(tag) + s = OER_tag_enc(tag_number, tag_class) + s + enc.write(field._tagging_enc(pkt, s, explicit_tag=field.explicit_tag)) + + +def oer_choice_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_field + from scapy.asn1.oer import OER_tag_dec, OER_tag_parts + + s = field._apply_tagging_dec(dec.remaining(), pkt) + tag_class, tag_number, payload = OER_tag_dec(s) + choice = None + for key, alternative in field.choices.items(): + if OER_tag_parts(key) == (tag_class, tag_number): + choice = alternative + break + if choice is None: + if not field.flexible_tag: + raise ASN1_Error( + "ASN1F_CHOICE: unexpected field in '%s' " + "(tag %s not in possible tags %s)" % ( + field.name, tag_class | tag_number, + list(field.choices.keys()) + ) + ) + choice = ASN1F_field + if hasattr(choice, "ASN1_root"): + val, remain = field.extract_packet( + choice, payload, _underlayer=pkt, _parent=pkt, + ) + elif isinstance(choice, type): + val, remain = choice(field.name, b"").m2i(pkt, payload) + else: + cls = ( + (choice.next_cls_cb(pkt) or choice.cls) + if choice.next_cls_cb else choice.cls + ) + val, remain = field.extract_packet( + cls, payload, _underlayer=pkt, _parent=pkt, + ) + field.set_val(pkt, val) + dec.set_remainder(remain) diff --git a/scapy/asn1/compound_uper.py b/scapy/asn1/compound_uper.py new file mode 100644 index 00000000000..b9b9d5e1be4 --- /dev/null +++ b/scapy/asn1/compound_uper.py @@ -0,0 +1,254 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""UPER compound-type encode/decode hooks (SEQUENCE, CHOICE, SEQUENCE OF, PACKET). + +Context-first signatures so these functions can be bound on +``UPER_EncoderContext`` / ``UPER_DecoderContext``. Imports of +``scapy.asn1.uper`` stay lazy so BER/OER paths do not pull UPER in. +""" + +from typing import Any + +from scapy.asn1.asn1 import ASN1_Error, ASN1_Object +from scapy.asn1.compound import sequence_decode_children + + +# ---- SEQUENCE ------------------------------------------------------------- + +def uper_sequence_encode_to(enc, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1fields import ASN1F_optional + + bit_enc = enc.bit_encoder + if field.constraints.extensible: + bit_enc.append_bit(0) + for opt in field.optionals: + bit_enc.append_bit(1 if opt.is_present(pkt) else 0) + for obj in field.seq: + if isinstance(obj, ASN1F_optional) and not obj.is_present(pkt): + continue + obj.encode_to(pkt, enc) + + +def uper_sequence_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.uper import UPER_Decoding_Error + + bit_dec = dec.bit_decoder + if field.constraints.extensible: + if bit_dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_SEQUENCE: extension additions are not supported" + ) + presence = [bit_dec.read_bit() for _ in field.optionals] + sequence_decode_children( + field, pkt, presence, + lambda obj: obj.decode_from(pkt, dec), + ) + + +# ---- SEQUENCE OF ---------------------------------------------------------- + +def uper_sequence_of_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.uper import UPER_Encoding_Error, UPER_constrained_int_enc + from scapy.asn1fields import ( + ASN1F_CHOICE, + ASN1F_PACKET, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ) + + if ( + not field.holds_packets and + isinstance(field.fld, (ASN1F_SEQUENCE, ASN1F_CHOICE, ASN1F_SEQUENCE_OF)) + ): + raise UPER_Encoding_Error( + "ASN1F_SEQUENCE_OF: compound ASN1F_field elements are not " + "supported in UPER; use an ASN1_Packet for structured items" + ) + + bit_enc = enc.bit_encoder + if value is None: + value = getattr(pkt, field.name) + if value is None: + value = [] + count = len(value) + + def append_items(offset, size): + # type: (int, int) -> None + for i in range(offset, offset + size): + item = value[i] + if field.holds_packets: + item.ASN1_root.encode_to(item, enc) + elif isinstance(field.fld, ASN1F_PACKET): + enc.encode_packet(field.fld, pkt, item) + else: + field.fld.encode_into(bit_enc, pkt, item) + + uper_min, uper_max = field.constraints.minimum, field.constraints.maximum + if field.constraints.extensible: + if ( + uper_min is not None and uper_max is not None and + uper_min <= count <= uper_max + ): + bit_enc.append_bit(0) + else: + bit_enc.append_bit(1) + bit_enc.append_fragmented(count, append_items) + return + if uper_min is not None and uper_max is not None: + UPER_constrained_int_enc(bit_enc, count, uper_min, uper_max) + append_items(0, count) + else: + bit_enc.append_fragmented(count, append_items) + + +def uper_sequence_of_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.uper import UPER_Decoding_Error, UPER_constrained_int_dec + from scapy.asn1fields import ( + ASN1F_CHOICE, + ASN1F_PACKET, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ) + + if ( + not field.holds_packets and + isinstance(field.fld, (ASN1F_SEQUENCE, ASN1F_CHOICE, ASN1F_SEQUENCE_OF)) + ): + raise UPER_Decoding_Error( + "ASN1F_SEQUENCE_OF: compound ASN1F_field elements are not " + "supported in UPER; use an ASN1_Packet for structured items" + ) + + bit_dec = dec.bit_decoder + lst = [] + + def read_items(count): + # type: (int) -> None + for _ in range(count): + if field.holds_packets: + p = field.cls() + p.add_underlayer(pkt) + p.add_parent(pkt) + p.ASN1_root.decode_from(p, dec) + lst.append(p) + elif isinstance(field.fld, ASN1F_PACKET): + lst.append(uper_packet_decode_from_decoder(field.fld, pkt, dec)) + else: + lst.append(field.fld.m2i_from_decoder(pkt, bit_dec)) + + if field.constraints.extensible and bit_dec.read_bit(): + bit_dec.read_fragmented(read_items) + else: + uper_min, uper_max = field.constraints.minimum, field.constraints.maximum + if uper_min is not None and uper_max is not None: + read_items(UPER_constrained_int_dec(bit_dec, uper_min, uper_max)) + else: + bit_dec.read_fragmented(read_items) + field.set_val(pkt, lst) + + +# ---- CHOICE ------------------------------------------------------------- + +def uper_choice_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + from scapy.asn1.uper import UPER_choice_index_enc + + bit_enc = enc.bit_encoder + if value is None: + value = getattr(pkt, field.name) + if value is None: + return + tag = field.alternative_tag(value) + if tag is None: + raise ASN1_Error( + "ASN1F_CHOICE: cannot encode unknown alternative in '%s'" % + field.name + ) + if field.constraints.extensible: + bit_enc.append_bit(0) + order = field.canonical_order + canon_idx = field.canonical_index[tag] + if len(order) > 1: + UPER_choice_index_enc(bit_enc, canon_idx, len(order)) + choice = order[canon_idx] + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + value.ASN1_root.encode_to(value, enc) + elif hasattr(choice, "cls"): + uper_packet_encode_to(enc, choice, pkt, value) + elif isinstance(choice, type): + choice(field.name, b"").encode_into(bit_enc, pkt, value) + else: + choice.encode_into(bit_enc, pkt, value) + + +def uper_choice_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + from scapy.asn1.uper import UPER_Decoding_Error, UPER_choice_index_dec + + bit_dec = dec.bit_decoder + if field.constraints.extensible: + if bit_dec.read_bit(): + raise UPER_Decoding_Error( + "ASN1F_CHOICE: extension additions are not supported" + ) + order = field.canonical_order + if len(order) > 1: + index = UPER_choice_index_dec(bit_dec, len(order)) + else: + index = 0 + if index >= len(order): + raise ASN1_Error( + "ASN1F_CHOICE: unexpected index %s in '%s'" % + (index, field.name) + ) + choice = order[index] + if isinstance(choice, type) and hasattr(choice, "ASN1_root"): + p = choice() + p.add_underlayer(pkt) + p.add_parent(pkt) + p.ASN1_root.decode_from(p, dec) + field.set_val(pkt, p) + return + if hasattr(choice, "cls"): + field.set_val(pkt, uper_packet_decode_from_decoder(choice, pkt, dec)) + return + if isinstance(choice, type): + field.set_val( + pkt, choice(field.name, b"").m2i_from_decoder(pkt, bit_dec), + ) + return + field.set_val(pkt, choice.m2i_from_decoder(pkt, bit_dec)) + + +# ---- PACKET --------------------------------------------------------------- + +def uper_packet_encode_to(enc, field, pkt, value=None): + # type: (Any, Any, Any, Any) -> None + if value is None: + value = getattr(pkt, field.name) + if value is None: + return + if isinstance(value, ASN1_Object): + value = value.val + value.ASN1_root.encode_to(value, enc) + + +def uper_packet_decode_from(dec, field, pkt): + # type: (Any, Any, Any) -> None + field.set_val(pkt, uper_packet_decode_from_decoder(field, pkt, dec)) + + +def uper_packet_decode_from_decoder(field, pkt, dec): + # type: (Any, Any, Any) -> Any + cls = (field.next_cls_cb(pkt) or field.cls) if field.next_cls_cb else field.cls + p = cls() + p.add_underlayer(pkt) + p.add_parent(pkt) + p.ASN1_root.decode_from(p, dec) + return p diff --git a/scapy/asn1/constraints.py b/scapy/asn1/constraints.py new file mode 100644 index 00000000000..8614e20a6bd --- /dev/null +++ b/scapy/asn1/constraints.py @@ -0,0 +1,168 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""Codec-neutral ASN.1 schema constraints. + +``minimum`` / ``maximum`` mean a value range for INTEGER and ENUMERATED +fields, and a SIZE constraint for string and BIT STRING fields. +``size_len`` is a fixed SIZE (octets or bits) used when both bounds coincide. +``extensible`` marks an extension marker on the constraint. +``unsigned`` selects unsigned INTEGER encoding where the codec supports it. +""" + +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Tuple + + +@dataclass(frozen=True) +class ASN1Constraints: + minimum: Optional[int] = None + maximum: Optional[int] = None + extensible: bool = False + unsigned: bool = False + + +_SUPPORTED_CONSTRAINTS = { + "minimum", + "maximum", + "extensible", + "unsigned", +} + + +def normalize_constraints(codec_opts): + # type: (Dict[str, Any]) -> ASN1Constraints + """Build ASN1Constraints from field kwargs.""" + for key in codec_opts: + if key not in _SUPPORTED_CONSTRAINTS: + raise TypeError("Unknown field constraint %r" % key) + return ASN1Constraints(**codec_opts) + + +def resolve_uper_int_bounds(field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + unsigned=None, # type: Optional[bool] + extensible=None # type: Optional[bool] + ): + # type: (...) -> Tuple[Optional[int], Optional[int], bool] + """Resolve UPER INTEGER root range and extensibility from field/kwargs. + + When no explicit ``minimum``/``maximum`` is set, a fixed ``size_len`` of + 1, 2, 4, or 8 with ``unsigned=True`` implies ``0 .. 256**n - 1``. + """ + if size_len is None and field is not None: + size_len = field.size_len + if minimum is None and maximum is None and field is not None: + minimum, maximum = field.constraints.minimum, field.constraints.maximum + if unsigned is None: + unsigned = bool(field.constraints.unsigned) if field is not None else False + if extensible is None: + extensible = bool(field.constraints.extensible) if field is not None else False + if minimum is None and maximum is None: + if size_len in (1, 2, 4, 8) and unsigned: + minimum, maximum = 0, (256 ** size_len) - 1 + return minimum, maximum, extensible + + +def resolve_uper_size_bounds(field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + extensible=None # type: Optional[bool] + ): + # type: (...) -> Tuple[Optional[int], Optional[int], bool] + """Resolve UPER SIZE bounds; ``size_len`` is a fixed SIZE.""" + if size_len is None and field is not None: + size_len = field.size_len + if minimum is None and maximum is None and field is not None: + minimum, maximum = field.constraints.minimum, field.constraints.maximum + if extensible is None: + extensible = bool(field.constraints.extensible) if field is not None else False + if size_len: + return size_len, size_len, extensible + return minimum, maximum, extensible + + +def resolve_oer_size_bounds(field=None, size_len=None): + # type: (Any, Optional[int]) -> Tuple[Optional[int], Optional[int]] + """Resolve OER SIZE bounds from ``size_len`` or field constraints.""" + if size_len is None and field is not None: + size_len = field.size_len + # ``size_len=0`` means unset (same as the historical ``if size_len:`` check). + if size_len: + return size_len, size_len + if field is not None: + return field.constraints.minimum, field.constraints.maximum + return None, None + + +def uper_enum_values(field=None, values=None): + # type: (Any, Optional[List[int]]) -> Optional[List[int]] + if values is not None: + return values + if field is not None and hasattr(field, "uper_enum_values"): + return field.uper_enum_values() + return None + + +def oer_int_wire_params(field=None, size_len=None, unsigned=None): + # type: (Any, Optional[int], Optional[bool]) -> Tuple[Optional[int], bool, Optional[int], Optional[int]] # noqa: E501 + """Derive OER INTEGER width and signedness from field constraints. + + Per X.696 sections 10.3-10.4, extensible integer constraints are encoded + as unbounded. A nonnegative lower bound without a fitting fixed upper + bound uses variable-width unsigned encoding. A fixed eight-octet width + is used only when ``maximum <= 2**64 - 1``. + """ + if size_len is None and field is not None: + size_len = field.size_len + if unsigned is None: + unsigned = bool(field.constraints.unsigned) if field is not None else False + if field is not None: + minimum, maximum = field.constraints.minimum, field.constraints.maximum + extensible = bool(field.constraints.extensible) + else: + minimum, maximum = None, None + extensible = False + + # Extension values may lie outside the root range. + val_min = None if extensible else minimum + val_max = None if extensible else maximum + + if size_len is not None: + if (not unsigned and minimum is not None and minimum >= 0 and + not extensible): + unsigned = True + return size_len, unsigned, val_min, val_max + + if extensible: + return None, unsigned, None, None + + if minimum is not None and minimum >= 0: + unsigned = True + if maximum is not None: + if maximum <= 0xFF: + size_len = 1 + elif maximum <= 0xFFFF: + size_len = 2 + elif maximum <= 0xFFFFFFFF: + size_len = 4 + elif maximum <= 0xFFFFFFFFFFFFFFFF: + size_len = 8 + # else: range exceeds 2^64-1 → variable unsigned + elif minimum is not None and maximum is not None: + unsigned = False + for sl, lo, hi in ( + (1, -128, 127), + (2, -32768, 32767), + (4, -2147483648, 2147483647), + (8, -9223372036854775808, 9223372036854775807), + ): + if minimum >= lo and maximum <= hi: + size_len = sl + break + + return size_len, unsigned, val_min, val_max diff --git a/scapy/asn1/context.py b/scapy/asn1/context.py new file mode 100644 index 00000000000..09adea7e1fb --- /dev/null +++ b/scapy/asn1/context.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""ASN.1 encoder and decoder contexts.""" + +from typing import Any + +from scapy.asn1.asn1 import ASN1_Codecs +from scapy.asn1.compound_ber import ( + ber_sequence_encode_to, ber_sequence_decode_from, + ber_sequence_of_encode_to, ber_sequence_of_decode_from, + ber_choice_encode_to, ber_choice_decode_from, + ber_packet_encode_to, ber_packet_decode_from, +) +from scapy.asn1.compound_oer import ( + oer_sequence_encode_to, oer_sequence_decode_from, + oer_sequence_of_encode_to, oer_sequence_of_decode_from, + oer_choice_encode_to, oer_choice_decode_from, +) +from scapy.asn1.compound_uper import ( + uper_sequence_encode_to, uper_sequence_decode_from, + uper_sequence_of_encode_to, uper_sequence_of_decode_from, + uper_choice_encode_to, uper_choice_decode_from, + uper_packet_encode_to, uper_packet_decode_from, +) + + +class ASN1Encoder(object): + codec = None # type: Any + + def finish(self): + # type: () -> bytes + raise NotImplementedError + + +class ASN1Decoder(object): + codec = None # type: Any + + def remaining(self): + # type: () -> bytes + raise NotImplementedError + + +class BER_Encoder(ASN1Encoder): + codec = ASN1_Codecs.BER + encode_sequence = ber_sequence_encode_to + encode_sequence_of = ber_sequence_of_encode_to + encode_choice = ber_choice_encode_to + encode_packet = ber_packet_encode_to + + def __init__(self, codec=None): + # type: (Any) -> None + self.codec = codec or self.codec + self._parts = [] # type: list[bytes] + + def write(self, data): + # type: (bytes) -> None + self._parts.append(data) + + def finish(self): + # type: () -> bytes + return b"".join(self._parts) + + +class BER_Decoder(ASN1Decoder): + codec = ASN1_Codecs.BER + decode_sequence = ber_sequence_decode_from + decode_sequence_of = ber_sequence_of_decode_from + decode_choice = ber_choice_decode_from + decode_packet = ber_packet_decode_from + + def __init__(self, data, codec=None): + # type: (bytes, Any) -> None + self.codec = codec or self.codec + self._data = data + + def remaining(self): + # type: () -> bytes + return self._data + + def set_remainder(self, remainder): + # type: (bytes) -> None + self._data = remainder + + +class OER_Encoder(BER_Encoder): + codec = ASN1_Codecs.OER + encode_sequence = oer_sequence_encode_to + encode_sequence_of = oer_sequence_of_encode_to + encode_choice = oer_choice_encode_to + + +class OER_Decoder(BER_Decoder): + codec = ASN1_Codecs.OER + decode_sequence = oer_sequence_decode_from + decode_sequence_of = oer_sequence_of_decode_from + decode_choice = oer_choice_decode_from + + +class UPER_EncoderContext(ASN1Encoder): + codec = ASN1_Codecs.PER + encode_sequence = uper_sequence_encode_to + encode_sequence_of = uper_sequence_of_encode_to + encode_choice = uper_choice_encode_to + encode_packet = uper_packet_encode_to + + def __init__(self): + # type: () -> None + # Lazy: keep BER/OER paths from importing scapy.asn1.uper. + from scapy.asn1.uper import UPER_Encoder + self.bit_encoder = UPER_Encoder() + + def finish(self): + # type: () -> bytes + return self.bit_encoder.as_bytes() + + +class UPER_DecoderContext(ASN1Decoder): + codec = ASN1_Codecs.PER + decode_sequence = uper_sequence_decode_from + decode_sequence_of = uper_sequence_of_decode_from + decode_choice = uper_choice_decode_from + decode_packet = uper_packet_decode_from + + def __init__(self, data): + # type: (bytes) -> None + from scapy.asn1.uper import UPER_Decoder + self.bit_decoder = UPER_Decoder(data) + + def remaining(self): + # type: () -> bytes + return self.bit_decoder.remaining_bytes() + + +def new_encoder(codec): + # type: (Any) -> ASN1Encoder + if codec is ASN1_Codecs.PER: + return UPER_EncoderContext() + if codec is ASN1_Codecs.OER: + return OER_Encoder() + return BER_Encoder(codec=codec) + + +def new_decoder(codec, data): + # type: (Any, bytes) -> ASN1Decoder + if codec is ASN1_Codecs.PER: + return UPER_DecoderContext(data) + if codec is ASN1_Codecs.OER: + return OER_Decoder(data) + return BER_Decoder(data, codec=codec) diff --git a/scapy/asn1/intutil.py b/scapy/asn1/intutil.py new file mode 100644 index 00000000000..9144ad28115 --- /dev/null +++ b/scapy/asn1/intutil.py @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""Shared two's-complement INTEGER helpers (X.690 / X.691 / X.696).""" + +from typing import Tuple + + +def twos_complement_octets(value): + # type: (int) -> Tuple[int, int] + """Shortest two's-complement width and unsigned payload. + + A negative value needs one bit less than its magnitude suggests, as + ``-2**(8n-1)`` still fits in ``n`` octets, hence the increment before + measuring (X.691 11.4 / X.696 10.4). + """ + magnitude = value + 1 if value < 0 else value + number_of_bytes = (magnitude.bit_length() + 8) // 8 + masked = value & ((1 << (8 * number_of_bytes)) - 1) + return number_of_bytes, masked + + +def from_twos_complement(masked, number_of_bytes): + # type: (int, int) -> int + """Interpret ``masked`` as a ``number_of_bytes``-octet two's complement.""" + sign_bit = 1 << (8 * number_of_bytes - 1) + if masked & sign_bit: + return masked - (1 << (8 * number_of_bytes)) + return masked diff --git a/scapy/asn1/oer.py b/scapy/asn1/oer.py new file mode 100644 index 00000000000..840f2e5d7bb --- /dev/null +++ b/scapy/asn1/oer.py @@ -0,0 +1,795 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Octet Encoding Rules (OER) for ASN.1 + +Basic-OER as specified in ITU-T X.696 | ISO/IEC 8825-7. + +``ASN1F_SEQUENCE`` emits the preamble required by 16.2.2: a presence bit per +``ASN1F_optional``/``ASN1F_DEFAULT`` component, preceded by an extension bit +for sequences declared with ``extensible=True``. Fixed size constraints +are expressed with ``size_len=`` (octets for strings, bits for BIT STRING). + +Tags declared on a field are not encoded: OER only puts a tag on the wire for +the chosen alternative of an ``ASN1F_CHOICE`` (20.2), so the ``implicit_tag=`` +and ``explicit_tag=`` of the alternatives are what selects it. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the canonical variant (C-OER). +""" + +import struct + +from scapy.compat import chb, bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.tag import asn1_tag_parts +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1Codec_metaclass, + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# DEFAULT components are described by the sequence preamble in OER/PER. + +from typing import ( + Any, + AnyStr, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, +) + +################## +# OER encoding # +################## + + +class OER_Encoding_Error(ASN1_Encoding_Error): + pass + + +class OER_Decoding_Error(ASN1_Decoding_Error): + pass + + +# OER tag classes (bits 8-7 of the first identifier octet) +OER_CLASS_UNIVERSAL = 0x00 +OER_CLASS_APPLICATION = 0x40 +OER_CLASS_CONTEXT = 0x80 +OER_CLASS_PRIVATE = 0xc0 + + +def _OER_check_len(name, s, number_of_bytes, offset=0): + # type: (str, bytes, int, int) -> None + """Raise unless s carries number_of_bytes octets past its first offset.""" + available = len(s) - offset + if available < number_of_bytes: + raise OER_Decoding_Error( + "%s: Got %i bytes while expecting %i" % + (name, available, number_of_bytes), + remaining=s + ) + + +def OER_len_enc(ll): + # type: (int) -> bytes + if ll < 128: + return chb(ll) + number_of_bytes = (ll.bit_length() + 7) // 8 + if number_of_bytes > 127: + raise OER_Encoding_Error( + "OER_len_enc: Length too long (%i) to be encoded" % + number_of_bytes + ) + return chb(0x80 | number_of_bytes) + ll.to_bytes(number_of_bytes, "big") + + +def OER_len_dec(s): + # type: (bytes) -> Tuple[int, bytes] + if not s: + raise OER_Decoding_Error("OER_len_dec: got empty string", remaining=s) + tmp_len = s[0] + if not tmp_len & 0x80: + return tmp_len, s[1:] + tmp_len &= 0x7f + _OER_check_len("OER_len_dec", s, tmp_len, offset=1) + ll = int.from_bytes(s[1:tmp_len + 1], "big") + return ll, s[tmp_len + 1:] + + +def OER_signed_integer_enc(i): + # type: (int) -> bytes + from scapy.asn1.intutil import twos_complement_octets + number_of_bytes, value = twos_complement_octets(i) + return OER_len_enc(number_of_bytes) + value.to_bytes(number_of_bytes, "big") + + +def OER_signed_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + from scapy.asn1.intutil import from_twos_complement + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_signed_integer_dec", s, number_of_bytes) + if number_of_bytes == 0: + raise OER_Decoding_Error( + "OER_signed_integer_dec: got an empty length determinant", + remaining=s + ) + value = int.from_bytes(s[:number_of_bytes], "big") + return from_twos_complement(value, number_of_bytes), s[number_of_bytes:] + + +def OER_unsigned_integer_enc(i): + # type: (int) -> bytes + if i < 0: + raise OER_Encoding_Error( + "OER_unsigned_integer_enc: %i is negative" % i + ) + number_of_bits = max(i.bit_length(), 1) + number_of_bytes = (number_of_bits + 7) // 8 + return OER_len_enc(number_of_bytes) + i.to_bytes(number_of_bytes, "big") + + +def OER_unsigned_integer_dec(s): + # type: (bytes) -> Tuple[int, bytes] + number_of_bytes, s = OER_len_dec(s) + _OER_check_len("OER_unsigned_integer_dec", s, number_of_bytes) + value = int.from_bytes(s[:number_of_bytes], "big") + return value, s[number_of_bytes:] + + +def OER_tag_enc(n, tag_class=OER_CLASS_CONTEXT): + # type: (int, int) -> bytes + if n < 63: + return chb(tag_class | n) + tag = bytearray([tag_class | 0x3f]) + encoded = [] + value = n + while value > 0: + encoded.append(0x80 | (value & 0x7f)) + value >>= 7 + encoded[0] &= 0x7f + encoded.reverse() + tag.extend(encoded) + return bytes(tag) + + +def OER_tag_dec(s): + # type: (bytes) -> Tuple[int, int, bytes] + if not s: + raise OER_Decoding_Error("OER_tag_dec: got empty string", remaining=s) + first = s[0] + tag_class = first & 0xc0 + tag_number = first & 0x3f + if tag_number != 0x3f: + return tag_class, tag_number, s[1:] + tag_number = 0 + i = 1 + while i < len(s): + c = s[i] + tag_number <<= 7 + tag_number |= c & 0x7f + i += 1 + if not (c & 0x80): + break + else: + raise OER_Decoding_Error("OER_tag_dec: unfinished tag", remaining=s) + return tag_class, tag_number, s[i:] + + +def OER_tag_parts(identifier): + # type: (int) -> Tuple[int, int] + # X.696 8.7 only keeps the class and the number, so the constructed flag + # must not leak into the encoded tag number. + tag_class, tag_number, _constructed = asn1_tag_parts(identifier) + return tag_class, tag_number + + +_K = TypeVar('_K') + + +class OERcodec_Object(Generic[_K], metaclass=ASN1Codec_metaclass): + codec = ASN1_Codecs.OER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + @classmethod + def check_string(cls, s): + # type: (bytes) -> None + if not s: + raise OER_Decoding_Error( + "%s: Got empty object while expecting %r" % + (cls.__name__, cls.tag), remaining=s + ) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + raise OER_Decoding_Error( + "%s: Cannot decode unknown OER type without context" % + cls.__name__, remaining=s + ) + + @classmethod + def dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + field=None, # type: Any + pkt=None, # type: Any + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + if field is not None: + _kwargs["field"] = field + if pkt is not None: + _kwargs["pkt"] = pkt + if not safe: + return cls.do_dec( + s, context=context, safe=safe, **_kwargs, + ) + try: + return cls.do_dec( + s, context=context, safe=safe, **_kwargs, + ) + except ASN1_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + field=None, # type: Any + pkt=None, # type: Any + **_kwargs # type: Any + ): + # type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] + return cls.dec( + s, context, safe=True, + field=field, pkt=pkt, + **_kwargs, + ) + + @classmethod + def enc(cls, s, field=None, pkt=None, size_len=None, **_kwargs): + # type: (_K, Any, Any, Optional[int], **Any) -> bytes + if isinstance(s, (str, bytes)): + return OERcodec_STRING.enc( + s, field=field, pkt=pkt, size_len=size_len, + ) + else: + try: + return OERcodec_INTEGER.enc( + int(s), field=field, pkt=pkt, size_len=size_len, + ) # type: ignore + except TypeError: + raise TypeError("Trying to encode an invalid value !") + + +# Tags declared on a field are not encoded for OER components (X.696); +# CHOICE writes its own alternative tags. Identity tagging is the ASN1Codec +# default when no tagging_enc/dec is registered. +ASN1_Codecs.OER.register_stem(OERcodec_Object) + + +########################## +# OERcodec objects # +########################## + +class OERcodec_INTEGER(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + _FIXED_FORMATS = { + True: {1: ">b", 2: ">h", 4: ">i", 8: ">q"}, + False: {1: ">B", 2: ">H", 4: ">I", 8: ">Q"}, + } + + @classmethod + def enc(cls, i, field=None, size_len=None, oer_unsigned=None, **_kwargs): + # type: (int, Any, Optional[int], Optional[bool], **Any) -> bytes + from scapy.asn1.constraints import oer_int_wire_params + size_len, oer_unsigned, minimum, maximum = oer_int_wire_params( + field, size_len, oer_unsigned, + ) + if minimum is not None and i < minimum: + raise OER_Encoding_Error( + "%s: %i is below minimum %i" % + (cls.__name__, i, minimum) + ) + if maximum is not None and i > maximum: + raise OER_Encoding_Error( + "%s: %i is above maximum %i" % + (cls.__name__, i, maximum) + ) + if oer_unsigned and i < 0: + raise OER_Encoding_Error( + "%s: %i is negative for an unsigned type" % (cls.__name__, i) + ) + # X.696 10: the width and the signedness follow the declared bounds of + # the type, never the value at hand, otherwise the decoder (which only + # knows the type) reads something else back. + if size_len in (1, 2, 4, 8): + signed = not oer_unsigned + try: + return struct.pack(cls._FIXED_FORMATS[signed][size_len], i) + except struct.error: + raise OER_Encoding_Error( + "%s: %i does not fit in %i %s octet(s)" % + (cls.__name__, i, size_len, + "signed" if signed else "unsigned") + ) + if oer_unsigned: + return OER_unsigned_integer_enc(i) + return OER_signed_integer_enc(i) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + field=None, # type: Any + size_len=None, # type: Optional[int] + oer_unsigned=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + from scapy.asn1.constraints import oer_int_wire_params + size_len, oer_unsigned, minimum, maximum = oer_int_wire_params( + field, size_len, oer_unsigned, + ) + if size_len in (1, 2, 4, 8): + _OER_check_len(cls.__name__, s, size_len) + x = struct.unpack( + cls._FIXED_FORMATS[not oer_unsigned][size_len], s[:size_len] + )[0] + if minimum is not None and x < minimum: + raise OER_Decoding_Error( + "%s: %i is below minimum %i" % + (cls.__name__, x, minimum), + remaining=s, + ) + if maximum is not None and x > maximum: + raise OER_Decoding_Error( + "%s: %i is above maximum %i" % + (cls.__name__, x, maximum), + remaining=s, + ) + return cls.asn1_object(x), s[size_len:] + if oer_unsigned: + x, t = OER_unsigned_integer_dec(s) + else: + x, t = OER_signed_integer_dec(s) + if minimum is not None and x < minimum: + raise OER_Decoding_Error( + "%s: %i is below minimum %i" % + (cls.__name__, x, minimum), + remaining=s, + ) + if maximum is not None and x > maximum: + raise OER_Decoding_Error( + "%s: %i is above maximum %i" % + (cls.__name__, x, maximum), + remaining=s, + ) + return cls.asn1_object(x), t + + +class OERcodec_BOOLEAN(OERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + return chb(0xff if i else 0x00) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + cls.check_string(s) + return cls.asn1_object(0 if s[0] == 0 else 1), s[1:] + + +def _oer_bitstr_to_bytes(bitstr): + # type: (bytes) -> bytes + padded = bitstr + b"0" * (-len(bitstr) % 8) + return bytes([int(padded[i:i + 8], 2) for i in range(0, len(padded), 8)]) + + +def _oer_bytes_to_bitstr(data): + # type: (bytes) -> str + return "".join(binrepr(x).zfill(8) for x in data) + + +class OERcodec_BIT_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + field=None, # type: Any + size_len=None, # type: Optional[int] + oer_unsigned=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[str], bytes] + from scapy.asn1.constraints import resolve_oer_size_bounds + minimum, maximum = resolve_oer_size_bounds(field, size_len) + if minimum is not None and maximum is not None and minimum == maximum: + number_of_bytes = (minimum + 7) // 8 + _OER_check_len(cls.__name__, s, number_of_bytes) + return ( + cls.tag.asn1_object( + _oer_bytes_to_bitstr(s[:number_of_bytes])[:minimum] + ), + s[number_of_bytes:], + ) + length, s = OER_len_dec(s) + if length == 0: + fs = "" + else: + _OER_check_len(cls.__name__, s, length) + unused_bits = s[0] + if safe and unused_bits > 7: + raise OER_Decoding_Error( + "OERcodec_BIT_STRING: too many unused_bits advertised", + remaining=s + ) + fs = _oer_bytes_to_bitstr(s[1:length]) + if unused_bits > 0: + fs = fs[:-unused_bits] + s = s[length:] + nbits = len(fs) + if minimum is not None and nbits < minimum: + raise OER_Decoding_Error( + "%s: got %i bits while expecting >= %i" % + (cls.__name__, nbits, minimum), + remaining=s, + ) + if maximum is not None and nbits > maximum: + raise OER_Decoding_Error( + "%s: got %i bits while expecting <= %i" % + (cls.__name__, nbits, maximum), + remaining=s, + ) + return cls.tag.asn1_object(fs), s + + @classmethod + def enc(cls, _s, field=None, size_len=None, **_kwargs): + # type: (AnyStr, Any, Optional[int], **Any) -> bytes + from scapy.asn1.constraints import resolve_oer_size_bounds + minimum, maximum = resolve_oer_size_bounds(field, size_len) + s = bytes_encode(_s) + nbits = len(s) + if minimum is not None and maximum is not None and minimum == maximum: + # X.696 13.3: a fixed size means the bits are written padded to a + # whole number of octets, without length or unused-bit count. + if nbits != minimum: + raise OER_Encoding_Error( + "%s: got %i bits while expecting %i" % + (cls.__name__, nbits, minimum), + encoded=_s + ) + return _oer_bitstr_to_bytes(s) + if minimum is not None and nbits < minimum: + raise OER_Encoding_Error( + "%s: got %i bits while expecting >= %i" % + (cls.__name__, nbits, minimum), + encoded=_s, + ) + if maximum is not None and nbits > maximum: + raise OER_Encoding_Error( + "%s: got %i bits while expecting <= %i" % + (cls.__name__, nbits, maximum), + encoded=_s, + ) + body = chb(-nbits % 8) + _oer_bitstr_to_bytes(s) + return OER_len_enc(len(body)) + body + + +class OERcodec_STRING(OERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def enc(cls, _s, field=None, size_len=None, **_kwargs): + # type: (Union[str, bytes], Any, Optional[int], **Any) -> bytes + from scapy.asn1.constraints import resolve_oer_size_bounds + minimum, maximum = resolve_oer_size_bounds(field, size_len) + s = bytes_encode(_s) + length = len(s) + if minimum is not None and maximum is not None and minimum == maximum: + # X.696 16.1: a fixed size means no length determinant. + if length != minimum: + raise OER_Encoding_Error( + "%s: got %i bytes while expecting %i" % + (cls.__name__, length, minimum), + encoded=_s + ) + return s + if minimum is not None and length < minimum: + raise OER_Encoding_Error( + "%s: got %i bytes while expecting >= %i" % + (cls.__name__, length, minimum), + encoded=_s, + ) + if maximum is not None and length > maximum: + raise OER_Encoding_Error( + "%s: got %i bytes while expecting <= %i" % + (cls.__name__, length, maximum), + encoded=_s, + ) + return OER_len_enc(length) + s + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + field=None, # type: Any + size_len=None, # type: Optional[int] + oer_unsigned=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[Any], bytes] + from scapy.asn1.constraints import resolve_oer_size_bounds + minimum, maximum = resolve_oer_size_bounds(field, size_len) + if minimum is not None and maximum is not None and minimum == maximum: + _OER_check_len(cls.__name__, s, minimum) + return cls.tag.asn1_object(s[:minimum]), s[minimum:] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + if minimum is not None and length < minimum: + raise OER_Decoding_Error( + "%s: got %i bytes while expecting >= %i" % + (cls.__name__, length, minimum), + remaining=s, + ) + if maximum is not None and length > maximum: + raise OER_Decoding_Error( + "%s: got %i bytes while expecting <= %i" % + (cls.__name__, length, maximum), + remaining=s, + ) + return cls.tag.asn1_object(s[:length]), s[length:] + + +class OERcodec_NULL(OERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def enc(cls, i, **_kwargs): + # type: (Any, **Any) -> bytes + return b"" + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[None], bytes] + return cls.asn1_object(None), s + + +class OERcodec_OID(OERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def enc(cls, _oid, **_kwargs): + # type: (AnyStr, **Any) -> bytes + from scapy.asn1.oid import oid_dotted_to_subidentifiers + oid = bytes_encode(_oid) + lst = oid_dotted_to_subidentifiers(oid) + body = b"".join(BER_num_enc(k) for k in lst) + return OER_len_enc(len(body)) + body + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[bytes], bytes] + length, s = OER_len_dec(s) + _OER_check_len(cls.__name__, s, length) + content, t = s[:length], s[length:] + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + from scapy.asn1.oid import oid_subidentifiers_to_dotted + return ( + cls.asn1_object(oid_subidentifiers_to_dotted(lst)), + t, + ) + + +class OERcodec_ENUMERATED(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def enc(cls, i, **_kwargs): + # type: (int, **Any) -> bytes + if 0 <= i <= 127: + return chb(i) + body = OER_signed_integer_enc(i)[1:] + return chb(0x80 | len(body)) + body + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[int], bytes] + if not s: + raise OER_Decoding_Error( + "%s: got empty string" % cls.__name__, remaining=s + ) + first = s[0] + if not (first & 0x80): + return cls.asn1_object(first), s[1:] + length = first & 0x7f + _OER_check_len(cls.__name__, s, length, offset=1) + value = int.from_bytes(s[1:length + 1], "big", signed=True) + return cls.asn1_object(value), s[length + 1:] + + +class OERcodec_UTF8_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class OERcodec_NUMERIC_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class OERcodec_PRINTABLE_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class OERcodec_T61_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class OERcodec_VIDEOTEX_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class OERcodec_IA5_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class OERcodec_GENERAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class OERcodec_UTC_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class OERcodec_GENERALIZED_TIME(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class OERcodec_ISO646_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class OERcodec_UNIVERSAL_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class OERcodec_BMP_STRING(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +class OERcodec_SEQUENCE(OERcodec_Object[Union[bytes, List['OERcodec_Object[Any]']]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[OERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + return b"".join(x.enc(cls.codec) for x in _ll) + + @classmethod + def do_dec(cls, + s, # type: bytes + context=None, # type: Optional[Type[ASN1_Class]] + safe=False, # type: bool + **_kwargs # type: Any + ): + # type: (...) -> Tuple[ASN1_Object[Union[bytes, List[Any]]], bytes] + raise OER_Decoding_Error( + "OERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=s + ) + + +class OERcodec_SET(OERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class OERcodec_IPADDRESS(OERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def enc(cls, ipaddr_ascii, field=None, size_len=None, **_kwargs): # type: ignore + # type: (str, Any, Optional[int], **Any) -> bytes + if size_len is None and field is not None: + size_len = field.size_len + try: + s = inet_aton(ipaddr_ascii) + except Exception: + raise OER_Encoding_Error("IPv4 address could not be encoded") + if size_len == len(s): + return s + return OER_len_enc(len(s)) + s + + @classmethod + def do_dec(cls, s, context=None, safe=False, + field=None, size_len=None, **_kwargs): + # type: (bytes, Optional[Any], bool, Any, Optional[int], **Any) -> Tuple[ASN1_Object[str], bytes] # noqa: E501 + if size_len is None and field is not None: + size_len = field.size_len + if size_len == 4: + raw, remain = s[:4], s[4:] + else: + length, remain = OER_len_dec(s) + if len(remain) < length: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + raw, remain = remain[:length], remain[length:] + try: + ipaddr_ascii = inet_ntoa(raw) + except Exception: + raise OER_Decoding_Error("IP address could not be decoded", + remaining=s) + return cls.asn1_object(ipaddr_ascii), remain + + +class OERcodec_COUNTER32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class OERcodec_COUNTER64(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class OERcodec_GAUGE32(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class OERcodec_TIME_TICKS(OERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS diff --git a/scapy/asn1/oid.py b/scapy/asn1/oid.py new file mode 100644 index 00000000000..5c1bd5e5908 --- /dev/null +++ b/scapy/asn1/oid.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""Shared OBJECT IDENTIFIER arc handling (X.690 section 8.19.4).""" + +from typing import List # noqa: F401 + + +def oid_decode_first_arc(first): + # type: (int) -> List[int] + """Split the first subidentifier into two registration arcs.""" + if first < 40: + return [0, first] + if first < 80: + return [1, first - 40] + return [2, first - 80] + + +def oid_subidentifiers_to_dotted(lst): + # type: (List[int]) -> bytes + if not lst: + return b"" + arcs = oid_decode_first_arc(lst[0]) + lst[1:] + return b".".join(str(k).encode("ascii") for k in arcs) + + +def oid_dotted_to_subidentifiers(oid): + # type: (bytes) -> List[int] + if not oid: + return [] + parts = [int(x) for x in oid.split(b".")] + if len(parts) >= 2: + return [40 * parts[0] + parts[1]] + parts[2:] + return parts diff --git a/scapy/asn1/tag.py b/scapy/asn1/tag.py new file mode 100644 index 00000000000..74c70e79a67 --- /dev/null +++ b/scapy/asn1/tag.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +"""Semantic ASN.1 tag decomposition for Scapy's legacy BER integer tags.""" + + +from scapy.asn1.ber import BER_id_enc + + +def asn1_tag_parts(identifier): + # type: (int) -> tuple + """Return (tag_class, tag_number, constructed) for a Scapy tag integer.""" + wire = BER_id_enc(identifier) + first = wire[0] + tag_class = first & 0xc0 + constructed = bool(first & 0x20) + if (first & 0x1f) != 0x1f: + return tag_class, first & 0x1f, constructed + tag_number = 0 + for c in wire[1:]: + tag_number <<= 7 + tag_number |= c & 0x7f + if not (c & 0x80): + break + return tag_class, tag_number, constructed diff --git a/scapy/asn1/uper.py b/scapy/asn1/uper.py new file mode 100644 index 00000000000..9e0da5bd871 --- /dev/null +++ b/scapy/asn1/uper.py @@ -0,0 +1,1187 @@ +# SPDX-License-Identifier: GPL-2.0-only +# This file is part of Scapy +# See https://scapy.net/ for more information + +""" +Unaligned Packed Encoding Rules (UPER) for ASN.1 + +As specified in ITU-T X.691 | ISO/IEC 8825-2. + +UPER is registered on ``ASN1_Codecs.PER``. Schema-driven encoding and decoding +(``ASN1F_SEQUENCE``, ``ASN1F_CHOICE``, ``ASN1F_SEQUENCE_OF``, +``ASN1F_ENUMERATED``) is supported for common field types. Value ranges are +declared with ``minimum=``/``maximum=``, fixed sizes with ``size_len=``, and +an extension marker with ``extensible=True``. Content of 16K units or +more is fragmented as required by 11.9.3.8. + +Not supported yet: extension additions (an encoding that carries them is +refused rather than misparsed), SET, REAL, and the known-multiplier character +string encodings (rejected rather than emitted as plain octets). + +``ASN1F_CHOICE`` alternatives are indexed in X.691 10.2 canonical tag order +(via ``ASN1F_CHOICE.canonical_order``). Declaration order is kept for +BER tag lookup (``choices``) and ``alternative_tag``. +""" + +from scapy.compat import bytes_encode +from scapy.utils import binrepr, inet_aton, inet_ntoa +from scapy.asn1.ber import BER_num_dec, BER_num_enc +from scapy.asn1.asn1 import ( + ASN1Codec_metaclass, + ASN1_Class, + ASN1_Class_UNIVERSAL, + ASN1_Codecs, + ASN1_DECODING_ERROR, + ASN1_Decoding_Error, + ASN1_Encoding_Error, + ASN1_Error, + ASN1_Object, + _ASN1_ERROR, +) +# DEFAULT components are described by the sequence preamble in OER/PER. + +from typing import ( + Any, + AnyStr, + Callable, + Generic, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, +) + + +################### +# UPER encoding # +################### + + +class UPER_Encoding_Error(ASN1_Encoding_Error): + pass + + +class UPER_Decoding_Error(ASN1_Decoding_Error): + pass + + +def UPER_bits_for_range(size): + # type: (int) -> int + if size <= 0: + return 0 + return size.bit_length() + + +# X.691 11.9.3.8: content of 16K units or more is split into fragments, each +# one holding a multiple of this many units. +UPER_FRAGMENT_SIZE = 16384 + + +def _uper_bits_to_bytes(value, number_of_bits): + # type: (int, int) -> bytes + # X.691 11.1: an encoding is padded with zero bits up to an octet + # boundary. + if number_of_bits == 0: + return b"" + padding = -number_of_bits % 8 + return (value << padding).to_bytes((number_of_bits + padding) // 8, "big") + + +class UPER_Encoder(object): + """Byte-oriented UPER bit writer. + + Completed octets live in a ``bytearray``; at most seven pending bits are + kept in ``_acc``. Large octet strings stay as bytes instead of being + folded into a growing multi-precision integer. + """ + + def __init__(self): + # type: () -> None + self._buf = bytearray() # type: bytearray + self._acc = 0 # type: int + self._nbits = 0 # type: int + + def append_bit(self, bit): + # type: (int) -> None + self._acc = (self._acc << 1) | (1 if bit else 0) + self._nbits += 1 + if self._nbits == 8: + self._buf.append(self._acc) + self._acc = 0 + self._nbits = 0 + + def append_bits(self, data, number_of_bits): + # type: (bytes, int) -> None + if number_of_bits == 0: + return + # Keep whole octets on the byte path; only the final 1–7 bits become + # an integer (avoids int.from_bytes of a multi-megabit BIT STRING). + full_bytes, remaining_bits = divmod(number_of_bits, 8) + if full_bytes: + self.append_bytes(data[:full_bytes]) + if remaining_bits: + self.append_non_negative_binary_integer( + data[full_bytes] >> (8 - remaining_bits), + remaining_bits, + ) + + def append_non_negative_binary_integer(self, value, number_of_bits): + # type: (int, int) -> None + if number_of_bits == 0: + return + value &= (1 << number_of_bits) - 1 + # Fill the pending octet first so the middle can be raw bytes. + if self._nbits: + space = 8 - self._nbits + if number_of_bits <= space: + self._acc = (self._acc << number_of_bits) | value + self._nbits += number_of_bits + if self._nbits == 8: + self._buf.append(self._acc) + self._acc = 0 + self._nbits = 0 + return + self._acc = ( + (self._acc << space) | (value >> (number_of_bits - space)) + ) + self._buf.append(self._acc) + number_of_bits -= space + value &= (1 << number_of_bits) - 1 + self._acc = 0 + self._nbits = 0 + full_bytes = number_of_bits // 8 + rem = number_of_bits % 8 + if full_bytes: + mid = value >> rem if rem else value + self._buf.extend(mid.to_bytes(full_bytes, "big")) + if rem: + value &= (1 << rem) - 1 + number_of_bits = rem + if number_of_bits: + self._acc = value + self._nbits = number_of_bits + + def append_bytes(self, data): + # type: (bytes) -> None + if not data: + return + if self._nbits == 0: + self._buf.extend(data) + return + # One bulk shift for the whole block rather than a Python loop per + # source octet (large OCTET STRINGs after a presence/extension bit). + offset = self._nbits + size = len(data) + value = int.from_bytes(data, "big") + self._buf.extend( + ( + (self._acc << (8 * size - offset)) | + (value >> offset) + ).to_bytes(size, "big") + ) + self._acc = value & ((1 << offset) - 1) + + def append_length_determinant(self, length): + # type: (int) -> None + # X.691 11.9.3.6/11.9.3.7 only define the one and two octet forms up + # to 16K. Longer content has to be fragmented, which requires slicing + # the content itself, so leave that to append_fragmented rather than + # silently emitting a determinant that does not match what follows. + if length >= UPER_FRAGMENT_SIZE: + raise UPER_Encoding_Error( + "UPER_Encoder: length %i requires fragmentation" % length + ) + if length < 128: + encoded = bytes([length]) + else: + encoded = bytes([(0x80 | (length >> 8)), (length & 0xff)]) + self.append_bytes(encoded) + + def append_fragmented(self, count, append_units): + # type: (int, Callable[[int, int], None]) -> None + # X.691 11.9.3.8: emit the content as fragments of at most 4 * 16K + # units, each preceded by its own determinant, and always terminate + # with a determinant below 16K (possibly zero). append_units(offset, + # size) appends the units of one fragment. + offset = 0 + remaining = count + while remaining >= UPER_FRAGMENT_SIZE: + number_of_fragments = min(remaining // UPER_FRAGMENT_SIZE, 4) + size = number_of_fragments * UPER_FRAGMENT_SIZE + self.append_bytes(bytes([0xc0 | number_of_fragments])) + append_units(offset, size) + offset += size + remaining -= size + self.append_length_determinant(remaining) + append_units(offset, remaining) + + def append_unconstrained_whole_number(self, value): + # type: (int) -> None + from scapy.asn1.intutil import twos_complement_octets + number_of_bytes, masked = twos_complement_octets(value) + self.append_length_determinant(number_of_bytes) + self.append_non_negative_binary_integer( + masked, 8 * number_of_bytes + ) + + def as_bytes(self): + # type: () -> bytes + if self._nbits == 0: + return bytes(self._buf) + # X.691 11.1: pad with zero bits up to an octet boundary. + return b"".join(( + self._buf, + bytes([self._acc << (8 - self._nbits)]), + )) + + +class UPER_Decoder(object): + def __init__(self, encoded): + # type: (bytes) -> None + # Byte buffer + bit cursor: avoid shifting a whole-input Python int + # on every small field read (super-linear on large encodings). + self._data = encoded + self._pos = 0 + self.total_number_of_bits = 8 * len(encoded) + + @property + def number_of_bits(self): + # type: () -> int + return self.total_number_of_bits - self._pos + + def _peek_bits_int(self, number_of_bits): + # type: (int) -> int + if number_of_bits == 0: + return 0 + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + start = self._pos // 8 + offset = self._pos % 8 + nbytes = (offset + number_of_bits + 7) // 8 + window = int.from_bytes(self._data[start:start + nbytes], "big") + shift = nbytes * 8 - offset - number_of_bits + return (window >> shift) & ((1 << number_of_bits) - 1) + + def _read_bits_int(self, number_of_bits): + # type: (int) -> int + value = self._peek_bits_int(number_of_bits) + self._pos += number_of_bits + return value + + def read_bit(self): + # type: () -> int + if not self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + byte_index = self._pos // 8 + bit_offset = self._pos % 8 + self._pos += 1 + return (self._data[byte_index] >> (7 - bit_offset)) & 1 + + def read_bits(self, number_of_bits): + # type: (int) -> bytes + # Whole-octet requests (aligned or not) use read_bytes(); only + # non-multiple-of-8 widths go through the small-field integer path. + if number_of_bits % 8 == 0: + return self.read_bytes(number_of_bits // 8) + return _uper_bits_to_bytes( + self._read_bits_int(number_of_bits), + number_of_bits, + ) + + def remaining(self): + # type: () -> bytes + n = self.number_of_bits + if n == 0: + return b"" + if self._pos % 8 == 0: + return self._data[self._pos // 8:] + return _uper_bits_to_bytes(self._peek_bits_int(n), n) + + def remaining_bytes(self): + # type: () -> bytes + # A standalone UPER encoding is padded to an octet boundary, so the + # bits left over inside the current octet are padding; only whole + # octets after it are actual remaining input / Scapy payload. + pad = -self._pos % 8 + if pad: + if pad > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: truncated padding") + if self._peek_bits_int(pad) != 0: + raise UPER_Decoding_Error( + "UPER_Decoder: non-zero padding bits", + remaining=self.remaining(), + ) + self._pos += pad + n = self.number_of_bits + if n == 0: + return b"" + if n % 8: + raise UPER_Decoding_Error("UPER_Decoder: truncated padding") + start = self._pos // 8 + return self._data[start:] + + def read_bytes(self, number_of_bytes): + # type: (int) -> bytes + number_of_bits = 8 * number_of_bytes + if number_of_bits > self.number_of_bits: + raise UPER_Decoding_Error("UPER_Decoder: out of data") + if number_of_bytes == 0: + return b"" + if self._pos % 8 == 0: + start = self._pos // 8 + end = start + number_of_bytes + self._pos += number_of_bits + return self._data[start:end] + return self._read_bits_int(number_of_bits).to_bytes( + number_of_bytes, "big", + ) + + def read_non_negative_binary_integer(self, number_of_bits): + # type: (int) -> int + return self._read_bits_int(number_of_bits) + + def _read_length_determinant(self): + # type: () -> Tuple[int, bool] + # Returns the number of units and whether more fragments follow. + value = self.read_non_negative_binary_integer(8) + if (value & 0x80) == 0x00: + return value, False + if (value & 0xc0) == 0x80: + return ( + ((value & 0x7f) << 8) | + self.read_non_negative_binary_integer(8) + ), False + if 0xc1 <= value <= 0xc4: + return (value & 0x0f) * UPER_FRAGMENT_SIZE, True + raise UPER_Decoding_Error( + "UPER_Decoder: bad length determinant 0x%02x" % value + ) + + def read_length_determinant(self): + # type: () -> int + length, fragmented = self._read_length_determinant() + if fragmented: + raise UPER_Decoding_Error( + "UPER_Decoder: unexpected fragmented length determinant" + ) + return length + + def read_fragmented(self, read_units): + # type: (Callable[[int], None]) -> None + # Counterpart of UPER_Encoder.append_fragmented: read_units(size) is + # called once per fragment, the last one being the (possibly empty) + # fragment introduced by a determinant below 16K. + while True: + size, fragmented = self._read_length_determinant() + read_units(size) + if not fragmented: + return + + def read_unconstrained_whole_number(self): + # type: () -> int + from scapy.asn1.intutil import from_twos_complement + number_of_bytes = self.read_length_determinant() + if number_of_bytes == 0: + raise UPER_Decoding_Error( + "UPER_Decoder: integer with an empty length determinant" + ) + enc = self.read_non_negative_binary_integer(8 * number_of_bytes) + return from_twos_complement(enc, number_of_bytes) + + +def UPER_constrained_int_enc(enc, value, minimum, maximum): + # type: (UPER_Encoder, int, int, int) -> None + # X.691 13.2.2: the field is sized after the range, so a value outside it + # cannot be expressed. Callers handle extensibility before coming here. + if not minimum <= value <= maximum: + raise UPER_Encoding_Error( + "UPER_constrained_int_enc: got %i while expecting %i..%i" % + (value, minimum, maximum) + ) + enc.append_non_negative_binary_integer( + value - minimum, UPER_bits_for_range(maximum - minimum) + ) + + +def UPER_constrained_int_dec(dec, minimum, maximum): + # type: (UPER_Decoder, int, int) -> int + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + value += minimum + if not minimum <= value <= maximum: + raise UPER_Decoding_Error( + "UPER_constrained_int_dec: got %i while expecting %i..%i" % + (value, minimum, maximum) + ) + return value + + +def UPER_semi_constrained_int_enc(enc, value, minimum): + # type: (UPER_Encoder, int, int) -> None + # X.691 11.7: encode the non-negative offset (value - lower_bound) as a + # normally small non-negative whole number (length determinant + octets). + if value < minimum: + raise UPER_Encoding_Error( + "UPER_semi_constrained_int_enc: got %i while expecting >= %i" % + (value, minimum) + ) + offset = value - minimum + number_of_bytes = max((offset.bit_length() + 7) // 8, 1) + enc.append_length_determinant(number_of_bytes) + enc.append_non_negative_binary_integer(offset, 8 * number_of_bytes) + + +def UPER_semi_constrained_int_dec(dec, minimum): + # type: (UPER_Decoder, int) -> int + number_of_bytes = dec.read_length_determinant() + if number_of_bytes == 0: + raise UPER_Decoding_Error( + "UPER_semi_constrained_int_dec: empty length determinant" + ) + offset = dec.read_non_negative_binary_integer(8 * number_of_bytes) + return offset + minimum + + +def _uper_check_size(name, unit, count, minimum, maximum): + # type: (str, str, int, int, int) -> None + # The determinant is sized after the constraint, so a value that violates + # it cannot be expressed: refuse rather than emit something the peer reads + # as a different length. + if not minimum <= count <= maximum: + raise UPER_Encoding_Error( + "%s: got %i %s while expecting %s" % + (name, count, unit, minimum if minimum == maximum + else "%i..%i" % (minimum, maximum)) + ) + + +def UPER_octet_string_enc(enc, data, minimum=None, maximum=None, + extensible=False): + # type: (UPER_Encoder, bytes, Optional[int], Optional[int], bool) -> None + length = len(data) + + def append_slice(offset, size): + # type: (int, int) -> None + enc.append_bytes(data[offset:offset + size]) + + if extensible and minimum is not None and maximum is not None: + if minimum <= length <= maximum: + enc.append_bit(0) + else: + enc.append_bit(1) + if length >= UPER_FRAGMENT_SIZE: + data = memoryview(data) + enc.append_fragmented(length, append_slice) + return + if minimum is not None and maximum is not None: + _uper_check_size( + "UPER_octet_string_enc", "octets", length, minimum, maximum, + ) + if minimum != maximum: + enc.append_non_negative_binary_integer( + length - minimum, + UPER_bits_for_range(maximum - minimum), + ) + enc.append_bytes(data) + else: + if length >= UPER_FRAGMENT_SIZE: + data = memoryview(data) + enc.append_fragmented(length, append_slice) + + +def UPER_octet_string_dec(dec, minimum=None, maximum=None, extensible=False): + # type: (UPER_Decoder, Optional[int], Optional[int], bool) -> bytes + if extensible and minimum is not None and maximum is not None: + if dec.read_bit(): + fragments = [] # type: List[bytes] + dec.read_fragmented( + lambda size: fragments.append(dec.read_bytes(size)) + ) + return b"".join(fragments) + if minimum is not None and maximum is not None: + length = minimum + if minimum != maximum: + length += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + return dec.read_bytes(length) + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + return b"".join(fragments) + + +def UPER_choice_index_enc(enc, index, number_of_choices): + # type: (UPER_Encoder, int, int) -> None + enc.append_non_negative_binary_integer( + index, UPER_bits_for_range(number_of_choices - 1) + ) + + +def UPER_choice_index_dec(dec, number_of_choices): + # type: (UPER_Decoder, int) -> int + return dec.read_non_negative_binary_integer( + UPER_bits_for_range(number_of_choices - 1) + ) + + +_K = TypeVar('_K') + + +class UPERcodec_Object(Generic[_K], metaclass=ASN1Codec_metaclass): + codec = ASN1_Codecs.PER + tag = ASN1_Class_UNIVERSAL.ANY + + @classmethod + def asn1_object(cls, val): + # type: (_K) -> ASN1_Object[_K] + return cls.tag.asn1_object(val) + + # The bit-oriented encode_into()/dec_from_decoder() pair is the primitive + # every codec implements; enc()/do_dec() below are the standalone (byte + # buffer) entry points, and pass every codec option straight through. + + @classmethod + def encode_into(cls, enc, s, **kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # No schema information here (ANY): guess from the Python type. + if isinstance(s, (str, bytes)): + UPERcodec_STRING.encode_into(enc, s, **kwargs) + return + try: + i = int(s) + except (TypeError, ValueError): + raise UPER_Encoding_Error( + "Cannot encode value %r for %s" % (s, cls.__name__), + encoded=s + ) + UPERcodec_INTEGER.encode_into(enc, i, **kwargs) + + @classmethod + def dec_from_decoder(cls, dec, **kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Any] + raise UPER_Decoding_Error( + "%s: Cannot decode unknown UPER type without context" % + cls.__name__, remaining=dec.remaining() + ) + + @classmethod + def enc(cls, s, **kwargs): + # type: (Any, **Any) -> bytes + enc = UPER_Encoder() + cls.encode_into(enc, s, **kwargs) + return enc.as_bytes() + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[Any], bytes] # noqa: E501 + dec = UPER_Decoder(s) + return cls.dec_from_decoder(dec, **kwargs), dec.remaining_bytes() + + @classmethod + def dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + if not safe: + return cls.do_dec(s, context, safe, **kwargs) + try: + return cls.do_dec(s, context, safe, **kwargs) + except ASN1_Error as e: + return ASN1_DECODING_ERROR(s, exc=e), b"" + + @classmethod + def safedec(cls, s, context=None, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], **Any) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes] # noqa: E501 + return cls.dec(s, context, safe=True, **kwargs) + + +# No field tagging on the wire for PER; identity tagging is the ASN1Codec +# default when no tagging_enc/dec is registered. +ASN1_Codecs.PER.register_stem(UPERcodec_Object) + + +######################### +# UPERcodec objects # +######################### + + +class UPERcodec_INTEGER(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.INTEGER + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + unsigned=None, # type: Optional[bool] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> None + from scapy.asn1.constraints import resolve_uper_int_bounds + minimum, maximum, extensible = resolve_uper_int_bounds( + field, size_len, minimum, maximum, unsigned, extensible, + ) + if extensible and minimum is not None and maximum is not None: + if minimum <= i <= maximum: + enc.append_bit(0) + else: + enc.append_bit(1) + enc.append_unconstrained_whole_number(i) + return + if minimum is not None and maximum is not None: + UPER_constrained_int_enc(enc, i, minimum, maximum) + elif minimum is not None: + UPER_semi_constrained_int_enc(enc, i, minimum) + else: + enc.append_unconstrained_whole_number(i) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + field=None, # type: Any + pkt=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + unsigned=None, # type: Optional[bool] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + from scapy.asn1.constraints import resolve_uper_int_bounds + minimum, maximum, extensible = resolve_uper_int_bounds( + field, size_len, minimum, maximum, unsigned, extensible, + ) + if extensible and minimum is not None and maximum is not None: + if dec.read_bit(): + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + if minimum is not None and maximum is not None: + value = UPER_constrained_int_dec(dec, minimum, maximum) + elif minimum is not None: + value = UPER_semi_constrained_int_dec(dec, minimum) + else: + value = dec.read_unconstrained_whole_number() + return cls.asn1_object(value) + + +class UPERcodec_BOOLEAN(UPERcodec_Object[int]): + tag = ASN1_Class_UNIVERSAL.BOOLEAN + + @classmethod + def encode_into(cls, enc, i, **_kwargs): + # type: (UPER_Encoder, int, **Any) -> None + enc.append_bit(1 if i else 0) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[int] + return cls.asn1_object(dec.read_bit()) + + +def _uper_bytes_to_bitstr(data, nbits): + # type: (bytes, int) -> str + bitstr = "".join(binrepr(x).zfill(8) for x in data) + return bitstr[:nbits] + + +class UPERcodec_BIT_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Any + field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> None + from scapy.asn1.constraints import resolve_uper_size_bounds + if isinstance(_s, tuple) and len(_s) == 2: + data, nbits = _s + s = bytes_encode(data) + elif isinstance(_s, str) and _s and all(c in "01" for c in _s): + nbits = len(_s) + padding = -nbits % 8 + value = int(_s or "0", 2) << padding + s = value.to_bytes( + max(1, (nbits + padding) // 8), + "big", + ) + else: + s = bytes_encode(_s) + nbits = 8 * len(s) + minimum, maximum, extensible = resolve_uper_size_bounds( + field, size_len, minimum, maximum, extensible, + ) + + def append_bits_slice(offset, size): + # type: (int, int) -> None + enc.append_bits( + s[offset // 8:(offset + size + 7) // 8], size + ) + + if extensible and minimum is not None and maximum is not None: + if minimum <= nbits <= maximum: + enc.append_bit(0) + else: + enc.append_bit(1) + if nbits >= UPER_FRAGMENT_SIZE: + s = memoryview(s) + enc.append_fragmented(nbits, append_bits_slice) + return + if minimum is not None and maximum is not None: + _uper_check_size(cls.__name__, "bits", nbits, minimum, maximum) + if minimum != maximum: + enc.append_non_negative_binary_integer( + nbits - minimum, UPER_bits_for_range(maximum - minimum) + ) + enc.append_bits(s, nbits) + else: + # X.691 16.11: the determinant counts bits, not octets, and no + # padding is inserted before whatever follows the bit string. + if nbits >= UPER_FRAGMENT_SIZE: + s = memoryview(s) + # Fragments hold whole multiples of 16K bits, so every chunk + # but the last starts and ends on an octet boundary. + enc.append_fragmented(nbits, append_bits_slice) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[str] + from scapy.asn1.constraints import resolve_uper_size_bounds + minimum, maximum, extensible = resolve_uper_size_bounds( + field, size_len, minimum, maximum, extensible, + ) + + def _read_unconstrained(): + # type: () -> ASN1_Object[str] + fragments = [] # type: List[bytes] + sizes = [] # type: List[int] + + def read_fragment(size): + # type: (int) -> None + fragments.append(dec.read_bits(size)) + sizes.append(size) + + dec.read_fragmented(read_fragment) + return cls.asn1_object( + _uper_bytes_to_bitstr(b"".join(fragments), sum(sizes)) + ) + + if extensible and minimum is not None and maximum is not None: + if dec.read_bit(): + return _read_unconstrained() + if minimum is not None and maximum is not None: + nbits = minimum + if minimum != maximum: + nbits += dec.read_non_negative_binary_integer( + UPER_bits_for_range(maximum - minimum) + ) + raw = dec.read_bits(nbits) + return cls.asn1_object(_uper_bytes_to_bitstr(raw, nbits)) + return _read_unconstrained() + + +class UPERcodec_STRING(UPERcodec_Object[str]): + tag = ASN1_Class_UNIVERSAL.STRING + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + _s, # type: Union[str, bytes] + field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> None + from scapy.asn1.constraints import resolve_uper_size_bounds + s = bytes_encode(_s) + minimum, maximum, extensible = resolve_uper_size_bounds( + field, size_len, minimum, maximum, extensible, + ) + UPER_octet_string_enc(enc, s, minimum, maximum, extensible) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + field=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[Any] + from scapy.asn1.constraints import resolve_uper_size_bounds + minimum, maximum, extensible = resolve_uper_size_bounds( + field, size_len, minimum, maximum, extensible, + ) + raw = UPER_octet_string_dec(dec, minimum, maximum, extensible) + return cls.asn1_object(raw) + + +class UPERcodec_NULL(UPERcodec_Object[None]): + tag = ASN1_Class_UNIVERSAL.NULL + + @classmethod + def encode_into(cls, enc, _s, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # NULL has an empty encoding. + return + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[None] + return cls.asn1_object(None) + + @classmethod + def do_dec(cls, s, context=None, safe=False, **kwargs): + # type: (bytes, Optional[Type[ASN1_Class]], bool, **Any) -> Tuple[ASN1_Object[None], bytes] # noqa: E501 + # NULL occupies no bits at all, so the input is left untouched. + return cls.asn1_object(None), s + + +class UPERcodec_OID(UPERcodec_Object[bytes]): + tag = ASN1_Class_UNIVERSAL.OID + + @classmethod + def encode_into(cls, enc, _oid, **_kwargs): + # type: (UPER_Encoder, AnyStr, **Any) -> None + from scapy.asn1.oid import oid_dotted_to_subidentifiers + oid = bytes_encode(_oid) + lst = oid_dotted_to_subidentifiers(oid) + body = b"".join(BER_num_enc(k) for k in lst) + if len(body) >= UPER_FRAGMENT_SIZE: + body = memoryview(body) + enc.append_fragmented( + len(body), + lambda offset, size: enc.append_bytes(body[offset:offset + size]), + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[bytes] + from scapy.asn1.oid import oid_subidentifiers_to_dotted + fragments = [] # type: List[bytes] + dec.read_fragmented(lambda size: fragments.append(dec.read_bytes(size))) + content = b"".join(fragments) + lst = [] + while content: + val, content = BER_num_dec(content) + lst.append(val) + return cls.asn1_object(oid_subidentifiers_to_dotted(lst)) + + +def UPER_enumerated_enc(enc, value, enum_values): + # type: (UPER_Encoder, int, List[int]) -> None + if not enum_values: + raise UPER_Encoding_Error("UPER_enumerated_enc: empty enumeration") + try: + index = enum_values.index(value) + except ValueError: + raise UPER_Encoding_Error( + "UPER_enumerated_enc: unknown enumeration value %r" % value + ) + UPER_choice_index_enc(enc, index, len(enum_values)) + + +def UPER_enumerated_dec(dec, enum_values): + # type: (UPER_Decoder, List[int]) -> int + if not enum_values: + raise UPER_Decoding_Error("UPER_enumerated_dec: empty enumeration") + index = UPER_choice_index_dec(dec, len(enum_values)) + if index >= len(enum_values): + raise UPER_Decoding_Error( + "UPER_enumerated_dec: index %i out of range" % index + ) + return enum_values[index] + + +class UPERcodec_ENUMERATED(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.ENUMERATED + + @classmethod + def encode_into(cls, + enc, # type: UPER_Encoder + i, # type: int + field=None, # type: Any + pkt=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> None + from scapy.asn1.constraints import ( + uper_enum_values as _uper_enum_values, + ) + if size_len is None and field is not None: + size_len = field.size_len + if minimum is None and maximum is None and field is not None: + minimum = field.constraints.minimum + maximum = field.constraints.maximum + uper_enum_values = _uper_enum_values( + field, values=uper_enum_values, + ) + if extensible is None: + extensible = ( + bool(field.constraints.extensible) if field is not None else False + ) + if uper_enum_values is not None: + if extensible: + # X.691 14.3: a one bit prefix says whether the value is an + # extension addition. Only root values can be encoded. + if i not in uper_enum_values: + raise UPER_Encoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + enc.append_bit(0) + UPER_enumerated_enc(enc, i, uper_enum_values) + return + lo, hi = cls._range( + size_len, minimum, maximum, UPER_Encoding_Error + ) + UPER_constrained_int_enc(enc, i, lo, hi) + + @classmethod + def dec_from_decoder(cls, + dec, # type: UPER_Decoder + field=None, # type: Any + pkt=None, # type: Any + size_len=None, # type: Optional[int] + minimum=None, # type: Optional[int] + maximum=None, # type: Optional[int] + uper_enum_values=None, # type: Optional[List[int]] + extensible=None, # type: Optional[bool] + **_kwargs # type: Any + ): + # type: (...) -> ASN1_Object[int] + from scapy.asn1.constraints import ( + uper_enum_values as _uper_enum_values, + ) + if size_len is None and field is not None: + size_len = field.size_len + if minimum is None and maximum is None and field is not None: + minimum = field.constraints.minimum + maximum = field.constraints.maximum + uper_enum_values = _uper_enum_values( + field, values=uper_enum_values, + ) + if extensible is None: + extensible = ( + bool(field.constraints.extensible) if field is not None else False + ) + if uper_enum_values is not None: + if extensible and dec.read_bit(): + raise UPER_Decoding_Error( + "UPERcodec_ENUMERATED: extension additions are not " + "supported" + ) + return cls.asn1_object(UPER_enumerated_dec(dec, uper_enum_values)) + lo, hi = cls._range( + size_len, minimum, maximum, UPER_Decoding_Error + ) + value = dec.read_non_negative_binary_integer( + UPER_bits_for_range(hi - lo) + ) + lo + return cls.asn1_object(value) + + @staticmethod + def _range(size_len, minimum, maximum, error): + # type: (Optional[int], Optional[int], Optional[int], Any) -> Tuple[int, int] # noqa: E501 + # Without the enumeration itself the index range has to come from + # the declared bounds; deriving it from the value at hand would + # make the width depend on the value, which the decoder cannot + # reproduce. + lo = minimum if minimum is not None else 0 + hi = maximum if maximum is not None else (size_len or None) + if hi is None: + raise error("UPERcodec_ENUMERATED: missing range") + return lo, hi + + +class UPERcodec_SEQUENCE(UPERcodec_Object[Union[bytes, List[Any]]]): + tag = ASN1_Class_UNIVERSAL.SEQUENCE + + @classmethod + def encode_into(cls, enc, _ll, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + # A finished encoding is padded to an octet boundary, so its real bit + # length is lost and it cannot be spliced into a bitstream. Sequences + # are encoded through the ASN1F_SEQUENCE hooks instead. + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def enc(cls, _ll, **_kwargs): + # type: (Union[bytes, List[UPERcodec_Object[Any]]], **Any) -> bytes + if isinstance(_ll, bytes): + return _ll + raise UPER_Encoding_Error( + "UPERcodec_SEQUENCE: schema-defined field order required" + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Union[bytes, List[Any]]] + raise UPER_Decoding_Error( + "UPERcodec_SEQUENCE: decoding requires schema-defined field order", + remaining=dec.remaining() + ) + + +class UPERcodec_SET(UPERcodec_SEQUENCE): + tag = ASN1_Class_UNIVERSAL.SET + + +class UPERcodec_IPADDRESS(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.IPADDRESS + + @classmethod + def encode_into(cls, enc, ipaddr_ascii, **_kwargs): + # type: (UPER_Encoder, str, **Any) -> None + try: + s = inet_aton(ipaddr_ascii) + except (TypeError, ValueError, OSError): + raise UPER_Encoding_Error("IPv4 address could not be encoded") + UPER_octet_string_enc(enc, s, 4, 4) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[str] + raw = UPER_octet_string_dec(dec, 4, 4) + try: + ipaddr_ascii = inet_ntoa(raw) + except (TypeError, ValueError, OSError): + raise UPER_Decoding_Error("IP address could not be decoded") + return cls.asn1_object(ipaddr_ascii) + + +class UPERcodec_COUNTER32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER32 + + +class UPERcodec_COUNTER64(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.COUNTER64 + + +class UPERcodec_GAUGE32(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.GAUGE32 + + +class UPERcodec_TIME_TICKS(UPERcodec_INTEGER): + tag = ASN1_Class_UNIVERSAL.TIME_TICKS + + +class UPERcodec_KNOWN_MULTIPLIER_STRING(UPERcodec_STRING): + # X.691 §3.7.16 / §30: NumericString, PrintableString, VisibleString + # (ISO646String), IA5String, BMPString, UniversalString. + @classmethod + def encode_into(cls, enc, s, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + raise UPER_Encoding_Error( + "%s: known-multiplier PER string encoding is not implemented" % + cls.__name__ + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Any] + raise UPER_Decoding_Error( + "%s: known-multiplier PER string decoding is not implemented" % + cls.__name__ + ) + + +class UPERcodec_UNSUPPORTED_TIME(UPERcodec_STRING): + @classmethod + def encode_into(cls, enc, s, **_kwargs): + # type: (UPER_Encoder, Any, **Any) -> None + raise UPER_Encoding_Error( + "%s: PER time encoding is not implemented" % cls.__name__ + ) + + @classmethod + def dec_from_decoder(cls, dec, **_kwargs): + # type: (UPER_Decoder, **Any) -> ASN1_Object[Any] + raise UPER_Decoding_Error( + "%s: PER time decoding is not implemented" % cls.__name__ + ) + + +class UPERcodec_UTF8_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.UTF8_STRING + + +class UPERcodec_NUMERIC_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.NUMERIC_STRING + + +class UPERcodec_PRINTABLE_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.PRINTABLE_STRING + + +class UPERcodec_T61_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.T61_STRING + + +class UPERcodec_VIDEOTEX_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.VIDEOTEX_STRING + + +class UPERcodec_IA5_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.IA5_STRING + + +class UPERcodec_GENERAL_STRING(UPERcodec_STRING): + tag = ASN1_Class_UNIVERSAL.GENERAL_STRING + + +class UPERcodec_UTC_TIME(UPERcodec_UNSUPPORTED_TIME): + tag = ASN1_Class_UNIVERSAL.UTC_TIME + + +class UPERcodec_GENERALIZED_TIME(UPERcodec_UNSUPPORTED_TIME): + tag = ASN1_Class_UNIVERSAL.GENERALIZED_TIME + + +class UPERcodec_ISO646_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.ISO646_STRING + + +class UPERcodec_UNIVERSAL_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.UNIVERSAL_STRING + + +class UPERcodec_BMP_STRING(UPERcodec_KNOWN_MULTIPLIER_STRING): + tag = ASN1_Class_UNIVERSAL.BMP_STRING + + +# KNOWN_MULTIPLIER inherits STRING's tag for registration; restore the +# generic STRING codec used by ASN1F_STRING (octet-string UPER path). +ASN1_Class_UNIVERSAL.STRING.register(ASN1_Codecs.PER, UPERcodec_STRING) diff --git a/scapy/asn1fields.py b/scapy/asn1fields.py index 0d92161153e..6d9a7ee4ff4 100644 --- a/scapy/asn1fields.py +++ b/scapy/asn1fields.py @@ -6,6 +6,12 @@ """ Classes that implement ASN.1 data structures. + +ASN.1 schema fields form a tree (``ASN1F_SEQUENCE``, ``ASN1F_CHOICE``, …), +not a flat ``fields_desc`` list like Scapy ``Field`` instances. The +``encode_to`` / ``decode_from`` methods are the tree analogue of +``Field.addfield`` / ``Field.getfield``; ``build`` / ``dissect`` delegate to +those entry points for backward compatibility. """ import copy @@ -17,6 +23,7 @@ ASN1_BOOLEAN, ASN1_Class, ASN1_Class_UNIVERSAL, + ASN1_Codecs, ASN1_Decoding_Error, ASN1_Error, ASN1_INTEGER, @@ -25,10 +32,10 @@ ASN1_Object, ASN1_STRING, ) -from scapy.asn1.ber import ( - BER_Decoding_Error, - BER_id_dec, -) +from scapy.asn1.ber import BER_Decoding_Error +from scapy.asn1.constraints import normalize_constraints +from scapy.asn1.context import new_decoder, new_encoder +from scapy.asn1.tag import asn1_tag_parts from scapy.base_classes import BasePacket from scapy.volatile import ( GeneralizedTime, @@ -92,6 +99,7 @@ def __init__(self, explicit_tag=None, # type: Optional[int] flexible_tag=False, # type: Optional[bool] size_len=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None if context is not None: @@ -104,6 +112,7 @@ def __init__(self, else: self.default = self.ASN1_tag.asn1_object(default) # type: ignore self.size_len = size_len + self.constraints = normalize_constraints(codec_opts) self.flexible_tag = flexible_tag if (implicit_tag is not None) and (explicit_tag is not None): err_msg = "field cannot be both implicitly and explicitly tagged" @@ -118,19 +127,34 @@ def register_owner(self, cls): # type: (Type[ASN1_Packet]) -> None self.owners.append(cls) - def _apply_diff_tag(self, diff_tag): - # type: (Optional[int]) -> None - # this implies that flexible_tag was True + def _apply_diff_tag(self, pkt, diff_tag): + # type: (ASN1_Packet, Optional[int]) -> None + # flexible_tag was True: record the observed tag on the packet so + # shared field descriptors stay immutable across interleaved decodes. if diff_tag is not None: - if self.implicit_tag is not None: - self.implicit_tag = diff_tag - elif self.explicit_tag is not None: - self.explicit_tag = diff_tag + tags = pkt._asn1_observed_tags + if tags is None: + tags = {} + pkt._asn1_observed_tags = tags + tags[self.name] = diff_tag + + def _tagging_tags(self, pkt): + # type: (ASN1_Packet) -> Tuple[Optional[int], Optional[int]] + imp = self.implicit_tag + exp = self.explicit_tag + if self.flexible_tag: + observed = getattr(pkt, "_asn1_observed_tags", None) or {} + diff = observed.get(self.name) + if diff is not None: + if imp is not None: + imp = diff + elif exp is not None: + exp = diff + return imp, exp def _tagging_dec(self, pkt, s, **kwargs): # type: (ASN1_Packet, bytes, **Any) -> Tuple[Optional[int], bytes] - # Codec provides tagging_*; OER implements real tags, UPER/PER use - # identity helpers (no BER-style tagging). + # Codec provides tagging_*; OER/PER register identity helpers. return pkt.ASN1_codec.tagging_dec(s, **kwargs) # type: ignore def _tagging_enc(self, pkt, s, **kwargs): @@ -151,20 +175,13 @@ def _apply_tagging_dec(self, s, pkt, hidden_tag=None, **kwargs): safe=self.flexible_tag, **kwargs, ) - self._apply_diff_tag(diff_tag) + self._apply_diff_tag(pkt, diff_tag) return s - def _codec_kwargs(self, pkt): - # type: (ASN1_Packet) -> Dict[str, Any] - # OER/UPER need extra constraints (oer_unsigned, uper_min/max, …) on - # every enc/dec call; override this instead of hardcoding BER size_len. - return {"size_len": self.size_len} - - def _use_object_enc(self, pkt, item): - # type: (ASN1_Packet, ASN1_Object[Any]) -> bool - # BER/LDAP: item.enc() when size_len is unset. UPER must override to - # False so constrained integers go through codec.enc(**kwargs). - return self.size_len is None + def normalize_encode_value(self, pkt, value): + # type: (ASN1_Packet, Any) -> Any + """Convert a human-facing value before codec encode (e.g. enum names).""" + return value def _encode_item(self, pkt, item): # type: (ASN1_Packet, Any) -> bytes @@ -181,15 +198,18 @@ def _encode_item(self, pkt, item): "Encoding Error: got %r instead of an %r for field [%s]" % (item, self.ASN1_tag, self.name) ) - if self._use_object_enc(pkt, item): - return item.enc(pkt.ASN1_codec) item = item.val elif hasattr(item, "self_build"): # Packet values (e.g. ASN1F_STRING_PacketField) must still go through # the BER type codec so the universal tag/length are applied. item = item.self_build() codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) - return codec.enc(item, **self._codec_kwargs(pkt)) + return cast( + bytes, + codec.enc( + item, field=self, pkt=pkt, size_len=self.size_len, + ), + ) def i2repr(self, pkt, x): # type: (ASN1_Packet, _I) -> str @@ -215,18 +235,28 @@ def m2i(self, pkt, s): """ s = self._apply_tagging_dec(s, pkt, _fname=self.name) codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) - dec = codec.safedec if self.flexible_tag else codec.dec - return dec(s, context=self.context, **self._codec_kwargs(pkt)) # type: ignore + decode = codec.safedec if self.flexible_tag else codec.dec + return cast( + Tuple[_A, bytes], + decode( + s, + context=self.context, + field=self, + pkt=pkt, + size_len=self.size_len, + ), + ) def i2m(self, pkt, x): # type: (ASN1_Packet, Union[bytes, _I, _A]) -> bytes if x is None: return b"" s = self._encode_item(pkt, x) + imp, exp = self._tagging_tags(pkt) return self._tagging_enc( pkt, s, - implicit_tag=self.implicit_tag, - explicit_tag=self.explicit_tag, + implicit_tag=imp, + explicit_tag=exp, ) def any2i(self, pkt, x): @@ -236,13 +266,14 @@ def any2i(self, pkt, x): def extract_packet(self, cls, # type: Type[ASN1_Packet] s, # type: bytes - _underlayer=None # type: Optional[ASN1_Packet] + _underlayer=None, # type: Optional[ASN1_Packet] + _parent=None # type: Optional[ASN1_Packet] ): # type: (...) -> Tuple[ASN1_Packet, bytes] try: - c = cls(s, _underlayer=_underlayer) + c = cls(s, _underlayer=_underlayer, _parent=_parent) except ASN1F_badsequence: - c = packet.Raw(s, _underlayer=_underlayer) # type: ignore + c = packet.Raw(s, _underlayer=_underlayer, _parent=_parent) # type: ignore cpad = c.getlayer(packet.Raw) s = b"" if cpad is not None: @@ -251,15 +282,70 @@ def extract_packet(self, del cpad.underlayer.payload return c, s + def m2i_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> Any + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + return codec.dec_from_decoder( + dec, field=self, pkt=pkt, size_len=self.size_len, + ) + + def dissect_from_decoder(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + self.set_val(pkt, self.m2i_from_decoder(pkt, dec)) + + def encode_into(self, bit_enc, pkt, value=None): + # type: (Any, ASN1_Packet, Any) -> None + """Encode into a raw UPER bit encoder (not a byte-oriented context).""" + if value is None: + value = getattr(pkt, self.name) + if value is None: + return + value = self.normalize_encode_value(pkt, value) + codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) + if isinstance(value, ASN1_Object): + if (self.ASN1_tag == ASN1_Class_UNIVERSAL.ANY or + value.tag == ASN1_Class_UNIVERSAL.RAW or + value.tag == ASN1_Class_UNIVERSAL.ERROR or + self.ASN1_tag == value.tag): + raw = value.val + else: + raise ASN1_Error( + "Encoding Error: got %r instead of an %r for field [%s]" % + (value, self.ASN1_tag, self.name) + ) + else: + raw = value + codec.encode_into( + bit_enc, raw, field=self, pkt=pkt, size_len=self.size_len, + ) + + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + if enc.codec is ASN1_Codecs.PER: + self.encode_into(enc.bit_encoder, pkt) + else: + enc.write(self.i2m(pkt, getattr(pkt, self.name))) + + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + if dec.codec is ASN1_Codecs.PER: + self.dissect_from_decoder(pkt, dec.bit_decoder) + else: + val, remain = self.m2i(pkt, dec.remaining()) + self.set_val(pkt, val) + dec.set_remainder(remain) + def build(self, pkt): # type: (ASN1_Packet) -> bytes - return self.i2m(pkt, getattr(pkt, self.name)) + enc = new_encoder(pkt.ASN1_codec) + self.encode_to(pkt, enc) + return cast(bytes, enc.finish()) def dissect(self, pkt, s): # type: (ASN1_Packet, bytes) -> bytes - v, s = self.m2i(pkt, s) - self.set_val(pkt, v) - return s + dec = new_decoder(pkt.ASN1_codec, s) + self.decode_from(pkt, dec) + return cast(bytes, dec.remaining()) def do_copy(self, x): # type: (Any) -> Any @@ -326,12 +412,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_enum_INTEGER, self).__init__( name, default, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts ) i2s = self.i2s = {} # type: Dict[int, str] s2i = self.s2i = {} # type: Dict[str, int] @@ -345,6 +433,18 @@ def __init__(self, i2s[k] = enum[k] s2i[enum[k]] = k + def uper_enum_values(self): + # type: () -> List[int] + # Sort on each call: i2s remains a normal mutable Scapy mapping, and + # ENUMERATED sets are normally tiny. + return sorted(self.i2s) + + def normalize_encode_value(self, pkt, value): + # type: (ASN1_Packet, Any) -> Any + if isinstance(value, str): + return self.s2i[value] + return value + def i2m(self, pkt, # type: ASN1_Packet s, # type: Union[bytes, str, int, ASN1_INTEGER] @@ -378,12 +478,14 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[int] + **codec_opts # type: Any ): # type: (...) -> None super(ASN1F_BIT_STRING, self).__init__( name, None, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) if isinstance(default, (bytes, str)): self.default = ASN1_BIT_STRING(default, @@ -494,11 +596,23 @@ class ASN1F_SEQUENCE(ASN1F_field[List[Any], List[Any]]): def __init__(self, *seq, **kwargs): # type: (*Any, **Any) -> None name = "dummy_seq_name" - default = [field.default for field in seq] + default = [] + for field in seq: + if isinstance(field, ASN1F_DEFAULT): + default.append(field._default) + elif isinstance(field, ASN1F_optional): + default.append(None) + else: + default.append(field.default) super(ASN1F_SEQUENCE, self).__init__( name, default, **kwargs ) self.seq = seq + # Codecs that describe presence out of band (OER/PER preambles) need + # the optional components in declaration order. + self.optionals = tuple( + f for f in seq if isinstance(f, ASN1F_optional) + ) # type: Tuple[ASN1F_optional, ...] self.islist = len(seq) > 1 def __repr__(self): @@ -514,45 +628,47 @@ def get_fields_list(self): return reduce(lambda x, y: x + y.get_fields_list(), self.seq, []) - def m2i(self, pkt, s): - # type: (Any, bytes) -> Tuple[Any, bytes] - """ - ASN1F_SEQUENCE behaves transparently, with nested ASN1_objects being - dissected one by one. Because we use obj.dissect (see loop below) - instead of obj.m2i (as we trust dissect to do the appropriate set_vals) - we do not directly retrieve the list of nested objects. - Thus m2i returns an empty list (along with the proper remainder). - It is discarded by dissect() and should not be missed elsewhere. - """ - s = self._apply_tagging_dec(s, pkt, _fname=pkt.name) - codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) - i, s, remain = codec.check_type_check_len(s) - if len(s) == 0: - for obj in self.seq: + def _dissect_sequence_children(self, pkt, s): + # type: (Any, bytes) -> bytes + def set_absent(obj): + # type: (Any) -> None + if isinstance(obj, (ASN1F_optional, ASN1F_DEFAULT)): + obj.set_missing(pkt) + else: obj.set_val(pkt, None) - else: + + if len(s) == 0: for obj in self.seq: - try: - s = obj.dissect(pkt, s) - except ASN1F_badsequence: - break - if len(s) > 0: - raise BER_Decoding_Error( - "unexpected remainder in %s" % pkt.name, - remaining=s, - ) + set_absent(obj) + return s + for idx, obj in enumerate(self.seq): + try: + s = obj.dissect(pkt, s) + except ASN1F_badsequence: + for absent in self.seq[idx:]: + set_absent(absent) + return s + return s + + def m2i(self, pkt, s): + # type: (Any, bytes) -> Tuple[Any, bytes] + dec = new_decoder(pkt.ASN1_codec, s) + self.decode_from(pkt, dec) + remain = dec.remaining() + if dec.codec is ASN1_Codecs.PER and remain: + from scapy.asn1.uper import UPER_Decoding_Error + raise UPER_Decoding_Error( + "unexpected remainder in %s" % pkt.__class__.__name__, + ) return [], remain - def dissect(self, pkt, s): - # type: (Any, bytes) -> bytes - _, x = self.m2i(pkt, s) - return x + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + enc.encode_sequence(self, pkt) - def build(self, pkt): - # type: (ASN1_Packet) -> bytes - s = reduce(lambda x, y: x + y.build(pkt), - self.seq, b"") - return super(ASN1F_SEQUENCE, self).i2m(pkt, s) + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + dec.decode_sequence(self, pkt) class ASN1F_SET(ASN1F_SEQUENCE): @@ -571,6 +687,10 @@ class ASN1F_SEQUENCE_OF(ASN1F_field[List[_SEQ_T], List[ASN1_Object[Any]]]): """ Two types are allowed as cls: ASN1_Packet, ASN1F_field + + Structured items are normally ASN1_Packet (or ASN1F_PACKET). Compound + ASN1F_field elements (SEQUENCE / CHOICE / SEQUENCE OF) remain constructible + for BER/OER; UPER rejects them at encode/decode time. """ ASN1_tag = ASN1_Class_UNIVERSAL.SEQUENCE islist = 1 @@ -582,6 +702,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[Any] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None if isinstance(cls, type) and issubclass(cls, ASN1F_field) or \ @@ -595,13 +716,14 @@ def __init__(self, elif hasattr(cls, "ASN1_root") or callable(cls): self.cls = cast("Type[ASN1_Packet]", cls) self._extract_packet = lambda s, pkt: self.extract_packet( - self.cls, s, _underlayer=pkt) + self.cls, s, _underlayer=pkt, _parent=pkt) self.holds_packets = 1 else: raise ValueError("cls should be an ASN1_Packet or ASN1_field") super(ASN1F_SEQUENCE_OF, self).__init__( name, None, context=context, - implicit_tag=implicit_tag, explicit_tag=explicit_tag + implicit_tag=implicit_tag, explicit_tag=explicit_tag, + **codec_opts, ) self.default = default @@ -611,41 +733,19 @@ def is_empty(self, # type: (...) -> bool return ASN1F_field.is_empty(self, pkt) - def m2i(self, - pkt, # type: ASN1_Packet - s, # type: bytes - ): - # type: (...) -> Tuple[List[Any], bytes] - s = self._apply_tagging_dec(s, pkt) - codec = self.ASN1_tag.get_codec(pkt.ASN1_codec) - i, s, remain = codec.check_type_check_len(s) - lst = [] - while s: - c, s = self._extract_packet(s, pkt) # type: ignore - if c: - lst.append(c) - if len(s) > 0: - raise BER_Decoding_Error( - "unexpected remainder in %s" % pkt.name, - remaining=s, - ) - return lst, remain + def m2i(self, pkt, s): + # type: (ASN1_Packet, bytes) -> Tuple[List[Any], bytes] + dec = new_decoder(pkt.ASN1_codec, s) + self.decode_from(pkt, dec) + return getattr(pkt, self.name), dec.remaining() - def build(self, pkt): - # type: (ASN1_Packet) -> bytes - val = getattr(pkt, self.name) - if isinstance(val, ASN1_Object) and \ - val.tag == ASN1_Class_UNIVERSAL.RAW: - s = cast(Union[List[_SEQ_T], bytes], val) - elif val is None: - s = b"" - elif self.holds_packets: - s = b"".join(bytes(i) for i in val) - else: - # BER: element fields may carry implicit/explicit tags; i2m - # matches m2i()/fld.m2i(). (Packet elements use bytes() above.) - s = b"".join(self.fld.i2m(pkt, i) for i in val) - return self.i2m(pkt, s) + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + enc.encode_sequence_of(self, pkt) + + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + dec.decode_sequence_of(self, pkt) def i2repr(self, pkt, x): # type: (ASN1_Packet, _I) -> str @@ -696,9 +796,25 @@ def __init__(self, field): self._field = field def __getattr__(self, attr): - # type: (str) -> Optional[Any] + # type: (str) -> Any + if attr.startswith("_"): + raise AttributeError(attr) return getattr(self._field, attr) + @property + def fld(self): + # type: () -> ASN1F_field[Any, Any] + return self._field + + def get_fields_list(self): + # type: () -> List[ASN1F_field[Any, Any]] + inner = self._field.get_fields_list() + if inner == [self._field]: + field = self._field.copy() + field.default = None + return [field] + return inner + def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] try: @@ -712,15 +828,40 @@ def dissect(self, pkt, s): try: return self._field.dissect(pkt, s) except (ASN1_Error, ASN1F_badsequence, ASN1_Decoding_Error): - self._field.set_val(pkt, None) + self.set_missing(pkt) return s + def is_present(self, pkt): + # type: (ASN1_Packet) -> bool + # Delegate to the wrapped field: an optional SEQUENCE uses a dummy + # name and is empty iff all of its children are. + return not self._field.is_empty(pkt) + + def set_missing(self, pkt): + # type: (ASN1_Packet) -> None + """Called when the encoding does not carry the component.""" + self._field.set_val(pkt, None) + + def is_empty(self, pkt): + # type: (ASN1_Packet) -> bool + return not self.is_present(pkt) + def build(self, pkt): # type: (ASN1_Packet) -> bytes - if self._field.is_empty(pkt): + # Through self, so that a DEFAULT component omits its default value. + if not self.is_present(pkt): return b"" return self._field.build(pkt) + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + if self.is_present(pkt): + self._field.encode_to(pkt, enc) + + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + self._field.decode_from(pkt, dec) + def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> Any return self._field.any2i(pkt, x) @@ -730,6 +871,44 @@ def i2repr(self, pkt, x): return self._field.i2repr(pkt, x) +class ASN1F_DEFAULT(ASN1F_optional): + """ + ASN.1 field holding a DEFAULT value: it is omitted from the encoding while + it holds that value, and restored when the encoding does not carry it. + + As with OPTIONAL components, a BER encoding only tells the component apart + from the one that follows it by its tag, so the schema must give it a + distinct one. OER and PER describe presence in the preamble instead. + """ + def __init__(self, field, default): + # type: (ASN1F_field[Any, Any], Any) -> None + super(ASN1F_DEFAULT, self).__init__(field) + self._default = default + + def get_fields_list(self): + # type: () -> List[ASN1F_field[Any, Any]] + inner = self._field.get_fields_list() + if inner == [self._field]: + return [self._field.copy()] + return inner + + def is_present(self, pkt): + # type: (ASN1_Packet) -> bool + val = getattr(pkt, self._field.name, None) + if val is None: + return False + if isinstance(val, ASN1_Object): + val = val.val + default = self._default + if isinstance(default, ASN1_Object): + default = default.val + return bool(val != default) + + def set_missing(self, pkt): + # type: (ASN1_Packet) -> None + self._field.set_val(pkt, self._default) + + class ASN1F_omit(ASN1F_field[None, None]): """ ASN.1 field that is not specified. This is simply omitted on the network. @@ -762,16 +941,17 @@ def __init__(self, name, default, *args, **kwargs): err_msg = "ASN1F_CHOICE has been called with an implicit_tag" raise ASN1_Error(err_msg) self.implicit_tag = None - for kwarg in ["context", "explicit_tag"]: - setattr(self, kwarg, kwargs.get(kwarg)) + context = kwargs.pop("context", None) + explicit_tag = kwargs.pop("explicit_tag", None) + # Remaining kwargs are codec constraints (e.g. extensible=). super(ASN1F_CHOICE, self).__init__( - name, None, context=self.context, - explicit_tag=self.explicit_tag + name, None, context=context, + explicit_tag=explicit_tag, + **kwargs ) self.default = default - self.current_choice = None self.choices = {} # type: Dict[int, _CHOICE_T] - self.pktchoices = {} + self.pktchoices = {} # type: Dict[type, Tuple[Optional[int], Optional[int]]] for p in args: if hasattr(p, "ASN1_root"): p = cast('ASN1_Packet', p) @@ -790,60 +970,51 @@ def __init__(self, name, default, *args, **kwargs): else: # should be ASN1F_PACKET instance self.choices[p.network_tag] = p - self.pktchoices[hash(p.cls)] = (p.implicit_tag, p.explicit_tag) # noqa: E501 + self.pktchoices[p.cls] = (p.implicit_tag, p.explicit_tag) else: raise ASN1_Error("ASN1F_CHOICE: no tag found for one field") + # X.691 10.2: PER indexes alternatives in canonical tag order. + canon_items = sorted( + self.choices.items(), + key=lambda item: asn1_tag_parts(item[0])[:2], + ) + self.canonical_order = [alt for _tag, alt in canon_items] + self.canonical_index = { + tag: i for i, (tag, _alt) in enumerate(canon_items) + } # type: Dict[int, int] + + def alternative_tag(self, x): + # type: (Any) -> Optional[int] + """Return the CHOICE alternative tag that carries x, or None.""" + for tag, choice in self.choices.items(): + if isinstance(choice, type): + if hasattr(choice, "ASN1_root"): + # ASN1_Packet subclass + if isinstance(x, choice): + return tag + elif isinstance(x, ASN1_Object) and x.tag == choice.ASN1_tag: + # ASN1F_field subclass + return tag + elif isinstance(x, choice.cls): + # ASN1F_PACKET instance, holding a tagged packet + return tag + return None def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Object[Any], bytes] - """ - First we have to retrieve the appropriate choice. - Then we extract the field/packet, according to this choice. - """ if len(s) == 0: raise ASN1_Error("ASN1F_CHOICE: got empty string") - s = self._apply_tagging_dec(s, pkt) - tag, _ = BER_id_dec(s) - if tag in self.choices: - choice = self.choices[tag] - else: - if self.flexible_tag: - choice = ASN1F_field - else: - raise ASN1_Error( - "ASN1F_CHOICE: unexpected field in '%s' " - "(tag %s not in possible tags %s)" % ( - self.name, tag, list(self.choices.keys()) - ) - ) - if hasattr(choice, "ASN1_root"): - # we don't want to import ASN1_Packet in this module... - return self.extract_packet(choice, s, _underlayer=pkt) # type: ignore - elif isinstance(choice, type): - return choice(self.name, b"").m2i(pkt, s) - else: - # XXX check properly if this is an ASN1F_PACKET - return choice.m2i(pkt, s) + dec = new_decoder(pkt.ASN1_codec, s) + self.decode_from(pkt, dec) + return getattr(pkt, self.name), dec.remaining() - def i2m(self, pkt, x): - # type: (ASN1_Packet, Any) -> bytes - if x is None: - s = b"" - else: - # Use the packet codec for ASN1_Object values; bytes(x) would - # follow conf.ASN1_default_codec instead. - if isinstance(x, ASN1_Object): - s = x.enc(pkt.ASN1_codec) - else: - s = bytes(x) - if hash(type(x)) in self.pktchoices: - imp, exp = self.pktchoices[hash(type(x))] - s = self._tagging_enc( - pkt, s, - implicit_tag=imp, - explicit_tag=exp, - ) - return self._tagging_enc(pkt, s, explicit_tag=self.explicit_tag) + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + enc.encode_choice(self, pkt) + + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + dec.decode_choice(self, pkt) def randval(self): # type: () -> RandChoice @@ -888,54 +1059,29 @@ def __init__(self, def m2i(self, pkt, s): # type: (ASN1_Packet, bytes) -> Tuple[Any, bytes] - if self.next_cls_cb: - cls = self.next_cls_cb(pkt) or self.cls - else: - cls = self.cls - if not hasattr(cls, "ASN1_root"): - # A normal Packet (!= ASN1) - return self.extract_packet(cls, s, _underlayer=pkt) - s = self._apply_tagging_dec( - s, pkt, - hidden_tag=cls.ASN1_root.ASN1_tag, # noqa: E501 - _fname=self.name, - ) - if not s: - return None, s - return self.extract_packet(cls, s, _underlayer=pkt) + dec = new_decoder(pkt.ASN1_codec, s) + self.decode_from(pkt, dec) + return getattr(pkt, self.name), dec.remaining() - def i2m(self, - pkt, # type: ASN1_Packet - x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 - ): - # type: (...) -> bytes - if x is None: - s = b"" - elif isinstance(x, bytes): - s = x - elif isinstance(x, ASN1_Object): - if x.val: - s = bytes(x.val) - else: - s = b"" - else: - s = bytes(x) - if not hasattr(x, "ASN1_root"): - # A normal Packet (!= ASN1) - return s - return self._tagging_enc( - pkt, s, - implicit_tag=self.implicit_tag, - explicit_tag=self.explicit_tag, - ) + def encode_to(self, pkt, enc): + # type: (ASN1_Packet, Any) -> None + enc.encode_packet(self, pkt) + + def decode_from(self, pkt, dec): + # type: (ASN1_Packet, Any) -> None + dec.decode_packet(self, pkt) def any2i(self, pkt, # type: ASN1_Packet x # type: Union[bytes, ASN1_Packet, None, ASN1_Object[Optional[ASN1_Packet]]] # noqa: E501 ): # type: (...) -> 'ASN1_Packet' + # Kerberos EncryptedData.get_usage() walks underlayer; X.509 and + # OER nested packets also use parent. Set both when available. if hasattr(x, "add_underlayer"): x.add_underlayer(pkt) # type: ignore + if hasattr(x, "add_parent"): + x.add_parent(pkt) # type: ignore return super(ASN1F_PACKET, self).any2i(pkt, x) def randval(self): # type: ignore @@ -975,7 +1121,7 @@ def m2i(self, pkt, s): # type: ignore raise BER_Decoding_Error("wrong bit string", remaining=s) if bit_string.val_readable: p, s = self.extract_packet(self.cls, bit_string.val_readable, - _underlayer=pkt) + _underlayer=pkt, _parent=pkt) else: return None, bit_string.val_readable if len(s) > 0: @@ -1003,6 +1149,7 @@ def __init__(self, context=None, # type: Optional[Any] implicit_tag=None, # type: Optional[int] explicit_tag=None, # type: Optional[Any] + **codec_opts # type: Any ): # type: (...) -> None self.mapping = mapping @@ -1011,7 +1158,8 @@ def __init__(self, default_readable=False, context=context, implicit_tag=implicit_tag, - explicit_tag=explicit_tag + explicit_tag=explicit_tag, + **codec_opts, ) def any2i(self, pkt, x): @@ -1056,6 +1204,8 @@ def any2i(self, pkt, x): # type: (ASN1_Packet, Any) -> Any if hasattr(x, "add_underlayer"): x.add_underlayer(pkt) + if hasattr(x, "add_parent"): + x.add_parent(pkt) return super(ASN1F_STRING_PacketField, self).any2i(pkt, x) @@ -1085,4 +1235,4 @@ def __init__(self, def m2i(self, pkt, s): # type: ignore # type: (ASN1_Packet, bytes) -> Tuple[ASN1_Packet, bytes] val = super(ASN1F_STRING_ENCAPS, self).m2i(pkt, s) - return self.cls(val[0].val, _underlayer=pkt), val[1] + return self.cls(val[0].val, _underlayer=pkt, _parent=pkt), val[1] diff --git a/scapy/asn1packet.py b/scapy/asn1packet.py index 058aecc0edb..bd235d1ac1e 100644 --- a/scapy/asn1packet.py +++ b/scapy/asn1packet.py @@ -15,6 +15,7 @@ from typing import ( Any, Dict, + Optional, Tuple, Type, cast, @@ -42,14 +43,22 @@ def __new__(cls, class ASN1_Packet(Packet, metaclass=ASN1Packet_metaclass): ASN1_root = cast('ASN1F_field[Any, Any]', None) - ASN1_codec = None + ASN1_codec = cast(Any, None) + _asn1_observed_tags = None # type: Optional[Dict[str, int]] def self_build(self): # type: () -> bytes if self.raw_packet_cache is not None: return self.raw_packet_cache - return self.ASN1_root.build(self) + from scapy.asn1.context import new_encoder + enc = new_encoder(self.ASN1_codec) + self.ASN1_root.encode_to(self, enc) + return cast(bytes, enc.finish()) def do_dissect(self, x): # type: (bytes) -> bytes - return self.ASN1_root.dissect(self, x) + self._asn1_observed_tags = {} + from scapy.asn1.context import new_decoder + dec = new_decoder(self.ASN1_codec, x) + self.ASN1_root.decode_from(self, dec) + return cast(bytes, dec.remaining()) diff --git a/test/scapy/layers/asn1.uts b/test/scapy/layers/asn1.uts index 9fa0bad0f44..2e3666d8c89 100644 --- a/test/scapy/layers/asn1.uts +++ b/test/scapy/layers/asn1.uts @@ -23,9 +23,12 @@ repr(ASN1_GENERALIZED_TIME("19991231235959")).startswith("1999-12-31 23:59:59 <" repr(ASN1_GENERALIZED_TIME("19991231235959.999")).startswith("1999-12-31 23:59:59.999 <") = with microseconds (invalid) assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.99x")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("1999123125959.9999")) +True + ASN.1 Generalized Time (Zulu) = Z short HH @@ -52,8 +55,10 @@ repr(ASN1_GENERALIZED_TIME("19991231235959.999+0100")).startswith("1999-12-31 23 repr(ASN1_GENERALIZED_TIME("19991231235959-2359")).startswith("1999-12-31 23:59:59 -2359 <") = offset invalid (offset >= 24h) assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959-2400")) + assert "invalid" in repr(ASN1_GENERALIZED_TIME("19991231235959+2400")) +True + ASN.1 UTC Time = UTC short HHMM @@ -83,10 +88,17 @@ ASN1_GENERALIZED_TIME("199912312359").datetime == datetime(1999, 12, 31, 23, 59) ASN1_GENERALIZED_TIME("19991231235959").datetime == datetime(1999, 12, 31, 23, 59, 59) = datetime assignment x = ASN1_GENERALIZED_TIME("19991231235959.999") + x.datetime = datetime(2020, 12, 31) + assert x.val == "20201231000000" + x.datetime = x.datetime.replace(tzinfo=timezone.utc) + x.val == "20201231000000Z" + +True + = datetime construction ASN1_GENERALIZED_TIME(datetime(2020, 12, 31)).val == "20201231000000" = datetime construction (UTC) @@ -101,3 +113,638 @@ ASN1_UTC_TIME(datetime(2020, 12, 31)).val == "201231000000" ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone.utc)).val == "201231000000Z" = UTC datetime construction (offset) ASN1_UTC_TIME(datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-23, minutes=-59)))).val == "201231000000-2359" + ++ ASN.1 cross-codec build and dissect += import OER and UPER codecs +import scapy.asn1.oer +import scapy.asn1.uper +from scapy.asn1.oer import * +from scapy.asn1.uper import * +from scapy.packet import raw += prepare helpers and packet classes +class BERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class BERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1), + ASN1F_STRING("s", "", size_len=3), + ) + +class BEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class BERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class BERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class BERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class BEROptionalSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("hdr", 0), + ASN1F_optional(ASN1F_SEQUENCE( + ASN1F_INTEGER("id", None), + ASN1F_STRING("label", None), + explicit_tag=0xA0, + )), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", minimum=1, maximum=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", minimum=1, maximum=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", minimum=16, maximum=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, minimum=0, maximum=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=-128, maximum=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) += ber oer per choice build +class BERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class PERChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +for cls in (BERChoice, OERChoice, PERChoice): + as_int = cls(c=ASN1_INTEGER(99)) + assert len(raw(as_int)) > 0 + decoded = _roundtrip(cls, as_int) + assert decoded.c.val == 99 + as_str = cls(c=ASN1_STRING(b"AB")) + assert len(raw(as_str)) > 0 + decoded = _roundtrip(cls, as_str) + assert decoded.c.val == b"AB" + +True + += ber oer per record dissect +for cls, data_hex in [ + ( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + ), + ( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", + ), + ( + UPERRecord, + "8095409a1a4041c0c04040408040c0", + ), +]: + _assert_record(_dissect(cls, data_hex)) + +True + += ber oer per constrained integer constraints +class BERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, + ) + +class OERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, + ) + +class PERConstrained(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, + ) + +for cls, expected in ( + (BERConstrained, b"\x02\x81\x02\x00\xc8"), + (OERConstrained, b"\xc8"), + (PERConstrained, b"\xc8"), +): + pkt = cls(n=200) + assert raw(pkt) == expected + assert _roundtrip(cls, pkt).n.val == 200 + assert cls.ASN1_root.constraints.unsigned is True + assert cls.ASN1_root.constraints.minimum == 0 + assert cls.ASN1_root.constraints.maximum == 255 + +True + += ber oer per empty sequence of +class BEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class PEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, minimum=0, maximum=7), + minimum=0, + maximum=3, + ) + +for cls in (BEREmptySeqOf, OEREmptySeqOf, PEREmptySeqOf): + pkt = cls(values=[]) + decoded = _roundtrip(cls, pkt) + assert decoded.values == [] + assert len(raw(pkt)) > 0 + +True + += ber oer per default component +class _BerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _OerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +class _PerDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 1), + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +# A component holding its default value is not encoded, and comes back as the +# default when the encoding does not carry it. An unset component counts as +# holding it, and the default may be given as an ASN.1 object. +for cls in (_BerDefault, _OerDefault, _PerDefault): + assert len(raw(cls(a=1, b=7))) < len(raw(cls(a=1, b=9))) + absent = _roundtrip(cls, cls(a=1, b=7)).b + assert getattr(absent, "val", absent) == 7 + assert _roundtrip(cls, cls(a=1, b=9)).b.val == 9 + assert raw(cls(a=1, b=None)) == raw(cls(a=1, b=7)) + +class _AsnObjectDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT(ASN1F_INTEGER("a", 7), ASN1_INTEGER(7)), + ) + +assert raw(_AsnObjectDefault(a=ASN1_INTEGER(7))) == raw(_AsnObjectDefault(a=7)) + +assert raw(_AsnObjectDefault(a=7)) == b"\x00" + +True + +% PR #5050 refactor targets (shared ASN.1) + +def _value(obj): + return getattr(obj, "val", obj) + ++ Finding - flexible BER tags must not mutate shared field descriptors += flexible explicit tag decode keeps schema descriptor immutable +class RefactorFlexibleExplicit(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, + explicit_tag=0xA0, + flexible_tag=True, + ) + +field = RefactorFlexibleExplicit.ASN1_root +original_tag = field.explicit_tag +canonical = raw(RefactorFlexibleExplicit(n=5)) +assert canonical[0] == 0xA0 +foreign = b"\xa1" + canonical[1:] +decoded = RefactorFlexibleExplicit(foreign) +assert _value(decoded.n) == 5 +# Packet-local tolerance must not rewrite the class-level schema. +assert field.explicit_tag == original_tag == 0xA0 +# A later independent build must still use the declared tag. +assert raw(RefactorFlexibleExplicit(n=5)) == canonical +True + += flexible implicit tag decode keeps schema descriptor immutable +class RefactorFlexibleImplicit(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, + implicit_tag=0x80, + flexible_tag=True, + ) + +field = RefactorFlexibleImplicit.ASN1_root +original_tag = field.implicit_tag +canonical = raw(RefactorFlexibleImplicit(n=5)) +assert canonical[0] == 0x80 +foreign = b"\x81" + canonical[1:] +decoded = RefactorFlexibleImplicit(foreign) +assert _value(decoded.n) == 5 +assert field.implicit_tag == original_tag == 0x80 +assert raw(RefactorFlexibleImplicit(n=5)) == canonical +True + += two flexible decodes with different observed tags do not influence each other +class RefactorFlexibleSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + explicit_tag=0xA0, + flexible_tag=True, + ), + ) + +field = RefactorFlexibleSequence.ASN1_root.seq[0] +canonical = raw(RefactorFlexibleSequence(n=7)) +# SEQUENCE wrapper is 0x30; component tag begins after the sequence length. +# Locate the declared explicit tag instead of assuming a BER length width. +pos = canonical.index(b"\xa0") +all( + _value(RefactorFlexibleSequence( + canonical[:pos] + bytes([observed_tag]) + canonical[pos + 1:] + ).n) == 7 + and field.explicit_tag == 0xA0 + and raw(RefactorFlexibleSequence(n=7)) == canonical + for observed_tag in [0xA1, 0xA2, 0xA3] +) + + ++ Finding - CHOICE packet classes must be dictionary keys, not hash(cls) += colliding class hashes do not corrupt BER CHOICE tagging +class RefactorCollidingASN1Meta(ASN1Packet_metaclass): + def __hash__(cls): + return 1 + +class RefactorCollisionA(ASN1_Packet, metaclass=RefactorCollidingASN1Meta): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("x", 0)) + +class RefactorCollisionB(ASN1_Packet, metaclass=RefactorCollidingASN1Meta): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("x", 0)) + +assert hash(RefactorCollisionA) == hash(RefactorCollisionB) + +class RefactorCollisionChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a", None, RefactorCollisionA, explicit_tag=0xA0), + ASN1F_PACKET("b", None, RefactorCollisionB, explicit_tag=0xA1), + ) + +raw_a = raw(RefactorCollisionChoice(c=RefactorCollisionA(x=1))) +raw_b = raw(RefactorCollisionChoice(c=RefactorCollisionB(x=2))) +assert raw_a[0] == 0xA0 +assert raw_b[0] == 0xA1 +assert isinstance(RefactorCollisionChoice(raw_a).c, RefactorCollisionA) +assert isinstance(RefactorCollisionChoice(raw_b).c, RefactorCollisionB) +True + + ++ Cross-codec schema contracts += identical schema values can be assigned as Python values or ASN1 objects +class RefactorBERInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) + +class RefactorOERPlainInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0) + +all( + raw(cls(n=value)) == raw(cls(n=ASN1_INTEGER(value))) + for cls in [RefactorBERInt, RefactorOERPlainInteger] + for value in [-1, 0, 1, 127] +) + += importing OER and UPER leaves representative BER packet wire behavior intact +ber_before = raw(RefactorBERInt(n=5)) +import scapy.asn1.oer +import scapy.asn1.uper +ber_after = raw(RefactorBERInt(n=5)) +assert ber_before == ber_after +assert _value(RefactorBERInt(ber_after).n) == 5 +True + +% PR #5050 review regressions ++ BER DEFAULT and X.509 extract_packet += BER DEFAULT-only empty SEQUENCE restores default value +class _BerDefaultOnly(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT(ASN1F_INTEGER("b", 7), 7), + ) + +decoded = _BerDefaultOnly(b"\x30\x00") +assert getattr(decoded.b, "val", decoded.b) == 7 + += extract_packet passes _underlayer for X.509 attribute types +from scapy.layers.x509 import X509_Attribute, X509_Extensions + +wire = bytes.fromhex("300f06092a864886f70d01090e31023000") +attr = X509_Attribute(wire) +assert attr.values[0].underlayer is attr +assert isinstance(attr.values[0].value, X509_Extensions) + += ASN1F_PACKET any2i sets underlayer for Kerberos EncryptedData.get_usage +from scapy.layers.kerberos import KRB_AP_REP, EncryptedData, EncAPRepPart + +ap_rep = KRB_AP_REP(encPart=EncryptedData()) +assert ap_rep.encPart.underlayer is ap_rep +assert ap_rep.encPart.parent is ap_rep +assert ap_rep.encPart.get_usage() == (12, EncAPRepPart) +True diff --git a/test/scapy/layers/ber.uts b/test/scapy/layers/ber.uts index 896f2ec746a..8ea788cde6b 100644 --- a/test/scapy/layers/ber.uts +++ b/test/scapy/layers/ber.uts @@ -457,39 +457,32 @@ assert BERcodec_STRING.enc(b"x", uper_max=10) == BERcodec_STRING.enc(b"x") BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1), uper_min=0) == BERcodec_SEQUENCE.enc(BERcodec_INTEGER.enc(1)) + ASN.1 codec tagging contract -= BER tagging is exposed on the codec -assert ASN1_Codecs.BER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" -diff, payload = ASN1_Codecs.BER.tagging_dec( - b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0 -) += BER applies tagging on encode and decode +from scapy.asn1.ber import BER_tagging_enc, BER_tagging_dec + +assert BER_tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\xa0\x01\x05" + +diff, payload = BER_tagging_dec(b"\xa0\x01\x05", hidden_tag=2, implicit_tag=0xA0) + diff is None and payload == b"\x02\x01\x05" -= identity tagging for PER-style codecs -def _id_tagging_enc(s, **kwargs): - return s += a non-BER codec leaves field tagging alone +class _NoTagging: + ASN1_codec = ASN1_Codecs.CER -def _id_tagging_dec(s, **kwargs): - return None, s +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) -ASN1_Codecs.PER.register_tagging(_id_tagging_enc, _id_tagging_dec) -try: - assert ASN1_Codecs.PER.tagging_enc(b"\x02\x01\x05", implicit_tag=0xA0) == b"\x02\x01\x05" - diff, payload = ASN1_Codecs.PER.tagging_dec( - b"\x02\x01\x05", hidden_tag=2, explicit_tag=0xA1 - ) - assert diff is None and payload == b"\x02\x01\x05" -finally: - del ASN1_Codecs.PER._tagging_enc - del ASN1_Codecs.PER._tagging_dec +assert fld._tagging_enc(_NoTagging(), b"\x02\x01\x05", explicit_tag=0xA0) == b"\x02\x01\x05" + +fld._tagging_dec(_NoTagging(), b"\x02\x01\x05", explicit_tag=0xA0) == (None, b"\x02\x01\x05") -= field _codec_kwargs and object-enc hooks += field constraints and ASN1_Object unwrap class P(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = ASN1F_INTEGER("n", 0) fld = P.ASN1_root -assert fld._codec_kwargs(P()) == {"size_len": None} -assert fld._use_object_enc(P(), ASN1_INTEGER(5)) is True +assert fld.size_len is None assert raw(P(n=ASN1_INTEGER(5))) == b"\x02\x01\x05" assert raw(P(n=5)) == b"\x02\x01\x05" @@ -498,18 +491,238 @@ class Sized(ASN1_Packet): ASN1_root = ASN1F_INTEGER("n", 0, size_len=1) sfld = Sized.ASN1_root -assert sfld._use_object_enc(Sized(), ASN1_INTEGER(5)) is False +assert sfld.size_len == 1 assert raw(Sized(n=ASN1_INTEGER(5))) == raw(Sized(n=5)) == b"\x02\x81\x01\x05" -= field encode with extra kwargs via _codec_kwargs override -class ExtraKwField(ASN1F_INTEGER): - def _codec_kwargs(self, pkt): - return {"size_len": self.size_len, "oer_unsigned": True, "uper_min": 0} += field encode ignores codec-specific constraints on BER +class ConstrainedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, + ) + +# BER uses size_len from the field; OER/PER keys live in constraints only. +assert raw(ConstrainedField(n=7)) == b"\x02\x81\x01\x07" +ConstrainedField(raw(ConstrainedField(n=7))).n.val == 7 + += field constraints storage +plain = ASN1F_INTEGER("n", 0) -class ExtraPkt(ASN1_Packet): +assert plain.constraints.unsigned is False +assert plain.size_len is None + +constrained = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, +) + +assert constrained.constraints.unsigned is True +assert constrained.constraints.minimum == 0 +assert constrained.constraints.maximum == 255 + +# Constraints must not become field attributes. +assert not hasattr(constrained, "oer_unsigned") + +assert not hasattr(constrained, "uper_min") + +assert constrained.size_len == 1 + +# BER still encodes with size_len from the field. +class ConstrainedBer(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ExtraKwField("n", 0) + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, minimum=0, maximum=255, + ) + +assert raw(ConstrainedBer(n=5)) == b"\x02\x81\x01\x05" + +assert ConstrainedBer(raw(ConstrainedBer(n=5))).n.val == 5 + +True + += CHOICE tag map properties +choice = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, +) + +assert list(choice.choices.keys()) == [2, 4] + +assert list(choice.choices.values())[0] is ASN1F_INTEGER + +assert list(choice.choices.values())[1] is ASN1F_STRING + +True + ++ ASN.1 BER build and dissect extras + += import helpers +from scapy.packet import raw + += prepare helpers +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val -# BER enc swallows unknown kwargs; round-trip still works. -assert raw(ExtraPkt(n=7)) == b"\x02\x01\x07" -ExtraPkt(raw(ExtraPkt(n=7))).n.val == 7 +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + += ber record build roundtrip +pkt = BERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(BERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += ber field dissect +tagged = _dissect(BERTaggedInteger, "a103020105") + +assert tagged.n.val == 5 + +fixed = _dissect(BERFixedFields, "300d02810200c80483000003414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(BEROptionalField, "3008020101a003020107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(BEROptionalField, "3003020101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(BERSequenceOfIntegers, "3009020101020102020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(BERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(BERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += ber record dissect +decoded = _dissect( + BERRecord, + "301a02012a01010104026869" + "a003020107" + "3009020101020102020103", + +) + +_assert_record(decoded) + +empty = _dissect(BERRecord, "300a02010101010004003000") + +_assert_record_empty(empty) + +True + +% PR #5050 review regressions ++ BER review fixes += field size_len is passed through to codecs +class P(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) + +fld = P.ASN1_root +assert fld.size_len is None +assert raw(P(n=ASN1_INTEGER(5))) == b"\x02\x01\x05" +assert raw(P(n=5)) == b"\x02\x01\x05" + += BER ignores unknown codec kwargs from shared call sites +# Codecs accept **kwargs so field=/pkt=/constraint keys do not TypeError on BER. +assert BERcodec_INTEGER.enc(7, field=None, pkt=None, oer_unsigned=True) == b"\x02\x01\x07" +assert BERcodec_INTEGER.enc(7, size_len=None) == b"\x02\x01\x07" + += BER OID first arc decode for 2.999.3 +obj, remain = BERcodec_OID.do_dec(BERcodec_OID.enc("2.999.3")) +assert str(obj.val) == "2.999.3" +assert remain == b"" + += BER build and dissect do not import UPER +import subprocess +import sys +code = r''' +import sys +from scapy.asn1fields import ASN1F_INTEGER +from scapy.asn1packet import ASN1_Packet +from scapy.asn1.asn1 import ASN1_Codecs +from scapy.packet import raw +class P(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) +assert "scapy.asn1.uper" not in sys.modules +raw(P(n=5)) +assert "scapy.asn1.uper" not in sys.modules +P(b"\x02\x01\x01") +assert "scapy.asn1.uper" not in sys.modules +''' +result = subprocess.run([sys.executable, "-c", code]) +assert result.returncode == 0 + += codec tagging dispatch is registered on ASN1_Codecs.BER +assert ASN1_Codecs.BER.tagging_enc(b"\x02\x01\x01") == b"\x02\x01\x01" +diff, s = ASN1_Codecs.BER.tagging_dec( + b"\x02\x01\x01", hidden_tag=ASN1_Class_UNIVERSAL.INTEGER) +assert s == b"\x02\x01\x01" +True diff --git a/test/scapy/layers/oer.uts b/test/scapy/layers/oer.uts new file mode 100644 index 00000000000..a005a8630b3 --- /dev/null +++ b/test/scapy/layers/oer.uts @@ -0,0 +1,1566 @@ +% Tests for ASN.1 OER encoding + +# +# Try me with: +# ./test/run_tests -t test/scapy/layers/oer.uts -N + ++ ASN.1 OER load += prepare helpers and packet classes +import scapy.asn1.oer + +from scapy.asn1.oer import * + +from scapy.packet import raw + +class OERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA1) + +class OERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class OEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ) + +class OERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class OERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class OERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class OERNestedSequenceTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ASN1F_INTEGER("id", 0), + ) + +class OERSequenceOfWithTrailing(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ASN1F_INTEGER("id", 0), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +INTEGER_VECTORS = [ + ("A", 0, lambda v: OERcodec_INTEGER.enc(v), b"\x01\x00"), + ("A", 128, lambda v: OERcodec_INTEGER.enc(v), b"\x02\x00\x80"), + ("A", 100000, lambda v: OERcodec_INTEGER.enc(v), b"\x03\x01\x86\xa0"), + ("A", -255, lambda v: OERcodec_INTEGER.enc(v), b"\x02\xff\x01"), + ("A", -1234567, lambda v: OERcodec_INTEGER.enc(v), b"\x03\xed)y"), + ("B", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\xfe"), + ("C", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=2), b"\xff\xfe"), + ("D", -2, lambda v: OERcodec_INTEGER.enc(v, size_len=4), b"\xff\xff\xff\xfe"), + ( + "E", + -2, + lambda v: OERcodec_INTEGER.enc(v, size_len=8), + b"\xff\xff\xff\xff\xff\xff\xff\xfe", + ), + # F to I have a lower bound of zero, so they are unsigned: the width and + # the signedness come from the declared type, not from the value. + ( + "F", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=1, oer_unsigned=True), + b"\x80", + ), + ( + "G", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x00\x80", + ), + ( + "G", + 1000, + lambda v: OERcodec_INTEGER.enc(v, size_len=2, oer_unsigned=True), + b"\x03\xe8", + ), + ( + "H", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=4, oer_unsigned=True), + b"\x00\x00\x00\x80", + ), + ( + "I", + 128, + lambda v: OERcodec_INTEGER.enc(v, size_len=8, oer_unsigned=True), + b"\x00\x00\x00\x00\x00\x00\x00\x80", + ), + ("B", 1, lambda v: OERcodec_INTEGER.enc(v, size_len=1), b"\x01"), + ("K", 1, lambda v: OER_unsigned_integer_enc(v), b"\x01\x01"), + ("K", 128, lambda v: OER_unsigned_integer_enc(v), b"\x01\x80"), + ("L", -128, lambda v: OER_signed_integer_enc(v), b"\x01\x80"), +] + +BOOLEAN_VECTORS = [ + (True, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\xff"), + (False, lambda v: OERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), +] + +ENUMERATED_VECTORS = [ + ("A", "a", 1, b"\x01"), + ("B", "a", 128, b"\x82\x00\x80"), + ("C", "a", 0, b"\x00"), + ("C", "b", 127, b"\x7f"), + ("E", "a", -1, b"\x81\xff"), +] + +OID_VECTORS = [ + ("1.2", lambda v: OERcodec_OID.enc(v), b"\x01*"), + ("1.2.3321", lambda v: OERcodec_OID.enc(v), b"\x03*\x99y"), +] + +OCTET_STRING_VECTORS = [ + (b"\x12\x34", 0, b"\x02\x124"), + (b"\x12\x34\x56", 3, b"\x124V"), +] + +BIT_STRING_VECTORS = [ + ("0100", b"\x02\x04@"), + ("01000001", b"\x02\x00A"), +] + +SCAPY_DECODE_VECTORS = [ + ("A", 42, b"\x01*"), + ("F", 200, b"\xc8"), + ("B", -99, b"\x9d"), +] + +_OER_CODEC_CLASSES = ( + OERcodec_INTEGER, + OERcodec_BOOLEAN, + OERcodec_NULL, + OERcodec_STRING, + OERcodec_OID, + OERcodec_ENUMERATED, + OERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + OER_Decoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class OERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0, explicit_tag=0xA0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (OERFuzzRecord,) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _value(obj): + return getattr(obj, "val", obj) + + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +class OEREmptySequenceOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class OEREnumField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b"}) + +class OERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_BIT_STRING("b", "0101") + +class OERNullRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_NULL("z", 0), + ASN1F_INTEGER("n", 0, size_len=1, unsigned=True), + ) + +class OEROidField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_OID("oid", "1.2.3") + +class OERInnerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, size_len=1, unsigned=True), + ) + +class OERPacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE("c", None, OERInnerSeq, ASN1F_INTEGER) + +class OERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class OERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class OERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, OERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, OERAltB, explicit_tag=0xA1), + ), + ) + +class OERUnsignedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, + ) + ++ ASN.1 OER codec += OER length determinant short form +OER_len_enc(3) == b"\x03" += OER length determinant long form +OER_len_enc(200) == b"\x81\xc8" += OER boolean false +OERcodec_BOOLEAN.enc(0) == b"\x00" += OER boolean true +OERcodec_BOOLEAN.enc(1) == b"\xff" += OER null +OERcodec_NULL.enc(None) == b"" += OER unconstrained integer +OERcodec_INTEGER.enc(4) == b"\x01\x04" += OER constrained unsigned integer +OERcodec_INTEGER.enc(4, size_len=1) == b"\x04" += OER constrained signed integer +OERcodec_INTEGER.enc(4, size_len=2) == b"\x00\x04" += OER enumerated short form +OERcodec_ENUMERATED.enc(6) == b"\x06" += OER octet string +OERcodec_STRING.enc(b"ABC") == b"\x03ABC" += OER OID +OERcodec_OID.enc("1.2.3") == b"\x02\x2a\x03" += OER integer roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(12345)) +x.val == 12345 and r == b"" += OER boolean roundtrip +x, r = OERcodec_BOOLEAN.do_dec(OERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += OER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.OER) == b"\x01*" += OER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.OER) is OERcodec_INTEGER + ++ ASN.1 OER codec (extended) += OER length zero +OER_len_enc(0) == b"\x00" += OER length boundary short form +OER_len_enc(127) == b"\x7f" += OER length boundary long form +OER_len_enc(128) == b"\x81\x80" += OER length roundtrip +l, r = OER_len_dec(OER_len_enc(999)) +l == 999 and r == b"" += OER signed integer zero +OER_signed_integer_enc(0) == b"\x01\x00" += OER signed integer negative +OER_signed_integer_enc(-255) == b"\x02\xff\x01" += OER signed integer large +OER_signed_integer_enc(100000) == b"\x03\x01\x86\xa0" += OER signed integer roundtrip +v, r = OER_signed_integer_dec(OER_signed_integer_enc(-1234567)) +v == -1234567 and r == b"" += OER unsigned integer zero +OER_unsigned_integer_enc(0) == b"\x01\x00" += OER unsigned integer roundtrip +v, r = OER_unsigned_integer_dec(OER_unsigned_integer_enc(65535)) +v == 65535 and r == b"" += OER fixed unsigned 1 byte +OERcodec_INTEGER.enc(255, size_len=1, oer_unsigned=True) == b"\xff" += OER fixed signed 2 bytes negative +OERcodec_INTEGER.enc(-2, size_len=2) == b"\xff\xfe" += OER fixed signed 4 bytes +OERcodec_INTEGER.enc(-2, size_len=4) == b"\xff\xff\xff\xfe" += OER enumerated long form +OERcodec_ENUMERATED.enc(128) == b"\x82\x00\x80" += OER enumerated negative +OERcodec_ENUMERATED.enc(-1) == b"\x81\xff" += OER enumerated roundtrip +x, r = OERcodec_ENUMERATED.do_dec(OERcodec_ENUMERATED.enc(128)) +x.val == 128 and r == b"" += OER null roundtrip +x, r = OERcodec_NULL.do_dec(OERcodec_NULL.enc(None)) +x.val is None and r == b"" += OER octet string empty +OERcodec_STRING.enc(b"") == b"\x00" += OER octet string fixed size +OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += OER octet string roundtrip +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34")) +x.val == b"\x12\x34" and r == b"" += OER OID 1.2 +OERcodec_OID.enc("1.2") == b"\x01\x2a" += OER OID roundtrip +x, r = OERcodec_OID.do_dec(OERcodec_OID.enc("1.2.3321")) +x.val == "1.2.3321" and r == b"" += OER bit string variable size +OERcodec_BIT_STRING.enc("0100") == b"\x02\x04\x40" += OER bit string roundtrip +x, r = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("01000001")) +x.val == "01000001" and r == b"" += OER IA5 string +OERcodec_IA5_STRING.enc(b"ABC") == b"\x03ABC" += OER tag short form +OER_tag_enc(1, OER_CLASS_CONTEXT) == b"\x81" += OER tag roundtrip +cls, num, r = OER_tag_dec(OER_tag_enc(1, OER_CLASS_CONTEXT)) +cls == OER_CLASS_CONTEXT and num == 1 and r == b"" += OER sequence concat +OERcodec_SEQUENCE.enc([ASN1_INTEGER(4), ASN1_INTEGER(5)]) == b"\x01\x04\x01\x05" += OER ASN1 boolean object +ASN1_BOOLEAN(1).enc(ASN1_Codecs.OER) == b"\xff" += OER ASN1 null object +ASN1_NULL(None).enc(ASN1_Codecs.OER) == b"" + ++ ASN.1 OER review fixes += OER fixed integer decode roundtrip +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(128, size_len=1, oer_unsigned=True), size_len=1, oer_unsigned=True) +x.val == 128 and r == b"" += OER fixed integer signed decode +x, r = OERcodec_INTEGER.do_dec(OERcodec_INTEGER.enc(-2, size_len=2), size_len=2) +x.val == -2 and r == b"" += OER fixed octet string decode +x, r = OERcodec_STRING.do_dec(OERcodec_STRING.enc(b"\x12\x34\x56", size_len=3), size_len=3) +x.val == b"\x12\x34\x56" and r == b"" += OER does not encode the tag of a component +# X.696 encodes none of the component tags; only CHOICE alternatives are tagged. +fld = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +pkt = OERTaggedInteger() + +assert fld._tagging_enc(pkt, b"\x05", explicit_tag=0xA0) == b"\x05" + +assert fld._tagging_dec(pkt, b"\x05", explicit_tag=0xA0) == (None, b"\x05") + +True += OER tag long form +# X.696 8.7.2.2: a tag number of 63 or more spills into continuation octets +assert OER_tag_enc(100, OER_CLASS_CONTEXT) == b"\xbf\x64" + +assert OER_tag_dec(b"\xbf\x64\x01") == (OER_CLASS_CONTEXT, 100, b"\x01") + +assert OER_tag_dec(OER_tag_enc(16384, OER_CLASS_PRIVATE)) == (OER_CLASS_PRIVATE, 16384, b"") + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"\xbf\x81")) + +_raises(OER_Decoding_Error, lambda: OER_tag_dec(b"")) + +True + ++ ASN.1 OER packets, interop and fuzz += oer field explicit tag +pkt = OERTaggedInteger(n=5) + +# X.696 encodes no tag for a component, whatever the tagging environment +assert raw(pkt) == b"\x01\x05" + +decoded = _roundtrip(OERTaggedInteger, pkt) + +assert decoded.n.val == 5 + +True + += oer field fixed size +pkt = OERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(OERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += oer field optional +present = OEROptionalField(id=1, extra=7) + +# \x80: preamble with the presence bit set for the single OPTIONAL component +assert raw(present) == b"\x80\x01\x01\x01\x07" + +decoded = _roundtrip(OEROptionalField, present) + +assert decoded.id.val == 1 + +assert decoded.extra.val == 7 + +absent = OEROptionalField(id=1, extra=None) + +assert raw(absent) == b"\x00\x01\x01" + +decoded = _roundtrip(OEROptionalField, absent) + +assert decoded.id.val == 1 + +assert decoded.extra is None + +True + += oer field sequence of +pkt = OERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == b"\x01\x03\x01\x01\x01\x02\x01\x03" + +decoded = _roundtrip(OERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field choice +as_int = OERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == b"\x02\x01c" + +decoded = _roundtrip(OERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = OERChoiceField(c=ASN1_STRING("x")) + +assert raw(as_str) == b"\x04\x01x" + +decoded = _roundtrip(OERChoiceField, as_str) + +assert decoded.c.val == b"x" + +True + += oer packet record +pkt = OERRecord( + id=42, flag=True, label="hi", extra=7, values=[1, 2, 3], +) + +expected = ( + b"\x80" + b"\x01*\xff\x02hi\x01\x07" + b"\x01\x03\x01\x01\x01\x02\x01\x03" +) + +assert raw(pkt) == expected + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = OERRecord(id=1, flag=False, label="", extra=None, values=[]) + +assert raw(empty) == b"\x00\x01\x01\x00\x00\x01\x00" + +decoded = _roundtrip(OERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += oer nested sequence +pkt = OERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == b"\x01\x05\x01\x03\xff" + +decoded = _roundtrip(OERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += oer nested sequence trailing +pkt = OERNestedSequenceTrailing(x=3, y=True, id=5) + +assert raw(pkt) == b"\x01\x03\xff\x01\x05" + +decoded = _roundtrip(OERNestedSequenceTrailing, pkt) + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +assert decoded.id.val == 5 + +True + += oer sequence of with trailing +pkt = OERSequenceOfWithTrailing(values=[1, 2], id=7) + +assert raw(pkt) == b"\x01\x02\x01\x01\x01\x02\x01\x07" + +decoded = _roundtrip(OERSequenceOfWithTrailing, pkt) + +assert [x.val for x in decoded.values] == [1, 2] + +assert decoded.id.val == 7 + +True + += primitive interop +for type_name, value, enc, expected in INTEGER_VECTORS: + got = enc(value) + assert got == expected, ( + "integer %s=%r: reference=%r scapy=%r" % + (type_name, value, expected, got) + ) + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(got) + assert remain == b"" and dec.val == value + elif type_name == "F": + dec, remain = OERcodec_INTEGER.do_dec( + got, size_len=1, oer_unsigned=True, + ) + assert remain == b"" and dec.val == value + elif type_name in ("G", "H", "I"): + size = {"G": 2, "H": 4, "I": 8}[type_name] + dec, remain = OERcodec_INTEGER.do_dec( + got, size_len=size, oer_unsigned=True, + ) + assert remain == b"" and dec.val == value + elif type_name in ("B", "C", "D", "E"): + size = {"B": 1, "C": 2, "D": 4, "E": 8}[type_name] + dec, remain = OERcodec_INTEGER.do_dec(got, size_len=size) + assert remain == b"" and dec.val == value + elif type_name == "K": + dec_val, remain = OER_unsigned_integer_dec(got) + assert remain == b"" and dec_val == value + elif type_name == "L": + dec_val, remain = OER_signed_integer_dec(got) + assert remain == b"" and dec_val == value + else: + raise AssertionError("unknown integer vector type %r" % type_name) + +for value, enc, expected in BOOLEAN_VECTORS: + got = enc(value) + assert got == expected + dec, remain = OERcodec_BOOLEAN.do_dec(got) + assert remain == b"" and dec.val == (1 if value else 0) + +got = OERcodec_NULL.enc(None) + +assert got == b"" + +for type_name, _enum_name, enum_val, expected in ENUMERATED_VECTORS: + got = OERcodec_ENUMERATED.enc(enum_val) + assert got == expected + dec, remain = OERcodec_ENUMERATED.do_dec(got) + assert remain == b"" and dec.val == enum_val + +for oid, enc, expected in OID_VECTORS: + got = enc(oid) + assert got == expected + dec, remain = OERcodec_OID.do_dec(got) + assert remain == b"" and dec.val == oid + +for data, fixed_size, expected in OCTET_STRING_VECTORS: + got = OERcodec_STRING.enc(data, size_len=fixed_size or 0) + assert got == expected + dec, remain = OERcodec_STRING.do_dec(got, size_len=fixed_size or 0) + assert remain == b"" and dec.val == data + +for bitstr, expected in BIT_STRING_VECTORS: + got = OERcodec_BIT_STRING.enc(bitstr) + assert got == expected + dec, remain = OERcodec_BIT_STRING.do_dec(got) + assert remain == b"" and dec.val == bitstr + +# Extra decode-only vectors (unsigned/fixed) not covered above by value. +for type_name, value, encoded in SCAPY_DECODE_VECTORS: + if type_name == "A": + dec, remain = OERcodec_INTEGER.do_dec(encoded) + elif type_name == "F": + dec, remain = OERcodec_INTEGER.do_dec( + encoded, size_len=1, oer_unsigned=True, + ) + else: + dec, remain = OERcodec_INTEGER.do_dec(encoded, size_len=1) + assert remain == b"" and dec.val == value + +True + += oer fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = raw(fuzz(cls())) + assert isinstance(data, bytes) + +True + += oer fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + cls(raw(fuzz(cls()))) + +True + += oer fuzz codec decode +iterations = 100 + +for codec in _OER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += oer fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 OER build and dissect += oer record build roundtrip +pkt = OERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(OERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += oer field dissect +tagged = _dissect(OERTaggedInteger, "0105") + +assert tagged.n.val == 5 + +fixed = _dissect(OERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(OEROptionalField, "8001010107") + +assert present.id.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(OEROptionalField, "000101") + +assert absent.id.val == 1 + +assert absent.extra is None + +seqof = _dissect(OERSequenceOfIntegers, "0103010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +as_int = _dissect(OERChoiceField, "020163") + +assert as_int.c.val == 99 + +as_str = _dissect(OERChoiceField, "040178") + +assert as_str.c.val == b"x" + +True + += oer record dissect +decoded = _dissect( + OERRecord, + "80" + "012aff0268690107" + "0103010101020103", +) +_assert_record(decoded) +empty = _dissect(OERRecord, "00010100000100") +_assert_record_empty(empty) + +True + + ++ ASN.1 OER coverage += oer error str +obj = ASN1_INTEGER(1) + +err = OER_Encoding_Error("enc", encoded=obj, remaining=b"z") + +assert "Already encoded" in str(err) + +err2 = OER_Decoding_Error("dec", decoded=obj, remaining=b"w") + +assert "Already decoded" in str(err2) + +True + += oer ipaddress and sequence +encoded = OERcodec_IPADDRESS.enc("127.0.0.1") + +obj, remain = OERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "127.0.0.1" + +assert remain == b"" + +fixed = OERcodec_IPADDRESS.enc("127.0.0.1", size_len=4) + +obj2, remain2 = OERcodec_IPADDRESS.do_dec(fixed, size_len=4) + +assert obj2.val == "127.0.0.1" + +assert remain2 == b"" + +_raises(OER_Encoding_Error, lambda: OERcodec_IPADDRESS.enc("bad-ip")) + +_raises(OER_Decoding_Error, lambda: OERcodec_IPADDRESS.do_dec(b"\x01")) + +assert OERcodec_SEQUENCE.enc(b"payload") == b"payload" + +assert OERcodec_SET.enc(b"payload") == b"payload" + +_raises(OER_Decoding_Error, lambda: OERcodec_SEQUENCE.do_dec(b"\x00")) + +empty, remain = OERcodec_BIT_STRING.do_dec(OERcodec_BIT_STRING.enc("")) + +assert empty.val == "" + +assert remain == b"" + +True + + ++ ASN.1 OER field dispatch and packet extras += oer constrained integer via constraints +fld = OERUnsignedField.ASN1_root + +assert fld.constraints.unsigned is True + +assert raw(OERUnsignedField(n=5)) == b"\x05" + +assert _roundtrip(OERUnsignedField, OERUnsignedField(n=5)).n.val == 5 + +True + += oer empty sequence of +pkt = OEREmptySequenceOf(values=[]) + +assert raw(pkt) == b"\x01\x00" + +decoded = _roundtrip(OEREmptySequenceOf, pkt) + +assert decoded.values == [] + +True + += oer enumerated field +pkt = OEREnumField(e=1) + +assert raw(pkt) == b"\x01" + +decoded = _roundtrip(OEREnumField, pkt) + +assert decoded.e.val == 1 + +True + += oer bit string field +pkt = OERBitStringField(b="0101") + +assert raw(pkt) == b"\x02\x04\x50" + +decoded = _roundtrip(OERBitStringField, pkt) + +assert decoded.b.val == "0101" + +True + += oer null and oid fields +null_pkt = OERNullRecord(z=0, n=2) + +assert raw(null_pkt) == b"\x02" + +assert _roundtrip(OERNullRecord, null_pkt).n.val == 2 + +oid_pkt = OEROidField(oid="1.2.3") + +assert raw(oid_pkt) == b"\x02\x2a\x03" + +assert _roundtrip(OEROidField, oid_pkt).oid.val == "1.2.3" + +True + += oer choice with packet alternative +pkt = OERPacketChoice(c=OERInnerSeq(x=3)) + +# \x10: universal 16 (SEQUENCE), without the BER constructed bit +assert raw(pkt) == b"\x10\x03" + +decoded = _roundtrip(OERPacketChoice, pkt) + +assert isinstance(decoded.c, OERInnerSeq) + +assert decoded.c.x.val == 3 + +as_int = OERPacketChoice(c=ASN1_INTEGER(9)) + +decoded_int = _roundtrip(OERPacketChoice, as_int) + +assert decoded_int.c.val == 9 + +True + += oer choice with tagged packet alternatives +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# in an AUTOMATIC TAGS module: the alternative tag is the only one encoded. +assert raw(OERTaggedChoice(c=OERAltA(i=4))) == b"\x80\x01\x04" + +assert raw(OERTaggedChoice(c=OERAltB(b=False))) == b"\x81\x00" + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltA(i=4))) + +assert isinstance(decoded.c, OERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(OERTaggedChoice, OERTaggedChoice(c=OERAltB(b=False))) + +assert isinstance(decoded.c, OERAltB) and decoded.c.b.val == 0 + +True + += oer dec reads constraints from field +# Direct codec calls without a field ignore schema constraints in kwargs. +x, remain = OERcodec_ENUMERATED.dec( + b"\x01", uper_enum_values=[0, 1], minimum=0, +) + +assert x.val == 1 + +assert remain == b"" + +True + + ++ ASN.1 OER X.696 conformance + += oer sequence preamble presence bits +# X.696 16.2.2: one presence bit per OPTIONAL/DEFAULT component, zero padded +# to a whole number of octets. Byte vectors checked against asn1tools. +class OERPreambleOne(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, unsigned=True), + ASN1F_optional(ASN1F_INTEGER("b", 0, size_len=1, unsigned=True)), + ) + +assert raw(OERPreambleOne(a=1, b=2)) == bytes.fromhex("800102") + +assert raw(OERPreambleOne(a=1, b=None)) == bytes.fromhex("0001") + +assert _dissect(OERPreambleOne, "800102").b.val == 2 + +assert _dissect(OERPreambleOne, "0001").b is None + +class OERPreambleTwo(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("a", 0, size_len=1, unsigned=True)), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + ) + +assert raw(OERPreambleTwo(a=1, b=None)) == bytes.fromhex("8001") + +assert raw(OERPreambleTwo(a=None, b=True)) == bytes.fromhex("40ff") + +# Nine optionals need a two-octet preamble. +class OERPreambleNine(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(*[ + ASN1F_optional(ASN1F_INTEGER(c, 0, size_len=1, unsigned=True)) + for c in "abcdefghi" + ]) + +nine = OERPreambleNine(a=1, b=None, c=None, d=None, e=None, f=None, g=None, + h=None, i=9) + +assert raw(nine) == bytes.fromhex("80800109") + +assert _roundtrip(OERPreambleNine, nine).i.val == 9 + +# A sequence without OPTIONAL/DEFAULT components has no preamble at all. +class OERNoPreamble(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, unsigned=True), + ) + +assert raw(OERNoPreamble(a=1)) == bytes.fromhex("01") + +# A DEFAULT component takes a presence bit too, and is omitted when it holds +# the default value. +from scapy.asn1fields import ASN1F_DEFAULT + +class OERDefault(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_DEFAULT( + ASN1F_INTEGER("a", 7, size_len=1, unsigned=True), 7, + ), + ) + +assert raw(OERDefault(a=7)) == bytes.fromhex("00") + +assert raw(OERDefault(a=9)) == bytes.fromhex("8009") + +# An absent DEFAULT is restored as the raw default value handed to +# ASN1F_DEFAULT, while a present one is decoded into an ASN1_INTEGER. +assert _dissect(OERDefault, "00").a == 7 + +assert _dissect(OERDefault, "8009").a.val == 9 + +True + += oer extensible sequence +class OERExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, unsigned=True), + extensible=True, + ) + +assert raw(OERExtSeq(a=1)) == bytes.fromhex("0001") + +assert _dissect(OERExtSeq, "0001").a.val == 1 + +class OERExtSeqOpt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("a", 0, size_len=1, unsigned=True), + ASN1F_optional(ASN1F_BOOLEAN("b", False)), + extensible=True, + ) + +assert raw(OERExtSeqOpt(a=1, b=True)) == bytes.fromhex("4001ff") + +# An encoding that actually carries extension additions is refused rather +# than silently misparsed. +_raises(OER_Decoding_Error, lambda: _dissect(OERExtSeq, "8001")) + +True + += oer fixed size bit string +# X.696 13.3: a fixed size drops both the length determinant and the +# unused-bit count. +class OERBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "", size_len=4)) + +assert raw(OERBits(b="1010")) == bytes.fromhex("a0") + +assert _dissect(OERBits, "a0").b.val == "1010" + +for bits, size, expected in [ + ("1010", 4, "a0"), + ("10100101", 8, "a5"), + ("101001010101", 12, "a550"), + ("1010010101011010", 16, "a55a"), +]: + assert OERcodec_BIT_STRING.enc(bits, size_len=size) == bytes.fromhex(expected) + obj, remain = OERcodec_BIT_STRING.do_dec(bytes.fromhex(expected), size_len=size) + assert obj.val == bits + assert remain == b"" + +# Unconstrained bit strings keep the length and unused-bit count. +assert OERcodec_BIT_STRING.enc("101") == bytes.fromhex("0205a0") + +# A value that does not match the declared size is refused. +_raises(OER_Encoding_Error, lambda: OERcodec_BIT_STRING.enc("101", size_len=4)) + +True + += oer fixed size octet string +# X.696 16.1: a fixed size means no length determinant. Sizes 1, 2, 4 and 8 +# used to encode without one but decode expecting one. +for size in (1, 2, 3, 4, 8): + value = b"x" * size + encoded = OERcodec_STRING.enc(value, size_len=size) + assert encoded == value + obj, remain = OERcodec_STRING.do_dec(encoded, size_len=size) + assert obj.val == value + assert remain == b"" + +class OERFixedOctets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "", size_len=4)) + +pkt = OERFixedOctets(s="abcd") + +assert raw(pkt) == b"abcd" + +assert _roundtrip(OERFixedOctets, pkt).s.val == b"abcd" + +# Unconstrained octet strings keep their length determinant. +assert OERcodec_STRING.enc(b"abc") == bytes.fromhex("03616263") + +_raises(OER_Encoding_Error, lambda: OERcodec_STRING.enc(b"abc", size_len=4)) + +True + += oer integer signedness follows the declared type +# X.696 10: the encoder must not pick the width or the signedness from the +# value, or the decoder (which only knows the type) reads something else back. +class OERSignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1)) + +class OERUnsignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, size_len=1, unsigned=True)) + +for cls, value, expected in [ + (OERSignedByte, 127, "7f"), + (OERSignedByte, -128, "80"), + (OERUnsignedByte, 255, "ff"), + (OERUnsignedByte, 0, "00"), +]: + pkt = cls(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(cls, pkt).a.val == value + +# 200 used to encode as an unsigned 0xc8 and read back as -56. +_raises(OER_Encoding_Error, lambda: raw(OERSignedByte(a=200))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=256))) + +_raises(OER_Encoding_Error, lambda: raw(OERUnsignedByte(a=-1))) + +True + += oer unbounded unsigned integer +# X.696 10.2: a lower bound of zero means the value is encoded unsigned, with +# no leading zero octet. Byte vectors checked against asn1tools. +class OERUnboundedUnsigned(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE(ASN1F_INTEGER("a", 0, unsigned=True)) + +for value, expected in [ + (0, "0100"), + (127, "017f"), + (128, "0180"), + (200, "01c8"), + (65535, "02ffff"), + (100000, "030186a0"), +]: + pkt = OERUnboundedUnsigned(a=value) + assert raw(pkt) == bytes.fromhex(expected), (value, raw(pkt).hex()) + assert _roundtrip(OERUnboundedUnsigned, pkt).a.val == value + +True + += oer integer with an empty length determinant +_raises(OER_Decoding_Error, lambda: OER_signed_integer_dec(b"\x00")) + +True + += oer choice rejects an unknown alternative tag +_raises(ASN1_Error, lambda: OERPacketChoice(b"\x40\x00")) + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(OERPacketChoice(c=None)) == b"" + +True + += oer untyped codec falls back on string and integer +assert OERcodec_Object.enc(b"hi") == b"\x02hi" + +assert OERcodec_Object.enc(5) == b"\x01\x05" + +_raises(TypeError, lambda: OERcodec_Object.enc(object())) + +# Without a schema there is nothing to tell one type from another +_raises(OER_Decoding_Error, lambda: OERcodec_Object.dec(b"\x01")) + +assert OERcodec_OID.enc(b"") == b"\x00" + +True + += oer sequence dissect of an empty encoding +# Mandatory components require content after the preamble (X.696 16). +# Packet(b"") builds an empty instance and does not dissect; call do_dissect. +_raises(OER_Decoding_Error, lambda: OERRecord().do_dissect(b"")) + +True + += oer empty SEQUENCE of only optionals with empty preamble +class OEROnlyOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional(ASN1F_INTEGER("a", 0, size_len=1, unsigned=True)), + ) + +# One presence bit, absent -> 0x00 preamble, no component body. +assert _dissect(OEROnlyOptional, "00").a is None +_raises(OER_Decoding_Error, lambda: OEROnlyOptional().do_dissect(b"")) + +True + += oer sequence of a pre-encoded value +# A RAW object is written as-is, without the quantity determinant +raw_items = ASN1_Class_UNIVERSAL.RAW.asn1_object(b"\x01\x01\x07") + +assert raw(OERSequenceOfIntegers(values=raw_items)) == b"\x01\x01\x07" + +True + +% PR #5050 refactor characterization (OER) ++ OER refactor contracts - field schema owns value constraints += component tags do not alter OER component encoding +class RefactorOERPlainInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0) + +class RefactorOERTaggedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, explicit_tag=0xA0) + +all( + raw(RefactorOERPlainInteger(n=value)) == raw( + RefactorOERTaggedInteger(n=value) + ) + for value in [0, 1, 127, 128, 255, -1, -128] +) + += constrained OER integer plain value and ASN1_INTEGER encode identically +class RefactorOERUnsignedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, size_len=1, unsigned=True) + +all( + raw(RefactorOERUnsignedByte(n=value)) == bytes([value]) + and raw(RefactorOERUnsignedByte(n=ASN1_INTEGER(value))) == bytes([value]) + and _value(RefactorOERUnsignedByte(bytes([value])).n) == value + for value in [0, 1, 127, 128, 254, 255] +) + += constrained OER unsigned integer rejects out-of-range values +_raises(OER_Encoding_Error, lambda: raw(RefactorOERUnsignedByte(n=-1))) +_raises(OER_Encoding_Error, lambda: raw(RefactorOERUnsignedByte(n=256))) +True + ++ OER refactor contracts - OPTIONAL and DEFAULT += OPTIONAL and DEFAULT share the OER sequence preamble +class RefactorOERPresence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, size_len=1, unsigned=True), + ASN1F_optional( + ASN1F_INTEGER("opt", 0, size_len=1, unsigned=True) + ), + ASN1F_DEFAULT( + ASN1F_INTEGER("mode", 3, size_len=1, unsigned=True), + 3, + ), + ) + +vectors = [ + (dict(id=1, opt=None, mode=3), bytes.fromhex("0001")), + (dict(id=1, opt=2, mode=3), bytes.fromhex("800102")), + (dict(id=1, opt=None, mode=4), bytes.fromhex("400104")), + (dict(id=1, opt=2, mode=4), bytes.fromhex("c0010204")), +] +all( + raw(RefactorOERPresence(**values)) == expected + and _value(RefactorOERPresence(expected).id) == 1 + and (_value(RefactorOERPresence(expected).opt) + if RefactorOERPresence(expected).opt is not None else None) == values["opt"] + and _value(RefactorOERPresence(expected).mode) == values["mode"] + for values, expected in vectors +) + += OER DEFAULT wire encoding is independent of plain versus ASN1 object default +plain = RefactorOERPresence(id=1, mode=3) +asn1_object = RefactorOERPresence(id=1, mode=ASN1_INTEGER(3)) +assert raw(plain) == raw(asn1_object) == bytes.fromhex("0001") +True + ++ OER refactor contracts - nested packets and dynamic selection += nested ASN1F_PACKET retains Scapy underlayer semantics +class RefactorOERInner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, size_len=1, unsigned=True), + ASN1F_BOOLEAN("flag", False), + ) + +class RefactorOEROuter(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, size_len=1, unsigned=True), + ASN1F_PACKET("inner", None, RefactorOERInner), + ASN1F_INTEGER("tail", 0, size_len=1, unsigned=True), + ) + +pkt = RefactorOEROuter( + id=1, + inner=RefactorOERInner(x=7, flag=True), + tail=9, +) +assert raw(pkt) == bytes.fromhex("0107ff09") +decoded = RefactorOEROuter(raw(pkt)) +assert _value(decoded.id) == 1 +assert _value(decoded.inner.x) == 7 +assert _value(decoded.inner.flag) == 1 +assert _value(decoded.tail) == 9 +assert decoded.inner.parent is decoded +assert decoded.inner.underlayer is decoded +True + += OER ASN1F_PACKET next_cls_cb selects child class during decode +class RefactorOERDynamicA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, size_len=1, unsigned=True), + ) + +class RefactorOERDynamicB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("flag", False), + ) + + +def _oer_dynamic_cls(pkt): + return RefactorOERDynamicA if _value(pkt.kind) == 0 else RefactorOERDynamicB + + +class RefactorOERDynamicOuter(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("kind", 0, size_len=1, unsigned=True), + ASN1F_PACKET( + "inner", None, RefactorOERDynamicA, + next_cls_cb=_oer_dynamic_cls, + ), + ) + +packet_a = RefactorOERDynamicOuter(kind=0, inner=RefactorOERDynamicA(x=5)) +packet_b = RefactorOERDynamicOuter(kind=1, inner=RefactorOERDynamicB(flag=True)) +assert raw(packet_a) == bytes.fromhex("0005") +assert raw(packet_b) == bytes.fromhex("01ff") +decoded_a = RefactorOERDynamicOuter(raw(packet_a)) +decoded_b = RefactorOERDynamicOuter(raw(packet_b)) +assert isinstance(decoded_a.inner, RefactorOERDynamicA) +assert _value(decoded_a.inner.x) == 5 +assert decoded_a.inner.parent is decoded_a +assert isinstance(decoded_b.inner, RefactorOERDynamicB) +assert _value(decoded_b.inner.flag) == 1 +assert decoded_b.inner.parent is decoded_b +True + + +% PR #5050 refactor targets (OER) ++ Finding - OER CHOICE must preserve semantic high tag numbers += OER CHOICE context-specific tag 100 uses high-tag-number form +high_context_100, remain = BER_id_dec(b"\xbf\x64") +assert remain == b"" + +class RefactorOERHighContextAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("flag", False), + ) + +class RefactorOERHighContextChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", None, + ASN1F_PACKET( + "high", None, RefactorOERHighContextAlt, + explicit_tag=high_context_100, + ), + ) + +expected = b"\xbf\x64\xff" +pkt = RefactorOERHighContextChoice(c=RefactorOERHighContextAlt(flag=True)) +assert raw(pkt) == expected +decoded = RefactorOERHighContextChoice(expected) +assert isinstance(decoded.c, RefactorOERHighContextAlt) +assert _value(decoded.c.flag) == 1 +True + += OER CHOICE private tag 100 keeps class and tag number +high_private_100, remain = BER_id_dec(b"\xff\x64") +assert remain == b"" + +class RefactorOERHighPrivateAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("flag", False), + ) + +class RefactorOERHighPrivateChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", None, + ASN1F_PACKET( + "high", None, RefactorOERHighPrivateAlt, + explicit_tag=high_private_100, + ), + ) + +expected = b"\xff\x64\x00" +pkt = RefactorOERHighPrivateChoice(c=RefactorOERHighPrivateAlt(flag=False)) +assert raw(pkt) == expected +decoded = RefactorOERHighPrivateChoice(expected) +assert isinstance(decoded.c, RefactorOERHighPrivateAlt) +assert _value(decoded.c.flag) == 0 +True + +% PR #5050 review regressions ++ OER review fixes += OER INTEGER (0..255) uses single octet width from bounds +class OERBoundedByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=255) + +assert raw(OERBoundedByte(n=5)) == b"\x05" +assert _roundtrip(OERBoundedByte, OERBoundedByte(n=5)).n.val == 5 + += OER INTEGER minimum=0 alone is variable unsigned +class OERNonNeg(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0) + +assert raw(OERNonNeg(n=128)) == b"\x01\x80" +_raises(OER_Encoding_Error, lambda: raw(OERNonNeg(n=-1))) +assert OERNonNeg(b"\x01\x80").n.val == 128 + += OER INTEGER maximum alone rejects values above the bound +class OERMax10(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, maximum=10) + +assert raw(OERMax10(n=10)) +_raises(OER_Encoding_Error, lambda: raw(OERMax10(n=11))) + += OER extensible (0..255) is unbounded and accepts 256 +class OERExtByte(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=255, extensible=True) + +assert raw(OERExtByte(n=256)) == OERcodec_INTEGER.enc(256) +assert _roundtrip(OERExtByte, OERExtByte(n=256)).n.val == 256 + += OER (0..2**80) uses variable-width unsigned +class OERHuge(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=2 ** 80) + +big = 2 ** 64 +assert raw(OERHuge(n=big)) == OER_unsigned_integer_enc(big) +assert _roundtrip(OERHuge, OERHuge(n=big)).n.val == big + += unknown constraint kwargs raise TypeError +_raises(TypeError, lambda: ASN1F_INTEGER( + "n", 0, **{"mini" "num": 0, "maxi" "mim": 255})) + += OER OID round-trip for 2.999.3 +obj, remain = OERcodec_OID.do_dec(OERcodec_OID.enc("2.999.3")) +assert str(obj.val) == "2.999.3" +assert remain == b"" + += OER string codec classes are picklable with correct module +import pickle +assert pickle.dumps(OERcodec_IA5_STRING) +assert OERcodec_IA5_STRING.__module__ == "scapy.asn1.oer" +True + += OER STRING fixed SIZE from minimum=maximum +class OERFixedSizeStr(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_STRING("s", b"", minimum=3, maximum=3) + +assert raw(OERFixedSizeStr(s=b"abc")) == b"abc" +assert _roundtrip(OERFixedSizeStr, OERFixedSizeStr(s=b"abc")).s.val == b"abc" +_raises(OER_Encoding_Error, lambda: raw(OERFixedSizeStr(s=b"ab"))) +True + += OER STRING ranged SIZE validates length +class OERRangedStr(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_STRING("s", b"", minimum=1, maximum=3) + +assert raw(OERRangedStr(s=b"ab")) == b"\x02ab" +assert _roundtrip(OERRangedStr, OERRangedStr(s=b"ab")).s.val == b"ab" +_raises(OER_Encoding_Error, lambda: raw(OERRangedStr(s=b"abcd"))) +True diff --git a/test/scapy/layers/uper.uts b/test/scapy/layers/uper.uts new file mode 100644 index 00000000000..6139e03a0aa --- /dev/null +++ b/test/scapy/layers/uper.uts @@ -0,0 +1,3733 @@ +% Tests for ASN.1 UPER encoding + +# +# Try me with: +# ./test/run_tests -t test/scapy/layers/uper.uts -N + ++ ASN.1 UPER load += prepare helpers and packet classes +import scapy.asn1.uper + +from scapy.asn1.uper import * +from scapy.asn1.context import UPER_EncoderContext, UPER_DecoderContext + +from scapy.packet import raw + +class UPERFixedFields(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, size_len=1, unsigned=True), + ASN1F_STRING("s", "", size_len=3), + ) + +class UPERIntegerField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0) + +class UPERBooleanField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BOOLEAN("b", False) + +class UPERStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", "") + +class UPERConstrainedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, size_len=1, unsigned=True, + ) + +class UPEROptionalField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class UPERSequenceOfIntegers(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +class UPERChoiceField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class UPERChoiceStringFirst(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_STRING(b""), ASN1F_STRING, ASN1F_INTEGER, + ) + +class UPERRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPEREnumeratedField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +class UPERBitStringField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING( + "bits", "0", minimum=1, maximum=20, + ) + +class UPERMessagePrefix(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("msgId", 0), + ASN1F_INTEGER("myflag", 0), + ASN1F_STRING("szDescription", "", size_len=10), + ASN1F_BOOLEAN("isReady", False), + ) + +class UPERSequenceWithChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +class UPERNullPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_NULL("n", None) + +class UPERVariableOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("data", "", minimum=1, maximum=20) + +class UPERConstrainedRangeInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=15) + +class UPERSequenceWithEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_ENUMERATED("state", 1, {1: "alpha", 200: "beta"}), + ) + +class UPERSequenceOfStrings(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("items", [], ASN1F_STRING) + +class UPERNestedSequence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERSequenceWithNull(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_NULL("n", None), + ) + +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "0", minimum=16, maximum=16) + +class UPERSequenceOfConstrainedInts(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("item", 0, minimum=0, maximum=255), + ) + +class UPERSignedInteger(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=-128, maximum=127) + +class UPERMultiOptional(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("a", 0)), + ASN1F_optional(ASN1F_STRING("b", "")), + ) + +def _roundtrip(cls, pkt): + # type: (type, ASN1_Packet) -> ASN1_Packet + return cls(raw(pkt)) + +CodecRoundtrip = Tuple[ + Type[Any], + Any, + Dict[str, Any], + Any, +] + +CODEC_ROUNDTRIPS = [ + (UPERcodec_NULL, None, {}, None), + (UPERcodec_BOOLEAN, 1, {}, 1), + (UPERcodec_BOOLEAN, 0, {}, 0), + (UPERcodec_INTEGER, 42, {}, 42), + (UPERcodec_INTEGER, -1, {}, -1), + (UPERcodec_INTEGER, 68719476736, {}, 68719476736), + (UPERcodec_INTEGER, 200, {"minimum": 0, "maximum": 255}, 200), + (UPERcodec_INTEGER, -1, {"minimum": -128, "maximum": 127}, -1), + (UPERcodec_INTEGER, 127, {"minimum": -128, "maximum": 127}, 127), + (UPERcodec_INTEGER, -128, {"minimum": -128, "maximum": 127}, -128), + (UPERcodec_STRING, b"AB", {}, b"AB"), + (UPERcodec_STRING, b"\x12\x34\x56", {"size_len": 3}, b"\x12\x34\x56"), + ( + UPERcodec_STRING, + bytes.fromhex("afbc4583"), + {"minimum": 1, "maximum": 20}, + bytes.fromhex("afbc4583"), + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1, 200]}, 1), + (UPERcodec_ENUMERATED, 200, {"uper_enum_values": [1, 200]}, 200), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"minimum": 1, "maximum": 20}, + "1010101111001101", + ), + ( + UPERcodec_BIT_STRING, + (bytes.fromhex("abcd"), 16), + {"minimum": 16, "maximum": 16}, + "1010101111001101", + ), + (UPERcodec_ENUMERATED, 1, {"uper_enum_values": [1]}, 1), +] + +DecodeVector = Tuple[ + str, + Any, + Type[Any], + Dict[str, Any], + Any, + bytes, +] + +DECODE_VECTORS = [ + ("A", True, UPERcodec_BOOLEAN, {}, 1, b"\x80"), + ("A", False, UPERcodec_BOOLEAN, {}, 0, b"\x00"), + ("B", 42, UPERcodec_INTEGER, {}, 42, b"\x01*"), + ("B", -1, UPERcodec_INTEGER, {}, -1, b"\x01\xff"), + ( + "C", + 200, + UPERcodec_INTEGER, + {"minimum": 0, "maximum": 255}, + 200, + b"\xc8", + ), + ( + "Signed", + -1, + UPERcodec_INTEGER, + {"minimum": -128, "maximum": 127}, + -1, + b"\x7f", + ), + ( + "Signed", + 127, + UPERcodec_INTEGER, + {"minimum": -128, "maximum": 127}, + 127, + b"\xff", + ), + ("D", b"AB", UPERcodec_STRING, {}, b"AB", b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + UPERcodec_STRING, + {"size_len": 3}, + b"\x12\x34\x56", + b"\x12\x34\x56", + ), + ("G", None, UPERcodec_NULL, {}, None, b""), + ("H", "alpha", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 1, b"\x00"), + ("H", "beta", UPERcodec_ENUMERATED, {"uper_enum_values": [1, 200]}, 200, b"\x80"), +] + +OID_ENCODE_VECTORS = [ + ("1.2.3", b"\x02*\x03"), + ("2.999.3", b"\x03\x887\x03"), +] + +def _assert_codec_roundtrip(codec, value, kwargs, expected): + # type: (Type[Any], Any, Dict[str, Any], Any) -> None + data = codec.enc(value, **kwargs) + decoded, _remain = codec.do_dec(data, **kwargs) + assert decoded.val == expected + +PRIMITIVE_VECTORS = [ + ("A", True, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x80"), + ("A", False, lambda v: UPERcodec_BOOLEAN.enc(1 if v else 0), b"\x00"), + ("B", 42, lambda v: UPERcodec_INTEGER.enc(v), b"\x01*"), + ("B", -1, lambda v: UPERcodec_INTEGER.enc(v), b"\x01\xff"), + ( + "C", + 200, + lambda v: UPERcodec_INTEGER.enc(v, minimum=0, maximum=255), + b"\xc8", + ), + ("D", b"AB", lambda v: UPERcodec_STRING.enc(v), b"\x02AB"), + ( + "E", + b"\x12\x34\x56", + lambda v: UPERcodec_STRING.enc(v, size_len=3), + b"\x12\x34\x56", + ), + ("G", None, lambda v: UPERcodec_NULL.enc(None), b""), + ( + "H", + "beta", + lambda v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), +] + +COMPOSITE_VECTORS = [ + ("Seq", {"id": 42, "flag": True}, b"\x00\x95@"), + ("Seq", {"id": 42, "flag": True, "extra": 7}, b"\x80\x95@A\xc0"), + ("SeqOf", [1, 2, 3], b"\x03\x01\x01\x01\x02\x01\x03"), + ("SeqOfC", [1, 200, 0], b"\x03\x01\xc8\x00"), + ("Choice", ("a", 99), b"\x00\xb1\x80"), + ("Choice", ("b", b"AB"), b"\x81 \xa1\x00"), + ("ChoiceC", ("a", 10), b"P"), + ("ChoiceC", ("b", b"AB"), b"\x81 \xa1\x00"), +] + +DECODE_PACKET_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +PACKET_REFERENCE_VECTORS = [ + ( + UPERNestedSequence, + {"id": 5, "x": 3, "y": True}, + bytes.fromhex("0105010380"), + ), + ( + UPERMultiOptional, + {"id": 1, "a": 2, "b": b"hi"}, + bytes.fromhex("c0404040809a1a40"), + ), +] + +def _encode_composite(typename, value): + # type: (str, Any) -> bytes + enc = UPER_Encoder() + if typename == "Seq": + enc.append_bit(1 if value.get("extra") is not None else 0) + UPERcodec_INTEGER.encode_into(enc, value["id"]) + UPERcodec_BOOLEAN.encode_into(enc, 1 if value["flag"] else 0) + if value.get("extra") is not None: + UPERcodec_INTEGER.encode_into(enc, value["extra"]) + return enc.as_bytes() + if typename == "SeqOf": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into(enc, item) + return enc.as_bytes() + if typename == "SeqOfC": + enc.append_length_determinant(len(value)) + for item in value: + UPERcodec_INTEGER.encode_into( + enc, item, minimum=0, maximum=255, + ) + return enc.as_bytes() + if typename == "Choice": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into(enc, payload) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + if typename == "ChoiceC": + alt, payload = value + index = 0 if alt == "a" else 1 + UPER_choice_index_enc(enc, index, 2) + if alt == "a": + UPERcodec_INTEGER.encode_into( + enc, payload, minimum=0, maximum=15, + ) + else: + UPERcodec_STRING.encode_into(enc, payload) + return enc.as_bytes() + raise ValueError("unknown composite type %s" % typename) + +BOOLEAN_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BOOLEAN " + "END" +) + +NULL_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= NULL " + "END" +) + +OCTET_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= OCTET STRING (SIZE(1..20)) " + "END" +) + +CHOICE_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= CHOICE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { one(1), two(2), three(3), four(4), thousand(1000) }, " + "buf OCTET STRING (SIZE(10)), " + "gg SEQUENCE { " + "int1 INTEGER(0..15), " + "int2 INTEGER(0..65535), " + "enm ENUMERATED { pone(1), ptwo(2), pthree(3), pfour(4), pthousand(1000) }, " + "buf [APPLICATION 104] OCTET STRING (SIZE(10)) " + "} " + "} " + "END" +) + +ENUMERATED_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= ENUMERATED { alpha(1), beta(200) } " + "END" +) + +BIT_STRING_VAR_SPEC = ( + "TEST-CASE DEFINITIONS AUTOMATIC TAGS::= BEGIN " + "MyPDU ::= BIT STRING (SIZE(1..20)) " + "END" +) + +README_MESSAGE_HEX = ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +README_MESSAGE_PREFIX_HEX = ( + "0101010248656c6c6f576f726c6480" +) + +ASN1SCC_VECTORS = [ + ( + "05-BOOLEAN/001 pdu1", + True, + lambda _v: UPERcodec_BOOLEAN.enc(1), + b"\x80", + ), + ( + "18-NULL/001 pdu1", + None, + lambda _v: UPERcodec_NULL.enc(None), + b"", + ), + ( + "06-OCTET-STRING/001 pdu1", + bytes.fromhex("afbc4583"), + lambda v: UPERcodec_STRING.enc(v, minimum=1, maximum=20), + bytes.fromhex("1d7de22c18"), + ), + ( + "05-BOOLEAN/001 pdu1 false", + False, + lambda _v: UPERcodec_BOOLEAN.enc(0), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 alpha", + "alpha", + lambda _v: UPERcodec_ENUMERATED.enc(1, uper_enum_values=[1, 200]), + b"\x00", + ), + ( + "04-ENUMERATED/001 pdu1 beta", + "beta", + lambda _v: UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), + b"\x80", + ), + ( + "09-CHOICE/001 pdu1 int1:10", + ("int1", 10), + lambda _v: _encode_choice_int1_10(), + b"\x14", + ), + ( + "08-BIT-STRING/001 pdu1 ABCD", + (bytes.fromhex("abcd"), 16), + lambda _v: UPERcodec_BIT_STRING.enc( + (bytes.fromhex("abcd"), 16), minimum=1, maximum=20, + ), + bytes.fromhex("7d5e68"), + ), +] + +def _encode_choice_int1_10(): + # type: () -> bytes + enc = UPER_Encoder() + UPER_choice_index_enc(enc, 0, 5) + UPERcodec_INTEGER.encode_into(enc, 10, minimum=0, maximum=15) + return enc.as_bytes() + +_UPER_CODEC_CLASSES = ( + UPERcodec_INTEGER, + UPERcodec_BOOLEAN, + UPERcodec_NULL, + UPERcodec_STRING, + UPERcodec_OID, + UPERcodec_ENUMERATED, + UPERcodec_BIT_STRING, +) + +_DECODE_ERRORS = ( + UPER_Decoding_Error, + UPER_Encoding_Error, + ASN1_Decoding_Error, + ASN1_Error, + ValueError, + IndexError, +) + +class UPERFuzzRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_BOOLEAN("flag", False), + ASN1F_STRING("label", ""), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER), + ) + +class UPERFuzzNested(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0), + ASN1F_BOOLEAN("y", False), + ), + ) + +class UPERFuzzEnumerated(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", 1, {1: "alpha", 200: "beta"}, + ) + +def _fuzz_packets(): + # type: () -> Iterable[Type[ASN1_Packet]] + return (UPERFuzzRecord, UPERFuzzNested, UPERFuzzEnumerated) + +def _record_kwargs(): + # type: () -> dict + return dict( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], + ) + +def _asn1_int(val): + # type: (Any) -> int + return val.val if hasattr(val, "val") else val + +def _assert_record(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 42 + assert decoded.flag.val == 1 + assert decoded.label.val == b"hi" + assert decoded.extra.val == 7 + assert [x.val for x in decoded.values] == [1, 2, 3] + +def _assert_record_empty(decoded): + # type: (ASN1_Packet) -> None + assert decoded.id.val == 1 + assert decoded.flag.val == 0 + assert decoded.label.val == b"" + assert decoded.extra is None + assert [x.val for x in decoded.values] == [] + +def _dissect(cls, data_hex): + # type: (Type[ASN1_Packet], str) -> ASN1_Packet + return cls(bytes.fromhex(data_hex)) + +from unittest import mock + +from scapy.asn1.ber import BER_Decoding_Error + +from scapy.asn1.oer import OER_Decoding_Error, OER_Encoding_Error + +from scapy.asn1.uper import ( + UPER_Decoding_Error, UPER_Encoding_Error, UPER_Decoder, UPER_Encoder, +) + +from scapy.packet import Raw, raw + +class _InnerRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("mode", ASN1_INTEGER(0), ["off", "on"]), + ) + +class _EncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_ENCAPS("payload", None, _InnerRecord), + ) + +class _FlagsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS("f", "000", ["read", "write", "exec"]), + ) + +class _SetOfRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SET_OF("items", [], ASN1F_INTEGER) + +class _PacketFieldRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_STRING_PacketField("data", b""), + ) + +class _ExplicitPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2) + +class _BitEncapsRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord), + ) + +def _raises(exc, func): + # type: (type, Any) -> None + try: + func() + except exc: + return + raise AssertionError("Expected %s" % exc.__name__) + +import scapy.asn1.oer + +from scapy.asn1.oer import * + +import scapy.asn1fields as asn1fields + +from scapy.asn1fields import ASN1F_DEFAULT + +def _val(x): + # type: (Any) -> Any + return x.val if hasattr(x, "val") else x + +class UPERSmallDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_DEFAULT( + ASN1F_INTEGER("n", 5, minimum=0, maximum=10), + 5, + ), + ) + +class UPEREmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, minimum=0, maximum=7), + minimum=0, + maximum=3, + ) + +class UPERExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, minimum=0, maximum=7), + minimum=1, + maximum=2, + extensible=True, + ) + +class UPERFlagsField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_FLAGS( + "f", "101", ["a", "b", "c"], minimum=3, maximum=3, + ) + +class UPERInnerPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, minimum=0, maximum=15), + ) + +class UPERWrappedPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_PACKET("inner", None, UPERInnerPacket), + ) + +class UPERConstrainedInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=255) + ++ ASN.1 UPER codec += UPER boolean true +UPERcodec_BOOLEAN.enc(1) == b"\x80" += UPER boolean false +UPERcodec_BOOLEAN.enc(0) == b"\x00" += UPER unconstrained integer +UPERcodec_INTEGER.enc(42) == b"\x01*" += UPER constrained integer +UPERcodec_INTEGER.enc(200, minimum=0, maximum=255) == b"\xc8" += UPER signed constrained integer +UPERcodec_INTEGER.enc(-1, minimum=-128, maximum=127) == b"\x7f" += UPER octet string +UPERcodec_STRING.enc(b"AB") == b"\x02AB" += UPER fixed octet string +UPERcodec_STRING.enc(b"\x12\x34\x56", size_len=3) == b"\x12\x34\x56" += UPER null +UPERcodec_NULL.enc(None) == b"" += UPER enumerated index +UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]) == b"\x80" += UPER bit string variable size +UPERcodec_BIT_STRING.enc((b"\xab\xcd", 16), minimum=1, maximum=20) == bytes.fromhex("7d5e68") += UPER enumerated roundtrip +x, r = UPERcodec_ENUMERATED.do_dec(UPERcodec_ENUMERATED.enc(200, uper_enum_values=[1, 200]), uper_enum_values=[1, 200]) +x.val == 200 and r == b"" += UPER integer roundtrip +x, r = UPERcodec_INTEGER.do_dec(UPERcodec_INTEGER.enc(-1)) +x.val == -1 and r == b"" += UPER boolean roundtrip +x, r = UPERcodec_BOOLEAN.do_dec(UPERcodec_BOOLEAN.enc(1)) +x.val == 1 and r == b"" += UPER ASN1 object encoding +ASN1_INTEGER(42).enc(ASN1_Codecs.PER) == b"\x01*" += UPER codec registration +ASN1_Class_UNIVERSAL.INTEGER.get_codec(ASN1_Codecs.PER) is UPERcodec_INTEGER + ++ ASN.1 UPER packets, helpers, interop and fuzz += uper field fixed size +pkt = UPERFixedFields(n=200, s=b"ABC") + +assert raw(pkt) == b"\xc8ABC" + +decoded = _roundtrip(UPERFixedFields, pkt) + +assert decoded.n.val == 200 + +assert decoded.s.val == b"ABC" + +True + += uper field integer +pkt = UPERIntegerField(n=12345) + +assert raw(pkt) == bytes.fromhex("023039") + +decoded = _roundtrip(UPERIntegerField, pkt) + +assert decoded.n.val == 12345 + +True + += uper field boolean +true_pkt = UPERBooleanField(b=True) + +assert raw(true_pkt) == b"\x80" + +decoded = _roundtrip(UPERBooleanField, true_pkt) + +assert decoded.b.val == 1 + +false_pkt = UPERBooleanField(b=False) + +assert raw(false_pkt) == b"\x00" + +decoded = _roundtrip(UPERBooleanField, false_pkt) + +assert decoded.b.val == 0 + +True + += uper field string +pkt = UPERStringField(s=b"hi") + +assert raw(pkt) == bytes.fromhex("026869") + +decoded = _roundtrip(UPERStringField, pkt) + +assert decoded.s.val == b"hi" + +True + += uper field constrained integer +pkt = UPERConstrainedInteger(n=200) + +assert raw(pkt) == b"\xc8" + +decoded = _roundtrip(UPERConstrainedInteger, pkt) + +assert decoded.n.val == 200 + +True + += uper field optional +present = UPEROptionalField(id=42, flag=True, extra=7) + +assert raw(present) == bytes.fromhex("80954041c0") + +decoded = _roundtrip(UPEROptionalField, present) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra.val == 7 + +absent = UPEROptionalField(id=42, flag=True, extra=None) + +assert raw(absent) == bytes.fromhex("009540") + +decoded = _roundtrip(UPEROptionalField, absent) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.extra is None + +True + += uper field sequence of +pkt = UPERSequenceOfIntegers(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("03010101020103") + +decoded = _roundtrip(UPERSequenceOfIntegers, pkt) + +assert [x.val for x in decoded.values] == [1, 2, 3] + +empty = UPERSequenceOfIntegers(values=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfIntegers, empty) + +assert [x.val for x in decoded.values] == [] + +True + += uper field choice +as_int = UPERChoiceField(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("00b180") + +decoded = _roundtrip(UPERChoiceField, as_int) + +assert decoded.c.val == 99 + +as_str = UPERChoiceField(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("8120a100") + +decoded = _roundtrip(UPERChoiceField, as_str) + +assert decoded.c.val == b"AB" + +True + += uper field choice canonical order +# X.691 10.2: INTEGER (UNIVERSAL 2) precedes OCTET STRING (UNIVERSAL 4), +# even when STRING is declared first. +as_str = UPERChoiceStringFirst(c=ASN1_STRING(b"AB")) + +assert raw(as_str) == bytes.fromhex("8120a100") + +decoded = _roundtrip(UPERChoiceStringFirst, as_str) + +assert decoded.c.val == b"AB" + +as_int = UPERChoiceStringFirst(c=ASN1_INTEGER(99)) + +assert raw(as_int) == bytes.fromhex("00b180") + +decoded = _roundtrip(UPERChoiceStringFirst, as_int) + +assert decoded.c.val == 99 + +True + += uper packet record +full = UPERRecord( + id=42, + flag=True, + label=b"hi", + extra=7, + values=[1, 2, 3], +) + +assert raw(full) == bytes.fromhex("8095409a1a4041c0c04040408040c0") + +decoded = _roundtrip(UPERRecord, full) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +pkt = UPERRecord( + id=42, + flag=True, + label=b"AB", + extra=None, + values=[1, 2], +) + +body = bytes.fromhex("0095409050808040404080") + +assert raw(pkt) == body + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"AB" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [1, 2] + +empty = UPERRecord( + id=1, + flag=False, + label=b"", + extra=None, + values=[], +) + +assert raw(empty) == bytes.fromhex("0080800000") + +decoded = _roundtrip(UPERRecord, empty) + +assert decoded.id.val == 1 + +assert decoded.flag.val == 0 + +assert decoded.label.val == b"" + +assert decoded.extra is None + +assert [x.val for x in decoded.values] == [] + +True + += uper field enumerated +alpha = UPEREnumeratedField(state=1) + +assert raw(alpha) == b"\x00" + +decoded = _roundtrip(UPEREnumeratedField, alpha) + +assert decoded.state.val == 1 + +beta = UPEREnumeratedField(state=200) + +assert raw(beta) == b"\x80" + +decoded = _roundtrip(UPEREnumeratedField, beta) + +assert decoded.state.val == 200 + +True + += uper field bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERBitStringField(bits=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("7d5e68") + +decoded = _roundtrip(UPERBitStringField, pkt) + +assert decoded.bits.val == "1010101111001101" + +True + += uper message prefix +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +assert raw(pkt) == bytes.fromhex("0101010248656c6c6f576f726c6480") + +decoded = _roundtrip(UPERMessagePrefix, pkt) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += uper sequence with choice +pkt = UPERSequenceWithChoice(id=42, c=ASN1_INTEGER(99)) + +body = raw(pkt) + +decoded = UPERSequenceWithChoice(body) + +assert decoded.id.val == 42 + +assert decoded.c.val == 99 + +as_str = UPERSequenceWithChoice(id=1, c=ASN1_STRING(b"AB")) + +decoded = UPERSequenceWithChoice(raw(as_str)) + +assert decoded.id.val == 1 + +assert decoded.c.val == b"AB" + +True + += uper null packet +pkt = UPERNullPacket() + +assert raw(pkt) == b"" + +decoded = _roundtrip(UPERNullPacket, pkt) + +assert decoded.n is None + +True + += uper variable octet string +pkt = UPERVariableOctetString(data=bytes.fromhex("afbc4583")) + +assert raw(pkt) == bytes.fromhex("1d7de22c18") + +decoded = _roundtrip(UPERVariableOctetString, pkt) + +assert decoded.data.val == bytes.fromhex("afbc4583") + +True + += uper constrained range integer +pkt = UPERConstrainedRangeInt(n=10) + +assert raw(pkt) == b"\xa0" + +decoded = _roundtrip(UPERConstrainedRangeInt, pkt) + +assert decoded.n.val == 10 + +True + += uper sequence with enumerated +pkt = UPERSequenceWithEnumerated(id=1, state=200) + +assert raw(pkt) == bytes.fromhex("010180") + +decoded = _roundtrip(UPERSequenceWithEnumerated, pkt) + +assert decoded.id.val == 1 + +assert decoded.state.val == 200 + +alpha = UPERSequenceWithEnumerated(id=7, state=1) + +assert raw(alpha) == bytes.fromhex("010700") + +decoded = _roundtrip(UPERSequenceWithEnumerated, alpha) + +assert decoded.state.val == 1 + +True + += uper sequence of strings +pkt = UPERSequenceOfStrings(items=[b"A", b"BC"]) + +assert raw(pkt) == bytes.fromhex("020141024243") + +decoded = _roundtrip(UPERSequenceOfStrings, pkt) + +assert [x.val for x in decoded.items] == [b"A", b"BC"] + +empty = UPERSequenceOfStrings(items=[]) + +assert raw(empty) == b"\x00" + +decoded = _roundtrip(UPERSequenceOfStrings, empty) + +assert [x.val for x in decoded.items] == [] + +True + += uper sequence choice hex +pkt = UPERSequenceWithChoice(id=1, c=ASN1_INTEGER(99)) + +assert raw(pkt) == bytes.fromhex("010100b180") + +decoded = UPERSequenceWithChoice(raw(pkt)) + +assert decoded.id.val == 1 + +assert decoded.c.val == 99 + +True + += uper nested sequence +pkt = UPERNestedSequence(id=5, x=3, y=True) + +assert raw(pkt) == bytes.fromhex("0105010380") + +decoded = _roundtrip(UPERNestedSequence, pkt) + +assert decoded.id.val == 5 + +assert decoded.x.val == 3 + +assert decoded.y.val == 1 + +True + += uper sequence with null +pkt = UPERSequenceWithNull(id=1) + +assert raw(pkt) == bytes.fromhex("0101") + +decoded = _roundtrip(UPERSequenceWithNull, pkt) + +assert decoded.id.val == 1 + +assert getattr(decoded.n, "val", decoded.n) is None + +True + += uper fixed bit string +from scapy.asn1.asn1 import ASN1_BIT_STRING + +pkt = UPERFixedBitString(b=ASN1_BIT_STRING("1010101111001101")) + +assert raw(pkt) == bytes.fromhex("abcd") + +decoded = _roundtrip(UPERFixedBitString, pkt) + +assert decoded.b.val == "1010101111001101" + +True + += uper sequence of constrained ints +pkt = UPERSequenceOfConstrainedInts(values=[1, 200, 0]) + +assert raw(pkt) == bytes.fromhex("0301c800") + +decoded = _roundtrip(UPERSequenceOfConstrainedInts, pkt) + +assert [x.val for x in decoded.values] == [1, 200, 0] + +True + += uper signed integer +for value, expected in [ + (0, b"\x80"), + (-1, b"\x7f"), + (127, b"\xff"), + (-128, b"\x00"), +]: + pkt = UPERSignedInteger(n=value) + assert raw(pkt) == expected + decoded = _roundtrip(UPERSignedInteger, pkt) + assert decoded.n.val == value + +True + += uper multi optional +both = UPERMultiOptional(id=1, a=2, b=b"hi") + +assert raw(both) == bytes.fromhex("c0404040809a1a40") + +decoded = _roundtrip(UPERMultiOptional, both) + +assert decoded.id.val == 1 + +assert decoded.a.val == 2 + +assert decoded.b.val == b"hi" + +none = UPERMultiOptional(id=1, a=None, b=None) + +assert raw(none) == bytes.fromhex("004040") + +decoded = _roundtrip(UPERMultiOptional, none) + +assert decoded.id.val == 1 + +assert decoded.a is None + +assert decoded.b is None + +only_a = UPERMultiOptional(id=3, a=9, b=None) + +assert raw(only_a) == bytes.fromhex("8040c04240") + +decoded = _roundtrip(UPERMultiOptional, only_a) + +assert decoded.id.val == 3 + +assert decoded.a.val == 9 + +assert decoded.b is None + +True + += uper length determinant +for length, expected in [ + (0, b"\x00"), + (1, b"\x01"), + (127, b"\x7f"), + (128, b"\x80\x80"), + (16383, b"\xbf\xff"), +]: + enc = UPER_Encoder() + enc.append_length_determinant(length) + assert enc.as_bytes() == expected + +True + += uper length determinant refuses lengths that need fragmentation +# X.691 11.9.3.8: the caller has to split the content, so a bare determinant +# of 16K or more would not match what follows it. +enc = UPER_Encoder() +try: + enc.append_length_determinant(16384) + assert False +except UPER_Encoding_Error: + pass + +True + += uper fragmented length determinant +# X.691 11.9.3.8: fragments of 16K units, always closed by a determinant +# below 16K. Byte vectors checked against asn1tools. +for count, expected_header in [ + (16384, b"\xc1"), + (32768, b"\xc2"), + (49152, b"\xc3"), + (65536, b"\xc4"), + (81920, b"\xc4"), +]: + enc = UPER_Encoder() + seen = [] + enc.append_fragmented(count, lambda offset, size: seen.append((offset, size))) + got = enc.as_bytes() + assert got.startswith(expected_header), (count, got[:1]) + assert sum(size for _, size in seen) == count, (count, seen) + assert got.endswith(b"\x00"), (count, got[-1:]) + +True + += uper fragmented length determinant roundtrip +for count in [0, 127, 16383, 16384, 40000, 70000]: + enc = UPER_Encoder() + enc.append_fragmented(count, lambda offset, size: None) + dec = UPER_Decoder(enc.as_bytes()) + seen = [] + dec.read_fragmented(lambda size: seen.append(size)) + assert sum(seen) == count, (count, seen) + +True + += uper count roundtrip +for count in [0, 1, 3, 127]: + enc = UPER_Encoder() + enc.append_length_determinant(count) + assert UPER_Decoder(enc.as_bytes()).read_length_determinant() == count + +True + += uper choice index roundtrip +for index, choices in [(0, 2), (1, 5), (3, 5)]: + enc = UPER_Encoder() + UPER_choice_index_enc(enc, index, choices) + got = UPER_choice_index_dec(UPER_Decoder(enc.as_bytes()), choices) + assert got == index + +True + += uper optional presence +enc = UPER_Encoder() + +for bit in [0, 1, 0]: + enc.append_bit(bit) + +assert enc.as_bytes() == b"\x40" + +True + += uper constrained integer +enc = UPER_Encoder() + +UPER_constrained_int_enc(enc, 10, 0, 15) + +assert UPER_constrained_int_dec(UPER_Decoder(enc.as_bytes()), 0, 15) == 10 + +True + += uper constrained signed integer +for value, expected in [(0, b"\x80"), (-1, b"\x7f"), (127, b"\xff"), (-128, b"\x00")]: + enc = UPER_Encoder() + UPER_constrained_int_enc(enc, value, -128, 127) + assert enc.as_bytes() == expected + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_constrained_int_dec(dec, -128, 127) == value + +True + += uper octet string roundtrip +for data, minimum, maximum in [ + (b"AB", None, None), + (b"\x12\x34\x56", 3, 3), + (bytes.fromhex("afbc4583"), 1, 20), +]: + enc = UPER_Encoder() + UPER_octet_string_enc(enc, data, minimum, maximum) + dec = UPER_Decoder(enc.as_bytes()) + assert UPER_octet_string_dec(dec, minimum, maximum) == data + assert dec.remaining_bytes() == b"" + +True + += uper chained encode into +enc = UPER_Encoder() + +UPERcodec_INTEGER.encode_into(enc, 42) + +UPERcodec_INTEGER.encode_into(enc, -7) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 42 + +assert dec.read_unconstrained_whole_number() == -7 + +True + += uper codec roundtrips +for codec, value, kwargs, expected in CODEC_ROUNDTRIPS: + _assert_codec_roundtrip(codec, value, kwargs, expected) + +True + += uper codec oid roundtrip +import scapy.all # noqa: F401 # loads conf.mib for ASN1_OID + +for oid in ("1.2.3", "1.2.840.113549"): + data = UPERcodec_OID.enc(oid) + decoded, remain = UPERcodec_OID.do_dec(data) + assert remain == b"" + assert decoded.val == oid + +True + += uper codec oid encode interop +for oid, expected in OID_ENCODE_VECTORS: + got = UPERcodec_OID.enc(oid) + assert got == expected, ( + "OID %r: expected %s, got %s" % + (oid, expected.hex(), got.hex()) + ) + +True + += uper codec reference decode +for _typename, _value, codec, kwargs, expected, encoded in DECODE_VECTORS: + decoded, _remain = codec.do_dec(encoded, **kwargs) + assert decoded.val == expected, ( + "%s %r: expected %r, got %r" % + (_typename, _value, expected, decoded.val) + ) + +True + += primitive interop +for typename, value, encoder, expected in PRIMITIVE_VECTORS: + got = encoder(value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += composite interop +for typename, value, expected in COMPOSITE_VECTORS: + got = _encode_composite(typename, value) + assert got == expected, ( + "%s %r: expected %s, got %s" % + (typename, value, expected.hex(), got.hex()) + ) + +True + += packet reference interop +for cls, pkt_kwargs, expected in PACKET_REFERENCE_VECTORS: + got = raw(cls(**pkt_kwargs)) + assert got == expected, ( + "%s: expected %s, got %s" % + (cls.__name__, expected.hex(), got.hex()) + ) + decoded = cls(got) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if value is None: + assert field is None + elif isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += packet decode vectors +for cls, pkt_kwargs, data in DECODE_PACKET_VECTORS: + decoded = cls(data) + for key, value in pkt_kwargs.items(): + field = getattr(decoded, key) + if isinstance(value, bool): + assert field.val == (1 if value else 0) + else: + assert field.val == value + +True + += asn1scc vectors +for name, _value, encoder, expected in ASN1SCC_VECTORS: + got = encoder(_value) + assert got == expected, ( + "%s: expected %s, got %s" % + (name, expected.hex(), got.hex()) + ) + +True + += asn1scc readme message prefix +from scapy.packet import raw + +expected = bytes.fromhex(README_MESSAGE_PREFIX_HEX) + +pkt = UPERMessagePrefix( + msgId=1, + myflag=2, + szDescription=b"HelloWorld", + isReady=True, +) + +got = raw(pkt) + +assert got == expected + +decoded = UPERMessagePrefix(got) + +assert decoded.msgId.val == 1 + +assert decoded.myflag.val == 2 + +assert decoded.szDescription.val == b"HelloWorld" + +assert decoded.isReady.val == 1 + +True + += asn1scc readme message reference +assert README_MESSAGE_HEX == ( + "010101020980cd191eb851eb851f48656c6c6f576f726c6480" +) + +True + += uper fuzz encode +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + data = raw(fuzz(cls())) + except _DECODE_ERRORS: + continue + assert isinstance(data, bytes) + +True + += uper fuzz roundtrip +iterations = 25 + +for cls in _fuzz_packets(): + for _ in range(iterations): + try: + cls(raw(fuzz(cls()))) + except _DECODE_ERRORS: + pass + +True + += uper fuzz codec decode +iterations = 100 + +for codec in _UPER_CODEC_CLASSES: + for _ in range(iterations): + data = os.urandom(random.randint(0, 64)) + try: + codec.safedec(data) + except _DECODE_ERRORS: + pass + +True + += uper fuzz packet decode +iterations = 100 + +for cls in _fuzz_packets(): + for _ in range(iterations): + data = os.urandom(random.randint(0, 128)) + try: + cls(data) + except _DECODE_ERRORS: + pass + +True + ++ ASN.1 UPER build and dissect += per record build roundtrip +pkt = UPERRecord(**_record_kwargs()) + +assert len(raw(pkt)) > 0 + +decoded = _roundtrip(UPERRecord, pkt) + +assert decoded.id.val == 42 + +assert decoded.flag.val == 1 + +assert decoded.label.val == b"hi" + +assert decoded.extra.val == 7 + +assert [x.val for x in decoded.values] == [1, 2, 3] + +True + += per default field build +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + minimum=0, maximum=86401, unsigned=True, + ), + 600, + ), + ) + +absent = UPERDefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _roundtrip(UPERDefaultRecord, absent) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 600 + +present = UPERDefaultRecord(id=1, count=86400) + +assert raw(present) == bytes.fromhex("80d46000") + +decoded = _roundtrip(UPERDefaultRecord, present) + +assert decoded.id.val == 1 + +assert _asn1_int(decoded.count) == 86400 + +True + += per extensible integer build +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + minimum=1, maximum=65535, + extensible=True, unsigned=True, + ), + ) + +in_range = UPERExtInt(n=42) + +assert raw(in_range) == bytes.fromhex("001480") + +decoded = _roundtrip(UPERExtInt, in_range) + +assert decoded.n.val == 42 + +out_of_range = UPERExtInt(n=1706733817) + +assert raw(out_of_range) == bytes.fromhex("8232dd587c80") + +decoded = _roundtrip(UPERExtInt, out_of_range) + +assert decoded.n.val == 1706733817 + +True + += per extensible integer as a bare root +~ per +class UPERBareExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER( + "n", 0, minimum=0, maximum=15, extensible=True, + ) + +class UPERWrappedExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, minimum=0, maximum=15, extensible=True), + ) + +# A bare root must encode the extension bit just like the nested field does. +assert raw(UPERBareExtInt(n=5)) == bytes.fromhex("28") + +assert raw(UPERBareExtInt(n=5)) == raw(UPERWrappedExtInt(n=5)) + +assert _dissect(UPERBareExtInt, "28").n.val == 5 + +assert _roundtrip(UPERBareExtInt, UPERBareExtInt(n=99)).n.val == 99 + +True + += per constrained sequence of build +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, minimum=0, maximum=7), + minimum=1, maximum=3, + ) + +pkt = UPERConstrainedSeqOf(items=[1, 2]) + +assert raw(pkt) == bytes.fromhex("4a") + +decoded = _roundtrip(UPERConstrainedSeqOf, pkt) + +assert [x.val for x in decoded.items] == [1, 2] + +True + += per field dissect +fixed = _dissect(UPERFixedFields, "c8414243") + +assert fixed.n.val == 200 + +assert fixed.s.val == b"ABC" + +present = _dissect(UPEROptionalField, "80954041c0") + +assert present.id.val == 42 + +assert present.flag.val == 1 + +assert present.extra.val == 7 + +absent = _dissect(UPEROptionalField, "009540") + +assert absent.id.val == 42 + +assert absent.flag.val == 1 + +assert absent.extra is None + +seqof = _dissect(UPERSequenceOfIntegers, "03010101020103") + +assert [x.val for x in seqof.values] == [1, 2, 3] + +empty_seqof = _dissect(UPERSequenceOfIntegers, "00") + +assert [x.val for x in empty_seqof.values] == [] + +as_int = _dissect(UPERChoiceField, "00b180") + +assert as_int.c.val == 99 + +as_str = _dissect(UPERChoiceField, "8120a100") + +assert as_str.c.val == b"AB" + +True + += per record dissect +decoded = _dissect( + UPERRecord, + "8095409a1a4041c0c04040408040c0", +) + +_assert_record(decoded) + +partial = _dissect(UPERRecord, "0095409050808040404080") + +assert partial.id.val == 42 + +assert partial.flag.val == 1 + +assert partial.label.val == b"AB" + +assert partial.extra is None + +assert [x.val for x in partial.values] == [1, 2] + +empty = _dissect(UPERRecord, "0080800000") + +_assert_record_empty(empty) + +True + += per default field dissect +class UPERDefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + minimum=0, maximum=86401, unsigned=True, + ), + 600, + ), + ) + +absent = _dissect(UPERDefaultRecord, "0080") + +assert absent.id.val == 1 + +assert _asn1_int(absent.count) == 600 + +present = _dissect(UPERDefaultRecord, "80d46000") + +assert present.id.val == 1 + +assert _asn1_int(present.count) == 86400 + +True + += per extensible integer dissect +class UPERExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER( + "n", 0, + minimum=1, maximum=65535, + extensible=True, unsigned=True, + ), + ) + +in_range = _dissect(UPERExtInt, "001480") + +assert in_range.n.val == 42 + +out_of_range = _dissect(UPERExtInt, "8232dd587c80") + +assert out_of_range.n.val == 1706733817 + +True + += per constrained sequence of dissect +class UPERConstrainedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_INTEGER("n", 0, minimum=0, maximum=7), + minimum=1, maximum=3, + ) + +decoded = _dissect(UPERConstrainedSeqOf, "4a") + +assert [x.val for x in decoded.items] == [1, 2] + +True + ++ ASN.1 UPER coverage += uper error str +obj = ASN1_INTEGER(2) + +err = UPER_Encoding_Error("enc", encoded=obj, remaining=b"x") + +assert "Already encoded" in str(err) + +err2 = UPER_Decoding_Error("dec", decoded=obj, remaining=b"y") + +assert "Already decoded" in str(err2) + +True + += uper length determinant extended +# X.691 11.9.3.8: multiples of 16K units are emitted as 0xc1..0xc4 fragments +# and the sequence is closed by a determinant below 16K. +def _fragment_headers(count): + enc = UPER_Encoder() + sizes = [] + enc.append_fragmented(count, lambda offset, size: sizes.append(size)) + return enc.as_bytes(), sizes + +assert _fragment_headers(32768) == (b"\xc2\x00", [32768, 0]) + +assert _fragment_headers(49152) == (b"\xc3\x00", [49152, 0]) + +assert _fragment_headers(65535) == (b"\xc3\xbf\xff", [49152, 16383]) + +assert _fragment_headers(65536) == (b"\xc4\x00", [65536, 0]) + +assert _fragment_headers(81920) == (b"\xc4\xc1\x00", [65536, 16384, 0]) + +True + += uper unconstrained whole number +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(-256) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == -256 + +enc = UPER_Encoder() + +enc.append_unconstrained_whole_number(0) + +dec = UPER_Decoder(enc.as_bytes()) + +assert dec.read_unconstrained_whole_number() == 0 + +True + += uper bit string paths +encoded = UPERcodec_BIT_STRING.enc("1010", minimum=1, maximum=20) + +obj, remain = UPERcodec_BIT_STRING.do_dec( + encoded, minimum=1, maximum=20, +) + +assert obj.val == "1010" + +encoded2 = UPERcodec_BIT_STRING.enc(b"\xab", minimum=4, maximum=8) + +obj2, _ = UPERcodec_BIT_STRING.do_dec(encoded2, minimum=4, maximum=8) + +assert len(obj2.val) == 8 + +fixed = UPERcodec_BIT_STRING.enc("1010101111001101", minimum=16, maximum=16) + +obj3, _ = UPERcodec_BIT_STRING.do_dec(fixed, minimum=16, maximum=16) + +assert obj3.val == "1010101111001101" + +True + += uper enumerated range +encoded = UPERcodec_ENUMERATED.enc(3, minimum=0, maximum=7) + +obj, remain = UPERcodec_ENUMERATED.do_dec(encoded, minimum=0, maximum=7) + +assert obj.val == 3 + +assert remain == b"" + +enc = UPER_Encoder() + +UPERcodec_ENUMERATED.encode_into(enc, 2, minimum=0, maximum=3) + +obj2 = UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(enc.as_bytes()), + minimum=0, + maximum=3, +) + +assert obj2.val == 2 + +True + += uper enumerated without a range +# The width would otherwise follow the value, which the decoder cannot redo +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.enc(3)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.do_dec(b"\x60")) + +True + += uper enumerated index follows the value order +# X.691 14.1: sort the enumeration by value, whatever order it was declared in +class UPERUnsortedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {2: "c", 0: "a", 1: "b"}), + ) + +# byte vectors from asn1tools for ENUMERATED { c(2), a(0), b(1) } +for value, expected in [(0, "00"), (1, "40"), (2, "80")]: + assert raw(UPERUnsortedEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERUnsortedEnum, expected).e.val == value + +True + += uper extensible enumerated +# X.691 14.3: a one bit prefix, zero for a value of the extension root +class UPERExtEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_ENUMERATED("e", 0, {0: "a", 1: "b", 2: "c"}, + extensible=True), + ) + +# byte vectors from asn1tools for ENUMERATED { a(0), b(1), c(2), ... } +for value, expected in [(0, "00"), (1, "20"), (2, "40")]: + assert raw(UPERExtEnum(e=value)) == bytes.fromhex(expected), value + assert _dissect(UPERExtEnum, expected).e.val == value + +enc = UPER_Encoder() + +_raises(UPER_Encoding_Error, lambda: UPERcodec_ENUMERATED.encode_into( + enc, 7, uper_enum_values=[0, 1, 2], extensible=True)) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_ENUMERATED.dec_from_decoder( + UPER_Decoder(b"\x80"), uper_enum_values=[0, 1, 2], extensible=True)) + +True + += uper octet string honours its size constraint +# A determinant sized after the constraint cannot express a violating length +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"AB", size_len=4)) + +_raises(UPER_Encoding_Error, lambda: UPERcodec_STRING.enc(b"ABCDEF", size_len=4)) + +assert UPERcodec_STRING.enc(b"ABCD", size_len=4) == b"ABCD" + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"A", minimum=2, maximum=4)) + +_raises(UPER_Encoding_Error, + lambda: UPERcodec_STRING.enc(b"ABCDEFGH", minimum=2, maximum=4)) + +True + += uper sequence errors +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.enc([ASN1_INTEGER(1)])) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_SEQUENCE.do_dec(b"\x00")) + +# A finished encoding is octet padded, so it cannot be spliced into a bitstream +_raises(UPER_Encoding_Error, lambda: UPERcodec_SEQUENCE.encode_into(UPER_Encoder(), b"raw")) + +assert UPERcodec_SET.enc(b"raw") == b"raw" + +True + += uper ipaddress +encoded = UPERcodec_IPADDRESS.enc("10.0.0.1") + +obj, remain = UPERcodec_IPADDRESS.do_dec(encoded) + +assert obj.val == "10.0.0.1" + +assert remain == b"" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_IPADDRESS.enc("bad-ip")) + +True + ++ ASN.1 fields coverage += asn1fields enum and flags +pkt = _InnerRecord(mode="on") + +built = raw(pkt) + +decoded = _InnerRecord(built) + +assert decoded.mode.val == 1 + +flags = _FlagsRecord(f="read+exec") + +assert flags.f.val == "101" + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +set_pkt = _SetOfRecord(items=[ASN1_INTEGER(0), ASN1_INTEGER(1)]) + +set_raw = raw(set_pkt) + +set_dec = _SetOfRecord(set_raw) + +assert [x.val for x in set_dec.items] == [0, 1] + +True + += asn1fields encaps and packet +inner = _InnerRecord(mode=1) + +enc = _EncapsRecord() + +enc.payload = inner + +enc_raw = raw(enc) + +enc_dec = _EncapsRecord(enc_raw) + +assert enc_dec.payload.mode.val == 1 + +pkt_field = _PacketFieldRecord() + +pkt_field.data = _InnerRecord(mode=0) + +pf_raw = raw(pkt_field) + +pf_dec = _PacketFieldRecord(pf_raw) + +assert isinstance(pf_dec.data.val, bytes) + +explicit = _ExplicitPacket() + +explicit.inner = _InnerRecord(mode=1) + +ex_raw = raw(explicit) + +ex_dec = _ExplicitPacket(ex_raw) + +assert ex_dec.inner.mode.val == 1 + +True + += asn1fields choice and special +class _OerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer = _OerChoiceRecord(c=ASN1_INTEGER(1)) + +oer_dec = _OerChoiceRecord(raw(oer)) + +assert oer_dec.c.val == 1 + +ber = _BerChoiceRecord(c=ASN1_INTEGER(0)) + +ber_dec = _BerChoiceRecord(raw(ber)) + +assert ber_dec.c.val == 0 + +inner_bytes = raw(_InnerRecord(mode=0)) + +bit_payload = ASN1_BIT_STRING( + inner_bytes, + readable=True, +) + +bit_pkt = _BitEncapsRecord(b=bit_payload) + +bit_dec = _BitEncapsRecord(raw(bit_pkt)) + +assert bit_dec.b.mode.val == 0 + +class _TicksRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_TIME_TICKS("t", ASN1_TIME_TICKS(0)) + +class _IpRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_IPADDRESS("addr", ASN1_STRING(b"")) + +ticks = _TicksRecord(t=ASN1_TIME_TICKS(1234)) + +assert raw(ticks).endswith(b"\x04\xd2") + +ip = _IpRecord() + +ip.addr = "192.168.1.1" + +assert raw(ip) == b"\x40\x04\xc0\xa8\x01\x01" + +True + += asn1fields optional dissect +class _OptRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_optional(ASN1F_INTEGER("extra", 0)), + ) + +class _BerChoiceRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +pkt = _OptRecord(id=0, extra=None) + +assert raw(pkt) + +decoded = _OptRecord(raw(pkt)) + +assert decoded.extra is None + +choice_rand = _BerChoiceRecord.ASN1_root.randval() + +assert choice_rand is not None + +True + += asn1fields default and omit +class _DefaultRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_DEFAULT( + ASN1F_INTEGER( + "count", 600, + minimum=0, maximum=86401, unsigned=True, + ), + 600, + ), + ) + +absent = _DefaultRecord(id=1) + +assert raw(absent) == b"\x00\x80" + +decoded = _DefaultRecord(raw(absent)) + +assert decoded.id.val == 1 + +assert decoded.count == 600 or decoded.count.val == 600 + +present = _DefaultRecord(id=1, count=86400) + +decoded = _DefaultRecord(raw(present)) + +assert decoded.count.val == 86400 + +class _OmitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_omit("ignored", None), + ) + +omit_pkt = _OmitRecord(id=7) + +assert raw(omit_pkt) == bytes.fromhex("3003020107") + +True + += asn1fields extensible per +class _ExtSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_optional(ASN1F_INTEGER("extra", 0, minimum=0, maximum=7)), + extensible=True, + ) + +pkt = _ExtSeq(id=2, extra=3) + +data = raw(pkt) + +decoded = _ExtSeq(data) + +assert decoded.id.val == 2 + +assert decoded.extra.val == 3 + +dec = UPER_DecoderContext(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtSeq.ASN1_root.decode_from(_ExtSeq(), dec), +) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + extensible=True, + ) + +choice = _ExtChoice(c=ASN1_INTEGER(4)) + +assert raw(choice) + +dec = UPER_DecoderContext(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.decode_from(_ExtChoice(), dec), +) + +class _InnerItem(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=7) + +class _ExtSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _InnerItem, + minimum=1, maximum=2, extensible=True, + ) + +in_range = _ExtSeqOf(items=[_InnerItem(n=1)]) + +assert raw(in_range) + +decoded = _ExtSeqOf(raw(in_range)) + +assert decoded.items[0].n.val == 1 + +out_of_range = _ExtSeqOf( + items=[_InnerItem(n=i) for i in range(4)], +) + +assert raw(out_of_range) + +decoded = _ExtSeqOf(raw(out_of_range)) + +assert len(decoded.items) == 4 + +True + += asn1fields sequence of advanced +class _Inner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=7) + +class _SeqOfPackets(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], _Inner, minimum=1, maximum=3, + ) + +pkt = _SeqOfPackets(items=[_Inner(n=1), _Inner(n=2)]) + +decoded = _SeqOfPackets(raw(pkt)) + +assert [x.n.val for x in decoded.items] == [1, 2] + +class _OerSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +oer_pkt = _OerSeqOf(values=[1, 2]) + +oer_dec = _OerSeqOf(raw(oer_pkt)) + +assert [x.val for x in oer_dec.values] == [1, 2] + +class _EmptySeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER) + +empty = _EmptySeqOf(values=None) + +assert raw(empty) == b"\x00" + +assert _EmptySeqOf.ASN1_root.i2repr(empty, None) == "[]" + +assert _EmptySeqOf.ASN1_root.i2repr( + _EmptySeqOf(values=[ASN1_INTEGER(1)]), + [ASN1_INTEGER(1)], +).startswith("[") + +_raises(ValueError, lambda: ASN1F_SEQUENCE_OF("bad", [], object())) + +# Compound ASN1F_field elements stay constructible (BER/OER); UPER rejects +# them at encode/decode time rather than at construction. +class _UperSeqOfSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", + [], + ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, minimum=0, maximum=15), + ), + ) + +_raises(UPER_Encoding_Error, lambda: raw(_UperSeqOfSeq(items=[]))) +_raises( + UPER_Decoding_Error, + lambda: _UperSeqOfSeq.ASN1_root.decode_from( + _UperSeqOfSeq(), UPER_DecoderContext(b"\x00"), + ), +) + +class _BerSeqOfChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], + ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING), + ) + +assert [x.val for x in _BerSeqOfChoice(raw( + _BerSeqOfChoice(items=[ASN1_INTEGER(5)]) +)).items] == [5] + +# ASN1F_PACKET elements remain valid (same as ASN1_Packet nesting). +class _PktItem(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=7) + +class _SeqOfPacketField(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "items", [], ASN1F_PACKET("item", None, _PktItem), + ) + +pkt = _SeqOfPacketField(items=[_PktItem(n=1), _PktItem(n=2)]) +assert [x.n.val for x in _SeqOfPacketField(raw(pkt)).items] == [1, 2] + +True + += asn1fields choice advanced +class _InnerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +class _NestedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), _InnerChoice, ASN1F_INTEGER, + ) + +nested = _NestedChoice(c=_InnerChoice(c=ASN1_STRING(b"xy"))) + +assert len(raw(nested)) > 0 + +nested_dec = _NestedChoice(raw(nested)) + +assert isinstance(nested_dec.c, (_InnerChoice, ASN1_STRING)) + +class _OerTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + explicit_tag=0xA1, + ) + +oer_choice = _OerTaggedChoice(c=ASN1_INTEGER(9)) + +assert raw(oer_choice) + +class _PacketChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", + ASN1_INTEGER(0), + ASN1F_PACKET("inner", None, _InnerRecord, explicit_tag=0xA2), + ASN1F_INTEGER, + ) + +packet_choice = _PacketChoice( + c=_InnerRecord(mode=ASN1_INTEGER(1)), +) + +packet_dec = _PacketChoice(raw(packet_choice)) + +assert packet_dec.c.mode.val == 1 + +class _PerChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +_raises( + ASN1_Error, + lambda: ASN1F_CHOICE( + "c", 0, ASN1F_INTEGER, implicit_tag=0xA0, + ), +) + +_raises( + ASN1_Error, + lambda: _PerChoice.ASN1_root.m2i(_PerChoice(), b""), +) + +_raises( + ASN1_Error, + lambda: UPER_EncoderContext().encode_choice( + _PerChoice.ASN1_root, _PerChoice(), 42, + ), +) + +True + += asn1fields enum bitstring and flags +class _NamedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_enum_INTEGER( + "state", 0, ["off", "on", "auto"], + ) + +named = _NamedEnum(state="on") + +built = raw(named) + +decoded = _NamedEnum(built) + +assert decoded.state.val == 1 + +assert "'on'" in _NamedEnum.ASN1_root.i2repr(decoded, decoded.state) + +class _BitRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING("bits", b"\xaa") + +assert raw(_BitRecord()) + +flags = _FlagsRecord() + +flags.f = ASN1_BIT_STRING("101") + +assert "read, exec" in _FlagsRecord.ASN1_root.seq[0].i2repr(flags, flags.f) + +class _BadBitEncaps(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_BIT_STRING_ENCAPS("b", None, _InnerRecord) + +_raises( + BER_Decoding_Error, + lambda: _BadBitEncaps.ASN1_root.m2i( + _BadBitEncaps(), + b"\x03\x02\x01\x00", + ), +) + +True + += asn1fields packet and sequence errors +class _PerInner(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("mode", 0, minimum=0, maximum=1) + +class _PacketWrap(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET("inner", None, _PerInner) + +inner = _PerInner(mode=1) + +wrap = _PacketWrap(inner=inner) + +decoded = _PacketWrap(raw(wrap)) + +assert decoded.inner.mode.val == 1 + +class _DynamicPacket(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_PACKET( + "inner", None, _PerInner, + next_cls_cb=lambda pkt: _PerInner, + ) + +dyn = _DynamicPacket(inner=_PerInner(mode=0)) + +root = _DynamicPacket.ASN1_root +assert (root.next_cls_cb(dyn) or root.cls) is _PerInner + +empty_packet = _PacketWrap(inner=None) + +assert raw(empty_packet) == b"" + +class _BerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + ASN1F_INTEGER("extra", 0), + ) + +_raises( + BER_Decoding_Error, + lambda: _BerSeq.ASN1_root.m2i( + _BerSeq(), + bytes.fromhex("300702010102010200ff"), + ), +) + +class _OerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, size_len=1, unsigned=True), + ) + +_, remain = _OerSeq.ASN1_root.m2i(_OerSeq(), b"\x01\xff") + +assert remain == b"\xff" + +class _PerSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ) + +_raises( + UPER_Decoding_Error, + lambda: _PerSeq.ASN1_root.m2i(_PerSeq(), b"\x80\xff"), +) + +empty_seq = _BerSeq() + +_BerSeq.ASN1_root._dissect_sequence_children(empty_seq, b"") + +assert empty_seq.id is None + +assert empty_seq.extra is None + +class _OptListRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_optional( + ASN1F_SEQUENCE_OF("items", [], ASN1F_INTEGER), + ), + ) + +opt_list = _OptListRecord(id=1, items=None) + +assert raw(opt_list) + +field = ASN1F_INTEGER("n", 0) + +with mock.patch.object( + _InnerRecord, "__init__", side_effect=ASN1F_badsequence, +): + pkt_obj, remain = field.extract_packet( + _InnerRecord, b"\xab\xcd", _parent=None, + ) + +assert isinstance(pkt_obj, Raw) + +assert pkt_obj.load == b"\xab\xcd" + +assert remain == b"\xab\xcd" + +True + += asn1fields more coverage +_raises( + ASN1_Error, + lambda: ASN1F_INTEGER("x", 0, implicit_tag=1, explicit_tag=2), +) + +class _IntRecord(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_INTEGER("n", 0) + +field = _IntRecord.ASN1_root + +_raises( + ASN1_Error, + lambda: field.i2m(_IntRecord(), ASN1_STRING(b"bad")), +) + +flex_field = ASN1F_INTEGER("n", 0, flexible_tag=True, explicit_tag=0xA0) + +obj, remain = flex_field.m2i(_IntRecord(), bytes.fromhex("a1020101")) + +assert obj.tag != ASN1_Class_UNIVERSAL.INTEGER or remain == b"" + +class _FlexSeq(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0), + explicit_tag=0xA1, + flexible_tag=True, + ) + +flex_seq = _FlexSeq(id=1) + +assert raw(flex_seq) + +decoded = _FlexSeq(raw(flex_seq)) + +assert decoded.id.val == 1 + +assert ASN1F_BOOLEAN("b", False).randval() is not None + +assert ASN1F_BIT_STRING("b", b"").randval() is not None + +assert ASN1F_OID("o", None).randval() is not None + +assert ASN1F_UTC_TIME("t", "").randval() is not None + +assert " 0 + +empty_inner, remain = packet_field.m2i(_FlexPacket(), b"") + +assert empty_inner is None and remain == b"" + +obj_val = packet_field.i2m(_FlexPacket(), _InnerRecord(mode=0)) + +assert len(obj_val) > 0 + +flags_field = _FlagsRecord.ASN1_root.seq[0] + +assert flags_field.i2repr(_FlagsRecord(), None) == "None" + +class _OerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_OerFlexSeqOf.ASN1_root.flexible_tag = True + +oer_seq = _OerFlexSeqOf(values=[1]) + +data = raw(oer_seq) + +decoded = _OerFlexSeqOf(data) + +assert decoded.values[0].val == 1 + +class _BerFlexSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER, + explicit_tag=0xA1, + ) + +_BerFlexSeqOf.ASN1_root.flexible_tag = True + +ber_seq = _BerFlexSeqOf(values=[2]) + +assert raw(ber_seq) + +class _ExtChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + extensible=True, + ) + +dec = UPER_DecoderContext(b"\x80") + +_raises( + UPER_Decoding_Error, + lambda: _ExtChoice.ASN1_root.decode_from(_ExtChoice(), dec), +) + +class _SingleChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", ASN1_INTEGER(0), ASN1F_INTEGER) + +single = _SingleChoice(c=ASN1_INTEGER(3)) + +assert raw(single) + +class _FlexChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + flexible_tag=True, + ) + +flex_choice = _FlexChoice(c=ASN1_INTEGER(4)) + +assert raw(flex_choice) + +class _OerPktChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.OER + ASN1_root = ASN1F_CHOICE( + "c", ASN1_INTEGER(0), ASN1F_INTEGER, ASN1F_STRING, + ) + +oer_pkt_choice = _OerPktChoice(c=ASN1_STRING(b"hi")) + +assert raw(oer_pkt_choice) + +True + + ++ ASN.1 UPER field dispatch and packet extras += uper DEFAULT published on asn1fields +assert ASN1F_DEFAULT is asn1fields.ASN1F_DEFAULT + +assert issubclass(ASN1F_DEFAULT, ASN1F_optional) + +enum_fld = ASN1F_ENUMERATED("e", 0, {1: "a", 2: "b"}) + +# ENUMERATED values are resolved per-codec from the field schema. +assert enum_fld.constraints.extensible is False + +assert enum_fld.uper_enum_values() == [1, 2] + +True + += uper constraints +fld = UPERConstrainedInt.ASN1_root + +assert fld.constraints.minimum == 0 and fld.constraints.maximum == 255 + +assert raw(UPERConstrainedInt(n=5)) == b"\x05" + +assert _val(_roundtrip(UPERConstrainedInt, UPERConstrainedInt(n=5)).n) == 5 + +True + += uper DEFAULT presence bit +absent = UPERSmallDefaultRecord(id=1, n=5) + +present = UPERSmallDefaultRecord(id=1, n=7) + +assert raw(absent) == bytes.fromhex("0080") + +assert raw(present) == bytes.fromhex("80b8") + +assert raw(absent) != raw(present) + +decoded_absent = _roundtrip(UPERSmallDefaultRecord, absent) + +decoded_present = _roundtrip(UPERSmallDefaultRecord, present) + +assert _val(decoded_absent.n) == 5 + +assert _val(decoded_present.n) == 7 + +True + += uper empty constrained sequence of +pkt = UPEREmptySeqOf(values=[]) + +assert raw(pkt) == b"\x00" + +decoded = _roundtrip(UPEREmptySeqOf, pkt) + +assert decoded.values == [] + +True + += uper extensible sequence of outside range +pkt = UPERExtSeqOf(values=[1, 2, 3]) + +assert raw(pkt) == bytes.fromhex("8194c0") + +decoded = _roundtrip(UPERExtSeqOf, pkt) + +assert [_val(x) for x in decoded.values] == [1, 2, 3] + +True + += uper FLAGS field +pkt = UPERFlagsField(f="101") + +assert raw(pkt) == bytes.fromhex("a0") + +decoded = _roundtrip(UPERFlagsField, pkt) + +assert decoded.f.val == "101" + +assert UPERFlagsField.ASN1_root.get_flags(decoded) == ["a", "c"] + +True + += uper nested ASN1F_PACKET +pkt = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +assert raw(pkt) == bytes.fromhex("0170") + +decoded = _roundtrip(UPERWrappedPacket, pkt) + +assert _val(decoded.id) == 1 + +assert _val(decoded.inner.x) == 7 + +True + += uper field encode_to nesting +built = UPERWrappedPacket(id=1, inner=UPERInnerPacket(x=7)) + +enc = UPER_EncoderContext() + +UPERWrappedPacket.ASN1_root.encode_to(built, enc) + +assert enc.finish() == raw(built) + +empty = UPERWrappedPacket() + +UPERWrappedPacket.ASN1_root.decode_from( + empty, UPER_DecoderContext(raw(built)), +) + +assert _val(empty.id) == 1 + +assert _val(empty.inner.x) == 7 + +True + += uper unconstrained bit string counts bits +# X.691 16.11: the length determinant of an unconstrained BIT STRING counts +# bits, not octets, and nothing is padded before the next field. Byte vectors +# checked against asn1tools. +class UPERFreeBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BIT_STRING("b", ""), + ASN1F_INTEGER("tail", 0, minimum=0, maximum=255), + ) + +for bits, expected in [ + ("", "00a5"), + ("1", "01d280"), + ("10110", "05b528"), + ("10110011", "08b3a5"), + ("1" * 20, "14fffffa50"), +]: + pkt = UPERFreeBitString(b=bits, tail=0xa5) + assert raw(pkt) == bytes.fromhex(expected), (bits, raw(pkt).hex()) + decoded = _roundtrip(UPERFreeBitString, pkt) + assert decoded.b.val == bits, (bits, decoded.b.val) + assert _val(decoded.tail) == 0xa5 + +True + += uper fixed size bit string refuses a mismatched length +class UPERFixedBitString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_BIT_STRING("b", "0" * 8, size_len=8)) + +assert raw(UPERFixedBitString(b="10110011")) == bytes.fromhex("b3") + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="101"))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERFixedBitString(b="1011001100"))) + +True + += uper octet string fragmentation +# X.691 11.9.3.8. Byte vectors checked against asn1tools: a fragment header, +# 16K octets, then the terminating determinant. +class UPERFreeOctetString(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_STRING("s", "")) + +data = bytes(i % 256 for i in range(16384)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc1" + data + b"\x00" + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +data = bytes(i % 256 for i in range(40000)) + +built = raw(UPERFreeOctetString(s=data)) + +assert built == b"\xc2" + data[:32768] + b"\x9c\x40" + data[32768:] + +assert _roundtrip(UPERFreeOctetString, UPERFreeOctetString(s=data)).s.val == data + +True + += uper sequence of fragmentation +class UPERFreeSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, minimum=0, maximum=255)), + ) + +items = [i % 256 for i in range(16385)] + +built = raw(UPERFreeSeqOf(values=items)) + +assert built == b"\xc1" + bytes(items[:16384]) + b"\x01" + bytes(items[16384:]) + +decoded = _roundtrip(UPERFreeSeqOf, UPERFreeSeqOf(values=items)) + +assert [_val(x) for x in decoded.values] == items + +True + += uper integer with an empty length determinant +_raises( + UPER_Decoding_Error, + lambda: UPER_Decoder(b"\x00").read_unconstrained_whole_number(), +) + +True + += uper choice with tagged packet alternatives +class UPERAltA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("i", 0), + ) + +class UPERAltB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("b", True), + ) + +class UPERTaggedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_CHOICE( + "c", None, + ASN1F_PACKET("a1", None, UPERAltA, explicit_tag=0xA0), + ASN1F_PACKET("a2", None, UPERAltB, explicit_tag=0xA1), + ), + ) + +# Reference (asn1tools) for +# Ch ::= SEQUENCE { c CHOICE { a1 A, a2 B } } +# A ::= SEQUENCE { i INTEGER }, B ::= SEQUENCE { b BOOLEAN } +# The alternative is picked by the type of the value, tags are not encoded. +assert raw(UPERTaggedChoice(c=UPERAltA(i=4))) == b"\x00\x82\x00" + +assert raw(UPERTaggedChoice(c=UPERAltB(b=False))) == b"\x80" + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltA(i=4))) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 4 + +decoded = _roundtrip(UPERTaggedChoice, UPERTaggedChoice(c=UPERAltB(b=False))) + +assert isinstance(decoded.c, UPERAltB) and decoded.c.b.val == 0 + +True + += uper choice with packet class alternatives +class UPERClassChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, UPERAltA, ASN1F_INTEGER) + +pkt = UPERClassChoice(c=UPERAltA(i=5)) + +# X.691 canonical order: INTEGER before SEQUENCE, so AltA is index 1 +assert raw(pkt) == b"\x80\x82\x80" + +decoded = _roundtrip(UPERClassChoice, pkt) + +assert isinstance(decoded.c, UPERAltA) and decoded.c.i.val == 5 + +decoded = _roundtrip(UPERClassChoice, UPERClassChoice(c=ASN1_INTEGER(3))) + +assert decoded.c.val == 3 + +# An unset CHOICE encodes to nothing, as the field is then absent +assert raw(UPERClassChoice(c=None)) == b"" + +True + += uper enumerated bounds +_raises(UPER_Encoding_Error, lambda: UPER_enumerated_enc(UPER_Encoder(), 0, [])) + +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\x00"), [])) + +# Three values are indexed on two bits, which can carry an index they do not +# define +_raises(UPER_Decoding_Error, lambda: UPER_enumerated_dec(UPER_Decoder(b"\xc0"), [0, 1, 2])) + +assert UPER_enumerated_dec(UPER_Decoder(b"\x40"), [0, 1, 2]) == 1 + +True + += uper untyped codec falls back on string and integer +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, b"hi") + +assert enc.as_bytes() == b"\x02hi" + +enc = UPER_Encoder() + +UPERcodec_Object.encode_into(enc, 5) + +assert enc.as_bytes() == b"\x01\x05" + +_raises(UPER_Encoding_Error, lambda: UPERcodec_Object.encode_into(UPER_Encoder(), object())) + +_raises(UPER_Decoding_Error, lambda: UPERcodec_Object.dec_from_decoder(UPER_Decoder(b"\x01"))) + +True + += uper choice with a single alternative +class UPEROneAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER) + +# X.691 23.5: one alternative leaves nothing to choose, so no index is encoded +assert raw(UPEROneAlt(c=ASN1_INTEGER(4))) == b"\x01\x04" + +assert _roundtrip(UPEROneAlt, UPEROneAlt(c=ASN1_INTEGER(4))).c.val == 4 + +class UPERThreeAlt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE("c", None, ASN1F_INTEGER, ASN1F_STRING, ASN1F_BOOLEAN) + +# Three alternatives are indexed on two bits, which can carry a fourth index +_raises(ASN1_Error, lambda: UPERThreeAlt(b"\xc0")) + +True + += uper sequence of an unset field +class UPERUnsetSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF("values", [], ASN1F_INTEGER("x", 0, minimum=0, maximum=255)), + ) + +assert raw(UPERUnsetSeqOf(values=None)) == b"\x00" + +assert raw(UPERUnsetSeqOf(values=[])) == b"\x00" + +True + += uper field rejects a value of another type +class UPERIntOnly(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0), + ) + +_raises(ASN1_Error, lambda: raw(UPERIntOnly(n=ASN1_STRING(b"x")))) + +enc = UPER_Encoder() + +UPERcodec_OID.encode_into(enc, b"") + +assert enc.as_bytes() == b"\x00" + +True + += uper constrained values honour their range +class UPERSmallInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, minimum=0, maximum=7), + ) + +assert raw(UPERSmallInt(n=5)) == b"\xa0" + +# The value is written on the width of the range, so one outside it would be +# read back as another value +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=100))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSmallInt(n=-3))) + +class UPERSmallExtInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("n", 0, minimum=0, maximum=7, extensible=True), + ) + +# An extensible range does accept it, as an extension addition +assert _roundtrip(UPERSmallExtInt, UPERSmallExtInt(n=100)).n.val == 100 + +True + += uper sequence of honours its size constraint +class UPERSizedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_SEQUENCE_OF( + "values", [], ASN1F_INTEGER("x", 0, minimum=0, maximum=255), + minimum=1, maximum=3, + ), + ) + +assert raw(UPERSizedSeqOf(values=[ASN1_INTEGER(7)])) == b"\x01\xc0" + +# An unset field is an empty one, which the constraint rules out here +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=[]))) + +_raises(UPER_Encoding_Error, lambda: raw(UPERSizedSeqOf(values=None))) + +True + +def _value(obj): + return getattr(obj, "val", obj) + + +% PR #5050 refactor characterization (UPER) ++ UPER refactor contracts - continuous bit stream += constrained fields pack across one octet without intermediate padding +class RefactorUPERFlatBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("a", False), + ASN1F_INTEGER("b", 0, minimum=0, maximum=7), + ASN1F_BOOLEAN("c", False), + ASN1F_INTEGER("d", 0, minimum=0, maximum=7), + ) + +pkt = RefactorUPERFlatBits(a=True, b=5, c=False, d=3) +assert raw(pkt) == b"\xd3" +decoded = RefactorUPERFlatBits(raw(pkt)) +assert _value(decoded.a) == 1 +assert _value(decoded.b) == 5 +assert _value(decoded.c) == 0 +assert _value(decoded.d) == 3 +True + += nested ASN1F_SEQUENCE does not introduce a byte boundary +class RefactorUPERNestedBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("a", False), + ASN1F_SEQUENCE( + ASN1F_INTEGER("b", 0, minimum=0, maximum=7), + ASN1F_BOOLEAN("c", False), + ), + ASN1F_INTEGER("d", 0, minimum=0, maximum=7), + ) + +pkt = RefactorUPERNestedBits(a=True, b=5, c=False, d=3) +assert raw(pkt) == b"\xd3" +decoded = RefactorUPERNestedBits(b"\xd3") +assert (_value(decoded.a), _value(decoded.b), _value(decoded.c), _value(decoded.d)) == (1, 5, 0, 3) +True + += nested ASN1F_PACKET shares the same UPER bit stream +class RefactorUPERInnerBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("b", 0, minimum=0, maximum=7), + ASN1F_BOOLEAN("c", False), + ) + +class RefactorUPERPacketBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("a", False), + ASN1F_PACKET("inner", None, RefactorUPERInnerBits), + ASN1F_INTEGER("d", 0, minimum=0, maximum=7), + ) + +pkt = RefactorUPERPacketBits( + a=True, + inner=RefactorUPERInnerBits(b=5, c=False), + d=3, +) +assert raw(pkt) == b"\xd3" +decoded = RefactorUPERPacketBits(b"\xd3") +assert _value(decoded.a) == 1 +assert _value(decoded.inner.b) == 5 +assert _value(decoded.inner.c) == 0 +assert _value(decoded.d) == 3 +assert decoded.inner.parent is decoded +assert decoded.inner.underlayer is decoded +True + += repeated nested packet build is deterministic +pkt = RefactorUPERPacketBits( + a=True, + inner=RefactorUPERInnerBits(b=5, c=False), + d=3, +) +first = raw(pkt) +second = raw(pkt) +third = raw(RefactorUPERPacketBits(first)) +assert first == second == third == b"\xd3" +True + ++ UPER refactor contracts - constrained whole numbers += one bit constrained range exact vectors +class RefactorUPERRange01(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=1) + +assert raw(RefactorUPERRange01(n=0)) == b"\x00" +assert raw(RefactorUPERRange01(n=1)) == b"\x80" +assert _value(RefactorUPERRange01(b"\x00").n) == 0 +assert _value(RefactorUPERRange01(b"\x80").n) == 1 +True + += two bit constrained range exact vectors +class RefactorUPERRange03(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=3) + +all( + raw(RefactorUPERRange03(n=value)) == expected + and _value(RefactorUPERRange03(expected).n) == value + for value, expected in [ + (0, b"\x00"), + (1, b"\x40"), + (2, b"\x80"), + (3, b"\xc0"), + ] +) + += non-zero lower bound is encoded as an offset +class RefactorUPERRange512(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 5, minimum=5, maximum=12) + +all( + raw(RefactorUPERRange512(n=value)) == expected + and _value(RefactorUPERRange512(expected).n) == value + for value, expected in [ + (5, b"\x00"), + (6, b"\x20"), + (7, b"\x40"), + (8, b"\x60"), + (9, b"\x80"), + (10, b"\xa0"), + (11, b"\xc0"), + (12, b"\xe0"), + ] +) + += singleton constrained range consumes zero bits +class RefactorUPERConstant(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 7, minimum=7, maximum=7) + +assert raw(RefactorUPERConstant(n=7)) == b"" +assert _value(RefactorUPERConstant(b"").n) == 7 +True + += constrained values outside the root range are rejected +_raises(UPER_Encoding_Error, lambda: raw(RefactorUPERRange03(n=-1))) +_raises(UPER_Encoding_Error, lambda: raw(RefactorUPERRange03(n=4))) +_raises(UPER_Encoding_Error, lambda: raw(RefactorUPERRange512(n=4))) +_raises(UPER_Encoding_Error, lambda: raw(RefactorUPERRange512(n=13))) +True + += plain int and ASN1_INTEGER use identical field constraints +all( + raw(RefactorUPERRange03(n=value)) == raw( + RefactorUPERRange03(n=ASN1_INTEGER(value)) + ) + for value in [0, 1, 2, 3] +) + += legacy oer_unsigned inference matches an explicit UPER 0 to 255 constraint +class RefactorUPERLegacyUnsigned(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, size_len=1, unsigned=True) + +class RefactorUPERExplicitUnsigned(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=255) + +all( + raw(RefactorUPERLegacyUnsigned(n=value)) + == raw(RefactorUPERExplicitUnsigned(n=value)) + == bytes([value]) + for value in [0, 1, 127, 128, 254, 255] +) + ++ UPER refactor contracts - OPTIONAL and DEFAULT += OPTIONAL and DEFAULT presence bits are a single continuous preamble +class RefactorUPERPresence(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("id", 0, minimum=0, maximum=255), + ASN1F_optional( + ASN1F_INTEGER("opt", 0, minimum=0, maximum=255) + ), + ASN1F_DEFAULT( + ASN1F_INTEGER("mode", 3, minimum=0, maximum=255), + 3, + ), + ) + +vectors = [ + (dict(id=1, opt=None, mode=3), bytes.fromhex("0040")), + (dict(id=1, opt=2, mode=3), bytes.fromhex("804080")), + (dict(id=1, opt=None, mode=4), bytes.fromhex("404100")), + (dict(id=1, opt=2, mode=4), bytes.fromhex("c0408100")), +] +all( + raw(RefactorUPERPresence(**values)) == expected + and _value(RefactorUPERPresence(expected).id) == 1 + and (_value(RefactorUPERPresence(expected).opt) + if RefactorUPERPresence(expected).opt is not None else None) == values["opt"] + and _value(RefactorUPERPresence(expected).mode) == values["mode"] + for values, expected in vectors +) + += DEFAULT omitted explicitly and DEFAULT omitted implicitly have identical wire form +implicit_default = RefactorUPERPresence(id=1, opt=None) +explicit_default = RefactorUPERPresence(id=1, opt=None, mode=3) +assert raw(implicit_default) == raw(explicit_default) == bytes.fromhex("0040") +True + += ASN1 object DEFAULT compares by semantic value +plain = RefactorUPERPresence(id=1, mode=3) +asn1_object = RefactorUPERPresence(id=1, mode=ASN1_INTEGER(3)) +assert raw(plain) == raw(asn1_object) == bytes.fromhex("0040") +True + ++ UPER refactor contracts - packet class selection += ASN1F_PACKET next_cls_cb selects child class during decode +class RefactorUPERDynamicA(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("x", 0, minimum=0, maximum=7), + ) + +class RefactorUPERDynamicB(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_BOOLEAN("flag", False), + ) + + +def _uper_dynamic_cls(pkt): + return RefactorUPERDynamicA if _value(pkt.kind) == 0 else RefactorUPERDynamicB + + +class RefactorUPERDynamicOuter(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("kind", 0, minimum=0, maximum=1), + ASN1F_PACKET( + "inner", None, RefactorUPERDynamicA, + next_cls_cb=_uper_dynamic_cls, + ), + ) + +packet_a = RefactorUPERDynamicOuter(kind=0, inner=RefactorUPERDynamicA(x=5)) +packet_b = RefactorUPERDynamicOuter(kind=1, inner=RefactorUPERDynamicB(flag=True)) +assert raw(packet_a) == b"\x50" +assert raw(packet_b) == b"\xc0" +decoded_a = RefactorUPERDynamicOuter(raw(packet_a)) +decoded_b = RefactorUPERDynamicOuter(raw(packet_b)) +assert isinstance(decoded_a.inner, RefactorUPERDynamicA) +assert _value(decoded_a.inner.x) == 5 +assert decoded_a.inner.parent is decoded_a +assert isinstance(decoded_b.inner, RefactorUPERDynamicB) +assert _value(decoded_b.inner.flag) == 1 +assert decoded_b.inner.parent is decoded_b +True + ++ UPER refactor contracts - SEQUENCE OF += fixed-size SEQUENCE OF packs elements without per-element padding +class RefactorUPERFixedSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, minimum=0, maximum=3), + minimum=4, + maximum=4, + ) + +pkt = RefactorUPERFixedSeqOf(values=[0, 1, 2, 3]) +assert raw(pkt) == b"\x1b" +decoded = RefactorUPERFixedSeqOf(b"\x1b") +assert [_value(item) for item in decoded.values] == [0, 1, 2, 3] +True + += constrained SEQUENCE OF count is encoded before continuous element bits +class RefactorUPERVariableSeqOf(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE_OF( + "values", + [], + ASN1F_INTEGER("item", 0, minimum=0, maximum=3), + minimum=1, + maximum=4, + ) + +# count=3 is offset 2 in range 1..4 -> '10', then 00 01 10 -> 10000110 +pkt = RefactorUPERVariableSeqOf(values=[0, 1, 2]) +assert raw(pkt) == b"\x86" +decoded = RefactorUPERVariableSeqOf(b"\x86") +assert [_value(item) for item in decoded.values] == [0, 1, 2] +True + + +% PR #5050 refactor targets (UPER) ++ Finding - UPER CHOICE indexes alternatives in canonical tag order += UPER CHOICE encode is independent of declaration order +class RefactorUPERChoiceTag0(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_NULL("n", None)) + +class RefactorUPERChoiceTag1(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_SEQUENCE(ASN1F_NULL("n", None)) + +class RefactorUPERReversedChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", None, + # Deliberately declare [1] before [0]. X.691 10.2/23.2 requires + # indexing root alternatives in canonical tag order. + ASN1F_PACKET( + "tag1", None, RefactorUPERChoiceTag1, + explicit_tag=0xA1, + ), + ASN1F_PACKET( + "tag0", None, RefactorUPERChoiceTag0, + explicit_tag=0xA0, + ), + ) + +# Canonical order is [0], [1], so tag0 has index 0 and tag1 has index 1. +assert raw(RefactorUPERReversedChoice(c=RefactorUPERChoiceTag0())) == b"\x00" +assert raw(RefactorUPERReversedChoice(c=RefactorUPERChoiceTag1())) == b"\x80" +True + += UPER CHOICE decode uses canonical tag order rather than declaration order +decoded_zero = RefactorUPERReversedChoice(b"\x00") +decoded_one = RefactorUPERReversedChoice(b"\x80") +assert isinstance(decoded_zero.c, RefactorUPERChoiceTag0) +assert isinstance(decoded_one.c, RefactorUPERChoiceTag1) +True + += same tagged CHOICE has same encoding when alternatives are declared canonically +class RefactorUPERCanonicalChoice(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_CHOICE( + "c", None, + ASN1F_PACKET( + "tag0", None, RefactorUPERChoiceTag0, + explicit_tag=0xA0, + ), + ASN1F_PACKET( + "tag1", None, RefactorUPERChoiceTag1, + explicit_tag=0xA1, + ), + ) + +all( + raw(RefactorUPERReversedChoice(c=value_reversed)) == raw( + RefactorUPERCanonicalChoice(c=value_canonical) + ) + for value_reversed, value_canonical in [ + (RefactorUPERChoiceTag0(), RefactorUPERChoiceTag0()), + (RefactorUPERChoiceTag1(), RefactorUPERChoiceTag1()), + ] +) + + +% PR #5050 architecture guard ++ Refactor architecture guard - importing UPER must not monkey-patch core field classes += isolated UPER import does not add codec methods to ASN1 field class dictionaries +code = r''' +from scapy.asn1fields import ( + ASN1F_CHOICE, + ASN1F_PACKET, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_field, + ASN1F_optional, +) +classes = ( + ASN1F_field, + ASN1F_SEQUENCE, + ASN1F_SEQUENCE_OF, + ASN1F_CHOICE, + ASN1F_PACKET, + ASN1F_optional, +) +watched = ( + "encode_into", + "m2i_from_decoder", + "dissect_from_decoder", +) +before = { + cls.__name__: tuple(name for name in watched if name in cls.__dict__) + for cls in classes +} +import scapy.asn1.uper +after = { + cls.__name__: tuple(name for name in watched if name in cls.__dict__) + for cls in classes +} +assert before == after, (before, after) +''' +result = subprocess.run([sys.executable, "-c", code]) +assert result.returncode == 0 +True + +% PR #5050 review regressions ++ UPER review fixes += top-level BOOLEAN padding keeps trailing whole octets as payload +from scapy.packet import Raw + +pkt = UPERBooleanField(b"\x80") +assert raw(pkt) == b"\x80" +assert pkt.getlayer(Raw) is None + +pkt = UPERBooleanField(b"\x80\x00") +assert bool(pkt.b) is True +assert raw(pkt) == b"\x80\x00" +assert pkt.getlayer(Raw) is not None +assert pkt.getlayer(Raw).load == b"\x00" + +pkt = UPERBooleanField(b"\x80\x01") +assert bool(pkt.b) is True +assert raw(pkt) == b"\x80\x01" +assert pkt.getlayer(Raw).load == b"\x01" + += constrained INTEGER decode rejects out-of-range code points +class UPERRange02(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 0, minimum=0, maximum=2) + +_raises(UPER_Decoding_Error, lambda: UPERRange02(b"\xc0")) + += UPER OID round-trip for 2.999.3 +enc = UPER_Encoder() +UPERcodec_OID.encode_into(enc, "2.999.3") +obj = UPERcodec_OID.dec_from_decoder(UPER_Decoder(enc.as_bytes())) +assert str(obj.val) == "2.999.3" + += UPER known-multiplier IA5 string encoding raises strict error +_raises(UPER_Encoding_Error, lambda: UPERcodec_IA5_STRING.enc(b"hi")) +_raises(UPER_Encoding_Error, lambda: UPERcodec_IA5_STRING.encode_into( + UPER_Encoder(), b"hi")) + += UPER non-known-multiplier UTF8String uses octet-string path +assert UPERcodec_UTF8_STRING.enc(b"hi") == UPERcodec_STRING.enc(b"hi") +obj, remain = UPERcodec_UTF8_STRING.do_dec(UPERcodec_UTF8_STRING.enc(b"hi")) +assert obj.val == b"hi" +assert remain == b"" + += UPER time types raise unsupported errors +_raises(UPER_Encoding_Error, lambda: UPERcodec_UTC_TIME.enc(b"250101000000Z")) +_raises(UPER_Encoding_Error, lambda: UPERcodec_GENERALIZED_TIME.enc( + b"20250101000000Z")) + += CHOICE encode_choice does not mutate packet fields +import copy +from scapy.asn1.context import UPER_EncoderContext + +pkt = UPERChoiceField(c=ASN1_INTEGER(1)) +before = copy.copy(pkt.fields) +enc = UPER_EncoderContext() +enc.encode_choice(pkt.ASN1_root, pkt, value=ASN1_INTEGER(9)) +assert pkt.fields == before +assert enc.finish() == bytes.fromhex("008480") +True + += UPER semi-constrained INTEGER (5..MAX) encodes offset +class UPERSemiInt(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_INTEGER("n", 5, minimum=5) + +assert raw(UPERSemiInt(n=5)) == b"\x01\x00" +assert UPERSemiInt(b"\x01\x00").n.val == 5 +assert raw(UPERSemiInt(n=6)) == b"\x01\x01" +assert _roundtrip(UPERSemiInt, UPERSemiInt(n=6)).n.val == 6 +_raises(UPER_Encoding_Error, lambda: raw(UPERSemiInt(n=4))) +True + += UPER extensible SIZE STRING root and extension +class UPERExtSizeStr(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_STRING("s", b"", minimum=1, maximum=3, extensible=True) + +assert raw(UPERExtSizeStr(s=b"a")) == bytes.fromhex("0c20") +assert _roundtrip(UPERExtSizeStr, UPERExtSizeStr(s=b"a")).s.val == b"a" +assert _roundtrip(UPERExtSizeStr, UPERExtSizeStr(s=b"abcd")).s.val == b"abcd" +True + += UPER extensible SIZE BIT STRING root and extension +class UPERExtSizeBits(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_BIT_STRING("b", "", minimum=1, maximum=3, extensible=True) + +root = raw(UPERExtSizeBits(b="1")) +assert _roundtrip(UPERExtSizeBits, UPERExtSizeBits(b="1")).b.val == "1" +assert _roundtrip(UPERExtSizeBits, UPERExtSizeBits(b="1010")).b.val == "1010" +True + += UPER ENUMERATED accepts named string assignment +class UPERNamedEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "e", 0, {0: "zero", 1: "one", 5: "five"}, + ) + +assert raw(UPERNamedEnum(e="one")) == raw(UPERNamedEnum(e=1)) +assert _roundtrip(UPERNamedEnum, UPERNamedEnum(e="one")).e.val == 1 +True + += UPER ENUMERATED string-keyed definition maps names to integer indexes +class UPERStrKeyEnum(ASN1_Packet): + ASN1_codec = ASN1_Codecs.PER + ASN1_root = ASN1F_ENUMERATED( + "state", + 1, + {"off": 0, "on": 1}, + ) + +assert UPERStrKeyEnum.ASN1_root.uper_enum_values() == [0, 1] +assert raw(UPERStrKeyEnum(state="on")) == raw(UPERStrKeyEnum(state=1)) +assert _roundtrip(UPERStrKeyEnum, UPERStrKeyEnum(state="on")).state.val == 1 +True