Newer
Older
gnexus-creds / tests / test_crypto.py
import hashlib

import pytest
from cryptography.exceptions import InvalidTag

from gnexus_creds import crypto
from gnexus_creds.errors import AppError


def test_derive_without_salt_is_legacy_sha256():
    assert crypto.derive_master_key("key") == hashlib.sha256(b"key").digest()


def test_derive_with_salt_is_stretched_and_differs():
    legacy = crypto.derive_master_key("key")
    salted = crypto.derive_master_key("key", "install-salt")
    assert salted != legacy
    assert len(salted) == 32
    # same call again hits the cache and stays deterministic
    assert crypto.derive_master_key("key", "install-salt") == salted


def test_text_envelope_roundtrip_with_aad():
    key = crypto.new_raw_key()
    payload = crypto.encrypt_text(key, "uk_abc", "hint", aad="user:secret:version")
    assert crypto.decrypt_text(key, payload, aad="user:secret:version") == "hint"


def test_legacy_envelope_still_decrypts_with_aad_context():
    # rows written before the aad binding carry no bound context — the
    # tolerant decrypt keeps them open (migration rebinds them)
    key = crypto.new_raw_key()
    payload = crypto.encrypt_text(key, "uk_abc", "legacy")
    assert crypto.decrypt_text(key, payload, aad="user:secret:version") == "legacy"


def test_transplanted_envelope_fails_authentication():
    key = crypto.new_raw_key()
    p1 = crypto.encrypt_text(key, "uk_abc", "one", aad="user:s1:v1")
    p2 = crypto.encrypt_text(key, "uk_abc", "two", aad="user:s2:v2")
    # transplant p2's ciphertext into p1's envelope (same user, same key)
    p1["ciphertext"] = p2["ciphertext"]
    p1["nonce"] = p2["nonce"]
    with pytest.raises(InvalidTag):
        crypto.decrypt_text(key, p1, aad="user:s1:v1")


def test_list_secrets_rejects_unknown_sort_by(db_session, actor):
    from gnexus_creds.models import Secret
    from gnexus_creds.services import list_secrets

    with pytest.raises(AppError) as exc:
        list_secrets(db_session, actor, sort_by="__dict__")
    assert exc.value.status_code == 422
    with pytest.raises(AppError) as exc:
        list_secrets(db_session, actor, sort_by="versions")  # a relationship
    assert exc.value.status_code == 422
    # whitelist values keep working
    list_secrets(db_session, actor, sort_by="title")
    list_secrets(db_session, actor, sort_by="updated_at")
    assert list_secrets(db_session, actor, limit=200)[1] == 0