use super::{ HashedPassword, PasswordHasher, HashSalt };
use std::io;
use std::ptr;
use std::mem;
use std::intrinsics::volatile_set_memory;
pub const CLEAR_TEXT_LEN: usize = 512 - 8;
pub const MIN_CLEAR_TEXT_LEN: usize = 16;
pub struct ClearTextPassword {
data: Box<ClearTextPasswordData>,
}
struct ClearTextPasswordData {
text: [u8; CLEAR_TEXT_LEN],
len: usize,
}
impl ClearTextPassword {
pub fn from_string(mut take: String) -> Option<ClearTextPassword> {
debug_assert!((take.len() as isize) >= 0, "Wtf, just how long is this string?!");
if take.len() < MIN_CLEAR_TEXT_LEN { return None; }
let mut ret = ClearTextPassword::empty_password();
(*ret.data).len = if take.len() > CLEAR_TEXT_LEN { CLEAR_TEXT_LEN } else { take.len() };
let src: *mut u8 = unsafe { mem::transmute(take.as_ptr()) };
let tgt: *mut u8 = (*ret.data).as_ptr_mut();
for i in 0..((*ret.data).len as isize) {
unsafe { ptr::write(tgt.offset(i), ptr::read(src.offset(i))) };
}
unsafe { volatile_set_memory(src, 0, take.capacity()) };
take.clear();
Some(ret)
}
pub fn from_vec(mut take: Vec<u8>) -> Option<ClearTextPassword> {
if take.len() < MIN_CLEAR_TEXT_LEN { return None; }
let mut ret = ClearTextPassword::empty_password();
(*ret.data).len = if take.len() > CLEAR_TEXT_LEN { CLEAR_TEXT_LEN } else { take.len() };
for i in 0..(*ret.data).len { (*ret.data).text[i] = take[i]; }
unsafe { volatile_set_memory(take.as_mut_ptr(), 0, take.capacity()) };
take.clear();
Some(ret)
}
pub fn from_slice(steal: &mut [u8]) -> Option<ClearTextPassword> {
if steal.len() < MIN_CLEAR_TEXT_LEN { return None; }
let mut ret = ClearTextPassword::empty_password();
(*ret.data).len = if steal.len() > CLEAR_TEXT_LEN { CLEAR_TEXT_LEN } else { steal.len() };
for i in 0..(*ret.data).len {
(*ret.data).text[i] = steal[i];
steal[i] = 0;
}
for i in (*ret.data).len..steal.len() { steal[i] = 0; }
Some(ret)
}
pub fn from_reader<R: io::Read>(r: &mut R) -> io::Result<Option<ClearTextPassword>> {
let mut ret = ClearTextPassword::empty_password();
let len = r.read((*ret.data).as_slice_mut())?;
if len < MIN_CLEAR_TEXT_LEN { return Ok(None); }
(*ret.data).len = len;
Ok(Some(ret))
}
pub fn hash<H: PasswordHasher>(&self, hasher: &H, salt: &HashSalt) -> HashedPassword {
let mut hash = HashedPassword::new();
unsafe { hasher.hash(hash.as_slice_mut(), (*self.data).get_password(), salt.as_slice()) };
hash
}
fn empty_password() -> ClearTextPassword { ClearTextPassword { data: box ClearTextPasswordData {
text: [0_u8; CLEAR_TEXT_LEN],
len: 0,
}}}
}
impl ClearTextPasswordData {
fn as_ptr_mut(&mut self) -> *mut u8 { &mut self.text[0] }
fn as_slice_mut(&mut self) -> &mut [u8] { &mut self.text[..] }
fn get_password(&self) -> &[u8] { &self.text[0..self.len] }
fn is_cleared(&self) -> bool {
for x in &self.text[..] {
if 0 != unsafe { ptr::read_volatile(x) } { return false; }
}
self.len == 0
}
}
impl Drop for ClearTextPasswordData {
fn drop(&mut self) {
let data: *mut u8 = &mut self.text[0];
unsafe { volatile_set_memory(data, 0, self.text.len()) };
unsafe { ptr::write_volatile(&mut self.len, 0) };
if cfg!(test) { assert!(self.is_cleared()); }
}
}
#[cfg(test)]
mod test {
use super::{ClearTextPassword, CLEAR_TEXT_LEN, MIN_CLEAR_TEXT_LEN};
use std::ptr;
use test::Bencher;
use std::io::Cursor;
#[test]
fn clear_text_len_is_reasonably_large() {
assert!(CLEAR_TEXT_LEN >= (256 - 8), "You are a bad person.");
assert!(MIN_CLEAR_TEXT_LEN >= 14, "Recommended by NIST (2016).");
}
#[test]
fn short_passwords_are_shitty() {
assert!(
ClearTextPassword::from_string("lego".to_owned()).is_none(),
"Really, 4 bytes passwords?"
);
}
#[cfg(not(skip_unsafe_tests))]
#[test]
#[allow(non_snake_case)]
fn take_string_and_zero___TRY_RERUN_IF_FAILED() {
let pw = "P@$$w0rd1!......".to_owned(); let data = pw.as_ptr();
let len = pw.len();
ClearTextPassword::from_string(pw).unwrap();
for i in 0..(len as isize) {
assert_eq!(unsafe { ptr::read_volatile(data.offset(i)) }, 0);
}
}
#[cfg(not(skip_unsafe_tests))]
#[test]
#[allow(non_snake_case)]
fn take_vec_and_zero___TRY_RERUN_IF_FAILED() {
let pw: Vec<u8> = vec![3, 4, 7, 42, 1,1,1,1,1,1,1,1,1,1,1,1];
let data = pw.as_ptr();
let len = pw.len();
ClearTextPassword::from_vec(pw).unwrap();
for i in 0..(len as isize) {
assert_eq!(unsafe { ptr::read_volatile(data.offset(i)) }, 0);
}
}
#[test]
fn take_slice_and_zero() {
let mut pw: [u8; 16] = [3, 4, 7, 42, 1,1,1,1,1,1,1,1,1,1,1,1];
ClearTextPassword::from_slice(&mut pw[..]).unwrap();
for x in &pw[..] { assert_eq!(*x, 0); }
}
#[test]
fn take_reader_and_hope() {
let pw: [u8; 16] = [3, 4, 7, 42, 1,1,1,1,1,1,1,1,1,1,1,1];
let mut reader = Cursor::new(&pw[..]);
ClearTextPassword::from_reader(&mut reader).unwrap().unwrap();
}
#[test]
fn dropping_really_clears() {
let _pw = ClearTextPassword::from_string(
"1234............".to_owned()
).unwrap(); }
#[bench]
fn dropping_really_clears_okay_im_serious(_: &mut Bencher) {
let _pw = ClearTextPassword::from_string(
"asdf............".to_owned()
).unwrap(); }
}