Improve decryption error handling

This commit is contained in:
2026-04-02 15:32:13 +03:00
Unverified
parent be7da286d4
commit 579b55477e
3 changed files with 66 additions and 9 deletions
@@ -14,7 +14,9 @@ import time
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import httpx import httpx
from nacl.exceptions import CryptoError
from fastapi import APIRouter, Depends, HTTPException, status, Request from fastapi import APIRouter, Depends, HTTPException, status, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -376,6 +378,20 @@ async def send_encrypted_message(
except HTTPException: except HTTPException:
raise raise
except CryptoError as e:
logger.warning(
"DM send: transport NaCl decrypt failed (message/file key mismatch or corrupt ciphertext): %s",
e,
)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=(
"Transport decryption failed: the encrypted message and each attachment must be "
"encrypted with the same client ephemeral keypair. For resumable uploads, the "
"ciphertext bytes on the server must match the transport fields in this request—"
"re-encrypt on the client or abort the upload session and start over."
),
) from e
except Exception as e: except Exception as e:
logger.exception("Error sending encrypted message: %s", e) logger.exception("Error sending encrypted message: %s", e)
raise HTTPException( raise HTTPException(
+16 -1
View File
@@ -10,6 +10,9 @@ import os
import logging import logging
import json import json
import httpx
from fastapi import HTTPException, status
# Import request models for in-process calls # Import request models for in-process calls
logger = logging.getLogger("uvicorn.error") logger = logging.getLogger("uvicorn.error")
@@ -418,11 +421,23 @@ async def process_message_with_files_in_messaging_service(
"files": transport_files, "files": transport_files,
} }
try: try:
import httpx
async with httpx.AsyncClient(timeout=timeout) as client: async with httpx.AsyncClient(timeout=timeout) as client:
r = await client.post(url, json=payload) r = await client.post(url, json=payload)
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
except httpx.HTTPStatusError as e:
if e.response.status_code == status.HTTP_400_BAD_REQUEST:
try:
body = e.response.json()
detail = body.get("detail", str(body)) if isinstance(body, dict) else str(body)
except Exception:
detail = (e.response.text or "").strip() or str(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=detail,
) from e
logger.error("Failed to process message+files in messaging service: %s", e)
raise
except Exception as e: except Exception as e:
logger.error("Failed to process message+files in messaging service: %s", e) logger.error("Failed to process message+files in messaging service: %s", e)
raise raise
+34 -8
View File
@@ -11,17 +11,21 @@ API Endpoints:
""" """
import logging import logging
import sys
import time import time
import base64 import base64
import os import os
from typing import Dict, Any from typing import Dict, Any
from fastapi import FastAPI, HTTPException, status from fastapi import FastAPI, HTTPException, status
from nacl.exceptions import CryptoError
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pydantic import BaseModel from pydantic import BaseModel
logger = logging.getLogger("uvicorn.error") logger = logging.getLogger("uvicorn.error")
_B64_DECODE_KW = {"validate": True} if sys.version_info >= (3, 11) else {}
# Import encryption modules # Import encryption modules
from .encryption import generate_nonce, TRANSPORT_NONCE_SIZE, decrypt_transport_blob, decrypt_transport_message from .encryption import generate_nonce, TRANSPORT_NONCE_SIZE, decrypt_transport_blob, decrypt_transport_message
from .processor import process_encrypted_message, process_encrypted_message_and_files from .processor import process_encrypted_message, process_encrypted_message_and_files
@@ -371,16 +375,29 @@ async def process_message_with_files(
plaintext_files: list[bytes] = [] plaintext_files: list[bytes] = []
filenames: list[str] = [] filenames: list[str] = []
for tf in transport_files: for idx, tf in enumerate(transport_files):
enc_b64 = tf.get("encrypted_file_data_b64", "") enc_b64 = tf.get("encrypted_file_data_b64", "")
transport_blob = base64.b64decode(enc_b64) try:
plaintext_files.append( transport_blob = base64.b64decode(enc_b64, **_B64_DECODE_KW)
decrypt_transport_blob( except Exception as e:
client_public_key_b64=client_public_key_b64, logger.error("Invalid base64 for transport file index=%s filename=%r: %s", idx, tf.get("filename"), e)
encrypted_blob=transport_blob, raise
ephemeral_private_key=private_key, try:
plaintext_files.append(
decrypt_transport_blob(
client_public_key_b64=client_public_key_b64,
encrypted_blob=transport_blob,
ephemeral_private_key=private_key,
)
) )
) except Exception as e:
logger.error(
"Transport file decrypt failed index=%s filename=%r (check same ephemeral as message): %s",
idx,
tf.get("filename"),
e,
)
raise
filenames.append(tf.get("filename", "file")) filenames.append(tf.get("filename", "file"))
return process_encrypted_message_and_files( return process_encrypted_message_and_files(
@@ -417,6 +434,15 @@ async def process_message_with_files_http(request: ProcessMessageWithFilesReques
recipient_public_key_b64=request.recipient_public_key_b64, recipient_public_key_b64=request.recipient_public_key_b64,
transport_files=transport_files, transport_files=transport_files,
) )
except CryptoError as e:
logger.warning("process-with-files: transport CryptoError: %s", e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=(
"Transport decryption failed: message and files must use the same client ephemeral "
"key as when file ciphertext was produced."
),
) from e
except Exception as e: except Exception as e:
logger.exception("Failed to process message with files: %s", e) logger.exception("Failed to process message with files: %s", e)
raise raise