-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add prompt implementation for service methods lock & unlock #173
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also needs a rebase
|
||
let mut common_secret_bytes = common_secret.to_bytes_be(); | ||
let mut common_secret_padded = vec![0; 192 - common_secret_bytes.len()]; | ||
// inefficient, but ok for now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is inefficient about it
server/src/service.rs
Outdated
let (unlocked, _not_unlocked) = self.set_locked(false, &objects).await?; | ||
let (unlocked, not_unlocked) = self.set_locked(false, &objects).await?; | ||
if !not_unlocked.is_empty() { | ||
let prompt = Prompt::new(self.clone()).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prompt also needs a way to pass it a future that would be executed when the prompt is done
Thanks for the prompt review @bilelmoussaoui |
client/src/crypto/native.rs
Outdated
@@ -1,6 +1,6 @@ | |||
use std::{ | |||
ops::{Mul, Rem, Shr}, | |||
sync::LazyLock, | |||
sync::{LazyLock, OnceLock}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lacks an openssl implementation as well as that is what most distros will likely use the daemon with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, thanks!
Co-authored-by: Dhanuka Warusadura <[email protected]> Signed-off-by: Dhanuka Warusadura <[email protected]>
db19706
to
49e0912
Compare
Sorry for the delay @bilelmoussaoui I have removed the prompt implementation for now. Just wanted get the crypto part and secret_exchange impl cleared before moving into the Prompt impl. I have removed a lot of unwraps, but I'm still not sure this is any good. Very much like to hear what you think of the current impl :) |
49e0912
to
e79dfe6
Compare
const CIPHER_TEXT_LEN: usize = 16; | ||
|
||
#[derive(Debug, Zeroize)] | ||
pub struct SecretExchange { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still not sure how the SecretExchange is supposed to be used. The current API, even if used internally only, is not really nice. why there is a need for a SecretExchange struct at all? who would call begin/create_shared_secret? and what about retrieve_secret? I would need to see how it is used in a real-case scenario to provide any meaningful feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thanks!
cf14fbe
to
1b96455
Compare
|
||
impl PrompterCallback { | ||
pub async fn new(service: Service) -> Self { | ||
let index = service.prompt_index().await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should create a PrompterProxy here and keepe it around for the functions below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. But I can't access self
inside tokio::spawn
. All these following functions are associated functions.
server/src/gnome/prompter.rs
Outdated
let objects = prompt.objects().clone(); | ||
let result = Value::new(&objects).try_to_owned().unwrap(); | ||
|
||
tokio::spawn(async move { | ||
let _ = service.set_locked(true, &objects, true).await; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing the objects, this should pass a future directly that you can spawn here. Keeping the logic that is supposed to be in Service::set_locked, stay there.
pub async fn prompt(&self) -> Option<Prompt> { | ||
let prompts = self.prompts.lock().await; | ||
|
||
prompts.last().cloned() | ||
} | ||
|
||
pub async fn remove_prompt(&self) { | ||
// prompts should always contain one item during a prompt related operation and | ||
// it should be cleaned up afterwards. | ||
self.prompts.lock().await.pop(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there would always be one single prompt (which i don't believe is true, except for maybe GNOME specifically) as the API clearly allows modal keyring dialogs so every app could have an ongoing prompt operation.
@@ -318,6 +336,7 @@ impl Service { | |||
&self, | |||
locked: bool, | |||
objects: &[OwnedObjectPath], | |||
prompt: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this needed?
server/src/gnome/prompter.rs
Outdated
tokio::spawn(PrompterCallback::perform_prompt( | ||
connection, path, "confirm", properties, exchange, | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this has to be in PrompterCallback,
SecretExchange allows exchange of secrets between two processes on the same system without exposing those secrets. See https://gnome.pages.gitlab.gnome.org/gcr/gcr-4/class.SecretExchange.html Signed-off-by: Dhanuka Warusadura <[email protected]>
1b96455
to
90ca8c6
Compare
server/src/gnome/prompter.rs
Outdated
match prompt.role() { | ||
PromptRole::Lock => { | ||
if properties.is_empty() { | ||
// First PromptReady call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't most of the prompting code be exactly the same no matter the PromptRole? the only thing that i see being different is the Properties and the future to execute once the prompt is done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is correct. I have just pushed the unlock commit (wip) and the code looks mostly the same with a couple of changes.
90ca8c6
to
5c626cb
Compare
#[derive(Debug, DeserializeDict, SerializeDict, Type)] | ||
#[zvariant(signature = "a{sv}")] | ||
// System prompt properties | ||
pub struct Properties { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this approach, during the second PromptReady call return I'm getting the following error, Unexpected non-0 padding byte 1
. I can't figure out why. I think this is related to this. Then again all the fields here are properly renamed using #[zvariant(rename = "")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check what gets send over the bus with busctl and see what is going on? Probably a mismatched type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried everything I can think of. I think this is happening due to a bug in zbus. Probably related to zbus issues #1193 and #320.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you write a minimal reproducer of the issue? I am using the exact same thing in ashpd and it works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you confirm this is caused by the properties and not the the reply param of PromptReady?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed the notification.
Yes I'm 100% certain this is caused by Properties struct. Everything works with the HashMap<&str, zvariant::OwnedValue>
approach.
git diff:
diff --git a/server/src/gnome/prompter.rs b/server/src/gnome/prompter.rs
index a139fba..b4104e5 100644
--- a/server/src/gnome/prompter.rs
+++ b/server/src/gnome/prompter.rs
@@ -1,11 +1,13 @@
// org.gnome.keyring.Prompter
// https://gitlab.gnome.org/GNOME/gcr/-/blob/main/gcr/org.gnome.keyring.Prompter.xml
+use std::collections::HashMap;
+
use clap::error::Result;
use oo7::dbus::ServiceError;
use serde::{Deserialize, Serialize};
use zbus::{
- interface, proxy,
+ interface, proxy, zvariant,
zvariant::{DeserializeDict, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value},
};
@@ -138,7 +140,7 @@ pub trait Prompter {
&self,
callback: OwnedObjectPath,
type_: PromptType,
- properties: Properties,
+ properties: HashMap<&str, OwnedValue>,
exchange: &str,
) -> Result<(), ServiceError>;
@@ -157,7 +159,7 @@ impl PrompterCallback {
pub async fn prompt_ready(
&self,
reply: Reply,
- _properties: Properties,
+ _properties: HashMap<&str, OwnedValue>,
exchange: &str,
#[zbus(connection)] connection: &zbus::Connection,
) -> Result<(), ServiceError> {
@@ -181,6 +183,21 @@ impl PrompterCallback {
})?;
let exchange = secret_exchange.begin();
+ let mut p: HashMap<&str, zvariant::OwnedValue> = HashMap::new();
+ p.insert("continue-label", Value::new("Lock").try_to_owned().unwrap());
+ p.insert(
+ "description",
+ Value::new("Confirm locking \"login\", Keyring.")
+ .try_to_owned()
+ .unwrap(),
+ );
+ p.insert(
+ "message",
+ Value::new("Lock Keyring").try_to_owned().unwrap(),
+ );
+ p.insert("caller-window", Value::new("").try_to_owned().unwrap());
+ p.insert("cancel-label", Value::new("Cancel").try_to_owned().unwrap());
+
let properties = Properties::for_lock();
let path = self.path.clone();
@@ -188,7 +205,7 @@ impl PrompterCallback {
connection.clone(),
path,
PromptType::Confirm,
- properties,
+ p,
exchange,
));
}
@@ -261,6 +278,8 @@ impl PrompterCallback {
})?;
self.service.set_secret_exchange_key(aes_key).await;
+ let p: HashMap<&str, zvariant::OwnedValue> = HashMap::new();
+
let properties = Properties::for_unlock();
let path = self.path.clone();
@@ -268,7 +287,7 @@ impl PrompterCallback {
connection.clone(),
path,
PromptType::Confirm,
- properties,
+ p,
daemon_exchange,
));
}
@@ -373,7 +392,7 @@ impl PrompterCallback {
connection: zbus::Connection,
path: OwnedObjectPath,
prompt_type: PromptType,
- properties: Properties,
+ properties: HashMap<&str, OwnedValue>,
exchange: String,
) -> Result<(), ServiceError> {
let prompter = PrompterProxy::new(&connection).await?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then keep the Properties struct there and add a todo comment about it so we can investigate it further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, thanks! I think I'll have to keep this diff as well. I can't test anything without it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just locally.
server/src/gnome/prompter.rs
Outdated
description: Option<String>, | ||
message: Option<String>, | ||
#[zvariant(rename = "caller-window")] | ||
caller_window: Option<String>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a WindowIdentifierType from ashpd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't know anything about ashpd. I'll look into it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to do anything special here, just use that type.
pub enum Reply { | ||
No, | ||
Yes, | ||
Empty, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does empty corresponds to? Please link in the code to where those answers are defined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check if you can use the zvariant::Optional type here and drop the Empty variant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey. I tried reply: Optional<Reply>
approach and I think the current approach with Empty variant is bit better or shorter. Because all we have to do is impl Serialize and Deserialize. When using Optional, I had to impl NoneValue
and bunch of String, &str traits. I just stopped at: trait FromStr
is not implemented for Reply
. Should I keep Empty
variant or continue with Optional
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use zvariant::Optional please. and you shouldn't have to implement that many traits but if you are struggling with it, you can leave it as is and i will take care of it before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks :)
Empty, | ||
} | ||
|
||
const NO: &str = "no"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for those consts
#[derive(Deserialize, Serialize, Debug, Type)] | ||
#[serde(rename_all = "lowercase")] | ||
#[zvariant(signature = "s")] | ||
// Possible values for PerformPrompt type parameter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this implement the serialisation traits or the derived ones are correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the derived ones are correct. I had to implement the serialisation and de-serialisation for Reply
because of the Empty variant.
pub async fn prompt_ready( | ||
&self, | ||
reply: Reply, | ||
_properties: Properties, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this unused? Already mentioned it before but you marked the thread as resolved except not really...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take another look.
#[zbus(connection)] connection: &zbus::Connection, | ||
#[zbus(object_server)] object_server: &zbus::ObjectServer, | ||
) -> Result<(), ServiceError> { | ||
let callback = PrompterCallback::new(self.service.clone()).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prompt callback creates the prompt path again, just pass it here instead
2399214
to
23067f3
Compare
cc @ueno, could you review & ack the crypto code please? |
@@ -47,6 +47,14 @@ impl Key { | |||
Ok(Self::new(crypto::generate_public_key(private_key)?)) | |||
} | |||
|
|||
pub fn generate_public_key_for_secret_exchange( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those new methods needs tests, similar to the previous ones to ensure we have the same behaviour in both native & openssl crypto.
@@ -86,6 +86,16 @@ pub(crate) fn generate_public_key(private_key: impl AsRef<[u8]>) -> Result<Vec<u | |||
Ok(public_key_uint.to_bytes_be()) | |||
} | |||
|
|||
pub(crate) fn generate_public_key_for_secret_exchange( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As those APIs are only useful for the secret exchange, which is only used by the GNOME implementation I don't think we should have them here and instead move them to the server side under /gnome. Especially that we have a Key::new
server/src/gnome/prompter.rs
Outdated
title: Some("Unlock Keyring".to_string()), | ||
choice_label: None, | ||
description: Some( | ||
"An application wants access to the keyring 'login', but it is locked".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes the keyring used. Shouldn't be hardcoded but rather passed as a parameter
exchange, | ||
&self.service.secret_exchange_key().await, | ||
) { | ||
match oo7::file::Keyring::open("login", secret).await { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, this assumes the keyring to open
) -> Result<(Vec<OwnedObjectPath>, OwnedObjectPath), ServiceError> { | ||
let (unlocked, _not_unlocked) = self.set_locked(false, &objects, false).await?; | ||
let (unlocked, not_unlocked) = self.set_locked(false, &objects, false).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as i mentioned earlier, Prompt should take a Future. The current code is way too complex just to avoid passing a future which would make everything much easier to follow
Even though gnome-keyring-daemon doesn't implement/show a prompt during the service method lock. The secret service spec says a prompt can be used during lock. So, we're adding this to the new daemon. Signed-off-by: Dhanuka Warusadura <[email protected]>
Signed-off-by: Dhanuka Warusadura <[email protected]>
23067f3
to
0575dac
Compare
Please see the commits.