common: add support for FIDO apps to cointool
also resize icons to the same 128x128 RGBA format as coin logos are using
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 956 B After Width: | Height: | Size: 830 B |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.1 KiB |
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "u2f.bin.coffee",
|
"label": "u2f.bin.coffee",
|
||||||
"u2f": ["1b3c16dd2f7c46e2b4c289dc16746bcc60dfcf0fb818e13215526e1408e7f468"]
|
"u2f": ["1b3c16dd2f7c46e2b4c289dc16746bcc60dfcf0fb818e13215526e1408e7f468"],
|
||||||
|
"demo": true
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "webauthn.bin.coffee",
|
"label": "webauthn.bin.coffee",
|
||||||
"webauthn": ["webauthn.bin.coffee"]
|
"webauthn": ["webauthn.bin.coffee"],
|
||||||
|
"demo": true
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "WebAuthn.io",
|
"label": "WebAuthn.io",
|
||||||
"webauthn": ["webauthn.io"]
|
"webauthn": ["webauthn.io"],
|
||||||
|
"demo": true
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "WebAuthn.me",
|
"label": "WebAuthn.me",
|
||||||
"webauthn": ["webauthn.me"]
|
"webauthn": ["webauthn.me"],
|
||||||
|
"demo": true
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"label": "demo.yubico.com",
|
"label": "demo.yubico.com",
|
||||||
"webauthn": ["demo.yubico.com"]
|
"webauthn": ["demo.yubico.com"],
|
||||||
|
"demo": true
|
||||||
}
|
}
|
||||||
|
@ -197,9 +197,9 @@ def validate_btc(coin):
|
|||||||
|
|
||||||
|
|
||||||
def _load_btc_coins():
|
def _load_btc_coins():
|
||||||
"""Load btc-like coins from `coins/*.json`"""
|
"""Load btc-like coins from `bitcoin/*.json`"""
|
||||||
coins = []
|
coins = []
|
||||||
for filename in glob.glob(os.path.join(DEFS_DIR, "coins", "*.json")):
|
for filename in glob.glob(os.path.join(DEFS_DIR, "bitcoin", "*.json")):
|
||||||
coin = load_json(filename)
|
coin = load_json(filename)
|
||||||
coin.update(
|
coin.update(
|
||||||
name=coin["coin_label"],
|
name=coin["coin_label"],
|
||||||
@ -259,6 +259,20 @@ def _load_misc():
|
|||||||
return others
|
return others
|
||||||
|
|
||||||
|
|
||||||
|
def _load_fido_apps():
|
||||||
|
"""Load btc-like coins from `coins/*.json`"""
|
||||||
|
apps = []
|
||||||
|
for filename in glob.glob(os.path.join(DEFS_DIR, "webauthn", "apps", "*.json")):
|
||||||
|
app_name = os.path.basename(filename)[:-5]
|
||||||
|
app = load_json(filename)
|
||||||
|
app.update(
|
||||||
|
key=app_name,
|
||||||
|
)
|
||||||
|
apps.append(app)
|
||||||
|
|
||||||
|
return apps
|
||||||
|
|
||||||
|
|
||||||
# ====== support info ======
|
# ====== support info ======
|
||||||
|
|
||||||
RELEASES_URL = "https://beta-wallet.trezor.io/data/firmware/{}/releases.json"
|
RELEASES_URL = "https://beta-wallet.trezor.io/data/firmware/{}/releases.json"
|
||||||
@ -559,6 +573,11 @@ def coin_info():
|
|||||||
return all_coins
|
return all_coins
|
||||||
|
|
||||||
|
|
||||||
|
def fido_info():
|
||||||
|
"""Returns info about known FIDO/U2F apps."""
|
||||||
|
return _load_fido_apps()
|
||||||
|
|
||||||
|
|
||||||
def search(coins, keyword):
|
def search(coins, keyword):
|
||||||
kwl = keyword.lower()
|
kwl = keyword.lower()
|
||||||
if isinstance(coins, CoinsInfo):
|
if isinstance(coins, CoinsInfo):
|
||||||
|
@ -168,7 +168,10 @@ def find_collisions(coins, field):
|
|||||||
"""Detects collisions in a given field. Returns buckets of colliding coins."""
|
"""Detects collisions in a given field. Returns buckets of colliding coins."""
|
||||||
collisions = defaultdict(list)
|
collisions = defaultdict(list)
|
||||||
for coin in coins:
|
for coin in coins:
|
||||||
value = coin[field]
|
values = coin[field]
|
||||||
|
if not isinstance(values, list):
|
||||||
|
values = [values]
|
||||||
|
for value in values:
|
||||||
collisions[value].append(coin)
|
collisions[value].append(coin)
|
||||||
return {k: v for k, v in collisions.items() if len(v) > 1}
|
return {k: v for k, v in collisions.items() if len(v) > 1}
|
||||||
|
|
||||||
@ -473,6 +476,64 @@ def check_segwit(coins):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
FIDO_KNOWN_KEYS = frozenset(
|
||||||
|
("key", "u2f", "webauthn", "label", "use_sign_count", "demo")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_fido(apps):
|
||||||
|
check_passed = True
|
||||||
|
|
||||||
|
uf2_hashes = find_collisions((a for a in apps if "u2f" in a), "u2f")
|
||||||
|
for key, bucket in uf2_hashes.items():
|
||||||
|
bucket_str = ", ".join(app["key"] for app in bucket)
|
||||||
|
u2f_hash_str = "colliding U2F hash " + crayon(None, key, bold=True) + ":"
|
||||||
|
print_log(logging.ERROR, u2f_hash_str, bucket_str)
|
||||||
|
check_passed = False
|
||||||
|
|
||||||
|
webauthn_domains = find_collisions((a for a in apps if "webauthn" in a), "webauthn")
|
||||||
|
for key, bucket in webauthn_domains.items():
|
||||||
|
bucket_str = ", ".join(app["key"] for app in bucket)
|
||||||
|
webauthn_str = "colliding WebAuthn domain " + crayon(None, key, bold=True) + ":"
|
||||||
|
print_log(logging.ERROR, webauthn_str, bucket_str)
|
||||||
|
check_passed = False
|
||||||
|
|
||||||
|
for app in apps:
|
||||||
|
if "label" not in app:
|
||||||
|
print_log(logging.ERROR, app["key"], ": missing label")
|
||||||
|
check_passed = False
|
||||||
|
|
||||||
|
if not app.get("u2f") and not app.get("webauthn"):
|
||||||
|
print_log(logging.ERROR, app["key"], ": no U2F nor WebAuthn addresses")
|
||||||
|
check_passed = False
|
||||||
|
|
||||||
|
unknown_keys = set(app.keys()) - FIDO_KNOWN_KEYS
|
||||||
|
if unknown_keys:
|
||||||
|
print_log(logging.ERROR, app["key"], ": unrecognized keys:", unknown_keys)
|
||||||
|
|
||||||
|
# check icons
|
||||||
|
icon_file = app["key"].lower() + ".png"
|
||||||
|
try:
|
||||||
|
icon = Image.open(
|
||||||
|
os.path.join(coin_info.DEFS_DIR, "webauthn", "apps", icon_file)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
if app.get("demo"):
|
||||||
|
log_level = logging.WARNING
|
||||||
|
else:
|
||||||
|
log_level = logging.ERROR
|
||||||
|
check_passed = False
|
||||||
|
print_log(log_level, app["key"], ": failed to open icon file", icon_file)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if icon.size != (128, 128) or icon.mode != "RGBA":
|
||||||
|
print_log(
|
||||||
|
logging.ERROR, app["key"], ": bad icon format (must be RGBA 128x128)"
|
||||||
|
)
|
||||||
|
check_passed = False
|
||||||
|
return check_passed
|
||||||
|
|
||||||
|
|
||||||
# ====== coindefs generators ======
|
# ====== coindefs generators ======
|
||||||
|
|
||||||
|
|
||||||
@ -638,6 +699,10 @@ def check(backend, icons, show_duplicates):
|
|||||||
if not check_key_uniformity(coinlist):
|
if not check_key_uniformity(coinlist):
|
||||||
all_checks_passed = False
|
all_checks_passed = False
|
||||||
|
|
||||||
|
print("Checking FIDO app definitions...")
|
||||||
|
if not check_fido(coin_info.fido_info()):
|
||||||
|
all_checks_passed = False
|
||||||
|
|
||||||
if not all_checks_passed:
|
if not all_checks_passed:
|
||||||
print("Some checks failed.")
|
print("Some checks failed.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -674,7 +739,7 @@ def dump(
|
|||||||
exclude_tokens,
|
exclude_tokens,
|
||||||
device,
|
device,
|
||||||
):
|
):
|
||||||
"""Dump coin data in JSON format
|
"""Dump coin data in JSON format.
|
||||||
|
|
||||||
This file is structured the same as the internal data. That is, top-level object
|
This file is structured the same as the internal data. That is, top-level object
|
||||||
is a dict with keys: 'bitcoin', 'eth', 'erc20', 'nem' and 'misc'. Value for each
|
is a dict with keys: 'bitcoin', 'eth', 'erc20', 'nem' and 'misc'. Value for each
|
||||||
|