first commit
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
from typing import overload, Any
|
||||
from collections.abc import Collection
|
||||
from ._rfc7515.model import (
|
||||
JWSAlgModel,
|
||||
HeaderMember,
|
||||
CompactSignature,
|
||||
GeneralJSONSignature,
|
||||
FlattenedJSONSignature,
|
||||
)
|
||||
from ._rfc7515.registry import (
|
||||
JWSRegistry,
|
||||
construct_registry,
|
||||
default_registry,
|
||||
)
|
||||
from ._rfc7515.compact import (
|
||||
sign_compact,
|
||||
verify_compact,
|
||||
detach_compact_content,
|
||||
)
|
||||
from ._rfc7515.json import (
|
||||
sign_general_json,
|
||||
sign_flattened_json,
|
||||
verify_general_json,
|
||||
verify_flattened_json,
|
||||
extract_general_json,
|
||||
detach_json_content,
|
||||
)
|
||||
from ._rfc7515.types import (
|
||||
HeaderDict,
|
||||
GeneralJSONSerialization,
|
||||
FlattenedJSONSerialization,
|
||||
)
|
||||
from ._rfc7797.util import is_rfc7797_enabled
|
||||
from ._rfc7797.compact import (
|
||||
sign_rfc7515_compact,
|
||||
extract_rfc7515_compact as extract_compact,
|
||||
)
|
||||
from ._rfc7797.json import (
|
||||
sign_rfc7797_json,
|
||||
extract_rfc7797_json as extract_flattened_json,
|
||||
)
|
||||
from .errors import BadSignatureError, MissingKeyError
|
||||
from .jwk import Key, KeyFlexible, guess_key
|
||||
from .jwa import setup_jws_algorithms
|
||||
from .util import to_bytes
|
||||
from .registry import Header
|
||||
|
||||
__all__ = [
|
||||
# types
|
||||
"HeaderDict",
|
||||
"GeneralJSONSerialization",
|
||||
"FlattenedJSONSerialization",
|
||||
# modules
|
||||
"JWSRegistry",
|
||||
"HeaderMember",
|
||||
"CompactSignature",
|
||||
"GeneralJSONSignature",
|
||||
"FlattenedJSONSignature",
|
||||
# methods
|
||||
"serialize_compact",
|
||||
"deserialize_compact",
|
||||
"extract_compact",
|
||||
"validate_compact",
|
||||
"serialize_json",
|
||||
"deserialize_json",
|
||||
"detach_content",
|
||||
# consts
|
||||
"default_registry",
|
||||
]
|
||||
|
||||
setup_jws_algorithms()
|
||||
|
||||
|
||||
def serialize_compact(
|
||||
protected: Header,
|
||||
payload: bytes | str,
|
||||
private_key: KeyFlexible | None,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> str:
|
||||
"""Generate a JWS Compact Serialization. The JWS Compact Serialization
|
||||
represents digitally signed or MACed content as a compact, URL-safe
|
||||
string, per Section 7.1.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
|
||||
BASE64URL(JWS Payload) || '.' ||
|
||||
BASE64URL(JWS Signature)
|
||||
|
||||
:param protected: protected header part of the JWS, in dict
|
||||
:param payload: payload data of the JWS, in bytes
|
||||
:param private_key: a flexible private key to sign the signature
|
||||
:param algorithms: a collection (list, tuple, or set) of allowed algorithms
|
||||
:param registry: a JWSRegistry to use
|
||||
:return: JWS in str
|
||||
"""
|
||||
if registry is None:
|
||||
registry = construct_registry(algorithms)
|
||||
|
||||
registry.check_header(protected)
|
||||
|
||||
is_rfc7797 = is_rfc7797_enabled(protected)
|
||||
obj = CompactSignature(protected, to_bytes(payload))
|
||||
alg: JWSAlgModel = registry.get_alg(protected["alg"])
|
||||
|
||||
# "none" algorithm requires no key
|
||||
key: Key | None = None
|
||||
if alg.name != "none":
|
||||
if private_key is None:
|
||||
raise MissingKeyError()
|
||||
|
||||
key = guess_key(private_key, obj, True, use="sig")
|
||||
alg.check_key(key)
|
||||
|
||||
if is_rfc7797:
|
||||
out = sign_rfc7515_compact(obj, alg, key)
|
||||
else:
|
||||
out = sign_compact(obj, alg, key)
|
||||
return out.decode("utf-8")
|
||||
|
||||
|
||||
def validate_compact(
|
||||
obj: CompactSignature,
|
||||
public_key: KeyFlexible | None,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> bool:
|
||||
"""Validate the JWS Compact Serialization with the given key.
|
||||
This method is usually used together with ``extract_compact``.
|
||||
|
||||
:param obj: object of the JWS Compact Serialization
|
||||
:param public_key: a flexible public key to verify the signature
|
||||
:param algorithms: a collection (list, tuple, or set) of allowed algorithms
|
||||
:param registry: a JWSRegistry to use
|
||||
"""
|
||||
if registry is None:
|
||||
registry = construct_registry(algorithms)
|
||||
|
||||
headers = obj.headers()
|
||||
registry.check_header(headers)
|
||||
alg: JWSAlgModel = registry.get_alg(headers["alg"])
|
||||
|
||||
# "none" algorithm requires no key
|
||||
if headers["alg"] == "none":
|
||||
return verify_compact(obj, alg, None)
|
||||
|
||||
if public_key is None:
|
||||
raise MissingKeyError()
|
||||
|
||||
key: Key = guess_key(public_key, obj, use="sig")
|
||||
alg.check_key(key)
|
||||
return verify_compact(obj, alg, key)
|
||||
|
||||
|
||||
def deserialize_compact(
|
||||
value: bytes | str,
|
||||
public_key: KeyFlexible | None,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
payload: bytes | str | None = None,
|
||||
) -> CompactSignature:
|
||||
"""Extract and validate the JWS Compact Serialization (in string, or bytes)
|
||||
with the given key. An JWE Compact Serialization looks like:
|
||||
|
||||
.. code-block:: text
|
||||
:caption: line breaks for display purposes only
|
||||
|
||||
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
|
||||
.
|
||||
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
|
||||
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
|
||||
.
|
||||
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
||||
|
||||
:param value: a string (or bytes) of the JWS Compact Serialization
|
||||
:param public_key: a flexible public key to verify the signature
|
||||
:param algorithms: a collection (list, tuple, or set) of allowed algorithms
|
||||
:param registry: a JWSRegistry to use
|
||||
:param payload: optional payload, required with detached content
|
||||
:raises BadSignatureError: when signature verification fails
|
||||
:return: object of the CompactSignature
|
||||
"""
|
||||
obj = extract_compact(to_bytes(value), payload, registry)
|
||||
if not validate_compact(obj, public_key, algorithms, registry):
|
||||
raise BadSignatureError()
|
||||
return obj
|
||||
|
||||
|
||||
@overload
|
||||
def serialize_json(
|
||||
members: list[HeaderDict],
|
||||
payload: bytes | str,
|
||||
private_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> GeneralJSONSerialization: ...
|
||||
|
||||
|
||||
@overload
|
||||
def serialize_json(
|
||||
members: HeaderDict,
|
||||
payload: bytes | str,
|
||||
private_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> FlattenedJSONSerialization: ...
|
||||
|
||||
|
||||
def serialize_json(
|
||||
members: HeaderDict | list[HeaderDict],
|
||||
payload: bytes | str,
|
||||
private_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> GeneralJSONSerialization | FlattenedJSONSerialization:
|
||||
"""Generate a JWS JSON Serialization (in dict). The JWS JSON Serialization
|
||||
represents digitally signed or MACed content as a JSON object. This representation
|
||||
is neither optimized for compactness nor URL-safe.
|
||||
|
||||
A general JWS JSON Serialization contains:
|
||||
|
||||
payload
|
||||
The "payload" member MUST be present and contain the value
|
||||
BASE64URL(JWS Payload).
|
||||
|
||||
signatures
|
||||
The "signatures" member value MUST be an array of JSON objects.
|
||||
Each object represents a signature or MAC over the JWS Payload and
|
||||
the JWS Protected Header.
|
||||
|
||||
A flatten JWS JSON Serialization looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{
|
||||
"payload":"<payload contents>",
|
||||
"protected":"<integrity-protected header contents>",
|
||||
"header":<non-integrity-protected header contents>,
|
||||
"signature":"<signature contents>"
|
||||
}
|
||||
"""
|
||||
if registry is None:
|
||||
registry = construct_registry(algorithms)
|
||||
|
||||
def find_key(obj: HeaderMember) -> Key:
|
||||
return guess_key(private_key, obj, True, use="sig")
|
||||
|
||||
_payload = to_bytes(payload)
|
||||
if isinstance(members, list):
|
||||
_members = [HeaderMember(**member) for member in members]
|
||||
return sign_general_json(_members, _payload, registry, find_key)
|
||||
else:
|
||||
member = HeaderMember(**members)
|
||||
if is_rfc7797_enabled(member.headers()):
|
||||
return sign_rfc7797_json(member, _payload, registry, find_key)
|
||||
return sign_flattened_json(member, _payload, registry, find_key)
|
||||
|
||||
|
||||
@overload
|
||||
def deserialize_json(
|
||||
value: GeneralJSONSerialization,
|
||||
public_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> GeneralJSONSignature: ...
|
||||
|
||||
|
||||
@overload
|
||||
def deserialize_json(
|
||||
value: FlattenedJSONSerialization,
|
||||
public_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> FlattenedJSONSignature: ...
|
||||
|
||||
|
||||
def deserialize_json(
|
||||
value: GeneralJSONSerialization | FlattenedJSONSerialization,
|
||||
public_key: KeyFlexible,
|
||||
algorithms: Collection[str] | None = None,
|
||||
registry: JWSRegistry | None = None,
|
||||
) -> GeneralJSONSignature | FlattenedJSONSignature:
|
||||
"""Extract and validate the JWS (in string) with the given key.
|
||||
|
||||
:param value: a dict of the JSON signature
|
||||
:param public_key: a flexible public key to verify the signature
|
||||
:param algorithms: a collection (list, tuple, or set) of allowed algorithms
|
||||
:param registry: a JWSRegistry to use
|
||||
:return: object of GeneralJSONSignature or FlattenedJSONSignature
|
||||
:raises BadSignatureError: when signature verification fails
|
||||
"""
|
||||
if registry is None:
|
||||
registry = construct_registry(algorithms)
|
||||
|
||||
def find_key(obj: HeaderMember) -> Key:
|
||||
return guess_key(public_key, obj, use="sig")
|
||||
|
||||
if "signatures" in value:
|
||||
general_obj = extract_general_json(value, registry)
|
||||
if not verify_general_json(general_obj, registry, find_key):
|
||||
raise BadSignatureError()
|
||||
return general_obj
|
||||
else:
|
||||
flattened_obj = extract_flattened_json(value, registry)
|
||||
if not verify_flattened_json(flattened_obj, registry, find_key):
|
||||
raise BadSignatureError()
|
||||
return flattened_obj
|
||||
|
||||
|
||||
@overload
|
||||
def detach_content(value: str) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
def detach_content(value: GeneralJSONSerialization) -> dict[str, Any]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def detach_content(value: FlattenedJSONSerialization) -> dict[str, Any]: ...
|
||||
|
||||
|
||||
def detach_content(value: Any) -> Any:
|
||||
"""In some contexts, it is useful to integrity-protect content that is
|
||||
not itself contained in a JWS. This method is an implementation of
|
||||
https://www.rfc-editor.org/rfc/rfc7515#appendix-F
|
||||
|
||||
It is used to detach the content of the compact and JSON serialization.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from joserfc import jws
|
||||
>>> from joserfc.jwk import OctKey
|
||||
>>> key = OctKey.import_key("secret")
|
||||
>>> encoded_text = jws.serialize_compact({"alg": "HS256"}, b"hello", key)
|
||||
>>> jws.detach_content(encoded_text)
|
||||
'eyJhbGciOiJIUzI1NiJ9..UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A'
|
||||
|
||||
You can also detach the JSON serialization:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> obj = jws.serialize_json({"protected": {"alg": "HS256"}}, b"hello", key)
|
||||
>>> jws.detach_content(obj)
|
||||
{
|
||||
'payload': '',
|
||||
'signature': 'UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A',
|
||||
'protected': 'eyJhbGciOiJIUzI1NiJ9'
|
||||
}
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return detach_compact_content(value)
|
||||
return detach_json_content(value)
|
||||
Reference in New Issue
Block a user