first commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import re
|
||||
from typing import Any
|
||||
from ..util import (
|
||||
to_bytes,
|
||||
to_str,
|
||||
json_b64encode,
|
||||
urlsafe_b64encode,
|
||||
urlsafe_b64decode,
|
||||
)
|
||||
from .._rfc7515.model import JWSAlgModel, CompactSignature
|
||||
from .._rfc7515.compact import decode_header
|
||||
from .._rfc7515.registry import JWSRegistry, default_registry
|
||||
from ..errors import DecodeError
|
||||
from .util import is_rfc7797_enabled
|
||||
|
||||
|
||||
def sign_rfc7515_compact(obj: CompactSignature, alg: JWSAlgModel, key: Any) -> bytes:
|
||||
header_segment = json_b64encode(obj.headers())
|
||||
signing_input = header_segment + b"." + obj.payload
|
||||
signature = urlsafe_b64encode(alg.sign(signing_input, key))
|
||||
|
||||
# if need to detach payload
|
||||
if __is_urlsafe_characters(obj.payload):
|
||||
out = signing_input + b"." + signature
|
||||
else:
|
||||
out = header_segment + b".." + signature
|
||||
return out
|
||||
|
||||
|
||||
def extract_rfc7515_compact(
|
||||
value: bytes, payload: bytes | str | None = None, registry: JWSRegistry | None = None
|
||||
) -> CompactSignature:
|
||||
"""Extract the JWS Compact Serialization from bytes to object.
|
||||
|
||||
:param value: JWS in bytes
|
||||
:param payload: optional payload, required with detached content
|
||||
:param registry: optional JWSRegistry instance
|
||||
:raise DecodeError: when decoding fails
|
||||
"""
|
||||
parts = value.split(b".")
|
||||
if len(parts) != 3:
|
||||
raise DecodeError("Invalid JSON Web Signature")
|
||||
|
||||
if registry is None:
|
||||
registry = default_registry
|
||||
|
||||
header_segment, payload_segment, signature_segment = parts
|
||||
|
||||
registry.validate_header_size(header_segment)
|
||||
registry.validate_signature_size(signature_segment)
|
||||
if payload_segment:
|
||||
registry.validate_payload_size(payload_segment)
|
||||
|
||||
protected = decode_header(header_segment)
|
||||
|
||||
if is_rfc7797_enabled(protected):
|
||||
if not payload_segment and payload:
|
||||
payload_segment = to_bytes(payload)
|
||||
registry.validate_payload_size(payload_segment)
|
||||
payload = payload_segment
|
||||
else:
|
||||
if not payload_segment and payload:
|
||||
payload = to_bytes(payload)
|
||||
payload_segment = urlsafe_b64encode(payload)
|
||||
else:
|
||||
try:
|
||||
payload = urlsafe_b64decode(payload_segment)
|
||||
except (TypeError, ValueError):
|
||||
raise DecodeError("Invalid payload")
|
||||
|
||||
obj = CompactSignature(protected, payload)
|
||||
obj.segments.update(
|
||||
{
|
||||
"header": header_segment,
|
||||
"payload": payload_segment,
|
||||
"signature": signature_segment,
|
||||
}
|
||||
)
|
||||
return obj
|
||||
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc7797#section-5.2
|
||||
# the application MUST ensure that the payload contains only the URL-safe
|
||||
# characters 'a'-'z', 'A'-'Z', '0'-'9', dash ('-'), underscore ('_'),
|
||||
# and tilde ('~')
|
||||
_re_urlsafe = re.compile("^[a-zA-Z0-9-_~]+$")
|
||||
|
||||
|
||||
def __is_urlsafe_characters(s: bytes | str) -> bool:
|
||||
return bool(_re_urlsafe.match(to_str(s)))
|
||||
@@ -0,0 +1,51 @@
|
||||
from .._rfc7515.types import FlattenedJSONSerialization, JSONSignatureDict
|
||||
from .._rfc7515.model import HeaderMember, FlattenedJSONSignature
|
||||
from .._rfc7515.registry import JWSRegistry
|
||||
from .._rfc7515.json import sign_json_member, FindKey
|
||||
from ..util import to_bytes, json_b64decode, urlsafe_b64decode
|
||||
from ..errors import DecodeError
|
||||
from .util import is_rfc7797_enabled
|
||||
|
||||
|
||||
def sign_rfc7797_json(
|
||||
member: HeaderMember,
|
||||
payload: bytes,
|
||||
registry: JWSRegistry,
|
||||
find_key: FindKey,
|
||||
) -> FlattenedJSONSerialization:
|
||||
signature = sign_json_member(payload, member, registry, find_key)
|
||||
data: FlattenedJSONSerialization = {"payload": payload.decode("utf-8"), **signature}
|
||||
return data
|
||||
|
||||
|
||||
def extract_rfc7797_json(value: FlattenedJSONSerialization, registry: JWSRegistry) -> FlattenedJSONSignature:
|
||||
if "protected" in value:
|
||||
protected_segment = to_bytes(value["protected"])
|
||||
registry.validate_header_size(protected_segment)
|
||||
protected = json_b64decode(protected_segment)
|
||||
else:
|
||||
protected = None
|
||||
|
||||
header = value.get("header")
|
||||
member = HeaderMember(protected, header)
|
||||
|
||||
payload_segment: bytes = value["payload"].encode("utf-8")
|
||||
registry.validate_payload_size(payload_segment)
|
||||
|
||||
if is_rfc7797_enabled(member.headers()):
|
||||
payload = payload_segment
|
||||
else:
|
||||
try:
|
||||
payload = urlsafe_b64decode(payload_segment)
|
||||
except (TypeError, ValueError):
|
||||
raise DecodeError("Invalid payload")
|
||||
|
||||
obj = FlattenedJSONSignature(member, payload)
|
||||
_sig: JSONSignatureDict = {"signature": value["signature"]}
|
||||
if "protected" in value:
|
||||
_sig["protected"] = value["protected"]
|
||||
if "header" in value:
|
||||
_sig["header"] = value["header"]
|
||||
obj.signature = _sig
|
||||
obj.segments = {"payload": payload_segment}
|
||||
return obj
|
||||
@@ -0,0 +1,17 @@
|
||||
from ..registry import Header
|
||||
from ..errors import MissingCritHeaderError
|
||||
|
||||
|
||||
def is_rfc7797_enabled(header: Header) -> bool:
|
||||
if "b64" not in header:
|
||||
return False
|
||||
|
||||
if header["b64"] is True:
|
||||
return False
|
||||
|
||||
# https://datatracker.ietf.org/doc/html/rfc7797#section-6
|
||||
crit = header.get("crit")
|
||||
if isinstance(crit, list) and "b64" in crit:
|
||||
return True
|
||||
|
||||
raise MissingCritHeaderError("b64")
|
||||
Reference in New Issue
Block a user