wasat.certs
Client certificate generation and storage management for Gemini connections.
ClientCertCallback
ClientCertCallback = Callable[
[GeminiURI, ClientCertificateStore],
Coroutine[
None,
None,
Literal["transient", "persistent", "ignore"],
],
]
Async callback function signature for resolving a client certificate requirement.
This callback is invoked when a client certificate is required. It receives the requested GeminiURI and the ClientCertificateStore instance to query or update.
ClientCertificateStore
Bases: Protocol
Protocol defining the interface for client certificate storage and retrieval.
close
async
close() -> None
Close the store, cleaning up transient resources if necessary.
create_credentials
async
create_credentials(
uri: GeminiURI,
*,
transient: bool = False,
common_name: str | None = None,
valid_days: int | None = 365,
key_type: Literal["ecdsa", "rsa"] = "ecdsa",
rsa_key_size: int = 2048,
ecdsa_curve: str = "secp256r1",
email: str | None = None,
user_id: str | None = None,
domain: str | None = None,
organisation: str | None = None,
country: str | None = None,
) -> tuple[Path, Path]
Generate and save a new self-signed client certificate and private key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
|
bool
|
If True, the certificate is generated in a temporary directory and not registered in the persistent store. |
False
|
|
str | None
|
The Common Name (CN) for the certificate. Defaults to the host. |
None
|
|
int | None
|
Number of days the certificate should be valid. If None, the certificate will expire on 9999-12-31. |
365
|
|
Literal['ecdsa', 'rsa']
|
The key type to generate ('ecdsa' or 'rsa'). |
'ecdsa'
|
|
int
|
RSA key size in bits. |
2048
|
|
str
|
ECDSA curve name. |
'secp256r1'
|
|
str | None
|
Optional email address. |
None
|
|
str | None
|
Optional user identifier. |
None
|
|
str | None
|
Optional domain name for Subject Alternative Name. |
None
|
|
str | None
|
Optional organisation name. |
None
|
|
str | None
|
Optional two-letter country code. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Path, Path]
|
A tuple of (cert_path, key_path) representing the generated certificate and key. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the key type is unsupported, the RSA key size is not one of 2048, 3072, or 4096, the ECDSA curve is not 'secp256r1' or 'secp384r1', or the country code is not exactly two characters. |
OSError
|
If creating directories or writing the certificate or key file to disk fails. |
RuntimeError
|
If saving the credentials or updating the store index fails. |
delete_credentials
async
Delete the certificate and key associated with the matching scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if deleted, False if no matching scope was found. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If updating the store index fails. |
get_credentials
async
Retrieve the certificate and private key paths matching the given URI.
This should perform path prefix matching to find the most specific matching certificate for the requested host, port, and path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Path, Path] | None
|
A tuple of (cert_path, key_path) or None if no certificate is stored |
tuple[Path, Path] | None
|
for this URI's scope. |
has_exact_credentials
async
Check if a certificate exists for the exact scope of the URI.
This checks if a certificate has been registered specifically for the exact host, port, and path of the given URI (without traversing parent scopes).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if a certificate is registered for this exact scope, False otherwise. |
register_credentials
async
register_credentials(
uri: GeminiURI,
cert_path: str | Path,
key_path: str | Path,
*,
transient: bool = False,
) -> None
Register existing certificate and private key paths for the URI's scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
|
str | Path
|
Path to the existing client certificate. |
required |
|
str | Path
|
Path to the existing private key. |
required |
|
bool
|
If True, registers as transient. |
False
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the registry is persistent, and the source files do not exist at the specified paths. |
OSError
|
If copying the certificate or private key files fails, or if creating the persistent store directory fails. |
RuntimeError
|
If registering or persisting the credentials in the store index fails. |
FileClientCertificateStore
Bases: ClientCertificateStore
File-based client certificate and key store.
Saves certificate files as PEM pairs and maintains a certs.json registry file
mapping Gemini scopes (host[:port]/path) to certificate filenames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str | Path
|
The directory where certificates, keys, and the index are stored. |
required |
store_dir
instance-attribute
The directory path for storing certificates and index.
close
async
close() -> None
Close the store, cleaning up transient directories if any were created.
create_credentials
async
create_credentials(
uri: GeminiURI,
*,
transient: bool = False,
common_name: str | None = None,
valid_days: int | None = 365,
key_type: Literal["ecdsa", "rsa"] = "ecdsa",
rsa_key_size: int = 2048,
ecdsa_curve: str = "secp256r1",
email: str | None = None,
user_id: str | None = None,
domain: str | None = None,
organisation: str | None = None,
country: str | None = None,
) -> tuple[Path, Path]
Generate and save a new self-signed client certificate and private key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
|
bool
|
If True, the certificate is generated in a temporary directory and not registered in the persistent store. |
False
|
|
str | None
|
The Common Name (CN) for the certificate. Defaults to the host. |
None
|
|
int | None
|
Number of days the certificate should be valid. If None, the certificate will expire on 9999-12-31. |
365
|
|
Literal['ecdsa', 'rsa']
|
The key type to generate ('ecdsa' or 'rsa'). |
'ecdsa'
|
|
int
|
RSA key size in bits. |
2048
|
|
str
|
ECDSA curve name. |
'secp256r1'
|
|
str | None
|
Optional email address. |
None
|
|
str | None
|
Optional user identifier. |
None
|
|
str | None
|
Optional domain name for Subject Alternative Name. |
None
|
|
str | None
|
Optional organisation name. |
None
|
|
str | None
|
Optional two-letter country code. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Path, Path]
|
A tuple of (cert_path, key_path) representing the generated certificate and key. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the key type is unsupported, the RSA key size is not one of 2048, 3072, or 4096, the ECDSA curve is not 'secp256r1' or 'secp384r1', or the country code is not exactly two characters. |
OSError
|
If creating the persistent store directory or the temporary transient directory fails, or if writing the certificate or key file to disk fails. |
RuntimeError
|
If saving the updated index ( |
delete_credentials
async
Delete the certificate and key associated with the matching scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if deleted, False if no matching scope was found. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If saving the updated index ( |
get_credentials
async
Retrieve the certificate and private key paths matching the given URI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Path, Path] | None
|
A tuple of (cert_path, key_path) or None if no certificate is stored |
tuple[Path, Path] | None
|
for this URI's scope. |
has_exact_credentials
async
Check if a certificate exists for the exact scope of the URI.
This checks if a certificate has been registered specifically for the exact host, port, and path of the given URI (without traversing parent scopes).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if a certificate is registered for this exact scope, False otherwise. |
register_credentials
async
register_credentials(
uri: GeminiURI,
cert_path: str | Path,
key_path: str | Path,
*,
transient: bool = False,
) -> None
Register existing certificate and private key paths for the URI's scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
GeminiURI
|
The target GeminiURI. |
required |
|
str | Path
|
Path to the existing client certificate. |
required |
|
str | Path
|
Path to the existing private key. |
required |
|
bool
|
If True, registers as transient. |
False
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
OSError
|
If |
RuntimeError
|
If |
generate_self_signed_cert
generate_self_signed_cert(
common_name: str,
*,
key_type: Literal["ecdsa", "rsa"] = "ecdsa",
rsa_key_size: int = 2048,
ecdsa_curve: str = "secp256r1",
valid_days: int | None = 365,
email: str | None = None,
user_id: str | None = None,
domain: str | None = None,
organisation: str | None = None,
country: str | None = None,
) -> tuple[bytes, bytes]
Generate a self-signed client certificate and private key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
The Common Name (CN) for the certificate. |
required |
|
Literal['ecdsa', 'rsa']
|
The key type to generate ('ecdsa' or 'rsa'). |
'ecdsa'
|
|
int
|
RSA key size in bits. |
2048
|
|
str
|
ECDSA curve name. |
'secp256r1'
|
|
int | None
|
Certificate validity in days. If None, the certificate will expire on 9999-12-31. |
365
|
|
str | None
|
Optional email address. |
None
|
|
str | None
|
Optional user identifier. |
None
|
|
str | None
|
Optional domain name for Subject Alternative Name. |
None
|
|
str | None
|
Optional organisation name. |
None
|
|
str | None
|
Optional two-letter country code. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[bytes, bytes]
|
A tuple containing (cert_pem, key_pem) as bytes. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the key type is unsupported, the RSA key size is not one of 2048, 3072, or 4096, the ECDSA curve is not 'secp256r1' or 'secp384r1', or the country code is not exactly two characters. |