pax_global_header00006660000000000000000000000064146670452700014525gustar00rootroot0000000000000052 comment=ad303c2b302e09f2436884df647f911b5218aaf5 python-asn1-2.7.1/000077500000000000000000000000001466704527000137155ustar00rootroot00000000000000python-asn1-2.7.1/.bumpversion.cfg000066400000000000000000000002361466704527000170260ustar00rootroot00000000000000[bumpversion] current_version = 2.7.1 commit = True tag = True [bumpversion:file:setup.py] [bumpversion:file:docs/conf.py] [bumpversion:file:src/asn1.py] python-asn1-2.7.1/.coveragerc000066400000000000000000000003741466704527000160420ustar00rootroot00000000000000[paths] source = src/asn1 */site-packages/asn1 [run] branch = true source = asn1 tests parallel = true data_file = .coverage_data/coverage [report] show_missing = true precision = 2 omit = *migrations* [xml] output = dist/coverage.xml python-asn1-2.7.1/.editorconfig000066400000000000000000000003271466704527000163740ustar00rootroot00000000000000# see http://editorconfig.org root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 [*.{bat,cmd,ps1}] end_of_line = crlf python-asn1-2.7.1/.github/000077500000000000000000000000001466704527000152555ustar00rootroot00000000000000python-asn1-2.7.1/.github/workflows/000077500000000000000000000000001466704527000173125ustar00rootroot00000000000000python-asn1-2.7.1/.github/workflows/tests.yml000066400000000000000000000013321466704527000211760ustar00rootroot00000000000000name: GitHub Tests on: push: branches: [master] pull_request: workflow_dispatch: jobs: build: runs-on: ubuntu-20.04 strategy: matrix: python-version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: LizardByte/setup-python-action@master with: python-version: ${{ matrix.python-version }} env: PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org" - name: Install dependencies run: | python -m pip install --upgrade pip wheel pip install tox tox-gh-actions - name: Test with tox run: tox python-asn1-2.7.1/.gitignore000066400000000000000000000011261466704527000157050ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs .eggs parts bin var sdist wheelhouse develop-eggs .installed.cfg lib lib64 venv*/ pyvenv*/ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox .coverage.* nosetests.xml coverage.xml htmlcov # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea *.iml *.komodoproject # Complexity output/*.html output/*/index.html # Sphinx docs/_build .DS_Store *~ .*.sw[po] .build .ve .env .cache .pytest .bootstrap .appveyor.token *.bak .python-version *.private .coverage_data/ python-asn1-2.7.1/.readthedocs.yaml000066400000000000000000000001351466704527000171430ustar00rootroot00000000000000version: 2 python: version: "3.8" install: - requirements: requirements-dev.txt python-asn1-2.7.1/.scrutinizer.yml000066400000000000000000000001131466704527000170720ustar00rootroot00000000000000checks: python: code_rating: true duplicate_code: true python-asn1-2.7.1/AUTHORS.rst000066400000000000000000000003201466704527000155670ustar00rootroot00000000000000 Authors ======= The following people have contributed to Python-ASN1. Collectively they own the copyright of this software. * Geert Jansen * Sebastien Andrivet python-asn1-2.7.1/CHANGELOG.rst000066400000000000000000000044101466704527000157350ustar00rootroot00000000000000Changelog ========= 2.7.1 (2024-08-07 ----------------- * Fix OID encoding/decoding for the first octet according to ITU-T X.690 (thanks to Ian Neal) 2.7.0 (2023-01-17) ------------------ * Add context manager support (thanks to Mastermind-U) 2.6.0 (2022-07-15) ------------------ * Add support for GeneralizedTime (thanks to vollkorntomate) 2.5.0 (2022-03-03) ------------------ * Fixes to BitString decoding and encoding of IA5String and UTCTime (thanks to 0xbf00) 2.4.2 (2021-10-29) ------------------ * Fix a minor mistake in the dump.py example * Add Python 3.9 and 3.10 2.4.1 (2020-07-16) ------------------ * Fix #89 - Replace explicit references to enum34 by enum-compat 2.4.0 (2020-06-23) ------------------ * Fix #21 - Invalid decoding in non-Universal classes * Fix #57 - Invalid encoding of non-Universal classes 2.3.1 (2020-04-06) ------------------ * No change in code, only in packaging and in texts (README, ...) 2.3.0 (2020-04-05) ------------------ * Tests: Replace nose by pytest * Add Python 3.8, remove Python 3.4 support * PR#26 (from rumbah): Add test for default (implicit) encoding types * PR#25 (from thomwiggers): Attempt to support BIT STRING * Fix wrong example code, see #27 * (from jcrowgey) Makes the package usable with pip install * Remove support of Python 3.3 (some dependencies do not support 3.3) * PR#15 (from ThePlasmaRailgun) Fix parsing of object identifier * PR#10 (from robinleander): Allow user to determine decoding tagtype 2.2.0 (2017-10-30) ------------------ * Use "true" enums instead of classes. Use enun34 backport for old Python versions. 2.1.1 (2017-10-30) ------------------ * Fix a bug (#9): two's complement corner case with values such as -32769. Add new test cases to test them. 2.1.0 (2016-12-18) ------------------ * Add more documentation * Use (simulated) enumerations * Add Python 2.6 in automated checks and tests * Add type hints (for static checking) and fix some code 2.0.0 (2016-12-16) ------------------ * First public release by Sebastien Andrivet * Support both python 2 and 3 (with Python-Future) * All strings are now in unicode * Add more ASN.1 tags (like PrintableString) * Fix errors in the example (dump.py) * Code reorganization 0.9 (2011-05-18) ---------------- * Initial public release by Geert Jansen python-asn1-2.7.1/CONTRIBUTING.rst000066400000000000000000000052321466704527000163600ustar00rootroot00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. Bug reports =========== When `reporting a bug `_ please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Documentation improvements ========================== Python-ASN1 could always use more documentation, whether as part of the official Python-ASN1 docs, in docstrings, or even on the web in blog posts, articles, and such. Feature requests and feedback ============================= The best way to send feedback is to file an issue at https://github.com/andrivet/python-asn1/pulls. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that code contributions are welcome :) Development =========== To set up Python-ASN1 for local development: 1. Fork `python-asn1 `_ (look for the "Fork" button). 2. Clone your fork locally:: git clone git@github.com:your_name_here/python-asn1.git 3. Create a branch for local development:: git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 4. When you're done making changes, run all the checks, doc builder and spell checker with `tox `_ one command:: tox 5. Commit your changes and push your branch to GitHub:: git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature 6. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- If you need some code review or feedback while you're developing the code just make the pull request. For merging, you should: 1. Include passing tests (run ``tox``) [1]_. 2. Update documentation when there's new API, functionality etc. 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will `run the tests `_ for each change you add in the pull request. It will be slower though ... Tips ---- To run a subset of tests:: tox -e envname -- py.test -k test_myfeature To run all the test environments in *parallel* (you need to ``pip install detox``):: detox python-asn1-2.7.1/LICENSE000066400000000000000000000020611466704527000147210ustar00rootroot00000000000000Copyright (c) 2007-2021 the Python-ASN1 authors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-asn1-2.7.1/MANIFEST.in000066400000000000000000000005661466704527000154620ustar00rootroot00000000000000graft docs graft examples graft src graft ci graft tests include .coveragerc include .cookiecutterrc include .editorconfig include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include README.rst include *.txt include *.cfg include *.ini include *.yml include *.yaml global-exclude *.py[cod] __pycache__ *.so *.dylib exclude .python-version python-asn1-2.7.1/README.rst000066400000000000000000000063351466704527000154130ustar00rootroot00000000000000.. start-badges |build| |version| |wheel| |supported-versions| |supported-implementations| .. |build| image:: https://github.com/andrivet/python-asn1/actions/workflows/tests.yml/badge.svg :target: https://github.com/andrivet/python-asn1 :alt: GitHub Actions .. |docs| image:: https://readthedocs.org/projects/python-asn1/badge/?style=flat :target: https://readthedocs.org/projects/python-asn1 :alt: Documentation Status .. |version| image:: https://img.shields.io/pypi/v/asn1.svg?style=flat :alt: PyPI Package latest release :target: https://pypi.org/project/asn1/ .. |wheel| image:: https://img.shields.io/pypi/wheel/asn1.svg?style=flat :alt: PyPI Wheel :target: https://pypi.org/project/asn1/ .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/asn1.svg?style=flat :alt: Supported versions :target: https://pypi.org/project/asn1/ .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/asn1.svg?style=flat :alt: Supported implementations :target: https://pypi.org/project/asn1/ .. end-badges ======== Overview ======== Python-ASN1 is a simple ASN.1 encoder and decoder for Python 2.7 and 3.5+. Features ======== - Support BER (parser) and DER (parser and generator) encoding (except indefinite lengths) - 100% python, compatible with version 2.7, 3.5 and higher - Can be integrated by just including a file into your project Dependencies ============== Python-ASN1 relies on `Python-Future `_ for Python 2 and 3 compatibility. To install Python-Future: .. code-block:: sh pip install future How to install Python-asn1 ========================== Install from PyPi with the following: .. code-block:: sh pip install asn1 or download the repository from `GitHub `_ and install with the following: .. code-block:: sh python setup.py install You can also simply include ``asn1.py`` into your project. How to use Python-asn1 ====================== .. note:: You can find more detailed documentation on the `Usage`_ page. .. _Usage: usage.rst Encoding -------- If you want to encode data and retrieve its DER-encoded representation, use code such as: .. code-block:: python import asn1 encoder = asn1.Encoder() encoder.start() encoder.write('1.2.3', asn1.Numbers.ObjectIdentifier) encoded_bytes = encoder.output() Decoding -------- If you want to decode ASN.1 from DER or BER encoded bytes, use code such as: .. code-block:: python import asn1 decoder = asn1.Decoder() decoder.start(encoded_bytes) tag, value = decoder.read() Documentation ============= The complete documentation is available on Read The Docs: `python-asn1.readthedocs.io `_ License ======= Python-ASN1 is free software that is made available under the MIT license. Consult the file LICENSE that is distributed together with this library for the exact licensing terms. Copyright ========= The following people have contributed to Python-ASN1. Collectively they own the copyright of this software. * Geert Jansen (geert@boskant.nl): `original implementation `_. * Sebastien Andrivet (sebastien@andrivet.com) python-asn1-2.7.1/docs/000077500000000000000000000000001466704527000146455ustar00rootroot00000000000000python-asn1-2.7.1/docs/authors.rst000066400000000000000000000000341466704527000170610ustar00rootroot00000000000000.. include:: ../AUTHORS.rst python-asn1-2.7.1/docs/changelog.rst000066400000000000000000000000361466704527000173250ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst python-asn1-2.7.1/docs/conf.py000066400000000000000000000026651466704527000161550ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import os import sys sys.path.insert(0, os.path.abspath('../src')) extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] if os.getenv('SPELLCHECK'): extensions += 'sphinxcontrib.spelling', spelling_show_suggestions = True spelling_lang = 'en_US' source_suffix = '.rst' master_doc = 'index' project = 'Python-ASN1' year = '2007-2022' author = 'Sebastien Andrivet' copyright = '{0}, {1}'.format(year, author) version = release = '2.7.1' pygments_style = 'trac' templates_path = ['.'] extlinks = { 'issue': ('https://github.com/andrivet/python-asn1/issues/%s', '#'), 'pr': ('https://github.com/andrivet/python-asn1/pull/%s', 'PR #'), } import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_options = { } html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = False html_sidebars = { '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } html_short_title = '%s-%s' % (project, version) napoleon_use_ivar = True napoleon_use_rtype = False napoleon_use_param = False suppress_warnings = ['image.nonlocal_uri'] default_role = 'any' linkcheck_anchors = False python-asn1-2.7.1/docs/contributing.rst000066400000000000000000000000411466704527000201010ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst python-asn1-2.7.1/docs/credits.rst000066400000000000000000000005001466704527000170270ustar00rootroot00000000000000Credits ======= The following projects have provided inspiration for Python-ASN1: * `pyasn1`_ This is another ASN1 encoder/decoder for Python. * `Samba`_ In particular **libads** for the stack based approach for building constructed types. .. _pyasn1: http://pyasn1.sourceforge.net .. _Samba: https://www.samba.org python-asn1-2.7.1/docs/examples.rst000066400000000000000000000112721466704527000172200ustar00rootroot00000000000000Examples ======== Dump ---- This command line utility reads a X509.v3 certificate (in DER or PEM encoding), decodes it and outputs a textual representation. It more or less mimics `openssl asn1parse `_: .. code-block:: shell $ python examples/dump.py examples/test.crt [U] SEQUENCE [U] SEQUENCE [C] 0x0 [U] INTEGER: 2 [U] INTEGER: 5424 [U] SEQUENCE [U] OBJECT: 1.2.840.113549.1.1.5 [U] NULL: None [U] SEQUENCE [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.6 [U] PRINTABLESTRING: -- [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.8 [U] PRINTABLESTRING: SomeState [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.7 [U] PRINTABLESTRING: SomeCity [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.10 [U] PRINTABLESTRING: SomeOrganization [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.11 [U] PRINTABLESTRING: SomeOrganizationalUnit [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.3 [U] PRINTABLESTRING: localhost.localdomain [U] SET [U] SEQUENCE [U] OBJECT: 1.2.840.113549.1.9.1 [U] IA5STRING: root@localhost.localdomain [U] SEQUENCE [U] UTCTIME: 080205092331Z [U] UTCTIME: 090204092331Z [U] SEQUENCE [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.6 [U] PRINTABLESTRING: -- [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.8 [U] PRINTABLESTRING: SomeState [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.7 [U] PRINTABLESTRING: SomeCity [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.10 [U] PRINTABLESTRING: SomeOrganization [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.11 [U] PRINTABLESTRING: SomeOrganizationalUnit [U] SET [U] SEQUENCE [U] OBJECT: 2.5.4.3 [U] PRINTABLESTRING: localhost.localdomain [U] SET [U] SEQUENCE [U] OBJECT: 1.2.840.113549.1.9.1 [U] IA5STRING: root@localhost.localdomain [U] SEQUENCE [U] SEQUENCE [U] OBJECT: 1.2.840.113549.1.1.1 [U] NULL: None [U] BIT STRING: 0xb'0030818902818100D51...10610203010001' [C] BIT STRING [U] SEQUENCE [U] SEQUENCE [U] OBJECT: 2.5.29.14 [U] OCTET STRING: 0xb'04140A4BFA8754177E30B4217156510FD291C3300236' [U] SEQUENCE [U] OBJECT: 2.5.29.35 [U] OCTET STRING: 0xb'3081DE80140A4...D61696E82021530' [U] SEQUENCE [U] OBJECT: 2.5.29.19 [U] OCTET STRING: 0xb'30030101FF' [U] SEQUENCE [U] OBJECT: 1.2.840.113549.1.1.5 [U] NULL: None [U] BIT STRING: 0xb'004E124658A357C59AABFA3...8A6245C7FC58B45A' The main function is ``pretty_print``: .. code-block:: python :linenos: def pretty_print(input_stream, output_stream, indent=0): """Pretty print ASN.1 data.""" while not input_stream.eof(): tag = input_stream.peek() if tag.typ == asn1.Types.Primitive: tag, value = input_stream.read() output_stream.write(' ' * indent) output_stream.write('[{}] {}: {}\n'.format(class_id_to_string(tag.cls), tag_id_to_string(tag.nr), value_to_string(tag.nr, value))) elif tag.typ == asn1.Types.Constructed: output_stream.write(' ' * indent) output_stream.write('[{}] {}\n'.format(class_id_to_string(tag.cls), tag_id_to_string(tag.nr))) input_stream.enter() pretty_print(input_stream, output_stream, indent + 2) input_stream.leave() This code: * **line 3**: Loops until its reaches the end of the input stream (with `Decoder.eof()`). * **line 4**: Looks (with `Decoder.peek()`) at the current tag. * **line 5**: If the tag is primitive (``TypePrimitive``) ... * **line 6**: ... the code reads (with `Decoder.read()`) the current tag. * **line 8**: Then it displays its number and value (after some mapping to be more user-fiendly). * **line 10**: If the tag is constructed (``TypeConstructed``) ... * **line 12**: ... the code displays its class and number. * **line 14**: Then it enters inside the tag and ... * **line 15**: ... calls itself recursively to decode the ASN.1 tags inside. * **line 16**: Leaves the current constructed tag (with `Decoder.leave()`) to continue the decoding of its siblings. python-asn1-2.7.1/docs/index.rst000066400000000000000000000004341466704527000165070ustar00rootroot00000000000000======== Contents ======== .. toctree:: :maxdepth: 1 readme installation usage examples introduction_to_asn1 reference/index contributing credits authors license changelog Indices and tables ================== * :ref:`genindex` * :ref:`search` python-asn1-2.7.1/docs/installation.rst000066400000000000000000000005721466704527000201040ustar00rootroot00000000000000============ Installation ============ Install from `PyPi `_ with the following: .. code-block:: sh pip install asn1 or download the repository from `GitHub `_ and install with the following: .. code-block:: sh python setup.py install You can also simply include ``asn1.py`` into your project. python-asn1-2.7.1/docs/introduction_to_asn1.rst000066400000000000000000000134451466704527000215530ustar00rootroot00000000000000Introduction to ASN.1 ===================== ASN.1 is short for `Abstract Syntax Notation One`_. It is a joint ISO/IEC and ITU-T standard for describing the syntax and encoding of data structures in a platform independent way. The syntax of data structures in ASN.1 is described by a syntax definiton language. This language is used to compose ever more complex data types from a few simple ones such as integer and string. Often you will see this language in other standards that define an application on top of ASN.1 (for example LDAP). It is outside the scope of this introduction to go any deeper into the ASN.1 syntax definition language. The encoding of data structures in ASN.1 is defined by so-called *encoding rules*. Not just one but many encoding rules exist, and occasionally new ones are added. Each of the encoding rules is powerful enough to encode any data type specified by the ASN.1 language. Because of the multiple encoding rules, it also means that is it not enough to know that a piece of data is ASN.1 to decode it: you also need to know the encoding rules with which it was encoded. ASN.1 was first conceived in 1984. Some people say it is old, other say it is mature. Fact is that many Internet protocols are built on top of ASN.1. Some of the most important of these protocols are: * **LDAP** (Lightweight Directory Access Protocol) * **Kerberos** (network authentication protocol * **SNMP** (Simple Network Management Protocol) * **X.509** (public key infrastructure, known from e.g. SSL certificates) ASN.1 is not the only standard for defining the syntax and encoding of data structures. Other standards are Sun's XDR_ (External Data Representation), and EBNF_ (Extended Backus-Naur Form). XDR is used primarily for ONC-RPC applications such as NFS (the Network File System). EBNF is perhaps the most used standard of them all: HTTP and XML (and thereby all its applications) are specified in EBNF. ASN.1 Data Types or Tags ------------------------ ASN.1 data types are either primitive data types such as integers and strings, or more complex ones built on them. Whatever the details of the data type, each of them has the following properties. * Its **number**.The number is a numerical identifier that identifies the data type uniquely amongst others in its class. For example, ASN.1 defines that the type ``integer`` has the number ``2``. * Its **class**. Four classes exist: **universal**, **application**, **context**, **private**. Each class defines the scope of the data type, i.e. how broad is the type available. Universal types are always available while the other ones are more restricted. All standard primitive types that are defined by ASN.1 are defined in the universal class. * Its **encoding type** (or just **type**). Two types exist: **primitive** and **constructed**. Primitively types contain just one data value such as a number or a string, while constructed types contain a collection of other primitive or constructed types. The three properties together (number, class, type) are also called the **tag** of a data type. ASN.1 Encoding -------------- As mentioned above, the encoding of data structures is defined by ASN.1 encoding rules. The most well-known encodings rules are: * **BER** (Basic Encoding Rules) * **DER** (Distinguished Encoding Rules) The BER and DER encodings are very similar: BER is a looser format and sometimes allows a single value to be encoded in different ways. The DER is a subset of BER and defines in case multiple encodings are possible which one needs to be used. This means that a BER parser can read DER (because that is just a special form of BER), and a DER generator automatically generates valid BER (because all DER is also BER.) **Python-ASN1** supports the DER and BER, and none of the other encoding rules. The vast majority of applications use these encoding rules, so at the moment no support for additional encoding rules is planned. When generating ASN.1, **Python-ASN1** will output DER, while for parsing it supports BER. This arrangement complies with the practise of being forgiving on input, and strict on output. The DER and BER encodings work according the the **TLV** (Tag, Length, Value) principle. A stream of encoded ASN.1 data consists of a sequence of zero or more (tags, lenght, value) tuples, one after the other. These tuples are also called **records** in this manual, although this is not a term that is defined in the ASN.1 standard. For each data type, DER and BER define how to encode the tag (which we remember is a combination of its number, class and type), the length (in octets) of the data value, and the data value itself. Because DER encoding results in a truly binary representation of the encoded data, a format has been devised for being able to send these in an encoding of printable characters so you can actually mail these things. * **PEM** (Privacy Enhanced Mail) defined in RFC 1421. In essence PEM files are just base64 encoded versions of the DER encoded data. In order to distinguish from the outside what kind of data is inside the DER encoded string, a header and footer are present around the data. References ---------- .. _ITU-T Study Group 17 - Languages for Telecommunication Systems: .. _Abstract Syntax Notation One: .. _ASN1: https://www.itu.int/en/ITU-T/studygroups/2017-2020/17/Pages/default.aspx .. _External Data Representation Standard: .. _XDR: https://datatracker.ietf.org/doc/html/rfc4506 .. _ISO\/IEC 14977\: Information technology -- Syntactic metalanguage -- Extended BNF: .. _EBNF: https://standards.iso.org/ittf/PubliclyAvailableStandards/s026153_ISO_IEC_14977_1996(E).zip * **ASN.1** `ITU-T Study Group 17 - Languages for Telecommunication Systems`_ * **XDR** `External Data Representation Standard`_ * **EBNF** `ISO\/IEC 14977\: Information technology -- Syntactic metalanguage -- Extended BNF`_ python-asn1-2.7.1/docs/license.rst000066400000000000000000000000511466704527000170150ustar00rootroot00000000000000License ======= .. include:: ../LICENSE python-asn1-2.7.1/docs/readme.rst000066400000000000000000000000331466704527000166300ustar00rootroot00000000000000.. include:: ../README.rst python-asn1-2.7.1/docs/reference/000077500000000000000000000000001466704527000166035ustar00rootroot00000000000000python-asn1-2.7.1/docs/reference/asn1.rst000066400000000000000000000001261466704527000201760ustar00rootroot00000000000000asn1 ==== .. testsetup:: from asn1 import * .. automodule:: asn1 :members: python-asn1-2.7.1/docs/reference/index.rst000066400000000000000000000000701466704527000204410ustar00rootroot00000000000000Reference ========= .. toctree:: :glob: asn1* python-asn1-2.7.1/docs/spelling_wordlist.txt000066400000000000000000000001551466704527000211530ustar00rootroot00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices python-asn1-2.7.1/docs/usage.rst000066400000000000000000000075441466704527000165150ustar00rootroot00000000000000Usage ===== .. note:: You can find a complete example in `examples`. The Python-ASN1 API is exposed by a single Python module named `asn1`. The main interface is provided by the following classes: * `Encoder`: Used to encode ASN.1. * `Decoder`: Used to decode ASN.1. * `Error`: Exception used to signal errors. Type Mapping ------------ The Python-ASN1 encoder and decoder make a difference between primitive and constructed data types. Primitive data types can be encoded and decoded directly with `read()` and `write()` methods. For these types, ASN.1 types are mapped directly to Python types and vice versa, as per the table below: ================ ================= ============= ASN.1 type Python type Default ================ ================= ============= Boolean bool yes Integer int yes OctetString bytes yes PrintableString str yes Null None yes ObjectIdentifier bytes no Enumerated int no ================ ================= ============= The column **default** is relevant for encoding only. Because ASN.1 has more data types than Python, the situation arises that one Python type corresponds to multiple ASN.1 types. In this sitution the to be encoded ASN.1 type cannot be determined from the Python type. The solution implemented in Python-ASN1 is that the most frequently used type will be the implicit default, and if another type is desired than that must be specified explicitly through the API. For constructed types, no type mapping is done at all, even for types where such a mapping would be possible such as the ASN.1 type **sequence of** which could be mapped to a Python list. For such types a stack based approach is used instead. In this approach, the user needs to explicitly enter/leave the constructed type using the `Encoder.enter()` and `Encoder.leave()` methods of the encoder and the `Decoder.enter()` and `Decoder.leave()` methods of the decoder. Encoding -------- If you want to encode data and retrieve its DER-encoded representation, use code such as: .. code-block:: python import asn1 encoder = asn1.Encoder() encoder.start() encoder.write('1.2.3', asn1.ObjectIdentifier) encoded_bytes = encoder.output() Decoding -------- If you want to decode ASN.1 from DER or BER encoded bytes, use code such as: .. code-block:: python import asn1 decoder = asn1.Decoder() decoder.start(encoded_bytes) tag, value = decoder.read() Constants --------- A few constants are defined in the `asn1` module. The constants immediately below correspond to ASN.1 numbers. They can be used as the ``nr`` parameter of the `Encoder.write()` method, and are returned as the first part of a ``(nr, typ, cls)`` tuple as returned by `Decoder.peek()` and `Decoder.read()`. ================ =========== Constant Value (hex) ================ =========== Boolean 0x01 Integer 0x02 OctetString 0x04 Null 0x05 ObjectIdentifier 0x06 Enumerated 0x0a Sequence 0x10 Set 0x11 ================ =========== The following constants define the two available encoding types (primitive and constructed) for ASN.1 data types. As above they can be used with the `Encoder.write()` and are returned by `Decoder.peek()` and `Decoder.read()`. ================ =========== Constant Value (hex) ================ =========== TypeConstructed 0x20 TypePrimitive 0x00 ================ =========== Finally the constants below define the different ASN.1 classes. As above they can be used with the `Encoder.write()` and are returned by `Decoder.peek()` and `Decoder.read()`. ================ =========== Constant Value (hex) ================ =========== ClassUniversal 0x00 ClassApplication 0x40 ClassContext 0x80 ClassPrivate 0xc0 ================ =========== python-asn1-2.7.1/examples/000077500000000000000000000000001466704527000155335ustar00rootroot00000000000000python-asn1-2.7.1/examples/dump.py000077500000000000000000000127101466704527000170560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # This file is part of Python-ASN1. Python-ASN1 is free software that is # made available under the MIT license. Consult the file "LICENSE" that # is distributed together with this file for the exact licensing terms. # # Python-ASN1 is copyright (c) 2007-2016 by the Python-ASN1 authors. See # the file "AUTHORS" for a complete overview. from __future__ import absolute_import, division, print_function, unicode_literals from builtins import open, bytes, str import sys import base64 import binascii import asn1 import optparse def read_pem(input_file): """Read PEM formatted input.""" data = [] state = 0 for line in input_file: if state == 0: if line.startswith('-----BEGIN'): state = 1 elif state == 1: if line.startswith('-----END'): state = 2 else: data.append(line) elif state == 2: break if state != 2: raise ValueError('No PEM encoded input found') data = ''.join(data) return base64.b64decode(data) tag_id_to_string_map = { asn1.Numbers.Boolean: "BOOLEAN", asn1.Numbers.Integer: "INTEGER", asn1.Numbers.BitString: "BIT STRING", asn1.Numbers.OctetString: "OCTET STRING", asn1.Numbers.Null: "NULL", asn1.Numbers.ObjectIdentifier: "OBJECT", asn1.Numbers.PrintableString: "PRINTABLESTRING", asn1.Numbers.IA5String: "IA5STRING", asn1.Numbers.UTCTime: "UTCTIME", asn1.Numbers.GeneralizedTime: "GENERALIZED TIME", asn1.Numbers.Enumerated: "ENUMERATED", asn1.Numbers.Sequence: "SEQUENCE", asn1.Numbers.Set: "SET" } class_id_to_string_map = { asn1.Classes.Universal: "U", asn1.Classes.Application: "A", asn1.Classes.Context: "C", asn1.Classes.Private: "P" } object_id_to_string_map = { "1.2.840.113549.1.1.1": "rsaEncryption", "1.2.840.113549.1.1.5": "sha1WithRSAEncryption", "1.3.6.1.5.5.7.1.1": "authorityInfoAccess", "2.5.4.3": "commonName", "2.5.4.4": "surname", "2.5.4.5": "serialNumber", "2.5.4.6": "countryName", "2.5.4.7": "localityName", "2.5.4.8": "stateOrProvinceName", "2.5.4.9": "streetAddress", "2.5.4.10": "organizationName", "2.5.4.11": "organizationalUnitName", "2.5.4.12": "title", "2.5.4.13": "description", "2.5.4.42": "givenName", "1.2.840.113549.1.9.1": "emailAddress", "2.5.29.14": "X509v3 Subject Key Identifier", "2.5.29.15": "X509v3 Key Usage", "2.5.29.16": "X509v3 Private Key Usage Period", "2.5.29.17": "X509v3 Subject Alternative Name", "2.5.29.18": "X509v3 Issuer Alternative Name", "2.5.29.19": "X509v3 Basic Constraints", "2.5.29.30": "X509v3 Name Constraints", "2.5.29.31": "X509v3 CRL Distribution Points", "2.5.29.32": "X509v3 Certificate Policies Extension", "2.5.29.33": "X509v3 Policy Mappings", "2.5.29.35": "X509v3 Authority Key Identifier", "2.5.29.36": "X509v3 Policy Constraints", "2.5.29.37": "X509v3 Extended Key Usage" } def tag_id_to_string(identifier): """Return a string representation of a ASN.1 id.""" if identifier in tag_id_to_string_map: return tag_id_to_string_map[identifier] return '{:#02x}'.format(identifier) def class_id_to_string(identifier): """Return a string representation of an ASN.1 class.""" if identifier in class_id_to_string_map: return class_id_to_string_map[identifier] raise ValueError('Illegal class: {:#02x}'.format(identifier)) def object_identifier_to_string(identifier): if identifier in object_id_to_string_map: return object_id_to_string_map[identifier] return identifier def value_to_string(tag_number, value): if tag_number == asn1.Numbers.ObjectIdentifier: return object_identifier_to_string(value) elif isinstance(value, bytes): return '0x' + str(binascii.hexlify(value).upper()) elif isinstance(value, str): return value else: return repr(value) def pretty_print(input_stream, output_stream, indent=0): """Pretty print ASN.1 data.""" while not input_stream.eof(): tag = input_stream.peek() if tag.typ == asn1.Types.Primitive: tag, value = input_stream.read() output_stream.write(' ' * indent) output_stream.write('[{}] {}: {}\n'.format(class_id_to_string(tag.cls), tag_id_to_string(tag.nr), value_to_string(tag.nr, value))) elif tag.typ == asn1.Types.Constructed: output_stream.write(' ' * indent) output_stream.write('[{}] {}\n'.format(class_id_to_string(tag.cls), tag_id_to_string(tag.nr))) input_stream.enter() pretty_print(input_stream, output_stream, indent + 2) input_stream.leave() # Main script parser = optparse.OptionParser() parser.add_option('-p', '--pem', dest='mode', action='store_const', const='pem', help='PEM encoded input') parser.add_option('-r', '--raw', dest='mode', action='store_const', const='raw', help='raw input') parser.add_option('-o', '--output', dest='output', help='output to FILE instead', metavar='FILE') parser.set_default('mode', 'pem') (opts, args) = parser.parse_args() if opts.mode == 'pem': input_file = open(args[0], 'r') input_data = read_pem(input_file) else: input_file = open(args[0], 'rb') input_data = input_file.read() if opts.output: output_file = open(opts.output, 'w') else: output_file = sys.stdout decoder = asn1.Decoder() decoder.start(input_data) pretty_print(decoder, output_file) python-asn1-2.7.1/examples/test.crt000066400000000000000000000026741466704527000172350ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEDjCCA3egAwIBAgICFTAwDQYJKoZIhvcNAQEFBQAwgbsxCzAJBgNVBAYTAi0t MRIwEAYDVQQIEwlTb21lU3RhdGUxETAPBgNVBAcTCFNvbWVDaXR5MRkwFwYDVQQK ExBTb21lT3JnYW5pemF0aW9uMR8wHQYDVQQLExZTb21lT3JnYW5pemF0aW9uYWxV bml0MR4wHAYDVQQDExVsb2NhbGhvc3QubG9jYWxkb21haW4xKTAnBgkqhkiG9w0B CQEWGnJvb3RAbG9jYWxob3N0LmxvY2FsZG9tYWluMB4XDTA4MDIwNTA5MjMzMVoX DTA5MDIwNDA5MjMzMVowgbsxCzAJBgNVBAYTAi0tMRIwEAYDVQQIEwlTb21lU3Rh dGUxETAPBgNVBAcTCFNvbWVDaXR5MRkwFwYDVQQKExBTb21lT3JnYW5pemF0aW9u MR8wHQYDVQQLExZTb21lT3JnYW5pemF0aW9uYWxVbml0MR4wHAYDVQQDExVsb2Nh bGhvc3QubG9jYWxkb21haW4xKTAnBgkqhkiG9w0BCQEWGnJvb3RAbG9jYWxob3N0 LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVGM1AkZAn Wnc3Isq6Bd8TMeh0Q09+CKOlds173TfQfxKegXOHVWYN2mjuOOs04vTrldXg3u8I V/kDFGmob3yk+mRROTbVCTdhgxOMQSW6YJEghltgteKDZWatBrNFcYNn0uVfQEJL N/iH0AlJuK00dqMbv8EPt/tDvmIzAgIQYQIDAQABo4IBHTCCARkwHQYDVR0OBBYE FApL+odUF34wtCFxVlEP0pHDMAI2MIHpBgNVHSMEgeEwgd6AFApL+odUF34wtCFx VlEP0pHDMAI2oYHBpIG+MIG7MQswCQYDVQQGEwItLTESMBAGA1UECBMJU29tZVN0 YXRlMREwDwYDVQQHEwhTb21lQ2l0eTEZMBcGA1UEChMQU29tZU9yZ2FuaXphdGlv bjEfMB0GA1UECxMWU29tZU9yZ2FuaXphdGlvbmFsVW5pdDEeMBwGA1UEAxMVbG9j YWxob3N0LmxvY2FsZG9tYWluMSkwJwYJKoZIhvcNAQkBFhpyb290QGxvY2FsaG9z dC5sb2NhbGRvbWFpboICFTAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOB gQBOEkZYo1fFmqv6MvXeh/t3qHk4HU/TfDoWYIJ9kqFY0lN7EZDsbbCwWO4ztHsd uJXYmMMQgYMIRuiauWy/j55z92GJxGobwZjGq/yRtlm4pQWRKrvEMBZTvxr+LwEl ru/HufqlU/jZ9Y+ukepXKPrfNAMp6JfuLp6KYkXH/Fi0Wg== -----END CERTIFICATE----- python-asn1-2.7.1/requirements-dev.txt000066400000000000000000000003761466704527000177630ustar00rootroot00000000000000-r requirements.txt pytest==8.3.2 tox==4.18.0 flake8==7.1.1 isort==5.13.2 docutils==0.18.1 check-manifest==0.49 readme-renderer==35.0 pygments==2.18.0 sphinx==7.3.7 sphinx_rtd_theme==1.3.0 coverage==7.6.1 twine==5.1.1 wheel==0.44.0 advbumpversion==1.2.0 python-asn1-2.7.1/requirements.txt000066400000000000000000000000411466704527000171740ustar00rootroot00000000000000future==1.0.0 enum-compat==0.0.3 python-asn1-2.7.1/setup.cfg000066400000000000000000000010451466704527000155360ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 140 exclude = tests/*,*/migrations/*,*/south_migrations/* [tool:pytest] norecursedirs = .git .tox .env dist build south_migrations migrations python_files = test_*.py *_test.py tests.py addopts = -rxEfsw --strict-markers --doctest-modules --doctest-glob=\*.rst --tb=short [isort] force_single_line=True line_length=120 known_first_party=asn1 default_section=THIRDPARTY forced_separate=test_asn1 skip = migrations, south_migrations python-asn1-2.7.1/setup.py000066400000000000000000000047111466704527000154320ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import from __future__ import print_function import io import re from glob import glob from os.path import basename from os.path import dirname from os.path import join from os.path import splitext from sys import version_info from setuptools import find_packages from setuptools import setup def read(*names, **kwargs): return io.open( join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8') ).read() install_requires = ['enum-compat'] if version_info[0] < 3: install_requires.append('future') setup( name='asn1', version='2.7.1', license='BSD', description='Python-ASN1 is a simple ASN.1 encoder and decoder for Python 2.7+ and 3.5+.', long_description='%s\n%s' % ( re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')) ), author='Sebastien Andrivet', author_email='sebastien@andrivet.com', url='https://github.com/andrivet/python-asn1', packages=find_packages('src'), package_dir={'': 'src'}, py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], include_package_data=True, zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Utilities', ], keywords=[ # eg: 'keyword1', 'keyword2', 'keyword3', ], install_requires=install_requires, extras_require={ # eg: # 'rst': ['docutils>=0.11'], # ':python_version=="2.6"': ['argparse'], }, ) python-asn1-2.7.1/src/000077500000000000000000000000001466704527000145045ustar00rootroot00000000000000python-asn1-2.7.1/src/asn1.py000066400000000000000000000602561466704527000157310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # This file is part of Python-ASN1. Python-ASN1 is free software that is # made available under the MIT license. Consult the file "LICENSE" that is # distributed together with this file for the exact licensing terms. # # Python-ASN1 is copyright (c) 2007-2016 by the Python-ASN1 authors. See the # file "AUTHORS" for a complete overview. """ This module provides ASN.1 encoder and decoder. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import collections import re from builtins import bytes from builtins import int from builtins import range from builtins import str from contextlib import contextmanager from enum import IntEnum from numbers import Number __version__ = "2.7.1" class Numbers(IntEnum): Boolean = 0x01 Integer = 0x02 BitString = 0x03 OctetString = 0x04 Null = 0x05 ObjectIdentifier = 0x06 Enumerated = 0x0a UTF8String = 0x0c Sequence = 0x10 Set = 0x11 PrintableString = 0x13 IA5String = 0x16 UTCTime = 0x17 GeneralizedTime = 0x18 UnicodeString = 0x1e class Types(IntEnum): Constructed = 0x20 Primitive = 0x00 class Classes(IntEnum): Universal = 0x00 Application = 0x40 Context = 0x80 Private = 0xc0 Tag = collections.namedtuple('Tag', 'nr typ cls') """A named tuple to represent ASN.1 tags as returned by `Decoder.peek()` and `Decoder.read()`.""" class Error(Exception): """ASN.11 encoding or decoding error.""" class Encoder(object): """ASN.1 encoder. Uses DER encoding. """ def __init__(self): # type: () -> None """Constructor.""" self.m_stack = None def start(self): # type: () -> None """This method instructs the encoder to start encoding a new ASN.1 output. This method may be called at any time to reset the encoder, and resets the current output (if any). """ self.m_stack = [[]] def enter(self, nr, cls=None): # type: (int, int) -> None """This method starts the construction of a constructed type. Args: nr (int): The desired ASN.1 type. Use ``Numbers`` enumeration. cls (int): This optional parameter specifies the class of the constructed type. The default class to use is the universal class. Use ``Classes`` enumeration. Returns: None Raises: `Error` """ if self.m_stack is None: raise Error('Encoder not initialized. Call start() first.') if cls is None: cls = Classes.Universal self._emit_tag(nr, Types.Constructed, cls) self.m_stack.append([]) def leave(self): # type: () -> None """This method completes the construction of a constructed type and writes the encoded representation to the output buffer. """ if self.m_stack is None: raise Error('Encoder not initialized. Call start() first.') if len(self.m_stack) == 1: raise Error('Tag stack is empty.') value = b''.join(self.m_stack[-1]) del self.m_stack[-1] self._emit_length(len(value)) self._emit(value) @contextmanager def construct(self, nr, cls=None): # type: (int, int) -> None """This method - context manager calls enter and leave methods, for better code mapping. Usage: ``` with encoder.construct(asn1.Numbers.Sequence): encoder.write(1) with encoder.construct(asn1.Numbers.Sequence): encoder.write('foo') encoder.write('bar') encoder.write(2) ``` encoder.output() will result following structure: SEQUENCE: INTEGER: 1 SEQUENCE: STRING: foo STRING: bar INTEGER: 2 Args: nr (int): The desired ASN.1 type. Use ``Numbers`` enumeration. cls (int): This optional parameter specifies the class of the constructed type. The default class to use is the universal class. Use ``Classes`` enumeration. Returns: None Raises: `Error` """ self.enter(nr, cls) yield self.leave() def write(self, value, nr=None, typ=None, cls=None): # type: (object, int, int, int) -> None """This method encodes one ASN.1 tag and writes it to the output buffer. Note: Normally, ``value`` will be the only parameter to this method. In this case Python-ASN1 will autodetect the correct ASN.1 type from the type of ``value``, and will output the encoded value based on this type. Args: value (any): The value of the ASN.1 tag to write. Python-ASN1 will try to autodetect the correct ASN.1 type from the type of ``value``. nr (int): If the desired ASN.1 type cannot be autodetected or is autodetected wrongly, the ``nr`` parameter can be provided to specify the ASN.1 type to be used. Use ``Numbers`` enumeration. typ (int): This optional parameter can be used to write constructed types to the output by setting it to indicate the constructed encoding type. In this case, ``value`` must already be valid ASN.1 encoded data as plain Python bytes. This is not normally how constructed types should be encoded though, see `Encoder.enter()` and `Encoder.leave()` for the recommended way of doing this. Use ``Types`` enumeration. cls (int): This parameter can be used to override the class of the ``value``. The default class is the universal class. Use ``Classes`` enumeration. Returns: None Raises: `Error` """ if self.m_stack is None: raise Error('Encoder not initialized. Call start() first.') if typ is None: typ = Types.Primitive if cls is None: cls = Classes.Universal if cls != Classes.Universal and nr is None: raise Error('Please specify a tag number (nr) when using classes Application, Context or Private') if nr is None: if isinstance(value, bool): nr = Numbers.Boolean elif isinstance(value, int): nr = Numbers.Integer elif isinstance(value, str): nr = Numbers.PrintableString elif isinstance(value, bytes): nr = Numbers.OctetString elif value is None: nr = Numbers.Null value = self._encode_value(cls, nr, value) self._emit_tag(nr, typ, cls) self._emit_length(len(value)) self._emit(value) def output(self): # type: () -> bytes """This method returns the encoded ASN.1 data as plain Python ``bytes``. This method can be called multiple times, also during encoding. In the latter case the data that has been encoded so far is returned. Note: It is an error to call this method if the encoder is still constructing a constructed type, i.e. if `Encoder.enter()` has been called more times that `Encoder.leave()`. Returns: bytes: The DER encoded ASN.1 data. Raises: `Error` """ if self.m_stack is None: raise Error('Encoder not initialized. Call start() first.') if len(self.m_stack) != 1: raise Error('Stack is not empty.') output = b''.join(self.m_stack[0]) return output def _emit_tag(self, nr, typ, cls): # type: (int, int, int) -> None """Emit a tag.""" if nr < 31: self._emit_tag_short(nr, typ, cls) else: self._emit_tag_long(nr, typ, cls) def _emit_tag_short(self, nr, typ, cls): # type: (int, int, int) -> None """Emit a short (< 31 bytes) tag.""" assert nr < 31 self._emit(bytes([nr | typ | cls])) def _emit_tag_long(self, nr, typ, cls): # type: (int, int, int) -> None """Emit a long (>= 31 bytes) tag.""" head = bytes([typ | cls | 0x1f]) self._emit(head) values = [(nr & 0x7f)] nr >>= 7 while nr: values.append((nr & 0x7f) | 0x80) nr >>= 7 values.reverse() for val in values: self._emit(bytes([val])) def _emit_length(self, length): # type: (int) -> None """Emit length octects.""" if length < 128: self._emit_length_short(length) else: self._emit_length_long(length) def _emit_length_short(self, length): # type: (int) -> None """Emit the short length form (< 128 octets).""" assert length < 128 self._emit(bytes([length])) def _emit_length_long(self, length): # type: (int) -> None """Emit the long length form (>= 128 octets).""" values = [] while length: values.append(length & 0xff) length >>= 8 values.reverse() # really for correctness as this should not happen anytime soon assert len(values) < 127 head = bytes([0x80 | len(values)]) self._emit(head) for val in values: self._emit(bytes([val])) def _emit(self, s): # type: (bytes) -> None """Emit raw bytes.""" assert isinstance(s, bytes) self.m_stack[-1].append(s) def _encode_value(self, cls, nr, value): # type: (int, int, any) -> bytes """Encode a value.""" if cls != Classes.Universal: return value if nr in (Numbers.Integer, Numbers.Enumerated): return self._encode_integer(value) if nr in (Numbers.OctetString, Numbers.PrintableString, Numbers.UTF8String, Numbers.IA5String, Numbers.UnicodeString, Numbers.UTCTime, Numbers.GeneralizedTime): return self._encode_octet_string(value) if nr == Numbers.BitString: return self._encode_bit_string(value) if nr == Numbers.Boolean: return self._encode_boolean(value) if nr == Numbers.Null: return self._encode_null() if nr == Numbers.ObjectIdentifier: return self._encode_object_identifier(value) return value @staticmethod def _encode_boolean(value): # type: (bool) -> bytes """Encode a boolean.""" return value and bytes(b'\xff') or bytes(b'\x00') @staticmethod def _encode_integer(value): # type: (int) -> bytes """Encode an integer.""" if value < 0: value = -value negative = True limit = 0x80 else: negative = False limit = 0x7f values = [] while value > limit: values.append(value & 0xff) value >>= 8 values.append(value & 0xff) if negative: # create two's complement for i in range(len(values)): # Invert bits values[i] = 0xff - values[i] for i in range(len(values)): # Add 1 values[i] += 1 if values[i] <= 0xff: break assert i != len(values) - 1 values[i] = 0x00 if negative and values[len(values) - 1] == 0x7f: # Two's complement corner case values.append(0xff) values.reverse() return bytes(values) @staticmethod def _encode_octet_string(value): # type: (object) -> bytes """Encode an octetstring.""" # Use the primitive encoding assert isinstance(value, str) or isinstance(value, bytes) if isinstance(value, str): return value.encode('utf-8') else: return value @staticmethod def _encode_bit_string(value): # type: (object) -> bytes """Encode a bitstring. Assumes no unused bytes.""" # Use the primitive encoding assert isinstance(value, bytes) return b'\x00' + value @staticmethod def _encode_null(): # type: () -> bytes """Encode a Null value.""" return bytes(b'') _re_oid = re.compile(r'^[0-9]+(\.[0-9]+)+$') def _encode_object_identifier(self, oid): # type: (str) -> bytes """Encode an object identifier.""" if not self._re_oid.match(oid): raise Error('Illegal object identifier') cmps = list(map(int, oid.split('.'))) if (cmps[0] <= 1 and cmps[1] > 39) or cmps[0] > 2: raise Error('Illegal object identifier') cmps = [40 * cmps[0] + cmps[1]] + cmps[2:] cmps.reverse() result = [] for cmp_data in cmps: result.append(cmp_data & 0x7f) while cmp_data > 0x7f: cmp_data >>= 7 result.append(0x80 | (cmp_data & 0x7f)) result.reverse() return bytes(result) class Decoder(object): """ASN.1 decoder. Understands BER (and DER which is a subset).""" def __init__(self): # type: () -> None """Constructor.""" self.m_stack = None self.m_tag = None def start(self, data): # type: (bytes) -> None """This method instructs the decoder to start decoding the ASN.1 input ``data``, which must be a passed in as plain Python bytes. This method may be called at any time to start a new decoding job. If this method is called while currently decoding another input, that decoding context is discarded. Note: It is not necessary to specify the encoding because the decoder assumes the input is in BER or DER format. Args: data (bytes): ASN.1 input, in BER or DER format, to be decoded. Returns: None Raises: `Error` """ if not isinstance(data, bytes): raise Error('Expecting bytes instance.') self.m_stack = [[0, bytes(data)]] self.m_tag = None def peek(self): # type: () -> Tag """This method returns the current ASN.1 tag (i.e. the tag that a subsequent `Decoder.read()` call would return) without updating the decoding offset. In case no more data is available from the input, this method returns ``None`` to signal end-of-file. This method is useful if you don't know whether the next tag will be a primitive or a constructed tag. Depending on the return value of `peek`, you would decide to either issue a `Decoder.read()` in case of a primitive type, or an `Decoder.enter()` in case of a constructed type. Note: Because this method does not advance the current offset in the input, calling it multiple times in a row will return the same value for all calls. Returns: `Tag`: The current ASN.1 tag. Raises: `Error` """ if self.m_stack is None: raise Error('No input selected. Call start() first.') if self._end_of_input(): return None if self.m_tag is None: self.m_tag = self._read_tag() return self.m_tag def read(self, tagnr=None): # type: (Number) -> (Tag, any) """This method decodes one ASN.1 tag from the input and returns it as a ``(tag, value)`` tuple. ``tag`` is a 3-tuple ``(nr, typ, cls)``, while ``value`` is a Python object representing the ASN.1 value. The offset in the input is increased so that the next `Decoder.read()` call will return the next tag. In case no more data is available from the input, this method returns ``None`` to signal end-of-file. Returns: `Tag`, value: The current ASN.1 tag and its value. Raises: `Error` """ if self.m_stack is None: raise Error('No input selected. Call start() first.') if self._end_of_input(): return None tag = self.peek() length = self._read_length() if tagnr is None: tagnr = tag.nr value = self._read_value(tag.cls, tagnr, length) self.m_tag = None return tag, value def eof(self): # type: () -> bool """Return True if we are at the end of input. Returns: bool: True if all input has been decoded, and False otherwise. """ return self._end_of_input() def enter(self): # type: () -> None """This method enters the constructed type that is at the current decoding offset. Note: It is an error to call `Decoder.enter()` if the to be decoded ASN.1 tag is not of a constructed type. Returns: None """ if self.m_stack is None: raise Error('No input selected. Call start() first.') tag = self.peek() if tag.typ != Types.Constructed: raise Error('Cannot enter a non-constructed tag.') length = self._read_length() bytes_data = self._read_bytes(length) self.m_stack.append([0, bytes_data]) self.m_tag = None def leave(self): # type: () -> None """This method leaves the last constructed type that was `Decoder.enter()`-ed. Note: It is an error to call `Decoder.leave()` if the current ASN.1 tag is not of a constructed type. Returns: None """ if self.m_stack is None: raise Error('No input selected. Call start() first.') if len(self.m_stack) == 1: raise Error('Tag stack is empty.') del self.m_stack[-1] self.m_tag = None def _read_tag(self): # type: () -> Tag """Read a tag from the input.""" byte = self._read_byte() cls = byte & 0xc0 typ = byte & 0x20 nr = byte & 0x1f if nr == 0x1f: # Long form of tag encoding nr = 0 while True: byte = self._read_byte() nr = (nr << 7) | (byte & 0x7f) if not byte & 0x80: break return Tag(nr=nr, typ=typ, cls=cls) def _read_length(self): # type: () -> int """Read a length from the input.""" byte = self._read_byte() if byte & 0x80: count = byte & 0x7f if count == 0x7f: raise Error('ASN1 syntax error') bytes_data = self._read_bytes(count) length = 0 for byte in bytes_data: length = (length << 8) | int(byte) try: length = int(length) except OverflowError: pass else: length = byte return length def _read_value(self, cls, nr, length): # type: (int, int, int) -> any """Read a value from the input.""" bytes_data = self._read_bytes(length) if cls != Classes.Universal: value = bytes_data elif nr == Numbers.Boolean: value = self._decode_boolean(bytes_data) elif nr in (Numbers.Integer, Numbers.Enumerated): value = self._decode_integer(bytes_data) elif nr == Numbers.OctetString: value = self._decode_octet_string(bytes_data) elif nr == Numbers.Null: value = self._decode_null(bytes_data) elif nr == Numbers.ObjectIdentifier: value = self._decode_object_identifier(bytes_data) elif nr in (Numbers.PrintableString, Numbers.IA5String, Numbers.UTF8String, Numbers.UTCTime, Numbers.GeneralizedTime): value = self._decode_printable_string(bytes_data) elif nr == Numbers.BitString: value = self._decode_bitstring(bytes_data) else: value = bytes_data return value def _read_byte(self): # type: () -> int """Return the next input byte, or raise an error on end-of-input.""" index, input_data = self.m_stack[-1] try: byte = input_data[index] except IndexError: raise Error('Premature end of input.') self.m_stack[-1][0] += 1 return byte def _read_bytes(self, count): # type: (int) -> bytes """Return the next ``count`` bytes of input. Raise error on end-of-input.""" index, input_data = self.m_stack[-1] bytes_data = input_data[index:index + count] if len(bytes_data) != count: raise Error('Premature end of input.') self.m_stack[-1][0] += count return bytes_data def _end_of_input(self): # type: () -> bool """Return True if we are at the end of input.""" index, input_data = self.m_stack[-1] assert not index > len(input_data) return index == len(input_data) @staticmethod def _decode_boolean(bytes_data): # type: (bytes) -> bool """Decode a boolean value.""" if len(bytes_data) != 1: raise Error('ASN1 syntax error') if bytes_data[0] == 0: return False return True @staticmethod def _decode_integer(bytes_data): # type: (bytes) -> int """Decode an integer value.""" values = [int(b) for b in bytes_data] # check if the integer is normalized if len(values) > 1 and (values[0] == 0xff and values[1] & 0x80 or values[0] == 0x00 and not (values[1] & 0x80)): raise Error('ASN1 syntax error') negative = values[0] & 0x80 if negative: # make positive by taking two's complement for i in range(len(values)): values[i] = 0xff - values[i] for i in range(len(values) - 1, -1, -1): values[i] += 1 if values[i] <= 0xff: break assert i > 0 values[i] = 0x00 value = 0 for val in values: value = (value << 8) | val if negative: value = -value try: value = int(value) except OverflowError: pass return value @staticmethod def _decode_octet_string(bytes_data): # type: (bytes) -> bytes """Decode an octet string.""" return bytes_data @staticmethod def _decode_null(bytes_data): # type: (bytes) -> any """Decode a Null value.""" if len(bytes_data) != 0: raise Error('ASN1 syntax error') return None @staticmethod def _decode_object_identifier(bytes_data): # type: (bytes) -> str """Decode an object identifier.""" result = [] value = 0 for i in range(len(bytes_data)): byte = int(bytes_data[i]) if value == 0 and byte == 0x80: raise Error('ASN1 syntax error') value = (value << 7) | (byte & 0x7f) if not byte & 0x80: result.append(value) value = 0 if len(result) == 0: raise Error('ASN1 syntax error') if result[0] // 40 <= 1: result = [result[0] // 40, result[0] % 40] + result[1:] else: result = [2, result[0] - 80] + result[1:] result = list(map(str, result)) return str('.'.join(result)) @staticmethod def _decode_printable_string(bytes_data): # type: (bytes) -> str """Decode a printable string.""" return bytes_data.decode('utf-8') @staticmethod def _decode_bitstring(bytes_data): # type: (bytes) -> str """Decode a bitstring.""" if len(bytes_data) == 0: raise Error('ASN1 syntax error') num_unused_bits = bytes_data[0] if not (0 <= num_unused_bits <= 7): raise Error('ASN1 syntax error') if num_unused_bits == 0: return bytes_data[1:] # Shift off unused bits remaining = bytearray(bytes_data[1:]) bitmask = (1 << num_unused_bits) - 1 removed_bits = 0 for i in range(len(remaining)): byte = int(remaining[i]) remaining[i] = (byte >> num_unused_bits) | (removed_bits << num_unused_bits) removed_bits = byte & bitmask return bytes(remaining) python-asn1-2.7.1/tests/000077500000000000000000000000001466704527000150575ustar00rootroot00000000000000python-asn1-2.7.1/tests/test_asn1.py000066400000000000000000001054371466704527000173440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # This file is part of Python-ASN1. Python-ASN1 is free software that is # made available under the MIT license. Consult the file "LICENSE" that # is distributed together with this file for the exact licensing terms. # # Python-ASN1 is copyright (c) 2007-2016 by the Python-ASN1 authors. See the # file "AUTHORS" for a complete overview. from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import base64 from builtins import int import pytest import asn1 class TestEncoder(object): """Test suite for ASN1 Encoder.""" def test_boolean(self): enc = asn1.Encoder() enc.start() enc.write(True, asn1.Numbers.Boolean) res = enc.output() assert res == b'\x01\x01\xff' def test_integer(self): enc = asn1.Encoder() enc.start() enc.write(1) res = enc.output() assert res == b'\x02\x01\x01' def test_long_integer(self): enc = asn1.Encoder() enc.start() enc.write(0x0102030405060708090a0b0c0d0e0f) res = enc.output() assert res == b'\x02\x0f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' def test_negative_integer(self): enc = asn1.Encoder() enc.start() enc.write(-1) res = enc.output() assert res == b'\x02\x01\xff' def test_long_negative_integer(self): enc = asn1.Encoder() enc.start() enc.write(-0x0102030405060708090a0b0c0d0e0f) res = enc.output() assert res == b'\x02\x0f\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf1' def test_twos_complement_boundaries(self): enc = asn1.Encoder() enc.start() enc.write(0) res = enc.output() assert res == b'\x02\x01\x00' enc = asn1.Encoder() enc.start() enc.write(1) res = enc.output() assert res == b'\x02\x01\x01' enc = asn1.Encoder() enc.start() enc.write(-0) res = enc.output() assert res == b'\x02\x01\x00' enc = asn1.Encoder() enc.start() enc.write(-1) res = enc.output() assert res == b'\x02\x01\xff' enc = asn1.Encoder() enc.start() enc.write(127) res = enc.output() assert res == b'\x02\x01\x7f' enc.start() enc.write(128) res = enc.output() assert res == b'\x02\x02\x00\x80' enc.start() enc.write(-127) res = enc.output() assert res == b'\x02\x01\x81' enc.start() enc.write(-128) res = enc.output() assert res == b'\x02\x01\x80' enc.start() enc.write(-129) res = enc.output() assert res == b'\x02\x02\xff\x7f' enc.start() enc.write(32767) res = enc.output() assert res == b'\x02\x02\x7f\xff' enc.start() enc.write(32768) res = enc.output() assert res == b'\x02\x03\x00\x80\x00' enc.start() enc.write(32769) res = enc.output() assert res == b'\x02\x03\x00\x80\x01' enc.start() enc.write(-32767) res = enc.output() assert res == b'\x02\x02\x80\x01' enc.start() enc.write(-32768) res = enc.output() assert res == b'\x02\x02\x80\x00' enc.start() enc.write(-32769) res = enc.output() assert res == b'\x02\x03\xff\x7f\xff' def test_octet_string(self): enc = asn1.Encoder() enc.start() enc.write(b'foo') res = enc.output() assert res == b'\x04\x03foo' def test_bitstring(self): enc = asn1.Encoder() enc.start() enc.write(b'\x12\x34\x56', asn1.Numbers.BitString) res = enc.output() assert res == b'\x03\x04\x00\x12\x34\x56' def test_printable_string(self): enc = asn1.Encoder() enc.start() enc.write(u'foo', nr=asn1.Numbers.PrintableString) res = enc.output() assert res == b'\x13\x03foo' def test_unicode_octet_string(self): enc = asn1.Encoder() enc.start() enc.write(u'fooé', nr=asn1.Numbers.OctetString) res = enc.output() assert res == b'\x04\x05\x66\x6f\x6f\xc3\xa9' def test_unicode_printable_string(self): enc = asn1.Encoder() enc.start() enc.write(u'fooé', nr=asn1.Numbers.PrintableString) res = enc.output() assert res == b'\x13\x05\x66\x6f\x6f\xc3\xa9' def test_null(self): enc = asn1.Encoder() enc.start() enc.write(None) res = enc.output() assert res == b'\x05\x00' def test_object_identifier(self): enc = asn1.Encoder() enc.start() enc.write('1.2.3', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x02\x2a\x03' def test_long_object_identifier(self): enc = asn1.Encoder() enc.start() enc.write('2.1482.3', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x03\x8c\x1a\x03' enc.start() enc.write('2.999.3', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x03\x88\x37\x03' enc.start() enc.write('1.39.3', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x02\x4f\x03' enc.start() enc.write('1.2.300000', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x04\x2a\x92\xa7\x60' def test_real_object_identifier(self): enc = asn1.Encoder() enc.start() enc.write('1.2.840.113554.1.2.1.1', asn1.Numbers.ObjectIdentifier) res = enc.output() assert res == b'\x06\x0a\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01' def test_enumerated(self): enc = asn1.Encoder() enc.start() enc.write(1, asn1.Numbers.Enumerated) res = enc.output() assert res == b'\x0a\x01\x01' def test_sequence(self): enc = asn1.Encoder() enc.start() enc.enter(asn1.Numbers.Sequence) enc.write(1) enc.write(b'foo') enc.leave() res = enc.output() assert res == b'\x30\x08\x02\x01\x01\x04\x03foo' def test_sequence_of(self): enc = asn1.Encoder() enc.start() enc.enter(asn1.Numbers.Sequence) enc.write(1) enc.write(2) enc.leave() res = enc.output() assert res == b'\x30\x06\x02\x01\x01\x02\x01\x02' def test_set(self): enc = asn1.Encoder() enc.start() enc.enter(asn1.Numbers.Set) enc.write(1) enc.write(b'foo') enc.leave() res = enc.output() assert res == b'\x31\x08\x02\x01\x01\x04\x03foo' def test_set_of(self): enc = asn1.Encoder() enc.start() enc.enter(asn1.Numbers.Set) enc.write(1) enc.write(2) enc.leave() res = enc.output() assert res == b'\x31\x06\x02\x01\x01\x02\x01\x02' def test_context(self): enc = asn1.Encoder() enc.start() enc.enter(1, asn1.Classes.Context) enc.write(1) enc.leave() res = enc.output() assert res == b'\xa1\x03\x02\x01\x01' def test_application(self): enc = asn1.Encoder() enc.start() enc.enter(1, asn1.Classes.Application) enc.write(1) enc.leave() res = enc.output() assert res == b'\x61\x03\x02\x01\x01' def test_private(self): enc = asn1.Encoder() enc.start() enc.enter(1, asn1.Classes.Private) enc.write(1) enc.leave() res = enc.output() assert res == b'\xe1\x03\x02\x01\x01' def test_long_tag_id(self): enc = asn1.Encoder() enc.start() enc.enter(0xffff) enc.write(1) enc.leave() res = enc.output() assert res == b'\x3f\x83\xff\x7f\x03\x02\x01\x01' def test_contextmanager_construct(self): enc = asn1.Encoder() enc.start() with enc.construct(asn1.Numbers.Sequence): enc.write(1) enc.write(b'foo') res = enc.output() assert res == b'\x30\x08\x02\x01\x01\x04\x03foo' def test_contextmanager_calls_enter(self): class TestEncoder(asn1.Encoder): def enter(self, nr, cls=None): raise RuntimeError() enc = TestEncoder() enc.start() with pytest.raises(RuntimeError): with enc.construct(asn1.Numbers.Sequence): enc.write(1) def test_contextmanager_calls_leave(self): class TestEncoder(asn1.Encoder): def leave(self): raise RuntimeError() enc = TestEncoder() enc.start() with pytest.raises(RuntimeError): with enc.construct(asn1.Numbers.Sequence): enc.write(1) def test_long_tag_length(self): enc = asn1.Encoder() enc.start() enc.write(b'x' * 0xffff) res = enc.output() assert res == b'\x04\x82\xff\xff' + b'x' * 0xffff def test_error_init(self): enc = asn1.Encoder() pytest.raises(asn1.Error, enc.enter, asn1.Numbers.Sequence) pytest.raises(asn1.Error, enc.leave) pytest.raises(asn1.Error, enc.write, 1) pytest.raises(asn1.Error, enc.output) def test_error_stack(self): enc = asn1.Encoder() enc.start() pytest.raises(asn1.Error, enc.leave) enc.enter(asn1.Numbers.Sequence) pytest.raises(asn1.Error, enc.output) enc.leave() pytest.raises(asn1.Error, enc.leave) def test_error_object_identifier(self): enc = asn1.Encoder() enc.start() pytest.raises(asn1.Error, enc.write, '1', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, '3.2.3', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, '1.40.3', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, '1.2.3.', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, '.1.2.3', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, 'foo', asn1.Numbers.ObjectIdentifier) pytest.raises(asn1.Error, enc.write, 'foo.bar', asn1.Numbers.ObjectIdentifier) def test_default_encoding(self): " Check that the encoder implicitly chooses the correct asn1 type " def check_defaults(value, number): default, explicit = asn1.Encoder(), asn1.Encoder() default.start() explicit.start() default.write(value) explicit.write(value, number) assert default.output() == explicit.output(), \ "default asn1 type for '{}' should be {!r}".format(type(value).__name__, number) check_defaults(True, asn1.Numbers.Boolean) check_defaults(12345, asn1.Numbers.Integer) check_defaults(b"byte string \x00\xff\xba\xdd", asn1.Numbers.OctetString) check_defaults(u"unicode string \U0001f4a9", asn1.Numbers.PrintableString) check_defaults(None, asn1.Numbers.Null) def test_context_no_tag_number(self): enc = asn1.Encoder() enc.start() with pytest.raises(asn1.Error): enc.write(b'\x00\x01\x02\x03\x04', typ=asn1.Types.Primitive, cls=asn1.Classes.Context) def test_context_with_tag_number_10(self): enc = asn1.Encoder() enc.start() enc.write(b'\x00\x01\x02\x03\x04', nr=10, typ=asn1.Types.Primitive, cls=asn1.Classes.Context) res = enc.output() assert res == b'\x8a\x05\x00\x01\x02\x03\x04' class TestDecoder(object): """Test suite for ASN1 Decoder.""" def test_boolean(self): buf = b'\x01\x01\xff' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Boolean, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert isinstance(val, int) assert val buf = b'\x01\x01\x01' dec.start(buf) tag, val = dec.read() assert isinstance(val, int) assert val buf = b'\x01\x01\x00' dec.start(buf) tag, val = dec.read() assert isinstance(val, int) assert not val def test_integer(self): buf = b'\x02\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Integer, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert isinstance(val, int) assert val == 1 def test_long_integer(self): buf = b'\x02\x0f\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == 0x0102030405060708090a0b0c0d0e0f def test_negative_integer(self): buf = b'\x02\x01\xff' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == -1 def test_long_negative_integer(self): buf = b'\x02\x0f\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6\xf5\xf4\xf3\xf2\xf1\xf1' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == -0x0102030405060708090a0b0c0d0e0f def test_twos_complement_boundaries(self): buf = b'\x02\x01\x7f' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == 127 buf = b'\x02\x02\x00\x80' dec.start(buf) tag, val = dec.read() assert val == 128 buf = b'\x02\x01\x80' dec.start(buf) tag, val = dec.read() assert val == -128 buf = b'\x02\x02\xff\x7f' dec.start(buf) tag, val = dec.read() assert val == -129 def test_octet_string(self): buf = b'\x04\x03foo' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.OctetString, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == b'foo' def test_printable_string(self): buf = b'\x13\x03foo' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.PrintableString, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == u'foo' def test_bitstring(self): buf = b'\x03\x04\x00\x12\x34\x56' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.BitString, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == b'\x12\x34\x56' def test_bitstring_unused_bits(self): buf = b'\x03\x04\x04\x12\x34\x50' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.BitString, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == b'\x01\x23\x45' def test_unicode_printable_string(self): buf = b'\x13\x05\x66\x6f\x6f\xc3\xa9' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.PrintableString, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == u'fooé' def test_null(self): buf = b'\x05\x00' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Null, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val is None def test_object_identifier(self): dec = asn1.Decoder() buf = b'\x06\x02\x2a\x03' dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.ObjectIdentifier, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert val == u'1.2.3' def test_long_object_identifier(self): dec = asn1.Decoder() buf = b'\x06\x03\x8c\x1a\x03' dec.start(buf) tag, val = dec.read() assert val == u'2.1482.3' buf = b'\x06\x03\x88\x37\x03' dec.start(buf) tag, val = dec.read() assert val == u'2.999.3' buf = b'\x06\x02\x4f\x03' dec.start(buf) tag, val = dec.read() assert val == u'1.39.3' buf = b'\x06\x04\x2a\x92\xa7\x60' dec.start(buf) tag, val = dec.read() assert val == u'1.2.300000' def test_real_object_identifier(self): dec = asn1.Decoder() buf = b'\x06\x0a\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01' dec.start(buf) tag, val = dec.read() assert val == u'1.2.840.113554.1.2.1.1' def test_enumerated(self): buf = b'\x0a\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Enumerated, asn1.Types.Primitive, asn1.Classes.Universal) tag, val = dec.read() assert isinstance(val, int) assert val == 1 def test_sequence(self): buf = b'\x30\x08\x02\x01\x01\x04\x03foo' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Sequence, asn1.Types.Constructed, asn1.Classes.Universal) dec.enter() tag, val = dec.read() assert val == 1 tag, val = dec.read() assert val == b'foo' def test_sequence_of(self): buf = b'\x30\x06\x02\x01\x01\x02\x01\x02' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Sequence, asn1.Types.Constructed, asn1.Classes.Universal) dec.enter() tag, val = dec.read() assert val == 1 tag, val = dec.read() assert val == 2 def test_set(self): buf = b'\x31\x08\x02\x01\x01\x04\x03foo' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Set, asn1.Types.Constructed, asn1.Classes.Universal) dec.enter() tag, val = dec.read() assert val == 1 tag, val = dec.read() assert val == b'foo' def test_set_of(self): buf = b'\x31\x06\x02\x01\x01\x02\x01\x02' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (asn1.Numbers.Set, asn1.Types.Constructed, asn1.Classes.Universal) dec.enter() tag, val = dec.read() assert val == 1 tag, val = dec.read() assert val == 2 def test_context(self): buf = b'\xa1\x03\x02\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (1, asn1.Types.Constructed, asn1.Classes.Context) dec.enter() tag, val = dec.read() assert val == 1 def test_application(self): buf = b'\x61\x03\x02\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (1, asn1.Types.Constructed, asn1.Classes.Application) dec.enter() tag, val = dec.read() assert val == 1 def test_private(self): buf = b'\xe1\x03\x02\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (1, asn1.Types.Constructed, asn1.Classes.Private) dec.enter() tag, val = dec.read() assert val == 1 def test_long_tag_id(self): buf = b'\x3f\x83\xff\x7f\x03\x02\x01\x01' dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag == (0xffff, asn1.Types.Constructed, asn1.Classes.Universal) dec.enter() tag, val = dec.read() assert val == 1 def test_long_tag_length(self): buf = b'\x04\x82\xff\xff' + b'x' * 0xffff dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == b'x' * 0xffff def test_read_multiple(self): buf = b'\x02\x01\x01\x02\x01\x02' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == 1 tag, val = dec.read() assert val == 2 assert dec.eof() def test_skip_primitive(self): buf = b'\x02\x01\x01\x02\x01\x02' dec = asn1.Decoder() dec.start(buf) dec.read() tag, val = dec.read() assert val == 2 assert dec.eof() def test_skip_constructed(self): buf = b'\x30\x06\x02\x01\x01\x02\x01\x02\x02\x01\x03' dec = asn1.Decoder() dec.start(buf) dec.read() tag, val = dec.read() assert val == 3 assert dec.eof() def test_error_init(self): dec = asn1.Decoder() pytest.raises(asn1.Error, dec.peek) pytest.raises(asn1.Error, dec.read) pytest.raises(asn1.Error, dec.enter) pytest.raises(asn1.Error, dec.leave) def test_error_stack(self): buf = b'\x30\x08\x02\x01\x01\x04\x03foo' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.leave) dec.enter() dec.leave() pytest.raises(asn1.Error, dec.leave) def test_no_input(self): dec = asn1.Decoder() dec.start(b'') tag = dec.peek() assert tag is None def test_error_missing_tag_bytes(self): buf = b'\x3f' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.peek) buf = b'\x3f\x83' dec.start(buf) pytest.raises(asn1.Error, dec.peek) def test_error_no_length_bytes(self): buf = b'\x02' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_missing_length_bytes(self): buf = b'\x04\x82\xff' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_too_many_length_bytes(self): buf = b'\x04\xff' + b'\xff' * 0x7f dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_no_value_bytes(self): buf = b'\x02\x01' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_missing_value_bytes(self): buf = b'\x02\x02\x01' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_non_normalized_positive_integer(self): buf = b'\x02\x02\x00\x01' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_non_normalized_negative_integer(self): buf = b'\x02\x02\xff\x80' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_non_normalised_object_identifier(self): buf = b'\x06\x02\x01\x80' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_error_bitstring_with_too_many_unused_bits(self): buf = b'\x03\x04\x08\x12\x34\x50' dec = asn1.Decoder() dec.start(buf) pytest.raises(asn1.Error, dec.read) def test_big_negative_integer(self): buf = b'\x02\x10\xff\x7f\x2b\x3a\x4d\xea\x48\x1e\x1f\x37\x7b\xa8\xbd\x7f\xb0\x16' dec = asn1.Decoder() dec.start(buf) tag, val = dec.read() assert val == -668929531791034950848739021124816874 assert dec.eof() def test_mix_context_universal(self): encoded = 'tYHKgAETgwgBgDgJAGMS9aQGgAQBAAAChQUAh7Mfc6YGgAQBAAABhwx0ZXN0LnRlc3Quc2WIAgEhqQigBoAECtiCBIsBAawuM' \ 'CyCDAIjYh+TlkBYdGMQQIMBAIQBAIUBAoYJFwkVAClUKwAAiAgAIvIQAG0Yj40JFwkUIylUKwAAjgIOEI8BAJEBAZIJRENQMk' \ 'dHU04xlQEAlgmRI3cAUGBTA/CXAgAAmAEAmwMi8hCdCFOTKXBYgkMQngECnx8CgAGfIAgAIvIQAG0Yjw==' buf = base64.b64decode(encoded) dec = asn1.Decoder() dec.start(buf) tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 21 dec.enter() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 0 assert value == b'\x13' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 3 assert value == b'\x01\x80\x38\x09\x00\x63\x12\xf5' tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 4 dec.enter() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 0 assert value == b'\x01\x00\x00\x02' dec.leave() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 5 assert value == b'\x00\x87\xB3\x1F\x73' tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 6 dec.enter() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 0 assert value == b'\x01\x00\x00\x01' dec.leave() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 7 assert value == b'test.test.se' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 8 assert value == b'\x01\x21' tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 9 dec.enter() tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 0 dec.enter() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 0 assert value == b'\x0A\xD8\x82\x04' dec.leave() dec.leave() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 11 assert value == b'\x01' tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Context assert tag.nr == 12 dec.enter() tag = dec.peek() assert tag.typ == asn1.Types.Constructed assert tag.cls == asn1.Classes.Universal assert tag.nr == 16 dec.enter() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 2 assert value == b'\x02\x23\x62\x1F\x93\x96\x40\x58\x74\x63\x10\x40' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 3 assert value == b'\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 4 assert value == b'\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 5 assert value == b'\x02' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 6 assert value == b'\x17\x09\x15\x00\x29\x54\x2B\x00\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 8 assert value == b'\x00\x22\xF2\x10\x00\x6D\x18\x8F' dec.leave() dec.leave() tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 13 assert value == b'\x17\x09\x14\x23\x29\x54\x2B\x00\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 14 assert value == b'\x0E\x10' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 15 assert value == b'\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 17 assert value == b'\x01' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 18 assert value == b'DCP2GGSN1' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 21 assert value == b'\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 22 assert value == b'\x91\x23\x77\x00\x50\x60\x53\x03\xF0' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 23 assert value == b'\x00\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 24 assert value == b'\x00' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 27 assert value == b'\x22\xF2\x10' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 29 assert value == b'\x53\x93\x29\x70\x58\x82\x43\x10' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 30 assert value == b'\x02' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 31 assert value == b'\x80\x01' tag, value = dec.read() assert tag.typ == asn1.Types.Primitive assert tag.cls == asn1.Classes.Context assert tag.nr == 32 assert value == b'\x00\x22\xF2\x10\x00\x6D\x18\x8F' assert dec.peek() is None class TestEncoderDecoder(object): """Test suite for ASN1 Encoder and Decoder.""" @staticmethod def assert_encode_decode(v, t): encoder = asn1.Encoder() encoder.start() encoder.write(v, t) encoded_bytes = encoder.output() decoder = asn1.Decoder() decoder.start(encoded_bytes) tag, value = decoder.read() assert value == v def test_boolean(self): for v in (True, False): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.Boolean) def test_big_numbers(self): for v in \ ( 668929531791034950848739021124816874, 667441897913742713771034596334288035, 664674827807729028941298133900846368, 666811959353093594446621165172641478, ): encoder = asn1.Encoder() encoder.start() encoder.write(v, asn1.Numbers.Integer) encoded_bytes = encoder.output() decoder = asn1.Decoder() decoder.start(encoded_bytes) tag, value = decoder.read() assert value == v def test_big_negative_numbers(self): for v in \ ( -668929531791034950848739021124816874, -667441897913742713771034596334288035, -664674827807729028941298133900846368, -666811959353093594446621165172641478, ): encoder = asn1.Encoder() encoder.start() encoder.write(v, asn1.Numbers.Integer) encoded_bytes = encoder.output() decoder = asn1.Decoder() decoder.start(encoded_bytes) tag, value = decoder.read() assert value == v def test_bitstring(self): for v in \ ( b'\x12\x34\x56', b'\x01', b'' ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.BitString) def test_octet_string(self): for v in \ ( b'foo', b'', b'A' * 257 ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.OctetString) def test_null(self): TestEncoderDecoder.assert_encode_decode(None, asn1.Numbers.Null) def test_real_object_identifier(self): TestEncoderDecoder.assert_encode_decode( '1.2.840.113554.1.2.1.1', asn1.Numbers.ObjectIdentifier ) def test_long_object_identifier(self): for v in \ ( '2.60.3', '2.999.3', '1.39.3', '1.2.300000' ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.ObjectIdentifier) def test_enumerated(self): for v in (1, 2, 42): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.Enumerated) def test_utf8_string(self): for v in \ ( 'foo', u'fooé' ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.UTF8String) def test_printable_string(self): for v in \ ( 'foo', u'fooé' ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.PrintableString) def test_ia5_string(self): TestEncoderDecoder.assert_encode_decode('foo', asn1.Numbers.IA5String) def test_utc_time(self): for v in \ ( '920521000000Z', '920622123421Z', '920722132100Z' ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.UTCTime) def test_generalized_time(self): for v in \ ( '19920521000000Z', '19920622123421.123Z', '20920722132100-0500', '20920722132100+0200', '20920722132100.123-0500', '20920722132100.123+0200', ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.GeneralizedTime) def test_unicode_string(self): for v in \ ( b'foo', u'fooé'.encode('utf-8') ): TestEncoderDecoder.assert_encode_decode(v, asn1.Numbers.UnicodeString) python-asn1-2.7.1/tox.ini000066400000000000000000000030221466704527000152250ustar00rootroot00000000000000; a generative tox configuration, see: https://testrun.org/tox/latest/config.html#generative-envlist [tox] envlist = clean, check, {py27,py35,py36,py38,py39,py310,py311}, report, docs [gh-actions] python = 2.7: py27 3.5: py35 3.6: py36 3.7: py37 3.8: py38 3.9: py39 3.10: py310 3.11: py311 [testenv] setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes passenv = * usedevelop = false deps = pytest nose future enum-compat coverage commands = pytest -vv tests coverage run -m pytest tests [testenv:spell] setenv = SPELLCHECK=1 commands = sphinx-build -b spelling docs build/tox/docs skip_install = true deps = -r{toxinidir}/requirements-dev.txt sphinxcontrib-spelling pyenchant [testenv:docs] deps = -r{toxinidir}/requirements-dev.txt commands = sphinx-build {posargs:-E} -b doctest docs build/tox/docs sphinx-build {posargs:-E} -b html docs build/tox/docs sphinx-build -b linkcheck docs build/tox/docs [testenv:check] deps = docutils check-manifest flake8 readme-renderer pygments isort skip_install = true commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 src tests setup.py isort --verbose --check-only --diff src tests setup.py [testenv:report] deps = coverage skip_install = true commands = coverage combine coverage report coverage html [testenv:clean] commands = coverage erase skip_install = true deps = coverage