Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions apps/plumeimpactor/src/certificate_reset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::sync::{Mutex, OnceLock, mpsc};

pub(crate) const WARNING: &str = "Impactor needs to reset your certificate. This breaks existing SideStore and AltStore installs.";

#[derive(Debug, Clone)]
pub struct ConfirmationRequest {
pub message: String,
responder: mpsc::Sender<bool>,
}

impl ConfirmationRequest {
pub fn respond(&self, accepted: bool) {
let _ = self.responder.send(accepted);
}
}

static REQUEST_TX: OnceLock<mpsc::Sender<ConfirmationRequest>> = OnceLock::new();
static REQUEST_RX: OnceLock<Mutex<mpsc::Receiver<ConfirmationRequest>>> = OnceLock::new();

fn request_channel() -> (
&'static mpsc::Sender<ConfirmationRequest>,
&'static Mutex<mpsc::Receiver<ConfirmationRequest>>,
) {
REQUEST_TX.get_or_init(|| {
let (tx, rx) = mpsc::channel();
let _ = REQUEST_RX.set(Mutex::new(rx));
tx
});

(
REQUEST_TX
.get()
.expect("request sender should be initialized"),
REQUEST_RX
.get()
.expect("request receiver should be initialized"),
)
}

pub fn request_confirmation(message: &str) -> bool {
let (response_tx, response_rx) = mpsc::channel();
let request = ConfirmationRequest {
message: message.to_string(),
responder: response_tx,
};

let (request_tx, _) = request_channel();
if request_tx.send(request).is_err() {
return false;
}

response_rx.recv().unwrap_or(false)
}

pub fn confirm() -> bool {
log::warn!("{WARNING}");
request_confirmation(WARNING)
}

pub fn wait_for_request() -> Option<ConfirmationRequest> {
let (_, request_rx) = request_channel();
request_rx.lock().ok()?.recv().ok()
}
1 change: 1 addition & 0 deletions apps/plumeimpactor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::refresh::spawn_refresh_daemon;
use single_instance::SingleInstance;

mod appearance;
mod certificate_reset;
mod defaults;
mod macos_app;
mod refresh;
Expand Down
4 changes: 4 additions & 0 deletions apps/plumeimpactor/src/refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,14 @@ impl RefreshDaemon {
};

let identity_is_new = {
let mut on_certificate_reset = crate::certificate_reset::confirm;
let identity = CertificateIdentity::new_with_session(
&session,
get_data_path(),
None,
team_id,
false,
Some(&mut on_certificate_reset),
)
.await
.map_err(|e| format!("Failed to create identity: {}", e))?;
Expand Down Expand Up @@ -274,12 +276,14 @@ impl RefreshDaemon {
};

let team_id_string = team_id.to_string();
let mut on_certificate_reset = crate::certificate_reset::confirm;
let signing_identity = CertificateIdentity::new_with_session(
session,
get_data_path(),
None,
&team_id_string,
false,
Some(&mut on_certificate_reset),
)
.await
.map_err(|e| format!("Failed to create signing identity: {}", e))?;
Expand Down
105 changes: 99 additions & 6 deletions apps/plumeimpactor/src/screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ pub(crate) mod settings;
mod utilties;
mod windows;

use std::collections::VecDeque;

use iced::Length::Fill;
use iced::widget::{button, container, pick_list, row, text};
use iced::widget::{button, column, container, pick_list, row, stack, text};
use iced::window;
use iced::{Element, Subscription, Task};

Expand Down Expand Up @@ -72,6 +74,9 @@ pub enum Message {
SettingsScreen(settings::Message),
InstallerScreen(package::Message),
ProgressScreen(progress::Message),
CertificateResetRequested(crate::certificate_reset::ConfirmationRequest),
ConfirmCertificateReset,
CancelCertificateReset,

// Installation
StartInstallation,
Expand All @@ -87,6 +92,7 @@ pub struct Impactor {
account_store: Option<AccountStore>,
login_windows: std::collections::HashMap<window::Id, login_window::LoginWindow>,
pending_installation: bool,
certificate_reset_queue: VecDeque<crate::certificate_reset::ConfirmationRequest>,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -132,6 +138,7 @@ impl Impactor {
account_store: Some(store),
login_windows: std::collections::HashMap::new(),
pending_installation: false,
certificate_reset_queue: VecDeque::new(),
},
open_task,
)
Expand All @@ -142,6 +149,18 @@ impl Impactor {
AccountStore::load_sync(&Some(path)).unwrap_or_default()
}

fn respond_to_next_certificate_reset(&mut self, accepted: bool) {
if let Some(request) = self.certificate_reset_queue.pop_front() {
request.respond(accepted);
}
}

fn cancel_pending_certificate_resets(&mut self) {
while let Some(request) = self.certificate_reset_queue.pop_front() {
request.respond(false);
}
}

pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::ComboBoxSelected(value) => {
Expand Down Expand Up @@ -312,6 +331,22 @@ impl Impactor {
Task::none()
}
}
Message::CertificateResetRequested(request) => {
self.certificate_reset_queue.push_back(request);
if self.main_window.is_none() {
Task::done(Message::ShowWindow)
} else {
Task::none()
}
}
Message::ConfirmCertificateReset => {
self.respond_to_next_certificate_reset(true);
Task::none()
}
Message::CancelCertificateReset => {
self.respond_to_next_certificate_reset(false);
Task::none()
}
Message::ShowWindow => {
crate::macos_app::set_main_window_visible(true);
if let Some(id) = self.main_window {
Expand All @@ -324,6 +359,7 @@ impl Impactor {
}
Message::HideWindow => {
if let Some(id) = self.main_window {
self.cancel_pending_certificate_resets();
self.main_window = None;
crate::macos_app::set_main_window_visible(false);
window::close(id)
Expand Down Expand Up @@ -722,6 +758,7 @@ impl Impactor {
};

let tray_menu_refresh_subscription = subscriptions::tray_menu_refresh_subscription();
let certificate_reset_subscription = subscriptions::certificate_reset_subscription();
let relaunch_subscription = subscriptions::relaunch_subscription();

let close_subscription = iced::event::listen_with(|event, _status, _id| {
Expand All @@ -737,14 +774,13 @@ impl Impactor {
hover_subscription,
progress_subscription,
tray_menu_refresh_subscription,
certificate_reset_subscription,
relaunch_subscription,
close_subscription,
])
}

pub fn view(&self, window_id: window::Id) -> Element<'_, Message> {
use iced::widget::{column, container};

if let Some(login_window) = self.login_windows.get(&window_id) {
return login_window
.view()
Expand All @@ -754,10 +790,16 @@ impl Impactor {
let has_device = self.selected_device.is_some();
let screen_content = self.view_current_screen(has_device);
let top_bar = self.view_top_bar();
let base: Element<'_, Message> =
container(column(vec![top_bar, screen_content]).spacing(appearance::THEME_PADDING))
.padding(appearance::THEME_PADDING)
.into();

container(column(vec![top_bar, screen_content]).spacing(appearance::THEME_PADDING))
.padding(appearance::THEME_PADDING)
.into()
if self.certificate_reset_queue.front().is_some() {
stack![base, self.view_certificate_reset_prompt()].into()
} else {
base
}
}

fn view_current_screen(&self, has_device: bool) -> Element<'_, Message> {
Expand Down Expand Up @@ -815,6 +857,57 @@ impl Impactor {
.into()
}

fn view_certificate_reset_prompt(&self) -> Element<'_, Message> {
let Some(request) = self.certificate_reset_queue.front() else {
return container(text("")).into();
};

let actions = row![
button(text("Cancel"))
.on_press(Message::CancelCertificateReset)
.style(appearance::s_button),
button(text("Continue"))
.on_press(Message::ConfirmCertificateReset)
.style(appearance::p_button),
]
.spacing(appearance::THEME_PADDING);

let dialog = container(
column![
text("Certificate reset required").size(appearance::THEME_FONT_SIZE + 2.0),
text(&request.message),
actions,
]
.spacing(appearance::THEME_PADDING),
)
.padding(appearance::THEME_PADDING * 2.0)
.max_width(420.0)
.style(|theme: &iced::Theme| container::Style {
background: Some(iced::Background::Color(theme.palette().background)),
border: iced::Border {
width: 1.0,
color: theme.palette().warning,
radius: appearance::THEME_CORNER_RADIUS.into(),
},
..Default::default()
});

container(dialog)
.width(Fill)
.height(Fill)
.center(Fill)
.style(|_theme: &iced::Theme| container::Style {
background: Some(iced::Background::Color(iced::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.45,
})),
..Default::default()
})
.into()
}

fn navigate_to_screen(&mut self, screen_type: ImpactorScreenType) {
match screen_type {
ImpactorScreenType::Main => {
Expand Down
29 changes: 29 additions & 0 deletions apps/plumeimpactor/src/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,28 @@ pub(crate) fn tray_menu_refresh_subscription() -> Subscription<Message> {
})
}

pub(crate) fn certificate_reset_subscription() -> Subscription<Message> {
Subscription::run(|| {
iced::stream::channel(
10,
|mut output: iced::futures::channel::mpsc::Sender<Message>| async move {
use iced::futures::{SinkExt, StreamExt};
let (tx, mut rx) = iced::futures::channel::mpsc::unbounded::<Message>();

std::thread::spawn(move || {
while let Some(request) = crate::certificate_reset::wait_for_request() {
let _ = tx.unbounded_send(Message::CertificateResetRequested(request));
}
});

while let Some(message) = rx.next().await {
let _ = output.send(message).await;
}
},
)
})
}

#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
pub(crate) fn relaunch_subscription() -> Subscription<Message> {
Subscription::run(|| {
Expand Down Expand Up @@ -309,12 +331,17 @@ pub(crate) async fn run_installation(
team_id
};

let mut on_certificate_reset = || {
send(crate::certificate_reset::WARNING.to_string(), 20);
crate::certificate_reset::confirm()
};
let identity = CertificateIdentity::new_with_session(
&session,
crate::defaults::get_data_path(),
None,
team_id,
false,
Some(&mut on_certificate_reset),
)
.await
.map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -569,12 +596,14 @@ pub(crate) async fn export_certificate(account: plume_store::GsaAccount) -> Resu
team_id
};

let mut on_certificate_reset = crate::certificate_reset::confirm;
let identity = CertificateIdentity::new_with_session(
&session,
crate::defaults::get_data_path(),
None,
team_id,
true,
Some(&mut on_certificate_reset),
)
.await
.map_err(|e| e.to_string())?;
Expand Down
12 changes: 9 additions & 3 deletions apps/plumesign/src/commands/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,15 @@ pub async fn execute(args: SignArgs) -> Result<()> {
} else if args.apple_id {
let session = get_authenticated_account().await?;
let team_id = teams(&session).await?;
let cert_identity =
CertificateIdentity::new_with_session(&session, get_data_path(), None, &team_id, false)
.await?;
let cert_identity = CertificateIdentity::new_with_session(
&session,
get_data_path(),
None,
&team_id,
false,
None,
)
.await?;

options.mode = SignerMode::Pem;
(
Expand Down
Loading