Mark ab/6881855 as merged

Bug: 172690556
Change-Id: Id713f4b80465d0fe3efbaf28edd3b1dfc52ffc02
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 @@
       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 @@
       language: python
       python: "3.7"
     - os: linux
+      arch: ppc64le
+      dist: bionic
+      language: python
+      python: "3.7"
+    - os: linux
       dist: xenial
       language: python
       python: "pypy"
diff --git a/METADATA b/METADATA
index 4b41dcd..02bee43 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@
     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 @@
                 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 @@
                 # 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 @@
 
         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 @@
         """
 
         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 @@
         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,6 +380,151 @@
             shutil.rmtree(staging_dir)
 
 
+def _sort_pep440_versions(releases, include_prerelease):
+    """
+    :param releases:
+        A list of unicode string PEP 440 version numbers
+
+    :param include_prerelease:
+        A boolean indicating if prerelease versions should be included
+
+    :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()
+
+    exe_suffix = None
+    if sys.platform == 'win32' and _pep425_implementation() == 'cp':
+        win_arch = 'win32' if sys.maxsize == 2147483647 else 'win-amd64'
+        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
@@ -322,16 +538,9 @@
         A unicode filesystem path to a requirements file
     """
 
-    valid_tags = _pep425tags()
-
-    exe_suffix = None
-    if sys.platform == 'win32' and _pep425_implementation() == 'cp':
-        win_arch = 'win32' if sys.maxsize == 2147483647 else 'win-amd64'
-        version_info = sys.version_info
-        exe_suffix = '.%s-py%d.%d.exe' % (win_arch, version_info[0], version_info[1])
-
     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 @@
             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']
+                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:
-                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
-            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)
 
diff --git a/readme.md b/readme.md
index 5f05a4e..62c070e 100644
--- a/readme.md
+++ b/readme.md
@@ -19,7 +19,6 @@
 [![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 @@
 
 ## Current Release
 
-1.3.0 - [changelog](changelog.md)
+1.4.0 - [changelog](changelog.md)
 
 ## Dependencies
 
@@ -156,10 +155,12 @@
 
 ## 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
 
diff --git a/setup.py b/setup.py
index ce6f2e2..991eb59 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
 
 
 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 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 @@
 
 
 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 @@
         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 @@
 
     _oid_pair = ('id', 'value')
     _oid_specs = {
-        '3.4.5': Seq,
+        '2.3.4.5': Seq,
     }
 
 
@@ -76,7 +76,7 @@
 
     _oid_pair = ('id', 'value')
     _oid_specs = {
-        '3.4.5': Seq,
+        '2.3.4.5': Seq,
     }
 
 
@@ -1314,14 +1314,14 @@
     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 @@
         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 @@
 
 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 @@
         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