Hello community, here is the log from the commit of package python-PyJWT for openSUSE:Factory checked in at 2014-11-14 09:19:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-PyJWT (Old) and /work/SRC/openSUSE:Factory/.python-PyJWT.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-PyJWT" Changes: -------- --- /work/SRC/openSUSE:Factory/python-PyJWT/python-PyJWT.changes 2014-09-03 20:50:01.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-PyJWT.new/python-PyJWT.changes 2014-11-14 09:19:19.000000000 +0100 @@ -1,0 +2,5 @@ +Thu Nov 6 09:08:37 UTC 2014 - [email protected] + +- Update to 0.3.0 + +------------------------------------------------------------------- Old: ---- PyJWT-0.2.1.tar.gz New: ---- PyJWT-0.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-PyJWT.spec ++++++ --- /var/tmp/diff_new_pack.S4Qbam/_old 2014-11-14 09:19:19.000000000 +0100 +++ /var/tmp/diff_new_pack.S4Qbam/_new 2014-11-14 09:19:19.000000000 +0100 @@ -17,7 +17,7 @@ Name: python-PyJWT -Version: 0.2.1 +Version: 0.3.0 Release: 0 Url: https://github.com/progrium/pyjwt Summary: JSON Web Token implementation in Python @@ -27,6 +27,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: python-devel BuildRequires: python-setuptools +Requires: python-ecdsa Requires: python-pycrypto %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} ++++++ PyJWT-0.2.1.tar.gz -> PyJWT-0.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/PKG-INFO new/PyJWT-0.3.0/PKG-INFO --- old/PyJWT-0.2.1/PKG-INFO 2014-04-28 19:10:49.000000000 +0200 +++ new/PyJWT-0.3.0/PKG-INFO 2014-10-22 07:37:36.000000000 +0200 @@ -1,46 +1,64 @@ Metadata-Version: 1.1 Name: PyJWT -Version: 0.2.1 +Version: 0.3.0 Summary: JSON Web Token implementation in Python Home-page: http://github.com/progrium/pyjwt Author: Jeff Lindsay Author-email: [email protected] License: MIT -Description: PyJWT [](https://travis-ci.org/progrium/pyjwt) - ===== +Description: # PyJWT [](https://travis-ci.org/progrium/pyjwt) + A Python implementation of [JSON Web Token draft 01](http://self-issued.info/docs/draft-jones-json-web-token-01.html). - Installing - ---------- + ## Installing + + ``` + $ pip install PyJWT + ``` + + **A Note on Dependencies**: + + The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on + using any of those algorithms, you'll need to install it as well. - sudo easy_install PyJWT + ``` + $ pip install PyCrypto + ``` - **Note**: The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on - using any of those algorithms you'll need to install it as well. + The Elliptic Curve Digital Signature algorithms depend on Python-ECDSA. If + you plan on using any of those algorithms, you'll need to install it as well. - sudo easy_install PyCrypto + ``` + $ pip install ecdsa + ``` - Usage - ----- + ## Usage - import jwt - jwt.encode({"some": "payload"}, "secret") + ```python + import jwt + jwt.encode({'some': 'payload'}, 'secret') + ``` Additional headers may also be specified. - jwt.encode({"some": "payload"}, "secret", headers={"kid": "230498151c214b788dd97f22b85410a5"}) + ```python + jwt.encode({'some': 'payload'}, 'secret', headers={'kid': '230498151c214b788dd97f22b85410a5'}) + ``` Note the resulting JWT will not be encrypted, but verifiable with a secret key. - jwt.decode("someJWTstring", "secret") + ```python + jwt.decode('someJWTstring', 'secret') + ``` If the secret is wrong, it will raise a `jwt.DecodeError` telling you as such. You can still get the payload by setting the `verify` argument to `False`. - jwt.decode("someJWTstring", verify=False) + ```python + jwt.decode('someJWTstring', verify=False) + ``` - Algorithms - ---------- + ## Algorithms The JWT spec supports several algorithms for cryptographic signing. This library currently supports: @@ -48,35 +66,46 @@ * HS256 - HMAC using SHA-256 hash algorithm (default) * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm + * ES256 - ECDSA signature algorithm using SHA-256 hash algorithm + * ES384 - ECDSA signature algorithm using SHA-384 hash algorithm + * ES512 - ECDSA signature algorithm using SHA-512 hash algorithm * RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm * RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm * RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm Change the algorithm with by setting it in encode: - jwt.encode({"some": "payload"}, "secret", "HS512") + ```python + jwt.encode({'some': 'payload'}, 'secret', 'HS512') + ``` When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both `jwt.encode()` and `jwt.decode()` (`"secret"` in the examples) is expected to - be an RSA private key as imported with `Crypto.PublicKey.RSA.importKey()`. + be an RSA public or private key as imported with `Crypto.PublicKey.RSA.importKey()`. - Tests - ----- + When using the ECDSA algorithms, the `key` argument is expected to + be an Elliptic Curve private key as imported with `ecdsa.SigningKey.from_pem()`, + or a public key as imported with `ecdsa.VerifyingKey.from_pem()`. + + ## Tests You can run tests from the project root after cloning with: - python tests/test_jwt.py + ``` + $ python tests/test_jwt.py + ``` - Support of reserved claim names - ------------------------------- + ## Support of reserved claim names JSON Web Token defines some reserved claim names and defines how they should be used. PyJWT supports these reserved claim names: - "exp" (Expiration Time) Claim + - "nbf" (Not Before Time) Claim + - "iss" (Issuer) Claim + - "aud" (Audience) Claim - Expiration Time Claim - ===================== + ### Expiration Time Claim From [draft 01 of the JWT spec](http://self-issued.info/docs/draft-jones-json-web-token-01.html#ReservedClaimName): @@ -91,18 +120,23 @@ You can pass the expiration time as a UTC UNIX timestamp (an int) or as a datetime, which will be converted into an int. For example: - jwt.encode({"exp": 1371720939}, "secret") + ```python + jwt.encode({'exp': 1371720939}, 'secret') - jwt.encode({"exp": datetime.utcnow()}, "secret") + jwt.encode({'exp': datetime.utcnow()}, 'secret') + ``` Expiration time is automatically verified in `jwt.decode()` and raises `jwt.ExpiredSignature` if the expiration time is in the past: - import jwt - try: - jwt.decode('JWT_STRING', "secret") - except jwt.ExpiredSignature: - # Signature has expired + ```python + import jwt + + try: + jwt.decode('JWT_STRING', 'secret') + except jwt.ExpiredSignature: + # Signature has expired + ``` Expiration time will be compared to the current UTC time (as given by `timegm(datetime.utcnow().utctimetuple())`), so be sure to use a UTC timestamp @@ -116,16 +150,69 @@ after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin: - import jwt, time - jwt_payload = jwt.encode({'exp': datetime.utcnow() + datetime.timedelta(seconds=30)}, 'secret') - time.sleep(32) - # Jwt payload is now expired - # But with some leeway, it will still validate - jwt.decode(jwt_payload, 'secret', leeway=10) + ```python + import datetime + import time + import jwt + + jwt_payload = jwt.encode({ + 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30) + }, 'secret') + + time.sleep(32) + + # JWT payload is now expired + # But with some leeway, it will still validate + jwt.decode(jwt_payload, 'secret', leeway=10) + ``` + + ### Not Before Time Claim + + > The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the nbf claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the nbf claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. + + The `nbf` claim works similarly to the `exp` claim above. + + ```python + jwt.encode({'nbf': 1371720939}, 'secret') + + jwt.encode({'nbf': datetime.utcnow()}, 'secret') + ``` + + ### Issuer Claim + + > The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. + + ```python + import jwt + + + payload = { + 'some': 'payload', + 'iss': 'urn:foo' + } + + token = jwt.encode(payload, 'secret') + decoded = jwt.decode(token, 'secret', issuer='urn:foo') + ``` + + ### Audience Claim + + > The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. + + ```python + import jwt + + + payload = { + 'some': 'payload', + 'aud': 'urn:foo' + } + token = jwt.encode(payload, 'secret') + decoded = jwt.decode(token, 'secret', audience='urn:foo') + ``` - License - ------- + ## License MIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/PyJWT.egg-info/PKG-INFO new/PyJWT-0.3.0/PyJWT.egg-info/PKG-INFO --- old/PyJWT-0.2.1/PyJWT.egg-info/PKG-INFO 2014-04-28 19:10:48.000000000 +0200 +++ new/PyJWT-0.3.0/PyJWT.egg-info/PKG-INFO 2014-10-22 07:37:35.000000000 +0200 @@ -1,46 +1,64 @@ Metadata-Version: 1.1 Name: PyJWT -Version: 0.2.1 +Version: 0.3.0 Summary: JSON Web Token implementation in Python Home-page: http://github.com/progrium/pyjwt Author: Jeff Lindsay Author-email: [email protected] License: MIT -Description: PyJWT [](https://travis-ci.org/progrium/pyjwt) - ===== +Description: # PyJWT [](https://travis-ci.org/progrium/pyjwt) + A Python implementation of [JSON Web Token draft 01](http://self-issued.info/docs/draft-jones-json-web-token-01.html). - Installing - ---------- + ## Installing + + ``` + $ pip install PyJWT + ``` + + **A Note on Dependencies**: + + The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on + using any of those algorithms, you'll need to install it as well. - sudo easy_install PyJWT + ``` + $ pip install PyCrypto + ``` - **Note**: The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on - using any of those algorithms you'll need to install it as well. + The Elliptic Curve Digital Signature algorithms depend on Python-ECDSA. If + you plan on using any of those algorithms, you'll need to install it as well. - sudo easy_install PyCrypto + ``` + $ pip install ecdsa + ``` - Usage - ----- + ## Usage - import jwt - jwt.encode({"some": "payload"}, "secret") + ```python + import jwt + jwt.encode({'some': 'payload'}, 'secret') + ``` Additional headers may also be specified. - jwt.encode({"some": "payload"}, "secret", headers={"kid": "230498151c214b788dd97f22b85410a5"}) + ```python + jwt.encode({'some': 'payload'}, 'secret', headers={'kid': '230498151c214b788dd97f22b85410a5'}) + ``` Note the resulting JWT will not be encrypted, but verifiable with a secret key. - jwt.decode("someJWTstring", "secret") + ```python + jwt.decode('someJWTstring', 'secret') + ``` If the secret is wrong, it will raise a `jwt.DecodeError` telling you as such. You can still get the payload by setting the `verify` argument to `False`. - jwt.decode("someJWTstring", verify=False) + ```python + jwt.decode('someJWTstring', verify=False) + ``` - Algorithms - ---------- + ## Algorithms The JWT spec supports several algorithms for cryptographic signing. This library currently supports: @@ -48,35 +66,46 @@ * HS256 - HMAC using SHA-256 hash algorithm (default) * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm + * ES256 - ECDSA signature algorithm using SHA-256 hash algorithm + * ES384 - ECDSA signature algorithm using SHA-384 hash algorithm + * ES512 - ECDSA signature algorithm using SHA-512 hash algorithm * RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm * RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm * RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm Change the algorithm with by setting it in encode: - jwt.encode({"some": "payload"}, "secret", "HS512") + ```python + jwt.encode({'some': 'payload'}, 'secret', 'HS512') + ``` When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both `jwt.encode()` and `jwt.decode()` (`"secret"` in the examples) is expected to - be an RSA private key as imported with `Crypto.PublicKey.RSA.importKey()`. + be an RSA public or private key as imported with `Crypto.PublicKey.RSA.importKey()`. - Tests - ----- + When using the ECDSA algorithms, the `key` argument is expected to + be an Elliptic Curve private key as imported with `ecdsa.SigningKey.from_pem()`, + or a public key as imported with `ecdsa.VerifyingKey.from_pem()`. + + ## Tests You can run tests from the project root after cloning with: - python tests/test_jwt.py + ``` + $ python tests/test_jwt.py + ``` - Support of reserved claim names - ------------------------------- + ## Support of reserved claim names JSON Web Token defines some reserved claim names and defines how they should be used. PyJWT supports these reserved claim names: - "exp" (Expiration Time) Claim + - "nbf" (Not Before Time) Claim + - "iss" (Issuer) Claim + - "aud" (Audience) Claim - Expiration Time Claim - ===================== + ### Expiration Time Claim From [draft 01 of the JWT spec](http://self-issued.info/docs/draft-jones-json-web-token-01.html#ReservedClaimName): @@ -91,18 +120,23 @@ You can pass the expiration time as a UTC UNIX timestamp (an int) or as a datetime, which will be converted into an int. For example: - jwt.encode({"exp": 1371720939}, "secret") + ```python + jwt.encode({'exp': 1371720939}, 'secret') - jwt.encode({"exp": datetime.utcnow()}, "secret") + jwt.encode({'exp': datetime.utcnow()}, 'secret') + ``` Expiration time is automatically verified in `jwt.decode()` and raises `jwt.ExpiredSignature` if the expiration time is in the past: - import jwt - try: - jwt.decode('JWT_STRING', "secret") - except jwt.ExpiredSignature: - # Signature has expired + ```python + import jwt + + try: + jwt.decode('JWT_STRING', 'secret') + except jwt.ExpiredSignature: + # Signature has expired + ``` Expiration time will be compared to the current UTC time (as given by `timegm(datetime.utcnow().utctimetuple())`), so be sure to use a UTC timestamp @@ -116,16 +150,69 @@ after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin: - import jwt, time - jwt_payload = jwt.encode({'exp': datetime.utcnow() + datetime.timedelta(seconds=30)}, 'secret') - time.sleep(32) - # Jwt payload is now expired - # But with some leeway, it will still validate - jwt.decode(jwt_payload, 'secret', leeway=10) + ```python + import datetime + import time + import jwt + + jwt_payload = jwt.encode({ + 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30) + }, 'secret') + + time.sleep(32) + + # JWT payload is now expired + # But with some leeway, it will still validate + jwt.decode(jwt_payload, 'secret', leeway=10) + ``` + + ### Not Before Time Claim + + > The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the nbf claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the nbf claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. + + The `nbf` claim works similarly to the `exp` claim above. + + ```python + jwt.encode({'nbf': 1371720939}, 'secret') + + jwt.encode({'nbf': datetime.utcnow()}, 'secret') + ``` + + ### Issuer Claim + + > The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. + + ```python + import jwt + + + payload = { + 'some': 'payload', + 'iss': 'urn:foo' + } + + token = jwt.encode(payload, 'secret') + decoded = jwt.decode(token, 'secret', issuer='urn:foo') + ``` + + ### Audience Claim + + > The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. + + ```python + import jwt + + + payload = { + 'some': 'payload', + 'aud': 'urn:foo' + } + token = jwt.encode(payload, 'secret') + decoded = jwt.decode(token, 'secret', audience='urn:foo') + ``` - License - ------- + ## License MIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/PyJWT.egg-info/SOURCES (Joses-iMac's conflicted copy 2014-10-17).txt new/PyJWT-0.3.0/PyJWT.egg-info/SOURCES (Joses-iMac's conflicted copy 2014-10-17).txt --- old/PyJWT-0.2.1/PyJWT.egg-info/SOURCES (Joses-iMac's conflicted copy 2014-10-17).txt 1970-01-01 01:00:00.000000000 +0100 +++ new/PyJWT-0.3.0/PyJWT.egg-info/SOURCES (Joses-iMac's conflicted copy 2014-10-17).txt 2014-10-15 01:07:45.000000000 +0200 @@ -0,0 +1,10 @@ +MANIFEST.in +README.md +setup.cfg +setup.py +PyJWT.egg-info/PKG-INFO +PyJWT.egg-info/SOURCES.txt +PyJWT.egg-info/dependency_links.txt +PyJWT.egg-info/top_level.txt +bin/jwt +jwt/__init__.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/PyJWT.egg-info/SOURCES.txt new/PyJWT-0.3.0/PyJWT.egg-info/SOURCES.txt --- old/PyJWT-0.2.1/PyJWT.egg-info/SOURCES.txt 2014-04-28 19:10:49.000000000 +0200 +++ new/PyJWT-0.3.0/PyJWT.egg-info/SOURCES.txt 2014-10-22 07:37:36.000000000 +0200 @@ -1,7 +1,9 @@ MANIFEST.in README.md +setup.cfg setup.py PyJWT.egg-info/PKG-INFO +PyJWT.egg-info/SOURCES (Joses-iMac's conflicted copy 2014-10-17).txt PyJWT.egg-info/SOURCES.txt PyJWT.egg-info/dependency_links.txt PyJWT.egg-info/top_level.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/README.md new/PyJWT-0.3.0/README.md --- old/PyJWT-0.2.1/README.md 2014-04-28 19:10:40.000000000 +0200 +++ new/PyJWT-0.3.0/README.md 2014-10-22 07:21:34.000000000 +0200 @@ -1,38 +1,56 @@ -PyJWT [](https://travis-ci.org/progrium/pyjwt) -===== +# PyJWT [](https://travis-ci.org/progrium/pyjwt) + A Python implementation of [JSON Web Token draft 01](http://self-issued.info/docs/draft-jones-json-web-token-01.html). -Installing ----------- +## Installing + +``` +$ pip install PyJWT +``` + +**A Note on Dependencies**: + +The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on +using any of those algorithms, you'll need to install it as well. - sudo easy_install PyJWT +``` +$ pip install PyCrypto +``` -**Note**: The RSASSA-PKCS1-v1_5 algorithms depend on PyCrypto. If you plan on -using any of those algorithms you'll need to install it as well. +The Elliptic Curve Digital Signature algorithms depend on Python-ECDSA. If +you plan on using any of those algorithms, you'll need to install it as well. - sudo easy_install PyCrypto +``` +$ pip install ecdsa +``` -Usage ------ +## Usage - import jwt - jwt.encode({"some": "payload"}, "secret") +```python +import jwt +jwt.encode({'some': 'payload'}, 'secret') +``` Additional headers may also be specified. - jwt.encode({"some": "payload"}, "secret", headers={"kid": "230498151c214b788dd97f22b85410a5"}) +```python +jwt.encode({'some': 'payload'}, 'secret', headers={'kid': '230498151c214b788dd97f22b85410a5'}) +``` Note the resulting JWT will not be encrypted, but verifiable with a secret key. - jwt.decode("someJWTstring", "secret") +```python +jwt.decode('someJWTstring', 'secret') +``` If the secret is wrong, it will raise a `jwt.DecodeError` telling you as such. You can still get the payload by setting the `verify` argument to `False`. - jwt.decode("someJWTstring", verify=False) +```python +jwt.decode('someJWTstring', verify=False) +``` -Algorithms ----------- +## Algorithms The JWT spec supports several algorithms for cryptographic signing. This library currently supports: @@ -40,35 +58,46 @@ * HS256 - HMAC using SHA-256 hash algorithm (default) * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm +* ES256 - ECDSA signature algorithm using SHA-256 hash algorithm +* ES384 - ECDSA signature algorithm using SHA-384 hash algorithm +* ES512 - ECDSA signature algorithm using SHA-512 hash algorithm * RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm * RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm * RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm Change the algorithm with by setting it in encode: - jwt.encode({"some": "payload"}, "secret", "HS512") +```python +jwt.encode({'some': 'payload'}, 'secret', 'HS512') +``` When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both `jwt.encode()` and `jwt.decode()` (`"secret"` in the examples) is expected to -be an RSA private key as imported with `Crypto.PublicKey.RSA.importKey()`. +be an RSA public or private key as imported with `Crypto.PublicKey.RSA.importKey()`. -Tests ------ +When using the ECDSA algorithms, the `key` argument is expected to +be an Elliptic Curve private key as imported with `ecdsa.SigningKey.from_pem()`, +or a public key as imported with `ecdsa.VerifyingKey.from_pem()`. + +## Tests You can run tests from the project root after cloning with: - python tests/test_jwt.py +``` +$ python tests/test_jwt.py +``` -Support of reserved claim names -------------------------------- +## Support of reserved claim names JSON Web Token defines some reserved claim names and defines how they should be used. PyJWT supports these reserved claim names: - "exp" (Expiration Time) Claim + - "nbf" (Not Before Time) Claim + - "iss" (Issuer) Claim + - "aud" (Audience) Claim -Expiration Time Claim -===================== +### Expiration Time Claim From [draft 01 of the JWT spec](http://self-issued.info/docs/draft-jones-json-web-token-01.html#ReservedClaimName): @@ -83,18 +112,23 @@ You can pass the expiration time as a UTC UNIX timestamp (an int) or as a datetime, which will be converted into an int. For example: - jwt.encode({"exp": 1371720939}, "secret") +```python +jwt.encode({'exp': 1371720939}, 'secret') - jwt.encode({"exp": datetime.utcnow()}, "secret") +jwt.encode({'exp': datetime.utcnow()}, 'secret') +``` Expiration time is automatically verified in `jwt.decode()` and raises `jwt.ExpiredSignature` if the expiration time is in the past: - import jwt - try: - jwt.decode('JWT_STRING', "secret") - except jwt.ExpiredSignature: - # Signature has expired +```python +import jwt + +try: + jwt.decode('JWT_STRING', 'secret') +except jwt.ExpiredSignature: + # Signature has expired +``` Expiration time will be compared to the current UTC time (as given by `timegm(datetime.utcnow().utctimetuple())`), so be sure to use a UTC timestamp @@ -108,15 +142,68 @@ after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin: - import jwt, time - jwt_payload = jwt.encode({'exp': datetime.utcnow() + datetime.timedelta(seconds=30)}, 'secret') - time.sleep(32) - # Jwt payload is now expired - # But with some leeway, it will still validate - jwt.decode(jwt_payload, 'secret', leeway=10) +```python +import datetime +import time +import jwt + +jwt_payload = jwt.encode({ + 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30) +}, 'secret') + +time.sleep(32) + +# JWT payload is now expired +# But with some leeway, it will still validate +jwt.decode(jwt_payload, 'secret', leeway=10) +``` + +### Not Before Time Claim + +> The nbf (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the nbf claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the nbf claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. + +The `nbf` claim works similarly to the `exp` claim above. + +```python +jwt.encode({'nbf': 1371720939}, 'secret') + +jwt.encode({'nbf': datetime.utcnow()}, 'secret') +``` + +### Issuer Claim + +> The iss (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The iss value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. + +```python +import jwt + + +payload = { + 'some': 'payload', + 'iss': 'urn:foo' +} + +token = jwt.encode(payload, 'secret') +decoded = jwt.decode(token, 'secret', issuer='urn:foo') +``` + +### Audience Claim + +> The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. + +```python +import jwt + + +payload = { + 'some': 'payload', + 'aud': 'urn:foo' +} +token = jwt.encode(payload, 'secret') +decoded = jwt.decode(token, 'secret', audience='urn:foo') +``` -License -------- +## License MIT diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/jwt/__init__.py new/PyJWT-0.3.0/jwt/__init__.py --- old/PyJWT-0.2.1/jwt/__init__.py 2014-04-28 19:10:40.000000000 +0200 +++ new/PyJWT-0.3.0/jwt/__init__.py 2014-10-22 07:37:04.000000000 +0200 @@ -19,14 +19,16 @@ except ImportError: import simplejson as json -__all__ = ['encode', 'decode', 'DecodeError'] - if sys.version_info >= (3, 0, 0): unicode = str basestring = str +__version__ = '0.3.0' +__all__ = ['encode', 'decode', 'DecodeError'] + + class DecodeError(Exception): pass @@ -35,7 +37,16 @@ pass +class InvalidAudience(Exception): + pass + + +class InvalidIssuer(Exception): + pass + + signing_methods = { + 'none': lambda msg, key: b'', 'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(), 'HS384': lambda msg, key: hmac.new(key, msg, hashlib.sha384).digest(), 'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest() @@ -47,15 +58,18 @@ 'HS512': lambda msg, key: hmac.new(key, msg, hashlib.sha512).digest() } + def prepare_HS_key(key): - if isinstance(key, basestring): - if isinstance(key, unicode): - key = key.encode('utf-8') - else: - raise TypeError("Expecting a string-formatted key.") + if not isinstance(key, basestring) and not isinstance(key, bytes): + raise TypeError('Expecting a string- or bytes-formatted key.') + + if isinstance(key, unicode): + key = key.encode('utf-8') + return key prepare_key_methods = { + 'none': lambda key: None, 'HS256': prepare_HS_key, 'HS384': prepare_HS_key, 'HS512': prepare_HS_key @@ -81,14 +95,17 @@ }) def prepare_RS_key(key): + if isinstance(key, RSA._RSAobj): + return key + if isinstance(key, basestring): if isinstance(key, unicode): key = key.encode('utf-8') + key = RSA.importKey(key) - elif isinstance(key, RSA._RSAobj): - pass else: - raise TypeError("Expecting a PEM- or RSA-formatted key.") + raise TypeError('Expecting a PEM- or RSA-formatted key.') + return key prepare_key_methods.update({ @@ -100,6 +117,57 @@ except ImportError: pass +try: + import ecdsa + from Crypto.Hash import SHA256 + from Crypto.Hash import SHA384 + from Crypto.Hash import SHA512 + + signing_methods.update({ + 'ES256': lambda msg, key: key.sign(msg, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der), + 'ES384': lambda msg, key: key.sign(msg, hashfunc=hashlib.sha384, sigencode=ecdsa.util.sigencode_der), + 'ES512': lambda msg, key: key.sign(msg, hashfunc=hashlib.sha512, sigencode=ecdsa.util.sigencode_der), + }) + + verify_methods.update({ + 'ES256': lambda msg, key, sig: key.verify(sig, msg, hashfunc=hashlib.sha256, sigdecode=ecdsa.util.sigdecode_der), + 'ES384': lambda msg, key, sig: key.verify(sig, msg, hashfunc=hashlib.sha384, sigdecode=ecdsa.util.sigdecode_der), + 'ES512': lambda msg, key, sig: key.verify(sig, msg, hashfunc=hashlib.sha512, sigdecode=ecdsa.util.sigdecode_der), + }) + + def prepare_ES_key(key): + if isinstance(key, ecdsa.SigningKey) or \ + isinstance(key, ecdsa.VerifyingKey): + return key + + if isinstance(key, basestring): + if isinstance(key, unicode): + key = key.encode('utf-8') + + # Attempt to load key. We don't know if it's + # a Signing Key or a Verifying Key, so we try + # the Verifying Key first. + try: + key = ecdsa.VerifyingKey.from_pem(key) + except ecdsa.der.UnexpectedDER: + try: + key = ecdsa.SigningKey.from_pem(key) + except: + raise + else: + raise TypeError('Expecting a PEM-formatted key.') + + return key + + prepare_key_methods.update({ + 'ES256': prepare_ES_key, + 'ES384': prepare_ES_key, + 'ES512': prepare_ES_key + }) + +except ImportError: + pass + def constant_time_compare(val1, val2): """ @@ -109,20 +177,26 @@ """ if len(val1) != len(val2): return False + result = 0 - if sys.version_info >= (3, 0, 0): # bytes are numbers + + if sys.version_info >= (3, 0, 0): + # Bytes are numbers for x, y in zip(val1, val2): result |= x ^ y else: for x, y in zip(val1, val2): result |= ord(x) ^ ord(y) + return result == 0 def base64url_decode(input): rem = len(input) % 4 + if rem > 0: input += b'=' * (4 - rem) + return base64.urlsafe_b64decode(input) @@ -136,28 +210,34 @@ header_data = base64url_decode(header_segment).decode('utf-8') return json.loads(header_data) except (ValueError, TypeError): - raise DecodeError("Invalid header encoding") + raise DecodeError('Invalid header encoding') def encode(payload, key, algorithm='HS256', headers=None): segments = [] + if algorithm is None: + algorithm = 'none' + # Check that we get a mapping if not isinstance(payload, Mapping): - raise TypeError("Expecting a mapping object, as json web token only" - "support json objects.") + raise TypeError('Expecting a mapping object, as json web token only' + 'support json objects.') # Header - header = {"typ": "JWT", "alg": algorithm} + header = {'typ': 'JWT', 'alg': algorithm} if headers: header.update(headers) + json_header = json.dumps(header, separators=(',', ':')).encode('utf-8') segments.append(base64url_encode(json_header)) # Payload - for time_claim in ['exp', 'iat', 'nbf']: # convert datetime to a intDate value in known time-format claims + for time_claim in ['exp', 'iat', 'nbf']: + # Convert datetime to a intDate value in known time-format claims if isinstance(payload.get(time_claim), datetime): payload[time_claim] = timegm(payload[time_claim].utctimetuple()) + json_payload = json.dumps(payload, separators=(',', ':')).encode('utf-8') segments.append(base64url_encode(json_payload)) @@ -167,17 +247,22 @@ key = prepare_key_methods[algorithm](key) signature = signing_methods[algorithm](signing_input, key) except KeyError: - raise NotImplementedError("Algorithm not supported") + raise NotImplementedError('Algorithm not supported') + segments.append(base64url_encode(signature)) + return b'.'.join(segments) -def decode(jwt, key='', verify=True, verify_expiration=True, leeway=0): +def decode(jwt, key='', verify=True, **kwargs): payload, signing_input, header, signature = load(jwt) if verify: + verify_expiration = kwargs.pop('verify_expiration', True) + leeway = kwargs.pop('leeway', 0) + verify_signature(payload, signing_input, header, signature, key, - verify_expiration, leeway) + verify_expiration, leeway, **kwargs) return payload @@ -189,49 +274,76 @@ signing_input, crypto_segment = jwt.rsplit(b'.', 1) header_segment, payload_segment = signing_input.split(b'.', 1) except ValueError: - raise DecodeError("Not enough segments") + raise DecodeError('Not enough segments') try: header_data = base64url_decode(header_segment) except (TypeError, binascii.Error): - raise DecodeError("Invalid header padding") + raise DecodeError('Invalid header padding') try: header = json.loads(header_data.decode('utf-8')) except ValueError as e: - raise DecodeError("Invalid header string: %s" % e) + raise DecodeError('Invalid header string: %s' % e) try: payload_data = base64url_decode(payload_segment) except (TypeError, binascii.Error): - raise DecodeError("Invalid payload padding") + raise DecodeError('Invalid payload padding') try: payload = json.loads(payload_data.decode('utf-8')) except ValueError as e: - raise DecodeError("Invalid payload string: %s" % e) + raise DecodeError('Invalid payload string: %s' % e) try: signature = base64url_decode(crypto_segment) except (TypeError, binascii.Error): - raise DecodeError("Invalid crypto padding") + raise DecodeError('Invalid crypto padding') return (payload, signing_input, header, signature) def verify_signature(payload, signing_input, header, signature, key='', - verify_expiration=True, leeway=0): + verify_expiration=True, leeway=0, **kwargs): try: - key = prepare_key_methods[header['alg']](key) - if header['alg'].startswith('HS'): - expected = verify_methods[header['alg']](signing_input, key) + algorithm = header['alg'].upper() + key = prepare_key_methods[algorithm](key) + + if algorithm.startswith('HS'): + expected = verify_methods[algorithm](signing_input, key) + if not constant_time_compare(signature, expected): - raise DecodeError("Signature verification failed") + raise DecodeError('Signature verification failed') else: - if not verify_methods[header['alg']](signing_input, key, signature): - raise DecodeError("Signature verification failed") + if not verify_methods[algorithm](signing_input, key, signature): + raise DecodeError('Signature verification failed') except KeyError: - raise DecodeError("Algorithm not supported") + raise DecodeError('Algorithm not supported') + + if 'nbf' in payload and verify_expiration: + utc_timestamp = timegm(datetime.utcnow().utctimetuple()) + + if payload['nbf'] > (utc_timestamp + leeway): + raise ExpiredSignature('Signature not yet valid') if 'exp' in payload and verify_expiration: utc_timestamp = timegm(datetime.utcnow().utctimetuple()) + if payload['exp'] < (utc_timestamp - leeway): - raise ExpiredSignature("Signature has expired") + raise ExpiredSignature('Signature has expired') + + audience = kwargs.get('audience') + + if audience: + if isinstance(audience, list): + audiences = audience + else: + audiences = [audience] + + if payload.get('aud') not in audiences: + raise InvalidAudience('Invalid audience') + + issuer = kwargs.get('issuer') + + if issuer: + if payload.get('iss') != issuer: + raise InvalidIssuer('Invalid issuer') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/setup.cfg new/PyJWT-0.3.0/setup.cfg --- old/PyJWT-0.2.1/setup.cfg 2014-04-28 19:10:49.000000000 +0200 +++ new/PyJWT-0.3.0/setup.cfg 2014-10-22 07:37:36.000000000 +0200 @@ -1,3 +1,6 @@ +[wheel] +universal = 1 + [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-0.2.1/setup.py new/PyJWT-0.3.0/setup.py --- old/PyJWT-0.2.1/setup.py 2014-04-28 19:10:40.000000000 +0200 +++ new/PyJWT-0.3.0/setup.py 2014-10-16 06:02:37.000000000 +0200 @@ -1,32 +1,54 @@ #!/usr/bin/env python import os +import sys +import re from setuptools import setup +def get_version(package): + """ + Return package version as listed in `__version__` in `init.py`. + """ + init_py = open(os.path.join(package, '__init__.py')).read() + return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) + + +version = get_version('jwt') + with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme: long_description = readme.read() +if sys.argv[-1] == 'publish': + os.system("python setup.py sdist upload") + os.system("python setup.py bdist_wheel upload") + print("You probably want to also tag the version now:") + print(" git tag -a %s -m 'version %s'" % (version, version)) + print(" git push --tags") + sys.exit() + + setup( - name="PyJWT", - version="0.2.1", - author="Jeff Lindsay", - author_email="[email protected]", - description="JSON Web Token implementation in Python", - license="MIT", - keywords="jwt json web token security signing", - url="http://github.com/progrium/pyjwt", + name='PyJWT', + version=version, + author='Jeff Lindsay', + author_email='[email protected]', + description='JSON Web Token implementation in Python', + license='MIT', + keywords='jwt json web token security signing', + url='http://github.com/progrium/pyjwt', packages=['jwt'], scripts=['bin/jwt'], long_description=long_description, classifiers=[ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Topic :: Utilities", + 'Development Status :: 3 - Alpha', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Topic :: Utilities', ], - test_suite='tests.test_jwt') + test_suite='tests.test_jwt' +) -- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
