From 8a82caf2fcbf0bf39f7bf3f9d3dbfa729f6f8a35 Mon Sep 17 00:00:00 2001 From: bunnie Date: Mon, 16 Aug 2021 23:35:36 +0800 Subject: [PATCH] intermediate check-in on rootkeys enclave --- services/root-keys/src/api.rs | 54 +++++++++++++ services/root-keys/src/implementation.rs | 72 +++++++++++++++++- services/root-keys/src/lib.rs | 96 ++++++++++++++++++++---- services/root-keys/src/main.rs | 22 ++++++ services/shellchat/src/cmds/keys.rs | 3 +- services/status/src/main.rs | 2 +- 6 files changed, 233 insertions(+), 16 deletions(-) diff --git a/services/root-keys/src/api.rs b/services/root-keys/src/api.rs index a076f753c..a4c0b0d46 100644 --- a/services/root-keys/src/api.rs +++ b/services/root-keys/src/api.rs @@ -15,6 +15,8 @@ pub(crate) enum Opcode { IsEfuseSecured, /// quick check to see if the JTAG can read its IDCODE IsJtagWorking, + /// initiate an AES operation + AesOperation, TestUx, @@ -41,6 +43,10 @@ pub(crate) enum Opcode { UxSignXousPasswordPolicy, UxSignXousRun, + /// Ux AES calls + //UxAesRequestPw, + //UxAesRun, + // General Ux calls UxGutter, // NOP for UX calls that require a destination UxGetPolicy, @@ -87,3 +93,51 @@ pub enum RootkeyResult { IntegrityError, FlashError, } + +/// AES operation definitions +pub use cipher::{BlockCipher, consts::U16}; + +/// Selects which key to use for the decryption/encryption oracle. +/// currently only one type is available, the User key, but dozens more +/// could be accommodated. +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, PartialEq, Eq)] +pub enum AesRootkeyType { + User0 = 0x28, + NoneSpecified = 0xff, +} + +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Copy, Clone)] +pub enum AesBlockType { + SingleBlock([u8; 16]), + ParBlock([[u8; 16]; 16]), +} +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Copy, Clone)] +pub enum AesOpType { + Encrypt, + Decrypt, +} +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Copy, Clone)] +pub struct AesOp { + /// the caller can try to request "any" index, but it's checked inside the oracle first. + pub key_index: u8, + pub block: AesBlockType, + pub aes_op: AesOpType, +} +impl AesOp { + pub fn clear(&mut self) { + match self.block { + AesBlockType::SingleBlock(mut blk) => { + for b in blk.iter_mut() { + *b = 0; + } + } + AesBlockType::ParBlock(mut blks) => { + for blk in blks.iter_mut() { + for b in blk.iter_mut() { + *b = 0; + } + } + } + } + } +} \ No newline at end of file diff --git a/services/root-keys/src/implementation.rs b/services/root-keys/src/implementation.rs index ca3ced3cc..ab01067e3 100644 --- a/services/root-keys/src/implementation.rs +++ b/services/root-keys/src/implementation.rs @@ -557,6 +557,7 @@ pub(crate) struct RootKeys { ticktimer: ticktimer_server::Ticktimer, xns: xous_names::XousNames, jtag: jtag::Jtag, + fake_key: [u8; 32], // a base set of random numbers used to respond to invalid keyloc requests in AES operations } impl<'a> RootKeys { @@ -619,6 +620,13 @@ impl<'a> RootKeys { let spinor = spinor::Spinor::new(&xns).expect("couldn't connect to spinor server"); spinor.register_soc_token().expect("couldn't register rootkeys as the one authorized writer to the gateware update area!"); let jtag = jtag::Jtag::new(&xns).expect("couldn't connect to JTAG server"); + let trng = trng::Trng::new(&xns).expect("couldn't connect to TRNG server"); + // respond to invalid key indices with a "fake" AES key. We try to foil attempts to "probe out" the + // oracle to discover the presence of null keys. + let mut fake_key: [u8; 32] = [0; 32]; + for k in fake_key.chunks_exact_mut(8) { + k.clone_from_slice(&trng.get_u64().unwrap().to_be_bytes()); + } let keys = RootKeys { keyrom: CSR::new(keyrom.as_mut_ptr() as *mut u32), @@ -636,12 +644,13 @@ impl<'a> RootKeys { boot_password_policy: PasswordRetentionPolicy::AlwaysKeep, cur_password_type: None, susres: susres::Susres::new_without_hook(&xns).expect("couldn't connect to susres without hook"), - trng: trng::Trng::new(&xns).expect("couldn't connect to TRNG server"), + trng, gfx: graphics_server::Gfx::new(&xns).expect("couldn't connect to gfx"), spinor, ticktimer: ticktimer_server::Ticktimer::new().expect("couldn't connect to ticktimer"), xns, jtag, + fake_key, }; keys @@ -663,6 +672,67 @@ impl<'a> RootKeys { } pub fn kernel_base(&self) -> u32 { self.kernel_base } + /// This implementation creates and destroys the AES key schedule on every function call + /// However, Rootkey operations are not meant to be used for streaming operations; they are typically + /// used to secure subkeys, so a bit of overhead on each call is OK in order to not keep excess secret + /// data laying around. + /// ASSUME: the caller has confirmed that the user password is valid and in cache + pub fn aes_op(&mut self, key_index: u8, op_type: AesOpType, block: &mut [u8; 16]) { + let key = match key_index { + KeyRomLocs::USER_KEY => { + let mut key_enc = self.read_key_256(KeyRomLocs::USER_KEY); + let pcache: &PasswordCache = unsafe{& *(self.pass_cache.as_ptr() as *const PasswordCache)}; + for (key, &pw) in + key_enc.iter_mut().zip(pcache.hashed_boot_pw.iter()) { + *key = *key ^ pw; + } + key_enc + }, + _ => { + // within a single boot, return a stable, non-changing fake ky based off of a single root + // fake key. This will make it a bit harder for an attacker to "probe out" an oracle and see + // which keys are null or which are populated. + self.fake_key[0] = key_index; + self.fake_key + } + }; + let cipher = Aes256::new(GenericArray::from_slice(&key)); + match op_type { + AesOpType::Decrypt => cipher.decrypt_block(block.try_into().unwrap()), + AesOpType::Encrypt => cipher.encrypt_block(block.try_into().unwrap()) + } + } + pub fn aes_par_op(&mut self, key_index: u8, op_type: AesOpType, blocks: &mut[[u8; 16]; 16]) { + let key = match key_index { + KeyRomLocs::USER_KEY => { + let mut key_enc = self.read_key_256(KeyRomLocs::USER_KEY); + let pcache: &PasswordCache = unsafe{& *(self.pass_cache.as_ptr() as *const PasswordCache)}; + for (key, &pw) in + key_enc.iter_mut().zip(pcache.hashed_boot_pw.iter()) { + *key = *key ^ pw; + } + key_enc + }, + _ => { + self.fake_key[0] = key_index; + self.fake_key + } + }; + let cipher = Aes256::new(GenericArray::from_slice(&key)); + match op_type { + AesOpType::Decrypt => { + for block in blocks.iter_mut() { + cipher.decrypt_block(block.try_into().unwrap()); + } + }, + AesOpType::Encrypt => { + for block in blocks.iter_mut() { + cipher.encrypt_block(block.try_into().unwrap()); + } + } + } + } + /// returns None if there is an obvious problem with the JTAG interface /// otherwise returns the result. "secured" would be the most paranoid setting /// which is all the bits burned. There are other combinations that are also diff --git a/services/root-keys/src/lib.rs b/services/root-keys/src/lib.rs index bd9c26531..4af70ceed 100644 --- a/services/root-keys/src/lib.rs +++ b/services/root-keys/src/lib.rs @@ -7,16 +7,12 @@ use api::*; pub mod key2bits; use xous::{CID, send_message, Message}; +use xous_ipc::Buffer; use num_traits::*; +use std::convert::TryInto; pub use cipher::{self, BlockCipher, BlockDecrypt, BlockEncrypt, consts::U16}; -/// 128-bit AES block -pub type Block = cipher::generic_array::GenericArray; - -/// 16 x 128-bit AES blocks to be processed in bulk -pub type ParBlocks = cipher::generic_array::GenericArray; - pub enum ImageType { All, Gateware, @@ -29,15 +25,20 @@ pub enum ImageType { pub struct RootKeys { conn: CID, // index of the key to use for the next encrypt/decrypt ops - key_index: Option, + key_index: AesRootkeyType, } impl RootKeys { - pub fn new(xns: &xous_names::XousNames) -> Result { + pub fn new(xns: &xous_names::XousNames, key_index: Option) -> Result { REFCOUNT.store(REFCOUNT.load(Ordering::Relaxed) + 1, Ordering::Relaxed); let conn = xns.request_connection_blocking(api::SERVER_NAME_KEYS).expect("Can't connect to Keys server"); + let index = if let Some(ki) = key_index { + ki + } else { + AesRootkeyType::NoneSpecified + }; Ok(RootKeys { conn, - key_index: None, + key_index: index, }) } pub fn conn(&self) -> CID {self.conn} @@ -51,6 +52,7 @@ impl RootKeys { Opcode::UxSelfSignXous.to_u32().unwrap() } + /// this function causes the staging gateware to be provisioned with a copy of our keys, /// while being encrypted to the AES key indicated inside the KEYROM pub fn provision_and_encrypt_staging_gateware(&mut self) -> Result<(), xous::Error> { @@ -179,6 +181,10 @@ impl Drop for RootKeys { } } +/// 128-bit AES block +pub type Block = cipher::generic_array::GenericArray; +/// 16 x 128-bit AES blocks to be processed in bulk +pub type ParBlocks = cipher::generic_array::GenericArray; impl BlockCipher for RootKeys { type BlockSize = U16; // 128-bit cipher @@ -187,19 +193,83 @@ impl BlockCipher for RootKeys { impl BlockEncrypt for RootKeys { fn encrypt_block(&self, block: &mut Block) { - // put data in a buf, send it to the server for encryption + let op = AesOp { + key_index: self.key_index.to_u8().unwrap(), + block: AesBlockType::SingleBlock(block.as_slice().try_into().unwrap()), + aes_op: AesOpType::Encrypt, + }; + let mut buf = Buffer::into_buf(op).unwrap(); + buf.lend_mut(self.conn, Opcode::AesOperation.to_u32().unwrap()).expect("couldn't initiate encrypt_block operation"); + let ret_op = buf.as_flat::().expect("got the wrong type of data structure back for encrypt_block"); + if let ArchivedAesBlockType::SingleBlock(b) = ret_op.block { + for (&src, dst) in b.iter().zip(block.as_mut_slice().iter_mut()) { + *dst = src; + } + } } fn encrypt_par_blocks(&self, blocks: &mut ParBlocks) { - + let mut pb_buf: [[u8; 16]; 16] = [[0; 16]; 16]; + for (dst_block, src_block) in pb_buf.iter_mut().zip(blocks.as_slice().iter()) { + for (dst, &src) in dst_block.iter_mut().zip(src_block.as_slice().iter()) { + *dst = src; + } + } + let op = AesOp { + key_index: self.key_index.to_u8().unwrap(), + block: AesBlockType::ParBlock(pb_buf), + aes_op: AesOpType::Encrypt, + }; + let mut buf = Buffer::into_buf(op).unwrap(); + buf.lend_mut(self.conn, Opcode::AesOperation.to_u32().unwrap()).expect("couldn't initiate encrypt_block operation"); + let ret_op = buf.as_flat::().expect("got the wrong type of data structure back for encrypt_block"); + if let ArchivedAesBlockType::ParBlock(pb) = ret_op.block { + for (b, pbs) in pb.iter().zip(blocks.as_mut_slice().iter_mut()) { + for (&src, dst) in b.iter().zip(pbs.as_mut_slice().iter_mut()) { + *dst = src; + } + } + } } } impl BlockDecrypt for RootKeys { fn decrypt_block(&self, block: &mut Block) { - + let op = AesOp { + key_index: self.key_index.to_u8().unwrap(), + block: AesBlockType::SingleBlock(block.as_slice().try_into().unwrap()), + aes_op: AesOpType::Decrypt, + }; + let mut buf = Buffer::into_buf(op).unwrap(); + buf.lend_mut(self.conn, Opcode::AesOperation.to_u32().unwrap()).expect("couldn't initiate encrypt_block operation"); + let ret_op = buf.as_flat::().expect("got the wrong type of data structure back for encrypt_block"); + if let ArchivedAesBlockType::SingleBlock(b) = ret_op.block { + for (&src, dst) in b.iter().zip(block.as_mut_slice().iter_mut()) { + *dst = src; + } + } } fn decrypt_par_blocks(&self, blocks: &mut ParBlocks) { - + let mut pb_buf: [[u8; 16]; 16] = [[0; 16]; 16]; + for (dst_block, src_block) in pb_buf.iter_mut().zip(blocks.as_slice().iter()) { + for (dst, &src) in dst_block.iter_mut().zip(src_block.as_slice().iter()) { + *dst = src; + } + } + let op = AesOp { + key_index: self.key_index.to_u8().unwrap(), + block: AesBlockType::ParBlock(pb_buf), + aes_op: AesOpType::Decrypt, + }; + let mut buf = Buffer::into_buf(op).unwrap(); + buf.lend_mut(self.conn, Opcode::AesOperation.to_u32().unwrap()).expect("couldn't initiate encrypt_block operation"); + let ret_op = buf.as_flat::().expect("got the wrong type of data structure back for encrypt_block"); + if let ArchivedAesBlockType::ParBlock(pb) = ret_op.block { + for (b, pbs) in pb.iter().zip(blocks.as_mut_slice().iter_mut()) { + for (&src, dst) in b.iter().zip(pbs.as_mut_slice().iter_mut()) { + *dst = src; + } + } + } } } diff --git a/services/root-keys/src/main.rs b/services/root-keys/src/main.rs index 4cc1bc07d..f2ba50093 100644 --- a/services/root-keys/src/main.rs +++ b/services/root-keys/src/main.rs @@ -325,6 +325,28 @@ fn xmain() -> ! { xous::return_scalar(msg.sender, 0).unwrap(); } }), + Some(Opcode::AesOperation) => { + let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + // as_flat saves a copy step, but we have to deserialize some enums manually + let aes_op = buffer.as_flat::().unwrap(); + // deserialize the specifier + let optype = match aes_op.aes_op { + ArchivedAesOpType::Decrypt => { + AesOpType::Decrypt + }, + ArchivedAesOpType::Encrypt => { + AesOpType::Encrypt + } + }; + match aes_op.block { + ArchivedAesBlockType::SingleBlock(mut b) => { + keys.aes_op(aes_op.key_index, optype, &mut b); + } + ArchivedAesBlockType::ParBlock(mut pb) => { + keys.aes_par_op(aes_op.key_index, optype, &mut pb); + } + } + }, // UX flow opcodes Some(Opcode::UxTryInitKeys) => msg_scalar_unpack!(msg, _, _, _, _, { diff --git a/services/shellchat/src/cmds/keys.rs b/services/shellchat/src/cmds/keys.rs index f38f11dbb..d98705d7b 100644 --- a/services/shellchat/src/cmds/keys.rs +++ b/services/shellchat/src/cmds/keys.rs @@ -1,4 +1,5 @@ use crate::{ShellCmdApi, CommonEnv}; +use root_keys::api::AesRootkeyType; use xous_ipc::String; #[derive(Debug)] @@ -33,7 +34,7 @@ impl Keys { Keys { testing_range, spinor, - rootkeys: root_keys::RootKeys::new(&xns).expect("couldn't allocate rootkeys API"), + rootkeys: root_keys::RootKeys::new(&xns, Some(AesRootkeyType::User0)).expect("couldn't allocate rootkeys API"), } } } diff --git a/services/status/src/main.rs b/services/status/src/main.rs index d50532f4e..c10b4c651 100644 --- a/services/status/src/main.rs +++ b/services/status/src/main.rs @@ -188,7 +188,7 @@ fn xmain() -> ! { if !debug_locked { sec_notes.lock().unwrap().insert("secnote.usb_unlock".to_string(), t!("secnote.usb_unlock", xous::LANG).to_string()); } - let keys = Arc::new(Mutex::new(root_keys::RootKeys::new(&xns).expect("couldn't connect to root_keys to query initialization state"))); + let keys = Arc::new(Mutex::new(root_keys::RootKeys::new(&xns, None).expect("couldn't connect to root_keys to query initialization state"))); if !keys.lock().unwrap().is_initialized().unwrap() { sec_notes.lock().unwrap().insert("secnotes.no_keys".to_string(), t!("secnote.no_keys", xous::LANG).to_string()); } else {