1+ import binascii
2+ import datetime
13import logging
2- logger = logging .getLogger (__name__ )
3-
4- try :
5- from cjson import encode as json_encode , decode as json_decode
6- except ImportError : # pragma: nocover
7- logger .warn ('cjson not found, falling back to stdlib json' )
8- from json import loads as json_decode , dumps as json_encode
9-
4+ import six
105import zlib
11- import datetime
126
137from base64 import urlsafe_b64encode , urlsafe_b64decode
148from collections import namedtuple
159from copy import deepcopy
16- from time import time
10+ from json import loads as json_decode , dumps as json_encode
1711from struct import pack
12+ from time import time
1813
1914from Crypto .Hash import HMAC , SHA256 , SHA384 , SHA512
2015from Crypto .Cipher import PKCS1_OAEP , AES
2116from Crypto .PublicKey import RSA
2217from Crypto .Random import get_random_bytes
2318from Crypto .Signature import PKCS1_v1_5 as PKCS1_v1_5_SIG
2419
20+ logger = logging .getLogger (__name__ )
21+
2522
2623__all__ = ['encrypt' , 'decrypt' , 'sign' , 'verify' ]
2724
6360class Error (Exception ):
6461 """ The base error type raised by jose
6562 """
66- pass
63+ def __init__ (self , message ):
64+ self .message = message
6765
6866
6967class Expired (Error ):
@@ -85,7 +83,7 @@ def serialize_compact(jwt):
8583 :returns: A string, representing the compact serialization of a
8684 :class:`~jose.JWE` or :class:`~jose.JWS`.
8785 """
88- return '.' .join (jwt )
86+ return six . b ( '.' ) .join (jwt )
8987
9088
9189def deserialize_compact (jwt ):
@@ -95,7 +93,7 @@ def deserialize_compact(jwt):
9593 :rtype: :class:`~jose.JWT`.
9694 :raises: :class:`~jose.Error` if the JWT is malformed
9795 """
98- parts = jwt .split ('.' )
96+ parts = jwt .split (six . b ( '.' ) )
9997
10098 # http://tools.ietf.org/html/
10199 # draft-ietf-jose-json-web-encryption-23#section-9
@@ -109,8 +107,8 @@ def deserialize_compact(jwt):
109107 return token_type (* parts )
110108
111109
112- def encrypt (claims , jwk , adata = '' , add_header = None , alg = 'RSA-OAEP' ,
113- enc = 'A128CBC-HS256' , rng = get_random_bytes , compression = None ):
110+ def encrypt (claims , jwk , adata = six . b ( '' ) , add_header = None , alg = 'RSA-OAEP' ,
111+ enc = 'A128CBC-HS256' , rng = get_random_bytes , compression = None ):
114112 """ Encrypts the given claims and produces a :class:`~jose.JWE`
115113
116114 :param claims: A `dict` representing the claims for this
@@ -139,14 +137,15 @@ def encrypt(claims, jwk, adata='', add_header=None, alg='RSA-OAEP',
139137 assert _TEMP_VER_KEY not in claims
140138 claims [_TEMP_VER_KEY ] = _TEMP_VER
141139
142- header = dict ((add_header or {}).items () + [
143- ('enc' , enc ), ('alg' , alg )])
140+ header = dict (
141+ list ((add_header or {}).items ()) + [('enc' , enc ), ('alg' , alg )]
142+ )
144143
145144 # promote the temp key to the header
146145 assert _TEMP_VER_KEY not in header
147146 header [_TEMP_VER_KEY ] = claims [_TEMP_VER_KEY ]
148147
149- plaintext = json_encode (claims )
148+ plaintext = six . b ( json_encode (claims ) )
150149
151150 # compress (if required)
152151 if compression is not None :
@@ -162,24 +161,29 @@ def encrypt(claims, jwk, adata='', add_header=None, alg='RSA-OAEP',
162161 ((cipher , _ ), key_size ), ((hash_fn , _ ), hash_mod ) = JWA [enc ]
163162 iv = rng (AES .block_size )
164163 encryption_key = rng (hash_mod .digest_size )
164+ encryption_key_index = hash_mod .digest_size // 2
165165
166- ciphertext = cipher (plaintext , encryption_key [- hash_mod .digest_size / 2 :], iv )
167- hash = hash_fn (_jwe_hash_str (ciphertext , iv , adata ),
168- encryption_key [:- hash_mod .digest_size / 2 ], hash_mod )
166+ ciphertext = cipher (
167+ plaintext , encryption_key [- encryption_key_index :], iv
168+ )
169+ hash = hash_fn (
170+ _jwe_hash_str (ciphertext , iv , adata ),
171+ encryption_key [:- encryption_key_index ], hash_mod
172+ )
169173
170174 # cek encryption
171175 (cipher , _ ), _ = JWA [alg ]
172176 encryption_key_ciphertext = cipher (encryption_key , jwk )
173177
174- return JWE (* map (b64encode_url ,
175- (json_encode (header ),
176- encryption_key_ciphertext ,
177- iv ,
178- ciphertext ,
179- auth_tag (hash ))))
178+ jwe_components = (
179+ json_encode (header ), encryption_key_ciphertext , iv , ciphertext ,
180+ auth_tag (hash )
181+ )
182+ return JWE (* map (b64encode_url , jwe_components ))
180183
181184
182- def decrypt (jwe , jwk , adata = '' , validate_claims = True , expiry_seconds = None ):
185+ def decrypt (jwe , jwk , adata = six .b ('' ), validate_claims = True ,
186+ expiry_seconds = None ):
183187 """ Decrypts a deserialized :class:`~jose.JWE`
184188
185189 :param jwe: An instance of :class:`~jose.JWE`
@@ -199,8 +203,9 @@ def decrypt(jwe, jwk, adata='', validate_claims=True, expiry_seconds=None):
199203 :raises: :class:`~jose.Error` if there is an error decrypting the JWE
200204 """
201205 header , encryption_key_ciphertext , iv , ciphertext , tag = map (
202- b64decode_url , jwe )
203- header = json_decode (header )
206+ b64decode_url , jwe
207+ )
208+ header = json_decode (header .decode ())
204209
205210 # decrypt cek
206211 (_ , decipher ), _ = JWA [header ['alg' ]]
@@ -211,9 +216,13 @@ def decrypt(jwe, jwk, adata='', validate_claims=True, expiry_seconds=None):
211216
212217 version = header .get (_TEMP_VER_KEY )
213218 if version :
214- plaintext = decipher (ciphertext , encryption_key [- mod .digest_size / 2 :], iv )
215- hash = hash_fn (_jwe_hash_str (ciphertext , iv , adata , version ),
216- encryption_key [:- mod .digest_size / 2 ], mod = mod )
219+ plaintext = decipher (
220+ ciphertext , encryption_key [- mod .digest_size // 2 :], iv
221+ )
222+ hash = hash_fn (
223+ _jwe_hash_str (ciphertext , iv , adata , version ),
224+ encryption_key [:- mod .digest_size // 2 ], mod = mod
225+ )
217226 else :
218227 plaintext = decipher (ciphertext , encryption_key [:- mod .digest_size ], iv )
219228 hash = hash_fn (_jwe_hash_str (ciphertext , iv , adata , version ),
@@ -231,7 +240,7 @@ def decrypt(jwe, jwk, adata='', validate_claims=True, expiry_seconds=None):
231240
232241 plaintext = decompress (plaintext )
233242
234- claims = json_decode (plaintext )
243+ claims = json_decode (plaintext . decode () )
235244 try :
236245 del claims [_TEMP_VER_KEY ]
237246 except KeyError :
@@ -257,11 +266,12 @@ def sign(claims, jwk, add_header=None, alg='HS256'):
257266 """
258267 (hash_fn , _ ), mod = JWA [alg ]
259268
260- header = dict (( add_header or {}).items () + [('alg' , alg )])
269+ header = dict (list (( add_header or {}).items () ) + [('alg' , alg )])
261270 header , payload = map (b64encode_url , map (json_encode , (header , claims )))
262271
263- sig = b64encode_url (hash_fn (_jws_hash_str (header , payload ), jwk ['k' ],
264- mod = mod ))
272+ sig = b64encode_url (
273+ hash_fn (_jws_hash_str (header , payload ), jwk ['k' ], mod = mod )
274+ )
265275
266276 return JWS (header , payload , sig )
267277
@@ -285,17 +295,18 @@ def verify(jws, jwk, alg, validate_claims=True, expiry_seconds=None):
285295 :raises: :class:`~jose.Error` if there is an error decrypting the JWE
286296 """
287297 header , payload , sig = map (b64decode_url , jws )
288- header = json_decode (header )
298+ header = json_decode (header . decode () )
289299 if alg != header ['alg' ]:
290300 raise Error ('Invalid algorithm' )
291301
292302 (_ , verify_fn ), mod = JWA [header ['alg' ]]
293303
294- if not verify_fn (_jws_hash_str (jws .header , jws .payload ),
295- jwk ['k' ], sig , mod = mod ):
304+ if not verify_fn (
305+ _jws_hash_str (jws .header , jws .payload ), jwk ['k' ], sig , mod = mod
306+ ):
296307 raise Error ('Mismatched signatures' )
297308
298- claims = json_decode (b64decode_url (jws .payload ))
309+ claims = json_decode (b64decode_url (jws .payload ). decode () )
299310 _validate (claims , validate_claims , expiry_seconds )
300311
301312 return JWT (header , claims )
@@ -305,27 +316,32 @@ def b64decode_url(istr):
305316 """ JWT Tokens may be truncated without the usual trailing padding '='
306317 symbols. Compensate by padding to the nearest 4 bytes.
307318 """
308- istr = encode_safe (istr )
309319 try :
310- return urlsafe_b64decode (istr + '=' * (4 - (len (istr ) % 4 )))
311- except TypeError as e :
320+ return urlsafe_b64decode (istr + six . b ( '=' ) * (4 - (len (istr ) % 4 )))
321+ except ( TypeError , binascii . Error ) as e :
312322 raise Error ('Unable to decode base64: %s' % (e ))
313323
314324
315325def b64encode_url (istr ):
316326 """ JWT Tokens may be truncated without the usual trailing padding '='
317327 symbols. Compensate by padding to the nearest 4 bytes.
318328 """
319- return urlsafe_b64encode (encode_safe (istr )).rstrip ('=' )
329+ return urlsafe_b64encode (encode_safe (istr )).rstrip (six . b ( '=' ) )
320330
321331
322- def encode_safe (istr , encoding = 'utf8' ):
323- try :
324- return istr .encode (encoding )
325- except UnicodeDecodeError :
326- # this will fail if istr is already encoded
327- pass
328- return istr
332+ if six .PY3 :
333+ def encode_safe (istr , encoding = 'utf8' ):
334+ if not isinstance (istr , bytes ):
335+ return bytes (istr , encoding = encoding )
336+ return istr
337+ else :
338+ def encode_safe (istr , encoding = 'utf8' ):
339+ try :
340+ return istr .encode (encoding )
341+ except UnicodeDecodeError :
342+ # this will fail if istr is already encoded
343+ pass
344+ return istr
329345
330346
331347def auth_tag (hmac ):
@@ -336,11 +352,15 @@ def auth_tag(hmac):
336352
337353def pad_pkcs7 (s ):
338354 sz = AES .block_size - (len (s ) % AES .block_size )
339- return s + (chr (sz ) * sz )
355+ return s + (six . int2byte (sz ) * sz )
340356
341357
342- def unpad_pkcs7 (s ):
343- return s [:- ord (s [- 1 ])]
358+ if six .PY3 :
359+ def unpad_pkcs7 (s ):
360+ return s [:- s [- 1 ]]
361+ else :
362+ def unpad_pkcs7 (s ):
363+ return s [:- ord (s [- 1 ])]
344364
345365
346366def encrypt_oaep (plaintext , jwk ):
@@ -391,14 +411,24 @@ def decrypt_aescbc(ciphertext, key, iv):
391411 return unpad_pkcs7 (AES .new (key , AES .MODE_CBC , iv ).decrypt (ciphertext ))
392412
393413
394- def const_compare (stra , strb ):
395- if len (stra ) != len (strb ):
396- return False
414+ if six .PY3 :
415+ def const_compare (stra , strb ):
416+ if len (stra ) != len (strb ):
417+ return False
418+
419+ res = 0
420+ for a , b in zip (stra , strb ):
421+ res |= a ^ b
422+ return res == 0
423+ else :
424+ def const_compare (stra , strb ):
425+ if len (stra ) != len (strb ):
426+ return False
397427
398- res = 0
399- for a , b in zip (stra , strb ):
400- res |= ord (a ) ^ ord (b )
401- return res == 0
428+ res = 0
429+ for a , b in zip (stra , strb ):
430+ res |= ord (a ) ^ ord (b )
431+ return res == 0
402432
403433
404434class _JWA (object ):
@@ -525,34 +555,36 @@ def _validate(claims, validate_claims, expiry_seconds):
525555 _check_not_before (now , not_before )
526556
527557
528- def _jwe_hash_str (ciphertext , iv , adata = '' , version = _TEMP_VER ):
558+ def _jwe_hash_str (ciphertext , iv , adata = six . b ( '' ) , version = _TEMP_VER ):
529559 # http://tools.ietf.org/html/
530560 # draft-ietf-jose-json-web-algorithms-24#section-5.2.2.1
531561 # Both tokens without version and with version 1 should be ignored in
532562 # the future as they use incorrect hashing. The version parameter
533563 # should also be removed.
534564 if not version :
535- return '.' .join ((adata , iv , ciphertext , str (len (adata ))))
565+ return six .b ('.' ).join (
566+ (adata , iv , ciphertext , six .b (str (len (adata ))))
567+ )
536568 elif version == 1 :
537- return '.' .join ((adata , iv , ciphertext , pack ("!Q" , len (adata ) * 8 )))
538- return '' .join ((adata , iv , ciphertext , pack ("!Q" , len (adata ) * 8 )))
569+ return six .b ('.' ).join (
570+ (adata , iv , ciphertext , pack ("!Q" , len (adata ) * 8 ))
571+ )
572+ return six .b ('' ).join (
573+ (adata , iv , ciphertext , pack ("!Q" , len (adata ) * 8 ))
574+ )
539575
540576
541577def _jws_hash_str (header , claims ):
542- return '.' .join ((header , claims ))
578+ return six . b ( '.' ) .join ((header , claims ))
543579
544580
545581def cli_decrypt (jwt , key ):
546- print decrypt (deserialize_compact (jwt ), {'k' :key },
547- validate_claims = False )
582+ print (decrypt (deserialize_compact (jwt ), {'k' : key }, validate_claims = False ))
548583
549584
550585def _cli ():
551586 import inspect
552- import sys
553-
554587 from argparse import ArgumentParser
555- from copy import copy
556588
557589 parser = ArgumentParser ()
558590 subparsers = parser .add_subparsers (dest = 'subparser_name' )
0 commit comments