Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict Allow Data Change to SpeculosBackend #15

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
33 changes: 14 additions & 19 deletions tests/functional/apps/eos.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from base58 import b58encode
from contextlib import contextmanager
from enum import IntEnum
from pycoin.ecdsa.secp256k1 import secp256k1_generator
from typing import Generator
import hashlib

from bip_utils.addr import EosAddrEncoder

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
Expand All @@ -13,6 +13,7 @@

from ragger.backend.interface import BackendInterface, RAPDU
from ragger.utils import split_message
from ragger.bip import pack_derivation_path


class INS(IntEnum):
Expand Down Expand Up @@ -86,16 +87,7 @@ def send_get_app_configuration(self) -> (bool, (int, int, int)):
return data_allowed, (major, minor, patch)

def compute_adress_from_public_key(self, public_key: bytes) -> str:

head = 0x03 if (public_key[64] & 0x01) == 0x01 else 0x02
public_key_compressed = bytearray([head]) + public_key[1:33]

ripemd = hashlib.new('ripemd160')
ripemd.update(public_key_compressed)
check = ripemd.digest()[:4]

buff = b58encode(public_key_compressed + check).decode("ascii")
return "EOS" + buff
return EosAddrEncoder.EncodeKey(public_key)

def parse_get_public_key_response(self, response: bytes, request_chaincode: bool) -> (bytes, str, bytes):
# response = public_key_len (1) ||
Expand Down Expand Up @@ -125,20 +117,22 @@ def parse_get_public_key_response(self, response: bytes, request_chaincode: bool

return public_key, address, chaincode

def send_get_public_key_non_confirm(self, derivation_path: bytes,
def send_get_public_key_non_confirm(self, derivation_path: str,
request_chaincode: bool) -> RAPDU:
p1 = P1_NON_CONFIRM
p2 = P2_CHAINCODE if request_chaincode else P2_NO_CHAINCODE
payload = pack_derivation_path(derivation_path)
return self._client.exchange(CLA, INS.INS_GET_PUBLIC_KEY,
p1, p2, derivation_path)
p1, p2, payload)

@contextmanager
def send_async_get_public_key_confirm(self, derivation_path: bytes,
def send_async_get_public_key_confirm(self, derivation_path: str,
request_chaincode: bool) -> Generator[None, None, None]:
p1 = P1_CONFIRM
p2 = P2_CHAINCODE if request_chaincode else P2_NO_CHAINCODE
payload = pack_derivation_path(derivation_path)
with self._client.exchange_async(CLA, INS.INS_GET_PUBLIC_KEY,
p1, p2, derivation_path):
p1, p2, payload):
yield

def _send_sign_message(self, message: bytes, first: bool) -> RAPDU:
Expand All @@ -159,9 +153,10 @@ def _send_async_sign_message(self, message: bytes,
yield

def send_async_sign_message(self,
derivation_path: bytes,
derivation_path: str,
message: bytes) -> Generator[None, None, None]:
messages = split_message(derivation_path + message, MAX_CHUNK_SIZE)
payload = pack_derivation_path(derivation_path) + message
messages = split_message(payload, MAX_CHUNK_SIZE)
first = True

if len(messages) > 1:
Expand All @@ -182,7 +177,7 @@ def check_canonical(self, signature: bytes) -> None:
assert signature[33] & 0x80 == 0
assert signature[33] != 0 or (signature[34] & 0x80) != 0

def verify_signature(self, derivation_path: bytes,
def verify_signature(self, derivation_path: str,
signing_digest: bytes, signature: bytes) -> None:
assert len(signature) == 65
self.check_canonical(signature)
Expand Down
28 changes: 15 additions & 13 deletions tests/functional/conftest.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import pytest
from pathlib import Path
from ragger import Firmware
from ragger.firmware import Firmware
from ragger.backend import SpeculosBackend, LedgerCommBackend, LedgerWalletBackend
from ragger.navigator import NanoNavigator
from ragger.utils import app_path_from_app_name


# This variable is needed for Speculos only (physical tests need the application to be already installed)
# Adapt this path to your 'tests/elfs' directory
APPS_DIRECTORY = (Path(__file__).parent.parent / "elfs").resolve()

# Adapt this name part of the compiled app <name>_<device>.elf in the APPS_DIRECTORY
APP_NAME = "eos"

BACKENDS = ["speculos", "ledgercomm", "ledgerwallet"]
Expand All @@ -28,7 +30,7 @@ def pytest_addoption(parser):


@pytest.fixture(scope="session")
def backend(pytestconfig):
def backend_name(pytestconfig):
return pytestconfig.getoption("backend")


Expand Down Expand Up @@ -85,29 +87,29 @@ def prepare_speculos_args(firmware: Firmware, display: bool):
# Depending on the "--backend" option value, a different backend is
# instantiated, and the tests will either run on Speculos or on a physical
# device depending on the backend
def create_backend(backend: str, firmware: Firmware, display: bool):
if backend.lower() == "ledgercomm":
def create_backend(backend_name: str, firmware: Firmware, display: bool):
if backend_name.lower() == "ledgercomm":
return LedgerCommBackend(firmware, interface="hid")
elif backend.lower() == "ledgerwallet":
elif backend_name.lower() == "ledgerwallet":
return LedgerWalletBackend(firmware)
elif backend.lower() == "speculos":
elif backend_name.lower() == "speculos":
args, kwargs = prepare_speculos_args(firmware, display)
return SpeculosBackend(*args, firmware, **kwargs)
else:
raise ValueError(f"Backend '{backend}' is unknown. Valid backends are: {BACKENDS}")
raise ValueError(f"Backend '{backend_name}' is unknown. Valid backends are: {BACKENDS}")


# This final fixture will return the properly configured backend client, to be used in tests
# This final fixture will return the properly configured backend, to be used in tests
@pytest.fixture
def client(backend, firmware, display):
with create_backend(backend, firmware, display) as b:
def backend(backend_name, firmware, display):
with create_backend(backend_name, firmware, display) as b:
yield b


@pytest.fixture
def navigator(client, firmware, golden_run):
def navigator(backend, firmware, golden_run):
if firmware.device.startswith("nano"):
return NanoNavigator(client, firmware, golden_run)
return NanoNavigator(backend, firmware, golden_run)
else:
raise ValueError(f"Device '{firmware.device}' is unsupported.")

Expand All @@ -117,7 +119,7 @@ def use_only_on_backend(request, backend):
if request.node.get_closest_marker('use_on_backend'):
current_backend = request.node.get_closest_marker('use_on_backend').args[0]
if current_backend != backend:
pytest.skip('skipped on this backend: {}'.format(current_backend))
pytest.skip(f'skipped on this backend: "{current_backend}"')


def pytest_configure(config):
Expand Down
3 changes: 2 additions & 1 deletion tests/functional/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ragger[tests,speculos]>=0.8.0
ragger[tests,speculos]>=1.1.0
base58
bip_utils
pycoin
asn1
Loading