venv added, updated
This commit is contained in:
722
myenv/lib/python3.12/site-packages/authlib/jose/rfc7516/jwe.py
Normal file
722
myenv/lib/python3.12/site-packages/authlib/jose/rfc7516/jwe.py
Normal file
@@ -0,0 +1,722 @@
|
||||
from collections import OrderedDict
|
||||
from copy import deepcopy
|
||||
|
||||
from authlib.common.encoding import (
|
||||
to_bytes, urlsafe_b64encode, json_b64encode, to_unicode
|
||||
)
|
||||
from authlib.jose.rfc7516.models import JWEAlgorithmWithTagAwareKeyAgreement, JWESharedHeader, JWEHeader
|
||||
from authlib.jose.util import (
|
||||
extract_header,
|
||||
extract_segment, ensure_dict,
|
||||
)
|
||||
from authlib.jose.errors import (
|
||||
DecodeError,
|
||||
MissingAlgorithmError,
|
||||
UnsupportedAlgorithmError,
|
||||
MissingEncryptionAlgorithmError,
|
||||
UnsupportedEncryptionAlgorithmError,
|
||||
UnsupportedCompressionAlgorithmError,
|
||||
InvalidHeaderParameterNameError, InvalidAlgorithmForMultipleRecipientsMode, KeyMismatchError,
|
||||
)
|
||||
|
||||
|
||||
class JsonWebEncryption:
|
||||
#: Registered Header Parameter Names defined by Section 4.1
|
||||
REGISTERED_HEADER_PARAMETER_NAMES = frozenset([
|
||||
'alg', 'enc', 'zip',
|
||||
'jku', 'jwk', 'kid',
|
||||
'x5u', 'x5c', 'x5t', 'x5t#S256',
|
||||
'typ', 'cty', 'crit'
|
||||
])
|
||||
|
||||
ALG_REGISTRY = {}
|
||||
ENC_REGISTRY = {}
|
||||
ZIP_REGISTRY = {}
|
||||
|
||||
def __init__(self, algorithms=None, private_headers=None):
|
||||
self._algorithms = algorithms
|
||||
self._private_headers = private_headers
|
||||
|
||||
@classmethod
|
||||
def register_algorithm(cls, algorithm):
|
||||
"""Register an algorithm for ``alg`` or ``enc`` or ``zip`` of JWE."""
|
||||
if not algorithm or algorithm.algorithm_type != 'JWE':
|
||||
raise ValueError(
|
||||
f'Invalid algorithm for JWE, {algorithm!r}')
|
||||
|
||||
if algorithm.algorithm_location == 'alg':
|
||||
cls.ALG_REGISTRY[algorithm.name] = algorithm
|
||||
elif algorithm.algorithm_location == 'enc':
|
||||
cls.ENC_REGISTRY[algorithm.name] = algorithm
|
||||
elif algorithm.algorithm_location == 'zip':
|
||||
cls.ZIP_REGISTRY[algorithm.name] = algorithm
|
||||
|
||||
def serialize_compact(self, protected, payload, key, sender_key=None):
|
||||
"""Generate a JWE Compact Serialization.
|
||||
|
||||
The JWE Compact Serialization represents encrypted content as a compact,
|
||||
URL-safe string. This string is::
|
||||
|
||||
BASE64URL(UTF8(JWE Protected Header)) || '.' ||
|
||||
BASE64URL(JWE Encrypted Key) || '.' ||
|
||||
BASE64URL(JWE Initialization Vector) || '.' ||
|
||||
BASE64URL(JWE Ciphertext) || '.' ||
|
||||
BASE64URL(JWE Authentication Tag)
|
||||
|
||||
Only one recipient is supported by the JWE Compact Serialization and
|
||||
it provides no syntax to represent JWE Shared Unprotected Header, JWE
|
||||
Per-Recipient Unprotected Header, or JWE AAD values.
|
||||
|
||||
:param protected: A dict of protected header
|
||||
:param payload: Payload (bytes or a value convertible to bytes)
|
||||
:param key: Public key used to encrypt payload
|
||||
:param sender_key: Sender's private key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: JWE compact serialization as bytes
|
||||
"""
|
||||
|
||||
# step 1: Prepare algorithms & key
|
||||
alg = self.get_header_alg(protected)
|
||||
enc = self.get_header_enc(protected)
|
||||
zip_alg = self.get_header_zip(protected)
|
||||
|
||||
self._validate_sender_key(sender_key, alg)
|
||||
self._validate_private_headers(protected, alg)
|
||||
|
||||
key = prepare_key(alg, protected, key)
|
||||
if sender_key is not None:
|
||||
sender_key = alg.prepare_key(sender_key)
|
||||
|
||||
# self._post_validate_header(protected, algorithm)
|
||||
|
||||
# step 2: Generate a random Content Encryption Key (CEK)
|
||||
# use enc_alg.generate_cek() in scope of upcoming .wrap or .generate_keys_and_prepare_headers call
|
||||
|
||||
# step 3: Encrypt the CEK with the recipient's public key
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement) and alg.key_size is not None:
|
||||
# For a JWE algorithm with tag-aware key agreement in case key agreement with key wrapping mode is used:
|
||||
# Defer key agreement with key wrapping until authentication tag is computed
|
||||
prep = alg.generate_keys_and_prepare_headers(enc, key, sender_key)
|
||||
epk = prep['epk']
|
||||
cek = prep['cek']
|
||||
protected.update(prep['header'])
|
||||
else:
|
||||
# In any other case:
|
||||
# Keep the normal steps order defined by RFC 7516
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
|
||||
wrapped = alg.wrap(enc, protected, key, sender_key)
|
||||
else:
|
||||
wrapped = alg.wrap(enc, protected, key)
|
||||
cek = wrapped['cek']
|
||||
ek = wrapped['ek']
|
||||
if 'header' in wrapped:
|
||||
protected.update(wrapped['header'])
|
||||
|
||||
# step 4: Generate a random JWE Initialization Vector
|
||||
iv = enc.generate_iv()
|
||||
|
||||
# step 5: Let the Additional Authenticated Data encryption parameter
|
||||
# be ASCII(BASE64URL(UTF8(JWE Protected Header)))
|
||||
protected_segment = json_b64encode(protected)
|
||||
aad = to_bytes(protected_segment, 'ascii')
|
||||
|
||||
# step 6: compress message if required
|
||||
if zip_alg:
|
||||
msg = zip_alg.compress(to_bytes(payload))
|
||||
else:
|
||||
msg = to_bytes(payload)
|
||||
|
||||
# step 7: perform encryption
|
||||
ciphertext, tag = enc.encrypt(msg, aad, iv, cek)
|
||||
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement) and alg.key_size is not None:
|
||||
# For a JWE algorithm with tag-aware key agreement in case key agreement with key wrapping mode is used:
|
||||
# Perform key agreement with key wrapping deferred at step 3
|
||||
wrapped = alg.agree_upon_key_and_wrap_cek(enc, protected, key, sender_key, epk, cek, tag)
|
||||
ek = wrapped['ek']
|
||||
|
||||
# step 8: build resulting message
|
||||
return b'.'.join([
|
||||
protected_segment,
|
||||
urlsafe_b64encode(ek),
|
||||
urlsafe_b64encode(iv),
|
||||
urlsafe_b64encode(ciphertext),
|
||||
urlsafe_b64encode(tag)
|
||||
])
|
||||
|
||||
def serialize_json(self, header_obj, payload, keys, sender_key=None):
|
||||
"""Generate a JWE JSON Serialization (in fully general syntax).
|
||||
|
||||
The JWE JSON Serialization represents encrypted content as a JSON
|
||||
object. This representation is neither optimized for compactness nor
|
||||
URL safe.
|
||||
|
||||
The following members are defined for use in top-level JSON objects
|
||||
used for the fully general JWE JSON Serialization syntax:
|
||||
|
||||
protected
|
||||
The "protected" member MUST be present and contain the value
|
||||
BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
|
||||
Header value is non-empty; otherwise, it MUST be absent. These
|
||||
Header Parameter values are integrity protected.
|
||||
|
||||
unprotected
|
||||
The "unprotected" member MUST be present and contain the value JWE
|
||||
Shared Unprotected Header when the JWE Shared Unprotected Header
|
||||
value is non-empty; otherwise, it MUST be absent. This value is
|
||||
represented as an unencoded JSON object, rather than as a string.
|
||||
These Header Parameter values are not integrity protected.
|
||||
|
||||
iv
|
||||
The "iv" member MUST be present and contain the value
|
||||
BASE64URL(JWE Initialization Vector) when the JWE Initialization
|
||||
Vector value is non-empty; otherwise, it MUST be absent.
|
||||
|
||||
aad
|
||||
The "aad" member MUST be present and contain the value
|
||||
BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
|
||||
otherwise, it MUST be absent. A JWE AAD value can be included to
|
||||
supply a base64url-encoded value to be integrity protected but not
|
||||
encrypted.
|
||||
|
||||
ciphertext
|
||||
The "ciphertext" member MUST be present and contain the value
|
||||
BASE64URL(JWE Ciphertext).
|
||||
|
||||
tag
|
||||
The "tag" member MUST be present and contain the value
|
||||
BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
|
||||
value is non-empty; otherwise, it MUST be absent.
|
||||
|
||||
recipients
|
||||
The "recipients" member value MUST be an array of JSON objects.
|
||||
Each object contains information specific to a single recipient.
|
||||
This member MUST be present with exactly one array element per
|
||||
recipient, even if some or all of the array element values are the
|
||||
empty JSON object "{}" (which can happen when all Header Parameter
|
||||
values are shared between all recipients and when no encrypted key
|
||||
is used, such as when doing Direct Encryption).
|
||||
|
||||
The following members are defined for use in the JSON objects that
|
||||
are elements of the "recipients" array:
|
||||
|
||||
header
|
||||
The "header" member MUST be present and contain the value JWE Per-
|
||||
Recipient Unprotected Header when the JWE Per-Recipient
|
||||
Unprotected Header value is non-empty; otherwise, it MUST be
|
||||
absent. This value is represented as an unencoded JSON object,
|
||||
rather than as a string. These Header Parameter values are not
|
||||
integrity protected.
|
||||
|
||||
encrypted_key
|
||||
The "encrypted_key" member MUST be present and contain the value
|
||||
BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
|
||||
non-empty; otherwise, it MUST be absent.
|
||||
|
||||
This implementation assumes that "alg" and "enc" header fields are
|
||||
contained in the protected or shared unprotected header.
|
||||
|
||||
:param header_obj: A dict of headers (in addition optionally contains JWE AAD)
|
||||
:param payload: Payload (bytes or a value convertible to bytes)
|
||||
:param keys: Public keys (or a single public key) used to encrypt payload
|
||||
:param sender_key: Sender's private key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: JWE JSON serialization (in fully general syntax) as dict
|
||||
|
||||
Example of `header_obj`::
|
||||
|
||||
{
|
||||
"protected": {
|
||||
"alg": "ECDH-1PU+A128KW",
|
||||
"enc": "A256CBC-HS512",
|
||||
"apu": "QWxpY2U",
|
||||
"apv": "Qm9iIGFuZCBDaGFybGll"
|
||||
},
|
||||
"unprotected": {
|
||||
"jku": "https://alice.example.com/keys.jwks"
|
||||
},
|
||||
"recipients": [
|
||||
{
|
||||
"header": {
|
||||
"kid": "bob-key-2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"kid": "2021-05-06"
|
||||
}
|
||||
}
|
||||
],
|
||||
"aad": b'Authenticate me too.'
|
||||
}
|
||||
"""
|
||||
if not isinstance(keys, list): # single key
|
||||
keys = [keys]
|
||||
|
||||
if not keys:
|
||||
raise ValueError("No keys have been provided")
|
||||
|
||||
header_obj = deepcopy(header_obj)
|
||||
|
||||
shared_header = JWESharedHeader.from_dict(header_obj)
|
||||
|
||||
recipients = header_obj.get('recipients')
|
||||
if recipients is None:
|
||||
recipients = [{} for _ in keys]
|
||||
for i in range(len(recipients)):
|
||||
if recipients[i] is None:
|
||||
recipients[i] = {}
|
||||
if 'header' not in recipients[i]:
|
||||
recipients[i]['header'] = {}
|
||||
|
||||
jwe_aad = header_obj.get('aad')
|
||||
|
||||
if len(keys) != len(recipients):
|
||||
raise ValueError("Count of recipient keys {} does not equal to count of recipients {}"
|
||||
.format(len(keys), len(recipients)))
|
||||
|
||||
# step 1: Prepare algorithms & key
|
||||
alg = self.get_header_alg(shared_header)
|
||||
enc = self.get_header_enc(shared_header)
|
||||
zip_alg = self.get_header_zip(shared_header)
|
||||
|
||||
self._validate_sender_key(sender_key, alg)
|
||||
self._validate_private_headers(shared_header, alg)
|
||||
for recipient in recipients:
|
||||
self._validate_private_headers(recipient['header'], alg)
|
||||
|
||||
for i in range(len(keys)):
|
||||
keys[i] = prepare_key(alg, recipients[i]['header'], keys[i])
|
||||
if sender_key is not None:
|
||||
sender_key = alg.prepare_key(sender_key)
|
||||
|
||||
# self._post_validate_header(protected, algorithm)
|
||||
|
||||
# step 2: Generate a random Content Encryption Key (CEK)
|
||||
# use enc_alg.generate_cek() in scope of upcoming .wrap or .generate_keys_and_prepare_headers call
|
||||
|
||||
# step 3: Encrypt the CEK with the recipient's public key
|
||||
preset = alg.generate_preset(enc, keys[0])
|
||||
if 'cek' in preset:
|
||||
cek = preset['cek']
|
||||
else:
|
||||
cek = None
|
||||
if len(keys) > 1 and cek is None:
|
||||
raise InvalidAlgorithmForMultipleRecipientsMode(alg.name)
|
||||
if 'header' in preset:
|
||||
shared_header.update_protected(preset['header'])
|
||||
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement) and alg.key_size is not None:
|
||||
# For a JWE algorithm with tag-aware key agreement in case key agreement with key wrapping mode is used:
|
||||
# Defer key agreement with key wrapping until authentication tag is computed
|
||||
epks = []
|
||||
for i in range(len(keys)):
|
||||
prep = alg.generate_keys_and_prepare_headers(enc, keys[i], sender_key, preset)
|
||||
if cek is None:
|
||||
cek = prep['cek']
|
||||
epks.append(prep['epk'])
|
||||
recipients[i]['header'].update(prep['header'])
|
||||
else:
|
||||
# In any other case:
|
||||
# Keep the normal steps order defined by RFC 7516
|
||||
for i in range(len(keys)):
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
|
||||
wrapped = alg.wrap(enc, shared_header, keys[i], sender_key, preset)
|
||||
else:
|
||||
wrapped = alg.wrap(enc, shared_header, keys[i], preset)
|
||||
if cek is None:
|
||||
cek = wrapped['cek']
|
||||
recipients[i]['encrypted_key'] = wrapped['ek']
|
||||
if 'header' in wrapped:
|
||||
recipients[i]['header'].update(wrapped['header'])
|
||||
|
||||
# step 4: Generate a random JWE Initialization Vector
|
||||
iv = enc.generate_iv()
|
||||
|
||||
# step 5: Compute the Encoded Protected Header value
|
||||
# BASE64URL(UTF8(JWE Protected Header)). If the JWE Protected Header
|
||||
# is not present, let this value be the empty string.
|
||||
# Let the Additional Authenticated Data encryption parameter be
|
||||
# ASCII(Encoded Protected Header). However, if a JWE AAD value is
|
||||
# present, instead let the Additional Authenticated Data encryption
|
||||
# parameter be ASCII(Encoded Protected Header || '.' || BASE64URL(JWE AAD)).
|
||||
aad = json_b64encode(shared_header.protected) if shared_header.protected else b''
|
||||
if jwe_aad is not None:
|
||||
aad += b'.' + urlsafe_b64encode(jwe_aad)
|
||||
aad = to_bytes(aad, 'ascii')
|
||||
|
||||
# step 6: compress message if required
|
||||
if zip_alg:
|
||||
msg = zip_alg.compress(to_bytes(payload))
|
||||
else:
|
||||
msg = to_bytes(payload)
|
||||
|
||||
# step 7: perform encryption
|
||||
ciphertext, tag = enc.encrypt(msg, aad, iv, cek)
|
||||
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement) and alg.key_size is not None:
|
||||
# For a JWE algorithm with tag-aware key agreement in case key agreement with key wrapping mode is used:
|
||||
# Perform key agreement with key wrapping deferred at step 3
|
||||
for i in range(len(keys)):
|
||||
wrapped = alg.agree_upon_key_and_wrap_cek(enc, shared_header, keys[i], sender_key, epks[i], cek, tag)
|
||||
recipients[i]['encrypted_key'] = wrapped['ek']
|
||||
|
||||
# step 8: build resulting message
|
||||
obj = OrderedDict()
|
||||
|
||||
if shared_header.protected:
|
||||
obj['protected'] = to_unicode(json_b64encode(shared_header.protected))
|
||||
|
||||
if shared_header.unprotected:
|
||||
obj['unprotected'] = shared_header.unprotected
|
||||
|
||||
for recipient in recipients:
|
||||
if not recipient['header']:
|
||||
del recipient['header']
|
||||
recipient['encrypted_key'] = to_unicode(urlsafe_b64encode(recipient['encrypted_key']))
|
||||
for member in set(recipient.keys()):
|
||||
if member not in {'header', 'encrypted_key'}:
|
||||
del recipient[member]
|
||||
obj['recipients'] = recipients
|
||||
|
||||
if jwe_aad is not None:
|
||||
obj['aad'] = to_unicode(urlsafe_b64encode(jwe_aad))
|
||||
|
||||
obj['iv'] = to_unicode(urlsafe_b64encode(iv))
|
||||
|
||||
obj['ciphertext'] = to_unicode(urlsafe_b64encode(ciphertext))
|
||||
|
||||
obj['tag'] = to_unicode(urlsafe_b64encode(tag))
|
||||
|
||||
return obj
|
||||
|
||||
def serialize(self, header, payload, key, sender_key=None):
|
||||
"""Generate a JWE Serialization.
|
||||
|
||||
It will automatically generate a compact or JSON serialization depending
|
||||
on `header` argument. If `header` is a dict with "protected",
|
||||
"unprotected" and/or "recipients" keys, it will call `serialize_json`,
|
||||
otherwise it will call `serialize_compact`.
|
||||
|
||||
:param header: A dict of header(s)
|
||||
:param payload: Payload (bytes or a value convertible to bytes)
|
||||
:param key: Public key(s) used to encrypt payload
|
||||
:param sender_key: Sender's private key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: JWE compact serialization as bytes or
|
||||
JWE JSON serialization as dict
|
||||
"""
|
||||
if 'protected' in header or 'unprotected' in header or 'recipients' in header:
|
||||
return self.serialize_json(header, payload, key, sender_key)
|
||||
|
||||
return self.serialize_compact(header, payload, key, sender_key)
|
||||
|
||||
def deserialize_compact(self, s, key, decode=None, sender_key=None):
|
||||
"""Extract JWE Compact Serialization.
|
||||
|
||||
:param s: JWE Compact Serialization as bytes
|
||||
:param key: Private key used to decrypt payload
|
||||
(optionally can be a tuple of kid and essentially key)
|
||||
:param decode: Function to decode payload data
|
||||
:param sender_key: Sender's public key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: dict with `header` and `payload` keys where `header` value is
|
||||
a dict containing protected header fields
|
||||
"""
|
||||
try:
|
||||
s = to_bytes(s)
|
||||
protected_s, ek_s, iv_s, ciphertext_s, tag_s = s.rsplit(b'.')
|
||||
except ValueError:
|
||||
raise DecodeError('Not enough segments')
|
||||
|
||||
protected = extract_header(protected_s, DecodeError)
|
||||
ek = extract_segment(ek_s, DecodeError, 'encryption key')
|
||||
iv = extract_segment(iv_s, DecodeError, 'initialization vector')
|
||||
ciphertext = extract_segment(ciphertext_s, DecodeError, 'ciphertext')
|
||||
tag = extract_segment(tag_s, DecodeError, 'authentication tag')
|
||||
|
||||
alg = self.get_header_alg(protected)
|
||||
enc = self.get_header_enc(protected)
|
||||
zip_alg = self.get_header_zip(protected)
|
||||
|
||||
self._validate_sender_key(sender_key, alg)
|
||||
self._validate_private_headers(protected, alg)
|
||||
|
||||
if isinstance(key, tuple) and len(key) == 2:
|
||||
# Ignore separately provided kid, extract essentially key only
|
||||
key = key[1]
|
||||
|
||||
key = prepare_key(alg, protected, key)
|
||||
|
||||
if sender_key is not None:
|
||||
sender_key = alg.prepare_key(sender_key)
|
||||
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
|
||||
# For a JWE algorithm with tag-aware key agreement:
|
||||
if alg.key_size is not None:
|
||||
# In case key agreement with key wrapping mode is used:
|
||||
# Provide authentication tag to .unwrap method
|
||||
cek = alg.unwrap(enc, ek, protected, key, sender_key, tag)
|
||||
else:
|
||||
# Otherwise, don't provide authentication tag to .unwrap method
|
||||
cek = alg.unwrap(enc, ek, protected, key, sender_key)
|
||||
else:
|
||||
# For any other JWE algorithm:
|
||||
# Don't provide authentication tag to .unwrap method
|
||||
cek = alg.unwrap(enc, ek, protected, key)
|
||||
|
||||
aad = to_bytes(protected_s, 'ascii')
|
||||
msg = enc.decrypt(ciphertext, aad, iv, tag, cek)
|
||||
|
||||
if zip_alg:
|
||||
payload = zip_alg.decompress(to_bytes(msg))
|
||||
else:
|
||||
payload = msg
|
||||
|
||||
if decode:
|
||||
payload = decode(payload)
|
||||
return {'header': protected, 'payload': payload}
|
||||
|
||||
def deserialize_json(self, obj, key, decode=None, sender_key=None):
|
||||
"""Extract JWE JSON Serialization.
|
||||
|
||||
:param obj: JWE JSON Serialization as dict or str
|
||||
:param key: Private key used to decrypt payload
|
||||
(optionally can be a tuple of kid and essentially key)
|
||||
:param decode: Function to decode payload data
|
||||
:param sender_key: Sender's public key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: dict with `header` and `payload` keys where `header` value is
|
||||
a dict containing `protected`, `unprotected`, `recipients` and/or
|
||||
`aad` keys
|
||||
"""
|
||||
obj = ensure_dict(obj, 'JWE')
|
||||
obj = deepcopy(obj)
|
||||
|
||||
if 'protected' in obj:
|
||||
protected = extract_header(to_bytes(obj['protected']), DecodeError)
|
||||
else:
|
||||
protected = None
|
||||
|
||||
unprotected = obj.get('unprotected')
|
||||
|
||||
recipients = obj['recipients']
|
||||
for recipient in recipients:
|
||||
if 'header' not in recipient:
|
||||
recipient['header'] = {}
|
||||
recipient['encrypted_key'] = extract_segment(
|
||||
to_bytes(recipient['encrypted_key']), DecodeError, 'encrypted key')
|
||||
|
||||
if 'aad' in obj:
|
||||
jwe_aad = extract_segment(to_bytes(obj['aad']), DecodeError, 'JWE AAD')
|
||||
else:
|
||||
jwe_aad = None
|
||||
|
||||
iv = extract_segment(to_bytes(obj['iv']), DecodeError, 'initialization vector')
|
||||
|
||||
ciphertext = extract_segment(to_bytes(obj['ciphertext']), DecodeError, 'ciphertext')
|
||||
|
||||
tag = extract_segment(to_bytes(obj['tag']), DecodeError, 'authentication tag')
|
||||
|
||||
shared_header = JWESharedHeader(protected, unprotected)
|
||||
|
||||
alg = self.get_header_alg(shared_header)
|
||||
enc = self.get_header_enc(shared_header)
|
||||
zip_alg = self.get_header_zip(shared_header)
|
||||
|
||||
self._validate_sender_key(sender_key, alg)
|
||||
self._validate_private_headers(shared_header, alg)
|
||||
for recipient in recipients:
|
||||
self._validate_private_headers(recipient['header'], alg)
|
||||
|
||||
kid = None
|
||||
if isinstance(key, tuple) and len(key) == 2:
|
||||
# Extract separately provided kid and essentially key
|
||||
kid = key[0]
|
||||
key = key[1]
|
||||
|
||||
key = alg.prepare_key(key)
|
||||
|
||||
if kid is None:
|
||||
# If kid has not been provided separately, try to get it from key itself
|
||||
kid = key.kid
|
||||
|
||||
if sender_key is not None:
|
||||
sender_key = alg.prepare_key(sender_key)
|
||||
|
||||
def _unwrap_with_sender_key_and_tag(ek, header):
|
||||
return alg.unwrap(enc, ek, header, key, sender_key, tag)
|
||||
|
||||
def _unwrap_with_sender_key_and_without_tag(ek, header):
|
||||
return alg.unwrap(enc, ek, header, key, sender_key)
|
||||
|
||||
def _unwrap_without_sender_key_and_tag(ek, header):
|
||||
return alg.unwrap(enc, ek, header, key)
|
||||
|
||||
def _unwrap_for_matching_recipient(unwrap_func):
|
||||
if kid is not None:
|
||||
for recipient in recipients:
|
||||
if recipient['header'].get('kid') == kid:
|
||||
header = JWEHeader(protected, unprotected, recipient['header'])
|
||||
return unwrap_func(recipient['encrypted_key'], header)
|
||||
|
||||
# Since no explicit match has been found, iterate over all the recipients
|
||||
error = None
|
||||
for recipient in recipients:
|
||||
header = JWEHeader(protected, unprotected, recipient['header'])
|
||||
try:
|
||||
return unwrap_func(recipient['encrypted_key'], header)
|
||||
except Exception as e:
|
||||
error = e
|
||||
else:
|
||||
if error is None:
|
||||
raise KeyMismatchError()
|
||||
else:
|
||||
raise error
|
||||
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
|
||||
# For a JWE algorithm with tag-aware key agreement:
|
||||
if alg.key_size is not None:
|
||||
# In case key agreement with key wrapping mode is used:
|
||||
# Provide authentication tag to .unwrap method
|
||||
cek = _unwrap_for_matching_recipient(_unwrap_with_sender_key_and_tag)
|
||||
else:
|
||||
# Otherwise, don't provide authentication tag to .unwrap method
|
||||
cek = _unwrap_for_matching_recipient(_unwrap_with_sender_key_and_without_tag)
|
||||
else:
|
||||
# For any other JWE algorithm:
|
||||
# Don't provide authentication tag to .unwrap method
|
||||
cek = _unwrap_for_matching_recipient(_unwrap_without_sender_key_and_tag)
|
||||
|
||||
aad = to_bytes(obj.get('protected', ''))
|
||||
if 'aad' in obj:
|
||||
aad += b'.' + to_bytes(obj['aad'])
|
||||
aad = to_bytes(aad, 'ascii')
|
||||
|
||||
msg = enc.decrypt(ciphertext, aad, iv, tag, cek)
|
||||
|
||||
if zip_alg:
|
||||
payload = zip_alg.decompress(to_bytes(msg))
|
||||
else:
|
||||
payload = msg
|
||||
|
||||
if decode:
|
||||
payload = decode(payload)
|
||||
|
||||
for recipient in recipients:
|
||||
if not recipient['header']:
|
||||
del recipient['header']
|
||||
for member in set(recipient.keys()):
|
||||
if member != 'header':
|
||||
del recipient[member]
|
||||
|
||||
header = {}
|
||||
if protected:
|
||||
header['protected'] = protected
|
||||
if unprotected:
|
||||
header['unprotected'] = unprotected
|
||||
header['recipients'] = recipients
|
||||
if jwe_aad is not None:
|
||||
header['aad'] = jwe_aad
|
||||
|
||||
return {
|
||||
'header': header,
|
||||
'payload': payload
|
||||
}
|
||||
|
||||
def deserialize(self, obj, key, decode=None, sender_key=None):
|
||||
"""Extract a JWE Serialization.
|
||||
|
||||
It supports both compact and JSON serialization.
|
||||
|
||||
:param obj: JWE compact serialization as bytes or
|
||||
JWE JSON serialization as dict or str
|
||||
:param key: Private key used to decrypt payload
|
||||
(optionally can be a tuple of kid and essentially key)
|
||||
:param decode: Function to decode payload data
|
||||
:param sender_key: Sender's public key in case
|
||||
JWEAlgorithmWithTagAwareKeyAgreement is used
|
||||
:return: dict with `header` and `payload` keys
|
||||
"""
|
||||
if isinstance(obj, dict):
|
||||
return self.deserialize_json(obj, key, decode, sender_key)
|
||||
|
||||
obj = to_bytes(obj)
|
||||
if obj.startswith(b'{') and obj.endswith(b'}'):
|
||||
return self.deserialize_json(obj, key, decode, sender_key)
|
||||
|
||||
return self.deserialize_compact(obj, key, decode, sender_key)
|
||||
|
||||
@staticmethod
|
||||
def parse_json(obj):
|
||||
"""Parse JWE JSON Serialization.
|
||||
|
||||
:param obj: JWE JSON Serialization as str or dict
|
||||
:return: Parsed JWE JSON Serialization as dict if `obj` is an str,
|
||||
or `obj` as is if `obj` is already a dict
|
||||
"""
|
||||
return ensure_dict(obj, 'JWE')
|
||||
|
||||
def get_header_alg(self, header):
|
||||
if 'alg' not in header:
|
||||
raise MissingAlgorithmError()
|
||||
|
||||
alg = header['alg']
|
||||
if self._algorithms is not None and alg not in self._algorithms:
|
||||
raise UnsupportedAlgorithmError()
|
||||
if alg not in self.ALG_REGISTRY:
|
||||
raise UnsupportedAlgorithmError()
|
||||
return self.ALG_REGISTRY[alg]
|
||||
|
||||
def get_header_enc(self, header):
|
||||
if 'enc' not in header:
|
||||
raise MissingEncryptionAlgorithmError()
|
||||
enc = header['enc']
|
||||
if self._algorithms is not None and enc not in self._algorithms:
|
||||
raise UnsupportedEncryptionAlgorithmError()
|
||||
if enc not in self.ENC_REGISTRY:
|
||||
raise UnsupportedEncryptionAlgorithmError()
|
||||
return self.ENC_REGISTRY[enc]
|
||||
|
||||
def get_header_zip(self, header):
|
||||
if 'zip' in header:
|
||||
z = header['zip']
|
||||
if self._algorithms is not None and z not in self._algorithms:
|
||||
raise UnsupportedCompressionAlgorithmError()
|
||||
if z not in self.ZIP_REGISTRY:
|
||||
raise UnsupportedCompressionAlgorithmError()
|
||||
return self.ZIP_REGISTRY[z]
|
||||
|
||||
def _validate_sender_key(self, sender_key, alg):
|
||||
if isinstance(alg, JWEAlgorithmWithTagAwareKeyAgreement):
|
||||
if sender_key is None:
|
||||
raise ValueError("{} algorithm requires sender_key but passed sender_key value is None"
|
||||
.format(alg.name))
|
||||
else:
|
||||
if sender_key is not None:
|
||||
raise ValueError("{} algorithm does not use sender_key but passed sender_key value is not None"
|
||||
.format(alg.name))
|
||||
|
||||
def _validate_private_headers(self, header, alg):
|
||||
# only validate private headers when developers set
|
||||
# private headers explicitly
|
||||
if self._private_headers is None:
|
||||
return
|
||||
|
||||
names = self.REGISTERED_HEADER_PARAMETER_NAMES.copy()
|
||||
names = names.union(self._private_headers)
|
||||
|
||||
if alg.EXTRA_HEADERS:
|
||||
names = names.union(alg.EXTRA_HEADERS)
|
||||
|
||||
for k in header:
|
||||
if k not in names:
|
||||
raise InvalidHeaderParameterNameError(k)
|
||||
|
||||
|
||||
def prepare_key(alg, header, key):
|
||||
if callable(key):
|
||||
key = key(header, None)
|
||||
elif key is None and 'jwk' in header:
|
||||
key = header['jwk']
|
||||
return alg.prepare_key(key)
|
||||
Reference in New Issue
Block a user