mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Improve decryption error handling
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,9 +375,14 @@ 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:
|
||||||
|
transport_blob = base64.b64decode(enc_b64, **_B64_DECODE_KW)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Invalid base64 for transport file index=%s filename=%r: %s", idx, tf.get("filename"), e)
|
||||||
|
raise
|
||||||
|
try:
|
||||||
plaintext_files.append(
|
plaintext_files.append(
|
||||||
decrypt_transport_blob(
|
decrypt_transport_blob(
|
||||||
client_public_key_b64=client_public_key_b64,
|
client_public_key_b64=client_public_key_b64,
|
||||||
@@ -381,6 +390,14 @@ async def process_message_with_files(
|
|||||||
ephemeral_private_key=private_key,
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user