110 lines
3.0 KiB
Python
110 lines
3.0 KiB
Python
from base64 import b64encode
|
|
from hashlib import sha256
|
|
|
|
import pytest
|
|
from pytest_mock.plugin import MockerFixture
|
|
|
|
from lnbits.utils.crypto import (
|
|
AESCipher,
|
|
fake_privkey,
|
|
random_secret_and_hash,
|
|
verify_preimage,
|
|
)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize(
|
|
"key",
|
|
[
|
|
"normal_string",
|
|
b"normal_bytes",
|
|
b"hex_string".hex(),
|
|
],
|
|
)
|
|
async def test_aes_encrypt_decrypt(key):
|
|
aes = AESCipher(key)
|
|
original_text = "Hello, World!"
|
|
encrypted_text = aes.encrypt(original_text.encode())
|
|
decrypted_text = aes.decrypt(encrypted_text)
|
|
assert original_text == decrypted_text
|
|
|
|
|
|
def test_random_secret_and_hash():
|
|
secret, payment_hash = random_secret_and_hash(16)
|
|
|
|
assert len(secret) == 32
|
|
assert payment_hash == sha256(bytes.fromhex(secret)).hexdigest()
|
|
|
|
|
|
def test_fake_privkey_is_deterministic():
|
|
assert fake_privkey("secret") == fake_privkey("secret")
|
|
assert fake_privkey("secret") != fake_privkey("other-secret")
|
|
|
|
|
|
def test_verify_preimage_success_and_failure():
|
|
preimage = "00" * 32
|
|
payment_hash = sha256(bytes.fromhex(preimage)).hexdigest()
|
|
|
|
assert verify_preimage(preimage, payment_hash) is True
|
|
assert verify_preimage(preimage, "0" * 64) is False
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_aes_urlsafe_encrypt_decrypt():
|
|
aes = AESCipher("normal_string")
|
|
|
|
encrypted_text = aes.encrypt(b"url-safe", urlsafe=True)
|
|
|
|
assert aes.decrypt(encrypted_text, urlsafe=True) == "url-safe"
|
|
|
|
|
|
def test_aes_derive_iv_and_key_requires_eight_byte_salt():
|
|
aes = AESCipher("normal_string")
|
|
|
|
with pytest.raises(ValueError, match="Salt must be 8 bytes"):
|
|
aes.derive_iv_and_key(b"short")
|
|
|
|
|
|
def test_aes_decrypt_rejects_invalid_salt_prefix():
|
|
aes = AESCipher("normal_string")
|
|
encrypted_text = b64encode(b"NotSalted__12345678ciphertext").decode()
|
|
|
|
with pytest.raises(ValueError, match="Invalid salt."):
|
|
aes.decrypt(encrypted_text)
|
|
|
|
|
|
def test_aes_decrypt_raises_for_cipher_errors(mocker: MockerFixture):
|
|
aes = AESCipher("normal_string")
|
|
fake_cipher = mocker.Mock()
|
|
fake_cipher.decrypt.side_effect = RuntimeError("boom")
|
|
|
|
mocker.patch.object(
|
|
aes,
|
|
"derive_iv_and_key",
|
|
return_value=(b"0" * aes.block_size, b"1" * 32),
|
|
)
|
|
mocker.patch("lnbits.utils.crypto.AES.new", return_value=fake_cipher)
|
|
|
|
encrypted_text = b64encode(b"Salted__12345678ciphertext").decode()
|
|
|
|
with pytest.raises(ValueError, match="Could not decrypt payload"):
|
|
aes.decrypt(encrypted_text)
|
|
|
|
|
|
def test_aes_decrypt_raises_for_invalid_utf8_output(mocker: MockerFixture):
|
|
aes = AESCipher("normal_string")
|
|
fake_cipher = mocker.Mock()
|
|
fake_cipher.decrypt.return_value = b"\xff\x01"
|
|
|
|
mocker.patch.object(
|
|
aes,
|
|
"derive_iv_and_key",
|
|
return_value=(b"0" * aes.block_size, b"1" * 32),
|
|
)
|
|
mocker.patch("lnbits.utils.crypto.AES.new", return_value=fake_cipher)
|
|
|
|
encrypted_text = b64encode(b"Salted__12345678ciphertext").decode()
|
|
|
|
with pytest.raises(ValueError, match="invalid UTF-8 data"):
|
|
aes.decrypt(encrypted_text)
|