mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
add first cut at basis key derivation function
This commit is contained in:
parent
af9f4cbde3
commit
5a12e83ff2
7 changed files with 165 additions and 9 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -1430,7 +1430,9 @@ dependencies = [
|
|||
"aes-gcm-siv",
|
||||
"bitfield",
|
||||
"bitflags",
|
||||
"blowfish",
|
||||
"cipher",
|
||||
"digest",
|
||||
"llio",
|
||||
"log",
|
||||
"log-server",
|
||||
|
|
@ -1440,6 +1442,7 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
"rkyv",
|
||||
"root-keys",
|
||||
"sha2",
|
||||
"spinor",
|
||||
"susres",
|
||||
"ticktimer-server",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ bitfield = "0.13.2"
|
|||
aes-gcm-siv = "0.10.3"
|
||||
llio = {path="../llio"}
|
||||
|
||||
# passwords
|
||||
sha2 = {path = "../engine-sha512"}
|
||||
digest = "0.9"
|
||||
|
||||
# bcrypt
|
||||
blowfish = { version = "0.8", features = ["bcrypt"] }
|
||||
|
||||
|
||||
[target.'cfg(not(any(windows,unix)))'.dependencies]
|
||||
utralib = { path = "../../utralib"}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ pub(crate) const PDDB_DEFAULT_SYSTEM_BASIS: &'static str = ".System";
|
|||
#[allow(dead_code)]
|
||||
pub(crate) const PDDB_FAST_SPACE_SYSTEM_BASIS: &'static str = ".FastSpace";
|
||||
|
||||
#[allow(dead_code)]
|
||||
// TODO: add hardware acceleration for BCRYPT so we can hit the OWASP target without excessive UX delay
|
||||
pub(crate) const BCRYPT_COST: u32 = 7; // 10 is the minimum recommended by OWASP; takes 5696 ms to verify @ 10 rounds; 804 ms to verify 7 rounds
|
||||
|
||||
|
||||
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)]
|
||||
pub(crate) enum Opcode {
|
||||
KeyRequest,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ mod fastspace;
|
|||
pub use fastspace::*;
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
mod bcrypt;
|
||||
pub use bcrypt::*;
|
||||
|
||||
// local to the backend
|
||||
mod murmur3;
|
||||
|
|
|
|||
76
services/pddb/src/backend/bcrypt.rs
Normal file
76
services/pddb/src/backend/bcrypt.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
|
||||
// this code is based on https://github.com/Keats/rust-bcrypt
|
||||
// the original crate is not used directly because the base64 wrappers cause the crate
|
||||
// to be incompatible with a no-std environment. We are storing binary data directly
|
||||
// in our use case.
|
||||
|
||||
// Cost constants
|
||||
#[allow(dead_code)]
|
||||
const MIN_COST: u32 = 4;
|
||||
#[allow(dead_code)]
|
||||
const MAX_COST: u32 = 31;
|
||||
|
||||
use blowfish::Blowfish;
|
||||
|
||||
fn setup(cost: u32, salt: &[u8], key: &[u8]) -> Blowfish {
|
||||
assert!(cost < 32);
|
||||
let mut state = Blowfish::bc_init_state();
|
||||
|
||||
state.salted_expand_key(salt, key);
|
||||
for _ in 0..1u32 << cost {
|
||||
state.bc_expand_key(key);
|
||||
state.bc_expand_key(salt);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
pub fn bcrypt(cost: u32, salt: &[u8], pw: &str, output: &mut [u8]) {
|
||||
assert!(salt.len() == 16);
|
||||
assert!(output.len() == 24);
|
||||
|
||||
let pw_len = if pw.len() > 72 {
|
||||
log::warn!("password of length {} is truncated to 72 bytes [reason: bcrypt limitation]", pw.len());
|
||||
72
|
||||
} else {
|
||||
pw.len() + 1
|
||||
};
|
||||
let mut plaintext_copy: [u8; 73] = [0; 73];
|
||||
for (src, dst) in pw.bytes().zip(plaintext_copy.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
plaintext_copy[72] = 0; // always null terminate
|
||||
|
||||
// this function takes the plaintext key and uses it to prime a ~4k region of stack with an s-box
|
||||
// that's used for the round function. The upstream Rust crypto crate does not wipe the sbox after use.
|
||||
// however, it seems non-trivial to reverse the original password from the s-boxes.
|
||||
let state = setup(cost, salt, &plaintext_copy[..pw_len]);
|
||||
|
||||
// erase the plaintext copy as soon as we're done with it
|
||||
// an unsafe method is used because the compiler will correctly reason that plaintext_copy goes out of scope
|
||||
// and these writes are never read, and therefore they may be optimized out.
|
||||
let pt_ptr = plaintext_copy.as_mut_ptr();
|
||||
for i in 0..plaintext_copy.len() {
|
||||
unsafe{pt_ptr.add(i).write_volatile(core::mem::zeroed());}
|
||||
}
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
// OrpheanBeholderScryDoubt
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
let mut ctext = [
|
||||
0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274,
|
||||
];
|
||||
for i in 0..3 {
|
||||
let i: usize = i * 2;
|
||||
for _ in 0..64 {
|
||||
let (l, r) = state.bc_encrypt(ctext[i], ctext[i + 1]);
|
||||
ctext[i] = l;
|
||||
ctext[i + 1] = r;
|
||||
}
|
||||
|
||||
let buf = ctext[i].to_be_bytes();
|
||||
output[i * 4..][..4].copy_from_slice(&buf);
|
||||
let buf = ctext[i + 1].to_be_bytes();
|
||||
output[(i + 1) * 4..][..4].copy_from_slice(&buf);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ use std::cmp::Reverse;
|
|||
use std::io::{Result, Error, ErrorKind};
|
||||
|
||||
/// Implementation-specific PDDB structures: for Precursor/Xous OS pair
|
||||
|
||||
pub(crate) const MBBB_PAGES: usize = 10;
|
||||
pub(crate) const FSCB_PAGES: usize = 16;
|
||||
|
||||
|
|
@ -30,9 +29,7 @@ pub(crate) struct StaticCryptoData {
|
|||
/// aes-256 key of the system basis, encrypted with the User0 root key
|
||||
pub(crate) system_key: [u8; AES_KEYSIZE],
|
||||
/// a pool of fixed data used to pick salts, based on a hash of the basis name
|
||||
pub(crate) salt_base: [u8; 2048],
|
||||
/// also random data, but no specific purpose
|
||||
pub(crate) reserved: [u8; 2016],
|
||||
pub(crate) salt_base: [u8; 4064],
|
||||
}
|
||||
impl Deref for StaticCryptoData {
|
||||
type Target = [u8];
|
||||
|
|
@ -1175,15 +1172,13 @@ impl PddbOs {
|
|||
}
|
||||
let mut crypto_keys = StaticCryptoData {
|
||||
system_key: [0; AES_KEYSIZE],
|
||||
salt_base: [0; 2048],
|
||||
reserved: [0; 2016],
|
||||
salt_base: [0; 4064],
|
||||
};
|
||||
// copy the encrypted key into the data structure for commit to Flash
|
||||
for (&src, dst) in basis_key_aes.iter().zip(crypto_keys.system_key.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
self.entropy.borrow_mut().get_slice(&mut crypto_keys.salt_base);
|
||||
self.entropy.borrow_mut().get_slice(&mut crypto_keys.reserved);
|
||||
self.patch_keys(crypto_keys.deref(), 0);
|
||||
// now we have a copy of the AES key necessary to encrypt the default System basis that we created in step 2.
|
||||
|
||||
|
|
@ -1342,6 +1337,73 @@ impl PddbOs {
|
|||
// for now, do nothing -- just indicate success with a returned empty set
|
||||
Some(Vec::<([u8; AES_KEYSIZE], String)>::new())
|
||||
}
|
||||
|
||||
/// Derives a 256-bit AES encryption key for a basis given a basis name and its password.
|
||||
/// You will also need to derive the AAD for the basis using the basis_name.
|
||||
pub(crate) fn basis_derive_key(&self, basis_name: &str, password: &str) -> [u8; AES_KEYSIZE] {
|
||||
use sha2::{FallbackStrategy, Sha512, Sha512Trunc256};
|
||||
use digest::Digest;
|
||||
use backend::bcrypt::*;
|
||||
|
||||
// 1. derive the salt from the "key" region. First step is to create the salt lookup
|
||||
// table, which is done by hashing the name and password together with SHA-512
|
||||
// manage the allocation of the data for the basis & password explicitly so that we may wipe them later
|
||||
let mut bname_copy = [0u8; BASIS_NAME_LEN];
|
||||
for (src, dst) in basis_name.bytes().zip(bname_copy.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
let mut plaintext_pw: [u8; 73] = [0; 73];
|
||||
for (src, dst) in password.bytes().zip(plaintext_pw.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
plaintext_pw[72] = 0; // always null terminate
|
||||
|
||||
let mut hasher = Sha512::new_with_strategy(FallbackStrategy::SoftwareOnly);
|
||||
hasher.update(&bname_copy);
|
||||
hasher.update(&plaintext_pw);
|
||||
let digest = hasher.finalize();
|
||||
// 2. now derive the actual salt by using indices from the digest to progressively
|
||||
// index into the salt table.
|
||||
let scd = self.static_crypto_data_get();
|
||||
let mut salt = [0u8; 16];
|
||||
for (index_bytes, dst) in digest.chunks(2).zip(salt.iter_mut()) {
|
||||
let index = u16::from_le_bytes(index_bytes.try_into().unwrap());
|
||||
*dst = scd.salt_base[index as usize % scd.salt_base.len()];
|
||||
log::info!("index: {}, dst: {}", index as usize % scd.salt_base.len(), *dst);
|
||||
}
|
||||
log::info!("derived salt: {:x?}", salt);
|
||||
|
||||
// 3. use the salt + password and run bcrypt on it to derive a key.
|
||||
let mut hashed_password: [u8; 24] = [0; 24];
|
||||
let start_time = self.timestamp_now();
|
||||
bcrypt(BCRYPT_COST, &salt, password, &mut hashed_password); // note: this internally makes a copy of the password, and destroys it
|
||||
let elapsed = self.timestamp_now() - start_time;
|
||||
log::info!("derived bcrypt password in {}ms", elapsed);
|
||||
|
||||
// 4. take the resulting 24-byte password and expand it to 32 bytes using sha512trunc256
|
||||
let mut expander = Sha512Trunc256::new_with_strategy(FallbackStrategy::SoftwareOnly);
|
||||
expander.update(hashed_password);
|
||||
let final_key = expander.finalize();
|
||||
let mut key = [0u8; AES_KEYSIZE];
|
||||
for (&src, dst) in final_key.iter().zip(key.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
|
||||
// 5. erase extra plaintext copies made of the basis name and password using a routine that
|
||||
// shouldn't be optimized out or re-ordered
|
||||
let bn_ptr = bname_copy.as_mut_ptr();
|
||||
for i in 0..bname_copy.len() {
|
||||
unsafe{bn_ptr.add(i).write_volatile(core::mem::zeroed());}
|
||||
}
|
||||
let pt_ptr = plaintext_pw.as_mut_ptr();
|
||||
for i in 0..plaintext_pw.len() {
|
||||
unsafe{pt_ptr.add(i).write_volatile(core::mem::zeroed());}
|
||||
}
|
||||
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
//6. return the key
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cstr_to_string(cstr: &[u8]) -> String {
|
||||
|
|
|
|||
|
|
@ -517,7 +517,8 @@ fn xmain() -> ! {
|
|||
- [done] in-place update consistency: general integrity then patch all keys with a new test pattern
|
||||
- [done] extend update consistency: general integrity then patch all keys with a longer test pattern
|
||||
- [done] key deletion torture test: delete every other key in a dictionary, then regenerate some of them with new data.
|
||||
- fast space exhaustion test: allocate and delete a bunch of stuff. trigger a fast-space regenerate.
|
||||
- [done] fast space exhaustion test: allocate and delete a bunch of stuff. trigger a fast-space regenerate.
|
||||
note: for faster stress-testing, we dialed the FSCB_PAGES to 4 and the FASTSPACE_PAGES to 1.
|
||||
- basis search: create basis A, populate with general integrity. create basis B, add test entries.
|
||||
hide basis B, confirm original A; mount basis B, confirm B overlay.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue