Site password derivation
This commit is contained in:
parent
4a5ada0bb0
commit
1e2d43a628
|
|
@ -1,4 +1,8 @@
|
||||||
use slint::SharedString;
|
use alloc::rc::Rc;
|
||||||
|
use slint::{SharedString, VecModel};
|
||||||
|
use spectre_api_sys::SpectreUserKey;
|
||||||
|
|
||||||
|
use crate::ui::storage::SpectreSite;
|
||||||
|
|
||||||
pub enum CallbackMessage {
|
pub enum CallbackMessage {
|
||||||
/// The escape key was pressed.
|
/// The escape key was pressed.
|
||||||
|
|
@ -9,12 +13,24 @@ pub enum CallbackMessage {
|
||||||
UserSites(CallbackMessageUserSites),
|
UserSites(CallbackMessageUserSites),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum LoginResult {
|
||||||
|
Failure,
|
||||||
|
Success {
|
||||||
|
user_key: SpectreUserKey,
|
||||||
|
sites: Rc<VecModel<SpectreSite>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub enum CallbackMessageLogin {
|
pub enum CallbackMessageLogin {
|
||||||
PwAccepted {
|
PwAccepted {
|
||||||
user_index: i32,
|
user_index: i32,
|
||||||
username: SharedString,
|
username: SharedString,
|
||||||
password: SharedString,
|
password: SharedString,
|
||||||
},
|
},
|
||||||
|
LoginResult {
|
||||||
|
username: SharedString,
|
||||||
|
result: LoginResult,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CallbackMessageUsers {
|
pub enum CallbackMessageUsers {
|
||||||
|
|
@ -24,8 +40,11 @@ pub enum CallbackMessageUsers {
|
||||||
pub enum CallbackMessageUserEdit {
|
pub enum CallbackMessageUserEdit {
|
||||||
ComputeIdenticon { password: SharedString },
|
ComputeIdenticon { password: SharedString },
|
||||||
ComputeKeyId { key: SharedString },
|
ComputeKeyId { key: SharedString },
|
||||||
ConfirmRequest { encrypted_key: SharedString },
|
ConfirmRequest,
|
||||||
ConfirmProcessed,
|
ConfirmProcessed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CallbackMessageUserSites {}
|
pub enum CallbackMessageUserSites {
|
||||||
|
SiteNameEdited { query: SharedString },
|
||||||
|
SiteNameAccepted { site_list_index: i32 },
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,46 @@
|
||||||
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
// #![cfg_attr(not(feature = "simulator"), no_main)]
|
||||||
|
|
||||||
use core::{cell::RefCell, ffi::CStr, ops::DerefMut};
|
use core::{cell::RefCell, ffi::CStr, ops::DerefMut, pin::Pin};
|
||||||
|
|
||||||
use alloc::{boxed::Box, ffi::CString, format, rc::Rc, vec};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
ffi::CString,
|
||||||
|
format,
|
||||||
|
rc::Rc,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
};
|
||||||
use embassy_time::Instant;
|
use embassy_time::Instant;
|
||||||
use hex::FromHexError;
|
use hex::FromHexError;
|
||||||
|
use i_slint_core::model::{ModelChangeListener, ModelChangeListenerContainer};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use password_hash::Key;
|
use password_hash::Key;
|
||||||
use slint::{Model, ModelExt, ModelRc, SharedString, VecModel};
|
use slint::{
|
||||||
use spectre_api_sys::{SpectreAlgorithm, SpectreKeyID, SpectreUserKey};
|
Model, ModelExt, ModelNotify, ModelRc, ModelTracker, SharedString, StandardListViewItem,
|
||||||
|
VecModel,
|
||||||
|
};
|
||||||
|
use spectre_api_sys::{
|
||||||
|
SpectreAlgorithm, SpectreCounter, SpectreKeyID, SpectreKeyPurpose, SpectreResultType,
|
||||||
|
SpectreSiteKey, SpectreUserKey,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "limit-fps")]
|
#[cfg(feature = "limit-fps")]
|
||||||
use crate::FRAME_DURATION_MIN;
|
use crate::FRAME_DURATION_MIN;
|
||||||
use crate::{
|
use crate::{
|
||||||
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
PSRAM_ALLOCATOR, SIGNAL_LCD_SUBMIT, SIGNAL_UI_RENDER,
|
||||||
db::{AcidDatabase, DbKey, DbPathSpectreUsers, PartitionAcid, ReadTransactionExt},
|
db::{
|
||||||
|
AcidDatabase, DbKey, DbPathSpectreUserSites, DbPathSpectreUsers, PartitionAcid,
|
||||||
|
ReadTransactionExt,
|
||||||
|
},
|
||||||
ffi::{alloc::__spre_free, crypto::ACTIVE_ENCRYPTED_USER_KEY},
|
ffi::{alloc::__spre_free, crypto::ACTIVE_ENCRYPTED_USER_KEY},
|
||||||
ui::{
|
ui::{
|
||||||
backend::SlintBackend,
|
backend::SlintBackend,
|
||||||
messages::{
|
messages::{
|
||||||
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit, CallbackMessageUsers,
|
CallbackMessage, CallbackMessageLogin, CallbackMessageUserEdit,
|
||||||
|
CallbackMessageUserSites, CallbackMessageUsers, LoginResult,
|
||||||
},
|
},
|
||||||
storage::{SpectreUserConfig, SpectreUsersConfig},
|
storage::{SpectreSite, SpectreSiteConfig, SpectreUserConfig, SpectreUsersConfig},
|
||||||
},
|
},
|
||||||
util::DurationExt,
|
util::DurationExt,
|
||||||
};
|
};
|
||||||
|
|
@ -66,95 +85,30 @@ fn spectre_derive_user_key(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spectre_derive_site_password(user_key: &SpectreUserKey, site_name: &CStr) -> String {
|
||||||
|
unsafe {
|
||||||
|
let site_password_c = &*spectre_api_sys::spectre_site_result(
|
||||||
|
user_key as *const SpectreUserKey,
|
||||||
|
site_name.as_ptr(),
|
||||||
|
SpectreResultType::SpectreResultDefaultResult,
|
||||||
|
core::ptr::null(),
|
||||||
|
SpectreCounter::Initial,
|
||||||
|
SpectreKeyPurpose::Authentication,
|
||||||
|
core::ptr::null(),
|
||||||
|
);
|
||||||
|
let site_password = CStr::from_ptr(site_password_c)
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
__spre_free(site_password_c as *const _ as *mut _);
|
||||||
|
site_password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
pub async fn run_renderer_task(backend: SlintBackend, flash_part_acid: PartitionAcid) {
|
||||||
let db = AcidDatabase::mount(flash_part_acid).await;
|
let db = AcidDatabase::mount(flash_part_acid).await;
|
||||||
|
|
||||||
// let value = SpectreUsersConfig {
|
|
||||||
// users: vec![SpectreUserConfig {
|
|
||||||
// username: "test".to_string(),
|
|
||||||
// encrypted_key: [0; _],
|
|
||||||
// key_id: [0; _],
|
|
||||||
// }],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// {
|
|
||||||
// let mut write = db.write_transaction().await;
|
|
||||||
// write
|
|
||||||
// .write(
|
|
||||||
// &DbKey::new(DbPathSpectreUserSite {
|
|
||||||
// user_sites: DbPathSpectreUserSites {
|
|
||||||
// username: "test".into(),
|
|
||||||
// },
|
|
||||||
// site: "example.org".into(),
|
|
||||||
// }),
|
|
||||||
// &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(),
|
|
||||||
// )
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// write
|
|
||||||
// .write(
|
|
||||||
// &DbKey::new(DbPathSpectreUserSite {
|
|
||||||
// user_sites: DbPathSpectreUserSites {
|
|
||||||
// username: "test".into(),
|
|
||||||
// },
|
|
||||||
// site: "sub.example.org".into(),
|
|
||||||
// }),
|
|
||||||
// &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(),
|
|
||||||
// )
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// write
|
|
||||||
// .write(
|
|
||||||
// &DbKey::new(DbPathSpectreUsers),
|
|
||||||
// &postcard::to_allocvec(&value).unwrap(),
|
|
||||||
// )
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// write.commit().await.unwrap();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let read_value = {
|
|
||||||
// let read = db.read_transaction().await;
|
|
||||||
// // TODO: https://github.com/embassy-rs/ekv/issues/20
|
|
||||||
// let mut buffer = vec![0; 256];
|
|
||||||
// let slice = read
|
|
||||||
// .read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// let read_value = postcard::from_bytes::<SpectreUsersConfig>(&slice).unwrap();
|
|
||||||
|
|
||||||
// let key_sites = DbKey::new(DbPathSpectreUserSites {
|
|
||||||
// username: "test".into(),
|
|
||||||
// });
|
|
||||||
// let mut key_buffer = [0_u8; ekv::config::MAX_KEY_SIZE];
|
|
||||||
// let mut cursor = read
|
|
||||||
// .read_range(key_sites.range_of_children())
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// while let Some((key_len, value_len)) =
|
|
||||||
// cursor.next(&mut key_buffer, &mut buffer).await.unwrap()
|
|
||||||
// {
|
|
||||||
// let key = DbKey::from_raw(key_buffer[..key_len].into());
|
|
||||||
// let mut key_segments = key.segments();
|
|
||||||
// let value = &buffer[..value_len];
|
|
||||||
// let site_config = postcard::from_bytes::<SpectreSiteConfig>(&value).unwrap();
|
|
||||||
// let site = SpectreSite {
|
|
||||||
// config: site_config,
|
|
||||||
// username: key_segments.nth(2).unwrap().into(),
|
|
||||||
// site_name: key_segments.nth(1).unwrap().into(),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// info!("site = {:#?}", site);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// read_value
|
|
||||||
// };
|
|
||||||
|
|
||||||
// info!("read_value = {:#?}", read_value);
|
|
||||||
// assert_eq!(value, read_value, "values do not match");
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// * Store a config as a versioned postcard-serialized struct
|
// * Store a config as a versioned postcard-serialized struct
|
||||||
// * Store accounts and sites as ranges in the DB
|
// * Store accounts and sites as ranges in the DB
|
||||||
|
|
@ -269,11 +223,31 @@ impl State {
|
||||||
|
|
||||||
main.on_user_edit_confirm({
|
main.on_user_edit_confirm({
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
move |encrypted_key| {
|
move |_encrypted_key| {
|
||||||
State::process_callback_message(
|
State::process_callback_message(
|
||||||
&state,
|
&state,
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest {
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest),
|
||||||
encrypted_key,
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_user_sites_site_name_edited({
|
||||||
|
let state = state.clone();
|
||||||
|
move |query| {
|
||||||
|
State::process_callback_message(
|
||||||
|
&state,
|
||||||
|
CallbackMessage::UserSites(CallbackMessageUserSites::SiteNameEdited { query }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
main.on_user_sites_site_name_accepted({
|
||||||
|
let state = state.clone();
|
||||||
|
move |site_list_index| {
|
||||||
|
State::process_callback_message(
|
||||||
|
&state,
|
||||||
|
CallbackMessage::UserSites(CallbackMessageUserSites::SiteNameAccepted {
|
||||||
|
site_list_index,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -315,6 +289,44 @@ impl State {
|
||||||
write.commit().await.unwrap();
|
write.commit().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_site_list(
|
||||||
|
sites: &Rc<VecModel<SpectreSite>>,
|
||||||
|
query: &SharedString,
|
||||||
|
) -> Rc<VecModel<SiteListEntry>> {
|
||||||
|
let site_list = Rc::new(VecModel::default());
|
||||||
|
|
||||||
|
for site in sites.iter() {
|
||||||
|
site_list.push(SiteListEntry::Existing(site.site_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.is_empty() {
|
||||||
|
site_list.push(SiteListEntry::New(query.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
site_list
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_site_list(&mut self) {
|
||||||
|
if let Some(StateUserSites {
|
||||||
|
sites,
|
||||||
|
query,
|
||||||
|
site_list,
|
||||||
|
..
|
||||||
|
}) = self.state_user_sites.as_mut()
|
||||||
|
{
|
||||||
|
*site_list = State::make_site_list(sites, query);
|
||||||
|
self.window
|
||||||
|
.set_user_sites_sites(ModelRc::from(Rc::new(site_list.clone().map(|site| {
|
||||||
|
let mut item = StandardListViewItem::default();
|
||||||
|
item.text = site.to_string();
|
||||||
|
item
|
||||||
|
}))));
|
||||||
|
} else {
|
||||||
|
self.window.invoke_user_sites_site_name_clear();
|
||||||
|
self.window.set_user_sites_sites(Default::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
let view = state_rc.borrow().view;
|
let view = state_rc.borrow().view;
|
||||||
match view {
|
match view {
|
||||||
|
|
@ -376,49 +388,149 @@ struct StateLogin {}
|
||||||
impl AppViewTrait for StateLogin {
|
impl AppViewTrait for StateLogin {
|
||||||
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
let mut state = state_rc.borrow_mut();
|
let mut state = state_rc.borrow_mut();
|
||||||
if let CallbackMessage::Login(CallbackMessageLogin::PwAccepted {
|
match message {
|
||||||
user_index,
|
CallbackMessage::Login(CallbackMessageLogin::PwAccepted {
|
||||||
username: _,
|
user_index,
|
||||||
password,
|
username: _,
|
||||||
}) = message
|
password,
|
||||||
{
|
}) => {
|
||||||
let Some(user) = state.users.users.row_data(user_index as usize) else {
|
let Some(user) = state.users.users.row_data(user_index as usize) else {
|
||||||
error!("Failed to find a user with index {user_index}.");
|
error!("Failed to find a user with index {user_index}.");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let username_c =
|
let username_c = CString::new(&*user.username)
|
||||||
CString::new(&*user.username).expect("Username cannot be converted to a C string.");
|
.expect("Username cannot be converted to a C string.");
|
||||||
let password_c =
|
let password_c =
|
||||||
CString::new(&*password).expect("Password cannot be converted to a C string.");
|
CString::new(&*password).expect("Password cannot be converted to a C string.");
|
||||||
let user_key =
|
let user_key =
|
||||||
spectre_derive_user_key(&username_c, &password_c, Some(user.encrypted_key));
|
spectre_derive_user_key(&username_c, &password_c, Some(user.encrypted_key));
|
||||||
|
|
||||||
// let site_key_start = Instant::now();
|
// {
|
||||||
// let site_key = &*spectre_api_sys::spectre_site_key(
|
// let mut write = db.write_transaction().await;
|
||||||
// user_key as *const SpectreUserKey,
|
// write
|
||||||
// c"example.org".as_ptr(),
|
// .write(
|
||||||
// SpectreCounter::Initial,
|
// &DbKey::new(DbPathSpectreUserSite {
|
||||||
// SpectreKeyPurpose::Authentication,
|
// user_sites: DbPathSpectreUserSites {
|
||||||
// c"".as_ptr(),
|
// username: "test".into(),
|
||||||
// );
|
// },
|
||||||
// let site_key_duration = Instant::now().duration_since(site_key_start);
|
// site: "example.org".into(),
|
||||||
// warn!(
|
// }),
|
||||||
// "Site key derived in {} seconds:\n{site_key:02x?}",
|
// &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(),
|
||||||
// site_key_duration.display_as_secs()
|
// )
|
||||||
// );
|
// .await
|
||||||
// TODO: Erase memory before freeing
|
// .unwrap();
|
||||||
// __spre_free(site_key as *const _ as *mut _);
|
// write
|
||||||
|
// .write(
|
||||||
|
// &DbKey::new(DbPathSpectreUserSite {
|
||||||
|
// user_sites: DbPathSpectreUserSites {
|
||||||
|
// username: "test".into(),
|
||||||
|
// },
|
||||||
|
// site: "sub.example.org".into(),
|
||||||
|
// }),
|
||||||
|
// &postcard::to_allocvec(&SpectreSiteConfig::default()).unwrap(),
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
// write
|
||||||
|
// .write(
|
||||||
|
// &DbKey::new(DbPathSpectreUsers),
|
||||||
|
// &postcard::to_allocvec(&value).unwrap(),
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
// write.commit().await.unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
if user.key_id == user_key.keyID.bytes {
|
if user.key_id != user_key.keyID.bytes {
|
||||||
info!("Correct password entered for user {:?}.", user.username);
|
State::process_callback_message(
|
||||||
state.state_user_sites = Some(StateUserSites {
|
&state_rc,
|
||||||
username: user.username,
|
CallbackMessage::Login(CallbackMessageLogin::LoginResult {
|
||||||
user_key,
|
username: user.username,
|
||||||
});
|
result: LoginResult::Failure,
|
||||||
state.set_view(AppState::UserSites, true, false);
|
}),
|
||||||
} else {
|
);
|
||||||
warn!("Incorrect password entered for user {:?}.", user.username);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slint::spawn_local({
|
||||||
|
let state_rc = state_rc.clone();
|
||||||
|
let username = user.username.clone();
|
||||||
|
let db = state.db.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let read = db.read_transaction().await;
|
||||||
|
// TODO: https://github.com/embassy-rs/ekv/issues/20
|
||||||
|
let mut buffer = vec![0; 256];
|
||||||
|
// let slice = read
|
||||||
|
// .read_to_vec(&DbKey::new(DbPathSpectreUsers), &mut buffer)
|
||||||
|
// .await
|
||||||
|
// .unwrap();
|
||||||
|
// let read_value = postcard::from_bytes::<SpectreUsersConfig>(&slice).unwrap();
|
||||||
|
|
||||||
|
let key_sites = DbKey::new(DbPathSpectreUserSites {
|
||||||
|
username: Cow::Borrowed(&username),
|
||||||
|
});
|
||||||
|
let mut key_buffer = [0_u8; ekv::config::MAX_KEY_SIZE];
|
||||||
|
let mut cursor = read
|
||||||
|
.read_range(key_sites.range_of_children())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let sites = VecModel::default();
|
||||||
|
|
||||||
|
while let Some((key_len, value_len)) =
|
||||||
|
cursor.next(&mut key_buffer, &mut buffer).await.unwrap()
|
||||||
|
{
|
||||||
|
let key = DbKey::from_raw(key_buffer[..key_len].into());
|
||||||
|
let mut key_segments = key.segments();
|
||||||
|
let value = &buffer[..value_len];
|
||||||
|
let site_config =
|
||||||
|
postcard::from_bytes::<SpectreSiteConfig>(&value).unwrap();
|
||||||
|
let site = SpectreSite {
|
||||||
|
config: site_config,
|
||||||
|
username: key_segments.nth(2).unwrap().as_ref().into(),
|
||||||
|
site_name: key_segments.nth(1).unwrap().as_ref().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("site = {:#?}", site);
|
||||||
|
sites.push(site);
|
||||||
|
}
|
||||||
|
|
||||||
|
State::process_callback_message(
|
||||||
|
&state_rc,
|
||||||
|
CallbackMessage::Login(CallbackMessageLogin::LoginResult {
|
||||||
|
username: user.username,
|
||||||
|
result: LoginResult::Success {
|
||||||
|
user_key,
|
||||||
|
sites: Rc::new(sites),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
CallbackMessage::Login(CallbackMessageLogin::LoginResult {
|
||||||
|
username,
|
||||||
|
result: LoginResult::Success { user_key, sites },
|
||||||
|
}) => {
|
||||||
|
info!("Correct password entered for user {:?}.", username);
|
||||||
|
state.state_user_sites = Some(StateUserSites {
|
||||||
|
username,
|
||||||
|
user_key,
|
||||||
|
query: SharedString::new(),
|
||||||
|
sites: sites.clone(),
|
||||||
|
site_list: Default::default(),
|
||||||
|
});
|
||||||
|
state.update_site_list();
|
||||||
|
state.set_view(AppState::UserSites, true, false);
|
||||||
|
}
|
||||||
|
CallbackMessage::Login(CallbackMessageLogin::LoginResult {
|
||||||
|
username,
|
||||||
|
result: LoginResult::Failure,
|
||||||
|
}) => {
|
||||||
|
warn!("Incorrect password entered for user {:?}.", username);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -538,9 +650,7 @@ impl AppViewTrait for StateUserEdit {
|
||||||
);
|
);
|
||||||
state.state_user_edit.encrypted_key = Some((key, user_key));
|
state.state_user_edit.encrypted_key = Some((key, user_key));
|
||||||
}
|
}
|
||||||
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest {
|
CallbackMessage::UserEdit(CallbackMessageUserEdit::ConfirmRequest) => {
|
||||||
encrypted_key: _,
|
|
||||||
}) => {
|
|
||||||
let Some((
|
let Some((
|
||||||
encrypted_key,
|
encrypted_key,
|
||||||
SpectreUserKey {
|
SpectreUserKey {
|
||||||
|
|
@ -580,6 +690,7 @@ impl AppViewTrait for StateUserEdit {
|
||||||
let state_rc = state_rc.clone();
|
let state_rc = state_rc.clone();
|
||||||
let db = state.db.clone();
|
let db = state.db.clone();
|
||||||
let users = state.users.clone();
|
let users = state.users.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
State::save_users(&db, &users).await;
|
State::save_users(&db, &users).await;
|
||||||
State::process_callback_message(
|
State::process_callback_message(
|
||||||
|
|
@ -602,6 +713,153 @@ impl AppViewTrait for StateUserEdit {
|
||||||
struct StateUserSites {
|
struct StateUserSites {
|
||||||
username: SharedString,
|
username: SharedString,
|
||||||
user_key: SpectreUserKey,
|
user_key: SpectreUserKey,
|
||||||
|
query: SharedString,
|
||||||
|
sites: Rc<VecModel<SpectreSite>>,
|
||||||
|
site_list: Rc<VecModel<SiteListEntry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppViewTrait for StateUserSites {}
|
impl AppViewTrait for StateUserSites {
|
||||||
|
fn process_callback_message(state_rc: &Rc<RefCell<State>>, message: CallbackMessage) {
|
||||||
|
let state = state_rc.clone();
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
match message {
|
||||||
|
CallbackMessage::Escape => {
|
||||||
|
state.set_view(AppState::Login, true, false);
|
||||||
|
}
|
||||||
|
CallbackMessage::UserSites(CallbackMessageUserSites::SiteNameEdited { query }) => {
|
||||||
|
if let Some(user_sites) = state.state_user_sites.as_mut() {
|
||||||
|
user_sites.query = query;
|
||||||
|
state.update_site_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CallbackMessage::UserSites(CallbackMessageUserSites::SiteNameAccepted {
|
||||||
|
site_list_index,
|
||||||
|
}) => {
|
||||||
|
let Some(user_sites) = state.state_user_sites.as_mut() else {
|
||||||
|
error!("User sites uninitialized.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(site_list_entry) = user_sites.site_list.row_data(site_list_index as usize)
|
||||||
|
else {
|
||||||
|
error!("Invalid site list entry index: {site_list_index}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
warn!("Site name accepted: {site_list_entry:?}");
|
||||||
|
let site_name = match site_list_entry {
|
||||||
|
SiteListEntry::New(site_name) => site_name,
|
||||||
|
SiteListEntry::Existing(site_name) => site_name,
|
||||||
|
};
|
||||||
|
let site_name_c = CString::new(&*site_name).unwrap();
|
||||||
|
let site_password =
|
||||||
|
spectre_derive_site_password(&user_sites.user_key, &site_name_c);
|
||||||
|
|
||||||
|
warn!("Site password: {site_password:?}");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SitesModel<M>(Pin<Box<ModelChangeListenerContainer<SitesModelInner<M>>>>)
|
||||||
|
where
|
||||||
|
M: Model + 'static;
|
||||||
|
|
||||||
|
struct SitesModelInner<M>
|
||||||
|
where
|
||||||
|
M: Model + 'static,
|
||||||
|
{
|
||||||
|
wrapped_model: M,
|
||||||
|
notify: ModelNotify,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> ModelChangeListener for SitesModelInner<M>
|
||||||
|
where
|
||||||
|
M: Model + 'static,
|
||||||
|
{
|
||||||
|
fn row_changed(self: Pin<&Self>, row: usize) {
|
||||||
|
self.notify
|
||||||
|
.row_changed(self.wrapped_model.row_count() - 1 - row);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_added(self: Pin<&Self>, index: usize, count: usize) {
|
||||||
|
let row_count = self.wrapped_model.row_count();
|
||||||
|
let old_row_count = row_count - count;
|
||||||
|
let index = old_row_count - index;
|
||||||
|
self.notify.row_added(index, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_removed(self: Pin<&Self>, index: usize, count: usize) {
|
||||||
|
let row_count = self.wrapped_model.row_count();
|
||||||
|
self.notify.row_removed(row_count - index, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(self: Pin<&Self>) {
|
||||||
|
self.notify.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> SitesModel<M>
|
||||||
|
where
|
||||||
|
M: Model + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(wrapped_model: M) -> Self {
|
||||||
|
let inner = SitesModelInner {
|
||||||
|
wrapped_model,
|
||||||
|
notify: Default::default(),
|
||||||
|
};
|
||||||
|
let container = Box::pin(ModelChangeListenerContainer::new(inner));
|
||||||
|
container
|
||||||
|
.wrapped_model
|
||||||
|
.model_tracker()
|
||||||
|
.attach_peer(container.as_ref().model_peer());
|
||||||
|
Self(container)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner model
|
||||||
|
pub fn source_model(&self) -> &M {
|
||||||
|
&self.0.as_ref().get().get_ref().wrapped_model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M> Model for SitesModel<M>
|
||||||
|
where
|
||||||
|
M: Model + 'static,
|
||||||
|
{
|
||||||
|
type Data = M::Data;
|
||||||
|
|
||||||
|
fn row_count(&self) -> usize {
|
||||||
|
self.0.wrapped_model.row_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||||
|
let count = self.0.wrapped_model.row_count();
|
||||||
|
self.0.wrapped_model.row_data(count.checked_sub(row + 1)?)
|
||||||
|
}
|
||||||
|
fn set_row_data(&self, row: usize, data: Self::Data) {
|
||||||
|
let count = self.0.as_ref().wrapped_model.row_count();
|
||||||
|
self.0.wrapped_model.set_row_data(count - row - 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn model_tracker(&self) -> &dyn ModelTracker {
|
||||||
|
&self.0.notify
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn core::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum SiteListEntry {
|
||||||
|
New(SharedString),
|
||||||
|
Existing(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SiteListEntry {
|
||||||
|
pub fn to_string(&self) -> SharedString {
|
||||||
|
match self {
|
||||||
|
SiteListEntry::New(site) => slint::format!("{site} <Add new site>"),
|
||||||
|
SiteListEntry::Existing(site) => site.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,12 @@ export component AppWindow inherits Window {
|
||||||
callback user_edit_compute_key_id <=> user_edit_view.compute_key_id;
|
callback user_edit_compute_key_id <=> user_edit_view.compute_key_id;
|
||||||
callback user_edit_confirm <=> user_edit_view.confirm;
|
callback user_edit_confirm <=> user_edit_view.confirm;
|
||||||
// User Sites View
|
// User Sites View
|
||||||
in property <[StandardListViewItem]> sites <=> user_sites_view.model;
|
in property <[StandardListViewItem]> user_sites_sites <=> user_sites_view.model;
|
||||||
callback site_pw_edited <=> user_sites_view.pw_edited;
|
callback user_sites_site_name_edited <=> user_sites_view.site_name_edited;
|
||||||
callback site_pw_accepted <=> user_sites_view.pw_accepted;
|
callback user_sites_site_name_accepted <=> user_sites_view.site_name_accepted;
|
||||||
|
public function user_sites_site_name_clear() {
|
||||||
|
user_sites_view.site_name_clear();
|
||||||
|
}
|
||||||
focus-scope := FocusScope {
|
focus-scope := FocusScope {
|
||||||
key-pressed(event) => {
|
key-pressed(event) => {
|
||||||
if event.text == "\u{1b}" {
|
if event.text == "\u{1b}" {
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,32 @@ export component UserSitesView inherits HorizontalLayout {
|
||||||
spacing: Style.spacing;
|
spacing: Style.spacing;
|
||||||
in property <[StandardListViewItem]> model <=> list_view_sites.model;
|
in property <[StandardListViewItem]> model <=> list_view_sites.model;
|
||||||
in-out property <int> current-item <=> list_view_sites.current-item;
|
in-out property <int> current-item <=> list_view_sites.current-item;
|
||||||
callback pw_edited <=> line_edit_site_pw.edited;
|
callback site_name_edited <=> line_edit_site_name.edited;
|
||||||
callback pw_accepted <=> line_edit_site_pw.accepted;
|
callback site_name_accepted(site_list_index: int);
|
||||||
VerticalLayout {
|
public function site_name_clear() {
|
||||||
spacing: Style.spacing;
|
line_edit_site_name.text = "";
|
||||||
Text {
|
}
|
||||||
text: "Send password for:";
|
FocusScope {
|
||||||
|
key-pressed(event) => {
|
||||||
|
if event.text == "\n" {
|
||||||
|
site_name_accepted(list_view_sites.current-item);
|
||||||
|
EventResult.accept
|
||||||
|
} else {
|
||||||
|
EventResult.reject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: Style.spacing;
|
||||||
|
Text {
|
||||||
|
text: "Send password for:";
|
||||||
|
}
|
||||||
|
|
||||||
line_edit_site_pw := LineEdit {
|
line_edit_site_name := LineEdit {
|
||||||
input-type: InputType.text;
|
input-type: InputType.text;
|
||||||
placeholder-text: "example.org";
|
placeholder-text: "example.org";
|
||||||
}
|
}
|
||||||
|
|
||||||
list_view_sites := StandardListView {
|
list_view_sites := StandardListView { }
|
||||||
model: [
|
|
||||||
{ text: "Test" },
|
|
||||||
{ text: "Test" },
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
firmware/password-hash/.cargo/config.toml
Normal file
2
firmware/password-hash/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[env] # These must be kept in sync with /.zed/settings.json
|
||||||
|
LIBSODIUM_INSTALL_DIR = "../libsodium/install-host"
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
# Compiling
|
# Compiling
|
||||||
|
|
||||||
Compile on Linux or in WSL.
|
Compile on Linux or in WSL.
|
||||||
|
|
||||||
|
Compile libsodium for the host:
|
||||||
|
```bash
|
||||||
|
cd ../libsodium
|
||||||
|
./configure --disable-shared --enable-static --prefix="$PWD/install-host"
|
||||||
|
make -j
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then compile and run password-hash:
|
||||||
|
```bash
|
||||||
|
cd ../password-hash
|
||||||
|
cargo run --release [--target x86_64-unknown-linux-gnu]
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ fn main() {
|
||||||
libsodium_install_dir.join("lib/libsodium.a").display()
|
libsodium_install_dir.join("lib/libsodium.a").display()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("cargo:warn=Environment variable `LIBSODIUM_INSTALL_DIR` missing!");
|
panic!("Environment variable `LIBSODIUM_INSTALL_DIR` missing!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue