Upgrade python/asn1crypto to 1.3.0

Test: None
Change-Id: I27182f97cedcedcd74d014428a888fb7bb1e3f0d
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 500e4d6..874d959 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@
   py26:
     macos:
       # macOS 10.12, last version with Python 2.6
-      xcode: 9.2.0
+      xcode: 9.0.1
     steps:
       - checkout
       - run: /usr/bin/python2.6 run.py deps
@@ -14,7 +14,14 @@
       xcode: 10.3.0
     steps:
       - checkout
+      - restore_cache:
+          keys:
+            - homebrew
       - run: brew install pypy
+      - save_cache:
+          key: homebrew
+          paths:
+            - /usr/local/Homebrew
       - run: pypy run.py deps
       - run: pypy run.py ci
 workflows:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ab5738c..8180e66 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@
       matrix:
         os:
           - ubuntu-18.04
-          - macOS-10.14
+          - macOS-latest
           - windows-2019
         python:
           - '2.7'
@@ -20,7 +20,7 @@
         exclude:
           - os: ubuntu-18.04
             arch: x86
-          - os: macOS-10.14
+          - os: macOS-latest
             arch: x86
     steps:
       - uses: actions/checkout@master
@@ -32,7 +32,18 @@
         run: python run.py deps
       - name: Run test suite
         run: python run.py ci
-      - name: Run test suite (OpenSSL/macOS)
+        env:
+          OSCRYPTO_USE_CTYPES: 'true'
+      - name: Run test suite (Mac cffi)
+        run: python run.py ci
+        if: runner.os == 'macOS'
+      - name: Run test suite (Mac OpenSSL)
+        run: python run.py ci
+        if: runner.os == 'macOS'
+        env:
+          OSCRYPTO_USE_OPENSSL: /usr/lib/libcrypto.dylib,/usr/lib/libssl.dylib
+          OSCRYPTO_USE_CTYPES: 'true'
+      - name: Run test suite (Mac OpenSSL/cffi)
         run: python run.py ci
         if: runner.os == 'macOS'
         env:
diff --git a/.travis.yml b/.travis.yml
index 3c5b0d6..93c1a7f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,6 +26,11 @@
       language: python
       python: "3.7"
     - os: linux
+      arch: arm64
+      dist: bionic
+      language: python
+      python: "3.7"
+    - os: linux
       dist: xenial
       language: python
       python: "pypy"
diff --git a/METADATA b/METADATA
index db30d4b..4b41dcd 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@
     type: GIT
     value: "https://github.com/wbond/asn1crypto"
   }
-  version: "1.0.0"
+  version: "1.3.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2019
-    month: 10
-    day: 2
+    year: 2020
+    month: 1
+    day: 4
   }
 }
diff --git a/asn1crypto/__init__.py b/asn1crypto/__init__.py
index afdeb43..2c93f00 100644
--- a/asn1crypto/__init__.py
+++ b/asn1crypto/__init__.py
@@ -6,4 +6,42 @@
 __all__ = [
     '__version__',
     '__version_info__',
+    'load_order',
 ]
+
+
+def load_order():
+    """
+    Returns a list of the module and sub-module names for asn1crypto in
+    dependency load order, for the sake of live reloading code
+
+    :return:
+        A list of unicode strings of module names, as they would appear in
+        sys.modules, ordered by which module should be reloaded first
+    """
+
+    return [
+        'asn1crypto._errors',
+        'asn1crypto._int',
+        'asn1crypto._ordereddict',
+        'asn1crypto._teletex_codec',
+        'asn1crypto._types',
+        'asn1crypto._inet',
+        'asn1crypto._iri',
+        'asn1crypto.version',
+        'asn1crypto.pem',
+        'asn1crypto.util',
+        'asn1crypto.parser',
+        'asn1crypto.core',
+        'asn1crypto.algos',
+        'asn1crypto.keys',
+        'asn1crypto.x509',
+        'asn1crypto.crl',
+        'asn1crypto.csr',
+        'asn1crypto.ocsp',
+        'asn1crypto.cms',
+        'asn1crypto.pdf',
+        'asn1crypto.pkcs12',
+        'asn1crypto.tsp',
+        'asn1crypto',
+    ]
diff --git a/asn1crypto/cms.py b/asn1crypto/cms.py
index 1fabc13..2115aed 100644
--- a/asn1crypto/cms.py
+++ b/asn1crypto/cms.py
@@ -100,6 +100,8 @@
         '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/rfc2633#page-26
+        '1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
         # https://tools.ietf.org/html/rfc3161#page-20
         '1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token',
         # https://tools.ietf.org/html/rfc6211#page-5
@@ -924,6 +926,26 @@
         return self._decompressed
 
 
+class RecipientKeyIdentifier(Sequence):
+    _fields = [
+        ('subjectKeyIdentifier', OctetString),
+        ('date', GeneralizedTime, {'optional': True}),
+        ('other', OtherKeyAttribute, {'optional': True}),
+    ]
+
+
+class SMIMEEncryptionKeyPreference(Choice):
+    _alternatives = [
+        ('issuer_and_serial_number', IssuerAndSerialNumber, {'implicit': 0}),
+        ('recipientKeyId', RecipientKeyIdentifier, {'implicit': 1}),
+        ('subjectAltKeyIdentifier', PublicKeyInfo, {'implicit': 2}),
+    ]
+
+
+class SMIMEEncryptionKeyPreferences(SetOf):
+    _child_spec = SMIMEEncryptionKeyPreference
+
+
 ContentInfo._oid_specs = {
     'data': OctetString,
     'signed_data': SignedData,
@@ -958,4 +980,5 @@
     'cms_algorithm_protection': SetOfCMSAlgorithmProtection,
     'microsoft_nested_signature': SetOfContentInfo,
     'microsoft_time_stamp_token': SetOfContentInfo,
+    'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
 }
diff --git a/asn1crypto/keys.py b/asn1crypto/keys.py
index 3d447e3..599929f 100644
--- a/asn1crypto/keys.py
+++ b/asn1crypto/keys.py
@@ -21,7 +21,7 @@
 
 from ._errors import unwrap, APIException
 from ._types import type_name, byte_cls
-from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams
+from .algos import _ForceNullParameters, DigestAlgorithm, EncryptionAlgorithm, RSAESOAEPParams, RSASSAPSSParams
 from .core import (
     Any,
     Asn1Value,
@@ -40,7 +40,6 @@
     SetOf,
 )
 from .util import int_from_bytes, int_to_bytes
-from asn1crypto.algos import RSASSAPSSParams
 
 
 class OtherPrimeInfo(Sequence):
@@ -355,23 +354,55 @@
         '1.2.840.10045.3.1.5': 'prime239v2',
         '1.2.840.10045.3.1.6': 'prime239v3',
         # https://tools.ietf.org/html/rfc5480#page-5
-        # http://www.secg.org/sec2-v2.pdf
+        # http://www.secg.org/SEC2-Ver-1.0.pdf
+        '1.2.840.10045.3.1.1': 'secp192r1',
+        '1.2.840.10045.3.1.7': 'secp256r1',
         '1.3.132.0.1': 'sect163k1',
+        '1.3.132.0.2': 'sect163r1',
+        '1.3.132.0.3': 'sect239k1',
+        '1.3.132.0.4': 'sect113r1',
+        '1.3.132.0.5': 'sect113r2',
+        '1.3.132.0.6': 'secp112r1',
+        '1.3.132.0.7': 'secp112r2',
+        '1.3.132.0.8': 'secp160r1',
+        '1.3.132.0.9': 'secp160k1',
         '1.3.132.0.10': 'secp256k1',
         '1.3.132.0.15': 'sect163r2',
-        '1.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.22': 'sect131r1',
+        '1.3.132.0.23': 'sect131r2',
+        '1.3.132.0.24': 'sect193r1',
+        '1.3.132.0.25': 'sect193r2',
+        '1.3.132.0.26': 'sect233k1',
+        '1.3.132.0.27': 'sect233r1',
+        '1.3.132.0.28': 'secp128r1',
+        '1.3.132.0.29': 'secp128r2',
+        '1.3.132.0.30': 'secp160r2',
+        '1.3.132.0.31': 'secp192k1',
+        '1.3.132.0.32': 'secp224k1',
+        '1.3.132.0.33': 'secp224r1',
         '1.3.132.0.34': 'secp384r1',
+        '1.3.132.0.35': 'secp521r1',
         '1.3.132.0.36': 'sect409k1',
         '1.3.132.0.37': 'sect409r1',
-        '1.3.132.0.35': 'secp521r1',
         '1.3.132.0.38': 'sect571k1',
         '1.3.132.0.39': 'sect571r1',
+        # https://tools.ietf.org/html/rfc5639#section-4.1
+        '1.3.36.3.3.2.8.1.1.1': 'brainpoolp160r1',
+        '1.3.36.3.3.2.8.1.1.2': 'brainpoolp160t1',
+        '1.3.36.3.3.2.8.1.1.3': 'brainpoolp192r1',
+        '1.3.36.3.3.2.8.1.1.4': 'brainpoolp192t1',
+        '1.3.36.3.3.2.8.1.1.5': 'brainpoolp224r1',
+        '1.3.36.3.3.2.8.1.1.6': 'brainpoolp224t1',
+        '1.3.36.3.3.2.8.1.1.7': 'brainpoolp256r1',
+        '1.3.36.3.3.2.8.1.1.8': 'brainpoolp256t1',
+        '1.3.36.3.3.2.8.1.1.9': 'brainpoolp320r1',
+        '1.3.36.3.3.2.8.1.1.10': 'brainpoolp320t1',
+        '1.3.36.3.3.2.8.1.1.11': 'brainpoolp384r1',
+        '1.3.36.3.3.2.8.1.1.12': 'brainpoolp384t1',
+        '1.3.36.3.3.2.8.1.1.13': 'brainpoolp512r1',
+        '1.3.36.3.3.2.8.1.1.14': 'brainpoolp512t1',
     }
 
     _key_sizes = {
@@ -404,22 +435,57 @@
         '1.2.840.10045.3.1.6': 30,
         # Order values used to compute these sourced from
         # http://www.secg.org/SEC2-Ver-1.0.pdf
+        # ceil(n.bit_length() / 8)
+        '1.2.840.10045.3.1.1': 24,
+        '1.2.840.10045.3.1.7': 32,
         '1.3.132.0.1': 21,
+        '1.3.132.0.2': 21,
+        '1.3.132.0.3': 30,
+        '1.3.132.0.4': 15,
+        '1.3.132.0.5': 15,
+        '1.3.132.0.6': 14,
+        '1.3.132.0.7': 14,
+        '1.3.132.0.8': 21,
+        '1.3.132.0.9': 21,
         '1.3.132.0.10': 32,
         '1.3.132.0.15': 21,
-        '1.2.840.10045.3.1.1': 24,
-        '1.3.132.0.33': 28,
-        '1.3.132.0.26': 29,
-        '1.2.840.10045.3.1.7': 32,
-        '1.3.132.0.27': 29,
         '1.3.132.0.16': 36,
         '1.3.132.0.17': 36,
+        '1.3.132.0.22': 17,
+        '1.3.132.0.23': 17,
+        '1.3.132.0.24': 25,
+        '1.3.132.0.25': 25,
+        '1.3.132.0.26': 29,
+        '1.3.132.0.27': 30,
+        '1.3.132.0.28': 16,
+        '1.3.132.0.29': 16,
+        '1.3.132.0.30': 21,
+        '1.3.132.0.31': 24,
+        '1.3.132.0.32': 29,
+        '1.3.132.0.33': 28,
         '1.3.132.0.34': 48,
-        '1.3.132.0.36': 51,
-        '1.3.132.0.37': 51,
         '1.3.132.0.35': 66,
+        '1.3.132.0.36': 51,
+        '1.3.132.0.37': 52,
         '1.3.132.0.38': 72,
         '1.3.132.0.39': 72,
+        # Order values used to compute these sourced from
+        # https://tools.ietf.org/html/rfc5639#section-3
+        # ceil(q.bit_length() / 8)
+        '1.3.36.3.3.2.8.1.1.1': 20,
+        '1.3.36.3.3.2.8.1.1.2': 20,
+        '1.3.36.3.3.2.8.1.1.3': 24,
+        '1.3.36.3.3.2.8.1.1.4': 24,
+        '1.3.36.3.3.2.8.1.1.5': 28,
+        '1.3.36.3.3.2.8.1.1.6': 28,
+        '1.3.36.3.3.2.8.1.1.7': 32,
+        '1.3.36.3.3.2.8.1.1.8': 32,
+        '1.3.36.3.3.2.8.1.1.9': 40,
+        '1.3.36.3.3.2.8.1.1.10': 40,
+        '1.3.36.3.3.2.8.1.1.11': 48,
+        '1.3.36.3.3.2.8.1.1.12': 48,
+        '1.3.36.3.3.2.8.1.1.13': 64,
+        '1.3.36.3.3.2.8.1.1.14': 64,
     }
 
     @classmethod
diff --git a/asn1crypto/util.py b/asn1crypto/util.py
index 4d743df..7196897 100644
--- a/asn1crypto/util.py
+++ b/asn1crypto/util.py
@@ -161,6 +161,16 @@
                 return False
             return self._offset == other._offset
 
+        def __getinitargs__(self):
+            """
+            Called by tzinfo.__reduce__ to support pickle and copy.
+
+            :return:
+                offset and name, to be used for __init__
+            """
+
+            return self._offset, self._name
+
         def tzname(self, dt):
             """
             :param dt:
diff --git a/asn1crypto/version.py b/asn1crypto/version.py
index 0c08d01..b7c352c 100644
--- a/asn1crypto/version.py
+++ b/asn1crypto/version.py
@@ -2,5 +2,5 @@
 from __future__ import unicode_literals, division, absolute_import, print_function
 
 
-__version__ = '1.0.0'
-__version_info__ = (1, 0, 0)
+__version__ = '1.3.0'
+__version_info__ = (1, 3, 0)
diff --git a/asn1crypto/x509.py b/asn1crypto/x509.py
index 8341bb2..2cce9a5 100644
--- a/asn1crypto/x509.py
+++ b/asn1crypto/x509.py
@@ -537,6 +537,8 @@
         '1.3.6.1.4.1.311.60.2.1.1': 'incorporation_locality',
         '1.3.6.1.4.1.311.60.2.1.2': 'incorporation_state_or_province',
         '1.3.6.1.4.1.311.60.2.1.3': 'incorporation_country',
+        # https://tools.ietf.org/html/rfc4519#section-2.39
+        '0.9.2342.19200300.100.1.1': 'user_id',
         # https://tools.ietf.org/html/rfc2247#section-4
         '0.9.2342.19200300.100.1.25': 'domain_component',
         # http://www.alvestrand.no/objectid/0.2.262.1.10.7.20.html
@@ -561,6 +563,7 @@
         'organizational_unit_name',
         'title',
         'common_name',
+        'user_id',
         'initials',
         'generation_qualifier',
         'surname',
@@ -642,6 +645,7 @@
             'platform_manufacturer': 'Platform Manufacturer',
             'platform_model': 'Platform Model',
             'platform_version': 'Platform Version',
+            'user_id': 'User ID',
         }.get(self.native, self.native)
 
 
@@ -688,6 +692,7 @@
         'platform_manufacturer': UTF8String,
         'platform_model': UTF8String,
         'platform_version': UTF8String,
+        'user_id': DirectoryString,
     }
 
     _prepped = None
@@ -2140,7 +2145,7 @@
 
     _processed_extensions = False
     _critical_extensions = None
-    _subject_directory_attributes = None
+    _subject_directory_attributes_value = None
     _key_identifier_value = None
     _key_usage_value = None
     _subject_alt_name_value = None
@@ -2229,7 +2234,7 @@
 
         if not self._processed_extensions:
             self._set_extensions()
-        return self._subject_directory_attributes
+        return self._subject_directory_attributes_value
 
     @property
     def key_identifier_value(self):
diff --git a/changelog.md b/changelog.md
index 3792d84..67d1766 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,33 @@
 # changelog
 
+## 1.3.0
+
+ - Added `encrypt_key_pref` (`1.2.840.113549.1.9.16.2.11`) to
+   `cms.CMSAttributeType()`, along with related structures
+ - Added Brainpool curves from RFC 5639 to `keys.NamedCurve()`
+ - Fixed `x509.Certificate().subject_directory_attributes_value`
+ - Fixed some incorrectly computed minimum elliptic curve primary key
+   encoding sizes in `keys.NamedCurve()`
+ - Fixed a `TypeError` when trying to call `.untag()` or `.copy()` on a
+   `core.UTCTime()` or `core.GeneralizedTime()`, or a value containing one,
+   when using Python 2
+
+## 1.2.0
+
+ - Added `asn1crypto.load_order()`, which returns a `list` of unicode strings
+   of the names of the fully-qualified module names for all of submodules of
+   the package. The module names are listed in their dependency load order.
+   This is primarily intended for the sake of implementing hot reloading.
+
+## 1.1.0
+
+ - Added User ID (`0.9.2342.19200300.100.1.1`) to `x509.NameType()`
+ - Added various EC named curves to `keys.NamedCurve()`
+
+## 1.0.1
+
+ - Fix an absolute import in `keys` to a relative import
+
 ## 1.0.0
 
  - Backwards Compatibility Breaks
diff --git a/dev/__init__.py b/dev/__init__.py
index 02e9c6c..a4b5cb4 100644
--- a/dev/__init__.py
+++ b/dev/__init__.py
@@ -15,6 +15,8 @@
     "ocspbuilder"
 ]
 
+task_keyword_args = []
+
 requires_oscrypto = False
 has_tests_package = True
 
diff --git a/dev/_import.py b/dev/_import.py
index 2599588..caad219 100644
--- a/dev/_import.py
+++ b/dev/_import.py
@@ -5,10 +5,15 @@
 import sys
 import os
 
-from . import build_root
+from . import build_root, package_name, package_root
+
+if sys.version_info < (3,):
+    getcwd = os.getcwdu
+else:
+    getcwd = os.getcwd
 
 
-def _import_from(mod, path, mod_dir=None):
+def _import_from(mod, path, mod_dir=None, allow_error=False):
     """
     Imports a module from a specific path
 
@@ -22,23 +27,33 @@
         If the sub directory of "path" is different than the "mod" name,
         pass the sub directory as a unicode string
 
+    :param allow_error:
+        If an ImportError should be raised when the module can't be imported
+
     :return:
         None if not loaded, otherwise the module
     """
 
     if mod_dir is None:
-        mod_dir = mod
+        mod_dir = mod.replace('.', os.sep)
 
     if not os.path.exists(path):
         return None
 
-    if not os.path.exists(os.path.join(path, mod_dir)):
+    if not os.path.exists(os.path.join(path, mod_dir)) \
+            and not os.path.exists(os.path.join(path, mod_dir + '.py')):
         return None
 
+    if os.sep in mod_dir:
+        append, mod_dir = mod_dir.rsplit(os.sep, 1)
+        path = os.path.join(path, append)
+
     try:
         mod_info = imp.find_module(mod_dir, [path])
         return imp.load_module(mod, *mod_info)
     except ImportError:
+        if allow_error:
+            raise
         return None
 
 
@@ -55,13 +70,18 @@
     """
 
     if print_info:
+        print('Working dir: ' + getcwd())
         print('Python ' + sys.version.replace('\n', ''))
 
     asn1crypto = None
     oscrypto = None
 
     if require_oscrypto:
-        oscrypto_dir = os.path.join(build_root, 'oscrypto')
+        # Some CI services don't use the package name for the dir
+        if package_name == 'oscrypto':
+            oscrypto_dir = package_root
+        else:
+            oscrypto_dir = os.path.join(build_root, 'oscrypto')
         oscrypto_tests = None
         if os.path.exists(oscrypto_dir):
             oscrypto_tests = _import_from('oscrypto_tests', oscrypto_dir, 'tests')
@@ -70,7 +90,10 @@
         asn1crypto, oscrypto = oscrypto_tests.local_oscrypto()
 
     else:
-        asn1crypto_dir = os.path.join(build_root, 'asn1crypto')
+        if package_name == 'asn1crypto':
+            asn1crypto_dir = package_root
+        else:
+            asn1crypto_dir = os.path.join(build_root, 'asn1crypto')
         if os.path.exists(asn1crypto_dir):
             asn1crypto = _import_from('asn1crypto', asn1crypto_dir)
         if asn1crypto is None:
diff --git a/dev/_task.py b/dev/_task.py
new file mode 100644
index 0000000..5cc257a
--- /dev/null
+++ b/dev/_task.py
@@ -0,0 +1,163 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import ast
+import _ast
+import os
+import sys
+
+from . import package_root, task_keyword_args
+from ._import import _import_from
+
+
+if sys.version_info < (3,):
+    byte_cls = str
+else:
+    byte_cls = bytes
+
+
+def _list_tasks():
+    """
+    Fetches a list of all valid tasks that may be run, and the args they
+    accept. Does not actually import the task module to prevent errors if a
+    user does not have the dependencies installed for every task.
+
+    :return:
+        A list of 2-element tuples:
+         0: a unicode string of the task name
+         1: a list of dicts containing the parameter definitions
+    """
+
+    out = []
+    dev_path = os.path.join(package_root, 'dev')
+    for fname in sorted(os.listdir(dev_path)):
+        if fname.startswith('.') or fname.startswith('_'):
+            continue
+        if not fname.endswith('.py'):
+            continue
+        name = fname[:-3]
+        args = ()
+
+        full_path = os.path.join(package_root, 'dev', fname)
+        with open(full_path, 'rb') as f:
+            full_code = f.read()
+            if sys.version_info >= (3,):
+                full_code = full_code.decode('utf-8')
+
+        task_node = ast.parse(full_code, filename=full_path)
+        for node in ast.iter_child_nodes(task_node):
+            if isinstance(node, _ast.Assign):
+                if len(node.targets) == 1 \
+                        and isinstance(node.targets[0], _ast.Name) \
+                        and node.targets[0].id == 'run_args':
+                    args = ast.literal_eval(node.value)
+                    break
+
+        out.append((name, args))
+    return out
+
+
+def show_usage():
+    """
+    Prints to stderr the valid options for invoking tasks
+    """
+
+    valid_tasks = []
+    for task in _list_tasks():
+        usage = task[0]
+        for run_arg in task[1]:
+            usage += ' '
+            name = run_arg.get('name', '')
+            if run_arg.get('required', False):
+                usage += '{%s}' % name
+            else:
+                usage += '[%s]' % name
+        valid_tasks.append(usage)
+
+    out = 'Usage: run.py'
+    for karg in task_keyword_args:
+        out += ' [%s=%s]' % (karg['name'], karg['placeholder'])
+    out += ' (%s)' % ' | '.join(valid_tasks)
+
+    print(out, file=sys.stderr)
+    sys.exit(1)
+
+
+def _get_arg(num):
+    """
+    :return:
+        A unicode string of the requested command line arg
+    """
+
+    if len(sys.argv) < num + 1:
+        return None
+    arg = sys.argv[num]
+    if isinstance(arg, byte_cls):
+        arg = arg.decode('utf-8')
+    return arg
+
+
+def run_task():
+    """
+    Parses the command line args, invoking the requested task
+    """
+
+    arg_num = 1
+    task = None
+    args = []
+    kwargs = {}
+
+    # We look for the task name, processing any global task keyword args
+    # by setting the appropriate env var
+    while True:
+        val = _get_arg(arg_num)
+        if val is None:
+            break
+
+        next_arg = False
+        for karg in task_keyword_args:
+            if val.startswith(karg['name'] + '='):
+                os.environ[karg['env_var']] = val[len(karg['name']) + 1:]
+                next_arg = True
+                break
+
+        if next_arg:
+            arg_num += 1
+            continue
+
+        task = val
+        break
+
+    if task is None:
+        show_usage()
+
+    task_mod = _import_from('dev.%s' % task, package_root, allow_error=True)
+    if task_mod is None:
+        show_usage()
+
+    run_args = task_mod.__dict__.get('run_args', [])
+    max_args = arg_num + 1 + len(run_args)
+
+    if len(sys.argv) > max_args:
+        show_usage()
+
+    for i, run_arg in enumerate(run_args):
+        val = _get_arg(arg_num + 1 + i)
+        if val is None:
+            if run_arg.get('required', False):
+                show_usage()
+            break
+
+        if run_arg.get('cast') == 'int' and val.isdigit():
+            val = int(val)
+
+        kwarg = run_arg.get('kwarg')
+        if kwarg:
+            kwargs[kwarg] = val
+        else:
+            args.append(val)
+
+    run = task_mod.__dict__.get('run')
+
+    result = run(*args, **kwargs)
+    sys.exit(int(not result))
diff --git a/dev/coverage.py b/dev/coverage.py
index b9a55de..eb03b53 100644
--- a/dev/coverage.py
+++ b/dev/coverage.py
@@ -54,7 +54,7 @@
     cov.start()
 
     from .tests import run as run_tests
-    result = run_tests()
+    result = run_tests(ci=ci)
     print()
 
     if ci:
diff --git a/dev/tests.py b/dev/tests.py
index a065c38..101c691 100644
--- a/dev/tests.py
+++ b/dev/tests.py
@@ -4,6 +4,7 @@
 import unittest
 import re
 import sys
+import warnings
 
 from . import requires_oscrypto
 from ._import import _preload
@@ -17,6 +18,19 @@
     from io import StringIO
 
 
+run_args = [
+    {
+        'name': 'regex',
+        'kwarg': 'matcher',
+    },
+    {
+        'name': 'repeat_count',
+        'kwarg': 'repeat',
+        'cast': 'int',
+    },
+]
+
+
 def run(matcher=None, repeat=1, ci=False):
     """
     Runs the tests
@@ -37,6 +51,8 @@
 
     _preload(requires_oscrypto, not ci)
 
+    warnings.filterwarnings("error")
+
     loader = unittest.TestLoader()
     # We have to manually track the list of applicable tests because for
     # some reason with Python 3.4 on Windows, the tests in a suite are replaced
diff --git a/dev/version.py b/dev/version.py
index 3027431..fe37d3d 100644
--- a/dev/version.py
+++ b/dev/version.py
@@ -8,6 +8,14 @@
 from . import package_root, package_name, has_tests_package
 
 
+run_args = [
+    {
+        'name': 'pep440_version',
+        'required': True
+    },
+]
+
+
 def run(new_version):
     """
     Updates the package version in the various locations
diff --git a/readme.md b/readme.md
index 8d76636..5f05a4e 100644
--- a/readme.md
+++ b/readme.md
@@ -112,7 +112,7 @@
 
 ## Current Release
 
-1.0.0 - [changelog](changelog.md)
+1.3.0 - [changelog](changelog.md)
 
 ## Dependencies
 
diff --git a/requires/coverage b/requires/coverage
index 8a80dcc..39126eb 100644
--- a/requires/coverage
+++ b/requires/coverage
@@ -1 +1,2 @@
-coverage >= 4.3.4 ; python_version != '3.2'
\ No newline at end of file
+coverage == 4.4.1 ; python_version == '2.6'
+coverage == 4.5.4 ; python_version != '3.2' and python_version != '2.6'
diff --git a/requires/lint b/requires/lint
index f5d0e74..121788b 100644
--- a/requires/lint
+++ b/requires/lint
@@ -1,7 +1,14 @@
-setuptools == 39.0.1 ; python_version == '2.7' or python_version >= '3.3'
+setuptools >= 39.0.1 ; python_version == '2.7' or python_version >= '3.3'
 enum34 == 1.1.6 ; python_version == '2.7' or python_version == '3.3'
-mccabe == 0.6.1 ; python_version == '2.7' or python_version >= '3.3'
-pycodestyle == 2.3.1 ; python_version == '2.7' or python_version >= '3.3'
-pyflakes == 1.6.0 ; python_version == '2.7' or python_version >= '3.3'
 configparser == 3.5.0 ; python_version == '2.7'
-flake8 == 3.5.0 ; python_version == '2.7' or python_version >= '3.3'
\ No newline at end of file
+mccabe == 0.6.1 ; python_version == '3.3'
+pycodestyle == 2.3.1 ; python_version == '3.3'
+pyflakes == 1.6.0 ; python_version == '3.3'
+flake8 == 3.5.0 ; python_version == '3.3'
+mccabe == 0.6.1 ; python_version == '2.7' or python_version >= '3.4'
+pycodestyle == 2.5.0 ; python_version == '2.7' or python_version >= '3.4'
+pyflakes == 2.1.1 ; python_version == '2.7' or python_version >= '3.4'
+functools32 == 3.2.3-2 ; python_version == '2.7'
+typing == 3.7.4.1 ; python_version == '2.7' or python_version == '3.4'
+entrypoints == 0.3 ; python_version == '2.7' or python_version >= '3.4'
+flake8 == 3.7.9 ; python_version == '2.7' or python_version >= '3.4'
diff --git a/requires/release b/requires/release
index 91cff65..3b81655 100644
--- a/requires/release
+++ b/requires/release
@@ -1,3 +1,3 @@
-wheel>=0.31.0
-twine>=1.11.0
-setuptools>=38.6.0
+wheel >= 0.31.0
+twine >= 1.11.0
+setuptools >= 38.6.0
diff --git a/run.py b/run.py
index 64666d9..2f53221 100644
--- a/run.py
+++ b/run.py
@@ -2,70 +2,7 @@
 # coding: utf-8
 from __future__ import unicode_literals, division, absolute_import, print_function
 
-import sys
-
-if sys.version_info < (3,):
-    byte_cls = str
-else:
-    byte_cls = bytes
+from dev._task import run_task
 
 
-def show_usage():
-    print('Usage: run.py (lint | tests [regex] | coverage | deps | ci | version {pep440_version} | build | release)', file=sys.stderr)
-    sys.exit(1)
-
-
-def get_arg(num):
-    if len(sys.argv) < num + 1:
-        return None, num
-    arg = sys.argv[num]
-    if isinstance(arg, byte_cls):
-        arg = arg.decode('utf-8')
-    return arg, num + 1
-
-
-if len(sys.argv) < 2 or len(sys.argv) > 3:
-    show_usage()
-
-task, next_arg = get_arg(1)
-
-if task not in set(['lint', 'tests', 'coverage', 'deps', 'ci', 'version', 'build', 'release']):
-    show_usage()
-
-if task != 'tests' and task != 'version' and len(sys.argv) == 3:
-    show_usage()
-
-params = []
-if task == 'lint':
-    from dev.lint import run
-
-elif task == 'tests':
-    from dev.tests import run
-    matcher, next_arg = get_arg(next_arg)
-    if matcher:
-        params.append(matcher)
-
-elif task == 'coverage':
-    from dev.coverage import run
-
-elif task == 'deps':
-    from dev.deps import run
-
-elif task == 'ci':
-    from dev.ci import run
-
-elif task == 'version':
-    from dev.version import run
-    if len(sys.argv) != 3:
-        show_usage()
-    pep440_version, next_arg = get_arg(next_arg)
-    params.append(pep440_version)
-
-elif task == 'build':
-    from dev.build import run
-
-elif task == 'release':
-    from dev.release import run
-
-result = run(*params)
-sys.exit(int(not result))
+run_task()
diff --git a/setup.py b/setup.py
index 71cfe92..ce6f2e2 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
 
 
 PACKAGE_NAME = 'asn1crypto'
-PACKAGE_VERSION = '1.0.0'
+PACKAGE_VERSION = '1.3.0'
 PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
 
 
diff --git a/tests/__init__.py b/tests/__init__.py
index d267878..b669e5a 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -6,8 +6,8 @@
 import unittest
 
 
-__version__ = '1.0.0'
-__version_info__ = (1, 0, 0)
+__version__ = '1.3.0'
+__version_info__ = (1, 3, 0)
 
 
 def _import_from(mod, path, mod_dir=None):
@@ -92,6 +92,7 @@
     from .test_cms import CMSTests
     from .test_crl import CRLTests
     from .test_csr import CSRTests
+    from .test_init import InitTests
     from .test_keys import KeysTests
     from .test_ocsp import OCSPTests
     from .test_pem import PEMTests
@@ -107,6 +108,7 @@
         CMSTests,
         CRLTests,
         CSRTests,
+        InitTests,
         KeysTests,
         OCSPTests,
         PEMTests,
diff --git a/tests/_unittest_compat.py b/tests/_unittest_compat.py
index 2d4985d..11715c2 100644
--- a/tests/_unittest_compat.py
+++ b/tests/_unittest_compat.py
@@ -6,40 +6,105 @@
 import re
 
 
+if sys.version_info < (3,):
+    str_cls = unicode  # noqa
+else:
+    str_cls = str
+
+
 _non_local = {'patched': False}
 
 
 def patch():
-    if not sys.version_info < (2, 7):
+    if sys.version_info >= (3, 0):
         return
 
     if _non_local['patched']:
         return
 
-    unittest.TestCase.assertIsInstance = _assert_is_instance
-    unittest.TestCase.assertRaises = _assert_raises
-    unittest.TestCase.assertRaisesRegexp = _assert_raises_regexp
+    if sys.version_info < (2, 7):
+        unittest.TestCase.assertIsInstance = _assert_is_instance
+        unittest.TestCase.assertRegex = _assert_regex
+        unittest.TestCase.assertRaises = _assert_raises
+        unittest.TestCase.assertRaisesRegex = _assert_raises_regex
+        unittest.TestCase.assertGreaterEqual = _assert_greater_equal
+        unittest.TestCase.assertLess = _assert_less
+        unittest.TestCase.assertLessEqual = _assert_less_equal
+        unittest.TestCase.assertIn = _assert_in
+        unittest.TestCase.assertNotIn = _assert_not_in
+    else:
+        unittest.TestCase.assertRegex = unittest.TestCase.assertRegexpMatches
+        unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
     _non_local['patched'] = True
 
 
+def _safe_repr(obj):
+    try:
+        return repr(obj)
+    except Exception:
+        return object.__repr__(obj)
+
+
+def _format_message(msg, standard_msg):
+    return msg or standard_msg
+
+
+def _assert_greater_equal(self, a, b, msg=None):
+    if not a >= b:
+        standard_msg = '%s not greater than or equal to %s' % (_safe_repr(a), _safe_repr(b))
+        self.fail(_format_message(msg, standard_msg))
+
+
+def _assert_less(self, a, b, msg=None):
+    if not a < b:
+        standard_msg = '%s not less than %s' % (_safe_repr(a), _safe_repr(b))
+        self.fail(_format_message(msg, standard_msg))
+
+
+def _assert_less_equal(self, a, b, msg=None):
+    if not a <= b:
+        standard_msg = '%s not less than or equal to %s' % (_safe_repr(a), _safe_repr(b))
+        self.fail(_format_message(msg, standard_msg))
+
+
 def _assert_is_instance(self, obj, cls, msg=None):
-    """Same as self.assertTrue(isinstance(obj, cls)), with a nicer
-    default message."""
     if not isinstance(obj, cls):
         if not msg:
             msg = '%s is not an instance of %r' % (obj, cls)
         self.fail(msg)
 
 
-def _assert_raises(self, expected_exception, callableObj=None, *args, **kwargs):  # noqa
-    context = _AssertRaisesContext(expected_exception, self)
+def _assert_in(self, member, container, msg=None):
+    if member not in container:
+        standard_msg = '%s not found in %s' % (_safe_repr(member), _safe_repr(container))
+        self.fail(_format_message(msg, standard_msg))
+
+
+def _assert_not_in(self, member, container, msg=None):
+    if member in container:
+        standard_msg = '%s found in %s' % (_safe_repr(member), _safe_repr(container))
+        self.fail(_format_message(msg, standard_msg))
+
+
+def _assert_regex(self, text, expected_regexp, msg=None):
+    """Fail the test unless the text matches the regular expression."""
+    if isinstance(expected_regexp, str_cls):
+        expected_regexp = re.compile(expected_regexp)
+    if not expected_regexp.search(text):
+        msg = msg or "Regexp didn't match"
+        msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text)
+        self.fail(msg)
+
+
+def _assert_raises(self, excClass, callableObj=None, *args, **kwargs):  # noqa
+    context = _AssertRaisesContext(excClass, self)
     if callableObj is None:
         return context
     with context:
         callableObj(*args, **kwargs)
 
 
-def _assert_raises_regexp(self, expected_exception, expected_regexp, callable_obj=None, *args, **kwargs):
+def _assert_raises_regex(self, expected_exception, expected_regexp, callable_obj=None, *args, **kwargs):
     if expected_regexp is not None:
         expected_regexp = re.compile(expected_regexp)
     context = _AssertRaisesContext(expected_exception, self, expected_regexp)
@@ -50,8 +115,6 @@
 
 
 class _AssertRaisesContext(object):
-    """A context manager used to implement TestCase.assertRaises* methods."""
-
     def __init__(self, expected, test_case, expected_regexp=None):
         self.expected = expected
         self.failureException = test_case.failureException
diff --git a/tests/fixtures/rfc3739.crt b/tests/fixtures/rfc3739.crt
new file mode 100644
index 0000000..93ec6ba
--- /dev/null
+++ b/tests/fixtures/rfc3739.crt
Binary files differ
diff --git a/tests/setup.py b/tests/setup.py
index 9f69479..fc20401 100644
--- a/tests/setup.py
+++ b/tests/setup.py
@@ -10,7 +10,7 @@
 
 
 PACKAGE_NAME = 'asn1crypto'
-PACKAGE_VERSION = '1.0.0'
+PACKAGE_VERSION = '1.3.0'
 TEST_PACKAGE_NAME = '%s_tests' % PACKAGE_NAME
 TESTS_ROOT = os.path.dirname(os.path.abspath(__file__))
 PACKAGE_ROOT = os.path.abspath(os.path.join(TESTS_ROOT, '..'))
diff --git a/tests/test_core.py b/tests/test_core.py
index aaff9f5..b9a7a82 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -409,6 +409,15 @@
             # Is past 2050
             core.UTCTime(datetime(2106, 2, 7, 6, 28, 16, tzinfo=util.timezone.utc))
 
+    def test_utctime_copy(self):
+        a = core.UTCTime(datetime(2019, 11, 11, 17, 45, 18, tzinfo=util.timezone.utc))
+        # Ensure _native is set because we want to test copy on the nested timezone object.
+        a.native
+        b = a.copy()
+        self.assertEqual(a.native, b.native)
+        self.assertEqual(a.contents, b.contents)
+        self.assertEqual(a.dump(), b.dump())
+
     @staticmethod
     def generalized_time_info():
         def tz(hours, minutes=0):
@@ -1173,7 +1182,7 @@
         self.assertEqual(b'\x6a\x03\x02\x01\x00', ati.dump(force=True))
 
     def test_required_field(self):
-        with self.assertRaisesRegexp(ValueError, '"id" is missing from structure'):
+        with self.assertRaisesRegex(ValueError, '"id" is missing from structure'):
             Seq({'value': core.Integer(5)}).dump()
 
     def test_explicit_application_tag_nested(self):
diff --git a/tests/test_init.py b/tests/test_init.py
new file mode 100644
index 0000000..b986458
--- /dev/null
+++ b/tests/test_init.py
@@ -0,0 +1,137 @@
+# coding: utf-8
+from __future__ import unicode_literals, division, absolute_import, print_function
+
+import ast
+import _ast
+import unittest
+import os
+import sys
+
+import asn1crypto as module
+
+
+# This handles situations where an import is importing a function from a
+# dotted path, e.g. "from . import ident", and ident is a function, not a
+# submodule
+MOD_MAP = {
+}
+
+
+def add_mod(mod_name, imports):
+    """
+    Maps pre-defined module.function to module import names
+
+    :param mod_name:
+        A unicode string of a fully-qualified module name being imported
+
+    :param imports:
+        A set of unicode strings of the modules that are being imported
+    """
+
+    imports.add(MOD_MAP.get(mod_name, mod_name))
+
+
+def walk_ast(parent_node, modname, imports):
+    """
+    Walks the AST for a module finding any imports and recording them
+
+    :param parent_node:
+        A node from the _ast module
+
+    :param modname:
+        A unicode string of the module we are walking the AST of
+
+    :param imports:
+        A set of unicode strings of the imports that have been found so far
+    """
+
+    for node in ast.iter_child_nodes(parent_node):
+        if isinstance(node, _ast.Import):
+            if node.names[0].name.startswith(module.__name__):
+                add_mod(node.names[0].name, imports)
+
+        elif isinstance(node, _ast.ImportFrom):
+            if node.level > 0:
+                if modname == module.__name__:
+                    base_mod = module.__name__
+                else:
+                    base_mod = '.'.join(modname.split('.')[:-node.level])
+                if node.module:
+                    base_mod += '.' + node.module
+            else:
+                base_mod = node.module
+
+            if not base_mod.startswith(module.__name__):
+                continue
+
+            if node.level > 0 and not node.module:
+                for n in node.names:
+                    add_mod(base_mod + '.' + n.name, imports)
+            else:
+                add_mod(base_mod, imports)
+
+        elif isinstance(node, _ast.If):
+            for subast in node.body:
+                walk_ast(subast, modname, imports)
+            for subast in node.orelse:
+                walk_ast(subast, modname, imports)
+
+        elif sys.version_info >= (3, 3) and isinstance(node, _ast.Try):
+            for subast in node.body:
+                walk_ast(subast, modname, imports)
+            for subast in node.orelse:
+                walk_ast(subast, modname, imports)
+            for subast in node.finalbody:
+                walk_ast(subast, modname, imports)
+
+        elif sys.version_info < (3, 3) and isinstance(node, _ast.TryFinally):
+            for subast in node.body:
+                walk_ast(subast, modname, imports)
+            for subast in node.finalbody:
+                walk_ast(subast, modname, imports)
+
+        elif sys.version_info < (3, 3) and isinstance(node, _ast.TryExcept):
+            for subast in node.body:
+                walk_ast(subast, modname, imports)
+            for subast in node.orelse:
+                walk_ast(subast, modname, imports)
+
+
+class InitTests(unittest.TestCase):
+
+    def test_load_order(self):
+        deps = {}
+
+        mod_root = os.path.abspath(os.path.dirname(module.__file__))
+        files = []
+        for root, dnames, fnames in os.walk(mod_root):
+            for f in fnames:
+                if f.endswith('.py'):
+                    full_path = os.path.join(root, f)
+                    rel_path = full_path.replace(mod_root + os.sep, '')
+                    files.append((full_path, rel_path))
+
+        for full_path, rel_path in sorted(files):
+            with open(full_path, 'rb') as f:
+                full_code = f.read()
+                if sys.version_info >= (3,):
+                    full_code = full_code.decode('utf-8')
+
+            modname = rel_path.replace('.py', '').replace(os.sep, '.')
+            if modname == '__init__':
+                modname = module.__name__
+            else:
+                modname = '%s.%s' % (module.__name__, modname)
+
+            imports = set([])
+            module_node = ast.parse(full_code, filename=full_path)
+            walk_ast(module_node, modname, imports)
+
+            deps[modname] = imports
+
+        load_order = module.load_order()
+        prev = set([])
+        for mod in load_order:
+            self.assertEqual(True, mod in deps)
+            self.assertEqual((mod, set([])), (mod, deps[mod] - prev))
+            prev.add(mod)
diff --git a/tests/test_pem.py b/tests/test_pem.py
index 8d7f274..db9857c 100644
--- a/tests/test_pem.py
+++ b/tests/test_pem.py
@@ -149,13 +149,13 @@
             self.assertEqual(expected_bytes, encoded_bytes)
 
     def test_armor_wrong_type(self):
-        with self.assertRaisesRegexp(TypeError, 'type_name must be a unicode string'):
+        with self.assertRaisesRegex(TypeError, 'type_name must be a unicode string'):
             pem.armor(b'CERTIFICATE', b'')
 
     def test_armor_wrong_type2(self):
-        with self.assertRaisesRegexp(TypeError, 'der_bytes must be a byte string'):
+        with self.assertRaisesRegex(TypeError, 'der_bytes must be a byte string'):
             pem.armor('CERTIFICATE', '')
 
     def test_detect_wrong_type(self):
-        with self.assertRaisesRegexp(TypeError, 'byte_string must be a byte string'):
+        with self.assertRaisesRegex(TypeError, 'byte_string must be a byte string'):
             pem.detect('CERTIFICATE')
diff --git a/tests/test_x509.py b/tests/test_x509.py
index f933911..cfeb485 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -620,6 +620,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 set(['key_usage', 'extended_key_usage', 'basic_constraints'])
             ),
+            (
+                'rfc3739.crt',
+                set(['key_usage'])
+            ),
         )
 
     @data('critical_extensions_info')
@@ -628,6 +632,87 @@
         self.assertEqual(critical_extensions, cert.critical_extensions)
 
     @staticmethod
+    def subject_directory_attributes_value_info():
+        return (
+            (
+                'keys/test-der.crt',
+                None
+            ),
+            (
+                'keys/test-inter-der.crt',
+                None
+            ),
+            (
+                'keys/test-third-der.crt',
+                None
+            ),
+            (
+                'geotrust_certs/GeoTrust_Universal_CA.crt',
+                None
+            ),
+            (
+                'geotrust_certs/GeoTrust_Primary_CA.crt',
+                None
+            ),
+            (
+                'geotrust_certs/GeoTrust_EV_SSL_CA_-_G4.crt',
+                None
+            ),
+            (
+                'geotrust_certs/codex.crt',
+                None
+            ),
+            (
+                'lets_encrypt/isrgrootx1.pem',
+                None
+            ),
+            (
+                'lets_encrypt/letsencryptauthorityx1.pem',
+                None
+            ),
+            (
+                'lets_encrypt/letsencryptauthorityx2.pem',
+                None
+            ),
+            (
+                'globalsign_example_keys/IssuingCA-der.cer',
+                None
+            ),
+            (
+                'globalsign_example_keys/rootCA.cer',
+                None
+            ),
+            (
+                'globalsign_example_keys/SSL1.cer',
+                None
+            ),
+            (
+                'globalsign_example_keys/SSL2.cer',
+                None
+            ),
+            (
+                'globalsign_example_keys/SSL3.cer',
+                None
+            ),
+            (
+                'rfc3739.crt',
+                [
+                    util.OrderedDict([('type', 'pda_country_of_citizenship'), ('values', ['DE'])]),
+                    util.OrderedDict([('type', 'pda_gender'), ('values', ['F'])]),
+                    util.OrderedDict([('type', 'pda_date_of_birth'), ('values', [
+                        datetime(1971, 10, 14, 12, 0, tzinfo=util.timezone.utc)])]),
+                    util.OrderedDict([('type', 'pda_place_of_birth'), ('values', ['Darmstadt'])]),
+                ]
+            ),
+        )
+
+    @data('subject_directory_attributes_value_info')
+    def subject_directory_attributes_value(self, relative_path, sda_value):
+        cert = self._load_cert(relative_path)
+        value = cert.subject_directory_attributes_value
+        self.assertEqual(sda_value, value.native if value else None)
+
+    @staticmethod
     def key_identifier_value_info():
         return (
             (
@@ -690,6 +775,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 b'G\xde\xa4\xe7\xea`\xe7\xee6\xc8\xf1\xd5\xb0F\x07\x07\x9eBh\xce'
             ),
+            (
+                'rfc3739.crt',
+                None
+            ),
         )
 
     @data('key_identifier_value_info')
@@ -761,6 +850,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 set(['digital_signature', 'key_encipherment'])
             ),
+            (
+                'rfc3739.crt',
+                set(['non_repudiation'])
+            ),
         )
 
     @data('key_usage_value_info')
@@ -836,6 +929,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('subject_alt_name_value_info')
@@ -907,6 +1004,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 {'ca': False, 'path_len_constraint': None}
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('basic_constraints_value_info')
@@ -1017,6 +1118,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('name_constraints_value_info')
@@ -1122,6 +1227,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('crl_distribution_points_value_info')
@@ -1315,6 +1424,15 @@
                     ])
                 ]
             ),
+            (
+                'rfc3739.crt',
+                [
+                    util.OrderedDict([
+                        ('policy_identifier', '1.3.36.8.1.1'),
+                        ('policy_qualifiers', None)
+                    ]),
+                ]
+            ),
         )
 
     @data('certificate_policies_value_info')
@@ -1386,6 +1504,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('policy_mappings_value_info')
@@ -1518,6 +1640,14 @@
                     ('authority_cert_serial_number', None)
                 ])
             ),
+            (
+                'rfc3739.crt',
+                util.OrderedDict([
+                    ('key_identifier', b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\xfe\xdc\xba\x98"),
+                    ('authority_cert_issuer', None),
+                    ('authority_cert_serial_number', None)
+                ])
+            ),
         )
 
     @data('authority_key_identifier_value_info')
@@ -1589,6 +1719,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('policy_constraints_value_info')
@@ -1659,6 +1793,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 ['server_auth', 'client_auth']
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('extended_key_usage_value_info')
@@ -1789,6 +1927,10 @@
                     ])
                 ]
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('authority_information_access_value_info')
@@ -1860,6 +2002,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('ocsp_no_check_value_info')
@@ -1945,6 +2091,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 425155524522
             ),
+            (
+                'rfc3739.crt',
+                1234567890,
+            ),
         )
 
     @data('serial_number_info')
@@ -2015,6 +2165,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 b'G\xde\xa4\xe7\xea`\xe7\xee6\xc8\xf1\xd5\xb0F\x07\x07\x9eBh\xce'
             ),
+            (
+                'rfc3739.crt',
+                None,
+            ),
         )
 
     @data('key_identifier_info')
@@ -2099,6 +2253,11 @@
                 b'_\xc0S\xb1\xeb}\xe3\x8e\xe4{\xdb\xd7\xe2\xd9}=3\x97|\x0c\x1e\xecz\xcc\x92u\x1f'
                 b'\xf0\x1d\xbc\x9f\xe4:425155524522'
             ),
+            (
+                'rfc3739.crt',
+                b"@\xde\x1b\xdb\xdc3a\x89:'D\xaf.G' \xb4<\xb3R8\xca;y\x8e\xfb\xef\x14\xbcE\x05F"
+                b":1234567890"
+            ),
         )
 
     @data('issuer_serial_info')
@@ -2169,6 +2328,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 b"'\xf8/\xe9]\xd7\r\xf4\xa8\xea\x87\x99=\xfd\x8e\xb3\x9e@\xd0\x91"
             ),
+            (
+                'rfc3739.crt',
+                b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\xfe\xdc\xba\x98"
+            ),
         )
 
     @data('authority_key_identifier_info')
@@ -2240,6 +2403,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 None
             ),
+            (
+                'rfc3739.crt',
+                None
+            ),
         )
 
     @data('authority_issuer_serial_info')
@@ -2310,6 +2477,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 ['http://ocsp.exampleovca.com/']
             ),
+            (
+                'rfc3739.crt',
+                []
+            ),
         )
 
     @data('ocsp_urls_info')
@@ -2416,6 +2587,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 []
             ),
+            (
+                'rfc3739.crt',
+                []
+            ),
         )
 
     @data('crl_distribution_points_info')
@@ -2487,6 +2662,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 ['*.google.com']
             ),
+            (
+                'rfc3739.crt',
+                []
+            ),
         )
 
     @data('valid_domains_info')
@@ -2557,6 +2736,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 []
             ),
+            (
+                'rfc3739.crt',
+                []
+            ),
         )
 
     @data('valid_ips_info')
@@ -2627,6 +2810,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 False
             ),
+            (
+                'rfc3739.crt',
+                False
+            ),
         )
 
     @data('self_issued_info')
@@ -2697,6 +2884,10 @@
                 'globalsign_example_keys/SSL3.cer',
                 'no'
             ),
+            (
+                'rfc3739.crt',
+                'no'
+            ),
         )
 
     @data('self_signed_info')
diff --git a/tox.ini b/tox.ini
index 1f2e7e4..dbf71ee 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py26,py27,py32,py33,py34,py35,py36,py37,pypy
+envlist = py26,py27,py32,py33,py34,py35,py36,py37,py38,pypy
 
 [testenv]
 deps = -rrequires/ci