126 lines
3.3 KiB
Python
126 lines
3.3 KiB
Python
"""
|
|
ⒸAngelaMos | 2025
|
|
Encryption endpoints for X3DH prekey bundles
|
|
"""
|
|
|
|
import logging
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, status
|
|
from pydantic import BaseModel
|
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
|
|
from app.models.Base import get_session
|
|
from app.services.prekey_service import prekey_service
|
|
from app.core.encryption.x3dh_manager import PreKeyBundle
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix = "/encryption", tags = ["encryption"])
|
|
|
|
|
|
class ClientKeysUpload(BaseModel):
|
|
identity_key: str
|
|
identity_key_ed25519: str
|
|
signed_prekey: str
|
|
signed_prekey_signature: str
|
|
one_time_prekeys: list[str]
|
|
|
|
|
|
@router.get(
|
|
"/prekey-bundle/{user_id}",
|
|
status_code = status.HTTP_200_OK,
|
|
response_model = PreKeyBundle
|
|
)
|
|
async def get_prekey_bundle(
|
|
user_id: UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> PreKeyBundle:
|
|
"""
|
|
Retrieves prekey bundle for initiating X3DH key exchange with a user
|
|
"""
|
|
bundle = await prekey_service.get_prekey_bundle(session, user_id)
|
|
|
|
unused_count = await prekey_service.get_unused_opk_count(session, user_id)
|
|
if unused_count < 20:
|
|
logger.info("User %s has %s OPKs, replenishing", user_id, unused_count)
|
|
await prekey_service.replenish_one_time_prekeys(session, user_id, 100)
|
|
|
|
return bundle
|
|
|
|
|
|
@router.post("/initialize-keys/{user_id}", status_code = status.HTTP_201_CREATED)
|
|
async def initialize_keys(
|
|
user_id: UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict[str,
|
|
str]:
|
|
"""
|
|
[DEPRECATED] Server-side key generation - kept for backwards compatibility
|
|
Initializes encryption keys for a user
|
|
"""
|
|
await prekey_service.initialize_user_keys(session, user_id)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": f"Initialized encryption keys for user {user_id}"
|
|
}
|
|
|
|
|
|
@router.post("/upload-keys/{user_id}", status_code = status.HTTP_201_CREATED)
|
|
async def upload_client_keys(
|
|
user_id: UUID,
|
|
keys: ClientKeysUpload,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict[str,
|
|
str]:
|
|
"""
|
|
Stores client-generated public keys for E2E encryption
|
|
"""
|
|
await prekey_service.store_client_keys(
|
|
session,
|
|
user_id,
|
|
keys.identity_key,
|
|
keys.identity_key_ed25519,
|
|
keys.signed_prekey,
|
|
keys.signed_prekey_signature,
|
|
keys.one_time_prekeys
|
|
)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": f"Stored client keys for user {user_id}"
|
|
}
|
|
|
|
|
|
@router.post("/rotate-signed-prekey/{user_id}", status_code = status.HTTP_200_OK)
|
|
async def rotate_signed_prekey(
|
|
user_id: UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict[str,
|
|
str]:
|
|
"""
|
|
Manually rotates signed prekey for a user
|
|
"""
|
|
await prekey_service.rotate_signed_prekey(session, user_id)
|
|
|
|
return {
|
|
"status": "success",
|
|
"message": f"Rotated signed prekey for user {user_id}"
|
|
}
|
|
|
|
|
|
@router.get("/opk-count/{user_id}", status_code = status.HTTP_200_OK)
|
|
async def get_opk_count(
|
|
user_id: UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
) -> dict[str,
|
|
int]:
|
|
"""
|
|
Returns count of unused one time prekeys for a user
|
|
"""
|
|
count = await prekey_service.get_unused_opk_count(session, user_id)
|
|
|
|
return {"unused_opks": count}
|