Skip to content

Commit

Permalink
Merge pull request #129 from mpdavis/backend-explicit-tests
Browse files Browse the repository at this point in the history
Merge in backend-explicit-tests
  • Loading branch information
zejn authored Apr 8, 2019
2 parents cc710e0 + 64cb29f commit 5d2e03f
Show file tree
Hide file tree
Showing 19 changed files with 1,124 additions and 246 deletions.
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

0 comments on commit 5d2e03f

Please sign in to comment.