[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