Skip to content

Commit

Permalink
Merge pull request #592 from LedgerHQ/cev/add-ledger-pki
Browse files Browse the repository at this point in the history
Add Ledger PKI
  • Loading branch information
cedelavergne-ledger authored Aug 7, 2024
2 parents c480a31 + 338f7ce commit 1964621
Show file tree
Hide file tree
Showing 19 changed files with 455 additions and 437 deletions.
113 changes: 113 additions & 0 deletions client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import rlp
from enum import IntEnum
from ragger.backend import BackendInterface
from ragger.firmware import Firmware
from ragger.error import ExceptionRAPDU
from ragger.utils import RAPDU
from typing import Optional

Expand All @@ -22,6 +24,7 @@ class StatusWord(IntEnum):
CONDITION_NOT_SATISFIED = 0x6985
REF_DATA_NOT_FOUND = 0x6a88
EXCEPTION_OVERFLOW = 0x6807
NOT_IMPLEMENTED = 0x911c


class DomainNameTag(IntEnum):
Expand All @@ -36,10 +39,52 @@ class DomainNameTag(IntEnum):
ADDRESS = 0x22


class PKIPubKeyUsage(IntEnum):
PUBKEY_USAGE_GENUINE_CHECK = 0x01
PUBKEY_USAGE_EXCHANGE_PAYLOAD = 0x02
PUBKEY_USAGE_NFT_METADATA = 0x03
PUBKEY_USAGE_TRUSTED_NAME = 0x04
PUBKEY_USAGE_BACKUP_PROVIDER = 0x05
PUBKEY_USAGE_RECOVER_ORCHESTRATOR = 0x06
PUBKEY_USAGE_PLUGIN_METADATA = 0x07
PUBKEY_USAGE_COIN_META = 0x08
PUBKEY_USAGE_SEED_ID_AUTH = 0x09


class PKIClient:
_CLA: int = 0xB0
_INS: int = 0x06

def __init__(self, client: BackendInterface) -> None:
self._client = client

def send_certificate(self, p1: PKIPubKeyUsage, payload: bytes) -> RAPDU:
try:
response = self.send_raw(p1, payload)
assert response.status == StatusWord.OK
except ExceptionRAPDU as err:
if err.status == StatusWord.NOT_IMPLEMENTED:
print("Ledger-PKI APDU not yet implemented. Legacy path will be used")

def send_raw(self, p1: PKIPubKeyUsage, payload: bytes) -> RAPDU:
header = bytearray()
header.append(self._CLA)
header.append(self._INS)
header.append(p1)
header.append(0x00)
header.append(len(payload))
return self._client.exchange_raw(header + payload)


class EthAppClient:
def __init__(self, client: BackendInterface):
self._client = client
self._firmware = client.firmware
self._cmd_builder = CommandBuilder()
self._pki_client: Optional[PKIClient] = None
if self._firmware != Firmware.NANOS:
# LedgerPKI not supported on Nanos
self._pki_client = PKIClient(self._client)

def _exchange_async(self, payload: bytes):
return self._client.exchange_async_raw(payload)
Expand Down Expand Up @@ -168,6 +213,23 @@ def perform_privacy_operation(self,
pubkey))

def provide_domain_name(self, challenge: int, name: str, addr: bytes) -> RAPDU:

if self._pki_client is None:
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
else:
# pylint: disable=line-too-long
if self._firmware == Firmware.NANOSP:
cert_apdu = "01010102010210040102000011040000000212010013020002140101160400000000200b446f6d61696e5f4e616d6530020007310108320121332102b91fbec173e3ba4a714e014ebc827b6f899a9fa7f4ac769cde284317a00f4f653401013501031546304402201b5188f5af5cd4d40d2e5eee85609323ee129b789082d079644c89c0df9b6ce0022076c5d26bb5c8db8ab02771ecd577f63f68eaf1c90523173f161f9c12f6e978bd" # noqa: E501
elif self._firmware == Firmware.NANOX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B446F6D61696E5F4E616D6530020007310108320121332102B91FBEC173E3BA4A714E014EBC827B6F899A9FA7F4AC769CDE284317A00F4F653401013501021546304402202CD052029B756890F0C56713409C58C24785FEFFD1A997E9C840A7BDB176B512022059A30E04E491CD27BD1DA1B5CB810CF8E4EAE67F6406F054FDFC371F7EB9F2C4" # noqa: E501
elif self._firmware == Firmware.STAX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B446F6D61696E5F4E616D6530020007310108320121332102B91FBEC173E3BA4A714E014EBC827B6F899A9FA7F4AC769CDE284317A00F4F65340101350104154630440220741DB4E738749D4188436419B20B9AEF8F07581312A9B3C9BAA3F3E879690F6002204C4A3510569247777BC43DB830D129ACA8985B88552E2E234E14D8AA2863026B" # noqa: E501
elif self._firmware == Firmware.FLEX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B446F6D61696E5F4E616D6530020007310108320121332102B91FBEC173E3BA4A714E014EBC827B6F899A9FA7F4AC769CDE284317A00F4F65340101350105154730450221008B6BBCE1716C0A06F110C77FE181F8395D1692441459A106411463F01A45D4A7022044AB69037E6FA9D1D1A409E00B202C2D4451D464C8E5D4962D509FE63153FE93" # noqa: E501
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))

payload = format_tlv(DomainNameTag.STRUCTURE_TYPE, 3) # TrustedDomainName
payload += format_tlv(DomainNameTag.STRUCTURE_VERSION, 1)
payload += format_tlv(DomainNameTag.SIGNER_KEY_ID, 0) # test key
Expand All @@ -194,6 +256,23 @@ def set_plugin(self,
key_id: int = 2,
algo_id: int = 1,
sig: Optional[bytes] = None) -> RAPDU:

if self._pki_client is None:
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
else:
# pylint: disable=line-too-long
if self._firmware == Firmware.NANOSP:
cert_apdu = "01010102010210040102000011040000000212010013020002140101160400000000200A53657420506C7567696E30020003310107320121332103C055BC4ECF055E2D85085D35127A3DE6705C7F885055CD7071E87671BF191FE3340101350103154630440220401824348DA0E435C9BF16C3591665CFA1B7D8E729971BE884027E02BD3C35A102202289EE207B73D98E9E6110CC143EB929F03B99D54C63023C99561D3CE164D30F" # noqa: E501
elif self._firmware == Firmware.NANOX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200A53657420506C7567696E30020003310107320121332103C055BC4ECF055E2D85085D35127A3DE6705C7F885055CD7071E87671BF191FE334010135010215473045022100E657DE255F954779E14D281E2E739D89DEF2E943B7FD4B4AFE49CF4FF7E1D84F022057F29C9AEA8FAA25C8438FDEE85C6DABF270E5CEC1655F17F2D9A6ADCD3ADC0E" # noqa: E501
elif self._firmware == Firmware.STAX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200A53657420506C7567696E30020003310107320121332103C055BC4ECF055E2D85085D35127A3DE6705C7F885055CD7071E87671BF191FE334010135010415473045022100B8AF9667C190B60BF350D8F8CA66A4BCEA22BF47D757CB7F88F8D16C7794BCDC02205F7D6C8E9294F73744A82E1062B10FFEB809252682112E71A419EFC78227211B" # noqa: E501
elif self._firmware == Firmware.FLEX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200A53657420506C7567696E30020003310107320121332103C055BC4ECF055E2D85085D35127A3DE6705C7F885055CD7071E87671BF191FE334010135010515473045022100F5069D8BCEDCF7CC55273266E3871B09FFCACD084B5753347A809DDDA67E6235022003CE65364BFA96B6FE7A9D8C13EC87B8E727E8B7BF4A63176F5D61AB8F97807E" # noqa: E501
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_PLUGIN_METADATA, bytes.fromhex(cert_apdu))

if sig is None:
# Temporarily get a command with an empty signature to extract the payload and
# compute the signature on it
Expand Down Expand Up @@ -227,6 +306,23 @@ def provide_nft_metadata(self,
key_id: int = 1,
algo_id: int = 1,
sig: Optional[bytes] = None) -> RAPDU:

if self._pki_client is None:
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
else:
# pylint: disable=line-too-long
if self._firmware == Firmware.NANOSP:
cert_apdu = "0101010201021004010200001104000000021201001302000214010116040000000020084e46545f496e666f300200043101033201213321023cfb5fb31905f4bd39d9d535a40c26aab51c5d7d3219b28ac942b980fb206cfb34010135010315473045022100d43e142a6639b27a79bc4f021854df48f1bc1e828ac47b105578cb527b69f525022078f6e6b3eb9bb787a0a29e85531ce3512c2d6481e761e840db0fb6b0898911a1" # noqa: E501
elif self._firmware == Firmware.NANOX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020084E46545F496E666F300200043101033201213321023CFB5FB31905F4BD39D9D535A40C26AAB51C5D7D3219B28AC942B980FB206CFB340101350102154730450221009BAE21BB8CBA6F95DDFF86AEEA991D63FA36A469A3071F61BDA8895F1A5F0AC3022061661F95D1513D3FDE81FFEA4B0C6D48ADCB27ED70915EE3ACD16A2A64CDE916" # noqa: E501
elif self._firmware == Firmware.STAX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020084E46545F496E666F300200043101033201213321023CFB5FB31905F4BD39D9D535A40C26AAB51C5D7D3219B28AC942B980FB206CFB3401013501041546304402201DEE04EC830FFDE5C98A708EC6865605FC14FF6105A54BE5230F2B954C673B940220581A0A5E42A7779140963703E43B3BEABE4C69284EDEF00E76BB5875E0810C9B" # noqa: E501
elif self._firmware == Firmware.FLEX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020084E46545F496E666F300200043101033201213321023CFB5FB31905F4BD39D9D535A40C26AAB51C5D7D3219B28AC942B980FB206CFB340101350105154730450221009ABCC7056D54C1B5DBB353178B13850C20521EE6884AA415AA61B329DB1D87F602204E308F273B8D18080184695438577F770524F717E5D08EE20ECBF1BC599F3538" # noqa: E501
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_NFT_METADATA, bytes.fromhex(cert_apdu))

if sig is None:
# Temporarily get a command with an empty signature to extract the payload and
# compute the signature on it
Expand Down Expand Up @@ -278,6 +374,23 @@ def provide_token_metadata(self,
decimals: int,
chain_id: int,
sig: Optional[bytes] = None) -> RAPDU:

if self._pki_client is None:
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
else:
# pylint: disable=line-too-long
if self._firmware == Firmware.NANOSP:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010310040102000015473045022100C15795C2AE41E6FAE6B1362EE1AE216428507D7C1D6939B928559CC7A1F6425C02206139CF2E133DD62F3E00F183E42109C9853AC62B6B70C5079B9A80DBB9D54AB5" # noqa: E501
elif self._firmware == Firmware.NANOX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010215473045022100E3B956F93FBFF0D41908483888F0F75D4714662A692F7A38DC6C41A13294F9370220471991BECB3CA4F43413CADC8FF738A8CC03568BFA832B4DCFE8C469080984E5" # noqa: E501
elif self._firmware == Firmware.STAX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501041546304402206731FCD3E2432C5CA162381392FD17AD3A41EEF852E1D706F21A656AB165263602204B89FAE8DBAF191E2D79FB00EBA80D613CB7EDF0BE960CB6F6B29D96E1437F5F" # noqa: E501
elif self._firmware == Firmware.FLEX:
cert_apdu = "01010102010211040000000212010013020002140101160400000000200B45524332305F546F6B656E300200063101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010515473045022100B59EA8B958AA40578A6FBE9BBFB761020ACD5DBD8AA863C11DA17F42B2AFDE790220186316059EFA58811337D47C7F815F772EA42BBBCEA4AE123D1118C80588F5CB" # noqa: E501
# pylint: enable=line-too-long

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))

if sig is None:
# Temporarily get a command with an empty signature to extract the payload and
# compute the signature on it
Expand Down
18 changes: 18 additions & 0 deletions client/src/ledger_app_clients/ethereum/eip712/InputData.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from ragger.firmware import Firmware

from client.client import PKIPubKeyUsage

# global variables
app_client: EthAppClient = None
filtering_paths: dict = {}
Expand Down Expand Up @@ -450,6 +452,22 @@ def process_data(aclient: EthAppClient,
pass
prepare_filtering(filters, message)

if aclient._pki_client is None:
print(f"Ledger-PKI Not supported on '{aclient._firmware.name}'")
else:
# pylint: disable=line-too-long
if aclient._firmware == Firmware.NANOSP:
cert_apdu = "0101010201021004010200001104000000021201001302000214010116040000000020104549503731325f46696c746572696e67300200053101083201213321024cca8fad496aa5040a00a7eb2f5cc3b85376d88ba147a7d7054a99c64056188734010135010315473045022100ef197e5b1cabb3de5dfc62f965db8536b0463d272c6fea38ebc73605715b1df9022017bef619d52a9728b37a9b5a33f0143bcdcc714694eed07c326796ffbb7c2958" # noqa: E501
elif aclient._firmware == Firmware.NANOX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020104549503731325F46696C746572696E67300200053101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C64056188734010135010215473045022100E07E129B0DC2A571D5205C3DB43BF4BB3463A2E9D2A4EEDBEC8FD3518CC5A95902205F80306EEF785C4D45BDCA1F25394A1341571BD1921C2740392DD22EB1ACDD8B" # noqa: E501
elif aclient._firmware == Firmware.STAX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020104549503731325F46696C746572696E67300200053101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501041546304402204EA7B30F0EEFEF25FAB3ADDA6609E25296C41DD1C5969A92FAE6B600AAC2902E02206212054E123F5F965F787AE7EE565E243F21B11725626D3FF058522D6BDCD995" # noqa: E501
elif aclient._firmware == Firmware.FLEX:
cert_apdu = "0101010201021104000000021201001302000214010116040000000020104549503731325F46696C746572696E67300200053101083201213321024CCA8FAD496AA5040A00A7EB2F5CC3B85376D88BA147A7D7054A99C6405618873401013501051546304402205FB5E970065A95C57F00FFA3964946251815527613724ED6745C37E303934BE702203CC9F4124B42806F0A7CA765CFAB5AADEB280C35AB8F809FC49ADC97D9B9CE15" # noqa: E501
# pylint: enable=line-too-long

aclient._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))

# send domain implementation
with app_client.eip712_send_struct_impl_root_struct(domain_typename):
enable_autonext()
Expand Down
37 changes: 20 additions & 17 deletions client/src/ledger_app_clients/ethereum/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,35 @@ def get_device_settings(firmware: Firmware) -> list[SettingID]:
]


def get_setting_per_page(firmware: Firmware) -> int:
if firmware == Firmware.STAX:
return 3
return 2


def get_setting_position(firmware: Firmware, setting: Union[NavInsID, SettingID]) -> tuple[int, int]:
settings_per_page = get_setting_per_page(firmware)
def get_setting_position(firmware: Firmware, setting_idx: int, per_page: int) -> tuple[int, int]:
if firmware == Firmware.STAX:
screen_height = 672 # px
header_height = 88 # px
footer_height = 92 # px
option_offset = 350 # px
x_offset = 350 # px
else:
screen_height = 600 # px
header_height = 92 # px
footer_height = 97 # px
option_offset = 420 # px
usable_height = screen_height - (header_height + footer_height)
setting_height = usable_height // settings_per_page
index_in_page = get_device_settings(firmware).index(SettingID(setting)) % settings_per_page
return option_offset, header_height + (setting_height * index_in_page) + (setting_height // 2)
x_offset = 420 # px
index_in_page = setting_idx % per_page
if index_in_page == 0:
y_offset = header_height + 10
elif per_page == 3:
if setting_idx == 1:
# 2nd setting over 3: middle of the screen
y_offset = screen_height // 2
else:
# Last setting
y_offset = screen_height - footer_height - 10
else:
# 2 per page, requesting the 2nd one; middle of screen is ok
y_offset = screen_height // 2
return x_offset, y_offset


def settings_toggle(firmware: Firmware, nav: Navigator, to_toggle: list[SettingID]):
moves: list[Union[NavIns, NavInsID]] = list()
moves: list[Union[NavIns, NavInsID]] = []
settings = get_device_settings(firmware)
# Assume the app is on the home page
if firmware.is_nano:
Expand All @@ -63,12 +66,12 @@ def settings_toggle(firmware: Firmware, nav: Navigator, to_toggle: list[SettingI
moves += [NavInsID.BOTH_CLICK] # Back
else:
moves += [NavInsID.USE_CASE_HOME_SETTINGS]
settings_per_page = get_setting_per_page(firmware)
settings_per_page = 3 if firmware == Firmware.STAX else 2
for setting in settings:
setting_idx = settings.index(setting)
if (setting_idx > 0) and (setting_idx % settings_per_page) == 0:
moves += [NavInsID.USE_CASE_SETTINGS_NEXT]
if setting in to_toggle:
moves += [NavIns(NavInsID.TOUCH, get_setting_position(firmware, setting))]
moves += [NavIns(NavInsID.TOUCH, get_setting_position(firmware, setting_idx, settings_per_page))]
moves += [NavInsID.USE_CASE_SETTINGS_MULTI_PAGE_EXIT]
nav.navigate(moves, screen_change_before_first_instruction=False)
61 changes: 0 additions & 61 deletions src/extra_tokens.c

This file was deleted.

Loading

0 comments on commit 1964621

Please sign in to comment.