Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6356382
contrib: add ASN.1 OER and UPER codecs
Jul 19, 2026
3f3c003
Remove python based unit tests
Aug 7, 2026
3dc8ee3
Cleanup asn1fields
Aug 8, 2026
99ece80
More tests
Aug 8, 2026
0b28220
More tests
Aug 8, 2026
307bd9f
Cleanup asn1fields
Aug 10, 2026
db638a3
Cleanup asn1fields
Aug 10, 2026
54628e9
Cleanup asn1fields
Aug 10, 2026
6ade4e5
oer: fix X.696 conformance of SEQUENCE and fixed-size strings
Aug 11, 2026
e78ef52
uper: fix bit string length, fragmentation and integer signedness
Aug 11, 2026
e107fd2
oer, uper: remove dead helpers and deduplicate the codecs
Aug 11, 2026
ea7e2fc
uper: fix enumerated indexing and octet string size constraints
Aug 11, 2026
5d5b656
oer, uper: fix choice alternatives, OER tagging and DEFAULT components
Aug 11, 2026
79f9d9d
uper: reject out of range constrained values, drop dead code
Aug 11, 2026
52ee7dd
asn1: let BER hook the tagging of a field, drop the codec-level one
Aug 11, 2026
804ee9c
asn1: register the field hooks of a codec by keyword, in one dictionary
Aug 12, 2026
fe2b418
oer, uper: inline single-use encoding helpers into their callers
Sep 1, 2026
57c4f77
asn1: move OER/UPER into core and unify compound encode/decode
Sep 1, 2026
40301ae
asn1: finish OER/UPER wire-path unification (P0–P3)
Sep 1, 2026
c474f4c
asn1: drop codec_kwargs legacy and read constraints from fields
Sep 1, 2026
4094eb9
Fix PR #5050 review defects in ASN.1 OER/UPER wire paths.
Sep 2, 2026
7af2e0f
Fix flake8 and mypy issues in ASN.1 OER/UPER changes.
Sep 2, 2026
66b3faf
Address remaining PR #5050 wire and compatibility review issues.
Sep 2, 2026
0b73e7a
Drop orb() from ASN.1 OER/UPER paths after upstream removal.
Sep 2, 2026
6d3344d
Fix codespell and Sphinx docstring issues from CI health/docs jobs.
Sep 2, 2026
b7942d3
Restore ASN.1 nested underlayer links used by Kerberos get_usage.
Sep 2, 2026
683c416
Drop OER/UPER contrib stubs and legacy constraint field aliases.
Sep 2, 2026
e8e3b36
Remove leftover OER/UPER compatibility wrappers and aliases.
Sep 2, 2026
aef594a
Simplify ASN.1 OER/UPER encode paths and share integer helpers.
Sep 2, 2026
d9c88d4
Fix remaining PR #5050 UPER/OER review findings.
Sep 2, 2026
ecaa0d3
Restrict AI-Assisted commit check to PR commits on merge checkouts.
Sep 2, 2026
f63089a
Finish ASN.1 OER/UPER codec-context architecture for PR #5050.
Sep 2, 2026
d32a06a
Restore AI-Assisted commit check scope for PR merge CI.
Sep 2, 2026
531af55
Fix ASN.1 mypy no-any-return and unused ignores.
Sep 2, 2026
4a06c0c
Simplify ASN.1 codec contexts and drop adapter helpers.
Sep 2, 2026
0753573
Trim ASN.1 encode hot-path adapters and BER SEQUENCE cost.
Sep 2, 2026
78606b2
Split ASN.1 compound hooks by codec.
Sep 2, 2026
23162a8
Speed up UPER bit reads and simplify OER length helpers.
Sep 2, 2026
dc1c9bb
Inline residual ASN.1 compound adapter helpers.
Sep 2, 2026
cd62c91
Make UPER decoding scale and trim ASN.1 decode allocations.
Sep 2, 2026
73ba320
Fix unaligned UPER read_bytes scaling and trim decode checks.
Sep 2, 2026
807ec3d
Clarify SEQUENCE OF element contract and compound encode APIs.
Sep 2, 2026
1e39079
Make UPER encoding byte-oriented and soften SEQUENCE OF limits.
Sep 3, 2026
c7cd967
Speed up UPER bit primitives with bulk window operations.
Sep 3, 2026
6d7d68c
Tighten OER/UPER hot-path leftovers and drop CI from this PR.
Sep 3, 2026
cf2cdd8
Fix string-keyed ENUMERATED and make memoryview fragmentation-only.
Sep 3, 2026
ccd96f0
Fold single-use ASN.1 helpers into the dedicated codecs.
Sep 8, 2026
57bb90f
Move single-class ASN.1 helpers into their codec classes.
Sep 8, 2026
3a92b22
Restrict AI-Assisted commit checks to the PR tip.
Sep 8, 2026
ec28f96
Move OER/UPER constraint helpers into their codec modules.
Sep 8, 2026
acf22ea
Preserve ASN.1 field hooks and fix OER/UPER length encoding.
Sep 10, 2026
b41f1c5
Keep BER codecs strict and reject SET on OER/UPER.
Sep 10, 2026
ee1723a
Reject OER SET encodings and treat SET OF as SEQUENCE OF.
Sep 10, 2026
12bafbc
Scan only the PR side of merge checkouts for AI trailers.
Sep 10, 2026
47a7d69
Reject empty OER unsigned integers and high-tag leading zeros.
Sep 10, 2026
75079b5
Reject unknown OER CHOICE alternatives and malformed BIT STRING encod…
Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .config/codespell_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,5 @@ wan
wanna
webp
widgits
UPER
uPER
85 changes: 71 additions & 14 deletions scapy/asn1/asn1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
113 changes: 33 additions & 80 deletions scapy/asn1/ber.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,7 +33,6 @@
from typing import (
Any,
AnyStr,
Dict,
Generic,
List,
Optional,
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -377,7 +327,6 @@ def dec(cls,
context=None, # type: Optional[Type[ASN1_Class]]
safe=False, # type: bool
_depth=0, # type: int
**_kwargs # type: Any
):
# type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes]
if _depth > MAX_BER_DEPTH:
Expand All @@ -404,21 +353,19 @@ def safedec(cls,
s, # type: bytes
context=None, # type: Optional[Type[ASN1_Class]]
_depth=0, # type: int
**_kwargs # type: Any
):
# type: (...) -> Tuple[Union[_ASN1_ERROR, ASN1_Object[_K]], bytes]
return cls.dec(s, context, safe=True, _depth=_depth)

@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.
def enc(cls, s, size_len=0):
# type: (_K, Optional[int]) -> bytes
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 !")

Expand All @@ -435,8 +382,9 @@ class BERcodec_INTEGER(BERcodec_Object[int]):
tag = ASN1_Class_UNIVERSAL.INTEGER

@classmethod
def enc(cls, i, size_len=0, **_kwargs): # type: ignore[override]
# type: (int, Optional[int], **Any) -> bytes
def enc(cls, i, size_len=0):
# type: (int, Optional[int]) -> bytes
size_len = 0 if size_len is None else int(size_len)
ls = []
while True:
ls.append(i & 0xff)
Expand Down Expand Up @@ -502,8 +450,9 @@ def do_dec(cls,
)

@classmethod
def enc(cls, _s, size_len=0, **_kwargs): # type: ignore[override]
# type: (AnyStr, Optional[int], **Any) -> bytes
def enc(cls, _s, size_len=0):
# type: (AnyStr, Optional[int]) -> 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:
Expand All @@ -521,8 +470,9 @@ class BERcodec_STRING(BERcodec_Object[str]):
tag = ASN1_Class_UNIVERSAL.STRING

@classmethod
def enc(cls, _s, size_len=0, **_kwargs): # type: ignore[override]
# type: (Union[str, bytes], Optional[int], **Any) -> bytes
def enc(cls, _s, size_len=0):
# type: (Union[str, bytes], Optional[int]) -> 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
Expand All @@ -543,8 +493,8 @@ class BERcodec_NULL(BERcodec_INTEGER):
tag = ASN1_Class_UNIVERSAL.NULL

@classmethod
def enc(cls, i, size_len=0, **_kwargs): # type: ignore[override]
# type: (int, Optional[int], **Any) -> bytes
def enc(cls, i, size_len=0):
# type: (int, Optional[int]) -> bytes
if i == 0:
return chb(int(cls.tag)) + b"\0"
else:
Expand All @@ -555,8 +505,9 @@ class BERcodec_OID(BERcodec_Object[bytes]):
tag = ASN1_Class_UNIVERSAL.OID

@classmethod
def enc(cls, _oid, size_len=0, **_kwargs): # type: ignore[override]
# type: (AnyStr, Optional[int], **Any) -> bytes
def enc(cls, _oid, size_len=0):
# type: (AnyStr, Optional[int]) -> 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".")]
Expand All @@ -582,11 +533,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,
)

Expand Down Expand Up @@ -647,8 +599,8 @@ class BERcodec_SEQUENCE(BERcodec_Object[Union[bytes, List[BERcodec_Object[Any]]]
tag = ASN1_Class_UNIVERSAL.SEQUENCE

@classmethod
def enc(cls, _ll, size_len=None, **_kwargs): # type: ignore[override]
# type: (Union[bytes, List[BERcodec_Object[Any]]], Optional[int], **Any) -> bytes # noqa: E501
def enc(cls, _ll, size_len=None):
# type: (Union[bytes, List[BERcodec_Object[Any]]], Optional[int]) -> bytes # noqa: E501
if isinstance(_ll, bytes):
ll = _ll
else:
Expand Down Expand Up @@ -703,8 +655,9 @@ class BERcodec_IPADDRESS(BERcodec_STRING):
tag = ASN1_Class_UNIVERSAL.IPADDRESS

@classmethod
def enc(cls, ipaddr_ascii, size_len=0, **_kwargs): # type: ignore[override]
# type: (str, Optional[int], **Any) -> bytes
def enc(cls, ipaddr_ascii, size_len=0): # type: ignore[override]
# type: (str, Optional[int]) -> bytes
size_len = 0 if size_len is None else int(size_len)
try:
s = inet_aton(ipaddr_ascii)
except Exception:
Expand Down
Loading
Loading