first commit
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import copy
|
||||
from typing import Any, Callable
|
||||
from .model import (
|
||||
HeaderMember,
|
||||
GeneralJSONSignature,
|
||||
FlattenedJSONSignature,
|
||||
)
|
||||
from .types import (
|
||||
JSONSignatureDict,
|
||||
GeneralJSONSerialization,
|
||||
FlattenedJSONSerialization,
|
||||
)
|
||||
from .registry import JWSRegistry
|
||||
from ..registry import reject_unprotected_crit_header
|
||||
from ..util import (
|
||||
to_bytes,
|
||||
json_b64encode,
|
||||
json_b64decode,
|
||||
urlsafe_b64encode,
|
||||
urlsafe_b64decode,
|
||||
)
|
||||
from ..errors import DecodeError
|
||||
|
||||
__all__ = [
|
||||
"FindKey",
|
||||
"sign_general_json",
|
||||
"sign_flattened_json",
|
||||
"sign_json_member",
|
||||
"extract_general_json",
|
||||
"verify_general_json",
|
||||
"verify_flattened_json",
|
||||
"detach_json_content",
|
||||
]
|
||||
|
||||
FindKey = Callable[[HeaderMember], Any]
|
||||
|
||||
|
||||
def sign_general_json(
|
||||
members: list[HeaderMember],
|
||||
payload: bytes,
|
||||
registry: JWSRegistry,
|
||||
find_key: FindKey,
|
||||
) -> GeneralJSONSerialization:
|
||||
payload_segment = urlsafe_b64encode(payload)
|
||||
signatures: list[JSONSignatureDict] = [
|
||||
sign_json_member(payload_segment, member, registry, find_key) for member in members
|
||||
]
|
||||
return {
|
||||
"payload": payload_segment.decode("utf-8"),
|
||||
"signatures": signatures,
|
||||
}
|
||||
|
||||
|
||||
def sign_flattened_json(
|
||||
member: HeaderMember,
|
||||
payload: bytes,
|
||||
registry: JWSRegistry,
|
||||
find_key: FindKey,
|
||||
) -> FlattenedJSONSerialization:
|
||||
payload_segment = urlsafe_b64encode(payload)
|
||||
signature = sign_json_member(payload_segment, member, registry, find_key)
|
||||
data: FlattenedJSONSerialization = {"payload": payload_segment.decode("utf-8"), **signature}
|
||||
return data
|
||||
|
||||
|
||||
def sign_json_member(
|
||||
payload_segment: bytes, member: HeaderMember, registry: JWSRegistry, find_key: FindKey
|
||||
) -> JSONSignatureDict:
|
||||
reject_unprotected_crit_header(member.header)
|
||||
headers = member.headers()
|
||||
registry.check_header(headers)
|
||||
alg = registry.get_alg(headers["alg"])
|
||||
key = find_key(member)
|
||||
alg.check_key(key)
|
||||
if member.protected:
|
||||
protected_segment = json_b64encode(member.protected)
|
||||
else:
|
||||
protected_segment = b""
|
||||
signing_input = b".".join([protected_segment, payload_segment])
|
||||
signature = urlsafe_b64encode(alg.sign(signing_input, key))
|
||||
rv: JSONSignatureDict = {"signature": signature.decode("utf-8")}
|
||||
if member.protected:
|
||||
rv["protected"] = protected_segment.decode("utf-8")
|
||||
if member.header:
|
||||
rv["header"] = member.header
|
||||
return rv
|
||||
|
||||
|
||||
def extract_general_json(value: GeneralJSONSerialization, registry: JWSRegistry) -> GeneralJSONSignature:
|
||||
payload_segment: bytes = value["payload"].encode("utf-8")
|
||||
registry.validate_payload_size(payload_segment)
|
||||
try:
|
||||
payload = urlsafe_b64decode(payload_segment)
|
||||
except (TypeError, ValueError):
|
||||
raise DecodeError("Invalid payload")
|
||||
|
||||
signatures: list[JSONSignatureDict] = value["signatures"]
|
||||
members = [__signature_to_member(sig, registry) for sig in signatures]
|
||||
obj = GeneralJSONSignature(members, payload)
|
||||
obj.signatures = signatures
|
||||
obj.segments = {"payload": payload_segment}
|
||||
return obj
|
||||
|
||||
|
||||
def __signature_to_member(sig: JSONSignatureDict, registry: JWSRegistry) -> HeaderMember:
|
||||
member = HeaderMember()
|
||||
if "protected" in sig:
|
||||
protected_segment = to_bytes(sig["protected"])
|
||||
registry.validate_header_size(protected_segment)
|
||||
member.protected = json_b64decode(protected_segment)
|
||||
if "header" in sig:
|
||||
member.header = sig["header"]
|
||||
return member
|
||||
|
||||
|
||||
def verify_general_json(obj: GeneralJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool:
|
||||
payload_segment = obj.segments["payload"]
|
||||
if not obj.signatures:
|
||||
return False
|
||||
|
||||
for index, signature in enumerate(obj.signatures):
|
||||
member = obj.members[index]
|
||||
if not verify_signature(member, signature, payload_segment, registry, find_key):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def verify_flattened_json(obj: FlattenedJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool:
|
||||
payload_segment = obj.segments["payload"]
|
||||
assert obj.signature is not None
|
||||
return verify_signature(obj.member, obj.signature, payload_segment, registry, find_key)
|
||||
|
||||
|
||||
def verify_signature(
|
||||
member: HeaderMember,
|
||||
signature: JSONSignatureDict,
|
||||
payload_segment: bytes,
|
||||
registry: JWSRegistry,
|
||||
find_key: FindKey,
|
||||
) -> bool:
|
||||
reject_unprotected_crit_header(member.header)
|
||||
headers = member.headers()
|
||||
registry.check_header(headers)
|
||||
alg = registry.get_alg(headers["alg"])
|
||||
key = find_key(member)
|
||||
alg.check_key(key)
|
||||
|
||||
if "protected" in signature:
|
||||
protected_segment = to_bytes(signature["protected"])
|
||||
else:
|
||||
protected_segment = b""
|
||||
|
||||
signature_segment = to_bytes(signature["signature"])
|
||||
registry.validate_signature_size(signature_segment)
|
||||
|
||||
sig = urlsafe_b64decode(signature_segment)
|
||||
signing_input = b".".join([protected_segment, payload_segment])
|
||||
return alg.verify(signing_input, sig, key)
|
||||
|
||||
|
||||
def detach_json_content(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# https://www.rfc-editor.org/rfc/rfc7515#appendix-F
|
||||
rv = copy.deepcopy(value) # don't alter original value
|
||||
if "payload" in rv:
|
||||
del rv["payload"]
|
||||
return rv
|
||||
Reference in New Issue
Block a user