aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-01-07 00:26:20 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-01-07 00:26:20 +0000
commitf9bf9b4162753bac4752faab67bd082e57c6145f (patch)
tree3a63d537d43732e48136170d9829fdde144cf6e9
parent941ebad703fb7c7373712c86889ec0843b01eaf6 (diff)
parent67a55d70b5793782d570553f94212d3d0e8b9fbc (diff)
downloadasn1crypto-android11-qpr1-d-release.tar.gz
Upgrade python/asn1crypto to 1.3.0 am: 4918382e86 am: a9a4b88828 am: 67a55d70b5r_aml_301500702android-mainline-12.0.0_r55android-mainline-11.0.0_r9android-mainline-11.0.0_r8android-mainline-11.0.0_r7android-mainline-11.0.0_r6android-mainline-11.0.0_r5android-mainline-11.0.0_r45android-mainline-11.0.0_r44android-mainline-11.0.0_r43android-mainline-11.0.0_r42android-mainline-11.0.0_r41android-mainline-11.0.0_r40android-mainline-11.0.0_r4android-mainline-11.0.0_r39android-mainline-11.0.0_r38android-mainline-11.0.0_r37android-mainline-11.0.0_r36android-mainline-11.0.0_r35android-mainline-11.0.0_r34android-mainline-11.0.0_r33android-mainline-11.0.0_r32android-mainline-11.0.0_r31android-mainline-11.0.0_r30android-mainline-11.0.0_r3android-mainline-11.0.0_r29android-mainline-11.0.0_r28android-mainline-11.0.0_r27android-mainline-11.0.0_r26android-mainline-11.0.0_r25android-mainline-11.0.0_r24android-mainline-11.0.0_r23android-mainline-11.0.0_r22android-mainline-11.0.0_r21android-mainline-11.0.0_r20android-mainline-11.0.0_r2android-mainline-11.0.0_r19android-mainline-11.0.0_r18android-mainline-11.0.0_r17android-mainline-11.0.0_r16android-mainline-11.0.0_r15android-mainline-11.0.0_r14android-mainline-11.0.0_r13android-mainline-11.0.0_r12android-mainline-11.0.0_r10android-mainline-11.0.0_r1android-11.0.0_r9android-11.0.0_r8android-11.0.0_r7android-11.0.0_r48android-11.0.0_r47android-11.0.0_r46android-11.0.0_r45android-11.0.0_r44android-11.0.0_r43android-11.0.0_r42android-11.0.0_r41android-11.0.0_r40android-11.0.0_r39android-11.0.0_r38android-11.0.0_r37android-11.0.0_r36android-11.0.0_r35android-11.0.0_r34android-11.0.0_r33android-11.0.0_r32android-11.0.0_r31android-11.0.0_r30android-11.0.0_r29android-11.0.0_r28android-11.0.0_r27android-11.0.0_r26android-11.0.0_r24android-11.0.0_r23android-11.0.0_r22android-11.0.0_r21android-11.0.0_r20android-11.0.0_r19android-11.0.0_r18android-11.0.0_r16android-11.0.0_r15android-11.0.0_r14android-11.0.0_r13android-11.0.0_r12android-11.0.0_r11android-11.0.0_r10android11-qpr3-s1-releaseandroid11-qpr3-releaseandroid11-qpr2-releaseandroid11-qpr1-s2-releaseandroid11-qpr1-s1-releaseandroid11-qpr1-releaseandroid11-qpr1-d-s1-releaseandroid11-qpr1-d-releaseandroid11-qpr1-c-releaseandroid11-mainline-tethering-releaseandroid11-mainline-sparse-2021-jan-releaseandroid11-mainline-sparse-2020-dec-releaseandroid11-mainline-releaseandroid11-mainline-permission-releaseandroid11-mainline-os-statsd-releaseandroid11-mainline-networkstack-releaseandroid11-mainline-media-swcodec-releaseandroid11-mainline-media-releaseandroid11-mainline-extservices-releaseandroid11-mainline-documentsui-releaseandroid11-mainline-conscrypt-releaseandroid11-mainline-cellbroadcast-releaseandroid11-mainline-captiveportallogin-releaseandroid11-devandroid11-d2-releaseandroid11-d1-s7-releaseandroid11-d1-s6-releaseandroid11-d1-s5-releaseandroid11-d1-s1-releaseandroid11-d1-releaseandroid11-d1-b-release
Change-Id: I755be65c30a23d78d712b75efcac32c11f8d9524
-rw-r--r--.circleci/config.yml9
-rw-r--r--.github/workflows/ci.yml17
-rw-r--r--.travis.yml5
-rw-r--r--METADATA8
-rw-r--r--asn1crypto/__init__.py38
-rw-r--r--asn1crypto/cms.py23
-rw-r--r--asn1crypto/keys.py98
-rw-r--r--asn1crypto/util.py10
-rw-r--r--asn1crypto/version.py4
-rw-r--r--asn1crypto/x509.py9
-rw-r--r--changelog.md28
-rw-r--r--dev/__init__.py2
-rw-r--r--dev/_import.py35
-rw-r--r--dev/_task.py163
-rw-r--r--dev/coverage.py2
-rw-r--r--dev/tests.py16
-rw-r--r--dev/version.py8
-rw-r--r--readme.md2
-rw-r--r--requires/coverage3
-rw-r--r--requires/lint17
-rw-r--r--requires/release6
-rw-r--r--run.py67
-rw-r--r--setup.py2
-rw-r--r--tests/__init__.py6
-rw-r--r--tests/_unittest_compat.py85
-rw-r--r--tests/fixtures/rfc3739.crtbin0 -> 788 bytes
-rw-r--r--tests/setup.py2
-rw-r--r--tests/test_core.py11
-rw-r--r--tests/test_init.py137
-rw-r--r--tests/test_pem.py6
-rw-r--r--tests/test_x509.py191
-rw-r--r--tox.ini2
32 files changed, 882 insertions, 130 deletions
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 @@ jobs:
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 @@ jobs:
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 @@ jobs:
matrix:
os:
- ubuntu-18.04
- - macOS-10.14
+ - macOS-latest
- windows-2019
python:
- '2.7'
@@ -20,7 +20,7 @@ jobs:
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 @@ jobs:
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 @@ matrix:
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 @@ third_party {
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 @@ from .version import __version__, __version_info__
__all__ = [
'__version__',
'__version_info__',
+ 'load_order',
]
+
+
+def load_order():
+ """
+ Returns a list of the module and sub-module names for asn1crypto in
+ dependency load order, for the sake of live reloading code
+
+ :return:
+ A list of unicode strings of module names, as they would appear in
+ sys.modules, ordered by which module should be reloaded first
+ """
+
+ return [
+ 'asn1crypto._errors',
+ 'asn1crypto._int',
+ 'asn1crypto._ordereddict',
+ 'asn1crypto._teletex_codec',
+ 'asn1crypto._types',
+ 'asn1crypto._inet',
+ 'asn1crypto._iri',
+ 'asn1crypto.version',
+ 'asn1crypto.pem',
+ 'asn1crypto.util',
+ 'asn1crypto.parser',
+ 'asn1crypto.core',
+ 'asn1crypto.algos',
+ 'asn1crypto.keys',
+ 'asn1crypto.x509',
+ 'asn1crypto.crl',
+ 'asn1crypto.csr',
+ 'asn1crypto.ocsp',
+ 'asn1crypto.cms',
+ 'asn1crypto.pdf',
+ 'asn1crypto.pkcs12',
+ 'asn1crypto.tsp',
+ 'asn1crypto',
+ ]
diff --git a/asn1crypto/cms.py b/asn1crypto/cms.py
index 1fabc13..2115aed 100644
--- a/asn1crypto/cms.py
+++ b/asn1crypto/cms.py
@@ -100,6 +100,8 @@ class CMSAttributeType(ObjectIdentifier):
'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 @@ class CompressedData(Sequence):
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 @@ CMSAttribute._oid_specs = {
'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 @@ import math
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 @@ from .core import (
SetOf,
)
from .util import int_from_bytes, int_to_bytes
-from asn1crypto.algos import RSASSAPSSParams
class OtherPrimeInfo(Sequence):
@@ -355,23 +354,55 @@ class NamedCurve(ObjectIdentifier):
'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 @@ class NamedCurve(ObjectIdentifier):
'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 @@ if sys.version_info <= (3,):
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 @@ class NameType(ObjectIdentifier):
'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 @@ class NameType(ObjectIdentifier):
'organizational_unit_name',
'title',
'common_name',
+ 'user_id',
'initials',
'generation_qualifier',
'surname',
@@ -642,6 +645,7 @@ class NameType(ObjectIdentifier):
'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 @@ class NameTypeAndValue(Sequence):
'platform_manufacturer': UTF8String,
'platform_model': UTF8String,
'platform_version': UTF8String,
+ 'user_id': DirectoryString,
}
_prepped = None
@@ -2140,7 +2145,7 @@ class Certificate(Sequence):
_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 @@ class Certificate(Sequence):
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 @@ other_packages = [
"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 imp
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 @@ def _import_from(mod, path, mod_dir=None):
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 @@ def _preload(require_oscrypto, print_info):
"""
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 @@ def _preload(require_oscrypto, print_info):
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 @@ def run(ci=False):
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 @@ from __future__ import unicode_literals, division, absolute_import, print_functi
import unittest
import re
import sys
+import warnings
from . import requires_oscrypto
from ._import import _preload
@@ -17,6 +18,19 @@ else:
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 @@ def run(matcher=None, repeat=1, ci=False):
_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 @@ import re
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 @@ faster to an order of magnitude or more.
## 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
+from dev._task import run_task
-if sys.version_info < (3,):
- byte_cls = str
-else:
- byte_cls = bytes
-
-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 @@ from setuptools.command.egg_info import egg_info
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 os
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 @@ def test_classes():
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 @@ def test_classes():
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 unittest
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 @@ def _assert_raises_regexp(self, expected_exception, expected_regexp, callable_ob
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 @@ from setuptools.command.egg_info import egg_info
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 @@ class CoreTests(unittest.TestCase):
# 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 @@ class CoreTests(unittest.TestCase):
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 @@ class PEMTests(unittest.TestCase):
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 @@ class X509Tests(unittest.TestCase):
'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 @@ class X509Tests(unittest.TestCase):
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 @@ class X509Tests(unittest.TestCase):
'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 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
set(['digital_signature', 'key_encipherment'])
),
+ (
+ 'rfc3739.crt',
+ set(['non_repudiation'])
+ ),
)
@data('key_usage_value_info')
@@ -836,6 +929,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('subject_alt_name_value_info')
@@ -907,6 +1004,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
{'ca': False, 'path_len_constraint': None}
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('basic_constraints_value_info')
@@ -1017,6 +1118,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('name_constraints_value_info')
@@ -1122,6 +1227,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('crl_distribution_points_value_info')
@@ -1315,6 +1424,15 @@ class X509Tests(unittest.TestCase):
])
]
),
+ (
+ 'rfc3739.crt',
+ [
+ util.OrderedDict([
+ ('policy_identifier', '1.3.36.8.1.1'),
+ ('policy_qualifiers', None)
+ ]),
+ ]
+ ),
)
@data('certificate_policies_value_info')
@@ -1386,6 +1504,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('policy_mappings_value_info')
@@ -1518,6 +1640,14 @@ class X509Tests(unittest.TestCase):
('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 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('policy_constraints_value_info')
@@ -1659,6 +1793,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
['server_auth', 'client_auth']
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('extended_key_usage_value_info')
@@ -1789,6 +1927,10 @@ class X509Tests(unittest.TestCase):
])
]
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('authority_information_access_value_info')
@@ -1860,6 +2002,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None,
+ ),
)
@data('ocsp_no_check_value_info')
@@ -1945,6 +2091,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
425155524522
),
+ (
+ 'rfc3739.crt',
+ 1234567890,
+ ),
)
@data('serial_number_info')
@@ -2015,6 +2165,10 @@ class X509Tests(unittest.TestCase):
'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 @@ class X509Tests(unittest.TestCase):
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 @@ class X509Tests(unittest.TestCase):
'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 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
None
),
+ (
+ 'rfc3739.crt',
+ None
+ ),
)
@data('authority_issuer_serial_info')
@@ -2310,6 +2477,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
['http://ocsp.exampleovca.com/']
),
+ (
+ 'rfc3739.crt',
+ []
+ ),
)
@data('ocsp_urls_info')
@@ -2416,6 +2587,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
[]
),
+ (
+ 'rfc3739.crt',
+ []
+ ),
)
@data('crl_distribution_points_info')
@@ -2487,6 +2662,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
['*.google.com']
),
+ (
+ 'rfc3739.crt',
+ []
+ ),
)
@data('valid_domains_info')
@@ -2557,6 +2736,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
[]
),
+ (
+ 'rfc3739.crt',
+ []
+ ),
)
@data('valid_ips_info')
@@ -2627,6 +2810,10 @@ class X509Tests(unittest.TestCase):
'globalsign_example_keys/SSL3.cer',
False
),
+ (
+ 'rfc3739.crt',
+ False
+ ),
)
@data('self_issued_info')
@@ -2697,6 +2884,10 @@ class X509Tests(unittest.TestCase):
'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