d582f15681
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
59 lines
1.6 KiB
Python
59 lines
1.6 KiB
Python
import os
|
|
from dataclasses import dataclass
|
|
|
|
from cryptography.exceptions import InvalidTag
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
|
from app.config import get_settings
|
|
from app.errors import AppError
|
|
|
|
_DEK_AAD = b"finance-import-dek-v1"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class EncryptedFile:
|
|
ciphertext: bytes
|
|
content_nonce: bytes
|
|
dek_wrapped: bytes
|
|
wrap_nonce: bytes
|
|
|
|
|
|
def encrypt_file(plaintext: bytes) -> EncryptedFile:
|
|
"""Per-file random DEK; DEK wrapped with the KEK from the secret manager."""
|
|
kek = get_settings().kek
|
|
dek = os.urandom(32)
|
|
content_nonce = os.urandom(12)
|
|
ciphertext = AESGCM(dek).encrypt(content_nonce, plaintext, None)
|
|
wrap_nonce = os.urandom(12)
|
|
dek_wrapped = AESGCM(kek).encrypt(wrap_nonce, dek, _DEK_AAD)
|
|
return EncryptedFile(
|
|
ciphertext=ciphertext,
|
|
content_nonce=content_nonce,
|
|
dek_wrapped=dek_wrapped,
|
|
wrap_nonce=wrap_nonce,
|
|
)
|
|
|
|
|
|
def decrypt_file(
|
|
*,
|
|
ciphertext: bytes,
|
|
content_nonce: bytes,
|
|
dek_wrapped: bytes,
|
|
wrap_nonce: bytes,
|
|
) -> bytes:
|
|
kek = get_settings().kek
|
|
try:
|
|
dek = AESGCM(kek).decrypt(wrap_nonce, dek_wrapped, _DEK_AAD)
|
|
return AESGCM(dek).decrypt(content_nonce, ciphertext, None)
|
|
except InvalidTag as exc:
|
|
raise AppError(
|
|
"Encrypted document failed integrity verification",
|
|
code="IMPORT_DECRYPT_FAILED",
|
|
status_code=409,
|
|
) from exc
|
|
|
|
|
|
def shred_dek_material() -> None:
|
|
"""Crypto-shred hook: deleting wrapped DEK rows in DB plus blobs removes access."""
|
|
return None
|