From 355dc21ce05bf42726103494caca14903e03b875 Mon Sep 17 00:00:00 2001 From: Marion LAFON Date: Fri, 30 Aug 2024 17:26:10 +0200 Subject: [PATCH] Change NbglReview by NbglChoice --- src/main.rs | 165 +++++++++++++++++++++++++++++----------------------- 1 file changed, 91 insertions(+), 74 deletions(-) diff --git a/src/main.rs b/src/main.rs index 709311f..a6ea0b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use ledger_secure_sdk_sys; use ledger_device_sdk::{io}; -use ledger_device_sdk::io::{ApduHeader, Reply, StatusWords}; +use ledger_device_sdk::io::{ApduHeader, Reply, StatusWords, Event, Comm}; use ledger_device_sdk::{ecc, nvm, NVMData}; use ledger_device_sdk::random::{rand_bytes, Random}; #[cfg(not(any(target_os = "stax", target_os = "flex")))] @@ -31,7 +31,7 @@ use include_gif::include_gif; #[cfg(any(target_os = "stax", target_os = "flex"))] use ledger_device_sdk::nbgl::{NbglGlyph, NbglHomeAndSettings}; #[cfg(any(target_os = "stax", target_os = "flex"))] -use ledger_device_sdk::nbgl::{Field, NbglReview}; +use ledger_device_sdk::nbgl::{init_comm, NbglStatus, NbglChoice, NbglSpinner}; #[cfg(not(any(target_os = "stax", target_os = "flex")))] use ledger_device_sdk::ui::bitmaps::{CERTIFICATE, DASHBOARD_X, Glyph}; @@ -72,6 +72,10 @@ pub enum Error { InsNotSupported } +pub enum AppSW { + Deny = 0x6985, + Ok = 0x9000, +} impl From for Reply { fn from(sw: Error) -> Reply { @@ -153,7 +157,12 @@ extern "C" fn sample_main() { // Create the communication manager, and configure it to accept only APDU from the 0xe0 class. // If any APDU with a wrong class value is received, comm will respond automatically with // BadCla status word. - let mut comm = io::Comm::new().set_expected_cla(0x0); + let mut comm = Comm::new().set_expected_cla(0x80); + + // Initialize reference to Comm instance for NBGL + // API calls. + #[cfg(any(target_os = "stax", target_os = "flex"))] + init_comm(&mut comm); // Developer mode / pending review popup // must be cleared with user interaction @@ -172,13 +181,14 @@ extern "C" fn sample_main() { let _ = ecc::bip32_derive(ecc::CurvesId::Secp256k1, &BIP32_PATH, &mut enc_key, None); // iteration counter - let mut c = 0; // lfsr with period 16*4 - 1 (63), all pixels divided in 8 boxes let mut lfsr = Lfsr::new(u8::random() & 0x3f, 0x30); + let mut c: i32 = 0; loop { - if let io::Event::Command(ins) = display_infos(passwords, &mut comm) { + // Wait for either a specific button push to exit the app + // or an APDU command + if let Event::Command(ins) = display_infos(&mut comm) { match ins { - // Get version string // Should comply with other apps standard Instruction::GetVersion => { @@ -189,13 +199,13 @@ extern "C" fn sample_main() { comm.append(VERSION.as_bytes()); comm.append(&[0]); // No flags comm.reply_ok(); - } + }, // Get number of stored passwords Instruction::GetSize => { let len: [u8; 4] = passwords.len().to_be_bytes(); comm.append(&len); comm.reply_ok(); - } + }, // Add a password // If P1 == 0, password is in the data // If P1 == 1, password must be generated by the device @@ -214,11 +224,11 @@ extern "C" fn sample_main() { Err(e) => e.into(), }); c = 0; - } + }, // Get password name // This is used by the client to list the names of stored password // Login is not returned. - Instruction::GetName => { + Instruction::GetName => { let mut index_bytes = [0; 4]; index_bytes.copy_from_slice(comm.get(5, 5 + 4)); let index = u32::from_be_bytes(index_bytes); @@ -229,7 +239,7 @@ extern "C" fn sample_main() { } None => comm.reply(Error::EntryNotFound), } - } + }, // Get password by name // Returns login and password data. Instruction::GetByName => { @@ -238,16 +248,19 @@ extern "C" fn sample_main() { match passwords.into_iter().find(|&&x| x.name == name) { Some(&p) => { if validate( + &[&"Get password"], &[name.as_str()], - &[&"Read", &"password"], + &[&"Read password"], &[&"Cancel"], ) { comm.append(p.login.bytes()); comm.append(p.pass.bytes()); comm.reply_ok(); + NbglStatus::new().text("").show(true); } else { comm.reply(Error::NoConsent); + NbglStatus::new().text("").show(false); } } None => { @@ -256,7 +269,7 @@ extern "C" fn sample_main() { } } c = 0; - } + }, // Display a password on the screen only, without communicating it // to the host. @@ -266,8 +279,9 @@ extern "C" fn sample_main() { match passwords.into_iter().find(|&&x| x.name == name) { Some(&p) => { if validate( + &[&"Show password on the device"] , &[name.as_str()], - &[&"Read", &"password"], + &[&"Read password"], &[&"Cancel"], ) { @@ -285,7 +299,7 @@ extern "C" fn sample_main() { } } c = 0; - } + }, // Delete password by name Instruction::DeleteByName => { @@ -294,15 +308,18 @@ extern "C" fn sample_main() { Some(p) => { if validate( + &[&"Delete password"], &[name.as_str()], - &[&"Remove", &"password"], + &[&"Remove password"], &[&"Cancel"], ) { passwords.remove(p); comm.reply_ok(); + NbglStatus::new().text("Password deleted").show(true); } else { comm.reply(Error::NoConsent); + NbglStatus::new().text("Operation rejected").show(false); } } None => { @@ -311,7 +328,7 @@ extern "C" fn sample_main() { } } c = 0; - } + }, // Export // P1 can be 0 for plaintext, 1 for encrypted export. Instruction::Export => match comm.get_apdu_metadata().p1 { @@ -322,7 +339,7 @@ extern "C" fn sample_main() { // Reserved for export Instruction::ExportNext => { comm.reply(StatusWords::Unknown); - } + }, // Import // P1 can be 0 for plaintext, 1 for encrypted import. Instruction::Import => match comm.get_apdu_metadata().p1 { @@ -333,30 +350,33 @@ extern "C" fn sample_main() { // Reserved for import Instruction::ImportNext => { comm.reply(StatusWords::Unknown); - } + }, Instruction::Clear => { // Remove all passwords comm.reply::( - if validate(&[], &[&"Remove all", &"passwords"], &[&"Cancel"]) + if validate(&[], &[&"Remove all passwords"], &[&"Confirm"], &[&"Cancel"]) { - if validate(&[], &[&"Are you", &"sure?"], &[&"Cancel"]) + if validate(&[], &[&"Are you sure?"], &[&"Confirm"], &[&"Cancel"]) { passwords.clear(); + NbglStatus::new().text("All password are removed").show(true); StatusWords::Ok.into() } else { + NbglStatus::new().text("Operation rejected").show(false); Error::NoConsent.into() } } else { + NbglStatus::new().text("Operation rejected").show(false); Error::NoConsent.into() }, ); c = 0; - } + }, // Exit Instruction::Quit => { comm.reply_ok(); ledger_secure_sdk_sys::exit_app(0); - } + }, // HasName Instruction::HasName => { let name = ArrayString::<32>::from_bytes(comm.get(5, 5 + 32)); @@ -369,17 +389,17 @@ extern "C" fn sample_main() { } } comm.reply_ok(); - } + }, } - } + }; } } - - #[cfg(not(any(target_os = "stax", target_os = "flex")))] fn validate( message:&[&str], + sub_message: &[&str], + confirm: &[&str], cancel: &[&str]) -> bool @@ -394,30 +414,34 @@ fn validate( message:&[&str], #[cfg(any(target_os = "stax", target_os = "flex"))] -fn validate( message:&[&str], +fn validate( message:&[&str], + + sub_message: &[&str], confirm: &[&str], cancel: &[&str]) -> bool { - let my_fields = [ - Field { - name: "Amount", - value: "111 CRAB", - }, - Field { - name: "Destination", - value: "0x1234567890ABCDEF1234567890ABCDEF12345678", - }, - Field { - name: "Memo", - value: "This is a test transaction.", - }, - ]; - NbglReview::<>::new() - .titles(confirm.first().unwrap_or(&""), "subtitle", message.first().unwrap_or(&"") ) - .show(&[]) + + let success = NbglChoice::new().show( + message.first().unwrap_or(&""), + sub_message.first().unwrap_or(&""), + confirm.first().unwrap_or(&""), + cancel.first().unwrap_or(&""), + ); + + if success { + return true; + } else { + return false; + } + + + +// NbglReview::<>::new() +// .titles(confirm.first().unwrap_or(&""), "", message.first().unwrap_or(&"") ) +// .show(&[my_fields[0]]) } @@ -482,25 +506,14 @@ fn int2dec(x: usize) -> [u8; 2] { /// Display global information about the app: /// - Current number of passwords stored /// - App Version -/// +/// #[cfg(not(any(target_os = "stax", target_os = "flex")))] -fn display_infos(passwords: &nvm::Collection, comm: &mut io::Comm) -> io::Event { - let mut stored_n = *b" passwords"; - let pwlen_bytes = int2dec(passwords.len()); - - stored_n[0] = pwlen_bytes[0]; - stored_n[1] = pwlen_bytes[1]; - - // safety: int2dec returns a [u8; 2] consisting of values between - // '0' and '9', thus is valid utf8 - let stored_str = unsafe { core::str::from_utf8_unchecked(&stored_n) }; - +fn display_infos(comm: &mut io::Comm) -> io::Event { const APP_ICON: Glyph = Glyph::from_include(include_gif!("crab.gif")); let pages = [ // The from trait allows to create different styles of pages // without having to use the new() function. &Page::from((["NanoPass", "is ready"], &APP_ICON)), - &Page::from((["Passwords", stored_str], true)), &Page::from((["Version", env!("CARGO_PKG_VERSION")], true)), &Page::from(("Quit", &DASHBOARD_X)), ]; @@ -512,21 +525,14 @@ fn display_infos(passwords: &nvm::Collection, comm: &mut io:: } } } -#[cfg(any(target_os = "stax", target_os = "flex"))] -fn display_infos(passwords: &nvm::Collection, comm: &mut io::Comm) -> io::Event { - let mut stored_n = *b" passwords"; - let pwlen_bytes = int2dec(passwords.len()); - - stored_n[0] = pwlen_bytes[0]; - stored_n[1] = pwlen_bytes[1]; - - // safety: int2dec returns a [u8; 2] consisting of values between - // '0' and '9', thus is valid utf8 - let stored_str = unsafe { core::str::from_utf8_unchecked(&stored_n) }; +#[cfg(any(target_os = "stax", target_os = "flex"))] +pub fn display_infos(_: &mut Comm) -> Event { + // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. const FERRIS: NbglGlyph = NbglGlyph::from_include(include_gif!("key_16x16.gif", NBGL)); // Display the home screen. + NbglHomeAndSettings::new() .glyph(&FERRIS) .infos( @@ -582,11 +588,13 @@ fn set_password( return match passwords.into_iter().position(|x| x.name == *name) { Some(index) => { // A password with this name already exists. - if !validate(&[name.as_str()], &[&"Update", &"password"], &[&"Cancel"]) + if !validate(&[name.as_str()], &[&"Update password"], &[&"Confirm"], &[&"Cancel"]) { + NbglStatus::new().text("Operation rejected").show(false); return Err(Error::NoConsent); } passwords.remove(index); + NbglStatus::new().text("").show(true); match passwords.add(&new_item) { Ok(()) => Ok(()), // We just removed a password, this should not happen @@ -595,10 +603,12 @@ fn set_password( } None => { // Ask user confirmation - if !validate(&[name.as_str()], &[&"Create", &"password"], &[&"Cancel"]) + if !validate(&[name.as_str()], &[&"Create password"], &[&"Confirm"], &[&"Cancel"]) { + NbglStatus::new().text("Operation rejected").show(false); return Err(Error::NoConsent); } + NbglStatus::new().text("").show(true); match passwords.add(&new_item) { Ok(()) => Ok(()), Err(nvm::StorageFullError) => Err(Error::StorageFull), @@ -618,7 +628,7 @@ fn export( enc_key: Option<&[u8; 32]>, ) { // Ask user confirmation - if !validate(&[], &[&"Export", &"passwords"], &[&"Cancel"]) { + if !validate(&[], &[&"Export passwords"], &[&"Confirm"], &[&"Cancel"]) { comm.reply(Error::NoConsent); return; } @@ -626,7 +636,7 @@ fn export( // If export is in plaintext, add a warning let encrypted = enc_key.is_some(); if !encrypted - && !validate(&[&"Export is plaintext!"], &[&"Confirm"], &[&"Cancel"]) + && !validate(&[], &[&"Export is plaintext!"], &[&"Confirm"], &[&"Cancel"]) { comm.reply(Error::NoConsent); return; @@ -721,7 +731,7 @@ fn import( count_bytes.copy_from_slice(comm.get(5, 5 + 4)); let mut count = u32::from_be_bytes(count_bytes); // Ask user confirmation - if !validate(&[], &[&"Import", &"passwords"], &[&"Cancel"]) { + if !validate(&[], &[&"Import passwords"], &[&"Confirm"], &[&"Cancel"]) { comm.reply(Error::NoConsent); return; } else { @@ -818,8 +828,15 @@ fn show_message(msg: &str) { #[cfg(any(target_os = "stax", target_os = "flex"))] fn show_message(msg: &str) { + NbglSpinner::new().text(msg).show(); } #[cfg(any(target_os = "stax", target_os = "flex"))] fn popup(msg: &str) { + let _info = NbglChoice::new().show( + msg, + "", + "Ok", + "" + ); } \ No newline at end of file