diff --git a/Cargo.lock b/Cargo.lock index 164a54d..ae55ebb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,7 +474,7 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "ledger_device_sdk" -version = "1.14.1" +version = "1.15.0" dependencies = [ "const-zero", "include_gif", diff --git a/ledger_device_sdk/Cargo.toml b/ledger_device_sdk/Cargo.toml index 872d606..23d488b 100644 --- a/ledger_device_sdk/Cargo.toml +++ b/ledger_device_sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ledger_device_sdk" -version = "1.14.1" +version = "1.15.0" authors = ["yhql", "yogh333", "agrojean-ledger", "kingofpayne"] edition = "2021" license.workspace = true diff --git a/ledger_device_sdk/src/hmac.rs b/ledger_device_sdk/src/hmac.rs new file mode 100644 index 0000000..f642680 --- /dev/null +++ b/ledger_device_sdk/src/hmac.rs @@ -0,0 +1,172 @@ +//! Hash-based message authentication code (HMAC) related functions +use ledger_secure_sdk_sys::{ + cx_hmac_final, cx_hmac_no_throw, cx_hmac_t, cx_hmac_update, CX_INVALID_PARAMETER, CX_LAST, + CX_OK, +}; + +pub mod ripemd; +pub mod sha2; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum HMACError { + InvalidParameter, + InvalidOutputLength, + InternalError, +} + +impl From for HMACError { + fn from(x: u32) -> HMACError { + match x { + CX_INVALID_PARAMETER => HMACError::InvalidParameter, + _ => HMACError::InternalError, + } + } +} + +impl From for u32 { + fn from(e: HMACError) -> u32 { + e as u32 + } +} + +/// Defines the behavior of a rust HMAC object. +/// The implementation for a given algorithm is done using a rust macro +/// to avoid code duplication since only the C structures and functions +/// imported from the C SDK change. +pub trait HMACInit: Sized { + /// Recovers a mutable version of the HMAC context that can be used + /// to call HMAC related method in the C SDK. + fn as_ctx_mut(&mut self) -> &mut cx_hmac_t; + /// Recovers a constant version of the HMAC context that can be used + /// to call HMAC related method in the C SDK. + fn as_ctx(&self) -> &cx_hmac_t; + /// Creates the HMAC object by initializing the associated context using + /// the related C structure. + fn new(key: &[u8]) -> Self; + + /// Computes a HMAC in one line by providing the complete input as well as the + /// output buffer. + /// An error can be returned if one of the parameter is invalid + /// or if the output buffer size is not enough. + fn hmac(&mut self, input: &[u8], output: &mut [u8]) -> Result<(), HMACError> { + let err = unsafe { + cx_hmac_no_throw( + self.as_ctx_mut(), + CX_LAST, + input.as_ptr(), + input.len(), + output.as_mut_ptr(), + output.len(), + ) + }; + if err != CX_OK { + Err(err.into()) + } else { + Ok(()) + } + } + + /// Updates the current HMAC object state with the given input data. + /// This method may be called as many times needed (useful for large bufferized + /// inputs). This method should not be called after `finalize`. + /// An error can be returned if the input is invalid or the context in a wrong state. + fn update(&mut self, input: &[u8]) -> Result<(), HMACError> { + let err = unsafe { cx_hmac_update(self.as_ctx_mut(), input.as_ptr(), input.len()) }; + if err != CX_OK { + Err(err.into()) + } else { + Ok(()) + } + } + + /// Finalizes the computation of the MAC and stores the result in the output buffer + /// as well as returning the MAC length. + /// This method should be called after one or many calls to `update`. + /// An error can be returned if one of the parameter is invalid + /// or if the output buffer size is not enough. + fn finalize(&mut self, output: &mut [u8]) -> Result { + let mut out_len = output.len(); + let err = unsafe { cx_hmac_final(self.as_ctx_mut(), output.as_mut_ptr(), &mut out_len) }; + if err != CX_OK { + Err(err.into()) + } else { + Ok(out_len) + } + } +} + +/// This macro can be used to implement the HMACInit trait for a given hash +/// algorithm by providing the structure name, the C context name, and the C +/// context initialization function. +macro_rules! impl_hmac { + ($typename:ident, $ctxname:ident, $initfname:ident) => { + #[derive(Default)] + #[allow(non_camel_case_types)] + pub struct $typename { + ctx: $ctxname, + } + impl HMACInit for $typename { + fn as_ctx_mut(&mut self) -> &mut cx_hmac_t { + unsafe { mem::transmute::<&mut $ctxname, &mut cx_hmac_t>(&mut self.ctx) } + } + + fn as_ctx(&self) -> &cx_hmac_t { + unsafe { mem::transmute::<&$ctxname, &cx_hmac_t>(&self.ctx) } + } + + fn new(key: &[u8]) -> Self { + let mut ctx: $typename = Default::default(); + let _err = unsafe { + $initfname(&mut ctx.ctx, key.as_ptr(), key.len().try_into().unwrap()) + }; + ctx + } + } + }; +} +pub(crate) use impl_hmac; + +#[cfg(test)] +mod tests { + use crate::assert_eq_err as assert_eq; + use crate::hmac::ripemd::Ripemd160; + use crate::hmac::HMACInit; + use crate::testing::TestType; + use testmacro::test_item as test; + + const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; + const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; + + #[test] + fn test_hmac_oneline() { + let mut mac = Ripemd160::new(TEST_KEY); + + let mut output: [u8; 20] = [0u8; 20]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, + 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, + ]; + assert_eq!(&output, &expected); + } + + #[test] + fn test_hmac_update() { + let mut mac = Ripemd160::new(TEST_KEY); + + let mut output: [u8; 20] = [0u8; 20]; + + let _ = mac.update(TEST_MSG); + + let res = mac.finalize(&mut output); + + let expected = [ + 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, + 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, + ]; + assert_eq!(&output, &expected); + assert_eq!(res.unwrap(), expected.len()); + } +} diff --git a/ledger_device_sdk/src/hmac/ripemd.rs b/ledger_device_sdk/src/hmac/ripemd.rs new file mode 100644 index 0000000..e09fc09 --- /dev/null +++ b/ledger_device_sdk/src/hmac/ripemd.rs @@ -0,0 +1,36 @@ +use super::HMACInit; +use core::mem; +use ledger_secure_sdk_sys::{cx_hmac_ripemd160_init_no_throw, cx_hmac_ripemd160_t, cx_hmac_t}; + +use super::impl_hmac; +impl_hmac!( + Ripemd160, + cx_hmac_ripemd160_t, + cx_hmac_ripemd160_init_no_throw +); + +#[cfg(test)] +mod tests { + use crate::assert_eq_err as assert_eq; + use crate::hmac::ripemd::*; + use crate::testing::TestType; + use testmacro::test_item as test; + + const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; + const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; + + #[test] + fn test_hmac_ripemd160() { + let mut mac = Ripemd160::new(TEST_KEY); + + let mut output: [u8; 20] = [0u8; 20]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0xfa, 0xde, 0x57, 0x70, 0xf8, 0xa5, 0x04, 0x1a, 0xac, 0xdb, 0xe1, 0xc5, 0x64, 0x21, + 0x0d, 0xa6, 0x89, 0x9b, 0x2e, 0x6f, + ]; + assert_eq!(&output, &expected); + } +} diff --git a/ledger_device_sdk/src/hmac/sha2.rs b/ledger_device_sdk/src/hmac/sha2.rs new file mode 100644 index 0000000..e8befbf --- /dev/null +++ b/ledger_device_sdk/src/hmac/sha2.rs @@ -0,0 +1,89 @@ +use super::HMACInit; +use core::mem; +use ledger_secure_sdk_sys::{ + cx_hmac_sha224_init, cx_hmac_sha256_init_no_throw, cx_hmac_sha256_t, cx_hmac_sha384_init, + cx_hmac_sha512_init_no_throw, cx_hmac_sha512_t, cx_hmac_t, +}; + +use super::impl_hmac; +impl_hmac!(Sha2_224, cx_hmac_sha256_t, cx_hmac_sha224_init); +impl_hmac!(Sha2_256, cx_hmac_sha256_t, cx_hmac_sha256_init_no_throw); +impl_hmac!(Sha2_384, cx_hmac_sha512_t, cx_hmac_sha384_init); +impl_hmac!(Sha2_512, cx_hmac_sha512_t, cx_hmac_sha512_init_no_throw); + +#[cfg(test)] +mod tests { + use crate::assert_eq_err as assert_eq; + use crate::hmac::sha2::*; + use crate::testing::TestType; + use testmacro::test_item as test; + + const TEST_MSG: &[u8; 29] = b"Not your keys, not your coins"; + const TEST_KEY: &[u8; 16] = b"hmac test key!!!"; + + #[test] + fn test_hmac_sha224() { + let mut mac = Sha2_224::new(TEST_KEY); + + let mut output: [u8; 28] = [0u8; 28]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0xc4, 0x64, 0x80, 0xfb, 0xea, 0xc7, 0x75, 0x6d, 0xee, 0xc1, 0x6a, 0xcb, 0x6d, 0xae, + 0x6a, 0xfa, 0x5d, 0x03, 0x17, 0x73, 0xd6, 0x4d, 0x49, 0xea, 0xa8, 0x5e, 0x4c, 0x1d, + ]; + assert_eq!(&output, &expected); + } + + #[test] + fn test_hmac_sha256() { + let mut mac = Sha2_256::new(TEST_KEY); + + let mut output: [u8; 32] = [0u8; 32]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0x4d, 0x23, 0x82, 0xff, 0xc3, 0xb0, 0x60, 0x48, 0x59, 0xc0, 0xe5, 0x28, 0xf3, 0x66, + 0xa0, 0xba, 0x5b, 0xcb, 0x2c, 0x24, 0x10, 0x9c, 0x9d, 0x0b, 0x3b, 0x0a, 0x75, 0x8d, + 0x0f, 0x5a, 0x2a, 0x13, + ]; + assert_eq!(&output, &expected); + } + + #[test] + fn test_hmac_sha384() { + let mut mac = Sha2_384::new(TEST_KEY); + + let mut output: [u8; 48] = [0u8; 48]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0x20, 0x6d, 0x0d, 0xfd, 0xfd, 0x22, 0x43, 0xde, 0xb0, 0x23, 0xf8, 0x56, 0x63, 0xd1, + 0xa2, 0x1e, 0xc1, 0x6a, 0x72, 0x6b, 0xa7, 0x8e, 0xc2, 0x25, 0xe2, 0x1e, 0x3e, 0x3b, + 0xb2, 0xf0, 0x55, 0x1d, 0x4e, 0xde, 0x5f, 0x81, 0xf6, 0xa1, 0xff, 0x8e, 0x76, 0x86, + 0xf1, 0x0f, 0x7a, 0xec, 0xbe, 0x35, + ]; + assert_eq!(&output, &expected); + } + + #[test] + fn test_hmac_sha512() { + let mut mac = Sha2_512::new(TEST_KEY); + + let mut output: [u8; 64] = [0u8; 64]; + + let _ = mac.hmac(TEST_MSG, &mut output); + + let expected = [ + 0x2d, 0x03, 0x14, 0x96, 0x68, 0x0e, 0xcc, 0x41, 0x2a, 0x42, 0xf2, 0x45, 0xf8, 0x0b, + 0x10, 0x87, 0x43, 0x96, 0x4d, 0x80, 0x5d, 0x93, 0x5c, 0xd1, 0x6b, 0x95, 0xc1, 0x7a, + 0xed, 0xbd, 0xd8, 0x8c, 0xf8, 0xa7, 0x60, 0xed, 0x04, 0xa2, 0x5b, 0x8d, 0xd8, 0x3d, + 0xa3, 0x13, 0xa1, 0x6a, 0x07, 0x33, 0x49, 0x06, 0x15, 0x79, 0x70, 0xf3, 0xe9, 0x9a, + 0xff, 0x25, 0xb6, 0x5e, 0x37, 0xd1, 0x7e, 0x2b, + ]; + assert_eq!(&output, &expected); + } +} diff --git a/ledger_device_sdk/src/lib.rs b/ledger_device_sdk/src/lib.rs index e91e279..7d76c31 100644 --- a/ledger_device_sdk/src/lib.rs +++ b/ledger_device_sdk/src/lib.rs @@ -14,6 +14,7 @@ pub mod ble; pub mod ccid; pub mod ecc; pub mod hash; +pub mod hmac; pub mod io; pub mod nvm; pub mod random;