Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-jwcrypto for openSUSE:Factory checked in at 2021-08-11 11:47:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jwcrypto (Old) and /work/SRC/openSUSE:Factory/.python-jwcrypto.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jwcrypto" Wed Aug 11 11:47:01 2021 rev:11 rq:910436 version:1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jwcrypto/python-jwcrypto.changes 2021-07-10 22:53:31.640210115 +0200 +++ /work/SRC/openSUSE:Factory/.python-jwcrypto.new.1899/python-jwcrypto.changes 2021-08-11 11:47:09.861750310 +0200 @@ -1,0 +2,13 @@ +Thu Aug 5 19:05:35 UTC 2021 - Michael Str??der <mich...@stroeder.com> + +- update to 1.0 + * Create SECURITY.md + * Allow empty payloads in JWS tokens + * Add tests to check empty payload support + * Drop python2 compatibility + * Fix python3 pylint issues + * Add explicit support to check 'typ' in JWT + * Drop support for importing old MutableMapping + * Disable annoying pep8 naming checks + +------------------------------------------------------------------- Old: ---- jwcrypto-0.9.1.tar.gz New: ---- jwcrypto-1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jwcrypto.spec ++++++ --- /var/tmp/diff_new_pack.efevCa/_old 2021-08-11 11:47:11.973747770 +0200 +++ /var/tmp/diff_new_pack.efevCa/_new 2021-08-11 11:47:11.973747770 +0200 @@ -18,8 +18,10 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} +%define skip_python2 1 + Name: python-jwcrypto -Version: 0.9.1 +Version: 1.0 Release: 0 Summary: Python module package implementing JOSE Web standards License: LGPL-3.0-only @@ -62,6 +64,6 @@ %files %{python_files} %{python_sitelib}/* %license LICENSE -%doc README.md +%doc README.md SECURITY.md %changelog ++++++ jwcrypto-0.9.1.tar.gz -> jwcrypto-1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/.github/workflows/build.yml new/jwcrypto-1.0/.github/workflows/build.yml --- old/jwcrypto-0.9.1/.github/workflows/build.yml 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/.github/workflows/build.yml 2021-08-02 10:49:08.000000000 +0200 @@ -14,7 +14,6 @@ "fail-fast": false, "matrix": { "name": [ - "python-27", "python-36", "python-37", "python-38", @@ -22,16 +21,10 @@ "doc", "sphinx", "lint", - "pep8py3", + "pep8", ], "include": [ { - "name": "python-27", - "python": "2.7", - "toxenv": "py27", - "arch": "x64", - }, - { "name": "python-36", "python": "3.6", "toxenv": "py36", @@ -74,9 +67,9 @@ "arch": "x64", }, { - "name": "pep8py3", + "name": "pep8", "python": "3.9", - "toxenv": "pep8py3", + "toxenv": "pep8", "arch": "x64", }, ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/.github/workflows/codeql-analysis.yml new/jwcrypto-1.0/.github/workflows/codeql-analysis.yml --- old/jwcrypto-0.9.1/.github/workflows/codeql-analysis.yml 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/.github/workflows/codeql-analysis.yml 2021-08-02 10:49:08.000000000 +0200 @@ -17,6 +17,9 @@ pull_request: # The branches below must be a subset of the branches above branches: [ master ] + paths-ignore: + - '**/*.md' + - '**/*.txt' schedule: - cron: '31 10 * * 5' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/Makefile new/jwcrypto-1.0/Makefile --- old/jwcrypto-0.9.1/Makefile 2021-06-01 16:52:31.000000000 +0200 +++ new/jwcrypto-1.0/Makefile 2021-08-02 10:49:08.000000000 +0200 @@ -7,8 +7,7 @@ pep8: # Check style consistency - tox -e pep8py2 - tox -e pep8py3 + tox -e pep8 clean: rm -fr build dist *.egg-info @@ -25,7 +24,6 @@ test: rm -f .coverage - tox -e py27 tox -e py36 --skip-missing-interpreter tox -e py37 --skip-missing-interpreter tox -e py38 --skip-missing-interpreter diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/PKG-INFO new/jwcrypto-1.0/PKG-INFO --- old/jwcrypto-0.9.1/PKG-INFO 2021-06-09 20:43:44.513565800 +0200 +++ new/jwcrypto-1.0/PKG-INFO 2021-08-02 10:49:25.848379100 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: jwcrypto -Version: 0.9.1 +Version: 1.0 Summary: Implementation of JOSE Web standards Home-page: https://github.com/latchset/jwcrypto Maintainer: JWCrypto Project Contributors @@ -8,7 +8,6 @@ License: LGPLv3+ Description: UNKNOWN Platform: UNKNOWN -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/README.md new/jwcrypto-1.0/README.md --- old/jwcrypto-0.9.1/README.md 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/README.md 2021-08-02 10:49:08.000000000 +0200 @@ -2,6 +2,7 @@ [![Changelog](https://img.shields.io/github/v/release/latchset/jwcrypto?label=changelog)](https://github.com/latchset/jwcrypto/releases) [![Build Status](https://github.com/latchset/jwcrypto/actions/workflows/build.yml/badge.svg)](https://github.com/latchset/jwcrypto/actions/workflows/build.yml) [![ppc64le Build](https://github.com/latchset/jwcrypto/actions/workflows/ppc64le.yml/badge.svg)](https://github.com/latchset/jwcrypto/actions/workflows/ppc64le.yml) +[![Code Scan](https://github.com/latchset/jwcrypto/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/latchset/jwcrypto/actions/workflows/codeql-analysis.yml) JWCrypto ======== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/SECURITY.md new/jwcrypto-1.0/SECURITY.md --- old/jwcrypto-0.9.1/SECURITY.md 1970-01-01 01:00:00.000000000 +0100 +++ new/jwcrypto-1.0/SECURITY.md 2021-08-02 10:49:08.000000000 +0200 @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 0.8 + | :white_check_mark: | +| < 0.8 | :x: | + +## Reporting a Vulnerability + +Please contact s...@redhat.com if you have found a security vulnerability + +Expect a response within 2 business days (not on week ends or holidays). + +If the vulnerbaility is confirmed and accepted you will be given instruction on any embargo or disclosure timeline via email. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/docs/source/conf.py new/jwcrypto-1.0/docs/source/conf.py --- old/jwcrypto-0.9.1/docs/source/conf.py 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/docs/source/conf.py 2021-08-02 10:49:08.000000000 +0200 @@ -53,9 +53,9 @@ # built documents. # # The short X.Y version. -version = '0.9' +version = '1.0' # The full version, including alpha/beta/rc tags. -release = '0.9.1' +release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/docs/source/index.rst new/jwcrypto-1.0/docs/source/index.rst --- old/jwcrypto-0.9.1/docs/source/index.rst 2015-07-10 18:38:15.000000000 +0200 +++ new/jwcrypto-1.0/docs/source/index.rst 2021-08-02 10:49:08.000000000 +0200 @@ -10,8 +10,7 @@ Encryption (JOSE) Web Standards as they are being developed in the JOSE_ IETF Working Group and related technology. -JWCrypto is Python2 and Python3 compatible and uses the Cryptography_ -package for all the crypto functions. +JWCrypto uses the Cryptography_ package for all the crypto functions. .. _JOSE: https://datatracker.ietf.org/wg/jose/charter/ .. _Cryptography: https://cryptography.io/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/common.py new/jwcrypto-1.0/jwcrypto/common.py --- old/jwcrypto-0.9.1/jwcrypto/common.py 2021-02-08 21:02:01.000000000 +0100 +++ new/jwcrypto-1.0/jwcrypto/common.py 2021-08-02 10:49:08.000000000 +0200 @@ -4,10 +4,7 @@ import json from base64 import urlsafe_b64decode, urlsafe_b64encode from collections import namedtuple -try: - from collections.abc import MutableMapping -except ImportError: - from collections import MutableMapping +from collections.abc import MutableMapping # Padding stripping versions as described in # RFC 7515 Appendix C diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/jwa.py new/jwcrypto-1.0/jwcrypto/jwa.py --- old/jwcrypto-0.9.1/jwcrypto/jwa.py 2021-02-08 21:02:01.000000000 +0100 +++ new/jwcrypto-1.0/jwcrypto/jwa.py 2021-08-02 10:49:08.000000000 +0200 @@ -1,8 +1,8 @@ # Copyright (C) 2016 JWCrypto Project Contributors - see LICENSE file -import abc import os import struct +from abc import ABCMeta, abstractmethod from binascii import hexlify, unhexlify from cryptography.exceptions import InvalidSignature @@ -17,8 +17,6 @@ from cryptography.hazmat.primitives.keywrap import aes_key_unwrap, aes_key_wrap from cryptography.hazmat.primitives.padding import PKCS7 -import six - from jwcrypto.common import InvalidCEKeyLength from jwcrypto.common import InvalidJWAAlgorithm from jwcrypto.common import InvalidJWEKeyLength @@ -31,33 +29,32 @@ # Implements RFC 7518 - JSON Web Algorithms (JWA) -@six.add_metaclass(abc.ABCMeta) -class JWAAlgorithm(object): +class JWAAlgorithm(metaclass=ABCMeta): - @abc.abstractproperty + @property + @abstractmethod def name(self): """The algorithm Name""" - pass - @abc.abstractproperty + @property + @abstractmethod def description(self): """A short description""" - pass - @abc.abstractproperty + @property + @abstractmethod def keysize(self): """The actual/recommended/minimum key size""" - pass - @abc.abstractproperty + @property + @abstractmethod def algorithm_usage_location(self): """One of 'alg', 'enc' or 'JWK'""" - pass - @abc.abstractproperty + @property + @abstractmethod def algorithm_use(self): """One of 'sig', 'kex', 'enc'""" - pass def _bitsize(x): @@ -1104,21 +1101,21 @@ try: return cls.instantiate_alg(name, use='sig') except KeyError: - raise InvalidJWAAlgorithm( - '%s is not a valid Signign algorithm name' % name) + raise InvalidJWAAlgorithm('%s is not a valid Signign algorithm' + ' name' % name) from None @classmethod def keymgmt_alg(cls, name): try: return cls.instantiate_alg(name, use='kex') except KeyError: - raise InvalidJWAAlgorithm( - '%s is not a valid Key Management algorithm name' % name) + raise InvalidJWAAlgorithm('%s is not a valid Key Management' + ' algorithm name' % name) from None @classmethod def encryption_alg(cls, name): try: return cls.instantiate_alg(name, use='enc') except KeyError: - raise InvalidJWAAlgorithm( - '%s is not a valid Encryption algorithm name' % name) + raise InvalidJWAAlgorithm('%s is not a valid Encryption' + ' algorithm name' % name) from None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/jwe.py new/jwcrypto-1.0/jwcrypto/jwe.py --- old/jwcrypto-0.9.1/jwcrypto/jwe.py 2021-02-08 21:02:01.000000000 +0100 +++ new/jwcrypto-1.0/jwcrypto/jwe.py 2021-08-02 10:49:08.000000000 +0200 @@ -477,10 +477,10 @@ if 'header' in djwe: o['header'] = json_encode(djwe['header']) - except ValueError: + except ValueError as e: c = raw_jwe.split('.') if len(c) != 5: - raise InvalidJWEData() + raise InvalidJWEData() from e p = base64url_decode(c[0]) o['protected'] = p.decode('utf-8') ekey = base64url_decode(c[1]) @@ -493,7 +493,7 @@ self.objects = o except Exception as e: # pylint: disable=broad-except - raise InvalidJWEData('Invalid format', repr(e)) + raise InvalidJWEData('Invalid format', repr(e)) from e if key: self.decrypt(key) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/jwk.py new/jwcrypto-1.0/jwcrypto/jwk.py --- old/jwcrypto-0.9.1/jwcrypto/jwk.py 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto/jwk.py 2021-08-02 10:49:08.000000000 +0200 @@ -13,8 +13,6 @@ from deprecated import deprecated -from six import iteritems - from jwcrypto.common import JWException from jwcrypto.common import base64url_decode, base64url_encode from jwcrypto.common import json_decode, json_encode @@ -263,8 +261,6 @@ on the key type or other constraints. """ - pass - class JWK(dict): """JSON Web Key object @@ -318,8 +314,8 @@ try: kty = kwargs['kty'] gen = getattr(obj, '_generate_%s' % kty) - except (KeyError, AttributeError): - raise InvalidJWKType(kty) + except (KeyError, AttributeError) as e: + raise InvalidJWKType(kty) from e gen(kwargs) return obj @@ -328,8 +324,8 @@ try: kty = params.pop('generate') gen = getattr(self, '_generate_%s' % kty) - except (KeyError, AttributeError): - raise InvalidJWKType(kty) + except (KeyError, AttributeError) as e: + raise InvalidJWKType(kty) from e gen(params) @@ -341,8 +337,8 @@ try: from jwcrypto.jwa import JWA alg = JWA.instantiate_alg(params['alg']) - except KeyError: - raise ValueError("Invalid 'alg' parameter") + except KeyError as e: + raise ValueError("Invalid 'alg' parameter") from e size = alg.keysize return size @@ -451,13 +447,13 @@ raise InvalidJWKValue('Must specify "crv" for OKP key generation') try: key = _OKP_CURVES_TABLE[params['crv']].privkey.generate() - except KeyError: + except KeyError as e: raise InvalidJWKValue('"%s" is not a supported curve for the ' - 'OKP key type' % params['crv']) + 'OKP key type' % params['crv']) from e self._import_pyca_pri_okp(key, **params) def _okp_curve_from_pyca_key(self, key): - for name, val in iteritems(_OKP_CURVES_TABLE): + for name, val in _OKP_CURVES_TABLE.items(): if isinstance(key, (val.pubkey, val.privkey)): return name raise InvalidJWKValue('Invalid OKP Key object %r' % key) @@ -509,7 +505,7 @@ while name in names: names.remove(name) - for name, val in iteritems(JWKValuesRegistry[kty]): + for name, val in JWKValuesRegistry[kty].items(): if val.required and name not in newkey: raise InvalidJWKValue('Missing required value %s' % name) if val.type == ParmType.unsupported and name in newkey: @@ -518,18 +514,18 @@ # Check that the value is base64url encoded try: base64url_decode(newkey[name]) - except Exception: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except raise InvalidJWKValue( '"%s" is not base64url encoded' % name - ) + ) from e if val.type == ParmType.b64u and name in newkey: # Check that the value is Base64urlUInt encoded try: self._decode_int(newkey[name]) - except Exception: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except raise InvalidJWKValue( '"%s" is not Base64urlUInt encoded' % name - ) + ) from e # Unknown key parameters are allowed for name in names: @@ -581,7 +577,7 @@ try: jkey = json_decode(key) except Exception as e: # pylint: disable=broad-except - raise InvalidJWKValue(e) + raise InvalidJWKValue from e obj.import_key(**jkey) return obj @@ -761,8 +757,8 @@ crv = self.get('crv') try: pubkey = _OKP_CURVES_TABLE[crv].pubkey - except KeyError: - raise InvalidJWKValue('Unknown curve "%s"' % crv) + except KeyError as e: + raise InvalidJWKValue('Unknown curve "%s"' % crv) from e x = base64url_decode(self.get('x')) return pubkey.from_public_bytes(x) @@ -771,8 +767,8 @@ crv = self.get('crv') try: privkey = _OKP_CURVES_TABLE[crv].privkey - except KeyError: - raise InvalidJWKValue('Unknown curve "%s"' % crv) + except KeyError as e: + raise InvalidJWKValue('Unknown curve "%s"' % crv) from e d = base64url_decode(self.get('d')) return privkey.from_private_bytes(d) @@ -883,6 +879,7 @@ data, backend=default_backend()) key = cert.public_key() except ValueError: + # pylint: disable=raise-missing-from raise e self.import_from_pyca(key) @@ -950,7 +947,7 @@ """ t = {'kty': self.get('kty')} - for name, val in iteritems(JWKValuesRegistry[t['kty']]): + for name, val in JWKValuesRegistry[t['kty']].items(): if val.required: t[name] = self.get(name) digest = hashes.Hash(hashalg, backend=default_backend()) @@ -980,17 +977,17 @@ # is used to indicate a 'None' key if v == b'' and kty != 'oct' and item != 'k': raise ValueError - except Exception: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except raise InvalidJWKValue( '"%s" is not base64url encoded' % item - ) + ) from e elif JWKValuesRegistry[kty][item].type == ParmType.b64u: try: self._decode_int(value) - except Exception: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except raise InvalidJWKValue( '"%s" is not Base64urlUInt encoded' % item - ) + ) from e super(JWK, self).__setitem__(item, value) return @@ -1013,7 +1010,7 @@ super(JWK, self).__setitem__(item, value) def update(self, *args, **kwargs): - for k, v in iteritems(dict(*args, **kwargs)): + for k, v in dict(*args, **kwargs).items(): self.__setitem__(k, v) def setdefault(self, key, default=None): @@ -1055,7 +1052,7 @@ return self.get(item) raise KeyError except KeyError: - raise AttributeError + raise AttributeError(item) from None def __setattr__(self, item, value): try: @@ -1066,7 +1063,7 @@ self.__setitem__(item, value) super(JWK, self).__setattr__(item, value) except KeyError: - raise AttributeError + raise AttributeError(item) from None @classmethod def from_password(cls, password): @@ -1079,7 +1076,7 @@ try: params['k'] = base64url_encode(password.encode('utf8')) except Exception as e: # pylint: disable=broad-except - raise InvalidJWKValue(e) + raise InvalidJWKValue from e obj.import_key(**params) return obj @@ -1130,7 +1127,7 @@ super(JWKSet, self).__setitem__(key, val) def update(self, *args, **kwargs): - for k, v in iteritems(dict(*args, **kwargs)): + for k, v in dict(*args, **kwargs).items(): self.__setitem__(k, v) def setdefault(self, key, default=None): @@ -1151,7 +1148,7 @@ a JSON object """ exp_dict = dict() - for k, v in iteritems(self): + for k, v in self.items(): if k == 'keys': keys = list() for jwk in v: @@ -1169,13 +1166,13 @@ """ try: jwkset = json_decode(keyset) - except Exception: # pylint: disable=broad-except - raise InvalidJWKValue() + except Exception as e: # pylint: disable=broad-except + raise InvalidJWKValue from e if 'keys' not in jwkset: - raise InvalidJWKValue() + raise InvalidJWKValue - for k, v in iteritems(jwkset): + for k, v in jwkset.items(): if k == 'keys': for jwk in v: self['keys'].add(JWK(**jwk)) @@ -1203,7 +1200,7 @@ def __repr__(self): repr_dict = dict() - for k, v in iteritems(self): + for k, v in self.items(): if k == 'keys': keys = list() for jwk in v: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/jws.py new/jwcrypto-1.0/jwcrypto/jws.py --- old/jwcrypto-0.9.1/jwcrypto/jws.py 2021-02-08 21:02:01.000000000 +0100 +++ new/jwcrypto-1.0/jwcrypto/jws.py 2021-08-02 10:49:08.000000000 +0200 @@ -163,7 +163,7 @@ 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)) + raise InvalidJWSSignature('Verification failed') from e return True @@ -180,8 +180,7 @@ :param header_registry: Optional additions to the header registry """ self.objects = dict() - if payload: - self.objects['payload'] = payload + self.objects['payload'] = payload self.verifylog = None self._allowed_algs = None self.header_registry = JWSEHeaderRegistry(JWSHeaderRegistry) @@ -400,7 +399,8 @@ except ValueError: c = raw_jws.split('.') if len(c) != 3: - raise InvalidJWSObject('Unrecognized representation') + raise InvalidJWSObject('Unrecognized' + ' representation') from None p = base64url_decode(str(c[0])) if len(p) > 0: o['protected'] = p.decode('utf-8') @@ -411,7 +411,7 @@ self.objects = o except Exception as e: # pylint: disable=broad-except - raise InvalidJWSObject('Invalid format', repr(e)) + raise InvalidJWSObject('Invalid format') from e if key: self.verify(key, alg) @@ -427,8 +427,7 @@ :param protected: The Protected Header (optional) :param header: The Unprotected Header (optional) - :raises InvalidJWSObject: if no payload has been set on the object, - or invalid headers are provided. + :raises InvalidJWSObject: if 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. @@ -436,9 +435,6 @@ unknown or otherwise not yet implemented. """ - if not self.objects.get('payload', None): - raise InvalidJWSObject('Missing Payload') - b64 = True p = dict() @@ -481,7 +477,8 @@ raise ValueError('"alg" not specified') c = JWSCore( - alg, key, protected, self.objects['payload'], self.allowed_algs + alg, key, protected, self.objects.get('payload'), + self.allowed_algs ) sig = c.sign() @@ -539,7 +536,7 @@ else: raise InvalidJWSOperation("Can't use compact encoding " "without protected header") - if self.objects.get('payload', False): + if self.objects.get('payload'): if self.objects.get('b64', True): payload = base64url_encode(self.objects['payload']) else: @@ -558,11 +555,11 @@ 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'] + payload = self.objects.get('payload', '') + if self.objects.get('b64', True): + sig['payload'] = base64url_encode(payload) + else: + sig['payload'] = payload if 'signature' in obj: if not obj.get('valid', False): raise InvalidJWSSignature("No valid signature found") @@ -590,11 +587,9 @@ @property def payload(self): - if 'payload' not in self.objects: - raise InvalidJWSOperation("Payload not available") if not self.is_valid: raise InvalidJWSOperation("Payload not verified") - return self.objects['payload'] + return self.objects.get('payload') def detach_payload(self): self.objects.pop('payload', None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/jwt.py new/jwcrypto-1.0/jwcrypto/jwt.py --- old/jwcrypto-0.9.1/jwcrypto/jwt.py 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto/jwt.py 2021-08-02 10:49:08.000000000 +0200 @@ -3,8 +3,6 @@ import time import uuid -from six import string_types - from jwcrypto.common import JWException, json_decode, json_encode from jwcrypto.jwe import JWE from jwcrypto.jwk import JWK, JWKSet @@ -306,17 +304,17 @@ def _check_string_claim(self, name, claims): if name not in claims: return - if not isinstance(claims[name], string_types): + if not isinstance(claims[name], str): raise JWTInvalidClaimFormat("Claim %s is not a StringOrURI type") def _check_array_or_string_claim(self, name, claims): if name not in claims: return if isinstance(claims[name], list): - if any(not isinstance(claim, string_types) for claim in claims): + if any(not isinstance(claim, str) for claim in claims): raise JWTInvalidClaimFormat( "Claim %s contains non StringOrURI types" % (name, )) - elif not isinstance(claims[name], string_types): + elif not isinstance(claims[name], str): raise JWTInvalidClaimFormat( "Claim %s is not a StringOrURI type" % (name, )) @@ -325,9 +323,9 @@ return try: int(claims[name]) - except ValueError: + except ValueError as e: raise JWTInvalidClaimFormat( - "Claim %s is not an integer" % (name, )) + "Claim %s is not an integer" % (name, )) from e def _check_exp(self, claim, limit, leeway): if claim < limit - leeway: @@ -347,6 +345,7 @@ self._check_integer_claim('nbf', claims) self._check_integer_claim('iat', claims) self._check_string_claim('jti', claims) + self._check_string_claim('typ', claims) if self._check_claims is None: if 'exp' in claims: @@ -363,10 +362,11 @@ claims = json_decode(self.claims) if not isinstance(claims, dict): raise ValueError() - except ValueError: + except ValueError as e: if self._check_claims is not None: - raise JWTInvalidClaimFormat( - "Claims check requested but claims is not a json dict") + raise JWTInvalidClaimFormat("Claims check requested " + "but claims is not a json " + "dict") from e return self._check_default_claims(claims) @@ -407,12 +407,28 @@ else: self._check_nbf(claims[name], time.time(), self._leeway) + elif name == 'typ': + if value is not None: + if self.norm_typ(value) != self.norm_typ(claims[name]): + raise JWTInvalidClaimValue("Invalid '%s' value. '%s'" + " does not normalize to " + "'%s'" % (name, + claims[name], + value)) + else: if value is not None and value != claims[name]: raise JWTInvalidClaimValue( "Invalid '%s' value. Expected '%s' got '%s'" % ( name, value, claims[name])) + def norm_typ(self, val): + lc = val.lower() + if '/' in lc: + return lc + else: + return 'application/' + lc + def make_signed_token(self, key): """Signs the payload. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto/tests.py new/jwcrypto-1.0/jwcrypto/tests.py --- old/jwcrypto-0.9.1/jwcrypto/tests.py 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto/tests.py 2021-08-02 10:49:08.000000000 +0200 @@ -934,6 +934,21 @@ jws_verify.verify(key.public()) self.assertEqual(jws_verify.payload, payload) + def test_jws_issue_224(self): + key = jwk.JWK().generate(kty='oct') + + # Test Empty payload is supported for creating and verifying signatures + s = jws.JWS(payload='') + s.add_signature(key, None, json_encode({"alg": "HS256"})) + o1 = s.serialize(compact=True) + self.assertTrue('..' in o1) + o2 = json_decode(s.serialize()) + self.assertEqual(o2['payload'], '') + + t = jws.JWS() + t.deserialize(o1) + t.verify(key) + E_A1_plaintext = \ [84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32, @@ -1492,6 +1507,38 @@ check_claims={"iss": "test", "exp": None, "string_claim": "test"}) + def test_claims_typ(self): + key = jwk.JWK().generate(kty='oct') + claims = '{"typ":"application/test"}' + string_header = '{"alg":"HS256"}' + t = jwt.JWT(string_header, claims) + t.make_signed_token(key) + token = t.serialize() + + # Same typ w/o application prefix + jwt.JWT(jwt=token, key=key, check_claims={"typ": "test"}) + self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token, + key=key, check_claims={"typ": "wrong"}) + + # Same typ w/ application prefix + jwt.JWT(jwt=token, key=key, check_claims={"typ": "application/test"}) + self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token, + key=key, check_claims={"typ": "application/wrong"}) + + # check that a '/' in the name makes it not be matched with + # 'application/' prefix + claims = '{"typ":"diffmime/test"}' + t = jwt.JWT(string_header, claims) + t.make_signed_token(key) + token = t.serialize() + self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token, + key=key, check_claims={"typ": "application/test"}) + self.assertRaises(jwt.JWTInvalidClaimValue, jwt.JWT, jwt=token, + key=key, check_claims={"typ": "test"}) + + # finally make sure it doesn't raise if not checked. + jwt.JWT(jwt=token, key=key) + def test_empty_claims(self): key = jwk.JWK().generate(kty='oct') @@ -1505,12 +1552,12 @@ c.deserialize(token, key) self.assertEqual('{}', c.claims) - # empty string is not valid + # empty string is also valid t = jwt.JWT('{"alg":"HS256"}', '') - self.assertEqual('', t.claims) - self.assertRaises(jws.InvalidJWSObject, t.make_signed_token, key) + t.make_signed_token(key) + token = t.serialize() - # but a space is fine + # also a space is fine t = jwt.JWT('{"alg":"HS256"}', ' ') self.assertEqual(' ', t.claims) t.make_signed_token(key) @@ -1640,8 +1687,8 @@ def test_no_default_rsa_1_5(self): s = jws.JWS('test') - with self.assertRaisesRegexp(jws.InvalidJWSOperation, - 'Algorithm not allowed'): + with self.assertRaisesRegex(jws.InvalidJWSOperation, + 'Algorithm not allowed'): s.add_signature(A2_key, alg="RSA1_5") def test_pbes2_hs256_aeskw(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto.egg-info/PKG-INFO new/jwcrypto-1.0/jwcrypto.egg-info/PKG-INFO --- old/jwcrypto-0.9.1/jwcrypto.egg-info/PKG-INFO 2021-06-09 20:43:44.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto.egg-info/PKG-INFO 2021-08-02 10:49:25.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: jwcrypto -Version: 0.9.1 +Version: 1.0 Summary: Implementation of JOSE Web standards Home-page: https://github.com/latchset/jwcrypto Maintainer: JWCrypto Project Contributors @@ -8,7 +8,6 @@ License: LGPLv3+ Description: UNKNOWN Platform: UNKNOWN -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto.egg-info/SOURCES.txt new/jwcrypto-1.0/jwcrypto.egg-info/SOURCES.txt --- old/jwcrypto-0.9.1/jwcrypto.egg-info/SOURCES.txt 2021-06-09 20:43:44.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto.egg-info/SOURCES.txt 2021-08-02 10:49:25.000000000 +0200 @@ -4,6 +4,7 @@ MANIFEST.in Makefile README.md +SECURITY.md setup.cfg setup.py tox.ini diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/jwcrypto.egg-info/requires.txt new/jwcrypto-1.0/jwcrypto.egg-info/requires.txt --- old/jwcrypto-0.9.1/jwcrypto.egg-info/requires.txt 2021-06-09 20:43:44.000000000 +0200 +++ new/jwcrypto-1.0/jwcrypto.egg-info/requires.txt 2021-08-02 10:49:25.000000000 +0200 @@ -1,3 +1,2 @@ cryptography>=2.3 deprecated -six diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/setup.py new/jwcrypto-1.0/setup.py --- old/jwcrypto-0.9.1/setup.py 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/setup.py 2021-08-02 10:49:08.000000000 +0200 @@ -6,7 +6,7 @@ setup( name = 'jwcrypto', - version = '0.9.1', + version = '1.0', license = 'LGPLv3+', maintainer = 'JWCrypto Project Contributors', maintainer_email = 's...@redhat.com', @@ -14,7 +14,6 @@ packages = ['jwcrypto'], description = 'Implementation of JOSE Web standards', classifiers = [ - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', @@ -27,6 +26,5 @@ install_requires = [ 'cryptography >= 2.3', 'deprecated', - 'six', ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwcrypto-0.9.1/tox.ini new/jwcrypto-1.0/tox.ini --- old/jwcrypto-0.9.1/tox.ini 2021-06-09 20:43:19.000000000 +0200 +++ new/jwcrypto-1.0/tox.ini 2021-08-02 10:49:08.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist = lint,py27,py36,py37,py38,py39,pep8py2,pep8py3,doc,sphinx +envlist = lint,py36,py37,py38,py39,pep8,doc,sphinx skip_missing_interpreters = true [testenv] @@ -15,37 +15,28 @@ {envpython} -m coverage report -m [testenv:lint] -basepython = python2.7 +basepython = python3.9 deps = pylint #sitepackages = True commands = {envpython} -m pylint -d c,r,i,W0613 -r n -f colorized --notes= --disable=star-args ./jwcrypto -[testenv:pep8py2] -basepython = python2.7 -deps = - flake8 - flake8-import-order - pep8-naming -commands = - {envpython} -m flake8 {posargs} jwcrypto - -[testenv:pep8py3] +[testenv:pep8] basepython = python3 deps = flake8 flake8-import-order pep8-naming commands = - {envpython} -m flake8 {posargs} jwcrypto + {envpython} -m flake8 {posargs} --ignore=N802,N818 jwcrypto [testenv:doc] deps = doc8 docutils markdown -basepython = python2.7 +basepython = python3 commands = doc8 --allow-long-titles README.md markdown_py README.md -f {toxworkdir}/README.md.html