aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/asn1crypto
diff options
context:
space:
mode:
authorvitalyisaev <vitalyisaev@ydb.tech>2023-11-14 09:58:56 +0300
committervitalyisaev <vitalyisaev@ydb.tech>2023-11-14 10:20:20 +0300
commitc2b2dfd9827a400a8495e172a56343462e3ceb82 (patch)
treecd4e4f597d01bede4c82dffeb2d780d0a9046bd0 /contrib/python/asn1crypto
parentd4ae8f119e67808cb0cf776ba6e0cf95296f2df7 (diff)
downloadydb-c2b2dfd9827a400a8495e172a56343462e3ceb82.tar.gz
YQ Connector: move tests from yql to ydb (OSS)
Перенос папки с тестами на Коннектор из папки yql в папку ydb (синхронизируется с github).
Diffstat (limited to 'contrib/python/asn1crypto')
-rw-r--r--contrib/python/asn1crypto/py2/.dist-info/METADATA307
-rw-r--r--contrib/python/asn1crypto/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/asn1crypto/py2/LICENSE19
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/__init__.py47
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_errors.py54
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_inet.py170
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_int.py22
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_iri.py291
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_ordereddict.py135
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_teletex_codec.py331
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/_types.py46
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/algos.py1189
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/cms.py1003
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/core.py5676
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/crl.py536
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/csr.py133
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/keys.py1301
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/ocsp.py703
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/parser.py292
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/pdf.py84
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/pem.py222
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/pkcs12.py193
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/tsp.py310
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/util.py878
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/version.py6
-rw-r--r--contrib/python/asn1crypto/py2/asn1crypto/x509.py3036
-rw-r--r--contrib/python/asn1crypto/py2/readme.md273
-rw-r--r--contrib/python/asn1crypto/py2/ya.make44
-rw-r--r--contrib/python/asn1crypto/py3/.dist-info/METADATA307
-rw-r--r--contrib/python/asn1crypto/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/asn1crypto/py3/LICENSE19
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/__init__.py47
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_errors.py54
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_inet.py170
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_int.py22
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_iri.py291
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_ordereddict.py135
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_teletex_codec.py331
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/_types.py46
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/algos.py1189
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/cms.py1003
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/core.py5676
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/crl.py536
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/csr.py133
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/keys.py1301
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/ocsp.py703
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/parser.py292
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/pdf.py84
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/pem.py222
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/pkcs12.py193
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/tsp.py310
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/util.py878
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/version.py6
-rw-r--r--contrib/python/asn1crypto/py3/asn1crypto/x509.py3036
-rw-r--r--contrib/python/asn1crypto/py3/readme.md273
-rw-r--r--contrib/python/asn1crypto/py3/ya.make44
-rw-r--r--contrib/python/asn1crypto/ya.make18
57 files changed, 34622 insertions, 0 deletions
diff --git a/contrib/python/asn1crypto/py2/.dist-info/METADATA b/contrib/python/asn1crypto/py2/.dist-info/METADATA
new file mode 100644
index 0000000000..21f03e3326
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/.dist-info/METADATA
@@ -0,0 +1,307 @@
+Metadata-Version: 2.1
+Name: asn1crypto
+Version: 1.5.1
+Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP
+Home-page: https://github.com/wbond/asn1crypto
+Author: wbond
+Author-email: will@wbond.net
+License: MIT
+Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Security :: Cryptography
+Description-Content-Type: text/markdown
+
+# asn1crypto
+
+A fast, pure Python library for parsing and serializing ASN.1 structures.
+
+ - [Features](#features)
+ - [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
+ - [Related Crypto Libraries](#related-crypto-libraries)
+ - [Current Release](#current-release)
+ - [Dependencies](#dependencies)
+ - [Installation](#installation)
+ - [License](#license)
+ - [Security Policy](#security-policy)
+ - [Documentation](#documentation)
+ - [Continuous Integration](#continuous-integration)
+ - [Testing](#testing)
+ - [Development](#development)
+ - [CI Tasks](#ci-tasks)
+
+[![GitHub Actions CI](https://github.com/wbond/asn1crypto/workflows/CI/badge.svg)](https://github.com/wbond/asn1crypto/actions?workflow=CI)
+[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
+[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/)
+
+## Features
+
+In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
+a bunch of ASN.1 structures for use with various common cryptography standards:
+
+| Standard | Module | Source |
+| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
+| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
+| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
+| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
+| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
+| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
+| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
+| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
+| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
+| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
+| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
+| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
+
+## Why Another Python ASN.1 Library?
+
+Python has long had the [pyasn1](https://pypi.org/project/pyasn1/) and
+[pyasn1_modules](https://pypi.org/project/pyasn1-modules/) available for
+parsing and serializing ASN.1 structures. While the project does include a
+comprehensive set of tools for parsing and serializing, the performance of the
+library can be very poor, especially when dealing with bit fields and parsing
+large structures such as CRLs.
+
+After spending extensive time using *pyasn1*, the following issues were
+identified:
+
+ 1. Poor performance
+ 2. Verbose, non-pythonic API
+ 3. Out-dated and incomplete definitions in *pyasn1-modules*
+ 4. No simple way to map data to native Python data structures
+ 5. No mechanism for overridden universal ASN.1 types
+
+The *pyasn1* API is largely method driven, and uses extensive configuration
+objects and lowerCamelCase names. There were no consistent options for
+converting types of native Python data structures. Since the project supports
+out-dated versions of Python, many newer language features are unavailable
+for use.
+
+Time was spent trying to profile issues with the performance, however the
+architecture made it hard to pin down the primary source of the poor
+performance. Attempts were made to improve performance by utilizing unreleased
+patches and delaying parsing using the `Any` type. Even with such changes, the
+performance was still unacceptably slow.
+
+Finally, a number of structures in the cryptographic space use universal data
+types such as `BitString` and `OctetString`, but interpret the data as other
+types. For instance, signatures are really byte strings, but are encoded as
+`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
+represent integers. Parsing these structures as the base universal types and
+then re-interpreting them wastes computation.
+
+*asn1crypto* uses the following techniques to improve performance, especially
+when extracting one or two fields from large, complex structures:
+
+ - Delayed parsing of byte string values
+ - Persistence of original ASN.1 encoded data until a value is changed
+ - Lazy loading of child fields
+ - Utilization of high-level Python stdlib modules
+
+While there is no extensive performance test suite, the
+`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
+late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
+under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
+same parsing took over 4,100 seconds.
+
+For smaller structures the performance difference can range from a few times
+faster to an order of magnitude or more.
+
+## Related Crypto Libraries
+
+*asn1crypto* is part of the modularcrypto family of Python packages:
+
+ - [asn1crypto](https://github.com/wbond/asn1crypto)
+ - [oscrypto](https://github.com/wbond/oscrypto)
+ - [csrbuilder](https://github.com/wbond/csrbuilder)
+ - [certbuilder](https://github.com/wbond/certbuilder)
+ - [crlbuilder](https://github.com/wbond/crlbuilder)
+ - [ocspbuilder](https://github.com/wbond/ocspbuilder)
+ - [certvalidator](https://github.com/wbond/certvalidator)
+
+## Current Release
+
+1.5.0 - [changelog](changelog.md)
+
+## Dependencies
+
+Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
+packages required.*
+
+## Installation
+
+```bash
+pip install asn1crypto
+```
+
+## License
+
+*asn1crypto* is licensed under the terms of the MIT license. See the
+[LICENSE](LICENSE) file for the exact license text.
+
+## Security Policy
+
+The security policies for this project are covered in
+[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
+
+## Documentation
+
+The documentation for *asn1crypto* is composed of tutorials on basic usage and
+links to the source for the various pre-defined type classes.
+
+### Tutorials
+
+ - [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
+ - [PEM Encoder and Decoder](docs/pem.md)
+
+### Reference
+
+ - [Universal types](asn1crypto/core.py), `asn1crypto.core`
+ - [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
+ - [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
+ - [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
+ - [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
+ - [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
+ - [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
+ - [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
+ - [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
+ - [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
+ - [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
+
+## Continuous Integration
+
+Various combinations of platforms and versions of Python are tested via:
+
+ - [macOS, Linux, Windows](https://github.com/wbond/asn1crypto/actions/workflows/ci.yml) via GitHub Actions
+ - [arm64](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
+
+## Testing
+
+Tests are written using `unittest` and require no third-party packages.
+
+Depending on what type of source is available for the package, the following
+commands can be used to run the test suite.
+
+### Git Repository
+
+When working within a Git working copy, or an archive of the Git repository,
+the full test suite is run via:
+
+```bash
+python run.py tests
+```
+
+To run only some tests, pass a regular expression as a parameter to `tests`.
+
+```bash
+python run.py tests ocsp
+```
+
+### PyPi Source Distribution
+
+When working within an extracted source distribution (aka `.tar.gz`) from
+PyPi, the full test suite is run via:
+
+```bash
+python setup.py test
+```
+
+### Package
+
+When the package has been installed via pip (or another method), the package
+`asn1crypto_tests` may be installed and invoked to run the full test suite:
+
+```bash
+pip install asn1crypto_tests
+python -m asn1crypto_tests
+```
+
+## Development
+
+To install the package used for linting, execute:
+
+```bash
+pip install --user -r requires/lint
+```
+
+The following command will run the linter:
+
+```bash
+python run.py lint
+```
+
+Support for code coverage can be installed via:
+
+```bash
+pip install --user -r requires/coverage
+```
+
+Coverage is measured by running:
+
+```bash
+python run.py coverage
+```
+
+To change the version number of the package, run:
+
+```bash
+python run.py version {pep440_version}
+```
+
+To install the necessary packages for releasing a new version on PyPI, run:
+
+```bash
+pip install --user -r requires/release
+```
+
+Releases are created by:
+
+ - Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
+ - Running the command:
+
+ ```bash
+ python run.py release
+ ```
+
+Existing releases can be found at https://pypi.org/project/asn1crypto/.
+
+## CI Tasks
+
+A task named `deps` exists to download and stage all necessary testing
+dependencies. On posix platforms, `curl` is used for downloads and on Windows
+PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
+related to getting pip to work properly and messing with `site-packages` for
+the version of Python being used.
+
+The `ci` task runs `lint` (if flake8 is available for the version of Python) and
+`coverage` (or `tests` if coverage is not available for the version of Python).
+If the current directory is a clean git working copy, the coverage data is
+submitted to codecov.io.
+
+```bash
+python run.py deps
+python run.py ci
+```
+
+
diff --git a/contrib/python/asn1crypto/py2/.dist-info/top_level.txt b/contrib/python/asn1crypto/py2/.dist-info/top_level.txt
new file mode 100644
index 0000000000..35a704e46d
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+asn1crypto
diff --git a/contrib/python/asn1crypto/py2/LICENSE b/contrib/python/asn1crypto/py2/LICENSE
new file mode 100644
index 0000000000..07b49ae99b
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2022 Will Bond <will@wbond.net>
+
+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.
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/__init__.py b/contrib/python/asn1crypto/py2/asn1crypto/__init__.py
new file mode 100644
index 0000000000..2c93f00ebb
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/__init__.py
@@ -0,0 +1,47 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .version import __version__, __version_info__
+
+__all__ = [
+ '__version__',
+ '__version_info__',
+ 'load_order',
+]
+
+
+def load_order():
+ """
+ Returns a list of the module and sub-module names for asn1crypto in
+ dependency load order, for the sake of live reloading code
+
+ :return:
+ A list of unicode strings of module names, as they would appear in
+ sys.modules, ordered by which module should be reloaded first
+ """
+
+ return [
+ 'asn1crypto._errors',
+ 'asn1crypto._int',
+ 'asn1crypto._ordereddict',
+ 'asn1crypto._teletex_codec',
+ 'asn1crypto._types',
+ 'asn1crypto._inet',
+ 'asn1crypto._iri',
+ 'asn1crypto.version',
+ 'asn1crypto.pem',
+ 'asn1crypto.util',
+ 'asn1crypto.parser',
+ 'asn1crypto.core',
+ 'asn1crypto.algos',
+ 'asn1crypto.keys',
+ 'asn1crypto.x509',
+ 'asn1crypto.crl',
+ 'asn1crypto.csr',
+ 'asn1crypto.ocsp',
+ 'asn1crypto.cms',
+ 'asn1crypto.pdf',
+ 'asn1crypto.pkcs12',
+ 'asn1crypto.tsp',
+ 'asn1crypto',
+ ]
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_errors.py b/contrib/python/asn1crypto/py2/asn1crypto/_errors.py
new file mode 100644
index 0000000000..d8797a2fd1
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_errors.py
@@ -0,0 +1,54 @@
+# coding: utf-8
+
+"""
+Exports the following items:
+
+ - unwrap()
+ - APIException()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import re
+import textwrap
+
+
+class APIException(Exception):
+ """
+ An exception indicating an API has been removed from asn1crypto
+ """
+
+ pass
+
+
+def unwrap(string, *params):
+ """
+ Takes a multi-line string and does the following:
+
+ - dedents
+ - converts newlines with text before and after into a single line
+ - strips leading and trailing whitespace
+
+ :param string:
+ The string to format
+
+ :param *params:
+ Params to interpolate into the string
+
+ :return:
+ The formatted string
+ """
+
+ output = textwrap.dedent(string)
+
+ # Unwrap lines, taking into account bulleted lists, ordered lists and
+ # underlines consisting of = signs
+ if output.find('\n') != -1:
+ output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output)
+
+ if params:
+ output = output % params
+
+ output = output.strip()
+
+ return output
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_inet.py b/contrib/python/asn1crypto/py2/asn1crypto/_inet.py
new file mode 100644
index 0000000000..045ba561cc
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_inet.py
@@ -0,0 +1,170 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import socket
+import struct
+
+from ._errors import unwrap
+from ._types import byte_cls, bytes_to_list, str_cls, type_name
+
+
+def inet_ntop(address_family, packed_ip):
+ """
+ Windows compatibility shim for socket.inet_ntop().
+
+ :param address_family:
+ socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
+
+ :param packed_ip:
+ A byte string of the network form of an IP address
+
+ :return:
+ A unicode string of the IP address
+ """
+
+ if address_family not in set([socket.AF_INET, socket.AF_INET6]):
+ raise ValueError(unwrap(
+ '''
+ address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
+ not %s
+ ''',
+ repr(socket.AF_INET),
+ repr(socket.AF_INET6),
+ repr(address_family)
+ ))
+
+ if not isinstance(packed_ip, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ packed_ip must be a byte string, not %s
+ ''',
+ type_name(packed_ip)
+ ))
+
+ required_len = 4 if address_family == socket.AF_INET else 16
+ if len(packed_ip) != required_len:
+ raise ValueError(unwrap(
+ '''
+ packed_ip must be %d bytes long - is %d
+ ''',
+ required_len,
+ len(packed_ip)
+ ))
+
+ if address_family == socket.AF_INET:
+ return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip))
+
+ octets = struct.unpack(b'!HHHHHHHH', packed_ip)
+
+ runs_of_zero = {}
+ longest_run = 0
+ zero_index = None
+ for i, octet in enumerate(octets + (-1,)):
+ if octet != 0:
+ if zero_index is not None:
+ length = i - zero_index
+ if length not in runs_of_zero:
+ runs_of_zero[length] = zero_index
+ longest_run = max(longest_run, length)
+ zero_index = None
+ elif zero_index is None:
+ zero_index = i
+
+ hexed = [hex(o)[2:] for o in octets]
+
+ if longest_run < 2:
+ return ':'.join(hexed)
+
+ zero_start = runs_of_zero[longest_run]
+ zero_end = zero_start + longest_run
+
+ return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:])
+
+
+def inet_pton(address_family, ip_string):
+ """
+ Windows compatibility shim for socket.inet_ntop().
+
+ :param address_family:
+ socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
+
+ :param ip_string:
+ A unicode string of an IP address
+
+ :return:
+ A byte string of the network form of the IP address
+ """
+
+ if address_family not in set([socket.AF_INET, socket.AF_INET6]):
+ raise ValueError(unwrap(
+ '''
+ address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
+ not %s
+ ''',
+ repr(socket.AF_INET),
+ repr(socket.AF_INET6),
+ repr(address_family)
+ ))
+
+ if not isinstance(ip_string, str_cls):
+ raise TypeError(unwrap(
+ '''
+ ip_string must be a unicode string, not %s
+ ''',
+ type_name(ip_string)
+ ))
+
+ if address_family == socket.AF_INET:
+ octets = ip_string.split('.')
+ error = len(octets) != 4
+ if not error:
+ ints = []
+ for o in octets:
+ o = int(o)
+ if o > 255 or o < 0:
+ error = True
+ break
+ ints.append(o)
+
+ if error:
+ raise ValueError(unwrap(
+ '''
+ ip_string must be a dotted string with four integers in the
+ range of 0 to 255, got %s
+ ''',
+ repr(ip_string)
+ ))
+
+ return struct.pack(b'!BBBB', *ints)
+
+ error = False
+ omitted = ip_string.count('::')
+ if omitted > 1:
+ error = True
+ elif omitted == 0:
+ octets = ip_string.split(':')
+ error = len(octets) != 8
+ else:
+ begin, end = ip_string.split('::')
+ begin_octets = begin.split(':')
+ end_octets = end.split(':')
+ missing = 8 - len(begin_octets) - len(end_octets)
+ octets = begin_octets + (['0'] * missing) + end_octets
+
+ if not error:
+ ints = []
+ for o in octets:
+ o = int(o, 16)
+ if o > 65535 or o < 0:
+ error = True
+ break
+ ints.append(o)
+
+ return struct.pack(b'!HHHHHHHH', *ints)
+
+ raise ValueError(unwrap(
+ '''
+ ip_string must be a valid ipv6 string, got %s
+ ''',
+ repr(ip_string)
+ ))
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_int.py b/contrib/python/asn1crypto/py2/asn1crypto/_int.py
new file mode 100644
index 0000000000..094fc958da
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_int.py
@@ -0,0 +1,22 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+
+def fill_width(bytes_, width):
+ """
+ Ensure a byte string representing a positive integer is a specific width
+ (in bytes)
+
+ :param bytes_:
+ The integer byte string
+
+ :param width:
+ The desired width as an integer
+
+ :return:
+ A byte string of the width specified
+ """
+
+ while len(bytes_) < width:
+ bytes_ = b'\x00' + bytes_
+ return bytes_
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_iri.py b/contrib/python/asn1crypto/py2/asn1crypto/_iri.py
new file mode 100644
index 0000000000..7394b4d571
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_iri.py
@@ -0,0 +1,291 @@
+# coding: utf-8
+
+"""
+Functions to convert unicode IRIs into ASCII byte string URIs and back. Exports
+the following items:
+
+ - iri_to_uri()
+ - uri_to_iri()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from encodings import idna # noqa
+import codecs
+import re
+import sys
+
+from ._errors import unwrap
+from ._types import byte_cls, str_cls, type_name, bytes_to_list, int_types
+
+if sys.version_info < (3,):
+ from urlparse import urlsplit, urlunsplit
+ from urllib import (
+ quote as urlquote,
+ unquote as unquote_to_bytes,
+ )
+
+else:
+ from urllib.parse import (
+ quote as urlquote,
+ unquote_to_bytes,
+ urlsplit,
+ urlunsplit,
+ )
+
+
+def iri_to_uri(value, normalize=False):
+ """
+ Encodes a unicode IRI into an ASCII byte string URI
+
+ :param value:
+ A unicode string of an IRI
+
+ :param normalize:
+ A bool that controls URI normalization
+
+ :return:
+ A byte string of the ASCII-encoded URI
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ scheme = None
+ # Python 2.6 doesn't split properly is the URL doesn't start with http:// or https://
+ if sys.version_info < (2, 7) and not value.startswith('http://') and not value.startswith('https://'):
+ real_prefix = None
+ prefix_match = re.match('^[^:]*://', value)
+ if prefix_match:
+ real_prefix = prefix_match.group(0)
+ value = 'http://' + value[len(real_prefix):]
+ parsed = urlsplit(value)
+ if real_prefix:
+ value = real_prefix + value[7:]
+ scheme = _urlquote(real_prefix[:-3])
+ else:
+ parsed = urlsplit(value)
+
+ if scheme is None:
+ scheme = _urlquote(parsed.scheme)
+ hostname = parsed.hostname
+ if hostname is not None:
+ hostname = hostname.encode('idna')
+ # RFC 3986 allows userinfo to contain sub-delims
+ username = _urlquote(parsed.username, safe='!$&\'()*+,;=')
+ password = _urlquote(parsed.password, safe='!$&\'()*+,;=')
+ port = parsed.port
+ if port is not None:
+ port = str_cls(port).encode('ascii')
+
+ netloc = b''
+ if username is not None:
+ netloc += username
+ if password:
+ netloc += b':' + password
+ netloc += b'@'
+ if hostname is not None:
+ netloc += hostname
+ if port is not None:
+ default_http = scheme == b'http' and port == b'80'
+ default_https = scheme == b'https' and port == b'443'
+ if not normalize or (not default_http and not default_https):
+ netloc += b':' + port
+
+ # RFC 3986 allows a path to contain sub-delims, plus "@" and ":"
+ path = _urlquote(parsed.path, safe='/!$&\'()*+,;=@:')
+ # RFC 3986 allows the query to contain sub-delims, plus "@", ":" , "/" and "?"
+ query = _urlquote(parsed.query, safe='/?!$&\'()*+,;=@:')
+ # RFC 3986 allows the fragment to contain sub-delims, plus "@", ":" , "/" and "?"
+ fragment = _urlquote(parsed.fragment, safe='/?!$&\'()*+,;=@:')
+
+ if normalize and query is None and fragment is None and path == b'/':
+ path = None
+
+ # Python 2.7 compat
+ if path is None:
+ path = ''
+
+ output = urlunsplit((scheme, netloc, path, query, fragment))
+ if isinstance(output, str_cls):
+ output = output.encode('latin1')
+ return output
+
+
+def uri_to_iri(value):
+ """
+ Converts an ASCII URI byte string into a unicode IRI
+
+ :param value:
+ An ASCII-encoded byte string of the URI
+
+ :return:
+ A unicode string of the IRI
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a byte string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ parsed = urlsplit(value)
+
+ scheme = parsed.scheme
+ if scheme is not None:
+ scheme = scheme.decode('ascii')
+
+ username = _urlunquote(parsed.username, remap=[':', '@'])
+ password = _urlunquote(parsed.password, remap=[':', '@'])
+ hostname = parsed.hostname
+ if hostname:
+ hostname = hostname.decode('idna')
+ port = parsed.port
+ if port and not isinstance(port, int_types):
+ port = port.decode('ascii')
+
+ netloc = ''
+ if username is not None:
+ netloc += username
+ if password:
+ netloc += ':' + password
+ netloc += '@'
+ if hostname is not None:
+ netloc += hostname
+ if port is not None:
+ netloc += ':' + str_cls(port)
+
+ path = _urlunquote(parsed.path, remap=['/'], preserve=True)
+ query = _urlunquote(parsed.query, remap=['&', '='], preserve=True)
+ fragment = _urlunquote(parsed.fragment)
+
+ return urlunsplit((scheme, netloc, path, query, fragment))
+
+
+def _iri_utf8_errors_handler(exc):
+ """
+ Error handler for decoding UTF-8 parts of a URI into an IRI. Leaves byte
+ sequences encoded in %XX format, but as part of a unicode string.
+
+ :param exc:
+ The UnicodeDecodeError exception
+
+ :return:
+ A 2-element tuple of (replacement unicode string, integer index to
+ resume at)
+ """
+
+ bytes_as_ints = bytes_to_list(exc.object[exc.start:exc.end])
+ replacements = ['%%%02x' % num for num in bytes_as_ints]
+ return (''.join(replacements), exc.end)
+
+
+codecs.register_error('iriutf8', _iri_utf8_errors_handler)
+
+
+def _urlquote(string, safe=''):
+ """
+ Quotes a unicode string for use in a URL
+
+ :param string:
+ A unicode string
+
+ :param safe:
+ A unicode string of character to not encode
+
+ :return:
+ None (if string is None) or an ASCII byte string of the quoted string
+ """
+
+ if string is None or string == '':
+ return None
+
+ # Anything already hex quoted is pulled out of the URL and unquoted if
+ # possible
+ escapes = []
+ if re.search('%[0-9a-fA-F]{2}', string):
+ # Try to unquote any percent values, restoring them if they are not
+ # valid UTF-8. Also, requote any safe chars since encoded versions of
+ # those are functionally different than the unquoted ones.
+ def _try_unescape(match):
+ byte_string = unquote_to_bytes(match.group(0))
+ unicode_string = byte_string.decode('utf-8', 'iriutf8')
+ for safe_char in list(safe):
+ unicode_string = unicode_string.replace(safe_char, '%%%02x' % ord(safe_char))
+ return unicode_string
+ string = re.sub('(?:%[0-9a-fA-F]{2})+', _try_unescape, string)
+
+ # Once we have the minimal set of hex quoted values, removed them from
+ # the string so that they are not double quoted
+ def _extract_escape(match):
+ escapes.append(match.group(0).encode('ascii'))
+ return '\x00'
+ string = re.sub('%[0-9a-fA-F]{2}', _extract_escape, string)
+
+ output = urlquote(string.encode('utf-8'), safe=safe.encode('utf-8'))
+ if not isinstance(output, byte_cls):
+ output = output.encode('ascii')
+
+ # Restore the existing quoted values that we extracted
+ if len(escapes) > 0:
+ def _return_escape(_):
+ return escapes.pop(0)
+ output = re.sub(b'%00', _return_escape, output)
+
+ return output
+
+
+def _urlunquote(byte_string, remap=None, preserve=None):
+ """
+ Unquotes a URI portion from a byte string into unicode using UTF-8
+
+ :param byte_string:
+ A byte string of the data to unquote
+
+ :param remap:
+ A list of characters (as unicode) that should be re-mapped to a
+ %XX encoding. This is used when characters are not valid in part of a
+ URL.
+
+ :param preserve:
+ A bool - indicates that the chars to be remapped if they occur in
+ non-hex form, should be preserved. E.g. / for URL path.
+
+ :return:
+ A unicode string
+ """
+
+ if byte_string is None:
+ return byte_string
+
+ if byte_string == b'':
+ return ''
+
+ if preserve:
+ replacements = ['\x1A', '\x1C', '\x1D', '\x1E', '\x1F']
+ preserve_unmap = {}
+ for char in remap:
+ replacement = replacements.pop(0)
+ preserve_unmap[replacement] = char
+ byte_string = byte_string.replace(char.encode('ascii'), replacement.encode('ascii'))
+
+ byte_string = unquote_to_bytes(byte_string)
+
+ if remap:
+ for char in remap:
+ byte_string = byte_string.replace(char.encode('ascii'), ('%%%02x' % ord(char)).encode('ascii'))
+
+ output = byte_string.decode('utf-8', 'iriutf8')
+
+ if preserve:
+ for replacement, original in preserve_unmap.items():
+ output = output.replace(replacement, original)
+
+ return output
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_ordereddict.py b/contrib/python/asn1crypto/py2/asn1crypto/_ordereddict.py
new file mode 100644
index 0000000000..2f18ab5ae9
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_ordereddict.py
@@ -0,0 +1,135 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# 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.
+
+import sys
+
+if not sys.version_info < (2, 7):
+
+ from collections import OrderedDict
+
+else:
+
+ from UserDict import DictMixin
+
+ class OrderedDict(dict, DictMixin):
+
+ def __init__(self, *args, **kwds):
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__end
+ except AttributeError:
+ self.clear()
+ self.update(*args, **kwds)
+
+ def clear(self):
+ self.__end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.__map = {} # key --> [key, prev, next]
+ dict.clear(self)
+
+ def __setitem__(self, key, value):
+ if key not in self:
+ end = self.__end
+ curr = end[1]
+ curr[2] = end[1] = self.__map[key] = [key, curr, end]
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ key, prev, next_ = self.__map.pop(key)
+ prev[2] = next_
+ next_[1] = prev
+
+ def __iter__(self):
+ end = self.__end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.__end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def popitem(self, last=True):
+ if not self:
+ raise KeyError('dictionary is empty')
+ if last:
+ key = reversed(self).next()
+ else:
+ key = iter(self).next()
+ value = self.pop(key)
+ return key, value
+
+ def __reduce__(self):
+ items = [[k, self[k]] for k in self]
+ tmp = self.__map, self.__end
+ del self.__map, self.__end
+ inst_dict = vars(self).copy()
+ self.__map, self.__end = tmp
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def keys(self):
+ return list(self)
+
+ setdefault = DictMixin.setdefault
+ update = DictMixin.update
+ pop = DictMixin.pop
+ values = DictMixin.values
+ items = DictMixin.items
+ iterkeys = DictMixin.iterkeys
+ itervalues = DictMixin.itervalues
+ iteritems = DictMixin.iteritems
+
+ def __repr__(self):
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, self.items())
+
+ def copy(self):
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedDict):
+ if len(self) != len(other):
+ return False
+ for p, q in zip(self.items(), other.items()):
+ if p != q:
+ return False
+ return True
+ return dict.__eq__(self, other)
+
+ def __ne__(self, other):
+ return not self == other
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_teletex_codec.py b/contrib/python/asn1crypto/py2/asn1crypto/_teletex_codec.py
new file mode 100644
index 0000000000..b5991aaf1d
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_teletex_codec.py
@@ -0,0 +1,331 @@
+# coding: utf-8
+
+"""
+Implementation of the teletex T.61 codec. Exports the following items:
+
+ - register()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import codecs
+
+
+class TeletexCodec(codecs.Codec):
+
+ def encode(self, input_, errors='strict'):
+ return codecs.charmap_encode(input_, errors, ENCODING_TABLE)
+
+ def decode(self, input_, errors='strict'):
+ return codecs.charmap_decode(input_, errors, DECODING_TABLE)
+
+
+class TeletexIncrementalEncoder(codecs.IncrementalEncoder):
+
+ def encode(self, input_, final=False):
+ return codecs.charmap_encode(input_, self.errors, ENCODING_TABLE)[0]
+
+
+class TeletexIncrementalDecoder(codecs.IncrementalDecoder):
+
+ def decode(self, input_, final=False):
+ return codecs.charmap_decode(input_, self.errors, DECODING_TABLE)[0]
+
+
+class TeletexStreamWriter(TeletexCodec, codecs.StreamWriter):
+
+ pass
+
+
+class TeletexStreamReader(TeletexCodec, codecs.StreamReader):
+
+ pass
+
+
+def teletex_search_function(name):
+ """
+ Search function for teletex codec that is passed to codecs.register()
+ """
+
+ if name != 'teletex':
+ return None
+
+ return codecs.CodecInfo(
+ name='teletex',
+ encode=TeletexCodec().encode,
+ decode=TeletexCodec().decode,
+ incrementalencoder=TeletexIncrementalEncoder,
+ incrementaldecoder=TeletexIncrementalDecoder,
+ streamreader=TeletexStreamReader,
+ streamwriter=TeletexStreamWriter,
+ )
+
+
+def register():
+ """
+ Registers the teletex codec
+ """
+
+ codecs.register(teletex_search_function)
+
+
+# http://en.wikipedia.org/wiki/ITU_T.61
+DECODING_TABLE = (
+ '\u0000'
+ '\u0001'
+ '\u0002'
+ '\u0003'
+ '\u0004'
+ '\u0005'
+ '\u0006'
+ '\u0007'
+ '\u0008'
+ '\u0009'
+ '\u000A'
+ '\u000B'
+ '\u000C'
+ '\u000D'
+ '\u000E'
+ '\u000F'
+ '\u0010'
+ '\u0011'
+ '\u0012'
+ '\u0013'
+ '\u0014'
+ '\u0015'
+ '\u0016'
+ '\u0017'
+ '\u0018'
+ '\u0019'
+ '\u001A'
+ '\u001B'
+ '\u001C'
+ '\u001D'
+ '\u001E'
+ '\u001F'
+ '\u0020'
+ '\u0021'
+ '\u0022'
+ '\ufffe'
+ '\ufffe'
+ '\u0025'
+ '\u0026'
+ '\u0027'
+ '\u0028'
+ '\u0029'
+ '\u002A'
+ '\u002B'
+ '\u002C'
+ '\u002D'
+ '\u002E'
+ '\u002F'
+ '\u0030'
+ '\u0031'
+ '\u0032'
+ '\u0033'
+ '\u0034'
+ '\u0035'
+ '\u0036'
+ '\u0037'
+ '\u0038'
+ '\u0039'
+ '\u003A'
+ '\u003B'
+ '\u003C'
+ '\u003D'
+ '\u003E'
+ '\u003F'
+ '\u0040'
+ '\u0041'
+ '\u0042'
+ '\u0043'
+ '\u0044'
+ '\u0045'
+ '\u0046'
+ '\u0047'
+ '\u0048'
+ '\u0049'
+ '\u004A'
+ '\u004B'
+ '\u004C'
+ '\u004D'
+ '\u004E'
+ '\u004F'
+ '\u0050'
+ '\u0051'
+ '\u0052'
+ '\u0053'
+ '\u0054'
+ '\u0055'
+ '\u0056'
+ '\u0057'
+ '\u0058'
+ '\u0059'
+ '\u005A'
+ '\u005B'
+ '\ufffe'
+ '\u005D'
+ '\ufffe'
+ '\u005F'
+ '\ufffe'
+ '\u0061'
+ '\u0062'
+ '\u0063'
+ '\u0064'
+ '\u0065'
+ '\u0066'
+ '\u0067'
+ '\u0068'
+ '\u0069'
+ '\u006A'
+ '\u006B'
+ '\u006C'
+ '\u006D'
+ '\u006E'
+ '\u006F'
+ '\u0070'
+ '\u0071'
+ '\u0072'
+ '\u0073'
+ '\u0074'
+ '\u0075'
+ '\u0076'
+ '\u0077'
+ '\u0078'
+ '\u0079'
+ '\u007A'
+ '\ufffe'
+ '\u007C'
+ '\ufffe'
+ '\ufffe'
+ '\u007F'
+ '\u0080'
+ '\u0081'
+ '\u0082'
+ '\u0083'
+ '\u0084'
+ '\u0085'
+ '\u0086'
+ '\u0087'
+ '\u0088'
+ '\u0089'
+ '\u008A'
+ '\u008B'
+ '\u008C'
+ '\u008D'
+ '\u008E'
+ '\u008F'
+ '\u0090'
+ '\u0091'
+ '\u0092'
+ '\u0093'
+ '\u0094'
+ '\u0095'
+ '\u0096'
+ '\u0097'
+ '\u0098'
+ '\u0099'
+ '\u009A'
+ '\u009B'
+ '\u009C'
+ '\u009D'
+ '\u009E'
+ '\u009F'
+ '\u00A0'
+ '\u00A1'
+ '\u00A2'
+ '\u00A3'
+ '\u0024'
+ '\u00A5'
+ '\u0023'
+ '\u00A7'
+ '\u00A4'
+ '\ufffe'
+ '\ufffe'
+ '\u00AB'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\u00B0'
+ '\u00B1'
+ '\u00B2'
+ '\u00B3'
+ '\u00D7'
+ '\u00B5'
+ '\u00B6'
+ '\u00B7'
+ '\u00F7'
+ '\ufffe'
+ '\ufffe'
+ '\u00BB'
+ '\u00BC'
+ '\u00BD'
+ '\u00BE'
+ '\u00BF'
+ '\ufffe'
+ '\u0300'
+ '\u0301'
+ '\u0302'
+ '\u0303'
+ '\u0304'
+ '\u0306'
+ '\u0307'
+ '\u0308'
+ '\ufffe'
+ '\u030A'
+ '\u0327'
+ '\u0332'
+ '\u030B'
+ '\u0328'
+ '\u030C'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\u2126'
+ '\u00C6'
+ '\u00D0'
+ '\u00AA'
+ '\u0126'
+ '\ufffe'
+ '\u0132'
+ '\u013F'
+ '\u0141'
+ '\u00D8'
+ '\u0152'
+ '\u00BA'
+ '\u00DE'
+ '\u0166'
+ '\u014A'
+ '\u0149'
+ '\u0138'
+ '\u00E6'
+ '\u0111'
+ '\u00F0'
+ '\u0127'
+ '\u0131'
+ '\u0133'
+ '\u0140'
+ '\u0142'
+ '\u00F8'
+ '\u0153'
+ '\u00DF'
+ '\u00FE'
+ '\u0167'
+ '\u014B'
+ '\ufffe'
+)
+ENCODING_TABLE = codecs.charmap_build(DECODING_TABLE)
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/_types.py b/contrib/python/asn1crypto/py2/asn1crypto/_types.py
new file mode 100644
index 0000000000..b9ca8cc79b
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/_types.py
@@ -0,0 +1,46 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import inspect
+import sys
+
+
+if sys.version_info < (3,):
+ str_cls = unicode # noqa
+ byte_cls = str
+ int_types = (int, long) # noqa
+
+ def bytes_to_list(byte_string):
+ return [ord(b) for b in byte_string]
+
+ chr_cls = chr
+
+else:
+ str_cls = str
+ byte_cls = bytes
+ int_types = int
+
+ bytes_to_list = list
+
+ def chr_cls(num):
+ return bytes([num])
+
+
+def type_name(value):
+ """
+ Returns a user-readable name for the type of an object
+
+ :param value:
+ A value to get the type name of
+
+ :return:
+ A unicode string of the object's type name
+ """
+
+ if inspect.isclass(value):
+ cls = value
+ else:
+ cls = value.__class__
+ if cls.__module__ in set(['builtins', '__builtin__']):
+ return cls.__name__
+ return '%s.%s' % (cls.__module__, cls.__name__)
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/algos.py b/contrib/python/asn1crypto/py2/asn1crypto/algos.py
new file mode 100644
index 0000000000..cdd0020a32
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/algos.py
@@ -0,0 +1,1189 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for various algorithms using in various aspects of public
+key cryptography. Exports the following items:
+
+ - AlgorithmIdentifier()
+ - AnyAlgorithmIdentifier()
+ - DigestAlgorithm()
+ - DigestInfo()
+ - DSASignature()
+ - EncryptionAlgorithm()
+ - HmacAlgorithm()
+ - KdfAlgorithm()
+ - Pkcs5MacAlgorithm()
+ - SignedDigestAlgorithm()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ._errors import unwrap
+from ._int import fill_width
+from .util import int_from_bytes, int_to_bytes
+from .core import (
+ Any,
+ Choice,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ Void,
+)
+
+
+# Structures and OIDs in this file are pulled from
+# https://tools.ietf.org/html/rfc3279, https://tools.ietf.org/html/rfc4055,
+# https://tools.ietf.org/html/rfc5758, https://tools.ietf.org/html/rfc7292,
+# http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
+
+class AlgorithmIdentifier(Sequence):
+ _fields = [
+ ('algorithm', ObjectIdentifier),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class _ForceNullParameters(object):
+ """
+ Various structures based on AlgorithmIdentifier require that the parameters
+ field be core.Null() for certain OIDs. This mixin ensures that happens.
+ """
+
+ # The following attribute, plus the parameters spec callback and custom
+ # __setitem__ are all to handle a situation where parameters should not be
+ # optional and must be Null for certain OIDs. More info at
+ # https://tools.ietf.org/html/rfc4055#page-15 and
+ # https://tools.ietf.org/html/rfc4055#section-2.1
+ _null_algos = set([
+ '1.2.840.113549.1.1.1', # rsassa_pkcs1v15 / rsaes_pkcs1v15 / rsa
+ '1.2.840.113549.1.1.11', # sha256_rsa
+ '1.2.840.113549.1.1.12', # sha384_rsa
+ '1.2.840.113549.1.1.13', # sha512_rsa
+ '1.2.840.113549.1.1.14', # sha224_rsa
+ '1.3.14.3.2.26', # sha1
+ '2.16.840.1.101.3.4.2.4', # sha224
+ '2.16.840.1.101.3.4.2.1', # sha256
+ '2.16.840.1.101.3.4.2.2', # sha384
+ '2.16.840.1.101.3.4.2.3', # sha512
+ ])
+
+ def _parameters_spec(self):
+ if self._oid_pair == ('algorithm', 'parameters'):
+ algo = self['algorithm'].native
+ if algo in self._oid_specs:
+ return self._oid_specs[algo]
+
+ if self['algorithm'].dotted in self._null_algos:
+ return Null
+
+ return None
+
+ _spec_callbacks = {
+ 'parameters': _parameters_spec
+ }
+
+ # We have to override this since the spec callback uses the value of
+ # algorithm to determine the parameter spec, however default values are
+ # assigned before setting a field, so a default value can't be based on
+ # another field value (unless it is a default also). Thus we have to
+ # manually check to see if the algorithm was set and parameters is unset,
+ # and then fix the value as appropriate.
+ def __setitem__(self, key, value):
+ res = super(_ForceNullParameters, self).__setitem__(key, value)
+ if key != 'algorithm':
+ return res
+ if self['algorithm'].dotted not in self._null_algos:
+ return res
+ if self['parameters'].__class__ != Void:
+ return res
+ self['parameters'] = Null()
+ return res
+
+
+class HmacAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.10': 'des_mac',
+ '1.2.840.113549.2.7': 'sha1',
+ '1.2.840.113549.2.8': 'sha224',
+ '1.2.840.113549.2.9': 'sha256',
+ '1.2.840.113549.2.10': 'sha384',
+ '1.2.840.113549.2.11': 'sha512',
+ '1.2.840.113549.2.12': 'sha512_224',
+ '1.2.840.113549.2.13': 'sha512_256',
+ '2.16.840.1.101.3.4.2.13': 'sha3_224',
+ '2.16.840.1.101.3.4.2.14': 'sha3_256',
+ '2.16.840.1.101.3.4.2.15': 'sha3_384',
+ '2.16.840.1.101.3.4.2.16': 'sha3_512',
+ }
+
+
+class HmacAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', HmacAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class DigestAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.2.2': 'md2',
+ '1.2.840.113549.2.5': 'md5',
+ '1.3.14.3.2.26': 'sha1',
+ '2.16.840.1.101.3.4.2.4': 'sha224',
+ '2.16.840.1.101.3.4.2.1': 'sha256',
+ '2.16.840.1.101.3.4.2.2': 'sha384',
+ '2.16.840.1.101.3.4.2.3': 'sha512',
+ '2.16.840.1.101.3.4.2.5': 'sha512_224',
+ '2.16.840.1.101.3.4.2.6': 'sha512_256',
+ '2.16.840.1.101.3.4.2.7': 'sha3_224',
+ '2.16.840.1.101.3.4.2.8': 'sha3_256',
+ '2.16.840.1.101.3.4.2.9': 'sha3_384',
+ '2.16.840.1.101.3.4.2.10': 'sha3_512',
+ '2.16.840.1.101.3.4.2.11': 'shake128',
+ '2.16.840.1.101.3.4.2.12': 'shake256',
+ '2.16.840.1.101.3.4.2.17': 'shake128_len',
+ '2.16.840.1.101.3.4.2.18': 'shake256_len',
+ }
+
+
+class DigestAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', DigestAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+# This structure is what is signed with a SignedDigestAlgorithm
+class DigestInfo(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm),
+ ('digest', OctetString),
+ ]
+
+
+class MaskGenAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.8': 'mgf1',
+ }
+
+
+class MaskGenAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', MaskGenAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'mgf1': DigestAlgorithm
+ }
+
+
+class TrailerField(Integer):
+ _map = {
+ 1: 'trailer_field_bc',
+ }
+
+
+class RSASSAPSSParams(Sequence):
+ _fields = [
+ (
+ 'hash_algorithm',
+ DigestAlgorithm,
+ {
+ 'explicit': 0,
+ 'default': {'algorithm': 'sha1'},
+ }
+ ),
+ (
+ 'mask_gen_algorithm',
+ MaskGenAlgorithm,
+ {
+ 'explicit': 1,
+ 'default': {
+ 'algorithm': 'mgf1',
+ 'parameters': {'algorithm': 'sha1'},
+ },
+ }
+ ),
+ (
+ 'salt_length',
+ Integer,
+ {
+ 'explicit': 2,
+ 'default': 20,
+ }
+ ),
+ (
+ 'trailer_field',
+ TrailerField,
+ {
+ 'explicit': 3,
+ 'default': 'trailer_field_bc',
+ }
+ ),
+ ]
+
+
+class SignedDigestAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.3': 'md5_rsa',
+ '1.3.14.3.2.29': 'sha1_rsa',
+ '1.3.14.7.2.3.1': 'md2_rsa',
+ '1.2.840.113549.1.1.2': 'md2_rsa',
+ '1.2.840.113549.1.1.4': 'md5_rsa',
+ '1.2.840.113549.1.1.5': 'sha1_rsa',
+ '1.2.840.113549.1.1.14': 'sha224_rsa',
+ '1.2.840.113549.1.1.11': 'sha256_rsa',
+ '1.2.840.113549.1.1.12': 'sha384_rsa',
+ '1.2.840.113549.1.1.13': 'sha512_rsa',
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ '1.2.840.10040.4.3': 'sha1_dsa',
+ '1.3.14.3.2.13': 'sha1_dsa',
+ '1.3.14.3.2.27': 'sha1_dsa',
+ '2.16.840.1.101.3.4.3.1': 'sha224_dsa',
+ '2.16.840.1.101.3.4.3.2': 'sha256_dsa',
+ '1.2.840.10045.4.1': 'sha1_ecdsa',
+ '1.2.840.10045.4.3.1': 'sha224_ecdsa',
+ '1.2.840.10045.4.3.2': 'sha256_ecdsa',
+ '1.2.840.10045.4.3.3': 'sha384_ecdsa',
+ '1.2.840.10045.4.3.4': 'sha512_ecdsa',
+ '2.16.840.1.101.3.4.3.9': 'sha3_224_ecdsa',
+ '2.16.840.1.101.3.4.3.10': 'sha3_256_ecdsa',
+ '2.16.840.1.101.3.4.3.11': 'sha3_384_ecdsa',
+ '2.16.840.1.101.3.4.3.12': 'sha3_512_ecdsa',
+ # For when the digest is specified elsewhere in a Sequence
+ '1.2.840.113549.1.1.1': 'rsassa_pkcs1v15',
+ '1.2.840.10040.4.1': 'dsa',
+ '1.2.840.10045.4': 'ecdsa',
+ # RFC 8410 -- https://tools.ietf.org/html/rfc8410
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+ _reverse_map = {
+ 'dsa': '1.2.840.10040.4.1',
+ 'ecdsa': '1.2.840.10045.4',
+ 'md2_rsa': '1.2.840.113549.1.1.2',
+ 'md5_rsa': '1.2.840.113549.1.1.4',
+ 'rsassa_pkcs1v15': '1.2.840.113549.1.1.1',
+ 'rsassa_pss': '1.2.840.113549.1.1.10',
+ 'sha1_dsa': '1.2.840.10040.4.3',
+ 'sha1_ecdsa': '1.2.840.10045.4.1',
+ 'sha1_rsa': '1.2.840.113549.1.1.5',
+ 'sha224_dsa': '2.16.840.1.101.3.4.3.1',
+ 'sha224_ecdsa': '1.2.840.10045.4.3.1',
+ 'sha224_rsa': '1.2.840.113549.1.1.14',
+ 'sha256_dsa': '2.16.840.1.101.3.4.3.2',
+ 'sha256_ecdsa': '1.2.840.10045.4.3.2',
+ 'sha256_rsa': '1.2.840.113549.1.1.11',
+ 'sha384_ecdsa': '1.2.840.10045.4.3.3',
+ 'sha384_rsa': '1.2.840.113549.1.1.12',
+ 'sha512_ecdsa': '1.2.840.10045.4.3.4',
+ 'sha512_rsa': '1.2.840.113549.1.1.13',
+ 'sha3_224_ecdsa': '2.16.840.1.101.3.4.3.9',
+ 'sha3_256_ecdsa': '2.16.840.1.101.3.4.3.10',
+ 'sha3_384_ecdsa': '2.16.840.1.101.3.4.3.11',
+ 'sha3_512_ecdsa': '2.16.840.1.101.3.4.3.12',
+ 'ed25519': '1.3.101.112',
+ 'ed448': '1.3.101.113',
+ }
+
+
+class SignedDigestAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', SignedDigestAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+ @property
+ def signature_algo(self):
+ """
+ :return:
+ A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa",
+ "ecdsa", "ed25519" or "ed448"
+ """
+
+ algorithm = self['algorithm'].native
+
+ algo_map = {
+ 'md2_rsa': 'rsassa_pkcs1v15',
+ 'md5_rsa': 'rsassa_pkcs1v15',
+ 'sha1_rsa': 'rsassa_pkcs1v15',
+ 'sha224_rsa': 'rsassa_pkcs1v15',
+ 'sha256_rsa': 'rsassa_pkcs1v15',
+ 'sha384_rsa': 'rsassa_pkcs1v15',
+ 'sha512_rsa': 'rsassa_pkcs1v15',
+ 'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
+ 'rsassa_pss': 'rsassa_pss',
+ 'sha1_dsa': 'dsa',
+ 'sha224_dsa': 'dsa',
+ 'sha256_dsa': 'dsa',
+ 'dsa': 'dsa',
+ 'sha1_ecdsa': 'ecdsa',
+ 'sha224_ecdsa': 'ecdsa',
+ 'sha256_ecdsa': 'ecdsa',
+ 'sha384_ecdsa': 'ecdsa',
+ 'sha512_ecdsa': 'ecdsa',
+ 'sha3_224_ecdsa': 'ecdsa',
+ 'sha3_256_ecdsa': 'ecdsa',
+ 'sha3_384_ecdsa': 'ecdsa',
+ 'sha3_512_ecdsa': 'ecdsa',
+ 'ecdsa': 'ecdsa',
+ 'ed25519': 'ed25519',
+ 'ed448': 'ed448',
+ }
+ if algorithm in algo_map:
+ return algo_map[algorithm]
+
+ raise ValueError(unwrap(
+ '''
+ Signature algorithm not known for %s
+ ''',
+ algorithm
+ ))
+
+ @property
+ def hash_algo(self):
+ """
+ :return:
+ A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
+ "sha384", "sha512", "sha512_224", "sha512_256" or "shake256"
+ """
+
+ algorithm = self['algorithm'].native
+
+ algo_map = {
+ 'md2_rsa': 'md2',
+ 'md5_rsa': 'md5',
+ 'sha1_rsa': 'sha1',
+ 'sha224_rsa': 'sha224',
+ 'sha256_rsa': 'sha256',
+ 'sha384_rsa': 'sha384',
+ 'sha512_rsa': 'sha512',
+ 'sha1_dsa': 'sha1',
+ 'sha224_dsa': 'sha224',
+ 'sha256_dsa': 'sha256',
+ 'sha1_ecdsa': 'sha1',
+ 'sha224_ecdsa': 'sha224',
+ 'sha256_ecdsa': 'sha256',
+ 'sha384_ecdsa': 'sha384',
+ 'sha512_ecdsa': 'sha512',
+ 'ed25519': 'sha512',
+ 'ed448': 'shake256',
+ }
+ if algorithm in algo_map:
+ return algo_map[algorithm]
+
+ if algorithm == 'rsassa_pss':
+ return self['parameters']['hash_algorithm']['algorithm'].native
+
+ raise ValueError(unwrap(
+ '''
+ Hash algorithm not known for %s
+ ''',
+ algorithm
+ ))
+
+
+class Pbkdf2Salt(Choice):
+ _alternatives = [
+ ('specified', OctetString),
+ ('other_source', AlgorithmIdentifier),
+ ]
+
+
+class Pbkdf2Params(Sequence):
+ _fields = [
+ ('salt', Pbkdf2Salt),
+ ('iteration_count', Integer),
+ ('key_length', Integer, {'optional': True}),
+ ('prf', HmacAlgorithm, {'default': {'algorithm': 'sha1'}}),
+ ]
+
+
+class KdfAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.5.12': 'pbkdf2'
+ }
+
+
+class KdfAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', KdfAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'pbkdf2': Pbkdf2Params
+ }
+
+
+class DHParameters(Sequence):
+ """
+ Original Name: DHParameter
+ Source: ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc section 9
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('g', Integer),
+ ('private_value_length', Integer, {'optional': True}),
+ ]
+
+
+class KeyExchangeAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.3.1': 'dh',
+ }
+
+
+class KeyExchangeAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', KeyExchangeAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dh': DHParameters,
+ }
+
+
+class Rc2Params(Sequence):
+ _fields = [
+ ('rc2_parameter_version', Integer, {'optional': True}),
+ ('iv', OctetString),
+ ]
+
+
+class Rc5ParamVersion(Integer):
+ _map = {
+ 16: 'v1-0'
+ }
+
+
+class Rc5Params(Sequence):
+ _fields = [
+ ('version', Rc5ParamVersion),
+ ('rounds', Integer),
+ ('block_size_in_bits', Integer),
+ ('iv', OctetString, {'optional': True}),
+ ]
+
+
+class Pbes1Params(Sequence):
+ _fields = [
+ ('salt', OctetString),
+ ('iterations', Integer),
+ ]
+
+
+class CcmParams(Sequence):
+ # https://tools.ietf.org/html/rfc5084
+ # aes_ICVlen: 4 | 6 | 8 | 10 | 12 | 14 | 16
+ _fields = [
+ ('aes_nonce', OctetString),
+ ('aes_icvlen', Integer),
+ ]
+
+
+class PSourceAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.9': 'p_specified',
+ }
+
+
+class PSourceAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', PSourceAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'p_specified': OctetString
+ }
+
+
+class RSAESOAEPParams(Sequence):
+ _fields = [
+ (
+ 'hash_algorithm',
+ DigestAlgorithm,
+ {
+ 'explicit': 0,
+ 'default': {'algorithm': 'sha1'}
+ }
+ ),
+ (
+ 'mask_gen_algorithm',
+ MaskGenAlgorithm,
+ {
+ 'explicit': 1,
+ 'default': {
+ 'algorithm': 'mgf1',
+ 'parameters': {'algorithm': 'sha1'}
+ }
+ }
+ ),
+ (
+ 'p_source_algorithm',
+ PSourceAlgorithm,
+ {
+ 'explicit': 2,
+ 'default': {
+ 'algorithm': 'p_specified',
+ 'parameters': b''
+ }
+ }
+ ),
+ ]
+
+
+class DSASignature(Sequence):
+ """
+ An ASN.1 class for translating between the OS crypto library's
+ representation of an (EC)DSA signature and the ASN.1 structure that is part
+ of various RFCs.
+
+ Original Name: DSS-Sig-Value
+ Source: https://tools.ietf.org/html/rfc3279#section-2.2.2
+ """
+
+ _fields = [
+ ('r', Integer),
+ ('s', Integer),
+ ]
+
+ @classmethod
+ def from_p1363(cls, data):
+ """
+ Reads a signature from a byte string encoding accordint to IEEE P1363,
+ which is used by Microsoft's BCryptSignHash() function.
+
+ :param data:
+ A byte string from BCryptSignHash()
+
+ :return:
+ A DSASignature object
+ """
+
+ r = int_from_bytes(data[0:len(data) // 2])
+ s = int_from_bytes(data[len(data) // 2:])
+ return cls({'r': r, 's': s})
+
+ def to_p1363(self):
+ """
+ Dumps a signature to a byte string compatible with Microsoft's
+ BCryptVerifySignature() function.
+
+ :return:
+ A byte string compatible with BCryptVerifySignature()
+ """
+
+ r_bytes = int_to_bytes(self['r'].native)
+ s_bytes = int_to_bytes(self['s'].native)
+
+ int_byte_length = max(len(r_bytes), len(s_bytes))
+ r_bytes = fill_width(r_bytes, int_byte_length)
+ s_bytes = fill_width(s_bytes, int_byte_length)
+
+ return r_bytes + s_bytes
+
+
+class EncryptionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.7': 'des',
+ '1.2.840.113549.3.7': 'tripledes_3key',
+ '1.2.840.113549.3.2': 'rc2',
+ '1.2.840.113549.3.4': 'rc4',
+ '1.2.840.113549.3.9': 'rc5',
+ # From http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html#AES
+ '2.16.840.1.101.3.4.1.1': 'aes128_ecb',
+ '2.16.840.1.101.3.4.1.2': 'aes128_cbc',
+ '2.16.840.1.101.3.4.1.3': 'aes128_ofb',
+ '2.16.840.1.101.3.4.1.4': 'aes128_cfb',
+ '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
+ '2.16.840.1.101.3.4.1.6': 'aes128_gcm',
+ '2.16.840.1.101.3.4.1.7': 'aes128_ccm',
+ '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
+ '2.16.840.1.101.3.4.1.21': 'aes192_ecb',
+ '2.16.840.1.101.3.4.1.22': 'aes192_cbc',
+ '2.16.840.1.101.3.4.1.23': 'aes192_ofb',
+ '2.16.840.1.101.3.4.1.24': 'aes192_cfb',
+ '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
+ '2.16.840.1.101.3.4.1.26': 'aes192_gcm',
+ '2.16.840.1.101.3.4.1.27': 'aes192_ccm',
+ '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
+ '2.16.840.1.101.3.4.1.41': 'aes256_ecb',
+ '2.16.840.1.101.3.4.1.42': 'aes256_cbc',
+ '2.16.840.1.101.3.4.1.43': 'aes256_ofb',
+ '2.16.840.1.101.3.4.1.44': 'aes256_cfb',
+ '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
+ '2.16.840.1.101.3.4.1.46': 'aes256_gcm',
+ '2.16.840.1.101.3.4.1.47': 'aes256_ccm',
+ '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
+ # From PKCS#5
+ '1.2.840.113549.1.5.13': 'pbes2',
+ '1.2.840.113549.1.5.1': 'pbes1_md2_des',
+ '1.2.840.113549.1.5.3': 'pbes1_md5_des',
+ '1.2.840.113549.1.5.4': 'pbes1_md2_rc2',
+ '1.2.840.113549.1.5.6': 'pbes1_md5_rc2',
+ '1.2.840.113549.1.5.10': 'pbes1_sha1_des',
+ '1.2.840.113549.1.5.11': 'pbes1_sha1_rc2',
+ # From PKCS#12
+ '1.2.840.113549.1.12.1.1': 'pkcs12_sha1_rc4_128',
+ '1.2.840.113549.1.12.1.2': 'pkcs12_sha1_rc4_40',
+ '1.2.840.113549.1.12.1.3': 'pkcs12_sha1_tripledes_3key',
+ '1.2.840.113549.1.12.1.4': 'pkcs12_sha1_tripledes_2key',
+ '1.2.840.113549.1.12.1.5': 'pkcs12_sha1_rc2_128',
+ '1.2.840.113549.1.12.1.6': 'pkcs12_sha1_rc2_40',
+ # PKCS#1 v2.2
+ '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ }
+
+
+class EncryptionAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', EncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'des': OctetString,
+ 'tripledes_3key': OctetString,
+ 'rc2': Rc2Params,
+ 'rc5': Rc5Params,
+ 'aes128_cbc': OctetString,
+ 'aes192_cbc': OctetString,
+ 'aes256_cbc': OctetString,
+ 'aes128_ofb': OctetString,
+ 'aes192_ofb': OctetString,
+ 'aes256_ofb': OctetString,
+ # From RFC5084
+ 'aes128_ccm': CcmParams,
+ 'aes192_ccm': CcmParams,
+ 'aes256_ccm': CcmParams,
+ # From PKCS#5
+ 'pbes1_md2_des': Pbes1Params,
+ 'pbes1_md5_des': Pbes1Params,
+ 'pbes1_md2_rc2': Pbes1Params,
+ 'pbes1_md5_rc2': Pbes1Params,
+ 'pbes1_sha1_des': Pbes1Params,
+ 'pbes1_sha1_rc2': Pbes1Params,
+ # From PKCS#12
+ 'pkcs12_sha1_rc4_128': Pbes1Params,
+ 'pkcs12_sha1_rc4_40': Pbes1Params,
+ 'pkcs12_sha1_tripledes_3key': Pbes1Params,
+ 'pkcs12_sha1_tripledes_2key': Pbes1Params,
+ 'pkcs12_sha1_rc2_128': Pbes1Params,
+ 'pkcs12_sha1_rc2_40': Pbes1Params,
+ # PKCS#1 v2.2
+ 'rsaes_oaep': RSAESOAEPParams,
+ }
+
+ @property
+ def kdf(self):
+ """
+ Returns the name of the key derivation function to use.
+
+ :return:
+ A unicode from of one of the following: "pbkdf1", "pbkdf2",
+ "pkcs12_kdf"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['algorithm'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ encryption_algo, _ = encryption_algo.split('_', 1)
+
+ if encryption_algo == 'pbes1':
+ return 'pbkdf1'
+
+ if encryption_algo == 'pkcs12':
+ return 'pkcs12_kdf'
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_hmac(self):
+ """
+ Returns the HMAC algorithm to use with the KDF.
+
+ :return:
+ A unicode string of one of the following: "md2", "md5", "sha1",
+ "sha224", "sha256", "sha384", "sha512"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['parameters']['prf']['algorithm'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ _, hmac_algo, _ = encryption_algo.split('_', 2)
+ return hmac_algo
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation hmac algorithm
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_salt(self):
+ """
+ Returns the byte string to use as the salt for the KDF.
+
+ :return:
+ A byte string
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ salt = self['parameters']['key_derivation_func']['parameters']['salt']
+
+ if salt.name == 'other_source':
+ raise ValueError(unwrap(
+ '''
+ Can not determine key derivation salt - the
+ reserved-for-future-use other source salt choice was
+ specified in the PBKDF2 params structure
+ '''
+ ))
+
+ return salt.native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ return self['parameters']['salt'].native
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation salt
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_iterations(self):
+ """
+ Returns the number of iterations that should be run via the KDF.
+
+ :return:
+ An integer
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['parameters']['iteration_count'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ return self['parameters']['iterations'].native
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation iterations
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def key_length(self):
+ """
+ Returns the key length to pass to the cipher/kdf. The PKCS#5 spec does
+ not specify a way to store the RC5 key length, however this tends not
+ to be a problem since OpenSSL does not support RC5 in PKCS#8 and OS X
+ does not provide an RC5 cipher for use in the Security Transforms
+ library.
+
+ :raises:
+ ValueError - when the key length can not be determined
+
+ :return:
+ An integer representing the length in bytes
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:3] == 'aes':
+ return {
+ 'aes128_': 16,
+ 'aes192_': 24,
+ 'aes256_': 32,
+ }[encryption_algo[0:7]]
+
+ cipher_lengths = {
+ 'des': 8,
+ 'tripledes_3key': 24,
+ }
+
+ if encryption_algo in cipher_lengths:
+ return cipher_lengths[encryption_algo]
+
+ if encryption_algo == 'rc2':
+ rc2_parameter_version = self['parameters']['rc2_parameter_version'].native
+
+ # See page 24 of
+ # http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
+ encoded_key_bits_map = {
+ 160: 5, # 40-bit
+ 120: 8, # 64-bit
+ 58: 16, # 128-bit
+ }
+
+ if rc2_parameter_version in encoded_key_bits_map:
+ return encoded_key_bits_map[rc2_parameter_version]
+
+ if rc2_parameter_version >= 256:
+ return rc2_parameter_version
+
+ if rc2_parameter_version is None:
+ return 4 # 32-bit default
+
+ raise ValueError(unwrap(
+ '''
+ Invalid RC2 parameter version found in EncryptionAlgorithm
+ parameters
+ '''
+ ))
+
+ if encryption_algo == 'pbes2':
+ key_length = self['parameters']['key_derivation_func']['parameters']['key_length'].native
+ if key_length is not None:
+ return key_length
+
+ # If the KDF params don't specify the key size, we can infer it from
+ # the encryption scheme for all schemes except for RC5. However, in
+ # practical terms, neither OpenSSL or OS X support RC5 for PKCS#8
+ # so it is unlikely to be an issue that is run into.
+
+ return self['parameters']['encryption_scheme'].key_length
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 8,
+ 'pbes1_md5_des': 8,
+ 'pbes1_md2_rc2': 8,
+ 'pbes1_md5_rc2': 8,
+ 'pbes1_sha1_des': 8,
+ 'pbes1_sha1_rc2': 8,
+ 'pkcs12_sha1_rc4_128': 16,
+ 'pkcs12_sha1_rc4_40': 5,
+ 'pkcs12_sha1_tripledes_3key': 24,
+ 'pkcs12_sha1_tripledes_2key': 16,
+ 'pkcs12_sha1_rc2_128': 16,
+ 'pkcs12_sha1_rc2_40': 5,
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_mode(self):
+ """
+ Returns the name of the encryption mode to use.
+
+ :return:
+ A unicode string from one of the following: "cbc", "ecb", "ofb",
+ "cfb", "wrap", "gcm", "ccm", "wrap_pad"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return encryption_algo[7:]
+
+ if encryption_algo[0:6] == 'pbes1_':
+ return 'cbc'
+
+ if encryption_algo[0:7] == 'pkcs12_':
+ return 'cbc'
+
+ if encryption_algo in set(['des', 'tripledes_3key', 'rc2', 'rc5']):
+ return 'cbc'
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_mode
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_cipher(self):
+ """
+ Returns the name of the symmetric encryption cipher to use. The key
+ length can be retrieved via the .key_length property to disabiguate
+ between different variations of TripleDES, AES, and the RC* ciphers.
+
+ :return:
+ A unicode string from one of the following: "rc2", "rc5", "des",
+ "tripledes", "aes"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return 'aes'
+
+ if encryption_algo in set(['des', 'rc2', 'rc5']):
+ return encryption_algo
+
+ if encryption_algo == 'tripledes_3key':
+ return 'tripledes'
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_cipher
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 'des',
+ 'pbes1_md5_des': 'des',
+ 'pbes1_md2_rc2': 'rc2',
+ 'pbes1_md5_rc2': 'rc2',
+ 'pbes1_sha1_des': 'des',
+ 'pbes1_sha1_rc2': 'rc2',
+ 'pkcs12_sha1_rc4_128': 'rc4',
+ 'pkcs12_sha1_rc4_40': 'rc4',
+ 'pkcs12_sha1_tripledes_3key': 'tripledes',
+ 'pkcs12_sha1_tripledes_2key': 'tripledes',
+ 'pkcs12_sha1_rc2_128': 'rc2',
+ 'pkcs12_sha1_rc2_40': 'rc2',
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_block_size(self):
+ """
+ Returns the block size of the encryption cipher, in bytes.
+
+ :return:
+ An integer that is the block size in bytes
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return 16
+
+ cipher_map = {
+ 'des': 8,
+ 'tripledes_3key': 8,
+ 'rc2': 8,
+ }
+ if encryption_algo in cipher_map:
+ return cipher_map[encryption_algo]
+
+ if encryption_algo == 'rc5':
+ return self['parameters']['block_size_in_bits'].native // 8
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_block_size
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 8,
+ 'pbes1_md5_des': 8,
+ 'pbes1_md2_rc2': 8,
+ 'pbes1_md5_rc2': 8,
+ 'pbes1_sha1_des': 8,
+ 'pbes1_sha1_rc2': 8,
+ 'pkcs12_sha1_rc4_128': 0,
+ 'pkcs12_sha1_rc4_40': 0,
+ 'pkcs12_sha1_tripledes_3key': 8,
+ 'pkcs12_sha1_tripledes_2key': 8,
+ 'pkcs12_sha1_rc2_128': 8,
+ 'pkcs12_sha1_rc2_40': 8,
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_iv(self):
+ """
+ Returns the byte string of the initialization vector for the encryption
+ scheme. Only the PBES2 stores the IV in the params. For PBES1, the IV
+ is derived from the KDF and this property will return None.
+
+ :return:
+ A byte string or None
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo in set(['rc2', 'rc5']):
+ return self['parameters']['iv'].native
+
+ # For DES/Triple DES and AES the IV is the entirety of the parameters
+ octet_string_iv_oids = set([
+ 'des',
+ 'tripledes_3key',
+ 'aes128_cbc',
+ 'aes192_cbc',
+ 'aes256_cbc',
+ 'aes128_ofb',
+ 'aes192_ofb',
+ 'aes256_ofb',
+ ])
+ if encryption_algo in octet_string_iv_oids:
+ return self['parameters'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_iv
+
+ # All of the PBES1 algos use their KDF to create the IV. For the pbkdf1,
+ # the KDF is told to generate a key that is an extra 8 bytes long, and
+ # that is used for the IV. For the PKCS#12 KDF, it is called with an id
+ # of 2 to generate the IV. In either case, we can't return the IV
+ # without knowing the user's password.
+ if encryption_algo.find('.') == -1:
+ return None
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+
+class Pbes2Params(Sequence):
+ _fields = [
+ ('key_derivation_func', KdfAlgorithm),
+ ('encryption_scheme', EncryptionAlgorithm),
+ ]
+
+
+class Pbmac1Params(Sequence):
+ _fields = [
+ ('key_derivation_func', KdfAlgorithm),
+ ('message_auth_scheme', HmacAlgorithm),
+ ]
+
+
+class Pkcs5MacId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.5.14': 'pbmac1',
+ }
+
+
+class Pkcs5MacAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', Pkcs5MacId),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'pbmac1': Pbmac1Params,
+ }
+
+
+EncryptionAlgorithm._oid_specs['pbes2'] = Pbes2Params
+
+
+class AnyAlgorithmId(ObjectIdentifier):
+ _map = {}
+
+ def _setup(self):
+ _map = self.__class__._map
+ for other_cls in (EncryptionAlgorithmId, SignedDigestAlgorithmId, DigestAlgorithmId):
+ for oid, name in other_cls._map.items():
+ _map[oid] = name
+
+
+class AnyAlgorithmIdentifier(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', AnyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {}
+
+ def _setup(self):
+ Sequence._setup(self)
+ specs = self.__class__._oid_specs
+ for other_cls in (EncryptionAlgorithm, SignedDigestAlgorithm):
+ for oid, spec in other_cls._oid_specs.items():
+ specs[oid] = spec
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/cms.py b/contrib/python/asn1crypto/py2/asn1crypto/cms.py
new file mode 100644
index 0000000000..c395b2274f
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/cms.py
@@ -0,0 +1,1003 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for cryptographic message syntax (CMS). Structures are also
+compatible with PKCS#7. Exports the following items:
+
+ - AuthenticatedData()
+ - AuthEnvelopedData()
+ - CompressedData()
+ - ContentInfo()
+ - DigestedData()
+ - EncryptedData()
+ - EnvelopedData()
+ - SignedAndEnvelopedData()
+ - SignedData()
+
+Other type classes are defined that help compose the types listed above.
+
+Most CMS structures in the wild are formatted as ContentInfo encapsulating one of the other types.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+try:
+ import zlib
+except (ImportError):
+ zlib = None
+
+from .algos import (
+ _ForceNullParameters,
+ DigestAlgorithm,
+ EncryptionAlgorithm,
+ EncryptionAlgorithmId,
+ HmacAlgorithm,
+ KdfAlgorithm,
+ RSAESOAEPParams,
+ SignedDigestAlgorithm,
+)
+from .core import (
+ Any,
+ BitString,
+ Choice,
+ Enumerated,
+ GeneralizedTime,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+ UTCTime,
+ UTF8String,
+)
+from .crl import CertificateList
+from .keys import PublicKeyInfo
+from .ocsp import OCSPResponse
+from .x509 import Attributes, Certificate, Extensions, GeneralName, GeneralNames, Name
+
+
+# These structures are taken from
+# ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-6.asc
+
+class ExtendedCertificateInfo(Sequence):
+ _fields = [
+ ('version', Integer),
+ ('certificate', Certificate),
+ ('attributes', Attributes),
+ ]
+
+
+class ExtendedCertificate(Sequence):
+ _fields = [
+ ('extended_certificate_info', ExtendedCertificateInfo),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+# These structures are taken from https://tools.ietf.org/html/rfc5652,
+# https://tools.ietf.org/html/rfc5083, http://tools.ietf.org/html/rfc2315,
+# https://tools.ietf.org/html/rfc5940, https://tools.ietf.org/html/rfc3274,
+# https://tools.ietf.org/html/rfc3281
+
+
+class CMSVersion(Integer):
+ _map = {
+ 0: 'v0',
+ 1: 'v1',
+ 2: 'v2',
+ 3: 'v3',
+ 4: 'v4',
+ 5: 'v5',
+ }
+
+
+class CMSAttributeType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.3': 'content_type',
+ '1.2.840.113549.1.9.4': 'message_digest',
+ '1.2.840.113549.1.9.5': 'signing_time',
+ '1.2.840.113549.1.9.6': 'counter_signature',
+ # https://datatracker.ietf.org/doc/html/rfc2633#section-2.5.2
+ '1.2.840.113549.1.9.15': 'smime_capabilities',
+ # https://tools.ietf.org/html/rfc2633#page-26
+ '1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
+ # https://tools.ietf.org/html/rfc3161#page-20
+ '1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token',
+ # https://tools.ietf.org/html/rfc6211#page-5
+ '1.2.840.113549.1.9.52': 'cms_algorithm_protection',
+ # https://docs.microsoft.com/en-us/previous-versions/hh968145(v%3Dvs.85)
+ '1.3.6.1.4.1.311.2.4.1': 'microsoft_nested_signature',
+ # Some places refer to this as SPC_RFC3161_OBJID, others szOID_RFC3161_counterSign.
+ # https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-crypt_algorithm_identifier
+ # refers to szOID_RFC3161_counterSign as "1.2.840.113549.1.9.16.1.4",
+ # but that OID is also called szOID_TIMESTAMP_TOKEN. Because of there being
+ # no canonical source for this OID, we give it our own name
+ '1.3.6.1.4.1.311.3.3.1': 'microsoft_time_stamp_token',
+ }
+
+
+class Time(Choice):
+ _alternatives = [
+ ('utc_time', UTCTime),
+ ('generalized_time', GeneralizedTime),
+ ]
+
+
+class ContentType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.7.1': 'data',
+ '1.2.840.113549.1.7.2': 'signed_data',
+ '1.2.840.113549.1.7.3': 'enveloped_data',
+ '1.2.840.113549.1.7.4': 'signed_and_enveloped_data',
+ '1.2.840.113549.1.7.5': 'digested_data',
+ '1.2.840.113549.1.7.6': 'encrypted_data',
+ '1.2.840.113549.1.9.16.1.2': 'authenticated_data',
+ '1.2.840.113549.1.9.16.1.9': 'compressed_data',
+ '1.2.840.113549.1.9.16.1.23': 'authenticated_enveloped_data',
+ }
+
+
+class CMSAlgorithmProtection(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm),
+ ('signature_algorithm', SignedDigestAlgorithm, {'implicit': 1, 'optional': True}),
+ ('mac_algorithm', HmacAlgorithm, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class SetOfContentType(SetOf):
+ _child_spec = ContentType
+
+
+class SetOfOctetString(SetOf):
+ _child_spec = OctetString
+
+
+class SetOfTime(SetOf):
+ _child_spec = Time
+
+
+class SetOfAny(SetOf):
+ _child_spec = Any
+
+
+class SetOfCMSAlgorithmProtection(SetOf):
+ _child_spec = CMSAlgorithmProtection
+
+
+class CMSAttribute(Sequence):
+ _fields = [
+ ('type', CMSAttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {}
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class CMSAttributes(SetOf):
+ _child_spec = CMSAttribute
+
+
+class IssuerSerial(Sequence):
+ _fields = [
+ ('issuer', GeneralNames),
+ ('serial', Integer),
+ ('issuer_uid', OctetBitString, {'optional': True}),
+ ]
+
+
+class AttCertVersion(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ }
+
+
+class AttCertSubject(Choice):
+ _alternatives = [
+ ('base_certificate_id', IssuerSerial, {'explicit': 0}),
+ ('subject_name', GeneralNames, {'explicit': 1}),
+ ]
+
+
+class AttCertValidityPeriod(Sequence):
+ _fields = [
+ ('not_before_time', GeneralizedTime),
+ ('not_after_time', GeneralizedTime),
+ ]
+
+
+class AttributeCertificateInfoV1(Sequence):
+ _fields = [
+ ('version', AttCertVersion, {'default': 'v1'}),
+ ('subject', AttCertSubject),
+ ('issuer', GeneralNames),
+ ('signature', SignedDigestAlgorithm),
+ ('serial_number', Integer),
+ ('att_cert_validity_period', AttCertValidityPeriod),
+ ('attributes', Attributes),
+ ('issuer_unique_id', OctetBitString, {'optional': True}),
+ ('extensions', Extensions, {'optional': True}),
+ ]
+
+
+class AttributeCertificateV1(Sequence):
+ _fields = [
+ ('ac_info', AttributeCertificateInfoV1),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+class DigestedObjectType(Enumerated):
+ _map = {
+ 0: 'public_key',
+ 1: 'public_key_cert',
+ 2: 'other_objy_types',
+ }
+
+
+class ObjectDigestInfo(Sequence):
+ _fields = [
+ ('digested_object_type', DigestedObjectType),
+ ('other_object_type_id', ObjectIdentifier, {'optional': True}),
+ ('digest_algorithm', DigestAlgorithm),
+ ('object_digest', OctetBitString),
+ ]
+
+
+class Holder(Sequence):
+ _fields = [
+ ('base_certificate_id', IssuerSerial, {'implicit': 0, 'optional': True}),
+ ('entity_name', GeneralNames, {'implicit': 1, 'optional': True}),
+ ('object_digest_info', ObjectDigestInfo, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class V2Form(Sequence):
+ _fields = [
+ ('issuer_name', GeneralNames, {'optional': True}),
+ ('base_certificate_id', IssuerSerial, {'explicit': 0, 'optional': True}),
+ ('object_digest_info', ObjectDigestInfo, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class AttCertIssuer(Choice):
+ _alternatives = [
+ ('v1_form', GeneralNames),
+ ('v2_form', V2Form, {'implicit': 0}),
+ ]
+
+
+class IetfAttrValue(Choice):
+ _alternatives = [
+ ('octets', OctetString),
+ ('oid', ObjectIdentifier),
+ ('string', UTF8String),
+ ]
+
+
+class IetfAttrValues(SequenceOf):
+ _child_spec = IetfAttrValue
+
+
+class IetfAttrSyntax(Sequence):
+ _fields = [
+ ('policy_authority', GeneralNames, {'implicit': 0, 'optional': True}),
+ ('values', IetfAttrValues),
+ ]
+
+
+class SetOfIetfAttrSyntax(SetOf):
+ _child_spec = IetfAttrSyntax
+
+
+class SvceAuthInfo(Sequence):
+ _fields = [
+ ('service', GeneralName),
+ ('ident', GeneralName),
+ ('auth_info', OctetString, {'optional': True}),
+ ]
+
+
+class SetOfSvceAuthInfo(SetOf):
+ _child_spec = SvceAuthInfo
+
+
+class RoleSyntax(Sequence):
+ _fields = [
+ ('role_authority', GeneralNames, {'implicit': 0, 'optional': True}),
+ ('role_name', GeneralName, {'explicit': 1}),
+ ]
+
+
+class SetOfRoleSyntax(SetOf):
+ _child_spec = RoleSyntax
+
+
+class ClassList(BitString):
+ _map = {
+ 0: 'unmarked',
+ 1: 'unclassified',
+ 2: 'restricted',
+ 3: 'confidential',
+ 4: 'secret',
+ 5: 'top_secret',
+ }
+
+
+class SecurityCategory(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier, {'implicit': 0}),
+ ('value', Any, {'explicit': 1}),
+ ]
+
+
+class SetOfSecurityCategory(SetOf):
+ _child_spec = SecurityCategory
+
+
+class Clearance(Sequence):
+ _fields = [
+ ('policy_id', ObjectIdentifier),
+ ('class_list', ClassList, {'default': set(['unclassified'])}),
+ ('security_categories', SetOfSecurityCategory, {'optional': True}),
+ ]
+
+
+class SetOfClearance(SetOf):
+ _child_spec = Clearance
+
+
+class BigTime(Sequence):
+ _fields = [
+ ('major', Integer),
+ ('fractional_seconds', Integer),
+ ('sign', Integer, {'optional': True}),
+ ]
+
+
+class LeapData(Sequence):
+ _fields = [
+ ('leap_time', BigTime),
+ ('action', Integer),
+ ]
+
+
+class SetOfLeapData(SetOf):
+ _child_spec = LeapData
+
+
+class TimingMetrics(Sequence):
+ _fields = [
+ ('ntp_time', BigTime),
+ ('offset', BigTime),
+ ('delay', BigTime),
+ ('expiration', BigTime),
+ ('leap_event', SetOfLeapData, {'optional': True}),
+ ]
+
+
+class SetOfTimingMetrics(SetOf):
+ _child_spec = TimingMetrics
+
+
+class TimingPolicy(Sequence):
+ _fields = [
+ ('policy_id', SequenceOf, {'spec': ObjectIdentifier}),
+ ('max_offset', BigTime, {'explicit': 0, 'optional': True}),
+ ('max_delay', BigTime, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class SetOfTimingPolicy(SetOf):
+ _child_spec = TimingPolicy
+
+
+class AttCertAttributeType(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.10.1': 'authentication_info',
+ '1.3.6.1.5.5.7.10.2': 'access_identity',
+ '1.3.6.1.5.5.7.10.3': 'charging_identity',
+ '1.3.6.1.5.5.7.10.4': 'group',
+ '2.5.4.72': 'role',
+ '2.5.4.55': 'clearance',
+ '1.3.6.1.4.1.601.10.4.1': 'timing_metrics',
+ '1.3.6.1.4.1.601.10.4.2': 'timing_policy',
+ }
+
+
+class AttCertAttribute(Sequence):
+ _fields = [
+ ('type', AttCertAttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {
+ 'authentication_info': SetOfSvceAuthInfo,
+ 'access_identity': SetOfSvceAuthInfo,
+ 'charging_identity': SetOfIetfAttrSyntax,
+ 'group': SetOfIetfAttrSyntax,
+ 'role': SetOfRoleSyntax,
+ 'clearance': SetOfClearance,
+ 'timing_metrics': SetOfTimingMetrics,
+ 'timing_policy': SetOfTimingPolicy,
+ }
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class AttCertAttributes(SequenceOf):
+ _child_spec = AttCertAttribute
+
+
+class AttributeCertificateInfoV2(Sequence):
+ _fields = [
+ ('version', AttCertVersion),
+ ('holder', Holder),
+ ('issuer', AttCertIssuer),
+ ('signature', SignedDigestAlgorithm),
+ ('serial_number', Integer),
+ ('att_cert_validity_period', AttCertValidityPeriod),
+ ('attributes', AttCertAttributes),
+ ('issuer_unique_id', OctetBitString, {'optional': True}),
+ ('extensions', Extensions, {'optional': True}),
+ ]
+
+
+class AttributeCertificateV2(Sequence):
+ # Handle the situation where a V2 cert is encoded as V1
+ _bad_tag = 1
+
+ _fields = [
+ ('ac_info', AttributeCertificateInfoV2),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+class OtherCertificateFormat(Sequence):
+ _fields = [
+ ('other_cert_format', ObjectIdentifier),
+ ('other_cert', Any),
+ ]
+
+
+class CertificateChoices(Choice):
+ _alternatives = [
+ ('certificate', Certificate),
+ ('extended_certificate', ExtendedCertificate, {'implicit': 0}),
+ ('v1_attr_cert', AttributeCertificateV1, {'implicit': 1}),
+ ('v2_attr_cert', AttributeCertificateV2, {'implicit': 2}),
+ ('other', OtherCertificateFormat, {'implicit': 3}),
+ ]
+
+ def validate(self, class_, tag, contents):
+ """
+ Ensures that the class and tag specified exist as an alternative. This
+ custom version fixes parsing broken encodings there a V2 attribute
+ # certificate is encoded as a V1
+
+ :param class_:
+ The integer class_ from the encoded value header
+
+ :param tag:
+ The integer tag from the encoded value header
+
+ :param contents:
+ A byte string of the contents of the value - used when the object
+ is explicitly tagged
+
+ :raises:
+ ValueError - when value is not a valid alternative
+ """
+
+ super(CertificateChoices, self).validate(class_, tag, contents)
+ if self._choice == 2:
+ if AttCertVersion.load(Sequence.load(contents)[0].dump()).native == 'v2':
+ self._choice = 3
+
+
+class CertificateSet(SetOf):
+ _child_spec = CertificateChoices
+
+
+class ContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content', Any, {'explicit': 0, 'optional': True}),
+ ]
+
+ _oid_pair = ('content_type', 'content')
+ _oid_specs = {}
+
+
+class SetOfContentInfo(SetOf):
+ _child_spec = ContentInfo
+
+
+class EncapsulatedContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content', ParsableOctetString, {'explicit': 0, 'optional': True}),
+ ]
+
+ _oid_pair = ('content_type', 'content')
+ _oid_specs = {}
+
+
+class IssuerAndSerialNumber(Sequence):
+ _fields = [
+ ('issuer', Name),
+ ('serial_number', Integer),
+ ]
+
+
+class SignerIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ]
+
+
+class DigestAlgorithms(SetOf):
+ _child_spec = DigestAlgorithm
+
+
+class CertificateRevocationLists(SetOf):
+ _child_spec = CertificateList
+
+
+class SCVPReqRes(Sequence):
+ _fields = [
+ ('request', ContentInfo, {'explicit': 0, 'optional': True}),
+ ('response', ContentInfo),
+ ]
+
+
+class OtherRevInfoFormatId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.16.2': 'ocsp_response',
+ '1.3.6.1.5.5.7.16.4': 'scvp',
+ }
+
+
+class OtherRevocationInfoFormat(Sequence):
+ _fields = [
+ ('other_rev_info_format', OtherRevInfoFormatId),
+ ('other_rev_info', Any),
+ ]
+
+ _oid_pair = ('other_rev_info_format', 'other_rev_info')
+ _oid_specs = {
+ 'ocsp_response': OCSPResponse,
+ 'scvp': SCVPReqRes,
+ }
+
+
+class RevocationInfoChoice(Choice):
+ _alternatives = [
+ ('crl', CertificateList),
+ ('other', OtherRevocationInfoFormat, {'implicit': 1}),
+ ]
+
+
+class RevocationInfoChoices(SetOf):
+ _child_spec = RevocationInfoChoice
+
+
+class SignerInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('sid', SignerIdentifier),
+ ('digest_algorithm', DigestAlgorithm),
+ ('signed_attrs', CMSAttributes, {'implicit': 0, 'optional': True}),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetString),
+ ('unsigned_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class SignerInfos(SetOf):
+ _child_spec = SignerInfo
+
+
+class SignedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('digest_algorithms', DigestAlgorithms),
+ ('encap_content_info', None),
+ ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
+ ('signer_infos', SignerInfos),
+ ]
+
+ def _encap_content_info_spec(self):
+ # If the encap_content_info is version v1, then this could be a PKCS#7
+ # structure, or a CMS structure. CMS wraps the encoded value in an
+ # Octet String tag.
+
+ # If the version is greater than 1, it is definite CMS
+ if self['version'].native != 'v1':
+ return EncapsulatedContentInfo
+
+ # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
+ # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
+ # allows Any
+ return ContentInfo
+
+ _spec_callbacks = {
+ 'encap_content_info': _encap_content_info_spec
+ }
+
+
+class OriginatorInfo(Sequence):
+ _fields = [
+ ('certs', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class RecipientIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ]
+
+
+class KeyEncryptionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
+ '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
+ '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
+ '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
+ '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
+ '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
+ }
+
+ _reverse_map = {
+ 'rsa': '1.2.840.113549.1.1.1',
+ 'rsaes_pkcs1v15': '1.2.840.113549.1.1.1',
+ 'rsaes_oaep': '1.2.840.113549.1.1.7',
+ 'aes128_wrap': '2.16.840.1.101.3.4.1.5',
+ 'aes128_wrap_pad': '2.16.840.1.101.3.4.1.8',
+ 'aes192_wrap': '2.16.840.1.101.3.4.1.25',
+ 'aes192_wrap_pad': '2.16.840.1.101.3.4.1.28',
+ 'aes256_wrap': '2.16.840.1.101.3.4.1.45',
+ 'aes256_wrap_pad': '2.16.840.1.101.3.4.1.48',
+ }
+
+
+class KeyEncryptionAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', KeyEncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'rsaes_oaep': RSAESOAEPParams,
+ }
+
+
+class KeyTransRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('rid', RecipientIdentifier),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class OriginatorIdentifierOrKey(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ('originator_key', PublicKeyInfo, {'implicit': 1}),
+ ]
+
+
+class OtherKeyAttribute(Sequence):
+ _fields = [
+ ('key_attr_id', ObjectIdentifier),
+ ('key_attr', Any),
+ ]
+
+
+class RecipientKeyIdentifier(Sequence):
+ _fields = [
+ ('subject_key_identifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class KeyAgreementRecipientIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('r_key_id', RecipientKeyIdentifier, {'implicit': 0}),
+ ]
+
+
+class RecipientEncryptedKey(Sequence):
+ _fields = [
+ ('rid', KeyAgreementRecipientIdentifier),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class RecipientEncryptedKeys(SequenceOf):
+ _child_spec = RecipientEncryptedKey
+
+
+class KeyAgreeRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator', OriginatorIdentifierOrKey, {'explicit': 0}),
+ ('ukm', OctetString, {'explicit': 1, 'optional': True}),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('recipient_encrypted_keys', RecipientEncryptedKeys),
+ ]
+
+
+class KEKIdentifier(Sequence):
+ _fields = [
+ ('key_identifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class KEKRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('kekid', KEKIdentifier),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class PasswordRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('key_derivation_algorithm', KdfAlgorithm, {'implicit': 0, 'optional': True}),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class OtherRecipientInfo(Sequence):
+ _fields = [
+ ('ori_type', ObjectIdentifier),
+ ('ori_value', Any),
+ ]
+
+
+class RecipientInfo(Choice):
+ _alternatives = [
+ ('ktri', KeyTransRecipientInfo),
+ ('kari', KeyAgreeRecipientInfo, {'implicit': 1}),
+ ('kekri', KEKRecipientInfo, {'implicit': 2}),
+ ('pwri', PasswordRecipientInfo, {'implicit': 3}),
+ ('ori', OtherRecipientInfo, {'implicit': 4}),
+ ]
+
+
+class RecipientInfos(SetOf):
+ _child_spec = RecipientInfo
+
+
+class EncryptedContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content_encryption_algorithm', EncryptionAlgorithm),
+ ('encrypted_content', OctetString, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class EnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class SignedAndEnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('recipient_infos', RecipientInfos),
+ ('digest_algorithms', DigestAlgorithms),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', CertificateRevocationLists, {'implicit': 1, 'optional': True}),
+ ('signer_infos', SignerInfos),
+ ]
+
+
+class DigestedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('digest_algorithm', DigestAlgorithm),
+ ('encap_content_info', None),
+ ('digest', OctetString),
+ ]
+
+ def _encap_content_info_spec(self):
+ # If the encap_content_info is version v1, then this could be a PKCS#7
+ # structure, or a CMS structure. CMS wraps the encoded value in an
+ # Octet String tag.
+
+ # If the version is greater than 1, it is definite CMS
+ if self['version'].native != 'v1':
+ return EncapsulatedContentInfo
+
+ # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
+ # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
+ # allows Any
+ return ContentInfo
+
+ _spec_callbacks = {
+ 'encap_content_info': _encap_content_info_spec
+ }
+
+
+class EncryptedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class AuthenticatedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('mac_algorithm', HmacAlgorithm),
+ ('digest_algorithm', DigestAlgorithm, {'implicit': 1, 'optional': True}),
+ # This does not require the _spec_callbacks approach of SignedData and
+ # DigestedData since AuthenticatedData was not part of PKCS#7
+ ('encap_content_info', EncapsulatedContentInfo),
+ ('auth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
+ ('mac', OctetString),
+ ('unauth_attrs', CMSAttributes, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class AuthEnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('auth_encrypted_content_info', EncryptedContentInfo),
+ ('auth_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ('mac', OctetString),
+ ('unauth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class CompressionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.16.3.8': 'zlib',
+ }
+
+
+class CompressionAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', CompressionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class CompressedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('compression_algorithm', CompressionAlgorithm),
+ ('encap_content_info', EncapsulatedContentInfo),
+ ]
+
+ _decompressed = None
+
+ @property
+ def decompressed(self):
+ if self._decompressed is None:
+ if zlib is None:
+ raise SystemError('The zlib module is not available')
+ self._decompressed = zlib.decompress(self['encap_content_info']['content'].native)
+ return self._decompressed
+
+
+class RecipientKeyIdentifier(Sequence):
+ _fields = [
+ ('subjectKeyIdentifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class SMIMEEncryptionKeyPreference(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber, {'implicit': 0}),
+ ('recipientKeyId', RecipientKeyIdentifier, {'implicit': 1}),
+ ('subjectAltKeyIdentifier', PublicKeyInfo, {'implicit': 2}),
+ ]
+
+
+class SMIMEEncryptionKeyPreferences(SetOf):
+ _child_spec = SMIMEEncryptionKeyPreference
+
+
+class SMIMECapabilityIdentifier(Sequence):
+ _fields = [
+ ('capability_id', EncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class SMIMECapabilites(SequenceOf):
+ _child_spec = SMIMECapabilityIdentifier
+
+
+class SetOfSMIMECapabilites(SetOf):
+ _child_spec = SMIMECapabilites
+
+
+ContentInfo._oid_specs = {
+ 'data': OctetString,
+ 'signed_data': SignedData,
+ 'enveloped_data': EnvelopedData,
+ 'signed_and_enveloped_data': SignedAndEnvelopedData,
+ 'digested_data': DigestedData,
+ 'encrypted_data': EncryptedData,
+ 'authenticated_data': AuthenticatedData,
+ 'compressed_data': CompressedData,
+ 'authenticated_enveloped_data': AuthEnvelopedData,
+}
+
+
+EncapsulatedContentInfo._oid_specs = {
+ 'signed_data': SignedData,
+ 'enveloped_data': EnvelopedData,
+ 'signed_and_enveloped_data': SignedAndEnvelopedData,
+ 'digested_data': DigestedData,
+ 'encrypted_data': EncryptedData,
+ 'authenticated_data': AuthenticatedData,
+ 'compressed_data': CompressedData,
+ 'authenticated_enveloped_data': AuthEnvelopedData,
+}
+
+
+CMSAttribute._oid_specs = {
+ 'content_type': SetOfContentType,
+ 'message_digest': SetOfOctetString,
+ 'signing_time': SetOfTime,
+ 'counter_signature': SignerInfos,
+ 'signature_time_stamp_token': SetOfContentInfo,
+ 'cms_algorithm_protection': SetOfCMSAlgorithmProtection,
+ 'microsoft_nested_signature': SetOfContentInfo,
+ 'microsoft_time_stamp_token': SetOfContentInfo,
+ 'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
+ 'smime_capabilities': SetOfSMIMECapabilites,
+}
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/core.py b/contrib/python/asn1crypto/py2/asn1crypto/core.py
new file mode 100644
index 0000000000..364c6b5cae
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/core.py
@@ -0,0 +1,5676 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for universal types. Exports the following items:
+
+ - load()
+ - Any()
+ - Asn1Value()
+ - BitString()
+ - BMPString()
+ - Boolean()
+ - CharacterString()
+ - Choice()
+ - EmbeddedPdv()
+ - Enumerated()
+ - GeneralizedTime()
+ - GeneralString()
+ - GraphicString()
+ - IA5String()
+ - InstanceOf()
+ - Integer()
+ - IntegerBitString()
+ - IntegerOctetString()
+ - Null()
+ - NumericString()
+ - ObjectDescriptor()
+ - ObjectIdentifier()
+ - OctetBitString()
+ - OctetString()
+ - PrintableString()
+ - Real()
+ - RelativeOid()
+ - Sequence()
+ - SequenceOf()
+ - Set()
+ - SetOf()
+ - TeletexString()
+ - UniversalString()
+ - UTCTime()
+ - UTF8String()
+ - VideotexString()
+ - VisibleString()
+ - VOID
+ - Void()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from datetime import datetime, timedelta
+from fractions import Fraction
+import binascii
+import copy
+import math
+import re
+import sys
+
+from . import _teletex_codec
+from ._errors import unwrap
+from ._ordereddict import OrderedDict
+from ._types import type_name, str_cls, byte_cls, int_types, chr_cls
+from .parser import _parse, _dump_header
+from .util import int_to_bytes, int_from_bytes, timezone, extended_datetime, create_timezone, utc_with_dst
+
+if sys.version_info <= (3,):
+ from cStringIO import StringIO as BytesIO
+
+ range = xrange # noqa
+ _PY2 = True
+
+else:
+ from io import BytesIO
+
+ _PY2 = False
+
+
+_teletex_codec.register()
+
+
+CLASS_NUM_TO_NAME_MAP = {
+ 0: 'universal',
+ 1: 'application',
+ 2: 'context',
+ 3: 'private',
+}
+
+CLASS_NAME_TO_NUM_MAP = {
+ 'universal': 0,
+ 'application': 1,
+ 'context': 2,
+ 'private': 3,
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+}
+
+METHOD_NUM_TO_NAME_MAP = {
+ 0: 'primitive',
+ 1: 'constructed',
+}
+
+
+_OID_RE = re.compile(r'^\d+(\.\d+)*$')
+
+
+# A global tracker to ensure that _setup() is called for every class, even
+# if is has been called for a parent class. This allows different _fields
+# definitions for child classes. Without such a construct, the child classes
+# would just see the parent class attributes and would use them.
+_SETUP_CLASSES = {}
+
+
+def load(encoded_data, strict=False):
+ """
+ Loads a BER/DER-encoded byte string and construct a universal object based
+ on the tag value:
+
+ - 1: Boolean
+ - 2: Integer
+ - 3: BitString
+ - 4: OctetString
+ - 5: Null
+ - 6: ObjectIdentifier
+ - 7: ObjectDescriptor
+ - 8: InstanceOf
+ - 9: Real
+ - 10: Enumerated
+ - 11: EmbeddedPdv
+ - 12: UTF8String
+ - 13: RelativeOid
+ - 16: Sequence,
+ - 17: Set
+ - 18: NumericString
+ - 19: PrintableString
+ - 20: TeletexString
+ - 21: VideotexString
+ - 22: IA5String
+ - 23: UTCTime
+ - 24: GeneralizedTime
+ - 25: GraphicString
+ - 26: VisibleString
+ - 27: GeneralString
+ - 28: UniversalString
+ - 29: CharacterString
+ - 30: BMPString
+
+ :param encoded_data:
+ A byte string of BER or DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :raises:
+ ValueError - when strict is True and trailing data is present
+ ValueError - when the encoded value tag a tag other than listed above
+ ValueError - when the ASN.1 header length is longer than the data
+ TypeError - when encoded_data is not a byte string
+
+ :return:
+ An instance of the one of the universal classes
+ """
+
+ return Asn1Value.load(encoded_data, strict=strict)
+
+
+class Asn1Value(object):
+ """
+ The basis of all ASN.1 values
+ """
+
+ # The integer 0 for primitive, 1 for constructed
+ method = None
+
+ # An integer 0 through 3 - see CLASS_NUM_TO_NAME_MAP for value
+ class_ = None
+
+ # An integer 1 or greater indicating the tag number
+ tag = None
+
+ # An alternate tag allowed for this type - used for handling broken
+ # structures where a string value is encoded using an incorrect tag
+ _bad_tag = None
+
+ # If the value has been implicitly tagged
+ implicit = False
+
+ # If explicitly tagged, a tuple of 2-element tuples containing the
+ # class int and tag int, from innermost to outermost
+ explicit = None
+
+ # The BER/DER header bytes
+ _header = None
+
+ # Raw encoded value bytes not including class, method, tag, length header
+ contents = None
+
+ # The BER/DER trailer bytes
+ _trailer = b''
+
+ # The native python representation of the value - this is not used by
+ # some classes since they utilize _bytes or _unicode
+ _native = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False, **kwargs):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ An instance of the current class
+ """
+
+ if not isinstance(encoded_data, byte_cls):
+ raise TypeError('encoded_data must be a byte string, not %s' % type_name(encoded_data))
+
+ spec = None
+ if cls.tag is not None:
+ spec = cls
+
+ value, _ = _parse_build(encoded_data, spec=spec, spec_params=kwargs, strict=strict)
+ return value
+
+ def __init__(self, explicit=None, implicit=None, no_explicit=False, tag_type=None, class_=None, tag=None,
+ optional=None, default=None, contents=None, method=None):
+ """
+ The optional parameter is not used, but rather included so we don't
+ have to delete it from the parameter dictionary when passing as keyword
+ args
+
+ :param explicit:
+ An int tag number for explicit tagging, or a 2-element tuple of
+ class and tag.
+
+ :param implicit:
+ An int tag number for implicit tagging, or a 2-element tuple of
+ class and tag.
+
+ :param no_explicit:
+ If explicit tagging info should be removed from this instance.
+ Used internally to allow contructing the underlying value that
+ has been wrapped in an explicit tag.
+
+ :param tag_type:
+ None for normal values, or one of "implicit", "explicit" for tagged
+ values. Deprecated in favor of explicit and implicit params.
+
+ :param class_:
+ The class for the value - defaults to "universal" if tag_type is
+ None, otherwise defaults to "context". Valid values include:
+ - "universal"
+ - "application"
+ - "context"
+ - "private"
+ Deprecated in favor of explicit and implicit params.
+
+ :param tag:
+ The integer tag to override - usually this is used with tag_type or
+ class_. Deprecated in favor of explicit and implicit params.
+
+ :param optional:
+ Dummy parameter that allows "optional" key in spec param dicts
+
+ :param default:
+ The default value to use if the value is currently None
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param method:
+ The method for the value - no default value since this is
+ normally set on a class. Valid values include:
+ - "primitive" or 0
+ - "constructed" or 1
+
+ :raises:
+ ValueError - when implicit, explicit, tag_type, class_ or tag are invalid values
+ """
+
+ try:
+ if self.__class__ not in _SETUP_CLASSES:
+ cls = self.__class__
+ # Allow explicit to be specified as a simple 2-element tuple
+ # instead of requiring the user make a nested tuple
+ if cls.explicit is not None and isinstance(cls.explicit[0], int_types):
+ cls.explicit = (cls.explicit, )
+ if hasattr(cls, '_setup'):
+ self._setup()
+ _SETUP_CLASSES[cls] = True
+
+ # Normalize tagging values
+ if explicit is not None:
+ if isinstance(explicit, int_types):
+ if class_ is None:
+ class_ = 'context'
+ explicit = (class_, explicit)
+ # Prevent both explicit and tag_type == 'explicit'
+ if tag_type == 'explicit':
+ tag_type = None
+ tag = None
+
+ if implicit is not None:
+ if isinstance(implicit, int_types):
+ if class_ is None:
+ class_ = 'context'
+ implicit = (class_, implicit)
+ # Prevent both implicit and tag_type == 'implicit'
+ if tag_type == 'implicit':
+ tag_type = None
+ tag = None
+
+ # Convert old tag_type API to explicit/implicit params
+ if tag_type is not None:
+ if class_ is None:
+ class_ = 'context'
+ if tag_type == 'explicit':
+ explicit = (class_, tag)
+ elif tag_type == 'implicit':
+ implicit = (class_, tag)
+ else:
+ raise ValueError(unwrap(
+ '''
+ tag_type must be one of "implicit", "explicit", not %s
+ ''',
+ repr(tag_type)
+ ))
+
+ if explicit is not None:
+ # Ensure we have a tuple of 2-element tuples
+ if len(explicit) == 2 and isinstance(explicit[1], int_types):
+ explicit = (explicit, )
+ for class_, tag in explicit:
+ invalid_class = None
+ if isinstance(class_, int_types):
+ if class_ not in CLASS_NUM_TO_NAME_MAP:
+ invalid_class = class_
+ else:
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ invalid_class = class_
+ class_ = CLASS_NAME_TO_NUM_MAP[class_]
+ if invalid_class is not None:
+ raise ValueError(unwrap(
+ '''
+ explicit class must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(invalid_class)
+ ))
+ if tag is not None:
+ if not isinstance(tag, int_types):
+ raise TypeError(unwrap(
+ '''
+ explicit tag must be an integer, not %s
+ ''',
+ type_name(tag)
+ ))
+ if self.explicit is None:
+ self.explicit = ((class_, tag), )
+ else:
+ self.explicit = self.explicit + ((class_, tag), )
+
+ elif implicit is not None:
+ class_, tag = implicit
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ raise ValueError(unwrap(
+ '''
+ implicit class must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(class_)
+ ))
+ if tag is not None:
+ if not isinstance(tag, int_types):
+ raise TypeError(unwrap(
+ '''
+ implicit tag must be an integer, not %s
+ ''',
+ type_name(tag)
+ ))
+ self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
+ self.tag = tag
+ self.implicit = True
+ else:
+ if class_ is not None:
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ raise ValueError(unwrap(
+ '''
+ class_ must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(class_)
+ ))
+ self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
+
+ if self.class_ is None:
+ self.class_ = 0
+
+ if tag is not None:
+ self.tag = tag
+
+ if method is not None:
+ if method not in set(["primitive", 0, "constructed", 1]):
+ raise ValueError(unwrap(
+ '''
+ method must be one of "primitive" or "constructed",
+ not %s
+ ''',
+ repr(method)
+ ))
+ if method == "primitive":
+ method = 0
+ elif method == "constructed":
+ method = 1
+ self.method = method
+
+ if no_explicit:
+ self.explicit = None
+
+ if contents is not None:
+ self.contents = contents
+
+ elif default is not None:
+ self.set(default)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ def __str__(self):
+ """
+ Since str is different in Python 2 and 3, this calls the appropriate
+ method, __unicode__() or __bytes__()
+
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return self.__bytes__()
+ else:
+ return self.__unicode__()
+
+ def __repr__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return '<%s %s b%s>' % (type_name(self), id(self), repr(self.dump()))
+ else:
+ return '<%s %s %s>' % (type_name(self), id(self), repr(self.dump()))
+
+ def __bytes__(self):
+ """
+ A fall-back method for print() in Python 2
+
+ :return:
+ A byte string of the output of repr()
+ """
+
+ return self.__repr__().encode('utf-8')
+
+ def __unicode__(self):
+ """
+ A fall-back method for print() in Python 3
+
+ :return:
+ A unicode string of the output of repr()
+ """
+
+ return self.__repr__()
+
+ def _new_instance(self):
+ """
+ Constructs a new copy of the current object, preserving any tagging
+
+ :return:
+ An Asn1Value object
+ """
+
+ new_obj = self.__class__()
+ new_obj.class_ = self.class_
+ new_obj.tag = self.tag
+ new_obj.implicit = self.implicit
+ new_obj.explicit = self.explicit
+ return new_obj
+
+ def __copy__(self):
+ """
+ Implements the copy.copy() interface
+
+ :return:
+ A new shallow copy of the current Asn1Value object
+ """
+
+ new_obj = self._new_instance()
+ new_obj._copy(self, copy.copy)
+ return new_obj
+
+ def __deepcopy__(self, memo):
+ """
+ Implements the copy.deepcopy() interface
+
+ :param memo:
+ A dict for memoization
+
+ :return:
+ A new deep copy of the current Asn1Value object
+ """
+
+ new_obj = self._new_instance()
+ memo[id(self)] = new_obj
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def copy(self):
+ """
+ Copies the object, preserving any special tagging from it
+
+ :return:
+ An Asn1Value object
+ """
+
+ return copy.deepcopy(self)
+
+ def retag(self, tagging, tag=None):
+ """
+ Copies the object, applying a new tagging to it
+
+ :param tagging:
+ A dict containing the keys "explicit" and "implicit". Legacy
+ API allows a unicode string of "implicit" or "explicit".
+
+ :param tag:
+ A integer tag number. Only used when tagging is a unicode string.
+
+ :return:
+ An Asn1Value object
+ """
+
+ # This is required to preserve the old API
+ if not isinstance(tagging, dict):
+ tagging = {tagging: tag}
+ new_obj = self.__class__(explicit=tagging.get('explicit'), implicit=tagging.get('implicit'))
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def untag(self):
+ """
+ Copies the object, removing any special tagging from it
+
+ :return:
+ An Asn1Value object
+ """
+
+ new_obj = self.__class__()
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Asn1Value object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ if self.__class__ != other.__class__:
+ raise TypeError(unwrap(
+ '''
+ Can not copy values from %s object to %s object
+ ''',
+ type_name(other),
+ type_name(self)
+ ))
+
+ self.contents = other.contents
+ self._native = copy_func(other._native)
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ prefix = ' ' * nest_level
+
+ # This interacts with Any and moves the tag, implicit, explicit, _header,
+ # contents, _footer to the parsed value so duplicate data isn't present
+ has_parsed = hasattr(self, 'parsed')
+
+ _basic_debug(prefix, self)
+ if has_parsed:
+ self.parsed.debug(nest_level + 2)
+ elif hasattr(self, 'chosen'):
+ self.chosen.debug(nest_level + 2)
+ else:
+ if _PY2 and isinstance(self.native, byte_cls):
+ print('%s Native: b%s' % (prefix, repr(self.native)))
+ else:
+ print('%s Native: %s' % (prefix, self.native))
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ contents = self.contents
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if self._header is None or force:
+ if isinstance(self, Constructable) and self._indefinite:
+ self.method = 0
+
+ header = _dump_header(self.class_, self.method, self.tag, self.contents)
+
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ header = _dump_header(class_, 1, tag, header + self.contents) + header
+
+ self._header = header
+ self._trailer = b''
+
+ return self._header + contents + self._trailer
+
+
+class ValueMap():
+ """
+ Basic functionality that allows for mapping values from ints or OIDs to
+ python unicode strings
+ """
+
+ # A dict from primitive value (int or OID) to unicode string. This needs
+ # to be defined in the source code
+ _map = None
+
+ # A dict from unicode string to int/OID. This is automatically generated
+ # from _map the first time it is needed
+ _reverse_map = None
+
+ def _setup(self):
+ """
+ Generates _reverse_map from _map
+ """
+
+ cls = self.__class__
+ if cls._map is None or cls._reverse_map is not None:
+ return
+ cls._reverse_map = {}
+ for key, value in cls._map.items():
+ cls._reverse_map[value] = key
+
+
+class Castable(object):
+ """
+ A mixin to handle converting an object between different classes that
+ represent the same encoded value, but with different rules for converting
+ to and from native Python values
+ """
+
+ def cast(self, other_class):
+ """
+ Converts the current object into an object of a different class. The
+ new class must use the ASN.1 encoding for the value.
+
+ :param other_class:
+ The class to instantiate the new object from
+
+ :return:
+ An instance of the type other_class
+ """
+
+ if other_class.tag != self.__class__.tag:
+ raise TypeError(unwrap(
+ '''
+ Can not covert a value from %s object to %s object since they
+ use different tags: %d versus %d
+ ''',
+ type_name(other_class),
+ type_name(self),
+ other_class.tag,
+ self.__class__.tag
+ ))
+
+ new_obj = other_class()
+ new_obj.class_ = self.class_
+ new_obj.implicit = self.implicit
+ new_obj.explicit = self.explicit
+ new_obj._header = self._header
+ new_obj.contents = self.contents
+ new_obj._trailer = self._trailer
+ if isinstance(self, Constructable):
+ new_obj.method = self.method
+ new_obj._indefinite = self._indefinite
+ return new_obj
+
+
+class Constructable(object):
+ """
+ A mixin to handle string types that may be constructed from chunks
+ contained within an indefinite length BER-encoded container
+ """
+
+ # Instance attribute indicating if an object was indefinite
+ # length when parsed - affects parsing and dumping
+ _indefinite = False
+
+ def _merge_chunks(self):
+ """
+ :return:
+ A concatenation of the native values of the contained chunks
+ """
+
+ if not self._indefinite:
+ return self._as_chunk()
+
+ pointer = 0
+ contents_len = len(self.contents)
+ output = None
+
+ while pointer < contents_len:
+ # We pass the current class as the spec so content semantics are preserved
+ sub_value, pointer = _parse_build(self.contents, pointer, spec=self.__class__)
+ if output is None:
+ output = sub_value._merge_chunks()
+ else:
+ output += sub_value._merge_chunks()
+
+ if output is None:
+ return self._as_chunk()
+
+ return output
+
+ def _as_chunk(self):
+ """
+ A method to return a chunk of data that can be combined for
+ constructed method values
+
+ :return:
+ A native Python value that can be added together. Examples include
+ byte strings, unicode strings or tuples.
+ """
+
+ return self.contents
+
+ def _setable_native(self):
+ """
+ Returns a native value that can be round-tripped into .set(), to
+ result in a DER encoding. This differs from .native in that .native
+ is designed for the end use, and may account for the fact that the
+ merged value is further parsed as ASN.1, such as in the case of
+ ParsableOctetString() and ParsableOctetBitString().
+
+ :return:
+ A python value that is valid to pass to .set()
+ """
+
+ return self.native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Constructable object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Constructable, self)._copy(other, copy_func)
+ # We really don't want to dump BER encodings, so if we see an
+ # indefinite encoding, let's re-encode it
+ if other._indefinite:
+ self.set(other._setable_native())
+
+
+class Void(Asn1Value):
+ """
+ A representation of an optional value that is not present. Has .native
+ property and .dump() method to be compatible with other value classes.
+ """
+
+ contents = b''
+
+ def __eq__(self, other):
+ """
+ :param other:
+ The other Primitive to compare to
+
+ :return:
+ A boolean
+ """
+
+ return other.__class__ == self.__class__
+
+ def __nonzero__(self):
+ return False
+
+ def __len__(self):
+ return 0
+
+ def __iter__(self):
+ return iter(())
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ None
+ """
+
+ return None
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ return b''
+
+
+VOID = Void()
+
+
+class Any(Asn1Value):
+ """
+ A value class that can contain any value, and allows for easy parsing of
+ the underlying encoded value using a spec. This is normally contained in
+ a Structure that has an ObjectIdentifier field and _oid_pair and _oid_specs
+ defined.
+ """
+
+ # The parsed value object
+ _parsed = None
+
+ def __init__(self, value=None, **kwargs):
+ """
+ Sets the value of the object before passing to Asn1Value.__init__()
+
+ :param value:
+ An Asn1Value object that will be set as the parsed value
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if value is not None:
+ if not isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ value must be an instance of Asn1Value, not %s
+ ''',
+ type_name(value)
+ ))
+
+ self._parsed = (value, value.__class__, None)
+ self.contents = value.dump()
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ The .native value from the parsed value object
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0].native
+
+ @property
+ def parsed(self):
+ """
+ Returns the parsed object from .parse()
+
+ :return:
+ The object returned by .parse()
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0]
+
+ def parse(self, spec=None, spec_params=None):
+ """
+ Parses the contents generically, or using a spec with optional params
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :return:
+ An object of the type spec, or if not present, a child of Asn1Value
+ """
+
+ if self._parsed is None or self._parsed[1:3] != (spec, spec_params):
+ try:
+ passed_params = spec_params or {}
+ _tag_type_to_explicit_implicit(passed_params)
+ if self.explicit is not None:
+ if 'explicit' in passed_params:
+ passed_params['explicit'] = self.explicit + passed_params['explicit']
+ else:
+ passed_params['explicit'] = self.explicit
+ contents = self._header + self.contents + self._trailer
+ parsed_value, _ = _parse_build(
+ contents,
+ spec=spec,
+ spec_params=passed_params
+ )
+ self._parsed = (parsed_value, spec, spec_params)
+
+ # Once we've parsed the Any value, clear any attributes from this object
+ # since they are now duplicate
+ self.tag = None
+ self.explicit = None
+ self.implicit = False
+ self._header = b''
+ self.contents = contents
+ self._trailer = b''
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._parsed[0]
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Any object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Any, self)._copy(other, copy_func)
+ self._parsed = copy_func(other._parsed)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0].dump(force=force)
+
+
+class Choice(Asn1Value):
+ """
+ A class to handle when a value may be one of several options
+ """
+
+ # The index in _alternatives of the validated alternative
+ _choice = None
+
+ # The name of the chosen alternative
+ _name = None
+
+ # The Asn1Value object for the chosen alternative
+ _parsed = None
+
+ # Choice overrides .contents to be a property so that the code expecting
+ # the .contents attribute will get the .contents of the chosen alternative
+ _contents = None
+
+ # A list of tuples in one of the following forms.
+ #
+ # Option 1, a unicode string field name and a value class
+ #
+ # ("name", Asn1ValueClass)
+ #
+ # Option 2, same as Option 1, but with a dict of class params
+ #
+ # ("name", Asn1ValueClass, {'explicit': 5})
+ _alternatives = None
+
+ # A dict that maps tuples of (class_, tag) to an index in _alternatives
+ _id_map = None
+
+ # A dict that maps alternative names to an index in _alternatives
+ _name_map = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False, **kwargs):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A instance of the current class
+ """
+
+ if not isinstance(encoded_data, byte_cls):
+ raise TypeError('encoded_data must be a byte string, not %s' % type_name(encoded_data))
+
+ value, _ = _parse_build(encoded_data, spec=cls, spec_params=kwargs, strict=strict)
+ return value
+
+ def _setup(self):
+ """
+ Generates _id_map from _alternatives to allow validating contents
+ """
+
+ cls = self.__class__
+ cls._id_map = {}
+ cls._name_map = {}
+ for index, info in enumerate(cls._alternatives):
+ if len(info) < 3:
+ info = info + ({},)
+ cls._alternatives[index] = info
+ id_ = _build_id_tuple(info[2], info[1])
+ cls._id_map[id_] = index
+ cls._name_map[info[0]] = index
+
+ def __init__(self, name=None, value=None, **kwargs):
+ """
+ Checks to ensure implicit tagging is not being used since it is
+ incompatible with Choice, then forwards on to Asn1Value.__init__()
+
+ :param name:
+ The name of the alternative to be set - used with value.
+ Alternatively this may be a dict with a single key being the name
+ and the value being the value, or a two-element tuple of the name
+ and the value.
+
+ :param value:
+ The alternative value to set - used with name
+
+ :raises:
+ ValueError - when implicit param is passed (or legacy tag_type param is "implicit")
+ """
+
+ _tag_type_to_explicit_implicit(kwargs)
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if kwargs.get('implicit') is not None:
+ raise ValueError(unwrap(
+ '''
+ The Choice type can not be implicitly tagged even if in an
+ implicit module - due to its nature any tagging must be
+ explicit
+ '''
+ ))
+
+ if name is not None:
+ if isinstance(name, dict):
+ if len(name) != 1:
+ raise ValueError(unwrap(
+ '''
+ When passing a dict as the "name" argument to %s,
+ it must have a single key/value - however %d were
+ present
+ ''',
+ type_name(self),
+ len(name)
+ ))
+ name, value = list(name.items())[0]
+
+ if isinstance(name, tuple):
+ if len(name) != 2:
+ raise ValueError(unwrap(
+ '''
+ When passing a tuple as the "name" argument to %s,
+ it must have two elements, the name and value -
+ however %d were present
+ ''',
+ type_name(self),
+ len(name)
+ ))
+ value = name[1]
+ name = name[0]
+
+ if name not in self._name_map:
+ raise ValueError(unwrap(
+ '''
+ The name specified, "%s", is not a valid alternative
+ for %s
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ self._choice = self._name_map[name]
+ _, spec, params = self._alternatives[self._choice]
+
+ if not isinstance(value, spec):
+ value = spec(value, **params)
+ else:
+ value = _fix_tagging(value, params)
+ self._parsed = value
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the chosen alternative
+ """
+
+ if self._parsed is not None:
+ return self._parsed.contents
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the chosen alternative
+ """
+
+ self._contents = value
+
+ @property
+ def name(self):
+ """
+ :return:
+ A unicode string of the field name of the chosen alternative
+ """
+ if not self._name:
+ self._name = self._alternatives[self._choice][0]
+ return self._name
+
+ def parse(self):
+ """
+ Parses the detected alternative
+
+ :return:
+ An Asn1Value object of the chosen alternative
+ """
+
+ if self._parsed is None:
+ try:
+ _, spec, params = self._alternatives[self._choice]
+ self._parsed, _ = _parse_build(self._contents, spec=spec, spec_params=params)
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._parsed
+
+ @property
+ def chosen(self):
+ """
+ :return:
+ An Asn1Value object of the chosen alternative
+ """
+
+ return self.parse()
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ The .native value from the contained value object
+ """
+
+ return self.chosen.native
+
+ def validate(self, class_, tag, contents):
+ """
+ Ensures that the class and tag specified exist as an alternative
+
+ :param class_:
+ The integer class_ from the encoded value header
+
+ :param tag:
+ The integer tag from the encoded value header
+
+ :param contents:
+ A byte string of the contents of the value - used when the object
+ is explicitly tagged
+
+ :raises:
+ ValueError - when value is not a valid alternative
+ """
+
+ id_ = (class_, tag)
+
+ if self.explicit is not None:
+ if self.explicit[-1] != id_:
+ raise ValueError(unwrap(
+ '''
+ %s was explicitly tagged, but the value provided does not
+ match the class and tag
+ ''',
+ type_name(self)
+ ))
+
+ ((class_, _, tag, _, _, _), _) = _parse(contents, len(contents))
+ id_ = (class_, tag)
+
+ if id_ in self._id_map:
+ self._choice = self._id_map[id_]
+ return
+
+ # This means the Choice was implicitly tagged
+ if self.class_ is not None and self.tag is not None:
+ if len(self._alternatives) > 1:
+ raise ValueError(unwrap(
+ '''
+ %s was implicitly tagged, but more than one alternative
+ exists
+ ''',
+ type_name(self)
+ ))
+ if id_ == (self.class_, self.tag):
+ self._choice = 0
+ return
+
+ asn1 = self._format_class_tag(class_, tag)
+ asn1s = [self._format_class_tag(pair[0], pair[1]) for pair in self._id_map]
+
+ raise ValueError(unwrap(
+ '''
+ Value %s did not match the class and tag of any of the alternatives
+ in %s: %s
+ ''',
+ asn1,
+ type_name(self),
+ ', '.join(asn1s)
+ ))
+
+ def _format_class_tag(self, class_, tag):
+ """
+ :return:
+ A unicode string of a human-friendly representation of the class and tag
+ """
+
+ return '[%s %s]' % (CLASS_NUM_TO_NAME_MAP[class_].upper(), tag)
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Choice object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Choice, self)._copy(other, copy_func)
+ self._choice = other._choice
+ self._name = other._name
+ self._parsed = copy_func(other._parsed)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ self._contents = self.chosen.dump(force=force)
+ if self._header is None or force:
+ self._header = b''
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ self._header = _dump_header(class_, 1, tag, self._header + self._contents) + self._header
+ return self._header + self._contents
+
+
+class Concat(object):
+ """
+ A class that contains two or more encoded child values concatentated
+ together. THIS IS NOT PART OF THE ASN.1 SPECIFICATION! This exists to handle
+ the x509.TrustedCertificate() class for OpenSSL certificates containing
+ extra information.
+ """
+
+ # A list of the specs of the concatenated values
+ _child_specs = None
+
+ _children = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A Concat object
+ """
+
+ return cls(contents=encoded_data, strict=strict)
+
+ def __init__(self, value=None, contents=None, strict=False):
+ """
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists in contents
+
+ :raises:
+ ValueError - when an error occurs with one of the children
+ TypeError - when an error occurs with one of the children
+ """
+
+ if contents is not None:
+ try:
+ contents_len = len(contents)
+ self._children = []
+
+ offset = 0
+ for spec in self._child_specs:
+ if offset < contents_len:
+ child_value, offset = _parse_build(contents, pointer=offset, spec=spec)
+ else:
+ child_value = spec()
+ self._children.append(child_value)
+
+ if strict and offset != contents_len:
+ extra_bytes = contents_len - offset
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % extra_bytes)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ if value is not None:
+ if self._children is None:
+ self._children = [None] * len(self._child_specs)
+ for index, data in enumerate(value):
+ self.__setitem__(index, data)
+
+ def __str__(self):
+ """
+ Since str is different in Python 2 and 3, this calls the appropriate
+ method, __unicode__() or __bytes__()
+
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return self.__bytes__()
+ else:
+ return self.__unicode__()
+
+ def __bytes__(self):
+ """
+ A byte string of the DER-encoded contents
+ """
+
+ return self.dump()
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return repr(self)
+
+ def __repr__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return '<%s %s %s>' % (type_name(self), id(self), repr(self.dump()))
+
+ def __copy__(self):
+ """
+ Implements the copy.copy() interface
+
+ :return:
+ A new shallow copy of the Concat object
+ """
+
+ new_obj = self.__class__()
+ new_obj._copy(self, copy.copy)
+ return new_obj
+
+ def __deepcopy__(self, memo):
+ """
+ Implements the copy.deepcopy() interface
+
+ :param memo:
+ A dict for memoization
+
+ :return:
+ A new deep copy of the Concat object and all child objects
+ """
+
+ new_obj = self.__class__()
+ memo[id(self)] = new_obj
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def copy(self):
+ """
+ Copies the object
+
+ :return:
+ A Concat object
+ """
+
+ return copy.deepcopy(self)
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Concat object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ if self.__class__ != other.__class__:
+ raise TypeError(unwrap(
+ '''
+ Can not copy values from %s object to %s object
+ ''',
+ type_name(other),
+ type_name(self)
+ ))
+
+ self._children = copy_func(other._children)
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ prefix = ' ' * nest_level
+ print('%s%s Object #%s' % (prefix, type_name(self), id(self)))
+ print('%s Children:' % (prefix,))
+ for child in self._children:
+ child.debug(nest_level + 2)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ contents = b''
+ for child in self._children:
+ contents += child.dump(force=force)
+ return contents
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the children
+ """
+
+ return self.dump()
+
+ def __len__(self):
+ """
+ :return:
+ Integer
+ """
+
+ return len(self._children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing children by index
+
+ :param key:
+ An integer of the child index
+
+ :raises:
+ KeyError - when an index is invalid
+
+ :return:
+ The Asn1Value object of the child specified
+ """
+
+ if key > len(self._child_specs) - 1 or key < 0:
+ raise KeyError(unwrap(
+ '''
+ No child is definition for position %d of %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ return self._children[key]
+
+ def __setitem__(self, key, value):
+ """
+ Allows settings children by index
+
+ :param key:
+ An integer of the child index
+
+ :param value:
+ An Asn1Value object to set the child to
+
+ :raises:
+ KeyError - when an index is invalid
+ ValueError - when the value is not an instance of Asn1Value
+ """
+
+ if key > len(self._child_specs) - 1 or key < 0:
+ raise KeyError(unwrap(
+ '''
+ No child is defined for position %d of %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ if not isinstance(value, Asn1Value):
+ raise ValueError(unwrap(
+ '''
+ Value for child %s of %s is not an instance of
+ asn1crypto.core.Asn1Value
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ self._children[key] = value
+
+ def __iter__(self):
+ """
+ :return:
+ An iterator of child values
+ """
+
+ return iter(self._children)
+
+
+class Primitive(Asn1Value):
+ """
+ Sets the class_ and method attributes for primitive, universal values
+ """
+
+ class_ = 0
+
+ method = 0
+
+ def __init__(self, value=None, default=None, contents=None, **kwargs):
+ """
+ Sets the value of the object before passing to Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+
+ :param contents:
+ A byte string of the encoded contents of the value
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if contents is not None:
+ self.contents = contents
+
+ elif value is not None:
+ self.set(value)
+
+ elif default is not None:
+ self.set(default)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = value
+ self.contents = value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if force:
+ native = self.native
+ self.contents = None
+ self.set(native)
+
+ return Asn1Value.dump(self)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ :param other:
+ The other Primitive to compare to
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, Primitive):
+ return False
+
+ if self.contents != other.contents:
+ return False
+
+ # We compare class tag numbers since object tag numbers could be
+ # different due to implicit or explicit tagging
+ if self.__class__.tag != other.__class__.tag:
+ return False
+
+ if self.__class__ == other.__class__ and self.contents == other.contents:
+ return True
+
+ # If the objects share a common base class that is not too low-level
+ # then we can compare the contents
+ self_bases = (set(self.__class__.__bases__) | set([self.__class__])) - set([Asn1Value, Primitive, ValueMap])
+ other_bases = (set(other.__class__.__bases__) | set([other.__class__])) - set([Asn1Value, Primitive, ValueMap])
+ if self_bases | other_bases:
+ return self.contents == other.contents
+
+ # When tagging is going on, do the extra work of constructing new
+ # objects to see if the dumped representation are the same
+ if self.implicit or self.explicit or other.implicit or other.explicit:
+ return self.untag().dump() == other.untag().dump()
+
+ return self.dump() == other.dump()
+
+
+class AbstractString(Constructable, Primitive):
+ """
+ A base class for all strings that have a known encoding. In general, we do
+ not worry ourselves with confirming that the decoded values match a specific
+ set of characters, only that they are decoded into a Python unicode string
+ """
+
+ # The Python encoding name to use when decoding or encoded the contents
+ _encoding = 'latin1'
+
+ # Instance attribute of (possibly-merged) unicode string
+ _unicode = None
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._unicode = value
+ self.contents = value.encode(self._encoding)
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = self._merge_chunks().decode(self._encoding)
+ return self._unicode
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another AbstractString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(AbstractString, self)._copy(other, copy_func)
+ self._unicode = other._unicode
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__unicode__()
+
+
+class Boolean(Primitive):
+ """
+ Represents a boolean in both ASN.1 and Python
+ """
+
+ tag = 1
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ True, False or another value that works with bool()
+ """
+
+ self._native = bool(value)
+ self.contents = b'\x00' if not value else b'\xff'
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ # Python 2
+ def __nonzero__(self):
+ """
+ :return:
+ True or False
+ """
+ return self.__bool__()
+
+ def __bool__(self):
+ """
+ :return:
+ True or False
+ """
+ return self.contents != b'\x00'
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ True, False or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.__bool__()
+ return self._native
+
+
+class Integer(Primitive, ValueMap):
+ """
+ Represents an integer in both ASN.1 and Python
+ """
+
+ tag = 2
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer, or a unicode string if _map is set
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, str_cls):
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s value is a unicode string, but no _map provided
+ ''',
+ type_name(self)
+ ))
+
+ if value not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s value, %s, is not present in the _map
+ ''',
+ type_name(self),
+ value
+ ))
+
+ value = self._reverse_map[value]
+
+ elif not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be an integer or unicode string when a name_map
+ is provided, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = self._map[value] if self._map and value in self._map else value
+
+ self.contents = int_to_bytes(value, signed=True)
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __int__(self):
+ """
+ :return:
+ An integer
+ """
+ return int_from_bytes(self.contents, signed=True)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.__int__()
+ if self._map is not None and self._native in self._map:
+ self._native = self._map[self._native]
+ return self._native
+
+
+class _IntegerBitString(object):
+ """
+ A mixin for IntegerBitString and BitString to parse the contents as an integer.
+ """
+
+ # Tuple of 1s and 0s; set through native
+ _unused_bits = ()
+
+ def _as_chunk(self):
+ """
+ Parse the contents of a primitive BitString encoding as an integer value.
+ Allows reconstructing indefinite length values.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A list with one tuple (value, bits, unused_bits) where value is an integer
+ with the value of the BitString, bits is the bit count of value and
+ unused_bits is a tuple of 1s and 0s.
+ """
+
+ if self._indefinite:
+ # return an empty chunk, for cases like \x23\x80\x00\x00
+ return []
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ value = int_from_bytes(self.contents[1:])
+ bits = (len(self.contents) - 1) * 8
+
+ if not unused_bits_len:
+ return [(value, bits, ())]
+
+ if len(self.contents) == 1:
+ # Disallowed by X.690 §8.6.2.3
+ raise ValueError('Empty bit string has {0} unused bits'.format(unused_bits_len))
+
+ if unused_bits_len > 7:
+ # Disallowed by X.690 §8.6.2.2
+ raise ValueError('Bit string has {0} unused bits'.format(unused_bits_len))
+
+ unused_bits = _int_to_bit_tuple(value & ((1 << unused_bits_len) - 1), unused_bits_len)
+ value >>= unused_bits_len
+ bits -= unused_bits_len
+
+ return [(value, bits, unused_bits)]
+
+ def _chunks_to_int(self):
+ """
+ Combines the chunks into a single value.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A tuple (value, bits, unused_bits) where value is an integer with the
+ value of the BitString, bits is the bit count of value and unused_bits
+ is a tuple of 1s and 0s.
+ """
+
+ if not self._indefinite:
+ # Fast path
+ return self._as_chunk()[0]
+
+ value = 0
+ total_bits = 0
+ unused_bits = ()
+
+ # X.690 §8.6.3 allows empty indefinite encodings
+ for chunk, bits, unused_bits in self._merge_chunks():
+ if total_bits & 7:
+ # Disallowed by X.690 §8.6.4
+ raise ValueError('Only last chunk in a bit string may have unused bits')
+ total_bits += bits
+ value = (value << bits) | chunk
+
+ return value, total_bits, unused_bits
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another _IntegerBitString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(_IntegerBitString, self)._copy(other, copy_func)
+ self._unused_bits = other._unused_bits
+
+ @property
+ def unused_bits(self):
+ """
+ The unused bits of the bit string encoding.
+
+ :return:
+ A tuple of 1s and 0s
+ """
+
+ # call native to set _unused_bits
+ self.native
+
+ return self._unused_bits
+
+
+class BitString(_IntegerBitString, Constructable, Castable, Primitive, ValueMap):
+ """
+ Represents a bit string from ASN.1 as a Python tuple of 1s and 0s
+ """
+
+ tag = 3
+
+ _size = None
+
+ def _setup(self):
+ """
+ Generates _reverse_map from _map
+ """
+
+ ValueMap._setup(self)
+
+ cls = self.__class__
+ if cls._map is not None:
+ cls._size = max(self._map.keys()) + 1
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer or a tuple of integers 0 and 1
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, set):
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ bits = [0] * self._size
+ self._native = value
+ for index in range(0, self._size):
+ key = self._map.get(index)
+ if key is None:
+ continue
+ if key in value:
+ bits[index] = 1
+
+ value = ''.join(map(str_cls, bits))
+
+ elif value.__class__ == tuple:
+ if self._map is None:
+ self._native = value
+ else:
+ self._native = set()
+ for index, bit in enumerate(value):
+ if bit:
+ name = self._map.get(index, index)
+ self._native.add(name)
+ value = ''.join(map(str_cls, value))
+
+ else:
+ raise TypeError(unwrap(
+ '''
+ %s value must be a tuple of ones and zeros or a set of unicode
+ strings, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if self._map is not None:
+ if len(value) > self._size:
+ raise ValueError(unwrap(
+ '''
+ %s value must be at most %s bits long, specified was %s long
+ ''',
+ type_name(self),
+ self._size,
+ len(value)
+ ))
+ # A NamedBitList must have trailing zero bit truncated. See
+ # https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
+ # section 11.2,
+ # https://tools.ietf.org/html/rfc5280#page-134 and
+ # https://www.ietf.org/mail-archive/web/pkix/current/msg10443.html
+ value = value.rstrip('0')
+ size = len(value)
+
+ size_mod = size % 8
+ extra_bits = 0
+ if size_mod != 0:
+ extra_bits = 8 - size_mod
+ value += '0' * extra_bits
+
+ size_in_bytes = int(math.ceil(size / 8))
+
+ if extra_bits:
+ extra_bits_byte = int_to_bytes(extra_bits)
+ else:
+ extra_bits_byte = b'\x00'
+
+ if value == '':
+ value_bytes = b''
+ else:
+ value_bytes = int_to_bytes(int(value, 2))
+ if len(value_bytes) != size_in_bytes:
+ value_bytes = (b'\x00' * (size_in_bytes - len(value_bytes))) + value_bytes
+
+ self.contents = extra_bits_byte + value_bytes
+ self._unused_bits = (0,) * extra_bits
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __getitem__(self, key):
+ """
+ Retrieves a boolean version of one of the bits based on a name from the
+ _map
+
+ :param key:
+ The unicode string of one of the bit names
+
+ :raises:
+ ValueError - when _map is not set or the key name is invalid
+
+ :return:
+ A boolean if the bit is set
+ """
+
+ is_int = isinstance(key, int_types)
+ if not is_int:
+ if not isinstance(self._map, dict):
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ if key not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(self),
+ key
+ ))
+
+ if self._native is None:
+ self.native
+
+ if self._map is None:
+ if len(self._native) >= key + 1:
+ return bool(self._native[key])
+ return False
+
+ if is_int:
+ key = self._map.get(key, key)
+
+ return key in self._native
+
+ def __setitem__(self, key, value):
+ """
+ Sets one of the bits based on a name from the _map
+
+ :param key:
+ The unicode string of one of the bit names
+
+ :param value:
+ A boolean value
+
+ :raises:
+ ValueError - when _map is not set or the key name is invalid
+ """
+
+ is_int = isinstance(key, int_types)
+ if not is_int:
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ if key not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(self),
+ key
+ ))
+
+ if self._native is None:
+ self.native
+
+ if self._map is None:
+ new_native = list(self._native)
+ max_key = len(new_native) - 1
+ if key > max_key:
+ new_native.extend([0] * (key - max_key))
+ new_native[key] = 1 if value else 0
+ self._native = tuple(new_native)
+
+ else:
+ if is_int:
+ key = self._map.get(key, key)
+
+ if value:
+ if key not in self._native:
+ self._native.add(key)
+ else:
+ if key in self._native:
+ self._native.remove(key)
+
+ self.set(self._native)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ If a _map is set, a set of names, or if no _map is set, a tuple of
+ integers 1 and 0. None if no value.
+ """
+
+ # For BitString we default the value to be all zeros
+ if self.contents is None:
+ if self._map is None:
+ self.set(())
+ else:
+ self.set(set())
+
+ if self._native is None:
+ int_value, bit_count, self._unused_bits = self._chunks_to_int()
+ bits = _int_to_bit_tuple(int_value, bit_count)
+
+ if self._map:
+ self._native = set()
+ for index, bit in enumerate(bits):
+ if bit:
+ name = self._map.get(index, index)
+ self._native.add(name)
+ else:
+ self._native = bits
+ return self._native
+
+
+class OctetBitString(Constructable, Castable, Primitive):
+ """
+ Represents a bit string in ASN.1 as a Python byte string
+ """
+
+ tag = 3
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ # Tuple of 1s and 0s; set through native
+ _unused_bits = ()
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + value
+ self._unused_bits = ()
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ if not self._indefinite:
+ self._bytes, self._unused_bits = self._as_chunk()[0]
+ else:
+ chunks = self._merge_chunks()
+ self._unused_bits = ()
+ for chunk in chunks:
+ if self._unused_bits:
+ # Disallowed by X.690 §8.6.4
+ raise ValueError('Only last chunk in a bit string may have unused bits')
+ self._unused_bits = chunk[1]
+ self._bytes = b''.join(chunk[0] for chunk in chunks)
+
+ return self._bytes
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another OctetBitString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(OctetBitString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+ self._unused_bits = other._unused_bits
+
+ def _as_chunk(self):
+ """
+ Allows reconstructing indefinite length values
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ List with one tuple, consisting of a byte string and an integer (unused bits)
+ """
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ if not unused_bits_len:
+ return [(self.contents[1:], ())]
+
+ if len(self.contents) == 1:
+ # Disallowed by X.690 §8.6.2.3
+ raise ValueError('Empty bit string has {0} unused bits'.format(unused_bits_len))
+
+ if unused_bits_len > 7:
+ # Disallowed by X.690 §8.6.2.2
+ raise ValueError('Bit string has {0} unused bits'.format(unused_bits_len))
+
+ mask = (1 << unused_bits_len) - 1
+ last_byte = ord(self.contents[-1]) if _PY2 else self.contents[-1]
+
+ # zero out the unused bits in the last byte.
+ zeroed_byte = last_byte & ~mask
+ value = self.contents[1:-1] + (chr(zeroed_byte) if _PY2 else bytes((zeroed_byte,)))
+
+ unused_bits = _int_to_bit_tuple(last_byte & mask, unused_bits_len)
+
+ return [(value, unused_bits)]
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__bytes__()
+
+ @property
+ def unused_bits(self):
+ """
+ The unused bits of the bit string encoding.
+
+ :return:
+ A tuple of 1s and 0s
+ """
+
+ # call native to set _unused_bits
+ self.native
+
+ return self._unused_bits
+
+
+class IntegerBitString(_IntegerBitString, Constructable, Castable, Primitive):
+ """
+ Represents a bit string in ASN.1 as a Python integer
+ """
+
+ tag = 3
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a positive integer, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value must be a positive integer, not %d
+ ''',
+ type_name(self),
+ value
+ ))
+
+ self._native = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + int_to_bytes(value, signed=True)
+ self._unused_bits = ()
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native, __, self._unused_bits = self._chunks_to_int()
+
+ return self._native
+
+
+class OctetString(Constructable, Castable, Primitive):
+ """
+ Represents a byte string in both ASN.1 and Python
+ """
+
+ tag = 4
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ self.contents = value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ self._bytes = self._merge_chunks()
+ return self._bytes
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another OctetString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(OctetString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__bytes__()
+
+
+class IntegerOctetString(Constructable, Castable, Primitive):
+ """
+ Represents a byte string in ASN.1 as a Python integer
+ """
+
+ tag = 4
+
+ # An explicit length in bytes the integer should be encoded to. This should
+ # generally not be used since DER defines a canonical encoding, however some
+ # use of this, such as when storing elliptic curve private keys, requires an
+ # exact number of bytes, even if the leading bytes are null.
+ _encoded_width = None
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a positive integer, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value must be a positive integer, not %d
+ ''',
+ type_name(self),
+ value
+ ))
+
+ self._native = value
+ self.contents = int_to_bytes(value, signed=False, width=self._encoded_width)
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = int_from_bytes(self._merge_chunks())
+ return self._native
+
+ def set_encoded_width(self, width):
+ """
+ Set the explicit enoding width for the integer
+
+ :param width:
+ An integer byte width to encode the integer to
+ """
+
+ self._encoded_width = width
+ # Make sure the encoded value is up-to-date with the proper width
+ if self.contents is not None and len(self.contents) != width:
+ self.set(self.native)
+
+
+class ParsableOctetString(Constructable, Castable, Primitive):
+
+ tag = 4
+
+ _parsed = None
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ def __init__(self, value=None, parsed=None, **kwargs):
+ """
+ Allows providing a parsed object that will be serialized to get the
+ byte string value
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param parsed:
+ If value is None and this is an Asn1Value object, this will be
+ set as the parsed value, and the value will be obtained by calling
+ .dump() on this object.
+ """
+
+ set_parsed = False
+ if value is None and parsed is not None and isinstance(parsed, Asn1Value):
+ value = parsed.dump()
+ set_parsed = True
+
+ Primitive.__init__(self, value=value, **kwargs)
+
+ if set_parsed:
+ self._parsed = (parsed, parsed.__class__, None)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ self.contents = value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def parse(self, spec=None, spec_params=None):
+ """
+ Parses the contents generically, or using a spec with optional params
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :return:
+ An object of the type spec, or if not present, a child of Asn1Value
+ """
+
+ if self._parsed is None or self._parsed[1:3] != (spec, spec_params):
+ parsed_value, _ = _parse_build(self.__bytes__(), spec=spec, spec_params=spec_params)
+ self._parsed = (parsed_value, spec, spec_params)
+ return self._parsed[0]
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ self._bytes = self._merge_chunks()
+ return self._bytes
+
+ def _setable_native(self):
+ """
+ Returns a byte string that can be passed into .set()
+
+ :return:
+ A python value that is valid to pass to .set()
+ """
+
+ return self.__bytes__()
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another ParsableOctetString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(ParsableOctetString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+ self._parsed = copy_func(other._parsed)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._parsed is not None:
+ return self._parsed[0].native
+ else:
+ return self.__bytes__()
+
+ @property
+ def parsed(self):
+ """
+ Returns the parsed object from .parse()
+
+ :return:
+ The object returned by .parse()
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0]
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._indefinite:
+ force = True
+
+ if force:
+ if self._parsed is not None:
+ native = self.parsed.dump(force=force)
+ else:
+ native = self.native
+ self.contents = None
+ self.set(native)
+
+ return Asn1Value.dump(self)
+
+
+class ParsableOctetBitString(ParsableOctetString):
+
+ tag = 3
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _as_chunk(self):
+ """
+ Allows reconstructing indefinite length values
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A byte string
+ """
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ if unused_bits_len:
+ raise ValueError('ParsableOctetBitString should have no unused bits')
+
+ return self.contents[1:]
+
+
+class Null(Primitive):
+ """
+ Represents a null value in ASN.1 as None in Python
+ """
+
+ tag = 5
+
+ contents = b''
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None
+ """
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ None
+ """
+
+ return None
+
+
+class ObjectIdentifier(Primitive, ValueMap):
+ """
+ Represents an object identifier in ASN.1 as a Python unicode dotted
+ integer string
+ """
+
+ tag = 6
+
+ # A unicode string of the dotted form of the object identifier
+ _dotted = None
+
+ @classmethod
+ def map(cls, value):
+ """
+ Converts a dotted unicode string OID into a mapped unicode string
+
+ :param value:
+ A dotted unicode string OID
+
+ :raises:
+ ValueError - when no _map dict has been defined on the class
+ TypeError - when value is not a unicode string
+
+ :return:
+ A mapped unicode string
+ """
+
+ if cls._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(cls)
+ ))
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ return cls._map.get(value, value)
+
+ @classmethod
+ def unmap(cls, value):
+ """
+ Converts a mapped unicode string value into a dotted unicode string OID
+
+ :param value:
+ A mapped unicode string OR dotted unicode string OID
+
+ :raises:
+ ValueError - when no _map dict has been defined on the class or the value can't be unmapped
+ TypeError - when value is not a unicode string
+
+ :return:
+ A dotted unicode string OID
+ """
+
+ if cls not in _SETUP_CLASSES:
+ cls()._setup()
+ _SETUP_CLASSES[cls] = True
+
+ if cls._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(cls)
+ ))
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ if value in cls._reverse_map:
+ return cls._reverse_map[value]
+
+ if not _OID_RE.match(value):
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(cls),
+ value
+ ))
+
+ return value
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string. May be a dotted integer string, or if _map is
+ provided, one of the mapped values.
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = value
+
+ if self._map is not None:
+ if value in self._reverse_map:
+ value = self._reverse_map[value]
+
+ self.contents = b''
+ first = None
+ for index, part in enumerate(value.split('.')):
+ part = int(part)
+
+ # The first two parts are merged into a single byte
+ if index == 0:
+ first = part
+ continue
+ elif index == 1:
+ if first > 2:
+ raise ValueError(unwrap(
+ '''
+ First arc must be one of 0, 1 or 2, not %s
+ ''',
+ repr(first)
+ ))
+ elif first < 2 and part >= 40:
+ raise ValueError(unwrap(
+ '''
+ Second arc must be less than 40 if first arc is 0 or
+ 1, not %s
+ ''',
+ repr(part)
+ ))
+ part = (first * 40) + part
+
+ encoded_part = chr_cls(0x7F & part)
+ part = part >> 7
+ while part > 0:
+ encoded_part = chr_cls(0x80 | (0x7F & part)) + encoded_part
+ part = part >> 7
+ self.contents += encoded_part
+
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return self.dotted
+
+ @property
+ def dotted(self):
+ """
+ :return:
+ A unicode string of the object identifier in dotted notation, thus
+ ignoring any mapped value
+ """
+
+ if self._dotted is None:
+ output = []
+
+ part = 0
+ for byte in self.contents:
+ if _PY2:
+ byte = ord(byte)
+ part = part * 128
+ part += byte & 127
+ # Last byte in subidentifier has the eighth bit set to 0
+ if byte & 0x80 == 0:
+ if len(output) == 0:
+ if part >= 80:
+ output.append(str_cls(2))
+ output.append(str_cls(part - 80))
+ elif part >= 40:
+ output.append(str_cls(1))
+ output.append(str_cls(part - 40))
+ else:
+ output.append(str_cls(0))
+ output.append(str_cls(part))
+ else:
+ output.append(str_cls(part))
+ part = 0
+
+ self._dotted = '.'.join(output)
+ return self._dotted
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None. If _map is not defined, the unicode string
+ is a string of dotted integers. If _map is defined and the dotted
+ string is present in the _map, the mapped value is returned.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.dotted
+ if self._map is not None and self._native in self._map:
+ self._native = self._map[self._native]
+ return self._native
+
+
+class ObjectDescriptor(Primitive):
+ """
+ Represents an object descriptor from ASN.1 - no Python implementation
+ """
+
+ tag = 7
+
+
+class InstanceOf(Primitive):
+ """
+ Represents an instance from ASN.1 - no Python implementation
+ """
+
+ tag = 8
+
+
+class Real(Primitive):
+ """
+ Represents a real number from ASN.1 - no Python implementation
+ """
+
+ tag = 9
+
+
+class Enumerated(Integer):
+ """
+ Represents a enumerated list of integers from ASN.1 as a Python
+ unicode string
+ """
+
+ tag = 10
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer or a unicode string from _map
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types) and not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be an integer or a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if isinstance(value, str_cls):
+ if value not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s value "%s" is not a valid value
+ ''',
+ type_name(self),
+ value
+ ))
+
+ value = self._reverse_map[value]
+
+ elif value not in self._map:
+ raise ValueError(unwrap(
+ '''
+ %s value %s is not a valid value
+ ''',
+ type_name(self),
+ value
+ ))
+
+ Integer.set(self, value)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self._map[self.__int__()]
+ return self._native
+
+
+class UTF8String(AbstractString):
+ """
+ Represents a UTF-8 string from ASN.1 as a Python unicode string
+ """
+
+ tag = 12
+ _encoding = 'utf-8'
+
+
+class RelativeOid(ObjectIdentifier):
+ """
+ Represents an object identifier in ASN.1 as a Python unicode dotted
+ integer string
+ """
+
+ tag = 13
+
+
+class Sequence(Asn1Value):
+ """
+ Represents a sequence of fields from ASN.1 as a Python object with a
+ dict-like interface
+ """
+
+ tag = 16
+
+ class_ = 0
+ method = 1
+
+ # A list of child objects, in order of _fields
+ children = None
+
+ # Sequence overrides .contents to be a property so that the mutated state
+ # of child objects can be checked to ensure everything is up-to-date
+ _contents = None
+
+ # Variable to track if the object has been mutated
+ _mutated = False
+
+ # A list of tuples in one of the following forms.
+ #
+ # Option 1, a unicode string field name and a value class
+ #
+ # ("name", Asn1ValueClass)
+ #
+ # Option 2, same as Option 1, but with a dict of class params
+ #
+ # ("name", Asn1ValueClass, {'explicit': 5})
+ _fields = []
+
+ # A dict with keys being the name of a field and the value being a unicode
+ # string of the method name on self to call to get the spec for that field
+ _spec_callbacks = None
+
+ # A dict that maps unicode string field names to an index in _fields
+ _field_map = None
+
+ # A list in the same order as _fields that has tuples in the form (class_, tag)
+ _field_ids = None
+
+ # An optional 2-element tuple that defines the field names of an OID field
+ # and the field that the OID should be used to help decode. Works with the
+ # _oid_specs attribute.
+ _oid_pair = None
+
+ # A dict with keys that are unicode string OID values and values that are
+ # Asn1Value classes to use for decoding a variable-type field.
+ _oid_specs = None
+
+ # A 2-element tuple of the indexes in _fields of the OID and value fields
+ _oid_nums = None
+
+ # Predetermined field specs to optimize away calls to _determine_spec()
+ _precomputed_specs = None
+
+ def __init__(self, value=None, default=None, **kwargs):
+ """
+ Allows setting field values before passing everything else along to
+ Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ check_existing = False
+ if value is None and default is not None:
+ check_existing = True
+ if self.children is None:
+ if self.contents is None:
+ check_existing = False
+ else:
+ self._parse_children()
+ value = default
+
+ if value is not None:
+ try:
+ # Fields are iterated in definition order to allow things like
+ # OID-based specs. Otherwise sometimes the value would be processed
+ # before the OID field, resulting in invalid value object creation.
+ if self._fields:
+ keys = [info[0] for info in self._fields]
+ unused_keys = set(value.keys())
+ else:
+ keys = value.keys()
+ unused_keys = set(keys)
+
+ for key in keys:
+ # If we are setting defaults, but a real value has already
+ # been set for the field, then skip it
+ if check_existing:
+ index = self._field_map[key]
+ if index < len(self.children) and self.children[index] is not VOID:
+ if key in unused_keys:
+ unused_keys.remove(key)
+ continue
+
+ if key in value:
+ self.__setitem__(key, value[key])
+ unused_keys.remove(key)
+
+ if len(unused_keys):
+ raise ValueError(unwrap(
+ '''
+ One or more unknown fields was passed to the constructor
+ of %s: %s
+ ''',
+ type_name(self),
+ ', '.join(sorted(list(unused_keys)))
+ ))
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ if self.children is None:
+ return self._contents
+
+ if self._is_mutated():
+ self._set_contents()
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._contents = value
+
+ def _is_mutated(self):
+ """
+ :return:
+ A boolean - if the sequence or any children (recursively) have been
+ mutated
+ """
+
+ mutated = self._mutated
+ if self.children is not None:
+ for child in self.children:
+ if isinstance(child, Sequence) or isinstance(child, SequenceOf):
+ mutated = mutated or child._is_mutated()
+
+ return mutated
+
+ def _lazy_child(self, index):
+ """
+ Builds a child object if the child has only been parsed into a tuple so far
+ """
+
+ child = self.children[index]
+ if child.__class__ == tuple:
+ child = self.children[index] = _build(*child)
+ return child
+
+ def __len__(self):
+ """
+ :return:
+ Integer
+ """
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return len(self.children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :raises:
+ KeyError - when a field name or index is invalid
+
+ :return:
+ The Asn1Value object of the field specified
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ if key >= len(self.children):
+ raise KeyError(unwrap(
+ '''
+ No field numbered %s is present in this %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ try:
+ return self._lazy_child(key)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def __setitem__(self, key, value):
+ """
+ Allows settings fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :param value:
+ A native Python datatype to set the field value to. This method will
+ construct the appropriate Asn1Value object from _fields.
+
+ :raises:
+ ValueError - when a field name or index is invalid
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(key)
+
+ new_value = self._make_value(field_name, field_spec, value_spec, field_params, value)
+
+ invalid_value = False
+ if isinstance(new_value, Any):
+ invalid_value = new_value.parsed is None
+ else:
+ invalid_value = new_value.contents is None
+
+ if invalid_value:
+ raise ValueError(unwrap(
+ '''
+ Value for field "%s" of %s is not set
+ ''',
+ field_name,
+ type_name(self)
+ ))
+
+ self.children[key] = new_value
+
+ if self._native is not None:
+ self._native[self._fields[key][0]] = self.children[key].native
+ self._mutated = True
+
+ def __delitem__(self, key):
+ """
+ Allows deleting optional or default fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :raises:
+ ValueError - when a field name or index is invalid, or the field is not optional or defaulted
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ name, _, params = self._fields[key]
+ if not params or ('default' not in params and 'optional' not in params):
+ raise ValueError(unwrap(
+ '''
+ Can not delete the value for the field "%s" of %s since it is
+ not optional or defaulted
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ if 'optional' in params:
+ self.children[key] = VOID
+ if self._native is not None:
+ self._native[name] = None
+ else:
+ self.__setitem__(key, None)
+ self._mutated = True
+
+ def __iter__(self):
+ """
+ :return:
+ An iterator of field key names
+ """
+
+ for info in self._fields:
+ yield info[0]
+
+ def _set_contents(self, force=False):
+ """
+ Updates the .contents attribute of the value with the encoded value of
+ all of the child objects
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ contents = BytesIO()
+ for index, info in enumerate(self._fields):
+ child = self.children[index]
+ if child is None:
+ child_dump = b''
+ elif child.__class__ == tuple:
+ if force:
+ child_dump = self._lazy_child(index).dump(force=force)
+ else:
+ child_dump = child[3] + child[4] + child[5]
+ else:
+ child_dump = child.dump(force=force)
+ # Skip values that are the same as the default
+ if info[2] and 'default' in info[2]:
+ default_value = info[1](**info[2])
+ if default_value.dump() == child_dump:
+ continue
+ contents.write(child_dump)
+ self._contents = contents.getvalue()
+
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _setup(self):
+ """
+ Generates _field_map, _field_ids and _oid_nums for use in parsing
+ """
+
+ cls = self.__class__
+ cls._field_map = {}
+ cls._field_ids = []
+ cls._precomputed_specs = []
+ for index, field in enumerate(cls._fields):
+ if len(field) < 3:
+ field = field + ({},)
+ cls._fields[index] = field
+ cls._field_map[field[0]] = index
+ cls._field_ids.append(_build_id_tuple(field[2], field[1]))
+
+ if cls._oid_pair is not None:
+ cls._oid_nums = (cls._field_map[cls._oid_pair[0]], cls._field_map[cls._oid_pair[1]])
+
+ for index, field in enumerate(cls._fields):
+ has_callback = cls._spec_callbacks is not None and field[0] in cls._spec_callbacks
+ is_mapped_oid = cls._oid_nums is not None and cls._oid_nums[1] == index
+ if has_callback or is_mapped_oid:
+ cls._precomputed_specs.append(None)
+ else:
+ cls._precomputed_specs.append((field[0], field[1], field[1], field[2], None))
+
+ def _determine_spec(self, index):
+ """
+ Determine how a value for a field should be constructed
+
+ :param index:
+ The field number
+
+ :return:
+ A tuple containing the following elements:
+ - unicode string of the field name
+ - Asn1Value class of the field spec
+ - Asn1Value class of the value spec
+ - None or dict of params to pass to the field spec
+ - None or Asn1Value class indicating the value spec was derived from an OID or a spec callback
+ """
+
+ name, field_spec, field_params = self._fields[index]
+ value_spec = field_spec
+ spec_override = None
+
+ if self._spec_callbacks is not None and name in self._spec_callbacks:
+ callback = self._spec_callbacks[name]
+ spec_override = callback(self)
+ if spec_override:
+ # Allow a spec callback to specify both the base spec and
+ # the override, for situations such as OctetString and parse_as
+ if spec_override.__class__ == tuple and len(spec_override) == 2:
+ field_spec, value_spec = spec_override
+ if value_spec is None:
+ value_spec = field_spec
+ spec_override = None
+ # When no field spec is specified, use a single return value as that
+ elif field_spec is None:
+ field_spec = spec_override
+ value_spec = field_spec
+ spec_override = None
+ else:
+ value_spec = spec_override
+
+ elif self._oid_nums is not None and self._oid_nums[1] == index:
+ oid = self._lazy_child(self._oid_nums[0]).native
+ if oid in self._oid_specs:
+ spec_override = self._oid_specs[oid]
+ value_spec = spec_override
+
+ return (name, field_spec, value_spec, field_params, spec_override)
+
+ def _make_value(self, field_name, field_spec, value_spec, field_params, value):
+ """
+ Contructs an appropriate Asn1Value object for a field
+
+ :param field_name:
+ A unicode string of the field name
+
+ :param field_spec:
+ An Asn1Value class that is the field spec
+
+ :param value_spec:
+ An Asn1Value class that is the vaue spec
+
+ :param field_params:
+ None or a dict of params for the field spec
+
+ :param value:
+ The value to construct an Asn1Value object from
+
+ :return:
+ An instance of a child class of Asn1Value
+ """
+
+ if value is None and 'optional' in field_params:
+ return VOID
+
+ specs_different = field_spec != value_spec
+ is_any = issubclass(field_spec, Any)
+
+ if issubclass(value_spec, Choice):
+ is_asn1value = isinstance(value, Asn1Value)
+ is_tuple = isinstance(value, tuple) and len(value) == 2
+ is_dict = isinstance(value, dict) and len(value) == 1
+ if not is_asn1value and not is_tuple and not is_dict:
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s, which has the
+ choice type of %s - value must be an instance of Asn1Value
+ ''',
+ field_name,
+ type_name(value_spec)
+ ))
+ if is_tuple or is_dict:
+ value = value_spec(value)
+ if not isinstance(value, value_spec):
+ wrapper = value_spec()
+ wrapper.validate(value.class_, value.tag, value.contents)
+ wrapper._parsed = value
+ new_value = wrapper
+ else:
+ new_value = value
+
+ elif isinstance(value, field_spec):
+ new_value = value
+ if specs_different:
+ new_value.parse(value_spec)
+
+ elif (not specs_different or is_any) and not isinstance(value, value_spec):
+ if (not is_any or specs_different) and isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ %s value must be %s, not %s
+ ''',
+ field_name,
+ type_name(value_spec),
+ type_name(value)
+ ))
+ new_value = value_spec(value, **field_params)
+
+ else:
+ if isinstance(value, value_spec):
+ new_value = value
+ else:
+ if isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ %s value must be %s, not %s
+ ''',
+ field_name,
+ type_name(value_spec),
+ type_name(value)
+ ))
+ new_value = value_spec(value)
+
+ # For when the field is OctetString or OctetBitString with embedded
+ # values we need to wrap the value in the field spec to get the
+ # appropriate encoded value.
+ if specs_different and not is_any:
+ wrapper = field_spec(value=new_value.dump(), **field_params)
+ wrapper._parsed = (new_value, new_value.__class__, None)
+ new_value = wrapper
+
+ new_value = _fix_tagging(new_value, field_params)
+
+ return new_value
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _fields.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ cls = self.__class__
+ if self._contents is None:
+ if self._fields:
+ self.children = [VOID] * len(self._fields)
+ for index, (_, _, params) in enumerate(self._fields):
+ if 'default' in params:
+ if cls._precomputed_specs[index]:
+ field_name, field_spec, value_spec, field_params, _ = cls._precomputed_specs[index]
+ else:
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(index)
+ self.children[index] = self._make_value(field_name, field_spec, value_spec, field_params, None)
+ return
+
+ try:
+ self.children = []
+ contents_length = len(self._contents)
+ child_pointer = 0
+ field = 0
+ field_len = len(self._fields)
+ parts = None
+ again = child_pointer < contents_length
+ while again:
+ if parts is None:
+ parts, child_pointer = _parse(self._contents, contents_length, pointer=child_pointer)
+ again = child_pointer < contents_length
+
+ if field < field_len:
+ _, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[field] or self._determine_spec(field))
+
+ # If the next value is optional or default, allow it to be absent
+ if field_params and ('optional' in field_params or 'default' in field_params):
+ if self._field_ids[field] != (parts[0], parts[2]) and field_spec != Any:
+
+ # See if the value is a valid choice before assuming
+ # that we have a missing optional or default value
+ choice_match = False
+ if issubclass(field_spec, Choice):
+ try:
+ tester = field_spec(**field_params)
+ tester.validate(parts[0], parts[2], parts[4])
+ choice_match = True
+ except (ValueError):
+ pass
+
+ if not choice_match:
+ if 'optional' in field_params:
+ self.children.append(VOID)
+ else:
+ self.children.append(field_spec(**field_params))
+ field += 1
+ again = True
+ continue
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ if spec_override:
+ child = parts + (field_spec, field_params, value_spec)
+ else:
+ child = parts + (field_spec, field_params)
+
+ # Handle situations where an optional or defaulted field definition is incorrect
+ elif field_len > 0 and field + 1 <= field_len:
+ missed_fields = []
+ prev_field = field - 1
+ while prev_field >= 0:
+ prev_field_info = self._fields[prev_field]
+ if len(prev_field_info) < 3:
+ break
+ if 'optional' in prev_field_info[2] or 'default' in prev_field_info[2]:
+ missed_fields.append(prev_field_info[0])
+ prev_field -= 1
+ plural = 's' if len(missed_fields) > 1 else ''
+ missed_field_names = ', '.join(missed_fields)
+ raise ValueError(unwrap(
+ '''
+ Data for field %s (%s class, %s method, tag %s) does
+ not match the field definition%s of %s
+ ''',
+ field + 1,
+ CLASS_NUM_TO_NAME_MAP.get(parts[0]),
+ METHOD_NUM_TO_NAME_MAP.get(parts[1]),
+ parts[2],
+ plural,
+ missed_field_names
+ ))
+
+ else:
+ child = parts
+
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+
+ self.children.append(child)
+ field += 1
+ parts = None
+
+ index = len(self.children)
+ while index < field_len:
+ name, field_spec, field_params = self._fields[index]
+ if 'default' in field_params:
+ self.children.append(field_spec(**field_params))
+ elif 'optional' in field_params:
+ self.children.append(VOID)
+ else:
+ raise ValueError(unwrap(
+ '''
+ Field "%s" is missing from structure
+ ''',
+ name
+ ))
+ index += 1
+
+ except (ValueError, TypeError) as e:
+ self.children = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def spec(self, field_name):
+ """
+ Determines the spec to use for the field specified. Depending on how
+ the spec is determined (_oid_pair or _spec_callbacks), it may be
+ necessary to set preceding field values before calling this. Usually
+ specs, if dynamic, are controlled by a preceding ObjectIdentifier
+ field.
+
+ :param field_name:
+ A unicode string of the field name to get the spec for
+
+ :return:
+ A child class of asn1crypto.core.Asn1Value that the field must be
+ encoded using
+ """
+
+ if not isinstance(field_name, str_cls):
+ raise TypeError(unwrap(
+ '''
+ field_name must be a unicode string, not %s
+ ''',
+ type_name(field_name)
+ ))
+
+ if self._fields is None:
+ raise ValueError(unwrap(
+ '''
+ Unable to retrieve spec for field %s in the class %s because
+ _fields has not been set
+ ''',
+ repr(field_name),
+ type_name(self)
+ ))
+
+ index = self._field_map[field_name]
+ info = self._determine_spec(index)
+
+ return info[2]
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An OrderedDict or None. If an OrderedDict, all child values are
+ recursively converted to native representation also.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ if self.children is None:
+ self._parse_children(recurse=True)
+ try:
+ self._native = OrderedDict()
+ for index, child in enumerate(self.children):
+ if child.__class__ == tuple:
+ child = _build(*child)
+ self.children[index] = child
+ try:
+ name = self._fields[index][0]
+ except (IndexError):
+ name = str_cls(index)
+ self._native[name] = child.native
+ except (ValueError, TypeError) as e:
+ self._native = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Sequence object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Sequence, self)._copy(other, copy_func)
+ if self.children is not None:
+ self.children = []
+ for child in other.children:
+ if child.__class__ == tuple:
+ self.children.append(child)
+ else:
+ self.children.append(child.copy())
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ prefix = ' ' * nest_level
+ _basic_debug(prefix, self)
+ for field_name in self:
+ child = self._lazy_child(self._field_map[field_name])
+ if child is not VOID:
+ print('%s Field "%s"' % (prefix, field_name))
+ child.debug(nest_level + 3)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ # We can't force encoding if we don't have a spec
+ if force and self._fields == [] and self.__class__ is Sequence:
+ force = False
+
+ if force:
+ self._set_contents(force=force)
+
+ if self._fields and self.children is not None:
+ for index, (field_name, _, params) in enumerate(self._fields):
+ if self.children[index] is not VOID:
+ continue
+ if 'default' in params or 'optional' in params:
+ continue
+ raise ValueError(unwrap(
+ '''
+ Field "%s" is missing from structure
+ ''',
+ field_name
+ ))
+
+ return Asn1Value.dump(self)
+
+
+class SequenceOf(Asn1Value):
+ """
+ Represents a sequence (ordered) of a single type of values from ASN.1 as a
+ Python object with a list-like interface
+ """
+
+ tag = 16
+
+ class_ = 0
+ method = 1
+
+ # A list of child objects
+ children = None
+
+ # SequenceOf overrides .contents to be a property so that the mutated state
+ # of child objects can be checked to ensure everything is up-to-date
+ _contents = None
+
+ # Variable to track if the object has been mutated
+ _mutated = False
+
+ # An Asn1Value class to use when parsing children
+ _child_spec = None
+
+ def __init__(self, value=None, default=None, contents=None, spec=None, **kwargs):
+ """
+ Allows setting child objects and the _child_spec via the spec parameter
+ before passing everything else along to Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param spec:
+ A class derived from Asn1Value to use to parse children
+ """
+
+ if spec:
+ self._child_spec = spec
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if contents is not None:
+ self.contents = contents
+ else:
+ if value is None and default is not None:
+ value = default
+
+ if value is not None:
+ for index, child in enumerate(value):
+ self.__setitem__(index, child)
+
+ # Make sure a blank list is serialized
+ if self.contents is None:
+ self._set_contents()
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ if self.children is None:
+ return self._contents
+
+ if self._is_mutated():
+ self._set_contents()
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._contents = value
+
+ def _is_mutated(self):
+ """
+ :return:
+ A boolean - if the sequence or any children (recursively) have been
+ mutated
+ """
+
+ mutated = self._mutated
+ if self.children is not None:
+ for child in self.children:
+ if isinstance(child, Sequence) or isinstance(child, SequenceOf):
+ mutated = mutated or child._is_mutated()
+
+ return mutated
+
+ def _lazy_child(self, index):
+ """
+ Builds a child object if the child has only been parsed into a tuple so far
+ """
+
+ child = self.children[index]
+ if child.__class__ == tuple:
+ child = _build(*child)
+ self.children[index] = child
+ return child
+
+ def _make_value(self, value):
+ """
+ Constructs a _child_spec value from a native Python data type, or
+ an appropriate Asn1Value object
+
+ :param value:
+ A native Python value, or some child of Asn1Value
+
+ :return:
+ An object of type _child_spec
+ """
+
+ if isinstance(value, self._child_spec):
+ new_value = value
+
+ elif issubclass(self._child_spec, Any):
+ if isinstance(value, Asn1Value):
+ new_value = value
+ else:
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s where the
+ _child_spec is Any - value must be an instance of Asn1Value
+ ''',
+ type_name(self)
+ ))
+
+ elif issubclass(self._child_spec, Choice):
+ if not isinstance(value, Asn1Value):
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s where the
+ _child_spec is the choice type %s - value must be an
+ instance of Asn1Value
+ ''',
+ type_name(self),
+ self._child_spec.__name__
+ ))
+ if not isinstance(value, self._child_spec):
+ wrapper = self._child_spec()
+ wrapper.validate(value.class_, value.tag, value.contents)
+ wrapper._parsed = value
+ value = wrapper
+ new_value = value
+
+ else:
+ return self._child_spec(value=value)
+
+ params = {}
+ if self._child_spec.explicit:
+ params['explicit'] = self._child_spec.explicit
+ if self._child_spec.implicit:
+ params['implicit'] = (self._child_spec.class_, self._child_spec.tag)
+ return _fix_tagging(new_value, params)
+
+ def __len__(self):
+ """
+ :return:
+ An integer
+ """
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return len(self.children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing children via index
+
+ :param key:
+ Integer index of child
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return self._lazy_child(key)
+
+ def __setitem__(self, key, value):
+ """
+ Allows overriding a child via index
+
+ :param key:
+ Integer index of child
+
+ :param value:
+ Native python datatype that will be passed to _child_spec to create
+ new child object
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ new_value = self._make_value(value)
+
+ # If adding at the end, create a space for the new value
+ if key == len(self.children):
+ self.children.append(None)
+ if self._native is not None:
+ self._native.append(None)
+
+ self.children[key] = new_value
+
+ if self._native is not None:
+ self._native[key] = self.children[key].native
+
+ self._mutated = True
+
+ def __delitem__(self, key):
+ """
+ Allows removing a child via index
+
+ :param key:
+ Integer index of child
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ self.children.pop(key)
+ if self._native is not None:
+ self._native.pop(key)
+
+ self._mutated = True
+
+ def __iter__(self):
+ """
+ :return:
+ An iter() of child objects
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ for index in range(0, len(self.children)):
+ yield self._lazy_child(index)
+
+ def __contains__(self, item):
+ """
+ :param item:
+ An object of the type cls._child_spec
+
+ :return:
+ A boolean if the item is contained in this SequenceOf
+ """
+
+ if item is None or item is VOID:
+ return False
+
+ if not isinstance(item, self._child_spec):
+ raise TypeError(unwrap(
+ '''
+ Checking membership in %s is only available for instances of
+ %s, not %s
+ ''',
+ type_name(self),
+ type_name(self._child_spec),
+ type_name(item)
+ ))
+
+ for child in self:
+ if child == item:
+ return True
+
+ return False
+
+ def append(self, value):
+ """
+ Allows adding a child to the end of the sequence
+
+ :param value:
+ Native python datatype that will be passed to _child_spec to create
+ new child object
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ self.children.append(self._make_value(value))
+
+ if self._native is not None:
+ self._native.append(self.children[-1].native)
+
+ self._mutated = True
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ contents = BytesIO()
+ for child in self:
+ contents.write(child.dump(force=force))
+ self._contents = contents.getvalue()
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _child_spec.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ try:
+ self.children = []
+ if self._contents is None:
+ return
+ contents_length = len(self._contents)
+ child_pointer = 0
+ while child_pointer < contents_length:
+ parts, child_pointer = _parse(self._contents, contents_length, pointer=child_pointer)
+ if self._child_spec:
+ child = parts + (self._child_spec,)
+ else:
+ child = parts
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+ self.children.append(child)
+ except (ValueError, TypeError) as e:
+ self.children = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def spec(self):
+ """
+ Determines the spec to use for child values.
+
+ :return:
+ A child class of asn1crypto.core.Asn1Value that child values must be
+ encoded using
+ """
+
+ return self._child_spec
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A list or None. If a list, all child values are recursively
+ converted to native representation also.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ if self.children is None:
+ self._parse_children(recurse=True)
+ try:
+ self._native = [child.native for child in self]
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another SequenceOf object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(SequenceOf, self)._copy(other, copy_func)
+ if self.children is not None:
+ self.children = []
+ for child in other.children:
+ if child.__class__ == tuple:
+ self.children.append(child)
+ else:
+ self.children.append(child.copy())
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ prefix = ' ' * nest_level
+ _basic_debug(prefix, self)
+ for child in self:
+ child.debug(nest_level + 1)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if force:
+ self._set_contents(force=force)
+
+ return Asn1Value.dump(self)
+
+
+class Set(Sequence):
+ """
+ Represents a set of fields (unordered) from ASN.1 as a Python object with a
+ dict-like interface
+ """
+
+ method = 1
+ class_ = 0
+ tag = 17
+
+ # A dict of 2-element tuples in the form (class_, tag) as keys and integers
+ # as values that are the index of the field in _fields
+ _field_ids = None
+
+ def _setup(self):
+ """
+ Generates _field_map, _field_ids and _oid_nums for use in parsing
+ """
+
+ cls = self.__class__
+ cls._field_map = {}
+ cls._field_ids = {}
+ cls._precomputed_specs = []
+ for index, field in enumerate(cls._fields):
+ if len(field) < 3:
+ field = field + ({},)
+ cls._fields[index] = field
+ cls._field_map[field[0]] = index
+ cls._field_ids[_build_id_tuple(field[2], field[1])] = index
+
+ if cls._oid_pair is not None:
+ cls._oid_nums = (cls._field_map[cls._oid_pair[0]], cls._field_map[cls._oid_pair[1]])
+
+ for index, field in enumerate(cls._fields):
+ has_callback = cls._spec_callbacks is not None and field[0] in cls._spec_callbacks
+ is_mapped_oid = cls._oid_nums is not None and cls._oid_nums[1] == index
+ if has_callback or is_mapped_oid:
+ cls._precomputed_specs.append(None)
+ else:
+ cls._precomputed_specs.append((field[0], field[1], field[1], field[2], None))
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _fields.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ cls = self.__class__
+ if self._contents is None:
+ if self._fields:
+ self.children = [VOID] * len(self._fields)
+ for index, (_, _, params) in enumerate(self._fields):
+ if 'default' in params:
+ if cls._precomputed_specs[index]:
+ field_name, field_spec, value_spec, field_params, _ = cls._precomputed_specs[index]
+ else:
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(index)
+ self.children[index] = self._make_value(field_name, field_spec, value_spec, field_params, None)
+ return
+
+ try:
+ child_map = {}
+ contents_length = len(self.contents)
+ child_pointer = 0
+ seen_field = 0
+ while child_pointer < contents_length:
+ parts, child_pointer = _parse(self.contents, contents_length, pointer=child_pointer)
+
+ id_ = (parts[0], parts[2])
+
+ field = self._field_ids.get(id_)
+ if field is None:
+ raise ValueError(unwrap(
+ '''
+ Data for field %s (%s class, %s method, tag %s) does
+ not match any of the field definitions
+ ''',
+ seen_field,
+ CLASS_NUM_TO_NAME_MAP.get(parts[0]),
+ METHOD_NUM_TO_NAME_MAP.get(parts[1]),
+ parts[2],
+ ))
+
+ _, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[field] or self._determine_spec(field))
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ if spec_override:
+ child = parts + (field_spec, field_params, value_spec)
+ else:
+ child = parts + (field_spec, field_params)
+
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+
+ child_map[field] = child
+ seen_field += 1
+
+ total_fields = len(self._fields)
+
+ for index in range(0, total_fields):
+ if index in child_map:
+ continue
+
+ name, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[index] or self._determine_spec(index))
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ missing = False
+
+ if not field_params:
+ missing = True
+ elif 'optional' not in field_params and 'default' not in field_params:
+ missing = True
+ elif 'optional' in field_params:
+ child_map[index] = VOID
+ elif 'default' in field_params:
+ child_map[index] = field_spec(**field_params)
+
+ if missing:
+ raise ValueError(unwrap(
+ '''
+ Missing required field "%s" from %s
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ self.children = []
+ for index in range(0, total_fields):
+ self.children.append(child_map[index])
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object.
+
+ This method is overridden because a Set needs to be encoded by
+ removing defaulted fields and then sorting the fields by tag.
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ child_tag_encodings = []
+ for index, child in enumerate(self.children):
+ child_encoding = child.dump(force=force)
+
+ # Skip encoding defaulted children
+ name, spec, field_params = self._fields[index]
+ if 'default' in field_params:
+ if spec(**field_params).dump() == child_encoding:
+ continue
+
+ child_tag_encodings.append((child.tag, child_encoding))
+ child_tag_encodings.sort(key=lambda ct: ct[0])
+
+ self._contents = b''.join([ct[1] for ct in child_tag_encodings])
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class SetOf(SequenceOf):
+ """
+ Represents a set (unordered) of a single type of values from ASN.1 as a
+ Python object with a list-like interface
+ """
+
+ tag = 17
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object.
+
+ This method is overridden because a SetOf needs to be encoded by
+ sorting the child encodings.
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ child_encodings = []
+ for child in self:
+ child_encodings.append(child.dump(force=force))
+
+ self._contents = b''.join(sorted(child_encodings))
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class EmbeddedPdv(Sequence):
+ """
+ A sequence structure
+ """
+
+ tag = 11
+
+
+class NumericString(AbstractString):
+ """
+ Represents a numeric string from ASN.1 as a Python unicode string
+ """
+
+ tag = 18
+ _encoding = 'latin1'
+
+
+class PrintableString(AbstractString):
+ """
+ Represents a printable string from ASN.1 as a Python unicode string
+ """
+
+ tag = 19
+ _encoding = 'latin1'
+
+
+class TeletexString(AbstractString):
+ """
+ Represents a teletex string from ASN.1 as a Python unicode string
+ """
+
+ tag = 20
+ _encoding = 'teletex'
+
+
+class VideotexString(OctetString):
+ """
+ Represents a videotex string from ASN.1 as a Python byte string
+ """
+
+ tag = 21
+
+
+class IA5String(AbstractString):
+ """
+ Represents an IA5 string from ASN.1 as a Python unicode string
+ """
+
+ tag = 22
+ _encoding = 'ascii'
+
+
+class AbstractTime(AbstractString):
+ """
+ Represents a time from ASN.1 as a Python datetime.datetime object
+ """
+
+ @property
+ def _parsed_time(self):
+ """
+ The parsed datetime string.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A dict with the parsed values
+ """
+
+ string = str_cls(self)
+
+ m = self._TIMESTRING_RE.match(string)
+ if not m:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s to a %s
+ ''',
+ string,
+ type_name(self),
+ ))
+
+ groups = m.groupdict()
+
+ tz = None
+ if groups['zulu']:
+ tz = timezone.utc
+ elif groups['dsign']:
+ sign = 1 if groups['dsign'] == '+' else -1
+ tz = create_timezone(sign * timedelta(
+ hours=int(groups['dhour']),
+ minutes=int(groups['dminute'] or 0)
+ ))
+
+ if groups['fraction']:
+ # Compute fraction in microseconds
+ fract = Fraction(
+ int(groups['fraction']),
+ 10 ** len(groups['fraction'])
+ ) * 1000000
+
+ if groups['minute'] is None:
+ fract *= 3600
+ elif groups['second'] is None:
+ fract *= 60
+
+ fract_usec = int(fract.limit_denominator(1))
+
+ else:
+ fract_usec = 0
+
+ return {
+ 'year': int(groups['year']),
+ 'month': int(groups['month']),
+ 'day': int(groups['day']),
+ 'hour': int(groups['hour']),
+ 'minute': int(groups['minute'] or 0),
+ 'second': int(groups['second'] or 0),
+ 'tzinfo': tz,
+ 'fraction': fract_usec,
+ }
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A datetime.datetime object, asn1crypto.util.extended_datetime object or
+ None. The datetime object is usually timezone aware. If it's naive, then
+ it's in the sender's local time; see X.680 sect. 42.3
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ parsed = self._parsed_time
+
+ fraction = parsed.pop('fraction', 0)
+
+ value = self._get_datetime(parsed)
+
+ if fraction:
+ value += timedelta(microseconds=fraction)
+
+ self._native = value
+
+ return self._native
+
+
+class UTCTime(AbstractTime):
+ """
+ Represents a UTC time from ASN.1 as a timezone aware Python datetime.datetime object
+ """
+
+ tag = 23
+
+ # Regular expression for UTCTime as described in X.680 sect. 43 and ISO 8601
+ _TIMESTRING_RE = re.compile(r'''
+ ^
+ # YYMMDD
+ (?P<year>\d{2})
+ (?P<month>\d{2})
+ (?P<day>\d{2})
+
+ # hhmm or hhmmss
+ (?P<hour>\d{2})
+ (?P<minute>\d{2})
+ (?P<second>\d{2})?
+
+ # Matches nothing, needed because GeneralizedTime uses this.
+ (?P<fraction>)
+
+ # Z or [-+]hhmm
+ (?:
+ (?P<zulu>Z)
+ |
+ (?:
+ (?P<dsign>[-+])
+ (?P<dhour>\d{2})
+ (?P<dminute>\d{2})
+ )
+ )
+ $
+ ''', re.X)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string or a datetime.datetime object
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, datetime):
+ if not value.tzinfo:
+ raise ValueError('Must be timezone aware')
+
+ # Convert value to UTC.
+ value = value.astimezone(utc_with_dst)
+
+ if not 1950 <= value.year <= 2049:
+ raise ValueError('Year of the UTCTime is not in range [1950, 2049], use GeneralizedTime instead')
+
+ value = value.strftime('%y%m%d%H%M%SZ')
+ if _PY2:
+ value = value.decode('ascii')
+
+ AbstractString.set(self, value)
+ # Set it to None and let the class take care of converting the next
+ # time that .native is called
+ self._native = None
+
+ def _get_datetime(self, parsed):
+ """
+ Create a datetime object from the parsed time.
+
+ :return:
+ An aware datetime.datetime object
+ """
+
+ # X.680 only specifies that UTCTime is not using a century.
+ # So "18" could as well mean 2118 or 1318.
+ # X.509 and CMS specify to use UTCTime for years earlier than 2050.
+ # Assume that UTCTime is only used for years [1950, 2049].
+ if parsed['year'] < 50:
+ parsed['year'] += 2000
+ else:
+ parsed['year'] += 1900
+
+ return datetime(**parsed)
+
+
+class GeneralizedTime(AbstractTime):
+ """
+ Represents a generalized time from ASN.1 as a Python datetime.datetime
+ object or asn1crypto.util.extended_datetime object in UTC
+ """
+
+ tag = 24
+
+ # Regular expression for GeneralizedTime as described in X.680 sect. 42 and ISO 8601
+ _TIMESTRING_RE = re.compile(r'''
+ ^
+ # YYYYMMDD
+ (?P<year>\d{4})
+ (?P<month>\d{2})
+ (?P<day>\d{2})
+
+ # hh or hhmm or hhmmss
+ (?P<hour>\d{2})
+ (?:
+ (?P<minute>\d{2})
+ (?P<second>\d{2})?
+ )?
+
+ # Optional fraction; [.,]dddd (one or more decimals)
+ # If Seconds are given, it's fractions of Seconds.
+ # Else if Minutes are given, it's fractions of Minutes.
+ # Else it's fractions of Hours.
+ (?:
+ [,.]
+ (?P<fraction>\d+)
+ )?
+
+ # Optional timezone. If left out, the time is in local time.
+ # Z or [-+]hh or [-+]hhmm
+ (?:
+ (?P<zulu>Z)
+ |
+ (?:
+ (?P<dsign>[-+])
+ (?P<dhour>\d{2})
+ (?P<dminute>\d{2})?
+ )
+ )?
+ $
+ ''', re.X)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string, a datetime.datetime object or an
+ asn1crypto.util.extended_datetime object
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, (datetime, extended_datetime)):
+ if not value.tzinfo:
+ raise ValueError('Must be timezone aware')
+
+ # Convert value to UTC.
+ value = value.astimezone(utc_with_dst)
+
+ if value.microsecond:
+ fraction = '.' + str(value.microsecond).zfill(6).rstrip('0')
+ else:
+ fraction = ''
+
+ value = value.strftime('%Y%m%d%H%M%S') + fraction + 'Z'
+ if _PY2:
+ value = value.decode('ascii')
+
+ AbstractString.set(self, value)
+ # Set it to None and let the class take care of converting the next
+ # time that .native is called
+ self._native = None
+
+ def _get_datetime(self, parsed):
+ """
+ Create a datetime object from the parsed time.
+
+ :return:
+ A datetime.datetime object or asn1crypto.util.extended_datetime object.
+ It may or may not be aware.
+ """
+
+ if parsed['year'] == 0:
+ # datetime does not support year 0. Use extended_datetime instead.
+ return extended_datetime(**parsed)
+ else:
+ return datetime(**parsed)
+
+
+class GraphicString(AbstractString):
+ """
+ Represents a graphic string from ASN.1 as a Python unicode string
+ """
+
+ tag = 25
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class VisibleString(AbstractString):
+ """
+ Represents a visible string from ASN.1 as a Python unicode string
+ """
+
+ tag = 26
+ _encoding = 'latin1'
+
+
+class GeneralString(AbstractString):
+ """
+ Represents a general string from ASN.1 as a Python unicode string
+ """
+
+ tag = 27
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class UniversalString(AbstractString):
+ """
+ Represents a universal string from ASN.1 as a Python unicode string
+ """
+
+ tag = 28
+ _encoding = 'utf-32-be'
+
+
+class CharacterString(AbstractString):
+ """
+ Represents a character string from ASN.1 as a Python unicode string
+ """
+
+ tag = 29
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class BMPString(AbstractString):
+ """
+ Represents a BMP string from ASN.1 as a Python unicode string
+ """
+
+ tag = 30
+ _encoding = 'utf-16-be'
+
+
+def _basic_debug(prefix, self):
+ """
+ Prints out basic information about an Asn1Value object. Extracted for reuse
+ among different classes that customize the debug information.
+
+ :param prefix:
+ A unicode string of spaces to prefix output line with
+
+ :param self:
+ The object to print the debugging information about
+ """
+
+ print('%s%s Object #%s' % (prefix, type_name(self), id(self)))
+ if self._header:
+ print('%s Header: 0x%s' % (prefix, binascii.hexlify(self._header or b'').decode('utf-8')))
+
+ has_header = self.method is not None and self.class_ is not None and self.tag is not None
+ if has_header:
+ method_name = METHOD_NUM_TO_NAME_MAP.get(self.method)
+ class_name = CLASS_NUM_TO_NAME_MAP.get(self.class_)
+
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ print(
+ '%s %s tag %s (explicitly tagged)' %
+ (
+ prefix,
+ CLASS_NUM_TO_NAME_MAP.get(class_),
+ tag
+ )
+ )
+ if has_header:
+ print('%s %s %s %s' % (prefix, method_name, class_name, self.tag))
+
+ elif self.implicit:
+ if has_header:
+ print('%s %s %s tag %s (implicitly tagged)' % (prefix, method_name, class_name, self.tag))
+
+ elif has_header:
+ print('%s %s %s tag %s' % (prefix, method_name, class_name, self.tag))
+
+ if self._trailer:
+ print('%s Trailer: 0x%s' % (prefix, binascii.hexlify(self._trailer or b'').decode('utf-8')))
+
+ print('%s Data: 0x%s' % (prefix, binascii.hexlify(self.contents or b'').decode('utf-8')))
+
+
+def _tag_type_to_explicit_implicit(params):
+ """
+ Converts old-style "tag_type" and "tag" params to "explicit" and "implicit"
+
+ :param params:
+ A dict of parameters to convert from tag_type/tag to explicit/implicit
+ """
+
+ if 'tag_type' in params:
+ if params['tag_type'] == 'explicit':
+ params['explicit'] = (params.get('class', 2), params['tag'])
+ elif params['tag_type'] == 'implicit':
+ params['implicit'] = (params.get('class', 2), params['tag'])
+ del params['tag_type']
+ del params['tag']
+ if 'class' in params:
+ del params['class']
+
+
+def _fix_tagging(value, params):
+ """
+ Checks if a value is properly tagged based on the spec, and re/untags as
+ necessary
+
+ :param value:
+ An Asn1Value object
+
+ :param params:
+ A dict of spec params
+
+ :return:
+ An Asn1Value that is properly tagged
+ """
+
+ _tag_type_to_explicit_implicit(params)
+
+ retag = False
+ if 'implicit' not in params:
+ if value.implicit is not False:
+ retag = True
+ else:
+ if isinstance(params['implicit'], tuple):
+ class_, tag = params['implicit']
+ else:
+ tag = params['implicit']
+ class_ = 'context'
+ if value.implicit is False:
+ retag = True
+ elif value.class_ != CLASS_NAME_TO_NUM_MAP[class_] or value.tag != tag:
+ retag = True
+
+ if params.get('explicit') != value.explicit:
+ retag = True
+
+ if retag:
+ return value.retag(params)
+ return value
+
+
+def _build_id_tuple(params, spec):
+ """
+ Builds a 2-element tuple used to identify fields by grabbing the class_
+ and tag from an Asn1Value class and the params dict being passed to it
+
+ :param params:
+ A dict of params to pass to spec
+
+ :param spec:
+ An Asn1Value class
+
+ :return:
+ A 2-element integer tuple in the form (class_, tag)
+ """
+
+ # Handle situations where the spec is not known at setup time
+ if spec is None:
+ return (None, None)
+
+ required_class = spec.class_
+ required_tag = spec.tag
+
+ _tag_type_to_explicit_implicit(params)
+
+ if 'explicit' in params:
+ if isinstance(params['explicit'], tuple):
+ required_class, required_tag = params['explicit']
+ else:
+ required_class = 2
+ required_tag = params['explicit']
+ elif 'implicit' in params:
+ if isinstance(params['implicit'], tuple):
+ required_class, required_tag = params['implicit']
+ else:
+ required_class = 2
+ required_tag = params['implicit']
+ if required_class is not None and not isinstance(required_class, int_types):
+ required_class = CLASS_NAME_TO_NUM_MAP[required_class]
+
+ required_class = params.get('class_', required_class)
+ required_tag = params.get('tag', required_tag)
+
+ return (required_class, required_tag)
+
+
+def _int_to_bit_tuple(value, bits):
+ """
+ Format value as a tuple of 1s and 0s.
+
+ :param value:
+ A non-negative integer to format
+
+ :param bits:
+ Number of bits in the output
+
+ :return:
+ A tuple of 1s and 0s with bits members.
+ """
+
+ if not value and not bits:
+ return ()
+
+ result = tuple(map(int, format(value, '0{0}b'.format(bits))))
+ if len(result) != bits:
+ raise ValueError('Result too large: {0} > {1}'.format(len(result), bits))
+
+ return result
+
+
+_UNIVERSAL_SPECS = {
+ 1: Boolean,
+ 2: Integer,
+ 3: BitString,
+ 4: OctetString,
+ 5: Null,
+ 6: ObjectIdentifier,
+ 7: ObjectDescriptor,
+ 8: InstanceOf,
+ 9: Real,
+ 10: Enumerated,
+ 11: EmbeddedPdv,
+ 12: UTF8String,
+ 13: RelativeOid,
+ 16: Sequence,
+ 17: Set,
+ 18: NumericString,
+ 19: PrintableString,
+ 20: TeletexString,
+ 21: VideotexString,
+ 22: IA5String,
+ 23: UTCTime,
+ 24: GeneralizedTime,
+ 25: GraphicString,
+ 26: VisibleString,
+ 27: GeneralString,
+ 28: UniversalString,
+ 29: CharacterString,
+ 30: BMPString
+}
+
+
+def _build(class_, method, tag, header, contents, trailer, spec=None, spec_params=None, nested_spec=None):
+ """
+ Builds an Asn1Value object generically, or using a spec with optional params
+
+ :param class_:
+ An integer representing the ASN.1 class
+
+ :param method:
+ An integer representing the ASN.1 method
+
+ :param tag:
+ An integer representing the ASN.1 tag
+
+ :param header:
+ A byte string of the ASN.1 header (class, method, tag, length)
+
+ :param contents:
+ A byte string of the ASN.1 value
+
+ :param trailer:
+ A byte string of any ASN.1 trailer (only used by indefinite length encodings)
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :param nested_spec:
+ For certain Asn1Value classes (such as OctetString and BitString), the
+ contents can be further parsed and interpreted as another Asn1Value.
+ This parameter controls the spec for that sub-parsing.
+
+ :return:
+ An object of the type spec, or if not specified, a child of Asn1Value
+ """
+
+ if spec_params is not None:
+ _tag_type_to_explicit_implicit(spec_params)
+
+ if header is None:
+ return VOID
+
+ header_set = False
+
+ # If an explicit specification was passed in, make sure it matches
+ if spec is not None:
+ # If there is explicit tagging and contents, we have to split
+ # the header and trailer off before we do the parsing
+ no_explicit = spec_params and 'no_explicit' in spec_params
+ if not no_explicit and (spec.explicit or (spec_params and 'explicit' in spec_params)):
+ if spec_params:
+ value = spec(**spec_params)
+ else:
+ value = spec()
+ original_explicit = value.explicit
+ explicit_info = reversed(original_explicit)
+ parsed_class = class_
+ parsed_method = method
+ parsed_tag = tag
+ to_parse = contents
+ explicit_header = header
+ explicit_trailer = trailer or b''
+ for expected_class, expected_tag in explicit_info:
+ if parsed_class != expected_class:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged class should have been
+ %s, but %s was found
+ ''',
+ type_name(value),
+ CLASS_NUM_TO_NAME_MAP.get(expected_class),
+ CLASS_NUM_TO_NAME_MAP.get(parsed_class, parsed_class)
+ ))
+ if parsed_method != 1:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged method should have
+ been %s, but %s was found
+ ''',
+ type_name(value),
+ METHOD_NUM_TO_NAME_MAP.get(1),
+ METHOD_NUM_TO_NAME_MAP.get(parsed_method, parsed_method)
+ ))
+ if parsed_tag != expected_tag:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged tag should have been
+ %s, but %s was found
+ ''',
+ type_name(value),
+ expected_tag,
+ parsed_tag
+ ))
+ info, _ = _parse(to_parse, len(to_parse))
+ parsed_class, parsed_method, parsed_tag, parsed_header, to_parse, parsed_trailer = info
+
+ if not isinstance(value, Choice):
+ explicit_header += parsed_header
+ explicit_trailer = parsed_trailer + explicit_trailer
+
+ value = _build(*info, spec=spec, spec_params={'no_explicit': True})
+ value._header = explicit_header
+ value._trailer = explicit_trailer
+ value.explicit = original_explicit
+ header_set = True
+ else:
+ if spec_params:
+ value = spec(contents=contents, **spec_params)
+ else:
+ value = spec(contents=contents)
+
+ if spec is Any:
+ pass
+
+ elif isinstance(value, Choice):
+ value.validate(class_, tag, contents)
+ try:
+ # Force parsing the Choice now
+ value.contents = header + value.contents
+ header = b''
+ value.parse()
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args
+ raise e
+
+ else:
+ if class_ != value.class_:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - class should have been %s, but %s was
+ found
+ ''',
+ type_name(value),
+ CLASS_NUM_TO_NAME_MAP.get(value.class_),
+ CLASS_NUM_TO_NAME_MAP.get(class_, class_)
+ ))
+ if method != value.method:
+ # Allow parsing a primitive method as constructed if the value
+ # is indefinite length. This is to allow parsing BER.
+ ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00'
+ if not ber_indef or not isinstance(value, Constructable):
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - method should have been %s, but %s was found
+ ''',
+ type_name(value),
+ METHOD_NUM_TO_NAME_MAP.get(value.method),
+ METHOD_NUM_TO_NAME_MAP.get(method, method)
+ ))
+ else:
+ value.method = method
+ value._indefinite = True
+ if tag != value.tag:
+ if isinstance(value._bad_tag, tuple):
+ is_bad_tag = tag in value._bad_tag
+ else:
+ is_bad_tag = tag == value._bad_tag
+ if not is_bad_tag:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - tag should have been %s, but %s was found
+ ''',
+ type_name(value),
+ value.tag,
+ tag
+ ))
+
+ # For explicitly tagged, un-speced parsings, we use a generic container
+ # since we will be parsing the contents and discarding the outer object
+ # anyway a little further on
+ elif spec_params and 'explicit' in spec_params:
+ original_value = Asn1Value(contents=contents, **spec_params)
+ original_explicit = original_value.explicit
+
+ to_parse = contents
+ explicit_header = header
+ explicit_trailer = trailer or b''
+ for expected_class, expected_tag in reversed(original_explicit):
+ info, _ = _parse(to_parse, len(to_parse))
+ _, _, _, parsed_header, to_parse, parsed_trailer = info
+ explicit_header += parsed_header
+ explicit_trailer = parsed_trailer + explicit_trailer
+ value = _build(*info, spec=spec, spec_params={'no_explicit': True})
+ value._header = header + value._header
+ value._trailer += trailer or b''
+ value.explicit = original_explicit
+ header_set = True
+
+ # If no spec was specified, allow anything and just process what
+ # is in the input data
+ else:
+ if tag not in _UNIVERSAL_SPECS:
+ raise ValueError(unwrap(
+ '''
+ Unknown element - %s class, %s method, tag %s
+ ''',
+ CLASS_NUM_TO_NAME_MAP.get(class_),
+ METHOD_NUM_TO_NAME_MAP.get(method),
+ tag
+ ))
+
+ spec = _UNIVERSAL_SPECS[tag]
+
+ value = spec(contents=contents, class_=class_)
+ ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00'
+ if ber_indef and isinstance(value, Constructable):
+ value._indefinite = True
+ value.method = method
+
+ if not header_set:
+ value._header = header
+ value._trailer = trailer or b''
+
+ # Destroy any default value that our contents have overwritten
+ value._native = None
+
+ if nested_spec:
+ try:
+ value.parse(nested_spec)
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args
+ raise e
+
+ return value
+
+
+def _parse_build(encoded_data, pointer=0, spec=None, spec_params=None, strict=False):
+ """
+ Parses a byte string generically, or using a spec with optional params
+
+ :param encoded_data:
+ A byte string that contains BER-encoded data
+
+ :param pointer:
+ The index in the byte string to parse from
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A 2-element tuple:
+ - 0: An object of the type spec, or if not specified, a child of Asn1Value
+ - 1: An integer indicating how many bytes were consumed
+ """
+
+ encoded_len = len(encoded_data)
+ info, new_pointer = _parse(encoded_data, encoded_len, pointer)
+ if strict and new_pointer != pointer + encoded_len:
+ extra_bytes = pointer + encoded_len - new_pointer
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % extra_bytes)
+ return (_build(*info, spec=spec, spec_params=spec_params), new_pointer)
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/crl.py b/contrib/python/asn1crypto/py2/asn1crypto/crl.py
new file mode 100644
index 0000000000..84cb168393
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/crl.py
@@ -0,0 +1,536 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for certificate revocation lists (CRL). Exports the
+following items:
+
+ - CertificateList()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import hashlib
+
+from .algos import SignedDigestAlgorithm
+from .core import (
+ Boolean,
+ Enumerated,
+ GeneralizedTime,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+)
+from .x509 import (
+ AuthorityInfoAccessSyntax,
+ AuthorityKeyIdentifier,
+ CRLDistributionPoints,
+ DistributionPointName,
+ GeneralNames,
+ Name,
+ ReasonFlags,
+ Time,
+)
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ 2: 'v3',
+ }
+
+
+class IssuingDistributionPoint(Sequence):
+ _fields = [
+ ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
+ ('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}),
+ ('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}),
+ ('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}),
+ ('indirect_crl', Boolean, {'implicit': 4, 'default': False}),
+ ('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}),
+ ]
+
+
+class TBSCertListExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.18': 'issuer_alt_name',
+ '2.5.29.20': 'crl_number',
+ '2.5.29.27': 'delta_crl_indicator',
+ '2.5.29.28': 'issuing_distribution_point',
+ '2.5.29.35': 'authority_key_identifier',
+ '2.5.29.46': 'freshest_crl',
+ '1.3.6.1.5.5.7.1.1': 'authority_information_access',
+ }
+
+
+class TBSCertListExtension(Sequence):
+ _fields = [
+ ('extn_id', TBSCertListExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'issuer_alt_name': GeneralNames,
+ 'crl_number': Integer,
+ 'delta_crl_indicator': Integer,
+ 'issuing_distribution_point': IssuingDistributionPoint,
+ 'authority_key_identifier': AuthorityKeyIdentifier,
+ 'freshest_crl': CRLDistributionPoints,
+ 'authority_information_access': AuthorityInfoAccessSyntax,
+ }
+
+
+class TBSCertListExtensions(SequenceOf):
+ _child_spec = TBSCertListExtension
+
+
+class CRLReason(Enumerated):
+ _map = {
+ 0: 'unspecified',
+ 1: 'key_compromise',
+ 2: 'ca_compromise',
+ 3: 'affiliation_changed',
+ 4: 'superseded',
+ 5: 'cessation_of_operation',
+ 6: 'certificate_hold',
+ 8: 'remove_from_crl',
+ 9: 'privilege_withdrawn',
+ 10: 'aa_compromise',
+ }
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A unicode string with revocation description that is suitable to
+ show to end-users. Starts with a lower case letter and phrased in
+ such a way that it makes sense after the phrase "because of" or
+ "due to".
+ """
+
+ return {
+ 'unspecified': 'an unspecified reason',
+ 'key_compromise': 'a compromised key',
+ 'ca_compromise': 'the CA being compromised',
+ 'affiliation_changed': 'an affiliation change',
+ 'superseded': 'certificate supersession',
+ 'cessation_of_operation': 'a cessation of operation',
+ 'certificate_hold': 'a certificate hold',
+ 'remove_from_crl': 'removal from the CRL',
+ 'privilege_withdrawn': 'privilege withdrawl',
+ 'aa_compromise': 'the AA being compromised',
+ }[self.native]
+
+
+class CRLEntryExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.21': 'crl_reason',
+ '2.5.29.23': 'hold_instruction_code',
+ '2.5.29.24': 'invalidity_date',
+ '2.5.29.29': 'certificate_issuer',
+ }
+
+
+class CRLEntryExtension(Sequence):
+ _fields = [
+ ('extn_id', CRLEntryExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'crl_reason': CRLReason,
+ 'hold_instruction_code': ObjectIdentifier,
+ 'invalidity_date': GeneralizedTime,
+ 'certificate_issuer': GeneralNames,
+ }
+
+
+class CRLEntryExtensions(SequenceOf):
+ _child_spec = CRLEntryExtension
+
+
+class RevokedCertificate(Sequence):
+ _fields = [
+ ('user_certificate', Integer),
+ ('revocation_date', Time),
+ ('crl_entry_extensions', CRLEntryExtensions, {'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _crl_reason_value = None
+ _invalidity_date_value = None
+ _certificate_issuer_value = None
+ _issuer_name = False
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['crl_entry_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def crl_reason_value(self):
+ """
+ This extension indicates the reason that a certificate was revoked.
+
+ :return:
+ None or a CRLReason object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_reason_value
+
+ @property
+ def invalidity_date_value(self):
+ """
+ This extension indicates the suspected date/time the private key was
+ compromised or the certificate became invalid. This would usually be
+ before the revocation date, which is when the CA processed the
+ revocation.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._invalidity_date_value
+
+ @property
+ def certificate_issuer_value(self):
+ """
+ This extension indicates the issuer of the certificate in question,
+ and is used in indirect CRLs. CRL entries without this extension are
+ for certificates issued from the last seen issuer.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._certificate_issuer_value
+
+ @property
+ def issuer_name(self):
+ """
+ :return:
+ None, or an asn1crypto.x509.Name object for the issuer of the cert
+ """
+
+ if self._issuer_name is False:
+ self._issuer_name = None
+ if self.certificate_issuer_value:
+ for general_name in self.certificate_issuer_value:
+ if general_name.name == 'directory_name':
+ self._issuer_name = general_name.chosen
+ break
+ return self._issuer_name
+
+
+class RevokedCertificates(SequenceOf):
+ _child_spec = RevokedCertificate
+
+
+class TbsCertList(Sequence):
+ _fields = [
+ ('version', Version, {'optional': True}),
+ ('signature', SignedDigestAlgorithm),
+ ('issuer', Name),
+ ('this_update', Time),
+ ('next_update', Time, {'optional': True}),
+ ('revoked_certificates', RevokedCertificates, {'optional': True}),
+ ('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class CertificateList(Sequence):
+ _fields = [
+ ('tbs_cert_list', TbsCertList),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _issuer_alt_name_value = None
+ _crl_number_value = None
+ _delta_crl_indicator_value = None
+ _issuing_distribution_point_value = None
+ _authority_key_identifier_value = None
+ _freshest_crl_value = None
+ _authority_information_access_value = None
+ _issuer_cert_urls = None
+ _delta_crl_distribution_points = None
+ _sha1 = None
+ _sha256 = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_cert_list']['crl_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def issuer_alt_name_value(self):
+ """
+ This extension allows associating one or more alternative names with
+ the issuer of the CRL.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._issuer_alt_name_value
+
+ @property
+ def crl_number_value(self):
+ """
+ This extension adds a monotonically increasing number to the CRL and is
+ used to distinguish different versions of the CRL.
+
+ :return:
+ None or an Integer object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_number_value
+
+ @property
+ def delta_crl_indicator_value(self):
+ """
+ This extension indicates a CRL is a delta CRL, and contains the CRL
+ number of the base CRL that it is a delta from.
+
+ :return:
+ None or an Integer object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._delta_crl_indicator_value
+
+ @property
+ def issuing_distribution_point_value(self):
+ """
+ This extension includes information about what types of revocations
+ and certificates are part of the CRL.
+
+ :return:
+ None or an IssuingDistributionPoint object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._issuing_distribution_point_value
+
+ @property
+ def authority_key_identifier_value(self):
+ """
+ This extension helps in identifying the public key with which to
+ validate the authenticity of the CRL.
+
+ :return:
+ None or an AuthorityKeyIdentifier object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._authority_key_identifier_value
+
+ @property
+ def freshest_crl_value(self):
+ """
+ This extension is used in complete CRLs to indicate where a delta CRL
+ may be located.
+
+ :return:
+ None or a CRLDistributionPoints object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._freshest_crl_value
+
+ @property
+ def authority_information_access_value(self):
+ """
+ This extension is used to provide a URL with which to download the
+ certificate used to sign this CRL.
+
+ :return:
+ None or an AuthorityInfoAccessSyntax object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._authority_information_access_value
+
+ @property
+ def issuer(self):
+ """
+ :return:
+ An asn1crypto.x509.Name object for the issuer of the CRL
+ """
+
+ return self['tbs_cert_list']['issuer']
+
+ @property
+ def authority_key_identifier(self):
+ """
+ :return:
+ None or a byte string of the key_identifier from the authority key
+ identifier extension
+ """
+
+ if not self.authority_key_identifier_value:
+ return None
+
+ return self.authority_key_identifier_value['key_identifier'].native
+
+ @property
+ def issuer_cert_urls(self):
+ """
+ :return:
+ A list of unicode strings that are URLs that should contain either
+ an individual DER-encoded X.509 certificate, or a DER-encoded CMS
+ message containing multiple certificates
+ """
+
+ if self._issuer_cert_urls is None:
+ self._issuer_cert_urls = []
+ if self.authority_information_access_value:
+ for entry in self.authority_information_access_value:
+ if entry['access_method'].native == 'ca_issuers':
+ location = entry['access_location']
+ if location.name != 'uniform_resource_identifier':
+ continue
+ url = location.native
+ if url.lower()[0:7] == 'http://':
+ self._issuer_cert_urls.append(url)
+ return self._issuer_cert_urls
+
+ @property
+ def delta_crl_distribution_points(self):
+ """
+ Returns delta CRL URLs - only applies to complete CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._delta_crl_distribution_points is None:
+ self._delta_crl_distribution_points = []
+
+ if self.freshest_crl_value is not None:
+ for distribution_point in self.freshest_crl_value:
+ distribution_point_name = distribution_point['distribution_point']
+ # RFC 5280 indicates conforming CA should not use the relative form
+ if distribution_point_name.name == 'name_relative_to_crl_issuer':
+ continue
+ # This library is currently only concerned with HTTP-based CRLs
+ for general_name in distribution_point_name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ self._delta_crl_distribution_points.append(distribution_point)
+
+ return self._delta_crl_distribution_points
+
+ @property
+ def signature(self):
+ """
+ :return:
+ A byte string of the signature
+ """
+
+ return self['signature'].native
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this certificate list
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this certificate list
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/csr.py b/contrib/python/asn1crypto/py2/asn1crypto/csr.py
new file mode 100644
index 0000000000..7d5ba44707
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/csr.py
@@ -0,0 +1,133 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for certificate signing requests (CSR). Exports the
+following items:
+
+ - CertificationRequest()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import SignedDigestAlgorithm
+from .core import (
+ Any,
+ BitString,
+ BMPString,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ Sequence,
+ SetOf,
+ UTF8String
+)
+from .keys import PublicKeyInfo
+from .x509 import DirectoryString, Extensions, Name
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc2986
+# and https://tools.ietf.org/html/rfc2985
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ }
+
+
+class CSRAttributeType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.7': 'challenge_password',
+ '1.2.840.113549.1.9.9': 'extended_certificate_attributes',
+ '1.2.840.113549.1.9.14': 'extension_request',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/a5eaae36-e9f3-4dc5-a687-bfa7115954f1
+ '1.3.6.1.4.1.311.13.2.2': 'microsoft_enrollment_csp_provider',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/7c677cba-030d-48be-ba2b-01e407705f34
+ '1.3.6.1.4.1.311.13.2.3': 'microsoft_os_version',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/64e5ff6d-c6dd-4578-92f7-b3d895f9b9c7
+ '1.3.6.1.4.1.311.21.20': 'microsoft_request_client_info',
+ }
+
+
+class SetOfDirectoryString(SetOf):
+ _child_spec = DirectoryString
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class SetOfAttributes(SetOf):
+ _child_spec = Attribute
+
+
+class SetOfExtensions(SetOf):
+ _child_spec = Extensions
+
+
+class MicrosoftEnrollmentCSProvider(Sequence):
+ _fields = [
+ ('keyspec', Integer),
+ ('cspname', BMPString), # cryptographic service provider name
+ ('signature', BitString),
+ ]
+
+
+class SetOfMicrosoftEnrollmentCSProvider(SetOf):
+ _child_spec = MicrosoftEnrollmentCSProvider
+
+
+class MicrosoftRequestClientInfo(Sequence):
+ _fields = [
+ ('clientid', Integer),
+ ('machinename', UTF8String),
+ ('username', UTF8String),
+ ('processname', UTF8String),
+ ]
+
+
+class SetOfMicrosoftRequestClientInfo(SetOf):
+ _child_spec = MicrosoftRequestClientInfo
+
+
+class CRIAttribute(Sequence):
+ _fields = [
+ ('type', CSRAttributeType),
+ ('values', Any),
+ ]
+
+ _oid_pair = ('type', 'values')
+ _oid_specs = {
+ 'challenge_password': SetOfDirectoryString,
+ 'extended_certificate_attributes': SetOfAttributes,
+ 'extension_request': SetOfExtensions,
+ 'microsoft_enrollment_csp_provider': SetOfMicrosoftEnrollmentCSProvider,
+ 'microsoft_os_version': SetOfDirectoryString,
+ 'microsoft_request_client_info': SetOfMicrosoftRequestClientInfo,
+ }
+
+
+class CRIAttributes(SetOf):
+ _child_spec = CRIAttribute
+
+
+class CertificationRequestInfo(Sequence):
+ _fields = [
+ ('version', Version),
+ ('subject', Name),
+ ('subject_pk_info', PublicKeyInfo),
+ ('attributes', CRIAttributes, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class CertificationRequest(Sequence):
+ _fields = [
+ ('certification_request_info', CertificationRequestInfo),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/keys.py b/contrib/python/asn1crypto/py2/asn1crypto/keys.py
new file mode 100644
index 0000000000..b4a87aea7b
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/keys.py
@@ -0,0 +1,1301 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for public and private keys. Exports the following items:
+
+ - DSAPrivateKey()
+ - ECPrivateKey()
+ - EncryptedPrivateKeyInfo()
+ - PrivateKeyInfo()
+ - PublicKeyInfo()
+ - RSAPrivateKey()
+ - RSAPublicKey()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import hashlib
+import math
+
+from ._errors import unwrap, APIException
+from ._types import type_name, byte_cls
+from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams, RSASSAPSSParams
+from .core import (
+ Any,
+ Asn1Value,
+ BitString,
+ Choice,
+ Integer,
+ IntegerOctetString,
+ Null,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ ParsableOctetBitString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .util import int_from_bytes, int_to_bytes
+
+
+class OtherPrimeInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-46
+ """
+
+ _fields = [
+ ('prime', Integer),
+ ('exponent', Integer),
+ ('coefficient', Integer),
+ ]
+
+
+class OtherPrimeInfos(SequenceOf):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-46
+ """
+
+ _child_spec = OtherPrimeInfo
+
+
+class RSAPrivateKeyVersion(Integer):
+ """
+ Original Name: Version
+ Source: https://tools.ietf.org/html/rfc3447#page-45
+ """
+
+ _map = {
+ 0: 'two-prime',
+ 1: 'multi',
+ }
+
+
+class RSAPrivateKey(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-45
+ """
+
+ _fields = [
+ ('version', RSAPrivateKeyVersion),
+ ('modulus', Integer),
+ ('public_exponent', Integer),
+ ('private_exponent', Integer),
+ ('prime1', Integer),
+ ('prime2', Integer),
+ ('exponent1', Integer),
+ ('exponent2', Integer),
+ ('coefficient', Integer),
+ ('other_prime_infos', OtherPrimeInfos, {'optional': True})
+ ]
+
+
+class RSAPublicKey(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-44
+ """
+
+ _fields = [
+ ('modulus', Integer),
+ ('public_exponent', Integer)
+ ]
+
+
+class DSAPrivateKey(Sequence):
+ """
+ The ASN.1 structure that OpenSSL uses to store a DSA private key that is
+ not part of a PKCS#8 structure. Reversed engineered from english-language
+ description on linked OpenSSL documentation page.
+
+ Original Name: None
+ Source: https://www.openssl.org/docs/apps/dsa.html
+ """
+
+ _fields = [
+ ('version', Integer),
+ ('p', Integer),
+ ('q', Integer),
+ ('g', Integer),
+ ('public_key', Integer),
+ ('private_key', Integer),
+ ]
+
+
+class _ECPoint():
+ """
+ In both PublicKeyInfo and PrivateKeyInfo, the EC public key is a byte
+ string that is encoded as a bit string. This class adds convenience
+ methods for converting to and from the byte string to a pair of integers
+ that are the X and Y coordinates.
+ """
+
+ @classmethod
+ def from_coords(cls, x, y):
+ """
+ Creates an ECPoint object from the X and Y integer coordinates of the
+ point
+
+ :param x:
+ The X coordinate, as an integer
+
+ :param y:
+ The Y coordinate, as an integer
+
+ :return:
+ An ECPoint object
+ """
+
+ x_bytes = int(math.ceil(math.log(x, 2) / 8.0))
+ y_bytes = int(math.ceil(math.log(y, 2) / 8.0))
+
+ num_bytes = max(x_bytes, y_bytes)
+
+ byte_string = b'\x04'
+ byte_string += int_to_bytes(x, width=num_bytes)
+ byte_string += int_to_bytes(y, width=num_bytes)
+
+ return cls(byte_string)
+
+ def to_coords(self):
+ """
+ Returns the X and Y coordinates for this EC point, as native Python
+ integers
+
+ :return:
+ A 2-element tuple containing integers (X, Y)
+ """
+
+ data = self.native
+ first_byte = data[0:1]
+
+ # Uncompressed
+ if first_byte == b'\x04':
+ remaining = data[1:]
+ field_len = len(remaining) // 2
+ x = int_from_bytes(remaining[0:field_len])
+ y = int_from_bytes(remaining[field_len:])
+ return (x, y)
+
+ if first_byte not in set([b'\x02', b'\x03']):
+ raise ValueError(unwrap(
+ '''
+ Invalid EC public key - first byte is incorrect
+ '''
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Compressed representations of EC public keys are not supported due
+ to patent US6252960
+ '''
+ ))
+
+
+class ECPoint(OctetString, _ECPoint):
+
+ pass
+
+
+class ECPointBitString(OctetBitString, _ECPoint):
+
+ pass
+
+
+class SpecifiedECDomainVersion(Integer):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 104
+ """
+ _map = {
+ 1: 'ecdpVer1',
+ 2: 'ecdpVer2',
+ 3: 'ecdpVer3',
+ }
+
+
+class FieldType(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 101
+ """
+
+ _map = {
+ '1.2.840.10045.1.1': 'prime_field',
+ '1.2.840.10045.1.2': 'characteristic_two_field',
+ }
+
+
+class CharacteristicTwoBasis(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _map = {
+ '1.2.840.10045.1.2.1.1': 'gn_basis',
+ '1.2.840.10045.1.2.1.2': 'tp_basis',
+ '1.2.840.10045.1.2.1.3': 'pp_basis',
+ }
+
+
+class Pentanomial(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _fields = [
+ ('k1', Integer),
+ ('k2', Integer),
+ ('k3', Integer),
+ ]
+
+
+class CharacteristicTwo(Sequence):
+ """
+ Original Name: Characteristic-two
+ Source: http://www.secg.org/sec1-v2.pdf page 101
+ """
+
+ _fields = [
+ ('m', Integer),
+ ('basis', CharacteristicTwoBasis),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('basis', 'parameters')
+ _oid_specs = {
+ 'gn_basis': Null,
+ 'tp_basis': Integer,
+ 'pp_basis': Pentanomial,
+ }
+
+
+class FieldID(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 100
+ """
+
+ _fields = [
+ ('field_type', FieldType),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('field_type', 'parameters')
+ _oid_specs = {
+ 'prime_field': Integer,
+ 'characteristic_two_field': CharacteristicTwo,
+ }
+
+
+class Curve(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 104
+ """
+
+ _fields = [
+ ('a', OctetString),
+ ('b', OctetString),
+ ('seed', OctetBitString, {'optional': True}),
+ ]
+
+
+class SpecifiedECDomain(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 103
+ """
+
+ _fields = [
+ ('version', SpecifiedECDomainVersion),
+ ('field_id', FieldID),
+ ('curve', Curve),
+ ('base', ECPoint),
+ ('order', Integer),
+ ('cofactor', Integer, {'optional': True}),
+ ('hash', DigestAlgorithm, {'optional': True}),
+ ]
+
+
+class NamedCurve(ObjectIdentifier):
+ """
+ Various named curves
+
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279#page-23,
+ https://tools.ietf.org/html/rfc5480#page-5
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-23
+ '1.2.840.10045.3.0.1': 'c2pnb163v1',
+ '1.2.840.10045.3.0.2': 'c2pnb163v2',
+ '1.2.840.10045.3.0.3': 'c2pnb163v3',
+ '1.2.840.10045.3.0.4': 'c2pnb176w1',
+ '1.2.840.10045.3.0.5': 'c2tnb191v1',
+ '1.2.840.10045.3.0.6': 'c2tnb191v2',
+ '1.2.840.10045.3.0.7': 'c2tnb191v3',
+ '1.2.840.10045.3.0.8': 'c2onb191v4',
+ '1.2.840.10045.3.0.9': 'c2onb191v5',
+ '1.2.840.10045.3.0.10': 'c2pnb208w1',
+ '1.2.840.10045.3.0.11': 'c2tnb239v1',
+ '1.2.840.10045.3.0.12': 'c2tnb239v2',
+ '1.2.840.10045.3.0.13': 'c2tnb239v3',
+ '1.2.840.10045.3.0.14': 'c2onb239v4',
+ '1.2.840.10045.3.0.15': 'c2onb239v5',
+ '1.2.840.10045.3.0.16': 'c2pnb272w1',
+ '1.2.840.10045.3.0.17': 'c2pnb304w1',
+ '1.2.840.10045.3.0.18': 'c2tnb359v1',
+ '1.2.840.10045.3.0.19': 'c2pnb368w1',
+ '1.2.840.10045.3.0.20': 'c2tnb431r1',
+ '1.2.840.10045.3.1.2': 'prime192v2',
+ '1.2.840.10045.3.1.3': 'prime192v3',
+ '1.2.840.10045.3.1.4': 'prime239v1',
+ '1.2.840.10045.3.1.5': 'prime239v2',
+ '1.2.840.10045.3.1.6': 'prime239v3',
+ # https://tools.ietf.org/html/rfc5480#page-5
+ # http://www.secg.org/SEC2-Ver-1.0.pdf
+ '1.2.840.10045.3.1.1': 'secp192r1',
+ '1.2.840.10045.3.1.7': 'secp256r1',
+ '1.3.132.0.1': 'sect163k1',
+ '1.3.132.0.2': 'sect163r1',
+ '1.3.132.0.3': 'sect239k1',
+ '1.3.132.0.4': 'sect113r1',
+ '1.3.132.0.5': 'sect113r2',
+ '1.3.132.0.6': 'secp112r1',
+ '1.3.132.0.7': 'secp112r2',
+ '1.3.132.0.8': 'secp160r1',
+ '1.3.132.0.9': 'secp160k1',
+ '1.3.132.0.10': 'secp256k1',
+ '1.3.132.0.15': 'sect163r2',
+ '1.3.132.0.16': 'sect283k1',
+ '1.3.132.0.17': 'sect283r1',
+ '1.3.132.0.22': 'sect131r1',
+ '1.3.132.0.23': 'sect131r2',
+ '1.3.132.0.24': 'sect193r1',
+ '1.3.132.0.25': 'sect193r2',
+ '1.3.132.0.26': 'sect233k1',
+ '1.3.132.0.27': 'sect233r1',
+ '1.3.132.0.28': 'secp128r1',
+ '1.3.132.0.29': 'secp128r2',
+ '1.3.132.0.30': 'secp160r2',
+ '1.3.132.0.31': 'secp192k1',
+ '1.3.132.0.32': 'secp224k1',
+ '1.3.132.0.33': 'secp224r1',
+ '1.3.132.0.34': 'secp384r1',
+ '1.3.132.0.35': 'secp521r1',
+ '1.3.132.0.36': 'sect409k1',
+ '1.3.132.0.37': 'sect409r1',
+ '1.3.132.0.38': 'sect571k1',
+ '1.3.132.0.39': 'sect571r1',
+ # https://tools.ietf.org/html/rfc5639#section-4.1
+ '1.3.36.3.3.2.8.1.1.1': 'brainpoolp160r1',
+ '1.3.36.3.3.2.8.1.1.2': 'brainpoolp160t1',
+ '1.3.36.3.3.2.8.1.1.3': 'brainpoolp192r1',
+ '1.3.36.3.3.2.8.1.1.4': 'brainpoolp192t1',
+ '1.3.36.3.3.2.8.1.1.5': 'brainpoolp224r1',
+ '1.3.36.3.3.2.8.1.1.6': 'brainpoolp224t1',
+ '1.3.36.3.3.2.8.1.1.7': 'brainpoolp256r1',
+ '1.3.36.3.3.2.8.1.1.8': 'brainpoolp256t1',
+ '1.3.36.3.3.2.8.1.1.9': 'brainpoolp320r1',
+ '1.3.36.3.3.2.8.1.1.10': 'brainpoolp320t1',
+ '1.3.36.3.3.2.8.1.1.11': 'brainpoolp384r1',
+ '1.3.36.3.3.2.8.1.1.12': 'brainpoolp384t1',
+ '1.3.36.3.3.2.8.1.1.13': 'brainpoolp512r1',
+ '1.3.36.3.3.2.8.1.1.14': 'brainpoolp512t1',
+ }
+
+ _key_sizes = {
+ # Order values used to compute these sourced from
+ # http://cr.openjdk.java.net/~vinnie/7194075/webrev-3/src/share/classes/sun/security/ec/CurveDB.java.html
+ '1.2.840.10045.3.0.1': 21,
+ '1.2.840.10045.3.0.2': 21,
+ '1.2.840.10045.3.0.3': 21,
+ '1.2.840.10045.3.0.4': 21,
+ '1.2.840.10045.3.0.5': 24,
+ '1.2.840.10045.3.0.6': 24,
+ '1.2.840.10045.3.0.7': 24,
+ '1.2.840.10045.3.0.8': 24,
+ '1.2.840.10045.3.0.9': 24,
+ '1.2.840.10045.3.0.10': 25,
+ '1.2.840.10045.3.0.11': 30,
+ '1.2.840.10045.3.0.12': 30,
+ '1.2.840.10045.3.0.13': 30,
+ '1.2.840.10045.3.0.14': 30,
+ '1.2.840.10045.3.0.15': 30,
+ '1.2.840.10045.3.0.16': 33,
+ '1.2.840.10045.3.0.17': 37,
+ '1.2.840.10045.3.0.18': 45,
+ '1.2.840.10045.3.0.19': 45,
+ '1.2.840.10045.3.0.20': 53,
+ '1.2.840.10045.3.1.2': 24,
+ '1.2.840.10045.3.1.3': 24,
+ '1.2.840.10045.3.1.4': 30,
+ '1.2.840.10045.3.1.5': 30,
+ '1.2.840.10045.3.1.6': 30,
+ # Order values used to compute these sourced from
+ # http://www.secg.org/SEC2-Ver-1.0.pdf
+ # ceil(n.bit_length() / 8)
+ '1.2.840.10045.3.1.1': 24,
+ '1.2.840.10045.3.1.7': 32,
+ '1.3.132.0.1': 21,
+ '1.3.132.0.2': 21,
+ '1.3.132.0.3': 30,
+ '1.3.132.0.4': 15,
+ '1.3.132.0.5': 15,
+ '1.3.132.0.6': 14,
+ '1.3.132.0.7': 14,
+ '1.3.132.0.8': 21,
+ '1.3.132.0.9': 21,
+ '1.3.132.0.10': 32,
+ '1.3.132.0.15': 21,
+ '1.3.132.0.16': 36,
+ '1.3.132.0.17': 36,
+ '1.3.132.0.22': 17,
+ '1.3.132.0.23': 17,
+ '1.3.132.0.24': 25,
+ '1.3.132.0.25': 25,
+ '1.3.132.0.26': 29,
+ '1.3.132.0.27': 30,
+ '1.3.132.0.28': 16,
+ '1.3.132.0.29': 16,
+ '1.3.132.0.30': 21,
+ '1.3.132.0.31': 24,
+ '1.3.132.0.32': 29,
+ '1.3.132.0.33': 28,
+ '1.3.132.0.34': 48,
+ '1.3.132.0.35': 66,
+ '1.3.132.0.36': 51,
+ '1.3.132.0.37': 52,
+ '1.3.132.0.38': 72,
+ '1.3.132.0.39': 72,
+ # Order values used to compute these sourced from
+ # https://tools.ietf.org/html/rfc5639#section-3
+ # ceil(q.bit_length() / 8)
+ '1.3.36.3.3.2.8.1.1.1': 20,
+ '1.3.36.3.3.2.8.1.1.2': 20,
+ '1.3.36.3.3.2.8.1.1.3': 24,
+ '1.3.36.3.3.2.8.1.1.4': 24,
+ '1.3.36.3.3.2.8.1.1.5': 28,
+ '1.3.36.3.3.2.8.1.1.6': 28,
+ '1.3.36.3.3.2.8.1.1.7': 32,
+ '1.3.36.3.3.2.8.1.1.8': 32,
+ '1.3.36.3.3.2.8.1.1.9': 40,
+ '1.3.36.3.3.2.8.1.1.10': 40,
+ '1.3.36.3.3.2.8.1.1.11': 48,
+ '1.3.36.3.3.2.8.1.1.12': 48,
+ '1.3.36.3.3.2.8.1.1.13': 64,
+ '1.3.36.3.3.2.8.1.1.14': 64,
+ }
+
+ @classmethod
+ def register(cls, name, oid, key_size):
+ """
+ Registers a new named elliptic curve that is not included in the
+ default list of named curves
+
+ :param name:
+ A unicode string of the curve name
+
+ :param oid:
+ A unicode string of the dotted format OID
+
+ :param key_size:
+ An integer of the number of bytes the private key should be
+ encoded to
+ """
+
+ cls._map[oid] = name
+ if cls._reverse_map is not None:
+ cls._reverse_map[name] = oid
+ cls._key_sizes[oid] = key_size
+
+
+class ECDomainParameters(Choice):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _alternatives = [
+ ('specified', SpecifiedECDomain),
+ ('named', NamedCurve),
+ ('implicit_ca', Null),
+ ]
+
+ @property
+ def key_size(self):
+ if self.name == 'implicit_ca':
+ raise ValueError(unwrap(
+ '''
+ Unable to calculate key_size from ECDomainParameters
+ that are implicitly defined by the CA key
+ '''
+ ))
+
+ if self.name == 'specified':
+ order = self.chosen['order'].native
+ return math.ceil(math.log(order, 2.0) / 8.0)
+
+ oid = self.chosen.dotted
+ if oid not in NamedCurve._key_sizes:
+ raise ValueError(unwrap(
+ '''
+ The asn1crypto.keys.NamedCurve %s does not have a registered key length,
+ please call asn1crypto.keys.NamedCurve.register()
+ ''',
+ repr(oid)
+ ))
+ return NamedCurve._key_sizes[oid]
+
+
+class ECPrivateKeyVersion(Integer):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 108
+ """
+
+ _map = {
+ 1: 'ecPrivkeyVer1',
+ }
+
+
+class ECPrivateKey(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 108
+ """
+
+ _fields = [
+ ('version', ECPrivateKeyVersion),
+ ('private_key', IntegerOctetString),
+ ('parameters', ECDomainParameters, {'explicit': 0, 'optional': True}),
+ ('public_key', ECPointBitString, {'explicit': 1, 'optional': True}),
+ ]
+
+ # Ensures the key is set to the correct length when encoding
+ _key_size = None
+
+ # This is necessary to ensure the private_key IntegerOctetString is encoded properly
+ def __setitem__(self, key, value):
+ res = super(ECPrivateKey, self).__setitem__(key, value)
+
+ if key == 'private_key':
+ if self._key_size is None:
+ # Infer the key_size from the existing private key if possible
+ pkey_contents = self['private_key'].contents
+ if isinstance(pkey_contents, byte_cls) and len(pkey_contents) > 1:
+ self.set_key_size(len(self['private_key'].contents))
+
+ elif self._key_size is not None:
+ self._update_key_size()
+
+ elif key == 'parameters' and isinstance(self['parameters'], ECDomainParameters) and \
+ self['parameters'].name != 'implicit_ca':
+ self.set_key_size(self['parameters'].key_size)
+
+ return res
+
+ def set_key_size(self, key_size):
+ """
+ Sets the key_size to ensure the private key is encoded to the proper length
+
+ :param key_size:
+ An integer byte length to encode the private_key to
+ """
+
+ self._key_size = key_size
+ self._update_key_size()
+
+ def _update_key_size(self):
+ """
+ Ensure the private_key explicit encoding width is set
+ """
+
+ if self._key_size is not None and isinstance(self['private_key'], IntegerOctetString):
+ self['private_key'].set_encoded_width(self._key_size)
+
+
+class DSAParams(Sequence):
+ """
+ Parameters for a DSA public or private key
+
+ Original Name: Dss-Parms
+ Source: https://tools.ietf.org/html/rfc3279#page-9
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('q', Integer),
+ ('g', Integer),
+ ]
+
+
+class Attribute(Sequence):
+ """
+ Source: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.501-198811-S!!PDF-E&type=items page 8
+ """
+
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class Attributes(SetOf):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _child_spec = Attribute
+
+
+class PrivateKeyAlgorithmId(ObjectIdentifier):
+ """
+ These OIDs for various public keys are reused when storing private keys
+ inside of a PKCS#8 structure
+
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-19
+ '1.2.840.113549.1.1.1': 'rsa',
+ # https://tools.ietf.org/html/rfc4055#page-8
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ # https://tools.ietf.org/html/rfc3279#page-18
+ '1.2.840.10040.4.1': 'dsa',
+ # https://tools.ietf.org/html/rfc3279#page-13
+ '1.2.840.10045.2.1': 'ec',
+ # https://tools.ietf.org/html/rfc8410#section-9
+ '1.3.101.110': 'x25519',
+ '1.3.101.111': 'x448',
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+
+class PrivateKeyAlgorithm(_ForceNullParameters, Sequence):
+ """
+ Original Name: PrivateKeyAlgorithmIdentifier
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _fields = [
+ ('algorithm', PrivateKeyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dsa': DSAParams,
+ 'ec': ECDomainParameters,
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+
+class PrivateKeyInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _fields = [
+ ('version', Integer),
+ ('private_key_algorithm', PrivateKeyAlgorithm),
+ ('private_key', ParsableOctetString),
+ ('attributes', Attributes, {'implicit': 0, 'optional': True}),
+ ]
+
+ def _private_key_spec(self):
+ algorithm = self['private_key_algorithm']['algorithm'].native
+ return {
+ 'rsa': RSAPrivateKey,
+ 'rsassa_pss': RSAPrivateKey,
+ 'dsa': Integer,
+ 'ec': ECPrivateKey,
+ # These should be treated as opaque octet strings according
+ # to RFC 8410
+ 'x25519': OctetString,
+ 'x448': OctetString,
+ 'ed25519': OctetString,
+ 'ed448': OctetString,
+ }[algorithm]
+
+ _spec_callbacks = {
+ 'private_key': _private_key_spec
+ }
+
+ _algorithm = None
+ _bit_size = None
+ _public_key = None
+ _fingerprint = None
+
+ @classmethod
+ def wrap(cls, private_key, algorithm):
+ """
+ Wraps a private key in a PrivateKeyInfo structure
+
+ :param private_key:
+ A byte string or Asn1Value object of the private key
+
+ :param algorithm:
+ A unicode string of "rsa", "dsa" or "ec"
+
+ :return:
+ A PrivateKeyInfo object
+ """
+
+ if not isinstance(private_key, byte_cls) and not isinstance(private_key, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ private_key must be a byte string or Asn1Value, not %s
+ ''',
+ type_name(private_key)
+ ))
+
+ if algorithm == 'rsa' or algorithm == 'rsassa_pss':
+ if not isinstance(private_key, RSAPrivateKey):
+ private_key = RSAPrivateKey.load(private_key)
+ params = Null()
+ elif algorithm == 'dsa':
+ if not isinstance(private_key, DSAPrivateKey):
+ private_key = DSAPrivateKey.load(private_key)
+ params = DSAParams()
+ params['p'] = private_key['p']
+ params['q'] = private_key['q']
+ params['g'] = private_key['g']
+ public_key = private_key['public_key']
+ private_key = private_key['private_key']
+ elif algorithm == 'ec':
+ if not isinstance(private_key, ECPrivateKey):
+ private_key = ECPrivateKey.load(private_key)
+ else:
+ private_key = private_key.copy()
+ params = private_key['parameters']
+ del private_key['parameters']
+ else:
+ raise ValueError(unwrap(
+ '''
+ algorithm must be one of "rsa", "dsa", "ec", not %s
+ ''',
+ repr(algorithm)
+ ))
+
+ private_key_algo = PrivateKeyAlgorithm()
+ private_key_algo['algorithm'] = PrivateKeyAlgorithmId(algorithm)
+ private_key_algo['parameters'] = params
+
+ container = cls()
+ container._algorithm = algorithm
+ container['version'] = Integer(0)
+ container['private_key_algorithm'] = private_key_algo
+ container['private_key'] = private_key
+
+ # Here we save the DSA public key if possible since it is not contained
+ # within the PKCS#8 structure for a DSA key
+ if algorithm == 'dsa':
+ container._public_key = public_key
+
+ return container
+
+ # This is necessary to ensure any contained ECPrivateKey is the
+ # correct size
+ def __setitem__(self, key, value):
+ res = super(PrivateKeyInfo, self).__setitem__(key, value)
+
+ algorithm = self['private_key_algorithm']
+
+ # When possible, use the parameter info to make sure the private key encoding
+ # retains any necessary leading bytes, instead of them being dropped
+ if (key == 'private_key_algorithm' or key == 'private_key') and \
+ algorithm['algorithm'].native == 'ec' and \
+ isinstance(algorithm['parameters'], ECDomainParameters) and \
+ algorithm['parameters'].name != 'implicit_ca' and \
+ isinstance(self['private_key'], ParsableOctetString) and \
+ isinstance(self['private_key'].parsed, ECPrivateKey):
+ self['private_key'].parsed.set_key_size(algorithm['parameters'].key_size)
+
+ return res
+
+ def unwrap(self):
+ """
+ Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or
+ ECPrivateKey object
+
+ :return:
+ An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().unwrap() has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().unwrap() instead')
+
+ @property
+ def curve(self):
+ """
+ Returns information about the curve used for an EC key
+
+ :raises:
+ ValueError - when the key is not an EC key
+
+ :return:
+ A two-element tuple, with the first element being a unicode string
+ of "implicit_ca", "specified" or "named". If the first element is
+ "implicit_ca", the second is None. If "specified", the second is
+ an OrderedDict that is the native version of SpecifiedECDomain. If
+ "named", the second is a unicode string of the curve name.
+ """
+
+ if self.algorithm != 'ec':
+ raise ValueError(unwrap(
+ '''
+ Only EC keys have a curve, this key is %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ params = self['private_key_algorithm']['parameters']
+ chosen = params.chosen
+
+ if params.name == 'implicit_ca':
+ value = None
+ else:
+ value = chosen.native
+
+ return (params.name, value)
+
+ @property
+ def hash_algo(self):
+ """
+ Returns the name of the family of hash algorithms used to generate a
+ DSA key
+
+ :raises:
+ ValueError - when the key is not a DSA key
+
+ :return:
+ A unicode string of "sha1" or "sha2"
+ """
+
+ if self.algorithm != 'dsa':
+ raise ValueError(unwrap(
+ '''
+ Only DSA keys are generated using a hash algorithm, this key is
+ %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ byte_len = math.log(self['private_key_algorithm']['parameters']['q'].native, 2) / 8
+
+ return 'sha1' if byte_len <= 20 else 'sha2'
+
+ @property
+ def algorithm(self):
+ """
+ :return:
+ A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
+ """
+
+ if self._algorithm is None:
+ self._algorithm = self['private_key_algorithm']['algorithm'].native
+ return self._algorithm
+
+ @property
+ def bit_size(self):
+ """
+ :return:
+ The bit size of the private key, as an integer
+ """
+
+ if self._bit_size is None:
+ if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
+ prime = self['private_key'].parsed['modulus'].native
+ elif self.algorithm == 'dsa':
+ prime = self['private_key_algorithm']['parameters']['p'].native
+ elif self.algorithm == 'ec':
+ prime = self['private_key'].parsed['private_key'].native
+ self._bit_size = int(math.ceil(math.log(prime, 2)))
+ modulus = self._bit_size % 8
+ if modulus != 0:
+ self._bit_size += 8 - modulus
+ return self._bit_size
+
+ @property
+ def byte_size(self):
+ """
+ :return:
+ The byte size of the private key, as an integer
+ """
+
+ return int(math.ceil(self.bit_size / 8))
+
+ @property
+ def public_key(self):
+ """
+ :return:
+ If an RSA key, an RSAPublicKey object. If a DSA key, an Integer
+ object. If an EC key, an ECPointBitString object.
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().public_key has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().public_key.unwrap() instead')
+
+ @property
+ def public_key_info(self):
+ """
+ :return:
+ A PublicKeyInfo object derived from this private key.
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().public_key_info has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().public_key.asn1 instead')
+
+ @property
+ def fingerprint(self):
+ """
+ Creates a fingerprint that can be compared with a public key to see if
+ the two form a pair.
+
+ This fingerprint is not compatible with fingerprints generated by any
+ other software.
+
+ :return:
+ A byte string that is a sha256 hash of selected components (based
+ on the key type)
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().fingerprint has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().fingerprint instead')
+
+
+class EncryptedPrivateKeyInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-4
+ """
+
+ _fields = [
+ ('encryption_algorithm', EncryptionAlgorithm),
+ ('encrypted_data', OctetString),
+ ]
+
+
+# These structures are from https://tools.ietf.org/html/rfc3279
+
+class ValidationParms(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3279#page-10
+ """
+
+ _fields = [
+ ('seed', BitString),
+ ('pgen_counter', Integer),
+ ]
+
+
+class DomainParameters(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3279#page-10
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('g', Integer),
+ ('q', Integer),
+ ('j', Integer, {'optional': True}),
+ ('validation_params', ValidationParms, {'optional': True}),
+ ]
+
+
+class PublicKeyAlgorithmId(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-19
+ '1.2.840.113549.1.1.1': 'rsa',
+ # https://tools.ietf.org/html/rfc3447#page-47
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ # https://tools.ietf.org/html/rfc4055#page-8
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ # https://tools.ietf.org/html/rfc3279#page-18
+ '1.2.840.10040.4.1': 'dsa',
+ # https://tools.ietf.org/html/rfc3279#page-13
+ '1.2.840.10045.2.1': 'ec',
+ # https://tools.ietf.org/html/rfc3279#page-10
+ '1.2.840.10046.2.1': 'dh',
+ # https://tools.ietf.org/html/rfc8410#section-9
+ '1.3.101.110': 'x25519',
+ '1.3.101.111': 'x448',
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+
+class PublicKeyAlgorithm(_ForceNullParameters, Sequence):
+ """
+ Original Name: AlgorithmIdentifier
+ Source: https://tools.ietf.org/html/rfc5280#page-18
+ """
+
+ _fields = [
+ ('algorithm', PublicKeyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dsa': DSAParams,
+ 'ec': ECDomainParameters,
+ 'dh': DomainParameters,
+ 'rsaes_oaep': RSAESOAEPParams,
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+
+class PublicKeyInfo(Sequence):
+ """
+ Original Name: SubjectPublicKeyInfo
+ Source: https://tools.ietf.org/html/rfc5280#page-17
+ """
+
+ _fields = [
+ ('algorithm', PublicKeyAlgorithm),
+ ('public_key', ParsableOctetBitString),
+ ]
+
+ def _public_key_spec(self):
+ algorithm = self['algorithm']['algorithm'].native
+ return {
+ 'rsa': RSAPublicKey,
+ 'rsaes_oaep': RSAPublicKey,
+ 'rsassa_pss': RSAPublicKey,
+ 'dsa': Integer,
+ # We override the field spec with ECPoint so that users can easily
+ # decompose the byte string into the constituent X and Y coords
+ 'ec': (ECPointBitString, None),
+ 'dh': Integer,
+ # These should be treated as opaque bit strings according
+ # to RFC 8410, and need not even be valid ASN.1
+ 'x25519': (OctetBitString, None),
+ 'x448': (OctetBitString, None),
+ 'ed25519': (OctetBitString, None),
+ 'ed448': (OctetBitString, None),
+ }[algorithm]
+
+ _spec_callbacks = {
+ 'public_key': _public_key_spec
+ }
+
+ _algorithm = None
+ _bit_size = None
+ _fingerprint = None
+ _sha1 = None
+ _sha256 = None
+
+ @classmethod
+ def wrap(cls, public_key, algorithm):
+ """
+ Wraps a public key in a PublicKeyInfo structure
+
+ :param public_key:
+ A byte string or Asn1Value object of the public key
+
+ :param algorithm:
+ A unicode string of "rsa"
+
+ :return:
+ A PublicKeyInfo object
+ """
+
+ if not isinstance(public_key, byte_cls) and not isinstance(public_key, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ public_key must be a byte string or Asn1Value, not %s
+ ''',
+ type_name(public_key)
+ ))
+
+ if algorithm != 'rsa' and algorithm != 'rsassa_pss':
+ raise ValueError(unwrap(
+ '''
+ algorithm must "rsa", not %s
+ ''',
+ repr(algorithm)
+ ))
+
+ algo = PublicKeyAlgorithm()
+ algo['algorithm'] = PublicKeyAlgorithmId(algorithm)
+ algo['parameters'] = Null()
+
+ container = cls()
+ container['algorithm'] = algo
+ if isinstance(public_key, Asn1Value):
+ public_key = public_key.untag().dump()
+ container['public_key'] = ParsableOctetBitString(public_key)
+
+ return container
+
+ def unwrap(self):
+ """
+ Unwraps an RSA public key into an RSAPublicKey object. Does not support
+ DSA or EC public keys since they do not have an unwrapped form.
+
+ :return:
+ An RSAPublicKey object
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PublicKeyInfo().unwrap() has been removed, '
+ 'please use oscrypto.asymmetric.PublicKey().unwrap() instead')
+
+ @property
+ def curve(self):
+ """
+ Returns information about the curve used for an EC key
+
+ :raises:
+ ValueError - when the key is not an EC key
+
+ :return:
+ A two-element tuple, with the first element being a unicode string
+ of "implicit_ca", "specified" or "named". If the first element is
+ "implicit_ca", the second is None. If "specified", the second is
+ an OrderedDict that is the native version of SpecifiedECDomain. If
+ "named", the second is a unicode string of the curve name.
+ """
+
+ if self.algorithm != 'ec':
+ raise ValueError(unwrap(
+ '''
+ Only EC keys have a curve, this key is %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ params = self['algorithm']['parameters']
+ chosen = params.chosen
+
+ if params.name == 'implicit_ca':
+ value = None
+ else:
+ value = chosen.native
+
+ return (params.name, value)
+
+ @property
+ def hash_algo(self):
+ """
+ Returns the name of the family of hash algorithms used to generate a
+ DSA key
+
+ :raises:
+ ValueError - when the key is not a DSA key
+
+ :return:
+ A unicode string of "sha1" or "sha2" or None if no parameters are
+ present
+ """
+
+ if self.algorithm != 'dsa':
+ raise ValueError(unwrap(
+ '''
+ Only DSA keys are generated using a hash algorithm, this key is
+ %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ parameters = self['algorithm']['parameters']
+ if parameters.native is None:
+ return None
+
+ byte_len = math.log(parameters['q'].native, 2) / 8
+
+ return 'sha1' if byte_len <= 20 else 'sha2'
+
+ @property
+ def algorithm(self):
+ """
+ :return:
+ A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
+ """
+
+ if self._algorithm is None:
+ self._algorithm = self['algorithm']['algorithm'].native
+ return self._algorithm
+
+ @property
+ def bit_size(self):
+ """
+ :return:
+ The bit size of the public key, as an integer
+ """
+
+ if self._bit_size is None:
+ if self.algorithm == 'ec':
+ self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8)
+ else:
+ if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
+ prime = self['public_key'].parsed['modulus'].native
+ elif self.algorithm == 'dsa':
+ prime = self['algorithm']['parameters']['p'].native
+ self._bit_size = int(math.ceil(math.log(prime, 2)))
+ modulus = self._bit_size % 8
+ if modulus != 0:
+ self._bit_size += 8 - modulus
+
+ return self._bit_size
+
+ @property
+ def byte_size(self):
+ """
+ :return:
+ The byte size of the public key, as an integer
+ """
+
+ return int(math.ceil(self.bit_size / 8))
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this public key info
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(byte_cls(self['public_key'])).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this public key info
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(byte_cls(self['public_key'])).digest()
+ return self._sha256
+
+ @property
+ def fingerprint(self):
+ """
+ Creates a fingerprint that can be compared with a private key to see if
+ the two form a pair.
+
+ This fingerprint is not compatible with fingerprints generated by any
+ other software.
+
+ :return:
+ A byte string that is a sha256 hash of selected components (based
+ on the key type)
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PublicKeyInfo().fingerprint has been removed, '
+ 'please use oscrypto.asymmetric.PublicKey().fingerprint instead')
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/ocsp.py b/contrib/python/asn1crypto/py2/asn1crypto/ocsp.py
new file mode 100644
index 0000000000..91c7fbf3ab
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/ocsp.py
@@ -0,0 +1,703 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for the online certificate status protocol (OCSP). Exports
+the following items:
+
+ - OCSPRequest()
+ - OCSPResponse()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ._errors import unwrap
+from .algos import DigestAlgorithm, SignedDigestAlgorithm
+from .core import (
+ Boolean,
+ Choice,
+ Enumerated,
+ GeneralizedTime,
+ IA5String,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+)
+from .crl import AuthorityInfoAccessSyntax, CRLReason
+from .keys import PublicKeyAlgorithm
+from .x509 import Certificate, GeneralName, GeneralNames, Name
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc6960
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1'
+ }
+
+
+class CertId(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm),
+ ('issuer_name_hash', OctetString),
+ ('issuer_key_hash', OctetString),
+ ('serial_number', Integer),
+ ]
+
+
+class ServiceLocator(Sequence):
+ _fields = [
+ ('issuer', Name),
+ ('locator', AuthorityInfoAccessSyntax),
+ ]
+
+
+class RequestExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.7': 'service_locator',
+ }
+
+
+class RequestExtension(Sequence):
+ _fields = [
+ ('extn_id', RequestExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'service_locator': ServiceLocator,
+ }
+
+
+class RequestExtensions(SequenceOf):
+ _child_spec = RequestExtension
+
+
+class Request(Sequence):
+ _fields = [
+ ('req_cert', CertId),
+ ('single_request_extensions', RequestExtensions, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _service_locator_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['single_request_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def service_locator_value(self):
+ """
+ This extension is used when communicating with an OCSP responder that
+ acts as a proxy for OCSP requests
+
+ :return:
+ None or a ServiceLocator object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._service_locator_value
+
+
+class Requests(SequenceOf):
+ _child_spec = Request
+
+
+class ResponseType(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.1': 'basic_ocsp_response',
+ }
+
+
+class AcceptableResponses(SequenceOf):
+ _child_spec = ResponseType
+
+
+class PreferredSignatureAlgorithm(Sequence):
+ _fields = [
+ ('sig_identifier', SignedDigestAlgorithm),
+ ('cert_identifier', PublicKeyAlgorithm, {'optional': True}),
+ ]
+
+
+class PreferredSignatureAlgorithms(SequenceOf):
+ _child_spec = PreferredSignatureAlgorithm
+
+
+class TBSRequestExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.2': 'nonce',
+ '1.3.6.1.5.5.7.48.1.4': 'acceptable_responses',
+ '1.3.6.1.5.5.7.48.1.8': 'preferred_signature_algorithms',
+ }
+
+
+class TBSRequestExtension(Sequence):
+ _fields = [
+ ('extn_id', TBSRequestExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'nonce': OctetString,
+ 'acceptable_responses': AcceptableResponses,
+ 'preferred_signature_algorithms': PreferredSignatureAlgorithms,
+ }
+
+
+class TBSRequestExtensions(SequenceOf):
+ _child_spec = TBSRequestExtension
+
+
+class TBSRequest(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('requestor_name', GeneralName, {'explicit': 1, 'optional': True}),
+ ('request_list', Requests),
+ ('request_extensions', TBSRequestExtensions, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class Certificates(SequenceOf):
+ _child_spec = Certificate
+
+
+class Signature(Sequence):
+ _fields = [
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ('certs', Certificates, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class OCSPRequest(Sequence):
+ _fields = [
+ ('tbs_request', TBSRequest),
+ ('optional_signature', Signature, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _nonce_value = None
+ _acceptable_responses_value = None
+ _preferred_signature_algorithms_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_request']['request_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def nonce_value(self):
+ """
+ This extension is used to prevent replay attacks by including a unique,
+ random value with each request/response pair
+
+ :return:
+ None or an OctetString object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._nonce_value
+
+ @property
+ def acceptable_responses_value(self):
+ """
+ This extension is used to allow the client and server to communicate
+ with alternative response formats other than just basic_ocsp_response,
+ although no other formats are defined in the standard.
+
+ :return:
+ None or an AcceptableResponses object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._acceptable_responses_value
+
+ @property
+ def preferred_signature_algorithms_value(self):
+ """
+ This extension is used by the client to define what signature algorithms
+ are preferred, including both the hash algorithm and the public key
+ algorithm, with a level of detail down to even the public key algorithm
+ parameters, such as curve name.
+
+ :return:
+ None or a PreferredSignatureAlgorithms object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._preferred_signature_algorithms_value
+
+
+class OCSPResponseStatus(Enumerated):
+ _map = {
+ 0: 'successful',
+ 1: 'malformed_request',
+ 2: 'internal_error',
+ 3: 'try_later',
+ 5: 'sign_required',
+ 6: 'unauthorized',
+ }
+
+
+class ResponderId(Choice):
+ _alternatives = [
+ ('by_name', Name, {'explicit': 1}),
+ ('by_key', OctetString, {'explicit': 2}),
+ ]
+
+
+# Custom class to return a meaningful .native attribute from CertStatus()
+class StatusGood(Null):
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None or 'good'
+ """
+
+ if value is not None and value != 'good' and not isinstance(value, Null):
+ raise ValueError(unwrap(
+ '''
+ value must be one of None, "good", not %s
+ ''',
+ repr(value)
+ ))
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ return 'good'
+
+
+# Custom class to return a meaningful .native attribute from CertStatus()
+class StatusUnknown(Null):
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None or 'unknown'
+ """
+
+ if value is not None and value != 'unknown' and not isinstance(value, Null):
+ raise ValueError(unwrap(
+ '''
+ value must be one of None, "unknown", not %s
+ ''',
+ repr(value)
+ ))
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ return 'unknown'
+
+
+class RevokedInfo(Sequence):
+ _fields = [
+ ('revocation_time', GeneralizedTime),
+ ('revocation_reason', CRLReason, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class CertStatus(Choice):
+ _alternatives = [
+ ('good', StatusGood, {'implicit': 0}),
+ ('revoked', RevokedInfo, {'implicit': 1}),
+ ('unknown', StatusUnknown, {'implicit': 2}),
+ ]
+
+
+class CrlId(Sequence):
+ _fields = [
+ ('crl_url', IA5String, {'explicit': 0, 'optional': True}),
+ ('crl_num', Integer, {'explicit': 1, 'optional': True}),
+ ('crl_time', GeneralizedTime, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class SingleResponseExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.3': 'crl',
+ '1.3.6.1.5.5.7.48.1.6': 'archive_cutoff',
+ # These are CRLEntryExtension values from
+ # https://tools.ietf.org/html/rfc5280
+ '2.5.29.21': 'crl_reason',
+ '2.5.29.24': 'invalidity_date',
+ '2.5.29.29': 'certificate_issuer',
+ # https://tools.ietf.org/html/rfc6962.html#page-13
+ '1.3.6.1.4.1.11129.2.4.5': 'signed_certificate_timestamp_list',
+ }
+
+
+class SingleResponseExtension(Sequence):
+ _fields = [
+ ('extn_id', SingleResponseExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'crl': CrlId,
+ 'archive_cutoff': GeneralizedTime,
+ 'crl_reason': CRLReason,
+ 'invalidity_date': GeneralizedTime,
+ 'certificate_issuer': GeneralNames,
+ 'signed_certificate_timestamp_list': OctetString,
+ }
+
+
+class SingleResponseExtensions(SequenceOf):
+ _child_spec = SingleResponseExtension
+
+
+class SingleResponse(Sequence):
+ _fields = [
+ ('cert_id', CertId),
+ ('cert_status', CertStatus),
+ ('this_update', GeneralizedTime),
+ ('next_update', GeneralizedTime, {'explicit': 0, 'optional': True}),
+ ('single_extensions', SingleResponseExtensions, {'explicit': 1, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _crl_value = None
+ _archive_cutoff_value = None
+ _crl_reason_value = None
+ _invalidity_date_value = None
+ _certificate_issuer_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['single_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def crl_value(self):
+ """
+ This extension is used to locate the CRL that a certificate's revocation
+ is contained within.
+
+ :return:
+ None or a CrlId object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_value
+
+ @property
+ def archive_cutoff_value(self):
+ """
+ This extension is used to indicate the date at which an archived
+ (historical) certificate status entry will no longer be available.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._archive_cutoff_value
+
+ @property
+ def crl_reason_value(self):
+ """
+ This extension indicates the reason that a certificate was revoked.
+
+ :return:
+ None or a CRLReason object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_reason_value
+
+ @property
+ def invalidity_date_value(self):
+ """
+ This extension indicates the suspected date/time the private key was
+ compromised or the certificate became invalid. This would usually be
+ before the revocation date, which is when the CA processed the
+ revocation.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._invalidity_date_value
+
+ @property
+ def certificate_issuer_value(self):
+ """
+ This extension indicates the issuer of the certificate in question.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._certificate_issuer_value
+
+
+class Responses(SequenceOf):
+ _child_spec = SingleResponse
+
+
+class ResponseDataExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.2': 'nonce',
+ '1.3.6.1.5.5.7.48.1.9': 'extended_revoke',
+ }
+
+
+class ResponseDataExtension(Sequence):
+ _fields = [
+ ('extn_id', ResponseDataExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'nonce': OctetString,
+ 'extended_revoke': Null,
+ }
+
+
+class ResponseDataExtensions(SequenceOf):
+ _child_spec = ResponseDataExtension
+
+
+class ResponseData(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('responder_id', ResponderId),
+ ('produced_at', GeneralizedTime),
+ ('responses', Responses),
+ ('response_extensions', ResponseDataExtensions, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class BasicOCSPResponse(Sequence):
+ _fields = [
+ ('tbs_response_data', ResponseData),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ('certs', Certificates, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class ResponseBytes(Sequence):
+ _fields = [
+ ('response_type', ResponseType),
+ ('response', ParsableOctetString),
+ ]
+
+ _oid_pair = ('response_type', 'response')
+ _oid_specs = {
+ 'basic_ocsp_response': BasicOCSPResponse,
+ }
+
+
+class OCSPResponse(Sequence):
+ _fields = [
+ ('response_status', OCSPResponseStatus),
+ ('response_bytes', ResponseBytes, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _nonce_value = None
+ _extended_revoke_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['response_bytes']['response'].parsed['tbs_response_data']['response_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def nonce_value(self):
+ """
+ This extension is used to prevent replay attacks on the request/response
+ exchange
+
+ :return:
+ None or an OctetString object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._nonce_value
+
+ @property
+ def extended_revoke_value(self):
+ """
+ This extension is used to signal that the responder will return a
+ "revoked" status for non-issued certificates.
+
+ :return:
+ None or a Null object (if present)
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._extended_revoke_value
+
+ @property
+ def basic_ocsp_response(self):
+ """
+ A shortcut into the BasicOCSPResponse sequence
+
+ :return:
+ None or an asn1crypto.ocsp.BasicOCSPResponse object
+ """
+
+ return self['response_bytes']['response'].parsed
+
+ @property
+ def response_data(self):
+ """
+ A shortcut into the parsed, ResponseData sequence
+
+ :return:
+ None or an asn1crypto.ocsp.ResponseData object
+ """
+
+ return self['response_bytes']['response'].parsed['tbs_response_data']
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/parser.py b/contrib/python/asn1crypto/py2/asn1crypto/parser.py
new file mode 100644
index 0000000000..2f5a63e101
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/parser.py
@@ -0,0 +1,292 @@
+# coding: utf-8
+
+"""
+Functions for parsing and dumping using the ASN.1 DER encoding. Exports the
+following items:
+
+ - emit()
+ - parse()
+ - peek()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import sys
+
+from ._types import byte_cls, chr_cls, type_name
+from .util import int_from_bytes, int_to_bytes
+
+_PY2 = sys.version_info <= (3,)
+_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available'
+_MAX_DEPTH = 10
+
+
+def emit(class_, method, tag, contents):
+ """
+ Constructs a byte string of an ASN.1 DER-encoded value
+
+ This is typically not useful. Instead, use one of the standard classes from
+ asn1crypto.core, or construct a new class with specific fields, and call the
+ .dump() method.
+
+ :param class_:
+ An integer ASN.1 class value: 0 (universal), 1 (application),
+ 2 (context), 3 (private)
+
+ :param method:
+ An integer ASN.1 method value: 0 (primitive), 1 (constructed)
+
+ :param tag:
+ An integer ASN.1 tag value
+
+ :param contents:
+ A byte string of the encoded byte contents
+
+ :return:
+ A byte string of the ASN.1 DER value (header and contents)
+ """
+
+ if not isinstance(class_, int):
+ raise TypeError('class_ must be an integer, not %s' % type_name(class_))
+
+ if class_ < 0 or class_ > 3:
+ raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_)
+
+ if not isinstance(method, int):
+ raise TypeError('method must be an integer, not %s' % type_name(method))
+
+ if method < 0 or method > 1:
+ raise ValueError('method must be 0 or 1, not %s' % method)
+
+ if not isinstance(tag, int):
+ raise TypeError('tag must be an integer, not %s' % type_name(tag))
+
+ if tag < 0:
+ raise ValueError('tag must be greater than zero, not %s' % tag)
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ return _dump_header(class_, method, tag, contents) + contents
+
+
+def parse(contents, strict=False):
+ """
+ Parses a byte string of ASN.1 BER/DER-encoded data.
+
+ This is typically not useful. Instead, use one of the standard classes from
+ asn1crypto.core, or construct a new class with specific fields, and call the
+ .load() class method.
+
+ :param contents:
+ A byte string of BER/DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :raises:
+ ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
+ TypeError - when contents is not a byte string
+
+ :return:
+ A 6-element tuple:
+ - 0: integer class (0 to 3)
+ - 1: integer method
+ - 2: integer tag
+ - 3: byte string header
+ - 4: byte string content
+ - 5: byte string trailer
+ """
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ contents_len = len(contents)
+ info, consumed = _parse(contents, contents_len)
+ if strict and consumed != contents_len:
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed))
+ return info
+
+
+def peek(contents):
+ """
+ Parses a byte string of ASN.1 BER/DER-encoded data to find the length
+
+ This is typically used to look into an encoded value to see how long the
+ next chunk of ASN.1-encoded data is. Primarily it is useful when a
+ value is a concatenation of multiple values.
+
+ :param contents:
+ A byte string of BER/DER-encoded data
+
+ :raises:
+ ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
+ TypeError - when contents is not a byte string
+
+ :return:
+ An integer with the number of bytes occupied by the ASN.1 value
+ """
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ info, consumed = _parse(contents, len(contents))
+ return consumed
+
+
+def _parse(encoded_data, data_len, pointer=0, lengths_only=False, depth=0):
+ """
+ Parses a byte string into component parts
+
+ :param encoded_data:
+ A byte string that contains BER-encoded data
+
+ :param data_len:
+ The integer length of the encoded data
+
+ :param pointer:
+ The index in the byte string to parse from
+
+ :param lengths_only:
+ A boolean to cause the call to return a 2-element tuple of the integer
+ number of bytes in the header and the integer number of bytes in the
+ contents. Internal use only.
+
+ :param depth:
+ The recursion depth when evaluating indefinite-length encoding.
+
+ :return:
+ A 2-element tuple:
+ - 0: A tuple of (class_, method, tag, header, content, trailer)
+ - 1: An integer indicating how many bytes were consumed
+ """
+
+ if depth > _MAX_DEPTH:
+ raise ValueError('Indefinite-length recursion limit exceeded')
+
+ start = pointer
+
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+
+ pointer += 1
+
+ tag = first_octet & 31
+ constructed = (first_octet >> 5) & 1
+ # Base 128 length using 8th bit as continuation indicator
+ if tag == 31:
+ tag = 0
+ while True:
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+ pointer += 1
+ if num == 0x80 and tag == 0:
+ raise ValueError('Non-minimal tag encoding')
+ tag *= 128
+ tag += num & 127
+ if num >> 7 == 0:
+ break
+ if tag < 31:
+ raise ValueError('Non-minimal tag encoding')
+
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+ pointer += 1
+ trailer = b''
+
+ if length_octet >> 7 == 0:
+ contents_end = pointer + (length_octet & 127)
+
+ else:
+ length_octets = length_octet & 127
+ if length_octets:
+ if data_len < pointer + length_octets:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (length_octets, data_len - pointer))
+ pointer += length_octets
+ contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)
+
+ else:
+ # To properly parse indefinite length values, we need to scan forward
+ # parsing headers until we find a value with a length of zero. If we
+ # just scanned looking for \x00\x00, nested indefinite length values
+ # would not work.
+ if not constructed:
+ raise ValueError('Indefinite-length element must be constructed')
+ contents_end = pointer
+ while data_len < contents_end + 2 or encoded_data[contents_end:contents_end+2] != b'\x00\x00':
+ _, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True, depth=depth+1)
+ contents_end += 2
+ trailer = b'\x00\x00'
+
+ if contents_end > data_len:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end - pointer, data_len - pointer))
+
+ if lengths_only:
+ return (pointer, contents_end)
+
+ return (
+ (
+ first_octet >> 6,
+ constructed,
+ tag,
+ encoded_data[start:pointer],
+ encoded_data[pointer:contents_end-len(trailer)],
+ trailer
+ ),
+ contents_end
+ )
+
+
+def _dump_header(class_, method, tag, contents):
+ """
+ Constructs the header bytes for an ASN.1 object
+
+ :param class_:
+ An integer ASN.1 class value: 0 (universal), 1 (application),
+ 2 (context), 3 (private)
+
+ :param method:
+ An integer ASN.1 method value: 0 (primitive), 1 (constructed)
+
+ :param tag:
+ An integer ASN.1 tag value
+
+ :param contents:
+ A byte string of the encoded byte contents
+
+ :return:
+ A byte string of the ASN.1 DER header
+ """
+
+ header = b''
+
+ id_num = 0
+ id_num |= class_ << 6
+ id_num |= method << 5
+
+ if tag >= 31:
+ cont_bit = 0
+ while tag > 0:
+ header = chr_cls(cont_bit | (tag & 0x7f)) + header
+ if not cont_bit:
+ cont_bit = 0x80
+ tag = tag >> 7
+ header = chr_cls(id_num | 31) + header
+ else:
+ header += chr_cls(id_num | tag)
+
+ length = len(contents)
+ if length <= 127:
+ header += chr_cls(length)
+ else:
+ length_bytes = int_to_bytes(length)
+ header += chr_cls(0x80 | len(length_bytes))
+ header += length_bytes
+
+ return header
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/pdf.py b/contrib/python/asn1crypto/py2/asn1crypto/pdf.py
new file mode 100644
index 0000000000..b72c886ce5
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/pdf.py
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for PDF signature structures. Adds extra oid mapping and
+value parsing to asn1crypto.x509.Extension() and asn1crypto.xms.CMSAttribute().
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .cms import CMSAttributeType, CMSAttribute
+from .core import (
+ Boolean,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .crl import CertificateList
+from .ocsp import OCSPResponse
+from .x509 import (
+ Extension,
+ ExtensionId,
+ GeneralName,
+ KeyPurposeId,
+)
+
+
+class AdobeArchiveRevInfo(Sequence):
+ _fields = [
+ ('version', Integer)
+ ]
+
+
+class AdobeTimestamp(Sequence):
+ _fields = [
+ ('version', Integer),
+ ('location', GeneralName),
+ ('requires_auth', Boolean, {'optional': True, 'default': False}),
+ ]
+
+
+class OtherRevInfo(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('value', OctetString),
+ ]
+
+
+class SequenceOfCertificateList(SequenceOf):
+ _child_spec = CertificateList
+
+
+class SequenceOfOCSPResponse(SequenceOf):
+ _child_spec = OCSPResponse
+
+
+class SequenceOfOtherRevInfo(SequenceOf):
+ _child_spec = OtherRevInfo
+
+
+class RevocationInfoArchival(Sequence):
+ _fields = [
+ ('crl', SequenceOfCertificateList, {'explicit': 0, 'optional': True}),
+ ('ocsp', SequenceOfOCSPResponse, {'explicit': 1, 'optional': True}),
+ ('other_rev_info', SequenceOfOtherRevInfo, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class SetOfRevocationInfoArchival(SetOf):
+ _child_spec = RevocationInfoArchival
+
+
+ExtensionId._map['1.2.840.113583.1.1.9.2'] = 'adobe_archive_rev_info'
+ExtensionId._map['1.2.840.113583.1.1.9.1'] = 'adobe_timestamp'
+ExtensionId._map['1.2.840.113583.1.1.10'] = 'adobe_ppklite_credential'
+Extension._oid_specs['adobe_archive_rev_info'] = AdobeArchiveRevInfo
+Extension._oid_specs['adobe_timestamp'] = AdobeTimestamp
+Extension._oid_specs['adobe_ppklite_credential'] = Null
+KeyPurposeId._map['1.2.840.113583.1.1.5'] = 'pdf_signing'
+CMSAttributeType._map['1.2.840.113583.1.1.8'] = 'adobe_revocation_info_archival'
+CMSAttribute._oid_specs['adobe_revocation_info_archival'] = SetOfRevocationInfoArchival
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/pem.py b/contrib/python/asn1crypto/py2/asn1crypto/pem.py
new file mode 100644
index 0000000000..511ea4b50d
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/pem.py
@@ -0,0 +1,222 @@
+# coding: utf-8
+
+"""
+Encoding DER to PEM and decoding PEM to DER. Exports the following items:
+
+ - armor()
+ - detect()
+ - unarmor()
+
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import base64
+import re
+import sys
+
+from ._errors import unwrap
+from ._types import type_name as _type_name, str_cls, byte_cls
+
+if sys.version_info < (3,):
+ from cStringIO import StringIO as BytesIO
+else:
+ from io import BytesIO
+
+
+def detect(byte_string):
+ """
+ Detect if a byte string seems to contain a PEM-encoded block
+
+ :param byte_string:
+ A byte string to look through
+
+ :return:
+ A boolean, indicating if a PEM-encoded block is contained in the byte
+ string
+ """
+
+ if not isinstance(byte_string, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ byte_string must be a byte string, not %s
+ ''',
+ _type_name(byte_string)
+ ))
+
+ return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1
+
+
+def armor(type_name, der_bytes, headers=None):
+ """
+ Armors a DER-encoded byte string in PEM
+
+ :param type_name:
+ A unicode string that will be capitalized and placed in the header
+ and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
+ will appear as "-----BEGIN CERTIFICATE-----" and
+ "-----END CERTIFICATE-----".
+
+ :param der_bytes:
+ A byte string to be armored
+
+ :param headers:
+ An OrderedDict of the header lines to write after the BEGIN line
+
+ :return:
+ A byte string of the PEM block
+ """
+
+ if not isinstance(der_bytes, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ der_bytes must be a byte string, not %s
+ ''' % _type_name(der_bytes)
+ ))
+
+ if not isinstance(type_name, str_cls):
+ raise TypeError(unwrap(
+ '''
+ type_name must be a unicode string, not %s
+ ''',
+ _type_name(type_name)
+ ))
+
+ type_name = type_name.upper().encode('ascii')
+
+ output = BytesIO()
+ output.write(b'-----BEGIN ')
+ output.write(type_name)
+ output.write(b'-----\n')
+ if headers:
+ for key in headers:
+ output.write(key.encode('ascii'))
+ output.write(b': ')
+ output.write(headers[key].encode('ascii'))
+ output.write(b'\n')
+ output.write(b'\n')
+ b64_bytes = base64.b64encode(der_bytes)
+ b64_len = len(b64_bytes)
+ i = 0
+ while i < b64_len:
+ output.write(b64_bytes[i:i + 64])
+ output.write(b'\n')
+ i += 64
+ output.write(b'-----END ')
+ output.write(type_name)
+ output.write(b'-----\n')
+
+ return output.getvalue()
+
+
+def _unarmor(pem_bytes):
+ """
+ Convert a PEM-encoded byte string into one or more DER-encoded byte strings
+
+ :param pem_bytes:
+ A byte string of the PEM-encoded data
+
+ :raises:
+ ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
+
+ :return:
+ A generator of 3-element tuples in the format: (object_type, headers,
+ der_bytes). The object_type is a unicode string of what is between
+ "-----BEGIN " and "-----". Examples include: "CERTIFICATE",
+ "PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
+ in the form "Name: Value" that are right after the begin line.
+ """
+
+ if not isinstance(pem_bytes, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ pem_bytes must be a byte string, not %s
+ ''',
+ _type_name(pem_bytes)
+ ))
+
+ # Valid states include: "trash", "headers", "body"
+ state = 'trash'
+ headers = {}
+ base64_data = b''
+ object_type = None
+
+ found_start = False
+ found_end = False
+
+ for line in pem_bytes.splitlines(False):
+ if line == b'':
+ continue
+
+ if state == "trash":
+ # Look for a starting line since some CA cert bundle show the cert
+ # into in a parsed format above each PEM block
+ type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
+ if not type_name_match:
+ continue
+ object_type = type_name_match.group(1).decode('ascii')
+
+ found_start = True
+ state = 'headers'
+ continue
+
+ if state == 'headers':
+ if line.find(b':') == -1:
+ state = 'body'
+ else:
+ decoded_line = line.decode('ascii')
+ name, value = decoded_line.split(':', 1)
+ headers[name] = value.strip()
+ continue
+
+ if state == 'body':
+ if line[0:5] in (b'-----', b'---- '):
+ der_bytes = base64.b64decode(base64_data)
+
+ yield (object_type, headers, der_bytes)
+
+ state = 'trash'
+ headers = {}
+ base64_data = b''
+ object_type = None
+ found_end = True
+ continue
+
+ base64_data += line
+
+ if not found_start or not found_end:
+ raise ValueError(unwrap(
+ '''
+ pem_bytes does not appear to contain PEM-encoded data - no
+ BEGIN/END combination found
+ '''
+ ))
+
+
+def unarmor(pem_bytes, multiple=False):
+ """
+ Convert a PEM-encoded byte string into a DER-encoded byte string
+
+ :param pem_bytes:
+ A byte string of the PEM-encoded data
+
+ :param multiple:
+ If True, function will return a generator
+
+ :raises:
+ ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
+
+ :return:
+ A 3-element tuple (object_name, headers, der_bytes). The object_name is
+ a unicode string of what is between "-----BEGIN " and "-----". Examples
+ include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
+ dict containing any lines in the form "Name: Value" that are right
+ after the begin line.
+ """
+
+ generator = _unarmor(pem_bytes)
+
+ if not multiple:
+ return next(generator)
+
+ return generator
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/pkcs12.py b/contrib/python/asn1crypto/py2/asn1crypto/pkcs12.py
new file mode 100644
index 0000000000..7ebcefeb31
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/pkcs12.py
@@ -0,0 +1,193 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for PKCS#12 files. Exports the following items:
+
+ - CertBag()
+ - CrlBag()
+ - Pfx()
+ - SafeBag()
+ - SecretBag()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import DigestInfo
+from .cms import ContentInfo, SignedData
+from .core import (
+ Any,
+ BMPString,
+ Integer,
+ ObjectIdentifier,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .keys import PrivateKeyInfo, EncryptedPrivateKeyInfo
+from .x509 import Certificate, KeyPurposeId
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc7292
+
+class MacData(Sequence):
+ _fields = [
+ ('mac', DigestInfo),
+ ('mac_salt', OctetString),
+ ('iterations', Integer, {'default': 1}),
+ ]
+
+
+class Version(Integer):
+ _map = {
+ 3: 'v3'
+ }
+
+
+class AttributeType(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc2985#page-18
+ '1.2.840.113549.1.9.20': 'friendly_name',
+ '1.2.840.113549.1.9.21': 'local_key_id',
+ # https://support.microsoft.com/en-us/kb/287547
+ '1.3.6.1.4.1.311.17.1': 'microsoft_local_machine_keyset',
+ # https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
+ # this is a set of OIDs, representing key usage, the usual value is a SET of one element OID 2.5.29.37.0
+ '2.16.840.1.113894.746875.1.1': 'trusted_key_usage',
+ }
+
+
+class SetOfAny(SetOf):
+ _child_spec = Any
+
+
+class SetOfBMPString(SetOf):
+ _child_spec = BMPString
+
+
+class SetOfOctetString(SetOf):
+ _child_spec = OctetString
+
+
+class SetOfKeyPurposeId(SetOf):
+ _child_spec = KeyPurposeId
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', AttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {
+ 'friendly_name': SetOfBMPString,
+ 'local_key_id': SetOfOctetString,
+ 'microsoft_csp_name': SetOfBMPString,
+ 'trusted_key_usage': SetOfKeyPurposeId,
+ }
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class Attributes(SetOf):
+ _child_spec = Attribute
+
+
+class Pfx(Sequence):
+ _fields = [
+ ('version', Version),
+ ('auth_safe', ContentInfo),
+ ('mac_data', MacData, {'optional': True})
+ ]
+
+ _authenticated_safe = None
+
+ @property
+ def authenticated_safe(self):
+ if self._authenticated_safe is None:
+ content = self['auth_safe']['content']
+ if isinstance(content, SignedData):
+ content = content['content_info']['content']
+ self._authenticated_safe = AuthenticatedSafe.load(content.native)
+ return self._authenticated_safe
+
+
+class AuthenticatedSafe(SequenceOf):
+ _child_spec = ContentInfo
+
+
+class BagId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.12.10.1.1': 'key_bag',
+ '1.2.840.113549.1.12.10.1.2': 'pkcs8_shrouded_key_bag',
+ '1.2.840.113549.1.12.10.1.3': 'cert_bag',
+ '1.2.840.113549.1.12.10.1.4': 'crl_bag',
+ '1.2.840.113549.1.12.10.1.5': 'secret_bag',
+ '1.2.840.113549.1.12.10.1.6': 'safe_contents',
+ }
+
+
+class CertId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.22.1': 'x509',
+ '1.2.840.113549.1.9.22.2': 'sdsi',
+ }
+
+
+class CertBag(Sequence):
+ _fields = [
+ ('cert_id', CertId),
+ ('cert_value', ParsableOctetString, {'explicit': 0}),
+ ]
+
+ _oid_pair = ('cert_id', 'cert_value')
+ _oid_specs = {
+ 'x509': Certificate,
+ }
+
+
+class CrlBag(Sequence):
+ _fields = [
+ ('crl_id', ObjectIdentifier),
+ ('crl_value', OctetString, {'explicit': 0}),
+ ]
+
+
+class SecretBag(Sequence):
+ _fields = [
+ ('secret_type_id', ObjectIdentifier),
+ ('secret_value', OctetString, {'explicit': 0}),
+ ]
+
+
+class SafeContents(SequenceOf):
+ pass
+
+
+class SafeBag(Sequence):
+ _fields = [
+ ('bag_id', BagId),
+ ('bag_value', Any, {'explicit': 0}),
+ ('bag_attributes', Attributes, {'optional': True}),
+ ]
+
+ _oid_pair = ('bag_id', 'bag_value')
+ _oid_specs = {
+ 'key_bag': PrivateKeyInfo,
+ 'pkcs8_shrouded_key_bag': EncryptedPrivateKeyInfo,
+ 'cert_bag': CertBag,
+ 'crl_bag': CrlBag,
+ 'secret_bag': SecretBag,
+ 'safe_contents': SafeContents
+ }
+
+
+SafeContents._child_spec = SafeBag
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/tsp.py b/contrib/python/asn1crypto/py2/asn1crypto/tsp.py
new file mode 100644
index 0000000000..f006da99c1
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/tsp.py
@@ -0,0 +1,310 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for the time stamp protocol (TSP). Exports the following
+items:
+
+ - TimeStampReq()
+ - TimeStampResp()
+
+Also adds TimeStampedData() support to asn1crypto.cms.ContentInfo(),
+TimeStampedData() and TSTInfo() support to
+asn1crypto.cms.EncapsulatedContentInfo() and some oids and value parsers to
+asn1crypto.cms.CMSAttribute().
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import DigestAlgorithm
+from .cms import (
+ CMSAttribute,
+ CMSAttributeType,
+ ContentInfo,
+ ContentType,
+ EncapsulatedContentInfo,
+)
+from .core import (
+ Any,
+ BitString,
+ Boolean,
+ Choice,
+ GeneralizedTime,
+ IA5String,
+ Integer,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+ UTF8String,
+)
+from .crl import CertificateList
+from .x509 import (
+ Attributes,
+ CertificatePolicies,
+ GeneralName,
+ GeneralNames,
+)
+
+
+# The structures in this file are based on https://tools.ietf.org/html/rfc3161,
+# https://tools.ietf.org/html/rfc4998, https://tools.ietf.org/html/rfc5544,
+# https://tools.ietf.org/html/rfc5035, https://tools.ietf.org/html/rfc2634
+
+class Version(Integer):
+ _map = {
+ 0: 'v0',
+ 1: 'v1',
+ 2: 'v2',
+ 3: 'v3',
+ 4: 'v4',
+ 5: 'v5',
+ }
+
+
+class MessageImprint(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm),
+ ('hashed_message', OctetString),
+ ]
+
+
+class Accuracy(Sequence):
+ _fields = [
+ ('seconds', Integer, {'optional': True}),
+ ('millis', Integer, {'implicit': 0, 'optional': True}),
+ ('micros', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class Extension(Sequence):
+ _fields = [
+ ('extn_id', ObjectIdentifier),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', OctetString),
+ ]
+
+
+class Extensions(SequenceOf):
+ _child_spec = Extension
+
+
+class TSTInfo(Sequence):
+ _fields = [
+ ('version', Version),
+ ('policy', ObjectIdentifier),
+ ('message_imprint', MessageImprint),
+ ('serial_number', Integer),
+ ('gen_time', GeneralizedTime),
+ ('accuracy', Accuracy, {'optional': True}),
+ ('ordering', Boolean, {'default': False}),
+ ('nonce', Integer, {'optional': True}),
+ ('tsa', GeneralName, {'explicit': 0, 'optional': True}),
+ ('extensions', Extensions, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class TimeStampReq(Sequence):
+ _fields = [
+ ('version', Version),
+ ('message_imprint', MessageImprint),
+ ('req_policy', ObjectIdentifier, {'optional': True}),
+ ('nonce', Integer, {'optional': True}),
+ ('cert_req', Boolean, {'default': False}),
+ ('extensions', Extensions, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class PKIStatus(Integer):
+ _map = {
+ 0: 'granted',
+ 1: 'granted_with_mods',
+ 2: 'rejection',
+ 3: 'waiting',
+ 4: 'revocation_warning',
+ 5: 'revocation_notification',
+ }
+
+
+class PKIFreeText(SequenceOf):
+ _child_spec = UTF8String
+
+
+class PKIFailureInfo(BitString):
+ _map = {
+ 0: 'bad_alg',
+ 2: 'bad_request',
+ 5: 'bad_data_format',
+ 14: 'time_not_available',
+ 15: 'unaccepted_policy',
+ 16: 'unaccepted_extensions',
+ 17: 'add_info_not_available',
+ 25: 'system_failure',
+ }
+
+
+class PKIStatusInfo(Sequence):
+ _fields = [
+ ('status', PKIStatus),
+ ('status_string', PKIFreeText, {'optional': True}),
+ ('fail_info', PKIFailureInfo, {'optional': True}),
+ ]
+
+
+class TimeStampResp(Sequence):
+ _fields = [
+ ('status', PKIStatusInfo),
+ ('time_stamp_token', ContentInfo),
+ ]
+
+
+class MetaData(Sequence):
+ _fields = [
+ ('hash_protected', Boolean),
+ ('file_name', UTF8String, {'optional': True}),
+ ('media_type', IA5String, {'optional': True}),
+ ('other_meta_data', Attributes, {'optional': True}),
+ ]
+
+
+class TimeStampAndCRL(Sequence):
+ _fields = [
+ ('time_stamp', EncapsulatedContentInfo),
+ ('crl', CertificateList, {'optional': True}),
+ ]
+
+
+class TimeStampTokenEvidence(SequenceOf):
+ _child_spec = TimeStampAndCRL
+
+
+class DigestAlgorithms(SequenceOf):
+ _child_spec = DigestAlgorithm
+
+
+class EncryptionInfo(Sequence):
+ _fields = [
+ ('encryption_info_type', ObjectIdentifier),
+ ('encryption_info_value', Any),
+ ]
+
+
+class PartialHashtree(SequenceOf):
+ _child_spec = OctetString
+
+
+class PartialHashtrees(SequenceOf):
+ _child_spec = PartialHashtree
+
+
+class ArchiveTimeStamp(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm, {'implicit': 0, 'optional': True}),
+ ('attributes', Attributes, {'implicit': 1, 'optional': True}),
+ ('reduced_hashtree', PartialHashtrees, {'implicit': 2, 'optional': True}),
+ ('time_stamp', ContentInfo),
+ ]
+
+
+class ArchiveTimeStampSequence(SequenceOf):
+ _child_spec = ArchiveTimeStamp
+
+
+class EvidenceRecord(Sequence):
+ _fields = [
+ ('version', Version),
+ ('digest_algorithms', DigestAlgorithms),
+ ('crypto_infos', Attributes, {'implicit': 0, 'optional': True}),
+ ('encryption_info', EncryptionInfo, {'implicit': 1, 'optional': True}),
+ ('archive_time_stamp_sequence', ArchiveTimeStampSequence),
+ ]
+
+
+class OtherEvidence(Sequence):
+ _fields = [
+ ('oe_type', ObjectIdentifier),
+ ('oe_value', Any),
+ ]
+
+
+class Evidence(Choice):
+ _alternatives = [
+ ('tst_evidence', TimeStampTokenEvidence, {'implicit': 0}),
+ ('ers_evidence', EvidenceRecord, {'implicit': 1}),
+ ('other_evidence', OtherEvidence, {'implicit': 2}),
+ ]
+
+
+class TimeStampedData(Sequence):
+ _fields = [
+ ('version', Version),
+ ('data_uri', IA5String, {'optional': True}),
+ ('meta_data', MetaData, {'optional': True}),
+ ('content', OctetString, {'optional': True}),
+ ('temporal_evidence', Evidence),
+ ]
+
+
+class IssuerSerial(Sequence):
+ _fields = [
+ ('issuer', GeneralNames),
+ ('serial_number', Integer),
+ ]
+
+
+class ESSCertID(Sequence):
+ _fields = [
+ ('cert_hash', OctetString),
+ ('issuer_serial', IssuerSerial, {'optional': True}),
+ ]
+
+
+class ESSCertIDs(SequenceOf):
+ _child_spec = ESSCertID
+
+
+class SigningCertificate(Sequence):
+ _fields = [
+ ('certs', ESSCertIDs),
+ ('policies', CertificatePolicies, {'optional': True}),
+ ]
+
+
+class SetOfSigningCertificates(SetOf):
+ _child_spec = SigningCertificate
+
+
+class ESSCertIDv2(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm, {'default': {'algorithm': 'sha256'}}),
+ ('cert_hash', OctetString),
+ ('issuer_serial', IssuerSerial, {'optional': True}),
+ ]
+
+
+class ESSCertIDv2s(SequenceOf):
+ _child_spec = ESSCertIDv2
+
+
+class SigningCertificateV2(Sequence):
+ _fields = [
+ ('certs', ESSCertIDv2s),
+ ('policies', CertificatePolicies, {'optional': True}),
+ ]
+
+
+class SetOfSigningCertificatesV2(SetOf):
+ _child_spec = SigningCertificateV2
+
+
+EncapsulatedContentInfo._oid_specs['tst_info'] = TSTInfo
+EncapsulatedContentInfo._oid_specs['timestamped_data'] = TimeStampedData
+ContentInfo._oid_specs['timestamped_data'] = TimeStampedData
+ContentType._map['1.2.840.113549.1.9.16.1.4'] = 'tst_info'
+ContentType._map['1.2.840.113549.1.9.16.1.31'] = 'timestamped_data'
+CMSAttributeType._map['1.2.840.113549.1.9.16.2.12'] = 'signing_certificate'
+CMSAttribute._oid_specs['signing_certificate'] = SetOfSigningCertificates
+CMSAttributeType._map['1.2.840.113549.1.9.16.2.47'] = 'signing_certificate_v2'
+CMSAttribute._oid_specs['signing_certificate_v2'] = SetOfSigningCertificatesV2
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/util.py b/contrib/python/asn1crypto/py2/asn1crypto/util.py
new file mode 100644
index 0000000000..7196897cec
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/util.py
@@ -0,0 +1,878 @@
+# coding: utf-8
+
+"""
+Miscellaneous data helpers, including functions for converting integers to and
+from bytes and UTC timezone. Exports the following items:
+
+ - OrderedDict()
+ - int_from_bytes()
+ - int_to_bytes()
+ - timezone.utc
+ - utc_with_dst
+ - create_timezone()
+ - inet_ntop()
+ - inet_pton()
+ - uri_to_iri()
+ - iri_to_uri()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import math
+import sys
+from datetime import datetime, date, timedelta, tzinfo
+
+from ._errors import unwrap
+from ._iri import iri_to_uri, uri_to_iri # noqa
+from ._ordereddict import OrderedDict # noqa
+from ._types import type_name
+
+if sys.platform == 'win32':
+ from ._inet import inet_ntop, inet_pton
+else:
+ from socket import inet_ntop, inet_pton # noqa
+
+
+# Python 2
+if sys.version_info <= (3,):
+
+ def int_to_bytes(value, signed=False, width=None):
+ """
+ Converts an integer to a byte string
+
+ :param value:
+ The integer to convert
+
+ :param signed:
+ If the byte string should be encoded using two's complement
+
+ :param width:
+ If None, the minimal possible size (but at least 1),
+ otherwise an integer of the byte width for the return value
+
+ :return:
+ A byte string
+ """
+
+ if value == 0 and width == 0:
+ return b''
+
+ # Handle negatives in two's complement
+ is_neg = False
+ if signed and value < 0:
+ is_neg = True
+ bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
+ value = (value + (1 << bits)) % (1 << bits)
+
+ hex_str = '%x' % value
+ if len(hex_str) & 1:
+ hex_str = '0' + hex_str
+
+ output = hex_str.decode('hex')
+
+ if signed and not is_neg and ord(output[0:1]) & 0x80:
+ output = b'\x00' + output
+
+ if width is not None:
+ if len(output) > width:
+ raise OverflowError('int too big to convert')
+ if is_neg:
+ pad_char = b'\xFF'
+ else:
+ pad_char = b'\x00'
+ output = (pad_char * (width - len(output))) + output
+ elif is_neg and ord(output[0:1]) & 0x80 == 0:
+ output = b'\xFF' + output
+
+ return output
+
+ def int_from_bytes(value, signed=False):
+ """
+ Converts a byte string to an integer
+
+ :param value:
+ The byte string to convert
+
+ :param signed:
+ If the byte string should be interpreted using two's complement
+
+ :return:
+ An integer
+ """
+
+ if value == b'':
+ return 0
+
+ num = long(value.encode("hex"), 16) # noqa
+
+ if not signed:
+ return num
+
+ # Check for sign bit and handle two's complement
+ if ord(value[0:1]) & 0x80:
+ bit_len = len(value) * 8
+ return num - (1 << bit_len)
+
+ return num
+
+ class timezone(tzinfo): # noqa
+ """
+ Implements datetime.timezone for py2.
+ Only full minute offsets are supported.
+ DST is not supported.
+ """
+
+ def __init__(self, offset, name=None):
+ """
+ :param offset:
+ A timedelta with this timezone's offset from UTC
+
+ :param name:
+ Name of the timezone; if None, generate one.
+ """
+
+ if not timedelta(hours=-24) < offset < timedelta(hours=24):
+ raise ValueError('Offset must be in [-23:59, 23:59]')
+
+ if offset.seconds % 60 or offset.microseconds:
+ raise ValueError('Offset must be full minutes')
+
+ self._offset = offset
+
+ if name is not None:
+ self._name = name
+ elif not offset:
+ self._name = 'UTC'
+ else:
+ self._name = 'UTC' + _format_offset(offset)
+
+ def __eq__(self, other):
+ """
+ Compare two timezones
+
+ :param other:
+ The other timezone to compare to
+
+ :return:
+ A boolean
+ """
+
+ if type(other) != timezone:
+ return False
+ return self._offset == other._offset
+
+ def __getinitargs__(self):
+ """
+ Called by tzinfo.__reduce__ to support pickle and copy.
+
+ :return:
+ offset and name, to be used for __init__
+ """
+
+ return self._offset, self._name
+
+ def tzname(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ Name of this timezone
+ """
+
+ return self._name
+
+ def utcoffset(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ A timedelta object with the offset from UTC
+ """
+
+ return self._offset
+
+ def dst(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ Zero timedelta
+ """
+
+ return timedelta(0)
+
+ timezone.utc = timezone(timedelta(0))
+
+# Python 3
+else:
+
+ from datetime import timezone # noqa
+
+ def int_to_bytes(value, signed=False, width=None):
+ """
+ Converts an integer to a byte string
+
+ :param value:
+ The integer to convert
+
+ :param signed:
+ If the byte string should be encoded using two's complement
+
+ :param width:
+ If None, the minimal possible size (but at least 1),
+ otherwise an integer of the byte width for the return value
+
+ :return:
+ A byte string
+ """
+
+ if width is None:
+ if signed:
+ if value < 0:
+ bits_required = abs(value + 1).bit_length()
+ else:
+ bits_required = value.bit_length()
+ if bits_required % 8 == 0:
+ bits_required += 1
+ else:
+ bits_required = value.bit_length()
+ width = math.ceil(bits_required / 8) or 1
+ return value.to_bytes(width, byteorder='big', signed=signed)
+
+ def int_from_bytes(value, signed=False):
+ """
+ Converts a byte string to an integer
+
+ :param value:
+ The byte string to convert
+
+ :param signed:
+ If the byte string should be interpreted using two's complement
+
+ :return:
+ An integer
+ """
+
+ return int.from_bytes(value, 'big', signed=signed)
+
+
+def _format_offset(off):
+ """
+ Format a timedelta into "[+-]HH:MM" format or "" for None
+ """
+
+ if off is None:
+ return ''
+ mins = off.days * 24 * 60 + off.seconds // 60
+ sign = '-' if mins < 0 else '+'
+ return sign + '%02d:%02d' % divmod(abs(mins), 60)
+
+
+class _UtcWithDst(tzinfo):
+ """
+ Utc class where dst does not return None; required for astimezone
+ """
+
+ def tzname(self, dt):
+ return 'UTC'
+
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def dst(self, dt):
+ return timedelta(0)
+
+
+utc_with_dst = _UtcWithDst()
+
+_timezone_cache = {}
+
+
+def create_timezone(offset):
+ """
+ Returns a new datetime.timezone object with the given offset.
+ Uses cached objects if possible.
+
+ :param offset:
+ A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59.
+
+ :return:
+ A datetime.timezone object
+ """
+
+ try:
+ tz = _timezone_cache[offset]
+ except KeyError:
+ tz = _timezone_cache[offset] = timezone(offset)
+ return tz
+
+
+class extended_date(object):
+ """
+ A datetime.datetime-like object that represents the year 0. This is just
+ to handle 0000-01-01 found in some certificates. Python's datetime does
+ not support year 0.
+
+ The proleptic gregorian calendar repeats itself every 400 years. Therefore,
+ the simplest way to format is to substitute year 2000.
+ """
+
+ def __init__(self, year, month, day):
+ """
+ :param year:
+ The integer 0
+
+ :param month:
+ An integer from 1 to 12
+
+ :param day:
+ An integer from 1 to 31
+ """
+
+ if year != 0:
+ raise ValueError('year must be 0')
+
+ self._y2k = date(2000, month, day)
+
+ @property
+ def year(self):
+ """
+ :return:
+ The integer 0
+ """
+
+ return 0
+
+ @property
+ def month(self):
+ """
+ :return:
+ An integer from 1 to 12
+ """
+
+ return self._y2k.month
+
+ @property
+ def day(self):
+ """
+ :return:
+ An integer from 1 to 31
+ """
+
+ return self._y2k.day
+
+ def strftime(self, format):
+ """
+ Formats the date using strftime()
+
+ :param format:
+ A strftime() format string
+
+ :return:
+ A str, the formatted date as a unicode string
+ in Python 3 and a byte string in Python 2
+ """
+
+ # Format the date twice, once with year 2000, once with year 4000.
+ # The only differences in the result will be in the millennium. Find them and replace by zeros.
+ y2k = self._y2k.strftime(format)
+ y4k = self._y2k.replace(year=4000).strftime(format)
+ return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
+
+ def isoformat(self):
+ """
+ Formats the date as %Y-%m-%d
+
+ :return:
+ The date formatted to %Y-%m-%d as a unicode string in Python 3
+ and a byte string in Python 2
+ """
+
+ return self.strftime('0000-%m-%d')
+
+ def replace(self, year=None, month=None, day=None):
+ """
+ Returns a new datetime.date or asn1crypto.util.extended_date
+ object with the specified components replaced
+
+ :return:
+ A datetime.date or asn1crypto.util.extended_date object
+ """
+
+ if year is None:
+ year = self.year
+ if month is None:
+ month = self.month
+ if day is None:
+ day = self.day
+
+ if year > 0:
+ cls = date
+ else:
+ cls = extended_date
+
+ return cls(
+ year,
+ month,
+ day
+ )
+
+ def __str__(self):
+ """
+ :return:
+ A str representing this extended_date, e.g. "0000-01-01"
+ """
+
+ return self.strftime('%Y-%m-%d')
+
+ def __eq__(self, other):
+ """
+ Compare two extended_date objects
+
+ :param other:
+ The other extended_date to compare to
+
+ :return:
+ A boolean
+ """
+
+ # datetime.date object wouldn't compare equal because it can't be year 0
+ if not isinstance(other, self.__class__):
+ return False
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ """
+ Compare two extended_date objects
+
+ :param other:
+ The other extended_date to compare to
+
+ :return:
+ A boolean
+ """
+
+ return not self.__eq__(other)
+
+ def _comparison_error(self, other):
+ raise TypeError(unwrap(
+ '''
+ An asn1crypto.util.extended_date object can only be compared to
+ an asn1crypto.util.extended_date or datetime.date object, not %s
+ ''',
+ type_name(other)
+ ))
+
+ def __cmp__(self, other):
+ """
+ Compare two extended_date or datetime.date objects
+
+ :param other:
+ The other extended_date object to compare to
+
+ :return:
+ An integer smaller than, equal to, or larger than 0
+ """
+
+ # self is year 0, other is >= year 1
+ if isinstance(other, date):
+ return -1
+
+ if not isinstance(other, self.__class__):
+ self._comparison_error(other)
+
+ if self._y2k < other._y2k:
+ return -1
+ if self._y2k > other._y2k:
+ return 1
+ return 0
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __le__(self, other):
+ return self.__cmp__(other) <= 0
+
+ def __gt__(self, other):
+ return self.__cmp__(other) > 0
+
+ def __ge__(self, other):
+ return self.__cmp__(other) >= 0
+
+
+class extended_datetime(object):
+ """
+ A datetime.datetime-like object that represents the year 0. This is just
+ to handle 0000-01-01 found in some certificates. Python's datetime does
+ not support year 0.
+
+ The proleptic gregorian calendar repeats itself every 400 years. Therefore,
+ the simplest way to format is to substitute year 2000.
+ """
+
+ # There are 97 leap days during 400 years.
+ DAYS_IN_400_YEARS = 400 * 365 + 97
+ DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS
+
+ def __init__(self, year, *args, **kwargs):
+ """
+ :param year:
+ The integer 0
+
+ :param args:
+ Other positional arguments; see datetime.datetime.
+
+ :param kwargs:
+ Other keyword arguments; see datetime.datetime.
+ """
+
+ if year != 0:
+ raise ValueError('year must be 0')
+
+ self._y2k = datetime(2000, *args, **kwargs)
+
+ @property
+ def year(self):
+ """
+ :return:
+ The integer 0
+ """
+
+ return 0
+
+ @property
+ def month(self):
+ """
+ :return:
+ An integer from 1 to 12
+ """
+
+ return self._y2k.month
+
+ @property
+ def day(self):
+ """
+ :return:
+ An integer from 1 to 31
+ """
+
+ return self._y2k.day
+
+ @property
+ def hour(self):
+ """
+ :return:
+ An integer from 1 to 24
+ """
+
+ return self._y2k.hour
+
+ @property
+ def minute(self):
+ """
+ :return:
+ An integer from 1 to 60
+ """
+
+ return self._y2k.minute
+
+ @property
+ def second(self):
+ """
+ :return:
+ An integer from 1 to 60
+ """
+
+ return self._y2k.second
+
+ @property
+ def microsecond(self):
+ """
+ :return:
+ An integer from 0 to 999999
+ """
+
+ return self._y2k.microsecond
+
+ @property
+ def tzinfo(self):
+ """
+ :return:
+ If object is timezone aware, a datetime.tzinfo object, else None.
+ """
+
+ return self._y2k.tzinfo
+
+ def utcoffset(self):
+ """
+ :return:
+ If object is timezone aware, a datetime.timedelta object, else None.
+ """
+
+ return self._y2k.utcoffset()
+
+ def time(self):
+ """
+ :return:
+ A datetime.time object
+ """
+
+ return self._y2k.time()
+
+ def date(self):
+ """
+ :return:
+ An asn1crypto.util.extended_date of the date
+ """
+
+ return extended_date(0, self.month, self.day)
+
+ def strftime(self, format):
+ """
+ Performs strftime(), always returning a str
+
+ :param format:
+ A strftime() format string
+
+ :return:
+ A str of the formatted datetime
+ """
+
+ # Format the datetime twice, once with year 2000, once with year 4000.
+ # The only differences in the result will be in the millennium. Find them and replace by zeros.
+ y2k = self._y2k.strftime(format)
+ y4k = self._y2k.replace(year=4000).strftime(format)
+ return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
+
+ def isoformat(self, sep='T'):
+ """
+ Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
+ date and time portions
+
+ :param set:
+ A single character of the separator to place between the date and
+ time
+
+ :return:
+ The formatted datetime as a unicode string in Python 3 and a byte
+ string in Python 2
+ """
+
+ s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second)
+ if self.microsecond:
+ s += '.%06d' % self.microsecond
+ return s + _format_offset(self.utcoffset())
+
+ def replace(self, year=None, *args, **kwargs):
+ """
+ Returns a new datetime.datetime or asn1crypto.util.extended_datetime
+ object with the specified components replaced
+
+ :param year:
+ The new year to substitute. None to keep it.
+
+ :param args:
+ Other positional arguments; see datetime.datetime.replace.
+
+ :param kwargs:
+ Other keyword arguments; see datetime.datetime.replace.
+
+ :return:
+ A datetime.datetime or asn1crypto.util.extended_datetime object
+ """
+
+ if year:
+ return self._y2k.replace(year, *args, **kwargs)
+
+ return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs))
+
+ def astimezone(self, tz):
+ """
+ Convert this extended_datetime to another timezone.
+
+ :param tz:
+ A datetime.tzinfo object.
+
+ :return:
+ A new extended_datetime or datetime.datetime object
+ """
+
+ return extended_datetime.from_y2k(self._y2k.astimezone(tz))
+
+ def timestamp(self):
+ """
+ Return POSIX timestamp. Only supported in python >= 3.3
+
+ :return:
+ A float representing the seconds since 1970-01-01 UTC. This will be a negative value.
+ """
+
+ return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400
+
+ def __str__(self):
+ """
+ :return:
+ A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00"
+ """
+
+ return self.isoformat(sep=' ')
+
+ def __eq__(self, other):
+ """
+ Compare two extended_datetime objects
+
+ :param other:
+ The other extended_datetime to compare to
+
+ :return:
+ A boolean
+ """
+
+ # Only compare against other datetime or extended_datetime objects
+ if not isinstance(other, (self.__class__, datetime)):
+ return False
+
+ # Offset-naive and offset-aware datetimes are never the same
+ if (self.tzinfo is None) != (other.tzinfo is None):
+ return False
+
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ """
+ Compare two extended_datetime objects
+
+ :param other:
+ The other extended_datetime to compare to
+
+ :return:
+ A boolean
+ """
+
+ return not self.__eq__(other)
+
+ def _comparison_error(self, other):
+ """
+ Raises a TypeError about the other object not being suitable for
+ comparison
+
+ :param other:
+ The object being compared to
+ """
+
+ raise TypeError(unwrap(
+ '''
+ An asn1crypto.util.extended_datetime object can only be compared to
+ an asn1crypto.util.extended_datetime or datetime.datetime object,
+ not %s
+ ''',
+ type_name(other)
+ ))
+
+ def __cmp__(self, other):
+ """
+ Compare two extended_datetime or datetime.datetime objects
+
+ :param other:
+ The other extended_datetime or datetime.datetime object to compare to
+
+ :return:
+ An integer smaller than, equal to, or larger than 0
+ """
+
+ if not isinstance(other, (self.__class__, datetime)):
+ self._comparison_error(other)
+
+ if (self.tzinfo is None) != (other.tzinfo is None):
+ raise TypeError("can't compare offset-naive and offset-aware datetimes")
+
+ diff = self - other
+ zero = timedelta(0)
+ if diff < zero:
+ return -1
+ if diff > zero:
+ return 1
+ return 0
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __le__(self, other):
+ return self.__cmp__(other) <= 0
+
+ def __gt__(self, other):
+ return self.__cmp__(other) > 0
+
+ def __ge__(self, other):
+ return self.__cmp__(other) >= 0
+
+ def __add__(self, other):
+ """
+ Adds a timedelta
+
+ :param other:
+ A datetime.timedelta object to add.
+
+ :return:
+ A new extended_datetime or datetime.datetime object.
+ """
+
+ return extended_datetime.from_y2k(self._y2k + other)
+
+ def __sub__(self, other):
+ """
+ Subtracts a timedelta or another datetime.
+
+ :param other:
+ A datetime.timedelta or datetime.datetime or extended_datetime object to subtract.
+
+ :return:
+ If a timedelta is passed, a new extended_datetime or datetime.datetime object.
+ Else a datetime.timedelta object.
+ """
+
+ if isinstance(other, timedelta):
+ return extended_datetime.from_y2k(self._y2k - other)
+
+ if isinstance(other, extended_datetime):
+ return self._y2k - other._y2k
+
+ if isinstance(other, datetime):
+ return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS)
+
+ return NotImplemented
+
+ def __rsub__(self, other):
+ return -(self - other)
+
+ @classmethod
+ def from_y2k(cls, value):
+ """
+ Revert substitution of year 2000.
+
+ :param value:
+ A datetime.datetime object which is 2000 years in the future.
+ :return:
+ A new extended_datetime or datetime.datetime object.
+ """
+
+ year = value.year - 2000
+
+ if year > 0:
+ new_cls = datetime
+ else:
+ new_cls = cls
+
+ return new_cls(
+ year,
+ value.month,
+ value.day,
+ value.hour,
+ value.minute,
+ value.second,
+ value.microsecond,
+ value.tzinfo
+ )
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/version.py b/contrib/python/asn1crypto/py2/asn1crypto/version.py
new file mode 100644
index 0000000000..966b57a5c0
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/version.py
@@ -0,0 +1,6 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+
+__version__ = '1.5.1'
+__version_info__ = (1, 5, 1)
diff --git a/contrib/python/asn1crypto/py2/asn1crypto/x509.py b/contrib/python/asn1crypto/py2/asn1crypto/x509.py
new file mode 100644
index 0000000000..8cfb2c78be
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/asn1crypto/x509.py
@@ -0,0 +1,3036 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for X.509 certificates. Exports the following items:
+
+ - Attributes()
+ - Certificate()
+ - Extensions()
+ - GeneralName()
+ - GeneralNames()
+ - Name()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from contextlib import contextmanager
+from encodings import idna # noqa
+import hashlib
+import re
+import socket
+import stringprep
+import sys
+import unicodedata
+
+from ._errors import unwrap
+from ._iri import iri_to_uri, uri_to_iri
+from ._ordereddict import OrderedDict
+from ._types import type_name, str_cls, bytes_to_list
+from .algos import AlgorithmIdentifier, AnyAlgorithmIdentifier, DigestAlgorithm, SignedDigestAlgorithm
+from .core import (
+ Any,
+ BitString,
+ BMPString,
+ Boolean,
+ Choice,
+ Concat,
+ Enumerated,
+ GeneralizedTime,
+ GeneralString,
+ IA5String,
+ Integer,
+ Null,
+ NumericString,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ PrintableString,
+ Sequence,
+ SequenceOf,
+ Set,
+ SetOf,
+ TeletexString,
+ UniversalString,
+ UTCTime,
+ UTF8String,
+ VisibleString,
+ VOID,
+)
+from .keys import PublicKeyInfo
+from .util import int_to_bytes, int_from_bytes, inet_ntop, inet_pton
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
+# and a few other supplementary sources, mostly due to extra supported
+# extension and name OIDs
+
+
+class DNSName(IA5String):
+
+ _encoding = 'idna'
+ _bad_tag = (12, 19)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.2
+
+ :param other:
+ Another DNSName object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, DNSName):
+ return False
+
+ return self.__unicode__().lower() == other.__unicode__().lower()
+
+ def set(self, value):
+ """
+ Sets the value of the DNS name
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value.startswith('.'):
+ encoded_value = b'.' + value[1:].encode(self._encoding)
+ else:
+ encoded_value = value.encode(self._encoding)
+
+ self._unicode = value
+ self.contents = encoded_value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class URI(IA5String):
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._unicode = value
+ self.contents = iri_to_uri(value)
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.4
+
+ :param other:
+ Another URI object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, URI):
+ return False
+
+ return iri_to_uri(self.native, True) == iri_to_uri(other.native, True)
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = uri_to_iri(self._merge_chunks())
+ return self._unicode
+
+
+class EmailAddress(IA5String):
+
+ _contents = None
+
+ # If the value has gone through the .set() method, thus normalizing it
+ _normalized = False
+
+ # In the wild we've seen this encoded as a UTF8String and PrintableString
+ _bad_tag = (12, 19)
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._normalized = False
+ self._contents = value
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value.find('@') != -1:
+ mailbox, hostname = value.rsplit('@', 1)
+ encoded_value = mailbox.encode('ascii') + b'@' + hostname.encode('idna')
+ else:
+ encoded_value = value.encode('ascii')
+
+ self._normalized = True
+ self._unicode = value
+ self.contents = encoded_value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ # We've seen this in the wild as a PrintableString, and since ascii is a
+ # subset of cp1252, we use the later for decoding to be more user friendly
+ if self._unicode is None:
+ contents = self._merge_chunks()
+ if contents.find(b'@') == -1:
+ self._unicode = contents.decode('cp1252')
+ else:
+ mailbox, hostname = contents.rsplit(b'@', 1)
+ self._unicode = mailbox.decode('cp1252') + '@' + hostname.decode('idna')
+ return self._unicode
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.5
+
+ :param other:
+ Another EmailAddress object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, EmailAddress):
+ return False
+
+ if not self._normalized:
+ self.set(self.native)
+ if not other._normalized:
+ other.set(other.native)
+
+ if self._contents.find(b'@') == -1 or other._contents.find(b'@') == -1:
+ return self._contents == other._contents
+
+ other_mailbox, other_hostname = other._contents.rsplit(b'@', 1)
+ mailbox, hostname = self._contents.rsplit(b'@', 1)
+
+ if mailbox != other_mailbox:
+ return False
+
+ if hostname.lower() != other_hostname.lower():
+ return False
+
+ return True
+
+
+class IPAddress(OctetString):
+ def parse(self, spec=None, spec_params=None):
+ """
+ This method is not applicable to IP addresses
+ """
+
+ raise ValueError(unwrap(
+ '''
+ IP address values can not be parsed
+ '''
+ ))
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string containing an IPv4 address, IPv4 address with CIDR,
+ an IPv6 address or IPv6 address with CIDR
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ original_value = value
+
+ has_cidr = value.find('/') != -1
+ cidr = 0
+ if has_cidr:
+ parts = value.split('/', 1)
+ value = parts[0]
+ cidr = int(parts[1])
+ if cidr < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range less than 0
+ ''',
+ type_name(self)
+ ))
+
+ if value.find(':') != -1:
+ family = socket.AF_INET6
+ if cidr > 128:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range bigger than 128, the maximum
+ value for an IPv6 address
+ ''',
+ type_name(self)
+ ))
+ cidr_size = 128
+ else:
+ family = socket.AF_INET
+ if cidr > 32:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range bigger than 32, the maximum
+ value for an IPv4 address
+ ''',
+ type_name(self)
+ ))
+ cidr_size = 32
+
+ cidr_bytes = b''
+ if has_cidr:
+ cidr_mask = '1' * cidr
+ cidr_mask += '0' * (cidr_size - len(cidr_mask))
+ cidr_bytes = int_to_bytes(int(cidr_mask, 2))
+ cidr_bytes = (b'\x00' * ((cidr_size // 8) - len(cidr_bytes))) + cidr_bytes
+
+ self._native = original_value
+ self.contents = inet_pton(family, value) + cidr_bytes
+ self._bytes = self.contents
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ byte_string = self.__bytes__()
+ byte_len = len(byte_string)
+ value = None
+ cidr_int = None
+ if byte_len in set([32, 16]):
+ value = inet_ntop(socket.AF_INET6, byte_string[0:16])
+ if byte_len > 16:
+ cidr_int = int_from_bytes(byte_string[16:])
+ elif byte_len in set([8, 4]):
+ value = inet_ntop(socket.AF_INET, byte_string[0:4])
+ if byte_len > 4:
+ cidr_int = int_from_bytes(byte_string[4:])
+ if cidr_int is not None:
+ cidr_bits = '{0:b}'.format(cidr_int)
+ cidr = len(cidr_bits.rstrip('0'))
+ value = value + '/' + str_cls(cidr)
+ self._native = value
+ return self._native
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ :param other:
+ Another IPAddress object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, IPAddress):
+ return False
+
+ return self.__bytes__() == other.__bytes__()
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class Attributes(SequenceOf):
+ _child_spec = Attribute
+
+
+class KeyUsage(BitString):
+ _map = {
+ 0: 'digital_signature',
+ 1: 'non_repudiation',
+ 2: 'key_encipherment',
+ 3: 'data_encipherment',
+ 4: 'key_agreement',
+ 5: 'key_cert_sign',
+ 6: 'crl_sign',
+ 7: 'encipher_only',
+ 8: 'decipher_only',
+ }
+
+
+class PrivateKeyUsagePeriod(Sequence):
+ _fields = [
+ ('not_before', GeneralizedTime, {'implicit': 0, 'optional': True}),
+ ('not_after', GeneralizedTime, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class NotReallyTeletexString(TeletexString):
+ """
+ OpenSSL (and probably some other libraries) puts ISO-8859-1
+ into TeletexString instead of ITU T.61. We use Windows-1252 when
+ decoding since it is a superset of ISO-8859-1, and less likely to
+ cause encoding issues, but we stay strict with encoding to prevent
+ us from creating bad data.
+ """
+
+ _decoding_encoding = 'cp1252'
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = self._merge_chunks().decode(self._decoding_encoding)
+ return self._unicode
+
+
+@contextmanager
+def strict_teletex():
+ try:
+ NotReallyTeletexString._decoding_encoding = 'teletex'
+ yield
+ finally:
+ NotReallyTeletexString._decoding_encoding = 'cp1252'
+
+
+class DirectoryString(Choice):
+ _alternatives = [
+ ('teletex_string', NotReallyTeletexString),
+ ('printable_string', PrintableString),
+ ('universal_string', UniversalString),
+ ('utf8_string', UTF8String),
+ ('bmp_string', BMPString),
+ # This is an invalid/bad alternative, but some broken certs use it
+ ('ia5_string', IA5String),
+ ]
+
+
+class NameType(ObjectIdentifier):
+ _map = {
+ '2.5.4.3': 'common_name',
+ '2.5.4.4': 'surname',
+ '2.5.4.5': 'serial_number',
+ '2.5.4.6': 'country_name',
+ '2.5.4.7': 'locality_name',
+ '2.5.4.8': 'state_or_province_name',
+ '2.5.4.9': 'street_address',
+ '2.5.4.10': 'organization_name',
+ '2.5.4.11': 'organizational_unit_name',
+ '2.5.4.12': 'title',
+ '2.5.4.15': 'business_category',
+ '2.5.4.17': 'postal_code',
+ '2.5.4.20': 'telephone_number',
+ '2.5.4.41': 'name',
+ '2.5.4.42': 'given_name',
+ '2.5.4.43': 'initials',
+ '2.5.4.44': 'generation_qualifier',
+ '2.5.4.45': 'unique_identifier',
+ '2.5.4.46': 'dn_qualifier',
+ '2.5.4.65': 'pseudonym',
+ '2.5.4.97': 'organization_identifier',
+ # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
+ '2.23.133.2.1': 'tpm_manufacturer',
+ '2.23.133.2.2': 'tpm_model',
+ '2.23.133.2.3': 'tpm_version',
+ '2.23.133.2.4': 'platform_manufacturer',
+ '2.23.133.2.5': 'platform_model',
+ '2.23.133.2.6': 'platform_version',
+ # https://tools.ietf.org/html/rfc2985#page-26
+ '1.2.840.113549.1.9.1': 'email_address',
+ # Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
+ '1.3.6.1.4.1.311.60.2.1.1': 'incorporation_locality',
+ '1.3.6.1.4.1.311.60.2.1.2': 'incorporation_state_or_province',
+ '1.3.6.1.4.1.311.60.2.1.3': 'incorporation_country',
+ # https://tools.ietf.org/html/rfc4519#section-2.39
+ '0.9.2342.19200300.100.1.1': 'user_id',
+ # https://tools.ietf.org/html/rfc2247#section-4
+ '0.9.2342.19200300.100.1.25': 'domain_component',
+ # http://www.alvestrand.no/objectid/0.2.262.1.10.7.20.html
+ '0.2.262.1.10.7.20': 'name_distinguisher',
+ }
+
+ # This order is largely based on observed order seen in EV certs from
+ # Symantec and DigiCert. Some of the uncommon name-related fields are
+ # just placed in what seems like a reasonable order.
+ preferred_order = [
+ 'incorporation_country',
+ 'incorporation_state_or_province',
+ 'incorporation_locality',
+ 'business_category',
+ 'serial_number',
+ 'country_name',
+ 'postal_code',
+ 'state_or_province_name',
+ 'locality_name',
+ 'street_address',
+ 'organization_name',
+ 'organizational_unit_name',
+ 'title',
+ 'common_name',
+ 'user_id',
+ 'initials',
+ 'generation_qualifier',
+ 'surname',
+ 'given_name',
+ 'name',
+ 'pseudonym',
+ 'dn_qualifier',
+ 'telephone_number',
+ 'email_address',
+ 'domain_component',
+ 'name_distinguisher',
+ 'organization_identifier',
+ 'tpm_manufacturer',
+ 'tpm_model',
+ 'tpm_version',
+ 'platform_manufacturer',
+ 'platform_model',
+ 'platform_version',
+ ]
+
+ @classmethod
+ def preferred_ordinal(cls, attr_name):
+ """
+ Returns an ordering value for a particular attribute key.
+
+ Unrecognized attributes and OIDs will be sorted lexically at the end.
+
+ :return:
+ An orderable value.
+
+ """
+
+ attr_name = cls.map(attr_name)
+ if attr_name in cls.preferred_order:
+ ordinal = cls.preferred_order.index(attr_name)
+ else:
+ ordinal = len(cls.preferred_order)
+
+ return (ordinal, attr_name)
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A human-friendly unicode string to display to users
+ """
+
+ return {
+ 'common_name': 'Common Name',
+ 'surname': 'Surname',
+ 'serial_number': 'Serial Number',
+ 'country_name': 'Country',
+ 'locality_name': 'Locality',
+ 'state_or_province_name': 'State/Province',
+ 'street_address': 'Street Address',
+ 'organization_name': 'Organization',
+ 'organizational_unit_name': 'Organizational Unit',
+ 'title': 'Title',
+ 'business_category': 'Business Category',
+ 'postal_code': 'Postal Code',
+ 'telephone_number': 'Telephone Number',
+ 'name': 'Name',
+ 'given_name': 'Given Name',
+ 'initials': 'Initials',
+ 'generation_qualifier': 'Generation Qualifier',
+ 'unique_identifier': 'Unique Identifier',
+ 'dn_qualifier': 'DN Qualifier',
+ 'pseudonym': 'Pseudonym',
+ 'email_address': 'Email Address',
+ 'incorporation_locality': 'Incorporation Locality',
+ 'incorporation_state_or_province': 'Incorporation State/Province',
+ 'incorporation_country': 'Incorporation Country',
+ 'domain_component': 'Domain Component',
+ 'name_distinguisher': 'Name Distinguisher',
+ 'organization_identifier': 'Organization Identifier',
+ 'tpm_manufacturer': 'TPM Manufacturer',
+ 'tpm_model': 'TPM Model',
+ 'tpm_version': 'TPM Version',
+ 'platform_manufacturer': 'Platform Manufacturer',
+ 'platform_model': 'Platform Model',
+ 'platform_version': 'Platform Version',
+ 'user_id': 'User ID',
+ }.get(self.native, self.native)
+
+
+class NameTypeAndValue(Sequence):
+ _fields = [
+ ('type', NameType),
+ ('value', Any),
+ ]
+
+ _oid_pair = ('type', 'value')
+ _oid_specs = {
+ 'common_name': DirectoryString,
+ 'surname': DirectoryString,
+ 'serial_number': DirectoryString,
+ 'country_name': DirectoryString,
+ 'locality_name': DirectoryString,
+ 'state_or_province_name': DirectoryString,
+ 'street_address': DirectoryString,
+ 'organization_name': DirectoryString,
+ 'organizational_unit_name': DirectoryString,
+ 'title': DirectoryString,
+ 'business_category': DirectoryString,
+ 'postal_code': DirectoryString,
+ 'telephone_number': PrintableString,
+ 'name': DirectoryString,
+ 'given_name': DirectoryString,
+ 'initials': DirectoryString,
+ 'generation_qualifier': DirectoryString,
+ 'unique_identifier': OctetBitString,
+ 'dn_qualifier': DirectoryString,
+ 'pseudonym': DirectoryString,
+ # https://tools.ietf.org/html/rfc2985#page-26
+ 'email_address': EmailAddress,
+ # Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
+ 'incorporation_locality': DirectoryString,
+ 'incorporation_state_or_province': DirectoryString,
+ 'incorporation_country': DirectoryString,
+ 'domain_component': DNSName,
+ 'name_distinguisher': DirectoryString,
+ 'organization_identifier': DirectoryString,
+ 'tpm_manufacturer': UTF8String,
+ 'tpm_model': UTF8String,
+ 'tpm_version': UTF8String,
+ 'platform_manufacturer': UTF8String,
+ 'platform_model': UTF8String,
+ 'platform_version': UTF8String,
+ 'user_id': DirectoryString,
+ }
+
+ _prepped = None
+
+ @property
+ def prepped_value(self):
+ """
+ Returns the value after being processed by the internationalized string
+ preparation as specified by RFC 5280
+
+ :return:
+ A unicode string
+ """
+
+ if self._prepped is None:
+ self._prepped = self._ldap_string_prep(self['value'].native)
+ return self._prepped
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another NameTypeAndValue object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, NameTypeAndValue):
+ return False
+
+ if other['type'].native != self['type'].native:
+ return False
+
+ return other.prepped_value == self.prepped_value
+
+ def _ldap_string_prep(self, string):
+ """
+ Implements the internationalized string preparation algorithm from
+ RFC 4518. https://tools.ietf.org/html/rfc4518#section-2
+
+ :param string:
+ A unicode string to prepare
+
+ :return:
+ A prepared unicode string, ready for comparison
+ """
+
+ # Map step
+ string = re.sub('[\u00ad\u1806\u034f\u180b-\u180d\ufe0f-\uff00\ufffc]+', '', string)
+ string = re.sub('[\u0009\u000a\u000b\u000c\u000d\u0085]', ' ', string)
+ if sys.maxunicode == 0xffff:
+ # Some installs of Python 2.7 don't support 8-digit unicode escape
+ # ranges, so we have to break them into pieces
+ # Original was: \U0001D173-\U0001D17A and \U000E0020-\U000E007F
+ string = re.sub('\ud834[\udd73-\udd7a]|\udb40[\udc20-\udc7f]|\U000e0001', '', string)
+ else:
+ string = re.sub('[\U0001D173-\U0001D17A\U000E0020-\U000E007F\U000e0001]', '', string)
+ string = re.sub(
+ '[\u0000-\u0008\u000e-\u001f\u007f-\u0084\u0086-\u009f\u06dd\u070f\u180e\u200c-\u200f'
+ '\u202a-\u202e\u2060-\u2063\u206a-\u206f\ufeff\ufff9-\ufffb]+',
+ '',
+ string
+ )
+ string = string.replace('\u200b', '')
+ string = re.sub('[\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000]', ' ', string)
+
+ string = ''.join(map(stringprep.map_table_b2, string))
+
+ # Normalize step
+ string = unicodedata.normalize('NFKC', string)
+
+ # Prohibit step
+ for char in string:
+ if stringprep.in_table_a1(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain unassigned code points
+ '''
+ ))
+
+ if stringprep.in_table_c8(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain change display or
+ zzzzdeprecated characters
+ '''
+ ))
+
+ if stringprep.in_table_c3(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain private use characters
+ '''
+ ))
+
+ if stringprep.in_table_c4(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain non-character code points
+ '''
+ ))
+
+ if stringprep.in_table_c5(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain surrogate code points
+ '''
+ ))
+
+ if char == '\ufffd':
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain the replacement character
+ '''
+ ))
+
+ # Check bidirectional step - here we ensure that we are not mixing
+ # left-to-right and right-to-left text in the string
+ has_r_and_al_cat = False
+ has_l_cat = False
+ for char in string:
+ if stringprep.in_table_d1(char):
+ has_r_and_al_cat = True
+ elif stringprep.in_table_d2(char):
+ has_l_cat = True
+
+ if has_r_and_al_cat:
+ first_is_r_and_al = stringprep.in_table_d1(string[0])
+ last_is_r_and_al = stringprep.in_table_d1(string[-1])
+
+ if has_l_cat or not first_is_r_and_al or not last_is_r_and_al:
+ raise ValueError(unwrap(
+ '''
+ X.509 Name object contains a malformed bidirectional
+ sequence
+ '''
+ ))
+
+ # Insignificant space handling step
+ string = ' ' + re.sub(' +', ' ', string).strip() + ' '
+
+ return string
+
+
+class RelativeDistinguishedName(SetOf):
+ _child_spec = NameTypeAndValue
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ output = []
+ values = self._get_values(self)
+ for key in sorted(values.keys()):
+ output.append('%s: %s' % (key, values[key]))
+ # Unit separator is used here since the normalization process for
+ # values moves any such character, and the keys are all dotted integers
+ # or under_score_words
+ return '\x1F'.join(output)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another RelativeDistinguishedName object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, RelativeDistinguishedName):
+ return False
+
+ if len(self) != len(other):
+ return False
+
+ self_types = self._get_types(self)
+ other_types = self._get_types(other)
+
+ if self_types != other_types:
+ return False
+
+ self_values = self._get_values(self)
+ other_values = self._get_values(other)
+
+ for type_name_ in self_types:
+ if self_values[type_name_] != other_values[type_name_]:
+ return False
+
+ return True
+
+ def _get_types(self, rdn):
+ """
+ Returns a set of types contained in an RDN
+
+ :param rdn:
+ A RelativeDistinguishedName object
+
+ :return:
+ A set object with unicode strings of NameTypeAndValue type field
+ values
+ """
+
+ return set([ntv['type'].native for ntv in rdn])
+
+ def _get_values(self, rdn):
+ """
+ Returns a dict of prepped values contained in an RDN
+
+ :param rdn:
+ A RelativeDistinguishedName object
+
+ :return:
+ A dict object with unicode strings of NameTypeAndValue value field
+ values that have been prepped for comparison
+ """
+
+ output = {}
+ [output.update([(ntv['type'].native, ntv.prepped_value)]) for ntv in rdn]
+ return output
+
+
+class RDNSequence(SequenceOf):
+ _child_spec = RelativeDistinguishedName
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ # Record separator is used here since the normalization process for
+ # values moves any such character, and the keys are all dotted integers
+ # or under_score_words
+ return '\x1E'.join(rdn.hashable for rdn in self)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another RDNSequence object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, RDNSequence):
+ return False
+
+ if len(self) != len(other):
+ return False
+
+ for index, self_rdn in enumerate(self):
+ if other[index] != self_rdn:
+ return False
+
+ return True
+
+
+class Name(Choice):
+ _alternatives = [
+ ('', RDNSequence),
+ ]
+
+ _human_friendly = None
+ _sha1 = None
+ _sha256 = None
+
+ @classmethod
+ def build(cls, name_dict, use_printable=False):
+ """
+ Creates a Name object from a dict of unicode string keys and values.
+ The keys should be from NameType._map, or a dotted-integer OID unicode
+ string.
+
+ :param name_dict:
+ A dict of name information, e.g. {"common_name": "Will Bond",
+ "country_name": "US", "organization_name": "Codex Non Sufficit LC"}
+
+ :param use_printable:
+ A bool - if PrintableString should be used for encoding instead of
+ UTF8String. This is for backwards compatibility with old software.
+
+ :return:
+ An x509.Name object
+ """
+
+ rdns = []
+ if not use_printable:
+ encoding_name = 'utf8_string'
+ encoding_class = UTF8String
+ else:
+ encoding_name = 'printable_string'
+ encoding_class = PrintableString
+
+ # Sort the attributes according to NameType.preferred_order
+ name_dict = OrderedDict(
+ sorted(
+ name_dict.items(),
+ key=lambda item: NameType.preferred_ordinal(item[0])
+ )
+ )
+
+ for attribute_name, attribute_value in name_dict.items():
+ attribute_name = NameType.map(attribute_name)
+ if attribute_name == 'email_address':
+ value = EmailAddress(attribute_value)
+ elif attribute_name == 'domain_component':
+ value = DNSName(attribute_value)
+ elif attribute_name in set(['dn_qualifier', 'country_name', 'serial_number']):
+ value = DirectoryString(
+ name='printable_string',
+ value=PrintableString(attribute_value)
+ )
+ else:
+ value = DirectoryString(
+ name=encoding_name,
+ value=encoding_class(attribute_value)
+ )
+
+ rdns.append(RelativeDistinguishedName([
+ NameTypeAndValue({
+ 'type': attribute_name,
+ 'value': value
+ })
+ ]))
+
+ return cls(name='', value=RDNSequence(rdns))
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ return self.chosen.hashable
+
+ def __len__(self):
+ return len(self.chosen)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another Name object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, Name):
+ return False
+ return self.chosen == other.chosen
+
+ @property
+ def native(self):
+ if self._native is None:
+ self._native = OrderedDict()
+ for rdn in self.chosen.native:
+ for type_val in rdn:
+ field_name = type_val['type']
+ if field_name in self._native:
+ existing = self._native[field_name]
+ if not isinstance(existing, list):
+ existing = self._native[field_name] = [existing]
+ existing.append(type_val['value'])
+ else:
+ self._native[field_name] = type_val['value']
+ return self._native
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A human-friendly unicode string containing the parts of the name
+ """
+
+ if self._human_friendly is None:
+ data = OrderedDict()
+ last_field = None
+ for rdn in self.chosen:
+ for type_val in rdn:
+ field_name = type_val['type'].human_friendly
+ last_field = field_name
+ if field_name in data:
+ data[field_name] = [data[field_name]]
+ data[field_name].append(type_val['value'])
+ else:
+ data[field_name] = type_val['value']
+ to_join = []
+ keys = data.keys()
+ if last_field == 'Country':
+ keys = reversed(list(keys))
+ for key in keys:
+ value = data[key]
+ native_value = self._recursive_humanize(value)
+ to_join.append('%s: %s' % (key, native_value))
+
+ has_comma = False
+ for element in to_join:
+ if element.find(',') != -1:
+ has_comma = True
+ break
+
+ separator = ', ' if not has_comma else '; '
+ self._human_friendly = separator.join(to_join[::-1])
+
+ return self._human_friendly
+
+ def _recursive_humanize(self, value):
+ """
+ Recursively serializes data compiled from the RDNSequence
+
+ :param value:
+ An Asn1Value object, or a list of Asn1Value objects
+
+ :return:
+ A unicode string
+ """
+
+ if isinstance(value, list):
+ return ', '.join(
+ reversed([self._recursive_humanize(sub_value) for sub_value in value])
+ )
+ return value.native
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this name
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this name
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
+
+
+class AnotherName(Sequence):
+ _fields = [
+ ('type_id', ObjectIdentifier),
+ ('value', Any, {'explicit': 0}),
+ ]
+
+
+class CountryName(Choice):
+ class_ = 1
+ tag = 1
+
+ _alternatives = [
+ ('x121_dcc_code', NumericString),
+ ('iso_3166_alpha2_code', PrintableString),
+ ]
+
+
+class AdministrationDomainName(Choice):
+ class_ = 1
+ tag = 2
+
+ _alternatives = [
+ ('numeric', NumericString),
+ ('printable', PrintableString),
+ ]
+
+
+class PrivateDomainName(Choice):
+ _alternatives = [
+ ('numeric', NumericString),
+ ('printable', PrintableString),
+ ]
+
+
+class PersonalName(Set):
+ _fields = [
+ ('surname', PrintableString, {'implicit': 0}),
+ ('given_name', PrintableString, {'implicit': 1, 'optional': True}),
+ ('initials', PrintableString, {'implicit': 2, 'optional': True}),
+ ('generation_qualifier', PrintableString, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class TeletexPersonalName(Set):
+ _fields = [
+ ('surname', TeletexString, {'implicit': 0}),
+ ('given_name', TeletexString, {'implicit': 1, 'optional': True}),
+ ('initials', TeletexString, {'implicit': 2, 'optional': True}),
+ ('generation_qualifier', TeletexString, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class OrganizationalUnitNames(SequenceOf):
+ _child_spec = PrintableString
+
+
+class TeletexOrganizationalUnitNames(SequenceOf):
+ _child_spec = TeletexString
+
+
+class BuiltInStandardAttributes(Sequence):
+ _fields = [
+ ('country_name', CountryName, {'optional': True}),
+ ('administration_domain_name', AdministrationDomainName, {'optional': True}),
+ ('network_address', NumericString, {'implicit': 0, 'optional': True}),
+ ('terminal_identifier', PrintableString, {'implicit': 1, 'optional': True}),
+ ('private_domain_name', PrivateDomainName, {'explicit': 2, 'optional': True}),
+ ('organization_name', PrintableString, {'implicit': 3, 'optional': True}),
+ ('numeric_user_identifier', NumericString, {'implicit': 4, 'optional': True}),
+ ('personal_name', PersonalName, {'implicit': 5, 'optional': True}),
+ ('organizational_unit_names', OrganizationalUnitNames, {'implicit': 6, 'optional': True}),
+ ]
+
+
+class BuiltInDomainDefinedAttribute(Sequence):
+ _fields = [
+ ('type', PrintableString),
+ ('value', PrintableString),
+ ]
+
+
+class BuiltInDomainDefinedAttributes(SequenceOf):
+ _child_spec = BuiltInDomainDefinedAttribute
+
+
+class TeletexDomainDefinedAttribute(Sequence):
+ _fields = [
+ ('type', TeletexString),
+ ('value', TeletexString),
+ ]
+
+
+class TeletexDomainDefinedAttributes(SequenceOf):
+ _child_spec = TeletexDomainDefinedAttribute
+
+
+class PhysicalDeliveryCountryName(Choice):
+ _alternatives = [
+ ('x121_dcc_code', NumericString),
+ ('iso_3166_alpha2_code', PrintableString),
+ ]
+
+
+class PostalCode(Choice):
+ _alternatives = [
+ ('numeric_code', NumericString),
+ ('printable_code', PrintableString),
+ ]
+
+
+class PDSParameter(Set):
+ _fields = [
+ ('printable_string', PrintableString, {'optional': True}),
+ ('teletex_string', TeletexString, {'optional': True}),
+ ]
+
+
+class PrintableAddress(SequenceOf):
+ _child_spec = PrintableString
+
+
+class UnformattedPostalAddress(Set):
+ _fields = [
+ ('printable_address', PrintableAddress, {'optional': True}),
+ ('teletex_string', TeletexString, {'optional': True}),
+ ]
+
+
+class E1634Address(Sequence):
+ _fields = [
+ ('number', NumericString, {'implicit': 0}),
+ ('sub_address', NumericString, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class NAddresses(SetOf):
+ _child_spec = OctetString
+
+
+class PresentationAddress(Sequence):
+ _fields = [
+ ('p_selector', OctetString, {'explicit': 0, 'optional': True}),
+ ('s_selector', OctetString, {'explicit': 1, 'optional': True}),
+ ('t_selector', OctetString, {'explicit': 2, 'optional': True}),
+ ('n_addresses', NAddresses, {'explicit': 3}),
+ ]
+
+
+class ExtendedNetworkAddress(Choice):
+ _alternatives = [
+ ('e163_4_address', E1634Address),
+ ('psap_address', PresentationAddress, {'implicit': 0})
+ ]
+
+
+class TerminalType(Integer):
+ _map = {
+ 3: 'telex',
+ 4: 'teletex',
+ 5: 'g3_facsimile',
+ 6: 'g4_facsimile',
+ 7: 'ia5_terminal',
+ 8: 'videotex',
+ }
+
+
+class ExtensionAttributeType(Integer):
+ _map = {
+ 1: 'common_name',
+ 2: 'teletex_common_name',
+ 3: 'teletex_organization_name',
+ 4: 'teletex_personal_name',
+ 5: 'teletex_organization_unit_names',
+ 6: 'teletex_domain_defined_attributes',
+ 7: 'pds_name',
+ 8: 'physical_delivery_country_name',
+ 9: 'postal_code',
+ 10: 'physical_delivery_office_name',
+ 11: 'physical_delivery_office_number',
+ 12: 'extension_of_address_components',
+ 13: 'physical_delivery_personal_name',
+ 14: 'physical_delivery_organization_name',
+ 15: 'extension_physical_delivery_address_components',
+ 16: 'unformatted_postal_address',
+ 17: 'street_address',
+ 18: 'post_office_box_address',
+ 19: 'poste_restante_address',
+ 20: 'unique_postal_name',
+ 21: 'local_postal_attributes',
+ 22: 'extended_network_address',
+ 23: 'terminal_type',
+ }
+
+
+class ExtensionAttribute(Sequence):
+ _fields = [
+ ('extension_attribute_type', ExtensionAttributeType, {'implicit': 0}),
+ ('extension_attribute_value', Any, {'explicit': 1}),
+ ]
+
+ _oid_pair = ('extension_attribute_type', 'extension_attribute_value')
+ _oid_specs = {
+ 'common_name': PrintableString,
+ 'teletex_common_name': TeletexString,
+ 'teletex_organization_name': TeletexString,
+ 'teletex_personal_name': TeletexPersonalName,
+ 'teletex_organization_unit_names': TeletexOrganizationalUnitNames,
+ 'teletex_domain_defined_attributes': TeletexDomainDefinedAttributes,
+ 'pds_name': PrintableString,
+ 'physical_delivery_country_name': PhysicalDeliveryCountryName,
+ 'postal_code': PostalCode,
+ 'physical_delivery_office_name': PDSParameter,
+ 'physical_delivery_office_number': PDSParameter,
+ 'extension_of_address_components': PDSParameter,
+ 'physical_delivery_personal_name': PDSParameter,
+ 'physical_delivery_organization_name': PDSParameter,
+ 'extension_physical_delivery_address_components': PDSParameter,
+ 'unformatted_postal_address': UnformattedPostalAddress,
+ 'street_address': PDSParameter,
+ 'post_office_box_address': PDSParameter,
+ 'poste_restante_address': PDSParameter,
+ 'unique_postal_name': PDSParameter,
+ 'local_postal_attributes': PDSParameter,
+ 'extended_network_address': ExtendedNetworkAddress,
+ 'terminal_type': TerminalType,
+ }
+
+
+class ExtensionAttributes(SequenceOf):
+ _child_spec = ExtensionAttribute
+
+
+class ORAddress(Sequence):
+ _fields = [
+ ('built_in_standard_attributes', BuiltInStandardAttributes),
+ ('built_in_domain_defined_attributes', BuiltInDomainDefinedAttributes, {'optional': True}),
+ ('extension_attributes', ExtensionAttributes, {'optional': True}),
+ ]
+
+
+class EDIPartyName(Sequence):
+ _fields = [
+ ('name_assigner', DirectoryString, {'implicit': 0, 'optional': True}),
+ ('party_name', DirectoryString, {'implicit': 1}),
+ ]
+
+
+class GeneralName(Choice):
+ _alternatives = [
+ ('other_name', AnotherName, {'implicit': 0}),
+ ('rfc822_name', EmailAddress, {'implicit': 1}),
+ ('dns_name', DNSName, {'implicit': 2}),
+ ('x400_address', ORAddress, {'implicit': 3}),
+ ('directory_name', Name, {'explicit': 4}),
+ ('edi_party_name', EDIPartyName, {'implicit': 5}),
+ ('uniform_resource_identifier', URI, {'implicit': 6}),
+ ('ip_address', IPAddress, {'implicit': 7}),
+ ('registered_id', ObjectIdentifier, {'implicit': 8}),
+ ]
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Does not support other_name, x400_address or edi_party_name
+
+ :param other:
+ The other GeneralName to compare to
+
+ :return:
+ A boolean
+ """
+
+ if self.name in ('other_name', 'x400_address', 'edi_party_name'):
+ raise ValueError(unwrap(
+ '''
+ Comparison is not supported for GeneralName objects of
+ choice %s
+ ''',
+ self.name
+ ))
+
+ if other.name in ('other_name', 'x400_address', 'edi_party_name'):
+ raise ValueError(unwrap(
+ '''
+ Comparison is not supported for GeneralName objects of choice
+ %s''',
+ other.name
+ ))
+
+ if self.name != other.name:
+ return False
+
+ return self.chosen == other.chosen
+
+
+class GeneralNames(SequenceOf):
+ _child_spec = GeneralName
+
+
+class Time(Choice):
+ _alternatives = [
+ ('utc_time', UTCTime),
+ ('general_time', GeneralizedTime),
+ ]
+
+
+class Validity(Sequence):
+ _fields = [
+ ('not_before', Time),
+ ('not_after', Time),
+ ]
+
+
+class BasicConstraints(Sequence):
+ _fields = [
+ ('ca', Boolean, {'default': False}),
+ ('path_len_constraint', Integer, {'optional': True}),
+ ]
+
+
+class AuthorityKeyIdentifier(Sequence):
+ _fields = [
+ ('key_identifier', OctetString, {'implicit': 0, 'optional': True}),
+ ('authority_cert_issuer', GeneralNames, {'implicit': 1, 'optional': True}),
+ ('authority_cert_serial_number', Integer, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class DistributionPointName(Choice):
+ _alternatives = [
+ ('full_name', GeneralNames, {'implicit': 0}),
+ ('name_relative_to_crl_issuer', RelativeDistinguishedName, {'implicit': 1}),
+ ]
+
+
+class ReasonFlags(BitString):
+ _map = {
+ 0: 'unused',
+ 1: 'key_compromise',
+ 2: 'ca_compromise',
+ 3: 'affiliation_changed',
+ 4: 'superseded',
+ 5: 'cessation_of_operation',
+ 6: 'certificate_hold',
+ 7: 'privilege_withdrawn',
+ 8: 'aa_compromise',
+ }
+
+
+class GeneralSubtree(Sequence):
+ _fields = [
+ ('base', GeneralName),
+ ('minimum', Integer, {'implicit': 0, 'default': 0}),
+ ('maximum', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class GeneralSubtrees(SequenceOf):
+ _child_spec = GeneralSubtree
+
+
+class NameConstraints(Sequence):
+ _fields = [
+ ('permitted_subtrees', GeneralSubtrees, {'implicit': 0, 'optional': True}),
+ ('excluded_subtrees', GeneralSubtrees, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class DistributionPoint(Sequence):
+ _fields = [
+ ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
+ ('reasons', ReasonFlags, {'implicit': 1, 'optional': True}),
+ ('crl_issuer', GeneralNames, {'implicit': 2, 'optional': True}),
+ ]
+
+ _url = False
+
+ @property
+ def url(self):
+ """
+ :return:
+ None or a unicode string of the distribution point's URL
+ """
+
+ if self._url is False:
+ self._url = None
+ name = self['distribution_point']
+ if name.name != 'full_name':
+ raise ValueError(unwrap(
+ '''
+ CRL distribution points that are relative to the issuer are
+ not supported
+ '''
+ ))
+
+ for general_name in name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ url = general_name.native
+ if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')):
+ self._url = url
+ break
+
+ return self._url
+
+
+class CRLDistributionPoints(SequenceOf):
+ _child_spec = DistributionPoint
+
+
+class DisplayText(Choice):
+ _alternatives = [
+ ('ia5_string', IA5String),
+ ('visible_string', VisibleString),
+ ('bmp_string', BMPString),
+ ('utf8_string', UTF8String),
+ ]
+
+
+class NoticeNumbers(SequenceOf):
+ _child_spec = Integer
+
+
+class NoticeReference(Sequence):
+ _fields = [
+ ('organization', DisplayText),
+ ('notice_numbers', NoticeNumbers),
+ ]
+
+
+class UserNotice(Sequence):
+ _fields = [
+ ('notice_ref', NoticeReference, {'optional': True}),
+ ('explicit_text', DisplayText, {'optional': True}),
+ ]
+
+
+class PolicyQualifierId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.2.1': 'certification_practice_statement',
+ '1.3.6.1.5.5.7.2.2': 'user_notice',
+ }
+
+
+class PolicyQualifierInfo(Sequence):
+ _fields = [
+ ('policy_qualifier_id', PolicyQualifierId),
+ ('qualifier', Any),
+ ]
+
+ _oid_pair = ('policy_qualifier_id', 'qualifier')
+ _oid_specs = {
+ 'certification_practice_statement': IA5String,
+ 'user_notice': UserNotice,
+ }
+
+
+class PolicyQualifierInfos(SequenceOf):
+ _child_spec = PolicyQualifierInfo
+
+
+class PolicyIdentifier(ObjectIdentifier):
+ _map = {
+ '2.5.29.32.0': 'any_policy',
+ }
+
+
+class PolicyInformation(Sequence):
+ _fields = [
+ ('policy_identifier', PolicyIdentifier),
+ ('policy_qualifiers', PolicyQualifierInfos, {'optional': True})
+ ]
+
+
+class CertificatePolicies(SequenceOf):
+ _child_spec = PolicyInformation
+
+
+class PolicyMapping(Sequence):
+ _fields = [
+ ('issuer_domain_policy', PolicyIdentifier),
+ ('subject_domain_policy', PolicyIdentifier),
+ ]
+
+
+class PolicyMappings(SequenceOf):
+ _child_spec = PolicyMapping
+
+
+class PolicyConstraints(Sequence):
+ _fields = [
+ ('require_explicit_policy', Integer, {'implicit': 0, 'optional': True}),
+ ('inhibit_policy_mapping', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class KeyPurposeId(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc5280#page-45
+ '2.5.29.37.0': 'any_extended_key_usage',
+ '1.3.6.1.5.5.7.3.1': 'server_auth',
+ '1.3.6.1.5.5.7.3.2': 'client_auth',
+ '1.3.6.1.5.5.7.3.3': 'code_signing',
+ '1.3.6.1.5.5.7.3.4': 'email_protection',
+ '1.3.6.1.5.5.7.3.5': 'ipsec_end_system',
+ '1.3.6.1.5.5.7.3.6': 'ipsec_tunnel',
+ '1.3.6.1.5.5.7.3.7': 'ipsec_user',
+ '1.3.6.1.5.5.7.3.8': 'time_stamping',
+ '1.3.6.1.5.5.7.3.9': 'ocsp_signing',
+ # http://tools.ietf.org/html/rfc3029.html#page-9
+ '1.3.6.1.5.5.7.3.10': 'dvcs',
+ # http://tools.ietf.org/html/rfc6268.html#page-16
+ '1.3.6.1.5.5.7.3.13': 'eap_over_ppp',
+ '1.3.6.1.5.5.7.3.14': 'eap_over_lan',
+ # https://tools.ietf.org/html/rfc5055#page-76
+ '1.3.6.1.5.5.7.3.15': 'scvp_server',
+ '1.3.6.1.5.5.7.3.16': 'scvp_client',
+ # https://tools.ietf.org/html/rfc4945#page-31
+ '1.3.6.1.5.5.7.3.17': 'ipsec_ike',
+ # https://tools.ietf.org/html/rfc5415#page-38
+ '1.3.6.1.5.5.7.3.18': 'capwap_ac',
+ '1.3.6.1.5.5.7.3.19': 'capwap_wtp',
+ # https://tools.ietf.org/html/rfc5924#page-8
+ '1.3.6.1.5.5.7.3.20': 'sip_domain',
+ # https://tools.ietf.org/html/rfc6187#page-7
+ '1.3.6.1.5.5.7.3.21': 'secure_shell_client',
+ '1.3.6.1.5.5.7.3.22': 'secure_shell_server',
+ # https://tools.ietf.org/html/rfc6494#page-7
+ '1.3.6.1.5.5.7.3.23': 'send_router',
+ '1.3.6.1.5.5.7.3.24': 'send_proxied_router',
+ '1.3.6.1.5.5.7.3.25': 'send_owner',
+ '1.3.6.1.5.5.7.3.26': 'send_proxied_owner',
+ # https://tools.ietf.org/html/rfc6402#page-10
+ '1.3.6.1.5.5.7.3.27': 'cmc_ca',
+ '1.3.6.1.5.5.7.3.28': 'cmc_ra',
+ '1.3.6.1.5.5.7.3.29': 'cmc_archive',
+ # https://tools.ietf.org/html/draft-ietf-sidr-bgpsec-pki-profiles-15#page-6
+ '1.3.6.1.5.5.7.3.30': 'bgpspec_router',
+ # https://www.ietf.org/proceedings/44/I-D/draft-ietf-ipsec-pki-req-01.txt
+ '1.3.6.1.5.5.8.2.2': 'ike_intermediate',
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378132(v=vs.85).aspx
+ # and https://support.microsoft.com/en-us/kb/287547
+ '1.3.6.1.4.1.311.10.3.1': 'microsoft_trust_list_signing',
+ '1.3.6.1.4.1.311.10.3.2': 'microsoft_time_stamp_signing',
+ '1.3.6.1.4.1.311.10.3.3': 'microsoft_server_gated',
+ '1.3.6.1.4.1.311.10.3.3.1': 'microsoft_serialized',
+ '1.3.6.1.4.1.311.10.3.4': 'microsoft_efs',
+ '1.3.6.1.4.1.311.10.3.4.1': 'microsoft_efs_recovery',
+ '1.3.6.1.4.1.311.10.3.5': 'microsoft_whql',
+ '1.3.6.1.4.1.311.10.3.6': 'microsoft_nt5',
+ '1.3.6.1.4.1.311.10.3.7': 'microsoft_oem_whql',
+ '1.3.6.1.4.1.311.10.3.8': 'microsoft_embedded_nt',
+ '1.3.6.1.4.1.311.10.3.9': 'microsoft_root_list_signer',
+ '1.3.6.1.4.1.311.10.3.10': 'microsoft_qualified_subordination',
+ '1.3.6.1.4.1.311.10.3.11': 'microsoft_key_recovery',
+ '1.3.6.1.4.1.311.10.3.12': 'microsoft_document_signing',
+ '1.3.6.1.4.1.311.10.3.13': 'microsoft_lifetime_signing',
+ '1.3.6.1.4.1.311.10.3.14': 'microsoft_mobile_device_software',
+ # https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography
+ '1.3.6.1.4.1.311.20.2.2': 'microsoft_smart_card_logon',
+ # https://opensource.apple.com/source
+ # - /Security/Security-57031.40.6/Security/libsecurity_keychain/lib/SecPolicy.cpp
+ # - /libsecurity_cssm/libsecurity_cssm-36064/lib/oidsalg.c
+ '1.2.840.113635.100.1.2': 'apple_x509_basic',
+ '1.2.840.113635.100.1.3': 'apple_ssl',
+ '1.2.840.113635.100.1.4': 'apple_local_cert_gen',
+ '1.2.840.113635.100.1.5': 'apple_csr_gen',
+ '1.2.840.113635.100.1.6': 'apple_revocation_crl',
+ '1.2.840.113635.100.1.7': 'apple_revocation_ocsp',
+ '1.2.840.113635.100.1.8': 'apple_smime',
+ '1.2.840.113635.100.1.9': 'apple_eap',
+ '1.2.840.113635.100.1.10': 'apple_software_update_signing',
+ '1.2.840.113635.100.1.11': 'apple_ipsec',
+ '1.2.840.113635.100.1.12': 'apple_ichat',
+ '1.2.840.113635.100.1.13': 'apple_resource_signing',
+ '1.2.840.113635.100.1.14': 'apple_pkinit_client',
+ '1.2.840.113635.100.1.15': 'apple_pkinit_server',
+ '1.2.840.113635.100.1.16': 'apple_code_signing',
+ '1.2.840.113635.100.1.17': 'apple_package_signing',
+ '1.2.840.113635.100.1.18': 'apple_id_validation',
+ '1.2.840.113635.100.1.20': 'apple_time_stamping',
+ '1.2.840.113635.100.1.21': 'apple_revocation',
+ '1.2.840.113635.100.1.22': 'apple_passbook_signing',
+ '1.2.840.113635.100.1.23': 'apple_mobile_store',
+ '1.2.840.113635.100.1.24': 'apple_escrow_service',
+ '1.2.840.113635.100.1.25': 'apple_profile_signer',
+ '1.2.840.113635.100.1.26': 'apple_qa_profile_signer',
+ '1.2.840.113635.100.1.27': 'apple_test_mobile_store',
+ '1.2.840.113635.100.1.28': 'apple_otapki_signer',
+ '1.2.840.113635.100.1.29': 'apple_test_otapki_signer',
+ '1.2.840.113625.100.1.30': 'apple_id_validation_record_signing_policy',
+ '1.2.840.113625.100.1.31': 'apple_smp_encryption',
+ '1.2.840.113625.100.1.32': 'apple_test_smp_encryption',
+ '1.2.840.113635.100.1.33': 'apple_server_authentication',
+ '1.2.840.113635.100.1.34': 'apple_pcs_escrow_service',
+ # http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.201-2.pdf
+ '2.16.840.1.101.3.6.8': 'piv_card_authentication',
+ '2.16.840.1.101.3.6.7': 'piv_content_signing',
+ # https://tools.ietf.org/html/rfc4556.html
+ '1.3.6.1.5.2.3.4': 'pkinit_kpclientauth',
+ '1.3.6.1.5.2.3.5': 'pkinit_kpkdc',
+ # https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html
+ '1.2.840.113583.1.1.5': 'adobe_authentic_documents_trust',
+ # https://www.idmanagement.gov/wp-content/uploads/sites/1171/uploads/fpki-pivi-cert-profiles.pdf
+ '2.16.840.1.101.3.8.7': 'fpki_pivi_content_signing'
+ }
+
+
+class ExtKeyUsageSyntax(SequenceOf):
+ _child_spec = KeyPurposeId
+
+
+class AccessMethod(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1': 'ocsp',
+ '1.3.6.1.5.5.7.48.2': 'ca_issuers',
+ '1.3.6.1.5.5.7.48.3': 'time_stamping',
+ '1.3.6.1.5.5.7.48.5': 'ca_repository',
+ }
+
+
+class AccessDescription(Sequence):
+ _fields = [
+ ('access_method', AccessMethod),
+ ('access_location', GeneralName),
+ ]
+
+
+class AuthorityInfoAccessSyntax(SequenceOf):
+ _child_spec = AccessDescription
+
+
+class SubjectInfoAccessSyntax(SequenceOf):
+ _child_spec = AccessDescription
+
+
+# https://tools.ietf.org/html/rfc7633
+class Features(SequenceOf):
+ _child_spec = Integer
+
+
+class EntrustVersionInfo(Sequence):
+ _fields = [
+ ('entrust_vers', GeneralString),
+ ('entrust_info_flags', BitString)
+ ]
+
+
+class NetscapeCertificateType(BitString):
+ _map = {
+ 0: 'ssl_client',
+ 1: 'ssl_server',
+ 2: 'email',
+ 3: 'object_signing',
+ 4: 'reserved',
+ 5: 'ssl_ca',
+ 6: 'email_ca',
+ 7: 'object_signing_ca',
+ }
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ 2: 'v3',
+ }
+
+
+class TPMSpecification(Sequence):
+ _fields = [
+ ('family', UTF8String),
+ ('level', Integer),
+ ('revision', Integer),
+ ]
+
+
+class SetOfTPMSpecification(SetOf):
+ _child_spec = TPMSpecification
+
+
+class TCGSpecificationVersion(Sequence):
+ _fields = [
+ ('major_version', Integer),
+ ('minor_version', Integer),
+ ('revision', Integer),
+ ]
+
+
+class TCGPlatformSpecification(Sequence):
+ _fields = [
+ ('version', TCGSpecificationVersion),
+ ('platform_class', OctetString),
+ ]
+
+
+class SetOfTCGPlatformSpecification(SetOf):
+ _child_spec = TCGPlatformSpecification
+
+
+class EKGenerationType(Enumerated):
+ _map = {
+ 0: 'internal',
+ 1: 'injected',
+ 2: 'internal_revocable',
+ 3: 'injected_revocable',
+ }
+
+
+class EKGenerationLocation(Enumerated):
+ _map = {
+ 0: 'tpm_manufacturer',
+ 1: 'platform_manufacturer',
+ 2: 'ek_cert_signer',
+ }
+
+
+class EKCertificateGenerationLocation(Enumerated):
+ _map = {
+ 0: 'tpm_manufacturer',
+ 1: 'platform_manufacturer',
+ 2: 'ek_cert_signer',
+ }
+
+
+class EvaluationAssuranceLevel(Enumerated):
+ _map = {
+ 1: 'level1',
+ 2: 'level2',
+ 3: 'level3',
+ 4: 'level4',
+ 5: 'level5',
+ 6: 'level6',
+ 7: 'level7',
+ }
+
+
+class EvaluationStatus(Enumerated):
+ _map = {
+ 0: 'designed_to_meet',
+ 1: 'evaluation_in_progress',
+ 2: 'evaluation_completed',
+ }
+
+
+class StrengthOfFunction(Enumerated):
+ _map = {
+ 0: 'basic',
+ 1: 'medium',
+ 2: 'high',
+ }
+
+
+class URIReference(Sequence):
+ _fields = [
+ ('uniform_resource_identifier', IA5String),
+ ('hash_algorithm', DigestAlgorithm, {'optional': True}),
+ ('hash_value', BitString, {'optional': True}),
+ ]
+
+
+class CommonCriteriaMeasures(Sequence):
+ _fields = [
+ ('version', IA5String),
+ ('assurance_level', EvaluationAssuranceLevel),
+ ('evaluation_status', EvaluationStatus),
+ ('plus', Boolean, {'default': False}),
+ ('strengh_of_function', StrengthOfFunction, {'implicit': 0, 'optional': True}),
+ ('profile_oid', ObjectIdentifier, {'implicit': 1, 'optional': True}),
+ ('profile_url', URIReference, {'implicit': 2, 'optional': True}),
+ ('target_oid', ObjectIdentifier, {'implicit': 3, 'optional': True}),
+ ('target_uri', URIReference, {'implicit': 4, 'optional': True}),
+ ]
+
+
+class SecurityLevel(Enumerated):
+ _map = {
+ 1: 'level1',
+ 2: 'level2',
+ 3: 'level3',
+ 4: 'level4',
+ }
+
+
+class FIPSLevel(Sequence):
+ _fields = [
+ ('version', IA5String),
+ ('level', SecurityLevel),
+ ('plus', Boolean, {'default': False}),
+ ]
+
+
+class TPMSecurityAssertions(Sequence):
+ _fields = [
+ ('version', Version, {'default': 'v1'}),
+ ('field_upgradable', Boolean, {'default': False}),
+ ('ek_generation_type', EKGenerationType, {'implicit': 0, 'optional': True}),
+ ('ek_generation_location', EKGenerationLocation, {'implicit': 1, 'optional': True}),
+ ('ek_certificate_generation_location', EKCertificateGenerationLocation, {'implicit': 2, 'optional': True}),
+ ('cc_info', CommonCriteriaMeasures, {'implicit': 3, 'optional': True}),
+ ('fips_level', FIPSLevel, {'implicit': 4, 'optional': True}),
+ ('iso_9000_certified', Boolean, {'implicit': 5, 'default': False}),
+ ('iso_9000_uri', IA5String, {'optional': True}),
+ ]
+
+
+class SetOfTPMSecurityAssertions(SetOf):
+ _child_spec = TPMSecurityAssertions
+
+
+class SubjectDirectoryAttributeId(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc2256#page-11
+ '2.5.4.52': 'supported_algorithms',
+ # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
+ '2.23.133.2.16': 'tpm_specification',
+ '2.23.133.2.17': 'tcg_platform_specification',
+ '2.23.133.2.18': 'tpm_security_assertions',
+ # https://tools.ietf.org/html/rfc3739#page-18
+ '1.3.6.1.5.5.7.9.1': 'pda_date_of_birth',
+ '1.3.6.1.5.5.7.9.2': 'pda_place_of_birth',
+ '1.3.6.1.5.5.7.9.3': 'pda_gender',
+ '1.3.6.1.5.5.7.9.4': 'pda_country_of_citizenship',
+ '1.3.6.1.5.5.7.9.5': 'pda_country_of_residence',
+ # https://holtstrom.com/michael/tools/asn1decoder.php
+ '1.2.840.113533.7.68.29': 'entrust_user_role',
+ }
+
+
+class SetOfGeneralizedTime(SetOf):
+ _child_spec = GeneralizedTime
+
+
+class SetOfDirectoryString(SetOf):
+ _child_spec = DirectoryString
+
+
+class SetOfPrintableString(SetOf):
+ _child_spec = PrintableString
+
+
+class SupportedAlgorithm(Sequence):
+ _fields = [
+ ('algorithm_identifier', AnyAlgorithmIdentifier),
+ ('intended_usage', KeyUsage, {'explicit': 0, 'optional': True}),
+ ('intended_certificate_policies', CertificatePolicies, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class SetOfSupportedAlgorithm(SetOf):
+ _child_spec = SupportedAlgorithm
+
+
+class SubjectDirectoryAttribute(Sequence):
+ _fields = [
+ ('type', SubjectDirectoryAttributeId),
+ ('values', Any),
+ ]
+
+ _oid_pair = ('type', 'values')
+ _oid_specs = {
+ 'supported_algorithms': SetOfSupportedAlgorithm,
+ 'tpm_specification': SetOfTPMSpecification,
+ 'tcg_platform_specification': SetOfTCGPlatformSpecification,
+ 'tpm_security_assertions': SetOfTPMSecurityAssertions,
+ 'pda_date_of_birth': SetOfGeneralizedTime,
+ 'pda_place_of_birth': SetOfDirectoryString,
+ 'pda_gender': SetOfPrintableString,
+ 'pda_country_of_citizenship': SetOfPrintableString,
+ 'pda_country_of_residence': SetOfPrintableString,
+ }
+
+ def _values_spec(self):
+ type_ = self['type'].native
+ if type_ in self._oid_specs:
+ return self._oid_specs[type_]
+ return SetOf
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class SubjectDirectoryAttributes(SequenceOf):
+ _child_spec = SubjectDirectoryAttribute
+
+
+class ExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.9': 'subject_directory_attributes',
+ '2.5.29.14': 'key_identifier',
+ '2.5.29.15': 'key_usage',
+ '2.5.29.16': 'private_key_usage_period',
+ '2.5.29.17': 'subject_alt_name',
+ '2.5.29.18': 'issuer_alt_name',
+ '2.5.29.19': 'basic_constraints',
+ '2.5.29.30': 'name_constraints',
+ '2.5.29.31': 'crl_distribution_points',
+ '2.5.29.32': 'certificate_policies',
+ '2.5.29.33': 'policy_mappings',
+ '2.5.29.35': 'authority_key_identifier',
+ '2.5.29.36': 'policy_constraints',
+ '2.5.29.37': 'extended_key_usage',
+ '2.5.29.46': 'freshest_crl',
+ '2.5.29.54': 'inhibit_any_policy',
+ '1.3.6.1.5.5.7.1.1': 'authority_information_access',
+ '1.3.6.1.5.5.7.1.11': 'subject_information_access',
+ # https://tools.ietf.org/html/rfc7633
+ '1.3.6.1.5.5.7.1.24': 'tls_feature',
+ '1.3.6.1.5.5.7.48.1.5': 'ocsp_no_check',
+ '1.2.840.113533.7.65.0': 'entrust_version_extension',
+ '2.16.840.1.113730.1.1': 'netscape_certificate_type',
+ # https://tools.ietf.org/html/rfc6962.html#page-14
+ '1.3.6.1.4.1.11129.2.4.2': 'signed_certificate_timestamp_list',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/3aec3e50-511a-42f9-a5d5-240af503e470
+ '1.3.6.1.4.1.311.20.2': 'microsoft_enroll_certtype',
+ }
+
+
+class Extension(Sequence):
+ _fields = [
+ ('extn_id', ExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'subject_directory_attributes': SubjectDirectoryAttributes,
+ 'key_identifier': OctetString,
+ 'key_usage': KeyUsage,
+ 'private_key_usage_period': PrivateKeyUsagePeriod,
+ 'subject_alt_name': GeneralNames,
+ 'issuer_alt_name': GeneralNames,
+ 'basic_constraints': BasicConstraints,
+ 'name_constraints': NameConstraints,
+ 'crl_distribution_points': CRLDistributionPoints,
+ 'certificate_policies': CertificatePolicies,
+ 'policy_mappings': PolicyMappings,
+ 'authority_key_identifier': AuthorityKeyIdentifier,
+ 'policy_constraints': PolicyConstraints,
+ 'extended_key_usage': ExtKeyUsageSyntax,
+ 'freshest_crl': CRLDistributionPoints,
+ 'inhibit_any_policy': Integer,
+ 'authority_information_access': AuthorityInfoAccessSyntax,
+ 'subject_information_access': SubjectInfoAccessSyntax,
+ 'tls_feature': Features,
+ 'ocsp_no_check': Null,
+ 'entrust_version_extension': EntrustVersionInfo,
+ 'netscape_certificate_type': NetscapeCertificateType,
+ 'signed_certificate_timestamp_list': OctetString,
+ # Not UTF8String as Microsofts docs claim, see:
+ # https://www.alvestrand.no/objectid/1.3.6.1.4.1.311.20.2.html
+ 'microsoft_enroll_certtype': BMPString,
+ }
+
+
+class Extensions(SequenceOf):
+ _child_spec = Extension
+
+
+class TbsCertificate(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('serial_number', Integer),
+ ('signature', SignedDigestAlgorithm),
+ ('issuer', Name),
+ ('validity', Validity),
+ ('subject', Name),
+ ('subject_public_key_info', PublicKeyInfo),
+ ('issuer_unique_id', OctetBitString, {'implicit': 1, 'optional': True}),
+ ('subject_unique_id', OctetBitString, {'implicit': 2, 'optional': True}),
+ ('extensions', Extensions, {'explicit': 3, 'optional': True}),
+ ]
+
+
+class Certificate(Sequence):
+ _fields = [
+ ('tbs_certificate', TbsCertificate),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature_value', OctetBitString),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _subject_directory_attributes_value = None
+ _key_identifier_value = None
+ _key_usage_value = None
+ _subject_alt_name_value = None
+ _issuer_alt_name_value = None
+ _basic_constraints_value = None
+ _name_constraints_value = None
+ _crl_distribution_points_value = None
+ _certificate_policies_value = None
+ _policy_mappings_value = None
+ _authority_key_identifier_value = None
+ _policy_constraints_value = None
+ _freshest_crl_value = None
+ _inhibit_any_policy_value = None
+ _extended_key_usage_value = None
+ _authority_information_access_value = None
+ _subject_information_access_value = None
+ _private_key_usage_period_value = None
+ _tls_feature_value = None
+ _ocsp_no_check_value = None
+ _issuer_serial = None
+ _authority_issuer_serial = False
+ _crl_distribution_points = None
+ _delta_crl_distribution_points = None
+ _valid_domains = None
+ _valid_ips = None
+ _self_issued = None
+ _self_signed = None
+ _sha1 = None
+ _sha256 = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_certificate']['extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def private_key_usage_period_value(self):
+ """
+ This extension is used to constrain the period over which the subject
+ private key may be used
+
+ :return:
+ None or a PrivateKeyUsagePeriod object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._private_key_usage_period_value
+
+ @property
+ def subject_directory_attributes_value(self):
+ """
+ This extension is used to contain additional identification attributes
+ about the subject.
+
+ :return:
+ None or a SubjectDirectoryAttributes object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_directory_attributes_value
+
+ @property
+ def key_identifier_value(self):
+ """
+ This extension is used to help in creating certificate validation paths.
+ It contains an identifier that should generally, but is not guaranteed
+ to, be unique.
+
+ :return:
+ None or an OctetString object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._key_identifier_value
+
+ @property
+ def key_usage_value(self):
+ """
+ This extension is used to define the purpose of the public key
+ contained within the certificate.
+
+ :return:
+ None or a KeyUsage
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._key_usage_value
+
+ @property
+ def subject_alt_name_value(self):
+ """
+ This extension allows for additional names to be associate with the
+ subject of the certificate. While it may contain a whole host of
+ possible names, it is usually used to allow certificates to be used
+ with multiple different domain names.
+
+ :return:
+ None or a GeneralNames object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_alt_name_value
+
+ @property
+ def issuer_alt_name_value(self):
+ """
+ This extension allows associating one or more alternative names with
+ the issuer of the certificate.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._issuer_alt_name_value
+
+ @property
+ def basic_constraints_value(self):
+ """
+ This extension is used to determine if the subject of the certificate
+ is a CA, and if so, what the maximum number of intermediate CA certs
+ after this are, before an end-entity certificate is found.
+
+ :return:
+ None or a BasicConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._basic_constraints_value
+
+ @property
+ def name_constraints_value(self):
+ """
+ This extension is used in CA certificates, and is used to limit the
+ possible names of certificates issued.
+
+ :return:
+ None or a NameConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._name_constraints_value
+
+ @property
+ def crl_distribution_points_value(self):
+ """
+ This extension is used to help in locating the CRL for this certificate.
+
+ :return:
+ None or a CRLDistributionPoints object
+ extension
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._crl_distribution_points_value
+
+ @property
+ def certificate_policies_value(self):
+ """
+ This extension defines policies in CA certificates under which
+ certificates may be issued. In end-entity certificates, the inclusion
+ of a policy indicates the issuance of the certificate follows the
+ policy.
+
+ :return:
+ None or a CertificatePolicies object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._certificate_policies_value
+
+ @property
+ def policy_mappings_value(self):
+ """
+ This extension allows mapping policy OIDs to other OIDs. This is used
+ to allow different policies to be treated as equivalent in the process
+ of validation.
+
+ :return:
+ None or a PolicyMappings object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._policy_mappings_value
+
+ @property
+ def authority_key_identifier_value(self):
+ """
+ This extension helps in identifying the public key with which to
+ validate the authenticity of the certificate.
+
+ :return:
+ None or an AuthorityKeyIdentifier object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._authority_key_identifier_value
+
+ @property
+ def policy_constraints_value(self):
+ """
+ This extension is used to control if policy mapping is allowed and
+ when policies are required.
+
+ :return:
+ None or a PolicyConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._policy_constraints_value
+
+ @property
+ def freshest_crl_value(self):
+ """
+ This extension is used to help locate any available delta CRLs
+
+ :return:
+ None or an CRLDistributionPoints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._freshest_crl_value
+
+ @property
+ def inhibit_any_policy_value(self):
+ """
+ This extension is used to prevent mapping of the any policy to
+ specific requirements
+
+ :return:
+ None or a Integer object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._inhibit_any_policy_value
+
+ @property
+ def extended_key_usage_value(self):
+ """
+ This extension is used to define additional purposes for the public key
+ beyond what is contained in the basic constraints.
+
+ :return:
+ None or an ExtKeyUsageSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._extended_key_usage_value
+
+ @property
+ def authority_information_access_value(self):
+ """
+ This extension is used to locate the CA certificate used to sign this
+ certificate, or the OCSP responder for this certificate.
+
+ :return:
+ None or an AuthorityInfoAccessSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._authority_information_access_value
+
+ @property
+ def subject_information_access_value(self):
+ """
+ This extension is used to access information about the subject of this
+ certificate.
+
+ :return:
+ None or a SubjectInfoAccessSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_information_access_value
+
+ @property
+ def tls_feature_value(self):
+ """
+ This extension is used to list the TLS features a server must respond
+ with if a client initiates a request supporting them.
+
+ :return:
+ None or a Features object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._tls_feature_value
+
+ @property
+ def ocsp_no_check_value(self):
+ """
+ This extension is used on certificates of OCSP responders, indicating
+ that revocation information for the certificate should never need to
+ be verified, thus preventing possible loops in path validation.
+
+ :return:
+ None or a Null object (if present)
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._ocsp_no_check_value
+
+ @property
+ def signature(self):
+ """
+ :return:
+ A byte string of the signature
+ """
+
+ return self['signature_value'].native
+
+ @property
+ def signature_algo(self):
+ """
+ :return:
+ A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa", "ecdsa"
+ """
+
+ return self['signature_algorithm'].signature_algo
+
+ @property
+ def hash_algo(self):
+ """
+ :return:
+ A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
+ "sha384", "sha512", "sha512_224", "sha512_256"
+ """
+
+ return self['signature_algorithm'].hash_algo
+
+ @property
+ def public_key(self):
+ """
+ :return:
+ The PublicKeyInfo object for this certificate
+ """
+
+ return self['tbs_certificate']['subject_public_key_info']
+
+ @property
+ def subject(self):
+ """
+ :return:
+ The Name object for the subject of this certificate
+ """
+
+ return self['tbs_certificate']['subject']
+
+ @property
+ def issuer(self):
+ """
+ :return:
+ The Name object for the issuer of this certificate
+ """
+
+ return self['tbs_certificate']['issuer']
+
+ @property
+ def serial_number(self):
+ """
+ :return:
+ An integer of the certificate's serial number
+ """
+
+ return self['tbs_certificate']['serial_number'].native
+
+ @property
+ def key_identifier(self):
+ """
+ :return:
+ None or a byte string of the certificate's key identifier from the
+ key identifier extension
+ """
+
+ if not self.key_identifier_value:
+ return None
+
+ return self.key_identifier_value.native
+
+ @property
+ def issuer_serial(self):
+ """
+ :return:
+ A byte string of the SHA-256 hash of the issuer concatenated with
+ the ascii character ":", concatenated with the serial number as
+ an ascii string
+ """
+
+ if self._issuer_serial is None:
+ self._issuer_serial = self.issuer.sha256 + b':' + str_cls(self.serial_number).encode('ascii')
+ return self._issuer_serial
+
+ @property
+ def not_valid_after(self):
+ """
+ :return:
+ A datetime of latest time when the certificate is still valid
+ """
+ return self['tbs_certificate']['validity']['not_after'].native
+
+ @property
+ def not_valid_before(self):
+ """
+ :return:
+ A datetime of the earliest time when the certificate is valid
+ """
+ return self['tbs_certificate']['validity']['not_before'].native
+
+ @property
+ def authority_key_identifier(self):
+ """
+ :return:
+ None or a byte string of the key_identifier from the authority key
+ identifier extension
+ """
+
+ if not self.authority_key_identifier_value:
+ return None
+
+ return self.authority_key_identifier_value['key_identifier'].native
+
+ @property
+ def authority_issuer_serial(self):
+ """
+ :return:
+ None or a byte string of the SHA-256 hash of the isser from the
+ authority key identifier extension concatenated with the ascii
+ character ":", concatenated with the serial number from the
+ authority key identifier extension as an ascii string
+ """
+
+ if self._authority_issuer_serial is False:
+ akiv = self.authority_key_identifier_value
+ if akiv and akiv['authority_cert_issuer'].native:
+ issuer = self.authority_key_identifier_value['authority_cert_issuer'][0].chosen
+ # We untag the element since it is tagged via being a choice from GeneralName
+ issuer = issuer.untag()
+ authority_serial = self.authority_key_identifier_value['authority_cert_serial_number'].native
+ self._authority_issuer_serial = issuer.sha256 + b':' + str_cls(authority_serial).encode('ascii')
+ else:
+ self._authority_issuer_serial = None
+ return self._authority_issuer_serial
+
+ @property
+ def crl_distribution_points(self):
+ """
+ Returns complete CRL URLs - does not include delta CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._crl_distribution_points is None:
+ self._crl_distribution_points = self._get_http_crl_distribution_points(self.crl_distribution_points_value)
+ return self._crl_distribution_points
+
+ @property
+ def delta_crl_distribution_points(self):
+ """
+ Returns delta CRL URLs - does not include complete CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._delta_crl_distribution_points is None:
+ self._delta_crl_distribution_points = self._get_http_crl_distribution_points(self.freshest_crl_value)
+ return self._delta_crl_distribution_points
+
+ def _get_http_crl_distribution_points(self, crl_distribution_points):
+ """
+ Fetches the DistributionPoint object for non-relative, HTTP CRLs
+ referenced by the certificate
+
+ :param crl_distribution_points:
+ A CRLDistributionPoints object to grab the DistributionPoints from
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ output = []
+
+ if crl_distribution_points is None:
+ return []
+
+ for distribution_point in crl_distribution_points:
+ distribution_point_name = distribution_point['distribution_point']
+ if distribution_point_name is VOID:
+ continue
+ # RFC 5280 indicates conforming CA should not use the relative form
+ if distribution_point_name.name == 'name_relative_to_crl_issuer':
+ continue
+ # This library is currently only concerned with HTTP-based CRLs
+ for general_name in distribution_point_name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ output.append(distribution_point)
+
+ return output
+
+ @property
+ def ocsp_urls(self):
+ """
+ :return:
+ A list of zero or more unicode strings of the OCSP URLs for this
+ cert
+ """
+
+ if not self.authority_information_access_value:
+ return []
+
+ output = []
+ for entry in self.authority_information_access_value:
+ if entry['access_method'].native == 'ocsp':
+ location = entry['access_location']
+ if location.name != 'uniform_resource_identifier':
+ continue
+ url = location.native
+ if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')):
+ output.append(url)
+ return output
+
+ @property
+ def valid_domains(self):
+ """
+ :return:
+ A list of unicode strings of valid domain names for the certificate.
+ Wildcard certificates will have a domain in the form: *.example.com
+ """
+
+ if self._valid_domains is None:
+ self._valid_domains = []
+
+ # For the subject alt name extension, we can look at the name of
+ # the choice selected since it distinguishes between domain names,
+ # email addresses, IPs, etc
+ if self.subject_alt_name_value:
+ for general_name in self.subject_alt_name_value:
+ if general_name.name == 'dns_name' and general_name.native not in self._valid_domains:
+ self._valid_domains.append(general_name.native)
+
+ # If there was no subject alt name extension, and the common name
+ # in the subject looks like a domain, that is considered the valid
+ # list. This is done because according to
+ # https://tools.ietf.org/html/rfc6125#section-6.4.4, the common
+ # name should not be used if the subject alt name is present.
+ else:
+ pattern = re.compile('^(\\*\\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$')
+ for rdn in self.subject.chosen:
+ for name_type_value in rdn:
+ if name_type_value['type'].native == 'common_name':
+ value = name_type_value['value'].native
+ if pattern.match(value):
+ self._valid_domains.append(value)
+
+ return self._valid_domains
+
+ @property
+ def valid_ips(self):
+ """
+ :return:
+ A list of unicode strings of valid IP addresses for the certificate
+ """
+
+ if self._valid_ips is None:
+ self._valid_ips = []
+
+ if self.subject_alt_name_value:
+ for general_name in self.subject_alt_name_value:
+ if general_name.name == 'ip_address':
+ self._valid_ips.append(general_name.native)
+
+ return self._valid_ips
+
+ @property
+ def ca(self):
+ """
+ :return;
+ A boolean - if the certificate is marked as a CA
+ """
+
+ return self.basic_constraints_value and self.basic_constraints_value['ca'].native
+
+ @property
+ def max_path_length(self):
+ """
+ :return;
+ None or an integer of the maximum path length
+ """
+
+ if not self.ca:
+ return None
+ return self.basic_constraints_value['path_len_constraint'].native
+
+ @property
+ def self_issued(self):
+ """
+ :return:
+ A boolean - if the certificate is self-issued, as defined by RFC
+ 5280
+ """
+
+ if self._self_issued is None:
+ self._self_issued = self.subject == self.issuer
+ return self._self_issued
+
+ @property
+ def self_signed(self):
+ """
+ :return:
+ A unicode string of "no" or "maybe". The "maybe" result will
+ be returned if the certificate issuer and subject are the same.
+ If a key identifier and authority key identifier are present,
+ they will need to match otherwise "no" will be returned.
+
+ To verify is a certificate is truly self-signed, the signature
+ will need to be verified. See the certvalidator package for
+ one possible solution.
+ """
+
+ if self._self_signed is None:
+ self._self_signed = 'no'
+ if self.self_issued:
+ if self.key_identifier:
+ if not self.authority_key_identifier:
+ self._self_signed = 'maybe'
+ elif self.authority_key_identifier == self.key_identifier:
+ self._self_signed = 'maybe'
+ else:
+ self._self_signed = 'maybe'
+ return self._self_signed
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA-1 hash of the DER-encoded bytes of this complete certificate
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha1_fingerprint(self):
+ """
+ :return:
+ A unicode string of the SHA-1 hash, formatted using hex encoding
+ with a space between each pair of characters, all uppercase
+ """
+
+ return ' '.join('%02X' % c for c in bytes_to_list(self.sha1))
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this complete
+ certificate
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
+
+ @property
+ def sha256_fingerprint(self):
+ """
+ :return:
+ A unicode string of the SHA-256 hash, formatted using hex encoding
+ with a space between each pair of characters, all uppercase
+ """
+
+ return ' '.join('%02X' % c for c in bytes_to_list(self.sha256))
+
+ def is_valid_domain_ip(self, domain_ip):
+ """
+ Check if a domain name or IP address is valid according to the
+ certificate
+
+ :param domain_ip:
+ A unicode string of a domain name or IP address
+
+ :return:
+ A boolean - if the domain or IP is valid for the certificate
+ """
+
+ if not isinstance(domain_ip, str_cls):
+ raise TypeError(unwrap(
+ '''
+ domain_ip must be a unicode string, not %s
+ ''',
+ type_name(domain_ip)
+ ))
+
+ encoded_domain_ip = domain_ip.encode('idna').decode('ascii').lower()
+
+ is_ipv6 = encoded_domain_ip.find(':') != -1
+ is_ipv4 = not is_ipv6 and re.match('^\\d+\\.\\d+\\.\\d+\\.\\d+$', encoded_domain_ip)
+ is_domain = not is_ipv6 and not is_ipv4
+
+ # Handle domain name checks
+ if is_domain:
+ if not self.valid_domains:
+ return False
+
+ domain_labels = encoded_domain_ip.split('.')
+
+ for valid_domain in self.valid_domains:
+ encoded_valid_domain = valid_domain.encode('idna').decode('ascii').lower()
+ valid_domain_labels = encoded_valid_domain.split('.')
+
+ # The domain must be equal in label length to match
+ if len(valid_domain_labels) != len(domain_labels):
+ continue
+
+ if valid_domain_labels == domain_labels:
+ return True
+
+ is_wildcard = self._is_wildcard_domain(encoded_valid_domain)
+ if is_wildcard and self._is_wildcard_match(domain_labels, valid_domain_labels):
+ return True
+
+ return False
+
+ # Handle IP address checks
+ if not self.valid_ips:
+ return False
+
+ family = socket.AF_INET if is_ipv4 else socket.AF_INET6
+ normalized_ip = inet_pton(family, encoded_domain_ip)
+
+ for valid_ip in self.valid_ips:
+ valid_family = socket.AF_INET if valid_ip.find('.') != -1 else socket.AF_INET6
+ normalized_valid_ip = inet_pton(valid_family, valid_ip)
+
+ if normalized_valid_ip == normalized_ip:
+ return True
+
+ return False
+
+ def _is_wildcard_domain(self, domain):
+ """
+ Checks if a domain is a valid wildcard according to
+ https://tools.ietf.org/html/rfc6125#section-6.4.3
+
+ :param domain:
+ A unicode string of the domain name, where any U-labels from an IDN
+ have been converted to A-labels
+
+ :return:
+ A boolean - if the domain is a valid wildcard domain
+ """
+
+ # The * character must be present for a wildcard match, and if there is
+ # most than one, it is an invalid wildcard specification
+ if domain.count('*') != 1:
+ return False
+
+ labels = domain.lower().split('.')
+
+ if not labels:
+ return False
+
+ # Wildcards may only appear in the left-most label
+ if labels[0].find('*') == -1:
+ return False
+
+ # Wildcards may not be embedded in an A-label from an IDN
+ if labels[0][0:4] == 'xn--':
+ return False
+
+ return True
+
+ def _is_wildcard_match(self, domain_labels, valid_domain_labels):
+ """
+ Determines if the labels in a domain are a match for labels from a
+ wildcard valid domain name
+
+ :param domain_labels:
+ A list of unicode strings, with A-label form for IDNs, of the labels
+ in the domain name to check
+
+ :param valid_domain_labels:
+ A list of unicode strings, with A-label form for IDNs, of the labels
+ in a wildcard domain pattern
+
+ :return:
+ A boolean - if the domain matches the valid domain
+ """
+
+ first_domain_label = domain_labels[0]
+ other_domain_labels = domain_labels[1:]
+
+ wildcard_label = valid_domain_labels[0]
+ other_valid_domain_labels = valid_domain_labels[1:]
+
+ # The wildcard is only allowed in the first label, so if
+ # The subsequent labels are not equal, there is no match
+ if other_domain_labels != other_valid_domain_labels:
+ return False
+
+ if wildcard_label == '*':
+ return True
+
+ wildcard_regex = re.compile('^' + wildcard_label.replace('*', '.*') + '$')
+ if wildcard_regex.match(first_domain_label):
+ return True
+
+ return False
+
+
+# The structures are taken from the OpenSSL source file x_x509a.c, and specify
+# extra information that is added to X.509 certificates to store trust
+# information about the certificate.
+
+class KeyPurposeIdentifiers(SequenceOf):
+ _child_spec = KeyPurposeId
+
+
+class SequenceOfAlgorithmIdentifiers(SequenceOf):
+ _child_spec = AlgorithmIdentifier
+
+
+class CertificateAux(Sequence):
+ _fields = [
+ ('trust', KeyPurposeIdentifiers, {'optional': True}),
+ ('reject', KeyPurposeIdentifiers, {'implicit': 0, 'optional': True}),
+ ('alias', UTF8String, {'optional': True}),
+ ('keyid', OctetString, {'optional': True}),
+ ('other', SequenceOfAlgorithmIdentifiers, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class TrustedCertificate(Concat):
+ _child_specs = [Certificate, CertificateAux]
diff --git a/contrib/python/asn1crypto/py2/readme.md b/contrib/python/asn1crypto/py2/readme.md
new file mode 100644
index 0000000000..4f1061f233
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/readme.md
@@ -0,0 +1,273 @@
+# asn1crypto
+
+A fast, pure Python library for parsing and serializing ASN.1 structures.
+
+ - [Features](#features)
+ - [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
+ - [Related Crypto Libraries](#related-crypto-libraries)
+ - [Current Release](#current-release)
+ - [Dependencies](#dependencies)
+ - [Installation](#installation)
+ - [License](#license)
+ - [Security Policy](#security-policy)
+ - [Documentation](#documentation)
+ - [Continuous Integration](#continuous-integration)
+ - [Testing](#testing)
+ - [Development](#development)
+ - [CI Tasks](#ci-tasks)
+
+[![GitHub Actions CI](https://github.com/wbond/asn1crypto/workflows/CI/badge.svg)](https://github.com/wbond/asn1crypto/actions?workflow=CI)
+[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
+[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/)
+
+## Features
+
+In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
+a bunch of ASN.1 structures for use with various common cryptography standards:
+
+| Standard | Module | Source |
+| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
+| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
+| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
+| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
+| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
+| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
+| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
+| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
+| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
+| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
+| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
+| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
+
+## Why Another Python ASN.1 Library?
+
+Python has long had the [pyasn1](https://pypi.org/project/pyasn1/) and
+[pyasn1_modules](https://pypi.org/project/pyasn1-modules/) available for
+parsing and serializing ASN.1 structures. While the project does include a
+comprehensive set of tools for parsing and serializing, the performance of the
+library can be very poor, especially when dealing with bit fields and parsing
+large structures such as CRLs.
+
+After spending extensive time using *pyasn1*, the following issues were
+identified:
+
+ 1. Poor performance
+ 2. Verbose, non-pythonic API
+ 3. Out-dated and incomplete definitions in *pyasn1-modules*
+ 4. No simple way to map data to native Python data structures
+ 5. No mechanism for overridden universal ASN.1 types
+
+The *pyasn1* API is largely method driven, and uses extensive configuration
+objects and lowerCamelCase names. There were no consistent options for
+converting types of native Python data structures. Since the project supports
+out-dated versions of Python, many newer language features are unavailable
+for use.
+
+Time was spent trying to profile issues with the performance, however the
+architecture made it hard to pin down the primary source of the poor
+performance. Attempts were made to improve performance by utilizing unreleased
+patches and delaying parsing using the `Any` type. Even with such changes, the
+performance was still unacceptably slow.
+
+Finally, a number of structures in the cryptographic space use universal data
+types such as `BitString` and `OctetString`, but interpret the data as other
+types. For instance, signatures are really byte strings, but are encoded as
+`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
+represent integers. Parsing these structures as the base universal types and
+then re-interpreting them wastes computation.
+
+*asn1crypto* uses the following techniques to improve performance, especially
+when extracting one or two fields from large, complex structures:
+
+ - Delayed parsing of byte string values
+ - Persistence of original ASN.1 encoded data until a value is changed
+ - Lazy loading of child fields
+ - Utilization of high-level Python stdlib modules
+
+While there is no extensive performance test suite, the
+`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
+late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
+under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
+same parsing took over 4,100 seconds.
+
+For smaller structures the performance difference can range from a few times
+faster to an order of magnitude or more.
+
+## Related Crypto Libraries
+
+*asn1crypto* is part of the modularcrypto family of Python packages:
+
+ - [asn1crypto](https://github.com/wbond/asn1crypto)
+ - [oscrypto](https://github.com/wbond/oscrypto)
+ - [csrbuilder](https://github.com/wbond/csrbuilder)
+ - [certbuilder](https://github.com/wbond/certbuilder)
+ - [crlbuilder](https://github.com/wbond/crlbuilder)
+ - [ocspbuilder](https://github.com/wbond/ocspbuilder)
+ - [certvalidator](https://github.com/wbond/certvalidator)
+
+## Current Release
+
+1.5.0 - [changelog](changelog.md)
+
+## Dependencies
+
+Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
+packages required.*
+
+## Installation
+
+```bash
+pip install asn1crypto
+```
+
+## License
+
+*asn1crypto* is licensed under the terms of the MIT license. See the
+[LICENSE](LICENSE) file for the exact license text.
+
+## Security Policy
+
+The security policies for this project are covered in
+[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
+
+## Documentation
+
+The documentation for *asn1crypto* is composed of tutorials on basic usage and
+links to the source for the various pre-defined type classes.
+
+### Tutorials
+
+ - [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
+ - [PEM Encoder and Decoder](docs/pem.md)
+
+### Reference
+
+ - [Universal types](asn1crypto/core.py), `asn1crypto.core`
+ - [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
+ - [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
+ - [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
+ - [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
+ - [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
+ - [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
+ - [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
+ - [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
+ - [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
+ - [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
+
+## Continuous Integration
+
+Various combinations of platforms and versions of Python are tested via:
+
+ - [macOS, Linux, Windows](https://github.com/wbond/asn1crypto/actions/workflows/ci.yml) via GitHub Actions
+ - [arm64](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
+
+## Testing
+
+Tests are written using `unittest` and require no third-party packages.
+
+Depending on what type of source is available for the package, the following
+commands can be used to run the test suite.
+
+### Git Repository
+
+When working within a Git working copy, or an archive of the Git repository,
+the full test suite is run via:
+
+```bash
+python run.py tests
+```
+
+To run only some tests, pass a regular expression as a parameter to `tests`.
+
+```bash
+python run.py tests ocsp
+```
+
+### PyPi Source Distribution
+
+When working within an extracted source distribution (aka `.tar.gz`) from
+PyPi, the full test suite is run via:
+
+```bash
+python setup.py test
+```
+
+### Package
+
+When the package has been installed via pip (or another method), the package
+`asn1crypto_tests` may be installed and invoked to run the full test suite:
+
+```bash
+pip install asn1crypto_tests
+python -m asn1crypto_tests
+```
+
+## Development
+
+To install the package used for linting, execute:
+
+```bash
+pip install --user -r requires/lint
+```
+
+The following command will run the linter:
+
+```bash
+python run.py lint
+```
+
+Support for code coverage can be installed via:
+
+```bash
+pip install --user -r requires/coverage
+```
+
+Coverage is measured by running:
+
+```bash
+python run.py coverage
+```
+
+To change the version number of the package, run:
+
+```bash
+python run.py version {pep440_version}
+```
+
+To install the necessary packages for releasing a new version on PyPI, run:
+
+```bash
+pip install --user -r requires/release
+```
+
+Releases are created by:
+
+ - Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
+ - Running the command:
+
+ ```bash
+ python run.py release
+ ```
+
+Existing releases can be found at https://pypi.org/project/asn1crypto/.
+
+## CI Tasks
+
+A task named `deps` exists to download and stage all necessary testing
+dependencies. On posix platforms, `curl` is used for downloads and on Windows
+PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
+related to getting pip to work properly and messing with `site-packages` for
+the version of Python being used.
+
+The `ci` task runs `lint` (if flake8 is available for the version of Python) and
+`coverage` (or `tests` if coverage is not available for the version of Python).
+If the current directory is a clean git working copy, the coverage data is
+submitted to codecov.io.
+
+```bash
+python run.py deps
+python run.py ci
+```
diff --git a/contrib/python/asn1crypto/py2/ya.make b/contrib/python/asn1crypto/py2/ya.make
new file mode 100644
index 0000000000..9c7463ea0e
--- /dev/null
+++ b/contrib/python/asn1crypto/py2/ya.make
@@ -0,0 +1,44 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+VERSION(1.5.1)
+
+LICENSE(MIT)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ asn1crypto/__init__.py
+ asn1crypto/_errors.py
+ asn1crypto/_inet.py
+ asn1crypto/_int.py
+ asn1crypto/_iri.py
+ asn1crypto/_ordereddict.py
+ asn1crypto/_teletex_codec.py
+ asn1crypto/_types.py
+ asn1crypto/algos.py
+ asn1crypto/cms.py
+ asn1crypto/core.py
+ asn1crypto/crl.py
+ asn1crypto/csr.py
+ asn1crypto/keys.py
+ asn1crypto/ocsp.py
+ asn1crypto/parser.py
+ asn1crypto/pdf.py
+ asn1crypto/pem.py
+ asn1crypto/pkcs12.py
+ asn1crypto/tsp.py
+ asn1crypto/util.py
+ asn1crypto/version.py
+ asn1crypto/x509.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/asn1crypto/py2/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/asn1crypto/py3/.dist-info/METADATA b/contrib/python/asn1crypto/py3/.dist-info/METADATA
new file mode 100644
index 0000000000..21f03e3326
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/.dist-info/METADATA
@@ -0,0 +1,307 @@
+Metadata-Version: 2.1
+Name: asn1crypto
+Version: 1.5.1
+Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP
+Home-page: https://github.com/wbond/asn1crypto
+Author: wbond
+Author-email: will@wbond.net
+License: MIT
+Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Security :: Cryptography
+Description-Content-Type: text/markdown
+
+# asn1crypto
+
+A fast, pure Python library for parsing and serializing ASN.1 structures.
+
+ - [Features](#features)
+ - [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
+ - [Related Crypto Libraries](#related-crypto-libraries)
+ - [Current Release](#current-release)
+ - [Dependencies](#dependencies)
+ - [Installation](#installation)
+ - [License](#license)
+ - [Security Policy](#security-policy)
+ - [Documentation](#documentation)
+ - [Continuous Integration](#continuous-integration)
+ - [Testing](#testing)
+ - [Development](#development)
+ - [CI Tasks](#ci-tasks)
+
+[![GitHub Actions CI](https://github.com/wbond/asn1crypto/workflows/CI/badge.svg)](https://github.com/wbond/asn1crypto/actions?workflow=CI)
+[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
+[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/)
+
+## Features
+
+In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
+a bunch of ASN.1 structures for use with various common cryptography standards:
+
+| Standard | Module | Source |
+| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
+| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
+| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
+| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
+| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
+| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
+| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
+| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
+| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
+| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
+| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
+| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
+
+## Why Another Python ASN.1 Library?
+
+Python has long had the [pyasn1](https://pypi.org/project/pyasn1/) and
+[pyasn1_modules](https://pypi.org/project/pyasn1-modules/) available for
+parsing and serializing ASN.1 structures. While the project does include a
+comprehensive set of tools for parsing and serializing, the performance of the
+library can be very poor, especially when dealing with bit fields and parsing
+large structures such as CRLs.
+
+After spending extensive time using *pyasn1*, the following issues were
+identified:
+
+ 1. Poor performance
+ 2. Verbose, non-pythonic API
+ 3. Out-dated and incomplete definitions in *pyasn1-modules*
+ 4. No simple way to map data to native Python data structures
+ 5. No mechanism for overridden universal ASN.1 types
+
+The *pyasn1* API is largely method driven, and uses extensive configuration
+objects and lowerCamelCase names. There were no consistent options for
+converting types of native Python data structures. Since the project supports
+out-dated versions of Python, many newer language features are unavailable
+for use.
+
+Time was spent trying to profile issues with the performance, however the
+architecture made it hard to pin down the primary source of the poor
+performance. Attempts were made to improve performance by utilizing unreleased
+patches and delaying parsing using the `Any` type. Even with such changes, the
+performance was still unacceptably slow.
+
+Finally, a number of structures in the cryptographic space use universal data
+types such as `BitString` and `OctetString`, but interpret the data as other
+types. For instance, signatures are really byte strings, but are encoded as
+`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
+represent integers. Parsing these structures as the base universal types and
+then re-interpreting them wastes computation.
+
+*asn1crypto* uses the following techniques to improve performance, especially
+when extracting one or two fields from large, complex structures:
+
+ - Delayed parsing of byte string values
+ - Persistence of original ASN.1 encoded data until a value is changed
+ - Lazy loading of child fields
+ - Utilization of high-level Python stdlib modules
+
+While there is no extensive performance test suite, the
+`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
+late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
+under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
+same parsing took over 4,100 seconds.
+
+For smaller structures the performance difference can range from a few times
+faster to an order of magnitude or more.
+
+## Related Crypto Libraries
+
+*asn1crypto* is part of the modularcrypto family of Python packages:
+
+ - [asn1crypto](https://github.com/wbond/asn1crypto)
+ - [oscrypto](https://github.com/wbond/oscrypto)
+ - [csrbuilder](https://github.com/wbond/csrbuilder)
+ - [certbuilder](https://github.com/wbond/certbuilder)
+ - [crlbuilder](https://github.com/wbond/crlbuilder)
+ - [ocspbuilder](https://github.com/wbond/ocspbuilder)
+ - [certvalidator](https://github.com/wbond/certvalidator)
+
+## Current Release
+
+1.5.0 - [changelog](changelog.md)
+
+## Dependencies
+
+Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
+packages required.*
+
+## Installation
+
+```bash
+pip install asn1crypto
+```
+
+## License
+
+*asn1crypto* is licensed under the terms of the MIT license. See the
+[LICENSE](LICENSE) file for the exact license text.
+
+## Security Policy
+
+The security policies for this project are covered in
+[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
+
+## Documentation
+
+The documentation for *asn1crypto* is composed of tutorials on basic usage and
+links to the source for the various pre-defined type classes.
+
+### Tutorials
+
+ - [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
+ - [PEM Encoder and Decoder](docs/pem.md)
+
+### Reference
+
+ - [Universal types](asn1crypto/core.py), `asn1crypto.core`
+ - [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
+ - [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
+ - [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
+ - [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
+ - [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
+ - [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
+ - [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
+ - [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
+ - [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
+ - [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
+
+## Continuous Integration
+
+Various combinations of platforms and versions of Python are tested via:
+
+ - [macOS, Linux, Windows](https://github.com/wbond/asn1crypto/actions/workflows/ci.yml) via GitHub Actions
+ - [arm64](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
+
+## Testing
+
+Tests are written using `unittest` and require no third-party packages.
+
+Depending on what type of source is available for the package, the following
+commands can be used to run the test suite.
+
+### Git Repository
+
+When working within a Git working copy, or an archive of the Git repository,
+the full test suite is run via:
+
+```bash
+python run.py tests
+```
+
+To run only some tests, pass a regular expression as a parameter to `tests`.
+
+```bash
+python run.py tests ocsp
+```
+
+### PyPi Source Distribution
+
+When working within an extracted source distribution (aka `.tar.gz`) from
+PyPi, the full test suite is run via:
+
+```bash
+python setup.py test
+```
+
+### Package
+
+When the package has been installed via pip (or another method), the package
+`asn1crypto_tests` may be installed and invoked to run the full test suite:
+
+```bash
+pip install asn1crypto_tests
+python -m asn1crypto_tests
+```
+
+## Development
+
+To install the package used for linting, execute:
+
+```bash
+pip install --user -r requires/lint
+```
+
+The following command will run the linter:
+
+```bash
+python run.py lint
+```
+
+Support for code coverage can be installed via:
+
+```bash
+pip install --user -r requires/coverage
+```
+
+Coverage is measured by running:
+
+```bash
+python run.py coverage
+```
+
+To change the version number of the package, run:
+
+```bash
+python run.py version {pep440_version}
+```
+
+To install the necessary packages for releasing a new version on PyPI, run:
+
+```bash
+pip install --user -r requires/release
+```
+
+Releases are created by:
+
+ - Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
+ - Running the command:
+
+ ```bash
+ python run.py release
+ ```
+
+Existing releases can be found at https://pypi.org/project/asn1crypto/.
+
+## CI Tasks
+
+A task named `deps` exists to download and stage all necessary testing
+dependencies. On posix platforms, `curl` is used for downloads and on Windows
+PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
+related to getting pip to work properly and messing with `site-packages` for
+the version of Python being used.
+
+The `ci` task runs `lint` (if flake8 is available for the version of Python) and
+`coverage` (or `tests` if coverage is not available for the version of Python).
+If the current directory is a clean git working copy, the coverage data is
+submitted to codecov.io.
+
+```bash
+python run.py deps
+python run.py ci
+```
+
+
diff --git a/contrib/python/asn1crypto/py3/.dist-info/top_level.txt b/contrib/python/asn1crypto/py3/.dist-info/top_level.txt
new file mode 100644
index 0000000000..35a704e46d
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+asn1crypto
diff --git a/contrib/python/asn1crypto/py3/LICENSE b/contrib/python/asn1crypto/py3/LICENSE
new file mode 100644
index 0000000000..07b49ae99b
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2022 Will Bond <will@wbond.net>
+
+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.
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/__init__.py b/contrib/python/asn1crypto/py3/asn1crypto/__init__.py
new file mode 100644
index 0000000000..2c93f00ebb
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/__init__.py
@@ -0,0 +1,47 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .version import __version__, __version_info__
+
+__all__ = [
+ '__version__',
+ '__version_info__',
+ 'load_order',
+]
+
+
+def load_order():
+ """
+ Returns a list of the module and sub-module names for asn1crypto in
+ dependency load order, for the sake of live reloading code
+
+ :return:
+ A list of unicode strings of module names, as they would appear in
+ sys.modules, ordered by which module should be reloaded first
+ """
+
+ return [
+ 'asn1crypto._errors',
+ 'asn1crypto._int',
+ 'asn1crypto._ordereddict',
+ 'asn1crypto._teletex_codec',
+ 'asn1crypto._types',
+ 'asn1crypto._inet',
+ 'asn1crypto._iri',
+ 'asn1crypto.version',
+ 'asn1crypto.pem',
+ 'asn1crypto.util',
+ 'asn1crypto.parser',
+ 'asn1crypto.core',
+ 'asn1crypto.algos',
+ 'asn1crypto.keys',
+ 'asn1crypto.x509',
+ 'asn1crypto.crl',
+ 'asn1crypto.csr',
+ 'asn1crypto.ocsp',
+ 'asn1crypto.cms',
+ 'asn1crypto.pdf',
+ 'asn1crypto.pkcs12',
+ 'asn1crypto.tsp',
+ 'asn1crypto',
+ ]
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_errors.py b/contrib/python/asn1crypto/py3/asn1crypto/_errors.py
new file mode 100644
index 0000000000..d8797a2fd1
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_errors.py
@@ -0,0 +1,54 @@
+# coding: utf-8
+
+"""
+Exports the following items:
+
+ - unwrap()
+ - APIException()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import re
+import textwrap
+
+
+class APIException(Exception):
+ """
+ An exception indicating an API has been removed from asn1crypto
+ """
+
+ pass
+
+
+def unwrap(string, *params):
+ """
+ Takes a multi-line string and does the following:
+
+ - dedents
+ - converts newlines with text before and after into a single line
+ - strips leading and trailing whitespace
+
+ :param string:
+ The string to format
+
+ :param *params:
+ Params to interpolate into the string
+
+ :return:
+ The formatted string
+ """
+
+ output = textwrap.dedent(string)
+
+ # Unwrap lines, taking into account bulleted lists, ordered lists and
+ # underlines consisting of = signs
+ if output.find('\n') != -1:
+ output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output)
+
+ if params:
+ output = output % params
+
+ output = output.strip()
+
+ return output
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_inet.py b/contrib/python/asn1crypto/py3/asn1crypto/_inet.py
new file mode 100644
index 0000000000..045ba561cc
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_inet.py
@@ -0,0 +1,170 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import socket
+import struct
+
+from ._errors import unwrap
+from ._types import byte_cls, bytes_to_list, str_cls, type_name
+
+
+def inet_ntop(address_family, packed_ip):
+ """
+ Windows compatibility shim for socket.inet_ntop().
+
+ :param address_family:
+ socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
+
+ :param packed_ip:
+ A byte string of the network form of an IP address
+
+ :return:
+ A unicode string of the IP address
+ """
+
+ if address_family not in set([socket.AF_INET, socket.AF_INET6]):
+ raise ValueError(unwrap(
+ '''
+ address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
+ not %s
+ ''',
+ repr(socket.AF_INET),
+ repr(socket.AF_INET6),
+ repr(address_family)
+ ))
+
+ if not isinstance(packed_ip, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ packed_ip must be a byte string, not %s
+ ''',
+ type_name(packed_ip)
+ ))
+
+ required_len = 4 if address_family == socket.AF_INET else 16
+ if len(packed_ip) != required_len:
+ raise ValueError(unwrap(
+ '''
+ packed_ip must be %d bytes long - is %d
+ ''',
+ required_len,
+ len(packed_ip)
+ ))
+
+ if address_family == socket.AF_INET:
+ return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip))
+
+ octets = struct.unpack(b'!HHHHHHHH', packed_ip)
+
+ runs_of_zero = {}
+ longest_run = 0
+ zero_index = None
+ for i, octet in enumerate(octets + (-1,)):
+ if octet != 0:
+ if zero_index is not None:
+ length = i - zero_index
+ if length not in runs_of_zero:
+ runs_of_zero[length] = zero_index
+ longest_run = max(longest_run, length)
+ zero_index = None
+ elif zero_index is None:
+ zero_index = i
+
+ hexed = [hex(o)[2:] for o in octets]
+
+ if longest_run < 2:
+ return ':'.join(hexed)
+
+ zero_start = runs_of_zero[longest_run]
+ zero_end = zero_start + longest_run
+
+ return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:])
+
+
+def inet_pton(address_family, ip_string):
+ """
+ Windows compatibility shim for socket.inet_ntop().
+
+ :param address_family:
+ socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
+
+ :param ip_string:
+ A unicode string of an IP address
+
+ :return:
+ A byte string of the network form of the IP address
+ """
+
+ if address_family not in set([socket.AF_INET, socket.AF_INET6]):
+ raise ValueError(unwrap(
+ '''
+ address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
+ not %s
+ ''',
+ repr(socket.AF_INET),
+ repr(socket.AF_INET6),
+ repr(address_family)
+ ))
+
+ if not isinstance(ip_string, str_cls):
+ raise TypeError(unwrap(
+ '''
+ ip_string must be a unicode string, not %s
+ ''',
+ type_name(ip_string)
+ ))
+
+ if address_family == socket.AF_INET:
+ octets = ip_string.split('.')
+ error = len(octets) != 4
+ if not error:
+ ints = []
+ for o in octets:
+ o = int(o)
+ if o > 255 or o < 0:
+ error = True
+ break
+ ints.append(o)
+
+ if error:
+ raise ValueError(unwrap(
+ '''
+ ip_string must be a dotted string with four integers in the
+ range of 0 to 255, got %s
+ ''',
+ repr(ip_string)
+ ))
+
+ return struct.pack(b'!BBBB', *ints)
+
+ error = False
+ omitted = ip_string.count('::')
+ if omitted > 1:
+ error = True
+ elif omitted == 0:
+ octets = ip_string.split(':')
+ error = len(octets) != 8
+ else:
+ begin, end = ip_string.split('::')
+ begin_octets = begin.split(':')
+ end_octets = end.split(':')
+ missing = 8 - len(begin_octets) - len(end_octets)
+ octets = begin_octets + (['0'] * missing) + end_octets
+
+ if not error:
+ ints = []
+ for o in octets:
+ o = int(o, 16)
+ if o > 65535 or o < 0:
+ error = True
+ break
+ ints.append(o)
+
+ return struct.pack(b'!HHHHHHHH', *ints)
+
+ raise ValueError(unwrap(
+ '''
+ ip_string must be a valid ipv6 string, got %s
+ ''',
+ repr(ip_string)
+ ))
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_int.py b/contrib/python/asn1crypto/py3/asn1crypto/_int.py
new file mode 100644
index 0000000000..094fc958da
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_int.py
@@ -0,0 +1,22 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+
+def fill_width(bytes_, width):
+ """
+ Ensure a byte string representing a positive integer is a specific width
+ (in bytes)
+
+ :param bytes_:
+ The integer byte string
+
+ :param width:
+ The desired width as an integer
+
+ :return:
+ A byte string of the width specified
+ """
+
+ while len(bytes_) < width:
+ bytes_ = b'\x00' + bytes_
+ return bytes_
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_iri.py b/contrib/python/asn1crypto/py3/asn1crypto/_iri.py
new file mode 100644
index 0000000000..7394b4d571
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_iri.py
@@ -0,0 +1,291 @@
+# coding: utf-8
+
+"""
+Functions to convert unicode IRIs into ASCII byte string URIs and back. Exports
+the following items:
+
+ - iri_to_uri()
+ - uri_to_iri()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from encodings import idna # noqa
+import codecs
+import re
+import sys
+
+from ._errors import unwrap
+from ._types import byte_cls, str_cls, type_name, bytes_to_list, int_types
+
+if sys.version_info < (3,):
+ from urlparse import urlsplit, urlunsplit
+ from urllib import (
+ quote as urlquote,
+ unquote as unquote_to_bytes,
+ )
+
+else:
+ from urllib.parse import (
+ quote as urlquote,
+ unquote_to_bytes,
+ urlsplit,
+ urlunsplit,
+ )
+
+
+def iri_to_uri(value, normalize=False):
+ """
+ Encodes a unicode IRI into an ASCII byte string URI
+
+ :param value:
+ A unicode string of an IRI
+
+ :param normalize:
+ A bool that controls URI normalization
+
+ :return:
+ A byte string of the ASCII-encoded URI
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ scheme = None
+ # Python 2.6 doesn't split properly is the URL doesn't start with http:// or https://
+ if sys.version_info < (2, 7) and not value.startswith('http://') and not value.startswith('https://'):
+ real_prefix = None
+ prefix_match = re.match('^[^:]*://', value)
+ if prefix_match:
+ real_prefix = prefix_match.group(0)
+ value = 'http://' + value[len(real_prefix):]
+ parsed = urlsplit(value)
+ if real_prefix:
+ value = real_prefix + value[7:]
+ scheme = _urlquote(real_prefix[:-3])
+ else:
+ parsed = urlsplit(value)
+
+ if scheme is None:
+ scheme = _urlquote(parsed.scheme)
+ hostname = parsed.hostname
+ if hostname is not None:
+ hostname = hostname.encode('idna')
+ # RFC 3986 allows userinfo to contain sub-delims
+ username = _urlquote(parsed.username, safe='!$&\'()*+,;=')
+ password = _urlquote(parsed.password, safe='!$&\'()*+,;=')
+ port = parsed.port
+ if port is not None:
+ port = str_cls(port).encode('ascii')
+
+ netloc = b''
+ if username is not None:
+ netloc += username
+ if password:
+ netloc += b':' + password
+ netloc += b'@'
+ if hostname is not None:
+ netloc += hostname
+ if port is not None:
+ default_http = scheme == b'http' and port == b'80'
+ default_https = scheme == b'https' and port == b'443'
+ if not normalize or (not default_http and not default_https):
+ netloc += b':' + port
+
+ # RFC 3986 allows a path to contain sub-delims, plus "@" and ":"
+ path = _urlquote(parsed.path, safe='/!$&\'()*+,;=@:')
+ # RFC 3986 allows the query to contain sub-delims, plus "@", ":" , "/" and "?"
+ query = _urlquote(parsed.query, safe='/?!$&\'()*+,;=@:')
+ # RFC 3986 allows the fragment to contain sub-delims, plus "@", ":" , "/" and "?"
+ fragment = _urlquote(parsed.fragment, safe='/?!$&\'()*+,;=@:')
+
+ if normalize and query is None and fragment is None and path == b'/':
+ path = None
+
+ # Python 2.7 compat
+ if path is None:
+ path = ''
+
+ output = urlunsplit((scheme, netloc, path, query, fragment))
+ if isinstance(output, str_cls):
+ output = output.encode('latin1')
+ return output
+
+
+def uri_to_iri(value):
+ """
+ Converts an ASCII URI byte string into a unicode IRI
+
+ :param value:
+ An ASCII-encoded byte string of the URI
+
+ :return:
+ A unicode string of the IRI
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a byte string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ parsed = urlsplit(value)
+
+ scheme = parsed.scheme
+ if scheme is not None:
+ scheme = scheme.decode('ascii')
+
+ username = _urlunquote(parsed.username, remap=[':', '@'])
+ password = _urlunquote(parsed.password, remap=[':', '@'])
+ hostname = parsed.hostname
+ if hostname:
+ hostname = hostname.decode('idna')
+ port = parsed.port
+ if port and not isinstance(port, int_types):
+ port = port.decode('ascii')
+
+ netloc = ''
+ if username is not None:
+ netloc += username
+ if password:
+ netloc += ':' + password
+ netloc += '@'
+ if hostname is not None:
+ netloc += hostname
+ if port is not None:
+ netloc += ':' + str_cls(port)
+
+ path = _urlunquote(parsed.path, remap=['/'], preserve=True)
+ query = _urlunquote(parsed.query, remap=['&', '='], preserve=True)
+ fragment = _urlunquote(parsed.fragment)
+
+ return urlunsplit((scheme, netloc, path, query, fragment))
+
+
+def _iri_utf8_errors_handler(exc):
+ """
+ Error handler for decoding UTF-8 parts of a URI into an IRI. Leaves byte
+ sequences encoded in %XX format, but as part of a unicode string.
+
+ :param exc:
+ The UnicodeDecodeError exception
+
+ :return:
+ A 2-element tuple of (replacement unicode string, integer index to
+ resume at)
+ """
+
+ bytes_as_ints = bytes_to_list(exc.object[exc.start:exc.end])
+ replacements = ['%%%02x' % num for num in bytes_as_ints]
+ return (''.join(replacements), exc.end)
+
+
+codecs.register_error('iriutf8', _iri_utf8_errors_handler)
+
+
+def _urlquote(string, safe=''):
+ """
+ Quotes a unicode string for use in a URL
+
+ :param string:
+ A unicode string
+
+ :param safe:
+ A unicode string of character to not encode
+
+ :return:
+ None (if string is None) or an ASCII byte string of the quoted string
+ """
+
+ if string is None or string == '':
+ return None
+
+ # Anything already hex quoted is pulled out of the URL and unquoted if
+ # possible
+ escapes = []
+ if re.search('%[0-9a-fA-F]{2}', string):
+ # Try to unquote any percent values, restoring them if they are not
+ # valid UTF-8. Also, requote any safe chars since encoded versions of
+ # those are functionally different than the unquoted ones.
+ def _try_unescape(match):
+ byte_string = unquote_to_bytes(match.group(0))
+ unicode_string = byte_string.decode('utf-8', 'iriutf8')
+ for safe_char in list(safe):
+ unicode_string = unicode_string.replace(safe_char, '%%%02x' % ord(safe_char))
+ return unicode_string
+ string = re.sub('(?:%[0-9a-fA-F]{2})+', _try_unescape, string)
+
+ # Once we have the minimal set of hex quoted values, removed them from
+ # the string so that they are not double quoted
+ def _extract_escape(match):
+ escapes.append(match.group(0).encode('ascii'))
+ return '\x00'
+ string = re.sub('%[0-9a-fA-F]{2}', _extract_escape, string)
+
+ output = urlquote(string.encode('utf-8'), safe=safe.encode('utf-8'))
+ if not isinstance(output, byte_cls):
+ output = output.encode('ascii')
+
+ # Restore the existing quoted values that we extracted
+ if len(escapes) > 0:
+ def _return_escape(_):
+ return escapes.pop(0)
+ output = re.sub(b'%00', _return_escape, output)
+
+ return output
+
+
+def _urlunquote(byte_string, remap=None, preserve=None):
+ """
+ Unquotes a URI portion from a byte string into unicode using UTF-8
+
+ :param byte_string:
+ A byte string of the data to unquote
+
+ :param remap:
+ A list of characters (as unicode) that should be re-mapped to a
+ %XX encoding. This is used when characters are not valid in part of a
+ URL.
+
+ :param preserve:
+ A bool - indicates that the chars to be remapped if they occur in
+ non-hex form, should be preserved. E.g. / for URL path.
+
+ :return:
+ A unicode string
+ """
+
+ if byte_string is None:
+ return byte_string
+
+ if byte_string == b'':
+ return ''
+
+ if preserve:
+ replacements = ['\x1A', '\x1C', '\x1D', '\x1E', '\x1F']
+ preserve_unmap = {}
+ for char in remap:
+ replacement = replacements.pop(0)
+ preserve_unmap[replacement] = char
+ byte_string = byte_string.replace(char.encode('ascii'), replacement.encode('ascii'))
+
+ byte_string = unquote_to_bytes(byte_string)
+
+ if remap:
+ for char in remap:
+ byte_string = byte_string.replace(char.encode('ascii'), ('%%%02x' % ord(char)).encode('ascii'))
+
+ output = byte_string.decode('utf-8', 'iriutf8')
+
+ if preserve:
+ for replacement, original in preserve_unmap.items():
+ output = output.replace(replacement, original)
+
+ return output
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_ordereddict.py b/contrib/python/asn1crypto/py3/asn1crypto/_ordereddict.py
new file mode 100644
index 0000000000..2f18ab5ae9
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_ordereddict.py
@@ -0,0 +1,135 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# 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.
+
+import sys
+
+if not sys.version_info < (2, 7):
+
+ from collections import OrderedDict
+
+else:
+
+ from UserDict import DictMixin
+
+ class OrderedDict(dict, DictMixin):
+
+ def __init__(self, *args, **kwds):
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__end
+ except AttributeError:
+ self.clear()
+ self.update(*args, **kwds)
+
+ def clear(self):
+ self.__end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.__map = {} # key --> [key, prev, next]
+ dict.clear(self)
+
+ def __setitem__(self, key, value):
+ if key not in self:
+ end = self.__end
+ curr = end[1]
+ curr[2] = end[1] = self.__map[key] = [key, curr, end]
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ key, prev, next_ = self.__map.pop(key)
+ prev[2] = next_
+ next_[1] = prev
+
+ def __iter__(self):
+ end = self.__end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.__end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def popitem(self, last=True):
+ if not self:
+ raise KeyError('dictionary is empty')
+ if last:
+ key = reversed(self).next()
+ else:
+ key = iter(self).next()
+ value = self.pop(key)
+ return key, value
+
+ def __reduce__(self):
+ items = [[k, self[k]] for k in self]
+ tmp = self.__map, self.__end
+ del self.__map, self.__end
+ inst_dict = vars(self).copy()
+ self.__map, self.__end = tmp
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def keys(self):
+ return list(self)
+
+ setdefault = DictMixin.setdefault
+ update = DictMixin.update
+ pop = DictMixin.pop
+ values = DictMixin.values
+ items = DictMixin.items
+ iterkeys = DictMixin.iterkeys
+ itervalues = DictMixin.itervalues
+ iteritems = DictMixin.iteritems
+
+ def __repr__(self):
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, self.items())
+
+ def copy(self):
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedDict):
+ if len(self) != len(other):
+ return False
+ for p, q in zip(self.items(), other.items()):
+ if p != q:
+ return False
+ return True
+ return dict.__eq__(self, other)
+
+ def __ne__(self, other):
+ return not self == other
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_teletex_codec.py b/contrib/python/asn1crypto/py3/asn1crypto/_teletex_codec.py
new file mode 100644
index 0000000000..b5991aaf1d
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_teletex_codec.py
@@ -0,0 +1,331 @@
+# coding: utf-8
+
+"""
+Implementation of the teletex T.61 codec. Exports the following items:
+
+ - register()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import codecs
+
+
+class TeletexCodec(codecs.Codec):
+
+ def encode(self, input_, errors='strict'):
+ return codecs.charmap_encode(input_, errors, ENCODING_TABLE)
+
+ def decode(self, input_, errors='strict'):
+ return codecs.charmap_decode(input_, errors, DECODING_TABLE)
+
+
+class TeletexIncrementalEncoder(codecs.IncrementalEncoder):
+
+ def encode(self, input_, final=False):
+ return codecs.charmap_encode(input_, self.errors, ENCODING_TABLE)[0]
+
+
+class TeletexIncrementalDecoder(codecs.IncrementalDecoder):
+
+ def decode(self, input_, final=False):
+ return codecs.charmap_decode(input_, self.errors, DECODING_TABLE)[0]
+
+
+class TeletexStreamWriter(TeletexCodec, codecs.StreamWriter):
+
+ pass
+
+
+class TeletexStreamReader(TeletexCodec, codecs.StreamReader):
+
+ pass
+
+
+def teletex_search_function(name):
+ """
+ Search function for teletex codec that is passed to codecs.register()
+ """
+
+ if name != 'teletex':
+ return None
+
+ return codecs.CodecInfo(
+ name='teletex',
+ encode=TeletexCodec().encode,
+ decode=TeletexCodec().decode,
+ incrementalencoder=TeletexIncrementalEncoder,
+ incrementaldecoder=TeletexIncrementalDecoder,
+ streamreader=TeletexStreamReader,
+ streamwriter=TeletexStreamWriter,
+ )
+
+
+def register():
+ """
+ Registers the teletex codec
+ """
+
+ codecs.register(teletex_search_function)
+
+
+# http://en.wikipedia.org/wiki/ITU_T.61
+DECODING_TABLE = (
+ '\u0000'
+ '\u0001'
+ '\u0002'
+ '\u0003'
+ '\u0004'
+ '\u0005'
+ '\u0006'
+ '\u0007'
+ '\u0008'
+ '\u0009'
+ '\u000A'
+ '\u000B'
+ '\u000C'
+ '\u000D'
+ '\u000E'
+ '\u000F'
+ '\u0010'
+ '\u0011'
+ '\u0012'
+ '\u0013'
+ '\u0014'
+ '\u0015'
+ '\u0016'
+ '\u0017'
+ '\u0018'
+ '\u0019'
+ '\u001A'
+ '\u001B'
+ '\u001C'
+ '\u001D'
+ '\u001E'
+ '\u001F'
+ '\u0020'
+ '\u0021'
+ '\u0022'
+ '\ufffe'
+ '\ufffe'
+ '\u0025'
+ '\u0026'
+ '\u0027'
+ '\u0028'
+ '\u0029'
+ '\u002A'
+ '\u002B'
+ '\u002C'
+ '\u002D'
+ '\u002E'
+ '\u002F'
+ '\u0030'
+ '\u0031'
+ '\u0032'
+ '\u0033'
+ '\u0034'
+ '\u0035'
+ '\u0036'
+ '\u0037'
+ '\u0038'
+ '\u0039'
+ '\u003A'
+ '\u003B'
+ '\u003C'
+ '\u003D'
+ '\u003E'
+ '\u003F'
+ '\u0040'
+ '\u0041'
+ '\u0042'
+ '\u0043'
+ '\u0044'
+ '\u0045'
+ '\u0046'
+ '\u0047'
+ '\u0048'
+ '\u0049'
+ '\u004A'
+ '\u004B'
+ '\u004C'
+ '\u004D'
+ '\u004E'
+ '\u004F'
+ '\u0050'
+ '\u0051'
+ '\u0052'
+ '\u0053'
+ '\u0054'
+ '\u0055'
+ '\u0056'
+ '\u0057'
+ '\u0058'
+ '\u0059'
+ '\u005A'
+ '\u005B'
+ '\ufffe'
+ '\u005D'
+ '\ufffe'
+ '\u005F'
+ '\ufffe'
+ '\u0061'
+ '\u0062'
+ '\u0063'
+ '\u0064'
+ '\u0065'
+ '\u0066'
+ '\u0067'
+ '\u0068'
+ '\u0069'
+ '\u006A'
+ '\u006B'
+ '\u006C'
+ '\u006D'
+ '\u006E'
+ '\u006F'
+ '\u0070'
+ '\u0071'
+ '\u0072'
+ '\u0073'
+ '\u0074'
+ '\u0075'
+ '\u0076'
+ '\u0077'
+ '\u0078'
+ '\u0079'
+ '\u007A'
+ '\ufffe'
+ '\u007C'
+ '\ufffe'
+ '\ufffe'
+ '\u007F'
+ '\u0080'
+ '\u0081'
+ '\u0082'
+ '\u0083'
+ '\u0084'
+ '\u0085'
+ '\u0086'
+ '\u0087'
+ '\u0088'
+ '\u0089'
+ '\u008A'
+ '\u008B'
+ '\u008C'
+ '\u008D'
+ '\u008E'
+ '\u008F'
+ '\u0090'
+ '\u0091'
+ '\u0092'
+ '\u0093'
+ '\u0094'
+ '\u0095'
+ '\u0096'
+ '\u0097'
+ '\u0098'
+ '\u0099'
+ '\u009A'
+ '\u009B'
+ '\u009C'
+ '\u009D'
+ '\u009E'
+ '\u009F'
+ '\u00A0'
+ '\u00A1'
+ '\u00A2'
+ '\u00A3'
+ '\u0024'
+ '\u00A5'
+ '\u0023'
+ '\u00A7'
+ '\u00A4'
+ '\ufffe'
+ '\ufffe'
+ '\u00AB'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\u00B0'
+ '\u00B1'
+ '\u00B2'
+ '\u00B3'
+ '\u00D7'
+ '\u00B5'
+ '\u00B6'
+ '\u00B7'
+ '\u00F7'
+ '\ufffe'
+ '\ufffe'
+ '\u00BB'
+ '\u00BC'
+ '\u00BD'
+ '\u00BE'
+ '\u00BF'
+ '\ufffe'
+ '\u0300'
+ '\u0301'
+ '\u0302'
+ '\u0303'
+ '\u0304'
+ '\u0306'
+ '\u0307'
+ '\u0308'
+ '\ufffe'
+ '\u030A'
+ '\u0327'
+ '\u0332'
+ '\u030B'
+ '\u0328'
+ '\u030C'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\ufffe'
+ '\u2126'
+ '\u00C6'
+ '\u00D0'
+ '\u00AA'
+ '\u0126'
+ '\ufffe'
+ '\u0132'
+ '\u013F'
+ '\u0141'
+ '\u00D8'
+ '\u0152'
+ '\u00BA'
+ '\u00DE'
+ '\u0166'
+ '\u014A'
+ '\u0149'
+ '\u0138'
+ '\u00E6'
+ '\u0111'
+ '\u00F0'
+ '\u0127'
+ '\u0131'
+ '\u0133'
+ '\u0140'
+ '\u0142'
+ '\u00F8'
+ '\u0153'
+ '\u00DF'
+ '\u00FE'
+ '\u0167'
+ '\u014B'
+ '\ufffe'
+)
+ENCODING_TABLE = codecs.charmap_build(DECODING_TABLE)
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/_types.py b/contrib/python/asn1crypto/py3/asn1crypto/_types.py
new file mode 100644
index 0000000000..b9ca8cc79b
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/_types.py
@@ -0,0 +1,46 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import inspect
+import sys
+
+
+if sys.version_info < (3,):
+ str_cls = unicode # noqa
+ byte_cls = str
+ int_types = (int, long) # noqa
+
+ def bytes_to_list(byte_string):
+ return [ord(b) for b in byte_string]
+
+ chr_cls = chr
+
+else:
+ str_cls = str
+ byte_cls = bytes
+ int_types = int
+
+ bytes_to_list = list
+
+ def chr_cls(num):
+ return bytes([num])
+
+
+def type_name(value):
+ """
+ Returns a user-readable name for the type of an object
+
+ :param value:
+ A value to get the type name of
+
+ :return:
+ A unicode string of the object's type name
+ """
+
+ if inspect.isclass(value):
+ cls = value
+ else:
+ cls = value.__class__
+ if cls.__module__ in set(['builtins', '__builtin__']):
+ return cls.__name__
+ return '%s.%s' % (cls.__module__, cls.__name__)
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/algos.py b/contrib/python/asn1crypto/py3/asn1crypto/algos.py
new file mode 100644
index 0000000000..cdd0020a32
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/algos.py
@@ -0,0 +1,1189 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for various algorithms using in various aspects of public
+key cryptography. Exports the following items:
+
+ - AlgorithmIdentifier()
+ - AnyAlgorithmIdentifier()
+ - DigestAlgorithm()
+ - DigestInfo()
+ - DSASignature()
+ - EncryptionAlgorithm()
+ - HmacAlgorithm()
+ - KdfAlgorithm()
+ - Pkcs5MacAlgorithm()
+ - SignedDigestAlgorithm()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ._errors import unwrap
+from ._int import fill_width
+from .util import int_from_bytes, int_to_bytes
+from .core import (
+ Any,
+ Choice,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ Void,
+)
+
+
+# Structures and OIDs in this file are pulled from
+# https://tools.ietf.org/html/rfc3279, https://tools.ietf.org/html/rfc4055,
+# https://tools.ietf.org/html/rfc5758, https://tools.ietf.org/html/rfc7292,
+# http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
+
+class AlgorithmIdentifier(Sequence):
+ _fields = [
+ ('algorithm', ObjectIdentifier),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class _ForceNullParameters(object):
+ """
+ Various structures based on AlgorithmIdentifier require that the parameters
+ field be core.Null() for certain OIDs. This mixin ensures that happens.
+ """
+
+ # The following attribute, plus the parameters spec callback and custom
+ # __setitem__ are all to handle a situation where parameters should not be
+ # optional and must be Null for certain OIDs. More info at
+ # https://tools.ietf.org/html/rfc4055#page-15 and
+ # https://tools.ietf.org/html/rfc4055#section-2.1
+ _null_algos = set([
+ '1.2.840.113549.1.1.1', # rsassa_pkcs1v15 / rsaes_pkcs1v15 / rsa
+ '1.2.840.113549.1.1.11', # sha256_rsa
+ '1.2.840.113549.1.1.12', # sha384_rsa
+ '1.2.840.113549.1.1.13', # sha512_rsa
+ '1.2.840.113549.1.1.14', # sha224_rsa
+ '1.3.14.3.2.26', # sha1
+ '2.16.840.1.101.3.4.2.4', # sha224
+ '2.16.840.1.101.3.4.2.1', # sha256
+ '2.16.840.1.101.3.4.2.2', # sha384
+ '2.16.840.1.101.3.4.2.3', # sha512
+ ])
+
+ def _parameters_spec(self):
+ if self._oid_pair == ('algorithm', 'parameters'):
+ algo = self['algorithm'].native
+ if algo in self._oid_specs:
+ return self._oid_specs[algo]
+
+ if self['algorithm'].dotted in self._null_algos:
+ return Null
+
+ return None
+
+ _spec_callbacks = {
+ 'parameters': _parameters_spec
+ }
+
+ # We have to override this since the spec callback uses the value of
+ # algorithm to determine the parameter spec, however default values are
+ # assigned before setting a field, so a default value can't be based on
+ # another field value (unless it is a default also). Thus we have to
+ # manually check to see if the algorithm was set and parameters is unset,
+ # and then fix the value as appropriate.
+ def __setitem__(self, key, value):
+ res = super(_ForceNullParameters, self).__setitem__(key, value)
+ if key != 'algorithm':
+ return res
+ if self['algorithm'].dotted not in self._null_algos:
+ return res
+ if self['parameters'].__class__ != Void:
+ return res
+ self['parameters'] = Null()
+ return res
+
+
+class HmacAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.10': 'des_mac',
+ '1.2.840.113549.2.7': 'sha1',
+ '1.2.840.113549.2.8': 'sha224',
+ '1.2.840.113549.2.9': 'sha256',
+ '1.2.840.113549.2.10': 'sha384',
+ '1.2.840.113549.2.11': 'sha512',
+ '1.2.840.113549.2.12': 'sha512_224',
+ '1.2.840.113549.2.13': 'sha512_256',
+ '2.16.840.1.101.3.4.2.13': 'sha3_224',
+ '2.16.840.1.101.3.4.2.14': 'sha3_256',
+ '2.16.840.1.101.3.4.2.15': 'sha3_384',
+ '2.16.840.1.101.3.4.2.16': 'sha3_512',
+ }
+
+
+class HmacAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', HmacAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class DigestAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.2.2': 'md2',
+ '1.2.840.113549.2.5': 'md5',
+ '1.3.14.3.2.26': 'sha1',
+ '2.16.840.1.101.3.4.2.4': 'sha224',
+ '2.16.840.1.101.3.4.2.1': 'sha256',
+ '2.16.840.1.101.3.4.2.2': 'sha384',
+ '2.16.840.1.101.3.4.2.3': 'sha512',
+ '2.16.840.1.101.3.4.2.5': 'sha512_224',
+ '2.16.840.1.101.3.4.2.6': 'sha512_256',
+ '2.16.840.1.101.3.4.2.7': 'sha3_224',
+ '2.16.840.1.101.3.4.2.8': 'sha3_256',
+ '2.16.840.1.101.3.4.2.9': 'sha3_384',
+ '2.16.840.1.101.3.4.2.10': 'sha3_512',
+ '2.16.840.1.101.3.4.2.11': 'shake128',
+ '2.16.840.1.101.3.4.2.12': 'shake256',
+ '2.16.840.1.101.3.4.2.17': 'shake128_len',
+ '2.16.840.1.101.3.4.2.18': 'shake256_len',
+ }
+
+
+class DigestAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', DigestAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+# This structure is what is signed with a SignedDigestAlgorithm
+class DigestInfo(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm),
+ ('digest', OctetString),
+ ]
+
+
+class MaskGenAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.8': 'mgf1',
+ }
+
+
+class MaskGenAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', MaskGenAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'mgf1': DigestAlgorithm
+ }
+
+
+class TrailerField(Integer):
+ _map = {
+ 1: 'trailer_field_bc',
+ }
+
+
+class RSASSAPSSParams(Sequence):
+ _fields = [
+ (
+ 'hash_algorithm',
+ DigestAlgorithm,
+ {
+ 'explicit': 0,
+ 'default': {'algorithm': 'sha1'},
+ }
+ ),
+ (
+ 'mask_gen_algorithm',
+ MaskGenAlgorithm,
+ {
+ 'explicit': 1,
+ 'default': {
+ 'algorithm': 'mgf1',
+ 'parameters': {'algorithm': 'sha1'},
+ },
+ }
+ ),
+ (
+ 'salt_length',
+ Integer,
+ {
+ 'explicit': 2,
+ 'default': 20,
+ }
+ ),
+ (
+ 'trailer_field',
+ TrailerField,
+ {
+ 'explicit': 3,
+ 'default': 'trailer_field_bc',
+ }
+ ),
+ ]
+
+
+class SignedDigestAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.3': 'md5_rsa',
+ '1.3.14.3.2.29': 'sha1_rsa',
+ '1.3.14.7.2.3.1': 'md2_rsa',
+ '1.2.840.113549.1.1.2': 'md2_rsa',
+ '1.2.840.113549.1.1.4': 'md5_rsa',
+ '1.2.840.113549.1.1.5': 'sha1_rsa',
+ '1.2.840.113549.1.1.14': 'sha224_rsa',
+ '1.2.840.113549.1.1.11': 'sha256_rsa',
+ '1.2.840.113549.1.1.12': 'sha384_rsa',
+ '1.2.840.113549.1.1.13': 'sha512_rsa',
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ '1.2.840.10040.4.3': 'sha1_dsa',
+ '1.3.14.3.2.13': 'sha1_dsa',
+ '1.3.14.3.2.27': 'sha1_dsa',
+ '2.16.840.1.101.3.4.3.1': 'sha224_dsa',
+ '2.16.840.1.101.3.4.3.2': 'sha256_dsa',
+ '1.2.840.10045.4.1': 'sha1_ecdsa',
+ '1.2.840.10045.4.3.1': 'sha224_ecdsa',
+ '1.2.840.10045.4.3.2': 'sha256_ecdsa',
+ '1.2.840.10045.4.3.3': 'sha384_ecdsa',
+ '1.2.840.10045.4.3.4': 'sha512_ecdsa',
+ '2.16.840.1.101.3.4.3.9': 'sha3_224_ecdsa',
+ '2.16.840.1.101.3.4.3.10': 'sha3_256_ecdsa',
+ '2.16.840.1.101.3.4.3.11': 'sha3_384_ecdsa',
+ '2.16.840.1.101.3.4.3.12': 'sha3_512_ecdsa',
+ # For when the digest is specified elsewhere in a Sequence
+ '1.2.840.113549.1.1.1': 'rsassa_pkcs1v15',
+ '1.2.840.10040.4.1': 'dsa',
+ '1.2.840.10045.4': 'ecdsa',
+ # RFC 8410 -- https://tools.ietf.org/html/rfc8410
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+ _reverse_map = {
+ 'dsa': '1.2.840.10040.4.1',
+ 'ecdsa': '1.2.840.10045.4',
+ 'md2_rsa': '1.2.840.113549.1.1.2',
+ 'md5_rsa': '1.2.840.113549.1.1.4',
+ 'rsassa_pkcs1v15': '1.2.840.113549.1.1.1',
+ 'rsassa_pss': '1.2.840.113549.1.1.10',
+ 'sha1_dsa': '1.2.840.10040.4.3',
+ 'sha1_ecdsa': '1.2.840.10045.4.1',
+ 'sha1_rsa': '1.2.840.113549.1.1.5',
+ 'sha224_dsa': '2.16.840.1.101.3.4.3.1',
+ 'sha224_ecdsa': '1.2.840.10045.4.3.1',
+ 'sha224_rsa': '1.2.840.113549.1.1.14',
+ 'sha256_dsa': '2.16.840.1.101.3.4.3.2',
+ 'sha256_ecdsa': '1.2.840.10045.4.3.2',
+ 'sha256_rsa': '1.2.840.113549.1.1.11',
+ 'sha384_ecdsa': '1.2.840.10045.4.3.3',
+ 'sha384_rsa': '1.2.840.113549.1.1.12',
+ 'sha512_ecdsa': '1.2.840.10045.4.3.4',
+ 'sha512_rsa': '1.2.840.113549.1.1.13',
+ 'sha3_224_ecdsa': '2.16.840.1.101.3.4.3.9',
+ 'sha3_256_ecdsa': '2.16.840.1.101.3.4.3.10',
+ 'sha3_384_ecdsa': '2.16.840.1.101.3.4.3.11',
+ 'sha3_512_ecdsa': '2.16.840.1.101.3.4.3.12',
+ 'ed25519': '1.3.101.112',
+ 'ed448': '1.3.101.113',
+ }
+
+
+class SignedDigestAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', SignedDigestAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+ @property
+ def signature_algo(self):
+ """
+ :return:
+ A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa",
+ "ecdsa", "ed25519" or "ed448"
+ """
+
+ algorithm = self['algorithm'].native
+
+ algo_map = {
+ 'md2_rsa': 'rsassa_pkcs1v15',
+ 'md5_rsa': 'rsassa_pkcs1v15',
+ 'sha1_rsa': 'rsassa_pkcs1v15',
+ 'sha224_rsa': 'rsassa_pkcs1v15',
+ 'sha256_rsa': 'rsassa_pkcs1v15',
+ 'sha384_rsa': 'rsassa_pkcs1v15',
+ 'sha512_rsa': 'rsassa_pkcs1v15',
+ 'rsassa_pkcs1v15': 'rsassa_pkcs1v15',
+ 'rsassa_pss': 'rsassa_pss',
+ 'sha1_dsa': 'dsa',
+ 'sha224_dsa': 'dsa',
+ 'sha256_dsa': 'dsa',
+ 'dsa': 'dsa',
+ 'sha1_ecdsa': 'ecdsa',
+ 'sha224_ecdsa': 'ecdsa',
+ 'sha256_ecdsa': 'ecdsa',
+ 'sha384_ecdsa': 'ecdsa',
+ 'sha512_ecdsa': 'ecdsa',
+ 'sha3_224_ecdsa': 'ecdsa',
+ 'sha3_256_ecdsa': 'ecdsa',
+ 'sha3_384_ecdsa': 'ecdsa',
+ 'sha3_512_ecdsa': 'ecdsa',
+ 'ecdsa': 'ecdsa',
+ 'ed25519': 'ed25519',
+ 'ed448': 'ed448',
+ }
+ if algorithm in algo_map:
+ return algo_map[algorithm]
+
+ raise ValueError(unwrap(
+ '''
+ Signature algorithm not known for %s
+ ''',
+ algorithm
+ ))
+
+ @property
+ def hash_algo(self):
+ """
+ :return:
+ A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
+ "sha384", "sha512", "sha512_224", "sha512_256" or "shake256"
+ """
+
+ algorithm = self['algorithm'].native
+
+ algo_map = {
+ 'md2_rsa': 'md2',
+ 'md5_rsa': 'md5',
+ 'sha1_rsa': 'sha1',
+ 'sha224_rsa': 'sha224',
+ 'sha256_rsa': 'sha256',
+ 'sha384_rsa': 'sha384',
+ 'sha512_rsa': 'sha512',
+ 'sha1_dsa': 'sha1',
+ 'sha224_dsa': 'sha224',
+ 'sha256_dsa': 'sha256',
+ 'sha1_ecdsa': 'sha1',
+ 'sha224_ecdsa': 'sha224',
+ 'sha256_ecdsa': 'sha256',
+ 'sha384_ecdsa': 'sha384',
+ 'sha512_ecdsa': 'sha512',
+ 'ed25519': 'sha512',
+ 'ed448': 'shake256',
+ }
+ if algorithm in algo_map:
+ return algo_map[algorithm]
+
+ if algorithm == 'rsassa_pss':
+ return self['parameters']['hash_algorithm']['algorithm'].native
+
+ raise ValueError(unwrap(
+ '''
+ Hash algorithm not known for %s
+ ''',
+ algorithm
+ ))
+
+
+class Pbkdf2Salt(Choice):
+ _alternatives = [
+ ('specified', OctetString),
+ ('other_source', AlgorithmIdentifier),
+ ]
+
+
+class Pbkdf2Params(Sequence):
+ _fields = [
+ ('salt', Pbkdf2Salt),
+ ('iteration_count', Integer),
+ ('key_length', Integer, {'optional': True}),
+ ('prf', HmacAlgorithm, {'default': {'algorithm': 'sha1'}}),
+ ]
+
+
+class KdfAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.5.12': 'pbkdf2'
+ }
+
+
+class KdfAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', KdfAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'pbkdf2': Pbkdf2Params
+ }
+
+
+class DHParameters(Sequence):
+ """
+ Original Name: DHParameter
+ Source: ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc section 9
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('g', Integer),
+ ('private_value_length', Integer, {'optional': True}),
+ ]
+
+
+class KeyExchangeAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.3.1': 'dh',
+ }
+
+
+class KeyExchangeAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', KeyExchangeAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dh': DHParameters,
+ }
+
+
+class Rc2Params(Sequence):
+ _fields = [
+ ('rc2_parameter_version', Integer, {'optional': True}),
+ ('iv', OctetString),
+ ]
+
+
+class Rc5ParamVersion(Integer):
+ _map = {
+ 16: 'v1-0'
+ }
+
+
+class Rc5Params(Sequence):
+ _fields = [
+ ('version', Rc5ParamVersion),
+ ('rounds', Integer),
+ ('block_size_in_bits', Integer),
+ ('iv', OctetString, {'optional': True}),
+ ]
+
+
+class Pbes1Params(Sequence):
+ _fields = [
+ ('salt', OctetString),
+ ('iterations', Integer),
+ ]
+
+
+class CcmParams(Sequence):
+ # https://tools.ietf.org/html/rfc5084
+ # aes_ICVlen: 4 | 6 | 8 | 10 | 12 | 14 | 16
+ _fields = [
+ ('aes_nonce', OctetString),
+ ('aes_icvlen', Integer),
+ ]
+
+
+class PSourceAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.9': 'p_specified',
+ }
+
+
+class PSourceAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', PSourceAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'p_specified': OctetString
+ }
+
+
+class RSAESOAEPParams(Sequence):
+ _fields = [
+ (
+ 'hash_algorithm',
+ DigestAlgorithm,
+ {
+ 'explicit': 0,
+ 'default': {'algorithm': 'sha1'}
+ }
+ ),
+ (
+ 'mask_gen_algorithm',
+ MaskGenAlgorithm,
+ {
+ 'explicit': 1,
+ 'default': {
+ 'algorithm': 'mgf1',
+ 'parameters': {'algorithm': 'sha1'}
+ }
+ }
+ ),
+ (
+ 'p_source_algorithm',
+ PSourceAlgorithm,
+ {
+ 'explicit': 2,
+ 'default': {
+ 'algorithm': 'p_specified',
+ 'parameters': b''
+ }
+ }
+ ),
+ ]
+
+
+class DSASignature(Sequence):
+ """
+ An ASN.1 class for translating between the OS crypto library's
+ representation of an (EC)DSA signature and the ASN.1 structure that is part
+ of various RFCs.
+
+ Original Name: DSS-Sig-Value
+ Source: https://tools.ietf.org/html/rfc3279#section-2.2.2
+ """
+
+ _fields = [
+ ('r', Integer),
+ ('s', Integer),
+ ]
+
+ @classmethod
+ def from_p1363(cls, data):
+ """
+ Reads a signature from a byte string encoding accordint to IEEE P1363,
+ which is used by Microsoft's BCryptSignHash() function.
+
+ :param data:
+ A byte string from BCryptSignHash()
+
+ :return:
+ A DSASignature object
+ """
+
+ r = int_from_bytes(data[0:len(data) // 2])
+ s = int_from_bytes(data[len(data) // 2:])
+ return cls({'r': r, 's': s})
+
+ def to_p1363(self):
+ """
+ Dumps a signature to a byte string compatible with Microsoft's
+ BCryptVerifySignature() function.
+
+ :return:
+ A byte string compatible with BCryptVerifySignature()
+ """
+
+ r_bytes = int_to_bytes(self['r'].native)
+ s_bytes = int_to_bytes(self['s'].native)
+
+ int_byte_length = max(len(r_bytes), len(s_bytes))
+ r_bytes = fill_width(r_bytes, int_byte_length)
+ s_bytes = fill_width(s_bytes, int_byte_length)
+
+ return r_bytes + s_bytes
+
+
+class EncryptionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.3.14.3.2.7': 'des',
+ '1.2.840.113549.3.7': 'tripledes_3key',
+ '1.2.840.113549.3.2': 'rc2',
+ '1.2.840.113549.3.4': 'rc4',
+ '1.2.840.113549.3.9': 'rc5',
+ # From http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html#AES
+ '2.16.840.1.101.3.4.1.1': 'aes128_ecb',
+ '2.16.840.1.101.3.4.1.2': 'aes128_cbc',
+ '2.16.840.1.101.3.4.1.3': 'aes128_ofb',
+ '2.16.840.1.101.3.4.1.4': 'aes128_cfb',
+ '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
+ '2.16.840.1.101.3.4.1.6': 'aes128_gcm',
+ '2.16.840.1.101.3.4.1.7': 'aes128_ccm',
+ '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
+ '2.16.840.1.101.3.4.1.21': 'aes192_ecb',
+ '2.16.840.1.101.3.4.1.22': 'aes192_cbc',
+ '2.16.840.1.101.3.4.1.23': 'aes192_ofb',
+ '2.16.840.1.101.3.4.1.24': 'aes192_cfb',
+ '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
+ '2.16.840.1.101.3.4.1.26': 'aes192_gcm',
+ '2.16.840.1.101.3.4.1.27': 'aes192_ccm',
+ '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
+ '2.16.840.1.101.3.4.1.41': 'aes256_ecb',
+ '2.16.840.1.101.3.4.1.42': 'aes256_cbc',
+ '2.16.840.1.101.3.4.1.43': 'aes256_ofb',
+ '2.16.840.1.101.3.4.1.44': 'aes256_cfb',
+ '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
+ '2.16.840.1.101.3.4.1.46': 'aes256_gcm',
+ '2.16.840.1.101.3.4.1.47': 'aes256_ccm',
+ '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
+ # From PKCS#5
+ '1.2.840.113549.1.5.13': 'pbes2',
+ '1.2.840.113549.1.5.1': 'pbes1_md2_des',
+ '1.2.840.113549.1.5.3': 'pbes1_md5_des',
+ '1.2.840.113549.1.5.4': 'pbes1_md2_rc2',
+ '1.2.840.113549.1.5.6': 'pbes1_md5_rc2',
+ '1.2.840.113549.1.5.10': 'pbes1_sha1_des',
+ '1.2.840.113549.1.5.11': 'pbes1_sha1_rc2',
+ # From PKCS#12
+ '1.2.840.113549.1.12.1.1': 'pkcs12_sha1_rc4_128',
+ '1.2.840.113549.1.12.1.2': 'pkcs12_sha1_rc4_40',
+ '1.2.840.113549.1.12.1.3': 'pkcs12_sha1_tripledes_3key',
+ '1.2.840.113549.1.12.1.4': 'pkcs12_sha1_tripledes_2key',
+ '1.2.840.113549.1.12.1.5': 'pkcs12_sha1_rc2_128',
+ '1.2.840.113549.1.12.1.6': 'pkcs12_sha1_rc2_40',
+ # PKCS#1 v2.2
+ '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ }
+
+
+class EncryptionAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', EncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'des': OctetString,
+ 'tripledes_3key': OctetString,
+ 'rc2': Rc2Params,
+ 'rc5': Rc5Params,
+ 'aes128_cbc': OctetString,
+ 'aes192_cbc': OctetString,
+ 'aes256_cbc': OctetString,
+ 'aes128_ofb': OctetString,
+ 'aes192_ofb': OctetString,
+ 'aes256_ofb': OctetString,
+ # From RFC5084
+ 'aes128_ccm': CcmParams,
+ 'aes192_ccm': CcmParams,
+ 'aes256_ccm': CcmParams,
+ # From PKCS#5
+ 'pbes1_md2_des': Pbes1Params,
+ 'pbes1_md5_des': Pbes1Params,
+ 'pbes1_md2_rc2': Pbes1Params,
+ 'pbes1_md5_rc2': Pbes1Params,
+ 'pbes1_sha1_des': Pbes1Params,
+ 'pbes1_sha1_rc2': Pbes1Params,
+ # From PKCS#12
+ 'pkcs12_sha1_rc4_128': Pbes1Params,
+ 'pkcs12_sha1_rc4_40': Pbes1Params,
+ 'pkcs12_sha1_tripledes_3key': Pbes1Params,
+ 'pkcs12_sha1_tripledes_2key': Pbes1Params,
+ 'pkcs12_sha1_rc2_128': Pbes1Params,
+ 'pkcs12_sha1_rc2_40': Pbes1Params,
+ # PKCS#1 v2.2
+ 'rsaes_oaep': RSAESOAEPParams,
+ }
+
+ @property
+ def kdf(self):
+ """
+ Returns the name of the key derivation function to use.
+
+ :return:
+ A unicode from of one of the following: "pbkdf1", "pbkdf2",
+ "pkcs12_kdf"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['algorithm'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ encryption_algo, _ = encryption_algo.split('_', 1)
+
+ if encryption_algo == 'pbes1':
+ return 'pbkdf1'
+
+ if encryption_algo == 'pkcs12':
+ return 'pkcs12_kdf'
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_hmac(self):
+ """
+ Returns the HMAC algorithm to use with the KDF.
+
+ :return:
+ A unicode string of one of the following: "md2", "md5", "sha1",
+ "sha224", "sha256", "sha384", "sha512"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['parameters']['prf']['algorithm'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ _, hmac_algo, _ = encryption_algo.split('_', 2)
+ return hmac_algo
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation hmac algorithm
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_salt(self):
+ """
+ Returns the byte string to use as the salt for the KDF.
+
+ :return:
+ A byte string
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ salt = self['parameters']['key_derivation_func']['parameters']['salt']
+
+ if salt.name == 'other_source':
+ raise ValueError(unwrap(
+ '''
+ Can not determine key derivation salt - the
+ reserved-for-future-use other source salt choice was
+ specified in the PBKDF2 params structure
+ '''
+ ))
+
+ return salt.native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ return self['parameters']['salt'].native
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation salt
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def kdf_iterations(self):
+ """
+ Returns the number of iterations that should be run via the KDF.
+
+ :return:
+ An integer
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['key_derivation_func']['parameters']['iteration_count'].native
+
+ if encryption_algo.find('.') == -1:
+ if encryption_algo.find('_') != -1:
+ return self['parameters']['iterations'].native
+
+ raise ValueError(unwrap(
+ '''
+ Encryption algorithm "%s" does not have a registered key
+ derivation function
+ ''',
+ encryption_algo
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s", can not determine key
+ derivation iterations
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def key_length(self):
+ """
+ Returns the key length to pass to the cipher/kdf. The PKCS#5 spec does
+ not specify a way to store the RC5 key length, however this tends not
+ to be a problem since OpenSSL does not support RC5 in PKCS#8 and OS X
+ does not provide an RC5 cipher for use in the Security Transforms
+ library.
+
+ :raises:
+ ValueError - when the key length can not be determined
+
+ :return:
+ An integer representing the length in bytes
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:3] == 'aes':
+ return {
+ 'aes128_': 16,
+ 'aes192_': 24,
+ 'aes256_': 32,
+ }[encryption_algo[0:7]]
+
+ cipher_lengths = {
+ 'des': 8,
+ 'tripledes_3key': 24,
+ }
+
+ if encryption_algo in cipher_lengths:
+ return cipher_lengths[encryption_algo]
+
+ if encryption_algo == 'rc2':
+ rc2_parameter_version = self['parameters']['rc2_parameter_version'].native
+
+ # See page 24 of
+ # http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf
+ encoded_key_bits_map = {
+ 160: 5, # 40-bit
+ 120: 8, # 64-bit
+ 58: 16, # 128-bit
+ }
+
+ if rc2_parameter_version in encoded_key_bits_map:
+ return encoded_key_bits_map[rc2_parameter_version]
+
+ if rc2_parameter_version >= 256:
+ return rc2_parameter_version
+
+ if rc2_parameter_version is None:
+ return 4 # 32-bit default
+
+ raise ValueError(unwrap(
+ '''
+ Invalid RC2 parameter version found in EncryptionAlgorithm
+ parameters
+ '''
+ ))
+
+ if encryption_algo == 'pbes2':
+ key_length = self['parameters']['key_derivation_func']['parameters']['key_length'].native
+ if key_length is not None:
+ return key_length
+
+ # If the KDF params don't specify the key size, we can infer it from
+ # the encryption scheme for all schemes except for RC5. However, in
+ # practical terms, neither OpenSSL or OS X support RC5 for PKCS#8
+ # so it is unlikely to be an issue that is run into.
+
+ return self['parameters']['encryption_scheme'].key_length
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 8,
+ 'pbes1_md5_des': 8,
+ 'pbes1_md2_rc2': 8,
+ 'pbes1_md5_rc2': 8,
+ 'pbes1_sha1_des': 8,
+ 'pbes1_sha1_rc2': 8,
+ 'pkcs12_sha1_rc4_128': 16,
+ 'pkcs12_sha1_rc4_40': 5,
+ 'pkcs12_sha1_tripledes_3key': 24,
+ 'pkcs12_sha1_tripledes_2key': 16,
+ 'pkcs12_sha1_rc2_128': 16,
+ 'pkcs12_sha1_rc2_40': 5,
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_mode(self):
+ """
+ Returns the name of the encryption mode to use.
+
+ :return:
+ A unicode string from one of the following: "cbc", "ecb", "ofb",
+ "cfb", "wrap", "gcm", "ccm", "wrap_pad"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return encryption_algo[7:]
+
+ if encryption_algo[0:6] == 'pbes1_':
+ return 'cbc'
+
+ if encryption_algo[0:7] == 'pkcs12_':
+ return 'cbc'
+
+ if encryption_algo in set(['des', 'tripledes_3key', 'rc2', 'rc5']):
+ return 'cbc'
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_mode
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_cipher(self):
+ """
+ Returns the name of the symmetric encryption cipher to use. The key
+ length can be retrieved via the .key_length property to disabiguate
+ between different variations of TripleDES, AES, and the RC* ciphers.
+
+ :return:
+ A unicode string from one of the following: "rc2", "rc5", "des",
+ "tripledes", "aes"
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return 'aes'
+
+ if encryption_algo in set(['des', 'rc2', 'rc5']):
+ return encryption_algo
+
+ if encryption_algo == 'tripledes_3key':
+ return 'tripledes'
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_cipher
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 'des',
+ 'pbes1_md5_des': 'des',
+ 'pbes1_md2_rc2': 'rc2',
+ 'pbes1_md5_rc2': 'rc2',
+ 'pbes1_sha1_des': 'des',
+ 'pbes1_sha1_rc2': 'rc2',
+ 'pkcs12_sha1_rc4_128': 'rc4',
+ 'pkcs12_sha1_rc4_40': 'rc4',
+ 'pkcs12_sha1_tripledes_3key': 'tripledes',
+ 'pkcs12_sha1_tripledes_2key': 'tripledes',
+ 'pkcs12_sha1_rc2_128': 'rc2',
+ 'pkcs12_sha1_rc2_40': 'rc2',
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_block_size(self):
+ """
+ Returns the block size of the encryption cipher, in bytes.
+
+ :return:
+ An integer that is the block size in bytes
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo[0:7] in set(['aes128_', 'aes192_', 'aes256_']):
+ return 16
+
+ cipher_map = {
+ 'des': 8,
+ 'tripledes_3key': 8,
+ 'rc2': 8,
+ }
+ if encryption_algo in cipher_map:
+ return cipher_map[encryption_algo]
+
+ if encryption_algo == 'rc5':
+ return self['parameters']['block_size_in_bits'].native // 8
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_block_size
+
+ if encryption_algo.find('.') == -1:
+ return {
+ 'pbes1_md2_des': 8,
+ 'pbes1_md5_des': 8,
+ 'pbes1_md2_rc2': 8,
+ 'pbes1_md5_rc2': 8,
+ 'pbes1_sha1_des': 8,
+ 'pbes1_sha1_rc2': 8,
+ 'pkcs12_sha1_rc4_128': 0,
+ 'pkcs12_sha1_rc4_40': 0,
+ 'pkcs12_sha1_tripledes_3key': 8,
+ 'pkcs12_sha1_tripledes_2key': 8,
+ 'pkcs12_sha1_rc2_128': 8,
+ 'pkcs12_sha1_rc2_40': 8,
+ }[encryption_algo]
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+ @property
+ def encryption_iv(self):
+ """
+ Returns the byte string of the initialization vector for the encryption
+ scheme. Only the PBES2 stores the IV in the params. For PBES1, the IV
+ is derived from the KDF and this property will return None.
+
+ :return:
+ A byte string or None
+ """
+
+ encryption_algo = self['algorithm'].native
+
+ if encryption_algo in set(['rc2', 'rc5']):
+ return self['parameters']['iv'].native
+
+ # For DES/Triple DES and AES the IV is the entirety of the parameters
+ octet_string_iv_oids = set([
+ 'des',
+ 'tripledes_3key',
+ 'aes128_cbc',
+ 'aes192_cbc',
+ 'aes256_cbc',
+ 'aes128_ofb',
+ 'aes192_ofb',
+ 'aes256_ofb',
+ ])
+ if encryption_algo in octet_string_iv_oids:
+ return self['parameters'].native
+
+ if encryption_algo == 'pbes2':
+ return self['parameters']['encryption_scheme'].encryption_iv
+
+ # All of the PBES1 algos use their KDF to create the IV. For the pbkdf1,
+ # the KDF is told to generate a key that is an extra 8 bytes long, and
+ # that is used for the IV. For the PKCS#12 KDF, it is called with an id
+ # of 2 to generate the IV. In either case, we can't return the IV
+ # without knowing the user's password.
+ if encryption_algo.find('.') == -1:
+ return None
+
+ raise ValueError(unwrap(
+ '''
+ Unrecognized encryption algorithm "%s"
+ ''',
+ encryption_algo
+ ))
+
+
+class Pbes2Params(Sequence):
+ _fields = [
+ ('key_derivation_func', KdfAlgorithm),
+ ('encryption_scheme', EncryptionAlgorithm),
+ ]
+
+
+class Pbmac1Params(Sequence):
+ _fields = [
+ ('key_derivation_func', KdfAlgorithm),
+ ('message_auth_scheme', HmacAlgorithm),
+ ]
+
+
+class Pkcs5MacId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.5.14': 'pbmac1',
+ }
+
+
+class Pkcs5MacAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', Pkcs5MacId),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'pbmac1': Pbmac1Params,
+ }
+
+
+EncryptionAlgorithm._oid_specs['pbes2'] = Pbes2Params
+
+
+class AnyAlgorithmId(ObjectIdentifier):
+ _map = {}
+
+ def _setup(self):
+ _map = self.__class__._map
+ for other_cls in (EncryptionAlgorithmId, SignedDigestAlgorithmId, DigestAlgorithmId):
+ for oid, name in other_cls._map.items():
+ _map[oid] = name
+
+
+class AnyAlgorithmIdentifier(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', AnyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {}
+
+ def _setup(self):
+ Sequence._setup(self)
+ specs = self.__class__._oid_specs
+ for other_cls in (EncryptionAlgorithm, SignedDigestAlgorithm):
+ for oid, spec in other_cls._oid_specs.items():
+ specs[oid] = spec
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/cms.py b/contrib/python/asn1crypto/py3/asn1crypto/cms.py
new file mode 100644
index 0000000000..c395b2274f
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/cms.py
@@ -0,0 +1,1003 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for cryptographic message syntax (CMS). Structures are also
+compatible with PKCS#7. Exports the following items:
+
+ - AuthenticatedData()
+ - AuthEnvelopedData()
+ - CompressedData()
+ - ContentInfo()
+ - DigestedData()
+ - EncryptedData()
+ - EnvelopedData()
+ - SignedAndEnvelopedData()
+ - SignedData()
+
+Other type classes are defined that help compose the types listed above.
+
+Most CMS structures in the wild are formatted as ContentInfo encapsulating one of the other types.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+try:
+ import zlib
+except (ImportError):
+ zlib = None
+
+from .algos import (
+ _ForceNullParameters,
+ DigestAlgorithm,
+ EncryptionAlgorithm,
+ EncryptionAlgorithmId,
+ HmacAlgorithm,
+ KdfAlgorithm,
+ RSAESOAEPParams,
+ SignedDigestAlgorithm,
+)
+from .core import (
+ Any,
+ BitString,
+ Choice,
+ Enumerated,
+ GeneralizedTime,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+ UTCTime,
+ UTF8String,
+)
+from .crl import CertificateList
+from .keys import PublicKeyInfo
+from .ocsp import OCSPResponse
+from .x509 import Attributes, Certificate, Extensions, GeneralName, GeneralNames, Name
+
+
+# These structures are taken from
+# ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-6.asc
+
+class ExtendedCertificateInfo(Sequence):
+ _fields = [
+ ('version', Integer),
+ ('certificate', Certificate),
+ ('attributes', Attributes),
+ ]
+
+
+class ExtendedCertificate(Sequence):
+ _fields = [
+ ('extended_certificate_info', ExtendedCertificateInfo),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+# These structures are taken from https://tools.ietf.org/html/rfc5652,
+# https://tools.ietf.org/html/rfc5083, http://tools.ietf.org/html/rfc2315,
+# https://tools.ietf.org/html/rfc5940, https://tools.ietf.org/html/rfc3274,
+# https://tools.ietf.org/html/rfc3281
+
+
+class CMSVersion(Integer):
+ _map = {
+ 0: 'v0',
+ 1: 'v1',
+ 2: 'v2',
+ 3: 'v3',
+ 4: 'v4',
+ 5: 'v5',
+ }
+
+
+class CMSAttributeType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.3': 'content_type',
+ '1.2.840.113549.1.9.4': 'message_digest',
+ '1.2.840.113549.1.9.5': 'signing_time',
+ '1.2.840.113549.1.9.6': 'counter_signature',
+ # https://datatracker.ietf.org/doc/html/rfc2633#section-2.5.2
+ '1.2.840.113549.1.9.15': 'smime_capabilities',
+ # https://tools.ietf.org/html/rfc2633#page-26
+ '1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
+ # https://tools.ietf.org/html/rfc3161#page-20
+ '1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token',
+ # https://tools.ietf.org/html/rfc6211#page-5
+ '1.2.840.113549.1.9.52': 'cms_algorithm_protection',
+ # https://docs.microsoft.com/en-us/previous-versions/hh968145(v%3Dvs.85)
+ '1.3.6.1.4.1.311.2.4.1': 'microsoft_nested_signature',
+ # Some places refer to this as SPC_RFC3161_OBJID, others szOID_RFC3161_counterSign.
+ # https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-crypt_algorithm_identifier
+ # refers to szOID_RFC3161_counterSign as "1.2.840.113549.1.9.16.1.4",
+ # but that OID is also called szOID_TIMESTAMP_TOKEN. Because of there being
+ # no canonical source for this OID, we give it our own name
+ '1.3.6.1.4.1.311.3.3.1': 'microsoft_time_stamp_token',
+ }
+
+
+class Time(Choice):
+ _alternatives = [
+ ('utc_time', UTCTime),
+ ('generalized_time', GeneralizedTime),
+ ]
+
+
+class ContentType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.7.1': 'data',
+ '1.2.840.113549.1.7.2': 'signed_data',
+ '1.2.840.113549.1.7.3': 'enveloped_data',
+ '1.2.840.113549.1.7.4': 'signed_and_enveloped_data',
+ '1.2.840.113549.1.7.5': 'digested_data',
+ '1.2.840.113549.1.7.6': 'encrypted_data',
+ '1.2.840.113549.1.9.16.1.2': 'authenticated_data',
+ '1.2.840.113549.1.9.16.1.9': 'compressed_data',
+ '1.2.840.113549.1.9.16.1.23': 'authenticated_enveloped_data',
+ }
+
+
+class CMSAlgorithmProtection(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm),
+ ('signature_algorithm', SignedDigestAlgorithm, {'implicit': 1, 'optional': True}),
+ ('mac_algorithm', HmacAlgorithm, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class SetOfContentType(SetOf):
+ _child_spec = ContentType
+
+
+class SetOfOctetString(SetOf):
+ _child_spec = OctetString
+
+
+class SetOfTime(SetOf):
+ _child_spec = Time
+
+
+class SetOfAny(SetOf):
+ _child_spec = Any
+
+
+class SetOfCMSAlgorithmProtection(SetOf):
+ _child_spec = CMSAlgorithmProtection
+
+
+class CMSAttribute(Sequence):
+ _fields = [
+ ('type', CMSAttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {}
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class CMSAttributes(SetOf):
+ _child_spec = CMSAttribute
+
+
+class IssuerSerial(Sequence):
+ _fields = [
+ ('issuer', GeneralNames),
+ ('serial', Integer),
+ ('issuer_uid', OctetBitString, {'optional': True}),
+ ]
+
+
+class AttCertVersion(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ }
+
+
+class AttCertSubject(Choice):
+ _alternatives = [
+ ('base_certificate_id', IssuerSerial, {'explicit': 0}),
+ ('subject_name', GeneralNames, {'explicit': 1}),
+ ]
+
+
+class AttCertValidityPeriod(Sequence):
+ _fields = [
+ ('not_before_time', GeneralizedTime),
+ ('not_after_time', GeneralizedTime),
+ ]
+
+
+class AttributeCertificateInfoV1(Sequence):
+ _fields = [
+ ('version', AttCertVersion, {'default': 'v1'}),
+ ('subject', AttCertSubject),
+ ('issuer', GeneralNames),
+ ('signature', SignedDigestAlgorithm),
+ ('serial_number', Integer),
+ ('att_cert_validity_period', AttCertValidityPeriod),
+ ('attributes', Attributes),
+ ('issuer_unique_id', OctetBitString, {'optional': True}),
+ ('extensions', Extensions, {'optional': True}),
+ ]
+
+
+class AttributeCertificateV1(Sequence):
+ _fields = [
+ ('ac_info', AttributeCertificateInfoV1),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+class DigestedObjectType(Enumerated):
+ _map = {
+ 0: 'public_key',
+ 1: 'public_key_cert',
+ 2: 'other_objy_types',
+ }
+
+
+class ObjectDigestInfo(Sequence):
+ _fields = [
+ ('digested_object_type', DigestedObjectType),
+ ('other_object_type_id', ObjectIdentifier, {'optional': True}),
+ ('digest_algorithm', DigestAlgorithm),
+ ('object_digest', OctetBitString),
+ ]
+
+
+class Holder(Sequence):
+ _fields = [
+ ('base_certificate_id', IssuerSerial, {'implicit': 0, 'optional': True}),
+ ('entity_name', GeneralNames, {'implicit': 1, 'optional': True}),
+ ('object_digest_info', ObjectDigestInfo, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class V2Form(Sequence):
+ _fields = [
+ ('issuer_name', GeneralNames, {'optional': True}),
+ ('base_certificate_id', IssuerSerial, {'explicit': 0, 'optional': True}),
+ ('object_digest_info', ObjectDigestInfo, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class AttCertIssuer(Choice):
+ _alternatives = [
+ ('v1_form', GeneralNames),
+ ('v2_form', V2Form, {'implicit': 0}),
+ ]
+
+
+class IetfAttrValue(Choice):
+ _alternatives = [
+ ('octets', OctetString),
+ ('oid', ObjectIdentifier),
+ ('string', UTF8String),
+ ]
+
+
+class IetfAttrValues(SequenceOf):
+ _child_spec = IetfAttrValue
+
+
+class IetfAttrSyntax(Sequence):
+ _fields = [
+ ('policy_authority', GeneralNames, {'implicit': 0, 'optional': True}),
+ ('values', IetfAttrValues),
+ ]
+
+
+class SetOfIetfAttrSyntax(SetOf):
+ _child_spec = IetfAttrSyntax
+
+
+class SvceAuthInfo(Sequence):
+ _fields = [
+ ('service', GeneralName),
+ ('ident', GeneralName),
+ ('auth_info', OctetString, {'optional': True}),
+ ]
+
+
+class SetOfSvceAuthInfo(SetOf):
+ _child_spec = SvceAuthInfo
+
+
+class RoleSyntax(Sequence):
+ _fields = [
+ ('role_authority', GeneralNames, {'implicit': 0, 'optional': True}),
+ ('role_name', GeneralName, {'explicit': 1}),
+ ]
+
+
+class SetOfRoleSyntax(SetOf):
+ _child_spec = RoleSyntax
+
+
+class ClassList(BitString):
+ _map = {
+ 0: 'unmarked',
+ 1: 'unclassified',
+ 2: 'restricted',
+ 3: 'confidential',
+ 4: 'secret',
+ 5: 'top_secret',
+ }
+
+
+class SecurityCategory(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier, {'implicit': 0}),
+ ('value', Any, {'explicit': 1}),
+ ]
+
+
+class SetOfSecurityCategory(SetOf):
+ _child_spec = SecurityCategory
+
+
+class Clearance(Sequence):
+ _fields = [
+ ('policy_id', ObjectIdentifier),
+ ('class_list', ClassList, {'default': set(['unclassified'])}),
+ ('security_categories', SetOfSecurityCategory, {'optional': True}),
+ ]
+
+
+class SetOfClearance(SetOf):
+ _child_spec = Clearance
+
+
+class BigTime(Sequence):
+ _fields = [
+ ('major', Integer),
+ ('fractional_seconds', Integer),
+ ('sign', Integer, {'optional': True}),
+ ]
+
+
+class LeapData(Sequence):
+ _fields = [
+ ('leap_time', BigTime),
+ ('action', Integer),
+ ]
+
+
+class SetOfLeapData(SetOf):
+ _child_spec = LeapData
+
+
+class TimingMetrics(Sequence):
+ _fields = [
+ ('ntp_time', BigTime),
+ ('offset', BigTime),
+ ('delay', BigTime),
+ ('expiration', BigTime),
+ ('leap_event', SetOfLeapData, {'optional': True}),
+ ]
+
+
+class SetOfTimingMetrics(SetOf):
+ _child_spec = TimingMetrics
+
+
+class TimingPolicy(Sequence):
+ _fields = [
+ ('policy_id', SequenceOf, {'spec': ObjectIdentifier}),
+ ('max_offset', BigTime, {'explicit': 0, 'optional': True}),
+ ('max_delay', BigTime, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class SetOfTimingPolicy(SetOf):
+ _child_spec = TimingPolicy
+
+
+class AttCertAttributeType(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.10.1': 'authentication_info',
+ '1.3.6.1.5.5.7.10.2': 'access_identity',
+ '1.3.6.1.5.5.7.10.3': 'charging_identity',
+ '1.3.6.1.5.5.7.10.4': 'group',
+ '2.5.4.72': 'role',
+ '2.5.4.55': 'clearance',
+ '1.3.6.1.4.1.601.10.4.1': 'timing_metrics',
+ '1.3.6.1.4.1.601.10.4.2': 'timing_policy',
+ }
+
+
+class AttCertAttribute(Sequence):
+ _fields = [
+ ('type', AttCertAttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {
+ 'authentication_info': SetOfSvceAuthInfo,
+ 'access_identity': SetOfSvceAuthInfo,
+ 'charging_identity': SetOfIetfAttrSyntax,
+ 'group': SetOfIetfAttrSyntax,
+ 'role': SetOfRoleSyntax,
+ 'clearance': SetOfClearance,
+ 'timing_metrics': SetOfTimingMetrics,
+ 'timing_policy': SetOfTimingPolicy,
+ }
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class AttCertAttributes(SequenceOf):
+ _child_spec = AttCertAttribute
+
+
+class AttributeCertificateInfoV2(Sequence):
+ _fields = [
+ ('version', AttCertVersion),
+ ('holder', Holder),
+ ('issuer', AttCertIssuer),
+ ('signature', SignedDigestAlgorithm),
+ ('serial_number', Integer),
+ ('att_cert_validity_period', AttCertValidityPeriod),
+ ('attributes', AttCertAttributes),
+ ('issuer_unique_id', OctetBitString, {'optional': True}),
+ ('extensions', Extensions, {'optional': True}),
+ ]
+
+
+class AttributeCertificateV2(Sequence):
+ # Handle the situation where a V2 cert is encoded as V1
+ _bad_tag = 1
+
+ _fields = [
+ ('ac_info', AttributeCertificateInfoV2),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+
+class OtherCertificateFormat(Sequence):
+ _fields = [
+ ('other_cert_format', ObjectIdentifier),
+ ('other_cert', Any),
+ ]
+
+
+class CertificateChoices(Choice):
+ _alternatives = [
+ ('certificate', Certificate),
+ ('extended_certificate', ExtendedCertificate, {'implicit': 0}),
+ ('v1_attr_cert', AttributeCertificateV1, {'implicit': 1}),
+ ('v2_attr_cert', AttributeCertificateV2, {'implicit': 2}),
+ ('other', OtherCertificateFormat, {'implicit': 3}),
+ ]
+
+ def validate(self, class_, tag, contents):
+ """
+ Ensures that the class and tag specified exist as an alternative. This
+ custom version fixes parsing broken encodings there a V2 attribute
+ # certificate is encoded as a V1
+
+ :param class_:
+ The integer class_ from the encoded value header
+
+ :param tag:
+ The integer tag from the encoded value header
+
+ :param contents:
+ A byte string of the contents of the value - used when the object
+ is explicitly tagged
+
+ :raises:
+ ValueError - when value is not a valid alternative
+ """
+
+ super(CertificateChoices, self).validate(class_, tag, contents)
+ if self._choice == 2:
+ if AttCertVersion.load(Sequence.load(contents)[0].dump()).native == 'v2':
+ self._choice = 3
+
+
+class CertificateSet(SetOf):
+ _child_spec = CertificateChoices
+
+
+class ContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content', Any, {'explicit': 0, 'optional': True}),
+ ]
+
+ _oid_pair = ('content_type', 'content')
+ _oid_specs = {}
+
+
+class SetOfContentInfo(SetOf):
+ _child_spec = ContentInfo
+
+
+class EncapsulatedContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content', ParsableOctetString, {'explicit': 0, 'optional': True}),
+ ]
+
+ _oid_pair = ('content_type', 'content')
+ _oid_specs = {}
+
+
+class IssuerAndSerialNumber(Sequence):
+ _fields = [
+ ('issuer', Name),
+ ('serial_number', Integer),
+ ]
+
+
+class SignerIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ]
+
+
+class DigestAlgorithms(SetOf):
+ _child_spec = DigestAlgorithm
+
+
+class CertificateRevocationLists(SetOf):
+ _child_spec = CertificateList
+
+
+class SCVPReqRes(Sequence):
+ _fields = [
+ ('request', ContentInfo, {'explicit': 0, 'optional': True}),
+ ('response', ContentInfo),
+ ]
+
+
+class OtherRevInfoFormatId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.16.2': 'ocsp_response',
+ '1.3.6.1.5.5.7.16.4': 'scvp',
+ }
+
+
+class OtherRevocationInfoFormat(Sequence):
+ _fields = [
+ ('other_rev_info_format', OtherRevInfoFormatId),
+ ('other_rev_info', Any),
+ ]
+
+ _oid_pair = ('other_rev_info_format', 'other_rev_info')
+ _oid_specs = {
+ 'ocsp_response': OCSPResponse,
+ 'scvp': SCVPReqRes,
+ }
+
+
+class RevocationInfoChoice(Choice):
+ _alternatives = [
+ ('crl', CertificateList),
+ ('other', OtherRevocationInfoFormat, {'implicit': 1}),
+ ]
+
+
+class RevocationInfoChoices(SetOf):
+ _child_spec = RevocationInfoChoice
+
+
+class SignerInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('sid', SignerIdentifier),
+ ('digest_algorithm', DigestAlgorithm),
+ ('signed_attrs', CMSAttributes, {'implicit': 0, 'optional': True}),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetString),
+ ('unsigned_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class SignerInfos(SetOf):
+ _child_spec = SignerInfo
+
+
+class SignedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('digest_algorithms', DigestAlgorithms),
+ ('encap_content_info', None),
+ ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
+ ('signer_infos', SignerInfos),
+ ]
+
+ def _encap_content_info_spec(self):
+ # If the encap_content_info is version v1, then this could be a PKCS#7
+ # structure, or a CMS structure. CMS wraps the encoded value in an
+ # Octet String tag.
+
+ # If the version is greater than 1, it is definite CMS
+ if self['version'].native != 'v1':
+ return EncapsulatedContentInfo
+
+ # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
+ # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
+ # allows Any
+ return ContentInfo
+
+ _spec_callbacks = {
+ 'encap_content_info': _encap_content_info_spec
+ }
+
+
+class OriginatorInfo(Sequence):
+ _fields = [
+ ('certs', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class RecipientIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ]
+
+
+class KeyEncryptionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.1.1': 'rsaes_pkcs1v15',
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ '2.16.840.1.101.3.4.1.5': 'aes128_wrap',
+ '2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
+ '2.16.840.1.101.3.4.1.25': 'aes192_wrap',
+ '2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
+ '2.16.840.1.101.3.4.1.45': 'aes256_wrap',
+ '2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
+ }
+
+ _reverse_map = {
+ 'rsa': '1.2.840.113549.1.1.1',
+ 'rsaes_pkcs1v15': '1.2.840.113549.1.1.1',
+ 'rsaes_oaep': '1.2.840.113549.1.1.7',
+ 'aes128_wrap': '2.16.840.1.101.3.4.1.5',
+ 'aes128_wrap_pad': '2.16.840.1.101.3.4.1.8',
+ 'aes192_wrap': '2.16.840.1.101.3.4.1.25',
+ 'aes192_wrap_pad': '2.16.840.1.101.3.4.1.28',
+ 'aes256_wrap': '2.16.840.1.101.3.4.1.45',
+ 'aes256_wrap_pad': '2.16.840.1.101.3.4.1.48',
+ }
+
+
+class KeyEncryptionAlgorithm(_ForceNullParameters, Sequence):
+ _fields = [
+ ('algorithm', KeyEncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'rsaes_oaep': RSAESOAEPParams,
+ }
+
+
+class KeyTransRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('rid', RecipientIdentifier),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class OriginatorIdentifierOrKey(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('subject_key_identifier', OctetString, {'implicit': 0}),
+ ('originator_key', PublicKeyInfo, {'implicit': 1}),
+ ]
+
+
+class OtherKeyAttribute(Sequence):
+ _fields = [
+ ('key_attr_id', ObjectIdentifier),
+ ('key_attr', Any),
+ ]
+
+
+class RecipientKeyIdentifier(Sequence):
+ _fields = [
+ ('subject_key_identifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class KeyAgreementRecipientIdentifier(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber),
+ ('r_key_id', RecipientKeyIdentifier, {'implicit': 0}),
+ ]
+
+
+class RecipientEncryptedKey(Sequence):
+ _fields = [
+ ('rid', KeyAgreementRecipientIdentifier),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class RecipientEncryptedKeys(SequenceOf):
+ _child_spec = RecipientEncryptedKey
+
+
+class KeyAgreeRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator', OriginatorIdentifierOrKey, {'explicit': 0}),
+ ('ukm', OctetString, {'explicit': 1, 'optional': True}),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('recipient_encrypted_keys', RecipientEncryptedKeys),
+ ]
+
+
+class KEKIdentifier(Sequence):
+ _fields = [
+ ('key_identifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class KEKRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('kekid', KEKIdentifier),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class PasswordRecipientInfo(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('key_derivation_algorithm', KdfAlgorithm, {'implicit': 0, 'optional': True}),
+ ('key_encryption_algorithm', KeyEncryptionAlgorithm),
+ ('encrypted_key', OctetString),
+ ]
+
+
+class OtherRecipientInfo(Sequence):
+ _fields = [
+ ('ori_type', ObjectIdentifier),
+ ('ori_value', Any),
+ ]
+
+
+class RecipientInfo(Choice):
+ _alternatives = [
+ ('ktri', KeyTransRecipientInfo),
+ ('kari', KeyAgreeRecipientInfo, {'implicit': 1}),
+ ('kekri', KEKRecipientInfo, {'implicit': 2}),
+ ('pwri', PasswordRecipientInfo, {'implicit': 3}),
+ ('ori', OtherRecipientInfo, {'implicit': 4}),
+ ]
+
+
+class RecipientInfos(SetOf):
+ _child_spec = RecipientInfo
+
+
+class EncryptedContentInfo(Sequence):
+ _fields = [
+ ('content_type', ContentType),
+ ('content_encryption_algorithm', EncryptionAlgorithm),
+ ('encrypted_content', OctetString, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class EnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class SignedAndEnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('recipient_infos', RecipientInfos),
+ ('digest_algorithms', DigestAlgorithms),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
+ ('crls', CertificateRevocationLists, {'implicit': 1, 'optional': True}),
+ ('signer_infos', SignerInfos),
+ ]
+
+
+class DigestedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('digest_algorithm', DigestAlgorithm),
+ ('encap_content_info', None),
+ ('digest', OctetString),
+ ]
+
+ def _encap_content_info_spec(self):
+ # If the encap_content_info is version v1, then this could be a PKCS#7
+ # structure, or a CMS structure. CMS wraps the encoded value in an
+ # Octet String tag.
+
+ # If the version is greater than 1, it is definite CMS
+ if self['version'].native != 'v1':
+ return EncapsulatedContentInfo
+
+ # Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
+ # CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
+ # allows Any
+ return ContentInfo
+
+ _spec_callbacks = {
+ 'encap_content_info': _encap_content_info_spec
+ }
+
+
+class EncryptedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('encrypted_content_info', EncryptedContentInfo),
+ ('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class AuthenticatedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('mac_algorithm', HmacAlgorithm),
+ ('digest_algorithm', DigestAlgorithm, {'implicit': 1, 'optional': True}),
+ # This does not require the _spec_callbacks approach of SignedData and
+ # DigestedData since AuthenticatedData was not part of PKCS#7
+ ('encap_content_info', EncapsulatedContentInfo),
+ ('auth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
+ ('mac', OctetString),
+ ('unauth_attrs', CMSAttributes, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class AuthEnvelopedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
+ ('recipient_infos', RecipientInfos),
+ ('auth_encrypted_content_info', EncryptedContentInfo),
+ ('auth_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
+ ('mac', OctetString),
+ ('unauth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class CompressionAlgorithmId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.16.3.8': 'zlib',
+ }
+
+
+class CompressionAlgorithm(Sequence):
+ _fields = [
+ ('algorithm', CompressionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class CompressedData(Sequence):
+ _fields = [
+ ('version', CMSVersion),
+ ('compression_algorithm', CompressionAlgorithm),
+ ('encap_content_info', EncapsulatedContentInfo),
+ ]
+
+ _decompressed = None
+
+ @property
+ def decompressed(self):
+ if self._decompressed is None:
+ if zlib is None:
+ raise SystemError('The zlib module is not available')
+ self._decompressed = zlib.decompress(self['encap_content_info']['content'].native)
+ return self._decompressed
+
+
+class RecipientKeyIdentifier(Sequence):
+ _fields = [
+ ('subjectKeyIdentifier', OctetString),
+ ('date', GeneralizedTime, {'optional': True}),
+ ('other', OtherKeyAttribute, {'optional': True}),
+ ]
+
+
+class SMIMEEncryptionKeyPreference(Choice):
+ _alternatives = [
+ ('issuer_and_serial_number', IssuerAndSerialNumber, {'implicit': 0}),
+ ('recipientKeyId', RecipientKeyIdentifier, {'implicit': 1}),
+ ('subjectAltKeyIdentifier', PublicKeyInfo, {'implicit': 2}),
+ ]
+
+
+class SMIMEEncryptionKeyPreferences(SetOf):
+ _child_spec = SMIMEEncryptionKeyPreference
+
+
+class SMIMECapabilityIdentifier(Sequence):
+ _fields = [
+ ('capability_id', EncryptionAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+
+class SMIMECapabilites(SequenceOf):
+ _child_spec = SMIMECapabilityIdentifier
+
+
+class SetOfSMIMECapabilites(SetOf):
+ _child_spec = SMIMECapabilites
+
+
+ContentInfo._oid_specs = {
+ 'data': OctetString,
+ 'signed_data': SignedData,
+ 'enveloped_data': EnvelopedData,
+ 'signed_and_enveloped_data': SignedAndEnvelopedData,
+ 'digested_data': DigestedData,
+ 'encrypted_data': EncryptedData,
+ 'authenticated_data': AuthenticatedData,
+ 'compressed_data': CompressedData,
+ 'authenticated_enveloped_data': AuthEnvelopedData,
+}
+
+
+EncapsulatedContentInfo._oid_specs = {
+ 'signed_data': SignedData,
+ 'enveloped_data': EnvelopedData,
+ 'signed_and_enveloped_data': SignedAndEnvelopedData,
+ 'digested_data': DigestedData,
+ 'encrypted_data': EncryptedData,
+ 'authenticated_data': AuthenticatedData,
+ 'compressed_data': CompressedData,
+ 'authenticated_enveloped_data': AuthEnvelopedData,
+}
+
+
+CMSAttribute._oid_specs = {
+ 'content_type': SetOfContentType,
+ 'message_digest': SetOfOctetString,
+ 'signing_time': SetOfTime,
+ 'counter_signature': SignerInfos,
+ 'signature_time_stamp_token': SetOfContentInfo,
+ 'cms_algorithm_protection': SetOfCMSAlgorithmProtection,
+ 'microsoft_nested_signature': SetOfContentInfo,
+ 'microsoft_time_stamp_token': SetOfContentInfo,
+ 'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
+ 'smime_capabilities': SetOfSMIMECapabilites,
+}
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/core.py b/contrib/python/asn1crypto/py3/asn1crypto/core.py
new file mode 100644
index 0000000000..364c6b5cae
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/core.py
@@ -0,0 +1,5676 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for universal types. Exports the following items:
+
+ - load()
+ - Any()
+ - Asn1Value()
+ - BitString()
+ - BMPString()
+ - Boolean()
+ - CharacterString()
+ - Choice()
+ - EmbeddedPdv()
+ - Enumerated()
+ - GeneralizedTime()
+ - GeneralString()
+ - GraphicString()
+ - IA5String()
+ - InstanceOf()
+ - Integer()
+ - IntegerBitString()
+ - IntegerOctetString()
+ - Null()
+ - NumericString()
+ - ObjectDescriptor()
+ - ObjectIdentifier()
+ - OctetBitString()
+ - OctetString()
+ - PrintableString()
+ - Real()
+ - RelativeOid()
+ - Sequence()
+ - SequenceOf()
+ - Set()
+ - SetOf()
+ - TeletexString()
+ - UniversalString()
+ - UTCTime()
+ - UTF8String()
+ - VideotexString()
+ - VisibleString()
+ - VOID
+ - Void()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from datetime import datetime, timedelta
+from fractions import Fraction
+import binascii
+import copy
+import math
+import re
+import sys
+
+from . import _teletex_codec
+from ._errors import unwrap
+from ._ordereddict import OrderedDict
+from ._types import type_name, str_cls, byte_cls, int_types, chr_cls
+from .parser import _parse, _dump_header
+from .util import int_to_bytes, int_from_bytes, timezone, extended_datetime, create_timezone, utc_with_dst
+
+if sys.version_info <= (3,):
+ from cStringIO import StringIO as BytesIO
+
+ range = xrange # noqa
+ _PY2 = True
+
+else:
+ from io import BytesIO
+
+ _PY2 = False
+
+
+_teletex_codec.register()
+
+
+CLASS_NUM_TO_NAME_MAP = {
+ 0: 'universal',
+ 1: 'application',
+ 2: 'context',
+ 3: 'private',
+}
+
+CLASS_NAME_TO_NUM_MAP = {
+ 'universal': 0,
+ 'application': 1,
+ 'context': 2,
+ 'private': 3,
+ 0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+}
+
+METHOD_NUM_TO_NAME_MAP = {
+ 0: 'primitive',
+ 1: 'constructed',
+}
+
+
+_OID_RE = re.compile(r'^\d+(\.\d+)*$')
+
+
+# A global tracker to ensure that _setup() is called for every class, even
+# if is has been called for a parent class. This allows different _fields
+# definitions for child classes. Without such a construct, the child classes
+# would just see the parent class attributes and would use them.
+_SETUP_CLASSES = {}
+
+
+def load(encoded_data, strict=False):
+ """
+ Loads a BER/DER-encoded byte string and construct a universal object based
+ on the tag value:
+
+ - 1: Boolean
+ - 2: Integer
+ - 3: BitString
+ - 4: OctetString
+ - 5: Null
+ - 6: ObjectIdentifier
+ - 7: ObjectDescriptor
+ - 8: InstanceOf
+ - 9: Real
+ - 10: Enumerated
+ - 11: EmbeddedPdv
+ - 12: UTF8String
+ - 13: RelativeOid
+ - 16: Sequence,
+ - 17: Set
+ - 18: NumericString
+ - 19: PrintableString
+ - 20: TeletexString
+ - 21: VideotexString
+ - 22: IA5String
+ - 23: UTCTime
+ - 24: GeneralizedTime
+ - 25: GraphicString
+ - 26: VisibleString
+ - 27: GeneralString
+ - 28: UniversalString
+ - 29: CharacterString
+ - 30: BMPString
+
+ :param encoded_data:
+ A byte string of BER or DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :raises:
+ ValueError - when strict is True and trailing data is present
+ ValueError - when the encoded value tag a tag other than listed above
+ ValueError - when the ASN.1 header length is longer than the data
+ TypeError - when encoded_data is not a byte string
+
+ :return:
+ An instance of the one of the universal classes
+ """
+
+ return Asn1Value.load(encoded_data, strict=strict)
+
+
+class Asn1Value(object):
+ """
+ The basis of all ASN.1 values
+ """
+
+ # The integer 0 for primitive, 1 for constructed
+ method = None
+
+ # An integer 0 through 3 - see CLASS_NUM_TO_NAME_MAP for value
+ class_ = None
+
+ # An integer 1 or greater indicating the tag number
+ tag = None
+
+ # An alternate tag allowed for this type - used for handling broken
+ # structures where a string value is encoded using an incorrect tag
+ _bad_tag = None
+
+ # If the value has been implicitly tagged
+ implicit = False
+
+ # If explicitly tagged, a tuple of 2-element tuples containing the
+ # class int and tag int, from innermost to outermost
+ explicit = None
+
+ # The BER/DER header bytes
+ _header = None
+
+ # Raw encoded value bytes not including class, method, tag, length header
+ contents = None
+
+ # The BER/DER trailer bytes
+ _trailer = b''
+
+ # The native python representation of the value - this is not used by
+ # some classes since they utilize _bytes or _unicode
+ _native = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False, **kwargs):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ An instance of the current class
+ """
+
+ if not isinstance(encoded_data, byte_cls):
+ raise TypeError('encoded_data must be a byte string, not %s' % type_name(encoded_data))
+
+ spec = None
+ if cls.tag is not None:
+ spec = cls
+
+ value, _ = _parse_build(encoded_data, spec=spec, spec_params=kwargs, strict=strict)
+ return value
+
+ def __init__(self, explicit=None, implicit=None, no_explicit=False, tag_type=None, class_=None, tag=None,
+ optional=None, default=None, contents=None, method=None):
+ """
+ The optional parameter is not used, but rather included so we don't
+ have to delete it from the parameter dictionary when passing as keyword
+ args
+
+ :param explicit:
+ An int tag number for explicit tagging, or a 2-element tuple of
+ class and tag.
+
+ :param implicit:
+ An int tag number for implicit tagging, or a 2-element tuple of
+ class and tag.
+
+ :param no_explicit:
+ If explicit tagging info should be removed from this instance.
+ Used internally to allow contructing the underlying value that
+ has been wrapped in an explicit tag.
+
+ :param tag_type:
+ None for normal values, or one of "implicit", "explicit" for tagged
+ values. Deprecated in favor of explicit and implicit params.
+
+ :param class_:
+ The class for the value - defaults to "universal" if tag_type is
+ None, otherwise defaults to "context". Valid values include:
+ - "universal"
+ - "application"
+ - "context"
+ - "private"
+ Deprecated in favor of explicit and implicit params.
+
+ :param tag:
+ The integer tag to override - usually this is used with tag_type or
+ class_. Deprecated in favor of explicit and implicit params.
+
+ :param optional:
+ Dummy parameter that allows "optional" key in spec param dicts
+
+ :param default:
+ The default value to use if the value is currently None
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param method:
+ The method for the value - no default value since this is
+ normally set on a class. Valid values include:
+ - "primitive" or 0
+ - "constructed" or 1
+
+ :raises:
+ ValueError - when implicit, explicit, tag_type, class_ or tag are invalid values
+ """
+
+ try:
+ if self.__class__ not in _SETUP_CLASSES:
+ cls = self.__class__
+ # Allow explicit to be specified as a simple 2-element tuple
+ # instead of requiring the user make a nested tuple
+ if cls.explicit is not None and isinstance(cls.explicit[0], int_types):
+ cls.explicit = (cls.explicit, )
+ if hasattr(cls, '_setup'):
+ self._setup()
+ _SETUP_CLASSES[cls] = True
+
+ # Normalize tagging values
+ if explicit is not None:
+ if isinstance(explicit, int_types):
+ if class_ is None:
+ class_ = 'context'
+ explicit = (class_, explicit)
+ # Prevent both explicit and tag_type == 'explicit'
+ if tag_type == 'explicit':
+ tag_type = None
+ tag = None
+
+ if implicit is not None:
+ if isinstance(implicit, int_types):
+ if class_ is None:
+ class_ = 'context'
+ implicit = (class_, implicit)
+ # Prevent both implicit and tag_type == 'implicit'
+ if tag_type == 'implicit':
+ tag_type = None
+ tag = None
+
+ # Convert old tag_type API to explicit/implicit params
+ if tag_type is not None:
+ if class_ is None:
+ class_ = 'context'
+ if tag_type == 'explicit':
+ explicit = (class_, tag)
+ elif tag_type == 'implicit':
+ implicit = (class_, tag)
+ else:
+ raise ValueError(unwrap(
+ '''
+ tag_type must be one of "implicit", "explicit", not %s
+ ''',
+ repr(tag_type)
+ ))
+
+ if explicit is not None:
+ # Ensure we have a tuple of 2-element tuples
+ if len(explicit) == 2 and isinstance(explicit[1], int_types):
+ explicit = (explicit, )
+ for class_, tag in explicit:
+ invalid_class = None
+ if isinstance(class_, int_types):
+ if class_ not in CLASS_NUM_TO_NAME_MAP:
+ invalid_class = class_
+ else:
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ invalid_class = class_
+ class_ = CLASS_NAME_TO_NUM_MAP[class_]
+ if invalid_class is not None:
+ raise ValueError(unwrap(
+ '''
+ explicit class must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(invalid_class)
+ ))
+ if tag is not None:
+ if not isinstance(tag, int_types):
+ raise TypeError(unwrap(
+ '''
+ explicit tag must be an integer, not %s
+ ''',
+ type_name(tag)
+ ))
+ if self.explicit is None:
+ self.explicit = ((class_, tag), )
+ else:
+ self.explicit = self.explicit + ((class_, tag), )
+
+ elif implicit is not None:
+ class_, tag = implicit
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ raise ValueError(unwrap(
+ '''
+ implicit class must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(class_)
+ ))
+ if tag is not None:
+ if not isinstance(tag, int_types):
+ raise TypeError(unwrap(
+ '''
+ implicit tag must be an integer, not %s
+ ''',
+ type_name(tag)
+ ))
+ self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
+ self.tag = tag
+ self.implicit = True
+ else:
+ if class_ is not None:
+ if class_ not in CLASS_NAME_TO_NUM_MAP:
+ raise ValueError(unwrap(
+ '''
+ class_ must be one of "universal", "application",
+ "context", "private", not %s
+ ''',
+ repr(class_)
+ ))
+ self.class_ = CLASS_NAME_TO_NUM_MAP[class_]
+
+ if self.class_ is None:
+ self.class_ = 0
+
+ if tag is not None:
+ self.tag = tag
+
+ if method is not None:
+ if method not in set(["primitive", 0, "constructed", 1]):
+ raise ValueError(unwrap(
+ '''
+ method must be one of "primitive" or "constructed",
+ not %s
+ ''',
+ repr(method)
+ ))
+ if method == "primitive":
+ method = 0
+ elif method == "constructed":
+ method = 1
+ self.method = method
+
+ if no_explicit:
+ self.explicit = None
+
+ if contents is not None:
+ self.contents = contents
+
+ elif default is not None:
+ self.set(default)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ def __str__(self):
+ """
+ Since str is different in Python 2 and 3, this calls the appropriate
+ method, __unicode__() or __bytes__()
+
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return self.__bytes__()
+ else:
+ return self.__unicode__()
+
+ def __repr__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return '<%s %s b%s>' % (type_name(self), id(self), repr(self.dump()))
+ else:
+ return '<%s %s %s>' % (type_name(self), id(self), repr(self.dump()))
+
+ def __bytes__(self):
+ """
+ A fall-back method for print() in Python 2
+
+ :return:
+ A byte string of the output of repr()
+ """
+
+ return self.__repr__().encode('utf-8')
+
+ def __unicode__(self):
+ """
+ A fall-back method for print() in Python 3
+
+ :return:
+ A unicode string of the output of repr()
+ """
+
+ return self.__repr__()
+
+ def _new_instance(self):
+ """
+ Constructs a new copy of the current object, preserving any tagging
+
+ :return:
+ An Asn1Value object
+ """
+
+ new_obj = self.__class__()
+ new_obj.class_ = self.class_
+ new_obj.tag = self.tag
+ new_obj.implicit = self.implicit
+ new_obj.explicit = self.explicit
+ return new_obj
+
+ def __copy__(self):
+ """
+ Implements the copy.copy() interface
+
+ :return:
+ A new shallow copy of the current Asn1Value object
+ """
+
+ new_obj = self._new_instance()
+ new_obj._copy(self, copy.copy)
+ return new_obj
+
+ def __deepcopy__(self, memo):
+ """
+ Implements the copy.deepcopy() interface
+
+ :param memo:
+ A dict for memoization
+
+ :return:
+ A new deep copy of the current Asn1Value object
+ """
+
+ new_obj = self._new_instance()
+ memo[id(self)] = new_obj
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def copy(self):
+ """
+ Copies the object, preserving any special tagging from it
+
+ :return:
+ An Asn1Value object
+ """
+
+ return copy.deepcopy(self)
+
+ def retag(self, tagging, tag=None):
+ """
+ Copies the object, applying a new tagging to it
+
+ :param tagging:
+ A dict containing the keys "explicit" and "implicit". Legacy
+ API allows a unicode string of "implicit" or "explicit".
+
+ :param tag:
+ A integer tag number. Only used when tagging is a unicode string.
+
+ :return:
+ An Asn1Value object
+ """
+
+ # This is required to preserve the old API
+ if not isinstance(tagging, dict):
+ tagging = {tagging: tag}
+ new_obj = self.__class__(explicit=tagging.get('explicit'), implicit=tagging.get('implicit'))
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def untag(self):
+ """
+ Copies the object, removing any special tagging from it
+
+ :return:
+ An Asn1Value object
+ """
+
+ new_obj = self.__class__()
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Asn1Value object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ if self.__class__ != other.__class__:
+ raise TypeError(unwrap(
+ '''
+ Can not copy values from %s object to %s object
+ ''',
+ type_name(other),
+ type_name(self)
+ ))
+
+ self.contents = other.contents
+ self._native = copy_func(other._native)
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ prefix = ' ' * nest_level
+
+ # This interacts with Any and moves the tag, implicit, explicit, _header,
+ # contents, _footer to the parsed value so duplicate data isn't present
+ has_parsed = hasattr(self, 'parsed')
+
+ _basic_debug(prefix, self)
+ if has_parsed:
+ self.parsed.debug(nest_level + 2)
+ elif hasattr(self, 'chosen'):
+ self.chosen.debug(nest_level + 2)
+ else:
+ if _PY2 and isinstance(self.native, byte_cls):
+ print('%s Native: b%s' % (prefix, repr(self.native)))
+ else:
+ print('%s Native: %s' % (prefix, self.native))
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ contents = self.contents
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if self._header is None or force:
+ if isinstance(self, Constructable) and self._indefinite:
+ self.method = 0
+
+ header = _dump_header(self.class_, self.method, self.tag, self.contents)
+
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ header = _dump_header(class_, 1, tag, header + self.contents) + header
+
+ self._header = header
+ self._trailer = b''
+
+ return self._header + contents + self._trailer
+
+
+class ValueMap():
+ """
+ Basic functionality that allows for mapping values from ints or OIDs to
+ python unicode strings
+ """
+
+ # A dict from primitive value (int or OID) to unicode string. This needs
+ # to be defined in the source code
+ _map = None
+
+ # A dict from unicode string to int/OID. This is automatically generated
+ # from _map the first time it is needed
+ _reverse_map = None
+
+ def _setup(self):
+ """
+ Generates _reverse_map from _map
+ """
+
+ cls = self.__class__
+ if cls._map is None or cls._reverse_map is not None:
+ return
+ cls._reverse_map = {}
+ for key, value in cls._map.items():
+ cls._reverse_map[value] = key
+
+
+class Castable(object):
+ """
+ A mixin to handle converting an object between different classes that
+ represent the same encoded value, but with different rules for converting
+ to and from native Python values
+ """
+
+ def cast(self, other_class):
+ """
+ Converts the current object into an object of a different class. The
+ new class must use the ASN.1 encoding for the value.
+
+ :param other_class:
+ The class to instantiate the new object from
+
+ :return:
+ An instance of the type other_class
+ """
+
+ if other_class.tag != self.__class__.tag:
+ raise TypeError(unwrap(
+ '''
+ Can not covert a value from %s object to %s object since they
+ use different tags: %d versus %d
+ ''',
+ type_name(other_class),
+ type_name(self),
+ other_class.tag,
+ self.__class__.tag
+ ))
+
+ new_obj = other_class()
+ new_obj.class_ = self.class_
+ new_obj.implicit = self.implicit
+ new_obj.explicit = self.explicit
+ new_obj._header = self._header
+ new_obj.contents = self.contents
+ new_obj._trailer = self._trailer
+ if isinstance(self, Constructable):
+ new_obj.method = self.method
+ new_obj._indefinite = self._indefinite
+ return new_obj
+
+
+class Constructable(object):
+ """
+ A mixin to handle string types that may be constructed from chunks
+ contained within an indefinite length BER-encoded container
+ """
+
+ # Instance attribute indicating if an object was indefinite
+ # length when parsed - affects parsing and dumping
+ _indefinite = False
+
+ def _merge_chunks(self):
+ """
+ :return:
+ A concatenation of the native values of the contained chunks
+ """
+
+ if not self._indefinite:
+ return self._as_chunk()
+
+ pointer = 0
+ contents_len = len(self.contents)
+ output = None
+
+ while pointer < contents_len:
+ # We pass the current class as the spec so content semantics are preserved
+ sub_value, pointer = _parse_build(self.contents, pointer, spec=self.__class__)
+ if output is None:
+ output = sub_value._merge_chunks()
+ else:
+ output += sub_value._merge_chunks()
+
+ if output is None:
+ return self._as_chunk()
+
+ return output
+
+ def _as_chunk(self):
+ """
+ A method to return a chunk of data that can be combined for
+ constructed method values
+
+ :return:
+ A native Python value that can be added together. Examples include
+ byte strings, unicode strings or tuples.
+ """
+
+ return self.contents
+
+ def _setable_native(self):
+ """
+ Returns a native value that can be round-tripped into .set(), to
+ result in a DER encoding. This differs from .native in that .native
+ is designed for the end use, and may account for the fact that the
+ merged value is further parsed as ASN.1, such as in the case of
+ ParsableOctetString() and ParsableOctetBitString().
+
+ :return:
+ A python value that is valid to pass to .set()
+ """
+
+ return self.native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Constructable object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Constructable, self)._copy(other, copy_func)
+ # We really don't want to dump BER encodings, so if we see an
+ # indefinite encoding, let's re-encode it
+ if other._indefinite:
+ self.set(other._setable_native())
+
+
+class Void(Asn1Value):
+ """
+ A representation of an optional value that is not present. Has .native
+ property and .dump() method to be compatible with other value classes.
+ """
+
+ contents = b''
+
+ def __eq__(self, other):
+ """
+ :param other:
+ The other Primitive to compare to
+
+ :return:
+ A boolean
+ """
+
+ return other.__class__ == self.__class__
+
+ def __nonzero__(self):
+ return False
+
+ def __len__(self):
+ return 0
+
+ def __iter__(self):
+ return iter(())
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ None
+ """
+
+ return None
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ return b''
+
+
+VOID = Void()
+
+
+class Any(Asn1Value):
+ """
+ A value class that can contain any value, and allows for easy parsing of
+ the underlying encoded value using a spec. This is normally contained in
+ a Structure that has an ObjectIdentifier field and _oid_pair and _oid_specs
+ defined.
+ """
+
+ # The parsed value object
+ _parsed = None
+
+ def __init__(self, value=None, **kwargs):
+ """
+ Sets the value of the object before passing to Asn1Value.__init__()
+
+ :param value:
+ An Asn1Value object that will be set as the parsed value
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if value is not None:
+ if not isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ value must be an instance of Asn1Value, not %s
+ ''',
+ type_name(value)
+ ))
+
+ self._parsed = (value, value.__class__, None)
+ self.contents = value.dump()
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ The .native value from the parsed value object
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0].native
+
+ @property
+ def parsed(self):
+ """
+ Returns the parsed object from .parse()
+
+ :return:
+ The object returned by .parse()
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0]
+
+ def parse(self, spec=None, spec_params=None):
+ """
+ Parses the contents generically, or using a spec with optional params
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :return:
+ An object of the type spec, or if not present, a child of Asn1Value
+ """
+
+ if self._parsed is None or self._parsed[1:3] != (spec, spec_params):
+ try:
+ passed_params = spec_params or {}
+ _tag_type_to_explicit_implicit(passed_params)
+ if self.explicit is not None:
+ if 'explicit' in passed_params:
+ passed_params['explicit'] = self.explicit + passed_params['explicit']
+ else:
+ passed_params['explicit'] = self.explicit
+ contents = self._header + self.contents + self._trailer
+ parsed_value, _ = _parse_build(
+ contents,
+ spec=spec,
+ spec_params=passed_params
+ )
+ self._parsed = (parsed_value, spec, spec_params)
+
+ # Once we've parsed the Any value, clear any attributes from this object
+ # since they are now duplicate
+ self.tag = None
+ self.explicit = None
+ self.implicit = False
+ self._header = b''
+ self.contents = contents
+ self._trailer = b''
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._parsed[0]
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Any object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Any, self)._copy(other, copy_func)
+ self._parsed = copy_func(other._parsed)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0].dump(force=force)
+
+
+class Choice(Asn1Value):
+ """
+ A class to handle when a value may be one of several options
+ """
+
+ # The index in _alternatives of the validated alternative
+ _choice = None
+
+ # The name of the chosen alternative
+ _name = None
+
+ # The Asn1Value object for the chosen alternative
+ _parsed = None
+
+ # Choice overrides .contents to be a property so that the code expecting
+ # the .contents attribute will get the .contents of the chosen alternative
+ _contents = None
+
+ # A list of tuples in one of the following forms.
+ #
+ # Option 1, a unicode string field name and a value class
+ #
+ # ("name", Asn1ValueClass)
+ #
+ # Option 2, same as Option 1, but with a dict of class params
+ #
+ # ("name", Asn1ValueClass, {'explicit': 5})
+ _alternatives = None
+
+ # A dict that maps tuples of (class_, tag) to an index in _alternatives
+ _id_map = None
+
+ # A dict that maps alternative names to an index in _alternatives
+ _name_map = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False, **kwargs):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A instance of the current class
+ """
+
+ if not isinstance(encoded_data, byte_cls):
+ raise TypeError('encoded_data must be a byte string, not %s' % type_name(encoded_data))
+
+ value, _ = _parse_build(encoded_data, spec=cls, spec_params=kwargs, strict=strict)
+ return value
+
+ def _setup(self):
+ """
+ Generates _id_map from _alternatives to allow validating contents
+ """
+
+ cls = self.__class__
+ cls._id_map = {}
+ cls._name_map = {}
+ for index, info in enumerate(cls._alternatives):
+ if len(info) < 3:
+ info = info + ({},)
+ cls._alternatives[index] = info
+ id_ = _build_id_tuple(info[2], info[1])
+ cls._id_map[id_] = index
+ cls._name_map[info[0]] = index
+
+ def __init__(self, name=None, value=None, **kwargs):
+ """
+ Checks to ensure implicit tagging is not being used since it is
+ incompatible with Choice, then forwards on to Asn1Value.__init__()
+
+ :param name:
+ The name of the alternative to be set - used with value.
+ Alternatively this may be a dict with a single key being the name
+ and the value being the value, or a two-element tuple of the name
+ and the value.
+
+ :param value:
+ The alternative value to set - used with name
+
+ :raises:
+ ValueError - when implicit param is passed (or legacy tag_type param is "implicit")
+ """
+
+ _tag_type_to_explicit_implicit(kwargs)
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if kwargs.get('implicit') is not None:
+ raise ValueError(unwrap(
+ '''
+ The Choice type can not be implicitly tagged even if in an
+ implicit module - due to its nature any tagging must be
+ explicit
+ '''
+ ))
+
+ if name is not None:
+ if isinstance(name, dict):
+ if len(name) != 1:
+ raise ValueError(unwrap(
+ '''
+ When passing a dict as the "name" argument to %s,
+ it must have a single key/value - however %d were
+ present
+ ''',
+ type_name(self),
+ len(name)
+ ))
+ name, value = list(name.items())[0]
+
+ if isinstance(name, tuple):
+ if len(name) != 2:
+ raise ValueError(unwrap(
+ '''
+ When passing a tuple as the "name" argument to %s,
+ it must have two elements, the name and value -
+ however %d were present
+ ''',
+ type_name(self),
+ len(name)
+ ))
+ value = name[1]
+ name = name[0]
+
+ if name not in self._name_map:
+ raise ValueError(unwrap(
+ '''
+ The name specified, "%s", is not a valid alternative
+ for %s
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ self._choice = self._name_map[name]
+ _, spec, params = self._alternatives[self._choice]
+
+ if not isinstance(value, spec):
+ value = spec(value, **params)
+ else:
+ value = _fix_tagging(value, params)
+ self._parsed = value
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the chosen alternative
+ """
+
+ if self._parsed is not None:
+ return self._parsed.contents
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the chosen alternative
+ """
+
+ self._contents = value
+
+ @property
+ def name(self):
+ """
+ :return:
+ A unicode string of the field name of the chosen alternative
+ """
+ if not self._name:
+ self._name = self._alternatives[self._choice][0]
+ return self._name
+
+ def parse(self):
+ """
+ Parses the detected alternative
+
+ :return:
+ An Asn1Value object of the chosen alternative
+ """
+
+ if self._parsed is None:
+ try:
+ _, spec, params = self._alternatives[self._choice]
+ self._parsed, _ = _parse_build(self._contents, spec=spec, spec_params=params)
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._parsed
+
+ @property
+ def chosen(self):
+ """
+ :return:
+ An Asn1Value object of the chosen alternative
+ """
+
+ return self.parse()
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ The .native value from the contained value object
+ """
+
+ return self.chosen.native
+
+ def validate(self, class_, tag, contents):
+ """
+ Ensures that the class and tag specified exist as an alternative
+
+ :param class_:
+ The integer class_ from the encoded value header
+
+ :param tag:
+ The integer tag from the encoded value header
+
+ :param contents:
+ A byte string of the contents of the value - used when the object
+ is explicitly tagged
+
+ :raises:
+ ValueError - when value is not a valid alternative
+ """
+
+ id_ = (class_, tag)
+
+ if self.explicit is not None:
+ if self.explicit[-1] != id_:
+ raise ValueError(unwrap(
+ '''
+ %s was explicitly tagged, but the value provided does not
+ match the class and tag
+ ''',
+ type_name(self)
+ ))
+
+ ((class_, _, tag, _, _, _), _) = _parse(contents, len(contents))
+ id_ = (class_, tag)
+
+ if id_ in self._id_map:
+ self._choice = self._id_map[id_]
+ return
+
+ # This means the Choice was implicitly tagged
+ if self.class_ is not None and self.tag is not None:
+ if len(self._alternatives) > 1:
+ raise ValueError(unwrap(
+ '''
+ %s was implicitly tagged, but more than one alternative
+ exists
+ ''',
+ type_name(self)
+ ))
+ if id_ == (self.class_, self.tag):
+ self._choice = 0
+ return
+
+ asn1 = self._format_class_tag(class_, tag)
+ asn1s = [self._format_class_tag(pair[0], pair[1]) for pair in self._id_map]
+
+ raise ValueError(unwrap(
+ '''
+ Value %s did not match the class and tag of any of the alternatives
+ in %s: %s
+ ''',
+ asn1,
+ type_name(self),
+ ', '.join(asn1s)
+ ))
+
+ def _format_class_tag(self, class_, tag):
+ """
+ :return:
+ A unicode string of a human-friendly representation of the class and tag
+ """
+
+ return '[%s %s]' % (CLASS_NUM_TO_NAME_MAP[class_].upper(), tag)
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Choice object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Choice, self)._copy(other, copy_func)
+ self._choice = other._choice
+ self._name = other._name
+ self._parsed = copy_func(other._parsed)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ self._contents = self.chosen.dump(force=force)
+ if self._header is None or force:
+ self._header = b''
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ self._header = _dump_header(class_, 1, tag, self._header + self._contents) + self._header
+ return self._header + self._contents
+
+
+class Concat(object):
+ """
+ A class that contains two or more encoded child values concatentated
+ together. THIS IS NOT PART OF THE ASN.1 SPECIFICATION! This exists to handle
+ the x509.TrustedCertificate() class for OpenSSL certificates containing
+ extra information.
+ """
+
+ # A list of the specs of the concatenated values
+ _child_specs = None
+
+ _children = None
+
+ @classmethod
+ def load(cls, encoded_data, strict=False):
+ """
+ Loads a BER/DER-encoded byte string using the current class as the spec
+
+ :param encoded_data:
+ A byte string of BER or DER encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A Concat object
+ """
+
+ return cls(contents=encoded_data, strict=strict)
+
+ def __init__(self, value=None, contents=None, strict=False):
+ """
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists in contents
+
+ :raises:
+ ValueError - when an error occurs with one of the children
+ TypeError - when an error occurs with one of the children
+ """
+
+ if contents is not None:
+ try:
+ contents_len = len(contents)
+ self._children = []
+
+ offset = 0
+ for spec in self._child_specs:
+ if offset < contents_len:
+ child_value, offset = _parse_build(contents, pointer=offset, spec=spec)
+ else:
+ child_value = spec()
+ self._children.append(child_value)
+
+ if strict and offset != contents_len:
+ extra_bytes = contents_len - offset
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % extra_bytes)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ if value is not None:
+ if self._children is None:
+ self._children = [None] * len(self._child_specs)
+ for index, data in enumerate(value):
+ self.__setitem__(index, data)
+
+ def __str__(self):
+ """
+ Since str is different in Python 2 and 3, this calls the appropriate
+ method, __unicode__() or __bytes__()
+
+ :return:
+ A unicode string
+ """
+
+ if _PY2:
+ return self.__bytes__()
+ else:
+ return self.__unicode__()
+
+ def __bytes__(self):
+ """
+ A byte string of the DER-encoded contents
+ """
+
+ return self.dump()
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return repr(self)
+
+ def __repr__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return '<%s %s %s>' % (type_name(self), id(self), repr(self.dump()))
+
+ def __copy__(self):
+ """
+ Implements the copy.copy() interface
+
+ :return:
+ A new shallow copy of the Concat object
+ """
+
+ new_obj = self.__class__()
+ new_obj._copy(self, copy.copy)
+ return new_obj
+
+ def __deepcopy__(self, memo):
+ """
+ Implements the copy.deepcopy() interface
+
+ :param memo:
+ A dict for memoization
+
+ :return:
+ A new deep copy of the Concat object and all child objects
+ """
+
+ new_obj = self.__class__()
+ memo[id(self)] = new_obj
+ new_obj._copy(self, copy.deepcopy)
+ return new_obj
+
+ def copy(self):
+ """
+ Copies the object
+
+ :return:
+ A Concat object
+ """
+
+ return copy.deepcopy(self)
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Concat object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ if self.__class__ != other.__class__:
+ raise TypeError(unwrap(
+ '''
+ Can not copy values from %s object to %s object
+ ''',
+ type_name(other),
+ type_name(self)
+ ))
+
+ self._children = copy_func(other._children)
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ prefix = ' ' * nest_level
+ print('%s%s Object #%s' % (prefix, type_name(self), id(self)))
+ print('%s Children:' % (prefix,))
+ for child in self._children:
+ child.debug(nest_level + 2)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ contents = b''
+ for child in self._children:
+ contents += child.dump(force=force)
+ return contents
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the children
+ """
+
+ return self.dump()
+
+ def __len__(self):
+ """
+ :return:
+ Integer
+ """
+
+ return len(self._children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing children by index
+
+ :param key:
+ An integer of the child index
+
+ :raises:
+ KeyError - when an index is invalid
+
+ :return:
+ The Asn1Value object of the child specified
+ """
+
+ if key > len(self._child_specs) - 1 or key < 0:
+ raise KeyError(unwrap(
+ '''
+ No child is definition for position %d of %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ return self._children[key]
+
+ def __setitem__(self, key, value):
+ """
+ Allows settings children by index
+
+ :param key:
+ An integer of the child index
+
+ :param value:
+ An Asn1Value object to set the child to
+
+ :raises:
+ KeyError - when an index is invalid
+ ValueError - when the value is not an instance of Asn1Value
+ """
+
+ if key > len(self._child_specs) - 1 or key < 0:
+ raise KeyError(unwrap(
+ '''
+ No child is defined for position %d of %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ if not isinstance(value, Asn1Value):
+ raise ValueError(unwrap(
+ '''
+ Value for child %s of %s is not an instance of
+ asn1crypto.core.Asn1Value
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ self._children[key] = value
+
+ def __iter__(self):
+ """
+ :return:
+ An iterator of child values
+ """
+
+ return iter(self._children)
+
+
+class Primitive(Asn1Value):
+ """
+ Sets the class_ and method attributes for primitive, universal values
+ """
+
+ class_ = 0
+
+ method = 0
+
+ def __init__(self, value=None, default=None, contents=None, **kwargs):
+ """
+ Sets the value of the object before passing to Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+
+ :param contents:
+ A byte string of the encoded contents of the value
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if contents is not None:
+ self.contents = contents
+
+ elif value is not None:
+ self.set(value)
+
+ elif default is not None:
+ self.set(default)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = value
+ self.contents = value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if force:
+ native = self.native
+ self.contents = None
+ self.set(native)
+
+ return Asn1Value.dump(self)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ :param other:
+ The other Primitive to compare to
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, Primitive):
+ return False
+
+ if self.contents != other.contents:
+ return False
+
+ # We compare class tag numbers since object tag numbers could be
+ # different due to implicit or explicit tagging
+ if self.__class__.tag != other.__class__.tag:
+ return False
+
+ if self.__class__ == other.__class__ and self.contents == other.contents:
+ return True
+
+ # If the objects share a common base class that is not too low-level
+ # then we can compare the contents
+ self_bases = (set(self.__class__.__bases__) | set([self.__class__])) - set([Asn1Value, Primitive, ValueMap])
+ other_bases = (set(other.__class__.__bases__) | set([other.__class__])) - set([Asn1Value, Primitive, ValueMap])
+ if self_bases | other_bases:
+ return self.contents == other.contents
+
+ # When tagging is going on, do the extra work of constructing new
+ # objects to see if the dumped representation are the same
+ if self.implicit or self.explicit or other.implicit or other.explicit:
+ return self.untag().dump() == other.untag().dump()
+
+ return self.dump() == other.dump()
+
+
+class AbstractString(Constructable, Primitive):
+ """
+ A base class for all strings that have a known encoding. In general, we do
+ not worry ourselves with confirming that the decoded values match a specific
+ set of characters, only that they are decoded into a Python unicode string
+ """
+
+ # The Python encoding name to use when decoding or encoded the contents
+ _encoding = 'latin1'
+
+ # Instance attribute of (possibly-merged) unicode string
+ _unicode = None
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._unicode = value
+ self.contents = value.encode(self._encoding)
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = self._merge_chunks().decode(self._encoding)
+ return self._unicode
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another AbstractString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(AbstractString, self)._copy(other, copy_func)
+ self._unicode = other._unicode
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__unicode__()
+
+
+class Boolean(Primitive):
+ """
+ Represents a boolean in both ASN.1 and Python
+ """
+
+ tag = 1
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ True, False or another value that works with bool()
+ """
+
+ self._native = bool(value)
+ self.contents = b'\x00' if not value else b'\xff'
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ # Python 2
+ def __nonzero__(self):
+ """
+ :return:
+ True or False
+ """
+ return self.__bool__()
+
+ def __bool__(self):
+ """
+ :return:
+ True or False
+ """
+ return self.contents != b'\x00'
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ True, False or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.__bool__()
+ return self._native
+
+
+class Integer(Primitive, ValueMap):
+ """
+ Represents an integer in both ASN.1 and Python
+ """
+
+ tag = 2
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer, or a unicode string if _map is set
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, str_cls):
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s value is a unicode string, but no _map provided
+ ''',
+ type_name(self)
+ ))
+
+ if value not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s value, %s, is not present in the _map
+ ''',
+ type_name(self),
+ value
+ ))
+
+ value = self._reverse_map[value]
+
+ elif not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be an integer or unicode string when a name_map
+ is provided, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = self._map[value] if self._map and value in self._map else value
+
+ self.contents = int_to_bytes(value, signed=True)
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __int__(self):
+ """
+ :return:
+ An integer
+ """
+ return int_from_bytes(self.contents, signed=True)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.__int__()
+ if self._map is not None and self._native in self._map:
+ self._native = self._map[self._native]
+ return self._native
+
+
+class _IntegerBitString(object):
+ """
+ A mixin for IntegerBitString and BitString to parse the contents as an integer.
+ """
+
+ # Tuple of 1s and 0s; set through native
+ _unused_bits = ()
+
+ def _as_chunk(self):
+ """
+ Parse the contents of a primitive BitString encoding as an integer value.
+ Allows reconstructing indefinite length values.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A list with one tuple (value, bits, unused_bits) where value is an integer
+ with the value of the BitString, bits is the bit count of value and
+ unused_bits is a tuple of 1s and 0s.
+ """
+
+ if self._indefinite:
+ # return an empty chunk, for cases like \x23\x80\x00\x00
+ return []
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ value = int_from_bytes(self.contents[1:])
+ bits = (len(self.contents) - 1) * 8
+
+ if not unused_bits_len:
+ return [(value, bits, ())]
+
+ if len(self.contents) == 1:
+ # Disallowed by X.690 §8.6.2.3
+ raise ValueError('Empty bit string has {0} unused bits'.format(unused_bits_len))
+
+ if unused_bits_len > 7:
+ # Disallowed by X.690 §8.6.2.2
+ raise ValueError('Bit string has {0} unused bits'.format(unused_bits_len))
+
+ unused_bits = _int_to_bit_tuple(value & ((1 << unused_bits_len) - 1), unused_bits_len)
+ value >>= unused_bits_len
+ bits -= unused_bits_len
+
+ return [(value, bits, unused_bits)]
+
+ def _chunks_to_int(self):
+ """
+ Combines the chunks into a single value.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A tuple (value, bits, unused_bits) where value is an integer with the
+ value of the BitString, bits is the bit count of value and unused_bits
+ is a tuple of 1s and 0s.
+ """
+
+ if not self._indefinite:
+ # Fast path
+ return self._as_chunk()[0]
+
+ value = 0
+ total_bits = 0
+ unused_bits = ()
+
+ # X.690 §8.6.3 allows empty indefinite encodings
+ for chunk, bits, unused_bits in self._merge_chunks():
+ if total_bits & 7:
+ # Disallowed by X.690 §8.6.4
+ raise ValueError('Only last chunk in a bit string may have unused bits')
+ total_bits += bits
+ value = (value << bits) | chunk
+
+ return value, total_bits, unused_bits
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another _IntegerBitString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(_IntegerBitString, self)._copy(other, copy_func)
+ self._unused_bits = other._unused_bits
+
+ @property
+ def unused_bits(self):
+ """
+ The unused bits of the bit string encoding.
+
+ :return:
+ A tuple of 1s and 0s
+ """
+
+ # call native to set _unused_bits
+ self.native
+
+ return self._unused_bits
+
+
+class BitString(_IntegerBitString, Constructable, Castable, Primitive, ValueMap):
+ """
+ Represents a bit string from ASN.1 as a Python tuple of 1s and 0s
+ """
+
+ tag = 3
+
+ _size = None
+
+ def _setup(self):
+ """
+ Generates _reverse_map from _map
+ """
+
+ ValueMap._setup(self)
+
+ cls = self.__class__
+ if cls._map is not None:
+ cls._size = max(self._map.keys()) + 1
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer or a tuple of integers 0 and 1
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, set):
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ bits = [0] * self._size
+ self._native = value
+ for index in range(0, self._size):
+ key = self._map.get(index)
+ if key is None:
+ continue
+ if key in value:
+ bits[index] = 1
+
+ value = ''.join(map(str_cls, bits))
+
+ elif value.__class__ == tuple:
+ if self._map is None:
+ self._native = value
+ else:
+ self._native = set()
+ for index, bit in enumerate(value):
+ if bit:
+ name = self._map.get(index, index)
+ self._native.add(name)
+ value = ''.join(map(str_cls, value))
+
+ else:
+ raise TypeError(unwrap(
+ '''
+ %s value must be a tuple of ones and zeros or a set of unicode
+ strings, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if self._map is not None:
+ if len(value) > self._size:
+ raise ValueError(unwrap(
+ '''
+ %s value must be at most %s bits long, specified was %s long
+ ''',
+ type_name(self),
+ self._size,
+ len(value)
+ ))
+ # A NamedBitList must have trailing zero bit truncated. See
+ # https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
+ # section 11.2,
+ # https://tools.ietf.org/html/rfc5280#page-134 and
+ # https://www.ietf.org/mail-archive/web/pkix/current/msg10443.html
+ value = value.rstrip('0')
+ size = len(value)
+
+ size_mod = size % 8
+ extra_bits = 0
+ if size_mod != 0:
+ extra_bits = 8 - size_mod
+ value += '0' * extra_bits
+
+ size_in_bytes = int(math.ceil(size / 8))
+
+ if extra_bits:
+ extra_bits_byte = int_to_bytes(extra_bits)
+ else:
+ extra_bits_byte = b'\x00'
+
+ if value == '':
+ value_bytes = b''
+ else:
+ value_bytes = int_to_bytes(int(value, 2))
+ if len(value_bytes) != size_in_bytes:
+ value_bytes = (b'\x00' * (size_in_bytes - len(value_bytes))) + value_bytes
+
+ self.contents = extra_bits_byte + value_bytes
+ self._unused_bits = (0,) * extra_bits
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __getitem__(self, key):
+ """
+ Retrieves a boolean version of one of the bits based on a name from the
+ _map
+
+ :param key:
+ The unicode string of one of the bit names
+
+ :raises:
+ ValueError - when _map is not set or the key name is invalid
+
+ :return:
+ A boolean if the bit is set
+ """
+
+ is_int = isinstance(key, int_types)
+ if not is_int:
+ if not isinstance(self._map, dict):
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ if key not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(self),
+ key
+ ))
+
+ if self._native is None:
+ self.native
+
+ if self._map is None:
+ if len(self._native) >= key + 1:
+ return bool(self._native[key])
+ return False
+
+ if is_int:
+ key = self._map.get(key, key)
+
+ return key in self._native
+
+ def __setitem__(self, key, value):
+ """
+ Sets one of the bits based on a name from the _map
+
+ :param key:
+ The unicode string of one of the bit names
+
+ :param value:
+ A boolean value
+
+ :raises:
+ ValueError - when _map is not set or the key name is invalid
+ """
+
+ is_int = isinstance(key, int_types)
+ if not is_int:
+ if self._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(self)
+ ))
+
+ if key not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(self),
+ key
+ ))
+
+ if self._native is None:
+ self.native
+
+ if self._map is None:
+ new_native = list(self._native)
+ max_key = len(new_native) - 1
+ if key > max_key:
+ new_native.extend([0] * (key - max_key))
+ new_native[key] = 1 if value else 0
+ self._native = tuple(new_native)
+
+ else:
+ if is_int:
+ key = self._map.get(key, key)
+
+ if value:
+ if key not in self._native:
+ self._native.add(key)
+ else:
+ if key in self._native:
+ self._native.remove(key)
+
+ self.set(self._native)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ If a _map is set, a set of names, or if no _map is set, a tuple of
+ integers 1 and 0. None if no value.
+ """
+
+ # For BitString we default the value to be all zeros
+ if self.contents is None:
+ if self._map is None:
+ self.set(())
+ else:
+ self.set(set())
+
+ if self._native is None:
+ int_value, bit_count, self._unused_bits = self._chunks_to_int()
+ bits = _int_to_bit_tuple(int_value, bit_count)
+
+ if self._map:
+ self._native = set()
+ for index, bit in enumerate(bits):
+ if bit:
+ name = self._map.get(index, index)
+ self._native.add(name)
+ else:
+ self._native = bits
+ return self._native
+
+
+class OctetBitString(Constructable, Castable, Primitive):
+ """
+ Represents a bit string in ASN.1 as a Python byte string
+ """
+
+ tag = 3
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ # Tuple of 1s and 0s; set through native
+ _unused_bits = ()
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + value
+ self._unused_bits = ()
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ if not self._indefinite:
+ self._bytes, self._unused_bits = self._as_chunk()[0]
+ else:
+ chunks = self._merge_chunks()
+ self._unused_bits = ()
+ for chunk in chunks:
+ if self._unused_bits:
+ # Disallowed by X.690 §8.6.4
+ raise ValueError('Only last chunk in a bit string may have unused bits')
+ self._unused_bits = chunk[1]
+ self._bytes = b''.join(chunk[0] for chunk in chunks)
+
+ return self._bytes
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another OctetBitString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(OctetBitString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+ self._unused_bits = other._unused_bits
+
+ def _as_chunk(self):
+ """
+ Allows reconstructing indefinite length values
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ List with one tuple, consisting of a byte string and an integer (unused bits)
+ """
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ if not unused_bits_len:
+ return [(self.contents[1:], ())]
+
+ if len(self.contents) == 1:
+ # Disallowed by X.690 §8.6.2.3
+ raise ValueError('Empty bit string has {0} unused bits'.format(unused_bits_len))
+
+ if unused_bits_len > 7:
+ # Disallowed by X.690 §8.6.2.2
+ raise ValueError('Bit string has {0} unused bits'.format(unused_bits_len))
+
+ mask = (1 << unused_bits_len) - 1
+ last_byte = ord(self.contents[-1]) if _PY2 else self.contents[-1]
+
+ # zero out the unused bits in the last byte.
+ zeroed_byte = last_byte & ~mask
+ value = self.contents[1:-1] + (chr(zeroed_byte) if _PY2 else bytes((zeroed_byte,)))
+
+ unused_bits = _int_to_bit_tuple(last_byte & mask, unused_bits_len)
+
+ return [(value, unused_bits)]
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__bytes__()
+
+ @property
+ def unused_bits(self):
+ """
+ The unused bits of the bit string encoding.
+
+ :return:
+ A tuple of 1s and 0s
+ """
+
+ # call native to set _unused_bits
+ self.native
+
+ return self._unused_bits
+
+
+class IntegerBitString(_IntegerBitString, Constructable, Castable, Primitive):
+ """
+ Represents a bit string in ASN.1 as a Python integer
+ """
+
+ tag = 3
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a positive integer, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value must be a positive integer, not %d
+ ''',
+ type_name(self),
+ value
+ ))
+
+ self._native = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + int_to_bytes(value, signed=True)
+ self._unused_bits = ()
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native, __, self._unused_bits = self._chunks_to_int()
+
+ return self._native
+
+
+class OctetString(Constructable, Castable, Primitive):
+ """
+ Represents a byte string in both ASN.1 and Python
+ """
+
+ tag = 4
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ self.contents = value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ self._bytes = self._merge_chunks()
+ return self._bytes
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another OctetString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(OctetString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ return self.__bytes__()
+
+
+class IntegerOctetString(Constructable, Castable, Primitive):
+ """
+ Represents a byte string in ASN.1 as a Python integer
+ """
+
+ tag = 4
+
+ # An explicit length in bytes the integer should be encoded to. This should
+ # generally not be used since DER defines a canonical encoding, however some
+ # use of this, such as when storing elliptic curve private keys, requires an
+ # exact number of bytes, even if the leading bytes are null.
+ _encoded_width = None
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a positive integer, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value must be a positive integer, not %d
+ ''',
+ type_name(self),
+ value
+ ))
+
+ self._native = value
+ self.contents = int_to_bytes(value, signed=False, width=self._encoded_width)
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An integer or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = int_from_bytes(self._merge_chunks())
+ return self._native
+
+ def set_encoded_width(self, width):
+ """
+ Set the explicit enoding width for the integer
+
+ :param width:
+ An integer byte width to encode the integer to
+ """
+
+ self._encoded_width = width
+ # Make sure the encoded value is up-to-date with the proper width
+ if self.contents is not None and len(self.contents) != width:
+ self.set(self.native)
+
+
+class ParsableOctetString(Constructable, Castable, Primitive):
+
+ tag = 4
+
+ _parsed = None
+
+ # Instance attribute of (possibly-merged) byte string
+ _bytes = None
+
+ def __init__(self, value=None, parsed=None, **kwargs):
+ """
+ Allows providing a parsed object that will be serialized to get the
+ byte string value
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param parsed:
+ If value is None and this is an Asn1Value object, this will be
+ set as the parsed value, and the value will be obtained by calling
+ .dump() on this object.
+ """
+
+ set_parsed = False
+ if value is None and parsed is not None and isinstance(parsed, Asn1Value):
+ value = parsed.dump()
+ set_parsed = True
+
+ Primitive.__init__(self, value=value, **kwargs)
+
+ if set_parsed:
+ self._parsed = (parsed, parsed.__class__, None)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ self.contents = value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def parse(self, spec=None, spec_params=None):
+ """
+ Parses the contents generically, or using a spec with optional params
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :return:
+ An object of the type spec, or if not present, a child of Asn1Value
+ """
+
+ if self._parsed is None or self._parsed[1:3] != (spec, spec_params):
+ parsed_value, _ = _parse_build(self.__bytes__(), spec=spec, spec_params=spec_params)
+ self._parsed = (parsed_value, spec, spec_params)
+ return self._parsed[0]
+
+ def __bytes__(self):
+ """
+ :return:
+ A byte string
+ """
+
+ if self.contents is None:
+ return b''
+ if self._bytes is None:
+ self._bytes = self._merge_chunks()
+ return self._bytes
+
+ def _setable_native(self):
+ """
+ Returns a byte string that can be passed into .set()
+
+ :return:
+ A python value that is valid to pass to .set()
+ """
+
+ return self.__bytes__()
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another ParsableOctetString object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(ParsableOctetString, self)._copy(other, copy_func)
+ self._bytes = other._bytes
+ self._parsed = copy_func(other._parsed)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A byte string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._parsed is not None:
+ return self._parsed[0].native
+ else:
+ return self.__bytes__()
+
+ @property
+ def parsed(self):
+ """
+ Returns the parsed object from .parse()
+
+ :return:
+ The object returned by .parse()
+ """
+
+ if self._parsed is None:
+ self.parse()
+
+ return self._parsed[0]
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._indefinite:
+ force = True
+
+ if force:
+ if self._parsed is not None:
+ native = self.parsed.dump(force=force)
+ else:
+ native = self.native
+ self.contents = None
+ self.set(native)
+
+ return Asn1Value.dump(self)
+
+
+class ParsableOctetBitString(ParsableOctetString):
+
+ tag = 3
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A byte string
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a byte string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._bytes = value
+ # Set the unused bits to 0
+ self.contents = b'\x00' + value
+ self._header = None
+ if self._indefinite:
+ self._indefinite = False
+ self.method = 0
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _as_chunk(self):
+ """
+ Allows reconstructing indefinite length values
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A byte string
+ """
+
+ unused_bits_len = ord(self.contents[0]) if _PY2 else self.contents[0]
+ if unused_bits_len:
+ raise ValueError('ParsableOctetBitString should have no unused bits')
+
+ return self.contents[1:]
+
+
+class Null(Primitive):
+ """
+ Represents a null value in ASN.1 as None in Python
+ """
+
+ tag = 5
+
+ contents = b''
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None
+ """
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ None
+ """
+
+ return None
+
+
+class ObjectIdentifier(Primitive, ValueMap):
+ """
+ Represents an object identifier in ASN.1 as a Python unicode dotted
+ integer string
+ """
+
+ tag = 6
+
+ # A unicode string of the dotted form of the object identifier
+ _dotted = None
+
+ @classmethod
+ def map(cls, value):
+ """
+ Converts a dotted unicode string OID into a mapped unicode string
+
+ :param value:
+ A dotted unicode string OID
+
+ :raises:
+ ValueError - when no _map dict has been defined on the class
+ TypeError - when value is not a unicode string
+
+ :return:
+ A mapped unicode string
+ """
+
+ if cls._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(cls)
+ ))
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ return cls._map.get(value, value)
+
+ @classmethod
+ def unmap(cls, value):
+ """
+ Converts a mapped unicode string value into a dotted unicode string OID
+
+ :param value:
+ A mapped unicode string OR dotted unicode string OID
+
+ :raises:
+ ValueError - when no _map dict has been defined on the class or the value can't be unmapped
+ TypeError - when value is not a unicode string
+
+ :return:
+ A dotted unicode string OID
+ """
+
+ if cls not in _SETUP_CLASSES:
+ cls()._setup()
+ _SETUP_CLASSES[cls] = True
+
+ if cls._map is None:
+ raise ValueError(unwrap(
+ '''
+ %s._map has not been defined
+ ''',
+ type_name(cls)
+ ))
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ value must be a unicode string, not %s
+ ''',
+ type_name(value)
+ ))
+
+ if value in cls._reverse_map:
+ return cls._reverse_map[value]
+
+ if not _OID_RE.match(value):
+ raise ValueError(unwrap(
+ '''
+ %s._map does not contain an entry for "%s"
+ ''',
+ type_name(cls),
+ value
+ ))
+
+ return value
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string. May be a dotted integer string, or if _map is
+ provided, one of the mapped values.
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._native = value
+
+ if self._map is not None:
+ if value in self._reverse_map:
+ value = self._reverse_map[value]
+
+ self.contents = b''
+ first = None
+ for index, part in enumerate(value.split('.')):
+ part = int(part)
+
+ # The first two parts are merged into a single byte
+ if index == 0:
+ first = part
+ continue
+ elif index == 1:
+ if first > 2:
+ raise ValueError(unwrap(
+ '''
+ First arc must be one of 0, 1 or 2, not %s
+ ''',
+ repr(first)
+ ))
+ elif first < 2 and part >= 40:
+ raise ValueError(unwrap(
+ '''
+ Second arc must be less than 40 if first arc is 0 or
+ 1, not %s
+ ''',
+ repr(part)
+ ))
+ part = (first * 40) + part
+
+ encoded_part = chr_cls(0x7F & part)
+ part = part >> 7
+ while part > 0:
+ encoded_part = chr_cls(0x80 | (0x7F & part)) + encoded_part
+ part = part >> 7
+ self.contents += encoded_part
+
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ return self.dotted
+
+ @property
+ def dotted(self):
+ """
+ :return:
+ A unicode string of the object identifier in dotted notation, thus
+ ignoring any mapped value
+ """
+
+ if self._dotted is None:
+ output = []
+
+ part = 0
+ for byte in self.contents:
+ if _PY2:
+ byte = ord(byte)
+ part = part * 128
+ part += byte & 127
+ # Last byte in subidentifier has the eighth bit set to 0
+ if byte & 0x80 == 0:
+ if len(output) == 0:
+ if part >= 80:
+ output.append(str_cls(2))
+ output.append(str_cls(part - 80))
+ elif part >= 40:
+ output.append(str_cls(1))
+ output.append(str_cls(part - 40))
+ else:
+ output.append(str_cls(0))
+ output.append(str_cls(part))
+ else:
+ output.append(str_cls(part))
+ part = 0
+
+ self._dotted = '.'.join(output)
+ return self._dotted
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None. If _map is not defined, the unicode string
+ is a string of dotted integers. If _map is defined and the dotted
+ string is present in the _map, the mapped value is returned.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self.dotted
+ if self._map is not None and self._native in self._map:
+ self._native = self._map[self._native]
+ return self._native
+
+
+class ObjectDescriptor(Primitive):
+ """
+ Represents an object descriptor from ASN.1 - no Python implementation
+ """
+
+ tag = 7
+
+
+class InstanceOf(Primitive):
+ """
+ Represents an instance from ASN.1 - no Python implementation
+ """
+
+ tag = 8
+
+
+class Real(Primitive):
+ """
+ Represents a real number from ASN.1 - no Python implementation
+ """
+
+ tag = 9
+
+
+class Enumerated(Integer):
+ """
+ Represents a enumerated list of integers from ASN.1 as a Python
+ unicode string
+ """
+
+ tag = 10
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ An integer or a unicode string from _map
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if not isinstance(value, int_types) and not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be an integer or a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if isinstance(value, str_cls):
+ if value not in self._reverse_map:
+ raise ValueError(unwrap(
+ '''
+ %s value "%s" is not a valid value
+ ''',
+ type_name(self),
+ value
+ ))
+
+ value = self._reverse_map[value]
+
+ elif value not in self._map:
+ raise ValueError(unwrap(
+ '''
+ %s value %s is not a valid value
+ ''',
+ type_name(self),
+ value
+ ))
+
+ Integer.set(self, value)
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ self._native = self._map[self.__int__()]
+ return self._native
+
+
+class UTF8String(AbstractString):
+ """
+ Represents a UTF-8 string from ASN.1 as a Python unicode string
+ """
+
+ tag = 12
+ _encoding = 'utf-8'
+
+
+class RelativeOid(ObjectIdentifier):
+ """
+ Represents an object identifier in ASN.1 as a Python unicode dotted
+ integer string
+ """
+
+ tag = 13
+
+
+class Sequence(Asn1Value):
+ """
+ Represents a sequence of fields from ASN.1 as a Python object with a
+ dict-like interface
+ """
+
+ tag = 16
+
+ class_ = 0
+ method = 1
+
+ # A list of child objects, in order of _fields
+ children = None
+
+ # Sequence overrides .contents to be a property so that the mutated state
+ # of child objects can be checked to ensure everything is up-to-date
+ _contents = None
+
+ # Variable to track if the object has been mutated
+ _mutated = False
+
+ # A list of tuples in one of the following forms.
+ #
+ # Option 1, a unicode string field name and a value class
+ #
+ # ("name", Asn1ValueClass)
+ #
+ # Option 2, same as Option 1, but with a dict of class params
+ #
+ # ("name", Asn1ValueClass, {'explicit': 5})
+ _fields = []
+
+ # A dict with keys being the name of a field and the value being a unicode
+ # string of the method name on self to call to get the spec for that field
+ _spec_callbacks = None
+
+ # A dict that maps unicode string field names to an index in _fields
+ _field_map = None
+
+ # A list in the same order as _fields that has tuples in the form (class_, tag)
+ _field_ids = None
+
+ # An optional 2-element tuple that defines the field names of an OID field
+ # and the field that the OID should be used to help decode. Works with the
+ # _oid_specs attribute.
+ _oid_pair = None
+
+ # A dict with keys that are unicode string OID values and values that are
+ # Asn1Value classes to use for decoding a variable-type field.
+ _oid_specs = None
+
+ # A 2-element tuple of the indexes in _fields of the OID and value fields
+ _oid_nums = None
+
+ # Predetermined field specs to optimize away calls to _determine_spec()
+ _precomputed_specs = None
+
+ def __init__(self, value=None, default=None, **kwargs):
+ """
+ Allows setting field values before passing everything else along to
+ Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+ """
+
+ Asn1Value.__init__(self, **kwargs)
+
+ check_existing = False
+ if value is None and default is not None:
+ check_existing = True
+ if self.children is None:
+ if self.contents is None:
+ check_existing = False
+ else:
+ self._parse_children()
+ value = default
+
+ if value is not None:
+ try:
+ # Fields are iterated in definition order to allow things like
+ # OID-based specs. Otherwise sometimes the value would be processed
+ # before the OID field, resulting in invalid value object creation.
+ if self._fields:
+ keys = [info[0] for info in self._fields]
+ unused_keys = set(value.keys())
+ else:
+ keys = value.keys()
+ unused_keys = set(keys)
+
+ for key in keys:
+ # If we are setting defaults, but a real value has already
+ # been set for the field, then skip it
+ if check_existing:
+ index = self._field_map[key]
+ if index < len(self.children) and self.children[index] is not VOID:
+ if key in unused_keys:
+ unused_keys.remove(key)
+ continue
+
+ if key in value:
+ self.__setitem__(key, value[key])
+ unused_keys.remove(key)
+
+ if len(unused_keys):
+ raise ValueError(unwrap(
+ '''
+ One or more unknown fields was passed to the constructor
+ of %s: %s
+ ''',
+ type_name(self),
+ ', '.join(sorted(list(unused_keys)))
+ ))
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ if self.children is None:
+ return self._contents
+
+ if self._is_mutated():
+ self._set_contents()
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._contents = value
+
+ def _is_mutated(self):
+ """
+ :return:
+ A boolean - if the sequence or any children (recursively) have been
+ mutated
+ """
+
+ mutated = self._mutated
+ if self.children is not None:
+ for child in self.children:
+ if isinstance(child, Sequence) or isinstance(child, SequenceOf):
+ mutated = mutated or child._is_mutated()
+
+ return mutated
+
+ def _lazy_child(self, index):
+ """
+ Builds a child object if the child has only been parsed into a tuple so far
+ """
+
+ child = self.children[index]
+ if child.__class__ == tuple:
+ child = self.children[index] = _build(*child)
+ return child
+
+ def __len__(self):
+ """
+ :return:
+ Integer
+ """
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return len(self.children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :raises:
+ KeyError - when a field name or index is invalid
+
+ :return:
+ The Asn1Value object of the field specified
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ if key >= len(self.children):
+ raise KeyError(unwrap(
+ '''
+ No field numbered %s is present in this %s
+ ''',
+ key,
+ type_name(self)
+ ))
+
+ try:
+ return self._lazy_child(key)
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def __setitem__(self, key, value):
+ """
+ Allows settings fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :param value:
+ A native Python datatype to set the field value to. This method will
+ construct the appropriate Asn1Value object from _fields.
+
+ :raises:
+ ValueError - when a field name or index is invalid
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(key)
+
+ new_value = self._make_value(field_name, field_spec, value_spec, field_params, value)
+
+ invalid_value = False
+ if isinstance(new_value, Any):
+ invalid_value = new_value.parsed is None
+ else:
+ invalid_value = new_value.contents is None
+
+ if invalid_value:
+ raise ValueError(unwrap(
+ '''
+ Value for field "%s" of %s is not set
+ ''',
+ field_name,
+ type_name(self)
+ ))
+
+ self.children[key] = new_value
+
+ if self._native is not None:
+ self._native[self._fields[key][0]] = self.children[key].native
+ self._mutated = True
+
+ def __delitem__(self, key):
+ """
+ Allows deleting optional or default fields by name or index
+
+ :param key:
+ A unicode string of the field name, or an integer of the field index
+
+ :raises:
+ ValueError - when a field name or index is invalid, or the field is not optional or defaulted
+ """
+
+ # We inline this check to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ if not isinstance(key, int_types):
+ if key not in self._field_map:
+ raise KeyError(unwrap(
+ '''
+ No field named "%s" defined for %s
+ ''',
+ key,
+ type_name(self)
+ ))
+ key = self._field_map[key]
+
+ name, _, params = self._fields[key]
+ if not params or ('default' not in params and 'optional' not in params):
+ raise ValueError(unwrap(
+ '''
+ Can not delete the value for the field "%s" of %s since it is
+ not optional or defaulted
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ if 'optional' in params:
+ self.children[key] = VOID
+ if self._native is not None:
+ self._native[name] = None
+ else:
+ self.__setitem__(key, None)
+ self._mutated = True
+
+ def __iter__(self):
+ """
+ :return:
+ An iterator of field key names
+ """
+
+ for info in self._fields:
+ yield info[0]
+
+ def _set_contents(self, force=False):
+ """
+ Updates the .contents attribute of the value with the encoded value of
+ all of the child objects
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ contents = BytesIO()
+ for index, info in enumerate(self._fields):
+ child = self.children[index]
+ if child is None:
+ child_dump = b''
+ elif child.__class__ == tuple:
+ if force:
+ child_dump = self._lazy_child(index).dump(force=force)
+ else:
+ child_dump = child[3] + child[4] + child[5]
+ else:
+ child_dump = child.dump(force=force)
+ # Skip values that are the same as the default
+ if info[2] and 'default' in info[2]:
+ default_value = info[1](**info[2])
+ if default_value.dump() == child_dump:
+ continue
+ contents.write(child_dump)
+ self._contents = contents.getvalue()
+
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _setup(self):
+ """
+ Generates _field_map, _field_ids and _oid_nums for use in parsing
+ """
+
+ cls = self.__class__
+ cls._field_map = {}
+ cls._field_ids = []
+ cls._precomputed_specs = []
+ for index, field in enumerate(cls._fields):
+ if len(field) < 3:
+ field = field + ({},)
+ cls._fields[index] = field
+ cls._field_map[field[0]] = index
+ cls._field_ids.append(_build_id_tuple(field[2], field[1]))
+
+ if cls._oid_pair is not None:
+ cls._oid_nums = (cls._field_map[cls._oid_pair[0]], cls._field_map[cls._oid_pair[1]])
+
+ for index, field in enumerate(cls._fields):
+ has_callback = cls._spec_callbacks is not None and field[0] in cls._spec_callbacks
+ is_mapped_oid = cls._oid_nums is not None and cls._oid_nums[1] == index
+ if has_callback or is_mapped_oid:
+ cls._precomputed_specs.append(None)
+ else:
+ cls._precomputed_specs.append((field[0], field[1], field[1], field[2], None))
+
+ def _determine_spec(self, index):
+ """
+ Determine how a value for a field should be constructed
+
+ :param index:
+ The field number
+
+ :return:
+ A tuple containing the following elements:
+ - unicode string of the field name
+ - Asn1Value class of the field spec
+ - Asn1Value class of the value spec
+ - None or dict of params to pass to the field spec
+ - None or Asn1Value class indicating the value spec was derived from an OID or a spec callback
+ """
+
+ name, field_spec, field_params = self._fields[index]
+ value_spec = field_spec
+ spec_override = None
+
+ if self._spec_callbacks is not None and name in self._spec_callbacks:
+ callback = self._spec_callbacks[name]
+ spec_override = callback(self)
+ if spec_override:
+ # Allow a spec callback to specify both the base spec and
+ # the override, for situations such as OctetString and parse_as
+ if spec_override.__class__ == tuple and len(spec_override) == 2:
+ field_spec, value_spec = spec_override
+ if value_spec is None:
+ value_spec = field_spec
+ spec_override = None
+ # When no field spec is specified, use a single return value as that
+ elif field_spec is None:
+ field_spec = spec_override
+ value_spec = field_spec
+ spec_override = None
+ else:
+ value_spec = spec_override
+
+ elif self._oid_nums is not None and self._oid_nums[1] == index:
+ oid = self._lazy_child(self._oid_nums[0]).native
+ if oid in self._oid_specs:
+ spec_override = self._oid_specs[oid]
+ value_spec = spec_override
+
+ return (name, field_spec, value_spec, field_params, spec_override)
+
+ def _make_value(self, field_name, field_spec, value_spec, field_params, value):
+ """
+ Contructs an appropriate Asn1Value object for a field
+
+ :param field_name:
+ A unicode string of the field name
+
+ :param field_spec:
+ An Asn1Value class that is the field spec
+
+ :param value_spec:
+ An Asn1Value class that is the vaue spec
+
+ :param field_params:
+ None or a dict of params for the field spec
+
+ :param value:
+ The value to construct an Asn1Value object from
+
+ :return:
+ An instance of a child class of Asn1Value
+ """
+
+ if value is None and 'optional' in field_params:
+ return VOID
+
+ specs_different = field_spec != value_spec
+ is_any = issubclass(field_spec, Any)
+
+ if issubclass(value_spec, Choice):
+ is_asn1value = isinstance(value, Asn1Value)
+ is_tuple = isinstance(value, tuple) and len(value) == 2
+ is_dict = isinstance(value, dict) and len(value) == 1
+ if not is_asn1value and not is_tuple and not is_dict:
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s, which has the
+ choice type of %s - value must be an instance of Asn1Value
+ ''',
+ field_name,
+ type_name(value_spec)
+ ))
+ if is_tuple or is_dict:
+ value = value_spec(value)
+ if not isinstance(value, value_spec):
+ wrapper = value_spec()
+ wrapper.validate(value.class_, value.tag, value.contents)
+ wrapper._parsed = value
+ new_value = wrapper
+ else:
+ new_value = value
+
+ elif isinstance(value, field_spec):
+ new_value = value
+ if specs_different:
+ new_value.parse(value_spec)
+
+ elif (not specs_different or is_any) and not isinstance(value, value_spec):
+ if (not is_any or specs_different) and isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ %s value must be %s, not %s
+ ''',
+ field_name,
+ type_name(value_spec),
+ type_name(value)
+ ))
+ new_value = value_spec(value, **field_params)
+
+ else:
+ if isinstance(value, value_spec):
+ new_value = value
+ else:
+ if isinstance(value, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ %s value must be %s, not %s
+ ''',
+ field_name,
+ type_name(value_spec),
+ type_name(value)
+ ))
+ new_value = value_spec(value)
+
+ # For when the field is OctetString or OctetBitString with embedded
+ # values we need to wrap the value in the field spec to get the
+ # appropriate encoded value.
+ if specs_different and not is_any:
+ wrapper = field_spec(value=new_value.dump(), **field_params)
+ wrapper._parsed = (new_value, new_value.__class__, None)
+ new_value = wrapper
+
+ new_value = _fix_tagging(new_value, field_params)
+
+ return new_value
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _fields.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ cls = self.__class__
+ if self._contents is None:
+ if self._fields:
+ self.children = [VOID] * len(self._fields)
+ for index, (_, _, params) in enumerate(self._fields):
+ if 'default' in params:
+ if cls._precomputed_specs[index]:
+ field_name, field_spec, value_spec, field_params, _ = cls._precomputed_specs[index]
+ else:
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(index)
+ self.children[index] = self._make_value(field_name, field_spec, value_spec, field_params, None)
+ return
+
+ try:
+ self.children = []
+ contents_length = len(self._contents)
+ child_pointer = 0
+ field = 0
+ field_len = len(self._fields)
+ parts = None
+ again = child_pointer < contents_length
+ while again:
+ if parts is None:
+ parts, child_pointer = _parse(self._contents, contents_length, pointer=child_pointer)
+ again = child_pointer < contents_length
+
+ if field < field_len:
+ _, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[field] or self._determine_spec(field))
+
+ # If the next value is optional or default, allow it to be absent
+ if field_params and ('optional' in field_params or 'default' in field_params):
+ if self._field_ids[field] != (parts[0], parts[2]) and field_spec != Any:
+
+ # See if the value is a valid choice before assuming
+ # that we have a missing optional or default value
+ choice_match = False
+ if issubclass(field_spec, Choice):
+ try:
+ tester = field_spec(**field_params)
+ tester.validate(parts[0], parts[2], parts[4])
+ choice_match = True
+ except (ValueError):
+ pass
+
+ if not choice_match:
+ if 'optional' in field_params:
+ self.children.append(VOID)
+ else:
+ self.children.append(field_spec(**field_params))
+ field += 1
+ again = True
+ continue
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ if spec_override:
+ child = parts + (field_spec, field_params, value_spec)
+ else:
+ child = parts + (field_spec, field_params)
+
+ # Handle situations where an optional or defaulted field definition is incorrect
+ elif field_len > 0 and field + 1 <= field_len:
+ missed_fields = []
+ prev_field = field - 1
+ while prev_field >= 0:
+ prev_field_info = self._fields[prev_field]
+ if len(prev_field_info) < 3:
+ break
+ if 'optional' in prev_field_info[2] or 'default' in prev_field_info[2]:
+ missed_fields.append(prev_field_info[0])
+ prev_field -= 1
+ plural = 's' if len(missed_fields) > 1 else ''
+ missed_field_names = ', '.join(missed_fields)
+ raise ValueError(unwrap(
+ '''
+ Data for field %s (%s class, %s method, tag %s) does
+ not match the field definition%s of %s
+ ''',
+ field + 1,
+ CLASS_NUM_TO_NAME_MAP.get(parts[0]),
+ METHOD_NUM_TO_NAME_MAP.get(parts[1]),
+ parts[2],
+ plural,
+ missed_field_names
+ ))
+
+ else:
+ child = parts
+
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+
+ self.children.append(child)
+ field += 1
+ parts = None
+
+ index = len(self.children)
+ while index < field_len:
+ name, field_spec, field_params = self._fields[index]
+ if 'default' in field_params:
+ self.children.append(field_spec(**field_params))
+ elif 'optional' in field_params:
+ self.children.append(VOID)
+ else:
+ raise ValueError(unwrap(
+ '''
+ Field "%s" is missing from structure
+ ''',
+ name
+ ))
+ index += 1
+
+ except (ValueError, TypeError) as e:
+ self.children = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def spec(self, field_name):
+ """
+ Determines the spec to use for the field specified. Depending on how
+ the spec is determined (_oid_pair or _spec_callbacks), it may be
+ necessary to set preceding field values before calling this. Usually
+ specs, if dynamic, are controlled by a preceding ObjectIdentifier
+ field.
+
+ :param field_name:
+ A unicode string of the field name to get the spec for
+
+ :return:
+ A child class of asn1crypto.core.Asn1Value that the field must be
+ encoded using
+ """
+
+ if not isinstance(field_name, str_cls):
+ raise TypeError(unwrap(
+ '''
+ field_name must be a unicode string, not %s
+ ''',
+ type_name(field_name)
+ ))
+
+ if self._fields is None:
+ raise ValueError(unwrap(
+ '''
+ Unable to retrieve spec for field %s in the class %s because
+ _fields has not been set
+ ''',
+ repr(field_name),
+ type_name(self)
+ ))
+
+ index = self._field_map[field_name]
+ info = self._determine_spec(index)
+
+ return info[2]
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ An OrderedDict or None. If an OrderedDict, all child values are
+ recursively converted to native representation also.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ if self.children is None:
+ self._parse_children(recurse=True)
+ try:
+ self._native = OrderedDict()
+ for index, child in enumerate(self.children):
+ if child.__class__ == tuple:
+ child = _build(*child)
+ self.children[index] = child
+ try:
+ name = self._fields[index][0]
+ except (IndexError):
+ name = str_cls(index)
+ self._native[name] = child.native
+ except (ValueError, TypeError) as e:
+ self._native = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another Sequence object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(Sequence, self)._copy(other, copy_func)
+ if self.children is not None:
+ self.children = []
+ for child in other.children:
+ if child.__class__ == tuple:
+ self.children.append(child)
+ else:
+ self.children.append(child.copy())
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ prefix = ' ' * nest_level
+ _basic_debug(prefix, self)
+ for field_name in self:
+ child = self._lazy_child(self._field_map[field_name])
+ if child is not VOID:
+ print('%s Field "%s"' % (prefix, field_name))
+ child.debug(nest_level + 3)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ # We can't force encoding if we don't have a spec
+ if force and self._fields == [] and self.__class__ is Sequence:
+ force = False
+
+ if force:
+ self._set_contents(force=force)
+
+ if self._fields and self.children is not None:
+ for index, (field_name, _, params) in enumerate(self._fields):
+ if self.children[index] is not VOID:
+ continue
+ if 'default' in params or 'optional' in params:
+ continue
+ raise ValueError(unwrap(
+ '''
+ Field "%s" is missing from structure
+ ''',
+ field_name
+ ))
+
+ return Asn1Value.dump(self)
+
+
+class SequenceOf(Asn1Value):
+ """
+ Represents a sequence (ordered) of a single type of values from ASN.1 as a
+ Python object with a list-like interface
+ """
+
+ tag = 16
+
+ class_ = 0
+ method = 1
+
+ # A list of child objects
+ children = None
+
+ # SequenceOf overrides .contents to be a property so that the mutated state
+ # of child objects can be checked to ensure everything is up-to-date
+ _contents = None
+
+ # Variable to track if the object has been mutated
+ _mutated = False
+
+ # An Asn1Value class to use when parsing children
+ _child_spec = None
+
+ def __init__(self, value=None, default=None, contents=None, spec=None, **kwargs):
+ """
+ Allows setting child objects and the _child_spec via the spec parameter
+ before passing everything else along to Asn1Value.__init__()
+
+ :param value:
+ A native Python datatype to initialize the object value with
+
+ :param default:
+ The default value if no value is specified
+
+ :param contents:
+ A byte string of the encoded contents of the value
+
+ :param spec:
+ A class derived from Asn1Value to use to parse children
+ """
+
+ if spec:
+ self._child_spec = spec
+
+ Asn1Value.__init__(self, **kwargs)
+
+ try:
+ if contents is not None:
+ self.contents = contents
+ else:
+ if value is None and default is not None:
+ value = default
+
+ if value is not None:
+ for index, child in enumerate(value):
+ self.__setitem__(index, child)
+
+ # Make sure a blank list is serialized
+ if self.contents is None:
+ self._set_contents()
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while constructing %s' % type_name(self),) + args
+ raise e
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ if self.children is None:
+ return self._contents
+
+ if self._is_mutated():
+ self._set_contents()
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._contents = value
+
+ def _is_mutated(self):
+ """
+ :return:
+ A boolean - if the sequence or any children (recursively) have been
+ mutated
+ """
+
+ mutated = self._mutated
+ if self.children is not None:
+ for child in self.children:
+ if isinstance(child, Sequence) or isinstance(child, SequenceOf):
+ mutated = mutated or child._is_mutated()
+
+ return mutated
+
+ def _lazy_child(self, index):
+ """
+ Builds a child object if the child has only been parsed into a tuple so far
+ """
+
+ child = self.children[index]
+ if child.__class__ == tuple:
+ child = _build(*child)
+ self.children[index] = child
+ return child
+
+ def _make_value(self, value):
+ """
+ Constructs a _child_spec value from a native Python data type, or
+ an appropriate Asn1Value object
+
+ :param value:
+ A native Python value, or some child of Asn1Value
+
+ :return:
+ An object of type _child_spec
+ """
+
+ if isinstance(value, self._child_spec):
+ new_value = value
+
+ elif issubclass(self._child_spec, Any):
+ if isinstance(value, Asn1Value):
+ new_value = value
+ else:
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s where the
+ _child_spec is Any - value must be an instance of Asn1Value
+ ''',
+ type_name(self)
+ ))
+
+ elif issubclass(self._child_spec, Choice):
+ if not isinstance(value, Asn1Value):
+ raise ValueError(unwrap(
+ '''
+ Can not set a native python value to %s where the
+ _child_spec is the choice type %s - value must be an
+ instance of Asn1Value
+ ''',
+ type_name(self),
+ self._child_spec.__name__
+ ))
+ if not isinstance(value, self._child_spec):
+ wrapper = self._child_spec()
+ wrapper.validate(value.class_, value.tag, value.contents)
+ wrapper._parsed = value
+ value = wrapper
+ new_value = value
+
+ else:
+ return self._child_spec(value=value)
+
+ params = {}
+ if self._child_spec.explicit:
+ params['explicit'] = self._child_spec.explicit
+ if self._child_spec.implicit:
+ params['implicit'] = (self._child_spec.class_, self._child_spec.tag)
+ return _fix_tagging(new_value, params)
+
+ def __len__(self):
+ """
+ :return:
+ An integer
+ """
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return len(self.children)
+
+ def __getitem__(self, key):
+ """
+ Allows accessing children via index
+
+ :param key:
+ Integer index of child
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ return self._lazy_child(key)
+
+ def __setitem__(self, key, value):
+ """
+ Allows overriding a child via index
+
+ :param key:
+ Integer index of child
+
+ :param value:
+ Native python datatype that will be passed to _child_spec to create
+ new child object
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ new_value = self._make_value(value)
+
+ # If adding at the end, create a space for the new value
+ if key == len(self.children):
+ self.children.append(None)
+ if self._native is not None:
+ self._native.append(None)
+
+ self.children[key] = new_value
+
+ if self._native is not None:
+ self._native[key] = self.children[key].native
+
+ self._mutated = True
+
+ def __delitem__(self, key):
+ """
+ Allows removing a child via index
+
+ :param key:
+ Integer index of child
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ self.children.pop(key)
+ if self._native is not None:
+ self._native.pop(key)
+
+ self._mutated = True
+
+ def __iter__(self):
+ """
+ :return:
+ An iter() of child objects
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ for index in range(0, len(self.children)):
+ yield self._lazy_child(index)
+
+ def __contains__(self, item):
+ """
+ :param item:
+ An object of the type cls._child_spec
+
+ :return:
+ A boolean if the item is contained in this SequenceOf
+ """
+
+ if item is None or item is VOID:
+ return False
+
+ if not isinstance(item, self._child_spec):
+ raise TypeError(unwrap(
+ '''
+ Checking membership in %s is only available for instances of
+ %s, not %s
+ ''',
+ type_name(self),
+ type_name(self._child_spec),
+ type_name(item)
+ ))
+
+ for child in self:
+ if child == item:
+ return True
+
+ return False
+
+ def append(self, value):
+ """
+ Allows adding a child to the end of the sequence
+
+ :param value:
+ Native python datatype that will be passed to _child_spec to create
+ new child object
+ """
+
+ # We inline this checks to prevent method invocation each time
+ if self.children is None:
+ self._parse_children()
+
+ self.children.append(self._make_value(value))
+
+ if self._native is not None:
+ self._native.append(self.children[-1].native)
+
+ self._mutated = True
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ contents = BytesIO()
+ for child in self:
+ contents.write(child.dump(force=force))
+ self._contents = contents.getvalue()
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _child_spec.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ try:
+ self.children = []
+ if self._contents is None:
+ return
+ contents_length = len(self._contents)
+ child_pointer = 0
+ while child_pointer < contents_length:
+ parts, child_pointer = _parse(self._contents, contents_length, pointer=child_pointer)
+ if self._child_spec:
+ child = parts + (self._child_spec,)
+ else:
+ child = parts
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+ self.children.append(child)
+ except (ValueError, TypeError) as e:
+ self.children = None
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def spec(self):
+ """
+ Determines the spec to use for child values.
+
+ :return:
+ A child class of asn1crypto.core.Asn1Value that child values must be
+ encoded using
+ """
+
+ return self._child_spec
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A list or None. If a list, all child values are recursively
+ converted to native representation also.
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ if self.children is None:
+ self._parse_children(recurse=True)
+ try:
+ self._native = [child.native for child in self]
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+ return self._native
+
+ def _copy(self, other, copy_func):
+ """
+ Copies the contents of another SequenceOf object to itself
+
+ :param object:
+ Another instance of the same class
+
+ :param copy_func:
+ An reference of copy.copy() or copy.deepcopy() to use when copying
+ lists, dicts and objects
+ """
+
+ super(SequenceOf, self)._copy(other, copy_func)
+ if self.children is not None:
+ self.children = []
+ for child in other.children:
+ if child.__class__ == tuple:
+ self.children.append(child)
+ else:
+ self.children.append(child.copy())
+
+ def debug(self, nest_level=1):
+ """
+ Show the binary data and parsed data in a tree structure
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ prefix = ' ' * nest_level
+ _basic_debug(prefix, self)
+ for child in self:
+ child.debug(nest_level + 1)
+
+ def dump(self, force=False):
+ """
+ Encodes the value using DER
+
+ :param force:
+ If the encoded contents already exist, clear them and regenerate
+ to ensure they are in DER format instead of BER format
+
+ :return:
+ A byte string of the DER-encoded value
+ """
+
+ # If the length is indefinite, force the re-encoding
+ if self._header is not None and self._header[-1:] == b'\x80':
+ force = True
+
+ if force:
+ self._set_contents(force=force)
+
+ return Asn1Value.dump(self)
+
+
+class Set(Sequence):
+ """
+ Represents a set of fields (unordered) from ASN.1 as a Python object with a
+ dict-like interface
+ """
+
+ method = 1
+ class_ = 0
+ tag = 17
+
+ # A dict of 2-element tuples in the form (class_, tag) as keys and integers
+ # as values that are the index of the field in _fields
+ _field_ids = None
+
+ def _setup(self):
+ """
+ Generates _field_map, _field_ids and _oid_nums for use in parsing
+ """
+
+ cls = self.__class__
+ cls._field_map = {}
+ cls._field_ids = {}
+ cls._precomputed_specs = []
+ for index, field in enumerate(cls._fields):
+ if len(field) < 3:
+ field = field + ({},)
+ cls._fields[index] = field
+ cls._field_map[field[0]] = index
+ cls._field_ids[_build_id_tuple(field[2], field[1])] = index
+
+ if cls._oid_pair is not None:
+ cls._oid_nums = (cls._field_map[cls._oid_pair[0]], cls._field_map[cls._oid_pair[1]])
+
+ for index, field in enumerate(cls._fields):
+ has_callback = cls._spec_callbacks is not None and field[0] in cls._spec_callbacks
+ is_mapped_oid = cls._oid_nums is not None and cls._oid_nums[1] == index
+ if has_callback or is_mapped_oid:
+ cls._precomputed_specs.append(None)
+ else:
+ cls._precomputed_specs.append((field[0], field[1], field[1], field[2], None))
+
+ def _parse_children(self, recurse=False):
+ """
+ Parses the contents and generates Asn1Value objects based on the
+ definitions from _fields.
+
+ :param recurse:
+ If child objects that are Sequence or SequenceOf objects should
+ be recursively parsed
+
+ :raises:
+ ValueError - when an error occurs parsing child objects
+ """
+
+ cls = self.__class__
+ if self._contents is None:
+ if self._fields:
+ self.children = [VOID] * len(self._fields)
+ for index, (_, _, params) in enumerate(self._fields):
+ if 'default' in params:
+ if cls._precomputed_specs[index]:
+ field_name, field_spec, value_spec, field_params, _ = cls._precomputed_specs[index]
+ else:
+ field_name, field_spec, value_spec, field_params, _ = self._determine_spec(index)
+ self.children[index] = self._make_value(field_name, field_spec, value_spec, field_params, None)
+ return
+
+ try:
+ child_map = {}
+ contents_length = len(self.contents)
+ child_pointer = 0
+ seen_field = 0
+ while child_pointer < contents_length:
+ parts, child_pointer = _parse(self.contents, contents_length, pointer=child_pointer)
+
+ id_ = (parts[0], parts[2])
+
+ field = self._field_ids.get(id_)
+ if field is None:
+ raise ValueError(unwrap(
+ '''
+ Data for field %s (%s class, %s method, tag %s) does
+ not match any of the field definitions
+ ''',
+ seen_field,
+ CLASS_NUM_TO_NAME_MAP.get(parts[0]),
+ METHOD_NUM_TO_NAME_MAP.get(parts[1]),
+ parts[2],
+ ))
+
+ _, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[field] or self._determine_spec(field))
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ if spec_override:
+ child = parts + (field_spec, field_params, value_spec)
+ else:
+ child = parts + (field_spec, field_params)
+
+ if recurse:
+ child = _build(*child)
+ if isinstance(child, (Sequence, SequenceOf)):
+ child._parse_children(recurse=True)
+
+ child_map[field] = child
+ seen_field += 1
+
+ total_fields = len(self._fields)
+
+ for index in range(0, total_fields):
+ if index in child_map:
+ continue
+
+ name, field_spec, value_spec, field_params, spec_override = (
+ cls._precomputed_specs[index] or self._determine_spec(index))
+
+ if field_spec is None or (spec_override and issubclass(field_spec, Any)):
+ field_spec = value_spec
+ spec_override = None
+
+ missing = False
+
+ if not field_params:
+ missing = True
+ elif 'optional' not in field_params and 'default' not in field_params:
+ missing = True
+ elif 'optional' in field_params:
+ child_map[index] = VOID
+ elif 'default' in field_params:
+ child_map[index] = field_spec(**field_params)
+
+ if missing:
+ raise ValueError(unwrap(
+ '''
+ Missing required field "%s" from %s
+ ''',
+ name,
+ type_name(self)
+ ))
+
+ self.children = []
+ for index in range(0, total_fields):
+ self.children.append(child_map[index])
+
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(self),) + args
+ raise e
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object.
+
+ This method is overridden because a Set needs to be encoded by
+ removing defaulted fields and then sorting the fields by tag.
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ child_tag_encodings = []
+ for index, child in enumerate(self.children):
+ child_encoding = child.dump(force=force)
+
+ # Skip encoding defaulted children
+ name, spec, field_params = self._fields[index]
+ if 'default' in field_params:
+ if spec(**field_params).dump() == child_encoding:
+ continue
+
+ child_tag_encodings.append((child.tag, child_encoding))
+ child_tag_encodings.sort(key=lambda ct: ct[0])
+
+ self._contents = b''.join([ct[1] for ct in child_tag_encodings])
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class SetOf(SequenceOf):
+ """
+ Represents a set (unordered) of a single type of values from ASN.1 as a
+ Python object with a list-like interface
+ """
+
+ tag = 17
+
+ def _set_contents(self, force=False):
+ """
+ Encodes all child objects into the contents for this object.
+
+ This method is overridden because a SetOf needs to be encoded by
+ sorting the child encodings.
+
+ :param force:
+ Ensure all contents are in DER format instead of possibly using
+ cached BER-encoded data
+ """
+
+ if self.children is None:
+ self._parse_children()
+
+ child_encodings = []
+ for child in self:
+ child_encodings.append(child.dump(force=force))
+
+ self._contents = b''.join(sorted(child_encodings))
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class EmbeddedPdv(Sequence):
+ """
+ A sequence structure
+ """
+
+ tag = 11
+
+
+class NumericString(AbstractString):
+ """
+ Represents a numeric string from ASN.1 as a Python unicode string
+ """
+
+ tag = 18
+ _encoding = 'latin1'
+
+
+class PrintableString(AbstractString):
+ """
+ Represents a printable string from ASN.1 as a Python unicode string
+ """
+
+ tag = 19
+ _encoding = 'latin1'
+
+
+class TeletexString(AbstractString):
+ """
+ Represents a teletex string from ASN.1 as a Python unicode string
+ """
+
+ tag = 20
+ _encoding = 'teletex'
+
+
+class VideotexString(OctetString):
+ """
+ Represents a videotex string from ASN.1 as a Python byte string
+ """
+
+ tag = 21
+
+
+class IA5String(AbstractString):
+ """
+ Represents an IA5 string from ASN.1 as a Python unicode string
+ """
+
+ tag = 22
+ _encoding = 'ascii'
+
+
+class AbstractTime(AbstractString):
+ """
+ Represents a time from ASN.1 as a Python datetime.datetime object
+ """
+
+ @property
+ def _parsed_time(self):
+ """
+ The parsed datetime string.
+
+ :raises:
+ ValueError - when an invalid value is passed
+
+ :return:
+ A dict with the parsed values
+ """
+
+ string = str_cls(self)
+
+ m = self._TIMESTRING_RE.match(string)
+ if not m:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s to a %s
+ ''',
+ string,
+ type_name(self),
+ ))
+
+ groups = m.groupdict()
+
+ tz = None
+ if groups['zulu']:
+ tz = timezone.utc
+ elif groups['dsign']:
+ sign = 1 if groups['dsign'] == '+' else -1
+ tz = create_timezone(sign * timedelta(
+ hours=int(groups['dhour']),
+ minutes=int(groups['dminute'] or 0)
+ ))
+
+ if groups['fraction']:
+ # Compute fraction in microseconds
+ fract = Fraction(
+ int(groups['fraction']),
+ 10 ** len(groups['fraction'])
+ ) * 1000000
+
+ if groups['minute'] is None:
+ fract *= 3600
+ elif groups['second'] is None:
+ fract *= 60
+
+ fract_usec = int(fract.limit_denominator(1))
+
+ else:
+ fract_usec = 0
+
+ return {
+ 'year': int(groups['year']),
+ 'month': int(groups['month']),
+ 'day': int(groups['day']),
+ 'hour': int(groups['hour']),
+ 'minute': int(groups['minute'] or 0),
+ 'second': int(groups['second'] or 0),
+ 'tzinfo': tz,
+ 'fraction': fract_usec,
+ }
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A datetime.datetime object, asn1crypto.util.extended_datetime object or
+ None. The datetime object is usually timezone aware. If it's naive, then
+ it's in the sender's local time; see X.680 sect. 42.3
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ parsed = self._parsed_time
+
+ fraction = parsed.pop('fraction', 0)
+
+ value = self._get_datetime(parsed)
+
+ if fraction:
+ value += timedelta(microseconds=fraction)
+
+ self._native = value
+
+ return self._native
+
+
+class UTCTime(AbstractTime):
+ """
+ Represents a UTC time from ASN.1 as a timezone aware Python datetime.datetime object
+ """
+
+ tag = 23
+
+ # Regular expression for UTCTime as described in X.680 sect. 43 and ISO 8601
+ _TIMESTRING_RE = re.compile(r'''
+ ^
+ # YYMMDD
+ (?P<year>\d{2})
+ (?P<month>\d{2})
+ (?P<day>\d{2})
+
+ # hhmm or hhmmss
+ (?P<hour>\d{2})
+ (?P<minute>\d{2})
+ (?P<second>\d{2})?
+
+ # Matches nothing, needed because GeneralizedTime uses this.
+ (?P<fraction>)
+
+ # Z or [-+]hhmm
+ (?:
+ (?P<zulu>Z)
+ |
+ (?:
+ (?P<dsign>[-+])
+ (?P<dhour>\d{2})
+ (?P<dminute>\d{2})
+ )
+ )
+ $
+ ''', re.X)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string or a datetime.datetime object
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, datetime):
+ if not value.tzinfo:
+ raise ValueError('Must be timezone aware')
+
+ # Convert value to UTC.
+ value = value.astimezone(utc_with_dst)
+
+ if not 1950 <= value.year <= 2049:
+ raise ValueError('Year of the UTCTime is not in range [1950, 2049], use GeneralizedTime instead')
+
+ value = value.strftime('%y%m%d%H%M%SZ')
+ if _PY2:
+ value = value.decode('ascii')
+
+ AbstractString.set(self, value)
+ # Set it to None and let the class take care of converting the next
+ # time that .native is called
+ self._native = None
+
+ def _get_datetime(self, parsed):
+ """
+ Create a datetime object from the parsed time.
+
+ :return:
+ An aware datetime.datetime object
+ """
+
+ # X.680 only specifies that UTCTime is not using a century.
+ # So "18" could as well mean 2118 or 1318.
+ # X.509 and CMS specify to use UTCTime for years earlier than 2050.
+ # Assume that UTCTime is only used for years [1950, 2049].
+ if parsed['year'] < 50:
+ parsed['year'] += 2000
+ else:
+ parsed['year'] += 1900
+
+ return datetime(**parsed)
+
+
+class GeneralizedTime(AbstractTime):
+ """
+ Represents a generalized time from ASN.1 as a Python datetime.datetime
+ object or asn1crypto.util.extended_datetime object in UTC
+ """
+
+ tag = 24
+
+ # Regular expression for GeneralizedTime as described in X.680 sect. 42 and ISO 8601
+ _TIMESTRING_RE = re.compile(r'''
+ ^
+ # YYYYMMDD
+ (?P<year>\d{4})
+ (?P<month>\d{2})
+ (?P<day>\d{2})
+
+ # hh or hhmm or hhmmss
+ (?P<hour>\d{2})
+ (?:
+ (?P<minute>\d{2})
+ (?P<second>\d{2})?
+ )?
+
+ # Optional fraction; [.,]dddd (one or more decimals)
+ # If Seconds are given, it's fractions of Seconds.
+ # Else if Minutes are given, it's fractions of Minutes.
+ # Else it's fractions of Hours.
+ (?:
+ [,.]
+ (?P<fraction>\d+)
+ )?
+
+ # Optional timezone. If left out, the time is in local time.
+ # Z or [-+]hh or [-+]hhmm
+ (?:
+ (?P<zulu>Z)
+ |
+ (?:
+ (?P<dsign>[-+])
+ (?P<dhour>\d{2})
+ (?P<dminute>\d{2})?
+ )
+ )?
+ $
+ ''', re.X)
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string, a datetime.datetime object or an
+ asn1crypto.util.extended_datetime object
+
+ :raises:
+ ValueError - when an invalid value is passed
+ """
+
+ if isinstance(value, (datetime, extended_datetime)):
+ if not value.tzinfo:
+ raise ValueError('Must be timezone aware')
+
+ # Convert value to UTC.
+ value = value.astimezone(utc_with_dst)
+
+ if value.microsecond:
+ fraction = '.' + str(value.microsecond).zfill(6).rstrip('0')
+ else:
+ fraction = ''
+
+ value = value.strftime('%Y%m%d%H%M%S') + fraction + 'Z'
+ if _PY2:
+ value = value.decode('ascii')
+
+ AbstractString.set(self, value)
+ # Set it to None and let the class take care of converting the next
+ # time that .native is called
+ self._native = None
+
+ def _get_datetime(self, parsed):
+ """
+ Create a datetime object from the parsed time.
+
+ :return:
+ A datetime.datetime object or asn1crypto.util.extended_datetime object.
+ It may or may not be aware.
+ """
+
+ if parsed['year'] == 0:
+ # datetime does not support year 0. Use extended_datetime instead.
+ return extended_datetime(**parsed)
+ else:
+ return datetime(**parsed)
+
+
+class GraphicString(AbstractString):
+ """
+ Represents a graphic string from ASN.1 as a Python unicode string
+ """
+
+ tag = 25
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class VisibleString(AbstractString):
+ """
+ Represents a visible string from ASN.1 as a Python unicode string
+ """
+
+ tag = 26
+ _encoding = 'latin1'
+
+
+class GeneralString(AbstractString):
+ """
+ Represents a general string from ASN.1 as a Python unicode string
+ """
+
+ tag = 27
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class UniversalString(AbstractString):
+ """
+ Represents a universal string from ASN.1 as a Python unicode string
+ """
+
+ tag = 28
+ _encoding = 'utf-32-be'
+
+
+class CharacterString(AbstractString):
+ """
+ Represents a character string from ASN.1 as a Python unicode string
+ """
+
+ tag = 29
+ # This is technically not correct since this type can contain any charset
+ _encoding = 'latin1'
+
+
+class BMPString(AbstractString):
+ """
+ Represents a BMP string from ASN.1 as a Python unicode string
+ """
+
+ tag = 30
+ _encoding = 'utf-16-be'
+
+
+def _basic_debug(prefix, self):
+ """
+ Prints out basic information about an Asn1Value object. Extracted for reuse
+ among different classes that customize the debug information.
+
+ :param prefix:
+ A unicode string of spaces to prefix output line with
+
+ :param self:
+ The object to print the debugging information about
+ """
+
+ print('%s%s Object #%s' % (prefix, type_name(self), id(self)))
+ if self._header:
+ print('%s Header: 0x%s' % (prefix, binascii.hexlify(self._header or b'').decode('utf-8')))
+
+ has_header = self.method is not None and self.class_ is not None and self.tag is not None
+ if has_header:
+ method_name = METHOD_NUM_TO_NAME_MAP.get(self.method)
+ class_name = CLASS_NUM_TO_NAME_MAP.get(self.class_)
+
+ if self.explicit is not None:
+ for class_, tag in self.explicit:
+ print(
+ '%s %s tag %s (explicitly tagged)' %
+ (
+ prefix,
+ CLASS_NUM_TO_NAME_MAP.get(class_),
+ tag
+ )
+ )
+ if has_header:
+ print('%s %s %s %s' % (prefix, method_name, class_name, self.tag))
+
+ elif self.implicit:
+ if has_header:
+ print('%s %s %s tag %s (implicitly tagged)' % (prefix, method_name, class_name, self.tag))
+
+ elif has_header:
+ print('%s %s %s tag %s' % (prefix, method_name, class_name, self.tag))
+
+ if self._trailer:
+ print('%s Trailer: 0x%s' % (prefix, binascii.hexlify(self._trailer or b'').decode('utf-8')))
+
+ print('%s Data: 0x%s' % (prefix, binascii.hexlify(self.contents or b'').decode('utf-8')))
+
+
+def _tag_type_to_explicit_implicit(params):
+ """
+ Converts old-style "tag_type" and "tag" params to "explicit" and "implicit"
+
+ :param params:
+ A dict of parameters to convert from tag_type/tag to explicit/implicit
+ """
+
+ if 'tag_type' in params:
+ if params['tag_type'] == 'explicit':
+ params['explicit'] = (params.get('class', 2), params['tag'])
+ elif params['tag_type'] == 'implicit':
+ params['implicit'] = (params.get('class', 2), params['tag'])
+ del params['tag_type']
+ del params['tag']
+ if 'class' in params:
+ del params['class']
+
+
+def _fix_tagging(value, params):
+ """
+ Checks if a value is properly tagged based on the spec, and re/untags as
+ necessary
+
+ :param value:
+ An Asn1Value object
+
+ :param params:
+ A dict of spec params
+
+ :return:
+ An Asn1Value that is properly tagged
+ """
+
+ _tag_type_to_explicit_implicit(params)
+
+ retag = False
+ if 'implicit' not in params:
+ if value.implicit is not False:
+ retag = True
+ else:
+ if isinstance(params['implicit'], tuple):
+ class_, tag = params['implicit']
+ else:
+ tag = params['implicit']
+ class_ = 'context'
+ if value.implicit is False:
+ retag = True
+ elif value.class_ != CLASS_NAME_TO_NUM_MAP[class_] or value.tag != tag:
+ retag = True
+
+ if params.get('explicit') != value.explicit:
+ retag = True
+
+ if retag:
+ return value.retag(params)
+ return value
+
+
+def _build_id_tuple(params, spec):
+ """
+ Builds a 2-element tuple used to identify fields by grabbing the class_
+ and tag from an Asn1Value class and the params dict being passed to it
+
+ :param params:
+ A dict of params to pass to spec
+
+ :param spec:
+ An Asn1Value class
+
+ :return:
+ A 2-element integer tuple in the form (class_, tag)
+ """
+
+ # Handle situations where the spec is not known at setup time
+ if spec is None:
+ return (None, None)
+
+ required_class = spec.class_
+ required_tag = spec.tag
+
+ _tag_type_to_explicit_implicit(params)
+
+ if 'explicit' in params:
+ if isinstance(params['explicit'], tuple):
+ required_class, required_tag = params['explicit']
+ else:
+ required_class = 2
+ required_tag = params['explicit']
+ elif 'implicit' in params:
+ if isinstance(params['implicit'], tuple):
+ required_class, required_tag = params['implicit']
+ else:
+ required_class = 2
+ required_tag = params['implicit']
+ if required_class is not None and not isinstance(required_class, int_types):
+ required_class = CLASS_NAME_TO_NUM_MAP[required_class]
+
+ required_class = params.get('class_', required_class)
+ required_tag = params.get('tag', required_tag)
+
+ return (required_class, required_tag)
+
+
+def _int_to_bit_tuple(value, bits):
+ """
+ Format value as a tuple of 1s and 0s.
+
+ :param value:
+ A non-negative integer to format
+
+ :param bits:
+ Number of bits in the output
+
+ :return:
+ A tuple of 1s and 0s with bits members.
+ """
+
+ if not value and not bits:
+ return ()
+
+ result = tuple(map(int, format(value, '0{0}b'.format(bits))))
+ if len(result) != bits:
+ raise ValueError('Result too large: {0} > {1}'.format(len(result), bits))
+
+ return result
+
+
+_UNIVERSAL_SPECS = {
+ 1: Boolean,
+ 2: Integer,
+ 3: BitString,
+ 4: OctetString,
+ 5: Null,
+ 6: ObjectIdentifier,
+ 7: ObjectDescriptor,
+ 8: InstanceOf,
+ 9: Real,
+ 10: Enumerated,
+ 11: EmbeddedPdv,
+ 12: UTF8String,
+ 13: RelativeOid,
+ 16: Sequence,
+ 17: Set,
+ 18: NumericString,
+ 19: PrintableString,
+ 20: TeletexString,
+ 21: VideotexString,
+ 22: IA5String,
+ 23: UTCTime,
+ 24: GeneralizedTime,
+ 25: GraphicString,
+ 26: VisibleString,
+ 27: GeneralString,
+ 28: UniversalString,
+ 29: CharacterString,
+ 30: BMPString
+}
+
+
+def _build(class_, method, tag, header, contents, trailer, spec=None, spec_params=None, nested_spec=None):
+ """
+ Builds an Asn1Value object generically, or using a spec with optional params
+
+ :param class_:
+ An integer representing the ASN.1 class
+
+ :param method:
+ An integer representing the ASN.1 method
+
+ :param tag:
+ An integer representing the ASN.1 tag
+
+ :param header:
+ A byte string of the ASN.1 header (class, method, tag, length)
+
+ :param contents:
+ A byte string of the ASN.1 value
+
+ :param trailer:
+ A byte string of any ASN.1 trailer (only used by indefinite length encodings)
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :param nested_spec:
+ For certain Asn1Value classes (such as OctetString and BitString), the
+ contents can be further parsed and interpreted as another Asn1Value.
+ This parameter controls the spec for that sub-parsing.
+
+ :return:
+ An object of the type spec, or if not specified, a child of Asn1Value
+ """
+
+ if spec_params is not None:
+ _tag_type_to_explicit_implicit(spec_params)
+
+ if header is None:
+ return VOID
+
+ header_set = False
+
+ # If an explicit specification was passed in, make sure it matches
+ if spec is not None:
+ # If there is explicit tagging and contents, we have to split
+ # the header and trailer off before we do the parsing
+ no_explicit = spec_params and 'no_explicit' in spec_params
+ if not no_explicit and (spec.explicit or (spec_params and 'explicit' in spec_params)):
+ if spec_params:
+ value = spec(**spec_params)
+ else:
+ value = spec()
+ original_explicit = value.explicit
+ explicit_info = reversed(original_explicit)
+ parsed_class = class_
+ parsed_method = method
+ parsed_tag = tag
+ to_parse = contents
+ explicit_header = header
+ explicit_trailer = trailer or b''
+ for expected_class, expected_tag in explicit_info:
+ if parsed_class != expected_class:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged class should have been
+ %s, but %s was found
+ ''',
+ type_name(value),
+ CLASS_NUM_TO_NAME_MAP.get(expected_class),
+ CLASS_NUM_TO_NAME_MAP.get(parsed_class, parsed_class)
+ ))
+ if parsed_method != 1:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged method should have
+ been %s, but %s was found
+ ''',
+ type_name(value),
+ METHOD_NUM_TO_NAME_MAP.get(1),
+ METHOD_NUM_TO_NAME_MAP.get(parsed_method, parsed_method)
+ ))
+ if parsed_tag != expected_tag:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - explicitly-tagged tag should have been
+ %s, but %s was found
+ ''',
+ type_name(value),
+ expected_tag,
+ parsed_tag
+ ))
+ info, _ = _parse(to_parse, len(to_parse))
+ parsed_class, parsed_method, parsed_tag, parsed_header, to_parse, parsed_trailer = info
+
+ if not isinstance(value, Choice):
+ explicit_header += parsed_header
+ explicit_trailer = parsed_trailer + explicit_trailer
+
+ value = _build(*info, spec=spec, spec_params={'no_explicit': True})
+ value._header = explicit_header
+ value._trailer = explicit_trailer
+ value.explicit = original_explicit
+ header_set = True
+ else:
+ if spec_params:
+ value = spec(contents=contents, **spec_params)
+ else:
+ value = spec(contents=contents)
+
+ if spec is Any:
+ pass
+
+ elif isinstance(value, Choice):
+ value.validate(class_, tag, contents)
+ try:
+ # Force parsing the Choice now
+ value.contents = header + value.contents
+ header = b''
+ value.parse()
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args
+ raise e
+
+ else:
+ if class_ != value.class_:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - class should have been %s, but %s was
+ found
+ ''',
+ type_name(value),
+ CLASS_NUM_TO_NAME_MAP.get(value.class_),
+ CLASS_NUM_TO_NAME_MAP.get(class_, class_)
+ ))
+ if method != value.method:
+ # Allow parsing a primitive method as constructed if the value
+ # is indefinite length. This is to allow parsing BER.
+ ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00'
+ if not ber_indef or not isinstance(value, Constructable):
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - method should have been %s, but %s was found
+ ''',
+ type_name(value),
+ METHOD_NUM_TO_NAME_MAP.get(value.method),
+ METHOD_NUM_TO_NAME_MAP.get(method, method)
+ ))
+ else:
+ value.method = method
+ value._indefinite = True
+ if tag != value.tag:
+ if isinstance(value._bad_tag, tuple):
+ is_bad_tag = tag in value._bad_tag
+ else:
+ is_bad_tag = tag == value._bad_tag
+ if not is_bad_tag:
+ raise ValueError(unwrap(
+ '''
+ Error parsing %s - tag should have been %s, but %s was found
+ ''',
+ type_name(value),
+ value.tag,
+ tag
+ ))
+
+ # For explicitly tagged, un-speced parsings, we use a generic container
+ # since we will be parsing the contents and discarding the outer object
+ # anyway a little further on
+ elif spec_params and 'explicit' in spec_params:
+ original_value = Asn1Value(contents=contents, **spec_params)
+ original_explicit = original_value.explicit
+
+ to_parse = contents
+ explicit_header = header
+ explicit_trailer = trailer or b''
+ for expected_class, expected_tag in reversed(original_explicit):
+ info, _ = _parse(to_parse, len(to_parse))
+ _, _, _, parsed_header, to_parse, parsed_trailer = info
+ explicit_header += parsed_header
+ explicit_trailer = parsed_trailer + explicit_trailer
+ value = _build(*info, spec=spec, spec_params={'no_explicit': True})
+ value._header = header + value._header
+ value._trailer += trailer or b''
+ value.explicit = original_explicit
+ header_set = True
+
+ # If no spec was specified, allow anything and just process what
+ # is in the input data
+ else:
+ if tag not in _UNIVERSAL_SPECS:
+ raise ValueError(unwrap(
+ '''
+ Unknown element - %s class, %s method, tag %s
+ ''',
+ CLASS_NUM_TO_NAME_MAP.get(class_),
+ METHOD_NUM_TO_NAME_MAP.get(method),
+ tag
+ ))
+
+ spec = _UNIVERSAL_SPECS[tag]
+
+ value = spec(contents=contents, class_=class_)
+ ber_indef = method == 1 and value.method == 0 and trailer == b'\x00\x00'
+ if ber_indef and isinstance(value, Constructable):
+ value._indefinite = True
+ value.method = method
+
+ if not header_set:
+ value._header = header
+ value._trailer = trailer or b''
+
+ # Destroy any default value that our contents have overwritten
+ value._native = None
+
+ if nested_spec:
+ try:
+ value.parse(nested_spec)
+ except (ValueError, TypeError) as e:
+ args = e.args[1:]
+ e.args = (e.args[0] + '\n while parsing %s' % type_name(value),) + args
+ raise e
+
+ return value
+
+
+def _parse_build(encoded_data, pointer=0, spec=None, spec_params=None, strict=False):
+ """
+ Parses a byte string generically, or using a spec with optional params
+
+ :param encoded_data:
+ A byte string that contains BER-encoded data
+
+ :param pointer:
+ The index in the byte string to parse from
+
+ :param spec:
+ A class derived from Asn1Value that defines what class_ and tag the
+ value should have, and the semantics of the encoded value. The
+ return value will be of this type. If omitted, the encoded value
+ will be decoded using the standard universal tag based on the
+ encoded tag number.
+
+ :param spec_params:
+ A dict of params to pass to the spec object
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :return:
+ A 2-element tuple:
+ - 0: An object of the type spec, or if not specified, a child of Asn1Value
+ - 1: An integer indicating how many bytes were consumed
+ """
+
+ encoded_len = len(encoded_data)
+ info, new_pointer = _parse(encoded_data, encoded_len, pointer)
+ if strict and new_pointer != pointer + encoded_len:
+ extra_bytes = pointer + encoded_len - new_pointer
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % extra_bytes)
+ return (_build(*info, spec=spec, spec_params=spec_params), new_pointer)
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/crl.py b/contrib/python/asn1crypto/py3/asn1crypto/crl.py
new file mode 100644
index 0000000000..84cb168393
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/crl.py
@@ -0,0 +1,536 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for certificate revocation lists (CRL). Exports the
+following items:
+
+ - CertificateList()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import hashlib
+
+from .algos import SignedDigestAlgorithm
+from .core import (
+ Boolean,
+ Enumerated,
+ GeneralizedTime,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+)
+from .x509 import (
+ AuthorityInfoAccessSyntax,
+ AuthorityKeyIdentifier,
+ CRLDistributionPoints,
+ DistributionPointName,
+ GeneralNames,
+ Name,
+ ReasonFlags,
+ Time,
+)
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ 2: 'v3',
+ }
+
+
+class IssuingDistributionPoint(Sequence):
+ _fields = [
+ ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
+ ('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}),
+ ('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}),
+ ('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}),
+ ('indirect_crl', Boolean, {'implicit': 4, 'default': False}),
+ ('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}),
+ ]
+
+
+class TBSCertListExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.18': 'issuer_alt_name',
+ '2.5.29.20': 'crl_number',
+ '2.5.29.27': 'delta_crl_indicator',
+ '2.5.29.28': 'issuing_distribution_point',
+ '2.5.29.35': 'authority_key_identifier',
+ '2.5.29.46': 'freshest_crl',
+ '1.3.6.1.5.5.7.1.1': 'authority_information_access',
+ }
+
+
+class TBSCertListExtension(Sequence):
+ _fields = [
+ ('extn_id', TBSCertListExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'issuer_alt_name': GeneralNames,
+ 'crl_number': Integer,
+ 'delta_crl_indicator': Integer,
+ 'issuing_distribution_point': IssuingDistributionPoint,
+ 'authority_key_identifier': AuthorityKeyIdentifier,
+ 'freshest_crl': CRLDistributionPoints,
+ 'authority_information_access': AuthorityInfoAccessSyntax,
+ }
+
+
+class TBSCertListExtensions(SequenceOf):
+ _child_spec = TBSCertListExtension
+
+
+class CRLReason(Enumerated):
+ _map = {
+ 0: 'unspecified',
+ 1: 'key_compromise',
+ 2: 'ca_compromise',
+ 3: 'affiliation_changed',
+ 4: 'superseded',
+ 5: 'cessation_of_operation',
+ 6: 'certificate_hold',
+ 8: 'remove_from_crl',
+ 9: 'privilege_withdrawn',
+ 10: 'aa_compromise',
+ }
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A unicode string with revocation description that is suitable to
+ show to end-users. Starts with a lower case letter and phrased in
+ such a way that it makes sense after the phrase "because of" or
+ "due to".
+ """
+
+ return {
+ 'unspecified': 'an unspecified reason',
+ 'key_compromise': 'a compromised key',
+ 'ca_compromise': 'the CA being compromised',
+ 'affiliation_changed': 'an affiliation change',
+ 'superseded': 'certificate supersession',
+ 'cessation_of_operation': 'a cessation of operation',
+ 'certificate_hold': 'a certificate hold',
+ 'remove_from_crl': 'removal from the CRL',
+ 'privilege_withdrawn': 'privilege withdrawl',
+ 'aa_compromise': 'the AA being compromised',
+ }[self.native]
+
+
+class CRLEntryExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.21': 'crl_reason',
+ '2.5.29.23': 'hold_instruction_code',
+ '2.5.29.24': 'invalidity_date',
+ '2.5.29.29': 'certificate_issuer',
+ }
+
+
+class CRLEntryExtension(Sequence):
+ _fields = [
+ ('extn_id', CRLEntryExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'crl_reason': CRLReason,
+ 'hold_instruction_code': ObjectIdentifier,
+ 'invalidity_date': GeneralizedTime,
+ 'certificate_issuer': GeneralNames,
+ }
+
+
+class CRLEntryExtensions(SequenceOf):
+ _child_spec = CRLEntryExtension
+
+
+class RevokedCertificate(Sequence):
+ _fields = [
+ ('user_certificate', Integer),
+ ('revocation_date', Time),
+ ('crl_entry_extensions', CRLEntryExtensions, {'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _crl_reason_value = None
+ _invalidity_date_value = None
+ _certificate_issuer_value = None
+ _issuer_name = False
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['crl_entry_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def crl_reason_value(self):
+ """
+ This extension indicates the reason that a certificate was revoked.
+
+ :return:
+ None or a CRLReason object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_reason_value
+
+ @property
+ def invalidity_date_value(self):
+ """
+ This extension indicates the suspected date/time the private key was
+ compromised or the certificate became invalid. This would usually be
+ before the revocation date, which is when the CA processed the
+ revocation.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._invalidity_date_value
+
+ @property
+ def certificate_issuer_value(self):
+ """
+ This extension indicates the issuer of the certificate in question,
+ and is used in indirect CRLs. CRL entries without this extension are
+ for certificates issued from the last seen issuer.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._certificate_issuer_value
+
+ @property
+ def issuer_name(self):
+ """
+ :return:
+ None, or an asn1crypto.x509.Name object for the issuer of the cert
+ """
+
+ if self._issuer_name is False:
+ self._issuer_name = None
+ if self.certificate_issuer_value:
+ for general_name in self.certificate_issuer_value:
+ if general_name.name == 'directory_name':
+ self._issuer_name = general_name.chosen
+ break
+ return self._issuer_name
+
+
+class RevokedCertificates(SequenceOf):
+ _child_spec = RevokedCertificate
+
+
+class TbsCertList(Sequence):
+ _fields = [
+ ('version', Version, {'optional': True}),
+ ('signature', SignedDigestAlgorithm),
+ ('issuer', Name),
+ ('this_update', Time),
+ ('next_update', Time, {'optional': True}),
+ ('revoked_certificates', RevokedCertificates, {'optional': True}),
+ ('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class CertificateList(Sequence):
+ _fields = [
+ ('tbs_cert_list', TbsCertList),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _issuer_alt_name_value = None
+ _crl_number_value = None
+ _delta_crl_indicator_value = None
+ _issuing_distribution_point_value = None
+ _authority_key_identifier_value = None
+ _freshest_crl_value = None
+ _authority_information_access_value = None
+ _issuer_cert_urls = None
+ _delta_crl_distribution_points = None
+ _sha1 = None
+ _sha256 = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_cert_list']['crl_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def issuer_alt_name_value(self):
+ """
+ This extension allows associating one or more alternative names with
+ the issuer of the CRL.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._issuer_alt_name_value
+
+ @property
+ def crl_number_value(self):
+ """
+ This extension adds a monotonically increasing number to the CRL and is
+ used to distinguish different versions of the CRL.
+
+ :return:
+ None or an Integer object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_number_value
+
+ @property
+ def delta_crl_indicator_value(self):
+ """
+ This extension indicates a CRL is a delta CRL, and contains the CRL
+ number of the base CRL that it is a delta from.
+
+ :return:
+ None or an Integer object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._delta_crl_indicator_value
+
+ @property
+ def issuing_distribution_point_value(self):
+ """
+ This extension includes information about what types of revocations
+ and certificates are part of the CRL.
+
+ :return:
+ None or an IssuingDistributionPoint object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._issuing_distribution_point_value
+
+ @property
+ def authority_key_identifier_value(self):
+ """
+ This extension helps in identifying the public key with which to
+ validate the authenticity of the CRL.
+
+ :return:
+ None or an AuthorityKeyIdentifier object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._authority_key_identifier_value
+
+ @property
+ def freshest_crl_value(self):
+ """
+ This extension is used in complete CRLs to indicate where a delta CRL
+ may be located.
+
+ :return:
+ None or a CRLDistributionPoints object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._freshest_crl_value
+
+ @property
+ def authority_information_access_value(self):
+ """
+ This extension is used to provide a URL with which to download the
+ certificate used to sign this CRL.
+
+ :return:
+ None or an AuthorityInfoAccessSyntax object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._authority_information_access_value
+
+ @property
+ def issuer(self):
+ """
+ :return:
+ An asn1crypto.x509.Name object for the issuer of the CRL
+ """
+
+ return self['tbs_cert_list']['issuer']
+
+ @property
+ def authority_key_identifier(self):
+ """
+ :return:
+ None or a byte string of the key_identifier from the authority key
+ identifier extension
+ """
+
+ if not self.authority_key_identifier_value:
+ return None
+
+ return self.authority_key_identifier_value['key_identifier'].native
+
+ @property
+ def issuer_cert_urls(self):
+ """
+ :return:
+ A list of unicode strings that are URLs that should contain either
+ an individual DER-encoded X.509 certificate, or a DER-encoded CMS
+ message containing multiple certificates
+ """
+
+ if self._issuer_cert_urls is None:
+ self._issuer_cert_urls = []
+ if self.authority_information_access_value:
+ for entry in self.authority_information_access_value:
+ if entry['access_method'].native == 'ca_issuers':
+ location = entry['access_location']
+ if location.name != 'uniform_resource_identifier':
+ continue
+ url = location.native
+ if url.lower()[0:7] == 'http://':
+ self._issuer_cert_urls.append(url)
+ return self._issuer_cert_urls
+
+ @property
+ def delta_crl_distribution_points(self):
+ """
+ Returns delta CRL URLs - only applies to complete CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._delta_crl_distribution_points is None:
+ self._delta_crl_distribution_points = []
+
+ if self.freshest_crl_value is not None:
+ for distribution_point in self.freshest_crl_value:
+ distribution_point_name = distribution_point['distribution_point']
+ # RFC 5280 indicates conforming CA should not use the relative form
+ if distribution_point_name.name == 'name_relative_to_crl_issuer':
+ continue
+ # This library is currently only concerned with HTTP-based CRLs
+ for general_name in distribution_point_name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ self._delta_crl_distribution_points.append(distribution_point)
+
+ return self._delta_crl_distribution_points
+
+ @property
+ def signature(self):
+ """
+ :return:
+ A byte string of the signature
+ """
+
+ return self['signature'].native
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this certificate list
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this certificate list
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/csr.py b/contrib/python/asn1crypto/py3/asn1crypto/csr.py
new file mode 100644
index 0000000000..7d5ba44707
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/csr.py
@@ -0,0 +1,133 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for certificate signing requests (CSR). Exports the
+following items:
+
+ - CertificationRequest()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import SignedDigestAlgorithm
+from .core import (
+ Any,
+ BitString,
+ BMPString,
+ Integer,
+ ObjectIdentifier,
+ OctetBitString,
+ Sequence,
+ SetOf,
+ UTF8String
+)
+from .keys import PublicKeyInfo
+from .x509 import DirectoryString, Extensions, Name
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc2986
+# and https://tools.ietf.org/html/rfc2985
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ }
+
+
+class CSRAttributeType(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.7': 'challenge_password',
+ '1.2.840.113549.1.9.9': 'extended_certificate_attributes',
+ '1.2.840.113549.1.9.14': 'extension_request',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/a5eaae36-e9f3-4dc5-a687-bfa7115954f1
+ '1.3.6.1.4.1.311.13.2.2': 'microsoft_enrollment_csp_provider',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/7c677cba-030d-48be-ba2b-01e407705f34
+ '1.3.6.1.4.1.311.13.2.3': 'microsoft_os_version',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/64e5ff6d-c6dd-4578-92f7-b3d895f9b9c7
+ '1.3.6.1.4.1.311.21.20': 'microsoft_request_client_info',
+ }
+
+
+class SetOfDirectoryString(SetOf):
+ _child_spec = DirectoryString
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class SetOfAttributes(SetOf):
+ _child_spec = Attribute
+
+
+class SetOfExtensions(SetOf):
+ _child_spec = Extensions
+
+
+class MicrosoftEnrollmentCSProvider(Sequence):
+ _fields = [
+ ('keyspec', Integer),
+ ('cspname', BMPString), # cryptographic service provider name
+ ('signature', BitString),
+ ]
+
+
+class SetOfMicrosoftEnrollmentCSProvider(SetOf):
+ _child_spec = MicrosoftEnrollmentCSProvider
+
+
+class MicrosoftRequestClientInfo(Sequence):
+ _fields = [
+ ('clientid', Integer),
+ ('machinename', UTF8String),
+ ('username', UTF8String),
+ ('processname', UTF8String),
+ ]
+
+
+class SetOfMicrosoftRequestClientInfo(SetOf):
+ _child_spec = MicrosoftRequestClientInfo
+
+
+class CRIAttribute(Sequence):
+ _fields = [
+ ('type', CSRAttributeType),
+ ('values', Any),
+ ]
+
+ _oid_pair = ('type', 'values')
+ _oid_specs = {
+ 'challenge_password': SetOfDirectoryString,
+ 'extended_certificate_attributes': SetOfAttributes,
+ 'extension_request': SetOfExtensions,
+ 'microsoft_enrollment_csp_provider': SetOfMicrosoftEnrollmentCSProvider,
+ 'microsoft_os_version': SetOfDirectoryString,
+ 'microsoft_request_client_info': SetOfMicrosoftRequestClientInfo,
+ }
+
+
+class CRIAttributes(SetOf):
+ _child_spec = CRIAttribute
+
+
+class CertificationRequestInfo(Sequence):
+ _fields = [
+ ('version', Version),
+ ('subject', Name),
+ ('subject_pk_info', PublicKeyInfo),
+ ('attributes', CRIAttributes, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class CertificationRequest(Sequence):
+ _fields = [
+ ('certification_request_info', CertificationRequestInfo),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ]
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/keys.py b/contrib/python/asn1crypto/py3/asn1crypto/keys.py
new file mode 100644
index 0000000000..b4a87aea7b
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/keys.py
@@ -0,0 +1,1301 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for public and private keys. Exports the following items:
+
+ - DSAPrivateKey()
+ - ECPrivateKey()
+ - EncryptedPrivateKeyInfo()
+ - PrivateKeyInfo()
+ - PublicKeyInfo()
+ - RSAPrivateKey()
+ - RSAPublicKey()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import hashlib
+import math
+
+from ._errors import unwrap, APIException
+from ._types import type_name, byte_cls
+from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams, RSASSAPSSParams
+from .core import (
+ Any,
+ Asn1Value,
+ BitString,
+ Choice,
+ Integer,
+ IntegerOctetString,
+ Null,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ ParsableOctetBitString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .util import int_from_bytes, int_to_bytes
+
+
+class OtherPrimeInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-46
+ """
+
+ _fields = [
+ ('prime', Integer),
+ ('exponent', Integer),
+ ('coefficient', Integer),
+ ]
+
+
+class OtherPrimeInfos(SequenceOf):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-46
+ """
+
+ _child_spec = OtherPrimeInfo
+
+
+class RSAPrivateKeyVersion(Integer):
+ """
+ Original Name: Version
+ Source: https://tools.ietf.org/html/rfc3447#page-45
+ """
+
+ _map = {
+ 0: 'two-prime',
+ 1: 'multi',
+ }
+
+
+class RSAPrivateKey(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-45
+ """
+
+ _fields = [
+ ('version', RSAPrivateKeyVersion),
+ ('modulus', Integer),
+ ('public_exponent', Integer),
+ ('private_exponent', Integer),
+ ('prime1', Integer),
+ ('prime2', Integer),
+ ('exponent1', Integer),
+ ('exponent2', Integer),
+ ('coefficient', Integer),
+ ('other_prime_infos', OtherPrimeInfos, {'optional': True})
+ ]
+
+
+class RSAPublicKey(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3447#page-44
+ """
+
+ _fields = [
+ ('modulus', Integer),
+ ('public_exponent', Integer)
+ ]
+
+
+class DSAPrivateKey(Sequence):
+ """
+ The ASN.1 structure that OpenSSL uses to store a DSA private key that is
+ not part of a PKCS#8 structure. Reversed engineered from english-language
+ description on linked OpenSSL documentation page.
+
+ Original Name: None
+ Source: https://www.openssl.org/docs/apps/dsa.html
+ """
+
+ _fields = [
+ ('version', Integer),
+ ('p', Integer),
+ ('q', Integer),
+ ('g', Integer),
+ ('public_key', Integer),
+ ('private_key', Integer),
+ ]
+
+
+class _ECPoint():
+ """
+ In both PublicKeyInfo and PrivateKeyInfo, the EC public key is a byte
+ string that is encoded as a bit string. This class adds convenience
+ methods for converting to and from the byte string to a pair of integers
+ that are the X and Y coordinates.
+ """
+
+ @classmethod
+ def from_coords(cls, x, y):
+ """
+ Creates an ECPoint object from the X and Y integer coordinates of the
+ point
+
+ :param x:
+ The X coordinate, as an integer
+
+ :param y:
+ The Y coordinate, as an integer
+
+ :return:
+ An ECPoint object
+ """
+
+ x_bytes = int(math.ceil(math.log(x, 2) / 8.0))
+ y_bytes = int(math.ceil(math.log(y, 2) / 8.0))
+
+ num_bytes = max(x_bytes, y_bytes)
+
+ byte_string = b'\x04'
+ byte_string += int_to_bytes(x, width=num_bytes)
+ byte_string += int_to_bytes(y, width=num_bytes)
+
+ return cls(byte_string)
+
+ def to_coords(self):
+ """
+ Returns the X and Y coordinates for this EC point, as native Python
+ integers
+
+ :return:
+ A 2-element tuple containing integers (X, Y)
+ """
+
+ data = self.native
+ first_byte = data[0:1]
+
+ # Uncompressed
+ if first_byte == b'\x04':
+ remaining = data[1:]
+ field_len = len(remaining) // 2
+ x = int_from_bytes(remaining[0:field_len])
+ y = int_from_bytes(remaining[field_len:])
+ return (x, y)
+
+ if first_byte not in set([b'\x02', b'\x03']):
+ raise ValueError(unwrap(
+ '''
+ Invalid EC public key - first byte is incorrect
+ '''
+ ))
+
+ raise ValueError(unwrap(
+ '''
+ Compressed representations of EC public keys are not supported due
+ to patent US6252960
+ '''
+ ))
+
+
+class ECPoint(OctetString, _ECPoint):
+
+ pass
+
+
+class ECPointBitString(OctetBitString, _ECPoint):
+
+ pass
+
+
+class SpecifiedECDomainVersion(Integer):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 104
+ """
+ _map = {
+ 1: 'ecdpVer1',
+ 2: 'ecdpVer2',
+ 3: 'ecdpVer3',
+ }
+
+
+class FieldType(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 101
+ """
+
+ _map = {
+ '1.2.840.10045.1.1': 'prime_field',
+ '1.2.840.10045.1.2': 'characteristic_two_field',
+ }
+
+
+class CharacteristicTwoBasis(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _map = {
+ '1.2.840.10045.1.2.1.1': 'gn_basis',
+ '1.2.840.10045.1.2.1.2': 'tp_basis',
+ '1.2.840.10045.1.2.1.3': 'pp_basis',
+ }
+
+
+class Pentanomial(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _fields = [
+ ('k1', Integer),
+ ('k2', Integer),
+ ('k3', Integer),
+ ]
+
+
+class CharacteristicTwo(Sequence):
+ """
+ Original Name: Characteristic-two
+ Source: http://www.secg.org/sec1-v2.pdf page 101
+ """
+
+ _fields = [
+ ('m', Integer),
+ ('basis', CharacteristicTwoBasis),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('basis', 'parameters')
+ _oid_specs = {
+ 'gn_basis': Null,
+ 'tp_basis': Integer,
+ 'pp_basis': Pentanomial,
+ }
+
+
+class FieldID(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 100
+ """
+
+ _fields = [
+ ('field_type', FieldType),
+ ('parameters', Any),
+ ]
+
+ _oid_pair = ('field_type', 'parameters')
+ _oid_specs = {
+ 'prime_field': Integer,
+ 'characteristic_two_field': CharacteristicTwo,
+ }
+
+
+class Curve(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 104
+ """
+
+ _fields = [
+ ('a', OctetString),
+ ('b', OctetString),
+ ('seed', OctetBitString, {'optional': True}),
+ ]
+
+
+class SpecifiedECDomain(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 103
+ """
+
+ _fields = [
+ ('version', SpecifiedECDomainVersion),
+ ('field_id', FieldID),
+ ('curve', Curve),
+ ('base', ECPoint),
+ ('order', Integer),
+ ('cofactor', Integer, {'optional': True}),
+ ('hash', DigestAlgorithm, {'optional': True}),
+ ]
+
+
+class NamedCurve(ObjectIdentifier):
+ """
+ Various named curves
+
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279#page-23,
+ https://tools.ietf.org/html/rfc5480#page-5
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-23
+ '1.2.840.10045.3.0.1': 'c2pnb163v1',
+ '1.2.840.10045.3.0.2': 'c2pnb163v2',
+ '1.2.840.10045.3.0.3': 'c2pnb163v3',
+ '1.2.840.10045.3.0.4': 'c2pnb176w1',
+ '1.2.840.10045.3.0.5': 'c2tnb191v1',
+ '1.2.840.10045.3.0.6': 'c2tnb191v2',
+ '1.2.840.10045.3.0.7': 'c2tnb191v3',
+ '1.2.840.10045.3.0.8': 'c2onb191v4',
+ '1.2.840.10045.3.0.9': 'c2onb191v5',
+ '1.2.840.10045.3.0.10': 'c2pnb208w1',
+ '1.2.840.10045.3.0.11': 'c2tnb239v1',
+ '1.2.840.10045.3.0.12': 'c2tnb239v2',
+ '1.2.840.10045.3.0.13': 'c2tnb239v3',
+ '1.2.840.10045.3.0.14': 'c2onb239v4',
+ '1.2.840.10045.3.0.15': 'c2onb239v5',
+ '1.2.840.10045.3.0.16': 'c2pnb272w1',
+ '1.2.840.10045.3.0.17': 'c2pnb304w1',
+ '1.2.840.10045.3.0.18': 'c2tnb359v1',
+ '1.2.840.10045.3.0.19': 'c2pnb368w1',
+ '1.2.840.10045.3.0.20': 'c2tnb431r1',
+ '1.2.840.10045.3.1.2': 'prime192v2',
+ '1.2.840.10045.3.1.3': 'prime192v3',
+ '1.2.840.10045.3.1.4': 'prime239v1',
+ '1.2.840.10045.3.1.5': 'prime239v2',
+ '1.2.840.10045.3.1.6': 'prime239v3',
+ # https://tools.ietf.org/html/rfc5480#page-5
+ # http://www.secg.org/SEC2-Ver-1.0.pdf
+ '1.2.840.10045.3.1.1': 'secp192r1',
+ '1.2.840.10045.3.1.7': 'secp256r1',
+ '1.3.132.0.1': 'sect163k1',
+ '1.3.132.0.2': 'sect163r1',
+ '1.3.132.0.3': 'sect239k1',
+ '1.3.132.0.4': 'sect113r1',
+ '1.3.132.0.5': 'sect113r2',
+ '1.3.132.0.6': 'secp112r1',
+ '1.3.132.0.7': 'secp112r2',
+ '1.3.132.0.8': 'secp160r1',
+ '1.3.132.0.9': 'secp160k1',
+ '1.3.132.0.10': 'secp256k1',
+ '1.3.132.0.15': 'sect163r2',
+ '1.3.132.0.16': 'sect283k1',
+ '1.3.132.0.17': 'sect283r1',
+ '1.3.132.0.22': 'sect131r1',
+ '1.3.132.0.23': 'sect131r2',
+ '1.3.132.0.24': 'sect193r1',
+ '1.3.132.0.25': 'sect193r2',
+ '1.3.132.0.26': 'sect233k1',
+ '1.3.132.0.27': 'sect233r1',
+ '1.3.132.0.28': 'secp128r1',
+ '1.3.132.0.29': 'secp128r2',
+ '1.3.132.0.30': 'secp160r2',
+ '1.3.132.0.31': 'secp192k1',
+ '1.3.132.0.32': 'secp224k1',
+ '1.3.132.0.33': 'secp224r1',
+ '1.3.132.0.34': 'secp384r1',
+ '1.3.132.0.35': 'secp521r1',
+ '1.3.132.0.36': 'sect409k1',
+ '1.3.132.0.37': 'sect409r1',
+ '1.3.132.0.38': 'sect571k1',
+ '1.3.132.0.39': 'sect571r1',
+ # https://tools.ietf.org/html/rfc5639#section-4.1
+ '1.3.36.3.3.2.8.1.1.1': 'brainpoolp160r1',
+ '1.3.36.3.3.2.8.1.1.2': 'brainpoolp160t1',
+ '1.3.36.3.3.2.8.1.1.3': 'brainpoolp192r1',
+ '1.3.36.3.3.2.8.1.1.4': 'brainpoolp192t1',
+ '1.3.36.3.3.2.8.1.1.5': 'brainpoolp224r1',
+ '1.3.36.3.3.2.8.1.1.6': 'brainpoolp224t1',
+ '1.3.36.3.3.2.8.1.1.7': 'brainpoolp256r1',
+ '1.3.36.3.3.2.8.1.1.8': 'brainpoolp256t1',
+ '1.3.36.3.3.2.8.1.1.9': 'brainpoolp320r1',
+ '1.3.36.3.3.2.8.1.1.10': 'brainpoolp320t1',
+ '1.3.36.3.3.2.8.1.1.11': 'brainpoolp384r1',
+ '1.3.36.3.3.2.8.1.1.12': 'brainpoolp384t1',
+ '1.3.36.3.3.2.8.1.1.13': 'brainpoolp512r1',
+ '1.3.36.3.3.2.8.1.1.14': 'brainpoolp512t1',
+ }
+
+ _key_sizes = {
+ # Order values used to compute these sourced from
+ # http://cr.openjdk.java.net/~vinnie/7194075/webrev-3/src/share/classes/sun/security/ec/CurveDB.java.html
+ '1.2.840.10045.3.0.1': 21,
+ '1.2.840.10045.3.0.2': 21,
+ '1.2.840.10045.3.0.3': 21,
+ '1.2.840.10045.3.0.4': 21,
+ '1.2.840.10045.3.0.5': 24,
+ '1.2.840.10045.3.0.6': 24,
+ '1.2.840.10045.3.0.7': 24,
+ '1.2.840.10045.3.0.8': 24,
+ '1.2.840.10045.3.0.9': 24,
+ '1.2.840.10045.3.0.10': 25,
+ '1.2.840.10045.3.0.11': 30,
+ '1.2.840.10045.3.0.12': 30,
+ '1.2.840.10045.3.0.13': 30,
+ '1.2.840.10045.3.0.14': 30,
+ '1.2.840.10045.3.0.15': 30,
+ '1.2.840.10045.3.0.16': 33,
+ '1.2.840.10045.3.0.17': 37,
+ '1.2.840.10045.3.0.18': 45,
+ '1.2.840.10045.3.0.19': 45,
+ '1.2.840.10045.3.0.20': 53,
+ '1.2.840.10045.3.1.2': 24,
+ '1.2.840.10045.3.1.3': 24,
+ '1.2.840.10045.3.1.4': 30,
+ '1.2.840.10045.3.1.5': 30,
+ '1.2.840.10045.3.1.6': 30,
+ # Order values used to compute these sourced from
+ # http://www.secg.org/SEC2-Ver-1.0.pdf
+ # ceil(n.bit_length() / 8)
+ '1.2.840.10045.3.1.1': 24,
+ '1.2.840.10045.3.1.7': 32,
+ '1.3.132.0.1': 21,
+ '1.3.132.0.2': 21,
+ '1.3.132.0.3': 30,
+ '1.3.132.0.4': 15,
+ '1.3.132.0.5': 15,
+ '1.3.132.0.6': 14,
+ '1.3.132.0.7': 14,
+ '1.3.132.0.8': 21,
+ '1.3.132.0.9': 21,
+ '1.3.132.0.10': 32,
+ '1.3.132.0.15': 21,
+ '1.3.132.0.16': 36,
+ '1.3.132.0.17': 36,
+ '1.3.132.0.22': 17,
+ '1.3.132.0.23': 17,
+ '1.3.132.0.24': 25,
+ '1.3.132.0.25': 25,
+ '1.3.132.0.26': 29,
+ '1.3.132.0.27': 30,
+ '1.3.132.0.28': 16,
+ '1.3.132.0.29': 16,
+ '1.3.132.0.30': 21,
+ '1.3.132.0.31': 24,
+ '1.3.132.0.32': 29,
+ '1.3.132.0.33': 28,
+ '1.3.132.0.34': 48,
+ '1.3.132.0.35': 66,
+ '1.3.132.0.36': 51,
+ '1.3.132.0.37': 52,
+ '1.3.132.0.38': 72,
+ '1.3.132.0.39': 72,
+ # Order values used to compute these sourced from
+ # https://tools.ietf.org/html/rfc5639#section-3
+ # ceil(q.bit_length() / 8)
+ '1.3.36.3.3.2.8.1.1.1': 20,
+ '1.3.36.3.3.2.8.1.1.2': 20,
+ '1.3.36.3.3.2.8.1.1.3': 24,
+ '1.3.36.3.3.2.8.1.1.4': 24,
+ '1.3.36.3.3.2.8.1.1.5': 28,
+ '1.3.36.3.3.2.8.1.1.6': 28,
+ '1.3.36.3.3.2.8.1.1.7': 32,
+ '1.3.36.3.3.2.8.1.1.8': 32,
+ '1.3.36.3.3.2.8.1.1.9': 40,
+ '1.3.36.3.3.2.8.1.1.10': 40,
+ '1.3.36.3.3.2.8.1.1.11': 48,
+ '1.3.36.3.3.2.8.1.1.12': 48,
+ '1.3.36.3.3.2.8.1.1.13': 64,
+ '1.3.36.3.3.2.8.1.1.14': 64,
+ }
+
+ @classmethod
+ def register(cls, name, oid, key_size):
+ """
+ Registers a new named elliptic curve that is not included in the
+ default list of named curves
+
+ :param name:
+ A unicode string of the curve name
+
+ :param oid:
+ A unicode string of the dotted format OID
+
+ :param key_size:
+ An integer of the number of bytes the private key should be
+ encoded to
+ """
+
+ cls._map[oid] = name
+ if cls._reverse_map is not None:
+ cls._reverse_map[name] = oid
+ cls._key_sizes[oid] = key_size
+
+
+class ECDomainParameters(Choice):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 102
+ """
+
+ _alternatives = [
+ ('specified', SpecifiedECDomain),
+ ('named', NamedCurve),
+ ('implicit_ca', Null),
+ ]
+
+ @property
+ def key_size(self):
+ if self.name == 'implicit_ca':
+ raise ValueError(unwrap(
+ '''
+ Unable to calculate key_size from ECDomainParameters
+ that are implicitly defined by the CA key
+ '''
+ ))
+
+ if self.name == 'specified':
+ order = self.chosen['order'].native
+ return math.ceil(math.log(order, 2.0) / 8.0)
+
+ oid = self.chosen.dotted
+ if oid not in NamedCurve._key_sizes:
+ raise ValueError(unwrap(
+ '''
+ The asn1crypto.keys.NamedCurve %s does not have a registered key length,
+ please call asn1crypto.keys.NamedCurve.register()
+ ''',
+ repr(oid)
+ ))
+ return NamedCurve._key_sizes[oid]
+
+
+class ECPrivateKeyVersion(Integer):
+ """
+ Original Name: None
+ Source: http://www.secg.org/sec1-v2.pdf page 108
+ """
+
+ _map = {
+ 1: 'ecPrivkeyVer1',
+ }
+
+
+class ECPrivateKey(Sequence):
+ """
+ Source: http://www.secg.org/sec1-v2.pdf page 108
+ """
+
+ _fields = [
+ ('version', ECPrivateKeyVersion),
+ ('private_key', IntegerOctetString),
+ ('parameters', ECDomainParameters, {'explicit': 0, 'optional': True}),
+ ('public_key', ECPointBitString, {'explicit': 1, 'optional': True}),
+ ]
+
+ # Ensures the key is set to the correct length when encoding
+ _key_size = None
+
+ # This is necessary to ensure the private_key IntegerOctetString is encoded properly
+ def __setitem__(self, key, value):
+ res = super(ECPrivateKey, self).__setitem__(key, value)
+
+ if key == 'private_key':
+ if self._key_size is None:
+ # Infer the key_size from the existing private key if possible
+ pkey_contents = self['private_key'].contents
+ if isinstance(pkey_contents, byte_cls) and len(pkey_contents) > 1:
+ self.set_key_size(len(self['private_key'].contents))
+
+ elif self._key_size is not None:
+ self._update_key_size()
+
+ elif key == 'parameters' and isinstance(self['parameters'], ECDomainParameters) and \
+ self['parameters'].name != 'implicit_ca':
+ self.set_key_size(self['parameters'].key_size)
+
+ return res
+
+ def set_key_size(self, key_size):
+ """
+ Sets the key_size to ensure the private key is encoded to the proper length
+
+ :param key_size:
+ An integer byte length to encode the private_key to
+ """
+
+ self._key_size = key_size
+ self._update_key_size()
+
+ def _update_key_size(self):
+ """
+ Ensure the private_key explicit encoding width is set
+ """
+
+ if self._key_size is not None and isinstance(self['private_key'], IntegerOctetString):
+ self['private_key'].set_encoded_width(self._key_size)
+
+
+class DSAParams(Sequence):
+ """
+ Parameters for a DSA public or private key
+
+ Original Name: Dss-Parms
+ Source: https://tools.ietf.org/html/rfc3279#page-9
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('q', Integer),
+ ('g', Integer),
+ ]
+
+
+class Attribute(Sequence):
+ """
+ Source: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.501-198811-S!!PDF-E&type=items page 8
+ """
+
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class Attributes(SetOf):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _child_spec = Attribute
+
+
+class PrivateKeyAlgorithmId(ObjectIdentifier):
+ """
+ These OIDs for various public keys are reused when storing private keys
+ inside of a PKCS#8 structure
+
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-19
+ '1.2.840.113549.1.1.1': 'rsa',
+ # https://tools.ietf.org/html/rfc4055#page-8
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ # https://tools.ietf.org/html/rfc3279#page-18
+ '1.2.840.10040.4.1': 'dsa',
+ # https://tools.ietf.org/html/rfc3279#page-13
+ '1.2.840.10045.2.1': 'ec',
+ # https://tools.ietf.org/html/rfc8410#section-9
+ '1.3.101.110': 'x25519',
+ '1.3.101.111': 'x448',
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+
+class PrivateKeyAlgorithm(_ForceNullParameters, Sequence):
+ """
+ Original Name: PrivateKeyAlgorithmIdentifier
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _fields = [
+ ('algorithm', PrivateKeyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dsa': DSAParams,
+ 'ec': ECDomainParameters,
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+
+class PrivateKeyInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-3
+ """
+
+ _fields = [
+ ('version', Integer),
+ ('private_key_algorithm', PrivateKeyAlgorithm),
+ ('private_key', ParsableOctetString),
+ ('attributes', Attributes, {'implicit': 0, 'optional': True}),
+ ]
+
+ def _private_key_spec(self):
+ algorithm = self['private_key_algorithm']['algorithm'].native
+ return {
+ 'rsa': RSAPrivateKey,
+ 'rsassa_pss': RSAPrivateKey,
+ 'dsa': Integer,
+ 'ec': ECPrivateKey,
+ # These should be treated as opaque octet strings according
+ # to RFC 8410
+ 'x25519': OctetString,
+ 'x448': OctetString,
+ 'ed25519': OctetString,
+ 'ed448': OctetString,
+ }[algorithm]
+
+ _spec_callbacks = {
+ 'private_key': _private_key_spec
+ }
+
+ _algorithm = None
+ _bit_size = None
+ _public_key = None
+ _fingerprint = None
+
+ @classmethod
+ def wrap(cls, private_key, algorithm):
+ """
+ Wraps a private key in a PrivateKeyInfo structure
+
+ :param private_key:
+ A byte string or Asn1Value object of the private key
+
+ :param algorithm:
+ A unicode string of "rsa", "dsa" or "ec"
+
+ :return:
+ A PrivateKeyInfo object
+ """
+
+ if not isinstance(private_key, byte_cls) and not isinstance(private_key, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ private_key must be a byte string or Asn1Value, not %s
+ ''',
+ type_name(private_key)
+ ))
+
+ if algorithm == 'rsa' or algorithm == 'rsassa_pss':
+ if not isinstance(private_key, RSAPrivateKey):
+ private_key = RSAPrivateKey.load(private_key)
+ params = Null()
+ elif algorithm == 'dsa':
+ if not isinstance(private_key, DSAPrivateKey):
+ private_key = DSAPrivateKey.load(private_key)
+ params = DSAParams()
+ params['p'] = private_key['p']
+ params['q'] = private_key['q']
+ params['g'] = private_key['g']
+ public_key = private_key['public_key']
+ private_key = private_key['private_key']
+ elif algorithm == 'ec':
+ if not isinstance(private_key, ECPrivateKey):
+ private_key = ECPrivateKey.load(private_key)
+ else:
+ private_key = private_key.copy()
+ params = private_key['parameters']
+ del private_key['parameters']
+ else:
+ raise ValueError(unwrap(
+ '''
+ algorithm must be one of "rsa", "dsa", "ec", not %s
+ ''',
+ repr(algorithm)
+ ))
+
+ private_key_algo = PrivateKeyAlgorithm()
+ private_key_algo['algorithm'] = PrivateKeyAlgorithmId(algorithm)
+ private_key_algo['parameters'] = params
+
+ container = cls()
+ container._algorithm = algorithm
+ container['version'] = Integer(0)
+ container['private_key_algorithm'] = private_key_algo
+ container['private_key'] = private_key
+
+ # Here we save the DSA public key if possible since it is not contained
+ # within the PKCS#8 structure for a DSA key
+ if algorithm == 'dsa':
+ container._public_key = public_key
+
+ return container
+
+ # This is necessary to ensure any contained ECPrivateKey is the
+ # correct size
+ def __setitem__(self, key, value):
+ res = super(PrivateKeyInfo, self).__setitem__(key, value)
+
+ algorithm = self['private_key_algorithm']
+
+ # When possible, use the parameter info to make sure the private key encoding
+ # retains any necessary leading bytes, instead of them being dropped
+ if (key == 'private_key_algorithm' or key == 'private_key') and \
+ algorithm['algorithm'].native == 'ec' and \
+ isinstance(algorithm['parameters'], ECDomainParameters) and \
+ algorithm['parameters'].name != 'implicit_ca' and \
+ isinstance(self['private_key'], ParsableOctetString) and \
+ isinstance(self['private_key'].parsed, ECPrivateKey):
+ self['private_key'].parsed.set_key_size(algorithm['parameters'].key_size)
+
+ return res
+
+ def unwrap(self):
+ """
+ Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or
+ ECPrivateKey object
+
+ :return:
+ An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().unwrap() has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().unwrap() instead')
+
+ @property
+ def curve(self):
+ """
+ Returns information about the curve used for an EC key
+
+ :raises:
+ ValueError - when the key is not an EC key
+
+ :return:
+ A two-element tuple, with the first element being a unicode string
+ of "implicit_ca", "specified" or "named". If the first element is
+ "implicit_ca", the second is None. If "specified", the second is
+ an OrderedDict that is the native version of SpecifiedECDomain. If
+ "named", the second is a unicode string of the curve name.
+ """
+
+ if self.algorithm != 'ec':
+ raise ValueError(unwrap(
+ '''
+ Only EC keys have a curve, this key is %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ params = self['private_key_algorithm']['parameters']
+ chosen = params.chosen
+
+ if params.name == 'implicit_ca':
+ value = None
+ else:
+ value = chosen.native
+
+ return (params.name, value)
+
+ @property
+ def hash_algo(self):
+ """
+ Returns the name of the family of hash algorithms used to generate a
+ DSA key
+
+ :raises:
+ ValueError - when the key is not a DSA key
+
+ :return:
+ A unicode string of "sha1" or "sha2"
+ """
+
+ if self.algorithm != 'dsa':
+ raise ValueError(unwrap(
+ '''
+ Only DSA keys are generated using a hash algorithm, this key is
+ %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ byte_len = math.log(self['private_key_algorithm']['parameters']['q'].native, 2) / 8
+
+ return 'sha1' if byte_len <= 20 else 'sha2'
+
+ @property
+ def algorithm(self):
+ """
+ :return:
+ A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
+ """
+
+ if self._algorithm is None:
+ self._algorithm = self['private_key_algorithm']['algorithm'].native
+ return self._algorithm
+
+ @property
+ def bit_size(self):
+ """
+ :return:
+ The bit size of the private key, as an integer
+ """
+
+ if self._bit_size is None:
+ if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
+ prime = self['private_key'].parsed['modulus'].native
+ elif self.algorithm == 'dsa':
+ prime = self['private_key_algorithm']['parameters']['p'].native
+ elif self.algorithm == 'ec':
+ prime = self['private_key'].parsed['private_key'].native
+ self._bit_size = int(math.ceil(math.log(prime, 2)))
+ modulus = self._bit_size % 8
+ if modulus != 0:
+ self._bit_size += 8 - modulus
+ return self._bit_size
+
+ @property
+ def byte_size(self):
+ """
+ :return:
+ The byte size of the private key, as an integer
+ """
+
+ return int(math.ceil(self.bit_size / 8))
+
+ @property
+ def public_key(self):
+ """
+ :return:
+ If an RSA key, an RSAPublicKey object. If a DSA key, an Integer
+ object. If an EC key, an ECPointBitString object.
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().public_key has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().public_key.unwrap() instead')
+
+ @property
+ def public_key_info(self):
+ """
+ :return:
+ A PublicKeyInfo object derived from this private key.
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().public_key_info has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().public_key.asn1 instead')
+
+ @property
+ def fingerprint(self):
+ """
+ Creates a fingerprint that can be compared with a public key to see if
+ the two form a pair.
+
+ This fingerprint is not compatible with fingerprints generated by any
+ other software.
+
+ :return:
+ A byte string that is a sha256 hash of selected components (based
+ on the key type)
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PrivateKeyInfo().fingerprint has been removed, '
+ 'please use oscrypto.asymmetric.PrivateKey().fingerprint instead')
+
+
+class EncryptedPrivateKeyInfo(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc5208#page-4
+ """
+
+ _fields = [
+ ('encryption_algorithm', EncryptionAlgorithm),
+ ('encrypted_data', OctetString),
+ ]
+
+
+# These structures are from https://tools.ietf.org/html/rfc3279
+
+class ValidationParms(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3279#page-10
+ """
+
+ _fields = [
+ ('seed', BitString),
+ ('pgen_counter', Integer),
+ ]
+
+
+class DomainParameters(Sequence):
+ """
+ Source: https://tools.ietf.org/html/rfc3279#page-10
+ """
+
+ _fields = [
+ ('p', Integer),
+ ('g', Integer),
+ ('q', Integer),
+ ('j', Integer, {'optional': True}),
+ ('validation_params', ValidationParms, {'optional': True}),
+ ]
+
+
+class PublicKeyAlgorithmId(ObjectIdentifier):
+ """
+ Original Name: None
+ Source: https://tools.ietf.org/html/rfc3279
+ """
+
+ _map = {
+ # https://tools.ietf.org/html/rfc3279#page-19
+ '1.2.840.113549.1.1.1': 'rsa',
+ # https://tools.ietf.org/html/rfc3447#page-47
+ '1.2.840.113549.1.1.7': 'rsaes_oaep',
+ # https://tools.ietf.org/html/rfc4055#page-8
+ '1.2.840.113549.1.1.10': 'rsassa_pss',
+ # https://tools.ietf.org/html/rfc3279#page-18
+ '1.2.840.10040.4.1': 'dsa',
+ # https://tools.ietf.org/html/rfc3279#page-13
+ '1.2.840.10045.2.1': 'ec',
+ # https://tools.ietf.org/html/rfc3279#page-10
+ '1.2.840.10046.2.1': 'dh',
+ # https://tools.ietf.org/html/rfc8410#section-9
+ '1.3.101.110': 'x25519',
+ '1.3.101.111': 'x448',
+ '1.3.101.112': 'ed25519',
+ '1.3.101.113': 'ed448',
+ }
+
+
+class PublicKeyAlgorithm(_ForceNullParameters, Sequence):
+ """
+ Original Name: AlgorithmIdentifier
+ Source: https://tools.ietf.org/html/rfc5280#page-18
+ """
+
+ _fields = [
+ ('algorithm', PublicKeyAlgorithmId),
+ ('parameters', Any, {'optional': True}),
+ ]
+
+ _oid_pair = ('algorithm', 'parameters')
+ _oid_specs = {
+ 'dsa': DSAParams,
+ 'ec': ECDomainParameters,
+ 'dh': DomainParameters,
+ 'rsaes_oaep': RSAESOAEPParams,
+ 'rsassa_pss': RSASSAPSSParams,
+ }
+
+
+class PublicKeyInfo(Sequence):
+ """
+ Original Name: SubjectPublicKeyInfo
+ Source: https://tools.ietf.org/html/rfc5280#page-17
+ """
+
+ _fields = [
+ ('algorithm', PublicKeyAlgorithm),
+ ('public_key', ParsableOctetBitString),
+ ]
+
+ def _public_key_spec(self):
+ algorithm = self['algorithm']['algorithm'].native
+ return {
+ 'rsa': RSAPublicKey,
+ 'rsaes_oaep': RSAPublicKey,
+ 'rsassa_pss': RSAPublicKey,
+ 'dsa': Integer,
+ # We override the field spec with ECPoint so that users can easily
+ # decompose the byte string into the constituent X and Y coords
+ 'ec': (ECPointBitString, None),
+ 'dh': Integer,
+ # These should be treated as opaque bit strings according
+ # to RFC 8410, and need not even be valid ASN.1
+ 'x25519': (OctetBitString, None),
+ 'x448': (OctetBitString, None),
+ 'ed25519': (OctetBitString, None),
+ 'ed448': (OctetBitString, None),
+ }[algorithm]
+
+ _spec_callbacks = {
+ 'public_key': _public_key_spec
+ }
+
+ _algorithm = None
+ _bit_size = None
+ _fingerprint = None
+ _sha1 = None
+ _sha256 = None
+
+ @classmethod
+ def wrap(cls, public_key, algorithm):
+ """
+ Wraps a public key in a PublicKeyInfo structure
+
+ :param public_key:
+ A byte string or Asn1Value object of the public key
+
+ :param algorithm:
+ A unicode string of "rsa"
+
+ :return:
+ A PublicKeyInfo object
+ """
+
+ if not isinstance(public_key, byte_cls) and not isinstance(public_key, Asn1Value):
+ raise TypeError(unwrap(
+ '''
+ public_key must be a byte string or Asn1Value, not %s
+ ''',
+ type_name(public_key)
+ ))
+
+ if algorithm != 'rsa' and algorithm != 'rsassa_pss':
+ raise ValueError(unwrap(
+ '''
+ algorithm must "rsa", not %s
+ ''',
+ repr(algorithm)
+ ))
+
+ algo = PublicKeyAlgorithm()
+ algo['algorithm'] = PublicKeyAlgorithmId(algorithm)
+ algo['parameters'] = Null()
+
+ container = cls()
+ container['algorithm'] = algo
+ if isinstance(public_key, Asn1Value):
+ public_key = public_key.untag().dump()
+ container['public_key'] = ParsableOctetBitString(public_key)
+
+ return container
+
+ def unwrap(self):
+ """
+ Unwraps an RSA public key into an RSAPublicKey object. Does not support
+ DSA or EC public keys since they do not have an unwrapped form.
+
+ :return:
+ An RSAPublicKey object
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PublicKeyInfo().unwrap() has been removed, '
+ 'please use oscrypto.asymmetric.PublicKey().unwrap() instead')
+
+ @property
+ def curve(self):
+ """
+ Returns information about the curve used for an EC key
+
+ :raises:
+ ValueError - when the key is not an EC key
+
+ :return:
+ A two-element tuple, with the first element being a unicode string
+ of "implicit_ca", "specified" or "named". If the first element is
+ "implicit_ca", the second is None. If "specified", the second is
+ an OrderedDict that is the native version of SpecifiedECDomain. If
+ "named", the second is a unicode string of the curve name.
+ """
+
+ if self.algorithm != 'ec':
+ raise ValueError(unwrap(
+ '''
+ Only EC keys have a curve, this key is %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ params = self['algorithm']['parameters']
+ chosen = params.chosen
+
+ if params.name == 'implicit_ca':
+ value = None
+ else:
+ value = chosen.native
+
+ return (params.name, value)
+
+ @property
+ def hash_algo(self):
+ """
+ Returns the name of the family of hash algorithms used to generate a
+ DSA key
+
+ :raises:
+ ValueError - when the key is not a DSA key
+
+ :return:
+ A unicode string of "sha1" or "sha2" or None if no parameters are
+ present
+ """
+
+ if self.algorithm != 'dsa':
+ raise ValueError(unwrap(
+ '''
+ Only DSA keys are generated using a hash algorithm, this key is
+ %s
+ ''',
+ self.algorithm.upper()
+ ))
+
+ parameters = self['algorithm']['parameters']
+ if parameters.native is None:
+ return None
+
+ byte_len = math.log(parameters['q'].native, 2) / 8
+
+ return 'sha1' if byte_len <= 20 else 'sha2'
+
+ @property
+ def algorithm(self):
+ """
+ :return:
+ A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
+ """
+
+ if self._algorithm is None:
+ self._algorithm = self['algorithm']['algorithm'].native
+ return self._algorithm
+
+ @property
+ def bit_size(self):
+ """
+ :return:
+ The bit size of the public key, as an integer
+ """
+
+ if self._bit_size is None:
+ if self.algorithm == 'ec':
+ self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8)
+ else:
+ if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
+ prime = self['public_key'].parsed['modulus'].native
+ elif self.algorithm == 'dsa':
+ prime = self['algorithm']['parameters']['p'].native
+ self._bit_size = int(math.ceil(math.log(prime, 2)))
+ modulus = self._bit_size % 8
+ if modulus != 0:
+ self._bit_size += 8 - modulus
+
+ return self._bit_size
+
+ @property
+ def byte_size(self):
+ """
+ :return:
+ The byte size of the public key, as an integer
+ """
+
+ return int(math.ceil(self.bit_size / 8))
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this public key info
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(byte_cls(self['public_key'])).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this public key info
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(byte_cls(self['public_key'])).digest()
+ return self._sha256
+
+ @property
+ def fingerprint(self):
+ """
+ Creates a fingerprint that can be compared with a private key to see if
+ the two form a pair.
+
+ This fingerprint is not compatible with fingerprints generated by any
+ other software.
+
+ :return:
+ A byte string that is a sha256 hash of selected components (based
+ on the key type)
+ """
+
+ raise APIException(
+ 'asn1crypto.keys.PublicKeyInfo().fingerprint has been removed, '
+ 'please use oscrypto.asymmetric.PublicKey().fingerprint instead')
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/ocsp.py b/contrib/python/asn1crypto/py3/asn1crypto/ocsp.py
new file mode 100644
index 0000000000..91c7fbf3ab
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/ocsp.py
@@ -0,0 +1,703 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for the online certificate status protocol (OCSP). Exports
+the following items:
+
+ - OCSPRequest()
+ - OCSPResponse()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ._errors import unwrap
+from .algos import DigestAlgorithm, SignedDigestAlgorithm
+from .core import (
+ Boolean,
+ Choice,
+ Enumerated,
+ GeneralizedTime,
+ IA5String,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+)
+from .crl import AuthorityInfoAccessSyntax, CRLReason
+from .keys import PublicKeyAlgorithm
+from .x509 import Certificate, GeneralName, GeneralNames, Name
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc6960
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1'
+ }
+
+
+class CertId(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm),
+ ('issuer_name_hash', OctetString),
+ ('issuer_key_hash', OctetString),
+ ('serial_number', Integer),
+ ]
+
+
+class ServiceLocator(Sequence):
+ _fields = [
+ ('issuer', Name),
+ ('locator', AuthorityInfoAccessSyntax),
+ ]
+
+
+class RequestExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.7': 'service_locator',
+ }
+
+
+class RequestExtension(Sequence):
+ _fields = [
+ ('extn_id', RequestExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'service_locator': ServiceLocator,
+ }
+
+
+class RequestExtensions(SequenceOf):
+ _child_spec = RequestExtension
+
+
+class Request(Sequence):
+ _fields = [
+ ('req_cert', CertId),
+ ('single_request_extensions', RequestExtensions, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _service_locator_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['single_request_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def service_locator_value(self):
+ """
+ This extension is used when communicating with an OCSP responder that
+ acts as a proxy for OCSP requests
+
+ :return:
+ None or a ServiceLocator object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._service_locator_value
+
+
+class Requests(SequenceOf):
+ _child_spec = Request
+
+
+class ResponseType(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.1': 'basic_ocsp_response',
+ }
+
+
+class AcceptableResponses(SequenceOf):
+ _child_spec = ResponseType
+
+
+class PreferredSignatureAlgorithm(Sequence):
+ _fields = [
+ ('sig_identifier', SignedDigestAlgorithm),
+ ('cert_identifier', PublicKeyAlgorithm, {'optional': True}),
+ ]
+
+
+class PreferredSignatureAlgorithms(SequenceOf):
+ _child_spec = PreferredSignatureAlgorithm
+
+
+class TBSRequestExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.2': 'nonce',
+ '1.3.6.1.5.5.7.48.1.4': 'acceptable_responses',
+ '1.3.6.1.5.5.7.48.1.8': 'preferred_signature_algorithms',
+ }
+
+
+class TBSRequestExtension(Sequence):
+ _fields = [
+ ('extn_id', TBSRequestExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'nonce': OctetString,
+ 'acceptable_responses': AcceptableResponses,
+ 'preferred_signature_algorithms': PreferredSignatureAlgorithms,
+ }
+
+
+class TBSRequestExtensions(SequenceOf):
+ _child_spec = TBSRequestExtension
+
+
+class TBSRequest(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('requestor_name', GeneralName, {'explicit': 1, 'optional': True}),
+ ('request_list', Requests),
+ ('request_extensions', TBSRequestExtensions, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class Certificates(SequenceOf):
+ _child_spec = Certificate
+
+
+class Signature(Sequence):
+ _fields = [
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ('certs', Certificates, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class OCSPRequest(Sequence):
+ _fields = [
+ ('tbs_request', TBSRequest),
+ ('optional_signature', Signature, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _nonce_value = None
+ _acceptable_responses_value = None
+ _preferred_signature_algorithms_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_request']['request_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def nonce_value(self):
+ """
+ This extension is used to prevent replay attacks by including a unique,
+ random value with each request/response pair
+
+ :return:
+ None or an OctetString object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._nonce_value
+
+ @property
+ def acceptable_responses_value(self):
+ """
+ This extension is used to allow the client and server to communicate
+ with alternative response formats other than just basic_ocsp_response,
+ although no other formats are defined in the standard.
+
+ :return:
+ None or an AcceptableResponses object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._acceptable_responses_value
+
+ @property
+ def preferred_signature_algorithms_value(self):
+ """
+ This extension is used by the client to define what signature algorithms
+ are preferred, including both the hash algorithm and the public key
+ algorithm, with a level of detail down to even the public key algorithm
+ parameters, such as curve name.
+
+ :return:
+ None or a PreferredSignatureAlgorithms object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._preferred_signature_algorithms_value
+
+
+class OCSPResponseStatus(Enumerated):
+ _map = {
+ 0: 'successful',
+ 1: 'malformed_request',
+ 2: 'internal_error',
+ 3: 'try_later',
+ 5: 'sign_required',
+ 6: 'unauthorized',
+ }
+
+
+class ResponderId(Choice):
+ _alternatives = [
+ ('by_name', Name, {'explicit': 1}),
+ ('by_key', OctetString, {'explicit': 2}),
+ ]
+
+
+# Custom class to return a meaningful .native attribute from CertStatus()
+class StatusGood(Null):
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None or 'good'
+ """
+
+ if value is not None and value != 'good' and not isinstance(value, Null):
+ raise ValueError(unwrap(
+ '''
+ value must be one of None, "good", not %s
+ ''',
+ repr(value)
+ ))
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ return 'good'
+
+
+# Custom class to return a meaningful .native attribute from CertStatus()
+class StatusUnknown(Null):
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ None or 'unknown'
+ """
+
+ if value is not None and value != 'unknown' and not isinstance(value, Null):
+ raise ValueError(unwrap(
+ '''
+ value must be one of None, "unknown", not %s
+ ''',
+ repr(value)
+ ))
+
+ self.contents = b''
+
+ @property
+ def native(self):
+ return 'unknown'
+
+
+class RevokedInfo(Sequence):
+ _fields = [
+ ('revocation_time', GeneralizedTime),
+ ('revocation_reason', CRLReason, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class CertStatus(Choice):
+ _alternatives = [
+ ('good', StatusGood, {'implicit': 0}),
+ ('revoked', RevokedInfo, {'implicit': 1}),
+ ('unknown', StatusUnknown, {'implicit': 2}),
+ ]
+
+
+class CrlId(Sequence):
+ _fields = [
+ ('crl_url', IA5String, {'explicit': 0, 'optional': True}),
+ ('crl_num', Integer, {'explicit': 1, 'optional': True}),
+ ('crl_time', GeneralizedTime, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class SingleResponseExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.3': 'crl',
+ '1.3.6.1.5.5.7.48.1.6': 'archive_cutoff',
+ # These are CRLEntryExtension values from
+ # https://tools.ietf.org/html/rfc5280
+ '2.5.29.21': 'crl_reason',
+ '2.5.29.24': 'invalidity_date',
+ '2.5.29.29': 'certificate_issuer',
+ # https://tools.ietf.org/html/rfc6962.html#page-13
+ '1.3.6.1.4.1.11129.2.4.5': 'signed_certificate_timestamp_list',
+ }
+
+
+class SingleResponseExtension(Sequence):
+ _fields = [
+ ('extn_id', SingleResponseExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'crl': CrlId,
+ 'archive_cutoff': GeneralizedTime,
+ 'crl_reason': CRLReason,
+ 'invalidity_date': GeneralizedTime,
+ 'certificate_issuer': GeneralNames,
+ 'signed_certificate_timestamp_list': OctetString,
+ }
+
+
+class SingleResponseExtensions(SequenceOf):
+ _child_spec = SingleResponseExtension
+
+
+class SingleResponse(Sequence):
+ _fields = [
+ ('cert_id', CertId),
+ ('cert_status', CertStatus),
+ ('this_update', GeneralizedTime),
+ ('next_update', GeneralizedTime, {'explicit': 0, 'optional': True}),
+ ('single_extensions', SingleResponseExtensions, {'explicit': 1, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _crl_value = None
+ _archive_cutoff_value = None
+ _crl_reason_value = None
+ _invalidity_date_value = None
+ _certificate_issuer_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['single_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def crl_value(self):
+ """
+ This extension is used to locate the CRL that a certificate's revocation
+ is contained within.
+
+ :return:
+ None or a CrlId object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_value
+
+ @property
+ def archive_cutoff_value(self):
+ """
+ This extension is used to indicate the date at which an archived
+ (historical) certificate status entry will no longer be available.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._archive_cutoff_value
+
+ @property
+ def crl_reason_value(self):
+ """
+ This extension indicates the reason that a certificate was revoked.
+
+ :return:
+ None or a CRLReason object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._crl_reason_value
+
+ @property
+ def invalidity_date_value(self):
+ """
+ This extension indicates the suspected date/time the private key was
+ compromised or the certificate became invalid. This would usually be
+ before the revocation date, which is when the CA processed the
+ revocation.
+
+ :return:
+ None or a GeneralizedTime object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._invalidity_date_value
+
+ @property
+ def certificate_issuer_value(self):
+ """
+ This extension indicates the issuer of the certificate in question.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._certificate_issuer_value
+
+
+class Responses(SequenceOf):
+ _child_spec = SingleResponse
+
+
+class ResponseDataExtensionId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1.2': 'nonce',
+ '1.3.6.1.5.5.7.48.1.9': 'extended_revoke',
+ }
+
+
+class ResponseDataExtension(Sequence):
+ _fields = [
+ ('extn_id', ResponseDataExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'nonce': OctetString,
+ 'extended_revoke': Null,
+ }
+
+
+class ResponseDataExtensions(SequenceOf):
+ _child_spec = ResponseDataExtension
+
+
+class ResponseData(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('responder_id', ResponderId),
+ ('produced_at', GeneralizedTime),
+ ('responses', Responses),
+ ('response_extensions', ResponseDataExtensions, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class BasicOCSPResponse(Sequence):
+ _fields = [
+ ('tbs_response_data', ResponseData),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature', OctetBitString),
+ ('certs', Certificates, {'explicit': 0, 'optional': True}),
+ ]
+
+
+class ResponseBytes(Sequence):
+ _fields = [
+ ('response_type', ResponseType),
+ ('response', ParsableOctetString),
+ ]
+
+ _oid_pair = ('response_type', 'response')
+ _oid_specs = {
+ 'basic_ocsp_response': BasicOCSPResponse,
+ }
+
+
+class OCSPResponse(Sequence):
+ _fields = [
+ ('response_status', OCSPResponseStatus),
+ ('response_bytes', ResponseBytes, {'explicit': 0, 'optional': True}),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _nonce_value = None
+ _extended_revoke_value = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['response_bytes']['response'].parsed['tbs_response_data']['response_extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def nonce_value(self):
+ """
+ This extension is used to prevent replay attacks on the request/response
+ exchange
+
+ :return:
+ None or an OctetString object
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._nonce_value
+
+ @property
+ def extended_revoke_value(self):
+ """
+ This extension is used to signal that the responder will return a
+ "revoked" status for non-issued certificates.
+
+ :return:
+ None or a Null object (if present)
+ """
+
+ if self._processed_extensions is False:
+ self._set_extensions()
+ return self._extended_revoke_value
+
+ @property
+ def basic_ocsp_response(self):
+ """
+ A shortcut into the BasicOCSPResponse sequence
+
+ :return:
+ None or an asn1crypto.ocsp.BasicOCSPResponse object
+ """
+
+ return self['response_bytes']['response'].parsed
+
+ @property
+ def response_data(self):
+ """
+ A shortcut into the parsed, ResponseData sequence
+
+ :return:
+ None or an asn1crypto.ocsp.ResponseData object
+ """
+
+ return self['response_bytes']['response'].parsed['tbs_response_data']
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/parser.py b/contrib/python/asn1crypto/py3/asn1crypto/parser.py
new file mode 100644
index 0000000000..2f5a63e101
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/parser.py
@@ -0,0 +1,292 @@
+# coding: utf-8
+
+"""
+Functions for parsing and dumping using the ASN.1 DER encoding. Exports the
+following items:
+
+ - emit()
+ - parse()
+ - peek()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import sys
+
+from ._types import byte_cls, chr_cls, type_name
+from .util import int_from_bytes, int_to_bytes
+
+_PY2 = sys.version_info <= (3,)
+_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available'
+_MAX_DEPTH = 10
+
+
+def emit(class_, method, tag, contents):
+ """
+ Constructs a byte string of an ASN.1 DER-encoded value
+
+ This is typically not useful. Instead, use one of the standard classes from
+ asn1crypto.core, or construct a new class with specific fields, and call the
+ .dump() method.
+
+ :param class_:
+ An integer ASN.1 class value: 0 (universal), 1 (application),
+ 2 (context), 3 (private)
+
+ :param method:
+ An integer ASN.1 method value: 0 (primitive), 1 (constructed)
+
+ :param tag:
+ An integer ASN.1 tag value
+
+ :param contents:
+ A byte string of the encoded byte contents
+
+ :return:
+ A byte string of the ASN.1 DER value (header and contents)
+ """
+
+ if not isinstance(class_, int):
+ raise TypeError('class_ must be an integer, not %s' % type_name(class_))
+
+ if class_ < 0 or class_ > 3:
+ raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_)
+
+ if not isinstance(method, int):
+ raise TypeError('method must be an integer, not %s' % type_name(method))
+
+ if method < 0 or method > 1:
+ raise ValueError('method must be 0 or 1, not %s' % method)
+
+ if not isinstance(tag, int):
+ raise TypeError('tag must be an integer, not %s' % type_name(tag))
+
+ if tag < 0:
+ raise ValueError('tag must be greater than zero, not %s' % tag)
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ return _dump_header(class_, method, tag, contents) + contents
+
+
+def parse(contents, strict=False):
+ """
+ Parses a byte string of ASN.1 BER/DER-encoded data.
+
+ This is typically not useful. Instead, use one of the standard classes from
+ asn1crypto.core, or construct a new class with specific fields, and call the
+ .load() class method.
+
+ :param contents:
+ A byte string of BER/DER-encoded data
+
+ :param strict:
+ A boolean indicating if trailing data should be forbidden - if so, a
+ ValueError will be raised when trailing data exists
+
+ :raises:
+ ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
+ TypeError - when contents is not a byte string
+
+ :return:
+ A 6-element tuple:
+ - 0: integer class (0 to 3)
+ - 1: integer method
+ - 2: integer tag
+ - 3: byte string header
+ - 4: byte string content
+ - 5: byte string trailer
+ """
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ contents_len = len(contents)
+ info, consumed = _parse(contents, contents_len)
+ if strict and consumed != contents_len:
+ raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed))
+ return info
+
+
+def peek(contents):
+ """
+ Parses a byte string of ASN.1 BER/DER-encoded data to find the length
+
+ This is typically used to look into an encoded value to see how long the
+ next chunk of ASN.1-encoded data is. Primarily it is useful when a
+ value is a concatenation of multiple values.
+
+ :param contents:
+ A byte string of BER/DER-encoded data
+
+ :raises:
+ ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
+ TypeError - when contents is not a byte string
+
+ :return:
+ An integer with the number of bytes occupied by the ASN.1 value
+ """
+
+ if not isinstance(contents, byte_cls):
+ raise TypeError('contents must be a byte string, not %s' % type_name(contents))
+
+ info, consumed = _parse(contents, len(contents))
+ return consumed
+
+
+def _parse(encoded_data, data_len, pointer=0, lengths_only=False, depth=0):
+ """
+ Parses a byte string into component parts
+
+ :param encoded_data:
+ A byte string that contains BER-encoded data
+
+ :param data_len:
+ The integer length of the encoded data
+
+ :param pointer:
+ The index in the byte string to parse from
+
+ :param lengths_only:
+ A boolean to cause the call to return a 2-element tuple of the integer
+ number of bytes in the header and the integer number of bytes in the
+ contents. Internal use only.
+
+ :param depth:
+ The recursion depth when evaluating indefinite-length encoding.
+
+ :return:
+ A 2-element tuple:
+ - 0: A tuple of (class_, method, tag, header, content, trailer)
+ - 1: An integer indicating how many bytes were consumed
+ """
+
+ if depth > _MAX_DEPTH:
+ raise ValueError('Indefinite-length recursion limit exceeded')
+
+ start = pointer
+
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+
+ pointer += 1
+
+ tag = first_octet & 31
+ constructed = (first_octet >> 5) & 1
+ # Base 128 length using 8th bit as continuation indicator
+ if tag == 31:
+ tag = 0
+ while True:
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+ pointer += 1
+ if num == 0x80 and tag == 0:
+ raise ValueError('Non-minimal tag encoding')
+ tag *= 128
+ tag += num & 127
+ if num >> 7 == 0:
+ break
+ if tag < 31:
+ raise ValueError('Non-minimal tag encoding')
+
+ if data_len < pointer + 1:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (1, data_len - pointer))
+ length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+ pointer += 1
+ trailer = b''
+
+ if length_octet >> 7 == 0:
+ contents_end = pointer + (length_octet & 127)
+
+ else:
+ length_octets = length_octet & 127
+ if length_octets:
+ if data_len < pointer + length_octets:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (length_octets, data_len - pointer))
+ pointer += length_octets
+ contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)
+
+ else:
+ # To properly parse indefinite length values, we need to scan forward
+ # parsing headers until we find a value with a length of zero. If we
+ # just scanned looking for \x00\x00, nested indefinite length values
+ # would not work.
+ if not constructed:
+ raise ValueError('Indefinite-length element must be constructed')
+ contents_end = pointer
+ while data_len < contents_end + 2 or encoded_data[contents_end:contents_end+2] != b'\x00\x00':
+ _, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True, depth=depth+1)
+ contents_end += 2
+ trailer = b'\x00\x00'
+
+ if contents_end > data_len:
+ raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end - pointer, data_len - pointer))
+
+ if lengths_only:
+ return (pointer, contents_end)
+
+ return (
+ (
+ first_octet >> 6,
+ constructed,
+ tag,
+ encoded_data[start:pointer],
+ encoded_data[pointer:contents_end-len(trailer)],
+ trailer
+ ),
+ contents_end
+ )
+
+
+def _dump_header(class_, method, tag, contents):
+ """
+ Constructs the header bytes for an ASN.1 object
+
+ :param class_:
+ An integer ASN.1 class value: 0 (universal), 1 (application),
+ 2 (context), 3 (private)
+
+ :param method:
+ An integer ASN.1 method value: 0 (primitive), 1 (constructed)
+
+ :param tag:
+ An integer ASN.1 tag value
+
+ :param contents:
+ A byte string of the encoded byte contents
+
+ :return:
+ A byte string of the ASN.1 DER header
+ """
+
+ header = b''
+
+ id_num = 0
+ id_num |= class_ << 6
+ id_num |= method << 5
+
+ if tag >= 31:
+ cont_bit = 0
+ while tag > 0:
+ header = chr_cls(cont_bit | (tag & 0x7f)) + header
+ if not cont_bit:
+ cont_bit = 0x80
+ tag = tag >> 7
+ header = chr_cls(id_num | 31) + header
+ else:
+ header += chr_cls(id_num | tag)
+
+ length = len(contents)
+ if length <= 127:
+ header += chr_cls(length)
+ else:
+ length_bytes = int_to_bytes(length)
+ header += chr_cls(0x80 | len(length_bytes))
+ header += length_bytes
+
+ return header
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/pdf.py b/contrib/python/asn1crypto/py3/asn1crypto/pdf.py
new file mode 100644
index 0000000000..b72c886ce5
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/pdf.py
@@ -0,0 +1,84 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for PDF signature structures. Adds extra oid mapping and
+value parsing to asn1crypto.x509.Extension() and asn1crypto.xms.CMSAttribute().
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .cms import CMSAttributeType, CMSAttribute
+from .core import (
+ Boolean,
+ Integer,
+ Null,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .crl import CertificateList
+from .ocsp import OCSPResponse
+from .x509 import (
+ Extension,
+ ExtensionId,
+ GeneralName,
+ KeyPurposeId,
+)
+
+
+class AdobeArchiveRevInfo(Sequence):
+ _fields = [
+ ('version', Integer)
+ ]
+
+
+class AdobeTimestamp(Sequence):
+ _fields = [
+ ('version', Integer),
+ ('location', GeneralName),
+ ('requires_auth', Boolean, {'optional': True, 'default': False}),
+ ]
+
+
+class OtherRevInfo(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('value', OctetString),
+ ]
+
+
+class SequenceOfCertificateList(SequenceOf):
+ _child_spec = CertificateList
+
+
+class SequenceOfOCSPResponse(SequenceOf):
+ _child_spec = OCSPResponse
+
+
+class SequenceOfOtherRevInfo(SequenceOf):
+ _child_spec = OtherRevInfo
+
+
+class RevocationInfoArchival(Sequence):
+ _fields = [
+ ('crl', SequenceOfCertificateList, {'explicit': 0, 'optional': True}),
+ ('ocsp', SequenceOfOCSPResponse, {'explicit': 1, 'optional': True}),
+ ('other_rev_info', SequenceOfOtherRevInfo, {'explicit': 2, 'optional': True}),
+ ]
+
+
+class SetOfRevocationInfoArchival(SetOf):
+ _child_spec = RevocationInfoArchival
+
+
+ExtensionId._map['1.2.840.113583.1.1.9.2'] = 'adobe_archive_rev_info'
+ExtensionId._map['1.2.840.113583.1.1.9.1'] = 'adobe_timestamp'
+ExtensionId._map['1.2.840.113583.1.1.10'] = 'adobe_ppklite_credential'
+Extension._oid_specs['adobe_archive_rev_info'] = AdobeArchiveRevInfo
+Extension._oid_specs['adobe_timestamp'] = AdobeTimestamp
+Extension._oid_specs['adobe_ppklite_credential'] = Null
+KeyPurposeId._map['1.2.840.113583.1.1.5'] = 'pdf_signing'
+CMSAttributeType._map['1.2.840.113583.1.1.8'] = 'adobe_revocation_info_archival'
+CMSAttribute._oid_specs['adobe_revocation_info_archival'] = SetOfRevocationInfoArchival
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/pem.py b/contrib/python/asn1crypto/py3/asn1crypto/pem.py
new file mode 100644
index 0000000000..511ea4b50d
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/pem.py
@@ -0,0 +1,222 @@
+# coding: utf-8
+
+"""
+Encoding DER to PEM and decoding PEM to DER. Exports the following items:
+
+ - armor()
+ - detect()
+ - unarmor()
+
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import base64
+import re
+import sys
+
+from ._errors import unwrap
+from ._types import type_name as _type_name, str_cls, byte_cls
+
+if sys.version_info < (3,):
+ from cStringIO import StringIO as BytesIO
+else:
+ from io import BytesIO
+
+
+def detect(byte_string):
+ """
+ Detect if a byte string seems to contain a PEM-encoded block
+
+ :param byte_string:
+ A byte string to look through
+
+ :return:
+ A boolean, indicating if a PEM-encoded block is contained in the byte
+ string
+ """
+
+ if not isinstance(byte_string, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ byte_string must be a byte string, not %s
+ ''',
+ _type_name(byte_string)
+ ))
+
+ return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1
+
+
+def armor(type_name, der_bytes, headers=None):
+ """
+ Armors a DER-encoded byte string in PEM
+
+ :param type_name:
+ A unicode string that will be capitalized and placed in the header
+ and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
+ will appear as "-----BEGIN CERTIFICATE-----" and
+ "-----END CERTIFICATE-----".
+
+ :param der_bytes:
+ A byte string to be armored
+
+ :param headers:
+ An OrderedDict of the header lines to write after the BEGIN line
+
+ :return:
+ A byte string of the PEM block
+ """
+
+ if not isinstance(der_bytes, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ der_bytes must be a byte string, not %s
+ ''' % _type_name(der_bytes)
+ ))
+
+ if not isinstance(type_name, str_cls):
+ raise TypeError(unwrap(
+ '''
+ type_name must be a unicode string, not %s
+ ''',
+ _type_name(type_name)
+ ))
+
+ type_name = type_name.upper().encode('ascii')
+
+ output = BytesIO()
+ output.write(b'-----BEGIN ')
+ output.write(type_name)
+ output.write(b'-----\n')
+ if headers:
+ for key in headers:
+ output.write(key.encode('ascii'))
+ output.write(b': ')
+ output.write(headers[key].encode('ascii'))
+ output.write(b'\n')
+ output.write(b'\n')
+ b64_bytes = base64.b64encode(der_bytes)
+ b64_len = len(b64_bytes)
+ i = 0
+ while i < b64_len:
+ output.write(b64_bytes[i:i + 64])
+ output.write(b'\n')
+ i += 64
+ output.write(b'-----END ')
+ output.write(type_name)
+ output.write(b'-----\n')
+
+ return output.getvalue()
+
+
+def _unarmor(pem_bytes):
+ """
+ Convert a PEM-encoded byte string into one or more DER-encoded byte strings
+
+ :param pem_bytes:
+ A byte string of the PEM-encoded data
+
+ :raises:
+ ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
+
+ :return:
+ A generator of 3-element tuples in the format: (object_type, headers,
+ der_bytes). The object_type is a unicode string of what is between
+ "-----BEGIN " and "-----". Examples include: "CERTIFICATE",
+ "PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
+ in the form "Name: Value" that are right after the begin line.
+ """
+
+ if not isinstance(pem_bytes, byte_cls):
+ raise TypeError(unwrap(
+ '''
+ pem_bytes must be a byte string, not %s
+ ''',
+ _type_name(pem_bytes)
+ ))
+
+ # Valid states include: "trash", "headers", "body"
+ state = 'trash'
+ headers = {}
+ base64_data = b''
+ object_type = None
+
+ found_start = False
+ found_end = False
+
+ for line in pem_bytes.splitlines(False):
+ if line == b'':
+ continue
+
+ if state == "trash":
+ # Look for a starting line since some CA cert bundle show the cert
+ # into in a parsed format above each PEM block
+ type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
+ if not type_name_match:
+ continue
+ object_type = type_name_match.group(1).decode('ascii')
+
+ found_start = True
+ state = 'headers'
+ continue
+
+ if state == 'headers':
+ if line.find(b':') == -1:
+ state = 'body'
+ else:
+ decoded_line = line.decode('ascii')
+ name, value = decoded_line.split(':', 1)
+ headers[name] = value.strip()
+ continue
+
+ if state == 'body':
+ if line[0:5] in (b'-----', b'---- '):
+ der_bytes = base64.b64decode(base64_data)
+
+ yield (object_type, headers, der_bytes)
+
+ state = 'trash'
+ headers = {}
+ base64_data = b''
+ object_type = None
+ found_end = True
+ continue
+
+ base64_data += line
+
+ if not found_start or not found_end:
+ raise ValueError(unwrap(
+ '''
+ pem_bytes does not appear to contain PEM-encoded data - no
+ BEGIN/END combination found
+ '''
+ ))
+
+
+def unarmor(pem_bytes, multiple=False):
+ """
+ Convert a PEM-encoded byte string into a DER-encoded byte string
+
+ :param pem_bytes:
+ A byte string of the PEM-encoded data
+
+ :param multiple:
+ If True, function will return a generator
+
+ :raises:
+ ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
+
+ :return:
+ A 3-element tuple (object_name, headers, der_bytes). The object_name is
+ a unicode string of what is between "-----BEGIN " and "-----". Examples
+ include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
+ dict containing any lines in the form "Name: Value" that are right
+ after the begin line.
+ """
+
+ generator = _unarmor(pem_bytes)
+
+ if not multiple:
+ return next(generator)
+
+ return generator
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/pkcs12.py b/contrib/python/asn1crypto/py3/asn1crypto/pkcs12.py
new file mode 100644
index 0000000000..7ebcefeb31
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/pkcs12.py
@@ -0,0 +1,193 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for PKCS#12 files. Exports the following items:
+
+ - CertBag()
+ - CrlBag()
+ - Pfx()
+ - SafeBag()
+ - SecretBag()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import DigestInfo
+from .cms import ContentInfo, SignedData
+from .core import (
+ Any,
+ BMPString,
+ Integer,
+ ObjectIdentifier,
+ OctetString,
+ ParsableOctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+)
+from .keys import PrivateKeyInfo, EncryptedPrivateKeyInfo
+from .x509 import Certificate, KeyPurposeId
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc7292
+
+class MacData(Sequence):
+ _fields = [
+ ('mac', DigestInfo),
+ ('mac_salt', OctetString),
+ ('iterations', Integer, {'default': 1}),
+ ]
+
+
+class Version(Integer):
+ _map = {
+ 3: 'v3'
+ }
+
+
+class AttributeType(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc2985#page-18
+ '1.2.840.113549.1.9.20': 'friendly_name',
+ '1.2.840.113549.1.9.21': 'local_key_id',
+ # https://support.microsoft.com/en-us/kb/287547
+ '1.3.6.1.4.1.311.17.1': 'microsoft_local_machine_keyset',
+ # https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
+ # this is a set of OIDs, representing key usage, the usual value is a SET of one element OID 2.5.29.37.0
+ '2.16.840.1.113894.746875.1.1': 'trusted_key_usage',
+ }
+
+
+class SetOfAny(SetOf):
+ _child_spec = Any
+
+
+class SetOfBMPString(SetOf):
+ _child_spec = BMPString
+
+
+class SetOfOctetString(SetOf):
+ _child_spec = OctetString
+
+
+class SetOfKeyPurposeId(SetOf):
+ _child_spec = KeyPurposeId
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', AttributeType),
+ ('values', None),
+ ]
+
+ _oid_specs = {
+ 'friendly_name': SetOfBMPString,
+ 'local_key_id': SetOfOctetString,
+ 'microsoft_csp_name': SetOfBMPString,
+ 'trusted_key_usage': SetOfKeyPurposeId,
+ }
+
+ def _values_spec(self):
+ return self._oid_specs.get(self['type'].native, SetOfAny)
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class Attributes(SetOf):
+ _child_spec = Attribute
+
+
+class Pfx(Sequence):
+ _fields = [
+ ('version', Version),
+ ('auth_safe', ContentInfo),
+ ('mac_data', MacData, {'optional': True})
+ ]
+
+ _authenticated_safe = None
+
+ @property
+ def authenticated_safe(self):
+ if self._authenticated_safe is None:
+ content = self['auth_safe']['content']
+ if isinstance(content, SignedData):
+ content = content['content_info']['content']
+ self._authenticated_safe = AuthenticatedSafe.load(content.native)
+ return self._authenticated_safe
+
+
+class AuthenticatedSafe(SequenceOf):
+ _child_spec = ContentInfo
+
+
+class BagId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.12.10.1.1': 'key_bag',
+ '1.2.840.113549.1.12.10.1.2': 'pkcs8_shrouded_key_bag',
+ '1.2.840.113549.1.12.10.1.3': 'cert_bag',
+ '1.2.840.113549.1.12.10.1.4': 'crl_bag',
+ '1.2.840.113549.1.12.10.1.5': 'secret_bag',
+ '1.2.840.113549.1.12.10.1.6': 'safe_contents',
+ }
+
+
+class CertId(ObjectIdentifier):
+ _map = {
+ '1.2.840.113549.1.9.22.1': 'x509',
+ '1.2.840.113549.1.9.22.2': 'sdsi',
+ }
+
+
+class CertBag(Sequence):
+ _fields = [
+ ('cert_id', CertId),
+ ('cert_value', ParsableOctetString, {'explicit': 0}),
+ ]
+
+ _oid_pair = ('cert_id', 'cert_value')
+ _oid_specs = {
+ 'x509': Certificate,
+ }
+
+
+class CrlBag(Sequence):
+ _fields = [
+ ('crl_id', ObjectIdentifier),
+ ('crl_value', OctetString, {'explicit': 0}),
+ ]
+
+
+class SecretBag(Sequence):
+ _fields = [
+ ('secret_type_id', ObjectIdentifier),
+ ('secret_value', OctetString, {'explicit': 0}),
+ ]
+
+
+class SafeContents(SequenceOf):
+ pass
+
+
+class SafeBag(Sequence):
+ _fields = [
+ ('bag_id', BagId),
+ ('bag_value', Any, {'explicit': 0}),
+ ('bag_attributes', Attributes, {'optional': True}),
+ ]
+
+ _oid_pair = ('bag_id', 'bag_value')
+ _oid_specs = {
+ 'key_bag': PrivateKeyInfo,
+ 'pkcs8_shrouded_key_bag': EncryptedPrivateKeyInfo,
+ 'cert_bag': CertBag,
+ 'crl_bag': CrlBag,
+ 'secret_bag': SecretBag,
+ 'safe_contents': SafeContents
+ }
+
+
+SafeContents._child_spec = SafeBag
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/tsp.py b/contrib/python/asn1crypto/py3/asn1crypto/tsp.py
new file mode 100644
index 0000000000..f006da99c1
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/tsp.py
@@ -0,0 +1,310 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for the time stamp protocol (TSP). Exports the following
+items:
+
+ - TimeStampReq()
+ - TimeStampResp()
+
+Also adds TimeStampedData() support to asn1crypto.cms.ContentInfo(),
+TimeStampedData() and TSTInfo() support to
+asn1crypto.cms.EncapsulatedContentInfo() and some oids and value parsers to
+asn1crypto.cms.CMSAttribute().
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .algos import DigestAlgorithm
+from .cms import (
+ CMSAttribute,
+ CMSAttributeType,
+ ContentInfo,
+ ContentType,
+ EncapsulatedContentInfo,
+)
+from .core import (
+ Any,
+ BitString,
+ Boolean,
+ Choice,
+ GeneralizedTime,
+ IA5String,
+ Integer,
+ ObjectIdentifier,
+ OctetString,
+ Sequence,
+ SequenceOf,
+ SetOf,
+ UTF8String,
+)
+from .crl import CertificateList
+from .x509 import (
+ Attributes,
+ CertificatePolicies,
+ GeneralName,
+ GeneralNames,
+)
+
+
+# The structures in this file are based on https://tools.ietf.org/html/rfc3161,
+# https://tools.ietf.org/html/rfc4998, https://tools.ietf.org/html/rfc5544,
+# https://tools.ietf.org/html/rfc5035, https://tools.ietf.org/html/rfc2634
+
+class Version(Integer):
+ _map = {
+ 0: 'v0',
+ 1: 'v1',
+ 2: 'v2',
+ 3: 'v3',
+ 4: 'v4',
+ 5: 'v5',
+ }
+
+
+class MessageImprint(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm),
+ ('hashed_message', OctetString),
+ ]
+
+
+class Accuracy(Sequence):
+ _fields = [
+ ('seconds', Integer, {'optional': True}),
+ ('millis', Integer, {'implicit': 0, 'optional': True}),
+ ('micros', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class Extension(Sequence):
+ _fields = [
+ ('extn_id', ObjectIdentifier),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', OctetString),
+ ]
+
+
+class Extensions(SequenceOf):
+ _child_spec = Extension
+
+
+class TSTInfo(Sequence):
+ _fields = [
+ ('version', Version),
+ ('policy', ObjectIdentifier),
+ ('message_imprint', MessageImprint),
+ ('serial_number', Integer),
+ ('gen_time', GeneralizedTime),
+ ('accuracy', Accuracy, {'optional': True}),
+ ('ordering', Boolean, {'default': False}),
+ ('nonce', Integer, {'optional': True}),
+ ('tsa', GeneralName, {'explicit': 0, 'optional': True}),
+ ('extensions', Extensions, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class TimeStampReq(Sequence):
+ _fields = [
+ ('version', Version),
+ ('message_imprint', MessageImprint),
+ ('req_policy', ObjectIdentifier, {'optional': True}),
+ ('nonce', Integer, {'optional': True}),
+ ('cert_req', Boolean, {'default': False}),
+ ('extensions', Extensions, {'implicit': 0, 'optional': True}),
+ ]
+
+
+class PKIStatus(Integer):
+ _map = {
+ 0: 'granted',
+ 1: 'granted_with_mods',
+ 2: 'rejection',
+ 3: 'waiting',
+ 4: 'revocation_warning',
+ 5: 'revocation_notification',
+ }
+
+
+class PKIFreeText(SequenceOf):
+ _child_spec = UTF8String
+
+
+class PKIFailureInfo(BitString):
+ _map = {
+ 0: 'bad_alg',
+ 2: 'bad_request',
+ 5: 'bad_data_format',
+ 14: 'time_not_available',
+ 15: 'unaccepted_policy',
+ 16: 'unaccepted_extensions',
+ 17: 'add_info_not_available',
+ 25: 'system_failure',
+ }
+
+
+class PKIStatusInfo(Sequence):
+ _fields = [
+ ('status', PKIStatus),
+ ('status_string', PKIFreeText, {'optional': True}),
+ ('fail_info', PKIFailureInfo, {'optional': True}),
+ ]
+
+
+class TimeStampResp(Sequence):
+ _fields = [
+ ('status', PKIStatusInfo),
+ ('time_stamp_token', ContentInfo),
+ ]
+
+
+class MetaData(Sequence):
+ _fields = [
+ ('hash_protected', Boolean),
+ ('file_name', UTF8String, {'optional': True}),
+ ('media_type', IA5String, {'optional': True}),
+ ('other_meta_data', Attributes, {'optional': True}),
+ ]
+
+
+class TimeStampAndCRL(Sequence):
+ _fields = [
+ ('time_stamp', EncapsulatedContentInfo),
+ ('crl', CertificateList, {'optional': True}),
+ ]
+
+
+class TimeStampTokenEvidence(SequenceOf):
+ _child_spec = TimeStampAndCRL
+
+
+class DigestAlgorithms(SequenceOf):
+ _child_spec = DigestAlgorithm
+
+
+class EncryptionInfo(Sequence):
+ _fields = [
+ ('encryption_info_type', ObjectIdentifier),
+ ('encryption_info_value', Any),
+ ]
+
+
+class PartialHashtree(SequenceOf):
+ _child_spec = OctetString
+
+
+class PartialHashtrees(SequenceOf):
+ _child_spec = PartialHashtree
+
+
+class ArchiveTimeStamp(Sequence):
+ _fields = [
+ ('digest_algorithm', DigestAlgorithm, {'implicit': 0, 'optional': True}),
+ ('attributes', Attributes, {'implicit': 1, 'optional': True}),
+ ('reduced_hashtree', PartialHashtrees, {'implicit': 2, 'optional': True}),
+ ('time_stamp', ContentInfo),
+ ]
+
+
+class ArchiveTimeStampSequence(SequenceOf):
+ _child_spec = ArchiveTimeStamp
+
+
+class EvidenceRecord(Sequence):
+ _fields = [
+ ('version', Version),
+ ('digest_algorithms', DigestAlgorithms),
+ ('crypto_infos', Attributes, {'implicit': 0, 'optional': True}),
+ ('encryption_info', EncryptionInfo, {'implicit': 1, 'optional': True}),
+ ('archive_time_stamp_sequence', ArchiveTimeStampSequence),
+ ]
+
+
+class OtherEvidence(Sequence):
+ _fields = [
+ ('oe_type', ObjectIdentifier),
+ ('oe_value', Any),
+ ]
+
+
+class Evidence(Choice):
+ _alternatives = [
+ ('tst_evidence', TimeStampTokenEvidence, {'implicit': 0}),
+ ('ers_evidence', EvidenceRecord, {'implicit': 1}),
+ ('other_evidence', OtherEvidence, {'implicit': 2}),
+ ]
+
+
+class TimeStampedData(Sequence):
+ _fields = [
+ ('version', Version),
+ ('data_uri', IA5String, {'optional': True}),
+ ('meta_data', MetaData, {'optional': True}),
+ ('content', OctetString, {'optional': True}),
+ ('temporal_evidence', Evidence),
+ ]
+
+
+class IssuerSerial(Sequence):
+ _fields = [
+ ('issuer', GeneralNames),
+ ('serial_number', Integer),
+ ]
+
+
+class ESSCertID(Sequence):
+ _fields = [
+ ('cert_hash', OctetString),
+ ('issuer_serial', IssuerSerial, {'optional': True}),
+ ]
+
+
+class ESSCertIDs(SequenceOf):
+ _child_spec = ESSCertID
+
+
+class SigningCertificate(Sequence):
+ _fields = [
+ ('certs', ESSCertIDs),
+ ('policies', CertificatePolicies, {'optional': True}),
+ ]
+
+
+class SetOfSigningCertificates(SetOf):
+ _child_spec = SigningCertificate
+
+
+class ESSCertIDv2(Sequence):
+ _fields = [
+ ('hash_algorithm', DigestAlgorithm, {'default': {'algorithm': 'sha256'}}),
+ ('cert_hash', OctetString),
+ ('issuer_serial', IssuerSerial, {'optional': True}),
+ ]
+
+
+class ESSCertIDv2s(SequenceOf):
+ _child_spec = ESSCertIDv2
+
+
+class SigningCertificateV2(Sequence):
+ _fields = [
+ ('certs', ESSCertIDv2s),
+ ('policies', CertificatePolicies, {'optional': True}),
+ ]
+
+
+class SetOfSigningCertificatesV2(SetOf):
+ _child_spec = SigningCertificateV2
+
+
+EncapsulatedContentInfo._oid_specs['tst_info'] = TSTInfo
+EncapsulatedContentInfo._oid_specs['timestamped_data'] = TimeStampedData
+ContentInfo._oid_specs['timestamped_data'] = TimeStampedData
+ContentType._map['1.2.840.113549.1.9.16.1.4'] = 'tst_info'
+ContentType._map['1.2.840.113549.1.9.16.1.31'] = 'timestamped_data'
+CMSAttributeType._map['1.2.840.113549.1.9.16.2.12'] = 'signing_certificate'
+CMSAttribute._oid_specs['signing_certificate'] = SetOfSigningCertificates
+CMSAttributeType._map['1.2.840.113549.1.9.16.2.47'] = 'signing_certificate_v2'
+CMSAttribute._oid_specs['signing_certificate_v2'] = SetOfSigningCertificatesV2
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/util.py b/contrib/python/asn1crypto/py3/asn1crypto/util.py
new file mode 100644
index 0000000000..7196897cec
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/util.py
@@ -0,0 +1,878 @@
+# coding: utf-8
+
+"""
+Miscellaneous data helpers, including functions for converting integers to and
+from bytes and UTC timezone. Exports the following items:
+
+ - OrderedDict()
+ - int_from_bytes()
+ - int_to_bytes()
+ - timezone.utc
+ - utc_with_dst
+ - create_timezone()
+ - inet_ntop()
+ - inet_pton()
+ - uri_to_iri()
+ - iri_to_uri()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import math
+import sys
+from datetime import datetime, date, timedelta, tzinfo
+
+from ._errors import unwrap
+from ._iri import iri_to_uri, uri_to_iri # noqa
+from ._ordereddict import OrderedDict # noqa
+from ._types import type_name
+
+if sys.platform == 'win32':
+ from ._inet import inet_ntop, inet_pton
+else:
+ from socket import inet_ntop, inet_pton # noqa
+
+
+# Python 2
+if sys.version_info <= (3,):
+
+ def int_to_bytes(value, signed=False, width=None):
+ """
+ Converts an integer to a byte string
+
+ :param value:
+ The integer to convert
+
+ :param signed:
+ If the byte string should be encoded using two's complement
+
+ :param width:
+ If None, the minimal possible size (but at least 1),
+ otherwise an integer of the byte width for the return value
+
+ :return:
+ A byte string
+ """
+
+ if value == 0 and width == 0:
+ return b''
+
+ # Handle negatives in two's complement
+ is_neg = False
+ if signed and value < 0:
+ is_neg = True
+ bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
+ value = (value + (1 << bits)) % (1 << bits)
+
+ hex_str = '%x' % value
+ if len(hex_str) & 1:
+ hex_str = '0' + hex_str
+
+ output = hex_str.decode('hex')
+
+ if signed and not is_neg and ord(output[0:1]) & 0x80:
+ output = b'\x00' + output
+
+ if width is not None:
+ if len(output) > width:
+ raise OverflowError('int too big to convert')
+ if is_neg:
+ pad_char = b'\xFF'
+ else:
+ pad_char = b'\x00'
+ output = (pad_char * (width - len(output))) + output
+ elif is_neg and ord(output[0:1]) & 0x80 == 0:
+ output = b'\xFF' + output
+
+ return output
+
+ def int_from_bytes(value, signed=False):
+ """
+ Converts a byte string to an integer
+
+ :param value:
+ The byte string to convert
+
+ :param signed:
+ If the byte string should be interpreted using two's complement
+
+ :return:
+ An integer
+ """
+
+ if value == b'':
+ return 0
+
+ num = long(value.encode("hex"), 16) # noqa
+
+ if not signed:
+ return num
+
+ # Check for sign bit and handle two's complement
+ if ord(value[0:1]) & 0x80:
+ bit_len = len(value) * 8
+ return num - (1 << bit_len)
+
+ return num
+
+ class timezone(tzinfo): # noqa
+ """
+ Implements datetime.timezone for py2.
+ Only full minute offsets are supported.
+ DST is not supported.
+ """
+
+ def __init__(self, offset, name=None):
+ """
+ :param offset:
+ A timedelta with this timezone's offset from UTC
+
+ :param name:
+ Name of the timezone; if None, generate one.
+ """
+
+ if not timedelta(hours=-24) < offset < timedelta(hours=24):
+ raise ValueError('Offset must be in [-23:59, 23:59]')
+
+ if offset.seconds % 60 or offset.microseconds:
+ raise ValueError('Offset must be full minutes')
+
+ self._offset = offset
+
+ if name is not None:
+ self._name = name
+ elif not offset:
+ self._name = 'UTC'
+ else:
+ self._name = 'UTC' + _format_offset(offset)
+
+ def __eq__(self, other):
+ """
+ Compare two timezones
+
+ :param other:
+ The other timezone to compare to
+
+ :return:
+ A boolean
+ """
+
+ if type(other) != timezone:
+ return False
+ return self._offset == other._offset
+
+ def __getinitargs__(self):
+ """
+ Called by tzinfo.__reduce__ to support pickle and copy.
+
+ :return:
+ offset and name, to be used for __init__
+ """
+
+ return self._offset, self._name
+
+ def tzname(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ Name of this timezone
+ """
+
+ return self._name
+
+ def utcoffset(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ A timedelta object with the offset from UTC
+ """
+
+ return self._offset
+
+ def dst(self, dt):
+ """
+ :param dt:
+ A datetime object; ignored.
+
+ :return:
+ Zero timedelta
+ """
+
+ return timedelta(0)
+
+ timezone.utc = timezone(timedelta(0))
+
+# Python 3
+else:
+
+ from datetime import timezone # noqa
+
+ def int_to_bytes(value, signed=False, width=None):
+ """
+ Converts an integer to a byte string
+
+ :param value:
+ The integer to convert
+
+ :param signed:
+ If the byte string should be encoded using two's complement
+
+ :param width:
+ If None, the minimal possible size (but at least 1),
+ otherwise an integer of the byte width for the return value
+
+ :return:
+ A byte string
+ """
+
+ if width is None:
+ if signed:
+ if value < 0:
+ bits_required = abs(value + 1).bit_length()
+ else:
+ bits_required = value.bit_length()
+ if bits_required % 8 == 0:
+ bits_required += 1
+ else:
+ bits_required = value.bit_length()
+ width = math.ceil(bits_required / 8) or 1
+ return value.to_bytes(width, byteorder='big', signed=signed)
+
+ def int_from_bytes(value, signed=False):
+ """
+ Converts a byte string to an integer
+
+ :param value:
+ The byte string to convert
+
+ :param signed:
+ If the byte string should be interpreted using two's complement
+
+ :return:
+ An integer
+ """
+
+ return int.from_bytes(value, 'big', signed=signed)
+
+
+def _format_offset(off):
+ """
+ Format a timedelta into "[+-]HH:MM" format or "" for None
+ """
+
+ if off is None:
+ return ''
+ mins = off.days * 24 * 60 + off.seconds // 60
+ sign = '-' if mins < 0 else '+'
+ return sign + '%02d:%02d' % divmod(abs(mins), 60)
+
+
+class _UtcWithDst(tzinfo):
+ """
+ Utc class where dst does not return None; required for astimezone
+ """
+
+ def tzname(self, dt):
+ return 'UTC'
+
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def dst(self, dt):
+ return timedelta(0)
+
+
+utc_with_dst = _UtcWithDst()
+
+_timezone_cache = {}
+
+
+def create_timezone(offset):
+ """
+ Returns a new datetime.timezone object with the given offset.
+ Uses cached objects if possible.
+
+ :param offset:
+ A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59.
+
+ :return:
+ A datetime.timezone object
+ """
+
+ try:
+ tz = _timezone_cache[offset]
+ except KeyError:
+ tz = _timezone_cache[offset] = timezone(offset)
+ return tz
+
+
+class extended_date(object):
+ """
+ A datetime.datetime-like object that represents the year 0. This is just
+ to handle 0000-01-01 found in some certificates. Python's datetime does
+ not support year 0.
+
+ The proleptic gregorian calendar repeats itself every 400 years. Therefore,
+ the simplest way to format is to substitute year 2000.
+ """
+
+ def __init__(self, year, month, day):
+ """
+ :param year:
+ The integer 0
+
+ :param month:
+ An integer from 1 to 12
+
+ :param day:
+ An integer from 1 to 31
+ """
+
+ if year != 0:
+ raise ValueError('year must be 0')
+
+ self._y2k = date(2000, month, day)
+
+ @property
+ def year(self):
+ """
+ :return:
+ The integer 0
+ """
+
+ return 0
+
+ @property
+ def month(self):
+ """
+ :return:
+ An integer from 1 to 12
+ """
+
+ return self._y2k.month
+
+ @property
+ def day(self):
+ """
+ :return:
+ An integer from 1 to 31
+ """
+
+ return self._y2k.day
+
+ def strftime(self, format):
+ """
+ Formats the date using strftime()
+
+ :param format:
+ A strftime() format string
+
+ :return:
+ A str, the formatted date as a unicode string
+ in Python 3 and a byte string in Python 2
+ """
+
+ # Format the date twice, once with year 2000, once with year 4000.
+ # The only differences in the result will be in the millennium. Find them and replace by zeros.
+ y2k = self._y2k.strftime(format)
+ y4k = self._y2k.replace(year=4000).strftime(format)
+ return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
+
+ def isoformat(self):
+ """
+ Formats the date as %Y-%m-%d
+
+ :return:
+ The date formatted to %Y-%m-%d as a unicode string in Python 3
+ and a byte string in Python 2
+ """
+
+ return self.strftime('0000-%m-%d')
+
+ def replace(self, year=None, month=None, day=None):
+ """
+ Returns a new datetime.date or asn1crypto.util.extended_date
+ object with the specified components replaced
+
+ :return:
+ A datetime.date or asn1crypto.util.extended_date object
+ """
+
+ if year is None:
+ year = self.year
+ if month is None:
+ month = self.month
+ if day is None:
+ day = self.day
+
+ if year > 0:
+ cls = date
+ else:
+ cls = extended_date
+
+ return cls(
+ year,
+ month,
+ day
+ )
+
+ def __str__(self):
+ """
+ :return:
+ A str representing this extended_date, e.g. "0000-01-01"
+ """
+
+ return self.strftime('%Y-%m-%d')
+
+ def __eq__(self, other):
+ """
+ Compare two extended_date objects
+
+ :param other:
+ The other extended_date to compare to
+
+ :return:
+ A boolean
+ """
+
+ # datetime.date object wouldn't compare equal because it can't be year 0
+ if not isinstance(other, self.__class__):
+ return False
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ """
+ Compare two extended_date objects
+
+ :param other:
+ The other extended_date to compare to
+
+ :return:
+ A boolean
+ """
+
+ return not self.__eq__(other)
+
+ def _comparison_error(self, other):
+ raise TypeError(unwrap(
+ '''
+ An asn1crypto.util.extended_date object can only be compared to
+ an asn1crypto.util.extended_date or datetime.date object, not %s
+ ''',
+ type_name(other)
+ ))
+
+ def __cmp__(self, other):
+ """
+ Compare two extended_date or datetime.date objects
+
+ :param other:
+ The other extended_date object to compare to
+
+ :return:
+ An integer smaller than, equal to, or larger than 0
+ """
+
+ # self is year 0, other is >= year 1
+ if isinstance(other, date):
+ return -1
+
+ if not isinstance(other, self.__class__):
+ self._comparison_error(other)
+
+ if self._y2k < other._y2k:
+ return -1
+ if self._y2k > other._y2k:
+ return 1
+ return 0
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __le__(self, other):
+ return self.__cmp__(other) <= 0
+
+ def __gt__(self, other):
+ return self.__cmp__(other) > 0
+
+ def __ge__(self, other):
+ return self.__cmp__(other) >= 0
+
+
+class extended_datetime(object):
+ """
+ A datetime.datetime-like object that represents the year 0. This is just
+ to handle 0000-01-01 found in some certificates. Python's datetime does
+ not support year 0.
+
+ The proleptic gregorian calendar repeats itself every 400 years. Therefore,
+ the simplest way to format is to substitute year 2000.
+ """
+
+ # There are 97 leap days during 400 years.
+ DAYS_IN_400_YEARS = 400 * 365 + 97
+ DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS
+
+ def __init__(self, year, *args, **kwargs):
+ """
+ :param year:
+ The integer 0
+
+ :param args:
+ Other positional arguments; see datetime.datetime.
+
+ :param kwargs:
+ Other keyword arguments; see datetime.datetime.
+ """
+
+ if year != 0:
+ raise ValueError('year must be 0')
+
+ self._y2k = datetime(2000, *args, **kwargs)
+
+ @property
+ def year(self):
+ """
+ :return:
+ The integer 0
+ """
+
+ return 0
+
+ @property
+ def month(self):
+ """
+ :return:
+ An integer from 1 to 12
+ """
+
+ return self._y2k.month
+
+ @property
+ def day(self):
+ """
+ :return:
+ An integer from 1 to 31
+ """
+
+ return self._y2k.day
+
+ @property
+ def hour(self):
+ """
+ :return:
+ An integer from 1 to 24
+ """
+
+ return self._y2k.hour
+
+ @property
+ def minute(self):
+ """
+ :return:
+ An integer from 1 to 60
+ """
+
+ return self._y2k.minute
+
+ @property
+ def second(self):
+ """
+ :return:
+ An integer from 1 to 60
+ """
+
+ return self._y2k.second
+
+ @property
+ def microsecond(self):
+ """
+ :return:
+ An integer from 0 to 999999
+ """
+
+ return self._y2k.microsecond
+
+ @property
+ def tzinfo(self):
+ """
+ :return:
+ If object is timezone aware, a datetime.tzinfo object, else None.
+ """
+
+ return self._y2k.tzinfo
+
+ def utcoffset(self):
+ """
+ :return:
+ If object is timezone aware, a datetime.timedelta object, else None.
+ """
+
+ return self._y2k.utcoffset()
+
+ def time(self):
+ """
+ :return:
+ A datetime.time object
+ """
+
+ return self._y2k.time()
+
+ def date(self):
+ """
+ :return:
+ An asn1crypto.util.extended_date of the date
+ """
+
+ return extended_date(0, self.month, self.day)
+
+ def strftime(self, format):
+ """
+ Performs strftime(), always returning a str
+
+ :param format:
+ A strftime() format string
+
+ :return:
+ A str of the formatted datetime
+ """
+
+ # Format the datetime twice, once with year 2000, once with year 4000.
+ # The only differences in the result will be in the millennium. Find them and replace by zeros.
+ y2k = self._y2k.strftime(format)
+ y4k = self._y2k.replace(year=4000).strftime(format)
+ return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
+
+ def isoformat(self, sep='T'):
+ """
+ Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
+ date and time portions
+
+ :param set:
+ A single character of the separator to place between the date and
+ time
+
+ :return:
+ The formatted datetime as a unicode string in Python 3 and a byte
+ string in Python 2
+ """
+
+ s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second)
+ if self.microsecond:
+ s += '.%06d' % self.microsecond
+ return s + _format_offset(self.utcoffset())
+
+ def replace(self, year=None, *args, **kwargs):
+ """
+ Returns a new datetime.datetime or asn1crypto.util.extended_datetime
+ object with the specified components replaced
+
+ :param year:
+ The new year to substitute. None to keep it.
+
+ :param args:
+ Other positional arguments; see datetime.datetime.replace.
+
+ :param kwargs:
+ Other keyword arguments; see datetime.datetime.replace.
+
+ :return:
+ A datetime.datetime or asn1crypto.util.extended_datetime object
+ """
+
+ if year:
+ return self._y2k.replace(year, *args, **kwargs)
+
+ return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs))
+
+ def astimezone(self, tz):
+ """
+ Convert this extended_datetime to another timezone.
+
+ :param tz:
+ A datetime.tzinfo object.
+
+ :return:
+ A new extended_datetime or datetime.datetime object
+ """
+
+ return extended_datetime.from_y2k(self._y2k.astimezone(tz))
+
+ def timestamp(self):
+ """
+ Return POSIX timestamp. Only supported in python >= 3.3
+
+ :return:
+ A float representing the seconds since 1970-01-01 UTC. This will be a negative value.
+ """
+
+ return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400
+
+ def __str__(self):
+ """
+ :return:
+ A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00"
+ """
+
+ return self.isoformat(sep=' ')
+
+ def __eq__(self, other):
+ """
+ Compare two extended_datetime objects
+
+ :param other:
+ The other extended_datetime to compare to
+
+ :return:
+ A boolean
+ """
+
+ # Only compare against other datetime or extended_datetime objects
+ if not isinstance(other, (self.__class__, datetime)):
+ return False
+
+ # Offset-naive and offset-aware datetimes are never the same
+ if (self.tzinfo is None) != (other.tzinfo is None):
+ return False
+
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ """
+ Compare two extended_datetime objects
+
+ :param other:
+ The other extended_datetime to compare to
+
+ :return:
+ A boolean
+ """
+
+ return not self.__eq__(other)
+
+ def _comparison_error(self, other):
+ """
+ Raises a TypeError about the other object not being suitable for
+ comparison
+
+ :param other:
+ The object being compared to
+ """
+
+ raise TypeError(unwrap(
+ '''
+ An asn1crypto.util.extended_datetime object can only be compared to
+ an asn1crypto.util.extended_datetime or datetime.datetime object,
+ not %s
+ ''',
+ type_name(other)
+ ))
+
+ def __cmp__(self, other):
+ """
+ Compare two extended_datetime or datetime.datetime objects
+
+ :param other:
+ The other extended_datetime or datetime.datetime object to compare to
+
+ :return:
+ An integer smaller than, equal to, or larger than 0
+ """
+
+ if not isinstance(other, (self.__class__, datetime)):
+ self._comparison_error(other)
+
+ if (self.tzinfo is None) != (other.tzinfo is None):
+ raise TypeError("can't compare offset-naive and offset-aware datetimes")
+
+ diff = self - other
+ zero = timedelta(0)
+ if diff < zero:
+ return -1
+ if diff > zero:
+ return 1
+ return 0
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __le__(self, other):
+ return self.__cmp__(other) <= 0
+
+ def __gt__(self, other):
+ return self.__cmp__(other) > 0
+
+ def __ge__(self, other):
+ return self.__cmp__(other) >= 0
+
+ def __add__(self, other):
+ """
+ Adds a timedelta
+
+ :param other:
+ A datetime.timedelta object to add.
+
+ :return:
+ A new extended_datetime or datetime.datetime object.
+ """
+
+ return extended_datetime.from_y2k(self._y2k + other)
+
+ def __sub__(self, other):
+ """
+ Subtracts a timedelta or another datetime.
+
+ :param other:
+ A datetime.timedelta or datetime.datetime or extended_datetime object to subtract.
+
+ :return:
+ If a timedelta is passed, a new extended_datetime or datetime.datetime object.
+ Else a datetime.timedelta object.
+ """
+
+ if isinstance(other, timedelta):
+ return extended_datetime.from_y2k(self._y2k - other)
+
+ if isinstance(other, extended_datetime):
+ return self._y2k - other._y2k
+
+ if isinstance(other, datetime):
+ return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS)
+
+ return NotImplemented
+
+ def __rsub__(self, other):
+ return -(self - other)
+
+ @classmethod
+ def from_y2k(cls, value):
+ """
+ Revert substitution of year 2000.
+
+ :param value:
+ A datetime.datetime object which is 2000 years in the future.
+ :return:
+ A new extended_datetime or datetime.datetime object.
+ """
+
+ year = value.year - 2000
+
+ if year > 0:
+ new_cls = datetime
+ else:
+ new_cls = cls
+
+ return new_cls(
+ year,
+ value.month,
+ value.day,
+ value.hour,
+ value.minute,
+ value.second,
+ value.microsecond,
+ value.tzinfo
+ )
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/version.py b/contrib/python/asn1crypto/py3/asn1crypto/version.py
new file mode 100644
index 0000000000..966b57a5c0
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/version.py
@@ -0,0 +1,6 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+
+__version__ = '1.5.1'
+__version_info__ = (1, 5, 1)
diff --git a/contrib/python/asn1crypto/py3/asn1crypto/x509.py b/contrib/python/asn1crypto/py3/asn1crypto/x509.py
new file mode 100644
index 0000000000..8cfb2c78be
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/asn1crypto/x509.py
@@ -0,0 +1,3036 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for X.509 certificates. Exports the following items:
+
+ - Attributes()
+ - Certificate()
+ - Extensions()
+ - GeneralName()
+ - GeneralNames()
+ - Name()
+
+Other type classes are defined that help compose the types listed above.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from contextlib import contextmanager
+from encodings import idna # noqa
+import hashlib
+import re
+import socket
+import stringprep
+import sys
+import unicodedata
+
+from ._errors import unwrap
+from ._iri import iri_to_uri, uri_to_iri
+from ._ordereddict import OrderedDict
+from ._types import type_name, str_cls, bytes_to_list
+from .algos import AlgorithmIdentifier, AnyAlgorithmIdentifier, DigestAlgorithm, SignedDigestAlgorithm
+from .core import (
+ Any,
+ BitString,
+ BMPString,
+ Boolean,
+ Choice,
+ Concat,
+ Enumerated,
+ GeneralizedTime,
+ GeneralString,
+ IA5String,
+ Integer,
+ Null,
+ NumericString,
+ ObjectIdentifier,
+ OctetBitString,
+ OctetString,
+ ParsableOctetString,
+ PrintableString,
+ Sequence,
+ SequenceOf,
+ Set,
+ SetOf,
+ TeletexString,
+ UniversalString,
+ UTCTime,
+ UTF8String,
+ VisibleString,
+ VOID,
+)
+from .keys import PublicKeyInfo
+from .util import int_to_bytes, int_from_bytes, inet_ntop, inet_pton
+
+
+# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
+# and a few other supplementary sources, mostly due to extra supported
+# extension and name OIDs
+
+
+class DNSName(IA5String):
+
+ _encoding = 'idna'
+ _bad_tag = (12, 19)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.2
+
+ :param other:
+ Another DNSName object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, DNSName):
+ return False
+
+ return self.__unicode__().lower() == other.__unicode__().lower()
+
+ def set(self, value):
+ """
+ Sets the value of the DNS name
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value.startswith('.'):
+ encoded_value = b'.' + value[1:].encode(self._encoding)
+ else:
+ encoded_value = value.encode(self._encoding)
+
+ self._unicode = value
+ self.contents = encoded_value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+
+class URI(IA5String):
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ self._unicode = value
+ self.contents = iri_to_uri(value)
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.4
+
+ :param other:
+ Another URI object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, URI):
+ return False
+
+ return iri_to_uri(self.native, True) == iri_to_uri(other.native, True)
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = uri_to_iri(self._merge_chunks())
+ return self._unicode
+
+
+class EmailAddress(IA5String):
+
+ _contents = None
+
+ # If the value has gone through the .set() method, thus normalizing it
+ _normalized = False
+
+ # In the wild we've seen this encoded as a UTF8String and PrintableString
+ _bad_tag = (12, 19)
+
+ @property
+ def contents(self):
+ """
+ :return:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ return self._contents
+
+ @contents.setter
+ def contents(self, value):
+ """
+ :param value:
+ A byte string of the DER-encoded contents of the sequence
+ """
+
+ self._normalized = False
+ self._contents = value
+
+ def set(self, value):
+ """
+ Sets the value of the string
+
+ :param value:
+ A unicode string
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ if value.find('@') != -1:
+ mailbox, hostname = value.rsplit('@', 1)
+ encoded_value = mailbox.encode('ascii') + b'@' + hostname.encode('idna')
+ else:
+ encoded_value = value.encode('ascii')
+
+ self._normalized = True
+ self._unicode = value
+ self.contents = encoded_value
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ # We've seen this in the wild as a PrintableString, and since ascii is a
+ # subset of cp1252, we use the later for decoding to be more user friendly
+ if self._unicode is None:
+ contents = self._merge_chunks()
+ if contents.find(b'@') == -1:
+ self._unicode = contents.decode('cp1252')
+ else:
+ mailbox, hostname = contents.rsplit(b'@', 1)
+ self._unicode = mailbox.decode('cp1252') + '@' + hostname.decode('idna')
+ return self._unicode
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.5
+
+ :param other:
+ Another EmailAddress object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, EmailAddress):
+ return False
+
+ if not self._normalized:
+ self.set(self.native)
+ if not other._normalized:
+ other.set(other.native)
+
+ if self._contents.find(b'@') == -1 or other._contents.find(b'@') == -1:
+ return self._contents == other._contents
+
+ other_mailbox, other_hostname = other._contents.rsplit(b'@', 1)
+ mailbox, hostname = self._contents.rsplit(b'@', 1)
+
+ if mailbox != other_mailbox:
+ return False
+
+ if hostname.lower() != other_hostname.lower():
+ return False
+
+ return True
+
+
+class IPAddress(OctetString):
+ def parse(self, spec=None, spec_params=None):
+ """
+ This method is not applicable to IP addresses
+ """
+
+ raise ValueError(unwrap(
+ '''
+ IP address values can not be parsed
+ '''
+ ))
+
+ def set(self, value):
+ """
+ Sets the value of the object
+
+ :param value:
+ A unicode string containing an IPv4 address, IPv4 address with CIDR,
+ an IPv6 address or IPv6 address with CIDR
+ """
+
+ if not isinstance(value, str_cls):
+ raise TypeError(unwrap(
+ '''
+ %s value must be a unicode string, not %s
+ ''',
+ type_name(self),
+ type_name(value)
+ ))
+
+ original_value = value
+
+ has_cidr = value.find('/') != -1
+ cidr = 0
+ if has_cidr:
+ parts = value.split('/', 1)
+ value = parts[0]
+ cidr = int(parts[1])
+ if cidr < 0:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range less than 0
+ ''',
+ type_name(self)
+ ))
+
+ if value.find(':') != -1:
+ family = socket.AF_INET6
+ if cidr > 128:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range bigger than 128, the maximum
+ value for an IPv6 address
+ ''',
+ type_name(self)
+ ))
+ cidr_size = 128
+ else:
+ family = socket.AF_INET
+ if cidr > 32:
+ raise ValueError(unwrap(
+ '''
+ %s value contains a CIDR range bigger than 32, the maximum
+ value for an IPv4 address
+ ''',
+ type_name(self)
+ ))
+ cidr_size = 32
+
+ cidr_bytes = b''
+ if has_cidr:
+ cidr_mask = '1' * cidr
+ cidr_mask += '0' * (cidr_size - len(cidr_mask))
+ cidr_bytes = int_to_bytes(int(cidr_mask, 2))
+ cidr_bytes = (b'\x00' * ((cidr_size // 8) - len(cidr_bytes))) + cidr_bytes
+
+ self._native = original_value
+ self.contents = inet_pton(family, value) + cidr_bytes
+ self._bytes = self.contents
+ self._header = None
+ if self._trailer != b'':
+ self._trailer = b''
+
+ @property
+ def native(self):
+ """
+ The native Python datatype representation of this value
+
+ :return:
+ A unicode string or None
+ """
+
+ if self.contents is None:
+ return None
+
+ if self._native is None:
+ byte_string = self.__bytes__()
+ byte_len = len(byte_string)
+ value = None
+ cidr_int = None
+ if byte_len in set([32, 16]):
+ value = inet_ntop(socket.AF_INET6, byte_string[0:16])
+ if byte_len > 16:
+ cidr_int = int_from_bytes(byte_string[16:])
+ elif byte_len in set([8, 4]):
+ value = inet_ntop(socket.AF_INET, byte_string[0:4])
+ if byte_len > 4:
+ cidr_int = int_from_bytes(byte_string[4:])
+ if cidr_int is not None:
+ cidr_bits = '{0:b}'.format(cidr_int)
+ cidr = len(cidr_bits.rstrip('0'))
+ value = value + '/' + str_cls(cidr)
+ self._native = value
+ return self._native
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ :param other:
+ Another IPAddress object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, IPAddress):
+ return False
+
+ return self.__bytes__() == other.__bytes__()
+
+
+class Attribute(Sequence):
+ _fields = [
+ ('type', ObjectIdentifier),
+ ('values', SetOf, {'spec': Any}),
+ ]
+
+
+class Attributes(SequenceOf):
+ _child_spec = Attribute
+
+
+class KeyUsage(BitString):
+ _map = {
+ 0: 'digital_signature',
+ 1: 'non_repudiation',
+ 2: 'key_encipherment',
+ 3: 'data_encipherment',
+ 4: 'key_agreement',
+ 5: 'key_cert_sign',
+ 6: 'crl_sign',
+ 7: 'encipher_only',
+ 8: 'decipher_only',
+ }
+
+
+class PrivateKeyUsagePeriod(Sequence):
+ _fields = [
+ ('not_before', GeneralizedTime, {'implicit': 0, 'optional': True}),
+ ('not_after', GeneralizedTime, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class NotReallyTeletexString(TeletexString):
+ """
+ OpenSSL (and probably some other libraries) puts ISO-8859-1
+ into TeletexString instead of ITU T.61. We use Windows-1252 when
+ decoding since it is a superset of ISO-8859-1, and less likely to
+ cause encoding issues, but we stay strict with encoding to prevent
+ us from creating bad data.
+ """
+
+ _decoding_encoding = 'cp1252'
+
+ def __unicode__(self):
+ """
+ :return:
+ A unicode string
+ """
+
+ if self.contents is None:
+ return ''
+ if self._unicode is None:
+ self._unicode = self._merge_chunks().decode(self._decoding_encoding)
+ return self._unicode
+
+
+@contextmanager
+def strict_teletex():
+ try:
+ NotReallyTeletexString._decoding_encoding = 'teletex'
+ yield
+ finally:
+ NotReallyTeletexString._decoding_encoding = 'cp1252'
+
+
+class DirectoryString(Choice):
+ _alternatives = [
+ ('teletex_string', NotReallyTeletexString),
+ ('printable_string', PrintableString),
+ ('universal_string', UniversalString),
+ ('utf8_string', UTF8String),
+ ('bmp_string', BMPString),
+ # This is an invalid/bad alternative, but some broken certs use it
+ ('ia5_string', IA5String),
+ ]
+
+
+class NameType(ObjectIdentifier):
+ _map = {
+ '2.5.4.3': 'common_name',
+ '2.5.4.4': 'surname',
+ '2.5.4.5': 'serial_number',
+ '2.5.4.6': 'country_name',
+ '2.5.4.7': 'locality_name',
+ '2.5.4.8': 'state_or_province_name',
+ '2.5.4.9': 'street_address',
+ '2.5.4.10': 'organization_name',
+ '2.5.4.11': 'organizational_unit_name',
+ '2.5.4.12': 'title',
+ '2.5.4.15': 'business_category',
+ '2.5.4.17': 'postal_code',
+ '2.5.4.20': 'telephone_number',
+ '2.5.4.41': 'name',
+ '2.5.4.42': 'given_name',
+ '2.5.4.43': 'initials',
+ '2.5.4.44': 'generation_qualifier',
+ '2.5.4.45': 'unique_identifier',
+ '2.5.4.46': 'dn_qualifier',
+ '2.5.4.65': 'pseudonym',
+ '2.5.4.97': 'organization_identifier',
+ # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
+ '2.23.133.2.1': 'tpm_manufacturer',
+ '2.23.133.2.2': 'tpm_model',
+ '2.23.133.2.3': 'tpm_version',
+ '2.23.133.2.4': 'platform_manufacturer',
+ '2.23.133.2.5': 'platform_model',
+ '2.23.133.2.6': 'platform_version',
+ # https://tools.ietf.org/html/rfc2985#page-26
+ '1.2.840.113549.1.9.1': 'email_address',
+ # Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
+ '1.3.6.1.4.1.311.60.2.1.1': 'incorporation_locality',
+ '1.3.6.1.4.1.311.60.2.1.2': 'incorporation_state_or_province',
+ '1.3.6.1.4.1.311.60.2.1.3': 'incorporation_country',
+ # https://tools.ietf.org/html/rfc4519#section-2.39
+ '0.9.2342.19200300.100.1.1': 'user_id',
+ # https://tools.ietf.org/html/rfc2247#section-4
+ '0.9.2342.19200300.100.1.25': 'domain_component',
+ # http://www.alvestrand.no/objectid/0.2.262.1.10.7.20.html
+ '0.2.262.1.10.7.20': 'name_distinguisher',
+ }
+
+ # This order is largely based on observed order seen in EV certs from
+ # Symantec and DigiCert. Some of the uncommon name-related fields are
+ # just placed in what seems like a reasonable order.
+ preferred_order = [
+ 'incorporation_country',
+ 'incorporation_state_or_province',
+ 'incorporation_locality',
+ 'business_category',
+ 'serial_number',
+ 'country_name',
+ 'postal_code',
+ 'state_or_province_name',
+ 'locality_name',
+ 'street_address',
+ 'organization_name',
+ 'organizational_unit_name',
+ 'title',
+ 'common_name',
+ 'user_id',
+ 'initials',
+ 'generation_qualifier',
+ 'surname',
+ 'given_name',
+ 'name',
+ 'pseudonym',
+ 'dn_qualifier',
+ 'telephone_number',
+ 'email_address',
+ 'domain_component',
+ 'name_distinguisher',
+ 'organization_identifier',
+ 'tpm_manufacturer',
+ 'tpm_model',
+ 'tpm_version',
+ 'platform_manufacturer',
+ 'platform_model',
+ 'platform_version',
+ ]
+
+ @classmethod
+ def preferred_ordinal(cls, attr_name):
+ """
+ Returns an ordering value for a particular attribute key.
+
+ Unrecognized attributes and OIDs will be sorted lexically at the end.
+
+ :return:
+ An orderable value.
+
+ """
+
+ attr_name = cls.map(attr_name)
+ if attr_name in cls.preferred_order:
+ ordinal = cls.preferred_order.index(attr_name)
+ else:
+ ordinal = len(cls.preferred_order)
+
+ return (ordinal, attr_name)
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A human-friendly unicode string to display to users
+ """
+
+ return {
+ 'common_name': 'Common Name',
+ 'surname': 'Surname',
+ 'serial_number': 'Serial Number',
+ 'country_name': 'Country',
+ 'locality_name': 'Locality',
+ 'state_or_province_name': 'State/Province',
+ 'street_address': 'Street Address',
+ 'organization_name': 'Organization',
+ 'organizational_unit_name': 'Organizational Unit',
+ 'title': 'Title',
+ 'business_category': 'Business Category',
+ 'postal_code': 'Postal Code',
+ 'telephone_number': 'Telephone Number',
+ 'name': 'Name',
+ 'given_name': 'Given Name',
+ 'initials': 'Initials',
+ 'generation_qualifier': 'Generation Qualifier',
+ 'unique_identifier': 'Unique Identifier',
+ 'dn_qualifier': 'DN Qualifier',
+ 'pseudonym': 'Pseudonym',
+ 'email_address': 'Email Address',
+ 'incorporation_locality': 'Incorporation Locality',
+ 'incorporation_state_or_province': 'Incorporation State/Province',
+ 'incorporation_country': 'Incorporation Country',
+ 'domain_component': 'Domain Component',
+ 'name_distinguisher': 'Name Distinguisher',
+ 'organization_identifier': 'Organization Identifier',
+ 'tpm_manufacturer': 'TPM Manufacturer',
+ 'tpm_model': 'TPM Model',
+ 'tpm_version': 'TPM Version',
+ 'platform_manufacturer': 'Platform Manufacturer',
+ 'platform_model': 'Platform Model',
+ 'platform_version': 'Platform Version',
+ 'user_id': 'User ID',
+ }.get(self.native, self.native)
+
+
+class NameTypeAndValue(Sequence):
+ _fields = [
+ ('type', NameType),
+ ('value', Any),
+ ]
+
+ _oid_pair = ('type', 'value')
+ _oid_specs = {
+ 'common_name': DirectoryString,
+ 'surname': DirectoryString,
+ 'serial_number': DirectoryString,
+ 'country_name': DirectoryString,
+ 'locality_name': DirectoryString,
+ 'state_or_province_name': DirectoryString,
+ 'street_address': DirectoryString,
+ 'organization_name': DirectoryString,
+ 'organizational_unit_name': DirectoryString,
+ 'title': DirectoryString,
+ 'business_category': DirectoryString,
+ 'postal_code': DirectoryString,
+ 'telephone_number': PrintableString,
+ 'name': DirectoryString,
+ 'given_name': DirectoryString,
+ 'initials': DirectoryString,
+ 'generation_qualifier': DirectoryString,
+ 'unique_identifier': OctetBitString,
+ 'dn_qualifier': DirectoryString,
+ 'pseudonym': DirectoryString,
+ # https://tools.ietf.org/html/rfc2985#page-26
+ 'email_address': EmailAddress,
+ # Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
+ 'incorporation_locality': DirectoryString,
+ 'incorporation_state_or_province': DirectoryString,
+ 'incorporation_country': DirectoryString,
+ 'domain_component': DNSName,
+ 'name_distinguisher': DirectoryString,
+ 'organization_identifier': DirectoryString,
+ 'tpm_manufacturer': UTF8String,
+ 'tpm_model': UTF8String,
+ 'tpm_version': UTF8String,
+ 'platform_manufacturer': UTF8String,
+ 'platform_model': UTF8String,
+ 'platform_version': UTF8String,
+ 'user_id': DirectoryString,
+ }
+
+ _prepped = None
+
+ @property
+ def prepped_value(self):
+ """
+ Returns the value after being processed by the internationalized string
+ preparation as specified by RFC 5280
+
+ :return:
+ A unicode string
+ """
+
+ if self._prepped is None:
+ self._prepped = self._ldap_string_prep(self['value'].native)
+ return self._prepped
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another NameTypeAndValue object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, NameTypeAndValue):
+ return False
+
+ if other['type'].native != self['type'].native:
+ return False
+
+ return other.prepped_value == self.prepped_value
+
+ def _ldap_string_prep(self, string):
+ """
+ Implements the internationalized string preparation algorithm from
+ RFC 4518. https://tools.ietf.org/html/rfc4518#section-2
+
+ :param string:
+ A unicode string to prepare
+
+ :return:
+ A prepared unicode string, ready for comparison
+ """
+
+ # Map step
+ string = re.sub('[\u00ad\u1806\u034f\u180b-\u180d\ufe0f-\uff00\ufffc]+', '', string)
+ string = re.sub('[\u0009\u000a\u000b\u000c\u000d\u0085]', ' ', string)
+ if sys.maxunicode == 0xffff:
+ # Some installs of Python 2.7 don't support 8-digit unicode escape
+ # ranges, so we have to break them into pieces
+ # Original was: \U0001D173-\U0001D17A and \U000E0020-\U000E007F
+ string = re.sub('\ud834[\udd73-\udd7a]|\udb40[\udc20-\udc7f]|\U000e0001', '', string)
+ else:
+ string = re.sub('[\U0001D173-\U0001D17A\U000E0020-\U000E007F\U000e0001]', '', string)
+ string = re.sub(
+ '[\u0000-\u0008\u000e-\u001f\u007f-\u0084\u0086-\u009f\u06dd\u070f\u180e\u200c-\u200f'
+ '\u202a-\u202e\u2060-\u2063\u206a-\u206f\ufeff\ufff9-\ufffb]+',
+ '',
+ string
+ )
+ string = string.replace('\u200b', '')
+ string = re.sub('[\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000]', ' ', string)
+
+ string = ''.join(map(stringprep.map_table_b2, string))
+
+ # Normalize step
+ string = unicodedata.normalize('NFKC', string)
+
+ # Prohibit step
+ for char in string:
+ if stringprep.in_table_a1(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain unassigned code points
+ '''
+ ))
+
+ if stringprep.in_table_c8(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain change display or
+ zzzzdeprecated characters
+ '''
+ ))
+
+ if stringprep.in_table_c3(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain private use characters
+ '''
+ ))
+
+ if stringprep.in_table_c4(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain non-character code points
+ '''
+ ))
+
+ if stringprep.in_table_c5(char):
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain surrogate code points
+ '''
+ ))
+
+ if char == '\ufffd':
+ raise ValueError(unwrap(
+ '''
+ X.509 Name objects may not contain the replacement character
+ '''
+ ))
+
+ # Check bidirectional step - here we ensure that we are not mixing
+ # left-to-right and right-to-left text in the string
+ has_r_and_al_cat = False
+ has_l_cat = False
+ for char in string:
+ if stringprep.in_table_d1(char):
+ has_r_and_al_cat = True
+ elif stringprep.in_table_d2(char):
+ has_l_cat = True
+
+ if has_r_and_al_cat:
+ first_is_r_and_al = stringprep.in_table_d1(string[0])
+ last_is_r_and_al = stringprep.in_table_d1(string[-1])
+
+ if has_l_cat or not first_is_r_and_al or not last_is_r_and_al:
+ raise ValueError(unwrap(
+ '''
+ X.509 Name object contains a malformed bidirectional
+ sequence
+ '''
+ ))
+
+ # Insignificant space handling step
+ string = ' ' + re.sub(' +', ' ', string).strip() + ' '
+
+ return string
+
+
+class RelativeDistinguishedName(SetOf):
+ _child_spec = NameTypeAndValue
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ output = []
+ values = self._get_values(self)
+ for key in sorted(values.keys()):
+ output.append('%s: %s' % (key, values[key]))
+ # Unit separator is used here since the normalization process for
+ # values moves any such character, and the keys are all dotted integers
+ # or under_score_words
+ return '\x1F'.join(output)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another RelativeDistinguishedName object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, RelativeDistinguishedName):
+ return False
+
+ if len(self) != len(other):
+ return False
+
+ self_types = self._get_types(self)
+ other_types = self._get_types(other)
+
+ if self_types != other_types:
+ return False
+
+ self_values = self._get_values(self)
+ other_values = self._get_values(other)
+
+ for type_name_ in self_types:
+ if self_values[type_name_] != other_values[type_name_]:
+ return False
+
+ return True
+
+ def _get_types(self, rdn):
+ """
+ Returns a set of types contained in an RDN
+
+ :param rdn:
+ A RelativeDistinguishedName object
+
+ :return:
+ A set object with unicode strings of NameTypeAndValue type field
+ values
+ """
+
+ return set([ntv['type'].native for ntv in rdn])
+
+ def _get_values(self, rdn):
+ """
+ Returns a dict of prepped values contained in an RDN
+
+ :param rdn:
+ A RelativeDistinguishedName object
+
+ :return:
+ A dict object with unicode strings of NameTypeAndValue value field
+ values that have been prepped for comparison
+ """
+
+ output = {}
+ [output.update([(ntv['type'].native, ntv.prepped_value)]) for ntv in rdn]
+ return output
+
+
+class RDNSequence(SequenceOf):
+ _child_spec = RelativeDistinguishedName
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ # Record separator is used here since the normalization process for
+ # values moves any such character, and the keys are all dotted integers
+ # or under_score_words
+ return '\x1E'.join(rdn.hashable for rdn in self)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another RDNSequence object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, RDNSequence):
+ return False
+
+ if len(self) != len(other):
+ return False
+
+ for index, self_rdn in enumerate(self):
+ if other[index] != self_rdn:
+ return False
+
+ return True
+
+
+class Name(Choice):
+ _alternatives = [
+ ('', RDNSequence),
+ ]
+
+ _human_friendly = None
+ _sha1 = None
+ _sha256 = None
+
+ @classmethod
+ def build(cls, name_dict, use_printable=False):
+ """
+ Creates a Name object from a dict of unicode string keys and values.
+ The keys should be from NameType._map, or a dotted-integer OID unicode
+ string.
+
+ :param name_dict:
+ A dict of name information, e.g. {"common_name": "Will Bond",
+ "country_name": "US", "organization_name": "Codex Non Sufficit LC"}
+
+ :param use_printable:
+ A bool - if PrintableString should be used for encoding instead of
+ UTF8String. This is for backwards compatibility with old software.
+
+ :return:
+ An x509.Name object
+ """
+
+ rdns = []
+ if not use_printable:
+ encoding_name = 'utf8_string'
+ encoding_class = UTF8String
+ else:
+ encoding_name = 'printable_string'
+ encoding_class = PrintableString
+
+ # Sort the attributes according to NameType.preferred_order
+ name_dict = OrderedDict(
+ sorted(
+ name_dict.items(),
+ key=lambda item: NameType.preferred_ordinal(item[0])
+ )
+ )
+
+ for attribute_name, attribute_value in name_dict.items():
+ attribute_name = NameType.map(attribute_name)
+ if attribute_name == 'email_address':
+ value = EmailAddress(attribute_value)
+ elif attribute_name == 'domain_component':
+ value = DNSName(attribute_value)
+ elif attribute_name in set(['dn_qualifier', 'country_name', 'serial_number']):
+ value = DirectoryString(
+ name='printable_string',
+ value=PrintableString(attribute_value)
+ )
+ else:
+ value = DirectoryString(
+ name=encoding_name,
+ value=encoding_class(attribute_value)
+ )
+
+ rdns.append(RelativeDistinguishedName([
+ NameTypeAndValue({
+ 'type': attribute_name,
+ 'value': value
+ })
+ ]))
+
+ return cls(name='', value=RDNSequence(rdns))
+
+ @property
+ def hashable(self):
+ """
+ :return:
+ A unicode string that can be used as a dict key or in a set
+ """
+
+ return self.chosen.hashable
+
+ def __len__(self):
+ return len(self.chosen)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
+
+ :param other:
+ Another Name object
+
+ :return:
+ A boolean
+ """
+
+ if not isinstance(other, Name):
+ return False
+ return self.chosen == other.chosen
+
+ @property
+ def native(self):
+ if self._native is None:
+ self._native = OrderedDict()
+ for rdn in self.chosen.native:
+ for type_val in rdn:
+ field_name = type_val['type']
+ if field_name in self._native:
+ existing = self._native[field_name]
+ if not isinstance(existing, list):
+ existing = self._native[field_name] = [existing]
+ existing.append(type_val['value'])
+ else:
+ self._native[field_name] = type_val['value']
+ return self._native
+
+ @property
+ def human_friendly(self):
+ """
+ :return:
+ A human-friendly unicode string containing the parts of the name
+ """
+
+ if self._human_friendly is None:
+ data = OrderedDict()
+ last_field = None
+ for rdn in self.chosen:
+ for type_val in rdn:
+ field_name = type_val['type'].human_friendly
+ last_field = field_name
+ if field_name in data:
+ data[field_name] = [data[field_name]]
+ data[field_name].append(type_val['value'])
+ else:
+ data[field_name] = type_val['value']
+ to_join = []
+ keys = data.keys()
+ if last_field == 'Country':
+ keys = reversed(list(keys))
+ for key in keys:
+ value = data[key]
+ native_value = self._recursive_humanize(value)
+ to_join.append('%s: %s' % (key, native_value))
+
+ has_comma = False
+ for element in to_join:
+ if element.find(',') != -1:
+ has_comma = True
+ break
+
+ separator = ', ' if not has_comma else '; '
+ self._human_friendly = separator.join(to_join[::-1])
+
+ return self._human_friendly
+
+ def _recursive_humanize(self, value):
+ """
+ Recursively serializes data compiled from the RDNSequence
+
+ :param value:
+ An Asn1Value object, or a list of Asn1Value objects
+
+ :return:
+ A unicode string
+ """
+
+ if isinstance(value, list):
+ return ', '.join(
+ reversed([self._recursive_humanize(sub_value) for sub_value in value])
+ )
+ return value.native
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA1 hash of the DER-encoded bytes of this name
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this name
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
+
+
+class AnotherName(Sequence):
+ _fields = [
+ ('type_id', ObjectIdentifier),
+ ('value', Any, {'explicit': 0}),
+ ]
+
+
+class CountryName(Choice):
+ class_ = 1
+ tag = 1
+
+ _alternatives = [
+ ('x121_dcc_code', NumericString),
+ ('iso_3166_alpha2_code', PrintableString),
+ ]
+
+
+class AdministrationDomainName(Choice):
+ class_ = 1
+ tag = 2
+
+ _alternatives = [
+ ('numeric', NumericString),
+ ('printable', PrintableString),
+ ]
+
+
+class PrivateDomainName(Choice):
+ _alternatives = [
+ ('numeric', NumericString),
+ ('printable', PrintableString),
+ ]
+
+
+class PersonalName(Set):
+ _fields = [
+ ('surname', PrintableString, {'implicit': 0}),
+ ('given_name', PrintableString, {'implicit': 1, 'optional': True}),
+ ('initials', PrintableString, {'implicit': 2, 'optional': True}),
+ ('generation_qualifier', PrintableString, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class TeletexPersonalName(Set):
+ _fields = [
+ ('surname', TeletexString, {'implicit': 0}),
+ ('given_name', TeletexString, {'implicit': 1, 'optional': True}),
+ ('initials', TeletexString, {'implicit': 2, 'optional': True}),
+ ('generation_qualifier', TeletexString, {'implicit': 3, 'optional': True}),
+ ]
+
+
+class OrganizationalUnitNames(SequenceOf):
+ _child_spec = PrintableString
+
+
+class TeletexOrganizationalUnitNames(SequenceOf):
+ _child_spec = TeletexString
+
+
+class BuiltInStandardAttributes(Sequence):
+ _fields = [
+ ('country_name', CountryName, {'optional': True}),
+ ('administration_domain_name', AdministrationDomainName, {'optional': True}),
+ ('network_address', NumericString, {'implicit': 0, 'optional': True}),
+ ('terminal_identifier', PrintableString, {'implicit': 1, 'optional': True}),
+ ('private_domain_name', PrivateDomainName, {'explicit': 2, 'optional': True}),
+ ('organization_name', PrintableString, {'implicit': 3, 'optional': True}),
+ ('numeric_user_identifier', NumericString, {'implicit': 4, 'optional': True}),
+ ('personal_name', PersonalName, {'implicit': 5, 'optional': True}),
+ ('organizational_unit_names', OrganizationalUnitNames, {'implicit': 6, 'optional': True}),
+ ]
+
+
+class BuiltInDomainDefinedAttribute(Sequence):
+ _fields = [
+ ('type', PrintableString),
+ ('value', PrintableString),
+ ]
+
+
+class BuiltInDomainDefinedAttributes(SequenceOf):
+ _child_spec = BuiltInDomainDefinedAttribute
+
+
+class TeletexDomainDefinedAttribute(Sequence):
+ _fields = [
+ ('type', TeletexString),
+ ('value', TeletexString),
+ ]
+
+
+class TeletexDomainDefinedAttributes(SequenceOf):
+ _child_spec = TeletexDomainDefinedAttribute
+
+
+class PhysicalDeliveryCountryName(Choice):
+ _alternatives = [
+ ('x121_dcc_code', NumericString),
+ ('iso_3166_alpha2_code', PrintableString),
+ ]
+
+
+class PostalCode(Choice):
+ _alternatives = [
+ ('numeric_code', NumericString),
+ ('printable_code', PrintableString),
+ ]
+
+
+class PDSParameter(Set):
+ _fields = [
+ ('printable_string', PrintableString, {'optional': True}),
+ ('teletex_string', TeletexString, {'optional': True}),
+ ]
+
+
+class PrintableAddress(SequenceOf):
+ _child_spec = PrintableString
+
+
+class UnformattedPostalAddress(Set):
+ _fields = [
+ ('printable_address', PrintableAddress, {'optional': True}),
+ ('teletex_string', TeletexString, {'optional': True}),
+ ]
+
+
+class E1634Address(Sequence):
+ _fields = [
+ ('number', NumericString, {'implicit': 0}),
+ ('sub_address', NumericString, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class NAddresses(SetOf):
+ _child_spec = OctetString
+
+
+class PresentationAddress(Sequence):
+ _fields = [
+ ('p_selector', OctetString, {'explicit': 0, 'optional': True}),
+ ('s_selector', OctetString, {'explicit': 1, 'optional': True}),
+ ('t_selector', OctetString, {'explicit': 2, 'optional': True}),
+ ('n_addresses', NAddresses, {'explicit': 3}),
+ ]
+
+
+class ExtendedNetworkAddress(Choice):
+ _alternatives = [
+ ('e163_4_address', E1634Address),
+ ('psap_address', PresentationAddress, {'implicit': 0})
+ ]
+
+
+class TerminalType(Integer):
+ _map = {
+ 3: 'telex',
+ 4: 'teletex',
+ 5: 'g3_facsimile',
+ 6: 'g4_facsimile',
+ 7: 'ia5_terminal',
+ 8: 'videotex',
+ }
+
+
+class ExtensionAttributeType(Integer):
+ _map = {
+ 1: 'common_name',
+ 2: 'teletex_common_name',
+ 3: 'teletex_organization_name',
+ 4: 'teletex_personal_name',
+ 5: 'teletex_organization_unit_names',
+ 6: 'teletex_domain_defined_attributes',
+ 7: 'pds_name',
+ 8: 'physical_delivery_country_name',
+ 9: 'postal_code',
+ 10: 'physical_delivery_office_name',
+ 11: 'physical_delivery_office_number',
+ 12: 'extension_of_address_components',
+ 13: 'physical_delivery_personal_name',
+ 14: 'physical_delivery_organization_name',
+ 15: 'extension_physical_delivery_address_components',
+ 16: 'unformatted_postal_address',
+ 17: 'street_address',
+ 18: 'post_office_box_address',
+ 19: 'poste_restante_address',
+ 20: 'unique_postal_name',
+ 21: 'local_postal_attributes',
+ 22: 'extended_network_address',
+ 23: 'terminal_type',
+ }
+
+
+class ExtensionAttribute(Sequence):
+ _fields = [
+ ('extension_attribute_type', ExtensionAttributeType, {'implicit': 0}),
+ ('extension_attribute_value', Any, {'explicit': 1}),
+ ]
+
+ _oid_pair = ('extension_attribute_type', 'extension_attribute_value')
+ _oid_specs = {
+ 'common_name': PrintableString,
+ 'teletex_common_name': TeletexString,
+ 'teletex_organization_name': TeletexString,
+ 'teletex_personal_name': TeletexPersonalName,
+ 'teletex_organization_unit_names': TeletexOrganizationalUnitNames,
+ 'teletex_domain_defined_attributes': TeletexDomainDefinedAttributes,
+ 'pds_name': PrintableString,
+ 'physical_delivery_country_name': PhysicalDeliveryCountryName,
+ 'postal_code': PostalCode,
+ 'physical_delivery_office_name': PDSParameter,
+ 'physical_delivery_office_number': PDSParameter,
+ 'extension_of_address_components': PDSParameter,
+ 'physical_delivery_personal_name': PDSParameter,
+ 'physical_delivery_organization_name': PDSParameter,
+ 'extension_physical_delivery_address_components': PDSParameter,
+ 'unformatted_postal_address': UnformattedPostalAddress,
+ 'street_address': PDSParameter,
+ 'post_office_box_address': PDSParameter,
+ 'poste_restante_address': PDSParameter,
+ 'unique_postal_name': PDSParameter,
+ 'local_postal_attributes': PDSParameter,
+ 'extended_network_address': ExtendedNetworkAddress,
+ 'terminal_type': TerminalType,
+ }
+
+
+class ExtensionAttributes(SequenceOf):
+ _child_spec = ExtensionAttribute
+
+
+class ORAddress(Sequence):
+ _fields = [
+ ('built_in_standard_attributes', BuiltInStandardAttributes),
+ ('built_in_domain_defined_attributes', BuiltInDomainDefinedAttributes, {'optional': True}),
+ ('extension_attributes', ExtensionAttributes, {'optional': True}),
+ ]
+
+
+class EDIPartyName(Sequence):
+ _fields = [
+ ('name_assigner', DirectoryString, {'implicit': 0, 'optional': True}),
+ ('party_name', DirectoryString, {'implicit': 1}),
+ ]
+
+
+class GeneralName(Choice):
+ _alternatives = [
+ ('other_name', AnotherName, {'implicit': 0}),
+ ('rfc822_name', EmailAddress, {'implicit': 1}),
+ ('dns_name', DNSName, {'implicit': 2}),
+ ('x400_address', ORAddress, {'implicit': 3}),
+ ('directory_name', Name, {'explicit': 4}),
+ ('edi_party_name', EDIPartyName, {'implicit': 5}),
+ ('uniform_resource_identifier', URI, {'implicit': 6}),
+ ('ip_address', IPAddress, {'implicit': 7}),
+ ('registered_id', ObjectIdentifier, {'implicit': 8}),
+ ]
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __eq__(self, other):
+ """
+ Does not support other_name, x400_address or edi_party_name
+
+ :param other:
+ The other GeneralName to compare to
+
+ :return:
+ A boolean
+ """
+
+ if self.name in ('other_name', 'x400_address', 'edi_party_name'):
+ raise ValueError(unwrap(
+ '''
+ Comparison is not supported for GeneralName objects of
+ choice %s
+ ''',
+ self.name
+ ))
+
+ if other.name in ('other_name', 'x400_address', 'edi_party_name'):
+ raise ValueError(unwrap(
+ '''
+ Comparison is not supported for GeneralName objects of choice
+ %s''',
+ other.name
+ ))
+
+ if self.name != other.name:
+ return False
+
+ return self.chosen == other.chosen
+
+
+class GeneralNames(SequenceOf):
+ _child_spec = GeneralName
+
+
+class Time(Choice):
+ _alternatives = [
+ ('utc_time', UTCTime),
+ ('general_time', GeneralizedTime),
+ ]
+
+
+class Validity(Sequence):
+ _fields = [
+ ('not_before', Time),
+ ('not_after', Time),
+ ]
+
+
+class BasicConstraints(Sequence):
+ _fields = [
+ ('ca', Boolean, {'default': False}),
+ ('path_len_constraint', Integer, {'optional': True}),
+ ]
+
+
+class AuthorityKeyIdentifier(Sequence):
+ _fields = [
+ ('key_identifier', OctetString, {'implicit': 0, 'optional': True}),
+ ('authority_cert_issuer', GeneralNames, {'implicit': 1, 'optional': True}),
+ ('authority_cert_serial_number', Integer, {'implicit': 2, 'optional': True}),
+ ]
+
+
+class DistributionPointName(Choice):
+ _alternatives = [
+ ('full_name', GeneralNames, {'implicit': 0}),
+ ('name_relative_to_crl_issuer', RelativeDistinguishedName, {'implicit': 1}),
+ ]
+
+
+class ReasonFlags(BitString):
+ _map = {
+ 0: 'unused',
+ 1: 'key_compromise',
+ 2: 'ca_compromise',
+ 3: 'affiliation_changed',
+ 4: 'superseded',
+ 5: 'cessation_of_operation',
+ 6: 'certificate_hold',
+ 7: 'privilege_withdrawn',
+ 8: 'aa_compromise',
+ }
+
+
+class GeneralSubtree(Sequence):
+ _fields = [
+ ('base', GeneralName),
+ ('minimum', Integer, {'implicit': 0, 'default': 0}),
+ ('maximum', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class GeneralSubtrees(SequenceOf):
+ _child_spec = GeneralSubtree
+
+
+class NameConstraints(Sequence):
+ _fields = [
+ ('permitted_subtrees', GeneralSubtrees, {'implicit': 0, 'optional': True}),
+ ('excluded_subtrees', GeneralSubtrees, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class DistributionPoint(Sequence):
+ _fields = [
+ ('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
+ ('reasons', ReasonFlags, {'implicit': 1, 'optional': True}),
+ ('crl_issuer', GeneralNames, {'implicit': 2, 'optional': True}),
+ ]
+
+ _url = False
+
+ @property
+ def url(self):
+ """
+ :return:
+ None or a unicode string of the distribution point's URL
+ """
+
+ if self._url is False:
+ self._url = None
+ name = self['distribution_point']
+ if name.name != 'full_name':
+ raise ValueError(unwrap(
+ '''
+ CRL distribution points that are relative to the issuer are
+ not supported
+ '''
+ ))
+
+ for general_name in name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ url = general_name.native
+ if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')):
+ self._url = url
+ break
+
+ return self._url
+
+
+class CRLDistributionPoints(SequenceOf):
+ _child_spec = DistributionPoint
+
+
+class DisplayText(Choice):
+ _alternatives = [
+ ('ia5_string', IA5String),
+ ('visible_string', VisibleString),
+ ('bmp_string', BMPString),
+ ('utf8_string', UTF8String),
+ ]
+
+
+class NoticeNumbers(SequenceOf):
+ _child_spec = Integer
+
+
+class NoticeReference(Sequence):
+ _fields = [
+ ('organization', DisplayText),
+ ('notice_numbers', NoticeNumbers),
+ ]
+
+
+class UserNotice(Sequence):
+ _fields = [
+ ('notice_ref', NoticeReference, {'optional': True}),
+ ('explicit_text', DisplayText, {'optional': True}),
+ ]
+
+
+class PolicyQualifierId(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.2.1': 'certification_practice_statement',
+ '1.3.6.1.5.5.7.2.2': 'user_notice',
+ }
+
+
+class PolicyQualifierInfo(Sequence):
+ _fields = [
+ ('policy_qualifier_id', PolicyQualifierId),
+ ('qualifier', Any),
+ ]
+
+ _oid_pair = ('policy_qualifier_id', 'qualifier')
+ _oid_specs = {
+ 'certification_practice_statement': IA5String,
+ 'user_notice': UserNotice,
+ }
+
+
+class PolicyQualifierInfos(SequenceOf):
+ _child_spec = PolicyQualifierInfo
+
+
+class PolicyIdentifier(ObjectIdentifier):
+ _map = {
+ '2.5.29.32.0': 'any_policy',
+ }
+
+
+class PolicyInformation(Sequence):
+ _fields = [
+ ('policy_identifier', PolicyIdentifier),
+ ('policy_qualifiers', PolicyQualifierInfos, {'optional': True})
+ ]
+
+
+class CertificatePolicies(SequenceOf):
+ _child_spec = PolicyInformation
+
+
+class PolicyMapping(Sequence):
+ _fields = [
+ ('issuer_domain_policy', PolicyIdentifier),
+ ('subject_domain_policy', PolicyIdentifier),
+ ]
+
+
+class PolicyMappings(SequenceOf):
+ _child_spec = PolicyMapping
+
+
+class PolicyConstraints(Sequence):
+ _fields = [
+ ('require_explicit_policy', Integer, {'implicit': 0, 'optional': True}),
+ ('inhibit_policy_mapping', Integer, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class KeyPurposeId(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc5280#page-45
+ '2.5.29.37.0': 'any_extended_key_usage',
+ '1.3.6.1.5.5.7.3.1': 'server_auth',
+ '1.3.6.1.5.5.7.3.2': 'client_auth',
+ '1.3.6.1.5.5.7.3.3': 'code_signing',
+ '1.3.6.1.5.5.7.3.4': 'email_protection',
+ '1.3.6.1.5.5.7.3.5': 'ipsec_end_system',
+ '1.3.6.1.5.5.7.3.6': 'ipsec_tunnel',
+ '1.3.6.1.5.5.7.3.7': 'ipsec_user',
+ '1.3.6.1.5.5.7.3.8': 'time_stamping',
+ '1.3.6.1.5.5.7.3.9': 'ocsp_signing',
+ # http://tools.ietf.org/html/rfc3029.html#page-9
+ '1.3.6.1.5.5.7.3.10': 'dvcs',
+ # http://tools.ietf.org/html/rfc6268.html#page-16
+ '1.3.6.1.5.5.7.3.13': 'eap_over_ppp',
+ '1.3.6.1.5.5.7.3.14': 'eap_over_lan',
+ # https://tools.ietf.org/html/rfc5055#page-76
+ '1.3.6.1.5.5.7.3.15': 'scvp_server',
+ '1.3.6.1.5.5.7.3.16': 'scvp_client',
+ # https://tools.ietf.org/html/rfc4945#page-31
+ '1.3.6.1.5.5.7.3.17': 'ipsec_ike',
+ # https://tools.ietf.org/html/rfc5415#page-38
+ '1.3.6.1.5.5.7.3.18': 'capwap_ac',
+ '1.3.6.1.5.5.7.3.19': 'capwap_wtp',
+ # https://tools.ietf.org/html/rfc5924#page-8
+ '1.3.6.1.5.5.7.3.20': 'sip_domain',
+ # https://tools.ietf.org/html/rfc6187#page-7
+ '1.3.6.1.5.5.7.3.21': 'secure_shell_client',
+ '1.3.6.1.5.5.7.3.22': 'secure_shell_server',
+ # https://tools.ietf.org/html/rfc6494#page-7
+ '1.3.6.1.5.5.7.3.23': 'send_router',
+ '1.3.6.1.5.5.7.3.24': 'send_proxied_router',
+ '1.3.6.1.5.5.7.3.25': 'send_owner',
+ '1.3.6.1.5.5.7.3.26': 'send_proxied_owner',
+ # https://tools.ietf.org/html/rfc6402#page-10
+ '1.3.6.1.5.5.7.3.27': 'cmc_ca',
+ '1.3.6.1.5.5.7.3.28': 'cmc_ra',
+ '1.3.6.1.5.5.7.3.29': 'cmc_archive',
+ # https://tools.ietf.org/html/draft-ietf-sidr-bgpsec-pki-profiles-15#page-6
+ '1.3.6.1.5.5.7.3.30': 'bgpspec_router',
+ # https://www.ietf.org/proceedings/44/I-D/draft-ietf-ipsec-pki-req-01.txt
+ '1.3.6.1.5.5.8.2.2': 'ike_intermediate',
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378132(v=vs.85).aspx
+ # and https://support.microsoft.com/en-us/kb/287547
+ '1.3.6.1.4.1.311.10.3.1': 'microsoft_trust_list_signing',
+ '1.3.6.1.4.1.311.10.3.2': 'microsoft_time_stamp_signing',
+ '1.3.6.1.4.1.311.10.3.3': 'microsoft_server_gated',
+ '1.3.6.1.4.1.311.10.3.3.1': 'microsoft_serialized',
+ '1.3.6.1.4.1.311.10.3.4': 'microsoft_efs',
+ '1.3.6.1.4.1.311.10.3.4.1': 'microsoft_efs_recovery',
+ '1.3.6.1.4.1.311.10.3.5': 'microsoft_whql',
+ '1.3.6.1.4.1.311.10.3.6': 'microsoft_nt5',
+ '1.3.6.1.4.1.311.10.3.7': 'microsoft_oem_whql',
+ '1.3.6.1.4.1.311.10.3.8': 'microsoft_embedded_nt',
+ '1.3.6.1.4.1.311.10.3.9': 'microsoft_root_list_signer',
+ '1.3.6.1.4.1.311.10.3.10': 'microsoft_qualified_subordination',
+ '1.3.6.1.4.1.311.10.3.11': 'microsoft_key_recovery',
+ '1.3.6.1.4.1.311.10.3.12': 'microsoft_document_signing',
+ '1.3.6.1.4.1.311.10.3.13': 'microsoft_lifetime_signing',
+ '1.3.6.1.4.1.311.10.3.14': 'microsoft_mobile_device_software',
+ # https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography
+ '1.3.6.1.4.1.311.20.2.2': 'microsoft_smart_card_logon',
+ # https://opensource.apple.com/source
+ # - /Security/Security-57031.40.6/Security/libsecurity_keychain/lib/SecPolicy.cpp
+ # - /libsecurity_cssm/libsecurity_cssm-36064/lib/oidsalg.c
+ '1.2.840.113635.100.1.2': 'apple_x509_basic',
+ '1.2.840.113635.100.1.3': 'apple_ssl',
+ '1.2.840.113635.100.1.4': 'apple_local_cert_gen',
+ '1.2.840.113635.100.1.5': 'apple_csr_gen',
+ '1.2.840.113635.100.1.6': 'apple_revocation_crl',
+ '1.2.840.113635.100.1.7': 'apple_revocation_ocsp',
+ '1.2.840.113635.100.1.8': 'apple_smime',
+ '1.2.840.113635.100.1.9': 'apple_eap',
+ '1.2.840.113635.100.1.10': 'apple_software_update_signing',
+ '1.2.840.113635.100.1.11': 'apple_ipsec',
+ '1.2.840.113635.100.1.12': 'apple_ichat',
+ '1.2.840.113635.100.1.13': 'apple_resource_signing',
+ '1.2.840.113635.100.1.14': 'apple_pkinit_client',
+ '1.2.840.113635.100.1.15': 'apple_pkinit_server',
+ '1.2.840.113635.100.1.16': 'apple_code_signing',
+ '1.2.840.113635.100.1.17': 'apple_package_signing',
+ '1.2.840.113635.100.1.18': 'apple_id_validation',
+ '1.2.840.113635.100.1.20': 'apple_time_stamping',
+ '1.2.840.113635.100.1.21': 'apple_revocation',
+ '1.2.840.113635.100.1.22': 'apple_passbook_signing',
+ '1.2.840.113635.100.1.23': 'apple_mobile_store',
+ '1.2.840.113635.100.1.24': 'apple_escrow_service',
+ '1.2.840.113635.100.1.25': 'apple_profile_signer',
+ '1.2.840.113635.100.1.26': 'apple_qa_profile_signer',
+ '1.2.840.113635.100.1.27': 'apple_test_mobile_store',
+ '1.2.840.113635.100.1.28': 'apple_otapki_signer',
+ '1.2.840.113635.100.1.29': 'apple_test_otapki_signer',
+ '1.2.840.113625.100.1.30': 'apple_id_validation_record_signing_policy',
+ '1.2.840.113625.100.1.31': 'apple_smp_encryption',
+ '1.2.840.113625.100.1.32': 'apple_test_smp_encryption',
+ '1.2.840.113635.100.1.33': 'apple_server_authentication',
+ '1.2.840.113635.100.1.34': 'apple_pcs_escrow_service',
+ # http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.201-2.pdf
+ '2.16.840.1.101.3.6.8': 'piv_card_authentication',
+ '2.16.840.1.101.3.6.7': 'piv_content_signing',
+ # https://tools.ietf.org/html/rfc4556.html
+ '1.3.6.1.5.2.3.4': 'pkinit_kpclientauth',
+ '1.3.6.1.5.2.3.5': 'pkinit_kpkdc',
+ # https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html
+ '1.2.840.113583.1.1.5': 'adobe_authentic_documents_trust',
+ # https://www.idmanagement.gov/wp-content/uploads/sites/1171/uploads/fpki-pivi-cert-profiles.pdf
+ '2.16.840.1.101.3.8.7': 'fpki_pivi_content_signing'
+ }
+
+
+class ExtKeyUsageSyntax(SequenceOf):
+ _child_spec = KeyPurposeId
+
+
+class AccessMethod(ObjectIdentifier):
+ _map = {
+ '1.3.6.1.5.5.7.48.1': 'ocsp',
+ '1.3.6.1.5.5.7.48.2': 'ca_issuers',
+ '1.3.6.1.5.5.7.48.3': 'time_stamping',
+ '1.3.6.1.5.5.7.48.5': 'ca_repository',
+ }
+
+
+class AccessDescription(Sequence):
+ _fields = [
+ ('access_method', AccessMethod),
+ ('access_location', GeneralName),
+ ]
+
+
+class AuthorityInfoAccessSyntax(SequenceOf):
+ _child_spec = AccessDescription
+
+
+class SubjectInfoAccessSyntax(SequenceOf):
+ _child_spec = AccessDescription
+
+
+# https://tools.ietf.org/html/rfc7633
+class Features(SequenceOf):
+ _child_spec = Integer
+
+
+class EntrustVersionInfo(Sequence):
+ _fields = [
+ ('entrust_vers', GeneralString),
+ ('entrust_info_flags', BitString)
+ ]
+
+
+class NetscapeCertificateType(BitString):
+ _map = {
+ 0: 'ssl_client',
+ 1: 'ssl_server',
+ 2: 'email',
+ 3: 'object_signing',
+ 4: 'reserved',
+ 5: 'ssl_ca',
+ 6: 'email_ca',
+ 7: 'object_signing_ca',
+ }
+
+
+class Version(Integer):
+ _map = {
+ 0: 'v1',
+ 1: 'v2',
+ 2: 'v3',
+ }
+
+
+class TPMSpecification(Sequence):
+ _fields = [
+ ('family', UTF8String),
+ ('level', Integer),
+ ('revision', Integer),
+ ]
+
+
+class SetOfTPMSpecification(SetOf):
+ _child_spec = TPMSpecification
+
+
+class TCGSpecificationVersion(Sequence):
+ _fields = [
+ ('major_version', Integer),
+ ('minor_version', Integer),
+ ('revision', Integer),
+ ]
+
+
+class TCGPlatformSpecification(Sequence):
+ _fields = [
+ ('version', TCGSpecificationVersion),
+ ('platform_class', OctetString),
+ ]
+
+
+class SetOfTCGPlatformSpecification(SetOf):
+ _child_spec = TCGPlatformSpecification
+
+
+class EKGenerationType(Enumerated):
+ _map = {
+ 0: 'internal',
+ 1: 'injected',
+ 2: 'internal_revocable',
+ 3: 'injected_revocable',
+ }
+
+
+class EKGenerationLocation(Enumerated):
+ _map = {
+ 0: 'tpm_manufacturer',
+ 1: 'platform_manufacturer',
+ 2: 'ek_cert_signer',
+ }
+
+
+class EKCertificateGenerationLocation(Enumerated):
+ _map = {
+ 0: 'tpm_manufacturer',
+ 1: 'platform_manufacturer',
+ 2: 'ek_cert_signer',
+ }
+
+
+class EvaluationAssuranceLevel(Enumerated):
+ _map = {
+ 1: 'level1',
+ 2: 'level2',
+ 3: 'level3',
+ 4: 'level4',
+ 5: 'level5',
+ 6: 'level6',
+ 7: 'level7',
+ }
+
+
+class EvaluationStatus(Enumerated):
+ _map = {
+ 0: 'designed_to_meet',
+ 1: 'evaluation_in_progress',
+ 2: 'evaluation_completed',
+ }
+
+
+class StrengthOfFunction(Enumerated):
+ _map = {
+ 0: 'basic',
+ 1: 'medium',
+ 2: 'high',
+ }
+
+
+class URIReference(Sequence):
+ _fields = [
+ ('uniform_resource_identifier', IA5String),
+ ('hash_algorithm', DigestAlgorithm, {'optional': True}),
+ ('hash_value', BitString, {'optional': True}),
+ ]
+
+
+class CommonCriteriaMeasures(Sequence):
+ _fields = [
+ ('version', IA5String),
+ ('assurance_level', EvaluationAssuranceLevel),
+ ('evaluation_status', EvaluationStatus),
+ ('plus', Boolean, {'default': False}),
+ ('strengh_of_function', StrengthOfFunction, {'implicit': 0, 'optional': True}),
+ ('profile_oid', ObjectIdentifier, {'implicit': 1, 'optional': True}),
+ ('profile_url', URIReference, {'implicit': 2, 'optional': True}),
+ ('target_oid', ObjectIdentifier, {'implicit': 3, 'optional': True}),
+ ('target_uri', URIReference, {'implicit': 4, 'optional': True}),
+ ]
+
+
+class SecurityLevel(Enumerated):
+ _map = {
+ 1: 'level1',
+ 2: 'level2',
+ 3: 'level3',
+ 4: 'level4',
+ }
+
+
+class FIPSLevel(Sequence):
+ _fields = [
+ ('version', IA5String),
+ ('level', SecurityLevel),
+ ('plus', Boolean, {'default': False}),
+ ]
+
+
+class TPMSecurityAssertions(Sequence):
+ _fields = [
+ ('version', Version, {'default': 'v1'}),
+ ('field_upgradable', Boolean, {'default': False}),
+ ('ek_generation_type', EKGenerationType, {'implicit': 0, 'optional': True}),
+ ('ek_generation_location', EKGenerationLocation, {'implicit': 1, 'optional': True}),
+ ('ek_certificate_generation_location', EKCertificateGenerationLocation, {'implicit': 2, 'optional': True}),
+ ('cc_info', CommonCriteriaMeasures, {'implicit': 3, 'optional': True}),
+ ('fips_level', FIPSLevel, {'implicit': 4, 'optional': True}),
+ ('iso_9000_certified', Boolean, {'implicit': 5, 'default': False}),
+ ('iso_9000_uri', IA5String, {'optional': True}),
+ ]
+
+
+class SetOfTPMSecurityAssertions(SetOf):
+ _child_spec = TPMSecurityAssertions
+
+
+class SubjectDirectoryAttributeId(ObjectIdentifier):
+ _map = {
+ # https://tools.ietf.org/html/rfc2256#page-11
+ '2.5.4.52': 'supported_algorithms',
+ # https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
+ '2.23.133.2.16': 'tpm_specification',
+ '2.23.133.2.17': 'tcg_platform_specification',
+ '2.23.133.2.18': 'tpm_security_assertions',
+ # https://tools.ietf.org/html/rfc3739#page-18
+ '1.3.6.1.5.5.7.9.1': 'pda_date_of_birth',
+ '1.3.6.1.5.5.7.9.2': 'pda_place_of_birth',
+ '1.3.6.1.5.5.7.9.3': 'pda_gender',
+ '1.3.6.1.5.5.7.9.4': 'pda_country_of_citizenship',
+ '1.3.6.1.5.5.7.9.5': 'pda_country_of_residence',
+ # https://holtstrom.com/michael/tools/asn1decoder.php
+ '1.2.840.113533.7.68.29': 'entrust_user_role',
+ }
+
+
+class SetOfGeneralizedTime(SetOf):
+ _child_spec = GeneralizedTime
+
+
+class SetOfDirectoryString(SetOf):
+ _child_spec = DirectoryString
+
+
+class SetOfPrintableString(SetOf):
+ _child_spec = PrintableString
+
+
+class SupportedAlgorithm(Sequence):
+ _fields = [
+ ('algorithm_identifier', AnyAlgorithmIdentifier),
+ ('intended_usage', KeyUsage, {'explicit': 0, 'optional': True}),
+ ('intended_certificate_policies', CertificatePolicies, {'explicit': 1, 'optional': True}),
+ ]
+
+
+class SetOfSupportedAlgorithm(SetOf):
+ _child_spec = SupportedAlgorithm
+
+
+class SubjectDirectoryAttribute(Sequence):
+ _fields = [
+ ('type', SubjectDirectoryAttributeId),
+ ('values', Any),
+ ]
+
+ _oid_pair = ('type', 'values')
+ _oid_specs = {
+ 'supported_algorithms': SetOfSupportedAlgorithm,
+ 'tpm_specification': SetOfTPMSpecification,
+ 'tcg_platform_specification': SetOfTCGPlatformSpecification,
+ 'tpm_security_assertions': SetOfTPMSecurityAssertions,
+ 'pda_date_of_birth': SetOfGeneralizedTime,
+ 'pda_place_of_birth': SetOfDirectoryString,
+ 'pda_gender': SetOfPrintableString,
+ 'pda_country_of_citizenship': SetOfPrintableString,
+ 'pda_country_of_residence': SetOfPrintableString,
+ }
+
+ def _values_spec(self):
+ type_ = self['type'].native
+ if type_ in self._oid_specs:
+ return self._oid_specs[type_]
+ return SetOf
+
+ _spec_callbacks = {
+ 'values': _values_spec
+ }
+
+
+class SubjectDirectoryAttributes(SequenceOf):
+ _child_spec = SubjectDirectoryAttribute
+
+
+class ExtensionId(ObjectIdentifier):
+ _map = {
+ '2.5.29.9': 'subject_directory_attributes',
+ '2.5.29.14': 'key_identifier',
+ '2.5.29.15': 'key_usage',
+ '2.5.29.16': 'private_key_usage_period',
+ '2.5.29.17': 'subject_alt_name',
+ '2.5.29.18': 'issuer_alt_name',
+ '2.5.29.19': 'basic_constraints',
+ '2.5.29.30': 'name_constraints',
+ '2.5.29.31': 'crl_distribution_points',
+ '2.5.29.32': 'certificate_policies',
+ '2.5.29.33': 'policy_mappings',
+ '2.5.29.35': 'authority_key_identifier',
+ '2.5.29.36': 'policy_constraints',
+ '2.5.29.37': 'extended_key_usage',
+ '2.5.29.46': 'freshest_crl',
+ '2.5.29.54': 'inhibit_any_policy',
+ '1.3.6.1.5.5.7.1.1': 'authority_information_access',
+ '1.3.6.1.5.5.7.1.11': 'subject_information_access',
+ # https://tools.ietf.org/html/rfc7633
+ '1.3.6.1.5.5.7.1.24': 'tls_feature',
+ '1.3.6.1.5.5.7.48.1.5': 'ocsp_no_check',
+ '1.2.840.113533.7.65.0': 'entrust_version_extension',
+ '2.16.840.1.113730.1.1': 'netscape_certificate_type',
+ # https://tools.ietf.org/html/rfc6962.html#page-14
+ '1.3.6.1.4.1.11129.2.4.2': 'signed_certificate_timestamp_list',
+ # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/3aec3e50-511a-42f9-a5d5-240af503e470
+ '1.3.6.1.4.1.311.20.2': 'microsoft_enroll_certtype',
+ }
+
+
+class Extension(Sequence):
+ _fields = [
+ ('extn_id', ExtensionId),
+ ('critical', Boolean, {'default': False}),
+ ('extn_value', ParsableOctetString),
+ ]
+
+ _oid_pair = ('extn_id', 'extn_value')
+ _oid_specs = {
+ 'subject_directory_attributes': SubjectDirectoryAttributes,
+ 'key_identifier': OctetString,
+ 'key_usage': KeyUsage,
+ 'private_key_usage_period': PrivateKeyUsagePeriod,
+ 'subject_alt_name': GeneralNames,
+ 'issuer_alt_name': GeneralNames,
+ 'basic_constraints': BasicConstraints,
+ 'name_constraints': NameConstraints,
+ 'crl_distribution_points': CRLDistributionPoints,
+ 'certificate_policies': CertificatePolicies,
+ 'policy_mappings': PolicyMappings,
+ 'authority_key_identifier': AuthorityKeyIdentifier,
+ 'policy_constraints': PolicyConstraints,
+ 'extended_key_usage': ExtKeyUsageSyntax,
+ 'freshest_crl': CRLDistributionPoints,
+ 'inhibit_any_policy': Integer,
+ 'authority_information_access': AuthorityInfoAccessSyntax,
+ 'subject_information_access': SubjectInfoAccessSyntax,
+ 'tls_feature': Features,
+ 'ocsp_no_check': Null,
+ 'entrust_version_extension': EntrustVersionInfo,
+ 'netscape_certificate_type': NetscapeCertificateType,
+ 'signed_certificate_timestamp_list': OctetString,
+ # Not UTF8String as Microsofts docs claim, see:
+ # https://www.alvestrand.no/objectid/1.3.6.1.4.1.311.20.2.html
+ 'microsoft_enroll_certtype': BMPString,
+ }
+
+
+class Extensions(SequenceOf):
+ _child_spec = Extension
+
+
+class TbsCertificate(Sequence):
+ _fields = [
+ ('version', Version, {'explicit': 0, 'default': 'v1'}),
+ ('serial_number', Integer),
+ ('signature', SignedDigestAlgorithm),
+ ('issuer', Name),
+ ('validity', Validity),
+ ('subject', Name),
+ ('subject_public_key_info', PublicKeyInfo),
+ ('issuer_unique_id', OctetBitString, {'implicit': 1, 'optional': True}),
+ ('subject_unique_id', OctetBitString, {'implicit': 2, 'optional': True}),
+ ('extensions', Extensions, {'explicit': 3, 'optional': True}),
+ ]
+
+
+class Certificate(Sequence):
+ _fields = [
+ ('tbs_certificate', TbsCertificate),
+ ('signature_algorithm', SignedDigestAlgorithm),
+ ('signature_value', OctetBitString),
+ ]
+
+ _processed_extensions = False
+ _critical_extensions = None
+ _subject_directory_attributes_value = None
+ _key_identifier_value = None
+ _key_usage_value = None
+ _subject_alt_name_value = None
+ _issuer_alt_name_value = None
+ _basic_constraints_value = None
+ _name_constraints_value = None
+ _crl_distribution_points_value = None
+ _certificate_policies_value = None
+ _policy_mappings_value = None
+ _authority_key_identifier_value = None
+ _policy_constraints_value = None
+ _freshest_crl_value = None
+ _inhibit_any_policy_value = None
+ _extended_key_usage_value = None
+ _authority_information_access_value = None
+ _subject_information_access_value = None
+ _private_key_usage_period_value = None
+ _tls_feature_value = None
+ _ocsp_no_check_value = None
+ _issuer_serial = None
+ _authority_issuer_serial = False
+ _crl_distribution_points = None
+ _delta_crl_distribution_points = None
+ _valid_domains = None
+ _valid_ips = None
+ _self_issued = None
+ _self_signed = None
+ _sha1 = None
+ _sha256 = None
+
+ def _set_extensions(self):
+ """
+ Sets common named extensions to private attributes and creates a list
+ of critical extensions
+ """
+
+ self._critical_extensions = set()
+
+ for extension in self['tbs_certificate']['extensions']:
+ name = extension['extn_id'].native
+ attribute_name = '_%s_value' % name
+ if hasattr(self, attribute_name):
+ setattr(self, attribute_name, extension['extn_value'].parsed)
+ if extension['critical'].native:
+ self._critical_extensions.add(name)
+
+ self._processed_extensions = True
+
+ @property
+ def critical_extensions(self):
+ """
+ Returns a set of the names (or OID if not a known extension) of the
+ extensions marked as critical
+
+ :return:
+ A set of unicode strings
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._critical_extensions
+
+ @property
+ def private_key_usage_period_value(self):
+ """
+ This extension is used to constrain the period over which the subject
+ private key may be used
+
+ :return:
+ None or a PrivateKeyUsagePeriod object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._private_key_usage_period_value
+
+ @property
+ def subject_directory_attributes_value(self):
+ """
+ This extension is used to contain additional identification attributes
+ about the subject.
+
+ :return:
+ None or a SubjectDirectoryAttributes object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_directory_attributes_value
+
+ @property
+ def key_identifier_value(self):
+ """
+ This extension is used to help in creating certificate validation paths.
+ It contains an identifier that should generally, but is not guaranteed
+ to, be unique.
+
+ :return:
+ None or an OctetString object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._key_identifier_value
+
+ @property
+ def key_usage_value(self):
+ """
+ This extension is used to define the purpose of the public key
+ contained within the certificate.
+
+ :return:
+ None or a KeyUsage
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._key_usage_value
+
+ @property
+ def subject_alt_name_value(self):
+ """
+ This extension allows for additional names to be associate with the
+ subject of the certificate. While it may contain a whole host of
+ possible names, it is usually used to allow certificates to be used
+ with multiple different domain names.
+
+ :return:
+ None or a GeneralNames object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_alt_name_value
+
+ @property
+ def issuer_alt_name_value(self):
+ """
+ This extension allows associating one or more alternative names with
+ the issuer of the certificate.
+
+ :return:
+ None or an x509.GeneralNames object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._issuer_alt_name_value
+
+ @property
+ def basic_constraints_value(self):
+ """
+ This extension is used to determine if the subject of the certificate
+ is a CA, and if so, what the maximum number of intermediate CA certs
+ after this are, before an end-entity certificate is found.
+
+ :return:
+ None or a BasicConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._basic_constraints_value
+
+ @property
+ def name_constraints_value(self):
+ """
+ This extension is used in CA certificates, and is used to limit the
+ possible names of certificates issued.
+
+ :return:
+ None or a NameConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._name_constraints_value
+
+ @property
+ def crl_distribution_points_value(self):
+ """
+ This extension is used to help in locating the CRL for this certificate.
+
+ :return:
+ None or a CRLDistributionPoints object
+ extension
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._crl_distribution_points_value
+
+ @property
+ def certificate_policies_value(self):
+ """
+ This extension defines policies in CA certificates under which
+ certificates may be issued. In end-entity certificates, the inclusion
+ of a policy indicates the issuance of the certificate follows the
+ policy.
+
+ :return:
+ None or a CertificatePolicies object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._certificate_policies_value
+
+ @property
+ def policy_mappings_value(self):
+ """
+ This extension allows mapping policy OIDs to other OIDs. This is used
+ to allow different policies to be treated as equivalent in the process
+ of validation.
+
+ :return:
+ None or a PolicyMappings object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._policy_mappings_value
+
+ @property
+ def authority_key_identifier_value(self):
+ """
+ This extension helps in identifying the public key with which to
+ validate the authenticity of the certificate.
+
+ :return:
+ None or an AuthorityKeyIdentifier object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._authority_key_identifier_value
+
+ @property
+ def policy_constraints_value(self):
+ """
+ This extension is used to control if policy mapping is allowed and
+ when policies are required.
+
+ :return:
+ None or a PolicyConstraints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._policy_constraints_value
+
+ @property
+ def freshest_crl_value(self):
+ """
+ This extension is used to help locate any available delta CRLs
+
+ :return:
+ None or an CRLDistributionPoints object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._freshest_crl_value
+
+ @property
+ def inhibit_any_policy_value(self):
+ """
+ This extension is used to prevent mapping of the any policy to
+ specific requirements
+
+ :return:
+ None or a Integer object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._inhibit_any_policy_value
+
+ @property
+ def extended_key_usage_value(self):
+ """
+ This extension is used to define additional purposes for the public key
+ beyond what is contained in the basic constraints.
+
+ :return:
+ None or an ExtKeyUsageSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._extended_key_usage_value
+
+ @property
+ def authority_information_access_value(self):
+ """
+ This extension is used to locate the CA certificate used to sign this
+ certificate, or the OCSP responder for this certificate.
+
+ :return:
+ None or an AuthorityInfoAccessSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._authority_information_access_value
+
+ @property
+ def subject_information_access_value(self):
+ """
+ This extension is used to access information about the subject of this
+ certificate.
+
+ :return:
+ None or a SubjectInfoAccessSyntax object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._subject_information_access_value
+
+ @property
+ def tls_feature_value(self):
+ """
+ This extension is used to list the TLS features a server must respond
+ with if a client initiates a request supporting them.
+
+ :return:
+ None or a Features object
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._tls_feature_value
+
+ @property
+ def ocsp_no_check_value(self):
+ """
+ This extension is used on certificates of OCSP responders, indicating
+ that revocation information for the certificate should never need to
+ be verified, thus preventing possible loops in path validation.
+
+ :return:
+ None or a Null object (if present)
+ """
+
+ if not self._processed_extensions:
+ self._set_extensions()
+ return self._ocsp_no_check_value
+
+ @property
+ def signature(self):
+ """
+ :return:
+ A byte string of the signature
+ """
+
+ return self['signature_value'].native
+
+ @property
+ def signature_algo(self):
+ """
+ :return:
+ A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa", "ecdsa"
+ """
+
+ return self['signature_algorithm'].signature_algo
+
+ @property
+ def hash_algo(self):
+ """
+ :return:
+ A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
+ "sha384", "sha512", "sha512_224", "sha512_256"
+ """
+
+ return self['signature_algorithm'].hash_algo
+
+ @property
+ def public_key(self):
+ """
+ :return:
+ The PublicKeyInfo object for this certificate
+ """
+
+ return self['tbs_certificate']['subject_public_key_info']
+
+ @property
+ def subject(self):
+ """
+ :return:
+ The Name object for the subject of this certificate
+ """
+
+ return self['tbs_certificate']['subject']
+
+ @property
+ def issuer(self):
+ """
+ :return:
+ The Name object for the issuer of this certificate
+ """
+
+ return self['tbs_certificate']['issuer']
+
+ @property
+ def serial_number(self):
+ """
+ :return:
+ An integer of the certificate's serial number
+ """
+
+ return self['tbs_certificate']['serial_number'].native
+
+ @property
+ def key_identifier(self):
+ """
+ :return:
+ None or a byte string of the certificate's key identifier from the
+ key identifier extension
+ """
+
+ if not self.key_identifier_value:
+ return None
+
+ return self.key_identifier_value.native
+
+ @property
+ def issuer_serial(self):
+ """
+ :return:
+ A byte string of the SHA-256 hash of the issuer concatenated with
+ the ascii character ":", concatenated with the serial number as
+ an ascii string
+ """
+
+ if self._issuer_serial is None:
+ self._issuer_serial = self.issuer.sha256 + b':' + str_cls(self.serial_number).encode('ascii')
+ return self._issuer_serial
+
+ @property
+ def not_valid_after(self):
+ """
+ :return:
+ A datetime of latest time when the certificate is still valid
+ """
+ return self['tbs_certificate']['validity']['not_after'].native
+
+ @property
+ def not_valid_before(self):
+ """
+ :return:
+ A datetime of the earliest time when the certificate is valid
+ """
+ return self['tbs_certificate']['validity']['not_before'].native
+
+ @property
+ def authority_key_identifier(self):
+ """
+ :return:
+ None or a byte string of the key_identifier from the authority key
+ identifier extension
+ """
+
+ if not self.authority_key_identifier_value:
+ return None
+
+ return self.authority_key_identifier_value['key_identifier'].native
+
+ @property
+ def authority_issuer_serial(self):
+ """
+ :return:
+ None or a byte string of the SHA-256 hash of the isser from the
+ authority key identifier extension concatenated with the ascii
+ character ":", concatenated with the serial number from the
+ authority key identifier extension as an ascii string
+ """
+
+ if self._authority_issuer_serial is False:
+ akiv = self.authority_key_identifier_value
+ if akiv and akiv['authority_cert_issuer'].native:
+ issuer = self.authority_key_identifier_value['authority_cert_issuer'][0].chosen
+ # We untag the element since it is tagged via being a choice from GeneralName
+ issuer = issuer.untag()
+ authority_serial = self.authority_key_identifier_value['authority_cert_serial_number'].native
+ self._authority_issuer_serial = issuer.sha256 + b':' + str_cls(authority_serial).encode('ascii')
+ else:
+ self._authority_issuer_serial = None
+ return self._authority_issuer_serial
+
+ @property
+ def crl_distribution_points(self):
+ """
+ Returns complete CRL URLs - does not include delta CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._crl_distribution_points is None:
+ self._crl_distribution_points = self._get_http_crl_distribution_points(self.crl_distribution_points_value)
+ return self._crl_distribution_points
+
+ @property
+ def delta_crl_distribution_points(self):
+ """
+ Returns delta CRL URLs - does not include complete CRLs
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ if self._delta_crl_distribution_points is None:
+ self._delta_crl_distribution_points = self._get_http_crl_distribution_points(self.freshest_crl_value)
+ return self._delta_crl_distribution_points
+
+ def _get_http_crl_distribution_points(self, crl_distribution_points):
+ """
+ Fetches the DistributionPoint object for non-relative, HTTP CRLs
+ referenced by the certificate
+
+ :param crl_distribution_points:
+ A CRLDistributionPoints object to grab the DistributionPoints from
+
+ :return:
+ A list of zero or more DistributionPoint objects
+ """
+
+ output = []
+
+ if crl_distribution_points is None:
+ return []
+
+ for distribution_point in crl_distribution_points:
+ distribution_point_name = distribution_point['distribution_point']
+ if distribution_point_name is VOID:
+ continue
+ # RFC 5280 indicates conforming CA should not use the relative form
+ if distribution_point_name.name == 'name_relative_to_crl_issuer':
+ continue
+ # This library is currently only concerned with HTTP-based CRLs
+ for general_name in distribution_point_name.chosen:
+ if general_name.name == 'uniform_resource_identifier':
+ output.append(distribution_point)
+
+ return output
+
+ @property
+ def ocsp_urls(self):
+ """
+ :return:
+ A list of zero or more unicode strings of the OCSP URLs for this
+ cert
+ """
+
+ if not self.authority_information_access_value:
+ return []
+
+ output = []
+ for entry in self.authority_information_access_value:
+ if entry['access_method'].native == 'ocsp':
+ location = entry['access_location']
+ if location.name != 'uniform_resource_identifier':
+ continue
+ url = location.native
+ if url.lower().startswith(('http://', 'https://', 'ldap://', 'ldaps://')):
+ output.append(url)
+ return output
+
+ @property
+ def valid_domains(self):
+ """
+ :return:
+ A list of unicode strings of valid domain names for the certificate.
+ Wildcard certificates will have a domain in the form: *.example.com
+ """
+
+ if self._valid_domains is None:
+ self._valid_domains = []
+
+ # For the subject alt name extension, we can look at the name of
+ # the choice selected since it distinguishes between domain names,
+ # email addresses, IPs, etc
+ if self.subject_alt_name_value:
+ for general_name in self.subject_alt_name_value:
+ if general_name.name == 'dns_name' and general_name.native not in self._valid_domains:
+ self._valid_domains.append(general_name.native)
+
+ # If there was no subject alt name extension, and the common name
+ # in the subject looks like a domain, that is considered the valid
+ # list. This is done because according to
+ # https://tools.ietf.org/html/rfc6125#section-6.4.4, the common
+ # name should not be used if the subject alt name is present.
+ else:
+ pattern = re.compile('^(\\*\\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$')
+ for rdn in self.subject.chosen:
+ for name_type_value in rdn:
+ if name_type_value['type'].native == 'common_name':
+ value = name_type_value['value'].native
+ if pattern.match(value):
+ self._valid_domains.append(value)
+
+ return self._valid_domains
+
+ @property
+ def valid_ips(self):
+ """
+ :return:
+ A list of unicode strings of valid IP addresses for the certificate
+ """
+
+ if self._valid_ips is None:
+ self._valid_ips = []
+
+ if self.subject_alt_name_value:
+ for general_name in self.subject_alt_name_value:
+ if general_name.name == 'ip_address':
+ self._valid_ips.append(general_name.native)
+
+ return self._valid_ips
+
+ @property
+ def ca(self):
+ """
+ :return;
+ A boolean - if the certificate is marked as a CA
+ """
+
+ return self.basic_constraints_value and self.basic_constraints_value['ca'].native
+
+ @property
+ def max_path_length(self):
+ """
+ :return;
+ None or an integer of the maximum path length
+ """
+
+ if not self.ca:
+ return None
+ return self.basic_constraints_value['path_len_constraint'].native
+
+ @property
+ def self_issued(self):
+ """
+ :return:
+ A boolean - if the certificate is self-issued, as defined by RFC
+ 5280
+ """
+
+ if self._self_issued is None:
+ self._self_issued = self.subject == self.issuer
+ return self._self_issued
+
+ @property
+ def self_signed(self):
+ """
+ :return:
+ A unicode string of "no" or "maybe". The "maybe" result will
+ be returned if the certificate issuer and subject are the same.
+ If a key identifier and authority key identifier are present,
+ they will need to match otherwise "no" will be returned.
+
+ To verify is a certificate is truly self-signed, the signature
+ will need to be verified. See the certvalidator package for
+ one possible solution.
+ """
+
+ if self._self_signed is None:
+ self._self_signed = 'no'
+ if self.self_issued:
+ if self.key_identifier:
+ if not self.authority_key_identifier:
+ self._self_signed = 'maybe'
+ elif self.authority_key_identifier == self.key_identifier:
+ self._self_signed = 'maybe'
+ else:
+ self._self_signed = 'maybe'
+ return self._self_signed
+
+ @property
+ def sha1(self):
+ """
+ :return:
+ The SHA-1 hash of the DER-encoded bytes of this complete certificate
+ """
+
+ if self._sha1 is None:
+ self._sha1 = hashlib.sha1(self.dump()).digest()
+ return self._sha1
+
+ @property
+ def sha1_fingerprint(self):
+ """
+ :return:
+ A unicode string of the SHA-1 hash, formatted using hex encoding
+ with a space between each pair of characters, all uppercase
+ """
+
+ return ' '.join('%02X' % c for c in bytes_to_list(self.sha1))
+
+ @property
+ def sha256(self):
+ """
+ :return:
+ The SHA-256 hash of the DER-encoded bytes of this complete
+ certificate
+ """
+
+ if self._sha256 is None:
+ self._sha256 = hashlib.sha256(self.dump()).digest()
+ return self._sha256
+
+ @property
+ def sha256_fingerprint(self):
+ """
+ :return:
+ A unicode string of the SHA-256 hash, formatted using hex encoding
+ with a space between each pair of characters, all uppercase
+ """
+
+ return ' '.join('%02X' % c for c in bytes_to_list(self.sha256))
+
+ def is_valid_domain_ip(self, domain_ip):
+ """
+ Check if a domain name or IP address is valid according to the
+ certificate
+
+ :param domain_ip:
+ A unicode string of a domain name or IP address
+
+ :return:
+ A boolean - if the domain or IP is valid for the certificate
+ """
+
+ if not isinstance(domain_ip, str_cls):
+ raise TypeError(unwrap(
+ '''
+ domain_ip must be a unicode string, not %s
+ ''',
+ type_name(domain_ip)
+ ))
+
+ encoded_domain_ip = domain_ip.encode('idna').decode('ascii').lower()
+
+ is_ipv6 = encoded_domain_ip.find(':') != -1
+ is_ipv4 = not is_ipv6 and re.match('^\\d+\\.\\d+\\.\\d+\\.\\d+$', encoded_domain_ip)
+ is_domain = not is_ipv6 and not is_ipv4
+
+ # Handle domain name checks
+ if is_domain:
+ if not self.valid_domains:
+ return False
+
+ domain_labels = encoded_domain_ip.split('.')
+
+ for valid_domain in self.valid_domains:
+ encoded_valid_domain = valid_domain.encode('idna').decode('ascii').lower()
+ valid_domain_labels = encoded_valid_domain.split('.')
+
+ # The domain must be equal in label length to match
+ if len(valid_domain_labels) != len(domain_labels):
+ continue
+
+ if valid_domain_labels == domain_labels:
+ return True
+
+ is_wildcard = self._is_wildcard_domain(encoded_valid_domain)
+ if is_wildcard and self._is_wildcard_match(domain_labels, valid_domain_labels):
+ return True
+
+ return False
+
+ # Handle IP address checks
+ if not self.valid_ips:
+ return False
+
+ family = socket.AF_INET if is_ipv4 else socket.AF_INET6
+ normalized_ip = inet_pton(family, encoded_domain_ip)
+
+ for valid_ip in self.valid_ips:
+ valid_family = socket.AF_INET if valid_ip.find('.') != -1 else socket.AF_INET6
+ normalized_valid_ip = inet_pton(valid_family, valid_ip)
+
+ if normalized_valid_ip == normalized_ip:
+ return True
+
+ return False
+
+ def _is_wildcard_domain(self, domain):
+ """
+ Checks if a domain is a valid wildcard according to
+ https://tools.ietf.org/html/rfc6125#section-6.4.3
+
+ :param domain:
+ A unicode string of the domain name, where any U-labels from an IDN
+ have been converted to A-labels
+
+ :return:
+ A boolean - if the domain is a valid wildcard domain
+ """
+
+ # The * character must be present for a wildcard match, and if there is
+ # most than one, it is an invalid wildcard specification
+ if domain.count('*') != 1:
+ return False
+
+ labels = domain.lower().split('.')
+
+ if not labels:
+ return False
+
+ # Wildcards may only appear in the left-most label
+ if labels[0].find('*') == -1:
+ return False
+
+ # Wildcards may not be embedded in an A-label from an IDN
+ if labels[0][0:4] == 'xn--':
+ return False
+
+ return True
+
+ def _is_wildcard_match(self, domain_labels, valid_domain_labels):
+ """
+ Determines if the labels in a domain are a match for labels from a
+ wildcard valid domain name
+
+ :param domain_labels:
+ A list of unicode strings, with A-label form for IDNs, of the labels
+ in the domain name to check
+
+ :param valid_domain_labels:
+ A list of unicode strings, with A-label form for IDNs, of the labels
+ in a wildcard domain pattern
+
+ :return:
+ A boolean - if the domain matches the valid domain
+ """
+
+ first_domain_label = domain_labels[0]
+ other_domain_labels = domain_labels[1:]
+
+ wildcard_label = valid_domain_labels[0]
+ other_valid_domain_labels = valid_domain_labels[1:]
+
+ # The wildcard is only allowed in the first label, so if
+ # The subsequent labels are not equal, there is no match
+ if other_domain_labels != other_valid_domain_labels:
+ return False
+
+ if wildcard_label == '*':
+ return True
+
+ wildcard_regex = re.compile('^' + wildcard_label.replace('*', '.*') + '$')
+ if wildcard_regex.match(first_domain_label):
+ return True
+
+ return False
+
+
+# The structures are taken from the OpenSSL source file x_x509a.c, and specify
+# extra information that is added to X.509 certificates to store trust
+# information about the certificate.
+
+class KeyPurposeIdentifiers(SequenceOf):
+ _child_spec = KeyPurposeId
+
+
+class SequenceOfAlgorithmIdentifiers(SequenceOf):
+ _child_spec = AlgorithmIdentifier
+
+
+class CertificateAux(Sequence):
+ _fields = [
+ ('trust', KeyPurposeIdentifiers, {'optional': True}),
+ ('reject', KeyPurposeIdentifiers, {'implicit': 0, 'optional': True}),
+ ('alias', UTF8String, {'optional': True}),
+ ('keyid', OctetString, {'optional': True}),
+ ('other', SequenceOfAlgorithmIdentifiers, {'implicit': 1, 'optional': True}),
+ ]
+
+
+class TrustedCertificate(Concat):
+ _child_specs = [Certificate, CertificateAux]
diff --git a/contrib/python/asn1crypto/py3/readme.md b/contrib/python/asn1crypto/py3/readme.md
new file mode 100644
index 0000000000..4f1061f233
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/readme.md
@@ -0,0 +1,273 @@
+# asn1crypto
+
+A fast, pure Python library for parsing and serializing ASN.1 structures.
+
+ - [Features](#features)
+ - [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
+ - [Related Crypto Libraries](#related-crypto-libraries)
+ - [Current Release](#current-release)
+ - [Dependencies](#dependencies)
+ - [Installation](#installation)
+ - [License](#license)
+ - [Security Policy](#security-policy)
+ - [Documentation](#documentation)
+ - [Continuous Integration](#continuous-integration)
+ - [Testing](#testing)
+ - [Development](#development)
+ - [CI Tasks](#ci-tasks)
+
+[![GitHub Actions CI](https://github.com/wbond/asn1crypto/workflows/CI/badge.svg)](https://github.com/wbond/asn1crypto/actions?workflow=CI)
+[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
+[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/)
+
+## Features
+
+In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
+a bunch of ASN.1 structures for use with various common cryptography standards:
+
+| Standard | Module | Source |
+| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
+| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
+| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
+| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
+| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
+| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
+| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
+| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
+| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
+| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
+| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
+| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
+| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
+
+## Why Another Python ASN.1 Library?
+
+Python has long had the [pyasn1](https://pypi.org/project/pyasn1/) and
+[pyasn1_modules](https://pypi.org/project/pyasn1-modules/) available for
+parsing and serializing ASN.1 structures. While the project does include a
+comprehensive set of tools for parsing and serializing, the performance of the
+library can be very poor, especially when dealing with bit fields and parsing
+large structures such as CRLs.
+
+After spending extensive time using *pyasn1*, the following issues were
+identified:
+
+ 1. Poor performance
+ 2. Verbose, non-pythonic API
+ 3. Out-dated and incomplete definitions in *pyasn1-modules*
+ 4. No simple way to map data to native Python data structures
+ 5. No mechanism for overridden universal ASN.1 types
+
+The *pyasn1* API is largely method driven, and uses extensive configuration
+objects and lowerCamelCase names. There were no consistent options for
+converting types of native Python data structures. Since the project supports
+out-dated versions of Python, many newer language features are unavailable
+for use.
+
+Time was spent trying to profile issues with the performance, however the
+architecture made it hard to pin down the primary source of the poor
+performance. Attempts were made to improve performance by utilizing unreleased
+patches and delaying parsing using the `Any` type. Even with such changes, the
+performance was still unacceptably slow.
+
+Finally, a number of structures in the cryptographic space use universal data
+types such as `BitString` and `OctetString`, but interpret the data as other
+types. For instance, signatures are really byte strings, but are encoded as
+`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
+represent integers. Parsing these structures as the base universal types and
+then re-interpreting them wastes computation.
+
+*asn1crypto* uses the following techniques to improve performance, especially
+when extracting one or two fields from large, complex structures:
+
+ - Delayed parsing of byte string values
+ - Persistence of original ASN.1 encoded data until a value is changed
+ - Lazy loading of child fields
+ - Utilization of high-level Python stdlib modules
+
+While there is no extensive performance test suite, the
+`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
+late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
+under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
+same parsing took over 4,100 seconds.
+
+For smaller structures the performance difference can range from a few times
+faster to an order of magnitude or more.
+
+## Related Crypto Libraries
+
+*asn1crypto* is part of the modularcrypto family of Python packages:
+
+ - [asn1crypto](https://github.com/wbond/asn1crypto)
+ - [oscrypto](https://github.com/wbond/oscrypto)
+ - [csrbuilder](https://github.com/wbond/csrbuilder)
+ - [certbuilder](https://github.com/wbond/certbuilder)
+ - [crlbuilder](https://github.com/wbond/crlbuilder)
+ - [ocspbuilder](https://github.com/wbond/ocspbuilder)
+ - [certvalidator](https://github.com/wbond/certvalidator)
+
+## Current Release
+
+1.5.0 - [changelog](changelog.md)
+
+## Dependencies
+
+Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
+packages required.*
+
+## Installation
+
+```bash
+pip install asn1crypto
+```
+
+## License
+
+*asn1crypto* is licensed under the terms of the MIT license. See the
+[LICENSE](LICENSE) file for the exact license text.
+
+## Security Policy
+
+The security policies for this project are covered in
+[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
+
+## Documentation
+
+The documentation for *asn1crypto* is composed of tutorials on basic usage and
+links to the source for the various pre-defined type classes.
+
+### Tutorials
+
+ - [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
+ - [PEM Encoder and Decoder](docs/pem.md)
+
+### Reference
+
+ - [Universal types](asn1crypto/core.py), `asn1crypto.core`
+ - [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
+ - [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
+ - [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
+ - [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
+ - [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
+ - [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
+ - [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
+ - [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
+ - [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
+ - [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
+
+## Continuous Integration
+
+Various combinations of platforms and versions of Python are tested via:
+
+ - [macOS, Linux, Windows](https://github.com/wbond/asn1crypto/actions/workflows/ci.yml) via GitHub Actions
+ - [arm64](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
+
+## Testing
+
+Tests are written using `unittest` and require no third-party packages.
+
+Depending on what type of source is available for the package, the following
+commands can be used to run the test suite.
+
+### Git Repository
+
+When working within a Git working copy, or an archive of the Git repository,
+the full test suite is run via:
+
+```bash
+python run.py tests
+```
+
+To run only some tests, pass a regular expression as a parameter to `tests`.
+
+```bash
+python run.py tests ocsp
+```
+
+### PyPi Source Distribution
+
+When working within an extracted source distribution (aka `.tar.gz`) from
+PyPi, the full test suite is run via:
+
+```bash
+python setup.py test
+```
+
+### Package
+
+When the package has been installed via pip (or another method), the package
+`asn1crypto_tests` may be installed and invoked to run the full test suite:
+
+```bash
+pip install asn1crypto_tests
+python -m asn1crypto_tests
+```
+
+## Development
+
+To install the package used for linting, execute:
+
+```bash
+pip install --user -r requires/lint
+```
+
+The following command will run the linter:
+
+```bash
+python run.py lint
+```
+
+Support for code coverage can be installed via:
+
+```bash
+pip install --user -r requires/coverage
+```
+
+Coverage is measured by running:
+
+```bash
+python run.py coverage
+```
+
+To change the version number of the package, run:
+
+```bash
+python run.py version {pep440_version}
+```
+
+To install the necessary packages for releasing a new version on PyPI, run:
+
+```bash
+pip install --user -r requires/release
+```
+
+Releases are created by:
+
+ - Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
+ - Running the command:
+
+ ```bash
+ python run.py release
+ ```
+
+Existing releases can be found at https://pypi.org/project/asn1crypto/.
+
+## CI Tasks
+
+A task named `deps` exists to download and stage all necessary testing
+dependencies. On posix platforms, `curl` is used for downloads and on Windows
+PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
+related to getting pip to work properly and messing with `site-packages` for
+the version of Python being used.
+
+The `ci` task runs `lint` (if flake8 is available for the version of Python) and
+`coverage` (or `tests` if coverage is not available for the version of Python).
+If the current directory is a clean git working copy, the coverage data is
+submitted to codecov.io.
+
+```bash
+python run.py deps
+python run.py ci
+```
diff --git a/contrib/python/asn1crypto/py3/ya.make b/contrib/python/asn1crypto/py3/ya.make
new file mode 100644
index 0000000000..ef1a15a2d8
--- /dev/null
+++ b/contrib/python/asn1crypto/py3/ya.make
@@ -0,0 +1,44 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(1.5.1)
+
+LICENSE(MIT)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ asn1crypto/__init__.py
+ asn1crypto/_errors.py
+ asn1crypto/_inet.py
+ asn1crypto/_int.py
+ asn1crypto/_iri.py
+ asn1crypto/_ordereddict.py
+ asn1crypto/_teletex_codec.py
+ asn1crypto/_types.py
+ asn1crypto/algos.py
+ asn1crypto/cms.py
+ asn1crypto/core.py
+ asn1crypto/crl.py
+ asn1crypto/csr.py
+ asn1crypto/keys.py
+ asn1crypto/ocsp.py
+ asn1crypto/parser.py
+ asn1crypto/pdf.py
+ asn1crypto/pem.py
+ asn1crypto/pkcs12.py
+ asn1crypto/tsp.py
+ asn1crypto/util.py
+ asn1crypto/version.py
+ asn1crypto/x509.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/asn1crypto/py3/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/asn1crypto/ya.make b/contrib/python/asn1crypto/ya.make
new file mode 100644
index 0000000000..75419d4566
--- /dev/null
+++ b/contrib/python/asn1crypto/ya.make
@@ -0,0 +1,18 @@
+PY23_LIBRARY()
+
+LICENSE(Service-Py23-Proxy)
+
+IF (PYTHON2)
+ PEERDIR(contrib/python/asn1crypto/py2)
+ELSE()
+ PEERDIR(contrib/python/asn1crypto/py3)
+ENDIF()
+
+NO_LINT()
+
+END()
+
+RECURSE(
+ py2
+ py3
+)