Cybersecurity-Projects/PROJECTS/advanced/encrypted-p2p-chat/backend/app/api/encryption.py

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}