Bläddra i källkod

HUE-6080 [core] Adding asn1crypto lib as required in metadata lib

Romain Rigaux 9 år sedan
förälder
incheckning
5d513cbbd6
30 ändrade filer med 15875 tillägg och 0 borttagningar
  1. 23 0
      desktop/core/ext-py/asn1crypto-0.22.0/PKG-INFO
  2. 9 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/__init__.py
  3. 314 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_elliptic_curve.py
  4. 45 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_errors.py
  5. 45 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_ffi.py
  6. 170 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_inet.py
  7. 159 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_int.py
  8. 288 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_iri.py
  9. 135 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_ordereddict.py
  10. 0 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_perf/__init__.py
  11. 65 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_perf/_big_num_ctypes.py
  12. 331 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_teletex_codec.py
  13. 46 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_types.py
  14. 1122 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/algos.py
  15. 915 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/cms.py
  16. 5099 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/core.py
  17. 536 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/crl.py
  18. 96 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/csr.py
  19. 1245 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/keys.py
  20. 649 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/ocsp.py
  21. 289 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/parser.py
  22. 84 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/pdf.py
  23. 222 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/pem.py
  24. 193 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/pkcs12.py
  25. 310 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/tsp.py
  26. 712 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/util.py
  27. 6 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/version.py
  28. 2681 0
      desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/x509.py
  29. 5 0
      desktop/core/ext-py/asn1crypto-0.22.0/setup.cfg
  30. 81 0
      desktop/core/ext-py/asn1crypto-0.22.0/setup.py

+ 23 - 0
desktop/core/ext-py/asn1crypto-0.22.0/PKG-INFO

@@ -0,0 +1,23 @@
+Metadata-Version: 1.1
+Name: asn1crypto
+Version: 0.22.0
+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
+Description: Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.
+Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+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 :: Implementation :: PyPy
+Classifier: Topic :: Security :: Cryptography

+ 9 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/__init__.py

@@ -0,0 +1,9 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from .version import __version__, __version_info__
+
+__all__ = [
+    '__version__',
+    '__version_info__',
+]

+ 314 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_elliptic_curve.py

@@ -0,0 +1,314 @@
+# coding: utf-8
+
+"""
+Classes and objects to represent prime-field elliptic curves and points on them.
+Exports the following items:
+
+ - PrimeCurve()
+ - PrimePoint()
+ - SECP192R1_CURVE
+ - SECP192R1_BASE_POINT
+ - SECP224R1_CURVE
+ - SECP224R1_BASE_POINT
+ - SECP256R1_CURVE
+ - SECP256R1_BASE_POINT
+ - SECP384R1_CURVE
+ - SECP384R1_BASE_POINT
+ - SECP521R1_CURVE
+ - SECP521R1_BASE_POINT
+
+The curve constants are all PrimeCurve() objects and the base point constants
+are all PrimePoint() objects.
+
+Some of the following source code is derived from
+http://webpages.charter.net/curryfans/peter/downloads.html, but has been heavily
+modified to fit into this projects lint settings. The original project license
+is listed below:
+
+Copyright (c) 2014 Peter Pearson
+
+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.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ._int import inverse_mod
+
+
+class PrimeCurve():
+    """
+    Elliptic curve over a prime field. Characteristic two field curves are not
+    supported.
+    """
+
+    def __init__(self, p, a, b):
+        """
+        The curve of points satisfying y^2 = x^3 + a*x + b (mod p)
+
+        :param p:
+            The prime number as an integer
+
+        :param a:
+            The component a as an integer
+
+        :param b:
+            The component b as an integer
+        """
+
+        self.p = p
+        self.a = a
+        self.b = b
+
+    def contains(self, point):
+        """
+        :param point:
+            A Point object
+
+        :return:
+            Boolean if the point is on this curve
+        """
+
+        y2 = point.y * point.y
+        x3 = point.x * point.x * point.x
+        return (y2 - (x3 + self.a * point.x + self.b)) % self.p == 0
+
+
+class PrimePoint():
+    """
+    A point on a prime-field elliptic curve
+    """
+
+    def __init__(self, curve, x, y, order=None):
+        """
+        :param curve:
+            A PrimeCurve object
+
+        :param x:
+            The x coordinate of the point as an integer
+
+        :param y:
+            The y coordinate of the point as an integer
+
+        :param order:
+            The order of the point, as an integer - optional
+        """
+
+        self.curve = curve
+        self.x = x
+        self.y = y
+        self.order = order
+
+        # self.curve is allowed to be None only for INFINITY:
+        if self.curve:
+            if not self.curve.contains(self):
+                raise ValueError('Invalid EC point')
+
+        if self.order:
+            if self * self.order != INFINITY:
+                raise ValueError('Invalid EC point')
+
+    def __cmp__(self, other):
+        """
+        :param other:
+            A PrimePoint object
+
+        :return:
+            0 if identical, 1 otherwise
+        """
+        if self.curve == other.curve and self.x == other.x and self.y == other.y:
+            return 0
+        else:
+            return 1
+
+    def __add__(self, other):
+        """
+        :param other:
+            A PrimePoint object
+
+        :return:
+            A PrimePoint object
+        """
+
+        # X9.62 B.3:
+
+        if other == INFINITY:
+            return self
+        if self == INFINITY:
+            return other
+        assert self.curve == other.curve
+        if self.x == other.x:
+            if (self.y + other.y) % self.curve.p == 0:
+                return INFINITY
+            else:
+                return self.double()
+
+        p = self.curve.p
+
+        l = ((other.y - self.y) * inverse_mod(other.x - self.x, p)) % p
+
+        x3 = (l * l - self.x - other.x) % p
+        y3 = (l * (self.x - x3) - self.y) % p
+
+        return PrimePoint(self.curve, x3, y3)
+
+    def __mul__(self, other):
+        """
+        :param other:
+            An integer to multiple the Point by
+
+        :return:
+            A PrimePoint object
+        """
+
+        def leftmost_bit(x):
+            assert x > 0
+            result = 1
+            while result <= x:
+                result = 2 * result
+            return result // 2
+
+        e = other
+        if self.order:
+            e = e % self.order
+        if e == 0:
+            return INFINITY
+        if self == INFINITY:
+            return INFINITY
+        assert e > 0
+
+        # From X9.62 D.3.2:
+
+        e3 = 3 * e
+        negative_self = PrimePoint(self.curve, self.x, -self.y, self.order)
+        i = leftmost_bit(e3) // 2
+        result = self
+        # print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 )
+        while i > 1:
+            result = result.double()
+            if (e3 & i) != 0 and (e & i) == 0:
+                result = result + self
+            if (e3 & i) == 0 and (e & i) != 0:
+                result = result + negative_self
+            # print ". . . i = %d, result = %s" % ( i, result )
+            i = i // 2
+
+        return result
+
+    def __rmul__(self, other):
+        """
+        :param other:
+            An integer to multiple the Point by
+
+        :return:
+            A PrimePoint object
+        """
+
+        return self * other
+
+    def double(self):
+        """
+        :return:
+            A PrimePoint object that is twice this point
+        """
+
+        # X9.62 B.3:
+
+        p = self.curve.p
+        a = self.curve.a
+
+        l = ((3 * self.x * self.x + a) * inverse_mod(2 * self.y, p)) % p
+
+        x3 = (l * l - 2 * self.x) % p
+        y3 = (l * (self.x - x3) - self.y) % p
+
+        return PrimePoint(self.curve, x3, y3)
+
+
+# This one point is the Point At Infinity for all purposes:
+INFINITY = PrimePoint(None, None, None)
+
+
+# NIST Curve P-192:
+SECP192R1_CURVE = PrimeCurve(
+    6277101735386680763835789423207666416083908700390324961279,
+    -3,
+    0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1
+)
+SECP192R1_BASE_POINT = PrimePoint(
+    SECP192R1_CURVE,
+    0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012,
+    0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811,
+    6277101735386680763835789423176059013767194773182842284081
+)
+
+
+# NIST Curve P-224:
+SECP224R1_CURVE = PrimeCurve(
+    26959946667150639794667015087019630673557916260026308143510066298881,
+    -3,
+    0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4
+)
+SECP224R1_BASE_POINT = PrimePoint(
+    SECP224R1_CURVE,
+    0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21,
+    0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34,
+    26959946667150639794667015087019625940457807714424391721682722368061
+)
+
+
+# NIST Curve P-256:
+SECP256R1_CURVE = PrimeCurve(
+    115792089210356248762697446949407573530086143415290314195533631308867097853951,
+    -3,
+    0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
+)
+SECP256R1_BASE_POINT = PrimePoint(
+    SECP256R1_CURVE,
+    0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
+    0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
+    115792089210356248762697446949407573529996955224135760342422259061068512044369
+)
+
+
+# NIST Curve P-384:
+SECP384R1_CURVE = PrimeCurve(
+    39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319,  # noqa
+    -3,
+    0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef
+)
+SECP384R1_BASE_POINT = PrimePoint(
+    SECP384R1_CURVE,
+    0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
+    0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f,
+    39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
+)
+
+
+# NIST Curve P-521:
+SECP521R1_CURVE = PrimeCurve(
+    6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151,  # noqa
+    -3,
+    0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00  # noqa
+)
+SECP521R1_BASE_POINT = PrimePoint(
+    SECP521R1_CURVE,
+    0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66,  # noqa
+    0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650,  # noqa
+    6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449  # noqa
+)

+ 45 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_errors.py

@@ -0,0 +1,45 @@
+# coding: utf-8
+
+"""
+Helper for formatting exception messages. Exports the following items:
+
+ - unwrap()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import re
+import textwrap
+
+
+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

+ 45 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_ffi.py

@@ -0,0 +1,45 @@
+# coding: utf-8
+
+"""
+FFI helper compatibility functions. Exports the following items:
+
+ - LibraryNotFoundError
+ - FFIEngineError
+ - bytes_from_buffer()
+ - buffer_from_bytes()
+ - null()
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ctypes import create_string_buffer
+
+
+def buffer_from_bytes(initializer):
+    return create_string_buffer(initializer)
+
+
+def bytes_from_buffer(buffer, maxlen=None):
+    return buffer.raw
+
+
+def null():
+    return None
+
+
+class LibraryNotFoundError(Exception):
+
+    """
+    An exception when trying to find a shared library
+    """
+
+    pass
+
+
+class FFIEngineError(Exception):
+
+    """
+    An exception when trying to instantiate ctypes or cffi
+    """
+
+    pass

+ 170 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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 compatiblity 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 compatiblity 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)
+    ))

+ 159 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_int.py

@@ -0,0 +1,159 @@
+# coding: utf-8
+
+"""
+Function for calculating the modular inverse. Exports the following items:
+
+ - inverse_mod()
+
+Source code is derived from
+http://webpages.charter.net/curryfans/peter/downloads.html, but has been heavily
+modified to fit into this projects lint settings. The original project license
+is listed below:
+
+Copyright (c) 2014 Peter Pearson
+
+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.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import math
+import platform
+
+from .util import int_to_bytes, int_from_bytes
+
+# First try to use ctypes with OpenSSL for better performance
+try:
+    from ._ffi import (
+        buffer_from_bytes,
+        bytes_from_buffer,
+        FFIEngineError,
+        LibraryNotFoundError,
+        null,
+    )
+
+    # Some versions of PyPy have segfault issues, so we just punt on PyPy
+    if platform.python_implementation() == 'PyPy':
+        raise EnvironmentError()
+
+    try:
+        from ._perf._big_num_ctypes import libcrypto
+
+        def inverse_mod(a, p):
+            """
+            Compute the modular inverse of a (mod p)
+
+            :param a:
+                An integer
+
+            :param p:
+                An integer
+
+            :return:
+                An integer
+            """
+
+            ctx = libcrypto.BN_CTX_new()
+
+            a_bytes = int_to_bytes(abs(a))
+            p_bytes = int_to_bytes(abs(p))
+
+            a_buf = buffer_from_bytes(a_bytes)
+            a_bn = libcrypto.BN_bin2bn(a_buf, len(a_bytes), null())
+            if a < 0:
+                libcrypto.BN_set_negative(a_bn, 1)
+
+            p_buf = buffer_from_bytes(p_bytes)
+            p_bn = libcrypto.BN_bin2bn(p_buf, len(p_bytes), null())
+            if p < 0:
+                libcrypto.BN_set_negative(p_bn, 1)
+
+            r_bn = libcrypto.BN_mod_inverse(null(), a_bn, p_bn, ctx)
+            r_len_bits = libcrypto.BN_num_bits(r_bn)
+            r_len = int(math.ceil(r_len_bits / 8))
+            r_buf = buffer_from_bytes(r_len)
+            libcrypto.BN_bn2bin(r_bn, r_buf)
+            r_bytes = bytes_from_buffer(r_buf, r_len)
+            result = int_from_bytes(r_bytes)
+
+            libcrypto.BN_free(a_bn)
+            libcrypto.BN_free(p_bn)
+            libcrypto.BN_free(r_bn)
+            libcrypto.BN_CTX_free(ctx)
+
+            return result
+    except (LibraryNotFoundError, FFIEngineError):
+        raise EnvironmentError()
+
+# If there was an issue using ctypes or OpenSSL, we fall back to pure python
+except (EnvironmentError, ImportError):
+
+    def inverse_mod(a, p):
+        """
+        Compute the modular inverse of a (mod p)
+
+        :param a:
+            An integer
+
+        :param p:
+            An integer
+
+        :return:
+            An integer
+        """
+
+        if a < 0 or p <= a:
+            a = a % p
+
+        # From Ferguson and Schneier, roughly:
+
+        c, d = a, p
+        uc, vc, ud, vd = 1, 0, 0, 1
+        while c != 0:
+            q, c, d = divmod(d, c) + (c,)
+            uc, vc, ud, vd = ud - q * uc, vd - q * vc, uc, vc
+
+        # At this point, d is the GCD, and ud*a+vd*p = d.
+        # If d == 1, this means that ud is a inverse.
+
+        assert d == 1
+        if ud > 0:
+            return ud
+        else:
+            return ud + p
+
+
+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_

+ 288 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_iri.py

@@ -0,0 +1,288 @@
+# 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):
+    """
+    Normalizes and encodes a unicode IRI into an ASCII byte string URI
+
+    :param value:
+        A unicode string of an IRI
+
+    :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 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 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

+ 135 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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

+ 0 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_perf/__init__.py


+ 65 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/_perf/_big_num_ctypes.py

@@ -0,0 +1,65 @@
+# coding: utf-8
+
+"""
+ctypes interface for BN_mod_inverse() function from OpenSSL. Exports the
+following items:
+
+ - libcrypto
+    - BN_bn2bin()
+    - BN_CTX_free()
+    - BN_CTX_new()
+    - BN_free()
+    - BN_mod_inverse()
+    - BN_new()
+    - BN_num_bits()
+    - BN_set_negative()
+
+Will raise asn1crypto._ffi.LibraryNotFoundError() if libcrypto can not be
+found. Will raise asn1crypto._ffi.FFIEngineError() if there is an error
+interfacing with libcrypto.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+from ctypes import CDLL, c_int, c_char_p, c_void_p
+from ctypes.util import find_library
+
+from .._ffi import LibraryNotFoundError, FFIEngineError
+
+
+try:
+    libcrypto_path = find_library('crypto')
+    if not libcrypto_path:
+        raise LibraryNotFoundError('The library libcrypto could not be found')
+
+    libcrypto = CDLL(libcrypto_path)
+
+    libcrypto.BN_new.argtypes = []
+    libcrypto.BN_new.restype = c_void_p
+
+    libcrypto.BN_bin2bn.argtypes = [c_char_p, c_int, c_void_p]
+    libcrypto.BN_bin2bn.restype = c_void_p
+
+    libcrypto.BN_bn2bin.argtypes = [c_void_p, c_char_p]
+    libcrypto.BN_bn2bin.restype = c_int
+
+    libcrypto.BN_set_negative.argtypes = [c_void_p, c_int]
+    libcrypto.BN_set_negative.restype = None
+
+    libcrypto.BN_num_bits.argtypes = [c_void_p]
+    libcrypto.BN_num_bits.restype = c_int
+
+    libcrypto.BN_free.argtypes = [c_void_p]
+    libcrypto.BN_free.restype = None
+
+    libcrypto.BN_CTX_new.argtypes = []
+    libcrypto.BN_CTX_new.restype = c_void_p
+
+    libcrypto.BN_CTX_free.argtypes = [c_void_p]
+    libcrypto.BN_CTX_free.restype = None
+
+    libcrypto.BN_mod_inverse.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p]
+    libcrypto.BN_mod_inverse.restype = c_void_p
+
+except (AttributeError):
+    raise FFIEngineError('Error initializing ctypes')

+ 331 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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)

+ 46 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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__)

+ 1122 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/algos.py

@@ -0,0 +1,1122 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for various algorithms using in various aspects of public
+key cryptography. Exports the following items:
+
+ - AlgorithmIdentifier()
+ - 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',
+    }
+
+
+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',
+    }
+
+
+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,
+            {
+                'tag_type': 'explicit',
+                'tag': 0,
+                'default': {'algorithm': 'sha1'},
+            }
+        ),
+        (
+            'mask_gen_algorithm',
+            MaskGenAlgorithm,
+            {
+                'tag_type': 'explicit',
+                'tag': 1,
+                'default': {
+                    'algorithm': 'mgf1',
+                    'parameters': {'algorithm': 'sha1'},
+                },
+            }
+        ),
+        (
+            'salt_length',
+            Integer,
+            {
+                'tag_type': 'explicit',
+                'tag': 2,
+                'default': 20,
+            }
+        ),
+        (
+            'trailer_field',
+            TrailerField,
+            {
+                'tag_type': 'explicit',
+                'tag': 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',
+        # 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',
+    }
+
+    _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',
+    }
+
+
+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" or
+            "ecdsa"
+        """
+
+        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',
+            'ecdsa': 'ecdsa',
+        }
+        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"
+        """
+
+        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',
+        }
+        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 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,
+            {
+                'tag_type': 'explicit',
+                'tag': 0,
+                'default': {'algorithm': 'sha1'}
+            }
+        ),
+        (
+            'mask_gen_algorithm',
+            MaskGenAlgorithm,
+            {
+                'tag_type': 'explicit',
+                'tag': 1,
+                'default': {
+                    'algorithm': 'mgf1',
+                    'parameters': {'algorithm': 'sha1'}
+                }
+            }
+        ),
+        (
+            'p_source_algorithm',
+            PSourceAlgorithm,
+            {
+                'tag_type': 'explicit',
+                'tag': 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.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 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_params = self['parameters'].parsed['encryption_scheme']['parameters'].parsed
+            rc2_parameter_version = rc2_params['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'].parsed['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'].parsed['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

+ 915 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/cms.py

@@ -0,0 +1,915 @@
+# 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.
+"""
+
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+try:
+    import zlib
+except (ImportError):
+    zlib = None
+
+from .algos import (
+    _ForceNullParameters,
+    DigestAlgorithm,
+    EncryptionAlgorithm,
+    HmacAlgorithm,
+    KdfAlgorithm,
+    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://tools.ietf.org/html/rfc3161#page-20
+        '1.2.840.113549.1.9.16.2.14': 'signature_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 SetOfContentType(SetOf):
+    _child_spec = ContentType
+
+
+class SetOfOctetString(SetOf):
+    _child_spec = OctetString
+
+
+class SetOfTime(SetOf):
+    _child_spec = Time
+
+
+class SetOfAny(SetOf):
+    _child_spec = Any
+
+
+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, {'tag_type': 'explicit', 'tag': 0}),
+        ('subject_name', GeneralNames, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('entity_name', GeneralNames, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('object_digest_info', ObjectDigestInfo, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+    ]
+
+
+class V2Form(Sequence):
+    _fields = [
+        ('issuer_name', GeneralNames, {'optional': True}),
+        ('base_certificate_id', IssuerSerial, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('object_digest_info', ObjectDigestInfo, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class AttCertIssuer(Choice):
+    _alternatives = [
+        ('v1_form', GeneralNames),
+        ('v2_form', V2Form, {'tag_type': 'explicit', 'tag': 0}),
+    ]
+
+
+class IetfAttrValue(Choice):
+    _alternatives = [
+        ('octets', OctetString),
+        ('oid', ObjectIdentifier),
+        ('string', UTF8String),
+    ]
+
+
+class IetfAttrValues(SequenceOf):
+    _child_spec = IetfAttrValue
+
+
+class IetfAttrSyntax(Sequence):
+    _fields = [
+        ('policy_authority', GeneralNames, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('role_name', GeneralName, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('value', Any, {'tag_type': 'implicit', 'tag': 1}),
+    ]
+
+
+class SetOfSecurityCategory(SetOf):
+    _child_spec = SecurityCategory
+
+
+class Clearance(Sequence):
+    _fields = [
+        ('policy_id', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 0}),
+        ('class_list', ClassList, {'tag_type': 'implicit', 'tag': 1, 'default': 'unclassified'}),
+        ('security_categories', SetOfSecurityCategory, {'tag_type': 'implicit', 'tag': 2, '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, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('max_delay', BigTime, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('v1_attr_cert', AttributeCertificateV1, {'tag_type': 'implicit', 'tag': 1}),
+        ('v2_attr_cert', AttributeCertificateV2, {'tag_type': 'implicit', 'tag': 2}),
+        ('other', OtherCertificateFormat, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+    ]
+
+
+class DigestAlgorithms(SetOf):
+    _child_spec = DigestAlgorithm
+
+
+class CertificateRevocationLists(SetOf):
+    _child_spec = CertificateList
+
+
+class SCVPReqRes(Sequence):
+    _fields = [
+        ('request', ContentInfo, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implciit', 'tag': 1}),
+    ]
+
+
+class RevocationInfoChoices(SetOf):
+    _child_spec = RevocationInfoChoice
+
+
+class SignerInfo(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('sid', SignerIdentifier),
+        ('digest_algorithm', DigestAlgorithm),
+        ('signed_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('signature_algorithm', SignedDigestAlgorithm),
+        ('signature', OctetString),
+        ('unsigned_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class SignerInfos(SetOf):
+    _child_spec = SignerInfo
+
+
+class SignedData(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('digest_algorithms', DigestAlgorithms),
+        ('encap_content_info', None),
+        ('certificates', CertificateSet, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('crls', RevocationInfoChoices, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('crls', RevocationInfoChoices, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class RecipientIdentifier(Choice):
+    _alternatives = [
+        ('issuer_and_serial_number', IssuerAndSerialNumber),
+        ('subject_key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0}),
+    ]
+
+
+class KeyEncryptionAlgorithmId(ObjectIdentifier):
+    _map = {
+        '1.2.840.113549.1.1.1': 'rsa',
+        '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',
+    }
+
+
+class KeyEncryptionAlgorithm(_ForceNullParameters, Sequence):
+    _fields = [
+        ('algorithm', KeyEncryptionAlgorithmId),
+        ('parameters', Any, {'optional': True}),
+    ]
+
+
+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, {'tag_type': 'implicit', 'tag': 0}),
+        ('originator_key', PublicKeyInfo, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+    ]
+
+
+class RecipientEncryptedKey(Sequence):
+    _fields = [
+        ('rid', KeyAgreementRecipientIdentifier),
+        ('encrypted_key', OctetString),
+    ]
+
+
+class RecipientEncryptedKeys(SequenceOf):
+    _child_spec = RecipientEncryptedKey
+
+
+class KeyAgreeRecipientInfo(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('originator', OriginatorIdentifierOrKey, {'tag_type': 'explicit', 'tag': 0}),
+        ('ukm', OctetString, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 1}),
+        ('kekri', KEKRecipientInfo, {'tag_type': 'implicit', 'tag': 2}),
+        ('pwri', PasswordRecipientInfo, {'tag_type': 'implicit', 'tag': 3}),
+        ('ori', OtherRecipientInfo, {'tag_type': 'implicit', 'tag': 4}),
+    ]
+
+
+class RecipientInfos(SetOf):
+    _child_spec = RecipientInfo
+
+
+class EncryptedContentInfo(Sequence):
+    _fields = [
+        ('content_type', ContentType),
+        ('content_encryption_algorithm', EncryptionAlgorithm),
+        ('encrypted_content', OctetString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+    ]
+
+
+class EnvelopedData(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('recipient_infos', RecipientInfos),
+        ('encrypted_content_info', EncryptedContentInfo),
+        ('unprotected_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class SignedAndEnvelopedData(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('recipient_infos', RecipientInfos),
+        ('digest_algorithms', DigestAlgorithms),
+        ('encrypted_content_info', EncryptedContentInfo),
+        ('certificates', CertificateSet, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('crls', CertificateRevocationLists, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class AuthenticatedData(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('recipient_infos', RecipientInfos),
+        ('mac_algorithm', HmacAlgorithm),
+        ('digest_algorithm', DigestAlgorithm, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+        ('mac', OctetString),
+        ('unauth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
+    ]
+
+
+class AuthEnvelopedData(Sequence):
+    _fields = [
+        ('version', CMSVersion),
+        ('originator_info', OriginatorInfo, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('recipient_infos', RecipientInfos),
+        ('auth_encrypted_content_info', EncryptedContentInfo),
+        ('auth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('mac', OctetString),
+        ('unauth_attrs', CMSAttributes, {'tag_type': 'implicit', 'tag': 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
+
+
+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,
+}

+ 5099 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/core.py

@@ -0,0 +1,5099 @@
+# 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
+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
+
+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('^\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
+
+    # A unicode string or None - "explicit" or "implicit" for
+    # tagged values, None for normal
+    tag_type = None
+
+    # If "explicit"ly tagged, the class and tag for the wrapped header
+    explicit_class = None
+    explicit_tag = 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, tag_type=None, class_=None, tag=None, optional=None, default=None, contents=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 tag_type:
+            None for normal values, or one of "implicit", "explicit" for tagged
+            values
+
+        :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"
+
+        :param tag:
+            The integer tag to override - usually this is used with tag_type or
+            class_
+
+        :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
+
+        :raises:
+            ValueError - when tag_type, class_ or tag are invalid values
+        """
+
+        try:
+            if self.__class__ not in _SETUP_CLASSES:
+                cls = self.__class__
+                if hasattr(cls, '_setup'):
+                    self._setup()
+                _SETUP_CLASSES[cls] = True
+
+            if tag_type is not None:
+                if tag_type not in ('implicit', 'explicit'):
+                    raise ValueError(unwrap(
+                        '''
+                        tag_type must be one of "implicit", "explicit", not %s
+                        ''',
+                        repr(tag_type)
+                    ))
+                self.tag_type = tag_type
+
+                if class_ is None:
+                    class_ = 'context'
+                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_)
+                    ))
+                class_ = CLASS_NAME_TO_NUM_MAP[class_]
+
+                if tag is not None:
+                    if not isinstance(tag, int_types):
+                        raise TypeError(unwrap(
+                            '''
+                            tag must be an integer, not %s
+                            ''',
+                            type_name(tag)
+                        ))
+
+                if tag_type == 'implicit':
+                    self.class_ = class_
+                    self.tag = tag
+                else:
+                    self.explicit_class = class_
+                    self.explicit_tag = tag
+            else:
+                if class_ is not None:
+                    if class_ not in CLASS_NUM_TO_NAME_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 tag is not None:
+                    self.tag = tag
+
+            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 differnt 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.tag_type = self.tag_type
+        new_obj.class_ = self.class_
+        new_obj.tag = self.tag
+        new_obj.explicit_class = self.explicit_class
+        new_obj.explicit_tag = self.explicit_tag
+        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, tag_type, tag):
+        """
+        Copies the object, applying a new tagging to it
+
+        :param tag_type:
+            A unicode string of "implicit" or "explicit"
+
+        :param tag:
+            A integer tag number
+
+        :return:
+            An Asn1Value object
+        """
+
+        new_obj = self.__class__(tag_type=tag_type, tag=tag)
+        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, tag_type, _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 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)
+            trailer = b''
+
+            if self.tag_type == 'explicit':
+                container = Asn1Value()
+                container.method = 1
+                container.class_ = self.explicit_class
+                container.tag = self.explicit_tag
+                container.contents = header + self.contents + trailer
+                # Force the container to generate the header and footer
+                container.dump()
+                header = container._header + header
+                trailer += container._trailer
+
+            self._header = header
+            self._trailer = trailer
+
+        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.tag_type = self.tag_type
+        new_obj.class_ = self.class_
+        new_obj.explicit_class = self.explicit_class
+        new_obj.explicit_tag = self.explicit_tag
+        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
+
+    # Class attribute that indicates the offset into self.contents
+    # that contains the chunks of data to merge
+    _chunks_offset = 0
+
+    def _merge_chunks(self):
+        """
+        :return:
+            A concatenation of the native values of the contained chunks
+        """
+
+        if not self._indefinite:
+            return self._as_chunk()
+
+        pointer = self._chunks_offset
+        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.
+        """
+
+        if self._chunks_offset == 0:
+            return self.contents
+        return self.contents[self._chunks_offset:]
+
+    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)
+        self.method = other.method
+        self._indefinite = other._indefinite
+
+
+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 a 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 Ans1Value, 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 a 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
+                if self.tag_type == 'explicit':
+                    passed_params = {} if not spec_params else spec_params.copy()
+                    passed_params['tag_type'] = self.tag_type
+                    passed_params['tag'] = self.explicit_tag
+                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_type = None
+                self.tag = None
+                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
+
+    # 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, {'tag_type': 'explicit', 'tag': 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, tag_type=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 the
+            name and the value.
+
+        :param value:
+            The alternative value to set - used with name
+
+        :param tag_type:
+            The tag_type of the value - None, "implicit" or "explicit"
+
+        :raises:
+            ValueError - when tag_type is "implicit"
+        """
+
+        kwargs['tag_type'] = tag_type
+        Asn1Value.__init__(self, **kwargs)
+
+        try:
+            if tag_type == 'implicit':
+                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 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 not None:
+            return self._parsed
+
+        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
+
+    @property
+    def chosen(self):
+        """
+        :return:
+            An Asn1Value object of the chosen alternative
+        """
+
+        return self.parse()
+
+    @property
+    def native(self):
+        """
+        The a 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.tag_type == 'explicit':
+            if (self.explicit_class, self.explicit_tag) != 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
+        """
+
+        self.contents = self.chosen.dump(force=force)
+        if self._header is None or force:
+            if self.tag_type == 'explicit':
+                self._header = _dump_header(self.explicit_class, 1, self.explicit_tag, self.contents)
+            else:
+                self._header = b''
+        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 differnt 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 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.tag_type is not None or other.tag_type is not None:
+            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 a 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 a 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 a 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 BitString(Constructable, Castable, Primitive, ValueMap, object):
+    """
+    Represents a bit string from ASN.1 as a Python tuple of 1s and 0s
+    """
+
+    tag = 3
+
+    _size = None
+
+    # Used with _as_chunk() from Constructable
+    _chunk = None
+    _chunks_offset = 1
+
+    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)
+            ))
+
+        self._chunk = None
+
+        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._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)
+
+    def _as_chunk(self):
+        """
+        Allows reconstructing indefinite length values
+
+        :return:
+            A tuple of integers
+        """
+
+        extra_bits = int_from_bytes(self.contents[0:1])
+        bit_string = '{0:b}'.format(int_from_bytes(self.contents[1:]))
+        byte_len = len(self.contents[1:])
+        bit_len = len(bit_string)
+
+        # Left-pad the bit string to a byte multiple to ensure we didn't
+        # lose any zero bits on the left
+        mod_bit_len = bit_len % 8
+        if mod_bit_len != 0:
+            bit_string = ('0' * (8 - mod_bit_len)) + bit_string
+            bit_len = len(bit_string)
+
+        if bit_len // 8 < byte_len:
+            missing_bytes = byte_len - (bit_len // 8)
+            bit_string = ('0' * (8 * missing_bytes)) + bit_string
+
+        # Trim off the extra bits on the right used to fill the last byte
+        if extra_bits > 0:
+            bit_string = bit_string[0:0 - extra_bits]
+
+        return tuple(map(int, tuple(bit_string)))
+
+    @property
+    def native(self):
+        """
+        The a 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:
+            bits = self._merge_chunks()
+            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
+
+    # Whenever dealing with octet-based bit strings, we really want the
+    # bytes, so we just ignore the unused bits portion since it isn't
+    # applicable to the current use case
+    # unused_bits = struct.unpack('>B', self.contents[0:1])[0]
+    _chunks_offset = 1
+
+    # Instance attribute of (possibly-merged) byte string
+    _bytes = None
+
+    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 __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 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
+
+    @property
+    def native(self):
+        """
+        The a native Python datatype representation of this value
+
+        :return:
+            A byte string or None
+        """
+
+        if self.contents is None:
+            return None
+
+        return self.__bytes__()
+
+
+class IntegerBitString(Constructable, Castable, Primitive):
+    """
+    Represents a bit string in ASN.1 as a Python integer
+    """
+
+    tag = 3
+
+    _chunks_offset = 1
+
+    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 an integer, not %s
+                ''',
+                type_name(self),
+                type_name(value)
+            ))
+
+        self._native = value
+        # Set the unused bits to 0
+        self.contents = b'\x00' + int_to_bytes(value, signed=True)
+        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
+
+        :return:
+            A unicode string of bits – 1s and 0s
+        """
+
+        extra_bits = int_from_bytes(self.contents[0:1])
+        bit_string = '{0:b}'.format(int_from_bytes(self.contents[1:]))
+
+        # Ensure we have leading zeros since these chunks may be concatenated together
+        mod_bit_len = len(bit_string) % 8
+        if mod_bit_len != 0:
+            bit_string = ('0' * (8 - mod_bit_len)) + bit_string
+
+        if extra_bits > 0:
+            return bit_string[0:0 - extra_bits]
+
+        return bit_string
+
+    @property
+    def native(self):
+        """
+        The a native Python datatype representation of this value
+
+        :return:
+            An integer or None
+        """
+
+        if self.contents is None:
+            return None
+
+        if self._native is None:
+            extra_bits = int_from_bytes(self.contents[0:1])
+            # Fast path
+            if not self._indefinite and extra_bits == 0:
+                self._native = int_from_bytes(self.contents[1:])
+            else:
+                if self._indefinite and extra_bits > 0:
+                    raise ValueError('Constructed bit string has extra bits on indefinite container')
+                self._native = int(self._merge_chunks(), 2)
+        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 a 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
+
+    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 an integer, not %s
+                ''',
+                type_name(self),
+                type_name(value)
+            ))
+
+        self._native = value
+        self.contents = int_to_bytes(value, signed=False)
+        self._header = None
+        if self._indefinite:
+            self._indefinite = False
+            self.method = 0
+        if self._trailer != b'':
+            self._trailer = b''
+
+    @property
+    def native(self):
+        """
+        The a 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
+
+
+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 _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 a 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 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
+
+    # Whenever dealing with octet-based bit strings, we really want the
+    # bytes, so we just ignore the unused bits portion since it isn't
+    # applicable to the current use case
+    # unused_bits = struct.unpack('>B', self.contents[0:1])[0]
+    _chunks_offset = 1
+
+    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''
+
+
+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 a 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:
+                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:
+                        output.append(str_cls(part // 40))
+                        output.append(str_cls(part % 40))
+                    else:
+                        output.append(str_cls(part))
+                    part = 0
+
+            self._dotted = '.'.join(output)
+        return self._dotted
+
+    @property
+    def native(self):
+        """
+        The a 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 a 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, {'tag_type': 'explicit', 'tag': 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
+        elif isinstance(new_value, Choice):
+            invalid_value = new_value.chosen.contents 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
+             - Ans1Value 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 fomr 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):
+            if not isinstance(value, Asn1Value):
+                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 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):
+            new_value = value_spec(value, **field_params)
+
+        else:
+            if isinstance(value, value_spec):
+                new_value = value
+            else:
+                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:
+            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 preceeding field values before calling this. Usually
+        specs, if dynamic, are controlled by a preceeding 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 a 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:
+                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 force:
+            self._set_contents(force=force)
+
+        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.tag_type is not None:
+            params['tag_type'] = self._child_spec.tag_type
+            if params['tag_type'] == 'explicit':
+                params['tag'] = self._child_spec.explicit_tag
+            else:
+                params['tag'] = 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:
+            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 a 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 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 native(self):
+        """
+        The a native Python datatype representation of this value
+
+        :return:
+            A datetime.datetime object in the UTC timezone or None
+        """
+
+        if self.contents is None:
+            return None
+
+        if self._native is None:
+            string = str_cls(self)
+            has_timezone = re.search('[-\\+]', string)
+
+            # We don't know what timezone it is in, or it is UTC because of a Z
+            # suffix, so we just assume UTC
+            if not has_timezone:
+                string = string.rstrip('Z')
+                date = self._date_by_len(string)
+                self._native = date.replace(tzinfo=timezone.utc)
+
+            else:
+                # Python 2 doesn't support the %z format code, so we have to manually
+                # process the timezone offset.
+                date = self._date_by_len(string[0:-5])
+
+                hours = int(string[-4:-2])
+                minutes = int(string[-2:])
+                delta = timedelta(hours=abs(hours), minutes=minutes)
+                if hours < 0:
+                    date -= delta
+                else:
+                    date += delta
+
+                self._native = date.replace(tzinfo=timezone.utc)
+
+        return self._native
+
+
+class UTCTime(AbstractTime):
+    """
+    Represents a UTC time from ASN.1 as a Python datetime.datetime object in UTC
+    """
+
+    tag = 23
+
+    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):
+            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 _date_by_len(self, string):
+        """
+        Parses a date from a string based on its length
+
+        :param string:
+            A unicode string to parse
+
+        :return:
+            A datetime.datetime object or a unicode string
+        """
+
+        strlen = len(string)
+
+        year_num = int(string[0:2])
+        if year_num < 50:
+            prefix = '20'
+        else:
+            prefix = '19'
+
+        if strlen == 10:
+            return datetime.strptime(prefix + string, '%Y%m%d%H%M')
+
+        if strlen == 12:
+            return datetime.strptime(prefix + string, '%Y%m%d%H%M%S')
+
+        return string
+
+
+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
+
+    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)):
+            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 _date_by_len(self, string):
+        """
+        Parses a date from a string based on its length
+
+        :param string:
+            A unicode string to parse
+
+        :return:
+            A datetime.datetime object, asn1crypto.util.extended_datetime object or
+            a unicode string
+        """
+
+        strlen = len(string)
+
+        date_format = None
+        if strlen == 10:
+            date_format = '%Y%m%d%H'
+        elif strlen == 12:
+            date_format = '%Y%m%d%H%M'
+        elif strlen == 14:
+            date_format = '%Y%m%d%H%M%S'
+        elif strlen == 18:
+            date_format = '%Y%m%d%H%M%S.%f'
+
+        if date_format:
+            if len(string) >= 4 and string[0:4] == '0000':
+                # Year 2000 shares a calendar with year 0, and is supported natively
+                t = datetime.strptime('2000' + string[4:], date_format)
+                return extended_datetime(
+                    0,
+                    t.month,
+                    t.day,
+                    t.hour,
+                    t.minute,
+                    t.second,
+                    t.microsecond,
+                    t.tzinfo
+                )
+            return datetime.strptime(string, date_format)
+
+        return string
+
+
+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.tag_type == 'explicit':
+        print(
+            '%s    %s tag %s (explicitly tagged)' %
+            (
+                prefix,
+                CLASS_NUM_TO_NAME_MAP.get(self.explicit_class),
+                self.explicit_tag
+            )
+        )
+        if has_header:
+            print('%s      %s %s %s' % (prefix, method_name, class_name, self.tag))
+
+    elif self.tag_type == '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))
+
+    print('%s  Data: 0x%s' % (prefix, binascii.hexlify(self.contents or b'').decode('utf-8')))
+
+
+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
+    """
+
+    if 'tag_type' in params:
+        required_tag_type = params['tag_type']
+        retag = False
+
+        if required_tag_type != value.tag_type:
+            retag = True
+
+        elif required_tag_type == 'explicit' and value.explicit_tag != params['tag']:
+            retag = True
+
+        elif required_tag_type == 'implicit' and value.tag != params['tag']:
+            retag = True
+
+        if retag:
+            return value.retag(params['tag_type'], params['tag'])
+        return value
+
+    if value.tag_type:
+        return value.untag()
+
+    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 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 = params.get('tag_type', spec.tag_type)
+    if tag_type is not None:
+        required_class = 2
+
+    required_class = params.get('class_', required_class)
+    required_tag = params.get('tag', required_tag)
+
+    return (required_class, required_tag)
+
+
+_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 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 spec_params:
+            value = spec(contents=contents, **spec_params)
+        else:
+            value = spec(contents=contents)
+
+        if spec is Any:
+            pass
+
+        elif value.tag_type == 'explicit':
+            if class_ != value.explicit_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(value.explicit_class),
+                    CLASS_NUM_TO_NAME_MAP.get(class_, class_)
+                ))
+            if 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(method, method)
+                ))
+            if tag != value.explicit_tag:
+                raise ValueError(unwrap(
+                    '''
+                    Error parsing %s - explicitly-tagged tag should have been
+                    %s, but %s was found
+                    ''',
+                    type_name(value),
+                    value.explicit_tag,
+                    tag
+                ))
+            original_value = value
+            info, _ = _parse(contents, len(contents))
+            value = _build(*info, spec=spec)
+            value._header = header + value._header
+            value._trailer += trailer or b''
+            value.tag_type = 'explicit'
+            value.explicit_class = original_value.explicit_class
+            value.explicit_tag = original_value.explicit_tag
+            header_set = True
+
+        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 and tag != value._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 'tag_type' in spec_params and spec_params['tag_type'] == 'explicit':
+        original_value = Asn1Value(contents=contents, **spec_params)
+        info, _ = _parse(contents, len(contents))
+        value = _build(*info, spec=spec)
+        value._header = header + value._header
+        value._trailer += trailer or b''
+        value.tag_type = 'explicit'
+        value.explicit_class = original_value.explicit_class
+        value.explicit_tag = original_value.explicit_tag
+        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)

+ 536 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('only_contains_user_certs', Boolean, {'tag_type': 'implicit', 'tag': 1, 'default': False}),
+        ('only_contains_ca_certs', Boolean, {'tag_type': 'implicit', 'tag': 2, 'default': False}),
+        ('only_some_reasons', ReasonFlags, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
+        ('indirect_crl', Boolean, {'tag_type': 'implicit', 'tag': 4, 'default': False}),
+        ('only_contains_attribute_certs', Boolean, {'tag_type': 'implicit', 'tag': 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),
+        ('revoked_certificates', RevokedCertificates, {'optional': True}),
+        ('crl_extensions', TBSCertListExtensions, {'tag_type': 'explicit', 'tag': 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

+ 96 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/csr.py

@@ -0,0 +1,96 @@
+# coding: utf-8
+
+"""
+ASN.1 type classes for certificate signing requests (CSR). Exports the
+following items:
+
+ - CertificatationRequest()
+
+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,
+    Integer,
+    ObjectIdentifier,
+    OctetBitString,
+    Sequence,
+    SetOf,
+)
+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',
+    }
+
+
+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 CRIAttribute(Sequence):
+    _fields = [
+        ('type', CSRAttributeType),
+        ('values', Any),
+    ]
+
+    _oid_pair = ('type', 'values')
+    _oid_specs = {
+        'challenge_password': SetOfDirectoryString,
+        'extended_certificate_attributes': SetOfAttributes,
+        'extension_request': SetOfExtensions,
+    }
+
+
+class CRIAttributes(SetOf):
+    _child_spec = CRIAttribute
+
+
+class CertificationRequestInfo(Sequence):
+    _fields = [
+        ('version', Version),
+        ('subject', Name),
+        ('subject_pk_info', PublicKeyInfo),
+        ('attributes', CRIAttributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+    ]
+
+
+class CertificationRequest(Sequence):
+    _fields = [
+        ('certification_request_info', CertificationRequestInfo),
+        ('signature_algorithm', SignedDigestAlgorithm),
+        ('signature', OctetBitString),
+    ]

+ 1245 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/keys.py

@@ -0,0 +1,1245 @@
+# 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 ._elliptic_curve import (
+    SECP192R1_BASE_POINT,
+    SECP224R1_BASE_POINT,
+    SECP256R1_BASE_POINT,
+    SECP384R1_BASE_POINT,
+    SECP521R1_BASE_POINT,
+    PrimeCurve,
+    PrimePoint,
+)
+from ._errors import unwrap
+from ._types import type_name, str_cls, byte_cls
+from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm
+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
+        '1.3.132.0.1': 'sect163k1',
+        '1.3.132.0.15': 'sect163r2',
+        '1.2.840.10045.3.1.1': 'secp192r1',
+        '1.3.132.0.33': 'secp224r1',
+        '1.3.132.0.26': 'sect233k1',
+        '1.2.840.10045.3.1.7': 'secp256r1',
+        '1.3.132.0.27': 'sect233r1',
+        '1.3.132.0.16': 'sect283k1',
+        '1.3.132.0.17': 'sect283r1',
+        '1.3.132.0.34': 'secp384r1',
+        '1.3.132.0.36': 'sect409k1',
+        '1.3.132.0.37': 'sect409r1',
+        '1.3.132.0.35': 'secp521r1',
+        '1.3.132.0.38': 'sect571k1',
+        '1.3.132.0.39': 'sect571r1',
+    }
+
+
+class ECDomainParameters(Choice):
+    """
+    Source: http://www.secg.org/sec1-v2.pdf page 102
+    """
+
+    _alternatives = [
+        ('specified', SpecifiedECDomain),
+        ('named', NamedCurve),
+        ('implicit_ca', Null),
+    ]
+
+
+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, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('public_key', ECPointBitString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+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/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',
+    }
+
+
+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,
+    }
+
+
+class PrivateKeyInfo(Sequence):
+    """
+    Source: https://tools.ietf.org/html/rfc5208#page-3
+    """
+
+    _fields = [
+        ('version', Integer),
+        ('private_key_algorithm', PrivateKeyAlgorithm),
+        ('private_key', ParsableOctetString),
+        ('attributes', Attributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+    ]
+
+    def _private_key_spec(self):
+        algorithm = self['private_key_algorithm']['algorithm'].native
+        return {
+            'rsa': RSAPrivateKey,
+            'dsa': Integer,
+            'ec': ECPrivateKey,
+        }[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':
+            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
+
+    def _compute_public_key(self):
+        """
+        Computes the public key corresponding to the current private key.
+
+        :return:
+            For RSA keys, an RSAPublicKey object. For DSA keys, an Integer
+            object. For EC keys, an ECPointBitString.
+        """
+
+        if self.algorithm == 'dsa':
+            params = self['private_key_algorithm']['parameters']
+            return Integer(pow(
+                params['g'].native,
+                self['private_key'].parsed.native,
+                params['p'].native
+            ))
+
+        if self.algorithm == 'rsa':
+            key = self['private_key'].parsed
+            return RSAPublicKey({
+                'modulus': key['modulus'],
+                'public_exponent': key['public_exponent'],
+            })
+
+        if self.algorithm == 'ec':
+            curve_type, details = self.curve
+
+            if curve_type == 'implicit_ca':
+                raise ValueError(unwrap(
+                    '''
+                    Unable to compute public key for EC key using Implicit CA
+                    parameters
+                    '''
+                ))
+
+            if curve_type == 'specified':
+                if details['field_id']['field_type'] == 'characteristic_two_field':
+                    raise ValueError(unwrap(
+                        '''
+                        Unable to compute public key for EC key over a
+                        characteristic two field
+                        '''
+                    ))
+
+                curve = PrimeCurve(
+                    details['field_id']['parameters'],
+                    int_from_bytes(details['curve']['a']),
+                    int_from_bytes(details['curve']['b'])
+                )
+                base_x, base_y = self['private_key_algorithm']['parameters'].chosen['base'].to_coords()
+                base_point = PrimePoint(curve, base_x, base_y)
+
+            elif curve_type == 'named':
+                if details not in ('secp192r1', 'secp224r1', 'secp256r1', 'secp384r1', 'secp521r1'):
+                    raise ValueError(unwrap(
+                        '''
+                        Unable to compute public key for EC named curve %s,
+                        parameters not currently included
+                        ''',
+                        details
+                    ))
+
+                base_point = {
+                    'secp192r1': SECP192R1_BASE_POINT,
+                    'secp224r1': SECP224R1_BASE_POINT,
+                    'secp256r1': SECP256R1_BASE_POINT,
+                    'secp384r1': SECP384R1_BASE_POINT,
+                    'secp521r1': SECP521R1_BASE_POINT,
+                }[details]
+
+            public_point = base_point * self['private_key'].parsed['private_key'].native
+            return ECPointBitString.from_coords(public_point.x, public_point.y)
+
+    def unwrap(self):
+        """
+        Unwraps the private key into an RSAPrivateKey, DSAPrivateKey or
+        ECPrivateKey object
+
+        :return:
+            An RSAPrivateKey, DSAPrivateKey or ECPrivateKey object
+        """
+
+        if self.algorithm == 'rsa':
+            return self['private_key'].parsed
+
+        if self.algorithm == 'dsa':
+            params = self['private_key_algorithm']['parameters']
+            return DSAPrivateKey({
+                'version': 0,
+                'p': params['p'],
+                'q': params['q'],
+                'g': params['g'],
+                'public_key': self.public_key,
+                'private_key': self['private_key'].parsed,
+            })
+
+        if self.algorithm == 'ec':
+            output = self['private_key'].parsed
+            output['parameters'] = self['private_key_algorithm']['parameters']
+            output['public_key'] = self.public_key
+            return output
+
+    @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", "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':
+                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.
+        """
+
+        if self._public_key is None:
+            if self.algorithm == 'ec':
+                key = self['private_key'].parsed
+                if key['public_key']:
+                    self._public_key = key['public_key'].untag()
+                else:
+                    self._public_key = self._compute_public_key()
+            else:
+                self._public_key = self._compute_public_key()
+
+        return self._public_key
+
+    @property
+    def public_key_info(self):
+        """
+        :return:
+            A PublicKeyInfo object derived from this private key.
+        """
+
+        return PublicKeyInfo({
+            'algorithm': {
+                'algorithm': self.algorithm,
+                'parameters': self['private_key_algorithm']['parameters']
+            },
+            'public_key': self.public_key
+        })
+
+    @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 compatiable with fingerprints generated by any
+        other software.
+
+        :return:
+            A byte string that is a sha256 hash of selected components (based
+            on the key type)
+        """
+
+        if self._fingerprint is None:
+            params = self['private_key_algorithm']['parameters']
+            key = self['private_key'].parsed
+
+            if self.algorithm == 'rsa':
+                to_hash = '%d:%d' % (
+                    key['modulus'].native,
+                    key['public_exponent'].native,
+                )
+
+            elif self.algorithm == 'dsa':
+                public_key = self.public_key
+                to_hash = '%d:%d:%d:%d' % (
+                    params['p'].native,
+                    params['q'].native,
+                    params['g'].native,
+                    public_key.native,
+                )
+
+            elif self.algorithm == 'ec':
+                public_key = key['public_key'].native
+                if public_key is None:
+                    public_key = self.public_key.native
+
+                if params.name == 'named':
+                    to_hash = '%s:' % params.chosen.native
+                    to_hash = to_hash.encode('utf-8')
+                    to_hash += public_key
+
+                elif params.name == 'implicit_ca':
+                    to_hash = public_key
+
+                elif params.name == 'specified':
+                    to_hash = '%s:' % params.chosen['field_id']['parameters'].native
+                    to_hash = to_hash.encode('utf-8')
+                    to_hash += b':' + params.chosen['curve']['a'].native
+                    to_hash += b':' + params.chosen['curve']['b'].native
+                    to_hash += public_key
+
+            if isinstance(to_hash, str_cls):
+                to_hash = to_hash.encode('utf-8')
+
+            self._fingerprint = hashlib.sha256(to_hash).digest()
+
+        return self._fingerprint
+
+
+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/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',
+    }
+
+
+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,
+    }
+
+
+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,
+            '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,
+        }[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':
+            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
+        """
+
+        if self.algorithm == 'rsa':
+            return self['public_key'].parsed
+
+        key_type = self.algorithm.upper()
+        a_an = 'an' if key_type == 'EC' else 'a'
+        raise ValueError(unwrap(
+            '''
+            Only RSA public keys may be unwrapped - this key is %s %s public
+            key
+            ''',
+            a_an,
+            key_type
+        ))
+
+    @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", "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 = ((len(self['public_key'].native) - 1) / 2) * 8
+            else:
+                if self.algorithm == 'rsa':
+                    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 compatiable with fingerprints generated by any
+        other software.
+
+        :return:
+            A byte string that is a sha256 hash of selected components (based
+            on the key type)
+        """
+
+        if self._fingerprint is None:
+            key_type = self['algorithm']['algorithm'].native
+            params = self['algorithm']['parameters']
+
+            if key_type == 'rsa':
+                key = self['public_key'].parsed
+                to_hash = '%d:%d' % (
+                    key['modulus'].native,
+                    key['public_exponent'].native,
+                )
+
+            elif key_type == 'dsa':
+                key = self['public_key'].parsed
+                to_hash = '%d:%d:%d:%d' % (
+                    params['p'].native,
+                    params['q'].native,
+                    params['g'].native,
+                    key.native,
+                )
+
+            elif key_type == 'ec':
+                key = self['public_key']
+
+                if params.name == 'named':
+                    to_hash = '%s:' % params.chosen.native
+                    to_hash = to_hash.encode('utf-8')
+                    to_hash += key.native
+
+                elif params.name == 'implicit_ca':
+                    to_hash = key.native
+
+                elif params.name == 'specified':
+                    to_hash = '%s:' % params.chosen['field_id']['parameters'].native
+                    to_hash = to_hash.encode('utf-8')
+                    to_hash += b':' + params.chosen['curve']['a'].native
+                    to_hash += b':' + params.chosen['curve']['b'].native
+                    to_hash += key.native
+
+            if isinstance(to_hash, str_cls):
+                to_hash = to_hash.encode('utf-8')
+
+            self._fingerprint = hashlib.sha256(to_hash).digest()
+
+        return self._fingerprint

+ 649 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/ocsp.py

@@ -0,0 +1,649 @@
+# 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 .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, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}),
+        ('requestor_name', GeneralName, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+        ('request_list', Requests),
+        ('request_extensions', TBSRequestExtensions, {'tag_type': 'explicit', 'tag': 2, 'optional': True}),
+    ]
+
+
+class Certificates(SequenceOf):
+    _child_spec = Certificate
+
+
+class Signature(Sequence):
+    _fields = [
+        ('signature_algorithm', SignedDigestAlgorithm),
+        ('signature', OctetBitString),
+        ('certs', Certificates, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+    ]
+
+
+class OCSPRequest(Sequence):
+    _fields = [
+        ('tbs_request', TBSRequest),
+        ('optional_signature', Signature, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 1}),
+        ('by_key', OctetString, {'tag_type': 'explicit', 'tag': 2}),
+    ]
+
+
+class RevokedInfo(Sequence):
+    _fields = [
+        ('revocation_time', GeneralizedTime),
+        ('revocation_reason', CRLReason, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+    ]
+
+
+class CertStatus(Choice):
+    _alternatives = [
+        ('good', Null, {'tag_type': 'implicit', 'tag': 0}),
+        ('revoked', RevokedInfo, {'tag_type': 'implicit', 'tag': 1}),
+        ('unknown', Null, {'tag_type': 'implicit', 'tag': 2}),
+    ]
+
+
+class CrlId(Sequence):
+    _fields = [
+        ('crl_url', IA5String, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('crl_num', Integer, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+        ('crl_time', GeneralizedTime, {'tag_type': 'explicit', 'tag': 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',
+    }
+
+
+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,
+    }
+
+
+class SingleResponseExtensions(SequenceOf):
+    _child_spec = SingleResponseExtension
+
+
+class SingleResponse(Sequence):
+    _fields = [
+        ('cert_id', CertId),
+        ('cert_status', CertStatus),
+        ('this_update', GeneralizedTime),
+        ('next_update', GeneralizedTime, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('single_extensions', SingleResponseExtensions, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}),
+        ('responder_id', ResponderId),
+        ('produced_at', GeneralizedTime),
+        ('responses', Responses),
+        ('response_extensions', ResponseDataExtensions, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class BasicOCSPResponse(Sequence):
+    _fields = [
+        ('tbs_response_data', ResponseData),
+        ('signature_algorithm', SignedDigestAlgorithm),
+        ('signature', OctetBitString),
+        ('certs', Certificates, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 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']

+ 289 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/parser.py

@@ -0,0 +1,289 @@
+# 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'
+
+
+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):
+    """
+    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.
+
+    :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 data_len < pointer + 2:
+        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (2, data_len - pointer))
+
+    start = pointer
+    first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+    pointer += 1
+
+    tag = first_octet & 31
+    # Base 128 length using 8th bit as continuation indicator
+    if tag == 31:
+        tag = 0
+        while True:
+            num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+            pointer += 1
+            tag *= 128
+            tag += num & 127
+            if num >> 7 == 0:
+                break
+
+    length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
+    pointer += 1
+
+    if length_octet >> 7 == 0:
+        if lengths_only:
+            return (pointer, pointer + (length_octet & 127))
+        contents_end = pointer + (length_octet & 127)
+
+    else:
+        length_octets = length_octet & 127
+        if length_octets:
+            pointer += length_octets
+            contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)
+            if lengths_only:
+                return (pointer, contents_end)
+
+        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.
+            contents_end = pointer
+            # Unfortunately we need to understand the contents of the data to
+            # properly scan forward, which bleeds some representation info into
+            # the parser. This condition handles the unused bits byte in
+            # constructed bit strings.
+            if tag == 3:
+                contents_end += 1
+            while contents_end < data_len:
+                sub_header_end, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True)
+                if contents_end == sub_header_end and encoded_data[contents_end - 2:contents_end] == b'\x00\x00':
+                    break
+            if lengths_only:
+                return (pointer, contents_end)
+            if contents_end > data_len:
+                raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
+            return (
+                (
+                    first_octet >> 6,
+                    (first_octet >> 5) & 1,
+                    tag,
+                    encoded_data[start:pointer],
+                    encoded_data[pointer:contents_end - 2],
+                    b'\x00\x00'
+                ),
+                contents_end
+            )
+
+    if contents_end > data_len:
+        raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
+    return (
+        (
+            first_octet >> 6,
+            (first_octet >> 5) & 1,
+            tag,
+            encoded_data[start:pointer],
+            encoded_data[pointer:contents_end],
+            b''
+        ),
+        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:
+        header += chr_cls(id_num | 31)
+        while tag > 0:
+            continuation_bit = 0x80 if tag > 0x7F else 0
+            header += chr_cls(continuation_bit | (tag & 0x7F))
+            tag = tag >> 7
+    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

+ 84 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('ocsp', SequenceOfOCSPResponse, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+        ('other_rev_info', SequenceOfOtherRevInfo, {'tag_type': 'explicit', 'tag': 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

+ 222 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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, 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 der_bytes:
+        A byte string to be armored
+
+    :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 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

+ 193 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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, {'tag_type': 'explicit', 'tag': 0}),
+    ]
+
+    _oid_pair = ('cert_id', 'cert_value')
+    _oid_specs = {
+        'x509': Certificate,
+    }
+
+
+class CrlBag(Sequence):
+    _fields = [
+        ('crl_id', ObjectIdentifier),
+        ('crl_value', OctetString, {'tag_type': 'explicit', 'tag': 0}),
+    ]
+
+
+class SecretBag(Sequence):
+    _fields = [
+        ('secret_type_id', ObjectIdentifier),
+        ('secret_value', OctetString, {'tag_type': 'explicit', 'tag': 0}),
+    ]
+
+
+class SafeContents(SequenceOf):
+    pass
+
+
+class SafeBag(Sequence):
+    _fields = [
+        ('bag_id', BagId),
+        ('bag_value', Any, {'tag_type': 'explicit', 'tag': 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

+ 310 - 0
desktop/core/ext-py/asn1crypto-0.22.0/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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('micros', Integer, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('extensions', Extensions, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 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(SequenceOf):
+    _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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('attributes', Attributes, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('reduced_hashtree', PartialHashtrees, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+        ('time_stamp', ContentInfo),
+    ]
+
+
+class ArchiveTimeStampSequence(SequenceOf):
+    _child_spec = ArchiveTimeStamp
+
+
+class EvidenceRecord(Sequence):
+    _fields = [
+        ('version', Version),
+        ('digest_algorithms', DigestAlgorithms),
+        ('crypto_infos', Attributes, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('encryption_info', EncryptionInfo, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('ers_evidence', EvidenceRecord, {'tag_type': 'implicit', 'tag': 1}),
+        ('other_evidence', OtherEvidence, {'tag_type': 'implicit', 'tag': 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

+ 712 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/util.py

@@ -0,0 +1,712 @@
+# 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
+ - 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, time
+
+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,):
+
+    from datetime import timedelta, tzinfo
+
+    py2 = True
+
+    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:
+            None == auto, otherwise an integer of the byte width for the return
+            value
+
+        :return:
+            A byte string
+        """
+
+        # 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 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 utc(tzinfo):  # noqa
+
+        def tzname(self, _):
+            return b'UTC+00:00'
+
+        def utcoffset(self, _):
+            return timedelta(0)
+
+        def dst(self, _):
+            return timedelta(0)
+
+    class timezone():  # noqa
+
+        utc = utc()
+
+
+# Python 3
+else:
+
+    from datetime import timezone  # noqa
+
+    py2 = False
+
+    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:
+            None == auto, 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)
+
+
+_DAYS_PER_MONTH_YEAR_0 = {
+    1: 31,
+    2: 29,  # Year 0 was a leap year
+    3: 31,
+    4: 30,
+    5: 31,
+    6: 30,
+    7: 31,
+    8: 31,
+    9: 30,
+    10: 31,
+    11: 30,
+    12: 31
+}
+
+
+class extended_date(object):
+    """
+    A datetime.date-like object that can represent the year 0. This is just
+    to handle 0000-01-01 found in some certificates.
+    """
+
+    year = None
+    month = None
+    day = None
+
+    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')
+
+        if month < 1 or month > 12:
+            raise ValueError('month is out of range')
+
+        if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
+            raise ValueError('day is out of range')
+
+        self.year = year
+        self.month = month
+        self.day = day
+
+    def _format(self, format):
+        """
+        Performs strftime(), always returning a unicode string
+
+        :param format:
+            A strftime() format string
+
+        :return:
+            A unicode string of the formatted date
+        """
+
+        format = format.replace('%Y', '0000')
+        # Year 0 is 1BC and a leap year. Leap years repeat themselves
+        # every 28 years. Because of adjustments and the proleptic gregorian
+        # calendar, the simplest way to format is to substitute year 2000.
+        temp = date(2000, self.month, self.day)
+        if '%c' in format:
+            c_out = temp.strftime('%c')
+            # Handle full years
+            c_out = c_out.replace('2000', '0000')
+            c_out = c_out.replace('%', '%%')
+            format = format.replace('%c', c_out)
+        if '%x' in format:
+            x_out = temp.strftime('%x')
+            # Handle formats such as 08/16/2000 or 16.08.2000
+            x_out = x_out.replace('2000', '0000')
+            x_out = x_out.replace('%', '%%')
+            format = format.replace('%x', x_out)
+        return temp.strftime(format)
+
+    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 strftime(self, format):
+        """
+        Formats the date using strftime()
+
+        :param format:
+            The strftime() format string
+
+        :return:
+            The formatted date as a unicode string in Python 3 and a byte
+            string in Python 2
+        """
+
+        output = self._format(format)
+        if py2:
+            return output.encode('utf-8')
+        return output
+
+    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):
+        if py2:
+            return self.__bytes__()
+        else:
+            return self.__unicode__()
+
+    def __bytes__(self):
+        return self.__unicode__().encode('utf-8')
+
+    def __unicode__(self):
+        return self._format('%Y-%m-%d')
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return False
+        return self.__cmp__(other) == 0
+
+    def __ne__(self, other):
+        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):
+        if isinstance(other, date):
+            return -1
+
+        if not isinstance(other, self.__class__):
+            self._comparison_error(other)
+
+        st = (
+            self.year,
+            self.month,
+            self.day
+        )
+        ot = (
+            other.year,
+            other.month,
+            other.day
+        )
+
+        if st < ot:
+            return -1
+        if st > ot:
+            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 can represent the year 0. This is just
+    to handle 0000-01-01 found in some certificates.
+    """
+
+    year = None
+    month = None
+    day = None
+    hour = None
+    minute = None
+    second = None
+    microsecond = None
+    tzinfo = None
+
+    def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
+        """
+        :param year:
+            The integer 0
+
+        :param month:
+            An integer from 1 to 12
+
+        :param day:
+            An integer from 1 to 31
+
+        :param hour:
+            An integer from 0 to 23
+
+        :param minute:
+            An integer from 0 to 59
+
+        :param second:
+            An integer from 0 to 59
+
+        :param microsecond:
+            An integer from 0 to 999999
+        """
+
+        if year != 0:
+            raise ValueError('year must be 0')
+
+        if month < 1 or month > 12:
+            raise ValueError('month is out of range')
+
+        if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
+            raise ValueError('day is out of range')
+
+        if hour < 0 or hour > 23:
+            raise ValueError('hour is out of range')
+
+        if minute < 0 or minute > 59:
+            raise ValueError('minute is out of range')
+
+        if second < 0 or second > 59:
+            raise ValueError('second is out of range')
+
+        if microsecond < 0 or microsecond > 999999:
+            raise ValueError('microsecond is out of range')
+
+        self.year = year
+        self.month = month
+        self.day = day
+        self.hour = hour
+        self.minute = minute
+        self.second = second
+        self.microsecond = microsecond
+        self.tzinfo = tzinfo
+
+    def date(self):
+        """
+        :return:
+            An asn1crypto.util.extended_date of the date
+        """
+
+        return extended_date(self.year, self.month, self.day)
+
+    def time(self):
+        """
+        :return:
+            A datetime.time object of the time
+        """
+
+        return time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo)
+
+    def utcoffset(self):
+        """
+        :return:
+            None or a datetime.timedelta() of the offset from UTC
+        """
+
+        if self.tzinfo is None:
+            return None
+        return self.tzinfo.utcoffset(self.replace(year=2000))
+
+    def dst(self):
+        """
+        :return:
+            None or a datetime.timedelta() of the daylight savings time offset
+        """
+
+        if self.tzinfo is None:
+            return None
+        return self.tzinfo.dst(self.replace(year=2000))
+
+    def tzname(self):
+        """
+        :return:
+            None or the name of the timezone as a unicode string in Python 3
+            and a byte string in Python 2
+        """
+
+        if self.tzinfo is None:
+            return None
+        return self.tzinfo.tzname(self.replace(year=2000))
+
+    def _format(self, format):
+        """
+        Performs strftime(), always returning a unicode string
+
+        :param format:
+            A strftime() format string
+
+        :return:
+            A unicode string of the formatted datetime
+        """
+
+        format = format.replace('%Y', '0000')
+        # Year 0 is 1BC and a leap year. Leap years repeat themselves
+        # every 28 years. Because of adjustments and the proleptic gregorian
+        # calendar, the simplest way to format is to substitute year 2000.
+        temp = datetime(
+            2000,
+            self.month,
+            self.day,
+            self.hour,
+            self.minute,
+            self.second,
+            self.microsecond,
+            self.tzinfo
+        )
+        if '%c' in format:
+            c_out = temp.strftime('%c')
+            # Handle full years
+            c_out = c_out.replace('2000', '0000')
+            c_out = c_out.replace('%', '%%')
+            format = format.replace('%c', c_out)
+        if '%x' in format:
+            x_out = temp.strftime('%x')
+            # Handle formats such as 08/16/2000 or 16.08.2000
+            x_out = x_out.replace('2000', '0000')
+            x_out = x_out.replace('%', '%%')
+            format = format.replace('%x', x_out)
+        return temp.strftime(format)
+
+    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
+        """
+
+        if self.microsecond == 0:
+            return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S' % sep)
+        return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S.%%f' % sep)
+
+    def strftime(self, format):
+        """
+        Formats the date using strftime()
+
+        :param format:
+            The strftime() format string
+
+        :return:
+            The formatted date as a unicode string in Python 3 and a byte
+            string in Python 2
+        """
+
+        output = self._format(format)
+        if py2:
+            return output.encode('utf-8')
+        return output
+
+    def replace(self, year=None, month=None, day=None, hour=None, minute=None,
+                second=None, microsecond=None, tzinfo=None):
+        """
+        Returns a new datetime.datetime or asn1crypto.util.extended_datetime
+        object with the specified components replaced
+
+        :return:
+            A datetime.datetime or asn1crypto.util.extended_datetime object
+        """
+
+        if year is None:
+            year = self.year
+        if month is None:
+            month = self.month
+        if day is None:
+            day = self.day
+        if hour is None:
+            hour = self.hour
+        if minute is None:
+            minute = self.minute
+        if second is None:
+            second = self.second
+        if microsecond is None:
+            microsecond = self.microsecond
+        if tzinfo is None:
+            tzinfo = self.tzinfo
+
+        if year > 0:
+            cls = datetime
+        else:
+            cls = extended_datetime
+
+        return cls(
+            year,
+            month,
+            day,
+            hour,
+            minute,
+            second,
+            microsecond,
+            tzinfo
+        )
+
+    def __str__(self):
+        if py2:
+            return self.__bytes__()
+        else:
+            return self.__unicode__()
+
+    def __bytes__(self):
+        return self.__unicode__().encode('utf-8')
+
+    def __unicode__(self):
+        format = '%Y-%m-%d %H:%M:%S'
+        if self.microsecond != 0:
+            format += '.%f'
+        return self._format(format)
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            return False
+        return self.__cmp__(other) == 0
+
+    def __ne__(self, other):
+        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):
+        so = self.utcoffset()
+        oo = other.utcoffset()
+
+        if (so is not None and oo is None) or (so is None and oo is not None):
+            raise TypeError("can't compare offset-naive and offset-aware datetimes")
+
+        if isinstance(other, datetime):
+            return -1
+
+        if not isinstance(other, self.__class__):
+            self._comparison_error(other)
+
+        st = (
+            self.year,
+            self.month,
+            self.day,
+            self.hour,
+            self.minute,
+            self.second,
+            self.microsecond,
+            so
+        )
+        ot = (
+            other.year,
+            other.month,
+            other.day,
+            other.hour,
+            other.minute,
+            other.second,
+            other.microsecond,
+            oo
+        )
+
+        if st < ot:
+            return -1
+        if st > ot:
+            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

+ 6 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/version.py

@@ -0,0 +1,6 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+
+__version__ = '0.22.0'
+__version_info__ = (0, 22, 0)

+ 2681 - 0
desktop/core/ext-py/asn1crypto-0.22.0/asn1crypto/x509.py

@@ -0,0 +1,2681 @@
+# 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 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, SignedDigestAlgorithm
+from .core import (
+    Any,
+    BitString,
+    BMPString,
+    Boolean,
+    Choice,
+    Concat,
+    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 = 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) == iri_to_uri(other.native)
+
+    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
+
+    @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
+        """
+
+        if self._unicode is None:
+            contents = self._merge_chunks()
+            if contents.find(b'@') == -1:
+                self._unicode = contents.decode('ascii')
+            else:
+                mailbox, hostname = contents.rsplit(b'@', 1)
+                self._unicode = mailbox.decode('ascii') + '@' + 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 a 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)
+            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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('not_after', GeneralizedTime, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class DirectoryString(Choice):
+    _alternatives = [
+        ('teletex_string', TeletexString),
+        ('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://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/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',
+        'initials',
+        'generation_qualifier',
+        'surname',
+        'given_name',
+        'name',
+        'pseudonym',
+        'dn_qualifier',
+        'telephone_number',
+        'email_address',
+        'domain_component',
+        'name_distinguisher',
+        'organization_identifier',
+    ]
+
+    @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',
+        }.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,
+    }
+
+    _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": "Codex Non Sufficit LC"}
+
+        :param use_printable:
+            A bool - if PrintableString should be used for encoding instead of
+            UTF8String. This is for backwards compatiblity 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, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('given_name', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('initials', PrintableString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+        ('generation_qualifier', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
+    ]
+
+
+class TeletexPersonalName(Set):
+    _fields = [
+        ('surname', TeletexString, {'tag_type': 'implicit', 'tag': 0}),
+        ('given_name', TeletexString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('initials', TeletexString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+        ('generation_qualifier', TeletexString, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('terminal_identifier', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('private_domain_name', PrivateDomainName, {'tag_type': 'explicit', 'tag': 2, 'optional': True}),
+        ('organization_name', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
+        ('numeric_user_identifier', NumericString, {'tag_type': 'implicit', 'tag': 4, 'optional': True}),
+        ('personal_name', PersonalName, {'tag_type': 'implicit', 'tag': 5, 'optional': True}),
+        ('organizational_unit_names', OrganizationalUnitNames, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('sub_address', NumericString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class NAddresses(SetOf):
+    _child_spec = OctetString
+
+
+class PresentationAddress(Sequence):
+    _fields = [
+        ('p_selector', OctetString, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('s_selector', OctetString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
+        ('t_selector', OctetString, {'tag_type': 'explicit', 'tag': 2, 'optional': True}),
+        ('n_addresses', NAddresses, {'tag_type': 'explicit', 'tag': 3}),
+    ]
+
+
+class ExtendedNetworkAddress(Choice):
+    _alternatives = [
+        ('e163_4_address', E1634Address),
+        ('psap_address', PresentationAddress, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0}),
+        ('extension_attribute_value', Any, {'tag_type': 'explicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('party_name', DirectoryString, {'tag_type': 'implicit', 'tag': 1}),
+    ]
+
+
+class GeneralName(Choice):
+    _alternatives = [
+        ('other_name', AnotherName, {'tag_type': 'implicit', 'tag': 0}),
+        ('rfc822_name', EmailAddress, {'tag_type': 'implicit', 'tag': 1}),
+        ('dns_name', DNSName, {'tag_type': 'implicit', 'tag': 2}),
+        ('x400_address', ORAddress, {'tag_type': 'implicit', 'tag': 3}),
+        ('directory_name', Name, {'tag_type': 'explicit', 'tag': 4}),
+        ('edi_party_name', EDIPartyName, {'tag_type': 'implicit', 'tag': 5}),
+        ('uniform_resource_identifier', URI, {'tag_type': 'implicit', 'tag': 6}),
+        ('ip_address', IPAddress, {'tag_type': 'implicit', 'tag': 7}),
+        ('registered_id', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('authority_cert_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('authority_cert_serial_number', Integer, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+    ]
+
+
+class DistributionPointName(Choice):
+    _alternatives = [
+        ('full_name', GeneralNames, {'tag_type': 'implicit', 'tag': 0}),
+        ('name_relative_to_crl_issuer', RelativeDistinguishedName, {'tag_type': 'implicit', 'tag': 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, {'tag_type': 'implicit', 'tag': 0, 'default': 0}),
+        ('maximum', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class GeneralSubtrees(SequenceOf):
+    _child_spec = GeneralSubtree
+
+
+class NameConstraints(Sequence):
+    _fields = [
+        ('permitted_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('excluded_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class DistributionPoint(Sequence):
+    _fields = [
+        ('distribution_point', DistributionPointName, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
+        ('reasons', ReasonFlags, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('crl_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 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[0:7] == 'http://':
+                        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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('inhibit_policy_mapping', Integer, {'tag_type': 'implicit', 'tag': 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://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://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',
+    }
+
+
+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 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',
+    }
+
+
+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': Attributes,
+        '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,
+    }
+
+
+class Extensions(SequenceOf):
+    _child_spec = Extension
+
+
+class Version(Integer):
+    _map = {
+        0: 'v1',
+        1: 'v2',
+        2: 'v3',
+    }
+
+
+class TbsCertificate(Sequence):
+    _fields = [
+        ('version', Version, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}),
+        ('serial_number', Integer),
+        ('signature', SignedDigestAlgorithm),
+        ('issuer', Name),
+        ('validity', Validity),
+        ('subject', Name),
+        ('subject_public_key_info', PublicKeyInfo),
+        ('issuer_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+        ('subject_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
+        ('extensions', Extensions, {'tag_type': 'explicit', 'tag': 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 = 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
+    _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 subject_directory_attributes_value(self):
+        """
+        This extension is used to contain additional identification attributes
+        about the subject.
+
+        :return:
+            None or an Attributes object
+        """
+
+        if not self._processed_extensions:
+            self._set_extensions()
+        return self._key_identifier_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 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()[0:7] == 'http://':
+                    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 "yes", "no" or "maybe". The "maybe" result will
+            be returned if the certificate does not contain a key identifier
+            extension, but is issued by the subject. In this case the
+            certificate signature will need to be verified using the subject
+            public key to determine a "yes" or "no" answer.
+        """
+
+        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 = 'yes'
+                    elif self.authority_key_identifier == self.key_identifier:
+                        self._self_signed = 'yes'
+                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
+
+    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, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
+        ('alias', UTF8String, {'optional': True}),
+        ('keyid', OctetString, {'optional': True}),
+        ('other', SequenceOfAlgorithmIdentifiers, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
+    ]
+
+
+class TrustedCertificate(Concat):
+    _child_specs = [Certificate, CertificateAux]

+ 5 - 0
desktop/core/ext-py/asn1crypto-0.22.0/setup.cfg

@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

+ 81 - 0
desktop/core/ext-py/asn1crypto-0.22.0/setup.py

@@ -0,0 +1,81 @@
+import os
+import shutil
+
+from setuptools import setup, find_packages, Command
+
+from asn1crypto import version
+
+
+class CleanCommand(Command):
+    user_options = [
+        ('all', 'a', '(Compatibility with original clean command)'),
+    ]
+
+    def initialize_options(self):
+        self.all = False
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        folder = os.path.dirname(os.path.abspath(__file__))
+        for sub_folder in ['build', 'dist', 'asn1crypto.egg-info']:
+            full_path = os.path.join(folder, sub_folder)
+            if os.path.exists(full_path):
+                shutil.rmtree(full_path)
+        for root, dirnames, filenames in os.walk(os.path.join(folder, 'asn1crypto')):
+            for filename in filenames:
+                if filename[-4:] == '.pyc':
+                    os.unlink(os.path.join(root, filename))
+            for dirname in list(dirnames):
+                if dirname == '__pycache__':
+                    shutil.rmtree(os.path.join(root, dirname))
+
+
+setup(
+    name='asn1crypto',
+    version=version.__version__,
+
+    description=(
+        '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'
+    ),
+    long_description='Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.',
+
+    url='https://github.com/wbond/asn1crypto',
+
+    author='wbond',
+    author_email='will@wbond.net',
+
+    license='MIT',
+
+    classifiers=[
+        'Development Status :: 4 - Beta',
+
+        'Intended Audience :: Developers',
+
+        'License :: OSI Approved :: MIT License',
+
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: Implementation :: PyPy',
+
+        'Topic :: Security :: Cryptography',
+    ],
+
+    keywords='asn1 crypto pki x509 certificate rsa dsa ec dh',
+
+    packages=find_packages(exclude=['tests*', 'dev*']),
+
+    test_suite='tests.make_suite',
+
+    cmdclass={
+        'clean': CleanCommand,
+    }
+)