Refactors Cryptographer to remove logs and avoid multi return types

Cryptographer now uses exceptions to report errors and does not use the log anymore
This commit is contained in:
Sergi Delgado Segura
2020-04-09 17:57:40 +02:00
parent b4ea005f55
commit 08701f0fee
12 changed files with 98 additions and 181 deletions

View File

@@ -6,25 +6,24 @@ import requests
from sys import argv from sys import argv
from uuid import uuid4 from uuid import uuid4
from coincurve import PublicKey from coincurve import PublicKey
from requests import Timeout, ConnectionError
from getopt import getopt, GetoptError from getopt import getopt, GetoptError
from requests import Timeout, ConnectionError
from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL
from cli.exceptions import TowerResponseError
from cli import DEFAULT_CONF, DATA_DIR, CONF_FILE_NAME, LOG_PREFIX from cli import DEFAULT_CONF, DATA_DIR, CONF_FILE_NAME, LOG_PREFIX
from cli.exceptions import InvalidKey, InvalidParameter, TowerResponseError
from cli.help import show_usage, help_add_appointment, help_get_appointment, help_register, help_get_all_appointments from cli.help import show_usage, help_add_appointment, help_get_appointment, help_register, help_get_all_appointments
import common.cryptographer
from common import constants from common import constants
from common.logger import Logger from common.logger import Logger
from common.appointment import Appointment from common.appointment import Appointment
from common.config_loader import ConfigLoader from common.config_loader import ConfigLoader
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
from common.tools import setup_logging, setup_data_folder from common.tools import setup_logging, setup_data_folder
from common.exceptions import InvalidKey, InvalidParameter, SignatureError
from common.tools import is_256b_hex_str, is_locator, compute_locator, is_compressed_pk from common.tools import is_256b_hex_str, is_locator, compute_locator, is_compressed_pk
logger = Logger(actor="Client", log_name_prefix=LOG_PREFIX) logger = Logger(actor="Client", log_name_prefix=LOG_PREFIX)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
def register(compressed_pk, teos_url): def register(compressed_pk, teos_url):
@@ -107,10 +106,6 @@ def add_appointment(appointment_data, cli_sk, teos_pk, teos_url):
appointment = Appointment.from_dict(appointment_data) appointment = Appointment.from_dict(appointment_data)
signature = Cryptographer.sign(appointment.serialize(), cli_sk) signature = Cryptographer.sign(appointment.serialize(), cli_sk)
# FIXME: the cryptographer should return exception we can capture
if not signature:
raise ValueError("The provided appointment cannot be signed")
data = {"appointment": appointment.to_dict(), "signature": signature} data = {"appointment": appointment.to_dict(), "signature": signature}
# Send appointment to the server. # Send appointment to the server.
@@ -124,7 +119,7 @@ def add_appointment(appointment_data, cli_sk, teos_pk, teos_url):
raise TowerResponseError("The response does not contain the signature of the appointment") raise TowerResponseError("The response does not contain the signature of the appointment")
rpk = Cryptographer.recover_pk(appointment.serialize(), signature) rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
if not Cryptographer.verify_rpk(teos_pk, rpk): if not teos_pk != Cryptographer.get_compressed_pk(rpk):
raise TowerResponseError("The returned appointment's signature is invalid") raise TowerResponseError("The returned appointment's signature is invalid")
logger.info("Appointment accepted and signed by the Eye of Satoshi") logger.info("Appointment accepted and signed by the Eye of Satoshi")
@@ -467,7 +462,7 @@ def main(command, args, command_line_conf):
except (FileNotFoundError, IOError, ConnectionError, ValueError) as e: except (FileNotFoundError, IOError, ConnectionError, ValueError) as e:
logger.error(str(e)) logger.error(str(e))
except (InvalidKey, InvalidParameter, TowerResponseError) as e: except (InvalidKey, InvalidParameter, TowerResponseError, SignatureError) as e:
logger.error(e.reason, **e.kwargs) logger.error(e.reason, **e.kwargs)
except Exception as e: except Exception as e:
logger.error("Unknown error occurred", error=str(e)) logger.error("Unknown error occurred", error=str(e))

View File

@@ -7,13 +7,14 @@ from cryptography.exceptions import InvalidTag
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from common.tools import is_256b_hex_str from common.tools import is_256b_hex_str
from common.exceptions import InvalidKey, InvalidParameter, SignatureError, EncryptionError
LN_MESSAGE_PREFIX = b"Lightning Signed Message:" LN_MESSAGE_PREFIX = b"Lightning Signed Message:"
def sha256d(message): def sha256d(message):
""" """
Computes the double sha256 of a given by message. Computes the double sha256 of a given message.
Args: Args:
message(:obj:`bytes`): the message to be used as input to the hash function. message(:obj:`bytes`): the message to be used as input to the hash function.
@@ -87,10 +88,6 @@ def sigrec_decode(sigrec):
return rsig + rid return rsig + rid
# FIXME: Common has not log file, so it needs to log in the same log as the caller. This is a temporary fix.
logger = None
class Cryptographer: class Cryptographer:
""" """
The :class:`Cryptographer` is in charge of all the cryptography in the tower. The :class:`Cryptographer` is in charge of all the cryptography in the tower.
@@ -99,63 +96,49 @@ class Cryptographer:
@staticmethod @staticmethod
def check_data_key_format(data, secret): def check_data_key_format(data, secret):
""" """
Checks that the data and secret that will be used to by ``encrypt`` / ``decrypt`` are properly Checks that the data and secret that will be used to by ``encrypt`` / ``decrypt`` are properly formatted.
formatted.
Args: Args:
data(:obj:`str`): the data to be encrypted. data(:obj:`str`): the data to be encrypted.
secret(:obj:`str`): the secret used to derive the encryption key. secret(:obj:`str`): the secret used to derive the encryption key.
Returns:
:obj:`bool`: Whether or not the ``key`` and ``data`` are properly formatted.
Raises: Raises:
:obj:`ValueError`: if either the ``key`` or ``data`` is not properly formatted. :obj:`InvalidParameter`: if either the ``key`` and/or ``data`` are not properly formatted.
""" """
if len(data) % 2: if len(data) % 2:
error = "Incorrect (Odd-length) value" raise InvalidParameter("Incorrect (Odd-length) data", data=data)
raise ValueError(error)
if not is_256b_hex_str(secret): if not is_256b_hex_str(secret):
error = "Secret must be a 32-byte hex value (64 hex chars)" raise InvalidParameter("Secret must be a 32-byte hex value (64 hex chars)", secret=secret)
raise ValueError(error)
return True
@staticmethod @staticmethod
def encrypt(blob, secret): def encrypt(message, secret):
""" """
Encrypts a given :obj:`Blob <common.cli.blob.Blob>` data using ``CHACHA20POLY1305``. Encrypts a given message data using ``CHACHA20POLY1305``.
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``. ``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
Args: Args:
blob (:obj:`Blob <common.cli.blob.Blob>`): a ``Blob`` object containing a raw penalty transaction. message (:obj:`str`): a message to be encrypted. Should be the hex-encoded commitment_tx.
secret (:obj:`str`): a value to used to derive the encryption key. Should be the dispute txid. secret (:obj:`str`): a value to used to derive the encryption key. Should be the dispute txid.
Returns: Returns:
:obj:`str`: The encrypted data (hex encoded). :obj:`str`: The encrypted data (hex-encoded).
Raises: Raises:
:obj:`ValueError`: if either the ``secret`` or ``blob`` is not properly formatted. :obj:`InvalidParameter`: if either the ``key`` and/or ``data`` are not properly formatted.
""" """
Cryptographer.check_data_key_format(blob.data, secret) Cryptographer.check_data_key_format(message, secret)
# Transaction to be encrypted
# FIXME: The blob data should contain more things that just the transaction. Leaving like this for now.
tx = unhexlify(blob.data)
# sk is the H(txid) (32-byte) and nonce is set to 0 (12-byte) # sk is the H(txid) (32-byte) and nonce is set to 0 (12-byte)
sk = sha256(unhexlify(secret)).digest() sk = sha256(unhexlify(secret)).digest()
nonce = bytearray(12) nonce = bytearray(12)
logger.debug("Encrypting blob", sk=hexlify(sk).decode(), nonce=hexlify(nonce).decode(), blob=blob.data)
# Encrypt the data # Encrypt the data
cipher = ChaCha20Poly1305(sk) cipher = ChaCha20Poly1305(sk)
encrypted_blob = cipher.encrypt(nonce=nonce, data=tx, associated_data=None) encrypted_blob = cipher.encrypt(nonce=nonce, data=unhexlify(message), associated_data=None)
encrypted_blob = hexlify(encrypted_blob).decode("utf8") encrypted_blob = hexlify(encrypted_blob).decode("utf8")
return encrypted_blob return encrypted_blob
@@ -164,20 +147,20 @@ class Cryptographer:
# ToDo: #20-test-tx-decrypting-edge-cases # ToDo: #20-test-tx-decrypting-edge-cases
def decrypt(encrypted_blob, secret): def decrypt(encrypted_blob, secret):
""" """
Decrypts a given :obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` using ``CHACHA20POLY1305``. Decrypts a given encrypted_blob using ``CHACHA20POLY1305``.
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``. ``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
Args: Args:
encrypted_blob(:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): an ``EncryptedBlob`` encrypted_blob(:obj:`str`): an encrypted blob of data potentially containing a penalty transaction.
potentially containing a penalty transaction. secret (:obj:`str`): a value used to derive the decryption key. Should be the dispute txid.
secret (:obj:`str`): a value to used to derive the decryption key. Should be the dispute txid.
Returns: Returns:
:obj:`str`: The decrypted data (hex encoded). :obj:`str`: The decrypted data (hex-encoded).
Raises: Raises:
:obj:`ValueError`: if either the ``secret`` or ``encrypted_blob`` is not properly formatted. :obj:`InvalidParameter`: if either the ``key`` and/or ``data`` are not properly formatted.
:obj:`EncryptionError`: if the data cannot be decrypted with the given key.
""" """
Cryptographer.check_data_key_format(encrypted_blob, secret) Cryptographer.check_data_key_format(encrypted_blob, secret)
@@ -186,13 +169,6 @@ class Cryptographer:
sk = sha256(unhexlify(secret)).digest() sk = sha256(unhexlify(secret)).digest()
nonce = bytearray(12) nonce = bytearray(12)
logger.info(
"Decrypting blob",
sk=hexlify(sk).decode(),
nonce=hexlify(nonce).decode(),
encrypted_blob=encrypted_blob.data,
)
# Decrypt # Decrypt
cipher = ChaCha20Poly1305(sk) cipher = ChaCha20Poly1305(sk)
data = unhexlify(encrypted_blob) data = unhexlify(encrypted_blob)
@@ -202,8 +178,7 @@ class Cryptographer:
blob = hexlify(blob).decode("utf8") blob = hexlify(blob).decode("utf8")
except InvalidTag: except InvalidTag:
blob = None raise EncryptionError("Can't decrypt blob with the provided key", blob=encrypted_blob, key=secret)
logger.error("Can't decrypt blob with the provided key")
return blob return blob
@@ -216,12 +191,15 @@ class Cryptographer:
file_path (:obj:`str`): the path to the key file to be loaded. file_path (:obj:`str`): the path to the key file to be loaded.
Returns: Returns:
:obj:`bytes` or :obj:`None`: the key file data if the file can be found and read. ``None`` otherwise. :obj:`bytes`: the key file data if the file can be found and read.
Raises:
:obj:`InvalidParameter`: if the file_path has wrong format or cannot be found.
:obj:`InvalidKey`: if the key cannot be loaded from the file. It covers temporary I/O errors.
""" """
if not isinstance(file_path, str): if not isinstance(file_path, str):
logger.error("Key file path was expected, {} received".format(type(file_path))) raise InvalidParameter("Key file path was expected, {} received".format(type(file_path)))
return None
try: try:
with open(file_path, "rb") as key_file: with open(file_path, "rb") as key_file:
@@ -229,12 +207,10 @@ class Cryptographer:
return key return key
except FileNotFoundError: except FileNotFoundError:
logger.error("Key file not found at {}. Please check your settings".format(file_path)) raise InvalidParameter("Key file not found at {}. Please check your settings".format(file_path))
return None
except IOError as e: except IOError as e:
logger.error("I/O error({}): {}".format(e.errno, e.strerror)) raise InvalidKey("Key file cannot be loaded", exception=e)
return None
@staticmethod @staticmethod
def load_private_key_der(sk_der): def load_private_key_der(sk_der):
@@ -245,50 +221,52 @@ class Cryptographer:
sk_der(:obj:`str`): a private key encoded in ``DER`` format. sk_der(:obj:`str`): a private key encoded in ``DER`` format.
Returns: Returns:
:obj:`PrivateKey` or :obj:`None`: A ``PrivateKey`` object. if the private key can be loaded. `None` :obj:`PrivateKey`: A ``PrivateKey`` object if the private key can be loaded.
otherwise.
Raises:
:obj:`InvalidKey`: if a ``PrivateKey`` cannot be loaded from the given data.
""" """
try: try:
sk = PrivateKey.from_der(sk_der) sk = PrivateKey.from_der(sk_der)
return sk return sk
except ValueError: except ValueError:
logger.error("The provided data cannot be deserialized (wrong size or format)") raise InvalidKey("The provided key data cannot be deserialized (wrong size or format)")
except TypeError: except TypeError:
logger.error("The provided data cannot be deserialized (wrong type)") raise InvalidKey("The provided key data cannot be deserialized (wrong type)")
return None
@staticmethod @staticmethod
def sign(message, sk): def sign(message, sk):
""" """
Signs a given data using a given secret key using ECDSA over secp256k1. Signs a given message with a given secret key using ECDSA over secp256k1.
Args: Args:
message(:obj:`bytes`): the data to be signed. message(:obj:`bytes`): the data to be signed.
sk(:obj:`PrivateKey`): the ECDSA secret key used to signed the data. sk(:obj:`PrivateKey`): the ECDSA secret key to be used to sign the data.
Returns: Returns:
:obj:`str` or :obj:`None`: The zbase32 signature of the given message is it can be signed. `None` otherwise. :obj:`str`: The zbase32 signature of the given message is it can be signed.
Raises:
:obj:`InvalidParameter`: if the message and/or secret key have a wrong value.
:obj:`SignatureError`: if there is an error during the signing process.
""" """
if not isinstance(message, bytes): if not isinstance(message, bytes):
logger.error("The message must be bytes. {} received".format(type(message))) raise InvalidParameter("Wrong value passed as message. Received {}, expected (bytes)".format(type(message)))
return None
if not isinstance(sk, PrivateKey): if not isinstance(sk, PrivateKey):
logger.error("The value passed as sk is not a private key (PrivateKey)") raise InvalidParameter("Wrong value passed as sk. Received {}, expected (PrivateKey)".format(type(message)))
return None
try: try:
rsig_rid = sk.sign_recoverable(LN_MESSAGE_PREFIX + message, hasher=sha256d) rsig_rid = sk.sign_recoverable(LN_MESSAGE_PREFIX + message, hasher=sha256d)
sigrec = sigrec_encode(rsig_rid) sigrec = sigrec_encode(rsig_rid)
zb32_sig = pyzbase32.encode_bytes(sigrec).decode() zb32_sig = pyzbase32.encode_bytes(sigrec).decode()
except ValueError: except ValueError as e:
logger.error("Couldn't sign the message") raise SignatureError("Couldn't sign the message. " + str(e))
return None
return zb32_sig return zb32_sig
@@ -302,16 +280,20 @@ class Cryptographer:
zb32_sig(:obj:`str`): the zbase32 signature of the message. zb32_sig(:obj:`str`): the zbase32 signature of the message.
Returns: Returns:
:obj:`PublicKey` or :obj:`None`: The recovered public key if it can be recovered. `None` otherwise. :obj:`PublicKey`: The public key if it can be recovered.
Raises:
:obj:`InvalidParameter`: if the message and/or signature have a wrong value.
:obj:`SignatureError`: if a public key cannot be recovered from the given signature.
""" """
if not isinstance(message, bytes): if not isinstance(message, bytes):
logger.error("The message must be bytes. {} received".format(type(message))) raise InvalidParameter("Wrong value passed as message. Received {}, expected (bytes)".format(type(message)))
return None
if not isinstance(zb32_sig, str): if not isinstance(zb32_sig, str):
logger.error("The zbase32_sig must be str. {} received".format(type(zb32_sig))) raise InvalidParameter(
return None "Wrong value passed as zbase32_sig. Received {}, expected (str)".format(type(zb32_sig))
)
sigrec = pyzbase32.decode_bytes(zb32_sig) sigrec = pyzbase32.decode_bytes(zb32_sig)
@@ -323,31 +305,13 @@ class Cryptographer:
except ValueError as e: except ValueError as e:
# Several errors fit here: Signature length != 65, wrong recover id and failed to parse signature. # Several errors fit here: Signature length != 65, wrong recover id and failed to parse signature.
# All of them return raise ValueError. # All of them return raise ValueError.
logger.error(str(e)) raise SignatureError("Cannot recover a public key from the given signature. " + str(e))
return None
except Exception as e: except Exception as e:
if "failed to recover ECDSA public key" in str(e): if "failed to recover ECDSA public key" in str(e):
logger.error("Cannot recover public key from signature") raise SignatureError("Cannot recover a public key from the given signature")
else: else:
logger.error("Unknown exception", error=str(e)) raise SignatureError("Unknown exception. " + str(e))
return None
@staticmethod
def verify_rpk(pk, rpk):
"""
Verifies that that a recovered public key matches a given one.
Args:
pk(:obj:`PublicKey`): a given public key (provided by the user).
rpk(:obj:`PublicKey`): a public key recovered via ``recover_pk``.
Returns:
:obj:`bool`: True if the public keys match, False otherwise.
"""
return pk.point() == rpk.point()
@staticmethod @staticmethod
def get_compressed_pk(pk): def get_compressed_pk(pk):
@@ -358,18 +322,19 @@ class Cryptographer:
pk(:obj:`PublicKey`): a given public key. pk(:obj:`PublicKey`): a given public key.
Returns: Returns:
:obj:`str` or :obj:`None`: A compressed, hex-encoded, public key (33-byte long) if it can be compressed. :obj:`str`: A compressed, hex-encoded, public key (33-byte long) if it can be compressed.
`None` oterwise.
Raises:
:obj:`InvalidParameter`: if the value passed as public key is not a PublicKey object.
:obj:`InvalidKey`: if the public key has not been properly created.
""" """
if not isinstance(pk, PublicKey): if not isinstance(pk, PublicKey):
logger.error("The received data is not a PublicKey object") raise InvalidParameter("Wrong value passed as pk. Received {}, expected (PublicKey)".format(type(pk)))
return None
try: try:
compressed_pk = pk.format(compressed=True) compressed_pk = pk.format(compressed=True)
return hexlify(compressed_pk).decode("utf-8") return hexlify(compressed_pk).decode("utf-8")
except TypeError as e: except TypeError as e:
logger.error("PublicKey has invalid initializer", error=str(e)) raise InvalidKey("PublicKey has invalid initializer", error=str(e))
return None

View File

@@ -1,5 +1,6 @@
from common.tools import is_compressed_pk from common.tools import is_compressed_pk
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
from common.exceptions import InvalidParameter, InvalidKey, SignatureError
class NotEnoughSlots(ValueError): class NotEnoughSlots(ValueError):
@@ -71,7 +72,7 @@ class Gatekeeper:
:obj:`IdentificationFailure`: if the user cannot be identified. :obj:`IdentificationFailure`: if the user cannot be identified.
""" """
if isinstance(message, bytes) and isinstance(signature, str): try:
rpk = Cryptographer.recover_pk(message, signature) rpk = Cryptographer.recover_pk(message, signature)
compressed_pk = Cryptographer.get_compressed_pk(rpk) compressed_pk = Cryptographer.get_compressed_pk(rpk)
@@ -80,7 +81,7 @@ class Gatekeeper:
else: else:
raise IdentificationFailure("User not found.") raise IdentificationFailure("User not found.")
else: except (InvalidParameter, InvalidKey, SignatureError):
raise IdentificationFailure("Wrong message or signature.") raise IdentificationFailure("Wrong message or signature.")
def fill_slots(self, user_pk, n): def fill_slots(self, user_pk, n):

View File

@@ -1,6 +1,5 @@
import re import re
import common.cryptographer
from common.logger import Logger from common.logger import Logger
from common.tools import is_locator from common.tools import is_locator
from common.constants import LOCATOR_LEN_HEX from common.constants import LOCATOR_LEN_HEX
@@ -9,7 +8,6 @@ from common.appointment import Appointment
from teos import errors, LOG_PREFIX from teos import errors, LOG_PREFIX
logger = Logger(actor="Inspector", log_name_prefix=LOG_PREFIX) logger = Logger(actor="Inspector", log_name_prefix=LOG_PREFIX)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
# FIXME: The inspector logs the wrong messages sent form the users. A possible attack surface would be to send a really # FIXME: The inspector logs the wrong messages sent form the users. A possible attack surface would be to send a really
# long field that, even if not accepted by TEOS, would be stored in the logs. This is a possible DoS surface # long field that, even if not accepted by TEOS, would be stored in the logs. This is a possible DoS surface

View File

@@ -3,7 +3,6 @@ from sys import argv, exit
from getopt import getopt, GetoptError from getopt import getopt, GetoptError
from signal import signal, SIGINT, SIGQUIT, SIGTERM from signal import signal, SIGINT, SIGQUIT, SIGTERM
import common.cryptographer
from common.logger import Logger from common.logger import Logger
from common.config_loader import ConfigLoader from common.config_loader import ConfigLoader
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
@@ -25,7 +24,6 @@ from teos.tools import can_connect_to_bitcoind, in_correct_network
from teos import LOG_PREFIX, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME from teos import LOG_PREFIX, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME
logger = Logger(actor="Daemon", log_name_prefix=LOG_PREFIX) logger = Logger(actor="Daemon", log_name_prefix=LOG_PREFIX)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
def handle_signals(signal_received, frame): def handle_signals(signal_received, frame):

View File

@@ -1,17 +1,17 @@
from queue import Queue from queue import Queue
from threading import Thread from threading import Thread
import common.cryptographer
from common.logger import Logger from common.logger import Logger
from common.tools import compute_locator from common.tools import compute_locator
from common.appointment import Appointment from common.appointment import Appointment
from common.exceptions import EncryptionError
from common.cryptographer import Cryptographer, hash_160 from common.cryptographer import Cryptographer, hash_160
from common.exceptions import InvalidParameter, SignatureError
from teos import LOG_PREFIX from teos import LOG_PREFIX
from teos.cleaner import Cleaner from teos.cleaner import Cleaner
logger = Logger(actor="Watcher", log_name_prefix=LOG_PREFIX) logger = Logger(actor="Watcher", log_name_prefix=LOG_PREFIX)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
class Watcher: class Watcher:
@@ -57,7 +57,7 @@ class Watcher:
last_known_block (:obj:`str`): the last block known by the ``Watcher``. last_known_block (:obj:`str`): the last block known by the ``Watcher``.
Raises: Raises:
ValueError: if `teos_sk_file` is not found. :obj:`InvalidKey <common.exceptions.InvalidKey>`: if teos sk cannot be loaded.
""" """
@@ -147,7 +147,14 @@ class Watcher:
self.db_manager.create_append_locator_map(appointment.locator, uuid) self.db_manager.create_append_locator_map(appointment.locator, uuid)
appointment_added = True appointment_added = True
signature = Cryptographer.sign(appointment.serialize(), self.signing_key)
try:
signature = Cryptographer.sign(appointment.serialize(), self.signing_key)
except (InvalidParameter, SignatureError):
# This should never happen since data is sanitized, just in case to avoid a crash
logger.error("Data couldn't be signed", appointment=appointment.to_dict())
signature = None
logger.info("New appointment accepted", locator=appointment.locator) logger.info("New appointment accepted", locator=appointment.locator)
@@ -297,7 +304,7 @@ class Watcher:
try: try:
penalty_rawtx = Cryptographer.decrypt(appointment.encrypted_blob, dispute_txid) penalty_rawtx = Cryptographer.decrypt(appointment.encrypted_blob, dispute_txid)
except ValueError: except EncryptionError:
penalty_rawtx = None penalty_rawtx = None
penalty_tx = self.block_processor.decode_raw_transaction(penalty_rawtx) penalty_tx = self.block_processor.decode_raw_transaction(penalty_rawtx)

View File

@@ -7,8 +7,6 @@ from binascii import hexlify
from coincurve import PrivateKey from coincurve import PrivateKey
from requests.exceptions import ConnectionError, Timeout from requests.exceptions import ConnectionError, Timeout
import common.cryptographer
from common.logger import Logger
from common.tools import compute_locator from common.tools import compute_locator
from common.appointment import Appointment from common.appointment import Appointment
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
@@ -18,8 +16,6 @@ from cli.exceptions import InvalidParameter, InvalidKey, TowerResponseError
from test.cli.unit.conftest import get_random_value_hex, get_config from test.cli.unit.conftest import get_random_value_hex, get_config
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=teos_cli.LOG_PREFIX)
config = get_config() config = get_config()
# dummy keys for the tests # dummy keys for the tests

View File

@@ -1,17 +1,12 @@
import os import os
from binascii import unhexlify from coincurve import PrivateKey, PublicKey
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from coincurve import PrivateKey, PublicKey
import common.cryptographer
from common.logger import Logger
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
from test.common.unit.conftest import get_random_value_hex from test.common.unit.conftest import get_random_value_hex
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix="")
data = "6097cdf52309b1b2124efeed36bd34f46dc1c25ad23ac86f28380f746254f777" data = "6097cdf52309b1b2124efeed36bd34f46dc1c25ad23ac86f28380f746254f777"
key = "b2e984a570f6f49bc38ace178e09147b0aa296cbb7c92eb01412f7e2d07b5659" key = "b2e984a570f6f49bc38ace178e09147b0aa296cbb7c92eb01412f7e2d07b5659"
encrypted_data = "8f31028097a8bf12a92e088caab5cf3fcddf0d35ed2b72c24b12269373efcdea04f9d2a820adafe830c20ff132d89810" encrypted_data = "8f31028097a8bf12a92e088caab5cf3fcddf0d35ed2b72c24b12269373efcdea04f9d2a820adafe830c20ff132d89810"
@@ -185,7 +180,7 @@ def test_sign_ground_truth():
sig = Cryptographer.sign(message, sk) sig = Cryptographer.sign(message, sk)
rpk = Cryptographer.recover_pk(message, sig) rpk = Cryptographer.recover_pk(message, sig)
assert Cryptographer.verify_rpk(PublicKey(unhexlify(c_lightning_rpk)), rpk) assert c_lightning_rpk == Cryptographer.get_compressed_pk(rpk)
def test_sign_wrong_sk(): def test_sign_wrong_sk():
@@ -221,7 +216,7 @@ def test_recover_pk_ground_truth():
rpk = Cryptographer.recover_pk(message, zsig) rpk = Cryptographer.recover_pk(message, zsig)
assert Cryptographer.verify_rpk(PublicKey(unhexlify(org_pk)), rpk) assert org_pk == Cryptographer.get_compressed_pk(rpk)
def test_recover_pk_wrong_inputs(): def test_recover_pk_wrong_inputs():
@@ -241,27 +236,6 @@ def test_recover_pk_wrong_inputs():
assert Cryptographer.recover_pk(message, bytes(104)) is None assert Cryptographer.recover_pk(message, bytes(104)) is None
def test_verify_pk():
sk, _ = generate_keypair()
message = b"Test message"
zbase32_sig = Cryptographer.sign(message, sk)
rpk = Cryptographer.recover_pk(message, zbase32_sig)
assert Cryptographer.verify_rpk(sk.public_key, rpk)
def test_verify_pk_wrong():
sk, _ = generate_keypair()
sk2, _ = generate_keypair()
message = b"Test message"
zbase32_sig = Cryptographer.sign(message, sk)
rpk = Cryptographer.recover_pk(message, zbase32_sig)
assert not Cryptographer.verify_rpk(sk2.public_key, rpk)
def test_get_compressed_pk(): def test_get_compressed_pk():
sk, pk = generate_keypair() sk, pk = generate_keypair()
compressed_pk = Cryptographer.get_compressed_pk(pk) compressed_pk = Cryptographer.get_compressed_pk(pk)

View File

@@ -8,8 +8,6 @@ from coincurve import PrivateKey
from cli.exceptions import TowerResponseError from cli.exceptions import TowerResponseError
from cli import teos_cli, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME from cli import teos_cli, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME
import common.cryptographer
from common.logger import Logger
from common.tools import compute_locator from common.tools import compute_locator
from common.appointment import Appointment from common.appointment import Appointment
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
@@ -25,7 +23,6 @@ from test.teos.e2e.conftest import (
) )
cli_config = get_config(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF) cli_config = get_config(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix="")
teos_base_endpoint = "http://{}:{}".format(cli_config.get("API_CONNECT"), cli_config.get("API_PORT")) teos_base_endpoint = "http://{}:{}".format(cli_config.get("API_CONNECT"), cli_config.get("API_PORT"))
teos_add_appointment_endpoint = "{}/add_appointment".format(teos_base_endpoint) teos_add_appointment_endpoint = "{}/add_appointment".format(teos_base_endpoint)
@@ -257,7 +254,7 @@ def test_appointment_wrong_decryption_key(bitcoin_cli):
# Check that the server has accepted the appointment # Check that the server has accepted the appointment
signature = response_json.get("signature") signature = response_json.get("signature")
rpk = Cryptographer.recover_pk(appointment.serialize(), signature) rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
assert Cryptographer.verify_rpk(teos_pk, rpk) is True assert teos_pk == Cryptographer.get_compressed_pk(rpk)
assert response_json.get("locator") == appointment.locator assert response_json.get("locator") == appointment.locator
# Trigger the appointment # Trigger the appointment

View File

@@ -10,25 +10,21 @@ from bitcoind_mock.bitcoind import BitcoindMock
from bitcoind_mock.conf import BTC_RPC_HOST, BTC_RPC_PORT from bitcoind_mock.conf import BTC_RPC_HOST, BTC_RPC_PORT
from bitcoind_mock.transaction import create_dummy_transaction from bitcoind_mock.transaction import create_dummy_transaction
from teos import DEFAULT_CONF
from teos.carrier import Carrier from teos.carrier import Carrier
from teos.tools import bitcoin_cli from teos.tools import bitcoin_cli
from teos.users_dbm import UsersDBM from teos.users_dbm import UsersDBM
from teos.gatekeeper import Gatekeeper from teos.gatekeeper import Gatekeeper
from teos import LOG_PREFIX, DEFAULT_CONF
from teos.responder import TransactionTracker from teos.responder import TransactionTracker
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.appointments_dbm import AppointmentsDBM from teos.appointments_dbm import AppointmentsDBM
import common.cryptographer
from common.logger import Logger
from common.tools import compute_locator from common.tools import compute_locator
from common.appointment import Appointment from common.appointment import Appointment
from common.constants import LOCATOR_LEN_HEX from common.constants import LOCATOR_LEN_HEX
from common.config_loader import ConfigLoader from common.config_loader import ConfigLoader
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
# Set params to connect to regtest for testing # Set params to connect to regtest for testing
DEFAULT_CONF["BTC_RPC_PORT"]["value"] = 18443 DEFAULT_CONF["BTC_RPC_PORT"]["value"] = 18443
DEFAULT_CONF["BTC_NETWORK"]["value"] = "regtest" DEFAULT_CONF["BTC_NETWORK"]["value"] = "regtest"

View File

@@ -2,19 +2,14 @@ import pytest
from binascii import unhexlify from binascii import unhexlify
import teos.errors as errors import teos.errors as errors
from teos import LOG_PREFIX
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.inspector import Inspector, InspectionFailed from teos.inspector import Inspector, InspectionFailed
import common.cryptographer
from common.logger import Logger
from common.appointment import Appointment from common.appointment import Appointment
from common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX from common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX
from test.teos.unit.conftest import get_random_value_hex, bitcoind_connect_params, get_config from test.teos.unit.conftest import get_random_value_hex, bitcoind_connect_params, get_config
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
NO_HEX_STRINGS = [ NO_HEX_STRINGS = [
"R" * LOCATOR_LEN_HEX, "R" * LOCATOR_LEN_HEX,
get_random_value_hex(LOCATOR_LEN_BYTES - 1) + "PP", get_random_value_hex(LOCATOR_LEN_BYTES - 1) + "PP",

View File

@@ -13,8 +13,6 @@ from teos.chain_monitor import ChainMonitor
from teos.appointments_dbm import AppointmentsDBM from teos.appointments_dbm import AppointmentsDBM
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
import common.cryptographer
from common.logger import Logger
from common.tools import compute_locator from common.tools import compute_locator
from common.cryptographer import Cryptographer from common.cryptographer import Cryptographer
@@ -28,9 +26,6 @@ from test.teos.unit.conftest import (
bitcoind_connect_params, bitcoind_connect_params,
) )
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
APPOINTMENTS = 5 APPOINTMENTS = 5
START_TIME_OFFSET = 1 START_TIME_OFFSET = 1
END_TIME_OFFSET = 1 END_TIME_OFFSET = 1
@@ -134,16 +129,16 @@ def test_add_appointment(watcher):
added_appointment, sig = watcher.add_appointment(appointment, user_pk) added_appointment, sig = watcher.add_appointment(appointment, user_pk)
assert added_appointment is True assert added_appointment is True
assert Cryptographer.verify_rpk( assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
watcher.signing_key.public_key, Cryptographer.recover_pk(appointment.serialize(), sig) Cryptographer.recover_pk(appointment.serialize(), sig)
) )
# Check that we can also add an already added appointment (same locator) # Check that we can also add an already added appointment (same locator)
added_appointment, sig = watcher.add_appointment(appointment, user_pk) added_appointment, sig = watcher.add_appointment(appointment, user_pk)
assert added_appointment is True assert added_appointment is True
assert Cryptographer.verify_rpk( assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
watcher.signing_key.public_key, Cryptographer.recover_pk(appointment.serialize(), sig) Cryptographer.recover_pk(appointment.serialize(), sig)
) )
# If two appointments with the same locator from the same user are added, they are overwritten, but if they come # If two appointments with the same locator from the same user are added, they are overwritten, but if they come
@@ -153,8 +148,8 @@ def test_add_appointment(watcher):
different_user_pk = get_random_value_hex(33) different_user_pk = get_random_value_hex(33)
added_appointment, sig = watcher.add_appointment(appointment, different_user_pk) added_appointment, sig = watcher.add_appointment(appointment, different_user_pk)
assert added_appointment is True assert added_appointment is True
assert Cryptographer.verify_rpk( assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
watcher.signing_key.public_key, Cryptographer.recover_pk(appointment.serialize(), sig) Cryptographer.recover_pk(appointment.serialize(), sig)
) )
assert len(watcher.locator_uuid_map[appointment.locator]) == 2 assert len(watcher.locator_uuid_map[appointment.locator]) == 2
@@ -172,8 +167,8 @@ def test_add_too_many_appointments(watcher):
added_appointment, sig = watcher.add_appointment(appointment, user_pk) added_appointment, sig = watcher.add_appointment(appointment, user_pk)
assert added_appointment is True assert added_appointment is True
assert Cryptographer.verify_rpk( assert Cryptographer.get_compressed_pk(watcher.signing_key.public_key) == Cryptographer.get_compressed_pk(
watcher.signing_key.public_key, Cryptographer.recover_pk(appointment.serialize(), sig) Cryptographer.recover_pk(appointment.serialize(), sig)
) )
appointment, dispute_tx = generate_dummy_appointment( appointment, dispute_tx = generate_dummy_appointment(