mirror of
https://github.com/trussed-dev/trussed.git
synced 2026-05-10 06:02:27 +00:00
Hackety-hack: implement TOTP-over-CTAP. Also downsize littlefs to "fix" micro-ecc by optimizing less
This commit is contained in:
+7
-3
@@ -24,11 +24,12 @@ des = { version = "0.3.0", optional = true }
|
||||
embedded-hal = { version = "0.2.3", features = ["unproven"] }
|
||||
generic-array = "0.12.3" # "0.13.2"
|
||||
# generic-array = { version = "0.13.2", default-features = false }
|
||||
heapless = "0.5.5"
|
||||
heapless = { version = "0.5.5", features = ["ufmt"] }
|
||||
hmac = "0.7.1"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde_cbor = { version = "0.11.0", default-features = false }
|
||||
serde-indexed = "0.0.4"
|
||||
sha-1 = { version = "0.8.2", default-features = false, optional = true }
|
||||
sha2 = { version = "0.8.0", default-features = false }
|
||||
ufmt = "0.1.0"
|
||||
|
||||
@@ -79,6 +80,7 @@ default-mechanisms = [
|
||||
"p256",
|
||||
"sha256",
|
||||
"tdes",
|
||||
"totp",
|
||||
"trng",
|
||||
]
|
||||
aes256-cbc = []
|
||||
@@ -88,9 +90,11 @@ hmac-sha256 = []
|
||||
p256 = []
|
||||
sha256 = []
|
||||
tdes = ["des"]
|
||||
trng = []
|
||||
totp = ["sha-1"]
|
||||
trng = ["sha-1"]
|
||||
|
||||
[patch.crates-io]
|
||||
heapless = { git = "https://github.com/nickray/heapless", branch = "nickray-udebug" }
|
||||
# heapless = { git = "https://github.com/nicolas-solokeys/heapless", branch = "bytebuf" }
|
||||
heapless = { path = "../../../heapless" }
|
||||
ufmt = { git = "https://github.com/nickray/ufmt", branch = "nickray-derive-empty-enums" }
|
||||
ufmt-macros = { git = "https://github.com/nickray/ufmt", branch = "nickray-derive-empty-enums" }
|
||||
|
||||
@@ -521,6 +521,19 @@ impl<Syscall: crate::pipe::Syscall> Client<Syscall> {
|
||||
self.encrypt(Mechanism::Tdes, key.clone(), message, &[], None)
|
||||
}
|
||||
|
||||
pub fn unsafe_inject_totp_key<'c>(&'c mut self, raw_key: &[u8; 20], persistence: StorageLocation)
|
||||
-> core::result::Result<FutureResult<'c, reply::UnsafeInjectKey>, ClientError>
|
||||
{
|
||||
cortex_m_semihosting::hprintln!("{}B: raw key: {:X?}", raw_key.len(), raw_key).ok();
|
||||
self.raw.request(request::UnsafeInjectKey {
|
||||
mechanism: Mechanism::Totp,
|
||||
raw_key: ShortData::from_slice(raw_key).unwrap(),
|
||||
attributes: StorageAttributes::new().set_persistence(persistence),
|
||||
})?;
|
||||
self.syscall.syscall();
|
||||
Ok(FutureResult::new(self))
|
||||
}
|
||||
|
||||
pub fn unsafe_inject_tdes_key<'c>(&'c mut self, raw_key: &[u8; 24], persistence: StorageLocation)
|
||||
-> core::result::Result<FutureResult<'c, reply::UnsafeInjectKey>, ClientError>
|
||||
{
|
||||
@@ -593,6 +606,14 @@ impl<Syscall: crate::pipe::Syscall> Client<Syscall> {
|
||||
self.sign(Mechanism::P256, key.clone(), message, format)
|
||||
}
|
||||
|
||||
pub fn sign_totp<'c>(&'c mut self, key: &ObjectHandle, timestamp: u64)
|
||||
-> core::result::Result<FutureResult<'c, reply::Sign>, ClientError>
|
||||
{
|
||||
self.sign(Mechanism::Totp, key.clone(),
|
||||
×tamp.to_le_bytes().as_ref(),
|
||||
SignatureSerialization::Raw,
|
||||
)
|
||||
}
|
||||
|
||||
// - mechanism: Mechanism
|
||||
// - wrapping_key: ObjectHandle
|
||||
|
||||
+6
-3
@@ -5,9 +5,6 @@ pub trait MechanismTrait {}
|
||||
pub struct Aes256Cbc {}
|
||||
mod aes256cbc;
|
||||
|
||||
pub struct Tdes {}
|
||||
mod tdes;
|
||||
|
||||
pub struct Chacha8Poly1305 {}
|
||||
mod chacha8poly1305;
|
||||
|
||||
@@ -24,6 +21,12 @@ mod p256;
|
||||
pub struct Sha256 {}
|
||||
mod sha256;
|
||||
|
||||
pub struct Totp {}
|
||||
mod totp;
|
||||
|
||||
pub struct Tdes {}
|
||||
mod tdes;
|
||||
|
||||
pub struct Trng {}
|
||||
mod trng;
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
use core::convert::TryInto;
|
||||
|
||||
use cortex_m_semihosting::hprintln;
|
||||
|
||||
use crate::api::*;
|
||||
use crate::error::Error;
|
||||
use crate::service::*;
|
||||
use crate::store::Store;
|
||||
use crate::types::*;
|
||||
|
||||
// code copied from https://github.com/avacariu/rust-oath
|
||||
|
||||
const DIGITS: u32 = 6;
|
||||
|
||||
// https://tools.ietf.org/html/rfc4226#section-5.3
|
||||
|
||||
fn hotp_raw(key: &[u8], counter: u64, digits: u32) -> u64 {
|
||||
hmac_and_truncate(key, &counter.to_be_bytes(), digits)
|
||||
}
|
||||
|
||||
fn hmac_and_truncate(key: &[u8], message: &[u8], digits: u32) -> u64 {
|
||||
use hmac::{Hmac, Mac};
|
||||
// let mut hmac = Hmac::<D>::new(GenericArray::from_slice(key));
|
||||
hprintln!("1").ok();
|
||||
let mut hmac = Hmac::<sha1::Sha1>::new_varkey(key).unwrap();
|
||||
hprintln!("2").ok();
|
||||
hmac.input(message);
|
||||
hprintln!("3").ok();
|
||||
let result = hmac.result();
|
||||
hprintln!("4").ok();
|
||||
|
||||
// output of `.code()` is GenericArray<u8, OutputSize>, again 20B
|
||||
// crypto-mac docs warn: "Be very careful using this method,
|
||||
// since incorrect use of the code value may permit timing attacks
|
||||
// which defeat the security provided by the Mac trait."
|
||||
let hs = result.code();
|
||||
hprintln!("5").ok();
|
||||
|
||||
dynamic_truncation(&hs) % 10_u64.pow(digits)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dynamic_truncation(hs: &[u8]) -> u64 {
|
||||
// low-order bits of byte 19 (last) of the 20B output
|
||||
let offset_bits = (*hs.last().unwrap() & 0xf) as usize;
|
||||
|
||||
let p = u32::from_be_bytes(hs[offset_bits..][..4].try_into().unwrap()) as u64;
|
||||
|
||||
// zero highest bit, avoids signed/unsigned "ambiguity"
|
||||
p & 0x7fff_ffff
|
||||
}
|
||||
|
||||
#[cfg(feature = "totp")]
|
||||
impl<R: RngRead, S: Store>
|
||||
UnsafeInjectKey<R, S> for super::Totp
|
||||
{
|
||||
fn unsafe_inject_key(resources: &mut ServiceResources<R, S>, request: request::UnsafeInjectKey)
|
||||
-> Result<reply::UnsafeInjectKey, Error>
|
||||
{
|
||||
// in usual format, secret is a 32B Base32 encoding of 20B actual secret bytes
|
||||
hprintln!("a").ok();
|
||||
if request.raw_key.len() != 20 {
|
||||
hprintln!("{}B: {:X?}", request.raw_key.len(), &request.raw_key).ok();
|
||||
return Err(Error::WrongMessageLength);
|
||||
}
|
||||
|
||||
hprintln!("b").ok();
|
||||
// store it
|
||||
let key_id = resources.store_key(
|
||||
request.attributes.persistence,
|
||||
KeyType::Secret,
|
||||
KeyKind::Symmetric20,
|
||||
&request.raw_key,
|
||||
)?;
|
||||
|
||||
Ok(reply::UnsafeInjectKey { key: ObjectHandle { object_id: key_id } })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "totp")]
|
||||
impl<R: RngRead, S: Store>
|
||||
Sign<R, S> for super::Totp
|
||||
{
|
||||
fn sign(resources: &mut ServiceResources<R, S>, request: request::Sign)
|
||||
-> Result<reply::Sign, Error>
|
||||
{
|
||||
let key_id = request.key.object_id;
|
||||
|
||||
let secret: [u8; 20] = resources
|
||||
.load_key(KeyType::Secret, None, &key_id)?
|
||||
.value.as_slice().try_into()
|
||||
.map_err(|_| Error::InternalError)?;
|
||||
|
||||
if request.message.len() != 8 {
|
||||
return Err(Error::InternalError);
|
||||
}
|
||||
let timestamp_as_le_bytes = request.message[..].try_into().unwrap();
|
||||
let timestamp = u64::from_le_bytes(timestamp_as_le_bytes);
|
||||
let totp_value: u64 = hotp_raw(&secret, timestamp, DIGITS);
|
||||
|
||||
// return signature (encode as LE)
|
||||
Ok(reply::Sign { signature: totp_value.to_le_bytes().as_ref().try_into().unwrap() })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "totp")]
|
||||
impl<R: RngRead, S: Store>
|
||||
Exists<R, S> for super::Totp
|
||||
{
|
||||
fn exists(resources: &mut ServiceResources<R, S>, request: request::Exists)
|
||||
-> Result<reply::Exists, Error>
|
||||
{
|
||||
let key_id = request.key.object_id;
|
||||
|
||||
let exists = resources.exists_key(KeyType::Secret, Some(KeyKind::Symmetric20), &key_id);
|
||||
Ok(reply::Exists { exists })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hotp() {
|
||||
assert_eq!(hotp_raw(b"\xff", 23, 6), 330795);
|
||||
|
||||
// test values from RFC 4226
|
||||
assert_eq!(hotp_raw(b"12345678901234567890", 0, 6), 755224);
|
||||
assert_eq!(hotp_raw(b"12345678901234567890", 1, 6), 287082);
|
||||
assert_ne!(hotp_raw(b"12345678901234567890", 1, 6), 287081);
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,7 @@ impl<R: RngRead, S: Store> ServiceResources<R, S> {
|
||||
|
||||
Mechanism::Ed25519 => mechanisms::Ed25519::exists(self, request),
|
||||
Mechanism::P256 => mechanisms::P256::exists(self, request),
|
||||
Mechanism::Totp => mechanisms::Totp::exists(self, request),
|
||||
_ => Err(Error::MechanismNotAvailable),
|
||||
|
||||
}.map(|reply| Reply::Exists(reply))
|
||||
@@ -223,6 +224,7 @@ impl<R: RngRead, S: Store> ServiceResources<R, S> {
|
||||
Request::UnsafeInjectKey(request) => {
|
||||
match request.mechanism {
|
||||
Mechanism::Tdes => mechanisms::Tdes::unsafe_inject_key(self, request),
|
||||
Mechanism::Totp => mechanisms::Totp::unsafe_inject_key(self, request),
|
||||
_ => Err(Error::MechanismNotAvailable),
|
||||
}.map(|reply| Reply::UnsafeInjectKey(reply))
|
||||
},
|
||||
@@ -668,6 +670,7 @@ impl<R: RngRead, S: Store> ServiceResources<R, S> {
|
||||
Mechanism::HmacSha256 => mechanisms::HmacSha256::sign(self, request),
|
||||
Mechanism::P256 => mechanisms::P256::sign(self, request),
|
||||
Mechanism::P256Prehashed => mechanisms::P256Prehashed::sign(self, request),
|
||||
Mechanism::Totp => mechanisms::Totp::sign(self, request),
|
||||
_ => Err(Error::MechanismNotAvailable),
|
||||
|
||||
}.map(|reply| Reply::Sign(reply))
|
||||
|
||||
@@ -157,6 +157,7 @@ pub enum KeyKind {
|
||||
SymmetricKey32 = 6, // or directly: SharedSecret32 —DeriveKey(HmacSha256)-> SymmetricKey32 —Encrypt(Aes256)-> ...
|
||||
Symmetric32Nonce12 = 7,
|
||||
Symmetric24 = 8,
|
||||
Symmetric20 = 9,
|
||||
// ThirtytwoByteBuf,
|
||||
}
|
||||
|
||||
@@ -384,6 +385,7 @@ pub enum Mechanism {
|
||||
// clients can also do hashing by themselves
|
||||
Sha256,
|
||||
Tdes,
|
||||
Totp,
|
||||
Trng,
|
||||
X25519,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user