Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge in backend-explicit-tests #129

Merged
merged 53 commits into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0861744
Initial work to enable testing of isolated cryptographic backends.
mattsb42-aws Aug 25, 2018
c9a3697
add git ignores for PyCharm, PyEnv, and PyTest metadata
mattsb42-aws Aug 25, 2018
e63c6c9
update tox envlist with the new testenv names
mattsb42-aws Sep 7, 2018
2954bac
update Travis config to run the explicit backend tests
mattsb42-aws Sep 10, 2018
4d7b598
fix typo that duplicated pycryptodome tests in Travis
mattsb42-aws Sep 10, 2018
b4d6ca2
Merge pull request #109 from mattsb42-aws/explicit-dependencies
mpdavis Nov 9, 2018
d6d2b30
import rsa backends with backend-explicit naming to avoid confusion
mattsb42-aws Dec 25, 2018
5ee69d8
make backend-explicit tests use backend-explicit keys
mattsb42-aws Dec 25, 2018
9b6fa7a
add ability to remove base crypto backend for tests
mattsb42-aws Dec 25, 2018
28dcc70
update Travis CI to run cryptography-only
mattsb42-aws Dec 25, 2018
9222673
isolate ecdsa key class imports to allow for missing library
mattsb42-aws Dec 25, 2018
04ef8b5
isolate asn1/der signature transformation to simplify testing
mattsb42-aws Dec 25, 2018
cea6ae8
add tests to verify der/asn1 conversions
mattsb42-aws Dec 25, 2018
e786a0e
remove dependency of pyca/cryptography backend on python-ecdsa
mattsb42-aws Dec 25, 2018
93a3c0a
JOSE ECDSA signature encoding is raw encoding, not ASN1
mattsb42-aws Dec 25, 2018
e2189cb
math.ceil returns a float in Python 2
mattsb42-aws Dec 25, 2018
26e42c0
disable Firebase tests on python-rsa backend
mattsb42-aws Dec 25, 2018
eaa6383
expand RSA and ECDSA compatibility tests to validate serialization co…
mattsb42-aws Dec 26, 2018
130c383
add 2048-bit RSA private key for tests
mattsb42-aws Dec 26, 2018
939950d
expand all tests that use an RSA private key to test with both sizes
mattsb42-aws Dec 26, 2018
404ddc9
add legacy read compatibility test for rsa_backend
mattsb42-aws Dec 26, 2018
419b7d0
rsa_backend uses pyasn1 directly so it needs to be a direct dependency
mattsb42-aws Dec 26, 2018
5dd8cbc
add tests got PKCS1-PKCS8 RSA private key encoding
mattsb42-aws Dec 26, 2018
9a4ec31
fix rsa_backend RSA private key PKCS8 encoding
mattsb42-aws Dec 26, 2018
edc4438
convert ASN1 check values to hex for better readability and add check…
mattsb42-aws Dec 27, 2018
2ac41b7
Merge pull request #116 from mattsb42-aws/compat-rsa
mpdavis Dec 27, 2018
b91fe07
Merge pull request #118 from mattsb42-aws/firebase
mpdavis Dec 27, 2018
ff8a03b
Merge pull request #117 from mattsb42-aws/no-ecdsa
mpdavis Dec 27, 2018
428c29c
Merge branch 'backend-explicit-tests' into fix-rsa
mattsb42-aws Dec 27, 2018
c28f15a
Merge pull request #120 from mattsb42-aws/fix-rsa
mpdavis Dec 27, 2018
8e78302
move asn1 private key processing into separate module
mattsb42-aws Dec 27, 2018
6eb982f
add public key encoding conversion functions
mattsb42-aws Dec 27, 2018
42c3e81
move pycrypto_backend PKCS8<->PKCS1 handling to _asn module
mattsb42-aws Dec 27, 2018
a61a392
move rsa_backend PKCS8<->PKCS1 handling to _asn module
mattsb42-aws Dec 27, 2018
76e3428
update tox and Travis configs to use -norsa modes for pycrypto/dome t…
mattsb42-aws Dec 27, 2018
555648f
add note to readme about dependencies that can be cleaned up dependin…
mattsb42-aws Dec 27, 2018
267f5ce
make cryptography_backend the default for RSAKey
mattsb42-aws Dec 28, 2018
f96daec
isolate pycrypto/dome-explict test and explicitly use pycrypto/dome b…
mattsb42-aws Dec 28, 2018
595dd71
Merge pull request #122 from mattsb42-aws/pyca-default
zejn Apr 2, 2019
228621d
fix tox norsa typo
mattsb42-aws Apr 2, 2019
ddcfbf9
remove unused import
mattsb42-aws Apr 2, 2019
a40e3b7
add PEM->DER conversion function to remove pycrypto_backend dependenc…
mattsb42-aws Apr 2, 2019
c256933
add cryptography version constraint for PyPy < 5.4
mattsb42-aws Apr 2, 2019
0a5e07e
add Travis testing for PyPy 5.7.1
mattsb42-aws Apr 2, 2019
84fd4b2
add Travis testing for CPython 3.6
mattsb42-aws Apr 2, 2019
03c8583
add Travis testing for CPython 3.7
mattsb42-aws Apr 2, 2019
49c3059
remove unused reference
mattsb42-aws Apr 2, 2019
93bb9a7
changing Travis run from pypy-3.5.1 to pypy3.5 per https://travis-ci.…
mattsb42-aws Apr 3, 2019
69a57d9
change tox compatibility installs to install extras rather than insta…
mattsb42-aws Apr 3, 2019
b169959
enable flake8 in tox and Travis configs
mattsb42-aws Apr 3, 2019
dd5d551
Merge pull request #121 from mattsb42-aws/asn-rsa
zejn Apr 7, 2019
3d7f084
Merge branch 'master' into merge-in-master
mattsb42-aws Apr 7, 2019
64cb29f
Merge pull request #128 from mattsb42-aws/merge-in-master
zejn Apr 7, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,12 @@ docs/_build/

# PyBuilder
target/

# PyCharm
.idea/

# PyEnv
.python-version

# PyTest
.pytest_cache/
117 changes: 105 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,114 @@
# detail: https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch
dist: precise
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "pypy-5.3.1"
install:
- pip install -U setuptools && pip install -U tox codecov tox-travis
script:
- tox
after_success:
- codecov
# matrix:
# include:
# - python: 3.6
# env:
# - TOX_ENV=flake8
# script: tox -e $TOX_ENV
matrix:
include:
# CPython 2.7
- python: 2.7
env: TOXENV=py27-base
- python: 2.7
env: TOXENV=py27-cryptography-only
- python: 2.7
env: TOXENV=py27-pycryptodome-norsa
- python: 2.7
env: TOXENV=py27-pycrypto-norsa
- python: 2.7
env: TOXENV=py27-compatibility
# CPython 3.4
- python: 3.4
env: TOXENV=py34-base
- python: 3.4
env: TOXENV=py34-cryptography-only
- python: 3.4
env: TOXENV=py34-pycryptodome-norsa
- python: 3.4
env: TOXENV=py34-pycrypto-norsa
- python: 3.4
env: TOXENV=py34-compatibility
# CPython 3.5
- python: 3.5
env: TOXENV=py35-base
- python: 3.5
env: TOXENV=py35-cryptography-only
- python: 3.5
env: TOXENV=py35-pycryptodome-norsa
- python: 3.5
env: TOXENV=py35-pycrypto-norsa
- python: 3.5
env: TOXENV=py35-compatibility
# CPython 3.5
- python: 3.5
env: TOXENV=py35-base
- python: 3.5
env: TOXENV=py35-cryptography-only
- python: 3.5
env: TOXENV=py35-pycryptodome-norsa
- python: 3.5
env: TOXENV=py35-pycrypto-norsa
- python: 3.5
env: TOXENV=py35-compatibility
# CPython 3.6
- python: 3.6
env: TOXENV=py35-base
- python: 3.6
env: TOXENV=py35-cryptography-only
- python: 3.6
env: TOXENV=py35-pycryptodome-norsa
- python: 3.6
env: TOXENV=py35-pycrypto-norsa
- python: 3.6
env: TOXENV=py35-compatibility
# CPython 3.7
# xenial + sudo are currently needed to get 3.7
# https://github.com/travis-ci/travis-ci/issues/9815
- python: 3.7
env: TOXENV=py35-base
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py35-cryptography-only
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py35-pycryptodome-norsa
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py35-pycrypto-norsa
dist: xenial
sudo: true
- python: 3.7
env: TOXENV=py35-compatibility
dist: xenial
sudo: true
# PyPy 5.3.1
- python: pypy-5.3.1
env: TOXENV=pypy-base
- python: pypy-5.3.1
env: TOXENV=pypy-cryptography-only
- python: pypy-5.3.1
env: TOXENV=pypy-pycryptodome-norsa
- python: pypy-5.3.1
env: TOXENV=pypy-pycrypto-norsa
- python: pypy-5.3.1
env: TOXENV=pypy-compatibility
# PyPy 3.5 (5.10.1?)
- python: pypy3.5
env: TOXENV=pypy-base
- python: pypy3.5
env: TOXENV=pypy-cryptography-only
- python: pypy3.5
env: TOXENV=pypy-pycryptodome-norsa
- python: pypy3.5
env: TOXENV=pypy-pycrypto-norsa
- python: pypy3.5
env: TOXENV=pypy-compatibility
# Linting
- python: 3.6
env: TOX_ENV=flake8
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ The crytography option is a good default.
$ pip install python-jose[pycryptodome]
$ pip install python-jose[pycrypto]

Due to complexities with setuptools, the ``python-rsa`` and ``python-ecdsa`` libraries are always installed.
If you use one of the custom backends and would like to clean up unneeded dependencies,
you can remove the following dependencies for each backend:

* ``cryptography``

* ``pip uninstall rsa ecdsa pyasn1``

* ``pycrypto`` or ``pycryptodome``

* ``pip uninstall rsa``

.. warning::

Uninstall carefully. Make sure that nothing else in your environment needs these
libraries before uninstalling them.


Usage
-----
Expand Down
4 changes: 2 additions & 2 deletions jose/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

try:
from jose.backends.pycrypto_backend import RSAKey # noqa: F401
from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401
except ImportError:
try:
from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401
from jose.backends.pycrypto_backend import RSAKey # noqa: F401
except ImportError:
from jose.backends.rsa_backend import RSAKey # noqa: F401

Expand Down
82 changes: 82 additions & 0 deletions jose/backends/_asn1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""ASN1 encoding helpers for converting between PKCS1 and PKCS8.

Required by rsa_backend and pycrypto_backend but not cryptography_backend.
"""
from pyasn1.codec.der import decoder, encoder
from pyasn1.type import namedtype, univ

RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1"


class RsaAlgorithmIdentifier(univ.Sequence):
"""ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers."""
componentType = namedtype.NamedTypes(
namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()),
namedtype.NamedType("parameters", univ.Null())
)


class PKCS8PrivateKey(univ.Sequence):
"""ASN1 structure for recording PKCS8 private keys."""
componentType = namedtype.NamedTypes(
namedtype.NamedType("version", univ.Integer()),
namedtype.NamedType("privateKeyAlgorithm", RsaAlgorithmIdentifier()),
namedtype.NamedType("privateKey", univ.OctetString())
)


class PublicKeyInfo(univ.Sequence):
"""ASN1 structure for recording PKCS8 public keys."""
componentType = namedtype.NamedTypes(
namedtype.NamedType("algorithm", RsaAlgorithmIdentifier()),
namedtype.NamedType("publicKey", univ.BitString())
)


def rsa_private_key_pkcs8_to_pkcs1(pkcs8_key):
"""Convert a PKCS8-encoded RSA private key to PKCS1."""
decoded_values = decoder.decode(pkcs8_key, asn1Spec=PKCS8PrivateKey())

try:
decoded_key = decoded_values[0]
except IndexError:
raise ValueError("Invalid private key encoding")

return decoded_key["privateKey"]


def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key):
"""Convert a PKCS1-encoded RSA private key to PKCS8."""
algorithm = RsaAlgorithmIdentifier()
algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID

pkcs8_key = PKCS8PrivateKey()
pkcs8_key["version"] = 0
pkcs8_key["privateKeyAlgorithm"] = algorithm
pkcs8_key["privateKey"] = pkcs1_key

return encoder.encode(pkcs8_key)


def rsa_public_key_pkcs1_to_pkcs8(pkcs1_key):
"""Convert a PKCS1-encoded RSA private key to PKCS8."""
algorithm = RsaAlgorithmIdentifier()
algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID

pkcs8_key = PublicKeyInfo()
pkcs8_key["algorithm"] = algorithm
pkcs8_key["publicKey"] = univ.BitString.fromOctetString(pkcs1_key)

return encoder.encode(pkcs8_key)


def rsa_public_key_pkcs8_to_pkcs1(pkcs8_key):
"""Convert a PKCS8-encoded RSA private key to PKCS1."""
decoded_values = decoder.decode(pkcs8_key, asn1Spec=PublicKeyInfo())

try:
decoded_key = decoded_values[0]
except IndexError:
raise ValueError("Invalid public key encoding.")

return decoded_key["publicKey"].asOctets()
46 changes: 39 additions & 7 deletions jose/backends/cryptography_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from __future__ import division

import math

import six
import ecdsa
from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der

try:
from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey
except ImportError:
EcdsaSigningKey = EcdsaVerifyingKey = None

from jose.backends.base import Key
from jose.utils import base64_to_long, long_to_base64
Expand All @@ -11,7 +18,9 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
from cryptography.utils import int_from_bytes, int_to_bytes
from cryptography.x509 import load_pem_x509_certificate


Expand All @@ -37,7 +46,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):
self.prepared_key = key
return

if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
if None not in (EcdsaSigningKey, EcdsaVerifyingKey) and isinstance(key, (EcdsaSigningKey, EcdsaVerifyingKey)):
# convert to PEM and let cryptography below load it as PEM
key = key.to_pem().decode('utf-8')

Expand Down Expand Up @@ -90,19 +99,42 @@ def _process_jwk(self, jwk_dict):
else:
return public.public_key(self.cryptography_backend())

def _sig_component_length(self):
"""Determine the correct serialization length for an encoded signature component.

This is the number of bytes required to encode the maximum key value.
"""
return int(math.ceil(self.prepared_key.key_size / 8.0))

def _der_to_raw(self, der_signature):
"""Convert signature from DER encoding to RAW encoding."""
r, s = decode_dss_signature(der_signature)
component_length = self._sig_component_length()
return int_to_bytes(r, component_length) + int_to_bytes(s, component_length)

def _raw_to_der(self, raw_signature):
"""Convert signature from RAW encoding to DER encoding."""
component_length = self._sig_component_length()
if len(raw_signature) != int(2 * component_length):
raise ValueError("Invalid signature")

r_bytes = raw_signature[:component_length]
s_bytes = raw_signature[component_length:]
r = int_from_bytes(r_bytes, "big")
s = int_from_bytes(s_bytes, "big")
return encode_dss_signature(r, s)

def sign(self, msg):
if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size:
raise TypeError("this curve (%s) is too short "
"for your digest (%d)" % (self.prepared_key.curve.name,
8 * self.hash_alg.digest_size))
signature = self.prepared_key.sign(msg, ec.ECDSA(self.hash_alg()))
order = (2 ** self.prepared_key.curve.key_size) - 1
return sigencode_string(*sigdecode_der(signature, order), order=order)
return self._der_to_raw(signature)

def verify(self, msg, sig):
order = (2 ** self.prepared_key.curve.key_size) - 1
signature = sigencode_der(*sigdecode_string(sig, order), order=order)
try:
signature = self._raw_to_der(sig)
self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg()))
return True
except Exception:
Expand Down
Loading