diff --git a/README.md b/README.md index e16ff3d..beffb3a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Module for generating and verifying [JSON Web Tokens](http://self-issued.info/do - **Note:** From version 2.0.1 the namespace has changed from `jwt` to `python_jwt`, in order to avoid conflict with [PyJWT](https://github.com/jpadilla/pyjwt). - **Note:** Versions 1.0.0 and later fix [a vulnerability](https://www.timmclean.net/2015/02/25/jwt-alg-none.html) in JSON Web Token verification so please upgrade if you're using this functionality. The API has changed so you will need to update your application. [verify_jwt](http://rawgit.davedoesdev.com/davedoesdev/python-jwt/master/docs/_build/html/index.html#python_jwt.verify_jwt) now requires you to specify which signature algorithms are allowed. - Uses [jwcrypto](https://jwcrypto.readthedocs.io) to do the heavy lifting. -- Supports [__RS256__, __RS384__, __RS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.3), [__PS256__, __PS384__, __PS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.5), [__HS256__, __HS384__, __HS512__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.2) and [__none__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.6) signature algorithms. +- Supports [__RS256__, __RS384__, __RS512__](https://tools.ietf.org/html/rfc7518#section-3.3), [__PS256__, __PS384__, __PS512__](https://tools.ietf.org/html/rfc7518#section-3.5), [__HS256__, __HS384__, __HS512__](https://tools.ietf.org/html/rfc7518#section-3.2), [__ES256__, __ES384__, __ES512__](https://tools.ietf.org/html/rfc7518#section-3.4), [__ES256K__](https://tools.ietf.org/html/draft-ietf-cose-webauthn-algorithms-05#section-3.2), [__EdDSA__](https://tools.ietf.org/html/rfc8037#section-3.1) and [__none__](http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14#section-3.6) signature algorithms. - Unit tests, including tests for interoperability with [jose](https://github.com/panva/jose). - Supports Python 2,7 and 3.6+. **Note:** [generate_jwt](http://rawgit.davedoesdev.com/davedoesdev/python-jwt/master/docs/_build/html/index.html#python_jwt.generate_jwt) returns the token as a Unicode string, even on Python 2.7. diff --git a/bench/generate_token_bench.py b/bench/generate_token_bench.py index 1341df4..b14f901 100755 --- a/bench/generate_token_bench.py +++ b/bench/generate_token_bench.py @@ -5,7 +5,7 @@ # pylint: disable=wrong-import-position,wrong-import-order from datetime import timedelta from bench.unitbench import Benchmark -from test.fixtures import payload, priv_keys, priv_key, algs +from test.fixtures import payload, priv_keys, algs from bench.reporter import Reporter import python_jwt as jwt @@ -25,7 +25,7 @@ def make_bench_generate_token(alg): """ Return function which will generate token for particular algorithm """ def f(_): """ Generate token """ - privk = priv_keys[alg].get('default', priv_key) + privk = priv_keys[alg]['python-jwt'] jwt.generate_jwt(payload, privk, alg, timedelta(seconds=5)) return f diff --git a/bench/load_key_bench.py b/bench/load_key_bench.py index 8fc66c2..3c7bb09 100755 --- a/bench/load_key_bench.py +++ b/bench/load_key_bench.py @@ -4,7 +4,7 @@ # pylint: disable=wrong-import-position,wrong-import-order from bench.unitbench import Benchmark -from test.fixtures import priv_pem, to_bytes_2and3 +from test.fixtures import rsa_priv_pem, to_bytes_2and3 from bench.reporter import Reporter from jwcrypto.jwk import JWK @@ -21,7 +21,7 @@ def repeats(self): def bench_RSA(self): """ Import key """ - JWK.from_pem(to_bytes_2and3(priv_pem)) + JWK.from_pem(to_bytes_2and3(rsa_priv_pem)) if __name__ == "__main__": #pylint: disable=W0402 diff --git a/bench/verify_token_bench.py b/bench/verify_token_bench.py index b8b4350..6e0a34b 100755 --- a/bench/verify_token_bench.py +++ b/bench/verify_token_bench.py @@ -5,7 +5,7 @@ # pylint: disable=wrong-import-position,wrong-import-order from datetime import timedelta from bench.unitbench import Benchmark -from test.fixtures import payload, priv_keys, priv_key, pub_keys, pub_key, algs +from test.fixtures import payload, priv_keys, pub_keys, algs from bench.reporter import Reporter import python_jwt as jwt @@ -23,11 +23,11 @@ def repeats(self): #pylint: disable=W0621 def make_bench_verify_token(alg): """ Return function which will generate token for particular algorithm """ - privk = priv_keys[alg].get('default', priv_key) + privk = priv_keys[alg]['python-jwt'] token = jwt.generate_jwt(payload, privk, alg, timedelta(days=1)) def f(_): """ Verify token """ - pubk = pub_keys[alg].get('default', pub_key) + pubk = pub_keys[alg]['python-jwt'] jwt.verify_jwt(token, pubk, [alg]) return f diff --git a/coverage/coverage.xml b/coverage/coverage.xml index 5b3666f..7d04680 100644 --- a/coverage/coverage.xml +++ b/coverage/coverage.xml @@ -1,5 +1,5 @@ - + @@ -40,64 +40,65 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - + + + + + - - - - + + + + - - + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - + + + + + + + diff --git a/coverage/html/index.html b/coverage/html/index.html index 7b0502b..f981f27 100644 --- a/coverage/html/index.html +++ b/coverage/html/index.html @@ -71,28 +71,28 @@

Coverage report: Total - 87 + 88 0 0 56 0 - 100% + 100% python_jwt/__init__.py - 87 + 88 0 0 56 0 - 100% + 100% @@ -107,7 +107,7 @@

Coverage report:

coverage.py v4.5.4, - created at 2019-11-14 09:26 + created at 2020-08-24 22:34

diff --git a/coverage/html/python_jwt___init___py.html b/coverage/html/python_jwt___init___py.html index 2a69866..192cab4 100644 --- a/coverage/html/python_jwt___init___py.html +++ b/coverage/html/python_jwt___init___py.html @@ -30,8 +30,8 @@

Coverage for python_jwt/__init__.py : Show keyboard shortcuts

- 87 statements   - 87 run + 88 statements   + 88 run 0 missing 0 excluded @@ -159,17 +159,17 @@

88

89

90

-

91

-

92

-

93

+

91

+

92

+

93

94

95

96

97

98

99

-

100

-

101

+

100

+

101

102

103

104

@@ -209,76 +209,76 @@

138

139

140

-

141

+

141

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

153

154

155

156

-

157

-

158

+

157

+

158

159

160

161

162

163

-

164

-

165

+

164

+

165

166

167

168

169

170

-

171

-

172

-

173

-

174

+

171

+

172

+

173

+

174

175

-

176

-

177

+

176

+

177

178

179

180

181

182

-

183

-

184

+

183

+

184

185

186

187

188

189

-

190

-

191

+

190

+

191

192

193

194

195

196

-

197

-

198

+

197

+

198

199

200

201

202

203

-

204

-

205

-

206

+

204

+

205

+

206

207

208

-

209

-

210

+

209

+

210

211

212

213

@@ -289,10 +289,11 @@

218

219

220

-

221

+

221

222

223

224

+

225

@@ -384,142 +385,143 @@

signature = '' 

else: 

token = JWS(json_encode(claims)) 

-

token.add_signature(priv_key, protected=header) 

-

signature = json_decode(token.serialize())['signature'] 

-

 

-

return u'%s.%s.%s' % ( 

-

base64url_encode(json_encode(header)), 

-

base64url_encode(json_encode(claims)), 

-

signature 

-

) 

-

 

-

#pylint: disable=R0912,too-many-locals 

-

 

-

def verify_jwt(jwt, 

-

pub_key=None, 

-

allowed_algs=None, 

-

iat_skew=timedelta(), 

-

checks_optional=False, 

-

ignore_not_implemented=False): 

-

""" 

-

Verify a JSON Web Token. 

-

 

-

:param jwt: The JSON Web Token to verify. 

-

:type jwt: str or unicode 

-

 

-

:param pub_key: The public key to be used to verify the token. Note: if you pass ``None`` and **allowed_algs** contains ``none`` then the token's signature will not be verified. 

-

:type pub_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ 

-

 

-

:param allowed_algs: Algorithms expected to be used to sign the token. The ``in`` operator is used to test membership. 

-

:type allowed_algs: list or NoneType (meaning an empty list) 

-

 

-

:param iat_skew: The amount of leeway to allow between the issuer's clock and the verifier's clock when verifiying that the token was generated in the past. Defaults to no leeway. 

-

:type iat_skew: datetime.timedelta 

-

 

-

:param checks_optional: If ``False``, then the token must contain the **typ** header property and the **iat**, **nbf** and **exp** claim properties. 

-

:type checks_optional: bool 

-

 

-

:param ignore_not_implemented: If ``False``, then the token must *not* contain the **jku**, **jwk**, **x5u**, **x5c** or **x5t** header properties. 

-

:type ignore_not_implemented: bool 

-

 

-

:rtype: tuple 

-

:returns: ``(header, claims)`` if the token was verified successfully. The token must pass the following tests: 

-

 

-

- Its header must contain a property **alg** with a value in **allowed_algs**. 

-

- Its signature must verify using **pub_key** (unless its algorithm is ``none`` and ``none`` is in **allowed_algs**). 

-

- If the corresponding property is present or **checks_optional** is ``False``: 

-

 

-

- Its header must contain a property **typ** with the value ``JWT``. 

-

- Its claims must contain a property **iat** which represents a date in the past (taking into account :obj:`iat_skew`). 

-

- Its claims must contain a property **nbf** which represents a date in the past. 

-

- Its claims must contain a property **exp** which represents a date in the future. 

-

 

-

:raises: If the token failed to verify. 

-

""" 

-

if allowed_algs is None: 

-

allowed_algs = [] 

-

 

-

if not isinstance(allowed_algs, list): 

-

# jwcrypto only supports list of allowed algorithms 

-

raise _JWTError('allowed_algs must be a list') 

-

 

-

header, claims, _ = jwt.split('.') 

-

 

-

parsed_header = json_decode(base64url_decode(header)) 

-

 

-

alg = parsed_header.get('alg') 

-

if alg is None: 

-

raise _JWTError('alg header not present') 

-

if alg not in allowed_algs: 

-

raise _JWTError('algorithm not allowed: ' + alg) 

-

 

-

if not ignore_not_implemented: 

-

for k in parsed_header: 

-

if k not in JWSHeaderRegistry: 

-

raise _JWTError('unknown header: ' + k) 

-

if not JWSHeaderRegistry[k].supported: 

-

raise _JWTError('header not implemented: ' + k) 

-

 

-

if pub_key: 

-

token = JWS() 

-

token.allowed_algs = allowed_algs 

-

token.deserialize(jwt, pub_key) 

-

elif 'none' not in allowed_algs: 

-

raise _JWTError('no key but none alg not allowed') 

-

 

-

parsed_claims = json_decode(base64url_decode(claims)) 

-

 

-

utcnow = datetime.utcnow() 

-

now = timegm(utcnow.utctimetuple()) 

-

 

-

typ = parsed_header.get('typ') 

-

if typ is None: 

-

if not checks_optional: 

-

raise _JWTError('typ header not present') 

-

elif typ != 'JWT': 

-

raise _JWTError('typ header is not JWT') 

-

 

-

iat = parsed_claims.get('iat') 

-

if iat is None: 

-

if not checks_optional: 

-

raise _JWTError('iat claim not present') 

-

elif iat > timegm((utcnow + iat_skew).utctimetuple()): 

-

raise _JWTError('issued in the future') 

-

 

-

nbf = parsed_claims.get('nbf') 

-

if nbf is None: 

-

if not checks_optional: 

-

raise _JWTError('nbf claim not present') 

-

elif nbf > now: 

-

raise _JWTError('not yet valid') 

-

 

-

exp = parsed_claims.get('exp') 

-

if exp is None: 

-

if not checks_optional: 

-

raise _JWTError('exp claim not present') 

-

elif exp <= now: 

-

raise _JWTError('expired') 

-

 

-

return parsed_header, parsed_claims 

-

 

-

#pylint: enable=R0912 

-

 

-

def process_jwt(jwt): 

-

""" 

-

Process a JSON Web Token without verifying it. 

-

 

-

Call this before :func:`verify_jwt` if you need access to the header or claims in the token before verifying it. For example, the claims might identify the issuer such that you can retrieve the appropriate public key. 

-

 

-

:param jwt: The JSON Web Token to verify. 

-

:type jwt: str or unicode 

-

 

-

:rtype: tuple 

-

:returns: ``(header, claims)`` 

-

""" 

-

header, claims, _ = jwt.split('.') 

-

parsed_header = json_decode(base64url_decode(header)) 

-

parsed_claims = json_decode(base64url_decode(claims)) 

-

return parsed_header, parsed_claims 

+

token.allowed_algs = [header['alg']] 

+

token.add_signature(priv_key, protected=header) 

+

signature = json_decode(token.serialize())['signature'] 

+

 

+

return u'%s.%s.%s' % ( 

+

base64url_encode(json_encode(header)), 

+

base64url_encode(json_encode(claims)), 

+

signature 

+

) 

+

 

+

#pylint: disable=R0912,too-many-locals 

+

 

+

def verify_jwt(jwt, 

+

pub_key=None, 

+

allowed_algs=None, 

+

iat_skew=timedelta(), 

+

checks_optional=False, 

+

ignore_not_implemented=False): 

+

""" 

+

Verify a JSON Web Token. 

+

 

+

:param jwt: The JSON Web Token to verify. 

+

:type jwt: str or unicode 

+

 

+

:param pub_key: The public key to be used to verify the token. Note: if you pass ``None`` and **allowed_algs** contains ``none`` then the token's signature will not be verified. 

+

:type pub_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_ 

+

 

+

:param allowed_algs: Algorithms expected to be used to sign the token. The ``in`` operator is used to test membership. 

+

:type allowed_algs: list or NoneType (meaning an empty list) 

+

 

+

:param iat_skew: The amount of leeway to allow between the issuer's clock and the verifier's clock when verifiying that the token was generated in the past. Defaults to no leeway. 

+

:type iat_skew: datetime.timedelta 

+

 

+

:param checks_optional: If ``False``, then the token must contain the **typ** header property and the **iat**, **nbf** and **exp** claim properties. 

+

:type checks_optional: bool 

+

 

+

:param ignore_not_implemented: If ``False``, then the token must *not* contain the **jku**, **jwk**, **x5u**, **x5c** or **x5t** header properties. 

+

:type ignore_not_implemented: bool 

+

 

+

:rtype: tuple 

+

:returns: ``(header, claims)`` if the token was verified successfully. The token must pass the following tests: 

+

 

+

- Its header must contain a property **alg** with a value in **allowed_algs**. 

+

- Its signature must verify using **pub_key** (unless its algorithm is ``none`` and ``none`` is in **allowed_algs**). 

+

- If the corresponding property is present or **checks_optional** is ``False``: 

+

 

+

- Its header must contain a property **typ** with the value ``JWT``. 

+

- Its claims must contain a property **iat** which represents a date in the past (taking into account :obj:`iat_skew`). 

+

- Its claims must contain a property **nbf** which represents a date in the past. 

+

- Its claims must contain a property **exp** which represents a date in the future. 

+

 

+

:raises: If the token failed to verify. 

+

""" 

+

if allowed_algs is None: 

+

allowed_algs = [] 

+

 

+

if not isinstance(allowed_algs, list): 

+

# jwcrypto only supports list of allowed algorithms 

+

raise _JWTError('allowed_algs must be a list') 

+

 

+

header, claims, _ = jwt.split('.') 

+

 

+

parsed_header = json_decode(base64url_decode(header)) 

+

 

+

alg = parsed_header.get('alg') 

+

if alg is None: 

+

raise _JWTError('alg header not present') 

+

if alg not in allowed_algs: 

+

raise _JWTError('algorithm not allowed: ' + alg) 

+

 

+

if not ignore_not_implemented: 

+

for k in parsed_header: 

+

if k not in JWSHeaderRegistry: 

+

raise _JWTError('unknown header: ' + k) 

+

if not JWSHeaderRegistry[k].supported: 

+

raise _JWTError('header not implemented: ' + k) 

+

 

+

if pub_key: 

+

token = JWS() 

+

token.allowed_algs = allowed_algs 

+

token.deserialize(jwt, pub_key) 

+

elif 'none' not in allowed_algs: 

+

raise _JWTError('no key but none alg not allowed') 

+

 

+

parsed_claims = json_decode(base64url_decode(claims)) 

+

 

+

utcnow = datetime.utcnow() 

+

now = timegm(utcnow.utctimetuple()) 

+

 

+

typ = parsed_header.get('typ') 

+

if typ is None: 

+

if not checks_optional: 

+

raise _JWTError('typ header not present') 

+

elif typ != 'JWT': 

+

raise _JWTError('typ header is not JWT') 

+

 

+

iat = parsed_claims.get('iat') 

+

if iat is None: 

+

if not checks_optional: 

+

raise _JWTError('iat claim not present') 

+

elif iat > timegm((utcnow + iat_skew).utctimetuple()): 

+

raise _JWTError('issued in the future') 

+

 

+

nbf = parsed_claims.get('nbf') 

+

if nbf is None: 

+

if not checks_optional: 

+

raise _JWTError('nbf claim not present') 

+

elif nbf > now: 

+

raise _JWTError('not yet valid') 

+

 

+

exp = parsed_claims.get('exp') 

+

if exp is None: 

+

if not checks_optional: 

+

raise _JWTError('exp claim not present') 

+

elif exp <= now: 

+

raise _JWTError('expired') 

+

 

+

return parsed_header, parsed_claims 

+

 

+

#pylint: enable=R0912 

+

 

+

def process_jwt(jwt): 

+

""" 

+

Process a JSON Web Token without verifying it. 

+

 

+

Call this before :func:`verify_jwt` if you need access to the header or claims in the token before verifying it. For example, the claims might identify the issuer such that you can retrieve the appropriate public key. 

+

 

+

:param jwt: The JSON Web Token to verify. 

+

:type jwt: str or unicode 

+

 

+

:rtype: tuple 

+

:returns: ``(header, claims)`` 

+

""" 

+

header, claims, _ = jwt.split('.') 

+

parsed_header = json_decode(base64url_decode(header)) 

+

parsed_claims = json_decode(base64url_decode(claims)) 

+

return parsed_header, parsed_claims 

@@ -530,7 +532,7 @@

« index     coverage.py v4.5.4, - created at 2019-11-14 09:26 + created at 2020-08-24 22:34

diff --git a/coverage/html/status.json b/coverage/html/status.json index e569428..a37a22e 100644 --- a/coverage/html/status.json +++ b/coverage/html/status.json @@ -1 +1 @@ -{"files":{"python_jwt___init___py":{"index":{"relative_filename":"python_jwt/__init__.py","html_filename":"python_jwt___init___py.html","nums":[1,87,0,0,56,0,0]},"hash":"a30ce36e7e59434b21d32b6904df4aee"}},"version":"4.5.4","settings":"fd659caad95e8103c8432140d3fc963a","format":1} \ No newline at end of file +{"files":{"python_jwt___init___py":{"index":{"relative_filename":"python_jwt/__init__.py","html_filename":"python_jwt___init___py.html","nums":[1,88,0,0,56,0,0]},"hash":"beb62ca86f41669adde5fabffb6a95e2"}},"version":"4.5.4","settings":"fd659caad95e8103c8432140d3fc963a","format":1} \ No newline at end of file diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 5936be6..4e33bad 100644 Binary files a/docs/_build/doctrees/environment.pickle and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree index a6d7c03..43cbedf 100644 Binary files a/docs/_build/doctrees/index.doctree and b/docs/_build/doctrees/index.doctree differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo index d102e8b..3fc11b1 100644 --- a/docs/_build/html/.buildinfo +++ b/docs/_build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 140e2ffcbe1eafc788c52ca0356131c2 +config: 33aeaf190651af5fbc811e7b45f9191a tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js index 55c023d..e42d212 100644 --- a/docs/_build/html/_static/documentation_options.js +++ b/docs/_build/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '3.2.6', + VERSION: '3.3.0', LANGUAGE: 'None', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html index ec416bd..8af9d44 100644 --- a/docs/_build/html/genindex.html +++ b/docs/_build/html/genindex.html @@ -7,7 +7,7 @@ - Index — python-jwt 3.2.6 documentation + Index — python-jwt 3.3.0 documentation @@ -27,7 +27,7 @@

Navigation

  • modules |
  • - + @@ -105,7 +105,7 @@

    Navigation

  • modules |
  • - + @@ -219,7 +219,7 @@

    Navigation

  • modules |
  • - + @@ -91,7 +91,7 @@

    Navigation

  • modules |
  • - + @@ -86,7 +86,7 @@

    Navigation

  • modules |
  • - +