91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
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)))
|