1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 23:38:09 +00:00

feat(core/ui): implement webauthn layouts for UI2

[no changelog]
This commit is contained in:
grdddj 2022-10-12 15:30:53 +02:00 committed by matejcik
parent e80712f4d9
commit 61277bd80a
54 changed files with 635 additions and 237 deletions

View File

@ -579,13 +579,13 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/reset.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/recovery.py'))
if EVERYTHING:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/webauthn.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/fido.py'))
if TREZOR_MODEL in ('T',):
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/__init__.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/reset.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/recovery.py'))
if EVERYTHING:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/webauthn.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/fido.py'))
elif TREZOR_MODEL in ('1',):
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/t1.py'))
elif TREZOR_MODEL in ('R',):

View File

@ -533,13 +533,13 @@ if FROZEN:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/reset.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/recovery.py'))
if EVERYTHING:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/webauthn.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/fido.py'))
if TREZOR_MODEL in ('T',):
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/__init__.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/reset.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/recovery.py'))
if EVERYTHING:
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/webauthn.py'))
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/tt_v2/fido.py'))
elif TREZOR_MODEL in ('1',):
SOURCE_PY.extend(Glob(SOURCE_PY_DIR + 'trezor/ui/layouts/t1.py'))
elif TREZOR_MODEL in ('R',):

View File

@ -20,6 +20,7 @@ static void _librust_qstrs(void) {
MP_QSTR_confirm_blob;
MP_QSTR_confirm_properties;
MP_QSTR_confirm_coinjoin;
MP_QSTR_confirm_fido;
MP_QSTR_confirm_joint_total;
MP_QSTR_confirm_modify_fee;
MP_QSTR_confirm_modify_output;
@ -91,4 +92,7 @@ static void _librust_qstrs(void) {
MP_QSTR_active;
MP_QSTR_info_button;
MP_QSTR_time_ms;
MP_QSTR_app_name;
MP_QSTR_icon_name;
MP_QSTR_accounts;
}

View File

@ -0,0 +1,210 @@
use core::ops::Deref;
use crate::ui::{
component::{Child, Component, Event, EventCtx, Image, Label},
display,
geometry::{Alignment, Insets, Rect},
model_tt::component::{
fido_icons::get_fido_icon_data,
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
};
use super::CancelConfirmMsg;
const ICON_HEIGHT: i16 = 70;
const SCROLLBAR_INSET_TOP: i16 = 5;
const SCROLLBAR_HEIGHT: i16 = 10;
const APP_NAME_PADDING: i16 = 12;
const APP_NAME_HEIGHT: i16 = 30;
pub enum FidoMsg {
Confirmed(usize),
Cancelled,
}
pub struct FidoConfirm<F: Fn(usize) -> T, T, U> {
page_swipe: Swipe,
app_name: Label<T>,
account_name: Label<T>,
icon: Child<Image>,
/// Function/closure that will return appropriate page on demand.
get_account: F,
scrollbar: ScrollBar,
fade: bool,
controls: U,
}
impl<F, T, U> FidoConfirm<F, T, U>
where
F: Fn(usize) -> T,
T: Deref<Target = str> + From<&'static str>,
U: Component<Msg = CancelConfirmMsg>,
{
pub fn new(
app_name: T,
get_account: F,
page_count: usize,
icon_name: Option<T>,
controls: U,
) -> Self {
let icon_data = get_fido_icon_data(icon_name.as_deref());
// Preparing scrollbar and setting its page-count.
let mut scrollbar = ScrollBar::horizontal();
scrollbar.set_count_and_active_page(page_count, 0);
// Preparing swipe component and setting possible initial
// swipe directions according to number of pages.
let mut page_swipe = Swipe::horizontal();
page_swipe.allow_right = scrollbar.has_previous_page();
page_swipe.allow_left = scrollbar.has_next_page();
Self {
app_name: Label::new(app_name, Alignment::Center, theme::TEXT_BOLD),
account_name: Label::new("".into(), Alignment::Center, theme::TEXT_BOLD),
page_swipe,
icon: Child::new(Image::new(icon_data)),
get_account,
scrollbar,
fade: false,
controls,
}
}
fn on_page_swipe(&mut self, ctx: &mut EventCtx, swipe: SwipeDirection) {
// Change the page number.
match swipe {
SwipeDirection::Left if self.scrollbar.has_next_page() => {
self.scrollbar.go_to_next_page();
}
SwipeDirection::Right if self.scrollbar.has_previous_page() => {
self.scrollbar.go_to_previous_page();
}
_ => {} // page did not change
};
// Disable swipes on the boundaries. Not allowing carousel effect.
self.page_swipe.allow_right = self.scrollbar.has_previous_page();
self.page_swipe.allow_left = self.scrollbar.has_next_page();
// Redraw the page.
ctx.request_paint();
// Reset backlight to normal level on next paint.
self.fade = true;
}
fn active_page(&self) -> usize {
self.scrollbar.active_page
}
}
impl<F, T, U> Component for FidoConfirm<F, T, U>
where
F: Fn(usize) -> T,
T: Deref<Target = str> + From<&'static str>,
U: Component<Msg = CancelConfirmMsg>,
{
type Msg = FidoMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.page_swipe.place(bounds);
// Place the control buttons.
let controls_area = self.controls.place(bounds);
// Get the image and content areas.
let content_area = bounds.inset(Insets::bottom(controls_area.height()));
let (image_area, content_area) = content_area.split_top(ICON_HEIGHT);
// In case of showing a scrollbar, getting its area and placing it.
let remaining_area = if self.scrollbar.page_count > 1 {
let (scrollbar_area, remaining_area) = content_area
.inset(Insets::top(SCROLLBAR_INSET_TOP))
.split_top(SCROLLBAR_HEIGHT);
self.scrollbar.place(scrollbar_area);
remaining_area
} else {
content_area
};
// Place the icon image.
self.icon.place(image_area);
// Place the text labels.
let (app_name_area, account_name_area) = remaining_area
.inset(Insets::top(APP_NAME_PADDING))
.split_top(APP_NAME_HEIGHT);
self.app_name.place(app_name_area);
self.account_name.place(account_name_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(swipe) = self.page_swipe.event(ctx, event) {
// Swipe encountered, update the page.
self.on_page_swipe(ctx, swipe);
}
if let Some(msg) = self.controls.event(ctx, event) {
// Some button was clicked, send results.
match msg {
CancelConfirmMsg::Confirmed => return Some(FidoMsg::Confirmed(self.active_page())),
CancelConfirmMsg::Cancelled => return Some(FidoMsg::Cancelled),
}
}
None
}
fn paint(&mut self) {
self.icon.paint();
self.controls.paint();
self.app_name.paint();
if self.scrollbar.page_count > 1 {
self.scrollbar.paint();
}
let current_account = (self.get_account)(self.active_page());
// Erasing the old text content before writing the new one.
let account_name_area = self.account_name.area();
let real_area = account_name_area
.with_height(account_name_area.height() + self.account_name.font().text_baseline() + 1);
display::rect_fill(real_area, theme::BG);
// Account name is optional.
// Showing it only if it differs from app name.
// (Dummy requests usually have some text as both app_name and account_name.)
if !current_account.is_empty() && current_account.deref() != self.app_name.text().deref() {
self.account_name.set_text(current_account);
self.account_name.paint();
}
if self.fade {
self.fade = false;
// Note that this is blocking and takes some time.
display::fade_backlight(theme::BACKLIGHT_NORMAL);
}
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.icon.bounds(sink);
self.app_name.bounds(sink);
self.account_name.bounds(sink);
}
}
#[cfg(feature = "ui_debug")]
impl<F, T, U> crate::trace::Trace for FidoConfirm<F, T, U>
where
F: Fn(usize) -> T,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("FidoPaginatedPage");
t.close();
}
}

View File

@ -0,0 +1,77 @@
//! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`)
//! do not edit manually!
const ICON_AWS: &[u8] = include_res!("model_tt/res/fido/icon_aws.toif");
const ICON_BINANCE: &[u8] = include_res!("model_tt/res/fido/icon_binance.toif");
const ICON_BITBUCKET: &[u8] = include_res!("model_tt/res/fido/icon_bitbucket.toif");
const ICON_BITFINEX: &[u8] = include_res!("model_tt/res/fido/icon_bitfinex.toif");
const ICON_BITWARDEN: &[u8] = include_res!("model_tt/res/fido/icon_bitwarden.toif");
const ICON_CLOUDFLARE: &[u8] = include_res!("model_tt/res/fido/icon_cloudflare.toif");
const ICON_COINBASE: &[u8] = include_res!("model_tt/res/fido/icon_coinbase.toif");
const ICON_DASHLANE: &[u8] = include_res!("model_tt/res/fido/icon_dashlane.toif");
const ICON_DROPBOX: &[u8] = include_res!("model_tt/res/fido/icon_dropbox.toif");
const ICON_DUO: &[u8] = include_res!("model_tt/res/fido/icon_duo.toif");
const ICON_FACEBOOK: &[u8] = include_res!("model_tt/res/fido/icon_facebook.toif");
const ICON_FASTMAIL: &[u8] = include_res!("model_tt/res/fido/icon_fastmail.toif");
const ICON_FEDORA: &[u8] = include_res!("model_tt/res/fido/icon_fedora.toif");
const ICON_GANDI: &[u8] = include_res!("model_tt/res/fido/icon_gandi.toif");
const ICON_GEMINI: &[u8] = include_res!("model_tt/res/fido/icon_gemini.toif");
const ICON_GITHUB: &[u8] = include_res!("model_tt/res/fido/icon_github.toif");
const ICON_GITLAB: &[u8] = include_res!("model_tt/res/fido/icon_gitlab.toif");
const ICON_GOOGLE: &[u8] = include_res!("model_tt/res/fido/icon_google.toif");
const ICON_INVITY: &[u8] = include_res!("model_tt/res/fido/icon_invity.toif");
const ICON_KEEPER: &[u8] = include_res!("model_tt/res/fido/icon_keeper.toif");
const ICON_KRAKEN: &[u8] = include_res!("model_tt/res/fido/icon_kraken.toif");
const ICON_LOGIN_GOV: &[u8] = include_res!("model_tt/res/fido/icon_login.gov.toif");
const ICON_MICROSOFT: &[u8] = include_res!("model_tt/res/fido/icon_microsoft.toif");
const ICON_MOJEID: &[u8] = include_res!("model_tt/res/fido/icon_mojeid.toif");
const ICON_NAMECHEAP: &[u8] = include_res!("model_tt/res/fido/icon_namecheap.toif");
const ICON_PROTON: &[u8] = include_res!("model_tt/res/fido/icon_proton.toif");
const ICON_SLUSHPOOL: &[u8] = include_res!("model_tt/res/fido/icon_slushpool.toif");
const ICON_STRIPE: &[u8] = include_res!("model_tt/res/fido/icon_stripe.toif");
const ICON_TUTANOTA: &[u8] = include_res!("model_tt/res/fido/icon_tutanota.toif");
/// Default icon when app does not have its own
const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif");
/// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] {
if let Some(icon_name) = icon_name {
match icon_name.as_ref() {
"aws" => ICON_AWS,
"binance" => ICON_BINANCE,
"bitbucket" => ICON_BITBUCKET,
"bitfinex" => ICON_BITFINEX,
"bitwarden" => ICON_BITWARDEN,
"cloudflare" => ICON_CLOUDFLARE,
"coinbase" => ICON_COINBASE,
"dashlane" => ICON_DASHLANE,
"dropbox" => ICON_DROPBOX,
"duo" => ICON_DUO,
"facebook" => ICON_FACEBOOK,
"fastmail" => ICON_FASTMAIL,
"fedora" => ICON_FEDORA,
"gandi" => ICON_GANDI,
"gemini" => ICON_GEMINI,
"github" => ICON_GITHUB,
"gitlab" => ICON_GITLAB,
"google" => ICON_GOOGLE,
"invity" => ICON_INVITY,
"keeper" => ICON_KEEPER,
"kraken" => ICON_KRAKEN,
"login.gov" => ICON_LOGIN_GOV,
"microsoft" => ICON_MICROSOFT,
"mojeid" => ICON_MOJEID,
"namecheap" => ICON_NAMECHEAP,
"proton" => ICON_PROTON,
"slushpool" => ICON_SLUSHPOOL,
"stripe" => ICON_STRIPE,
"tutanota" => ICON_TUTANOTA,
_ => ICON_WEBAUTHN,
}
} else {
ICON_WEBAUTHN
}
}

View File

@ -0,0 +1,34 @@
//! generated from webauthn_icons.rs.mako
//! (by running `make templates` in `core`)
//! do not edit manually!
<%
icons: list[tuple[str, str]] = []
for app in fido:
if app.icon is not None:
# Variable names cannot have a dot in themselves
icon_name = app.key
var_name = icon_name.replace(".", "_").upper()
icons.append((icon_name, var_name))
%>\
% for icon_name, var_name in icons:
const ICON_${var_name}: &[u8] = include_res!("model_tt/res/fido/icon_${icon_name}.toif");
% endfor
/// Default icon when app does not have its own
const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif");
/// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] {
if let Some(icon_name) = icon_name {
match icon_name.as_ref() {
% for icon_name, var_name in icons:
"${icon_name}" => ICON_${var_name},
% endfor
_ => ICON_WEBAUTHN,
}
} else {
ICON_WEBAUTHN
}
}

View File

@ -1,5 +1,7 @@
mod button;
mod dialog;
mod fido;
mod fido_icons;
mod frame;
mod hold_to_confirm;
mod keyboard;
@ -14,6 +16,7 @@ pub use button::{
CancelInfoConfirmMsg, SelectWordMsg,
};
pub use dialog::{Dialog, DialogMsg, IconDialog};
pub use fido::{FidoConfirm, FidoMsg};
pub use frame::{Frame, NotificationFrame};
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use keyboard::{

View File

@ -6,7 +6,9 @@ use crate::{
error::Error,
micropython::{
buffer::StrBuffer,
gc::Gc,
iter::{Iter, IterBuf},
list::List,
map::Map,
module::Module,
obj::Obj,
@ -37,11 +39,11 @@ use crate::{
use super::{
component::{
Bip39Input, Button, ButtonMsg, ButtonStyleSheet, CancelConfirmMsg, CancelInfoConfirmMsg,
Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput,
MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame, NumberInputDialog,
NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard,
PinKeyboardMsg, SelectWordCount, SelectWordCountMsg, SelectWordMsg, Slip39Input,
SwipeHoldPage, SwipePage,
Dialog, DialogMsg, FidoConfirm, FidoMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
IconDialog, MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NotificationFrame,
NumberInputDialog, NumberInputDialogMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
PinKeyboard, PinKeyboardMsg, SelectWordCount, SelectWordCountMsg, SelectWordMsg,
Slip39Input, SwipeHoldPage, SwipePage,
},
theme,
};
@ -89,6 +91,20 @@ impl TryFrom<SelectWordCountMsg> for Obj {
}
}
impl<F, T, U> ComponentMsgObj for FidoConfirm<F, T, U>
where
F: Fn(usize) -> T,
T: Deref<Target = str> + From<&'static str>,
U: Component<Msg = CancelConfirmMsg>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
FidoMsg::Confirmed(page) => Ok((page as u8).into()),
FidoMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}
impl<T, U> ComponentMsgObj for Dialog<T, U>
where
T: ComponentMsgObj,
@ -661,6 +677,37 @@ extern "C" fn new_show_error(n_args: usize, args: *const Obj, kwargs: *mut Map)
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let app_name: StrBuffer = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?;
let icon: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?;
let accounts: Gc<List> = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?;
// Cache the page count so that we can move `accounts` into the closure.
let page_count = accounts.len();
// Closure to lazy-load the information on given page index.
// Done like this to allow arbitrarily many pages without
// the need of any allocation here in Rust.
let get_page = move |page_index| {
let account = unwrap!(accounts.get(page_index));
account.try_into().unwrap_or_else(|_| "".into())
};
let controls = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()),
Button::with_text("CONFIRM").styled(theme::button_confirm()),
2,
);
let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls);
let obj = LayoutObj::new(Frame::new(title, fido_page).with_border(theme::borders()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], kwargs: &Map| {
let icon = BlendedImage::new(
@ -1172,6 +1219,19 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// """Decrease or increase transaction fee."""
Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(),
/// def confirm_fido(
/// *,
/// title: str,
/// app_name: str,
/// icon_name: str | None,
/// accounts: list[str | None],
/// ) -> int | object:
/// """FIDO confirmation.
///
/// Returns page index in case of confirmation and CANCELLED otherwise.
/// """
Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(),
/// def show_error(
/// *,
/// title: str,

Binary file not shown.

Binary file not shown.

View File

@ -159,6 +159,19 @@ def confirm_modify_fee(
"""Decrease or increase transaction fee."""
# rust/src/ui/model_tt/layout.rs
def confirm_fido(
*,
title: str,
app_name: str,
icon_name: str | None,
accounts: list[str | None],
) -> int | object:
"""FIDO confirmation.
Returns page index in case of confirmation and CANCELLED otherwise.
"""
# rust/src/ui/model_tt/layout.rs
def show_error(
*,

View File

@ -155,6 +155,8 @@ trezor.ui.layouts
import trezor.ui.layouts
trezor.ui.layouts.common
import trezor.ui.layouts.common
trezor.ui.layouts.fido
import trezor.ui.layouts.fido
trezor.ui.layouts.recovery
import trezor.ui.layouts.recovery
trezor.ui.layouts.reset
@ -165,6 +167,8 @@ trezor.ui.layouts.tr
import trezor.ui.layouts.tr
trezor.ui.layouts.tt_v2
import trezor.ui.layouts.tt_v2
trezor.ui.layouts.tt_v2.fido
import trezor.ui.layouts.tt_v2.fido
trezor.ui.layouts.tt_v2.recovery
import trezor.ui.layouts.tt_v2.recovery
trezor.ui.layouts.tt_v2.reset
@ -405,12 +409,6 @@ if not utils.BITCOIN_ONLY:
import trezor.enums.TezosBallotType
trezor.enums.TezosContractType
import trezor.enums.TezosContractType
trezor.ui.components.common.webauthn
import trezor.ui.components.common.webauthn
trezor.ui.layouts.tt_v2.webauthn
import trezor.ui.layouts.tt_v2.webauthn
trezor.ui.layouts.webauthn
import trezor.ui.layouts.webauthn
apps.binance
import apps.binance
apps.binance.get_address

View File

@ -1,27 +1,8 @@
from typing import TYPE_CHECKING
from trezor.ui.components.common.webauthn import ConfirmInfo
if TYPE_CHECKING:
from trezor.messages import WebAuthnAddResidentCredential, Success
from trezor.wire import Context
from .credential import Fido2Credential
class ConfirmAddCredential(ConfirmInfo):
def __init__(self, cred: Fido2Credential):
super().__init__()
self._cred = cred
self.load_icon(cred.rp_id_hash)
def get_header(self) -> str:
return "Import credential"
def app_name(self) -> str:
return self._cred.app_name()
def account_name(self) -> str | None:
return self._cred.account_name()
async def add_resident_credential(
@ -30,7 +11,7 @@ async def add_resident_credential(
import storage.device as storage_device
from trezor import wire
from trezor.ui.layouts import show_error_and_raise
from trezor.ui.layouts.webauthn import confirm_webauthn
from trezor.ui.layouts.fido import confirm_fido
from trezor.messages import Success
from .credential import Fido2Credential
from .resident_credentials import store_resident_credential
@ -49,8 +30,13 @@ async def add_resident_credential(
"The credential you are trying to import does\nnot belong to this authenticator.",
)
if not await confirm_webauthn(ctx, ConfirmAddCredential(cred)):
raise wire.ActionCancelled
await confirm_fido(
ctx,
"Import credential",
cred.app_name(),
cred.icon_name(),
[cred.account_name()],
)
if store_resident_credential(cred):
return Success(message="Credential added")

View File

@ -71,6 +71,12 @@ class Credential:
def app_name(self) -> str:
raise NotImplementedError
def icon_name(self) -> str | None:
from . import knownapps
fido_app = knownapps.by_rp_id_hash(self.rp_id_hash)
return None if fido_app is None else fido_app.icon_name
def account_name(self) -> str | None:
return None

View File

@ -5,13 +5,10 @@ from micropython import const
from typing import TYPE_CHECKING
import storage.device as storage_device
from trezor import config, io, log, loop, utils, workflow
from trezor import config, io, log, loop, utils, wire, workflow
from trezor.crypto import hashlib
from trezor.crypto.curve import nist256p1
from trezor.ui.components.common.confirm import Pageable
from trezor.ui.components.common.webauthn import ConfirmInfo
from trezor.ui.layouts import show_popup
from trezor.ui.layouts.webauthn import confirm_webauthn
from apps.base import set_homescreen
from apps.common import cbor
@ -20,7 +17,7 @@ from . import common
from .credential import Credential, Fido2Credential
if TYPE_CHECKING:
from typing import Any, Callable, Coroutine, Iterable, Iterator
from typing import Any, Awaitable, Callable, Coroutine, Iterable, Iterator
from .credential import U2fCredential
HID = io.HID
@ -587,6 +584,33 @@ async def verify_user(keepalive_callback: KeepaliveCallback) -> bool:
return ret
def _confirm_fido_choose(title: str, credentials: list[Credential]) -> Awaitable[int]:
from trezor.ui.layouts.fido import confirm_fido
from . import knownapps
assert len(credentials) > 0
repr_credential = credentials[0]
if __debug__:
for cred in credentials:
assert cred.rp_id_hash == repr_credential.rp_id_hash
app_name = repr_credential.app_name()
app = knownapps.by_rp_id_hash(repr_credential.rp_id_hash)
icon_name = None if app is None else app.icon_name
return confirm_fido(
None, title, app_name, icon_name, [c.account_name() for c in credentials]
)
async def _confirm_fido(title: str, credential: Credential) -> bool:
try:
await _confirm_fido_choose(title, [credential])
return True
except wire.ActionCancelled:
return False
class State:
def __init__(self, cid: int, iface: HID) -> None:
self.cid = cid
@ -616,13 +640,11 @@ class State:
pass
class U2fState(State, ConfirmInfo):
class U2fState(State):
def __init__(self, cid: int, iface: HID, req_data: bytes, cred: Credential) -> None:
State.__init__(self, cid, iface)
ConfirmInfo.__init__(self)
self._cred = cred
self._req_data = req_data
self.load_icon(self._cred.rp_id_hash)
def timeout_ms(self) -> int:
return _U2F_CONFIRM_TIMEOUT_MS
@ -658,10 +680,7 @@ class U2fConfirmRegister(U2fState):
)
return False
else:
return await confirm_webauthn(None, self)
def get_header(self) -> str:
return "U2F Register"
return await _confirm_fido("U2F Register", self._cred)
def __eq__(self, other: object) -> bool:
return (
@ -675,11 +694,8 @@ class U2fConfirmAuthenticate(U2fState):
def __init__(self, cid: int, iface: HID, req_data: bytes, cred: Credential) -> None:
super().__init__(cid, iface, req_data, cred)
def get_header(self) -> str:
return "U2F Authenticate"
async def confirm_dialog(self) -> bool:
return await confirm_webauthn(None, self)
return await _confirm_fido("U2F Authenticate", self._cred)
def __eq__(self, other: object) -> bool:
return (
@ -767,7 +783,7 @@ class Fido2Unlock(Fido2State):
await send_cmd(self.resp, self.iface)
class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
class Fido2ConfirmMakeCredential(Fido2State):
def __init__(
self,
cid: int,
@ -778,24 +794,13 @@ class Fido2ConfirmMakeCredential(Fido2State, ConfirmInfo):
user_verification: bool,
) -> None:
Fido2State.__init__(self, cid, iface)
ConfirmInfo.__init__(self)
self._client_data_hash = client_data_hash
self._cred = cred
self._resident = resident
self._user_verification = user_verification
self.load_icon(cred.rp_id_hash)
def get_header(self) -> str:
return "FIDO2 Register"
def app_name(self) -> str:
return self._cred.app_name()
def account_name(self) -> str | None:
return self._cred.account_name()
async def confirm_dialog(self) -> bool:
if not await confirm_webauthn(None, self):
if not await _confirm_fido("FIDO2 Register", self._cred):
return False
if self._user_verification:
return await verify_user(KeepaliveCallback(self.cid, self.iface))
@ -839,7 +844,7 @@ class Fido2ConfirmExcluded(Fido2ConfirmMakeCredential):
)
class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
class Fido2ConfirmGetAssertion(Fido2State):
def __init__(
self,
cid: int,
@ -851,30 +856,21 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
user_verification: bool,
) -> None:
Fido2State.__init__(self, cid, iface)
ConfirmInfo.__init__(self)
Pageable.__init__(self)
self._client_data_hash = client_data_hash
self._creds = creds
self._hmac_secret = hmac_secret
self._resident = resident
self._user_verification = user_verification
self.load_icon(self._creds[0].rp_id_hash)
def get_header(self) -> str:
return "FIDO2 Authenticate"
def app_name(self) -> str:
return self._creds[self.page()].app_name()
def account_name(self) -> str | None:
return self._creds[self.page()].account_name()
def page_count(self) -> int:
return len(self._creds)
self._selected_cred: Credential | None = None
async def confirm_dialog(self) -> bool:
if not await confirm_webauthn(None, self, pageable=self):
# There is a choice from more than one credential.
try:
index = await _confirm_fido_choose("FIDO2 Authenticate", self._creds)
except wire.ActionCancelled:
return False
self._selected_cred = self._creds[index]
if self._user_verification:
return await verify_user(KeepaliveCallback(self.cid, self.iface))
return True
@ -882,13 +878,13 @@ class Fido2ConfirmGetAssertion(Fido2State, ConfirmInfo, Pageable):
async def on_confirm(self) -> None:
cid = self.cid # local_cache_attribute
cred = self._creds[self.page()]
assert self._selected_cred is not None
try:
send_cmd_sync(cmd_keepalive(cid, _KEEPALIVE_STATUS_PROCESSING), self.iface)
response_data = cbor_get_assertion_sign(
self._client_data_hash,
cred.rp_id_hash,
cred,
self._selected_cred.rp_id_hash,
self._selected_cred,
self._hmac_secret,
self._resident,
True,
@ -954,9 +950,9 @@ class Fido2ConfirmReset(Fido2State):
super().__init__(cid, iface)
async def confirm_dialog(self) -> bool:
from trezor.ui.layouts.webauthn import confirm_webauthn_reset
from trezor.ui.layouts.fido import confirm_fido_reset
return await confirm_webauthn_reset()
return await confirm_fido_reset()
async def on_confirm(self) -> None:
import storage.resident_credentials

View File

@ -9,12 +9,12 @@ class FIDOApp:
def __init__(
self,
label: str,
icon: str | None,
icon_name: str | None,
use_sign_count: bool | None,
use_self_attestation: bool | None,
) -> None:
self.label = label
self.icon = icon
self.icon_name = icon_name
self.use_sign_count = use_sign_count
self.use_self_attestation = use_self_attestation
@ -25,7 +25,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Amazon Web Services
return FIDOApp(
"aws.amazon.com", # label
"apps/webauthn/res/icon_aws.toif", # icon
"aws", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -33,7 +33,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Binance
return FIDOApp(
"www.binance.com", # label
"apps/webauthn/res/icon_binance.toif", # icon
"binance", # icon_name
False, # use_sign_count
True, # use_self_attestation
)
@ -41,7 +41,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Binance
return FIDOApp(
"binance.com", # label
"apps/webauthn/res/icon_binance.toif", # icon
"binance", # icon_name
False, # use_sign_count
True, # use_self_attestation
)
@ -49,7 +49,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Bitbucket
return FIDOApp(
"bitbucket.org", # label
"apps/webauthn/res/icon_bitbucket.toif", # icon
"bitbucket", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -57,7 +57,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Bitfinex
return FIDOApp(
"www.bitfinex.com", # label
"apps/webauthn/res/icon_bitfinex.toif", # icon
"bitfinex", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -65,7 +65,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Bitwarden
return FIDOApp(
"vault.bitwarden.com", # label
"apps/webauthn/res/icon_bitwarden.toif", # icon
"bitwarden", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -73,7 +73,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Cloudflare
return FIDOApp(
"dash.cloudflare.com", # label
"apps/webauthn/res/icon_cloudflare.toif", # icon
"cloudflare", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -81,7 +81,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Coinbase
return FIDOApp(
"coinbase.com", # label
"apps/webauthn/res/icon_coinbase.toif", # icon
"coinbase", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -89,7 +89,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Dashlane
return FIDOApp(
"www.dashlane.com", # label
"apps/webauthn/res/icon_dashlane.toif", # icon
"dashlane", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -97,7 +97,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Dropbox
return FIDOApp(
"www.dropbox.com", # label
"apps/webauthn/res/icon_dropbox.toif", # icon
"dropbox", # icon_name
False, # use_sign_count
None, # use_self_attestation
)
@ -105,7 +105,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Dropbox
return FIDOApp(
"www.dropbox.com", # label
"apps/webauthn/res/icon_dropbox.toif", # icon
"dropbox", # icon_name
False, # use_sign_count
None, # use_self_attestation
)
@ -113,7 +113,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Duo
return FIDOApp(
"duosecurity.com", # label
"apps/webauthn/res/icon_duo.toif", # icon
"duo", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -121,7 +121,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Facebook
return FIDOApp(
"facebook.com", # label
"apps/webauthn/res/icon_facebook.toif", # icon
"facebook", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -129,7 +129,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for FastMail
return FIDOApp(
"www.fastmail.com", # label
"apps/webauthn/res/icon_fastmail.toif", # icon
"fastmail", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -137,7 +137,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for FastMail
return FIDOApp(
"fastmail.com", # label
"apps/webauthn/res/icon_fastmail.toif", # icon
"fastmail", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -145,7 +145,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Fedora
return FIDOApp(
"fedoraproject.org", # label
"apps/webauthn/res/icon_fedora.toif", # icon
"fedora", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -153,7 +153,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Gandi
return FIDOApp(
"gandi.net", # label
"apps/webauthn/res/icon_gandi.toif", # icon
"gandi", # icon_name
False, # use_sign_count
None, # use_self_attestation
)
@ -161,7 +161,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Gandi
return FIDOApp(
"gandi.net", # label
"apps/webauthn/res/icon_gandi.toif", # icon
"gandi", # icon_name
False, # use_sign_count
None, # use_self_attestation
)
@ -169,7 +169,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Gemini
return FIDOApp(
"gemini.com", # label
"apps/webauthn/res/icon_gemini.toif", # icon
"gemini", # icon_name
False, # use_sign_count
True, # use_self_attestation
)
@ -177,7 +177,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for GitHub
return FIDOApp(
"github.com", # label
"apps/webauthn/res/icon_github.toif", # icon
"github", # icon_name
True, # use_sign_count
None, # use_self_attestation
)
@ -185,7 +185,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for GitHub
return FIDOApp(
"github.com", # label
"apps/webauthn/res/icon_github.toif", # icon
"github", # icon_name
True, # use_sign_count
None, # use_self_attestation
)
@ -193,7 +193,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for GitLab
return FIDOApp(
"gitlab.com", # label
"apps/webauthn/res/icon_gitlab.toif", # icon
"gitlab", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -201,7 +201,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Google
return FIDOApp(
"google.com", # label
"apps/webauthn/res/icon_google.toif", # icon
"google", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -209,7 +209,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Google
return FIDOApp(
"google.com", # label
"apps/webauthn/res/icon_google.toif", # icon
"google", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -217,7 +217,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Invity
return FIDOApp(
"invity.io", # label
"apps/webauthn/res/icon_invity.toif", # icon
"invity", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -225,7 +225,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Keeper
return FIDOApp(
"keepersecurity.com", # label
"apps/webauthn/res/icon_keeper.toif", # icon
"keeper", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -233,7 +233,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Keeper
return FIDOApp(
"keepersecurity.eu", # label
"apps/webauthn/res/icon_keeper.toif", # icon
"keeper", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -241,7 +241,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Kraken
return FIDOApp(
"kraken.com", # label
"apps/webauthn/res/icon_kraken.toif", # icon
"kraken", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -249,7 +249,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for login.gov
return FIDOApp(
"secure.login.gov", # label
"apps/webauthn/res/icon_login.gov.toif", # icon
"login.gov", # icon_name
False, # use_sign_count
None, # use_self_attestation
)
@ -257,7 +257,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Microsoft
return FIDOApp(
"login.microsoft.com", # label
"apps/webauthn/res/icon_microsoft.toif", # icon
"microsoft", # icon_name
False, # use_sign_count
False, # use_self_attestation
)
@ -265,7 +265,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for mojeID
return FIDOApp(
"mojeid.cz", # label
"apps/webauthn/res/icon_mojeid.toif", # icon
"mojeid", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -273,7 +273,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Namecheap
return FIDOApp(
"www.namecheap.com", # label
"apps/webauthn/res/icon_namecheap.toif", # icon
"namecheap", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -281,7 +281,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for Proton
return FIDOApp(
"proton.me", # label
"apps/webauthn/res/icon_proton.toif", # icon
"proton", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -289,7 +289,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Slush Pool
return FIDOApp(
"slushpool.com", # label
"apps/webauthn/res/icon_slushpool.toif", # icon
"slushpool", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -297,7 +297,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Slush Pool
return FIDOApp(
"slushpool.com", # label
"apps/webauthn/res/icon_slushpool.toif", # icon
"slushpool", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -305,7 +305,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Stripe
return FIDOApp(
"stripe.com", # label
"apps/webauthn/res/icon_stripe.toif", # icon
"stripe", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -313,7 +313,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for Tutanota
return FIDOApp(
"tutanota.com", # label
"apps/webauthn/res/icon_tutanota.toif", # icon
"tutanota", # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -321,7 +321,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# U2F key for u2f.bin.coffee
return FIDOApp(
"u2f.bin.coffee", # label
None, # icon
None, # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -329,7 +329,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for webauthn.bin.coffee
return FIDOApp(
"webauthn.bin.coffee", # label
None, # icon
None, # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -337,7 +337,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for WebAuthn.io
return FIDOApp(
"webauthn.io", # label
None, # icon
None, # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -345,7 +345,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for WebAuthn.me
return FIDOApp(
"webauthn.me", # label
None, # icon
None, # icon_name
None, # use_sign_count
None, # use_self_attestation
)
@ -353,7 +353,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# WebAuthn key for demo.yubico.com
return FIDOApp(
"demo.yubico.com", # label
None, # icon
None, # icon_name
None, # use_sign_count
None, # use_self_attestation
)

View File

@ -9,12 +9,12 @@ class FIDOApp:
def __init__(
self,
label: str,
icon: str | None,
icon_name: str | None,
use_sign_count: bool | None,
use_self_attestation: bool | None,
) -> None:
self.label = label
self.icon = icon
self.icon_name = icon_name
self.use_sign_count = use_sign_count
self.use_self_attestation = use_self_attestation
@ -30,9 +30,9 @@ for app in fido:
rp_id_hash = sha256(origin.encode()).digest()
fido_entries.append((origin, rp_id_hash, "WebAuthn", app))
if app.icon is not None:
app.icon_res = f"apps/webauthn/res/icon_{app.key}.toif"
app.icon_name = app.key
else:
app.icon_res = None
app.icon_name = None
%>\
# fmt: off
def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
@ -41,7 +41,7 @@ def by_rp_id_hash(rp_id_hash: bytes) -> FIDOApp | None:
# ${type} key for ${app.name}
return FIDOApp(
${black_repr(label)}, # label
${black_repr(app.icon_res)}, # icon
${black_repr(app.icon_name)}, # icon_name
${black_repr(app.use_sign_count)}, # use_sign_count
${black_repr(app.use_self_attestation)}, # use_self_attestation
)

View File

@ -1,29 +1,10 @@
from typing import TYPE_CHECKING
from trezor.ui.components.common.webauthn import ConfirmInfo
if TYPE_CHECKING:
from trezor.messages import WebAuthnRemoveResidentCredential, Success
from .credential import Fido2Credential
from trezor.wire import Context
class ConfirmRemoveCredential(ConfirmInfo):
def __init__(self, cred: Fido2Credential):
super().__init__()
self._cred = cred
self.load_icon(cred.rp_id_hash)
def get_header(self) -> str:
return "Remove credential"
def app_name(self) -> str:
return self._cred.app_name()
def account_name(self) -> str | None:
return self._cred.account_name()
async def remove_resident_credential(
ctx: Context, msg: WebAuthnRemoveResidentCredential
) -> Success:
@ -31,7 +12,7 @@ async def remove_resident_credential(
import storage.resident_credentials
from trezor import wire
from trezor.messages import Success
from trezor.ui.layouts.webauthn import confirm_webauthn
from trezor.ui.layouts.fido import confirm_fido
from .resident_credentials import get_resident_credential
if not storage.device.is_initialized():
@ -43,8 +24,13 @@ async def remove_resident_credential(
if cred is None:
raise wire.ProcessError("Invalid credential index.")
if not await confirm_webauthn(ctx, ConfirmRemoveCredential(cred)):
raise wire.ActionCancelled
await confirm_fido(
ctx,
"Remove credential",
cred.app_name(),
cred.icon_name(),
[cred.account_name()],
)
assert cred.index is not None
storage.resident_credentials.delete(cred.index)

View File

@ -1,25 +0,0 @@
DEFAULT_ICON = "apps/webauthn/res/icon_webauthn.toif"
class ConfirmInfo:
def __init__(self) -> None:
self.app_icon: bytes | None = None
def get_header(self) -> str:
raise NotImplementedError
def app_name(self) -> str:
raise NotImplementedError
def account_name(self) -> str | None:
return None
def load_icon(self, rp_id_hash: bytes) -> None:
from trezor import res
from apps.webauthn import knownapps
fido_app = knownapps.by_rp_id_hash(rp_id_hash)
if fido_app is not None and fido_app.icon is not None:
self.app_icon = res.load(fido_app.icon)
else:
self.app_icon = res.load(DEFAULT_ICON)

View File

@ -0,0 +1 @@
from .tt_v2.fido import * # noqa: F401,F403

View File

@ -0,0 +1,96 @@
from typing import TYPE_CHECKING
from trezor.enums import ButtonRequestType
import trezorui2
from ..common import interact
from . import _RustLayout
if TYPE_CHECKING:
from trezor.loop import AwaitableTask
from trezor.wire import GenericContext
if __debug__:
from trezor import io
from ... import Result
class _RustFidoLayoutImpl(_RustLayout):
def create_tasks(self) -> tuple[AwaitableTask, ...]:
return (
self.handle_timers(),
self.handle_input_and_rendering(),
self.handle_swipe(),
self.handle_debug_confirm(),
)
async def handle_debug_confirm(self) -> None:
from apps.debug import confirm_signal
try:
await confirm_signal()
except Result as r:
if r.value is not trezorui2.CONFIRMED:
raise
else:
return
for event, x, y in (
(io.TOUCH_START, 220, 220),
(io.TOUCH_END, 220, 220),
):
msg = self.layout.touch_event(event, x, y)
self.layout.paint()
if msg is not None:
raise Result(msg)
_RustFidoLayout = _RustFidoLayoutImpl
else:
_RustFidoLayout = _RustLayout
async def confirm_fido(
ctx: GenericContext | None,
header: str,
app_name: str,
icon_name: str | None,
accounts: list[str | None],
) -> int:
"""Webauthn confirmation for one or more credentials."""
confirm = _RustFidoLayout(
trezorui2.confirm_fido(
title=header.upper(),
app_name=app_name,
icon_name=icon_name,
accounts=accounts,
)
)
if ctx is None:
result = await confirm
else:
result = await interact(ctx, confirm, "confirm_fido", ButtonRequestType.Other)
# The Rust side returns either an int or `CANCELLED`. We detect the int situation
# and assume cancellation otherwise.
if isinstance(result, int):
return result
# Late import won't get executed on the happy path.
from trezor.wire import ActionCancelled
raise ActionCancelled
async def confirm_fido_reset() -> bool:
confirm = _RustLayout(
trezorui2.confirm_action(
title="FIDO2 RESET",
action="erase all credentials?",
description="Do you really want to",
reverse=True,
)
)
return (await confirm) is trezorui2.CONFIRMED

View File

@ -1,49 +0,0 @@
from typing import TYPE_CHECKING
from trezor.enums import ButtonRequestType
import trezorui2
from ...components.common.confirm import is_confirmed
from ...components.common.webauthn import ConfirmInfo
from ..common import interact
from . import _RustLayout
if TYPE_CHECKING:
from trezor.wire import GenericContext
Pageable = object
async def confirm_webauthn(
ctx: GenericContext | None,
info: ConfirmInfo,
pageable: Pageable | None = None,
) -> bool:
if pageable is not None:
raise NotImplementedError
confirm = _RustLayout(
trezorui2.confirm_blob(
title=info.get_header().upper(),
data=f"{info.app_name()}\n{info.account_name()}",
)
)
if ctx is None:
return is_confirmed(await confirm)
else:
return is_confirmed(
await interact(ctx, confirm, "confirm_webauthn", ButtonRequestType.Other)
)
async def confirm_webauthn_reset() -> bool:
return is_confirmed(
await _RustLayout(
trezorui2.confirm_blob(
title="FIDO2 RESET",
data="Do you really want to\nerase all credentials?",
)
)
)

View File

@ -1 +0,0 @@
from .tt_v2.webauthn import * # noqa: F401,F403

View File

@ -12,7 +12,9 @@ HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent
ICON_SIZE = (64, 64)
DESTINATION = ROOT / "core" / "src" / "apps" / "webauthn" / "res"
DESTINATION = (
ROOT / "core" / "embed" / "rust" / "src" / "ui" / "model_tt" / "res" / "fido"
)
EXCLUDE = {"icon_webauthn"}
# insert ../../common/tools to sys.path, so that we can import coin_info

View File

@ -4,7 +4,8 @@ set -e
CWD=`dirname "$0"`
RENDER="$CWD/../vendor/trezor-common/tools/cointool.py render"
FIND_TEMPLATES="find $CWD/../src -name *.mako -not -name _proto*"
# Search both in `core/src` and `core/embed`
FIND_TEMPLATES="find $CWD/.. -name *.mako -not -name _proto*"
check_results() {
CHECK_FAIL=0

View File

@ -1667,7 +1667,7 @@
"TT_tezos-test_sign_tx.py::test_tezos_smart_contract_delegation": "683a47bb078a50bf08a16446c90fab70a68a3cec8689abca494fd99b31da2ea6",
"TT_tezos-test_sign_tx.py::test_tezos_smart_contract_transfer": "295eac0f3667c0af660575fedefa98cae89ec0882892dc8769adf61751dc12fb",
"TT_tezos-test_sign_tx.py::test_tezos_smart_contract_transfer_to_contract": "3e781bd83c0019ba51b9034b8bc53c5a09ea9c5c6fe73570004d46a061ba43ea",
"TT_webauthn-test_msg_webauthn.py::test_add_remove": "7ac75b93373119b69a38c4bacfa51579d7052abc8b10f74f937a78e9a4afb5d9",
"TT_webauthn-test_msg_webauthn.py::test_add_remove": "943e29255d6738cc6824c2565001c05906c2cd032aa2eb09993782564e890a16",
"TT_webauthn-test_u2f_counter.py::test_u2f_counter": "c6a8e270ce726c7693e2ff88f9af57c56f2d3d8b1cc9c04b6f1dc71e13fcb88e",
"TT_zcash-test_sign_tx.py::test_external_presigned": "8781b601169bd64c90ee4dd9c517af905e2cf5fe10bdb474116d17f3d633e06a",
"TT_zcash-test_sign_tx.py::test_one_two": "003f12c0ff194bde070fdeb6c8663bf322efca094e9787b2304231c25938b715",