Skip to content

Commit

Permalink
Merge branch 'master' into zejn/rsa-privkey-no-verify
Browse files Browse the repository at this point in the history
  • Loading branch information
blag committed Dec 19, 2019
2 parents 40920a5 + 1390392 commit 5eb382e
Show file tree
Hide file tree
Showing 19 changed files with 111 additions and 143 deletions.
28 changes: 3 additions & 25 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ after_success:
- codecov
matrix:
include:
# Linting
- python: 3.6
env: TOX_ENV=flake8
# CPython 2.7
- python: 2.7
env: TOXENV=py27-base
Expand All @@ -21,17 +24,6 @@ matrix:
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
Expand Down Expand Up @@ -77,17 +69,6 @@ matrix:
env: TOXENV=py37-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
Expand All @@ -99,6 +80,3 @@ matrix:
env: TOXENV=pypy-pycrypto-norsa
- python: pypy3.5
env: TOXENV=pypy-compatibility
# Linting
- python: 3.6
env: TOX_ENV=flake8
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Changelog #

## Development ##

* Fix `to_dict` output, which should always be JSON encodeable. #139 (fixes #127 and #137)

## 3.1.0 -- 2019-12-10 ##

This is a greatly overdue release.

### Features ###

* Improve `JWT.decode()` #76 (fixes #75)
* Sort headers when serializing to allow for headless JWT #136 (fixes #80)
* Adjust dependency handling
- Use PyCryptodome instead of PyCrypto #83
- Update package dependencies #124 (fixes #158)
* Avoid using deprecated methods #85
* Support X509 certificates #107
* Isolate and flesh out cryptographic backends to enable independent operation #129 (fixes #114)
- Remove pyca/cryptography backend's dependency on python-ecdsa #117
- Remove pycrypto/dome backends' dependency on python-rsa #121
- Make pyca/cryptography backend the preferred backend if multiple backends are present #122

### Bugfixes/Improvements ###

* Enable flake8 check in tox/TravisCI #77
* Fix `crytography` dependency typo #94
* Trigger tests using `python setup.py test` #97
* Properly raise an error if a claim is expected and not given #98
* Typo fixes #110
* Fix invalid RSA private key PKCS8 encoding by python-rsa backend #120 (fixes #119)
* Remove `future` dependency #134 (fixes #112)
* Fix incorrect use of `pytest.raises(message=...)` #141
* Typo fix #143
* Clarify sign docstring to allow for `dict` payload #150

### Housekeeping ###

* Streamline the code a bit and update classifiers #87
* Fix typo and rephrase `access_token` documentation #89
* Code linting now mostly honors flake8 #101
* Document using a `dict` for `jwt.encode` and `jwt.decode` #103
* Include docs and tests in source distributions #111
* Updating README descriptions of crypto backends #130
* Document versioning policy #131
* Add `CHANGELOG.rst` #132 (fixes #99)
* Simplify and extend `.travis.yml` #135
* Move `CHANGELOG.rst` to `CHANGELOG.md` and update it #159
41 changes: 0 additions & 41 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,41 +0,0 @@
---------
Changelog
---------

3.1.0 -- 2019-??-??
^^^^^^^^^^^^^^^^^^^

Major
"""""

* Require claim options added.
`#98 <https://github.com/mpdavis/python-jose/pull/98>`_
* Isolate and flesh out cryptographic backends to enable independent operation.
`#114 <https://github.com/mpdavis/python-jose/issues/114>`_
`#129 <https://github.com/mpdavis/python-jose/pull/129>`_
* Remove pyca/cryptography backend's dependency on python-ecdsa.
`#117 <https://github.com/mpdavis/python-jose/pull/117>`_
* Remove pycrypto/dome backends' dependency on python-rsa.
`#121 <https://github.com/mpdavis/python-jose/pull/121>`_
* Make pyca/cryptography backend the preferred backend if multiple backends are present.
`#122 <https://github.com/mpdavis/python-jose/pull/122>`_
* Allow for headless JWT by sorting headers when serializing.
`#136 <https://github.com/mpdavis/python-jose/pull/136>`_

Bugfixes
""""""""

* Fix invalid RSA private key PKCS8 encoding by python-rsa backend.
`#120 <https://github.com/mpdavis/python-jose/pull/120>`_

Housekeeping
""""""""""""

* Test each cryptographic backend independently in CI.
`#114 <https://github.com/mpdavis/python-jose/issues/114>`_
`#129 <https://github.com/mpdavis/python-jose/pull/129>`_
`#135 <https://github.com/mpdavis/python-jose/pull/135>`_
* Add flake8 checks in CI.
* Add CPython 3.7 and PyPy 3.5 testing in CI.
* Remove package future as a dependency, not needed anymore.
* Fix warnings from py.test.
2 changes: 1 addition & 1 deletion jose/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

__version__ = "3.0.1"
__version__ = "3.1.0"
__author__ = 'Michael Davis'
__license__ = 'MIT'
__copyright__ = 'Copyright 2016 Michael Davis'
Expand Down
26 changes: 13 additions & 13 deletions jose/backends/cryptography_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'EC':
raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get('kty'))

if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
raise JWKError('Mandatory parameters are missing')
Expand Down Expand Up @@ -183,15 +183,15 @@ def to_dict(self):
'alg': self._algorithm,
'kty': 'EC',
'crv': crv,
'x': long_to_base64(public_key.public_numbers().x, size=key_size),
'y': long_to_base64(public_key.public_numbers().y, size=key_size),
'x': long_to_base64(public_key.public_numbers().x, size=key_size).decode('ASCII'),
'y': long_to_base64(public_key.public_numbers().y, size=key_size).decode('ASCII'),
}

if not self.is_public():
data['d'] = long_to_base64(
self.prepared_key.private_numbers().private_value,
size=key_size
)
).decode('ASCII')

return data

Expand Down Expand Up @@ -244,7 +244,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'RSA':
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty'))

e = base64_to_long(jwk_dict.get('e', 256))
n = base64_to_long(jwk_dict.get('n'))
Expand Down Expand Up @@ -354,18 +354,18 @@ def to_dict(self):
data = {
'alg': self._algorithm,
'kty': 'RSA',
'n': long_to_base64(public_key.public_numbers().n),
'e': long_to_base64(public_key.public_numbers().e),
'n': long_to_base64(public_key.public_numbers().n).decode('ASCII'),
'e': long_to_base64(public_key.public_numbers().e).decode('ASCII'),
}

if not self.is_public():
data.update({
'd': long_to_base64(self.prepared_key.private_numbers().d),
'p': long_to_base64(self.prepared_key.private_numbers().p),
'q': long_to_base64(self.prepared_key.private_numbers().q),
'dp': long_to_base64(self.prepared_key.private_numbers().dmp1),
'dq': long_to_base64(self.prepared_key.private_numbers().dmq1),
'qi': long_to_base64(self.prepared_key.private_numbers().iqmp),
'd': long_to_base64(self.prepared_key.private_numbers().d).decode('ASCII'),
'p': long_to_base64(self.prepared_key.private_numbers().p).decode('ASCII'),
'q': long_to_base64(self.prepared_key.private_numbers().q).decode('ASCII'),
'dp': long_to_base64(self.prepared_key.private_numbers().dmp1).decode('ASCII'),
'dq': long_to_base64(self.prepared_key.private_numbers().dmq1).decode('ASCII'),
'qi': long_to_base64(self.prepared_key.private_numbers().iqmp).decode('ASCII'),
})

return data
8 changes: 4 additions & 4 deletions jose/backends/ecdsa_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def __init__(self, key, algorithm):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'EC':
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get('kty'))

if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
raise JWKError('Mandatory parameters are missing')
Expand Down Expand Up @@ -131,14 +131,14 @@ def to_dict(self):
'alg': self._algorithm,
'kty': 'EC',
'crv': crv,
'x': long_to_base64(public_key.pubkey.point.x(), size=key_size),
'y': long_to_base64(public_key.pubkey.point.y(), size=key_size),
'x': long_to_base64(public_key.pubkey.point.x(), size=key_size).decode('ASCII'),
'y': long_to_base64(public_key.pubkey.point.y(), size=key_size).decode('ASCII'),
}

if not self.is_public():
data['d'] = long_to_base64(
self.prepared_key.privkey.secret_multiplier,
size=key_size
)
).decode('ASCII')

return data
18 changes: 9 additions & 9 deletions jose/backends/pycrypto_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(self, key, algorithm):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'RSA':
raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty'))

e = base64_to_long(jwk_dict.get('e', 256))
n = base64_to_long(jwk_dict.get('n'))
Expand Down Expand Up @@ -187,8 +187,8 @@ def to_dict(self):
data = {
'alg': self._algorithm,
'kty': 'RSA',
'n': long_to_base64(self.prepared_key.n),
'e': long_to_base64(self.prepared_key.e),
'n': long_to_base64(self.prepared_key.n).decode('ASCII'),
'e': long_to_base64(self.prepared_key.e).decode('ASCII'),
}

if not self.is_public():
Expand All @@ -203,12 +203,12 @@ def to_dict(self):
dp = self.prepared_key.d % (self.prepared_key.p - 1)
dq = self.prepared_key.d % (self.prepared_key.q - 1)
data.update({
'd': long_to_base64(self.prepared_key.d),
'p': long_to_base64(self.prepared_key.q),
'q': long_to_base64(self.prepared_key.p),
'dp': long_to_base64(dq),
'dq': long_to_base64(dp),
'qi': long_to_base64(self.prepared_key.u),
'd': long_to_base64(self.prepared_key.d).decode('ASCII'),
'p': long_to_base64(self.prepared_key.q).decode('ASCII'),
'q': long_to_base64(self.prepared_key.p).decode('ASCII'),
'dp': long_to_base64(dq).decode('ASCII'),
'dq': long_to_base64(dp).decode('ASCII'),
'qi': long_to_base64(self.prepared_key.u).decode('ASCII'),
})

return data
18 changes: 9 additions & 9 deletions jose/backends/rsa_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def __init__(self, key, algorithm):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'RSA':
raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty'))

e = base64_to_long(jwk_dict.get('e'))
n = base64_to_long(jwk_dict.get('n'))
Expand Down Expand Up @@ -248,18 +248,18 @@ def to_dict(self):
data = {
'alg': self._algorithm,
'kty': 'RSA',
'n': long_to_base64(public_key.n),
'e': long_to_base64(public_key.e),
'n': long_to_base64(public_key.n).decode('ASCII'),
'e': long_to_base64(public_key.e).decode('ASCII'),
}

if not self.is_public():
data.update({
'd': long_to_base64(self._prepared_key.d),
'p': long_to_base64(self._prepared_key.p),
'q': long_to_base64(self._prepared_key.q),
'dp': long_to_base64(self._prepared_key.exp1),
'dq': long_to_base64(self._prepared_key.exp2),
'qi': long_to_base64(self._prepared_key.coef),
'd': long_to_base64(self._prepared_key.d).decode('ASCII'),
'p': long_to_base64(self._prepared_key.p).decode('ASCII'),
'q': long_to_base64(self._prepared_key.q).decode('ASCII'),
'dp': long_to_base64(self._prepared_key.exp1).decode('ASCII'),
'dq': long_to_base64(self._prepared_key.exp2).decode('ASCII'),
'qi': long_to_base64(self._prepared_key.coef).decode('ASCII'),
})

return data
4 changes: 0 additions & 4 deletions jose/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ class JWTClaimsError(JWTError):
pass


class JWTSignatureError(JWTError):
pass


class ExpiredSignatureError(JWTError):
pass

Expand Down
13 changes: 6 additions & 7 deletions jose/jwk.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from jose.constants import ALGORITHMS
from jose.exceptions import JWKError
from jose.utils import base64url_decode, base64url_encode
from jose.utils import constant_time_string_compare
from jose.backends.base import Key

try:
Expand Down Expand Up @@ -36,7 +35,7 @@ def get_key(algorithm):

def register_key(algorithm, key_class):
if not issubclass(key_class, Key):
raise TypeError("Key class not a subclass of jwk.Key")
raise TypeError("Key class is not a subclass of jwk.Key")
ALGORITHMS.KEYS[algorithm] = key_class
ALGORITHMS.SUPPORTED.add(algorithm)
return True
Expand All @@ -53,11 +52,11 @@ def construct(key_data, algorithm=None):
algorithm = key_data.get('alg', None)

if not algorithm:
raise JWKError('Unable to find a algorithm for key: %s' % key_data)
raise JWKError('Unable to find an algorithm for key: %s' % key_data)

key_class = get_key(algorithm)
if not key_class:
raise JWKError('Unable to find a algorithm for key: %s' % key_data)
raise JWKError('Unable to find an algorithm for key: %s' % key_data)
return key_class(key_data, algorithm)


Expand Down Expand Up @@ -119,7 +118,7 @@ def __init__(self, key, algorithm):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'oct':
raise JWKError("Incorrect key type. Expected: 'oct', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get('kty'))

k = jwk_dict.get('k')
k = k.encode('utf-8')
Expand All @@ -132,11 +131,11 @@ def sign(self, msg):
return hmac.new(self.prepared_key, msg, self.hash_alg).digest()

def verify(self, msg, sig):
return constant_time_string_compare(sig, self.sign(msg))
return hmac.compare_digest(sig, self.sign(msg))

def to_dict(self):
return {
'alg': self._algorithm,
'kty': 'oct',
'k': base64url_encode(self.prepared_key),
'k': base64url_encode(self.prepared_key).decode('ASCII'),
}
2 changes: 1 addition & 1 deletion jose/jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256):
"""Signs a claims set and returns a JWS string.
Args:
payload (str): A string to sign
payload (str or dict): A string to sign
key (str or dict): The key to use for signing the claim set. Can be
individual JWK or JWK set.
headers (dict, optional): A set of headers that will be added to
Expand Down
Loading

0 comments on commit 5eb382e

Please sign in to comment.