API reference¶
This page is generated from the source docstrings and type annotations.
Signing¶
URLAuth
¶
URLAuth(
keys: KeySet | Iterable,
*,
signing_key_id: str = "",
ignore_query_params: Iterable[str] | None = None,
ttl: int = 60 * 10,
)
Sign and verify URLs over the URL and an expiry, via a pluggable backend.
A signer is configured with a set of keys. Several keys may be supplied
so that keys can be rotated without invalidating signatures that are still
in flight: signing uses one key, but :meth:verify accepts any of them. The
keys may mix algorithms -- an HMAC key and an Ed25519 key can live in the
same :class:~pysigned.backends.KeySet.
The cryptography is delegated to the keyset's :class:~pysigned.backends.Backend,
which dispatches on each key's type. Everything else -- query
canonicalisation, expiry, rotation -- is backend-agnostic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys
|
KeySet | Iterable
|
A :class: |
required |
signing_key_id
|
str
|
Id of the key to sign with. Defaults to the most recently added key (which must be able to sign). |
''
|
ttl
|
int
|
Seconds a signature stays valid (default 10 minutes). |
60 * 10
|
Source code in src/pysigned/signature.py
sign
¶
Sign a URL, returning it with sig and exp query params added.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL being signed as a string. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The signed URL as a string. |
Source code in src/pysigned/signature.py
verify
¶
Verify a signature produced by :meth:sign.
Every configured key is checked, so signatures made with a rotated-out key still verify.
Source code in src/pysigned/signature.py
Keys¶
Key
dataclass
¶
Bases: KeyLike
A signing/verifying key: raw bytes plus a stable id.
Subclasses supply two hooks: _validate (raise on bad key material) and
_id_bytes (the bytes the id fingerprint is hashed from).
_id_bytes is not "the public part" of the key. It is only what the
fingerprint is computed over. A symmetric HMAC key has no public counterpart,
so its _id_bytes is the secret key itself -- safe to hash into an id only
because SHA-256 is one-way, and safe to show in repr only because repr
truncates. An asymmetric Ed25519 key uses its genuinely public bytes.
Ed25519KeyPair
¶
Ed25519KeyPair(
private_key: Ed25519PrivateKey | bytes,
public_key: Ed25519PublicKey | bytes | None = None,
id: str = "",
)
Bases: KeyLike
An Ed25519 keypair, wrapping a private key and its public key.
Can both sign and verify. Its id is fingerprinted from the public
key, so it matches the id of the corresponding :class:Ed25519PublicKey, and
neither the id nor the repr ever expose the seed.
Source code in src/pysigned/keys.py
generate
classmethod
¶
Generate a new random Ed25519 keypair.
from_private_bytes
classmethod
¶
Build a keypair from a 32-byte Ed25519 private seed.
Source code in src/pysigned/keys.py
public
¶
public() -> Ed25519PublicKey
Ed25519PublicKey
dataclass
¶
KeySet
¶
KeySet(
keys: Iterable[KeyValue], backend: Backend | None = None
)
An id-keyed, read-only collection of keys parsed by a backend.
Keys of different algorithms may be mixed freely; signing and verifying each key dispatches on its type via the backend.
Source code in src/pysigned/keys.py
from_jwks
classmethod
¶
from_jwks(
jwks: dict[str, Any], backend: Backend | None = None
) -> Self
Build a KeySet from a JWKS (a {"keys": [...]} mapping of JWKs).
Each JWK becomes an :class:HMACKey, :class:Ed25519KeyPair, or
:class:Ed25519PublicKey depending on its kty/crv and whether a
private component (d) is present.
Source code in src/pysigned/keys.py
from_env
classmethod
¶
from_env(
environment_key: str, backend: Backend | None = None
) -> Self
Build a KeySet from a JWKS stored as JSON in an environment variable.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/pysigned/keys.py
from_url
classmethod
¶
from_url(url: str, backend: Backend | None = None) -> Self
Build a KeySet by fetching a JWKS document over HTTP(S).
The body at url must be a JSON JWKS (a {"keys": [...]} mapping),
as produced by :meth:from_jwks.
Source code in src/pysigned/keys.py
Backend¶
Backend
¶
Parses key values and signs/verifies with whichever algorithm a key uses.
Every key value already carries its own algorithm -- raw bytes and
:class:~pysigned.keys.HMACKey are symmetric HMAC, while
:class:~pysigned.keys.Ed25519KeyPair /
:class:~pysigned.keys.Ed25519PublicKey are asymmetric -- so the backend
dispatches on the key type rather than being fixed to one algorithm. A single
:class:KeySet (and therefore a single :class:~pysigned.signature.URLAuth)
can hold HMAC and Ed25519 keys together. Everything algorithm-agnostic (URL
canonicalisation, expiry, key rotation) lives in
:class:~pysigned.signature.URLAuth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
digest
|
str
|
Hash name used for HMAC keys (default |
DIGEST
|
Source code in src/pysigned/backends.py
parse_key
¶
parse_key(value: KeyValue) -> Key | Ed25519KeyPair
Wrap a user-supplied key value as a :class:~pysigned.keys.KeyLike.
Already-wrapped keys pass through; raw bytes or a (bytes, id)
tuple become an :class:~pysigned.keys.HMACKey. Ed25519 keys must be
wrapped explicitly because raw bytes can't distinguish private from
public -- and, now, HMAC from Ed25519.
Source code in src/pysigned/backends.py
sign
¶
sign(key: Key | Ed25519KeyPair, message: bytes) -> str
Sign message with key, returning a hex-encoded signature.
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Source code in src/pysigned/backends.py
verify
¶
verify(
key: Key | Ed25519KeyPair | Ed25519PublicKey,
message: bytes,
signature: str,
) -> bool
Check signature against message for key.
Returns False (rather than raising) for an unrecognised key type or
a malformed/invalid signature.
Source code in src/pysigned/backends.py
FastAPI extension¶
SignedRoute
¶
SignedRoute(
*,
keyset: KeySet | Iterable | None = None,
keyset_getter: KeysetGetter | None = None,
signing_key_id: str = "",
ignore_query_params: Iterable[str] | None = None,
error_status: int = HTTP_403_FORBIDDEN,
ttl: int | None = None,
)
A FastAPI dependency that verifies a request's URL signature.
Wraps :class:~pysigned.URLAuth for use with FastAPI's dependency
injection. Wire it in via Depends, either on a single route or
globally on a router/app, and it raises an :class:~fastapi.HTTPException
when the request's URL fails verification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keyset
|
KeySet | Iterable | None
|
A fixed :class: |
None
|
keyset_getter
|
KeysetGetter | None
|
An async callable that resolves a
:class: |
None
|
signing_key_id
|
str
|
Id of the key new signatures would be signed with.
Unused for verification, but forwarded to
:class: |
''
|
ignore_query_params
|
Iterable[str] | None
|
Query params excluded from the signed message, e.g. tracking params appended after signing. |
None
|
error_status
|
int
|
HTTP status code raised when verification fails. Defaults to 403 Forbidden. |
HTTP_403_FORBIDDEN
|
ttl
|
int | None
|
Overrides :class: |
None
|
Source code in src/pysigned/extensions/fastapi.py
__call__
async
¶
Verify the request's URL, raising on failure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The incoming request, supplied by FastAPI. |
required |
Raises:
| Type | Description |
|---|---|
HTTPException
|
With |
ValueError
|
If |
Source code in src/pysigned/extensions/fastapi.py
KeysetGetter
¶
Bases: Protocol
Callable that resolves a :class:~pysigned.KeySet for a request.
Use this instead of a static keyset when the keys depend on
per-request state, e.g. fetching keys for a tenant from a database.