From d0c1b320e71c20c467892cd7f35f9892f12e0dae Mon Sep 17 00:00:00 2001 From: GroM Date: Mon, 29 Sep 2025 19:47:49 +0200 Subject: [PATCH 01/12] Add C PKI API bindings --- ledger_secure_sdk_sys/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ledger_secure_sdk_sys/build.rs b/ledger_secure_sdk_sys/build.rs index 5fb51b56..31df2c94 100644 --- a/ledger_secure_sdk_sys/build.rs +++ b/ledger_secure_sdk_sys/build.rs @@ -588,6 +588,7 @@ impl SDKBuilder<'_> { "include/syscalls.h", "include/os_ux.h", "lib_standard_app/swap_lib_calls.h", + "include/os_pki.h", /* pki */ ], ); From f717cbf8912302b9a060de60a16d0addce7ef26f Mon Sep 17 00:00:00 2001 From: GroM Date: Tue, 30 Sep 2025 18:39:46 +0200 Subject: [PATCH 02/12] PKI management in io_legacy --- ledger_device_sdk/examples/pki.rs | 98 +++++++++++++++++++ .../examples/test_pki_load_fake.apdu | 1 + .../examples/test_pki_load_flex.apdu | 2 + .../examples/test_pki_load_nanosplus.apdu | 3 + .../examples/test_pki_load_nanox.apdu | 2 + .../examples/test_pki_load_stax.apdu | 2 + ledger_device_sdk/src/io_legacy.rs | 32 +++++- ledger_device_sdk/src/lib.rs | 1 + ledger_device_sdk/src/pki.rs | 62 ++++++++++++ 9 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 ledger_device_sdk/examples/pki.rs create mode 100644 ledger_device_sdk/examples/test_pki_load_fake.apdu create mode 100644 ledger_device_sdk/examples/test_pki_load_flex.apdu create mode 100644 ledger_device_sdk/examples/test_pki_load_nanosplus.apdu create mode 100644 ledger_device_sdk/examples/test_pki_load_nanox.apdu create mode 100644 ledger_device_sdk/examples/test_pki_load_stax.apdu create mode 100644 ledger_device_sdk/src/pki.rs diff --git a/ledger_device_sdk/examples/pki.rs b/ledger_device_sdk/examples/pki.rs new file mode 100644 index 00000000..8ba07956 --- /dev/null +++ b/ledger_device_sdk/examples/pki.rs @@ -0,0 +1,98 @@ +#![no_std] +#![no_main] + +use include_gif::include_gif; +use ledger_device_sdk::hash::HashInit; +use ledger_device_sdk::io::*; +use ledger_device_sdk::nbgl::{NbglAction, NbglGlyph}; +use ledger_device_sdk::ecc::CurvesId; +use ledger_device_sdk::pki::pki_verify_data; + +ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); +pub enum Instruction { + GetVersion = 0x01, + GetAppName = 0x02, + CheckPki = 0x03 +} + +impl TryFrom for Instruction { + type Error = StatusWords; + fn try_from(value: ApduHeader) -> Result { + match value.ins { + 1 => Ok(Instruction::GetVersion), + 2 => Ok(Instruction::GetAppName), + 3 => Ok(Instruction::CheckPki), + _ => Err(StatusWords::NothingReceived), + } + } +} + +#[no_mangle] +extern "C" fn sample_main() { + + let mut comm = Comm::new(); + + #[cfg(target_os = "apex_p")] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("examples/crab_48x48.png", NBGL)); + #[cfg(any(target_os = "stax", target_os = "flex"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); + #[cfg(any(target_os = "nanosplus", target_os = "nanox"))] + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("examples/crab_14x14.png", NBGL)); + + // Create NBGL action + let _action = NbglAction::new() + .message("Press Stop to exit") + .action_text("Stop") + .glyph(&FERRIS); + + loop { + let ins: Instruction = comm.next_command(); + match ins { + Instruction::GetVersion => { + ledger_device_sdk::testing::debug_print("GetVersion\n"); + let version = [0, 1, 0]; // version 0.1.0 + comm.append(&version); + comm.reply_ok(); + } + Instruction::GetAppName => { + let app_name = b"PKI Example"; + comm.append(app_name); + comm.reply_ok(); + } + Instruction::CheckPki => { + ledger_device_sdk::testing::debug_print("Starting PKI Check\n"); + let buffer = match comm.get_data() { + Ok(buf) => buf, + Err(_err) => { + ledger_device_sdk::testing::debug_print("Failed to get data: {}\n"); + break; + } + }; + + let mut data = [0u8; 80]; + data.copy_from_slice(&buffer[..80]); + + let mut hasher = ledger_device_sdk::hash::sha2::Sha2_256::new(); + let mut hash_data = [0u8; 32]; + let _ = hasher.hash(&data[..], &mut hash_data); + + let mut signature = [0u8; 72]; + signature.copy_from_slice(&buffer[82..]); + + match pki_verify_data(&mut hash_data[..], 8u8, CurvesId::Secp256k1, signature.as_mut_slice()) { + Ok(()) => { + ledger_device_sdk::testing::debug_print("PKI Check successful\n"); + comm.reply_ok(); + }, + Err(err) => { + ledger_device_sdk::testing::debug_print("PKI Check failed\n"); + comm.reply(err); + }, + } + } + } + } +} \ No newline at end of file diff --git a/ledger_device_sdk/examples/test_pki_load_fake.apdu b/ledger_device_sdk/examples/test_pki_load_fake.apdu new file mode 100644 index 00000000..649051a7 --- /dev/null +++ b/ledger_device_sdk/examples/test_pki_load_fake.apdu @@ -0,0 +1 @@ +B0060E00B0010101020102110400000002120100130200021401011604000000002016496E737472756374696F6E5F44657363726970746F723002000F31010D3201213321028E03CDF2147B980260C7800A07199D910D381E6F3F45BF625E805D466E96F03F340101350103154730450221009929503B375B192B1E91AF0B1AA9039E68A399932F30EE74CE376C1C7939CF2002201B1EAB947C29FA7B1D66A15DC8A9208BC363F289EB7EB71F973FF81154094674 diff --git a/ledger_device_sdk/examples/test_pki_load_flex.apdu b/ledger_device_sdk/examples/test_pki_load_flex.apdu new file mode 100644 index 00000000..8e10f5ed --- /dev/null +++ b/ledger_device_sdk/examples/test_pki_load_flex.apdu @@ -0,0 +1,2 @@ +=> b0060800a701010102010211040000000212010013020002140101160400000000200d44796e616d69635f546f6b656e3002000e310108320121332102e3c05b637a0626ab382004a9350beb1de47958a3b9fc1efc6e16a72104c05fe734010135010515473045022100b31ef0a2398641fe938c55568dfc5f1a4297244c765a0c0659821361b812a4c6022023559941a0d58feda2b37008dde3b1b27c85a4aeeaff7d74bc68e093cec4585e +=> e00300009a010190020101030201f50406536f6c616e610504474f524b0601060733100100112c333850677a704a597532486b695976563871655046616b423874756f625064476d324646456e374470756d7012000848304602210091d254b2f9e8aad92754fc6275b4d218227ce89370cb724348d5c4426a9e95d6022100d1186c28811f334848867efa10b0611ad2dfdf6b50fcecdd55c11d572241b437 diff --git a/ledger_device_sdk/examples/test_pki_load_nanosplus.apdu b/ledger_device_sdk/examples/test_pki_load_nanosplus.apdu new file mode 100644 index 00000000..e25b8192 --- /dev/null +++ b/ledger_device_sdk/examples/test_pki_load_nanosplus.apdu @@ -0,0 +1,3 @@ +=> b0060800a601010102010211040000000212010013020002140101160400000000200d44796e616d69635f546f6b656e3002000e310108320121332102e3c05b637a0626ab382004a9350beb1de47958a3b9fc1efc6e16a72104c05fe73401013501031546304402207756cfa4c31d0732f45fe12ad824262c1359bd49a89ee847a5322d99023e1d2802204d6b36f57be3dd3d0ff2bbd5ecc71faae5d52899742673200d9f8236a58913e3 +=> e00300009a010190020101030201f50406536f6c616e610504474f524b0601060733100100112c333850677a704a597532486b695976563871655046616b423874756f625064476d324646456e374470756d7012000848304602210091d254b2f9e8aad92754fc6275b4d218227ce89370cb724348d5c4426a9e95d6022100d1186c28811f334848867efa10b0611ad2dfdf6b50fcecdd55c11d572241b437 + \ No newline at end of file diff --git a/ledger_device_sdk/examples/test_pki_load_nanox.apdu b/ledger_device_sdk/examples/test_pki_load_nanox.apdu new file mode 100644 index 00000000..7a4aa81f --- /dev/null +++ b/ledger_device_sdk/examples/test_pki_load_nanox.apdu @@ -0,0 +1,2 @@ +=> b0060800a701010102010211040000000212010013020002140101160400000000200d44796e616d69635f546f6b656e3002000e310108320121332102e3c05b637a0626ab382004a9350beb1de47958a3b9fc1efc6e16a72104c05fe734010135010215473045022100b483bb3a8d6b4eef83dd75e7adbfb4330f5c46bbcfaf343b6997b964c6da1dcb022077f1f41f2c7931cf191364d0a4071e1db8ce4fa510bc0e7e7517e7e973f4a1da +=> e00300009a010190020101030201f50406536f6c616e610504474f524b0601060733100100112c333850677a704a597532486b695976563871655046616b423874756f625064476d324646456e374470756d7012000848304602210091d254b2f9e8aad92754fc6275b4d218227ce89370cb724348d5c4426a9e95d6022100d1186c28811f334848867efa10b0611ad2dfdf6b50fcecdd55c11d572241b437 diff --git a/ledger_device_sdk/examples/test_pki_load_stax.apdu b/ledger_device_sdk/examples/test_pki_load_stax.apdu new file mode 100644 index 00000000..8ffe4a69 --- /dev/null +++ b/ledger_device_sdk/examples/test_pki_load_stax.apdu @@ -0,0 +1,2 @@ +=> b0060800a601010102010211040000000212010013020002140101160400000000200d44796e616d69635f546f6b656e3002000e310108320121332102e3c05b637a0626ab382004a9350beb1de47958a3b9fc1efc6e16a72104c05fe734010135010415463044022037c48a6217ce3ea24351b4deb714a8420a64f985df62e1820d421c6ac8aff75f0220290a3296a4263346866f52520197c6fa2a5e6e32e464833d3598706ee9158db0 +=> e00300009a010190020101030201f50406536f6c616e610504474f524b0601060733100100112c333850677a704a597532486b695976563871655046616b423874756f625064476d324646456e374470756d7012000848304602210091d254b2f9e8aad92754fc6275b4d218227ce89370cb724348d5c4426a9e95d6022100d1186c28811f334848867efa10b0611ad2dfdf6b50fcecdd55c11d572241b437 diff --git a/ledger_device_sdk/src/io_legacy.rs b/ledger_device_sdk/src/io_legacy.rs index b1871249..b2c9229d 100644 --- a/ledger_device_sdk/src/io_legacy.rs +++ b/ledger_device_sdk/src/io_legacy.rs @@ -54,6 +54,7 @@ pub enum SyscallError { NotSupported, InvalidState, Timeout, + InvalidPkiCertificate, Unspecified, } @@ -69,6 +70,7 @@ impl From for SyscallError { 8 => SyscallError::NotSupported, 9 => SyscallError::InvalidState, 10 => SyscallError::Timeout, + 0x422F | 0x4230..0x4239 | 0x422D | 0x3301 | 0x422E | 0x5720 | 0x4118 => SyscallError::InvalidPkiCertificate, _ => SyscallError::Unspecified, } } @@ -326,8 +328,8 @@ impl Comm { return None; } - // Manage BOLOS specific APDUs B0xx0000 - if self.io_buffer[1] == 0xB0 && self.io_buffer[3] == 0x00 && self.io_buffer[4] == 0x00 { + // Manage BOLOS specific APDUs B0xxyyzz + if self.io_buffer[1] == 0xB0 { handle_bolos_apdu(self, self.io_buffer[2]); return None; } @@ -682,11 +684,15 @@ fn default_nbgl_reply_status(reply: Reply) { } } +const BOLOS_INS_GET_VERSION: u8 = 0x01; +const BOLOS_INS_QUIT: u8 = 0xa7; +const BOLOS_INS_SET_PKI_CERT: u8 = 0x06; + // BOLOS APDU Handling (see https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps) fn handle_bolos_apdu(com: &mut Comm, ins: u8) { match ins { // Get Information INS: retrieve App name and version - 0x01 => { + BOLOS_INS_GET_VERSION => { unsafe { com.tx_length = 0; com.io_buffer[com.tx_length] = 0x01; @@ -717,10 +723,28 @@ fn handle_bolos_apdu(com: &mut Comm, ins: u8) { com.reply_ok(); } // Quit Application INS - 0xa7 => { + BOLOS_INS_QUIT => { com.reply_ok(); crate::exit_app(0); } + BOLOS_INS_SET_PKI_CERT => { + unsafe { + let public_key = cx_ecfp_384_public_key_t::default(); + let err = os_pki_load_certificate( + com.io_buffer[3], + com.io_buffer[6..].as_mut_ptr(), + com.io_buffer[5] as usize, + core::ptr::null_mut(), + core::ptr::null_mut(), + &public_key as *const cx_ecfp_384_public_key_t as *mut cx_ecfp_384_public_key_t, + ); + if err != 0 { + com.reply(SyscallError::from(err)); + return; + } + com.reply_ok(); + } + } _ => { com.reply(StatusWords::BadIns); } diff --git a/ledger_device_sdk/src/lib.rs b/ledger_device_sdk/src/lib.rs index afe892cc..0949d66e 100644 --- a/ledger_device_sdk/src/lib.rs +++ b/ledger_device_sdk/src/lib.rs @@ -26,6 +26,7 @@ pub mod io { pub mod libcall; pub mod math; pub mod nvm; +pub mod pki; pub mod random; pub mod screen; pub mod seph; diff --git a/ledger_device_sdk/src/pki.rs b/ledger_device_sdk/src/pki.rs new file mode 100644 index 00000000..0dc3c0bb --- /dev/null +++ b/ledger_device_sdk/src/pki.rs @@ -0,0 +1,62 @@ +use ledger_secure_sdk_sys::{os_pki_get_info, os_pki_verify, CERTIFICATE_TRUSTED_NAME_MAXLEN, cx_ecfp_384_public_key_t}; +use crate::ecc::CurvesId; +use crate::io::Reply; + +pub enum PkiVerifyError { + Success = 0, + MissingCertificate = 1, + WrongCertificateUsage = 2, + WrongCertificateCurve = 3, + WrongSignature = 4, +} + +impl From for Reply { + fn from(exc: PkiVerifyError) -> Reply { + Reply(0x6900 + exc as u16) + } +} + +pub fn pki_verify_data( + data: &mut [u8], + expected_key_usage: u8, + expected_curve: CurvesId, + signature: &mut [u8], +) -> Result<(), PkiVerifyError> { + + let certificate_name = [0u8; CERTIFICATE_TRUSTED_NAME_MAXLEN as usize]; + let mut certficate_name_len: usize = 0; + let mut key_usage: u8 = 0; + let mut pub_key = cx_ecfp_384_public_key_t::default(); + + let err = unsafe { + os_pki_get_info( + &mut key_usage as *mut u8, + certificate_name.as_ptr() as *mut u8, + &mut certficate_name_len as *mut usize, + &mut pub_key as *mut cx_ecfp_384_public_key_t, + ) + }; + if err != 0 { + return Err(PkiVerifyError::MissingCertificate); + } + if key_usage != expected_key_usage { + return Err(PkiVerifyError::WrongCertificateUsage); + } + if pub_key.curve != expected_curve as u8 { + return Err(PkiVerifyError::WrongCertificateCurve); + } + + let err = unsafe { + os_pki_verify( + data.as_mut_ptr() as *mut u8, + data.len(), + signature.as_mut_ptr() as *mut u8, + signature.len() + ) + }; + if err == true { + Ok(()) + } else { + Err(PkiVerifyError::WrongSignature) + } +} \ No newline at end of file From 7faa458643340e6ce991bbf473d374403f472a25 Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 10:52:05 +0200 Subject: [PATCH 03/12] Run cargo fmt --- ledger_device_sdk/examples/pki.rs | 26 ++++++++++++--------- ledger_device_sdk/src/io_legacy.rs | 36 +++++++++++++++--------------- ledger_device_sdk/src/pki.rs | 9 ++++---- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/ledger_device_sdk/examples/pki.rs b/ledger_device_sdk/examples/pki.rs index 8ba07956..fc1ac981 100644 --- a/ledger_device_sdk/examples/pki.rs +++ b/ledger_device_sdk/examples/pki.rs @@ -2,17 +2,17 @@ #![no_main] use include_gif::include_gif; +use ledger_device_sdk::ecc::CurvesId; use ledger_device_sdk::hash::HashInit; use ledger_device_sdk::io::*; use ledger_device_sdk::nbgl::{NbglAction, NbglGlyph}; -use ledger_device_sdk::ecc::CurvesId; use ledger_device_sdk::pki::pki_verify_data; ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); pub enum Instruction { GetVersion = 0x01, GetAppName = 0x02, - CheckPki = 0x03 + CheckPki = 0x03, } impl TryFrom for Instruction { @@ -29,9 +29,8 @@ impl TryFrom for Instruction { #[no_mangle] extern "C" fn sample_main() { - let mut comm = Comm::new(); - + #[cfg(target_os = "apex_p")] const FERRIS: NbglGlyph = NbglGlyph::from_include(include_gif!("examples/crab_48x48.png", NBGL)); @@ -47,7 +46,7 @@ extern "C" fn sample_main() { .message("Press Stop to exit") .action_text("Stop") .glyph(&FERRIS); - + loop { let ins: Instruction = comm.next_command(); match ins { @@ -80,19 +79,24 @@ extern "C" fn sample_main() { let _ = hasher.hash(&data[..], &mut hash_data); let mut signature = [0u8; 72]; - signature.copy_from_slice(&buffer[82..]); - - match pki_verify_data(&mut hash_data[..], 8u8, CurvesId::Secp256k1, signature.as_mut_slice()) { + signature.copy_from_slice(&buffer[82..]); + + match pki_verify_data( + &mut hash_data[..], + 8u8, + CurvesId::Secp256k1, + signature.as_mut_slice(), + ) { Ok(()) => { ledger_device_sdk::testing::debug_print("PKI Check successful\n"); comm.reply_ok(); - }, + } Err(err) => { ledger_device_sdk::testing::debug_print("PKI Check failed\n"); comm.reply(err); - }, + } } } } } -} \ No newline at end of file +} diff --git a/ledger_device_sdk/src/io_legacy.rs b/ledger_device_sdk/src/io_legacy.rs index b2c9229d..9565c676 100644 --- a/ledger_device_sdk/src/io_legacy.rs +++ b/ledger_device_sdk/src/io_legacy.rs @@ -70,7 +70,9 @@ impl From for SyscallError { 8 => SyscallError::NotSupported, 9 => SyscallError::InvalidState, 10 => SyscallError::Timeout, - 0x422F | 0x4230..0x4239 | 0x422D | 0x3301 | 0x422E | 0x5720 | 0x4118 => SyscallError::InvalidPkiCertificate, + 0x422F | 0x4230..0x4239 | 0x422D | 0x3301 | 0x422E | 0x5720 | 0x4118 => { + SyscallError::InvalidPkiCertificate + } _ => SyscallError::Unspecified, } } @@ -727,24 +729,22 @@ fn handle_bolos_apdu(com: &mut Comm, ins: u8) { com.reply_ok(); crate::exit_app(0); } - BOLOS_INS_SET_PKI_CERT => { - unsafe { - let public_key = cx_ecfp_384_public_key_t::default(); - let err = os_pki_load_certificate( - com.io_buffer[3], - com.io_buffer[6..].as_mut_ptr(), - com.io_buffer[5] as usize, - core::ptr::null_mut(), - core::ptr::null_mut(), - &public_key as *const cx_ecfp_384_public_key_t as *mut cx_ecfp_384_public_key_t, - ); - if err != 0 { - com.reply(SyscallError::from(err)); - return; - } - com.reply_ok(); + BOLOS_INS_SET_PKI_CERT => unsafe { + let public_key = cx_ecfp_384_public_key_t::default(); + let err = os_pki_load_certificate( + com.io_buffer[3], + com.io_buffer[6..].as_mut_ptr(), + com.io_buffer[5] as usize, + core::ptr::null_mut(), + core::ptr::null_mut(), + &public_key as *const cx_ecfp_384_public_key_t as *mut cx_ecfp_384_public_key_t, + ); + if err != 0 { + com.reply(SyscallError::from(err)); + return; } - } + com.reply_ok(); + }, _ => { com.reply(StatusWords::BadIns); } diff --git a/ledger_device_sdk/src/pki.rs b/ledger_device_sdk/src/pki.rs index 0dc3c0bb..73ea5fed 100644 --- a/ledger_device_sdk/src/pki.rs +++ b/ledger_device_sdk/src/pki.rs @@ -1,6 +1,8 @@ -use ledger_secure_sdk_sys::{os_pki_get_info, os_pki_verify, CERTIFICATE_TRUSTED_NAME_MAXLEN, cx_ecfp_384_public_key_t}; use crate::ecc::CurvesId; use crate::io::Reply; +use ledger_secure_sdk_sys::{ + cx_ecfp_384_public_key_t, os_pki_get_info, os_pki_verify, CERTIFICATE_TRUSTED_NAME_MAXLEN, +}; pub enum PkiVerifyError { Success = 0, @@ -22,7 +24,6 @@ pub fn pki_verify_data( expected_curve: CurvesId, signature: &mut [u8], ) -> Result<(), PkiVerifyError> { - let certificate_name = [0u8; CERTIFICATE_TRUSTED_NAME_MAXLEN as usize]; let mut certficate_name_len: usize = 0; let mut key_usage: u8 = 0; @@ -51,7 +52,7 @@ pub fn pki_verify_data( data.as_mut_ptr() as *mut u8, data.len(), signature.as_mut_ptr() as *mut u8, - signature.len() + signature.len(), ) }; if err == true { @@ -59,4 +60,4 @@ pub fn pki_verify_data( } else { Err(PkiVerifyError::WrongSignature) } -} \ No newline at end of file +} From f0d3d072162b24523720396bd75cbc9613f72a2c Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:32:04 +0200 Subject: [PATCH 04/12] Move APDU samples for PKI example in dedicated folder --- .../examples/{test_pki_load_fake.apdu => apdu/test_pki_fail.apdu} | 0 .../examples/{test_pki_load_flex.apdu => apdu/test_pki_flex.apdu} | 0 .../test_pki_nanosplus.apdu} | 0 .../{test_pki_load_nanox.apdu => apdu/test_pki_nanox.apdu} | 0 .../examples/{test_pki_load_stax.apdu => apdu/test_pki_stax.apdu} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename ledger_device_sdk/examples/{test_pki_load_fake.apdu => apdu/test_pki_fail.apdu} (100%) rename ledger_device_sdk/examples/{test_pki_load_flex.apdu => apdu/test_pki_flex.apdu} (100%) rename ledger_device_sdk/examples/{test_pki_load_nanosplus.apdu => apdu/test_pki_nanosplus.apdu} (100%) rename ledger_device_sdk/examples/{test_pki_load_nanox.apdu => apdu/test_pki_nanox.apdu} (100%) rename ledger_device_sdk/examples/{test_pki_load_stax.apdu => apdu/test_pki_stax.apdu} (100%) diff --git a/ledger_device_sdk/examples/test_pki_load_fake.apdu b/ledger_device_sdk/examples/apdu/test_pki_fail.apdu similarity index 100% rename from ledger_device_sdk/examples/test_pki_load_fake.apdu rename to ledger_device_sdk/examples/apdu/test_pki_fail.apdu diff --git a/ledger_device_sdk/examples/test_pki_load_flex.apdu b/ledger_device_sdk/examples/apdu/test_pki_flex.apdu similarity index 100% rename from ledger_device_sdk/examples/test_pki_load_flex.apdu rename to ledger_device_sdk/examples/apdu/test_pki_flex.apdu diff --git a/ledger_device_sdk/examples/test_pki_load_nanosplus.apdu b/ledger_device_sdk/examples/apdu/test_pki_nanosplus.apdu similarity index 100% rename from ledger_device_sdk/examples/test_pki_load_nanosplus.apdu rename to ledger_device_sdk/examples/apdu/test_pki_nanosplus.apdu diff --git a/ledger_device_sdk/examples/test_pki_load_nanox.apdu b/ledger_device_sdk/examples/apdu/test_pki_nanox.apdu similarity index 100% rename from ledger_device_sdk/examples/test_pki_load_nanox.apdu rename to ledger_device_sdk/examples/apdu/test_pki_nanox.apdu diff --git a/ledger_device_sdk/examples/test_pki_load_stax.apdu b/ledger_device_sdk/examples/apdu/test_pki_stax.apdu similarity index 100% rename from ledger_device_sdk/examples/test_pki_load_stax.apdu rename to ledger_device_sdk/examples/apdu/test_pki_stax.apdu From 11ea1754c008bfae50710403ca789d74c271548a Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:33:44 +0200 Subject: [PATCH 05/12] Use const to manage BOLOS specific APDUs --- ledger_device_sdk/src/io_legacy.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ledger_device_sdk/src/io_legacy.rs b/ledger_device_sdk/src/io_legacy.rs index 9565c676..d1a73ce1 100644 --- a/ledger_device_sdk/src/io_legacy.rs +++ b/ledger_device_sdk/src/io_legacy.rs @@ -686,9 +686,9 @@ fn default_nbgl_reply_status(reply: Reply) { } } -const BOLOS_INS_GET_VERSION: u8 = 0x01; -const BOLOS_INS_QUIT: u8 = 0xa7; -const BOLOS_INS_SET_PKI_CERT: u8 = 0x06; +pub(crate) const BOLOS_INS_GET_VERSION: u8 = 0x01; +pub(crate) const BOLOS_INS_QUIT: u8 = 0xa7; +pub(crate) const BOLOS_INS_SET_PKI_CERT: u8 = 0x06; // BOLOS APDU Handling (see https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps) fn handle_bolos_apdu(com: &mut Comm, ins: u8) { @@ -732,9 +732,9 @@ fn handle_bolos_apdu(com: &mut Comm, ins: u8) { BOLOS_INS_SET_PKI_CERT => unsafe { let public_key = cx_ecfp_384_public_key_t::default(); let err = os_pki_load_certificate( - com.io_buffer[3], - com.io_buffer[6..].as_mut_ptr(), - com.io_buffer[5] as usize, + com.io_buffer[3], // P1 + com.io_buffer[6..].as_mut_ptr(), // Data + com.io_buffer[5] as usize, // Length core::ptr::null_mut(), core::ptr::null_mut(), &public_key as *const cx_ecfp_384_public_key_t as *mut cx_ecfp_384_public_key_t, From 1255a778b154d48a19a638075d93f801f43f6c88 Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:34:08 +0200 Subject: [PATCH 06/12] Update PKI example --- ledger_device_sdk/examples/pki.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ledger_device_sdk/examples/pki.rs b/ledger_device_sdk/examples/pki.rs index fc1ac981..703a0d6c 100644 --- a/ledger_device_sdk/examples/pki.rs +++ b/ledger_device_sdk/examples/pki.rs @@ -5,7 +5,7 @@ use include_gif::include_gif; use ledger_device_sdk::ecc::CurvesId; use ledger_device_sdk::hash::HashInit; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{NbglAction, NbglGlyph}; +use ledger_device_sdk::nbgl::{NbglGlyph, NbglHomeAndSettings}; use ledger_device_sdk::pki::pki_verify_data; ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); @@ -41,11 +41,16 @@ extern "C" fn sample_main() { const FERRIS: NbglGlyph = NbglGlyph::from_include(include_gif!("examples/crab_14x14.png", NBGL)); - // Create NBGL action - let _action = NbglAction::new() - .message("Press Stop to exit") - .action_text("Stop") - .glyph(&FERRIS); + let mut home = NbglHomeAndSettings::new() + .glyph(&FERRIS) + .infos( + "PKI Example App", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + ) + .tagline("Send APDU from apdu folder"); + + home.show_and_return(); loop { let ins: Instruction = comm.next_command(); From 1641933621c85ec3731f0977e17957a2dd75d802 Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:34:55 +0200 Subject: [PATCH 07/12] [io_new] Manage PKI certificate BOLOS specific APDU --- ledger_device_sdk/src/io_new/bolos.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/ledger_device_sdk/src/io_new/bolos.rs b/ledger_device_sdk/src/io_new/bolos.rs index 51b01472..c8f1a344 100644 --- a/ledger_device_sdk/src/io_new/bolos.rs +++ b/ledger_device_sdk/src/io_new/bolos.rs @@ -1,11 +1,16 @@ use super::{Comm, StatusWords, DEFAULT_BUF_SIZE}; use ledger_secure_sdk_sys::*; +use crate::io_legacy::SyscallError; +use crate::io_legacy::BOLOS_INS_GET_VERSION; +use crate::io_legacy::BOLOS_INS_QUIT; +use crate::io_legacy::BOLOS_INS_SET_PKI_CERT; + /// Handle internal BOLOS APDUs (CLA = 0xB0, P1 = 0x00, P2 = 0x00). pub(crate) fn handle_bolos_apdu(comm: &mut Comm, ins: u8) { match ins { // Get Information INS: retrieve App name and version - 0x01 => { + BOLOS_INS_GET_VERSION => { let mut response = comm.begin_response(); let _ = response.append(&[0x01]); const MAX_TAG_LENGTH: u8 = 32; // maximum length for the buffer containing app name/version. @@ -53,10 +58,26 @@ pub(crate) fn handle_bolos_apdu(comm: &mut Comm, ins: u8) { let _ = response.send(StatusWords::Ok); } // Quit Application INS - 0xA7 | 0xa7 => { + BOLOS_INS_QUIT => { let _ = comm.begin_response().send(StatusWords::Ok); crate::exit_app(0); } + BOLOS_INS_SET_PKI_CERT => unsafe { + let public_key = cx_ecfp_384_public_key_t::default(); + let err = os_pki_load_certificate( + comm.buf[3], // P1 + comm.buf[6..].as_mut_ptr(), // Data + comm.buf[5] as usize, // Length + core::ptr::null_mut(), + core::ptr::null_mut(), + &public_key as *const cx_ecfp_384_public_key_t as *mut cx_ecfp_384_public_key_t, + ); + if err != 0 { + comm.begin_response().send(SyscallError::from(err)); + return; + } + comm.begin_response().send(StatusWords::Ok); + }, // Unknown INS within BOLOS namespace _ => { let _ = comm.begin_response().send(StatusWords::BadIns); From e001e8c5fcc658585ebc58411dadf591f6610d2f Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:45:02 +0200 Subject: [PATCH 08/12] Bump versions --- Cargo.lock | 2 +- ledger_device_sdk/Cargo.toml | 2 +- ledger_secure_sdk_sys/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bcda5fa..a685b7a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,7 @@ dependencies = [ [[package]] name = "ledger_secure_sdk_sys" -version = "1.11.3" +version = "1.11.4" dependencies = [ "bindgen", "cc", diff --git a/ledger_device_sdk/Cargo.toml b/ledger_device_sdk/Cargo.toml index 62865f04..e69e40e2 100644 --- a/ledger_device_sdk/Cargo.toml +++ b/ledger_device_sdk/Cargo.toml @@ -20,7 +20,7 @@ rand_core = { version = "0.6.3", default-features = false } zeroize = { version = "1.6.0", default-features = false } numtoa = "0.2.4" const-zero = "0.1.1" -ledger_secure_sdk_sys = { path = "../ledger_secure_sdk_sys", version = "1.11.3" } +ledger_secure_sdk_sys = { path = "../ledger_secure_sdk_sys", version = "1.11.4" } [features] debug = [] diff --git a/ledger_secure_sdk_sys/Cargo.toml b/ledger_secure_sdk_sys/Cargo.toml index 54adc101..f7234b2c 100644 --- a/ledger_secure_sdk_sys/Cargo.toml +++ b/ledger_secure_sdk_sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ledger_secure_sdk_sys" -version = "1.11.3" +version = "1.11.4" authors = ["yhql", "agrojean-ledger", "yogh333"] edition = "2021" license.workspace = true From 18f5c0d3704adae6e50f32a09125e7f5c1ec5edd Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 11:49:12 +0200 Subject: [PATCH 09/12] Update doc --- ledger_device_sdk/src/pki.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ledger_device_sdk/src/pki.rs b/ledger_device_sdk/src/pki.rs index 73ea5fed..d70ac4a9 100644 --- a/ledger_device_sdk/src/pki.rs +++ b/ledger_device_sdk/src/pki.rs @@ -4,6 +4,7 @@ use ledger_secure_sdk_sys::{ cx_ecfp_384_public_key_t, os_pki_get_info, os_pki_verify, CERTIFICATE_TRUSTED_NAME_MAXLEN, }; +/// PKI verification errors pub enum PkiVerifyError { Success = 0, MissingCertificate = 1, @@ -17,7 +18,15 @@ impl From for Reply { Reply(0x6900 + exc as u16) } } - +/// Verify data using the loaded certificate +/// # Arguments +/// * `data` - The data to verify. +/// * `expected_key_usage` - The expected key usage of the certificate. +/// * `expected_curve` - The expected curve of the certificate. See `CurvesId` enum +/// * `signature` - The signature to verify +/// # Returns +/// * `Ok(())` if the verification is successful +/// * `Err(PkiVerifyError)` if the verification fails pub fn pki_verify_data( data: &mut [u8], expected_key_usage: u8, From 5c300db1e9f9c63df4dda8db6fa7c3013ca40783 Mon Sep 17 00:00:00 2001 From: GroM Date: Wed, 1 Oct 2025 12:06:26 +0200 Subject: [PATCH 10/12] Fix cargo fmt --- ledger_device_sdk/src/pki.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger_device_sdk/src/pki.rs b/ledger_device_sdk/src/pki.rs index d70ac4a9..43f22429 100644 --- a/ledger_device_sdk/src/pki.rs +++ b/ledger_device_sdk/src/pki.rs @@ -20,7 +20,7 @@ impl From for Reply { } /// Verify data using the loaded certificate /// # Arguments -/// * `data` - The data to verify. +/// * `data` - The data to verify. /// * `expected_key_usage` - The expected key usage of the certificate. /// * `expected_curve` - The expected curve of the certificate. See `CurvesId` enum /// * `signature` - The signature to verify From ea4b558436e63df2b50d361c580028f62b77d318 Mon Sep 17 00:00:00 2001 From: GroM Date: Thu, 2 Oct 2025 15:12:53 +0200 Subject: [PATCH 11/12] Bump versions --- Cargo.lock | 2 +- ledger_device_sdk/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a685b7a5..0c9a9c18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,7 +327,7 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "ledger_device_sdk" -version = "1.27.1" +version = "1.28.0" dependencies = [ "const-zero", "include_gif", diff --git a/ledger_device_sdk/Cargo.toml b/ledger_device_sdk/Cargo.toml index e69e40e2..4c2b110e 100644 --- a/ledger_device_sdk/Cargo.toml +++ b/ledger_device_sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ledger_device_sdk" -version = "1.27.1" +version = "1.28.0" authors = ["yhql", "yogh333", "agrojean-ledger", "kingofpayne"] edition = "2021" license.workspace = true From b1641a3934e0d35b05689cdbfd5532750960ff17 Mon Sep 17 00:00:00 2001 From: GroM Date: Thu, 2 Oct 2025 15:22:44 +0200 Subject: [PATCH 12/12] Remove testmacro (internal crate only, not published) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 61c736ef..634ab43c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ | [ledger_device_sdk](./ledger_device_sdk) | Rust SDK | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_device_sdk%2FCargo.toml&query=%24.package.version&label=version) | | [ledger_secure_sdk_sys](./ledger_secure_sdk_sys) | [C SDK](https://github.com/LedgerHQ/ledger-secure-sdk) bindings | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Fledger_secure_sdk_sys%2FCargo.toml&query=%24.package.version&label=version) | | [include_gif](./include_gif) | Procedural macro to integrate logo in the UI/UX | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Finclude_gif%2FCargo.toml&query=%24.package.version&label=version) | -| [testmacro](./testmacro) | Procedural macro used by unit and integrations tests | ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FLedgerHQ%2Fledger-device-rust-sdk%2Frefs%2Fheads%2Fmaster%2Ftestmacro%2FCargo.toml&query=%24.package.version&label=version) | ## Docker builder