diff options
author | Haibo Huang <hhb@google.com> | 2020-10-29 18:32:59 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-10-29 18:32:59 +0000 |
commit | 84d95d24f25eb713e65c33a83a31f4c3805485c0 (patch) | |
tree | 951b5e9d3a2406aacba331f73e047ea99d6704a5 | |
parent | dca49b83ca3ff3a3b9f24f3914b85a680bd67b10 (diff) | |
parent | 47da9b5dc54b6b0064c870efc77fd8500edb9472 (diff) | |
download | asn1crypto-84d95d24f25eb713e65c33a83a31f4c3805485c0.tar.gz |
Upgrade python/asn1crypto to 1.4.0 am: 2b20719855 am: 47da9b5dc5
Original change: https://android-review.googlesource.com/c/platform/external/python/asn1crypto/+/1479353
Change-Id: Icc21f5cb3d1f128b2ee4e79bed2f210ee464264a
-rw-r--r-- | .circleci/config.yml | 17 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
-rw-r--r-- | .travis.yml | 5 | ||||
-rw-r--r-- | METADATA | 6 | ||||
-rw-r--r-- | asn1crypto/core.py | 26 | ||||
-rw-r--r-- | asn1crypto/keys.py | 2 | ||||
-rw-r--r-- | asn1crypto/version.py | 4 | ||||
-rw-r--r-- | asn1crypto/x509.py | 2 | ||||
-rw-r--r-- | changelog.md | 11 | ||||
-rw-r--r-- | codecov.json | 3 | ||||
-rw-r--r-- | dev/deps.py | 287 | ||||
-rw-r--r-- | readme.md | 13 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | tests/__init__.py | 4 | ||||
-rw-r--r-- | tests/setup.py | 5 | ||||
-rw-r--r-- | tests/test_core.py | 37 | ||||
-rw-r--r-- | tests/test_keys.py | 10 |
17 files changed, 345 insertions, 91 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 874d959..e135408 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,16 +14,13 @@ 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 + - run: curl --location -O https://bitbucket.org/pypy/pypy/downloads/pypy2.7-v7.3.1-osx64.tar.bz2 + - run: tar xvf pypy2.7-v7.3.1-osx64.tar.bz2 + - run: mv pypy2.7-v7.3.1-osx64 pypy + - run: xattr -rc pypy + - run: ./pypy/bin/pypy -m ensurepip + - run: ./pypy/bin/pypy run.py deps + - run: ./pypy/bin/pypy run.py ci workflows: version: 2 python-26: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8180e66..e91e6d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ name: CI -on: [push] +on: [push, pull_request] jobs: build: diff --git a/.travis.yml b/.travis.yml index 93c1a7f..eb0ca1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,11 @@ matrix: language: python python: "3.7" - os: linux + arch: ppc64le + dist: bionic + language: python + python: "3.7" + - os: linux dist: xenial language: python python: "pypy" @@ -9,11 +9,11 @@ third_party { type: GIT value: "https://github.com/wbond/asn1crypto" } - version: "1.3.0" + version: "1.4.0" license_type: NOTICE last_upgrade_date { year: 2020 - month: 1 - day: 4 + month: 10 + day: 28 } } diff --git a/asn1crypto/core.py b/asn1crypto/core.py index 933f8ca..7133367 100644 --- a/asn1crypto/core.py +++ b/asn1crypto/core.py @@ -3104,6 +3104,21 @@ class ObjectIdentifier(Primitive, ValueMap): first = part continue elif index == 1: + if first > 2: + raise ValueError(unwrap( + ''' + First arc must be one of 0, 1 or 2, not %s + ''', + repr(first) + )) + elif first < 2 and part >= 40: + raise ValueError(unwrap( + ''' + Second arc must be less than 40 if first arc is 0 or + 1, not %s + ''', + repr(part) + )) part = (first * 40) + part encoded_part = chr_cls(0x7F & part) @@ -3145,8 +3160,15 @@ class ObjectIdentifier(Primitive, ValueMap): # Last byte in subidentifier has the eighth bit set to 0 if byte & 0x80 == 0: if len(output) == 0: - output.append(str_cls(part // 40)) - output.append(str_cls(part % 40)) + if part >= 80: + output.append(str_cls(2)) + output.append(str_cls(part - 80)) + elif part >= 40: + output.append(str_cls(1)) + output.append(str_cls(part - 40)) + else: + output.append(str_cls(0)) + output.append(str_cls(part)) else: output.append(str_cls(part)) part = 0 diff --git a/asn1crypto/keys.py b/asn1crypto/keys.py index 599929f..96b763e 100644 --- a/asn1crypto/keys.py +++ b/asn1crypto/keys.py @@ -1216,7 +1216,7 @@ class PublicKeyInfo(Sequence): if self._bit_size is None: if self.algorithm == 'ec': - self._bit_size = ((len(self['public_key'].native) - 1) / 2) * 8 + self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8) else: if self.algorithm == 'rsa': prime = self['public_key'].parsed['modulus'].native diff --git a/asn1crypto/version.py b/asn1crypto/version.py index b7c352c..3cf4892 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.3.0' -__version_info__ = (1, 3, 0) +__version__ = '1.4.0' +__version_info__ = (1, 4, 0) diff --git a/asn1crypto/x509.py b/asn1crypto/x509.py index 2cce9a5..16f7deb 100644 --- a/asn1crypto/x509.py +++ b/asn1crypto/x509.py @@ -1136,7 +1136,7 @@ class Name(Choice): """ if isinstance(value, list): - return', '.join( + return ', '.join( reversed([self._recursive_humanize(sub_value) for sub_value in value]) ) return value.native diff --git a/changelog.md b/changelog.md index 67d1766..46eb459 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # changelog +## 1.4.0 + + - `core.ObjectIdentifier` and all derived classes now obey X.660 ยง7.6 and + thus restrict the first arc to 0 to 2, and the second arc to less than + 40 if the first arc is 0 or 1. This also fixes parsing of OIDs where the + first arc is 2 and the second arc is greater than 39. + - Fixed `keys.PublicKeyInfo.bit_size` to return an int rather than a float + on Python 3 when working with elliptic curve keys + - Fixed the `asn1crypto-tests` sdist on PyPi to work properly to generate a + .whl + ## 1.3.0 - Added `encrypt_key_pref` (`1.2.840.113549.1.9.16.2.11`) to diff --git a/codecov.json b/codecov.json index 2bec947..ad045a7 100644 --- a/codecov.json +++ b/codecov.json @@ -1,4 +1,5 @@ { "slug": "wbond/asn1crypto", - "token": "98876f5e-6517-4def-85ce-c6e508eee35a" + "token": "98876f5e-6517-4def-85ce-c6e508eee35a", + "disabled": true } diff --git a/dev/deps.py b/dev/deps.py index 7014172..8f52336 100644 --- a/dev/deps.py +++ b/dev/deps.py @@ -96,7 +96,78 @@ def _tuple_from_ver(version_string): A tuple of integers """ - return tuple(map(int, version_string.split('.'))) + match = re.search( + r'(\d+(?:\.\d+)*)' + r'([-._]?(?:alpha|a|beta|b|preview|pre|c|rc)\.?\d*)?' + r'(-\d+|(?:[-._]?(?:rev|r|post)\.?\d*))?' + r'([-._]?dev\.?\d*)?', + version_string + ) + if not match: + return tuple() + + nums = tuple(map(int, match.group(1).split('.'))) + + pre = match.group(2) + if pre: + pre = pre.replace('alpha', 'a') + pre = pre.replace('beta', 'b') + pre = pre.replace('preview', 'rc') + pre = pre.replace('pre', 'rc') + pre = re.sub(r'(?<!r)c', 'rc', pre) + pre = pre.lstrip('._-') + pre_dig_match = re.search(r'\d+', pre) + if pre_dig_match: + pre_dig = int(pre_dig_match.group(0)) + else: + pre_dig = 0 + pre = pre.rstrip('0123456789') + + pre_num = { + 'a': -3, + 'b': -2, + 'rc': -1, + }[pre] + + pre_tup = (pre_num, pre_dig) + else: + pre_tup = tuple() + + post = match.group(3) + if post: + post_dig_match = re.search(r'\d+', post) + if post_dig_match: + post_dig = int(post_dig_match.group(0)) + else: + post_dig = 0 + post_tup = (1, post_dig) + else: + post_tup = tuple() + + dev = match.group(4) + if dev: + dev_dig_match = re.search(r'\d+', dev) + if dev_dig_match: + dev_dig = int(dev_dig_match.group(0)) + else: + dev_dig = 0 + dev_tup = (-4, dev_dig) + else: + dev_tup = tuple() + + normalized = [nums] + if pre_tup: + normalized.append(pre_tup) + if post_tup: + normalized.append(post_tup) + if dev_tup: + normalized.append(dev_tup) + # This ensures regular releases happen after dev and prerelease, but + # before post releases + if not pre_tup and not post_tup and not dev_tup: + normalized.append((0, 0)) + + return tuple(normalized) def _open_archive(path): @@ -309,17 +380,99 @@ def _extract_package(deps_dir, pkg_path, pkg_dir): shutil.rmtree(staging_dir) -def _stage_requirements(deps_dir, path): +def _sort_pep440_versions(releases, include_prerelease): """ - Installs requirements without using Python to download, since - different services are limiting to TLS 1.2, and older version of - Python do not support that + :param releases: + A list of unicode string PEP 440 version numbers - :param deps_dir: - A unicode path to a temporary diretory to use for downloads + :param include_prerelease: + A boolean indicating if prerelease versions should be included - :param path: - A unicode filesystem path to a requirements file + :return: + A sorted generator of 2-element tuples: + 0: A unicode string containing a PEP 440 version number + 1: A tuple of tuples containing integers - this is the output of + _tuple_from_ver() for the PEP 440 version number and is intended + for comparing versions + """ + + parsed_versions = [] + for v in releases: + t = _tuple_from_ver(v) + if not include_prerelease and t[1][0] < 0: + continue + parsed_versions.append((v, t)) + + return sorted(parsed_versions, key=lambda v: v[1]) + + +def _is_valid_python_version(python_version, requires_python): + """ + Verifies the "python_version" and "requires_python" keys from a PyPi + download record are applicable to the current version of Python + + :param python_version: + The "python_version" value from a PyPi download JSON structure. This + should be one of: "py2", "py3", "py2.py3" or "source". + + :param requires_python: + The "requires_python" value from a PyPi download JSON structure. This + will be None, or a comma-separated list of conditions that must be + true. Ex: ">=3.5", "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + """ + + if python_version == "py2" and sys.version_info >= (3,): + return False + if python_version == "py3" and sys.version_info < (3,): + return False + + if requires_python is not None: + + def _ver_tuples(ver_str): + ver_str = ver_str.strip() + if ver_str.endswith('.*'): + ver_str = ver_str[:-2] + cond_tup = tuple(map(int, ver_str.split('.'))) + return (sys.version_info[:len(cond_tup)], cond_tup) + + for part in map(str_cls.strip, requires_python.split(',')): + if part.startswith('!='): + sys_tup, cond_tup = _ver_tuples(part[2:]) + if sys_tup == cond_tup: + return False + elif part.startswith('>='): + sys_tup, cond_tup = _ver_tuples(part[2:]) + if sys_tup < cond_tup: + return False + elif part.startswith('>'): + sys_tup, cond_tup = _ver_tuples(part[1:]) + if sys_tup <= cond_tup: + return False + elif part.startswith('<='): + sys_tup, cond_tup = _ver_tuples(part[2:]) + if sys_tup > cond_tup: + return False + elif part.startswith('<'): + sys_tup, cond_tup = _ver_tuples(part[1:]) + if sys_tup >= cond_tup: + return False + elif part.startswith('=='): + sys_tup, cond_tup = _ver_tuples(part[2:]) + if sys_tup != cond_tup: + return False + + return True + + +def _locate_suitable_download(downloads): + """ + :param downloads: + A list of dicts containing a key "url", "python_version" and + "requires_python" + + :return: + A unicode string URL, or None if not a valid release for the current + version of Python """ valid_tags = _pep425tags() @@ -330,8 +483,64 @@ def _stage_requirements(deps_dir, path): version_info = sys.version_info exe_suffix = '.%s-py%d.%d.exe' % (win_arch, version_info[0], version_info[1]) + wheels = {} + whl = None + tar_bz2 = None + tar_gz = None + exe = None + for download in downloads: + if not _is_valid_python_version(download.get('python_version'), download.get('requires_python')): + continue + + if exe_suffix and download['url'].endswith(exe_suffix): + exe = download['url'] + if download['url'].endswith('.whl'): + parts = os.path.basename(download['url']).split('-') + tag_impl = parts[-3] + tag_abi = parts[-2] + tag_arch = parts[-1].split('.')[0] + wheels[(tag_impl, tag_abi, tag_arch)] = download['url'] + if download['url'].endswith('.tar.bz2'): + tar_bz2 = download['url'] + if download['url'].endswith('.tar.gz'): + tar_gz = download['url'] + + # Find the most-specific wheel possible + for tag in valid_tags: + if tag in wheels: + whl = wheels[tag] + break + + if exe_suffix and exe: + url = exe + elif whl: + url = whl + elif tar_bz2: + url = tar_bz2 + elif tar_gz: + url = tar_gz + else: + return None + + return url + + +def _stage_requirements(deps_dir, path): + """ + Installs requirements without using Python to download, since + different services are limiting to TLS 1.2, and older version of + Python do not support that + + :param deps_dir: + A unicode path to a temporary diretory to use for downloads + + :param path: + A unicode filesystem path to a requirements file + """ + packages = _parse_requires(path) for p in packages: + url = None pkg = p['pkg'] pkg_sub_dir = None if p['type'] == 'url': @@ -359,53 +568,25 @@ def _stage_requirements(deps_dir, path): if os.path.exists(json_dest): os.remove(json_dest) - latest = pkg_info['info']['version'] - if p['type'] == '>=': - if _tuple_from_ver(p['ver']) > _tuple_from_ver(latest): - raise Exception('Unable to find version %s of %s, newest is %s' % (p['ver'], pkg, latest)) - version = latest - elif p['type'] == '==': + if p['type'] == '==': if p['ver'] not in pkg_info['releases']: raise Exception('Unable to find version %s of %s' % (p['ver'], pkg)) - version = p['ver'] - else: - version = latest - - wheels = {} - whl = None - tar_bz2 = None - tar_gz = None - exe = None - for download in pkg_info['releases'][version]: - if exe_suffix and download['url'].endswith(exe_suffix): - exe = download['url'] - if download['url'].endswith('.whl'): - parts = os.path.basename(download['url']).split('-') - tag_impl = parts[-3] - tag_abi = parts[-2] - tag_arch = parts[-1].split('.')[0] - wheels[(tag_impl, tag_abi, tag_arch)] = download['url'] - if download['url'].endswith('.tar.bz2'): - tar_bz2 = download['url'] - if download['url'].endswith('.tar.gz'): - tar_gz = download['url'] - - # Find the most-specific wheel possible - for tag in valid_tags: - if tag in wheels: - whl = wheels[tag] - break - - if exe_suffix and exe: - url = exe - elif whl: - url = whl - elif tar_bz2: - url = tar_bz2 - elif tar_gz: - url = tar_gz + url = _locate_suitable_download(pkg_info['releases'][p['ver']]) + if not url: + raise Exception('Unable to find a compatible download of %s == %s' % (pkg, p['ver'])) else: - raise Exception('Unable to find suitable download for %s' % pkg) + p_ver_tup = _tuple_from_ver(p['ver']) + for ver_str, ver_tup in reversed(_sort_pep440_versions(pkg_info['releases'], False)): + if p['type'] == '>=' and ver_tup < p_ver_tup: + break + url = _locate_suitable_download(pkg_info['releases'][ver_str]) + if url: + break + if not url: + if p['type'] == '>=': + raise Exception('Unable to find a compatible download of %s >= %s' % (pkg, p['ver'])) + else: + raise Exception('Unable to find a compatible download of %s' % pkg) local_path = _download(url, deps_dir) @@ -19,7 +19,6 @@ A fast, pure Python library for parsing and serializing ASN.1 structures. [![Travis CI](https://api.travis-ci.org/wbond/asn1crypto.svg?branch=master)](https://travis-ci.org/wbond/asn1crypto) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/asn1crypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/asn1crypto) [![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto) -[![Codecov](https://codecov.io/gh/wbond/asn1crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/wbond/asn1crypto) [![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/) ## Features @@ -112,7 +111,7 @@ faster to an order of magnitude or more. ## Current Release -1.3.0 - [changelog](changelog.md) +1.4.0 - [changelog](changelog.md) ## Dependencies @@ -156,10 +155,12 @@ links to the source for the various pre-defined type classes. ## Continuous Integration - - [Windows](https://ci.appveyor.com/project/wbond/asn1crypto/history) via AppVeyor - - [OS X](https://circleci.com/gh/wbond/asn1crypto) via CircleCI - - [Linux](https://travis-ci.org/wbond/asn1crypto/builds) via Travis CI - - [Test Coverage](https://codecov.io/gh/wbond/asn1crypto/commits) via Codecov +Various combinations of platforms and versions of Python are tested via: + + - [AppVeyor](https://ci.appveyor.com/project/wbond/asn1crypto/history) + - [CircleCI](https://circleci.com/gh/wbond/asn1crypto) + - [GitHub Actions](https://github.com/wbond/asn1crypto/actions) + - [Travis CI](https://travis-ci.org/wbond/asn1crypto/builds) ## Testing @@ -10,7 +10,7 @@ from setuptools.command.egg_info import egg_info PACKAGE_NAME = 'asn1crypto' -PACKAGE_VERSION = '1.3.0' +PACKAGE_VERSION = '1.4.0' PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/__init__.py b/tests/__init__.py index b669e5a..3b87410 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,8 +6,8 @@ import os import unittest -__version__ = '1.3.0' -__version_info__ = (1, 3, 0) +__version__ = '1.4.0' +__version_info__ = (1, 4, 0) def _import_from(mod, path, mod_dir=None): diff --git a/tests/setup.py b/tests/setup.py index fc20401..94aa438 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -10,10 +10,9 @@ from setuptools.command.egg_info import egg_info PACKAGE_NAME = 'asn1crypto' -PACKAGE_VERSION = '1.3.0' +PACKAGE_VERSION = '1.4.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, '..')) # setuptools 38.6.0 and newer know about long_description_content_type, but @@ -60,7 +59,7 @@ class EggInfoCommand(egg_info): if not os.path.exists(egg_info_path): os.mkdir(egg_info_path) shutil.copy2( - os.path.join(PACKAGE_ROOT, 'LICENSE'), + os.path.join(TESTS_ROOT, 'LICENSE'), os.path.join(egg_info_path, 'LICENSE') ) egg_info.run(self) diff --git a/tests/test_core.py b/tests/test_core.py index b9a7a82..9821c63 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -64,7 +64,7 @@ class NestSeqAny(core.Sequence): _oid_pair = ('id', 'value') _oid_specs = { - '3.4.5': Seq, + '2.3.4.5': Seq, } @@ -76,7 +76,7 @@ class NestSeqExplicit(core.Sequence): _oid_pair = ('id', 'value') _oid_specs = { - '3.4.5': Seq, + '2.3.4.5': Seq, } @@ -1314,14 +1314,14 @@ class CoreTests(unittest.TestCase): def test_wrong_asn1value3(self): with self.assertRaises(TypeError): NestSeqAny({ - 'id': '3.4.5', + 'id': '2.3.4.5', 'value': core.Integer(1) }) def test_wrong_asn1value4(self): with self.assertRaises(TypeError): NestSeqExplicit({ - 'id': '3.4.5', + 'id': '2.3.4.5', 'value': core.Integer(1) }) @@ -1334,3 +1334,32 @@ class CoreTests(unittest.TestCase): b.set_encoded_width(4) self.assertEqual(1, b.native) self.assertEqual(b'\x04\x04\x00\x00\x00\x01', b.dump()) + + @staticmethod + def object_identifier_info(): + return ( + ("0.0", b"\x06\x01\x00"), + ("0.39", b"\x06\x01\x27"), + ("1.0", b"\x06\x01\x28"), + ("1.39", b"\x06\x01\x4f"), + ("2.0", b"\x06\x01\x50"), + ("2.39", b"\x06\x01\x77"), + ("2.100.3", b"\x06\x03\x81\x34\x03"), + ("2.16.840.1.113730.1.1", b"\x06\x09\x60\x86\x48\x01\x86\xf8\x42\x01\x01"), + ) + + @data('object_identifier_info') + def object_identifier(self, native, der_bytes): + oid = core.ObjectIdentifier(native) + self.assertEqual(der_bytes, oid.dump()) + self.assertEqual(native, core.ObjectIdentifier.load(der_bytes).native) + + def test_broken_object_identifier(self): + with self.assertRaisesRegex(ValueError, "First arc must be "): + core.ObjectIdentifier("3.4.5") + + with self.assertRaisesRegex(ValueError, "Second arc must be "): + core.ObjectIdentifier("1.100.1000") + + with self.assertRaisesRegex(ValueError, "Second arc must be "): + core.ObjectIdentifier("0.40") diff --git a/tests/test_keys.py b/tests/test_keys.py index 2f2856e..eefd48f 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -1,8 +1,9 @@ # coding: utf-8 from __future__ import unicode_literals, division, absolute_import, print_function -import unittest import os +import sys +import unittest from asn1crypto import keys, core, util @@ -11,6 +12,11 @@ from ._unittest_compat import patch patch() +if sys.version_info < (3,): + int_types = (int, long) # noqa +else: + int_types = int + tests_root = os.path.dirname(__file__) fixtures_dir = os.path.join(tests_root, 'fixtures') @@ -481,7 +487,9 @@ class KeysTests(unittest.TestCase): with open(os.path.join(fixtures_dir, public_key_file), 'rb') as f: public_key = keys.PublicKeyInfo.load(f.read()) + self.assertIsInstance(private_key.bit_size, int_types) self.assertEqual(bit_size, private_key.bit_size) + self.assertIsInstance(public_key.bit_size, int_types) self.assertEqual(bit_size, public_key.bit_size) @staticmethod |