intermediate check-in on rootkeys enclave

This commit is contained in:
bunnie 2021-08-16 23:35:36 +08:00
parent ccc577f8d7
commit 8a82caf2fc
6 changed files with 233 additions and 16 deletions

View file

@ -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;
}
}
}
}
}
}

View file

@ -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

View file

@ -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<u8, cipher::consts::U16>;
/// 16 x 128-bit AES blocks to be processed in bulk
pub type ParBlocks = cipher::generic_array::GenericArray<Block, cipher::consts::U16>;
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<u8>,
key_index: AesRootkeyType,
}
impl RootKeys {
pub fn new(xns: &xous_names::XousNames) -> Result<Self, xous::Error> {
pub fn new(xns: &xous_names::XousNames, key_index: Option<AesRootkeyType>) -> Result<Self, xous::Error> {
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<u8, cipher::consts::U16>;
/// 16 x 128-bit AES blocks to be processed in bulk
pub type ParBlocks = cipher::generic_array::GenericArray<Block, cipher::consts::U16>;
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::<AesOp, _>().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::<AesOp, _>().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::<AesOp, _>().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::<AesOp, _>().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;
}
}
}
}
}

View file

@ -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::<AesOp, _>().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, _, _, _, _, {

View file

@ -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"),
}
}
}

View file

@ -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 {