mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
intermediate check-in on rootkeys enclave
This commit is contained in:
parent
ccc577f8d7
commit
8a82caf2fc
6 changed files with 233 additions and 16 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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, _, _, _, _, {
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue