[Pkg-freeipa-devel] [Git][freeipa-team/python-jwcrypto][master] 17 commits: Update copyrights in docs

Timo Aaltonen (@tjaalton) gitlab at salsa.debian.org
Tue Mar 29 08:36:04 BST 2022



Timo Aaltonen pushed to branch master at FreeIPA packaging / python-jwcrypto


Commits:
31240bc8 by DefteZ at 2021-08-03T07:46:23-04:00
Update copyrights in docs

- - - - -
ea363532 by Clément Schreiner at 2021-08-06T04:41:04-04:00
Set python requirement to 3.6+ in setup.py

- - - - -
fbff1646 by Simo Sorce at 2021-08-24T06:08:03-04:00
Try to apt-get update before build

Sounds like some caching is preventing builds as older packages are not found (index and repo are not in sync). Try an update to see if we can fix this.
- - - - -
09caa05d by Simo Sorce at 2021-08-24T06:08:03-04:00
Fix linter complaints

Signed-off-by: Simo Sorce <simo at redhat.com>

- - - - -
d468f262 by Mads Jensen at 2021-08-24T06:14:11-04:00
Replace list and dict constructions with literals.

- - - - -
7adb86f0 by Mads Jensen at 2021-08-24T06:14:11-04:00
Remove explicit object-inheritance

- - - - -
14c942d6 by Simo Sorce at 2021-09-17T12:11:13-04:00
Check provided claims for type correctness

Issue #239 brought up that check_claims are not actually checked to be
of the right type and this can lead to confusion in some case, as well
as defer error reporting after potentially costly signature
computations.

Check for general claims type validity upfront where appropriate.

Resolves #239

Signed-off-by: Simo Sorce <simo at redhat.com>

- - - - -
dedf67b2 by Simo Sorce at 2021-10-27T11:26:32-04:00
Update setup.cfg

Addresses #246
- - - - -
fced5444 by codedust at 2021-11-08T08:42:14-05:00
Fix key_op in get_op_key for RSA unwrap operation

This fixes a bug where JWE decryption fails with JWKs with
`key_ops: ["unwrapKey"]`.

JWE does not define any algorithm to asymmetrically encrypt content
direcly. Instead, algorithms like RSA-OAEP-256 wrap an AES key which is
then used to symmetrically encrypt the content.

Section 4.3 of RFC 7517 defines the `unwrapKey` operation as "decrypt
key and validate decryption, if applicable" which is exactly what RSA
keys are used for in algorithms like RSA-OAEP-256.

see https://datatracker.ietf.org/doc/html/rfc7517#section-4.3

- - - - -
07d440c3 by Simo Sorce at 2021-11-30T13:04:35-05:00
Update python versions used for testing

Signed-off-by: Simo Sorce <simo at redhat.com>

- - - - -
dd312ca4 by Peter Meerwald-Stadler at 2021-11-30T13:07:07-05:00
Fix trivial typos in comments and documentation

- - - - -
3ba74083 by Peter Meerwald-Stadler at 2021-11-30T13:07:07-05:00
Document InvalidJWSOperation exception for JWSCore constructor

- - - - -
ddff0b0c by Simo Sorce at 2021-11-30T16:40:06-05:00
Cache pub/pri keys on retrieval

Pyca rightfully performs consistency checks when importing keys and
these operations are rather expensive. So cache keys once generated so
that repeated uses of the same JWK do not incur undue cost of reloading
the keys from scratch for each subsequent operation.

with a simple test by hand:
$ python
>>> from jwcrypto import jwk
>>> def test():
...     key = jwk.JWK.generate(kty='RSA', size=2048)
...     for i in range(1000):
...             k = key._get_private_key()
...
>>> import timeit

Before the patch:
>>> print(timeit.timeit("test()", setup="from __main__ import test", number=10))
35.80328264506534

After the patch:
>>> print(timeit.timeit("test()", setup="from __main__ import test", number=10))
0.9109518649056554

Resolves #243

Signed-off-by: Simo Sorce <simo at redhat.com>

- - - - -
ce1646c4 by Simo Sorce at 2021-11-30T16:47:45-05:00
Version 1.1

Signed-off-by: Simo Sorce <simo at redhat.com>

- - - - -
5e242d45 by Timo Aaltonen at 2022-03-29T10:30:14+03:00
Merge branch 'upstream'

- - - - -
6872a9e8 by Timo Aaltonen at 2022-03-29T10:32:19+03:00
version bump

- - - - -
5c1faed5 by Timo Aaltonen at 2022-03-29T10:33:55+03:00
releasing package python-jwcrypto version 1.1.0-1

- - - - -


13 changed files:

- .github/workflows/build.yml
- debian/changelog
- docs/source/conf.py
- jwcrypto/common.py
- jwcrypto/jwa.py
- jwcrypto/jwe.py
- jwcrypto/jwk.py
- jwcrypto/jws.py
- jwcrypto/jwt.py
- jwcrypto/tests.py
- setup.cfg
- setup.py
- tox.ini


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -18,6 +18,7 @@
                         "python-37",
                         "python-38",
                         "python-39",
+                        "python-310",
                         "doc",
                         "sphinx",
                         "lint",
@@ -48,27 +49,33 @@
                             "toxenv": "py39",
                             "arch": "x64",
                         },
+                        {
+                            "name": "python-310",
+                            "python": "3.10",
+                            "toxenv": "py310",
+                            "arch": "x64",
+                        },
                         {
                             "name": "doc",
-                            "python": "3.9",
+                            "python": "3.10",
                             "toxenv": "doc",
                             "arch": "x64",
                         },
                         {
                             "name": "sphinx",
-                            "python": "3.9",
+                            "python": "3.10",
                             "toxenv": "sphinx",
                             "arch": "x64",
                         },
                         {
                             "name": "lint",
-                            "python": "3.9",
+                            "python": "3.10",
                             "toxenv": "lint",
                             "arch": "x64",
                         },
                         {
                             "name": "pep8",
-                            "python": "3.9",
+                            "python": "3.10",
                             "toxenv": "pep8",
                             "arch": "x64",
                         },
@@ -84,6 +91,7 @@
                         "architecture": "${{ matrix.arch }}"
                     },
                 },
+                { "run": "sudo apt-get update" },
                 { "run": "sudo apt-get install cargo" },
                 { "run": "pip --version" },
                 { "run": "pip install tox" },


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+python-jwcrypto (1.1.0-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Timo Aaltonen <tjaalton at debian.org>  Tue, 29 Mar 2022 10:33:50 +0300
+
 python-jwcrypto (1.0.0-2) unstable; urgency=medium
 
   * rules: Don't remove .egg-info. (Closes: #995363)


=====================================
docs/source/conf.py
=====================================
@@ -46,16 +46,16 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'JWCrypto'
-copyright = u'2016-2018, JWCrypto Contributors'
+copyright = u'2016-2021, JWCrypto Contributors'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '1.0'
+version = '1.1'
 # The full version, including alpha/beta/rc tags.
-release = '1.0'
+release = '1.1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.


=====================================
jwcrypto/common.py
=====================================
@@ -111,7 +111,7 @@ class InvalidJWEKeyLength(JWException):
 class InvalidJWSERegOperation(JWException):
     """Invalid JWSE Header Registry Operation.
 
-    This exception is raised when there is an error in trying ot add a JW
+    This exception is raised when there is an error in trying to add a JW
     Signature or Encryption header to the Registry.
     """
 


=====================================
jwcrypto/jwa.py
=====================================
@@ -82,7 +82,7 @@ def _decode_int(n):
     return int(hexlify(n), 16)
 
 
-class _RawJWS(object):
+class _RawJWS:
 
     def sign(self, key, payload):
         raise NotImplementedError
@@ -333,7 +333,7 @@ class _None(_RawNone, JWAAlgorithm):
     algorithm_use = 'sig'
 
 
-class _RawKeyMgmt(object):
+class _RawKeyMgmt:
 
     def wrap(self, key, bitsize, cek, headers):
         raise NotImplementedError
@@ -353,7 +353,7 @@ class _RSA(_RawKeyMgmt):
         if key['kty'] != 'RSA':
             raise InvalidJWEKeyType('RSA', key['kty'])
 
-    # FIXME: get key size and insure > 2048 bits
+    # FIXME: get key size and ensure > 2048 bits
     def wrap(self, key, bitsize, cek, headers):
         self._check_key(key)
         if not cek:
@@ -364,7 +364,7 @@ class _RSA(_RawKeyMgmt):
 
     def unwrap(self, key, bitsize, ek, headers):
         self._check_key(key)
-        rk = key.get_op_key('decrypt')
+        rk = key.get_op_key('unwrapKey')
         cek = rk.decrypt(ek, self.padfn)
         if _bitsize(cek) != bitsize:
             raise InvalidJWEKeyLength(bitsize, _bitsize(cek))
@@ -581,7 +581,7 @@ class _Pbes2HsAesKw(_RawKeyMgmt):
 
     def _get_key(self, alg, key, p2s, p2c):
         if not isinstance(key, JWK):
-            # backwards compatiblity for old interface
+            # backwards compatibility for old interface
             if isinstance(key, bytes):
                 plain = key
             else:
@@ -836,7 +836,6 @@ class _EdDsa(_RawJWS, JWAAlgorithm):
     keysize = None
 
     def sign(self, key, payload):
-
         if key['crv'] in ['Ed25519', 'Ed448']:
             skey = key.get_op_key('sign')
             return skey.sign(payload)
@@ -849,7 +848,7 @@ class _EdDsa(_RawJWS, JWAAlgorithm):
         raise NotImplementedError
 
 
-class _RawJWE(object):
+class _RawJWE:
 
     def encrypt(self, k, a, m):
         raise NotImplementedError
@@ -1042,7 +1041,7 @@ class _A256Gcm(_AesGcm, JWAAlgorithm):
     algorithm_use = 'enc'
 
 
-class JWA(object):
+class JWA:
     """JWA Signing Algorithms.
 
     This class provides access to all JWA algorithms.
@@ -1101,7 +1100,7 @@ class JWA(object):
         try:
             return cls.instantiate_alg(name, use='sig')
         except KeyError:
-            raise InvalidJWAAlgorithm('%s is not a valid Signign algorithm'
+            raise InvalidJWAAlgorithm('%s is not a valid Signing algorithm'
                                       ' name' % name) from None
 
     @classmethod


=====================================
jwcrypto/jwe.py
=====================================
@@ -39,7 +39,7 @@ default_allowed_algs = [
     'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
     'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
     'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
-    # Content Encryption Algoritms
+    # Content Encryption Algorithms
     'A128CBC-HS256', 'A192CBC-HS384', 'A256CBC-HS512',
     'A128GCM', 'A192GCM', 'A256GCM']
 """Default allowed algorithms"""
@@ -70,7 +70,7 @@ InvalidJWEKeyType = common.InvalidJWEKeyType
 InvalidJWEOperation = common.InvalidJWEOperation
 
 
-class JWE(object):
+class JWE:
     """JSON Web Encryption object
 
     This object represent a JWE token.
@@ -91,7 +91,7 @@ class JWE(object):
         :param header_registry: Optional additions to the header registry
         """
         self._allowed_algs = None
-        self.objects = dict()
+        self.objects = {}
         self.plaintext = None
         self.header_registry = JWSEHeaderRegistry(JWEHeaderRegistry)
         if header_registry:
@@ -164,7 +164,7 @@ class JWE(object):
         return h1
 
     def _get_jose_header(self, header=None):
-        jh = dict()
+        jh = {}
         if 'protected' in self.objects:
             ph = json_decode(self.objects['protected'])
             jh = self._merge_headers(jh, ph)
@@ -229,7 +229,7 @@ class JWE(object):
         jh = self._get_jose_header(header)
         alg, enc = self._get_alg_enc_from_headers(jh)
 
-        rec = dict()
+        rec = {}
         if header:
             rec['header'] = header
 
@@ -250,8 +250,8 @@ class JWE(object):
         if 'recipients' in self.objects:
             self.objects['recipients'].append(rec)
         elif 'encrypted_key' in self.objects or 'header' in self.objects:
-            self.objects['recipients'] = list()
-            n = dict()
+            self.objects['recipients'] = []
+            n = {}
             if 'encrypted_key' in self.objects:
                 n['encrypted_key'] = self.objects.pop('encrypted_key')
             if 'header' in self.objects:
@@ -267,7 +267,7 @@ class JWE(object):
         :param compact(boolean): if True generates the compact
          representation, otherwise generates a standard JSON format.
 
-        :raises InvalidJWEOperation: if the object cannot serialized
+        :raises InvalidJWEOperation: if the object cannot be serialized
          with the compact representation and `compact` is True.
         :raises InvalidJWEOperation: if no recipients have been added
          to the object.
@@ -300,7 +300,7 @@ class JWE(object):
                 rec = self.objects
             if 'header' in rec:
                 # The AESGCMKW algorithm generates data (iv, tag) we put in the
-                # per-recipient unpotected header by default. Move it to the
+                # per-recipient unprotected header by default. Move it to the
                 # protected header and re-encrypt the payload, as the protected
                 # header is used as additional authenticated data.
                 h = json_decode(rec['header'])
@@ -329,9 +329,9 @@ class JWE(object):
             if 'aad' in obj:
                 enc['aad'] = base64url_encode(obj['aad'])
             if 'recipients' in obj:
-                enc['recipients'] = list()
+                enc['recipients'] = []
                 for rec in obj['recipients']:
-                    e = dict()
+                    e = {}
                     if 'encrypted_key' in rec:
                         e['encrypted_key'] = \
                             base64url_encode(rec['encrypted_key'])
@@ -361,7 +361,7 @@ class JWE(object):
         jh = self._get_jose_header(ppe.get('header', None))
 
         # TODO: allow caller to specify list of headers it understands
-        self._check_crit(jh.get('crit', dict()))
+        self._check_crit(jh.get('crit', {}))
 
         for hdr in jh:
             if hdr in self.header_registry:
@@ -407,7 +407,7 @@ class JWE(object):
 
         if 'ciphertext' not in self.objects:
             raise InvalidJWEOperation("No available ciphertext")
-        self.decryptlog = list()
+        self.decryptlog = []
 
         if 'recipients' in self.objects:
             for rec in self.objects['recipients']:
@@ -442,11 +442,11 @@ class JWE(object):
         :raises InvalidJWEOperation: if the decryption fails.
         """
 
-        self.objects = dict()
+        self.objects = {}
         self.plaintext = None
         self.cek = None
 
-        o = dict()
+        o = {}
         try:
             try:
                 djwe = json_decode(raw_jwe)
@@ -461,9 +461,9 @@ class JWE(object):
                 if 'aad' in djwe:
                     o['aad'] = base64url_decode(djwe['aad'])
                 if 'recipients' in djwe:
-                    o['recipients'] = list()
+                    o['recipients'] = []
                     for rec in djwe['recipients']:
-                        e = dict()
+                        e = {}
                         if 'encrypted_key' in rec:
                             e['encrypted_key'] = \
                                 base64url_decode(rec['encrypted_key'])


=====================================
jwcrypto/jwk.py
=====================================
@@ -18,7 +18,7 @@ from jwcrypto.common import base64url_decode, base64url_encode
 from jwcrypto.common import json_decode, json_encode
 
 
-class UnimplementedOKPCurveKey(object):
+class UnimplementedOKPCurveKey:
     @classmethod
     def generate(cls):
         raise NotImplementedError
@@ -243,7 +243,7 @@ class InvalidJWKOperation(JWException):
             op = JWKOperationsRegistry[self.op]
         else:
             op = 'Unknown(%s)' % self.op
-        valid = list()
+        valid = []
         for v in self.values:
             if v in list(JWKOperationsRegistry.keys()):
                 valid.append(JWKOperationsRegistry[v])
@@ -265,7 +265,7 @@ class InvalidJWKValue(JWException):
 class JWK(dict):
     """JSON Web Key object
 
-    This object represent a Key.
+    This object represents a Key.
     It must be instantiated by using the standard defined key/value pairs
     as arguments of the initialization function.
     """
@@ -292,7 +292,7 @@ class JWK(dict):
          * OKP: crv(str) (one of Ed25519, Ed448, X25519, X448)
 
         Deprecated:
-        Alternatively if the 'generate' parameter is provided, with a
+        Alternatively if the 'generate' parameter is provided with a
         valid key type as value then a new key will be generated according
         to the defaults or provided key strength options (type specific)..
 
@@ -301,6 +301,8 @@ class JWK(dict):
             are provided.
         """
         super(JWK, self).__init__()
+        self._cache_pub_k = None
+        self._cache_pri_k = None
 
         if 'generate' in kwargs:
             self.generate_key(**kwargs)
@@ -483,8 +485,10 @@ class JWK(dict):
         self.import_key(**params)
 
     def import_key(self, **kwargs):
-        newkey = dict()
+        newkey = {}
         key_vals = 0
+        self._cache_pub_k = None
+        self._cache_pri_k = None
 
         names = list(kwargs.keys())
 
@@ -591,7 +595,7 @@ class JWK(dict):
         """
         if private_key is True:
             # Use _export_all for backwards compatibility, as this
-            # function allows to export symmetrict keys too
+            # function allows to export symmetric keys too
             return self._export_all(as_dict)
 
         return self.export_public(as_dict)
@@ -624,7 +628,7 @@ class JWK(dict):
         return pub
 
     def _export_all(self, as_dict=False):
-        d = dict()
+        d = {}
         d.update(self)
         if as_dict is True:
             return d
@@ -704,7 +708,7 @@ class JWK(dict):
         :param arg: an optional curve name
 
         :raises InvalidJWKType: the key is not an EC or OKP key.
-        :raises InvalidJWKValue: if the curve names is invalid.
+        :raises InvalidJWKValue: if the curve name is invalid.
         """
         crv = self.get('crv')
         if self.get('kty') not in ['EC', 'OKP']:
@@ -730,57 +734,93 @@ class JWK(dict):
     def _decode_int(self, n):
         return int(hexlify(base64url_decode(n)), 16)
 
-    def _rsa_pub(self):
+    def _rsa_pub_n(self):
         e = self._decode_int(self.get('e'))
         n = self._decode_int(self.get('n'))
         return rsa.RSAPublicNumbers(e, n)
 
-    def _rsa_pri(self):
+    def _rsa_pri_n(self):
         p = self._decode_int(self.get('p'))
         q = self._decode_int(self.get('q'))
         d = self._decode_int(self.get('d'))
         dp = self._decode_int(self.get('dp'))
         dq = self._decode_int(self.get('dq'))
         qi = self._decode_int(self.get('qi'))
-        return rsa.RSAPrivateNumbers(p, q, d, dp, dq, qi, self._rsa_pub())
+        return rsa.RSAPrivateNumbers(p, q, d, dp, dq, qi, self._rsa_pub_n())
 
-    def _ec_pub(self, curve):
+    def _rsa_pub(self):
+        k = self._cache_pub_k
+        if k is None:
+            k = self._rsa_pub_n().public_key(default_backend())
+            self._cache_pub_k = k
+        return k
+
+    def _rsa_pri(self):
+        k = self._cache_pri_k
+        if k is None:
+            k = self._rsa_pri_n().private_key(default_backend())
+            self._cache_pri_k = k
+        return k
+
+    def _ec_pub_n(self, curve):
         x = self._decode_int(self.get('x'))
         y = self._decode_int(self.get('y'))
         return ec.EllipticCurvePublicNumbers(x, y, self.get_curve(curve))
 
-    def _ec_pri(self, curve):
+    def _ec_pri_n(self, curve):
         d = self._decode_int(self.get('d'))
-        return ec.EllipticCurvePrivateNumbers(d, self._ec_pub(curve))
+        return ec.EllipticCurvePrivateNumbers(d, self._ec_pub_n(curve))
+
+    def _ec_pub(self, curve):
+        k = self._cache_pub_k
+        if k is None:
+            k = self._ec_pub_n(curve).public_key(default_backend())
+            self._cache_pub_k = k
+        return k
+
+    def _ec_pri(self, curve):
+        k = self._cache_pri_k
+        if k is None:
+            k = self._ec_pri_n(curve).private_key(default_backend())
+            self._cache_pri_k = k
+        return k
 
     def _okp_pub(self):
-        crv = self.get('crv')
-        try:
-            pubkey = _OKP_CURVES_TABLE[crv].pubkey
-        except KeyError as e:
-            raise InvalidJWKValue('Unknown curve "%s"' % crv) from e
+        k = self._cache_pub_k
+        if k is None:
+            crv = self.get('crv')
+            try:
+                pubkey = _OKP_CURVES_TABLE[crv].pubkey
+            except KeyError as e:
+                raise InvalidJWKValue('Unknown curve "%s"' % crv) from e
 
-        x = base64url_decode(self.get('x'))
-        return pubkey.from_public_bytes(x)
+            x = base64url_decode(self.get('x'))
+            k = pubkey.from_public_bytes(x)
+            self._cache_pub_k = k
+        return k
 
     def _okp_pri(self):
-        crv = self.get('crv')
-        try:
-            privkey = _OKP_CURVES_TABLE[crv].privkey
-        except KeyError as e:
-            raise InvalidJWKValue('Unknown curve "%s"' % crv) from e
+        k = self._cache_pri_k
+        if k is None:
+            crv = self.get('crv')
+            try:
+                privkey = _OKP_CURVES_TABLE[crv].privkey
+            except KeyError as e:
+                raise InvalidJWKValue('Unknown curve "%s"' % crv) from e
 
-        d = base64url_decode(self.get('d'))
-        return privkey.from_private_bytes(d)
+            d = base64url_decode(self.get('d'))
+            k = privkey.from_private_bytes(d)
+            self._cache_pri_k = k
+        return k
 
     def _get_public_key(self, arg=None):
         ktype = self.get('kty')
         if ktype == 'oct':
             return self.get('k')
         elif ktype == 'RSA':
-            return self._rsa_pub().public_key(default_backend())
+            return self._rsa_pub()
         elif ktype == 'EC':
-            return self._ec_pub(arg).public_key(default_backend())
+            return self._ec_pub(arg)
         elif ktype == 'OKP':
             return self._okp_pub()
         else:
@@ -791,9 +831,9 @@ class JWK(dict):
         if ktype == 'oct':
             return self.get('k')
         elif ktype == 'RSA':
-            return self._rsa_pri().private_key(default_backend())
+            return self._rsa_pri()
         elif ktype == 'EC':
-            return self._ec_pri(arg).private_key(default_backend())
+            return self._ec_pri(arg)
         elif ktype == 'OKP':
             return self._okp_pri()
         else:
@@ -807,7 +847,7 @@ class JWK(dict):
         :param operation: The requested operation.
          The valid set of operations is available in the
          :data:`JWKOperationsRegistry` registry.
-        :param arg: an optional, context specific, argument
+        :param arg: An optional, context specific, argument.
          For example a curve name.
 
         :raises InvalidJWKOperation: if the operation is unknown or
@@ -969,6 +1009,9 @@ class JWK(dict):
 
         # Check if item is a key value and verify its format
         if item in list(JWKValuesRegistry[kty].keys()):
+            # Invalidate cached keys if any
+            self._cache_pub_k = None
+            self._cache_pri_k = None
             if JWKValuesRegistry[kty][item].type == ParmType.b64:
                 try:
                     v = base64url_decode(value)
@@ -1028,6 +1071,12 @@ class JWK(dict):
                 if self.get(name) is not None:
                     raise KeyError("Cannot remove 'kty', values present")
 
+        kty = self.get('kty')
+        if kty is not None and item in list(JWKValuesRegistry[kty].keys()):
+            # Invalidate cached keys if any
+            self._cache_pub_k = None
+            self._cache_pri_k = None
+
         super(JWK, self).__delitem__(item)
 
     def __eq__(self, other):
@@ -1082,7 +1131,7 @@ class JWK(dict):
 
     # Prevent accidental disclosure of key material via repr()
     def __repr__(self):
-        repr_dict = dict()
+        repr_dict = {}
         repr_dict['kid'] = self.get('kid', 'Missing Key ID')
         repr_dict['thumbprint'] = self.thumbprint()
         return json_encode(repr_dict)
@@ -1147,10 +1196,10 @@ class JWKSet(dict):
         :param as_dict(bool): Whether to return a dict instead of
                               a JSON object
         """
-        exp_dict = dict()
+        exp_dict = {}
         for k, v in self.items():
             if k == 'keys':
-                keys = list()
+                keys = []
                 for jwk in v:
                     keys.append(jwk.export(private_keys, as_dict=True))
                 v = keys
@@ -1199,10 +1248,10 @@ class JWKSet(dict):
         return None
 
     def __repr__(self):
-        repr_dict = dict()
+        repr_dict = {}
         for k, v in self.items():
             if k == 'keys':
-                keys = list()
+                keys = []
                 for jwk in v:
                     keys.append(repr(jwk))
                 v = keys


=====================================
jwcrypto/jws.py
=====================================
@@ -85,7 +85,7 @@ class InvalidJWSOperation(JWException):
         super(InvalidJWSOperation, self).__init__(msg)
 
 
-class JWSCore(object):
+class JWSCore:
     """The inner JWS Core object.
 
     This object SHOULD NOT be used directly, the JWS object should be
@@ -108,6 +108,7 @@ class JWSCore(object):
         :raises ValueError: if the key is not a :class:`JWK` object
         :raises InvalidJWAAlgorithm: if the algorithm is not valid, is
             unknown or otherwise not yet implemented.
+        :raises InvalidJWSOperation: if the algorithm is not allowed.
         """
         self.alg = alg
         self.engine = self._jwa(alg, algs)
@@ -124,7 +125,7 @@ class JWSCore(object):
 
             self.protected = base64url_encode(header.encode('utf-8'))
         else:
-            self.header = dict()
+            self.header = {}
             self.protected = ''
         self.payload = payload
 
@@ -167,7 +168,7 @@ class JWSCore(object):
         return True
 
 
-class JWS(object):
+class JWS:
     """JSON Web Signature object
 
     This object represent a JWS token.
@@ -179,7 +180,7 @@ class JWS(object):
         :param payload(bytes): An arbitrary value (optional).
         :param header_registry: Optional additions to the header registry
         """
-        self.objects = dict()
+        self.objects = {}
         self.objects['payload'] = payload
         self.verifylog = None
         self._allowed_algs = None
@@ -236,7 +237,7 @@ class JWS(object):
             if hn is None:
                 continue
             if header is None:
-                header = dict()
+                header = {}
             for h in list(hn.keys()):
                 if h in self.header_registry:
                     if self.header_registry[h].mustprotect:
@@ -253,7 +254,7 @@ class JWS(object):
 
     # TODO: support selecting key with 'kid' and passing in multiple keys
     def _verify(self, alg, key, payload, signature, protected, header=None):
-        p = dict()
+        p = {}
         # verify it is a valid JSON object and decode
         if protected is not None:
             p = json_decode(protected)
@@ -292,13 +293,13 @@ class JWS(object):
         """Verifies a JWS token.
 
         :param key: The (:class:`jwcrypto.jwk.JWK`) verification key.
-        :param alg: The signing algorithm (optional). usually the algorithm
+        :param alg: The signing algorithm (optional). Usually the algorithm
             is known as it is provided with the JOSE Headers of the token.
 
         :raises InvalidJWSSignature: if the verification fails.
         """
 
-        self.verifylog = list()
+        self.verifylog = []
         self.objects['valid'] = False
         obj = self.objects
         if 'signature' in obj:
@@ -332,8 +333,7 @@ class JWS(object):
                                       'signatures' + repr(self.verifylog))
 
     def _deserialize_signature(self, s):
-        o = dict()
-        o['signature'] = base64url_decode(str(s['signature']))
+        o = {'signature': base64url_decode(str(s['signature']))}
         if 'protected' in s:
             p = base64url_decode(str(s['protected']))
             o['protected'] = p.decode('utf-8')
@@ -369,19 +369,19 @@ class JWS(object):
         :param key: A (:class:`jwcrypto.jwk.JWK`) verification key (optional).
          If a key is provided a verification step will be attempted after
          the object is successfully deserialized.
-        :param alg: The signing algorithm (optional). usually the algorithm
+        :param alg: The signing algorithm (optional). Usually the algorithm
          is known as it is provided with the JOSE Headers of the token.
 
         :raises InvalidJWSObject: if the raw object is an invalid JWS token.
         :raises InvalidJWSSignature: if the verification fails.
         """
-        self.objects = dict()
-        o = dict()
+        self.objects = {}
+        o = {}
         try:
             try:
                 djws = json_decode(raw_jws)
                 if 'signatures' in djws:
-                    o['signatures'] = list()
+                    o['signatures'] = []
                     for s in djws['signatures']:
                         os = self._deserialize_signature(s)
                         o['signatures'].append(os)
@@ -437,7 +437,7 @@ class JWS(object):
 
         b64 = True
 
-        p = dict()
+        p = {}
         if protected:
             if isinstance(protected, dict):
                 p = protected
@@ -482,20 +482,20 @@ class JWS(object):
         )
         sig = c.sign()
 
-        o = dict()
-        o['signature'] = base64url_decode(sig['signature'])
+        o = {
+            'signature': base64url_decode(sig['signature']),
+            'valid': True,
+        }
         if protected:
             o['protected'] = protected
         if header:
             o['header'] = h
-        o['valid'] = True
 
         if 'signatures' in self.objects:
             self.objects['signatures'].append(o)
         elif 'signature' in self.objects:
-            self.objects['signatures'] = list()
-            n = dict()
-            n['signature'] = self.objects.pop('signature')
+            self.objects['signatures'] = []
+            n = {'signature': self.objects.pop('signature')}
             if 'protected' in self.objects:
                 n['protected'] = self.objects.pop('protected')
             if 'header' in self.objects:
@@ -554,7 +554,7 @@ class JWS(object):
                              base64url_encode(self.objects['signature'])])
         else:
             obj = self.objects
-            sig = dict()
+            sig = {}
             payload = self.objects.get('payload', '')
             if self.objects.get('b64', True):
                 sig['payload'] = base64url_encode(payload)
@@ -569,7 +569,7 @@ class JWS(object):
                 if 'header' in obj:
                     sig['header'] = obj['header']
             elif 'signatures' in obj:
-                sig['signatures'] = list()
+                sig['signatures'] = []
                 for o in obj['signatures']:
                     if not o.get('valid', False):
                         continue
@@ -602,16 +602,16 @@ class JWS(object):
                 p = json_decode(obj['protected'])
             else:
                 p = None
-            return self._merge_check_headers(p, obj.get('header', dict()))
+            return self._merge_check_headers(p, obj.get('header', {}))
         elif 'signatures' in self.objects:
-            jhl = list()
+            jhl = []
             for o in obj['signatures']:
-                jh = dict()
+                jh = {}
                 if 'protected' in o:
                     p = json_decode(o['protected'])
                 else:
                     p = None
-                jh = self._merge_check_headers(p, o.get('header', dict()))
+                jh = self._merge_check_headers(p, o.get('header', {}))
                 jhl.append(jh)
             return jhl
         else:


=====================================
jwcrypto/jwt.py
=====================================
@@ -21,9 +21,9 @@ JWTClaimsRegistry = {'iss': 'Issuer',
 
 
 class JWTExpired(JWException):
-    """Json Web Token is expired.
+    """JSON Web Token is expired.
 
-    This exception is raised when a token is expired accoring to its claims.
+    This exception is raised when a token is expired according to its claims.
     """
 
     def __init__(self, message=None, exception=None):
@@ -38,7 +38,7 @@ class JWTExpired(JWException):
 
 
 class JWTNotYetValid(JWException):
-    """Json Web Token is not yet valid.
+    """JSON Web Token is not yet valid.
 
     This exception is raised when a token is not valid yet according to its
     claims.
@@ -56,7 +56,7 @@ class JWTNotYetValid(JWException):
 
 
 class JWTMissingClaim(JWException):
-    """Json Web Token claim is invalid.
+    """JSON Web Token claim is invalid.
 
     This exception is raised when a claim does not match the expected value.
     """
@@ -73,7 +73,7 @@ class JWTMissingClaim(JWException):
 
 
 class JWTInvalidClaimValue(JWException):
-    """Json Web Token claim is invalid.
+    """JSON Web Token claim is invalid.
 
     This exception is raised when a claim does not match the expected value.
     """
@@ -90,7 +90,7 @@ class JWTInvalidClaimValue(JWException):
 
 
 class JWTInvalidClaimFormat(JWException):
-    """Json Web Token claim format is invalid.
+    """JSON Web Token claim format is invalid.
 
     This exception is raised when a claim is not in a valid format.
     """
@@ -108,7 +108,7 @@ class JWTInvalidClaimFormat(JWException):
 
 # deprecated and not used anymore
 class JWTMissingKeyID(JWException):
-    """Json Web Token is missing key id.
+    """JSON Web Token is missing key id.
 
     This exception is raised when trying to decode a JWT with a key set
     that does not have a kid value in its header.
@@ -126,7 +126,7 @@ class JWTMissingKeyID(JWException):
 
 
 class JWTMissingKey(JWException):
-    """Json Web Token is using a key not in the key set.
+    """JSON Web Token is using a key not in the key set.
 
     This exception is raised if the key that was used is not available
     in the passed key set.
@@ -143,7 +143,7 @@ class JWTMissingKey(JWException):
         super(JWTMissingKey, self).__init__(msg)
 
 
-class JWT(object):
+class JWT:
     """JSON Web token object
 
     This object represent a generic token.
@@ -195,6 +195,8 @@ class JWT(object):
             self._reg_claims = default_claims
 
         if check_claims is not None:
+            if check_claims is not False:
+                self._check_check_claims(check_claims)
             self._check_claims = check_claims
 
         if claims is not None:
@@ -302,13 +304,13 @@ class JWT(object):
         self._add_jti_claim(claims)
 
     def _check_string_claim(self, name, claims):
-        if name not in claims:
+        if name not in claims or claims[name] is None:
             return
         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:
+        if name not in claims or claims[name] is None:
             return
         if isinstance(claims[name], list):
             if any(not isinstance(claim, str) for claim in claims):
@@ -319,7 +321,7 @@ class JWT(object):
                 "Claim %s is not a StringOrURI type" % (name, ))
 
     def _check_integer_claim(self, name, claims):
-        if name not in claims:
+        if name not in claims or claims[name] is None:
             return
         try:
             int(claims[name])
@@ -353,6 +355,16 @@ class JWT(object):
             if 'nbf' in claims:
                 self._check_nbf(claims['nbf'], time.time(), self._leeway)
 
+    def _check_check_claims(self, check_claims):
+        self._check_string_claim('iss', check_claims)
+        self._check_string_claim('sub', check_claims)
+        self._check_string_claim('aud', check_claims)
+        self._check_integer_claim('exp', check_claims)
+        self._check_integer_claim('nbf', check_claims)
+        self._check_integer_claim('iat', check_claims)
+        self._check_string_claim('jti', check_claims)
+        self._check_string_claim('typ', check_claims)
+
     def _check_provided_claims(self):
         # check_claims can be set to False to skip any check
         if self._check_claims is False:
@@ -484,7 +496,7 @@ class JWT(object):
         if self._algs:
             self.token.allowed_algs = self._algs
 
-        self.deserializelog = list()
+        self.deserializelog = []
         # now deserialize and also decrypt/verify (or raise) if we
         # have a key
         if key is None:


=====================================
jwcrypto/tests.py
=====================================
@@ -237,7 +237,7 @@ zl9HYIMxATFyqSiD9jsx
 -----END CERTIFICATE-----
 """
 
-PublicCertThumbprint = u'7KITkGJF74IZ9NKVvHfuJILbuIZny6j-roaNjB1vgiA'
+PublicCertThumbprint = '7KITkGJF74IZ9NKVvHfuJILbuIZny6j-roaNjB1vgiA'
 
 # RFC 8037 - A.2
 PublicKeys_EdDsa = {
@@ -392,7 +392,7 @@ class TestJWK(unittest.TestCase):
         self.assertEqual(kec1.get_op_key('verify').public_numbers().x,
                          kec2.get_op_key('verify').public_numbers().x)
         self.assertRaises(jwk.InvalidJWKValue,
-                          jwk.JWK.from_pyca, dict())
+                          jwk.JWK.from_pyca, {})
 
     def test_jwk_from_json(self):
         k = jwk.JWK.generate(kty='oct', size=256)
@@ -501,7 +501,7 @@ class TestJWK(unittest.TestCase):
         key = jwk.JWK.from_pem(RSAPrivatePEM, password=RSAPrivatePassword)
         k = key.export_private(as_dict=True)
         self.assertEqual(k['kid'],
-                         u'x31vrbZceU2qOPLtrUwPkLa3PNakMn9tOsq_ntFVrJc')
+                         'x31vrbZceU2qOPLtrUwPkLa3PNakMn9tOsq_ntFVrJc')
         keyset = jwk.JWKSet.from_json(json_encode(PrivateKeys))
         ks = keyset.export(as_dict=True)
         self.assertTrue('keys' in ks)
@@ -1581,6 +1581,15 @@ class TestJWT(unittest.TestCase):
         # the oct key before hitting the ES one
         jwt.JWT(jwt=token, key=ks)
 
+    def test_Issue_239(self):
+        claims = {"aud": "www.example.com"}
+        check_claims = {"aud": ["www.example.com", "account"]}
+        key = jwk.JWK(generate='oct', size=256)
+        token = jwt.JWT(header={"alg": "HS256"}, claims=claims)
+        token.make_signed_token(key)
+        self.assertRaises(jwt.JWTInvalidClaimFormat, jwt.JWT, key=key,
+                          jwt=token.serialize(), check_claims=check_claims)
+
 
 class ConformanceTests(unittest.TestCase):
 


=====================================
setup.cfg
=====================================
@@ -1,6 +1,3 @@
-[bdist_wheel]
-universal = 1
-
 [aliases]
 packages = clean --all egg_info bdist_wheel sdist --format=zip sdist --format=gztar
 release = packages register upload


=====================================
setup.py
=====================================
@@ -6,7 +6,7 @@ from setuptools import setup
 
 setup(
     name = 'jwcrypto',
-    version = '1.0',
+    version = '1.1',
     license = 'LGPLv3+',
     maintainer = 'JWCrypto Project Contributors',
     maintainer_email = 'simo at redhat.com',
@@ -18,6 +18,7 @@ setup(
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
         'Intended Audience :: Developers',
         'Topic :: Security',
         'Topic :: Software Development :: Libraries :: Python Modules'
@@ -27,4 +28,5 @@ setup(
         'cryptography >= 2.3',
         'deprecated',
     ],
+    python_requires = '>= 3.6',
 )


=====================================
tox.ini
=====================================
@@ -1,5 +1,5 @@
 [tox]
-envlist = lint,py36,py37,py38,py39,pep8,doc,sphinx
+envlist = lint,py36,py37,py38,py39,py310,pep8,doc,sphinx
 skip_missing_interpreters = true
 
 [testenv]
@@ -15,7 +15,7 @@ commands =
     {envpython} -m coverage report -m
 
 [testenv:lint]
-basepython = python3.9
+basepython = python3.10
 deps =
     pylint
 #sitepackages = True
@@ -23,7 +23,7 @@ commands =
     {envpython} -m pylint -d c,r,i,W0613 -r n -f colorized --notes= --disable=star-args ./jwcrypto
 
 [testenv:pep8]
-basepython = python3
+basepython = python3.10
 deps =
     flake8
     flake8-import-order
@@ -36,13 +36,13 @@ deps =
     doc8
     docutils
     markdown
-basepython = python3
+basepython = python3.10
 commands =
     doc8 --allow-long-titles README.md
     markdown_py README.md -f {toxworkdir}/README.md.html
 
 [testenv:sphinx]
-basepython = python3
+basepython = python3.10
 changedir = docs/source
 deps =
     sphinx



View it on GitLab: https://salsa.debian.org/freeipa-team/python-jwcrypto/-/compare/d4e95646f7a743a3682ed86ef57a21371ada16c3...5c1faed5ab1024d163b3d71ce8ca919a67caf118

-- 
View it on GitLab: https://salsa.debian.org/freeipa-team/python-jwcrypto/-/compare/d4e95646f7a743a3682ed86ef57a21371ada16c3...5c1faed5ab1024d163b3d71ce8ca919a67caf118
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-freeipa-devel/attachments/20220329/fa591133/attachment-0001.htm>


More information about the Pkg-freeipa-devel mailing list