From 5da3ff1e270f6ca27d15d19a7249c0791f46b2a2 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Tue, 7 Aug 2018 23:01:01 +0300 Subject: [PATCH] Decode path printing --- VERSION | 2 +- doc/examples.rst | 42 +++++++++++++++++++++-- doc/install.rst | 10 +++--- doc/news.rst | 10 ++++++ pyderasn.py | 88 +++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 131 insertions(+), 21 deletions(-) diff --git a/VERSION b/VERSION index e4fba21..24ee5b1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.12 +3.13 diff --git a/doc/examples.rst b/doc/examples.rst index f5b58d9..25e2cf4 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -321,6 +321,9 @@ good enough for the certificate above:: . . . 9E:12:F7:AA:83:10:BD:D1:7C:98:FA:C7:AE:D4:0E:2C [...] +Human readable OIDs +___________________ + If you have got dictionaries with ObjectIdentifiers, like example one from ``tests/test_crts.py``:: @@ -350,6 +353,41 @@ then you can pass it to pretty printer to see human readable OIDs:: 79 [1,1, 9] . . . . . . . . . . >: PrintableString PrintableString Barcelona [...] +Decode paths +____________ + +Each decoded element has so-called decode path: sequence of structure +names it is passing during the decode process. Each element has its own +unique path inside the whole ASN.1 tree. You can print it out with +``--print-decode-path`` option:: + + % python -m pyderasn --schema path.to:Certificate --print-decode-path path/to/file + 0 [1,3,1604] Certificate SEQUENCE [] + 4 [1,3,1453] . tbsCertificate: TBSCertificate SEQUENCE [tbsCertificate] + 10-2 [1,1, 1] . . version: [0] EXPLICIT Version INTEGER v3 OPTIONAL [tbsCertificate:version] + 13 [1,1, 3] . . serialNumber: CertificateSerialNumber INTEGER 61595 [tbsCertificate:serialNumber] + 18 [1,1, 13] . . signature: AlgorithmIdentifier SEQUENCE [tbsCertificate:signature] + 20 [1,1, 9] . . . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5 [tbsCertificate:signature:algorithm] + 31 [0,0, 2] . . . parameters: [UNIV 5] ANY OPTIONAL [tbsCertificate:signature:parameters] + . . . . 05:00 + 33 [0,0, 278] . . issuer: Name CHOICE rdnSequence [tbsCertificate:issuer] + 33 [1,3, 274] . . . rdnSequence: RDNSequence SEQUENCE OF [tbsCertificate:issuer:rdnSequence] + 37 [1,1, 11] . . . . 0: RelativeDistinguishedName SET OF [tbsCertificate:issuer:rdnSequence:0] + 39 [1,1, 9] . . . . . 0: AttributeTypeAndValue SEQUENCE [tbsCertificate:issuer:rdnSequence:0:0] + 41 [1,1, 3] . . . . . . type: AttributeType OBJECT IDENTIFIER 2.5.4.6 [tbsCertificate:issuer:rdnSequence:0:0:type] + 46 [0,0, 4] . . . . . . value: [UNIV 19] AttributeValue ANY [tbsCertificate:issuer:rdnSequence:0:0:value] + . . . . . . . 13:02:45:53 + 46 [1,1, 2] . . . . . . . DEFINED BY 2.5.4.6: CountryName PrintableString ES [tbsCertificate:issuer:rdnSequence:0:0:value:DEFINED BY 2.5.4.6] + [...] + +Now you can print only the specified tree, for example signature algorithm:: + + % python -m pyderasn --schema path.to:Certificate --decode-path-only tbsCertificate:signature path/to/file + 18 [1,1, 13] AlgorithmIdentifier SEQUENCE + 20 [1,1, 9] . algorithm: OBJECT IDENTIFIER 1.2.840.113549.1.1.5 + 31 [0,0, 2] . parameters: [UNIV 5] ANY OPTIONAL + . . 05:00 + Descriptive errors ------------------ @@ -358,13 +396,13 @@ If you have bad DER/BER, then errors will show you where error occurred:: % python -m pyderasn --schema tests.test_crts:Certificate path/to/bad/file Traceback (most recent call last): [...] - pyderasn.DecodeError: UTCTime (tbsCertificate.validity.notAfter.utcTime) (at 328) invalid UTCTime format + pyderasn.DecodeError: UTCTime (tbsCertificate:validity:notAfter:utcTime) (at 328) invalid UTCTime format :: % python -m pyderasn path/to/bad/file [...] - pyderasn.DecodeError: UTCTime (0.SequenceOf.4.SequenceOf.1.UTCTime) (at 328) invalid UTCTime format + pyderasn.DecodeError: UTCTime (0:SequenceOf:4:SequenceOf:1:UTCTime) (at 328) invalid UTCTime format You can see, so called, decode path inside the structures: ``tbsCertificate`` -> ``validity`` -> ``notAfter`` -> ``utcTime`` and diff --git a/doc/install.rst b/doc/install.rst index 2e62b09..7ade15a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -4,11 +4,11 @@ Install Preferable way is to :ref:`download ` tarball with the signature from `official website `__:: - % wget http://pyderasn.cypherpunks.ru/pyderasn-3.7.tar.xz - % wget http://pyderasn.cypherpunks.ru/pyderasn-3.7.tar.xz.sig - % gpg --verify pyderasn-3.7.tar.xz.sig pyderasn-3.7.tar.xz - % xz -d < pyderasn-3.7.tar.xz | tar xf - - % cd pyderasn-3.7 + % wget http://pyderasn.cypherpunks.ru/pyderasn-3.13.tar.xz + % wget http://pyderasn.cypherpunks.ru/pyderasn-3.13.tar.xz.sig + % gpg --verify pyderasn-3.13.tar.xz.sig pyderasn-3.13.tar.xz + % xz -d < pyderasn-3.13.tar.xz | tar xf - + % cd pyderasn-3.13 % python setup.py install # or copy pyderasn.py (+six.py, possibly termcolor.py) to your PYTHONPATH diff --git a/doc/news.rst b/doc/news.rst index 7e5d52b..6e2427c 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -1,6 +1,16 @@ News ==== +.. _release3.13: + +3.13 +---- +* DecodeError's decode paths are separated with ``:``, instead of ``.``, + because of colliding with dots in OIDs +* Ability to print element decode paths with ``--print-decode-path`` + command line option (and corresponding keyword argument) +* Ability to print tree's branch specified with ``--decode-path-only`` + .. _release3.12: 3.12 diff --git a/pyderasn.py b/pyderasn.py index 3f0e8ca..3b7d6cc 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -274,7 +274,7 @@ You can specify multiple fields, that will be autodecoded -- that is why ``defines`` kwarg is a sequence. You can specify defined field relatively or absolutely to current decode path. For example ``defines`` for AlgorithmIdentifier of X.509's -``tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm``:: +``tbsCertificate:subjectPublicKeyInfo:algorithm:algorithm``:: ( (("parameters",), { @@ -302,8 +302,7 @@ Following types can be automatically decoded (DEFINED BY): When any of those fields is automatically decoded, then ``.defined`` attribute contains ``(OID, value)`` tuple. ``OID`` tells by which OID it was defined, ``value`` contains corresponding decoded value. For example -above, ``content_info["content"].defined == (id_signedData, -signed_data)``. +above, ``content_info["content"].defined == (id_signedData, signed_data)``. .. _defines_by_path_ctx: @@ -641,7 +640,7 @@ class DecodeError(Exception): c for c in ( "" if self.klass is None else self.klass.__name__, ( - ("(%s)" % ".".join(str(dp) for dp in self.decode_path)) + ("(%s)" % ":".join(str(dp) for dp in self.decode_path)) if len(self.decode_path) > 0 else "" ), ("(at %d)" % self.offset) if self.offset > 0 else "", @@ -1268,6 +1267,8 @@ def pp_console_row( with_offsets=False, with_blob=True, with_colours=False, + with_decode_path=False, + decode_path_len_decrease=0, ): cols = [] if with_offsets: @@ -1288,8 +1289,9 @@ def pp_console_row( ) col = _colourize(col, "green", with_colours, ()) cols.append(col) - if len(pp.decode_path) > 0: - cols.append(" ." * (len(pp.decode_path))) + decode_path_len = len(pp.decode_path) - decode_path_len_decrease + if decode_path_len > 0: + cols.append(" ." * decode_path_len) ent = pp.decode_path[-1] if isinstance(ent, DecodePathDefBy): cols.append(_colourize("DEFINED BY", "red", with_colours, ("reverse",))) @@ -1336,13 +1338,20 @@ def pp_console_row( cols.append(_colourize("OPTIONAL", "red", with_colours)) if pp.default: cols.append(_colourize("DEFAULT", "red", with_colours)) + if with_decode_path: + cols.append(_colourize( + "[%s]" % ":".join(str(p) for p in pp.decode_path), + "grey", + with_colours, + )) return " ".join(cols) -def pp_console_blob(pp): +def pp_console_blob(pp, decode_path_len_decrease=0): cols = [" " * len("XXXXXYYZ [X,X,XXXX]Z")] - if len(pp.decode_path) > 0: - cols.append(" ." * (len(pp.decode_path) + 1)) + decode_path_len = len(pp.decode_path) - decode_path_len_decrease + if decode_path_len > 0: + cols.append(" ." * (decode_path_len + 1)) if isinstance(pp.blob, binary_type): blob = hexenc(pp.blob).upper() for i in range(0, len(blob), 32): @@ -1354,7 +1363,14 @@ def pp_console_blob(pp): yield " ".join(cols + [", ".join(pp.blob)]) -def pprint(obj, oids=None, big_blobs=False, with_colours=False): +def pprint( + obj, + oids=None, + big_blobs=False, + with_colours=False, + with_decode_path=False, + decode_path_only=(), +): """Pretty print object :param Obj obj: object you want to pretty print @@ -1365,10 +1381,19 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False): lines :param with_colours: colourize output, if ``termcolor`` library is available + :param with_decode_path: print decode path + :param decode_path_only: print only that specified decode path """ def _pprint_pps(pps): for pp in pps: if hasattr(pp, "_fields"): + if ( + decode_path_only != () and + tuple( + str(p) for p in pp.decode_path[:len(decode_path_only)] + ) != decode_path_only + ): + continue if big_blobs: yield pp_console_row( pp, @@ -1376,8 +1401,13 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False): with_offsets=True, with_blob=False, with_colours=with_colours, + with_decode_path=with_decode_path, + decode_path_len_decrease=len(decode_path_only), ) - for row in pp_console_blob(pp): + for row in pp_console_blob( + pp, + decode_path_len_decrease=len(decode_path_only), + ): yield row else: yield pp_console_row( @@ -1386,6 +1416,8 @@ def pprint(obj, oids=None, big_blobs=False, with_colours=False): with_offsets=True, with_blob=True, with_colours=with_colours, + with_decode_path=with_decode_path, + decode_path_len_decrease=len(decode_path_only), ) else: for row in _pprint_pps(pp): @@ -5138,10 +5170,21 @@ def generic_decoder(): # pragma: no cover __slots__ = () schema = choice - def pprint_any(obj, oids=None, with_colours=False): + def pprint_any( + obj, + oids=None, + with_colours=False, + with_decode_path=False, + decode_path_only=(), + ): def _pprint_pps(pps): for pp in pps: if hasattr(pp, "_fields"): + if ( + decode_path_only != () and + pp.decode_path[:len(decode_path_only)] != decode_path_only + ): + continue if pp.asn1_type_name == Choice.asn1_type_name: continue pp_kwargs = pp._asdict() @@ -5153,8 +5196,13 @@ def generic_decoder(): # pragma: no cover with_offsets=True, with_blob=False, with_colours=with_colours, + with_decode_path=with_decode_path, + decode_path_len_decrease=len(decode_path_only), ) - for row in pp_console_blob(pp): + for row in pp_console_blob( + pp, + decode_path_len_decrease=len(decode_path_only), + ): yield row else: for row in _pprint_pps(pp): @@ -5189,6 +5237,15 @@ def main(): # pragma: no cover action='store_true', help="Disallow BER encoding", ) + parser.add_argument( + "--print-decode-path", + action='store_true', + help="Print decode paths", + ) + parser.add_argument( + "--decode-path-only", + help="Print only specified decode path", + ) parser.add_argument( "DERFile", type=argparse.FileType("rb"), @@ -5213,6 +5270,11 @@ def main(): # pragma: no cover obj, oids=oids, with_colours=True if environ.get("NO_COLOR") is None else False, + with_decode_path=args.print_decode_path, + decode_path_only=( + () if args.decode_path_only is None else + tuple(args.decode_path_only.split(":")) + ), )) if tail != b"": print("\nTrailing data: %s" % hexenc(tail)) -- 2.44.0