From 2fde47bb6ea88b6dd9f602ec28b55f925d314355 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 24 Oct 2016 15:27:03 -0700 Subject: [PATCH 1/7] ASCII Readme --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8e7e911..b110d0a 100644 --- a/README.md +++ b/README.md @@ -15,22 +15,22 @@ Algorithms The JWS spec reserves several algorithms for cryptographic signing. Out of the 9, this library currently supports 7: -**HMAC** – native +**HMAC** -- native -* HS256 – HMAC using SHA-256 hash algorithm -* HS384 – HMAC using SHA-384 hash algorithm -* HS512 – HMAC using SHA-512 hash algorithm +* HS256 -- HMAC using SHA-256 hash algorithm +* HS384 -- HMAC using SHA-384 hash algorithm +* HS512 -- HMAC using SHA-512 hash algorithm -**RSA** – requires pycrypto >= 2.5: ``pip install pycrypto`` +**RSA** -- requires pycrypto >= 2.5: ``pip install pycrypto`` -* RS256 – RSA using SHA-256 hash algorithm +* RS256 -- RSA using SHA-256 hash algorithm -**ECDSA** – requires ecdsa lib: ``pip install ecdsa`` +**ECDSA** -- requires ecdsa lib: ``pip install ecdsa`` -* ES256 – ECDSA using P-256 curve and SHA-256 hash algorithm -* ES384 – ECDSA using P-384 curve and SHA-384 hash algorithm -* ES512 – ECDSA using P-521 curve and SHA-512 hash algorithm +* ES256 -- ECDSA using P-256 curve and SHA-256 hash algorithm +* ES384 -- ECDSA using P-384 curve and SHA-384 hash algorithm +* ES512 -- ECDSA using P-521 curve and SHA-512 hash algorithm There is also a mechanism for extending functionality by adding your own algorithms without cracking open the whole codebase. See the advanced usage From e2db9da5c2f074d684ac36a487cafc8bccad8b0d Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 10:33:01 +0000 Subject: [PATCH 2/7] switch to reStructuredText for PyPI --- MANIFEST.in | 2 +- README.md => README.rst | 58 ++++++++++------- README.txt | 136 ---------------------------------------- setup.py | 2 +- 4 files changed, 39 insertions(+), 159 deletions(-) rename README.md => README.rst (74%) delete mode 100644 README.txt diff --git a/MANIFEST.in b/MANIFEST.in index 4bf4483..9561fb1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.md \ No newline at end of file +include README.rst diff --git a/README.md b/README.rst similarity index 74% rename from README.md rename to README.rst index b110d0a..af90d27 100644 --- a/README.md +++ b/README.rst @@ -1,47 +1,56 @@ python-jws -===== -A Python implementation of [JSON Web Signatures draft 02](http://self-issued.info/docs/draft-jones-json-web-signature.html) +========== -Also now works on Python 3.3+ as well as Python 2.7+. However, it's a naive conversion to support both Python 2 and Python 3 so there may well be hidden bugs. +A Python implementation of `JSON Web Signatures draft +02 `__ + +Also now works on Python 3.3+ as well as Python 2.7+. However, it's a +naive conversion to support both Python 2 and Python 3 so there may well +be hidden bugs. Installing ---------- - $ pip install jws +:: + $ pip install jws Algorithms ---------- -The JWS spec reserves several algorithms for cryptographic signing. Out of the 9, this library currently supports 7: +The JWS spec reserves several algorithms for cryptographic signing. Out +of the 9, this library currently supports 7: **HMAC** -- native -* HS256 -- HMAC using SHA-256 hash algorithm -* HS384 -- HMAC using SHA-384 hash algorithm -* HS512 -- HMAC using SHA-512 hash algorithm - +- HS256 -- HMAC using SHA-256 hash algorithm +- HS384 -- HMAC using SHA-384 hash algorithm +- HS512 -- HMAC using SHA-512 hash algorithm **RSA** -- requires pycrypto >= 2.5: ``pip install pycrypto`` -* RS256 -- RSA using SHA-256 hash algorithm +- RS256 -- RSA using SHA-256 hash algorithm **ECDSA** -- requires ecdsa lib: ``pip install ecdsa`` -* ES256 -- ECDSA using P-256 curve and SHA-256 hash algorithm -* ES384 -- ECDSA using P-384 curve and SHA-384 hash algorithm -* ES512 -- ECDSA using P-521 curve and SHA-512 hash algorithm +- ES256 -- ECDSA using P-256 curve and SHA-256 hash algorithm +- ES384 -- ECDSA using P-384 curve and SHA-384 hash algorithm +- ES512 -- ECDSA using P-521 curve and SHA-512 hash algorithm There is also a mechanism for extending functionality by adding your own -algorithms without cracking open the whole codebase. See the advanced usage -section for an example. +algorithms without cracking open the whole codebase. See the advanced +usage section for an example. -For RSA and ECDSA, all crypto libraries are lazily loaded so you won't need the dependencies unless you try to use the functionality. +For RSA and ECDSA, all crypto libraries are lazily loaded so you won't +need the dependencies unless you try to use the functionality. Usage ----- + Let's check out some examples. +:: + >>> import jws >>> header = { 'alg': 'HS256' } >>> payload = { 'claim': 'JSON is the raddest.', 'iss': 'brianb' } @@ -55,6 +64,8 @@ Let's check out some examples. Now with a real key! +:: + >>> import ecdsa >>> sk256 = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) >>> vk = sk256.get_verifying_key() @@ -65,8 +76,11 @@ Now with a real key! Advanced Usage -------------- + Make this file +:: + # file: sillycrypto.py import jws from jws.algos import AlgorithmBase, SignatureError @@ -90,6 +104,8 @@ Make this file And in an interpreter: +:: + >>> import jws >>> header = { 'alg': 'F7U12' } >>> payload = { 'claim': 'wutt' } @@ -107,9 +123,8 @@ And in an interpreter: .... jws.exceptions.SignatureError: nope - Other Stuff ---------- +----------- Check out https://github.com/brianloveswords/python-jws/blob/master/examples/minijwt.py @@ -120,10 +135,11 @@ https://github.com/brianloveswords/python-jws/blob/master/examples/ragecrypto.py for a rage-comic inspired cryptography extension. TODO -------- -* Write about all the rad stuff that can be done around headers (as extensible as crypto algos) -* Pull in JWK support +---- +- Write about all the rad stuff that can be done around headers (as + extensible as crypto algos) +- Pull in JWK support Tests ----- diff --git a/README.txt b/README.txt deleted file mode 100644 index 8e7e911..0000000 --- a/README.txt +++ /dev/null @@ -1,136 +0,0 @@ -python-jws -===== -A Python implementation of [JSON Web Signatures draft 02](http://self-issued.info/docs/draft-jones-json-web-signature.html) - -Also now works on Python 3.3+ as well as Python 2.7+. However, it's a naive conversion to support both Python 2 and Python 3 so there may well be hidden bugs. - -Installing ----------- - $ pip install jws - - - -Algorithms ----------- -The JWS spec reserves several algorithms for cryptographic signing. Out of the 9, this library currently supports 7: - - -**HMAC** – native - -* HS256 – HMAC using SHA-256 hash algorithm -* HS384 – HMAC using SHA-384 hash algorithm -* HS512 – HMAC using SHA-512 hash algorithm - - -**RSA** – requires pycrypto >= 2.5: ``pip install pycrypto`` - -* RS256 – RSA using SHA-256 hash algorithm - -**ECDSA** – requires ecdsa lib: ``pip install ecdsa`` - -* ES256 – ECDSA using P-256 curve and SHA-256 hash algorithm -* ES384 – ECDSA using P-384 curve and SHA-384 hash algorithm -* ES512 – ECDSA using P-521 curve and SHA-512 hash algorithm - -There is also a mechanism for extending functionality by adding your own -algorithms without cracking open the whole codebase. See the advanced usage -section for an example. - -For RSA and ECDSA, all crypto libraries are lazily loaded so you won't need the dependencies unless you try to use the functionality. - -Usage ------ -Let's check out some examples. - - >>> import jws - >>> header = { 'alg': 'HS256' } - >>> payload = { 'claim': 'JSON is the raddest.', 'iss': 'brianb' } - >>> signature = jws.sign(header, payload, 'secret') - >>> jws.verify(header, payload, signature, 'secret') - True - >>> jws.verify(header, payload, signature, 'badbadbad') - Traceback (most recent call last): - ... - jws.exceptions.SignatureError: Could not validate signature - -Now with a real key! - - >>> import ecdsa - >>> sk256 = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) - >>> vk = sk256.get_verifying_key() - >>> header = { 'alg': 'ES256' } - >>> sig = jws.sign(header, payload, sk256) - >>> jws.verify(header, payload, sig, vk) - True - -Advanced Usage --------------- -Make this file - - # file: sillycrypto.py - import jws - from jws.algos import AlgorithmBase, SignatureError - class FXUY(AlgorithmBase): - def __init__(self, x, y): - self.x = int(x) - self.y = int(y) - def sign(self, msg, key): - return 'verysecure' * self.x + key * self.y - - def verify(self, msg, sig, key): - if sig != self.sign(msg, key): - raise SignatureError('nope') - return True - - jws.algos.CUSTOM += [ - # a regular expression with two named matching groups. (x and y) - # named groups will be sent to the class constructor - (r'^F(?P\d)U(?P\d{2})$', FXUY), - ] - -And in an interpreter: - - >>> import jws - >>> header = { 'alg': 'F7U12' } - >>> payload = { 'claim': 'wutt' } - >>> sig = jws.sign(header, payload, '') - Traceback (most recent call last): - .... - jws.exceptions.AlgorithmNotImplemented: "F7U12" not implemented. - >>> - >>> import sillycrypto - >>> sig = jws.sign(header, payload, '') - >>> jws.verify(header, payload, sig, '') - True - >>> jws.verify(header, payload, sig, 'y u no verify?') - Traceback (most recent call last): - .... - jws.exceptions.SignatureError: nope - - -Other Stuff ---------- - -Check out -https://github.com/brianloveswords/python-jws/blob/master/examples/minijwt.py -for a 14-line implemention of JWT. - -See -https://github.com/brianloveswords/python-jws/blob/master/examples/ragecrypto.py -for a rage-comic inspired cryptography extension. - -TODO -------- -* Write about all the rad stuff that can be done around headers (as extensible as crypto algos) -* Pull in JWK support - - -Tests ------ - -use nosetests - -License -------- - -MIT diff --git a/setup.py b/setup.py index b49390c..4383ec6 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def read(fname): keywords = "jws json web security signing", url = "http://github.com/brianlovesdata/python-jws", packages=['jws'], - long_description=read('README.md'), + long_description=read('README.rst'), classifiers=[ "Development Status :: 3 - Alpha", "Topic :: Utilities", From 40d979b0769ee4f6fcc6155ae45cae649f6da901 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 10:34:02 +0000 Subject: [PATCH 3/7] PyPI now rejects register calls upload is now register/upload --- fabfile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fabfile.py b/fabfile.py index 153bed1..c18ae17 100644 --- a/fabfile.py +++ b/fabfile.py @@ -2,7 +2,7 @@ # Automate the release def release(): - local("python setup.py sdist register upload") - local("python2.5 setup.py bdist_egg register upload") - local("python2.6 setup.py bdist_egg register upload") - local("python2.7 setup.py bdist_egg register upload") \ No newline at end of file + local("python setup.py sdist upload") + local("python2.5 setup.py bdist_egg upload") + local("python2.6 setup.py bdist_egg upload") + local("python2.7 setup.py bdist_egg upload") From 6af3652be8575d2f87fe33d4ce7fde567fc02ee2 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 10:34:44 +0000 Subject: [PATCH 4/7] support universal wheels --- fabfile.py | 2 +- setup.cfg | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 setup.cfg diff --git a/fabfile.py b/fabfile.py index c18ae17..a76a6d6 100644 --- a/fabfile.py +++ b/fabfile.py @@ -2,7 +2,7 @@ # Automate the release def release(): - local("python setup.py sdist upload") + local("python setup.py sdist bdist_wheel upload") local("python2.5 setup.py bdist_egg upload") local("python2.6 setup.py bdist_egg upload") local("python2.7 setup.py bdist_egg upload") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 From 07a9912cca55c0dd6d44e443dcf8a6b04784fba7 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 13:05:06 +0000 Subject: [PATCH 5/7] configure tox --- .gitignore | 102 ++++++++++++++++++++++++++++-- .travis.yml | 8 +++ setup.py | 5 +- jws/tests.py => tests/test_jws.py | 0 tox.ini | 18 ++++++ 5 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 .travis.yml rename jws/tests.py => tests/test_jws.py (100%) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 1b0684b..a49d9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,99 @@ -*.pyc -**/*.pyc -*.egg-info -build -dist +# Created by https://www.gitignore.io/api/python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +.venv/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# End of https://www.gitignore.io/api/python + /apidesign.txt /pycrypto/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..34e77cc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: python +python: + - "2.7" + - "3.5" + - "3.6" +install: pip install tox-travis +script: tox +cache: pip diff --git a/setup.py b/setup.py index 4383ec6..2f6f923 100644 --- a/setup.py +++ b/setup.py @@ -14,11 +14,14 @@ def read(fname): keywords = "jws json web security signing", url = "http://github.com/brianlovesdata/python-jws", packages=['jws'], + extras_require={ + 'rsa': ['pycrypto>=2.6.1, <3.0.0'], + 'ecdsa': ['ecdsa>=0.13.0, <0.14.0'], + }, long_description=read('README.rst'), classifiers=[ "Development Status :: 3 - Alpha", "Topic :: Utilities", "License :: OSI Approved :: MIT License", ], - test_suite = 'nose.collector', ) diff --git a/jws/tests.py b/tests/test_jws.py similarity index 100% rename from jws/tests.py rename to tests/test_jws.py diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5c0260a --- /dev/null +++ b/tox.ini @@ -0,0 +1,18 @@ +[tox] +envlist = py{27,35,36}, lint + +[testenv] +commands = py.test {posargs} +deps = + pycrypto==2.6.1 + ecdsa==0.13.0 + pytest==3.0.5 + +[testenv:lint] +deps = + flake8==3.2.1 +commands=flake8 jws tests setup.py + +[travis] +python = + 3.6: py36, lint From 49720e9eeb31344fe26831a3ff2aecba84171b54 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 13:12:51 +0000 Subject: [PATCH 6/7] fix tests for python 3 --- jws/utils.py | 38 +++++++++++++++++++++----------------- tests/test_jws.py | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/jws/utils.py b/jws/utils.py index 091adf4..3cbf66f 100644 --- a/jws/utils.py +++ b/jws/utils.py @@ -2,6 +2,7 @@ import base64 import json +import hmac import sys if sys.version < '3': @@ -30,21 +31,24 @@ def from_base64(a): return base64url_decode(a) def encode(a): return to_base64(to_json(a)) def decode(a): return from_json(from_base64(a)) -#Taken from Django Source Code -def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - - For the sake of simplicity, this function executes in constant time only - when the two strings have the same length. It short-circuits when they - have different lengths. - """ - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 +if hmac.compare_digest: + constant_time_compare = hmac.compare_digest +else: + # Taken from Django Source Code + def constant_time_compare(val1, val2): + """ + Returns True if the two strings are equal, False otherwise. + + The time taken is independent of the number of characters that match. + + For the sake of simplicity, this function executes in constant time only + when the two strings have the same length. It short-circuits when they + have different lengths. + """ + if len(val1) != len(val2): + return False + result = 0 + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) + return result == 0 diff --git a/tests/test_jws.py b/tests/test_jws.py index 092ae0c..2f1c8af 100644 --- a/tests/test_jws.py +++ b/tests/test_jws.py @@ -142,7 +142,7 @@ def test_valid_hmac512(self): def test_invalid_hmac(self): header = {'alg': 'HS512'} sig = jws.sign(header, self.payload, 'secret') - self.assertRaises(jws.SignatureError(header, self.payload, sig, 'failwhale')) + self.assertRaises(jws.SignatureError, jws.verify, header, self.payload, sig, 'failwhale') class TestJWS_rsa(unittest.TestCase): private = rsa.generate(2048) From 1e18411e0fcf3a2dc0ef23b2c870d5ea8912fd64 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 11 Jan 2017 13:20:48 +0000 Subject: [PATCH 7/7] ignore lint errors --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index 5c0260a..e827fcd 100644 --- a/tox.ini +++ b/tox.ini @@ -16,3 +16,7 @@ commands=flake8 jws tests setup.py [travis] python = 3.6: py36, lint + +[flake8] +ignore = E131,E201,E202,E228,E231,E241,E251,E261,E262,E266,E301,E302,E305,E306,E701,E704,F401,F403,F405,F821 +max_line_length=118