Hello community,
here is the log from the commit of package python-jwcrypto for
openSUSE:Leap:15.2 checked in at 2020-04-20 12:55:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python-jwcrypto (Old)
and /work/SRC/openSUSE:Leap:15.2/.python-jwcrypto.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jwcrypto"
Mon Apr 20 12:55:47 2020 rev:12 rq:795595 version:0.7
Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/python-jwcrypto/python-jwcrypto.changes
2020-03-09 18:07:09.148870741 +0100
+++
/work/SRC/openSUSE:Leap:15.2/.python-jwcrypto.new.2738/python-jwcrypto.changes
2020-04-20 12:55:56.960771854 +0200
@@ -1,0 +2,15 @@
+Mon Mar 30 08:15:59 UTC 2020 - Michael Ströder <[email protected]>
+
+- update to upstream release 0.7.0
+ * Allow to use JWKSet on a JWT with no KID
+ * Fixed JWE jose_header
+ * Added JWE/JWS custom registry header implementation
+ * RFC 8037 - Support for Ed25519, Ed448
+ * Stricter OKP key generation parms check
+ * Add X25519/X448 support
+ * Simplify internal code curve selection
+ * Fix encoding length of EC keys Coordinates
+ * Add the ability to verify 'none' signatures
+ * Import ABC from collections.abc instead of collections for Python 3.9
compatibility
+
+-------------------------------------------------------------------
Old:
----
jwcrypto-0.6.0.tar.gz
New:
----
jwcrypto-0.7.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jwcrypto.spec ++++++
--- /var/tmp/diff_new_pack.ErxoKS/_old 2020-04-20 12:55:57.328772431 +0200
+++ /var/tmp/diff_new_pack.ErxoKS/_new 2020-04-20 12:55:57.332772437 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-jwcrypto
-Version: 0.6.0
+Version: 0.7
Release: 0
Summary: Python module package implementing JOSE Web standards
License: LGPL-3.0-only
++++++ jwcrypto-0.6.0.tar.gz -> jwcrypto-0.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/PKG-INFO new/jwcrypto-0.7/PKG-INFO
--- old/jwcrypto-0.6.0/PKG-INFO 2018-11-05 16:18:50.000000000 +0100
+++ new/jwcrypto-0.7/PKG-INFO 2020-02-19 18:17:34.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: jwcrypto
-Version: 0.6.0
+Version: 0.7
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/common.py
new/jwcrypto-0.7/jwcrypto/common.py
--- old/jwcrypto-0.6.0/jwcrypto/common.py 2018-06-27 08:24:18.000000000
+0200
+++ new/jwcrypto-0.7/jwcrypto/common.py 2020-02-19 17:12:20.000000000 +0100
@@ -1,8 +1,13 @@
# Copyright (C) 2015 JWCrypto Project Contributors - see LICENSE file
+import copy
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
# Padding stripping versions as described in
# RFC 7515 Appendix C
@@ -56,7 +61,7 @@
"""Invalid CEK Key Length.
This exception is raised when a Content Encryption Key does not match
- the required lenght.
+ the required length.
"""
def __init__(self, expected, obtained):
@@ -98,9 +103,91 @@
"""Invalid JWE Key Length.
This exception is raised when the provided JWK Key does not match
- the lenght required by the sepcified algorithm.
+ the length required by the sepcified algorithm.
"""
def __init__(self, expected, obtained):
- msg = 'Expected key of lenght %d, got %d' % (expected, obtained)
+ msg = 'Expected key of length %d, got %d' % (expected, obtained)
super(InvalidJWEKeyLength, self).__init__(msg)
+
+
+class InvalidJWSERegOperation(JWException):
+ """Invalid JWSE Header Registry Operation.
+
+ This exception is raised when there is an error in trying ot add a JW
+ Signature or Encryption header to the Registry.
+ """
+
+ def __init__(self, message=None, exception=None):
+ msg = None
+ if message:
+ msg = message
+ else:
+ msg = 'Unknown Operation Failure'
+ if exception:
+ msg += ' {%s}' % repr(exception)
+ super(InvalidJWSERegOperation, self).__init__(msg)
+
+
+# JWSE Header Registry definitions
+
+# RFC 7515 - 9.1: JSON Web Signature and Encryption Header Parameters Registry
+# HeaderParameters are for both JWS and JWE
+JWSEHeaderParameter = namedtuple('Parameter',
+ 'description mustprotect supported check_fn')
+
+
+class JWSEHeaderRegistry(MutableMapping):
+ def __init__(self, init_registry=None):
+ if init_registry:
+ if isinstance(init_registry, dict):
+ self._registry = copy.deepcopy(init_registry)
+ else:
+ raise InvalidJWSERegOperation('Unknown input type')
+ else:
+ self._registry = {}
+
+ MutableMapping.__init__(self)
+
+ def check_header(self, h, value):
+ if h not in self._registry:
+ raise InvalidJWSERegOperation('No header "%s" found in registry'
+ % h)
+
+ param = self._registry[h]
+ if param.check_fn is None:
+ return True
+ else:
+ return param.check_fn(value)
+
+ def __getitem__(self, key):
+ return self._registry.__getitem__(key)
+
+ def __iter__(self):
+ return self._registry.__iter__()
+
+ def __delitem__(self, key):
+ if self._registry[key].mustprotect or \
+ self._registry[key].supported:
+ raise InvalidJWSERegOperation('Unable to delete protected or '
+ 'supported field')
+ else:
+ self._registry.__delitem__(key)
+
+ def __setitem__(self, h, jwse_header_param):
+ # Check if a header is not supported
+ if h in self._registry:
+ p = self._registry[h]
+ if p.supported:
+ raise InvalidJWSERegOperation('Supported header already exists'
+ ' in registry')
+ elif p.mustprotect and not jwse_header_param.mustprotect:
+ raise InvalidJWSERegOperation('Header specified should be'
+ 'a protected header')
+ else:
+ del self._registry[h]
+
+ self._registry[h] = jwse_header_param
+
+ def __len__(self):
+ return self._registry.__len__()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/jwa.py
new/jwcrypto-0.7/jwcrypto/jwa.py
--- old/jwcrypto-0.6.0/jwcrypto/jwa.py 2018-11-05 16:04:15.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto/jwa.py 2020-02-19 17:12:20.000000000 +0100
@@ -70,7 +70,7 @@
def _randombits(x):
if x % 8 != 0:
- raise ValueError("lenght must be a multiple of 8")
+ raise ValueError("length must be a multiple of 8")
return os.urandom(_inbytes(x))
@@ -161,7 +161,8 @@
return ''
def verify(self, key, payload, signature):
- raise InvalidSignature('The "none" signature cannot be verified')
+ if key.key_type != 'oct' or key.get_op_key() != '':
+ raise InvalidSignature('The "none" signature cannot be verified')
class _HS256(_RawHMAC, JWAAlgorithm):
@@ -693,8 +694,12 @@
def _check_key(self, key):
if not isinstance(key, JWK):
raise ValueError('key is not a JWK object')
- if key.key_type != 'EC':
- raise InvalidJWEKeyType('EC', key.key_type)
+ if key.key_type not in ['EC', 'OKP']:
+ raise InvalidJWEKeyType('EC or OKP', key.key_type)
+ if key.key_type == 'OKP':
+ if key.key_curve not in ['X25519', 'X448']:
+ raise InvalidJWEKeyType('X25519 or X448',
+ key.key_curve)
def _derive(self, privkey, pubkey, alg, bitsize, headers):
# OtherInfo is defined in NIST SP 56A 5.8.1.2.1
@@ -718,7 +723,13 @@
# no SuppPrivInfo
- shared_key = privkey.exchange(ec.ECDH(), pubkey)
+ # Shared Key generation
+ if isinstance(privkey, ec.EllipticCurvePrivateKey):
+ shared_key = privkey.exchange(ec.ECDH(), pubkey)
+ else:
+ # X25519/X448
+ shared_key = privkey.exchange(pubkey)
+
ckdf = ConcatKDFHash(algorithm=hashes.SHA256(),
length=_inbytes(bitsize),
otherinfo=otherinfo,
@@ -802,6 +813,28 @@
algorithm_use = 'kex'
+class _EdDsa(_RawJWS, JWAAlgorithm):
+
+ name = 'EdDSA'
+ description = 'EdDSA using Ed25519 or Ed448 algorithms'
+ algorithm_usage_location = 'alg'
+ algorithm_use = 'sig'
+ keysize = None
+
+ def sign(self, key, payload):
+
+ if key.key_curve in ['Ed25519', 'Ed448']:
+ skey = key.get_op_key('sign')
+ return skey.sign(payload)
+ raise NotImplementedError
+
+ def verify(self, key, payload, signature):
+ if key.key_curve in ['Ed25519', 'Ed448']:
+ pkey = key.get_op_key('verify')
+ return pkey.verify(signature, payload)
+ raise NotImplementedError
+
+
class _RawJWE(object):
def encrypt(self, k, a, m):
@@ -1026,6 +1059,7 @@
'ECDH-ES+A128KW': _EcdhEsAes128Kw,
'ECDH-ES+A192KW': _EcdhEsAes192Kw,
'ECDH-ES+A256KW': _EcdhEsAes256Kw,
+ 'EdDSA': _EdDsa,
'A128GCMKW': _A128GcmKw,
'A192GCMKW': _A192GcmKw,
'A256GCMKW': _A256GcmKw,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/jwe.py
new/jwcrypto-0.7/jwcrypto/jwe.py
--- old/jwcrypto-0.6.0/jwcrypto/jwe.py 2018-11-05 16:04:15.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto/jwe.py 2019-05-27 14:17:28.000000000 +0200
@@ -4,6 +4,7 @@
from jwcrypto import common
from jwcrypto.common import JWException
+from jwcrypto.common import JWSEHeaderParameter, JWSEHeaderRegistry
from jwcrypto.common import base64url_decode, base64url_encode
from jwcrypto.common import json_decode, json_encode
from jwcrypto.jwa import JWA
@@ -11,20 +12,23 @@
# RFC 7516 - 4.1
# name: (description, supported?)
-JWEHeaderRegistry = {'alg': ('Algorithm', True),
- 'enc': ('Encryption Algorithm', True),
- 'zip': ('Compression Algorithm', True),
- 'jku': ('JWK Set URL', False),
- 'jwk': ('JSON Web Key', False),
- 'kid': ('Key ID', True),
- 'x5u': ('X.509 URL', False),
- 'x5c': ('X.509 Certificate Chain', False),
- 'x5t': ('X.509 Certificate SHA-1 Thumbprint', False),
- 'x5t#S256': ('X.509 Certificate SHA-256 Thumbprint',
- False),
- 'typ': ('Type', True),
- 'cty': ('Content Type', True),
- 'crit': ('Critical', True)}
+JWEHeaderRegistry = {
+ 'alg': JWSEHeaderParameter('Algorithm', False, True, None),
+ 'enc': JWSEHeaderParameter('Encryption Algorithm', False, True, None),
+ 'zip': JWSEHeaderParameter('Compression Algorithm', False, True, None),
+ 'jku': JWSEHeaderParameter('JWK Set URL', False, False, None),
+ 'jwk': JWSEHeaderParameter('JSON Web Key', False, False, None),
+ 'kid': JWSEHeaderParameter('Key ID', False, True, None),
+ 'x5u': JWSEHeaderParameter('X.509 URL', False, False, None),
+ 'x5c': JWSEHeaderParameter('X.509 Certificate Chain', False, False, None),
+ 'x5t': JWSEHeaderParameter('X.509 Certificate SHA-1 Thumbprint', False,
+ False, None),
+ 'x5t#S256': JWSEHeaderParameter('X.509 Certificate SHA-256 Thumbprint',
+ False, False, None),
+ 'typ': JWSEHeaderParameter('Type', False, True, None),
+ 'cty': JWSEHeaderParameter('Content Type', False, True, None),
+ 'crit': JWSEHeaderParameter('Critical', True, True, None),
+}
"""Registry of valid header parameters"""
default_allowed_algs = [
@@ -73,7 +77,8 @@
"""
def __init__(self, plaintext=None, protected=None, unprotected=None,
- aad=None, algs=None, recipient=None, header=None):
+ aad=None, algs=None, recipient=None, header=None,
+ header_registry=None):
"""Creates a JWE token.
:param plaintext(bytes): An arbitrary plaintext to be encrypted.
@@ -83,10 +88,14 @@
:param algs: An optional list of allowed algorithms
:param recipient: An optional, default recipient key
:param header: An optional header for the default recipient
+ :param header_registry: Optional additions to the header registry
"""
self._allowed_algs = None
self.objects = dict()
self.plaintext = None
+ self.header_registry = JWSEHeaderRegistry(JWEHeaderRegistry)
+ if header_registry:
+ self.header_registry.update(header_registry)
if plaintext is not None:
if isinstance(plaintext, bytes):
self.plaintext = plaintext
@@ -339,10 +348,10 @@
def _check_crit(self, crit):
for k in crit:
- if k not in JWEHeaderRegistry:
+ if k not in self.header_registry:
raise InvalidJWEData('Unknown critical header: "%s"' % k)
else:
- if not JWEHeaderRegistry[k][1]:
+ if not self.header_registry[k].supported:
raise InvalidJWEData('Unsupported critical header: '
'"%s"' % k)
@@ -354,6 +363,11 @@
# TODO: allow caller to specify list of headers it understands
self._check_crit(jh.get('crit', dict()))
+ for hdr in jh:
+ if hdr in self.header_registry:
+ if not self.header_registry.check_header(hdr, self):
+ raise InvalidJWEData('Failed header check')
+
alg = self._jwa_keymgmt(jh.get('alg', None))
enc = self._jwa_enc(jh.get('enc', None))
@@ -492,7 +506,7 @@
@property
def jose_header(self):
- jh = self._get_jose_header()
+ jh = self._get_jose_header(self.objects.get('header'))
if len(jh) == 0:
raise InvalidJWEOperation("JOSE Header not available")
return jh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/jwk.py
new/jwcrypto-0.7/jwcrypto/jwk.py
--- old/jwcrypto-0.6.0/jwcrypto/jwk.py 2018-11-05 16:04:15.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto/jwk.py 2020-02-19 17:12:20.000000000 +0100
@@ -18,10 +18,76 @@
from jwcrypto.common import json_decode, json_encode
-# RFC 7518 - 7.4
+class UnimplementedOKPCurveKey(object):
+ @classmethod
+ def generate(cls):
+ raise NotImplementedError
+
+ @classmethod
+ def from_public_bytes(cls, *args):
+ raise NotImplementedError
+
+ @classmethod
+ def from_private_bytes(cls, *args):
+ raise NotImplementedError
+
+
+ImplementedOkpCurves = []
+
+
+# Handle the best we can older versions of python cryptography that
+# do not yet implement these interfaces properly
+try:
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
+ Ed25519PublicKey, Ed25519PrivateKey
+ )
+ ImplementedOkpCurves.append('Ed25519')
+except ImportError:
+ Ed25519PublicKey = UnimplementedOKPCurveKey
+ Ed25519PrivateKey = UnimplementedOKPCurveKey
+try:
+ from cryptography.hazmat.primitives.asymmetric.ed448 import (
+ Ed448PublicKey, Ed448PrivateKey
+ )
+ ImplementedOkpCurves.append('Ed448')
+except ImportError:
+ Ed448PublicKey = UnimplementedOKPCurveKey
+ Ed448PrivateKey = UnimplementedOKPCurveKey
+try:
+ from cryptography.hazmat.primitives.asymmetric.x25519 import (
+ X25519PublicKey, X25519PrivateKey
+ )
+ priv_bytes = getattr(X25519PrivateKey, 'from_private_bytes', None)
+ if priv_bytes is None:
+ raise ImportError
+ ImplementedOkpCurves.append('X25519')
+except ImportError:
+ X25519PublicKey = UnimplementedOKPCurveKey
+ X25519PrivateKey = UnimplementedOKPCurveKey
+try:
+ from cryptography.hazmat.primitives.asymmetric.x448 import (
+ X448PublicKey, X448PrivateKey
+ )
+ ImplementedOkpCurves.append('X448')
+except ImportError:
+ X448PublicKey = UnimplementedOKPCurveKey
+ X448PrivateKey = UnimplementedOKPCurveKey
+
+
+_OKP_CURVE = namedtuple('Name', 'pubkey privkey')
+_OKP_CURVES_TABLE = {
+ 'Ed25519': _OKP_CURVE(Ed25519PublicKey, Ed25519PrivateKey),
+ 'Ed448': _OKP_CURVE(Ed448PublicKey, Ed448PrivateKey),
+ 'X25519': _OKP_CURVE(X25519PublicKey, X25519PrivateKey),
+ 'X448': _OKP_CURVE(X448PublicKey, X448PrivateKey)
+}
+
+
+# RFC 7518 - 7.4 , RFC 8037 - 5
JWKTypesRegistry = {'EC': 'Elliptic Curve',
'RSA': 'RSA',
- 'oct': 'Octet sequence'}
+ 'oct': 'Octet sequence',
+ 'OKP': 'Octet Key Pair'}
"""Registry of valid Key Types"""
@@ -31,7 +97,7 @@
class ParmType(Enum):
name = 'A string with a name'
b64 = 'Base64url Encoded'
- b64U = 'Base64urlUint Encoded'
+ b64u = 'Base64urlUint Encoded'
unsupported = 'Unsupported Parameter'
@@ -45,21 +111,26 @@
},
'RSA': {
'n': JWKParameter('Modulus', True, True, ParmType.b64),
- 'e': JWKParameter('Exponent', True, True, ParmType.b64U),
- 'd': JWKParameter('Private Exponent', False, False, ParmType.b64U),
- 'p': JWKParameter('First Prime Factor', False, False, ParmType.b64U),
- 'q': JWKParameter('Second Prime Factor', False, False, ParmType.b64U),
+ 'e': JWKParameter('Exponent', True, True, ParmType.b64u),
+ 'd': JWKParameter('Private Exponent', False, False, ParmType.b64u),
+ 'p': JWKParameter('First Prime Factor', False, False, ParmType.b64u),
+ 'q': JWKParameter('Second Prime Factor', False, False, ParmType.b64u),
'dp': JWKParameter('First Factor CRT Exponent',
- False, False, ParmType.b64U),
+ False, False, ParmType.b64u),
'dq': JWKParameter('Second Factor CRT Exponent',
- False, False, ParmType.b64U),
+ False, False, ParmType.b64u),
'qi': JWKParameter('First CRT Coefficient',
- False, False, ParmType.b64U),
+ False, False, ParmType.b64u),
'oth': JWKParameter('Other Primes Info',
False, False, ParmType.unsupported),
},
'oct': {
'k': JWKParameter('Key Value', False, True, ParmType.b64),
+ },
+ 'OKP': {
+ 'crv': JWKParameter('Curve', True, True, ParmType.name),
+ 'x': JWKParameter('Public Key', True, True, ParmType.b64),
+ 'd': JWKParameter('Private Key', False, False, ParmType.b64),
}
}
"""Registry of valid key values"""
@@ -79,10 +150,14 @@
}
"""Regstry of valid key parameters"""
-# RFC 7518 - 7.6
+# RFC 7518 - 7.6 , RFC 8037 - 5
JWKEllipticCurveRegistry = {'P-256': 'P-256 curve',
'P-384': 'P-384 curve',
- 'P-521': 'P-521 curve'}
+ 'P-521': 'P-521 curve',
+ 'Ed25519': 'Ed25519 signature algorithm key pairs',
+ 'Ed448': 'Ed448 signature algorithm key pairs',
+ 'X25519': 'X25519 function key pairs',
+ 'X448': 'X448 function key pairs'}
"""Registry of allowed Elliptic Curves"""
# RFC 7517 - 8.2
@@ -212,7 +287,8 @@
Valid options per type, when generating new keys:
* oct: size(int)
* RSA: public_exponent(int), size(int)
- * EC: curve(str) (one of P-256, P-384, P-521)
+ * EC: crv(str) (one of P-256, P-384, P-521)
+ * OKP: crv(str) (one of Ed25519, Ed448, X25519, X448)
Deprecated:
Alternatively if the 'generate' parameter is provided, with a
@@ -274,9 +350,17 @@
params['k'] = base64url_encode(key)
self.import_key(**params)
- def _encode_int(self, i):
- intg = hex(i).rstrip("L").lstrip("0x")
- return base64url_encode(unhexlify((len(intg) % 2) * '0' + intg))
+ def _encode_int(self, i, bit_size=None):
+ extend = 0
+ if bit_size is not None:
+ extend = ((bit_size + 7) // 8) * 2
+ hexi = hex(i).rstrip("L").lstrip("0x")
+ hexl = len(hexi)
+ if extend > hexl:
+ extend -= hexl
+ else:
+ extend = hexl % 2
+ return base64url_encode(unhexlify(extend * '0' + hexi))
def _generate_RSA(self, params):
pubexp = 65537
@@ -317,6 +401,8 @@
return ec.SECP384R1()
elif name == 'P-521':
return ec.SECP521R1()
+ elif name in _OKP_CURVES_TABLE:
+ return name
else:
raise InvalidJWKValue('Unknown Elliptic Curve Type')
@@ -334,12 +420,13 @@
def _import_pyca_pri_ec(self, key, **params):
pn = key.private_numbers()
+ key_size = pn.public_numbers.curve.key_size
params.update(
kty='EC',
crv=JWKpycaCurveMap[key.curve.name],
- x=self._encode_int(pn.public_numbers.x),
- y=self._encode_int(pn.public_numbers.y),
- d=self._encode_int(pn.private_value)
+ x=self._encode_int(pn.public_numbers.x, key_size),
+ y=self._encode_int(pn.public_numbers.y, key_size),
+ d=self._encode_int(pn.private_value, key_size)
)
self.import_key(**params)
@@ -353,6 +440,40 @@
)
self.import_key(**params)
+ def _generate_OKP(self, params):
+ if 'crv' not in params:
+ raise InvalidJWKValue('Must specify "crv" for OKP key generation')
+ try:
+ key = _OKP_CURVES_TABLE[params['crv']].privkey.generate()
+ except KeyError:
+ raise InvalidJWKValue('"%s" is not a supported curve for the '
+ 'OKP key type' % params['crv'])
+ self._import_pyca_pri_okp(key, **params)
+
+ def _import_pyca_pri_okp(self, key, **params):
+ params.update(
+ kty='OKP',
+ crv=params['crv'],
+ d=base64url_encode(key.private_bytes(
+ serialization.Encoding.Raw,
+ serialization.PrivateFormat.Raw,
+ serialization.NoEncryption())),
+ x=base64url_encode(key.public_key().public_bytes(
+ serialization.Encoding.Raw,
+ serialization.PublicFormat.Raw))
+ )
+ self.import_key(**params)
+
+ def _import_pyca_pub_okp(self, key, **params):
+ params.update(
+ kty='OKP',
+ crv=params['crv'],
+ x=base64url_encode(key.public_bytes(
+ serialization.Encoding.Raw,
+ serialization.PrivateFormat.Raw))
+ )
+ self.import_key(**params)
+
def import_key(self, **kwargs):
names = list(kwargs.keys())
@@ -385,7 +506,7 @@
raise InvalidJWKValue(
'"%s" is not base64url encoded' % name
)
- if val[3] == ParmType.b64U and name in self._key:
+ if val[3] == ParmType.b64u and name in self._key:
# Check that the value is Base64urlUInt encoded
try:
self._decode_int(self._key[name])
@@ -547,8 +668,8 @@
@property
def key_curve(self):
"""The Curve Name."""
- if self._params['kty'] != 'EC':
- raise InvalidJWKType('Not an EC key')
+ if self._params['kty'] not in ['EC', 'OKP']:
+ raise InvalidJWKType('Not an EC or OKP key')
return self._key['crv']
def get_curve(self, arg):
@@ -556,12 +677,12 @@
:param arg: an optional curve name
- :raises InvalidJWKType: the key is not an EC key.
+ :raises InvalidJWKType: the key is not an EC or OKP key.
:raises InvalidJWKValue: if the curve names is invalid.
"""
k = self._key
- if self._params['kty'] != 'EC':
- raise InvalidJWKType('Not an EC key')
+ if self._params['kty'] not in ['EC', 'OKP']:
+ raise InvalidJWKType('Not an EC or OKP key')
if arg and k['crv'] != arg:
raise InvalidJWKValue('Curve requested is "%s", but '
'key curve is "%s"' % (arg, k['crv']))
@@ -605,6 +726,22 @@
return ec.EllipticCurvePrivateNumbers(self._decode_int(k['d']),
self._ec_pub(k, curve))
+ def _okp_pub(self, k):
+ try:
+ pubkey = _OKP_CURVES_TABLE[k['crv']].pubkey
+ except KeyError:
+ raise InvalidJWKValue('Unknown curve "%s"' % k['crv'])
+
+ return pubkey.from_public_bytes(base64url_decode(k['x']))
+
+ def _okp_pri(self, k):
+ try:
+ privkey = _OKP_CURVES_TABLE[k['crv']].privkey
+ except KeyError:
+ raise InvalidJWKValue('Unknown curve "%s"' % k['crv'])
+
+ return privkey.from_private_bytes(base64url_decode(k['d']))
+
def _get_public_key(self, arg=None):
if self._params['kty'] == 'oct':
return self._key['k']
@@ -612,6 +749,8 @@
return self._rsa_pub(self._key).public_key(default_backend())
elif self._params['kty'] == 'EC':
return self._ec_pub(self._key, arg).public_key(default_backend())
+ elif self._params['kty'] == 'OKP':
+ return self._okp_pub(self._key)
else:
raise NotImplementedError
@@ -622,6 +761,8 @@
return self._rsa_pri(self._key).private_key(default_backend())
elif self._params['kty'] == 'EC':
return self._ec_pri(self._key, arg).private_key(default_backend())
+ elif self._params['kty'] == 'OKP':
+ return self._okp_pri(self._key)
else:
raise NotImplementedError
@@ -673,6 +814,10 @@
self._import_pyca_pri_ec(key)
elif isinstance(key, ec.EllipticCurvePublicKey):
self._import_pyca_pub_ec(key)
+ elif isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)):
+ self._import_pyca_pri_okp(key)
+ elif isinstance(key, (Ed25519PublicKey, Ed448PublicKey)):
+ self._import_pyca_pub_okp(key)
else:
raise InvalidJWKValue('Unknown key object %r' % key)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/jws.py
new/jwcrypto-0.7/jwcrypto/jws.py
--- old/jwcrypto-0.6.0/jwcrypto/jws.py 2018-11-05 16:04:15.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto/jws.py 2020-02-19 17:12:20.000000000 +0100
@@ -1,33 +1,27 @@
# Copyright (C) 2015 JWCrypto Project Contributors - see LICENSE file
-from collections import namedtuple
-
from jwcrypto.common import JWException
+from jwcrypto.common import JWSEHeaderParameter, JWSEHeaderRegistry
from jwcrypto.common import base64url_decode, base64url_encode
from jwcrypto.common import json_decode, json_encode
from jwcrypto.jwa import JWA
from jwcrypto.jwk import JWK
-
-# RFC 7515 - 9.1
-# name: (description, supported?)
-JWSHeaderParameter = namedtuple('Parameter',
- 'description mustprotect supported')
JWSHeaderRegistry = {
- 'alg': JWSHeaderParameter('Algorithm', False, True),
- 'jku': JWSHeaderParameter('JWK Set URL', False, False),
- 'jwk': JWSHeaderParameter('JSON Web Key', False, False),
- 'kid': JWSHeaderParameter('Key ID', False, True),
- 'x5u': JWSHeaderParameter('X.509 URL', False, False),
- 'x5c': JWSHeaderParameter('X.509 Certificate Chain', False, False),
- 'x5t': JWSHeaderParameter(
- 'X.509 Certificate SHA-1 Thumbprint', False, False),
- 'x5t#S256': JWSHeaderParameter(
- 'X.509 Certificate SHA-256 Thumbprint', False, False),
- 'typ': JWSHeaderParameter('Type', False, True),
- 'cty': JWSHeaderParameter('Content Type', False, True),
- 'crit': JWSHeaderParameter('Critical', True, True),
- 'b64': JWSHeaderParameter('Base64url-Encode Payload', True, True)
+ 'alg': JWSEHeaderParameter('Algorithm', False, True, None),
+ 'jku': JWSEHeaderParameter('JWK Set URL', False, False, None),
+ 'jwk': JWSEHeaderParameter('JSON Web Key', False, False, None),
+ 'kid': JWSEHeaderParameter('Key ID', False, True, None),
+ 'x5u': JWSEHeaderParameter('X.509 URL', False, False, None),
+ 'x5c': JWSEHeaderParameter('X.509 Certificate Chain', False, False, None),
+ 'x5t': JWSEHeaderParameter(
+ 'X.509 Certificate SHA-1 Thumbprint', False, False, None),
+ 'x5t#S256': JWSEHeaderParameter(
+ 'X.509 Certificate SHA-256 Thumbprint', False, False, None),
+ 'typ': JWSEHeaderParameter('Type', False, True, None),
+ 'cty': JWSEHeaderParameter('Content Type', False, True, None),
+ 'crit': JWSEHeaderParameter('Critical', True, True, None),
+ 'b64': JWSEHeaderParameter('Base64url-Encode Payload', True, True, None)
}
"""Registry of valid header parameters"""
@@ -35,7 +29,8 @@
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'ES256', 'ES384', 'ES512',
- 'PS256', 'PS384', 'PS512']
+ 'PS256', 'PS384', 'PS512',
+ 'EdDSA']
"""Default allowed algorithms"""
@@ -178,16 +173,20 @@
This object represent a JWS token.
"""
- def __init__(self, payload=None):
+ def __init__(self, payload=None, header_registry=None):
"""Creates a JWS object.
:param payload(bytes): An arbitrary value (optional).
+ :param header_registry: Optional additions to the header registry
"""
self.objects = dict()
if payload:
self.objects['payload'] = payload
self.verifylog = None
self._allowed_algs = None
+ self.header_registry = JWSEHeaderRegistry(JWSHeaderRegistry)
+ if header_registry:
+ self.header_registry.update(header_registry)
@property
def allowed_algs(self):
@@ -213,6 +212,7 @@
return self.objects.get('valid', False)
# TODO: allow caller to specify list of headers it understands
+ # FIXME: Merge and check to be changed to two separate functions
def _merge_check_headers(self, protected, *headers):
header = None
crit = []
@@ -221,11 +221,11 @@
crit = protected['crit']
# Check immediately if we support these critical headers
for k in crit:
- if k not in JWSHeaderRegistry:
+ if k not in self.header_registry:
raise InvalidJWSObject(
'Unknown critical header: "%s"' % k)
else:
- if not JWSHeaderRegistry[k][1]:
+ if not self.header_registry[k].supported:
raise InvalidJWSObject(
'Unsupported critical header: "%s"' % k)
header = protected
@@ -239,8 +239,8 @@
if header is None:
header = dict()
for h in list(hn.keys()):
- if h in JWSHeaderRegistry:
- if JWSHeaderRegistry[h].mustprotect:
+ if h in self.header_registry:
+ if self.header_registry[h].mustprotect:
raise InvalidJWSObject('"%s" must be protected' % h)
if h in header:
raise InvalidJWSObject('Duplicate header: "%s"' % h)
@@ -266,7 +266,12 @@
raise InvalidJWSSignature('Invalid Unprotected header')
# Merge and check (critical) headers
- self._merge_check_headers(p, header)
+ chk_hdrs = self._merge_check_headers(p, header)
+ for hdr in chk_hdrs:
+ if hdr in self.header_registry:
+ if not self.header_registry.check_header(hdr, self):
+ raise InvalidJWSSignature('Failed header check')
+
# check 'alg' is present
if alg is None and 'alg' not in p:
raise InvalidJWSSignature('No "alg" in headers')
@@ -419,7 +424,7 @@
:param alg: An optional algorithm name. If already provided as an
element of the protected or unprotected header it can be safely
omitted.
- :param potected: The Protected Header (optional)
+ :param protected: The Protected Header (optional)
:param header: The Unprotected Header (optional)
:raises InvalidJWSObject: if no payload has been set on the object,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/jwt.py
new/jwcrypto-0.7/jwcrypto/jwt.py
--- old/jwcrypto-0.6.0/jwcrypto/jwt.py 2018-11-05 16:04:15.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto/jwt.py 2019-05-27 14:17:28.000000000 +0200
@@ -108,6 +108,7 @@
super(JWTInvalidClaimFormat, self).__init__(msg)
+# deprecated and not used anymore
class JWTMissingKeyID(JWException):
"""Json Web Token is missing key id.
@@ -187,6 +188,7 @@
self._check_claims = None
self._leeway = 60 # 1 minute clock skew allowed
self._validity = 600 # 10 minutes validity (up to 11 with leeway)
+ self.deserializelog = None
if header:
self.header = header
@@ -462,28 +464,37 @@
if self._algs:
self.token.allowed_algs = self._algs
+ self.deserializelog = list()
# now deserialize and also decrypt/verify (or raise) if we
# have a key
if key is None:
self.token.deserialize(jwt, None)
elif isinstance(key, JWK):
self.token.deserialize(jwt, key)
+ self.deserializelog.append("Success")
elif isinstance(key, JWKSet):
self.token.deserialize(jwt, None)
- if 'kid' not in self.token.jose_header:
- raise JWTMissingKeyID('No key ID in JWT header')
-
- token_key = key.get_key(self.token.jose_header['kid'])
- if not token_key:
- raise JWTMissingKey('Key ID %s not in key set'
- % self.token.jose_header['kid'])
-
- if isinstance(self.token, JWE):
- self.token.decrypt(token_key)
- elif isinstance(self.token, JWS):
- self.token.verify(token_key)
+ if 'kid' in self.token.jose_header:
+ kid_key = key.get_key(self.token.jose_header['kid'])
+ if not kid_key:
+ raise JWTMissingKey('Key ID %s not in key set'
+ % self.token.jose_header['kid'])
+ self.token.deserialize(jwt, kid_key)
else:
- raise RuntimeError("Unknown Token Type")
+ for k in key:
+ try:
+ self.token.deserialize(jwt, k)
+ self.deserializelog.append("Success")
+ break
+ except Exception as e: # pylint: disable=broad-except
+ keyid = k.key_id
+ if keyid is None:
+ keyid = k.thumbprint()
+ self.deserializelog.append('Key [%s] failed: [%s]' % (
+ keyid, repr(e)))
+ continue
+ if "Success" not in self.deserializelog:
+ raise JWTMissingKey('No working key found in key set')
else:
raise ValueError("Unrecognized Key Type")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto/tests.py
new/jwcrypto-0.7/jwcrypto/tests.py
--- old/jwcrypto-0.6.0/jwcrypto/tests.py 2018-11-05 16:04:15.000000000
+0100
+++ new/jwcrypto-0.7/jwcrypto/tests.py 2020-02-19 17:12:20.000000000 +0100
@@ -14,6 +14,8 @@
from jwcrypto import jwk
from jwcrypto import jws
from jwcrypto import jwt
+from jwcrypto.common import InvalidJWSERegOperation
+from jwcrypto.common import JWSEHeaderParameter
from jwcrypto.common import base64url_decode, base64url_encode
from jwcrypto.common import json_decode, json_encode
@@ -234,6 +236,29 @@
PublicCertThumbprint = u'7KITkGJF74IZ9NKVvHfuJILbuIZny6j-roaNjB1vgiA'
+# RFC 8037 - A.2
+PublicKeys_EdDsa = {
+ "keys": [
+ {
+ "kty": "OKP",
+ "crv": "Ed25519",
+ "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
+ },
+ ],
+ "thumbprints": ["kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k"]
+}
+
+# RFC 8037 - A.1
+PrivateKeys_EdDsa = {
+ "keys": [
+ {
+ "kty": "OKP",
+ "crv": "Ed25519",
+ "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
+ "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"},
+ ]
+}
+
class TestJWK(unittest.TestCase):
def test_create_pubKeys(self):
@@ -295,6 +320,11 @@
key = jwk.JWK.generate(kty='EC', curve='P-256', crv='P-521')
key.get_curve('P-521')
+ def test_generate_OKP_keys(self):
+ for crv in jwk.ImplementedOkpCurves:
+ key = jwk.JWK.generate(kty='OKP', crv=crv)
+ self.assertEqual(key.get_curve(crv), crv)
+
def test_import_pyca_keys(self):
rsa1 = rsa.generate_private_key(65537, 1024, default_backend())
krsa1 = jwk.JWK.from_pyca(rsa1)
@@ -411,6 +441,23 @@
with self.assertRaises(jwk.InvalidJWKValue):
jwk.JWK(kty='oct', k=b'\x01')
+ def test_create_pubKeys_eddsa(self):
+ keylist = PublicKeys_EdDsa['keys']
+ for key in keylist:
+ jwk.JWK(**key)
+
+ def test_create_priKeys_eddsa(self):
+ keylist = PrivateKeys_EdDsa['keys']
+ for key in keylist:
+ jwk.JWK(**key)
+
+ def test_thumbprint_eddsa(self):
+ for i in range(0, len(PublicKeys_EdDsa['keys'])):
+ k = jwk.JWK(**PublicKeys_EdDsa['keys'][i])
+ self.assertEqual(
+ k.thumbprint(),
+ PublicKeys_EdDsa['thumbprints'][i])
+
# RFC 7515 - A.1
A1_protected = \
@@ -613,6 +660,20 @@
'ZJTkVEIl0sDQogImh0dHA6Ly9leGFtcGxlLmNvbS9VTkRFRklORUQiOnRydWUNCn0.' + \
'RkFJTA.'
+customhdr_jws_example = \
+ '{' + \
+ '"payload":' + \
+ '"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGF' + \
+ 'tcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",' + \
+ '"protected":"eyJhbGciOiJFUzI1NiJ9",' + \
+ '"header":' + \
+ '{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d", ' + \
+ '"custom1":"custom_val"},' + \
+ '"signature":' + \
+ '"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8IS' + \
+ 'lSApmWQxfKTUJqPP3-Kg6NU1Q"' + \
+ '}'
+
class TestJWS(unittest.TestCase):
def check_sign(self, test):
@@ -651,8 +712,7 @@
self.check_sign, A5_example)
a5_bis = {'allowed_algs': ['none']}
a5_bis.update(A5_example)
- with self.assertRaises(jws.InvalidJWSSignature):
- self.check_sign(a5_bis)
+ self.check_sign(a5_bis)
def test_A6(self):
s = jws.JWS(A6_example['payload'])
@@ -679,6 +739,53 @@
jws.InvalidJWSSignature(s.deserialize, E_negative)
s.verify(None)
+ def test_customhdr_jws(self):
+ # Test pass header check
+ def jws_chk1(jwobj):
+ return jwobj.jose_header['custom1'] == 'custom_val'
+
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, jws_chk1)
+ newreg = {'custom1': newhdr}
+ s = jws.JWS(A6_example['payload'], header_registry=newreg)
+ s.deserialize(customhdr_jws_example, A6_example['key2'])
+
+ # Test fail header check
+ def jws_chk2(jwobj):
+ return jwobj.jose_header['custom1'] == 'custom_not'
+
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, jws_chk2)
+ newreg = {'custom1': newhdr}
+ s = jws.JWS(A6_example['payload'], header_registry=newreg)
+ with self.assertRaises(jws.InvalidJWSSignature):
+ s.deserialize(customhdr_jws_example, A6_example['key2'])
+
+ def test_customhdr_jws_exists(self):
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, None)
+ newreg = {'alg': newhdr}
+ with self.assertRaises(InvalidJWSERegOperation):
+ jws.JWS(A6_example['payload'], header_registry=newreg)
+
+ def test_EdDsa_signing_and_verification(self):
+ examples = []
+ if 'Ed25519' in jwk.ImplementedOkpCurves:
+ examples = [E_Ed25519]
+ for curve_example in examples:
+ key = jwk.JWK.from_json(curve_example['key_json'])
+ payload = curve_example['payload']
+ protected_header = curve_example['protected_header']
+ jws_test = jws.JWS(payload)
+ jws_test.add_signature(key, None,
+ json_encode(protected_header), None)
+ jws_test_serialization_compact = \
+ jws_test.serialize(compact=True)
+ self.assertEqual(jws_test_serialization_compact,
+ curve_example['jws_serialization_compact'])
+ jws_verify = jws.JWS()
+ jws_verify.deserialize(jws_test_serialization_compact)
+ jws_verify.verify(key.public())
+ self.assertEqual(jws_verify.payload.decode('utf-8'),
+ curve_example['payload'])
+
E_A1_plaintext = \
[84, 104, 101, 32, 116, 114, 117, 101, 32, 115, 105, 103, 110, 32,
@@ -839,6 +946,16 @@
'"ciphertext":"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",' \
'"tag":"Mz-VPPyU4RlcuYv1IwIvzw"}'
+customhdr_jwe_ex = \
+ '{"protected":"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",' \
+ '"unprotected":{"jku":"https://server.example.com/keys.jwks"},' \
+ '"header":{"alg":"A128KW","kid":"7", "custom1":"custom_val"},' \
+ '"encrypted_key":' \
+ '"6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ",' \
+ '"iv":"AxY8DCtDaGlsbGljb3RoZQ",' \
+ '"ciphertext":"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",' \
+ '"tag":"Mz-VPPyU4RlcuYv1IwIvzw"}'
+
Issue_136_Protected_Header_no_epk = {
"alg": "ECDH-ES+A256KW",
"enc": "A256CBC-HS512"}
@@ -862,6 +979,23 @@
"x": "FPrb_xwxe8SBP3kO-e-WsofFp7n5-yc_tGgfAvqAP8g",
"y": "lM3HuyKMYUVsYdGqiWlkwTZbGO3Fh-hyadq8lfkTgBc"}
+# RFC 8037 A.1
+E_Ed25519 = {
+ 'key_json': '{"kty": "OKP",'
+ '"crv": "Ed25519", '
+ '"d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", '
+ '"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}',
+ 'payload': 'Example of Ed25519 signing',
+ 'protected_header': {"alg": "EdDSA"},
+ 'jws_serialization_compact': 'eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBF'
+ 'ZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCjP0Jzl'
+ 'nLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki'
+ '4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg'}
+
+X25519_Protected_Header_no_epk = {
+ "alg": "ECDH-ES+A128KW",
+ "enc": "A128GCM"}
+
class TestJWE(unittest.TestCase):
def check_enc(self, plaintext, protected, key, vector):
@@ -938,6 +1072,42 @@
e.deserialize(Issue_136_Contributed_JWE,
jwk.JWK(**Issue_136_Contributed_Key))
+ def test_customhdr_jwe(self):
+ def jwe_chk1(jwobj):
+ return jwobj.jose_header['custom1'] == 'custom_val'
+
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, jwe_chk1)
+ newreg = {'custom1': newhdr}
+ e = jwe.JWE(header_registry=newreg)
+ e.deserialize(customhdr_jwe_ex, E_A4_ex['key2'])
+
+ def jwe_chk2(jwobj):
+ return jwobj.jose_header['custom1'] == 'custom_not'
+
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, jwe_chk2)
+ newreg = {'custom1': newhdr}
+ e = jwe.JWE(header_registry=newreg)
+ with self.assertRaises(jwe.InvalidJWEData):
+ e.deserialize(customhdr_jwe_ex, E_A4_ex['key2'])
+
+ def test_customhdr_jwe_exists(self):
+ newhdr = JWSEHeaderParameter('Custom header 1', False, True, None)
+ newreg = {'alg': newhdr}
+ with self.assertRaises(InvalidJWSERegOperation):
+ jwe.JWE(header_registry=newreg)
+
+ def test_X25519_ECDH(self):
+ plaintext = b"plain"
+ protected = json_encode(X25519_Protected_Header_no_epk)
+ if 'X25519' in jwk.ImplementedOkpCurves:
+ x25519key = jwk.JWK.generate(kty='OKP', crv='X25519')
+ e1 = jwe.JWE(plaintext, protected)
+ e1.add_recipient(x25519key)
+ enc = e1.serialize()
+ e2 = jwe.JWE()
+ e2.deserialize(enc, x25519key)
+ self.assertEqual(e2.payload, plaintext)
+
MMA_vector_key = jwk.JWK(**E_A2_key)
MMA_vector_ok_cek = \
@@ -1094,20 +1264,35 @@
def test_decrypt_keyset(self):
key = jwk.JWK(kid='testkey', **E_A2_key)
- keyset = jwk.JWKSet()
- # decrypt without keyid
- t = jwt.JWT(A1_header, A1_claims)
+ keyset = jwk.JWKSet.from_json(json_encode(PrivateKeys))
+
+ # encrypt a new JWT with kid
+ header = copy.copy(A1_header)
+ header['kid'] = 'testkey'
+ t = jwt.JWT(header, A1_claims)
t.make_encrypted_token(key)
token = t.serialize()
- self.assertRaises(jwt.JWTMissingKeyID, jwt.JWT, jwt=token,
- key=keyset)
- # encrypt a new JWT
+ # try to decrypt without a matching key
+ self.assertRaises(jwt.JWTMissingKey, jwt.JWT, jwt=token, key=keyset)
+ # now decrypt with key
+ keyset.add(key)
+ jwt.JWT(jwt=token, key=keyset, check_claims={'exp': 1300819380})
+
+ # encrypt a new JWT with wrong kid
header = copy.copy(A1_header)
- header['kid'] = 'testkey'
+ header['kid'] = '1'
t = jwt.JWT(header, A1_claims)
t.make_encrypted_token(key)
token = t.serialize()
- # try to decrypt without key
+ self.assertRaises(jwe.InvalidJWEData, jwt.JWT, jwt=token, key=keyset)
+
+ keyset = jwk.JWKSet.from_json(json_encode(PrivateKeys))
+ # encrypt a new JWT with no kid
+ header = copy.copy(A1_header)
+ t = jwt.JWT(header, A1_claims)
+ t.make_encrypted_token(key)
+ token = t.serialize()
+ # try to decrypt without a matching key
self.assertRaises(jwt.JWTMissingKey, jwt.JWT, jwt=token, key=keyset)
# now decrypt with key
keyset.add(key)
@@ -1238,6 +1423,19 @@
check.deserialize(enc, key)
self.assertEqual(b'plain', check.payload)
+ def test_none_key(self):
+ e = "eyJhbGciOiJub25lIn0." + \
+ "eyJpc3MiOiJqb2UiLCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZX0."
+ token = jwt.JWT(algs=['none'])
+ k = jwk.JWK(generate='oct', size=0)
+ token.deserialize(jwt=e, key=k)
+ self.assertEqual(json_decode(token.claims),
+ {"iss": "joe", "http://example.com/is_root": True})
+ with self.assertRaises(KeyError):
+ token = jwt.JWT()
+ token.deserialize(jwt=e)
+ json_decode(token.claims)
+
class JWATests(unittest.TestCase):
def test_jwa_create(self):
@@ -1246,6 +1444,8 @@
self.assertIn(cls.algorithm_usage_location, {'alg', 'enc'})
if name == 'ECDH-ES':
self.assertIs(cls.keysize, None)
+ elif name == 'EdDSA':
+ self.assertIs(cls.keysize, None)
else:
self.assertIsInstance(cls.keysize, int)
self.assertGreaterEqual(cls.keysize, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/jwcrypto.egg-info/PKG-INFO
new/jwcrypto-0.7/jwcrypto.egg-info/PKG-INFO
--- old/jwcrypto-0.6.0/jwcrypto.egg-info/PKG-INFO 2018-11-05
16:18:50.000000000 +0100
+++ new/jwcrypto-0.7/jwcrypto.egg-info/PKG-INFO 2020-02-19 18:17:34.000000000
+0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: jwcrypto
-Version: 0.6.0
+Version: 0.7
Summary: Implementation of JOSE Web standards
Home-page: https://github.com/latchset/jwcrypto
Maintainer: JWCrypto Project Contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/setup.py new/jwcrypto-0.7/setup.py
--- old/jwcrypto-0.6.0/setup.py 2018-11-05 16:13:11.000000000 +0100
+++ new/jwcrypto-0.7/setup.py 2020-02-19 18:15:54.000000000 +0100
@@ -6,7 +6,7 @@
setup(
name = 'jwcrypto',
- version = '0.6.0',
+ version = '0.7',
license = 'LGPLv3+',
maintainer = 'JWCrypto Project Contributors',
maintainer_email = '[email protected]',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jwcrypto-0.6.0/tox.ini new/jwcrypto-0.7/tox.ini
--- old/jwcrypto-0.6.0/tox.ini 2018-06-27 12:24:24.000000000 +0200
+++ new/jwcrypto-0.7/tox.ini 2020-02-19 17:12:20.000000000 +0100
@@ -8,7 +8,7 @@
deps =
pytest
coverage
-sitepackages = True
+#sitepackages = True
commands =
{envpython} -bb -m coverage run -m pytest --capture=no --strict {posargs}
{envpython} -m coverage report -m
@@ -17,7 +17,7 @@
basepython = python2.7
deps =
pylint
-sitepackages = True
+#sitepackages = True
commands =
{envpython} -m pylint -d c,r,i,W0613 -r n -f colorized --notes=
--disable=star-args ./jwcrypto