diff --git a/.cargo/config.toml b/.cargo/config.toml index e0a9b52d..f34ab830 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,7 +8,7 @@ runner = "speculos -m nanox --display=headless" runner = "speculos -m nanosp --display=headless" [target.stax] -runner = "speculos -a 13 --model stax" +runner = "speculos --model stax" [unstable] build-std = ["core"] diff --git a/Cargo.lock b/Cargo.lock index 88fb20e6..66f09ec3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "const-zero" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c6565524986fe3225da0beb9b4aa55ebc73cd57ff8cb4ccf016ca4c8d006af" + [[package]] name = "either" version = "1.9.0" @@ -298,6 +304,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" name = "ledger_device_sdk" version = "1.5.1" dependencies = [ + "const-zero", "include_gif", "ledger_device_sdk", "ledger_secure_sdk_sys", @@ -314,6 +321,7 @@ version = "1.2.0" dependencies = [ "bindgen", "cc", + "glob", ] [[package]] diff --git a/ledger_device_sdk/examples/stax.rs b/ledger_device_sdk/examples/stax.rs index 50587ba5..56cc2f62 100644 --- a/ledger_device_sdk/examples/stax.rs +++ b/ledger_device_sdk/examples/stax.rs @@ -40,7 +40,7 @@ extern "C" fn sample_main() { .app_name("Stax Sample\0") .info_contents(env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS")) .icon(&BTC_BMP) - .show_home::() + .show::() { Event::Command(_) => { let fields = [ @@ -57,7 +57,7 @@ extern "C" fn sample_main() { value: "Value 3\0", }, ]; - if NbglReview::new(&mut comm).review_transaction(&fields) { + if NbglReview.review_transaction(&fields) { debug_print("Validation result: true\n"); } else { debug_print("Validation result: false\n"); diff --git a/ledger_device_sdk/src/io.rs b/ledger_device_sdk/src/io.rs index 8623e9e9..8fc65fcf 100644 --- a/ledger_device_sdk/src/io.rs +++ b/ledger_device_sdk/src/io.rs @@ -84,7 +84,7 @@ impl From for Reply { } /// Possible events returned by [`Comm::next_event`] -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Clone)] pub enum Event { /// APDU event Command(T), @@ -103,6 +103,7 @@ pub struct Comm { pub apdu_buffer: [u8; 260], pub rx: usize, pub tx: usize, + pub event_pending: bool, buttons: ButtonsState, /// Expected value for the APDU CLA byte. /// If defined, [`Comm`] will automatically reply with [`StatusWords::BadCla`] when an APDU @@ -137,6 +138,7 @@ impl Comm { apdu_buffer: [0u8; 260], rx: 0, tx: 0, + event_pending: false, buttons: ButtonsState::new(), expected_cla: None, } @@ -247,6 +249,13 @@ impl Comm { T: TryFrom, Reply: From<>::Error>, { + // if self.event_pending { + // self.event_pending = false; + // if let Some(value) = self.check_event() { + // return value; + // } + // } + let mut spi_buffer = [0u8; 128]; unsafe { @@ -271,7 +280,80 @@ impl Comm { } } - pub fn decode_event(&mut self, spi_buffer: &mut [u8; 128]) -> Option> + pub fn next_event_ahead(&mut self) -> bool + where + T: TryFrom, + Reply: From<>::Error>, + { + // if self.event_pending { + // let event: Option> = self.check_event(); + // if event.is_some() { + // return true; + // } + // } + + let mut spi_buffer = [0u8; 128]; + + // unsafe { + // G_io_app.apdu_state = APDU_IDLE; + // G_io_app.apdu_media = IO_APDU_MEDIA_NONE; + // G_io_app.apdu_length = 0; + // } + + // Signal end of command stream from SE to MCU + // And prepare reception + if !sys_seph::is_status_sent() { + sys_seph::send_general_status(); + } + // Fetch the next message from the MCU } + let _rx = sys_seph::seph_recv(&mut spi_buffer, 0); + return self.detect_apdu::(&mut spi_buffer); + } + + pub fn check_event(&mut self) -> Option> + where + T: TryFrom, + Reply: From<>::Error>, + { + if self.event_pending { + self.event_pending = false; + // Reject incomplete APDUs + if self.rx < 4 { + self.reply(StatusWords::BadLen); + return None; + } + + // Check for data length by using `get_data` + if let Err(sw) = self.get_data() { + self.reply(sw); + return None; + } + + // If CLA filtering is enabled, automatically reject APDUs with wrong CLA + if let Some(cla) = self.expected_cla { + if self.apdu_buffer[0] != cla { + self.reply(StatusWords::BadCla); + return None; + } + } + + let res = T::try_from(*self.get_apdu_metadata()); + match res { + Ok(ins) => { + return Some(Event::Command(ins)); + } + Err(sw) => { + // Invalid Ins code. Send automatically an error, mask + // the bad instruction to the application and just + // discard this event. + self.reply(sw); + } + } + } + None + } + + pub fn process_event(&mut self, spi_buffer: &mut [u8; 128]) -> Option> where T: TryFrom, Reply: From<>::Error>, @@ -332,44 +414,38 @@ impl Comm { _ => (), } + None + } + + pub fn decode_event(&mut self, spi_buffer: &mut [u8; 128]) -> Option> + where + T: TryFrom, + Reply: From<>::Error>, + { + if let Some(event) = self.process_event(spi_buffer) { + return Some(event); + } if unsafe { G_io_app.apdu_state } != APDU_IDLE && unsafe { G_io_app.apdu_length } > 0 { self.rx = unsafe { G_io_app.apdu_length as usize }; + return self.check_event(); + } + None + } - // Reject incomplete APDUs - if self.rx < 4 { - self.reply(StatusWords::BadLen); - return None; - } - - // Check for data length by using `get_data` - if let Err(sw) = self.get_data() { - self.reply(sw); - return None; - } - - // If CLA filtering is enabled, automatically reject APDUs with wrong CLA - if let Some(cla) = self.expected_cla { - if self.apdu_buffer[0] != cla { - self.reply(StatusWords::BadCla); - return None; - } - } + fn detect_apdu(&mut self, spi_buffer: &mut [u8; 128]) -> bool + where + T: TryFrom, + Reply: From<>::Error>, + { + let _: Option> = self.decode_event(spi_buffer); - let res = T::try_from(*self.get_apdu_metadata()); - match res { - Ok(ins) => { - return Some(Event::Command(ins)); - } - Err(sw) => { - // Invalid Ins code. Send automatically an error, mask - // the bad instruction to the application and just - // discard this event. - self.reply(sw); - } - } + if unsafe { G_io_app.apdu_state } != APDU_IDLE && unsafe { G_io_app.apdu_length } > 0 { + self.rx = unsafe { G_io_app.apdu_length as usize }; + self.event_pending = true; + return true; } - None + false } /// Wait for the next Command event. Discards received button events. diff --git a/ledger_device_sdk/src/nbgl.rs b/ledger_device_sdk/src/nbgl.rs index ddf36ca2..86c18192 100644 --- a/ledger_device_sdk/src/nbgl.rs +++ b/ledger_device_sdk/src/nbgl.rs @@ -1,26 +1,10 @@ -use crate::io::{ApduHeader, Comm, Event, Reply, StatusWords}; +use crate::io::{ApduHeader, Comm, Event, Reply}; use const_zero::const_zero; use core::mem::transmute; use ledger_secure_sdk_sys::nbgl_icon_details_t; use ledger_secure_sdk_sys::*; -struct DummyEvent; - -impl TryFrom for DummyEvent { - type Error = StatusWords; - - fn try_from(value: ApduHeader) -> Result { - match value.ins { - 1 => Ok(Self), - _ => Err(StatusWords::NothingReceived), - } - } -} - -pub struct NbglHome<'a> { - comm: &'a mut Comm, -} - +pub struct NbglHome; pub struct NbglReview; pub struct NbglAddressConfirm; @@ -44,7 +28,6 @@ struct NbglContext { review_reject_string: [u8; 40], review_pairs: [ledger_secure_sdk_sys::nbgl_layoutTagValue_t; 10], nb_pairs: u8, - review_status: ReviewStatus, } const INFOTYPES: [*const ::core::ffi::c_char; 2] = [ @@ -52,94 +35,48 @@ const INFOTYPES: [*const ::core::ffi::c_char; 2] = [ "Developer\0".as_ptr() as *const ::core::ffi::c_char, ]; -fn process_touch_events() { - if !seph::is_status_sent() { - seph::send_general_status(); - } - - let mut buf = [0u8; 8]; - while seph::is_status_sent() { - seph::seph_recv(&mut buf, 0); - - match buf[0] as u32 { - SEPROXYHAL_TAG_FINGER_EVENT => { - unsafe { - ux_process_finger_event(buf.as_mut_ptr()); - } - } - SEPROXYHAL_TAG_TICKER_EVENT => unsafe { - ux_process_ticker_event(); - } - _ => (), - } - } -} - -fn get_events>(comm: &mut Comm) -> Event -where - Reply: From<>::Error>, -{ - loop { - if let Event::Command(ins) = comm.next_event() { - return Event::Command(ins); - } - } -} - -fn wait_review_result() -> bool { - loop { - process_touch_events(); - unsafe { - if CTX.review_status == ReviewStatus::Validate { +#[no_mangle] +pub extern "C" fn recv_and_process_event(return_on_apdu: bool) -> bool { + unsafe { + if let Some(comm) = COMM_REF.as_mut() { + let apdu_received = comm.next_event_ahead::(); + if apdu_received && return_on_apdu { return true; - } else if CTX.review_status == ReviewStatus::Reject { - return false; } } } -} - -fn display_review_result_cbk(confirm: bool) { - let result_string: *const ::core::ffi::c_char; - unsafe { - if confirm { - CTX.review_status = ReviewStatus::Validate; - result_string = CTX.review_confirm_string.as_ptr() as *const ::core::ffi::c_char; - } else { - CTX.review_status = ReviewStatus::Reject; - result_string = CTX.review_reject_string.as_ptr() as *const ::core::ffi::c_char; - } - - // We don't need the callback here as we're going to display the home screen from the main app loop - ledger_secure_sdk_sys::nbgl_useCaseStatus(result_string, confirm, None); - } + false } #[no_mangle] pub static mut G_ux_params: bolos_ux_params_t = unsafe { const_zero!(bolos_ux_params_t) }; static mut CTX: NbglContext = unsafe { const_zero!(NbglContext) }; +static mut COMM_REF: Option<&mut Comm> = None; -impl<'a> NbglHome<'a> { - pub fn new(comm: &'a mut Comm) -> NbglHome<'a> { - NbglHome { comm } +impl NbglHome { + pub fn new(comm: &mut Comm) -> NbglHome { + unsafe { + COMM_REF = Some(transmute(comm)); + } + NbglHome {} } - pub fn app_name(self, app_name: &'static str) -> NbglHome<'a> { + pub fn app_name(self, app_name: &'static str) -> NbglHome { unsafe { CTX.name[..app_name.len()].copy_from_slice(app_name.as_bytes()); } self } - pub fn icon(self, icon: &'static [u8]) -> NbglHome<'a> { + pub fn icon(self, icon: &'static [u8]) -> NbglHome { unsafe { CTX.icon = Some(icon); } self } - pub fn info_contents(self, version: &str, author: &str) -> NbglHome<'a> { + pub fn info_contents(self, version: &str, author: &str) -> NbglHome { unsafe { CTX.info_contents[0][..version.len()].copy_from_slice(version.as_bytes()); CTX.info_contents[1][..author.len()].copy_from_slice(author.as_bytes()); @@ -147,63 +84,25 @@ impl<'a> NbglHome<'a> { self } - pub fn show_home>(&mut self) -> Event + pub fn show>(&mut self) -> Event where Reply: From<>::Error>, { - Self::display_home_cbk(); - return get_events(self.comm); - } - - fn display_home_cbk() { unsafe { - let icon = nbgl_icon_details_t { - width: 64, - height: 64, - bpp: 2, - isFile: true, - bitmap: CTX.icon.unwrap().as_ptr(), - }; - - ledger_secure_sdk_sys::nbgl_useCaseHome( - CTX.name.as_ptr() as *const core::ffi::c_char, - &icon as *const nbgl_icon_details_t, - core::ptr::null(), - true as bool, - transmute((|| Self::display_settings_cbk()) as fn()), - transmute((|| exit_app(12)) as fn()), - ); - } - } - - fn display_settings_cbk() { - unsafe { - let nav = |page: u8, content: *mut nbgl_pageContent_s| { - if page == 0 { - (*content).type_ = ledger_secure_sdk_sys::INFOS_LIST; - (*content).__bindgen_anon_1.infosList.nbInfos = 2; - (*content).__bindgen_anon_1.infosList.infoTypes = INFOTYPES.as_ptr(); - (*content).__bindgen_anon_1.infosList.infoContents = [ - CTX.info_contents[0].as_ptr() as *const ::core::ffi::c_char, - CTX.info_contents[1].as_ptr() as *const ::core::ffi::c_char, - ] - .as_ptr() - as *const *const ::core::ffi::c_char; - } else { - return false; + loop { + match ledger_secure_sdk_sys::sync_nbgl_useCaseHomeAndSettings() { + ledger_secure_sdk_sys::NBGL_SYNC_RET_RX_APDU => { + if let Some(comm) = COMM_REF.as_mut() { + if let Some(value) = comm.check_event() { + return value; + } + } + } + _ => { + panic!("Unexpected return value from sync_nbgl_useCaseHome"); + } } - true - }; - - ledger_secure_sdk_sys::nbgl_useCaseSettings( - CTX.name.as_ptr() as *const core::ffi::c_char, - 0 as u8, - 1 as u8, - false as bool, - transmute((|| Self::display_home_cbk()) as fn()), - transmute(nav as fn(u8, *mut nbgl_pageContent_s) -> bool), - transmute((|| exit_app(12)) as fn()), - ); + } } } } @@ -236,7 +135,6 @@ impl NbglReview { CTX.review_pairs.len() } as u8; - CTX.review_status = ReviewStatus::Pending; let confirm_string = "TRANSACTION\nSIGNED\0"; CTX.review_confirm_string[..confirm_string.len()] .copy_from_slice(confirm_string.as_bytes()); @@ -244,21 +142,6 @@ impl NbglReview { CTX.review_reject_string[..reject_string.len()] .copy_from_slice(reject_string.as_bytes()); - ledger_secure_sdk_sys::nbgl_useCaseReviewStart( - &icon as *const nbgl_icon_details_t, - "Review tx\0".as_ptr() as *const ::core::ffi::c_char, - "Subtitle\0".as_ptr() as *const ::core::ffi::c_char, - "Reject transaction\0".as_ptr() as *const ::core::ffi::c_char, - transmute((|| Self::display_static_review_cbk()) as fn()), - transmute((|| display_review_result_cbk(false)) as fn()), - ); - - return wait_review_result(); - } - } - - fn display_static_review_cbk() { - unsafe { let tag_value_list: nbgl_layoutTagValueList_t = nbgl_layoutTagValueList_t { pairs: CTX.review_pairs.as_mut_ptr() as *mut nbgl_layoutTagValue_t, callback: None, @@ -270,31 +153,30 @@ impl NbglReview { wrapping: false, }; - // Using this icon causes a crash - // let icon = nbgl_icon_details_t { - // width: 64, - // height: 64, - // bpp: 2, - // isFile: true, - // bitmap: CTX.icon.unwrap().as_ptr(), - // }; - - let info_long_press: nbgl_pageInfoLongPress_t = nbgl_pageInfoLongPress_t { - text: "Validate tx\0".as_ptr() as *const ::core::ffi::c_char, - icon: core::ptr::null(), - longPressText: "Hold to sign\0".as_ptr() as *const ::core::ffi::c_char, - longPressToken: 0, - tuneId: 0, - }; - - ledger_secure_sdk_sys::nbgl_useCaseStaticReview( + let sync_ret = ledger_secure_sdk_sys::sync_nbgl_useCaseTransactionReview( &tag_value_list as *const nbgl_layoutTagValueList_t, - &info_long_press as *const nbgl_pageInfoLongPress_t, - "Reject tx\0".as_ptr() as *const ::core::ffi::c_char, - transmute( - (|confirm| display_review_result_cbk(confirm)) as fn(confirm: bool), - ), + &icon as *const nbgl_icon_details_t, + "Review tx\0".as_ptr() as *const ::core::ffi::c_char, + "Subtitle\0".as_ptr() as *const ::core::ffi::c_char, + "Tx confirmed\0".as_ptr() as *const ::core::ffi::c_char, ); + + match sync_ret { + ledger_secure_sdk_sys::NBGL_SYNC_RET_SUCCESS => { + ledger_secure_sdk_sys::sync_nbgl_useCaseStatus( + CTX.review_confirm_string.as_ptr() as *const ::core::ffi::c_char, + true, + ); + return true; + } + _ => { + ledger_secure_sdk_sys::sync_nbgl_useCaseStatus( + CTX.review_reject_string.as_ptr() as *const ::core::ffi::c_char, + false, + ); + return false; + } + } } } } @@ -302,7 +184,6 @@ impl NbglReview { // ================================================================================================= impl NbglAddressConfirm { - pub fn verify_address(&mut self, address: &str) -> bool { unsafe { let icon = nbgl_icon_details_t { @@ -313,42 +194,24 @@ impl NbglAddressConfirm { bitmap: CTX.icon.unwrap().as_ptr(), }; - CTX.nb_pairs = 1; - CTX.review_pairs[0] = nbgl_layoutTagValue_t { - item: "\0".as_ptr() as *const ::core::ffi::c_char, // Unused - value: address.as_ptr() as *const ::core::ffi::c_char, - valueIcon: core::ptr::null(), - }; - CTX.review_status = ReviewStatus::Pending; - - ledger_secure_sdk_sys::nbgl_useCaseReviewStart( + let sync_ret = sync_nbgl_useCaseAddressReview( + address.as_ptr() as *const ::core::ffi::c_char, &icon as *const nbgl_icon_details_t, "Verify address\0".as_ptr() as *const ::core::ffi::c_char, core::ptr::null(), - "Cancel\0".as_ptr() as *const ::core::ffi::c_char, - transmute((|| Self::display_address_confirmation_cbk()) as fn()), - None, ); - return wait_review_result(); - } - } - - fn display_address_confirmation_cbk() { - unsafe { - let confirm_string = "ADDRESS\nVERIFIED\0"; - CTX.review_confirm_string[..confirm_string.len()] - .copy_from_slice(confirm_string.as_bytes()); - let reject_string = "Address verification\ncancelled\0"; - CTX.review_reject_string[..reject_string.len()] - .copy_from_slice(reject_string.as_bytes()); - - ledger_secure_sdk_sys::nbgl_useCaseAddressConfirmation( - CTX.review_pairs[0].value as *const ::core::ffi::c_char, - transmute( - (|confirm| display_review_result_cbk(confirm)) as fn(confirm: bool), - ), - ); + match sync_ret { + ledger_secure_sdk_sys::NBGL_SYNC_RET_SUCCESS => { + return true; + } + ledger_secure_sdk_sys::NBGL_SYNC_RET_REJECTED => { + return false; + } + _ => { + panic!("Unexpected return value from sync_nbgl_useCaseTransactionReview"); + } + } } } } @@ -401,4 +264,4 @@ extern "C" fn io_seproxyhal_play_tune(tune_index: u8) { #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { exit_app(1); -} \ No newline at end of file +} diff --git a/ledger_secure_sdk_sys/build.rs b/ledger_secure_sdk_sys/build.rs index 34a083f4..8ae5c9de 100644 --- a/ledger_secure_sdk_sys/build.rs +++ b/ledger_secure_sdk_sys/build.rs @@ -1,8 +1,8 @@ extern crate cc; +use glob::glob; use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs::File, io::BufRead, io::BufReader, io::Read}; -use glob::glob; #[cfg(feature = "ccid")] const DEFINES_CCID: [(&str, Option<&str>); 2] = @@ -222,7 +222,7 @@ fn clone_sdk(device: &Device) -> PathBuf { ), Device::Stax => ( Path::new("https://github.com/LedgerHQ/ledger-secure-sdk"), - "API_LEVEL_13", + "API_LEVEL_14", ), }; @@ -494,7 +494,7 @@ impl SDKBuilder { Device::NanoS => ("nanos", "sdk_nanos.h"), Device::NanoX => ("nanox", "sdk_nanox.h"), Device::NanoSPlus => ("nanos2", "sdk_nanosp.h"), - Device::Stax => ("stax", "sdk_stax.h") + Device::Stax => ("stax", "sdk_stax.h"), }; bindings = bindings.clang_arg(format!("-I{bsdk}/target/{include_path}/include/")); bindings = bindings.header(header); @@ -529,6 +529,12 @@ impl SDKBuilder { .to_str() .unwrap(), ) + .header( + self.bolos_sdk + .join("lib_nbgl/include/nbgl_sync.h") + .to_str() + .unwrap(), + ) .header(self.bolos_sdk.join("lib_ux_stax/ux.h").to_str().unwrap()) } _ => (), diff --git a/ledger_secure_sdk_sys/sdk_stax.h b/ledger_secure_sdk_sys/sdk_stax.h index 639f7c2f..56e6a12f 100644 --- a/ledger_secure_sdk_sys/sdk_stax.h +++ b/ledger_secure_sdk_sys/sdk_stax.h @@ -1,5 +1,6 @@ #define HAVE_NBGL #define NBGL_USE_CASE +#define HAVE_SE_TOUCH #define HAVE_PIEZO_SOUND #define NBGL_PAGE #define HAVE_LOCAL_APDU_BUFFER diff --git a/ledger_secure_sdk_sys/src/c/src.c b/ledger_secure_sdk_sys/src/c/src.c index 76361e1b..0bc0fe48 100644 --- a/ledger_secure_sdk_sys/src/c/src.c +++ b/ledger_secure_sdk_sys/src/c/src.c @@ -54,7 +54,7 @@ void printhex_c(char* str, uint32_t m); "movt %[result], #:upper16:" #SYM "(sbrel)\n\t" \ "add %[result], r9, %[result]" \ : [result] "=r" (DST)) -#elif defined(TARGET_NANOX) +#elif defined(TARGET_NANOX) || defined(TARGET_STAX) # define SYMBOL_SBREL_ADDRESS(DST, SYM) \ __asm volatile( \ "ldr %[result], =" #SYM "(sbrel)\n\t" \