Hello community,
here is the log from the commit of package python-jwcrypto for openSUSE:Factory
checked in at 2019-03-24 15:01:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jwcrypto (Old)
and /work/SRC/openSUSE:Factory/.python-jwcrypto.new.25356 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jwcrypto"
Sun Mar 24 15:01:45 2019 rev:7 rq:687924 version:0.6.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-jwcrypto/python-jwcrypto.changes
2018-12-24 11:38:52.233586353 +0100
+++
/work/SRC/openSUSE:Factory/.python-jwcrypto.new.25356/python-jwcrypto.changes
2019-03-24 15:01:45.947165724 +0100
@@ -1,0 +2,10 @@
+Fri Mar 22 18:45:19 UTC 2019 - Michael Ströder <[email protected]>
+
+- update to upstream release 0.6.0
+ * Use python-cryptography's AES key wrapping
+ * Add tests for key wrapping where CEK < KEK
+ * Fix ECDH-ES key exchange for CEK greater than KEK
+ * Add support for RFC7797
+ * Fix JWK.from_json
+
+-------------------------------------------------------------------
Old:
----
jwcrypto-0.5.0.tar.gz
New:
----
jwcrypto-0.6.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jwcrypto.spec ++++++
--- /var/tmp/diff_new_pack.JqxOd9/_old 2019-03-24 15:01:46.443165660 +0100
+++ /var/tmp/diff_new_pack.JqxOd9/_new 2019-03-24 15:01:46.447165660 +0100
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-jwcrypto
-Version: 0.5.0
+Version: 0.6.0
Release: 0
Summary: Python module package implementing JOSE Web standards
License: LGPL-3.0-only
++++++ jwcrypto-0.5.0.tar.gz -> jwcrypto-0.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/PKG-INFO new/jwcrypto-0.6.0/PKG-INFO
--- old/jwcrypto-0.5.0/PKG-INFO 2018-06-27 12:27:24.000000000 +0200
+++ new/jwcrypto-0.6.0/PKG-INFO 2018-11-05 16:18:50.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: jwcrypto
-Version: 0.5.0
+Version: 0.6.0
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/jwa.py
new/jwcrypto-0.6.0/jwcrypto/jwa.py
--- old/jwcrypto-0.5.0/jwcrypto/jwa.py 2018-06-27 08:24:22.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto/jwa.py 2018-11-05 16:04:15.000000000 +0100
@@ -14,6 +14,7 @@
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from cryptography.hazmat.primitives.keywrap import aes_key_unwrap, aes_key_wrap
from cryptography.hazmat.primitives.padding import PKCS7
import six
@@ -439,49 +440,14 @@
if not cek:
cek = _randombits(bitsize)
- # Implement RFC 3394 Key Unwrap - 2.2.2
- # TODO: Use cryptography once issue #1733 is resolved
- iv = 'a6a6a6a6a6a6a6a6'
- a = unhexlify(iv)
- r = [cek[i:i + 8] for i in range(0, len(cek), 8)]
- n = len(r)
- for j in range(0, 6):
- for i in range(0, n):
- e = Cipher(algorithms.AES(rk), modes.ECB(),
- backend=self.backend).encryptor()
- b = e.update(a + r[i]) + e.finalize()
- a = _encode_int(_decode_int(b[:8]) ^ ((n * j) + i + 1), 64)
- r[i] = b[-8:]
- ek = a
- for i in range(0, n):
- ek += r[i]
+ ek = aes_key_wrap(rk, cek, default_backend())
+
return {'cek': cek, 'ek': ek}
def unwrap(self, key, bitsize, ek, headers):
rk = self._get_key(key, 'decrypt')
- # Implement RFC 3394 Key Unwrap - 2.2.3
- # TODO: Use cryptography once issue #1733 is resolved
- iv = 'a6a6a6a6a6a6a6a6'
- aiv = unhexlify(iv)
-
- r = [ek[i:i + 8] for i in range(0, len(ek), 8)]
- a = r.pop(0)
- n = len(r)
- for j in range(5, -1, -1):
- for i in range(n - 1, -1, -1):
- da = _decode_int(a)
- atr = _encode_int((da ^ ((n * j) + i + 1)), 64) + r[i]
- d = Cipher(algorithms.AES(rk), modes.ECB(),
- backend=self.backend).decryptor()
- b = d.update(atr) + d.finalize()
- a = b[:8]
- r[i] = b[-8:]
-
- if a != aiv:
- raise RuntimeError('Decryption Failed')
-
- cek = b''.join(r)
+ cek = aes_key_unwrap(rk, ek, default_backend())
if _bitsize(cek) != bitsize:
raise InvalidJWEKeyLength(bitsize, _bitsize(cek))
return cek
@@ -761,23 +727,24 @@
def wrap(self, key, bitsize, cek, headers):
self._check_key(key)
+ dk_size = self.keysize
if self.keysize is None:
if cek is not None:
raise InvalidJWEOperation('ECDH-ES cannot use an existing CEK')
alg = headers['enc']
+ dk_size = bitsize
else:
- bitsize = self.keysize
alg = headers['alg']
epk = JWK.generate(kty=key.key_type, crv=key.key_curve)
dk = self._derive(epk.get_op_key('unwrapKey'),
key.get_op_key('wrapKey'),
- alg, bitsize, headers)
+ alg, dk_size, headers)
if self.keysize is None:
ret = {'cek': dk}
else:
- aeskw = self.aeskwmap[bitsize]()
+ aeskw = self.aeskwmap[self.keysize]()
kek = JWK(kty="oct", use="enc", k=base64url_encode(dk))
ret = aeskw.wrap(kek, bitsize, cek, headers)
@@ -788,20 +755,21 @@
if 'epk' not in headers:
raise ValueError('Invalid Header, missing "epk" parameter')
self._check_key(key)
+ dk_size = self.keysize
if self.keysize is None:
alg = headers['enc']
+ dk_size = bitsize
else:
- bitsize = self.keysize
alg = headers['alg']
epk = JWK(**headers['epk'])
dk = self._derive(key.get_op_key('unwrapKey'),
epk.get_op_key('wrapKey'),
- alg, bitsize, headers)
+ alg, dk_size, headers)
if self.keysize is None:
return dk
else:
- aeskw = self.aeskwmap[bitsize]()
+ aeskw = self.aeskwmap[self.keysize]()
kek = JWK(kty="oct", use="enc", k=base64url_encode(dk))
cek = aeskw.unwrap(kek, bitsize, ek, headers)
return cek
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/jwe.py
new/jwcrypto-0.6.0/jwcrypto/jwe.py
--- old/jwcrypto-0.5.0/jwcrypto/jwe.py 2018-06-27 08:24:22.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto/jwe.py 2018-11-05 16:04:15.000000000 +0100
@@ -109,7 +109,7 @@
json_decode(unprotected) # check header encoding
self.objects['unprotected'] = unprotected
if algs:
- self.allowed_algs = algs
+ self._allowed_algs = algs
if recipient:
self.add_recipient(recipient, header=header)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/jwk.py
new/jwcrypto-0.6.0/jwcrypto/jwk.py
--- old/jwcrypto-0.5.0/jwcrypto/jwk.py 2018-06-27 12:24:24.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto/jwk.py 2018-11-05 16:04:15.000000000 +0100
@@ -441,7 +441,8 @@
jkey = json_decode(key)
except Exception as e: # pylint: disable=broad-except
raise InvalidJWKValue(e)
- return obj.import_key(**jkey)
+ obj.import_key(**jkey)
+ return obj
def export(self, private_key=True):
"""Exports the key in the standard JSON format.
@@ -854,8 +855,6 @@
else:
self[k] = v
- return self
-
@classmethod
def from_json(cls, keyset):
"""Creates a RFC 7517 keyset from the standard JSON format.
@@ -863,7 +862,8 @@
:param keyset: The RFC 7517 representation of a JOSE Keyset.
"""
obj = cls()
- return obj.import_keyset(keyset)
+ obj.import_keyset(keyset)
+ return obj
def get_key(self, kid):
"""Gets a key from the set.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/jws.py
new/jwcrypto-0.6.0/jwcrypto/jws.py
--- old/jwcrypto-0.5.0/jwcrypto/jws.py 2018-06-27 08:24:22.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto/jws.py 2018-11-05 16:04:15.000000000 +0100
@@ -1,5 +1,7 @@
# Copyright (C) 2015 JWCrypto Project Contributors - see LICENSE file
+from collections import namedtuple
+
from jwcrypto.common import JWException
from jwcrypto.common import base64url_decode, base64url_encode
from jwcrypto.common import json_decode, json_encode
@@ -9,18 +11,24 @@
# RFC 7515 - 9.1
# name: (description, supported?)
-JWSHeaderRegistry = {'alg': ('Algorithm', True),
- 'jku': ('JWK Set URL', False),
- 'jwk': ('JSON Web Key', False),
- 'kid': ('Key ID', True),
- 'x5u': ('X.509 URL', False),
- 'x5c': ('X.509 Certificate Chain', False),
- 'x5t': ('X.509 Certificate SHA-1 Thumbprint', False),
- 'x5t#S256': ('X.509 Certificate SHA-256 Thumbprint',
- False),
- 'typ': ('Type', True),
- 'cty': ('Content Type', True),
- 'crit': ('Critical', True)}
+JWSHeaderParameter = namedtuple('Parameter',
+ 'description mustprotect supported')
+JWSHeaderRegistry = {
+ 'alg': JWSHeaderParameter('Algorithm', False, True),
+ 'jku': JWSHeaderParameter('JWK Set URL', False, False),
+ 'jwk': JWSHeaderParameter('JSON Web Key', False, False),
+ 'kid': JWSHeaderParameter('Key ID', False, True),
+ 'x5u': JWSHeaderParameter('X.509 URL', False, False),
+ 'x5c': JWSHeaderParameter('X.509 Certificate Chain', False, False),
+ 'x5t': JWSHeaderParameter(
+ 'X.509 Certificate SHA-1 Thumbprint', False, False),
+ 'x5t#S256': JWSHeaderParameter(
+ 'X.509 Certificate SHA-256 Thumbprint', False, False),
+ 'typ': JWSHeaderParameter('Type', False, True),
+ 'cty': JWSHeaderParameter('Content Type', False, True),
+ 'crit': JWSHeaderParameter('Critical', True, True),
+ 'b64': JWSHeaderParameter('Base64url-Encode Payload', True, True)
+}
"""Registry of valid header parameters"""
default_allowed_algs = [
@@ -114,11 +122,16 @@
if header is not None:
if isinstance(header, dict):
+ self.header = header
header = json_encode(header)
+ else:
+ self.header = json_decode(header)
+
self.protected = base64url_encode(header.encode('utf-8'))
else:
+ self.header = dict()
self.protected = ''
- self.payload = base64url_encode(payload)
+ self.payload = payload
def _jwa(self, name, allowed):
if allowed is None:
@@ -127,12 +140,22 @@
raise InvalidJWSOperation('Algorithm not allowed')
return JWA.signing_alg(name)
+ def _payload(self):
+ if self.header.get('b64', True):
+ return base64url_encode(self.payload).encode('utf-8')
+ else:
+ if isinstance(self.payload, bytes):
+ return self.payload
+ else:
+ return self.payload.encode('utf-8')
+
def sign(self):
"""Generates a signature"""
- sigin = ('.'.join([self.protected, self.payload])).encode('utf-8')
+ payload = self._payload()
+ sigin = b'.'.join([self.protected.encode('utf-8'), payload])
signature = self.engine.sign(self.key, sigin)
return {'protected': self.protected,
- 'payload': self.payload,
+ 'payload': payload,
'signature': base64url_encode(signature)}
def verify(self, signature):
@@ -141,7 +164,8 @@
:raises InvalidJWSSignature: if the verification fails.
"""
try:
- sigin = ('.'.join([self.protected, self.payload])).encode('utf-8')
+ payload = self._payload()
+ sigin = b'.'.join([self.protected.encode('utf-8'), payload])
self.engine.verify(self.key, sigin, signature)
except Exception as e: # pylint: disable=broad-except
raise InvalidJWSSignature('Verification failed', repr(e))
@@ -165,16 +189,6 @@
self.verifylog = None
self._allowed_algs = None
- def _check_crit(self, crit):
- for k in crit:
- if k not in JWSHeaderRegistry:
- raise InvalidJWSSignature('Unknown critical header: '
- '"%s"' % k)
- else:
- if not JWSHeaderRegistry[k][1]:
- raise InvalidJWSSignature('Unsupported critical '
- 'header: "%s"' % k)
-
@property
def allowed_algs(self):
"""Allowed algorithms.
@@ -198,31 +212,61 @@
def is_valid(self):
return self.objects.get('valid', False)
- def _merge_headers(self, h1, h2):
- for k in list(h1.keys()):
- if k in h2:
- raise InvalidJWSObject('Duplicate header: "%s"' % k)
- h1.update(h2)
- return h1
+ # TODO: allow caller to specify list of headers it understands
+ def _merge_check_headers(self, protected, *headers):
+ header = None
+ crit = []
+ if protected is not None:
+ if 'crit' in protected:
+ crit = protected['crit']
+ # Check immediately if we support these critical headers
+ for k in crit:
+ if k not in JWSHeaderRegistry:
+ raise InvalidJWSObject(
+ 'Unknown critical header: "%s"' % k)
+ else:
+ if not JWSHeaderRegistry[k][1]:
+ raise InvalidJWSObject(
+ 'Unsupported critical header: "%s"' % k)
+ header = protected
+ if 'b64' in header:
+ if not isinstance(header['b64'], bool):
+ raise InvalidJWSObject('b64 header must be a boolean')
+
+ for hn in headers:
+ if hn is None:
+ continue
+ if header is None:
+ header = dict()
+ for h in list(hn.keys()):
+ if h in JWSHeaderRegistry:
+ if JWSHeaderRegistry[h].mustprotect:
+ raise InvalidJWSObject('"%s" must be protected' % h)
+ if h in header:
+ raise InvalidJWSObject('Duplicate header: "%s"' % h)
+ header.update(hn)
+
+ for k in crit:
+ if k not in header:
+ raise InvalidJWSObject('Missing critical header "%s"' % k)
+
+ return header
# TODO: support selecting key with 'kid' and passing in multiple keys
def _verify(self, alg, key, payload, signature, protected, header=None):
- # verify it is a valid JSON object and keep a decode copy
+ p = dict()
+ # verify it is a valid JSON object and decode
if protected is not None:
p = json_decode(protected)
- else:
- p = dict()
- if not isinstance(p, dict):
- raise InvalidJWSSignature('Invalid Protected header')
+ if not isinstance(p, dict):
+ raise InvalidJWSSignature('Invalid Protected header')
# merge heders, and verify there are no duplicates
if header:
if not isinstance(header, dict):
raise InvalidJWSSignature('Invalid Unprotected header')
- p = self._merge_headers(p, header)
- # verify critical headers
- # TODO: allow caller to specify list of headers it understands
- if 'crit' in p:
- self._check_crit(p['crit'])
+
+ # Merge and check (critical) headers
+ self._merge_check_headers(p, header)
# check 'alg' is present
if alg is None and 'alg' not in p:
raise InvalidJWSSignature('No "alg" in headers')
@@ -283,6 +327,33 @@
raise InvalidJWSSignature('Verification failed for all '
'signatures' + repr(self.verifylog))
+ def _deserialize_signature(self, s):
+ o = dict()
+ o['signature'] = base64url_decode(str(s['signature']))
+ if 'protected' in s:
+ p = base64url_decode(str(s['protected']))
+ o['protected'] = p.decode('utf-8')
+ if 'header' in s:
+ o['header'] = s['header']
+ return o
+
+ def _deserialize_b64(self, o, protected):
+ if protected is None:
+ b64n = None
+ else:
+ p = json_decode(protected)
+ b64n = p.get('b64')
+ if b64n is not None:
+ if not isinstance(b64n, bool):
+ raise InvalidJWSObject('b64 header must be boolean')
+ b64 = o.get('b64')
+ if b64 == b64n:
+ return
+ elif b64 is None:
+ o['b64'] = b64n
+ else:
+ raise InvalidJWSObject('conflicting b64 values')
+
def deserialize(self, raw_jws, key=None, alg=None):
"""Deserialize a JWS token.
@@ -305,25 +376,21 @@
try:
try:
djws = json_decode(raw_jws)
- o['payload'] = base64url_decode(str(djws['payload']))
if 'signatures' in djws:
o['signatures'] = list()
for s in djws['signatures']:
- os = dict()
- os['signature'] = base64url_decode(str(s['signature']))
- if 'protected' in s:
- p = base64url_decode(str(s['protected']))
- os['protected'] = p.decode('utf-8')
- if 'header' in s:
- os['header'] = s['header']
+ os = self._deserialize_signature(s)
o['signatures'].append(os)
+ self._deserialize_b64(o, os.get('protected'))
else:
- o['signature'] = base64url_decode(str(djws['signature']))
- if 'protected' in djws:
- p = base64url_decode(str(djws['protected']))
- o['protected'] = p.decode('utf-8')
- if 'header' in djws:
- o['header'] = djws['header']
+ o = self._deserialize_signature(djws)
+ self._deserialize_b64(o, o.get('protected'))
+
+ if 'payload' in djws:
+ if o.get('b64', True):
+ o['payload'] = base64url_decode(str(djws['payload']))
+ else:
+ o['payload'] = djws['payload']
except ValueError:
c = raw_jws.split('.')
@@ -332,6 +399,7 @@
p = base64url_decode(str(c[0]))
if len(p) > 0:
o['protected'] = p.decode('utf-8')
+ self._deserialize_b64(o, o['protected'])
o['payload'] = base64url_decode(str(c[1]))
o['signature'] = base64url_decode(str(c[2]))
@@ -354,7 +422,8 @@
:param potected: The Protected Header (optional)
:param header: The Unprotected Header (optional)
- :raises InvalidJWSObject: if no payload has been set on the object.
+ :raises InvalidJWSObject: if no payload has been set on the object,
+ or invalid headers are provided.
:raises ValueError: if the key is not a :class:`JWK` object.
:raises ValueError: if the algorithm is missing or is not provided
by one of the headers.
@@ -365,20 +434,36 @@
if not self.objects.get('payload', None):
raise InvalidJWSObject('Missing Payload')
+ b64 = True
+
p = dict()
if protected:
if isinstance(protected, dict):
- protected = json_encode(protected)
- p = json_decode(protected)
- # TODO: allow caller to specify list of headers it understands
- if 'crit' in p:
- self._check_crit(p['crit'])
+ p = protected
+ protected = json_encode(p)
+ else:
+ p = json_decode(protected)
+ # If b64 is present we must enforce criticality
+ if 'b64' in list(p.keys()):
+ crit = p.get('crit', [])
+ if 'b64' not in crit:
+ raise InvalidJWSObject('b64 header must always be critical')
+ b64 = p['b64']
+
+ if 'b64' in self.objects:
+ if b64 != self.objects['b64']:
+ raise InvalidJWSObject('Mixed b64 headers on signatures')
+
+ h = None
if header:
if isinstance(header, dict):
+ h = header
header = json_encode(header)
- h = json_decode(header)
- p = self._merge_headers(p, h)
+ else:
+ h = json_decode(header)
+
+ p = self._merge_check_headers(p, h)
if 'alg' in p:
if alg is None:
@@ -417,6 +502,7 @@
self.objects['signatures'].append(o)
else:
self.objects.update(o)
+ self.objects['b64'] = b64
def serialize(self, compact=False):
"""Serializes the object into a JWS token.
@@ -429,7 +515,6 @@
:raises InvalidJWSSignature: if no signature has been added
to the object, or no valid signature can be found.
"""
-
if compact:
if 'signatures' in self.objects:
raise InvalidJWSOperation("Can't use compact encoding with "
@@ -442,23 +527,40 @@
protected = base64url_encode(self.objects['protected'])
else:
protected = ''
- return '.'.join([protected,
- base64url_encode(self.objects['payload']),
+ if self.objects.get('payload', False):
+ if self.objects.get('b64', True):
+ payload = base64url_encode(self.objects['payload'])
+ else:
+ if isinstance(self.objects['payload'], bytes):
+ payload = self.objects['payload'].decode('utf-8')
+ else:
+ payload = self.objects['payload']
+ if '.' in payload:
+ raise InvalidJWSOperation(
+ "Can't use compact encoding with unencoded "
+ "payload that uses the . character")
+ else:
+ payload = ''
+ return '.'.join([protected, payload,
base64url_encode(self.objects['signature'])])
else:
obj = self.objects
+ sig = dict()
+ if self.objects.get('payload', False):
+ if self.objects.get('b64', True):
+ sig['payload'] = base64url_encode(self.objects['payload'])
+ else:
+ sig['payload'] = self.objects['payload']
if 'signature' in obj:
if not obj.get('valid', False):
raise InvalidJWSSignature("No valid signature found")
- sig = {'payload': base64url_encode(obj['payload']),
- 'signature': base64url_encode(obj['signature'])}
+ sig['signature'] = base64url_encode(obj['signature'])
if 'protected' in obj:
sig['protected'] = base64url_encode(obj['protected'])
if 'header' in obj:
sig['header'] = obj['header']
elif 'signatures' in obj:
- sig = {'payload': base64url_encode(obj['payload']),
- 'signatures': list()}
+ sig['signatures'] = list()
for o in obj['signatures']:
if not o.get('valid', False):
continue
@@ -482,24 +584,27 @@
raise InvalidJWSOperation("Payload not verified")
return self.objects['payload']
+ def detach_payload(self):
+ self.objects.pop('payload', None)
+
@property
def jose_header(self):
obj = self.objects
if 'signature' in obj:
- jh = dict()
if 'protected' in obj:
p = json_decode(obj['protected'])
- jh = self._merge_headers(jh, p)
- jh = self._merge_headers(jh, obj.get('header', dict()))
- return jh
+ else:
+ p = None
+ return self._merge_check_headers(p, obj.get('header', dict()))
elif 'signatures' in self.objects:
jhl = list()
for o in obj['signatures']:
jh = dict()
if 'protected' in o:
p = json_decode(o['protected'])
- jh = self._merge_headers(jh, p)
- jh = self._merge_headers(jh, o.get('header', dict()))
+ else:
+ p = None
+ jh = self._merge_check_headers(p, o.get('header', dict()))
jhl.append(jh)
return jhl
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/jwt.py
new/jwcrypto-0.6.0/jwcrypto/jwt.py
--- old/jwcrypto-0.5.0/jwcrypto/jwt.py 2018-06-27 08:24:22.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto/jwt.py 2018-11-05 16:04:15.000000000 +0100
@@ -212,9 +212,15 @@
@header.setter
def header(self, h):
if isinstance(h, dict):
- self._header = json_encode(h)
+ eh = json_encode(h)
else:
- self._header = h
+ eh = h
+ h = json_decode(eh)
+
+ if h.get('b64') is False:
+ raise ValueError("b64 header is invalid."
+ "JWTs cannot use unencoded payloads")
+ self._header = eh
@property
def claims(self):
@@ -280,7 +286,7 @@
def _add_jti_claim(self, claims):
if 'jti' in claims or 'jti' not in self._reg_claims:
return
- claims['jti'] = uuid.uuid4()
+ claims['jti'] = str(uuid.uuid4())
def _add_default_claims(self, claims):
if self._reg_claims is None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto/tests.py
new/jwcrypto-0.6.0/jwcrypto/tests.py
--- old/jwcrypto-0.5.0/jwcrypto/tests.py 2018-06-27 08:24:22.000000000
+0200
+++ new/jwcrypto-0.6.0/jwcrypto/tests.py 2018-11-05 16:04:15.000000000
+0100
@@ -311,11 +311,17 @@
self.assertRaises(jwk.InvalidJWKValue,
jwk.JWK.from_pyca, dict())
+ def test_jwk_from_json(self):
+ k = jwk.JWK.generate(kty='oct', size=256)
+ y = jwk.JWK.from_json(k.export())
+ self.assertEqual(k.export(), y.export())
+
def test_jwkset(self):
k = jwk.JWK(**RSAPrivateKey)
ks = jwk.JWKSet()
ks.add(k)
- ks2 = jwk.JWKSet().import_keyset(ks.export())
+ ks2 = jwk.JWKSet()
+ ks2.import_keyset(ks.export())
self.assertEqual(len(ks), len(ks2))
self.assertEqual(len(ks), 1)
k1 = ks.get_key(RSAPrivateKey['kid'])
@@ -833,6 +839,29 @@
'"ciphertext":"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",' \
'"tag":"Mz-VPPyU4RlcuYv1IwIvzw"}'
+Issue_136_Protected_Header_no_epk = {
+ "alg": "ECDH-ES+A256KW",
+ "enc": "A256CBC-HS512"}
+
+Issue_136_Contributed_JWE = \
+ "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJr" \
+ "aWQiOiJrZXkxIiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4Ijoi" \
+ "cDNpU241cEFSNUpYUE5aVF9SSEw2MTJMUGliWEI2WDhvTE9EOXFrN2NhTSIsInki" \
+ "OiI1Y04yQ2FqeXM3SVlDSXFEby1QUHF2bVQ1RzFvMEEtU0JicEQ5NFBOb3NNIn19" \
+ ".wG51hYE_Vma8tvFKVyeZs4lsHhXiarEw3-59eWHPmhRflDAKrMvnBw1urezo_Bz" \
+ "ZyPJ76m42ORQPbhEu5NvbJk3vgdgcp03j" \
+ ".lRttW8r6P6zM0uYDQt0EjQ.qnOnz7biCbqdLEdUH3acMamFm-cBRCSTFb83tNPrgDU" \
+ ".vZnwYpYjzrTaYritwMzaguaAMsq9rQOWe8NUHICv2hg"
+
+Issue_136_Contributed_Key = {
+ "alg": "ECDH-ES+A128KW",
+ "crv": "P-256",
+ "d": "F2PnliYin65AoIUxL1CwwzBPNeL2TyZPAKtkXOP50l8",
+ "kid": "key1",
+ "kty": "EC",
+ "x": "FPrb_xwxe8SBP3kO-e-WsofFp7n5-yc_tGgfAvqAP8g",
+ "y": "lM3HuyKMYUVsYdGqiWlkwTZbGO3Fh-hyadq8lfkTgBc"}
+
class TestJWE(unittest.TestCase):
def check_enc(self, plaintext, protected, key, vector):
@@ -895,6 +924,20 @@
recipient=E_A1_ex['key'])
e.serialize(compact=True)
+ def test_JWE_Issue_136(self):
+ plaintext = "plain"
+ protected = json_encode(Issue_136_Protected_Header_no_epk)
+ key = jwk.JWK.generate(kty='EC', crv='P-521')
+ e = jwe.JWE(plaintext, protected)
+ e.add_recipient(key)
+ enc = e.serialize()
+ e.deserialize(enc, key)
+ self.assertEqual(e.payload, plaintext.encode('utf-8'))
+
+ e = jwe.JWE()
+ e.deserialize(Issue_136_Contributed_JWE,
+ jwk.JWK(**Issue_136_Contributed_Key))
+
MMA_vector_key = jwk.JWK(**E_A2_key)
MMA_vector_ok_cek = \
@@ -1233,3 +1276,57 @@
self.assertEqual(inst.name, name)
else:
self.fail((name, cls))
+
+
+# RFC 7797
+
+rfc7797_e_header = '{"alg":"HS256"}'
+rfc7797_u_header = '{"alg":"HS256","b64":false,"crit":["b64"]}'
+rfc7797_payload = "$.02"
+
+
+class TestUnencodedPayload(unittest.TestCase):
+
+ def test_regular(self):
+ result = \
+ 'eyJhbGciOiJIUzI1NiJ9.JC4wMg.' + \
+ '5mvfOroL-g7HyqJoozehmsaqmvTYGEq5jTI1gVvoEoQ'
+
+ s = jws.JWS(rfc7797_payload)
+ s.add_signature(jwk.JWK(**SymmetricKeys['keys'][1]),
+ protected=rfc7797_e_header)
+ sig = s.serialize(compact=True)
+ self.assertEqual(sig, result)
+
+ def test_compat_unencoded(self):
+ result = \
+ 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..' + \
+ 'A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY'
+
+ s = jws.JWS(rfc7797_payload)
+ s.add_signature(jwk.JWK(**SymmetricKeys['keys'][1]),
+ protected=rfc7797_u_header)
+ # check unencoded payload is in serialized form
+ sig = s.serialize()
+ self.assertEqual(json_decode(sig)['payload'], rfc7797_payload)
+ # check error raises if we try to get compact serialization
+ with self.assertRaises(jws.InvalidJWSOperation):
+ sig = s.serialize(compact=True)
+ # check compact serialization is allowed with detached payload
+ s.detach_payload()
+ sig = s.serialize(compact=True)
+ self.assertEqual(sig, result)
+
+ def test_misses_crit(self):
+ s = jws.JWS(rfc7797_payload)
+ with self.assertRaises(jws.InvalidJWSObject):
+ s.add_signature(jwk.JWK(**SymmetricKeys['keys'][1]),
+ protected={"alg": "HS256", "b64": False})
+
+ def test_mismatching_encoding(self):
+ s = jws.JWS(rfc7797_payload)
+ s.add_signature(jwk.JWK(**SymmetricKeys['keys'][0]),
+ protected=rfc7797_e_header)
+ with self.assertRaises(jws.InvalidJWSObject):
+ s.add_signature(jwk.JWK(**SymmetricKeys['keys'][1]),
+ protected=rfc7797_u_header)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/jwcrypto.egg-info/PKG-INFO
new/jwcrypto-0.6.0/jwcrypto.egg-info/PKG-INFO
--- old/jwcrypto-0.5.0/jwcrypto.egg-info/PKG-INFO 2018-06-27
12:27:24.000000000 +0200
+++ new/jwcrypto-0.6.0/jwcrypto.egg-info/PKG-INFO 2018-11-05
16:18:50.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: jwcrypto
-Version: 0.5.0
+Version: 0.6.0
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.5.0/setup.py new/jwcrypto-0.6.0/setup.py
--- old/jwcrypto-0.5.0/setup.py 2018-06-27 12:27:18.000000000 +0200
+++ new/jwcrypto-0.6.0/setup.py 2018-11-05 16:13:11.000000000 +0100
@@ -6,7 +6,7 @@
setup(
name = 'jwcrypto',
- version = '0.5.0',
+ version = '0.6.0',
license = 'LGPLv3+',
maintainer = 'JWCrypto Project Contributors',
maintainer_email = '[email protected]',