]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Check for OID arc values normalization
[pyderasn.git] / pyderasn.py
index 3b7d6cc75dcd5398cd5d440ee742fa26c9a4986c..4e9def7bff226e6a53c6b4b4abba12ad3787480b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # coding: utf-8
 # PyDERASN -- Python ASN.1 DER/BER codec with abstract structures
-# Copyright (C) 2017-2018 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2017-2019 Sergey Matveev <stargrave@stargrave.org>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as
@@ -213,9 +213,11 @@ decoding process.
 
 Currently available context options:
 
+* :ref:`allow_default_values <allow_default_values_ctx>`
+* :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
 * :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
-* :ref:`strict_default_existence <strict_default_existence_ctx>`
 
 .. _pprinting:
 
@@ -380,19 +382,41 @@ DER. But you can optionally enable BER decoding with setting ``bered``
 :ref:`context <ctx>` argument to True. Indefinite lengths and
 constructed primitive types should be parsed successfully.
 
-* If object is encoded in BER form (not the DER one), then ``bered``
+* If object is encoded in BER form (not the DER one), then ``ber_encoded``
   attribute is set to True. Only ``BOOLEAN``, ``BIT STRING``, ``OCTET
-  STRING`` can contain it.
+  STRING``, ``SEQUENCE``, ``SET``, ``SET OF`` can contain it.
 * If object has an indefinite length encoding, then its ``lenindef``
   attribute is set to True. Only ``BIT STRING``, ``OCTET STRING``,
   ``SEQUENCE``, ``SET``, ``SEQUENCE OF``, ``SET OF``, ``ANY`` can
   contain it.
 * If object has an indefinite length encoded explicit tag, then
   ``expl_lenindef`` is set to True.
+* If object has either any of BER-related encoding (explicit tag
+  indefinite length, object's indefinite length, BER-encoding) or any
+  underlying component has that kind of encoding, then ``bered``
+  attribute is set to True. For example SignedData CMS can have
+  ``ContentInfo:content:signerInfos:*`` ``bered`` value set to True, but
+  ``ContentInfo:content:signerInfos:*:signedAttrs`` won't.
 
 EOC (end-of-contents) token's length is taken in advance in object's
 value length.
 
+.. _allow_expl_oob_ctx:
+
+Allow explicit tag out-of-bound
+-------------------------------
+
+Invalid BER encoding could contain ``EXPLICIT`` tag containing more than
+one value, more than one object. If you set ``allow_expl_oob`` context
+option to True, then no error will be raised and that invalid encoding
+will be silently further processed. But pay attention that offsets and
+lengths will be invalid in that case.
+
+.. warning::
+
+   This option should be used only for skipping some decode errors, just
+   to see the decoded structure somehow.
+
 Primitive types
 ---------------
 
@@ -491,6 +515,7 @@ Various
 -------
 
 .. autofunction:: pyderasn.abs_decode_path
+.. autofunction:: pyderasn.colonize_hex
 .. autofunction:: pyderasn.hexenc
 .. autofunction:: pyderasn.hexdec
 .. autofunction:: pyderasn.tag_encode
@@ -515,9 +540,11 @@ from codecs import getdecoder
 from codecs import getencoder
 from collections import namedtuple
 from collections import OrderedDict
+from copy import copy
 from datetime import datetime
 from math import ceil
 from os import environ
+from string import ascii_letters
 from string import digits
 
 from six import add_metaclass
@@ -530,12 +557,13 @@ from six import iterbytes
 from six import PY2
 from six import string_types
 from six import text_type
+from six import unichr as six_unichr
 from six.moves import xrange as six_xrange
 
 
 try:
     from termcolor import colored
-except ImportError:
+except ImportError:  # pragma: no cover
     def colored(what, *args):
         return what
 
@@ -617,7 +645,11 @@ LENINDEF_PP_CHAR = "I" if PY2 else "∞"
 # Errors
 ########################################################################
 
-class DecodeError(Exception):
+class ASN1Error(ValueError):
+    pass
+
+
+class DecodeError(ASN1Error):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
         """
         :param str msg: reason of decode failing
@@ -672,7 +704,7 @@ class InvalidOID(DecodeError):
     pass
 
 
-class ObjUnknown(ValueError):
+class ObjUnknown(ASN1Error):
     def __init__(self, name):
         super(ObjUnknown, self).__init__()
         self.name = name
@@ -684,7 +716,7 @@ class ObjUnknown(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class ObjNotReady(ValueError):
+class ObjNotReady(ASN1Error):
     def __init__(self, name):
         super(ObjNotReady, self).__init__()
         self.name = name
@@ -696,7 +728,7 @@ class ObjNotReady(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class InvalidValueType(ValueError):
+class InvalidValueType(ASN1Error):
     def __init__(self, expected_types):
         super(InvalidValueType, self).__init__()
         self.expected_types = expected_types
@@ -710,7 +742,7 @@ class InvalidValueType(ValueError):
         return "%s(%s)" % (self.__class__.__name__, self)
 
 
-class BoundsError(ValueError):
+class BoundsError(ASN1Error):
     def __init__(self, bound_min, value, bound_max):
         super(BoundsError, self).__init__()
         self.bound_min = bound_min
@@ -904,7 +936,7 @@ class Obj(object):
         "vlen",
         "expl_lenindef",
         "lenindef",
-        "bered",
+        "ber_encoded",
     )
 
     def __init__(
@@ -926,7 +958,7 @@ class Obj(object):
         self.default = None
         self.expl_lenindef = False
         self.lenindef = False
-        self.bered = False
+        self.ber_encoded = False
 
     @property
     def ready(self):  # pragma: no cover
@@ -938,6 +970,12 @@ class Obj(object):
         if not self.ready:
             raise ObjNotReady(self.__class__.__name__)
 
+    @property
+    def bered(self):
+        """Is either object or any elements inside is BER encoded?
+        """
+        return self.expl_lenindef or self.lenindef or self.ber_encoded
+
     @property
     def decoded(self):
         """Is object decoded?
@@ -992,6 +1030,7 @@ class Obj(object):
             decode_path=(),
             ctx=None,
             tag_only=False,
+            _ctx_immutable=True,
     ):
         """Decode the data
 
@@ -999,14 +1038,17 @@ class Obj(object):
         :param int offset: initial data's offset
         :param bool leavemm: do we need to leave memoryview of remaining
                     data as is, or convert it to bytes otherwise
-        :param ctx: optional :ref:`context <ctx>` governing decoding process.
+        :param ctx: optional :ref:`context <ctx>` governing decoding process
         :param tag_only: decode only the tag, without length and contents
                          (used only in Choice and Set structures, trying to
                          determine if tag satisfies the scheme)
+        :param _ctx_immutable: do we need to copy ``ctx`` before using it
         :returns: (Obj, remaining data)
         """
         if ctx is None:
             ctx = {}
+        elif _ctx_immutable:
+            ctx = copy(ctx)
         tlv = memoryview(data)
         if self._expl is None:
             result = self._decode(
@@ -1054,7 +1096,7 @@ class Obj(object):
                     ctx=ctx,
                     tag_only=tag_only,
                 )
-                if tag_only:
+                if tag_only:  # pragma: no cover
                     return
                 obj, tail = result
                 eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
@@ -1089,9 +1131,16 @@ class Obj(object):
                     ctx=ctx,
                     tag_only=tag_only,
                 )
-                if tag_only:
+                if tag_only:  # pragma: no cover
                     return
                 obj, tail = result
+                if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
+                    raise DecodeError(
+                        "explicit tag out-of-bound, longer than data",
+                        klass=self.__class__,
+                        decode_path=decode_path,
+                        offset=offset,
+                    )
         return obj, (tail if leavemm else tail.tobytes())
 
     @property
@@ -1133,7 +1182,10 @@ class Obj(object):
         return self.expl_tlvlen if self.expled else self.tlvlen
 
     def pps_lenindef(self, decode_path):
-        if self.lenindef:
+        if self.lenindef and not (
+            getattr(self, "defined", None) is not None and
+            self.defined[1].lenindef
+        ):
             yield _pp(
                 asn1_type_name="EOC",
                 obj_name="",
@@ -1145,6 +1197,7 @@ class Obj(object):
                 tlen=1,
                 llen=1,
                 vlen=0,
+                ber_encoded=True,
                 bered=True,
             )
         if self.expl_lenindef:
@@ -1156,6 +1209,7 @@ class Obj(object):
                 tlen=1,
                 llen=1,
                 vlen=0,
+                ber_encoded=True,
                 bered=True,
             )
 
@@ -1188,6 +1242,7 @@ class DecodePathDefBy(object):
 ########################################################################
 
 PP = namedtuple("PP", (
+    "obj",
     "asn1_type_name",
     "obj_name",
     "decode_path",
@@ -1207,11 +1262,13 @@ PP = namedtuple("PP", (
     "expl_vlen",
     "expl_lenindef",
     "lenindef",
+    "ber_encoded",
     "bered",
 ))
 
 
 def _pp(
+        obj=None,
         asn1_type_name="unknown",
         obj_name="unknown",
         decode_path=(),
@@ -1231,9 +1288,11 @@ def _pp(
         expl_vlen=None,
         expl_lenindef=False,
         lenindef=False,
+        ber_encoded=False,
         bered=False,
 ):
     return PP(
+        obj,
         asn1_type_name,
         obj_name,
         decode_path,
@@ -1253,6 +1312,7 @@ def _pp(
         expl_vlen,
         expl_lenindef,
         lenindef,
+        ber_encoded,
         bered,
     )
 
@@ -1261,6 +1321,12 @@ def _colourize(what, colour, with_colours, attrs=("bold",)):
     return colored(what, colour, attrs=attrs) if with_colours else what
 
 
+def colonize_hex(hexed):
+    """Separate hexadecimal string with colons
+    """
+    return ":".join(hexed[i:i + 2] for i in range(0, len(hexed), 2))
+
+
 def pp_console_row(
         pp,
         oids=None,
@@ -1280,7 +1346,9 @@ def pp_console_row(
             ),
             LENINDEF_PP_CHAR if pp.expl_lenindef else " ",
         )
-        cols.append(_colourize(col, "red", with_colours, ()))
+        col = _colourize(col, "red", with_colours, ())
+        col += _colourize("B", "red", with_colours) if pp.bered else " "
+        cols.append(col)
         col = "[%d,%d,%4d]%s" % (
             pp.tlen,
             pp.llen,
@@ -1317,7 +1385,7 @@ def pp_console_row(
         cols.append(_colourize(col, "blue", with_colours))
     if pp.asn1_type_name.replace(" ", "") != pp.obj_name.upper():
         cols.append(_colourize(pp.obj_name, "magenta", with_colours))
-    if pp.bered:
+    if pp.ber_encoded:
         cols.append(_colourize("BER", "red", with_colours))
     cols.append(_colourize(pp.asn1_type_name, "cyan", with_colours))
     if pp.value is not None:
@@ -1329,6 +1397,15 @@ def pp_console_row(
                 value in oids
         ):
             cols.append(_colourize("(%s)" % oids[value], "green", with_colours))
+        if pp.asn1_type_name == Integer.asn1_type_name:
+            hex_repr = hex(int(pp.obj._value))[2:].upper()
+            if len(hex_repr) % 2 != 0:
+                hex_repr = "0" + hex_repr
+            cols.append(_colourize(
+                "(%s)" % colonize_hex(hex_repr),
+                "green",
+                with_colours,
+            ))
     if with_blob:
         if isinstance(pp.blob, binary_type):
             cols.append(hexenc(pp.blob))
@@ -1348,7 +1425,7 @@ def pp_console_row(
 
 
 def pp_console_blob(pp, decode_path_len_decrease=0):
-    cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")]
+    cols = [" " * len("XXXXXYYZZ [X,X,XXXX]Z")]
     decode_path_len = len(pp.decode_path) - decode_path_len_decrease
     if decode_path_len > 0:
         cols.append(" ." * (decode_path_len + 1))
@@ -1356,9 +1433,7 @@ def pp_console_blob(pp, decode_path_len_decrease=0):
         blob = hexenc(pp.blob).upper()
         for i in range(0, len(blob), 32):
             chunk = blob[i:i + 32]
-            yield " ".join(cols + [":".join(
-                chunk[j:j + 2] for j in range(0, len(chunk), 2)
-            )])
+            yield " ".join(cols + [colonize_hex(chunk)])
     elif isinstance(pp.blob, tuple):
         yield " ".join(cols + [", ".join(pp.blob)])
 
@@ -1580,14 +1655,14 @@ class Boolean(Obj):
                 offset=offset,
             )
         first_octet = byte2int(v)
-        bered = False
+        ber_encoded = False
         if first_octet == 0:
             value = False
         elif first_octet == 0xFF:
             value = True
         elif ctx.get("bered", False):
             value = True
-            bered = True
+            ber_encoded = True
         else:
             raise DecodeError(
                 "unacceptable Boolean value",
@@ -1603,7 +1678,7 @@ class Boolean(Obj):
             optional=self.optional,
             _decoded=(offset, 1, 1),
         )
-        obj.bered = bered
+        obj.ber_encoded = ber_encoded
         return obj, v[1:]
 
     def __repr__(self):
@@ -1611,6 +1686,7 @@ class Boolean(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1628,6 +1704,7 @@ class Boolean(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
             bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
@@ -1937,6 +2014,7 @@ class Integer(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -1954,6 +2032,7 @@ class Integer(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
             yield pp
@@ -2268,7 +2347,7 @@ class BitString(Obj):
                 offset=offset,
             )
         if t == self.tag:
-            if tag_only:
+            if tag_only:  # pragma: no cover
                 return
             return self._decode_chunk(lv, offset, decode_path, ctx)
         if t == self.tag_constructed:
@@ -2279,7 +2358,7 @@ class BitString(Obj):
                     decode_path=decode_path,
                     offset=offset,
                 )
-            if tag_only:
+            if tag_only:  # pragma: no cover
                 return
             lenindef = False
             try:
@@ -2333,6 +2412,7 @@ class BitString(Obj):
                         decode_path=sub_decode_path,
                         leavemm=True,
                         ctx=ctx,
+                        _ctx_immutable=False,
                     )
                 except TagMismatch:
                     raise DecodeError(
@@ -2377,7 +2457,7 @@ class BitString(Obj):
                 _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
             )
             obj.lenindef = lenindef
-            obj.bered = True
+            obj.ber_encoded = True
             return obj, (v[EOC_LEN:] if lenindef else v)
         raise TagMismatch(
             klass=self.__class__,
@@ -2397,6 +2477,7 @@ class BitString(Obj):
             if len(self.specs) > 0:
                 blob = tuple(self.named)
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2416,6 +2497,7 @@ class BitString(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
             lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
             bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
@@ -2696,6 +2778,7 @@ class OctetString(Obj):
                         decode_path=sub_decode_path,
                         leavemm=True,
                         ctx=ctx,
+                        _ctx_immutable=False,
                     )
                 except TagMismatch:
                     raise DecodeError(
@@ -2733,7 +2816,7 @@ class OctetString(Obj):
                     offset=offset,
                 )
             obj.lenindef = lenindef
-            obj.bered = True
+            obj.ber_encoded = True
             return obj, (v[EOC_LEN:] if lenindef else v)
         raise TagMismatch(
             klass=self.__class__,
@@ -2746,6 +2829,7 @@ class OctetString(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2765,6 +2849,7 @@ class OctetString(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
             lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
             bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
@@ -2859,7 +2944,7 @@ class Null(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         try:
             l, _, v = len_decode(lv)
@@ -2890,6 +2975,7 @@ class Null(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -2905,6 +2991,7 @@ class Null(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
             yield pp
@@ -3109,7 +3196,7 @@ class ObjectIdentifier(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         try:
             l, llen, v = len_decode(lv)
@@ -3141,6 +3228,8 @@ class ObjectIdentifier(Obj):
             arc = 0
             while True:
                 octet = indexbytes(v, i)
+                if i == 0 and octet == 0x80 and not ctx.get("bered", False):
+                    raise DecodeError("non normalized arc encoding")
                 arc = (arc << 7) | (octet & 0x7F)
                 if octet & 0x80 == 0:
                     arcs.append(arc)
@@ -3179,6 +3268,7 @@ class ObjectIdentifier(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3196,6 +3286,7 @@ class ObjectIdentifier(Obj):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
             yield pp
@@ -3404,6 +3495,7 @@ class CommonString(OctetString):
         if self.ready:
             value = hexenc(bytes(self)) if no_unicode else self.__unicode__()
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3421,6 +3513,8 @@ class CommonString(OctetString):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
             yield pp
@@ -3433,29 +3527,57 @@ class UTF8String(CommonString):
     asn1_type_name = "UTF8String"
 
 
-class NumericString(CommonString):
+class AllowableCharsMixin(object):
+    @property
+    def allowable_chars(self):
+        if PY2:
+            return self._allowable_chars
+        return set(six_unichr(c) for c in self._allowable_chars)
+
+
+class NumericString(AllowableCharsMixin, CommonString):
     """Numeric string
 
-    Its value is properly sanitized: only ASCII digits can be stored.
+    Its value is properly sanitized: only ASCII digits with spaces can
+    be stored.
+
+    >>> NumericString().allowable_chars
+    set(['3', '4', '7', '5', '1', '0', '8', '9', ' ', '6', '2'])
     """
     __slots__ = ()
     tag_default = tag_encode(18)
     encoding = "ascii"
     asn1_type_name = "NumericString"
-    allowable_chars = set(digits.encode("ascii"))
+    _allowable_chars = set(digits.encode("ascii") + b" ")
 
     def _value_sanitize(self, value):
         value = super(NumericString, self)._value_sanitize(value)
-        if not set(value) <= self.allowable_chars:
+        if not set(value) <= self._allowable_chars:
             raise DecodeError("non-numeric value")
         return value
 
 
-class PrintableString(CommonString):
+class PrintableString(AllowableCharsMixin, CommonString):
+    """Printable string
+
+    Its value is properly sanitized: see X.680 41.4 table 10.
+
+    >>> PrintableString().allowable_chars
+    >>> set([' ', "'", ..., 'z'])
+    """
     __slots__ = ()
     tag_default = tag_encode(19)
     encoding = "ascii"
     asn1_type_name = "PrintableString"
+    _allowable_chars = set(
+        (ascii_letters + digits + " '()+,-./:=?").encode("ascii")
+    )
+
+    def _value_sanitize(self, value):
+        value = super(PrintableString, self)._value_sanitize(value)
+        if not set(value) <= self._allowable_chars:
+            raise DecodeError("non-printable value")
+        return value
 
 
 class TeletexString(CommonString):
@@ -3606,6 +3728,7 @@ class UTCTime(CommonString):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3623,6 +3746,8 @@ class UTCTime(CommonString):
             expl_llen=self.expl_llen if self.expled else None,
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for pp in self.pps_lenindef(decode_path):
             yield pp
@@ -3821,6 +3946,13 @@ class Choice(Obj):
     def ready(self):
         return self._value is not None and self._value[1].ready
 
+    @property
+    def bered(self):
+        return self.expl_lenindef or (
+            (self._value is not None) and
+            self._value[1].bered
+        )
+
     def copy(self):
         obj = self.__class__(schema=self.specs)
         obj._expl = self._expl
@@ -3910,6 +4042,7 @@ class Choice(Obj):
                     decode_path=sub_decode_path,
                     ctx=ctx,
                     tag_only=True,
+                    _ctx_immutable=False,
                 )
             except TagMismatch:
                 continue
@@ -3920,7 +4053,7 @@ class Choice(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         value, tail = spec.decode(
             tlv,
@@ -3928,6 +4061,7 @@ class Choice(Obj):
             leavemm=True,
             decode_path=sub_decode_path,
             ctx=ctx,
+            _ctx_immutable=False,
         )
         obj = self.__class__(
             schema=self.specs,
@@ -3947,6 +4081,7 @@ class Choice(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -3960,6 +4095,7 @@ class Choice(Obj):
             llen=self.llen,
             vlen=self.vlen,
             expl_lenindef=self.expl_lenindef,
+            bered=self.bered,
         )
         if self.ready:
             yield self.value.pps(decode_path=decode_path + (self.choice,))
@@ -4048,6 +4184,14 @@ class Any(Obj):
     def ready(self):
         return self._value is not None
 
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef:
+            return True
+        if self.defined is None:
+            return False
+        return self.defined[1].bered
+
     def copy(self):
         obj = self.__class__()
         obj._value = self._value
@@ -4120,6 +4264,7 @@ class Any(Obj):
                     decode_path=decode_path + (str(chunk_i),),
                     leavemm=True,
                     ctx=ctx,
+                    _ctx_immutable=False,
                 )
                 vlen += chunk.tlvlen
                 sub_offset += chunk.tlvlen
@@ -4164,6 +4309,7 @@ class Any(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4182,6 +4328,7 @@ class Any(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
             lenindef=self.lenindef,
+            bered=self.bered,
         )
         defined_by, defined = self.defined or (None, None)
         if defined_by is not None:
@@ -4212,8 +4359,7 @@ def get_def_by_path(defines_by_path, sub_decode_path):
 def abs_decode_path(decode_path, rel_path):
     """Create an absolute decode path from current and relative ones
 
-    :param decode_path: current decode path, starting point.
-                        Tuple of strings
+    :param decode_path: current decode path, starting point. Tuple of strings
     :param rel_path: relative path to ``decode_path``. Tuple of strings.
                      If first tuple's element is "/", then treat it as
                      an absolute path, ignoring ``decode_path`` as
@@ -4314,18 +4460,14 @@ class Sequence(Obj):
 
     All defaulted values are always optional.
 
-    .. _strict_default_existence_ctx:
+    .. _allow_default_values_ctx:
 
-    .. warning::
-
-       When decoded DER contains defaulted value inside, then
-       technically this is not valid DER encoding. But we allow and pass
-       it **by default**. Of course reencoding of that kind of DER will
-       result in different binary representation (validly without
-       defaulted value inside). You can enable strict defaulted values
-       existence validation by setting ``"strict_default_existence":
-       True`` :ref:`context <ctx>` option -- decoding process will raise
-       an exception if defaulted value is met.
+    DER prohibits default value encoding and will raise an error if
+    default value is unexpectedly met during decode.
+    If :ref:`bered <bered_ctx>` context option is set, then no error
+    will be raised, but ``bered`` attribute set. You can disable strict
+    defaulted values existence validation by setting
+    ``"allow_default_values": True`` :ref:`context <ctx>` option.
 
     Two sequences are equal if they have equal specification (schema),
     implicit/explicit tagging and the same values.
@@ -4383,6 +4525,12 @@ class Sequence(Obj):
                     return False
         return True
 
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef or self.ber_encoded:
+            return True
+        return any(value.bered for value in self._value.values())
+
     def copy(self):
         obj = self.__class__(schema=self.specs)
         obj.tag = self.tag
@@ -4482,13 +4630,14 @@ class Sequence(Obj):
                 decode_path=decode_path,
                 offset=offset,
             )
-        if tag_only:
+        if tag_only:  # pragma: no cover
             return
         lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
         except LenIndefForm as err:
-            if not ctx.get("bered", False):
+            if not ctx_bered:
                 raise err.__class__(
                     msg=err.msg,
                     klass=self.__class__,
@@ -4516,6 +4665,8 @@ class Sequence(Obj):
         vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
+        ber_encoded = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
         for name, spec in self.specs.items():
             if spec.optional and (
                     (lenindef and v[:EOC_LEN].tobytes() == EOC) or
@@ -4530,6 +4681,7 @@ class Sequence(Obj):
                     leavemm=True,
                     decode_path=sub_decode_path,
                     ctx=ctx,
+                    _ctx_immutable=False,
                 )
             except TagMismatch:
                 if spec.optional:
@@ -4554,6 +4706,7 @@ class Sequence(Obj):
                             leavemm=True,
                             decode_path=sub_sub_decode_path,
                             ctx=ctx,
+                            _ctx_immutable=False,
                         )
                         if len(defined_tail) > 0:
                             raise DecodeError(
@@ -4573,6 +4726,7 @@ class Sequence(Obj):
                         leavemm=True,
                         decode_path=sub_decode_path + (DecodePathDefBy(defined_by),),
                         ctx=ctx,
+                        _ctx_immutable=False,
                     )
                     if len(defined_tail) > 0:
                         raise DecodeError(
@@ -4588,15 +4742,15 @@ class Sequence(Obj):
             sub_offset += value_len
             v = v_tail
             if spec.default is not None and value == spec.default:
-                if ctx.get("strict_default_existence", False):
+                if ctx_bered or ctx_allow_default_values:
+                    ber_encoded = True
+                else:
                     raise DecodeError(
                         "DEFAULT value met",
                         klass=self.__class__,
                         decode_path=sub_decode_path,
                         offset=sub_offset,
                     )
-                else:
-                    continue
             values[name] = value
 
             spec_defines = getattr(spec, "defines", ())
@@ -4639,6 +4793,7 @@ class Sequence(Obj):
         )
         obj._value = values
         obj.lenindef = lenindef
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
     def __repr__(self):
@@ -4653,6 +4808,7 @@ class Sequence(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -4670,6 +4826,8 @@ class Sequence(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
             lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for name in self.specs:
             value = self._value.get(name)
@@ -4684,6 +4842,14 @@ class Set(Sequence):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+    .. _allow_unordered_set_ctx:
+
+    DER prohibits unordered values encoding and will raise an error
+    during decode. If If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occure. Also you can disable strict values
+    ordering check by setting ``"allow_unordered_set": True``
+    :ref:`context <ctx>` option.
     """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
@@ -4714,10 +4880,11 @@ class Set(Sequence):
         if tag_only:
             return
         lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
         except LenIndefForm as err:
-            if not ctx.get("bered", False):
+            if not ctx_bered:
                 raise err.__class__(
                     msg=err.msg,
                     klass=self.__class__,
@@ -4744,6 +4911,10 @@ class Set(Sequence):
         vlen = 0
         sub_offset = offset + tlen + llen
         values = {}
+        ber_encoded = False
+        ctx_allow_default_values = ctx.get("allow_default_values", False)
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
         specs_items = self.specs.items
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
@@ -4758,6 +4929,7 @@ class Set(Sequence):
                         decode_path=sub_decode_path,
                         ctx=ctx,
                         tag_only=True,
+                        _ctx_immutable=False,
                     )
                 except TagMismatch:
                     continue
@@ -4774,14 +4946,35 @@ class Set(Sequence):
                 leavemm=True,
                 decode_path=sub_decode_path,
                 ctx=ctx,
+                _ctx_immutable=False,
             )
             value_len = value.fulllen
+            if value_prev.tobytes() > v[:value_len].tobytes():
+                if ctx_bered or ctx_allow_unordered_set:
+                    ber_encoded = True
+                else:
+                    raise DecodeError(
+                        "unordered " + self.asn1_type_name,
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
+            if spec.default is None or value != spec.default:
+                pass
+            elif ctx_bered or ctx_allow_default_values:
+                ber_encoded = True
+            else:
+                raise DecodeError(
+                    "DEFAULT value met",
+                    klass=self.__class__,
+                    decode_path=sub_decode_path,
+                    offset=sub_offset,
+                )
+            values[name] = value
+            value_prev = v[:value_len]
             sub_offset += value_len
             vlen += value_len
             v = v_tail
-            if spec.default is None or value != spec.default:  # pragma: no cover
-                # SeqMixing.test_encoded_default_accepted covers that place
-                values[name] = value
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -4790,7 +4983,6 @@ class Set(Sequence):
             optional=self.optional,
             _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
-        obj._value = values
         if lenindef:
             if v[:EOC_LEN].tobytes() != EOC:
                 raise DecodeError(
@@ -4801,6 +4993,7 @@ class Set(Sequence):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
+        obj._value = values
         if not obj.ready:
             raise DecodeError(
                 "not all values are ready",
@@ -4808,6 +5001,7 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
 
@@ -4906,6 +5100,12 @@ class SequenceOf(Obj):
     def ready(self):
         return all(v.ready for v in self._value)
 
+    @property
+    def bered(self):
+        if self.expl_lenindef or self.lenindef or self.ber_encoded:
+            return True
+        return any(v.bered for v in self._value)
+
     def copy(self):
         obj = self.__class__(schema=self.spec)
         obj._bound_min = self._bound_min
@@ -4991,7 +5191,7 @@ class SequenceOf(Obj):
         v = b"".join(self._encoded_values())
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only, ordering_check=False):
         try:
             t, tlen, lv = tag_strip(tlv)
         except DecodeError as err:
@@ -5010,10 +5210,11 @@ class SequenceOf(Obj):
         if tag_only:
             return
         lenindef = False
+        ctx_bered = ctx.get("bered", False)
         try:
             l, llen, v = len_decode(lv)
         except LenIndefForm as err:
-            if not ctx.get("bered", False):
+            if not ctx_bered:
                 raise err.__class__(
                     msg=err.msg,
                     klass=self.__class__,
@@ -5041,22 +5242,39 @@ class SequenceOf(Obj):
         vlen = 0
         sub_offset = offset + tlen + llen
         _value = []
+        ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
+        value_prev = memoryview(v[:0])
+        ber_encoded = False
         spec = self.spec
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
+            sub_decode_path = decode_path + (str(len(_value)),)
             value, v_tail = spec.decode(
                 v,
                 sub_offset,
                 leavemm=True,
-                decode_path=decode_path + (str(len(_value)),),
+                decode_path=sub_decode_path,
                 ctx=ctx,
+                _ctx_immutable=False,
             )
             value_len = value.fulllen
+            if ordering_check:
+                if value_prev.tobytes() > v[:value_len].tobytes():
+                    if ctx_bered or ctx_allow_unordered_set:
+                        ber_encoded = True
+                    else:
+                        raise DecodeError(
+                            "unordered " + self.asn1_type_name,
+                            klass=self.__class__,
+                            decode_path=sub_decode_path,
+                            offset=sub_offset,
+                        )
+                value_prev = v[:value_len]
+            _value.append(value)
             sub_offset += value_len
             vlen += value_len
             v = v_tail
-            _value.append(value)
         try:
             obj = self.__class__(
                 value=_value,
@@ -5085,6 +5303,7 @@ class SequenceOf(Obj):
                 )
             obj.lenindef = True
             tail = v[EOC_LEN:]
+        obj.ber_encoded = ber_encoded
         return obj, tail
 
     def __repr__(self):
@@ -5095,6 +5314,7 @@ class SequenceOf(Obj):
 
     def pps(self, decode_path=()):
         yield _pp(
+            obj=self,
             asn1_type_name=self.asn1_type_name,
             obj_name=self.__class__.__name__,
             decode_path=decode_path,
@@ -5112,6 +5332,8 @@ class SequenceOf(Obj):
             expl_vlen=self.expl_vlen if self.expled else None,
             expl_lenindef=self.expl_lenindef,
             lenindef=self.lenindef,
+            ber_encoded=self.ber_encoded,
+            bered=self.bered,
         )
         for i, value in enumerate(self._value):
             yield value.pps(decode_path=decode_path + (str(i),))
@@ -5134,6 +5356,16 @@ class SetOf(SequenceOf):
         v = b"".join(raws)
         return b"".join((self.tag, len_encode(len(v)), v))
 
+    def _decode(self, tlv, offset, decode_path, ctx, tag_only):
+        return super(SetOf, self)._decode(
+            tlv,
+            offset,
+            decode_path,
+            ctx,
+            tag_only,
+            ordering_check=True,
+        )
+
 
 def obj_by_path(pypath):  # pragma: no cover
     """Import object specified as string Python path
@@ -5234,18 +5466,23 @@ def main():  # pragma: no cover
     )
     parser.add_argument(
         "--nobered",
-        action='store_true',
+        action="store_true",
         help="Disallow BER encoding",
     )
     parser.add_argument(
         "--print-decode-path",
-        action='store_true',
+        action="store_true",
         help="Print decode paths",
     )
     parser.add_argument(
         "--decode-path-only",
         help="Print only specified decode path",
     )
+    parser.add_argument(
+        "--allow-expl-oob",
+        action="store_true",
+        help="Allow explicit tag out-of-bound",
+    )
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
@@ -5262,7 +5499,10 @@ def main():  # pragma: no cover
         pprinter = partial(pprint, big_blobs=True)
     else:
         schema, pprinter = generic_decoder()
-    ctx = {"bered": not args.nobered}
+    ctx = {
+        "bered": not args.nobered,
+        "allow_expl_oob": args.allow_expl_oob,
+    }
     if args.defines_by_path is not None:
         ctx["defines_by_path"] = obj_by_path(args.defines_by_path)
     obj, tail = schema().decode(der, ctx=ctx)