Repository: tlsfuzzer/python-ecdsa

Issue:
Limit acceptable point encoding formats (raw encoding in ASN.1 files)
While methods like https://github.com/tlsfuzzer/python-ecdsa/blob/73f3d3e47a8b48f60b71baa968cfaec0beeb4917/src/ecdsa/ecdh.py#L240 and https://github.com/tlsfuzzer/python-ecdsa/blob/73f3d3e47a8b48f60b71baa968cfaec0beeb4917/src/ecdsa/keys.py#L275-L277 will conveniently accept all valid point encoding formats for a selected curve, this may lead to a not standard-compliant behaviour. For example, in TLS 1.2, unless compressed point format was negotiated before, its use is forbidden; raw format is never correct.

Extend those methods to allow specifying allowed key formats, update
https://github.com/tlsfuzzer/python-ecdsa/blob/73f3d3e47a8b48f60b71baa968cfaec0beeb4917/src/ecdsa/keys.py#L411
to use it (raw encoding is not allowed in ASN.1, we shouldn't accept it).

Discussion:

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 935c7df..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,299 +0,0 @@
-name: GitHub CI
-
-on:
-  push:
-    branches:
-      - master
-  pull_request:
-
-jobs:
-  test:
-    runs-on: ${{ matrix.os }}
-    container: ${{ matrix.container }}
-    strategy:
-      fail-fast: false
-      matrix:
-        include:
-          - name: py2.6
-            os: ubuntu-latest
-            container: centos:6
-            python-version: 2.6
-          - name: py2.7
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: py27
-          - name: py2.7 with old gmpy
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: py27_old_gmpy
-          - name: py2.7 with old gmpy2
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: py27_old_gmpy2
-          - name: py2.7 with old six
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: py27_old_six
-          - name: py2.7 with gmpy
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: gmpypy27
-          - name: py2.7 with gmpy2
-            os: ubuntu-16.04
-            python-version: 2.7
-            tox-env: gmpy2py27
-          - name: py3.3
-            os: ubuntu-18.04
-            python-version: 3.3
-            tox-env: py33
-          - name: py3.4
-            os: ubuntu-18.04
-            python-version: 3.4
-            tox-env: py34
-          - name: py3.5
-            os: ubuntu-18.04
-            python-version: 3.5
-            tox-env: py35
-          - name: py3.6
-            os: ubuntu-18.04
-            python-version: 3.6
-            tox-env: py36
-          - name: py3.7
-            os: ubuntu-latest
-            python-version: 3.7
-            tox-env: py37
-          - name: py3.8
-            os: ubuntu-latest
-            python-version: 3.8
-            tox-env: py38
-          - name: py3.9
-            os: ubuntu-latest
-            python-version: 3.9
-            tox-env: py39
-          - name: py3.9 with gmpy
-            os: ubuntu-latest
-            python-version: 3.9
-            tox-env: gmpypy39
-          - name: py3.9 with gmpy2
-            os: ubuntu-latest
-            python-version: 3.9
-            tox-env: gmpy2py39
-          - name: pypy
-            os: ubuntu-latest
-            python-version: pypy-2.7
-            tox-env: pypy
-          - name: pypy3
-            os: ubuntu-latest
-            python-version: pypy-3.7
-            tox-env: pypy3
-          # special configurations
-          - name: py2.7 with instrumental
-            os: ubuntu-16.04
-            python-version: 2.7
-            opt-deps: ['instrumental']
-          - name: code checks
-            os: ubuntu-latest
-            python-version: 3.9
-            tox-env: codechecks
-    steps:
-      - uses: actions/checkout@v2
-        if: ${{ !matrix.container }}
-        with:
-          fetch-depth: 50
-      - uses: actions/checkout@v1
-        # centos 6 doesn't have glibc new enough for the nodejs used by v2
-        if: ${{ matrix.container }}
-        with:
-          fetch-depth: 50
-      - name: Ensure dependencies on CentOS
-        if: ${{ matrix.container }}
-        run: |
-          ls /etc/yum.repos.d/
-          cat /etc/yum.repos.d/CentOS-Base.repo
-          rm /etc/yum.repos.d/CentOS-Base.repo
-          cat > /etc/yum.repos.d/CentOS-Base.repo <<EOF
-          [base]
-          name=CentOS-$releasever - Base
-          baseurl=https://vault.centos.org/6.10/os/x86_64/
-          gpgcheck=1
-          gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
-
-          [updates]
-          name=CentOS-$releasever - Updates
-          baseurl=https://vault.centos.org/6.10/updates/x86_64/
-          gpgcheck=1
-          gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
-
-          [extras]
-          name=CentOS-$releasever - Extras
-          baseurl=https://vault.centos.org/6.10/extras/x86_64/
-          gpgcheck=1
-          gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6
-
-          EOF
-          echo installing
-          yum clean all
-          yum repolist all
-          yum install -y git make python curl gcc libffi-devel python-devel glibc-devel openssl-devel wget
-      - name: Verify git status
-        run: |
-          git status
-          git remote -v
-      - name: Ensure we have baseline branch for quality coverage
-        run: git fetch origin master:refs/remotes/origin/master
-      - name: Set up Python ${{ matrix.python-version }}
-        # we use containers to use the native python version from them
-        if: ${{ !matrix.container }}
-        uses: actions/setup-python@v2
-        with:
-          python-version: ${{ matrix.python-version }}
-      - name: Display Python version
-        run: python -c "import sys; print(sys.version)"
-      - name: Display installed python package versions
-        run: |
-          pip list || :
-      - name: Ensure working pip on 3.3
-        if: ${{ matrix.python-version == '3.3' }}
-        run: |
-          curl -o get-pip.py https://bootstrap.pypa.io/pip/3.3/get-pip.py
-          python get-pip.py
-      - name: Ensure working pip on 2.6
-        if: ${{ matrix.python-version == '2.6' }}
-        run: |
-          # pypi deprecated SNI-less access to the CDN, so we have to download
-          # the packages manually
-          curl -o get-pip.py https://bootstrap.pypa.io/pip/2.6/get-pip.py
-          wget https://files.pythonhosted.org/packages/ac/95/a05b56bb975efa78d3557efa36acaf9cf5d2fd0ee0062060493687432e03/pip-9.0.3-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/27/f6/fabfc9c71c9b1b99d2ec4768a6e1f73b2e924f51c89d436302b8c2a25459/setuptools-36.8.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/8a/e9/8468cd68b582b06ef554be0b96b59f59779627131aad48f8a5bce4b13450/wheel-0.29.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl
-          python get-pip.py pip-9.0.3-py2.py3-none-any.whl setuptools-36.8.0-py2.py3-none-any.whl wheel-0.29.0-py2.py3-none-any.whl argparse-1.4.0-py2.py3-none-any.whl
-          pip list
-          wget https://files.pythonhosted.org/packages/3b/7e/293d19ccd106119e35db4bf3e111b1895098f618b455b758aa636496cf03/setuptools-28.8.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/83/53/e120833aa2350db333df89a40dea3b310dd9dabf6f29eaa18934a597dc79/wheel-0.30.0a0-py2.py3-none-any.whl
-          pip install setuptools-28.8.0-py2.py3-none-any.whl wheel-0.30.0a0-py2.py3-none-any.whl
-      - name: Install instrumental
-        if: ${{ contains(matrix.opt-deps, 'instrumental') }}
-        run: pip install instrumental
-      - name: Install gmpy
-        if: ${{ contains(matrix.tox-env, 'gmpyp') }}
-        run: pip install gmpy
-      - name: Install gmpy2 dependencies
-        if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }}
-        run: sudo apt-get install -y libmpfr-dev libmpc-dev
-      - name: Install gmpy2
-        if: ${{ contains(matrix.tox-env, 'gmpy2') || contains(matrix.tox-env, 'instrumental') }}
-        run: pip install gmpy2
-      - name: Install build dependencies (2.6)
-        if: ${{ matrix.python-version == '2.6' }}
-        run: |
-          wget https://files.pythonhosted.org/packages/1d/4e/20c679f8c5948f7c48591fde33d442e716af66a31a88f5791850a75041eb/tox-2.9.1-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/d9/9d/077582a4c6d771e3b742631e6c1d3688f48210626de488e032776242b3f2/inflect-0.3.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/79/db/7c0cfe4aa8341a5fab4638952520d8db6ab85ff84505e12c00ea311c3516/pyOpenSSL-17.5.0-py2.py3-none-any.whl 
-          wget https://files.pythonhosted.org/packages/2d/bf/960e5a422db3ac1a5e612cb35ca436c3fc985ed4b7ed13a1b4879006f450/cffi-1.13.2.tar.gz
-          wget https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/72/20/7f0f433060a962200b7272b8c12ba90ef5b903e218174301d0abfd523813/unittest2-1.1.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/a8/5a/5cf074e1c6681dcbb4e640113f58bed16955e7da9a6c8090b518031775e7/hypothesis-2.0.0.tar.gz
-          wget https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz
-          wget https://files.pythonhosted.org/packages/e6/35/f187bdf23be87092bd0f1200d43d23076cee4d0dec109f195173fd3ebc79/mock-2.0.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/ed/ea/e20b5cbebf45d3096e8138ab74eda139595d827677f38e9dd543e6015bdf/virtualenv-15.2.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/53/67/9620edf7803ab867b175e4fd23c7b8bd8eba11cb761514dcd2e726ef07da/py-1.4.34-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/65/26/32b8464df2a97e6dd1b656ed26b2c194606c16fe163c695a992b36c11cdf/six-1.13.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/4d/d1/e478b8a33230f85f38e35b386376fbd115219de2a2c4c8783610851ad1c3/pluggy-0.5.2-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/78/c5/7188f15a92413096c93053d5304718e1f6ba88b818357d05d19250ebff85/cryptography-2.1.4.tar.gz
-          wget https://files.pythonhosted.org/packages/8c/2d/aad7f16146f4197a11f8e91fb81df177adcc2073d36a17b1491fd09df6ed/pycparser-2.18.tar.gz
-          wget https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz
-          wget https://files.pythonhosted.org/packages/65/47/7e02164a2a3db50ed6d8a6ab1d6d60b69c4c3fdf57a284257925dfc12bda/requests-2.19.1-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/17/0a/6ac05a3723017a967193456a2efa0aa9ac4b51456891af1e2353bb9de21e/traceback2-1.4.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/31/77/3781f65cafe55480b56914def99022a5d2965a4bb269655c89ef2f1de3cd/importlib-1.0.4.zip
-          wget https://files.pythonhosted.org/packages/7d/b0/23d19892f8d91ec9c5b8a2035659bce23587fed419d68fa3d70b6abf8bcd/Counter-1.0.0.tar.gz
-          wget https://files.pythonhosted.org/packages/69/cb/f5be453359271714c01b9bd06126eaf2e368f1fddfff30818754b5ac2328/funcsigs-1.0.2-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/fb/48/69046506f6ac61c1eaa9a0d42d22d54673b69e176d30ca98e3f61513e980/pbr-5.5.1-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/b5/a8/56be92dcd4a5bf1998705a9b4028249fe7c9a035b955fe93b6a3e5b829f8/asn1crypto-1.4.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/6f/2c/a9386903ece2ea85e9807e0e062174dc26fdce8b05f216d00491be29fad5/enum34-1.1.10-py2-none-any.whl
-          wget https://files.pythonhosted.org/packages/c2/f8/49697181b1651d8347d24c095ce46c7346c37335ddc7d255833e7cde674d/ipaddress-1.0.23-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/bd/c9/6fdd990019071a4a32a5e7cb78a1d92c53851ef4f56f62a3486e6a7d8ffb/urllib3-1.23-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/5e/a0/5f06e1e1d463903cf0c0eebeb751791119ed7a4b3737fdc9a77f1cdfb51f/certifi-2020.12.5-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/c7/a3/c5da2a44c85bfbb6eebcfc1dde24933f8704441b98fdde6528f4831757a6/linecache2-1.0.0-py2.py3-none-any.whl
-          wget https://files.pythonhosted.org/packages/53/25/ef88e8e45db141faa9598fbf7ad0062df8f50f881a36ed6a0073e1572126/ordereddict-1.1.tar.gz
-          wget https://files.pythonhosted.org/packages/ef/41/d8a61f1b2ba308e96b36106e95024977e30129355fd12087f23e4b9852a1/pytest-3.2.5-py2.py3-none-any.whl
-          pip install pycparser-2.18.tar.gz importlib-1.0.4.zip Counter-1.0.0.tar.gz tox-2.9.1-py2.py3-none-any.whl inflect-0.3.0-py2.py3-none-any.whl pyOpenSSL-17.5.0-py2.py3-none-any.whl cffi-1.13.2.tar.gz idna-2.7-py2.py3-none-any.whl unittest2-1.1.0-py2.py3-none-any.whl hypothesis-2.0.0.tar.gz coverage-4.5.4.tar.gz mock-2.0.0-py2.py3-none-any.whl virtualenv-15.2.0-py2.py3-none-any.whl py-1.4.34-py2.py3-none-any.whl six-1.13.0-py2.py3-none-any.whl pluggy-0.5.2-py2.py3-none-any.whl cryptography-2.1.4.tar.gz docopt-0.6.2.tar.gz requests-2.19.1-py2.py3-none-any.whl traceback2-1.4.0-py2.py3-none-any.whl funcsigs-1.0.2-py2.py3-none-any.whl pbr-5.5.1-py2.py3-none-any.whl asn1crypto-1.4.0-py2.py3-none-any.whl enum34-1.1.10-py2-none-any.whl ipaddress-1.0.23-py2.py3-none-any.whl chardet-3.0.4-py2.py3-none-any.whl urllib3-1.23-py2.py3-none-any.whl certifi-2020.12.5-py2.py3-none-any.whl linecache2-1.0.0-py2.py3-none-any.whl ordereddict-1.1.tar.gz pytest-3.2.5-py2.py3-none-any.whl git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls
-      - name: Install build dependencies
-        if: ${{ matrix.python-version != '2.6' }}
-        run: |
-          PYTHON_VERSION=${{ matrix.python-version }}
-          PYTHON_VERSION=${PYTHON_VERSION#pypy-}
-          if [[ -e build-requirements-${PYTHON_VERSION}.txt ]]; then
-            pip install -r build-requirements-${PYTHON_VERSION}.txt;
-          else
-            pip install -r build-requirements.txt;
-          fi
-      - name: Display installed python package versions
-        run: pip list
-      - name: Test native speed
-        # tox uses pip to install dependenceis, so it breaks on py2.6
-        if: ${{ !contains(matrix.tox-env, 'gmpy') && matrix.python-version != '2.6'}}
-        run: tox -e speed
-      - name: Test speed with gmpy
-        if: ${{ contains(matrix.tox-env, 'gmpyp') }}
-        run: tox -e speedgmpy
-      - name: Test speed with gmpy2
-        if: ${{ contains(matrix.tox-env, 'gmpy2') }}
-        run: tox -e speedgmpy2
-      - name: Run unit tests (2.6)
-        if: ${{ matrix.python-version == '2.6' }}
-        run: |
-          # because tox uses pip, and pip is broken on py2.6, we need run the
-          # tests directly on the system
-          coverage run --branch -m pytest
-      - name: Run unit tests
-        if: ${{ matrix.tox-env }}
-        run: tox -e ${{ matrix.tox-env }}
-      - name: instrumental test coverage on PR
-        if: ${{ contains(matrix.opt-deps, 'instrumental') && github.event.pull_request }}
-        env:
-          BASE_REF: ${{ github.event.pull_request.base.ref }}
-        run: |
-          git fetch origin $BASE_REF
-          MERGE_BASE=$(git merge-base origin/$BASE_REF HEAD)
-          echo "MERGE_BASE:" $MERGE_BASE
-          git checkout $MERGE_BASE
-          instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
-          instrumental -f .instrumental.cov -s
-          instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental
-          git checkout $GITHUB_SHA
-          instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
-          instrumental -f .instrumental.cov -sr
-          instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1
-      - name: instrumental test coverage on push
-        if: ${{ contains(matrix.opt-deps, 'instrumental') && !github.event.pull_request }}
-        run: |
-          instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa
-          instrumental -f .instrumental.cov -s
-          # just log the values when merging
-          instrumental -f .instrumental.cov -s | python diff-instrumental.py
-      - name: Publish coverage to Coveralls
-        if: ${{ !matrix.opt-deps && matrix.tox-env != 'codechecks' }}
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          COVERALLS_FLAG_NAME: ${{ matrix.name }}
-          COVERALLS_PARALLEL: true
-          COVERALLS_SERVICE_NAME: github
-        run: coveralls
-
-  coveralls:
-    name: Indicate completion to coveralls.io
-    needs: test
-    runs-on: ubuntu-latest
-    container: python:3-slim
-    steps:
-      - name: Install coveralls
-        run: |
-          pip3 install --upgrade coveralls
-      - name: Send "finished" signal to coveralls
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          COVERALLS_SERVICE_NAME: github
-        run: |
-          coveralls --finish
diff --git a/README.md b/README.md
index 2c77a6c..86aadc0 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Pure-Python ECDSA and ECDH
 
-[![Build Status](https://github.com/tlsfuzzer/python-ecdsa/workflows/GitHub%20CI/badge.svg?branch=master)](https://github.com/tlsfuzzer/python-ecdsa/actions?query=workflow%3A%22GitHub+CI%22+branch%3Amaster)
+[![Build Status](https://travis-ci.com/tlsfuzzer/python-ecdsa.svg?branch=master)](https://travis-ci.com/tlsfuzzer/python-ecdsa)
 [![Coverage Status](https://coveralls.io/repos/github/tlsfuzzer/python-ecdsa/badge.svg?branch=master)](https://coveralls.io/github/tlsfuzzer/python-ecdsa?branch=master)
 [![condition coverage](https://img.shields.io/badge/condition%20coverage-87%25-yellow)](https://travis-ci.com/github/tlsfuzzer/python-ecdsa/jobs/458951056#L544)
 [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/tlsfuzzer/python-ecdsa.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tlsfuzzer/python-ecdsa/context:python)
diff --git a/build-requirements-2.6.txt b/build-requirements-2.6.txt
index ee3d22f..2d0d14e 100644
--- a/build-requirements-2.6.txt
+++ b/build-requirements-2.6.txt
@@ -1,10 +1,6 @@
 tox
-inflect<0.3.1
-pyopenssl<18
-cffi<1.14
-git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls
+coveralls<1.3.0
 idna<2.8
 unittest2
 hypothesis<3
-coverage
-mock==2.0.0
+coverage==4.5.4
diff --git a/build-requirements-2.7.txt b/build-requirements-2.7.txt
deleted file mode 100644
index b053e03..0000000
--- a/build-requirements-2.7.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-tox
-git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls
-hypothesis
-pytest>=4.6.0
-coverage
diff --git a/build-requirements-3.3.txt b/build-requirements-3.3.txt
index d817d2b..9d486f7 100644
--- a/build-requirements-3.3.txt
+++ b/build-requirements-3.3.txt
@@ -1,9 +1,9 @@
-git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls
+python-coveralls
 pluggy<0.6
 tox<3
 wheel<0.30
 virtualenv==15.2.0
 enum34
 hypothesis<3.44
-coverage<5.0
+coverage==4.5.4
 urllib3<=1.25.8
diff --git a/build-requirements-3.4.txt b/build-requirements-3.4.txt
index ce3e0fe..4d95aae 100644
--- a/build-requirements-3.4.txt
+++ b/build-requirements-3.4.txt
@@ -1,6 +1,6 @@
 tox
-git+https://github.com/tomato42/coveralls-python.git@add-py26#egg=coveralls
+python-coveralls
 hypothesis
 pytest>=4.6.0
 PyYAML<5.3
-coverage
+coverage==4.5.4
diff --git a/build-requirements.txt b/build-requirements.txt
index a35f6ab..a7b75c1 100644
--- a/build-requirements.txt
+++ b/build-requirements.txt
@@ -1,5 +1,5 @@
 tox
-coveralls
+python-coveralls
 hypothesis
 pytest>=4.6.0
-coverage
+coverage==4.5.4
diff --git a/src/ecdsa/ecdh.py b/src/ecdsa/ecdh.py
index a12e94e..824a09b 100644
--- a/src/ecdsa/ecdh.py
+++ b/src/ecdsa/ecdh.py
@@ -216,7 +216,7 @@ class ECDH(object):
 
         :return: public (verifying) key from local private key.
         :rtype: VerifyingKey object
-       """
+        """
         return self.private_key.get_verifying_key()
 
     def load_received_public_key(self, public_key):
@@ -237,7 +237,9 @@ class ECDH(object):
             raise InvalidCurveError("Curve mismatch.")
         self.public_key = public_key
 
-    def load_received_public_key_bytes(self, public_key_str):
+    def load_received_public_key_bytes(
+        self, public_key_str, valid_encodings=None
+    ):
         """
         Load public key from byte string.
 
@@ -247,9 +249,16 @@ class ECDH(object):
 
         :param public_key_str: public key in bytes string format
         :type public_key_str: :term:`bytes-like object`
+        :param valid_encodings: list of acceptable point encoding formats,
+            supported ones are: :term:`uncompressed`, :term:`compressed`,
+            :term:`hybrid`, and :term:`raw encoding` (specified with ``raw``
+            name). All formats by default (specified with ``None``).
+        :type valid_encodings: :term:`set-like object`
         """
         return self.load_received_public_key(
-            VerifyingKey.from_string(public_key_str, self.curve)
+            VerifyingKey.from_string(
+                public_key_str, self.curve, valid_encodings
+            )
         )
 
     def load_received_public_key_der(self, public_key_der):
diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py
index ded7cfb..e072354 100644
--- a/src/ecdsa/keys.py
+++ b/src/ecdsa/keys.py
@@ -64,6 +64,10 @@ Primary classes for performing signing and verification operations.
         string) is endianess dependant! Signature computed over ``array.array``
         of integers on a big-endian system will not be verified on a
         little-endian system and vice-versa.
+
+    set-like object
+        All the types that support the ``in`` operator, like ``list``,
+        ``tuple``, ``set``, ``frozenset``, etc.
 """
 
 import binascii
@@ -332,7 +336,12 @@ class VerifyingKey(object):
 
     @classmethod
     def from_string(
-        cls, string, curve=NIST192p, hashfunc=sha1, validate_point=True
+        cls,
+        string,
+        curve=NIST192p,
+        hashfunc=sha1,
+        validate_point=True,
+        valid_encodings=None,
     ):
         """
         Initialise the object from byte encoding of public key.
@@ -355,6 +364,11 @@ class VerifyingKey(object):
         :param validate_point: whether to verify that the point lays on the
             provided curve or not, defaults to True
         :type validate_point: bool
+        :param valid_encodings: list of acceptable point encoding formats,
+            supported ones are: :term:`uncompressed`, :term:`compressed`,
+            :term:`hybrid`, and :term:`raw encoding` (specified with ``raw``
+            name). All formats by default (specified with ``None``).
+        :type valid_encodings: :term:`set-like object`
 
         :raises MalformedPointError: if the public point does not lay on the
             curve or the encoding is invalid
@@ -362,31 +376,43 @@ class VerifyingKey(object):
         :return: Initialised VerifyingKey object
         :rtype: VerifyingKey
         """
+        if valid_encodings is None:
+            valid_encodings = set(
+                ["uncompressed", "compressed", "hybrid", "raw"]
+            )
         string = normalise_bytes(string)
         sig_len = len(string)
-        if sig_len == curve.verifying_key_length:
+        if sig_len == curve.verifying_key_length and "raw" in valid_encodings:
             point = cls._from_raw_encoding(string, curve)
-        elif sig_len == curve.verifying_key_length + 1:
-            if string[:1] in (b("\x06"), b("\x07")):
+        elif sig_len == curve.verifying_key_length + 1 and (
+            "hybrid" in valid_encodings or "uncompressed" in valid_encodings
+        ):
+            if (
+                string[:1] in (b("\x06"), b("\x07"))
+                and "hybrid" in valid_encodings
+            ):
                 point = cls._from_hybrid(string, curve, validate_point)
-            elif string[:1] == b("\x04"):
+            elif string[:1] == b("\x04") and "uncompressed" in valid_encodings:
                 point = cls._from_raw_encoding(string[1:], curve)
             else:
                 raise MalformedPointError(
                     "Invalid X9.62 encoding of the public point"
                 )
-        elif sig_len == curve.verifying_key_length // 2 + 1:
+        elif (
+            sig_len == curve.verifying_key_length // 2 + 1
+            and "compressed" in valid_encodings
+        ):
             point = cls._from_compressed(string, curve)
         else:
             raise MalformedPointError(
                 "Length of string does not match lengths of "
-                "any of the supported encodings of {0} "
-                "curve.".format(curve.name)
+                "any of the enabled ({1}) encodings of {0} "
+                "curve.".format(curve.name, ", ".join(valid_encodings))
             )
         return cls.from_public_point(point, curve, hashfunc, validate_point)
 
     @classmethod
-    def from_pem(cls, string, hashfunc=sha1):
+    def from_pem(cls, string, hashfunc=sha1, valid_encodings=None):
         """
         Initialise from public key stored in :term:`PEM` format.
 
@@ -400,14 +426,23 @@ class VerifyingKey(object):
 
         :param string: text with PEM-encoded public ECDSA key
         :type string: str
+        :param valid_encodings: list of allowed point encodings.
+            By default :term:`uncompressed`, :term:`compressed`, and
+            :term:`hybrid`. To read malformed files, include
+            :term:`raw encoding` with ``raw`` in the list.
+        :type valid_encodings: :term:`set-like object
 
         :return: Initialised VerifyingKey object
         :rtype: VerifyingKey
         """
-        return cls.from_der(der.unpem(string), hashfunc=hashfunc)
+        return cls.from_der(
+            der.unpem(string),
+            hashfunc=hashfunc,
+            valid_encodings=valid_encodings,
+        )
 
     @classmethod
-    def from_der(cls, string, hashfunc=sha1):
+    def from_der(cls, string, hashfunc=sha1, valid_encodings=None):
         """
         Initialise the key stored in :term:`DER` format.
 
@@ -432,10 +467,17 @@ class VerifyingKey(object):
 
         :param string: binary string with the DER encoding of public ECDSA key
         :type string: bytes-like object
+        :param valid_encodings: list of allowed point encodings.
+            By default :term:`uncompressed`, :term:`compressed`, and
+            :term:`hybrid`. To read malformed files, include
+            :term:`raw encoding` with ``raw`` in the list.
+        :type valid_encodings: :term:`set-like object
 
         :return: Initialised VerifyingKey object
         :rtype: VerifyingKey
         """
+        if valid_encodings is None:
+            valid_encodings = set(["uncompressed", "compressed", "hybrid"])
         string = normalise_bytes(string)
         # [[oid_ecPublicKey,oid_curve], point_str_bitstring]
         s1, empty = der.remove_sequence(string)
@@ -467,7 +509,12 @@ class VerifyingKey(object):
         # raw encoding of point is invalid in DER files
         if len(point_str) == curve.verifying_key_length:
             raise der.UnexpectedDER("Malformed encoding of public point")
-        return cls.from_string(point_str, curve, hashfunc=hashfunc)
+        return cls.from_string(
+            point_str,
+            curve,
+            hashfunc=hashfunc,
+            valid_encodings=valid_encodings,
+        )
 
     @classmethod
     def from_public_key_recovery(
diff --git a/tox.ini b/tox.ini
index 742f3fb..60562de 100644
--- a/tox.ini
+++ b/tox.ini
@@ -31,7 +31,7 @@ deps =
      py: pytest
      py: hypothesis
      py{33}: wheel<0.30
-     coverage
+     coverage==4.5.4
 commands = coverage run --branch -m pytest {posargs:src/ecdsa}
 
 [testenv:py27_old_gmpy]
@@ -62,7 +62,7 @@ deps =
      instrumental
      hypothesis
      pytest>=4.6.0
-     coverage
+     coverage==4.5.4
      six
 commands =
          instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' {envbindir}/pytest {posargs:src/ecdsa}


Test patch:
diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py
index 58cf961..31353f4 100644
--- a/src/ecdsa/test_keys.py
+++ b/src/ecdsa/test_keys.py
@@ -13,7 +13,7 @@ import array
 import pytest
 import hashlib
 
-from .keys import VerifyingKey, SigningKey
+from .keys import VerifyingKey, SigningKey, MalformedPointError
 from .der import unpem
 from .util import (
     sigencode_string,
@@ -153,6 +153,12 @@ class TestVerifyingKeyFromDer(unittest.TestCase):
 
         cls.sk2 = SigningKey.generate(vk.curve)
 
+    def test_load_key_with_disabled_format(self):
+        with self.assertRaises(MalformedPointError) as e:
+            VerifyingKey.from_der(self.key_bytes, valid_encodings=["raw"])
+
+        self.assertIn("enabled (raw) encodings", str(e.exception))
+
     def test_custom_hashfunc(self):
         vk = VerifyingKey.from_der(self.key_bytes, hashlib.sha256)
 
diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py
index f61981f..d1418c0 100644
--- a/src/ecdsa/test_pyecdsa.py
+++ b/src/ecdsa/test_pyecdsa.py
@@ -722,6 +722,65 @@ class ECDSA(unittest.TestCase):
         from_uncompressed = VerifyingKey.from_string(b("\x06") + enc)
         self.assertEqual(from_uncompressed.pubkey.point, vk.pubkey.point)
 
+    def test_uncompressed_decoding_as_only_alowed(self):
+        enc = b(
+            "\x04"
+            "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"
+            "\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4"
+            "z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*"
+        )
+        vk = VerifyingKey.from_string(enc, valid_encodings=("uncompressed",))
+        sk = SigningKey.from_secret_exponent(123456789)
+
+        self.assertEqual(vk, sk.verifying_key)
+
+    def test_raw_decoding_with_blocked_format(self):
+        enc = b(
+            "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"
+            "\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4"
+            "z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*"
+        )
+        with self.assertRaises(MalformedPointError) as exp:
+            VerifyingKey.from_string(enc, valid_encodings=("hybrid",))
+
+        self.assertIn("hybrid", str(exp.exception))
+
+    def test_uncompressed_decoding_with_blocked_format(self):
+        enc = b(
+            "\x04"
+            "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"
+            "\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4"
+            "z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*"
+        )
+        with self.assertRaises(MalformedPointError) as exp:
+            VerifyingKey.from_string(enc, valid_encodings=("hybrid",))
+
+        self.assertIn("Invalid X9.62 encoding", str(exp.exception))
+
+    def test_hybrid_decoding_with_blocked_format(self):
+        enc = b(
+            "\x06"
+            "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"
+            "\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4"
+            "z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*"
+        )
+        with self.assertRaises(MalformedPointError) as exp:
+            VerifyingKey.from_string(enc, valid_encodings=("uncompressed",))
+
+        self.assertIn("Invalid X9.62 encoding", str(exp.exception))
+
+    def test_compressed_decoding_with_blocked_format(self):
+        enc = b(
+            "\x02"
+            "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"
+            "\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4"
+            "z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*"
+        )[:25]
+        with self.assertRaises(MalformedPointError) as exp:
+            VerifyingKey.from_string(enc, valid_encodings=("hybrid", "raw"))
+
+        self.assertIn("(hybrid, raw)", str(exp.exception))
+
     def test_decoding_with_malformed_uncompressed(self):
         enc = b(
             "\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3"