From 59b8b5ad5478b7721af959d9cc4bb2d2a3dfbc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Tue, 4 Aug 2015 09:55:38 +0200 Subject: [PATCH 1/7] Made some changes to get python 2 and 3 compatible (String handling). --- src/jwkest/__init__.py | 12 ++++++++++++ src/jwkest/aes_key_wrap.py | 3 ++- src/jwkest/jwe.py | 8 ++------ src/jwkest/jwk.py | 8 ++++---- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/jwkest/__init__.py b/src/jwkest/__init__.py index ae1e1ce..3f85d3c 100644 --- a/src/jwkest/__init__.py +++ b/src/jwkest/__init__.py @@ -193,3 +193,15 @@ def constant_time_compare(a, b): for c, d in zip(a, b): r |= c ^ d return r == 0 + +def as_bytes(s): + """ + Convert an unicode string to bytes. + :param s: Unicode / bytes string + :return: bytes string + """ + try: + s = s.encode() + except AttributeError: + pass + return s diff --git a/src/jwkest/aes_key_wrap.py b/src/jwkest/aes_key_wrap.py index 07039fa..037f0fb 100644 --- a/src/jwkest/aes_key_wrap.py +++ b/src/jwkest/aes_key_wrap.py @@ -16,6 +16,7 @@ PyCrypto's AES. """ from __future__ import division + try: from builtins import hex from builtins import range @@ -39,7 +40,7 @@ def aes_unwrap_key_and_iv(kek, wrapped): B = decrypt(ciphertext) a = QUAD.unpack(B[:8])[0] r[i] = B[8:] - return "".join(r[1:]), a + return b"".join(r[1:]), a def aes_unwrap_key(kek, wrapped, iv=0xa6a6a6a6a6a6a6a6): diff --git a/src/jwkest/jwe.py b/src/jwkest/jwe.py index 2240340..13ea75f 100644 --- a/src/jwkest/jwe.py +++ b/src/jwkest/jwe.py @@ -404,18 +404,14 @@ def encrypt(self, key, iv="", cek="", **kwargs): # If no iv and cek are given generate them cek, iv = self._generate_key_and_iv(self["enc"], cek, iv) - if isinstance(key, six.string_types): - kek = key - else: - kek = intarr2str(key) # The iv for this function must be 64 bit # Which is certainly different from the one above - jek = aes_wrap_key(kek, cek) + jek = aes_wrap_key(key, cek) _enc = self["enc"] - ctxt, tag, cek = self.enc_setup(_enc, _msg, jwe.b64_encode_header(), + ctxt, tag, cek = self.enc_setup(_enc, _msg.encode(), jwe.b64_encode_header(), cek, iv=iv) return jwe.pack(parts=[jek, iv, ctxt, tag]) diff --git a/src/jwkest/jwk.py b/src/jwkest/jwk.py index 815fe0d..5f6751c 100644 --- a/src/jwkest/jwk.py +++ b/src/jwkest/jwk.py @@ -15,7 +15,7 @@ from requests import request -from jwkest import base64url_to_long +from jwkest import base64url_to_long, as_bytes from jwkest import base64_to_long from jwkest import long_to_base64 from jwkest import JWKESTException @@ -66,15 +66,15 @@ def intarr2str(arr): def sha256_digest(msg): - return hashlib.sha256(msg).digest() + return hashlib.sha256(as_bytes(msg)).digest() def sha384_digest(msg): - return hashlib.sha384(msg).digest() + return hashlib.sha384(as_bytes(msg)).digest() def sha512_digest(msg): - return hashlib.sha512(msg).digest() + return hashlib.sha512(as_bytes(msg)).digest() # ============================================================================= From b05648f375bd5b4be661edfa8fda2abbea54e57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Thu, 6 Aug 2015 10:46:26 +0200 Subject: [PATCH 2/7] Added previously removed function call, but with a new if condition. --- src/jwkest/jwe.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jwkest/jwe.py b/src/jwkest/jwe.py index 13ea75f..2acfe12 100644 --- a/src/jwkest/jwe.py +++ b/src/jwkest/jwe.py @@ -404,10 +404,14 @@ def encrypt(self, key, iv="", cek="", **kwargs): # If no iv and cek are given generate them cek, iv = self._generate_key_and_iv(self["enc"], cek, iv) + if isinstance(key, six.binary_type): + kek = key + else: + kek = intarr2str(key) # The iv for this function must be 64 bit # Which is certainly different from the one above - jek = aes_wrap_key(key, cek) + jek = aes_wrap_key(kek, cek) _enc = self["enc"] From 1840a1ab1c624d75c9b97b056e4a98bfe41ac0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Mon, 10 Aug 2015 09:37:07 +0200 Subject: [PATCH 3/7] Some more string conversions --- src/jwkest/__init__.py | 14 ++++++++++++++ src/jwkest/jwe.py | 6 +++--- src/jwkest/jwk.py | 2 +- src/jwkest/jws.py | 8 ++++---- src/jwkest/jwt.py | 8 ++++---- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/jwkest/__init__.py b/src/jwkest/__init__.py index 3f85d3c..7da571b 100644 --- a/src/jwkest/__init__.py +++ b/src/jwkest/__init__.py @@ -194,6 +194,7 @@ def constant_time_compare(a, b): r |= c ^ d return r == 0 + def as_bytes(s): """ Convert an unicode string to bytes. @@ -205,3 +206,16 @@ def as_bytes(s): except AttributeError: pass return s + + +def as_unicode(b): + """ + Convert a byte string to a unicode string + :param b: byte string + :return: unicode string + """ + try: + b = b.decode() + except AttributeError: + pass + return b diff --git a/src/jwkest/jwe.py b/src/jwkest/jwe.py index 2acfe12..867580d 100644 --- a/src/jwkest/jwe.py +++ b/src/jwkest/jwe.py @@ -18,7 +18,7 @@ from Crypto.Cipher import PKCS1_v1_5 from Crypto.Cipher import PKCS1_OAEP -from jwkest import b64d +from jwkest import b64d, as_bytes from jwkest import b64e from jwkest import JWKESTException from jwkest import MissingKey @@ -456,7 +456,7 @@ def encrypt(self, key, iv="", cek="", **kwargs): :return: A jwe """ - _msg = self.msg + _msg = as_bytes(self.msg) if "zip" in self: if self["zip"] == "DEF": _msg = zlib.compress(_msg) @@ -681,7 +681,7 @@ def decrypt(self, token, keys=None, alg=None): for key in keys: _key = key.encryption_key(alg=_alg, private=False) try: - msg = decrypter.decrypt(bytes(token), _key) + msg = decrypter.decrypt(as_bytes(token), _key) except (KeyError, DecryptionFailed): pass else: diff --git a/src/jwkest/jwk.py b/src/jwkest/jwk.py index 5f6751c..54caee4 100644 --- a/src/jwkest/jwk.py +++ b/src/jwkest/jwk.py @@ -517,7 +517,7 @@ class SYMKey(Key): def __init__(self, kty="oct", alg="", use="", kid="", key=None, x5c=None, x5t="", x5u="", k="", mtrl=""): - Key.__init__(self, kty, alg, use, kid, key, x5c, x5t, x5u) + Key.__init__(self, kty, alg, use, kid, as_bytes(key), x5c, x5t, x5u) self.k = k if not self.key and self.k: if isinstance(self.k, str): diff --git a/src/jwkest/jws.py b/src/jwkest/jws.py index 86e4483..2baefbd 100644 --- a/src/jwkest/jws.py +++ b/src/jwkest/jws.py @@ -21,7 +21,7 @@ from Crypto.Util.number import bytes_to_long import sys -from jwkest import b64d +from jwkest import b64d, as_unicode from jwkest import b64e from jwkest import constant_time_compare from jwkest import safe_str_cmp @@ -72,11 +72,11 @@ class SignerAlgError(JWSException): def left_hash(msg, func="HS256"): """ 128 bits == 16 bytes """ if func == 'HS256': - return b64e(sha256_digest(msg)[:16]) + return as_unicode(b64e(sha256_digest(msg)[:16])) elif func == 'HS384': - return b64e(sha384_digest(msg)[:24]) + return as_unicode(b64e(sha384_digest(msg)[:24])) elif func == 'HS512': - return b64e(sha512_digest(msg)[:32]) + return as_unicode(b64e(sha512_digest(msg)[:32])) def mpint(b): diff --git a/src/jwkest/jwt.py b/src/jwkest/jwt.py index 50e7c72..6b8944f 100644 --- a/src/jwkest/jwt.py +++ b/src/jwkest/jwt.py @@ -1,6 +1,6 @@ import json import six -from jwkest import b64d +from jwkest import b64d, as_unicode from jwkest import b64e from jwkest import BadSyntax @@ -56,7 +56,7 @@ def unpack(self, token): part = split_token(token) self.b64part = part self.part = [b64d(p) for p in part] - self.headers = json.loads(self.part[0].decode("utf-8")) + self.headers = json.loads(self.part[0].decode()) return self def pack(self, parts, headers=None): @@ -74,10 +74,10 @@ def pack(self, parts, headers=None): _all = [b64encode_item(headers)] _all.extend([b64encode_item(p) for p in parts]) - return b".".join(_all) + return ".".join([a.decode() for a in _all]) def payload(self): - _msg = self.part[1].decode("utf-8") + _msg = as_unicode(self.part[1]) # If not JSON web token assume JSON if "cty" in self.headers and self.headers["cty"].lower() != "jwt": From a90e9e2a07ed947aa0d916989b892ebc95e204b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Mon, 10 Aug 2015 09:37:56 +0200 Subject: [PATCH 4/7] Some string conversions --- tests/test_1_jwt.py | 2 +- tests/test_3_jws.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_1_jwt.py b/tests/test_1_jwt.py index 8b1cb95..4778b33 100644 --- a/tests/test_1_jwt.py +++ b/tests/test_1_jwt.py @@ -13,7 +13,7 @@ def test_pack_jwt(): jwt = _jwt.pack(parts=[{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": True}, ""]) - p = jwt.split(b'.') + p = jwt.split('.') assert len(p) == 3 diff --git a/tests/test_3_jws.py b/tests/test_3_jws.py index 22dae9f..1003c18 100644 --- a/tests/test_3_jws.py +++ b/tests/test_3_jws.py @@ -286,7 +286,7 @@ def test_signer_ps256_fail(): keys = [RSAKey(key=import_rsa_key_from_file(KEY))] #keys[0]._keytype = "private" _jws = JWS(payload, alg="PS256") - _jwt = _jws.sign_compact(keys)[:-5] + b'abcde' + _jwt = _jws.sign_compact(keys)[:-5] + 'abcde' _rj = JWS() try: From 1e2af19cf6d2819347b1ecc552ad5e29df794e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Mon, 10 Aug 2015 12:39:43 +0200 Subject: [PATCH 5/7] Added UnicodeDecodeError catch to as_bytes and as_unicode --- src/jwkest/__init__.py | 4 ++-- tests/test_3_jws.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jwkest/__init__.py b/src/jwkest/__init__.py index c496354..465f256 100644 --- a/src/jwkest/__init__.py +++ b/src/jwkest/__init__.py @@ -207,7 +207,7 @@ def as_bytes(s): """ try: s = s.encode() - except AttributeError: + except (AttributeError, UnicodeDecodeError): pass return s @@ -220,6 +220,6 @@ def as_unicode(b): """ try: b = b.decode() - except AttributeError: + except (AttributeError, UnicodeDecodeError): pass return b diff --git a/tests/test_3_jws.py b/tests/test_3_jws.py index f03e5b7..a1699a6 100644 --- a/tests/test_3_jws.py +++ b/tests/test_3_jws.py @@ -133,13 +133,13 @@ def test_hmac_from_keyrep(): def test_left_hash_hs256(): - hsh = jws.left_hash(b'Please take a moment to register today') - assert hsh == b'rCFHVJuxTqRxOsn2IUzgvA' + hsh = jws.left_hash('Please take a moment to register today') + assert hsh == 'rCFHVJuxTqRxOsn2IUzgvA' def test_left_hash_hs512(): - hsh = jws.left_hash(b'Please take a moment to register today', "HS512") - assert hsh == b'_h6feWLt8zbYcOFnaBmekTzMJYEHdVTaXlDgJSWsEeY' + hsh = jws.left_hash('Please take a moment to register today', "HS512") + assert hsh == '_h6feWLt8zbYcOFnaBmekTzMJYEHdVTaXlDgJSWsEeY' def test_rs256(): From db9f98f04eb073bc453886663cc577e242e9eb7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Mon, 10 Aug 2015 14:02:23 +0200 Subject: [PATCH 6/7] String conversions --- src/jwkest/jws.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/jwkest/jws.py b/src/jwkest/jws.py index 770cbfb..72a06d8 100644 --- a/src/jwkest/jws.py +++ b/src/jwkest/jws.py @@ -249,10 +249,7 @@ class JWx(object): """ def __init__(self, msg=None, with_digest=False, **kwargs): - if six.PY3 and isinstance(msg, six.string_types): - self.msg = msg.encode("utf-8") - else: - self.msg = msg + self.msg = msg self._dict = {} self.with_digest = with_digest @@ -480,9 +477,9 @@ def sign_compact(self, keys=None, protected=None): raise UnknownAlgorithm(_alg) _input = jwt.pack(parts=[self.msg]) - sig = _signer.sign(_input, key.get_key(alg=_alg, private=True)) + sig = _signer.sign(_input.encode("utf-8"), key.get_key(alg=_alg, private=True)) logger.debug("Signed message using key with kid=%s" % key.kid) - return b".".join([_input, b64encode_item(sig)]) + return ".".join([_input, b64encode_item(sig).decode("utf-8")]) def verify_compact(self, jws, keys=None, allow_none=False, sigalg=None): """ From 23722a5aa5f62a9982d472a8bc2bbb207458b370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Hedstr=C3=B6m?= Date: Thu, 13 Aug 2015 16:53:47 +0200 Subject: [PATCH 7/7] Fixed some string conversions --- tests/test_1_jwt.py | 2 -- tests/test_3_jws.py | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_1_jwt.py b/tests/test_1_jwt.py index 51a8ada..0c8785d 100644 --- a/tests/test_1_jwt.py +++ b/tests/test_1_jwt.py @@ -40,8 +40,6 @@ def test_unpack_str(): "http://example.com/is_root": True} jwt = _jwt.pack(parts=[payload, ""]) - jwt = jwt.decode('utf-8') - _jwt2 = JWT().unpack(jwt) assert _jwt2 out_payload = _jwt2.payload() diff --git a/tests/test_3_jws.py b/tests/test_3_jws.py index 7d5d580..cf8f0de 100644 --- a/tests/test_3_jws.py +++ b/tests/test_3_jws.py @@ -368,9 +368,9 @@ def test_signer_protected_headers(): exp_protected = protected.copy() exp_protected['alg'] = 'ES256' - enc_header, enc_payload, sig = _jwt.split(b'.') - assert json.loads(b64d(enc_header).decode("utf-8")) == exp_protected - assert b64d(enc_payload).decode("utf-8") == payload + enc_header, enc_payload, sig = _jwt.split('.') + assert json.loads(b64d(enc_header.encode("utf-8")).decode("utf-8")) == exp_protected + assert b64d(enc_payload.encode("utf-8")).decode("utf-8") == payload _rj = JWS() info = _rj.verify_compact(_jwt, keys) @@ -384,7 +384,7 @@ def test_verify_protected_headers(): protected = dict(header1=u"header1 is protected", header2="header2 is protected too", a=1) _jwt = _jws.sign_compact(keys, protected=protected) - protectedHeader, enc_payload, sig = _jwt.split(b".") + protectedHeader, enc_payload, sig = _jwt.split(".") data = dict(payload=enc_payload, signatures=[ dict( header=dict(alg=u"ES256", jwk=_key.serialize()),