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) 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)))