#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""Python ASN.1 DER/BER codec with abstract structures
+"""Python ASN.1 DER/CER/BER codec with abstract structures
-This library allows you to marshal various structures in ASN.1 DER
+This library allows you to marshal various structures in ASN.1 DER/CER
format, unmarshal BER/CER/DER ones.
>>> i = Integer(123)
(and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
could be BERed.
+Also it could be helpful to add quick ASN.1 pprinting command in your
+pdb's configuration file::
+
+ alias pp1 import pyderasn ;; print(pyderasn.pprint(%1, oid_maps=(locals().get("OID_STR_TO_NAME", {}),)))
+
.. _definedby:
DEFINED BY
class SignedAttributes(SetOf):
schema = Attribute()
- bounds = (1, 32)
+ bounds = (1, float("+inf"))
der_forced = True
.. _agg_octet_string:
.. autoclass:: pyderasn.PrintableString
:members: __init__, allow_asterisk, allow_ampersand
+IA5String
+_________
+.. autoclass:: pyderasn.IA5String
+
+VisibleString
+_____________
+.. autoclass:: pyderasn.VisibleString
+
UTCTime
_______
.. autoclass:: pyderasn.UTCTime
from datetime import timedelta
from io import BytesIO
from math import ceil
-from mmap import mmap
-from mmap import PROT_READ
from operator import attrgetter
from string import ascii_letters
from string import digits
def colored(what, *args, **kwargs):
return what
-__version__ = "7.4"
+__version__ = "8.1"
__all__ = (
"agg_octet_string",
:param fd: file object
:returns: memoryview over read-only mmap-ing of the whole file
+
+ .. warning::
+
+ It is known to work under neither Python 2.x nor Windows.
"""
- return memoryview(mmap(fd.fileno(), 0, prot=PROT_READ))
+ import mmap
+ return memoryview(mmap.mmap(fd.fileno(), length=0, prot=mmap.PROT_READ))
+
def pureint(value):
if not set(value) <= DECIMALS:
raise ValueError("non-pure integer")
return int(value)
+
def fractions2float(fractions_raw):
pureint(fractions_raw)
return float("0." + fractions_raw)
if len(path) != len(sub_decode_path):
continue
for p1, p2 in zip(path, sub_decode_path):
- if (not p1 is any) and (p1 != p2):
+ if (p1 is not any) and (p1 != p2):
break
else:
return define
raise DecodeError("unfinished tag")
if indexbytes(data, i) & 0x80 == 0:
break
+ if i == 1 and indexbytes(data, 1) < 0x1F:
+ raise DecodeError("unexpected long form")
+ if i > 1 and indexbytes(data, 1) & 0x7F == 0:
+ raise DecodeError("leading zero byte in tag value")
i += 1
return data[:i], i, data[i:]
yield None
return
_decode_path, obj, tail = result
- if not _decode_path is decode_path:
+ if _decode_path is not decode_path:
yield result
else:
try:
yield None
return
_decode_path, obj, tail = result
- if not _decode_path is decode_path:
+ if _decode_path is not decode_path:
yield result
eoc_expected, tail = tail[:EOC_LEN], tail[EOC_LEN:]
if eoc_expected.tobytes() != EOC:
yield None
return
_decode_path, obj, tail = result
- if not _decode_path is decode_path:
+ if _decode_path is not decode_path:
yield result
if obj.tlvlen < l and not ctx.get("allow_expl_oob", False):
raise DecodeError(
int2byte(0),
octets[offset:offset + 999],
)))
- tail = octets[offset+999:]
+ tail = octets[offset + 999:]
if len(tail) > 0:
tail = int2byte((8 - bit_len % 8) % 8) + tail
write_full(writer, b"".join((
LEN1K,
octets[offset:offset + 1000],
)))
- tail = octets[offset+1000:]
+ tail = octets[offset + 1000:]
if len(tail) > 0:
write_full(writer, b"".join((
OctetString.tag_default,
:header-rows: 1
* - Class
- - Text Encoding
+ - Text Encoding, validation
* - :py:class:`pyderasn.UTF8String`
- utf-8
* - :py:class:`pyderasn.NumericString`
- - ascii
+ - proper alphabet validation
* - :py:class:`pyderasn.PrintableString`
- - ascii
+ - proper alphabet validation
* - :py:class:`pyderasn.TeletexString`
- - ascii
+ - iso-8859-1
* - :py:class:`pyderasn.T61String`
- - ascii
+ - iso-8859-1
* - :py:class:`pyderasn.VideotexString`
- iso-8859-1
* - :py:class:`pyderasn.IA5String`
- - ascii
+ - proper alphabet validation
* - :py:class:`pyderasn.GraphicString`
- iso-8859-1
- * - :py:class:`pyderasn.VisibleString`
- - ascii
- * - :py:class:`pyderasn.ISO646String`
- - ascii
+ * - :py:class:`pyderasn.VisibleString`, :py:class:`pyderasn.ISO646String`
+ - proper alphabet validation
* - :py:class:`pyderasn.GeneralString`
- iso-8859-1
* - :py:class:`pyderasn.UniversalString`
return self._allowable_chars
return frozenset(six_unichr(c) for c in self._allowable_chars)
+ def _value_sanitize(self, value):
+ value = super(AllowableCharsMixin, self)._value_sanitize(value)
+ if not frozenset(value) <= self._allowable_chars:
+ raise DecodeError("non satisfying alphabet value")
+ return value
+
class NumericString(AllowableCharsMixin, CommonString):
"""Numeric string
asn1_type_name = "NumericString"
_allowable_chars = frozenset(digits.encode("ascii") + b" ")
- def _value_sanitize(self, value):
- value = super(NumericString, self)._value_sanitize(value)
- if not frozenset(value) <= self._allowable_chars:
- raise DecodeError("non-numeric value")
- return value
-
PrintableStringState = namedtuple(
"PrintableStringState",
"""
return self._ampersand <= self._allowable_chars
- def _value_sanitize(self, value):
- value = super(PrintableString, self)._value_sanitize(value)
- if not frozenset(value) <= self._allowable_chars:
- raise DecodeError("non-printable value")
- return value
-
def __getstate__(self):
return PrintableStringState(
*super(PrintableString, self).__getstate__(),
class TeletexString(CommonString):
__slots__ = ()
tag_default = tag_encode(20)
- encoding = "ascii"
+ encoding = "iso-8859-1"
asn1_type_name = "TeletexString"
asn1_type_name = "VideotexString"
-class IA5String(CommonString):
+class IA5String(AllowableCharsMixin, CommonString):
+ """IA5 string
+
+ Its value is properly sanitized: it is a mix of
+
+ * http://www.itscj.ipsj.or.jp/iso-ir/006.pdf (G)
+ * http://www.itscj.ipsj.or.jp/iso-ir/001.pdf (C0)
+ * DEL character (0x7F)
+
+ It is just 7-bit ASCII.
+
+ >>> IA5String().allowable_chars
+ frozenset(["NUL", ... "DEL"])
+ """
__slots__ = ()
tag_default = tag_encode(22)
encoding = "ascii"
asn1_type_name = "IA5"
+ _allowable_chars = frozenset(b"".join(
+ six_unichr(c).encode("ascii") for c in six_xrange(128)
+ ))
LEN_YYMMDDHHMMSSZ = len("YYMMDDHHMMSSZ")
LEN_LEN_YYYYMMDDHHMMSSZ = len_encode(LEN_YYYYMMDDHHMMSSZ)
-class VisibleString(CommonString):
+class VisibleString(AllowableCharsMixin, CommonString):
+ """Visible string
+
+ Its value is properly sanitized. ASCII subset from space to tilde is
+ allowed: http://www.itscj.ipsj.or.jp/iso-ir/006.pdf
+
+ >>> VisibleString().allowable_chars
+ frozenset([" ", ... "~"])
+ """
__slots__ = ()
tag_default = tag_encode(26)
encoding = "ascii"
asn1_type_name = "VisibleString"
+ _allowable_chars = frozenset(b"".join(
+ six_unichr(c).encode("ascii") for c in six_xrange(ord(" "), ord("~") + 1)
+ ))
+
+
+class ISO646String(VisibleString):
+ __slots__ = ()
+ asn1_type_name = "ISO646String"
UTCTimeState = namedtuple(
.. warning::
- Pay attention that UTCTime can not hold full year, so all years
- having < 50 years are treated as 20xx, 19xx otherwise, according
- to X.509 recommendation.
+ Only **naive** ``datetime`` objects are supported.
+ Library assumes that all work is done in UTC.
+
+ .. warning::
+
+ Pay attention that ``UTCTime`` can not hold full year, so all years
+ having < 50 years are treated as 20xx, 19xx otherwise, according to
+ X.509 recommendation. Use ``GeneralizedTime`` instead for
+ removing ambiguity.
.. warning::
- No strict validation of UTC offsets are made, but very crude:
+ No strict validation of UTC offsets are made (only applicable to
+ **BER**), but very crude:
* minutes are not exceeding 60
* offset value is not exceeding 14 hours
if isinstance(value, self.__class__):
return value._value, None
if value.__class__ == datetime:
+ if value.tzinfo is not None:
+ raise ValueError("only naive datetime supported")
return self._dt_sanitize(value), None
raise InvalidValueType((self.__class__, datetime))
.. warning::
- Only microsecond fractions are supported in DER encoding.
- :py:exc:`pyderasn.DecodeError` will be raised during decoding of
- higher precision values.
+ Only **naive** datetime objects are supported.
+ Library assumes that all work is done in UTC.
.. warning::
- BER encoded data can loss information (accuracy) during decoding
- because of float transformations.
+ Only **microsecond** fractions are supported in DER encoding.
+ :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+ higher precision values.
.. warning::
- Local times (without explicit timezone specification) are treated
- as UTC one, no transformations are made.
+ **BER** encoded data can loss information (accuracy) during
+ decoding because of float transformations.
.. warning::
- Zero year is unsupported.
+ **Zero** year is unsupported.
"""
__slots__ = ()
tag_default = tag_encode(24)
asn1_type_name = "GraphicString"
-class ISO646String(VisibleString):
- __slots__ = ()
- asn1_type_name = "ISO646String"
-
-
class GeneralString(CommonString):
__slots__ = ()
tag_default = tag_encode(27)
def _value_sanitize(self, value):
if value.__class__ == binary_type:
if len(value) == 0:
- raise ValueError("Any value can not be empty")
+ raise ValueError("%s value can not be empty" % self.__class__.__name__)
return value
if isinstance(value, self.__class__):
return value._value
92 2b 39 20 65 91 e6 8e 95 93 1a 58 df 02 78 ea |.+9 e......X..x.|
^^^^^^^^^^^^^^^^
"""
- return "".join((chr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
+ return "".join((six_unichr(b) if 0x20 <= b <= 0x7E else ".") for b in ba)
def hexdump(raw):
[("header", "[TLV]len: "), "%d/%d/%d" % (
pp.tlen, pp.llen, pp.vlen,
)],
+ [("header", "TLVlen: "), "%d" % sum((
+ pp.tlen, pp.llen, pp.vlen,
+ ))],
[("header", "Slice: "), "[%d:%d]" % (
pp.offset, pp.offset + pp.tlen + pp.llen + pp.vlen,
)],
lines.append([" [TLV]len: %d/%d/%d" % (
pp.expl_tlen, pp.expl_llen, pp.expl_vlen,
)])
+ lines.append([" TLVlen: %d" % sum((
+ pp.expl_tlen, pp.expl_llen, pp.expl_vlen,
+ ))])
lines.append([" Slice: [%d:%d]" % (
pp.expl_offset,
pp.expl_offset + pp.expl_tlen + pp.expl_llen + pp.expl_vlen,
help="Path to BER/CER/DER file you want to decode",
)
args = parser.parse_args()
- if PY2:
+ try:
+ raw = file_mmaped(args.RAWFile)[args.skip:]
+ except:
args.RAWFile.seek(args.skip)
raw = memoryview(args.RAWFile.read())
args.RAWFile.close()
- else:
- raw = file_mmaped(args.RAWFile)[args.skip:]
oid_maps = (
[obj_by_path(_path) for _path in (args.oids or "").split(",")]
if args.oids else ()