edit in pijul/src/remote/ssh.rs at line 2
+ use std::convert::TryInto;
replacement in pijul/src/remote/ssh.rs at line 309
− sender: Option<tokio::sync::mpsc::Sender<crate::Identity>>,
+ sender: Option<tokio::sync::mpsc::Sender<crate::identity::Complete>>,
replacement in pijul/src/remote/ssh.rs at line 1041
[5.5863]→[5.5863:5918](∅→∅) − revision = revision.max(id.last_modified);
+ revision = revision.max(id.last_modified.timestamp());
replacement in pijul/src/remote/ssh.rs at line 1044
[5.5962]→[5.5962:5983](∅→∅) + Ok(revision.try_into().unwrap())
replacement in pijul/src/remote/http.rs at line 468
[5.12184]→[5.12184:12222](∅→∅) − id: Vec<crate::Identity>,
+ id: Vec<crate::identity::Complete>,
replacement in pijul/src/remote/http.rs at line 478
[5.12543]→[5.12543:12605](∅→∅) − serde_json::to_writer_pretty(&mut id_file, &id)?;
+ serde_json::to_writer_pretty(&mut id_file, &id.as_portable())?;
replacement in pijul/src/identity/repair.rs at line 2
− use super::{get_valid_password, Complete};
− use crate::commands::Identity;
edit in pijul/src/identity/repair.rs at line 7
− use std::convert::TryInto;
edit in pijul/src/identity/repair.rs at line 37
[3.1412]→[3.1412:3106](∅→∅) − #[derive(Debug)]
− pub(super) struct PotentialPublicKey {
− pub key: PublicKey,
− pub generated_by: GeneratedBy,
− }
−
− #[derive(Debug)]
− pub(super) enum GeneratedBy {
− SecretKey,
− PublicKey,
− Identity,
− }
−
− impl PotentialPublicKey {
− /// Loads all given public keys into an array.
− ///
− /// # Arguments
− /// * `public_key` - The public key to validate
− /// * `identity` - The identity to validate
− /// * `secret_key` - The secret key to validate
− /// * `password` - The password used to decrypt the secret key
− /// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
− pub(super) fn load_public_keys(
− public_key: Option<PublicKey>,
− identity: Option<Identity>,
− secret_key: &SecretKey,
− password: Option<String>,
− name: &str,
− ) -> Result<Vec<Self>, anyhow::Error> {
− // Construct a vec of all potential (not yet validated) public keys, and make sure they all match
− let mut public_keys: Vec<Self> = vec![];
−
− let user_password = get_valid_password(secret_key, password, name)?;
−
− public_keys.push(Self {
− key: secret_key.load(user_password.as_deref())?.public_key(),
− generated_by: GeneratedBy::SecretKey,
− });
−
− if let Some(key) = public_key {
− public_keys.push(Self {
− key,
− generated_by: GeneratedBy::PublicKey,
− });
− }
−
− if let Some(user_identity) = identity {
− public_keys.push(Self {
− key: user_identity.public_key,
− generated_by: GeneratedBy::Identity,
− });
− }
−
− Ok(public_keys)
− }
− }
−
replacement in pijul/src/identity/repair.rs at line 57
[3.3880]→[3.3880:3945](∅→∅) − let extraction_result = Complete::from_old_format(None);
+ let extraction_result = Complete::from_old_format();
replacement in pijul/src/identity/repair.rs at line 76
[3.4969]→[3.4969:5070](∅→∅) − let identity_path = format!("identities/{}", &old_identity.identity.public_key.key);
+ let identity_path = format!("identities/{}", &old_identity.public_key.key);
replacement in pijul/src/identity/repair.rs at line 124
[3.6793]→[3.6793:7160](∅→∅) − let public_keys = PotentialPublicKey::load_public_keys(
− None,
− Some(self.identity.clone()),
− &self.secret_key,
− self.password.clone(),
− &self.name,
− )?;
− if public_keys.len() >= 2 {
− let mut keys_iter = public_keys.iter();
− let first_key = keys_iter.next().unwrap();
+ let public_key = &self.public_key;
+ let decryped_public_key = self.decrypt()?.0.public_key();
replacement in pijul/src/identity/repair.rs at line 127
[3.7161]→[3.7161:8040](∅→∅) − let mut different_keys = vec![first_key];
− for key in keys_iter {
− if !different_keys.iter().any(|k| k.key == key.key) {
− different_keys.push(key);
− }
− }
−
− if different_keys.len() != 1 {
− let mut stderr = std::io::stderr();
− writeln!(stderr, "{MISMATCHED_KEYS_MESSAGE}")?;
− writeln!(stderr, "Got the following public key signatures:")?;
−
− for key in different_keys {
− let source = match key.generated_by {
− GeneratedBy::Identity => "Identity",
− GeneratedBy::PublicKey => "Public key",
− GeneratedBy::SecretKey => "Secret key",
− };
−
− writeln!(stderr, "{} ({source})", key.key.key)?;
− }
+ if public_key.key != decryped_public_key.key {
+ let mut stderr = std::io::stderr();
+ writeln!(stderr, "{MISMATCHED_KEYS_MESSAGE}")?;
+ writeln!(stderr, "Got the following public key signatures:")?;
+ writeln!(stderr, "Plaintext public key: {public_key:#?}")?;
+ writeln!(stderr, "Decrypted public key: {decryped_public_key:#?}")?;
replacement in pijul/src/identity/repair.rs at line 134
[3.8041]→[3.8041:8089](∅→∅) replacement in pijul/src/identity/repair.rs at line 187
[3.9995]→[3.9995:10086](∅→∅) − fn from_old_format(mut password: Option<String>) -> Result<Self, IdentityParseError> {
+ fn from_old_format() -> Result<Self, IdentityParseError> {
replacement in pijul/src/identity/repair.rs at line 232
[3.12053]→[3.12053:12124](∅→∅) − let identity: Option<Identity> = if identities_path.exists() {
+ let identity: Option<Complete> = if identities_path.exists() {
replacement in pijul/src/identity/repair.rs at line 236
[3.12303]→[3.12303:12363](∅→∅) − let mut identities: Vec<Identity> = vec![];
+ let mut identities: Vec<Complete> = vec![];
replacement in pijul/src/identity/repair.rs at line 251
[3.13095]→[3.13095:13169](∅→∅) − let deserialization_result: Result<Identity, _> =
+ let deserialization_result: Result<Complete, _> =
replacement in pijul/src/identity/repair.rs at line 275
[3.13841]→[3.13841:13904](∅→∅) − let author: config::Author = if config_path.exists() {
+ let config: super::Config = if config_path.exists() {
replacement in pijul/src/identity/repair.rs at line 285
[3.14336]→[3.14336:14367](∅→∅) +
+ super::Config {
+ key_path: config_data.author.key_path.clone(),
+ author: config_data.author,
+ }
replacement in pijul/src/identity/repair.rs at line 291
[3.14384]→[3.14384:14417](∅→∅) + let mut author = config::Author::default();
+ author.user_name = identity
replacement in pijul/src/identity/repair.rs at line 294
[3.14443]→[3.14443:14646](∅→∅) − .map_or_else(String::new, |x| x.login.clone());
− config::Author {
− login,
− email: None,
− full_name: None,
− }
− };
+ .map_or_else(String::new, |x| x.config.author.user_name.clone());
replacement in pijul/src/identity/repair.rs at line 296
[3.14647]→[3.14647:14762](∅→∅) − let origin = if let Some(id) = identity {
− id.origin
− } else {
− String::new()
+ super::Config {
+ key_path: None,
+ author,
+ }
edit in pijul/src/identity/repair.rs at line 302
[3.14774]→[3.14774:14909](∅→∅) − // If the secret is encrypted, prompt for password
− password = get_valid_password(&secret_key, password.clone(), "")?;
−
replacement in pijul/src/identity/repair.rs at line 304
[3.14980]→[3.14980:15434](∅→∅) − password,
− secret_key,
− Identity {
− login: author.login,
− name: author.full_name,
− email: author.email,
− origin,
− last_modified: chrono::DateTime::timestamp(&chrono::offset::Utc::now())
− .try_into()
− .context("Failed to create UNIX timestamp")?,
− public_key,
− },
− true,
+ config,
+ public_key,
+ Some(super::Credentials::from(secret_key)),
replacement in pijul/src/identity/mod.rs at line 42
[3.16999]→[3.16999:17069](∅→∅) − pub use load::{choose_identity_name, decrypt_secret_key, public_key};
+ pub use load::{choose_identity_name, public_key};
replacement in pijul/src/identity/mod.rs at line 46
[3.17121]→[3.17121:17142](∅→∅) + use crate::config::Author;
replacement in pijul/src/identity/mod.rs at line 48
[3.17143]→[3.17143:17181](∅→∅) − use libpijul::key::{SKey, SecretKey};
+ use libpijul::key::{PublicKey, SKey, SecretKey};
edit in pijul/src/identity/mod.rs at line 50
[3.17182]→[3.17182:17209](∅→∅) − use std::convert::TryInto;
edit in pijul/src/identity/mod.rs at line 53
+ use std::path::PathBuf;
edit in pijul/src/identity/mod.rs at line 56
+ use once_cell::sync::OnceCell;
+ use serde::{Deserialize, Serialize};
edit in pijul/src/identity/mod.rs at line 59
+ #[derive(Clone, Debug, Serialize, Deserialize)]
+ pub struct Config {
+ #[serde(flatten)]
+ pub author: Author,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub key_path: Option<PathBuf>,
+ }
+
+ impl Default for Config {
+ fn default() -> Self {
+ Self {
+ key_path: None,
+ author: Author::default(),
+ }
+ }
+ }
+
+ impl From<Author> for Config {
+ fn from(author: Author) -> Self {
+ Self {
+ key_path: None,
+ author,
+ }
+ }
+ }
+
edit in pijul/src/identity/mod.rs at line 86
+ pub struct Credentials {
+ secret_key: SecretKey,
+ password: OnceCell<String>,
+ }
+
+ impl Credentials {
+ pub fn new(secret_key: SecretKey, password: Option<String>) -> Self {
+ Self {
+ secret_key,
+ password: if let Some(pw) = password {
+ OnceCell::from(pw)
+ } else {
+ OnceCell::new()
+ },
+ }
+ }
+ }
+
+ impl From<SecretKey> for Credentials {
+ fn from(secret_key: SecretKey) -> Self {
+ Self {
+ secret_key,
+ password: OnceCell::new(),
+ }
+ }
+ }
+
+ impl Credentials {
+ pub fn decrypt(&mut self, name: &str) -> Result<(SKey, Option<String>), anyhow::Error> {
+ if self.secret_key.encryption.is_none() {
+ // Don't mind what the given password is, the secret key has no encryption
+ // Make sure to revoke the password
+ self.password.take();
+ Ok((self.secret_key.load(None)?, None))
+ } else if let Ok(key) = self
+ .secret_key
+ .load(self.password.get().map(String::as_str))
+ {
+ // The password matches secret key, no extra work needed
+ Ok((key, self.password.get().map(|x| x.to_owned())))
+ } else {
+ // Password does not match secret key
+ let mut stderr = std::io::stderr();
+ let mut password_attempt = String::new();
+
+ // Try a password stored in the keychain
+ if let Ok(password) = keyring::Entry::new("pijul", name).get_password() {
+ password_attempt = password;
+ }
+
+ // Re-prompt as long as the password doesn't work
+ while self.secret_key.load(Some(&password_attempt)).is_err() {
+ writeln!(stderr, "Password does not match secret key")?;
+
+ password_attempt = Password::with_theme(config::load_theme()?.as_ref())
+ .with_prompt("Password for secret key")
+ .allow_empty_password(true)
+ .interact()?;
+ }
+
+ // Update the password
+ keyring::Entry::new("pijul", name).set_password(&password_attempt)?;
+ self.password.set(password_attempt.clone()).unwrap();
+
+ Ok((
+ self.secret_key.load(Some(&password_attempt))?,
+ Some(password_attempt),
+ ))
+ }
+ }
+ }
+
+ #[derive(Clone, Debug, Serialize, Deserialize)]
edit in pijul/src/identity/mod.rs at line 161
replacement in pijul/src/identity/mod.rs at line 163
[3.17453]→[3.17453:17573](∅→∅) − pub password: Option<String>,
− pub secret_key: SecretKey,
− pub identity: Identity,
− pub link_remote: bool,
+ #[serde(flatten)]
+ pub config: Config,
+ pub last_modified: chrono::DateTime<chrono::Utc>,
+ pub public_key: PublicKey,
+ #[serde(skip)]
+ credentials: Option<Credentials>,
replacement in pijul/src/identity/mod.rs at line 176
[3.17743]→[3.17743:18053](∅→∅) − /// * `password` - Password used to decrypt the secret key
− /// * `secret_key` - The corresponding secret key
− /// * `identity` - Information about the author including name, email, login
− /// * `link_remote` - If this identity should be linked with a remote (e.g. the Nest)
− pub const fn new(
+ /// * `config` - User configuration including author details & SSH key
+ /// * `public_key` - The user's public key
+ /// * `credentials` - The user's secret data including secret key & password
+ pub fn new(
replacement in pijul/src/identity/mod.rs at line 181
[3.18075]→[3.18075:18195](∅→∅) − password: Option<String>,
− secret_key: SecretKey,
− identity: Identity,
− link_remote: bool,
+ config: Config,
+ public_key: PublicKey,
+ credentials: Option<Credentials>,
edit in pijul/src/identity/mod.rs at line 185
+ if name.is_empty() {
+ panic!("Identity name cannot be empty!");
+ }
+
replacement in pijul/src/identity/mod.rs at line 191
[3.18244]→[3.18244:18337](∅→∅) − password,
− secret_key,
− identity,
− link_remote,
+ config,
+ public_key,
+ credentials,
+ last_modified: chrono::offset::Utc::now(),
edit in pijul/src/identity/mod.rs at line 197
replacement in pijul/src/identity/mod.rs at line 201
[3.18571]→[3.18571:18634](∅→∅) − let author: config::Author = if config_path.exists() {
+ let author: Author = if config_path.exists() {
replacement in pijul/src/identity/mod.rs at line 209
[3.18937]→[3.18937:19100](∅→∅) − config::Author {
− login: String::new(),
− email: None,
− full_name: Some(whoami::realname()),
− }
replacement in pijul/src/identity/mod.rs at line 217
[3.19269]→[3.19269:19697](∅→∅) − None,
− secret_key.save(None),
− Identity {
− login: author.login,
− name: author.full_name,
− origin: String::from("ssh.pijul.com"),
− email: author.email,
− last_modified: chrono::DateTime::timestamp(&chrono::offset::Utc::now())
− .try_into()?,
− public_key,
− },
− true,
+ Config::from(author),
+ public_key,
+ Some(Credentials::from(secret_key.save(None))),
edit in pijul/src/identity/mod.rs at line 221
+ }
+
+ /// Returns the secret key, if one exists
+ pub fn secret_key(&self) -> Option<SecretKey> {
+ if let Some(credentials) = &self.credentials {
+ Some(credentials.secret_key.clone())
+ } else {
+ None
+ }
+ }
+
+ /// Strips the identity of any device-specific information, such as key path & identity name
+ /// Returns the stripped identity
+ pub fn as_portable(&self) -> Self {
+ Self {
+ name: String::new(),
+ last_modified: chrono::offset::Utc::now(),
+ config: Config {
+ key_path: None,
+ author: self.config.author.clone(),
+ },
+ public_key: self.public_key.clone(),
+ credentials: None,
+ }
+ }
+
+ /// Decrypts the user's secret key, prompting the user for password if necessary
+ /// Returns a tuple containing the decrypted key & the valid password
+ pub fn decrypt(&self) -> Result<(SKey, Option<String>), anyhow::Error> {
+ self.credentials.clone().unwrap().decrypt(&self.name)
+ }
+
+ fn change_password(&mut self) -> Result<(), anyhow::Error> {
+ let (decryped_key, _) = self.decrypt()?;
+
+ let user_password = Password::with_theme(config::load_theme()?.as_ref())
+ .with_prompt("New password")
+ .allow_empty_password(true)
+ .with_confirmation("Confirm password", "Password mismatch")
+ .interact()?;
+
+ let password = if user_password.is_empty() {
+ OnceCell::new()
+ } else {
+ // User has entered a password, add it to the keyring
+ keyring::Entry::new("pijul", &self.name).set_password(&user_password)?;
+
+ OnceCell::from(user_password)
+ };
+
+ // Update the key pair to match this new password
+ self.public_key = decryped_key.public_key();
+ self.credentials = Some(Credentials {
+ secret_key: decryped_key.save(password.get().map(String::as_str)),
+ password,
+ });
+
+ Ok(())
replacement in pijul/src/identity/mod.rs at line 286
[3.19976]→[3.19976:20095](∅→∅) − let has_username = !self.identity.login.is_empty();
− let has_remote = !self.identity.origin.is_empty();
+ let has_username = !self.config.author.user_name.is_empty();
+ let has_remote = !self.config.author.origin.is_empty();
replacement in pijul/src/identity/mod.rs at line 292
[3.20227]→[3.20227:20285](∅→∅) − self.identity.login, self.identity.origin
+ self.config.author.user_name, self.config.author.origin
replacement in pijul/src/identity/mod.rs at line 295
[3.20333]→[3.20333:20390](∅→∅) − Some(format!(" [@{}]", self.identity.login))
+ Some(format!(" [@{}]", self.config.author.user_name))
replacement in pijul/src/identity/mod.rs at line 297
[3.20421]→[3.20421:20479](∅→∅) − Some(format!(" [:{}]", self.identity.origin))
+ Some(format!(" [:{}]", self.config.author.origin))
edit in pijul/src/identity/mod.rs at line 303
[3.20598]→[3.20598:21293](∅→∅) − }
− }
−
− /// Loads a password, either from the keychain or from user input
− ///
− /// # Arguments
− /// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
− fn load_password(name: &str) -> Result<Option<String>, anyhow::Error> {
− if let Ok(password) = keyring::Entry::new("pijul", name).get_password() {
− if password.is_empty() {
− return Ok(None);
− }
−
− return Ok(Some(password));
− }
−
− let pw = Password::with_theme(config::load_theme()?.as_ref())
− .with_prompt("Password for secret key")
− .allow_empty_password(true)
− .interact()?;
−
− if pw.is_empty() {
− Ok(None)
− } else {
− Ok(Some(pw))
edit in pijul/src/identity/mod.rs at line 305
[3.21301]→[3.21301:22354](∅→∅) −
− /// Validates a given password, prompting for user input if needed.
− ///
− /// # Arguments
− /// * `secret_key` - The secret key to decrypt
− /// * `password` - The password to validate
− /// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
− pub fn get_valid_password(
− secret_key: &SecretKey,
− password: Option<String>,
− name: &str,
− ) -> Result<Option<String>, anyhow::Error> {
− if secret_key.encryption.is_some() || secret_key.load(None).is_err() {
− if secret_key.load(password.as_deref()).is_ok() {
− Ok(password)
− } else {
− let mut user_password = load_password(name)?;
−
− // If the user enters the wrong password, reprompt
− let mut stderr = std::io::stderr();
− while secret_key.load(user_password.as_deref()).is_err() {
− writeln!(stderr, "Incorrect password! Please try again.")?;
− user_password = load_password(name)?;
− }
−
− Ok(user_password)
− }
− } else {
− Ok(None)
− }
− }
edit in pijul/src/identity/load.rs at line 2
[3.22415]→[3.22415:22446](∅→∅) − use super::get_valid_password;
edit in pijul/src/identity/load.rs at line 4
[3.22486]→[3.22486:22507](∅→∅) replacement in pijul/src/identity/load.rs at line 49
[3.23838]→[3.23838:23891](∅→∅) − let identity: Identity = toml::from_str(&text)?;
+ let identity: Complete = toml::from_str(&text)?;
edit in pijul/src/identity/load.rs at line 121
[3.26039]→[3.26039:26734](∅→∅) − /// Decrypts the secret key associated with the given identity name. If the given password is invalid, the user may be prompted.
− ///
− /// # Arguments
− /// * `identity_name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
− /// * `password` - The password used to attempt decryption
− pub fn decrypt_secret_key(
− identity_name: &str,
− password: Option<String>,
− ) -> Result<(libpijul::key::SecretKey, libpijul::key::SKey), anyhow::Error> {
− let secret_key = secret_key(identity_name)?;
− let password = get_valid_password(&secret_key, password, identity_name)?;
− let decrypted_key = secret_key.load(password.as_deref())?;
−
− Ok((secret_key, decrypted_key))
− }
−
replacement in pijul/src/identity/load.rs at line 130
[3.27158]→[3.27158:27215](∅→∅) − let identity: Identity = toml::from_str(&text)?;
+ let identity: Complete = toml::from_str(&text)?;
replacement in pijul/src/identity/load.rs at line 136
[3.27331]→[3.27331:27413](∅→∅) − None,
− secret_key,
− identity,
− true,
+ identity.config,
+ identity.public_key,
+ Some(super::Credentials::from(secret_key)),
replacement in pijul/src/identity/create.rs at line 2
[3.28199]→[3.28199:28242](∅→∅) − use super::{get_valid_password, Complete};
edit in pijul/src/identity/create.rs at line 6
[3.28326]→[3.28326:28347](∅→∅) edit in pijul/src/identity/create.rs at line 7
edit in pijul/src/identity/create.rs at line 9
[3.28368]→[3.28368:28401](∅→∅) − use std::{convert::TryInto, fs};
replacement in pijul/src/identity/create.rs at line 11
[3.28431]→[3.28431:28474](∅→∅) − use dialoguer::{Confirm, Input, Password};
+ use dialoguer::{Confirm, FuzzySelect, Input};
edit in pijul/src/identity/create.rs at line 16
+ use thrussh_keys::key::PublicKey;
replacement in pijul/src/identity/create.rs at line 29
[3.28970]→[3.28970:29026](∅→∅) − /// Prompt the user to make changes to an identity.
+ /// Prompt the user to make changes to an identity, returning the new identity
replacement in pijul/src/identity/create.rs at line 33
[3.29133]→[3.29133:29225](∅→∅) − pub fn prompt_changes(mut self, replace_current: bool) -> Result<Self, anyhow::Error> {
+ pub async fn prompt_changes(
+ &self,
+ replace_current: bool,
+ link_remote: bool,
+ ) -> Result<Self, anyhow::Error> {
+ let mut new_identity = self.clone();
replacement in pijul/src/identity/create.rs at line 41
[3.29270]→[3.29270:29341](∅→∅) − let identity_name: String = Input::with_theme(config.as_ref())
+ new_identity.name = Input::with_theme(config.as_ref())
replacement in pijul/src/identity/create.rs at line 46
[3.29520]→[3.29520:29546](∅→∅) replacement in pijul/src/identity/create.rs at line 65
[3.30212]→[3.30212:30279](∅→∅) − let full_name: String = Input::with_theme(config.as_ref())
+ new_identity.config.author.display_name = Input::with_theme(config.as_ref())
replacement in pijul/src/identity/create.rs at line 68
[3.30351]→[3.30351:30565](∅→∅) − .default(whoami::realname())
− .with_initial_text(if replace_current {
− self.identity.name.unwrap_or_default()
− } else {
− String::new()
− })
+ .with_initial_text(&self.config.author.display_name)
replacement in pijul/src/identity/create.rs at line 71
[3.30597]→[3.30597:30665](∅→∅) − let user_email: String = Input::with_theme(config.as_ref())
+ new_identity.config.author.email = Input::with_theme(config.as_ref())
replacement in pijul/src/identity/create.rs at line 74
[3.30753]→[3.30753:30825](∅→∅) − .with_initial_text(self.identity.email.unwrap_or_default())
+ .with_initial_text(&self.config.author.email)
replacement in pijul/src/identity/create.rs at line 83
[3.31135]→[3.31135:31272](∅→∅) − let email: Option<String> = if user_email.is_empty() {
− None
− } else {
− Some(user_email)
− };
+
+ if Confirm::with_theme(config.as_ref())
+ .with_prompt(&format!(
+ "Would you like to change the default SSH key? (Current key: {})",
+ if let Some(path) = &self.config.key_path {
+ format!("{path:#?}")
+ } else {
+ String::from("none")
+ }
+ ))
+ .default(false)
+ .interact()?
+ {
+ new_identity.prompt_ssh().await?;
+ }
+
+ if Confirm::with_theme(config.as_ref())
+ .with_prompt(&format!(
+ "Do you want to change the encryption? (Current status: {})",
+ self.credentials
+ .clone()
+ .unwrap()
+ .secret_key
+ .encryption
+ .map_or("not encrypted", |_| "encrypted")
+ ))
+ .default(false)
+ .interact()?
+ {
+ new_identity.change_password()?;
+ }
+
+ // Update the expiry AFTER potential secret key reset
+ new_identity.prompt_expiry()?;
+
+ if Confirm::with_theme(config.as_ref())
+ .with_prompt("Do you want to link this identity to a remote?")
+ .default(true)
+ .interact()?
+ {
+ new_identity.prompt_remote(link_remote).await?;
+ }
+
+ new_identity.last_modified = chrono::offset::Utc::now();
+
+ Ok(new_identity)
+ }
+
+ async fn prompt_ssh(&mut self) -> Result<(), anyhow::Error> {
+ let mut ssh_agent = thrussh_keys::agent::client::AgentClient::connect_env().await?;
+ let identities = ssh_agent.request_identities().await?;
+ let ssh_dir = dirs_next::home_dir().unwrap().join(".ssh");
+
+ let selection = FuzzySelect::with_theme(config::load_theme()?.as_ref())
+ .with_prompt("Select key")
+ .items(
+ &identities
+ .iter()
+ .map(|id| {
+ format!(
+ "{}: {} ({})",
+ id.name(),
+ id.fingerprint(),
+ ssh_dir
+ .join(match id {
+ PublicKey::Ed25519(_) =>
+ thrussh_keys::key::ED25519.identity_file(),
+ PublicKey::RSA { ref hash, .. } => hash.name().identity_file(),
+ })
+ .display(),
+ )
+ })
+ .collect::<Vec<_>>(),
+ )
+ .default(0)
+ .interact()?;
+
+ self.config.key_path = Some(ssh_dir.join(match identities[selection] {
+ PublicKey::Ed25519(_) => thrussh_keys::key::ED25519.identity_file(),
+ PublicKey::RSA { ref hash, .. } => hash.name().identity_file(),
+ }));
+
+ Ok(())
+ }
+
+ async fn prompt_remote(&mut self, link_remote: bool) -> Result<(), IdentityCreateError> {
+ let config = config::load_theme()?;
+
+ self.config.author.user_name = Input::with_theme(config.as_ref())
+ .with_prompt("Remote username")
+ .default(whoami::username())
+ .with_initial_text(&self.config.author.user_name)
+ .interact_text()
+ .unwrap();
+
+ self.config.author.origin = Input::with_theme(config.as_ref())
+ .with_prompt("Remote URL")
+ .with_initial_text(&self.config.author.origin)
+ .default(String::from("ssh.pijul.com"))
+ .interact_text()
+ .unwrap();
+
+ // Prove the identity to the server
+ if link_remote
+ && self
+ .prove(*NO_CERT_CHECK.get_or_init(|| false))
+ .await
+ .is_err()
+ {
+ return Err(IdentityCreateError::ProveFailed(self.name.clone()));
+ }
+
+ Ok(())
+ }
edit in pijul/src/identity/create.rs at line 198
+ fn prompt_expiry(&mut self) -> Result<(), anyhow::Error> {
+ let config = config::load_theme()?;
+
edit in pijul/src/identity/create.rs at line 202
[3.31307]→[3.31307:31329](∅→∅) replacement in pijul/src/identity/create.rs at line 206
[3.31446]→[3.31446:31507](∅→∅) − let expiry = if Confirm::with_theme(config.as_ref())
+ self.public_key.expires = if Confirm::with_theme(config.as_ref())
edit in pijul/src/identity/create.rs at line 240
[3.32814]→[3.32814:33802](∅→∅) − };
−
− let link_remote: bool = Confirm::with_theme(config.as_ref())
− .with_prompt("Do you want to link this identity to a remote?")
− .default(self.link_remote)
− .interact()?;
−
− let user_name = if link_remote {
− Input::with_theme(config.as_ref())
− .with_prompt("Remote username")
− .default(whoami::username())
− .with_initial_text(self.identity.login)
− .interact_text()?
− } else {
− String::new()
− };
−
− let remote_url = if link_remote {
− Input::with_theme(config.as_ref())
− .with_prompt("Remote URL")
− .with_initial_text(if replace_current {
− self.identity.origin
− } else {
− String::new()
− })
− .default(String::from("ssh.pijul.com"))
− .interact_text()?
− } else {
− String::new()
edit in pijul/src/identity/create.rs at line 241
[3.33813]→[3.33813:34535](∅→∅) −
− let change_password = Confirm::with_theme(config.as_ref())
− .with_prompt(&format!(
− "Do you want to change the encryption? (Current status: {})",
− self.secret_key
− .encryption
− .clone()
− .map_or("not encrypted", |_| "encrypted")
− ))
− .default(false)
− .interact()?;
−
− let password = if change_password {
− let user_password = Password::with_theme(config.as_ref())
− .with_prompt("Secret key password")
− .allow_empty_password(true)
− .with_confirmation("Confirm password", "Password mismatch")
− .interact()?;
replacement in pijul/src/identity/create.rs at line 242
[3.34536]→[3.34536:35714](∅→∅) − let pass = if user_password.is_empty() {
− None
− } else {
− Some(user_password)
− };
−
− // Update the key pair to match this new password
− let loaded_key = self.secret_key.clone().load(
− get_valid_password(&self.secret_key, self.password, &identity_name)?.as_deref(),
− )?;
− self.identity.public_key = loaded_key.public_key();
− self.secret_key = loaded_key.save(pass.as_deref());
−
− pass
− } else {
− self.password
− };
−
− // Update the expiry AFTER potential secret key reset
− self.identity.public_key.expires = expiry;
−
− Ok(Self::new(
− identity_name,
− password,
− self.secret_key,
− Identity {
− public_key: self.identity.public_key,
− login: user_name,
− name: Some(full_name),
− origin: remote_url,
− email,
− last_modified: chrono::DateTime::timestamp(&chrono::offset::Utc::now())
− .try_into()?,
− },
− link_remote,
− ))
replacement in pijul/src/identity/create.rs at line 261
[3.36298]→[3.36298:36365](∅→∅) − let config_data = toml::to_string_pretty(&self.identity)?;
+ let config_data = toml::to_string_pretty(&self)?;
replacement in pijul/src/identity/create.rs at line 265
[3.36512]→[3.36512:36584](∅→∅) − let key_data = serde_json::to_string_pretty(&self.secret_key)?;
+ let key_data = serde_json::to_string_pretty(&self.secret_key())?;
replacement in pijul/src/identity/create.rs at line 279
[3.37098]→[3.37098:37154](∅→∅) − &self.identity.login, &self.identity.origin
+ &self.config.author.user_name, &self.config.author.origin
replacement in pijul/src/identity/create.rs at line 282
[3.37167]→[3.37167:37218](∅→∅) − let remote = self.identity.origin.clone();
+ let remote = self.config.author.origin.clone();
replacement in pijul/src/identity/create.rs at line 310
[3.38119]→[3.38119:38194](∅→∅) − let (_, key) = super::load::decrypt_secret_key(&self.name, None)?;
+ let (key, _password) = self.credentials.clone().unwrap().decrypt(&self.name)?;
replacement in pijul/src/identity/create.rs at line 322
[3.38554]→[3.38554:38568](∅→∅) replacement in pijul/src/identity/create.rs at line 325
[3.38620]→[3.38620:38665](∅→∅) − ) -> Result<Self, IdentityCreateError> {
+ ) -> Result<(), IdentityCreateError> {
+ // Prompt the user to edit changes interactively
replacement in pijul/src/identity/create.rs at line 328
[3.38713]→[3.38713:38730](∅→∅) replacement in pijul/src/identity/create.rs at line 330
[3.38747]→[3.38747:38787](∅→∅) − self.prompt_changes(false)?
+ self.prompt_changes(false, link_remote).await?
replacement in pijul/src/identity/create.rs at line 335
[3.38842]→[3.38842:39610](∅→∅) − // Only exchange keys with the server if the user agreed to do so
− if link_remote
− && confirmed_identity.link_remote
− && confirmed_identity
− .prove(*NO_CERT_CHECK.get_or_init(|| false))
− .await
− .is_err()
− {
− return Err(IdentityCreateError::ProveFailed(
− confirmed_identity.name.clone(),
− ));
− }
−
− // If the user has entered a password, add it to the keyring
− if let Some(password) = confirmed_identity.password.clone() {
− Entry::new("pijul", &confirmed_identity.name)
− .set_password(&password)
− .context("Unable to write to keychain")?;
− }
−
− Ok(confirmed_identity)
replacement in pijul/src/identity/create.rs at line 359
[3.40456]→[3.40456:40520](∅→∅) − if let Some(password) = new_identity.password.clone() {
+ if let Some(password) = new_identity.credentials.clone().unwrap().password.get() {
replacement in pijul/src/config.rs at line 22
[5.182]→[2.23:138](∅→∅),
[2.138]→[5.236:302](∅→∅),
[5.12750]→[5.236:302](∅→∅),
[5.236]→[5.236:302](∅→∅) − // This is for backwards compatability with older versions
− #[serde(alias = "name")]
− pub login: String,
− pub email: Option<String>,
− pub full_name: Option<String>,
+ // Older versions called this 'name', but 'user_name' is more descriptive
+ #[serde(alias = "name", default, skip_serializing_if = "String::is_empty")]
+ pub user_name: String,
+ #[serde(alias = "full_name", default, skip_serializing_if = "String::is_empty")]
+ pub display_name: String,
+ #[serde(default, skip_serializing_if = "String::is_empty")]
+ pub email: String,
+ #[serde(default, skip_serializing_if = "String::is_empty")]
+ pub origin: String,
+ // This has been moved to identity::Config, but we should still be able to read the values
+ #[serde(default, skip_serializing)]
+ pub key_path: Option<PathBuf>,
+ }
+
+ impl Default for Author {
+ fn default() -> Self {
+ Self {
+ user_name: String::new(),
+ email: String::new(),
+ display_name: whoami::realname(),
+ origin: String::new(),
+ key_path: None,
+ }
+ }
replacement in pijul/src/commands/record.rs at line 145
[5.1069]→[4.571:727](∅→∅) − let (_, key) = crate::identity::decrypt_secret_key(
− &crate::identity::choose_identity_name(false).await?,
− None,
− )?;
+ let (key, _password) =
+ crate::identity::Complete::load(&crate::identity::choose_identity_name(false).await?)?
+ .decrypt()?;
replacement in pijul/src/commands/protocol.rs at line 399
[5.2347]→[5.2347:2431](∅→∅) − let id: Result<crate::Identity, _> = serde_json::from_reader(&mut idf);
+ let id: Result<crate::identity::Complete, _> = serde_json::from_reader(&mut idf);
replacement in pijul/src/commands/protocol.rs at line 404
[5.2578]→[5.2578:2639](∅→∅) − serde_json::to_writer(&mut o, &id).unwrap();
+ serde_json::to_writer(&mut o, &id.as_portable()).unwrap();
edit in pijul/src/commands/mod.rs at line 276
[5.5215]→[5.332:357](∅→∅),
[5.357]→[4.1276:1324](∅→∅),
[4.1324]→[5.398:420](∅→∅),
[5.398]→[5.398:420](∅→∅),
[5.20024]→[5.20024:20140](∅→∅),
[5.20140]→[5.480:544](∅→∅),
[5.480]→[5.480:544](∅→∅),
[5.544]→[5.20141:20165](∅→∅),
[5.20165]→[5.564:627](∅→∅),
[5.564]→[5.564:627](∅→∅),
[5.627]→[5.20166:20197](∅→∅),
[5.20197]→[5.44:66](∅→∅),
[5.66]→[5.20197:20225](∅→∅),
[5.20197]→[5.20197:20225](∅→∅),
[5.20225]→[4.1325:1371](∅→∅) − }
−
− use serde_derive::*;
−
− #[derive(Clone, Debug, Serialize, Deserialize)]
− pub struct Identity {
− pub login: String,
− #[serde(default, skip_serializing_if = "Option::is_none")]
− pub name: Option<String>,
− #[serde(default, skip_serializing_if = "String::is_empty")]
− pub origin: String,
− #[serde(default, skip_serializing_if = "Option::is_none")]
− pub email: Option<String>,
− #[serde(default)]
− pub last_modified: u64,
− pub public_key: libpijul::key::PublicKey,
replacement in pijul/src/commands/log.rs at line 377
[5.994]→[5.13074:13174](∅→∅),
[5.13074]→[5.13074:13174](∅→∅) − if let Ok(id_) = serde_json::from_reader::<_, super::Identity>(f) {
+ if let Ok(id_) =
+ serde_json::from_reader::<_, crate::identity::Complete>(f)
+ {
edit in pijul/src/commands/log.rs at line 385
replacement in pijul/src/commands/log.rs at line 388
[4.1706]→[4.1706:1862](∅→∅) − if &identity.identity.public_key.key == e.key() {
− id = Some(identity.identity);
+ if &identity.public_key.key == e.key() {
+ id = Some(identity);
replacement in pijul/src/commands/log.rs at line 395
[5.13350]→[5.1907:2142](∅→∅) − if let Some(ref name) = id.name {
− if let Some(ref email) = id.email {
− e.insert(format!("{} ({}) <{}>", name, id.login, email))
+ if id.config.author.display_name.is_empty() {
+ e.insert(id.config.author.user_name)
+ } else {
+ if id.config.author.email.is_empty() {
+ e.insert(format!(
+ "{} ({})",
+ id.config.author.display_name,
+ id.config.author.user_name
+ ))
replacement in pijul/src/commands/log.rs at line 405
[5.2187]→[5.2187:2272](∅→∅) − e.insert(format!("{} ({})", name, id.login))
+ e.insert(format!(
+ "{} ({}) <{}>",
+ id.config.author.display_name,
+ id.config.author.user_name,
+ id.config.author.email
+ ))
edit in pijul/src/commands/log.rs at line 412
[5.2310]→[5.2310:2406](∅→∅) − } else {
− e.insert(id.login)
replacement in pijul/src/commands/identity.rs at line 2
[3.40839]→[3.40839:40929](∅→∅) − config,
− identity::{choose_identity_name, fix_identities, Complete},
− Identity,
+ config::{self, Author},
+ identity::{self, choose_identity_name, fix_identities, Complete},
replacement in pijul/src/commands/identity.rs at line 6
[3.40933]→[3.40933:40973](∅→∅) − use std::{convert::TryInto, io::Write};
replacement in pijul/src/commands/identity.rs at line 9
[3.40992]→[3.40992:41036](∅→∅) − use chrono::{DateTime, NaiveDateTime, Utc};
+ use chrono::{DateTime, Utc};
replacement in pijul/src/commands/identity.rs at line 219
[3.47911]→[3.47911:48435](∅→∅) − Ok(Complete {
− name: identity_name.unwrap_or(default.name),
− password: pw.clone(),
− secret_key: secret_key.save(pw.as_deref()),
− identity: Identity {
− login: login.unwrap_or(default.identity.login),
− name: display_name.or(default.identity.name),
− origin: origin.unwrap_or(default.identity.origin),
− email: email.or(default.identity.email),
− last_modified: default.identity.last_modified,
− public_key: secret_key.public_key(),
+ Ok(Complete::new(
+ identity_name.unwrap_or(default.name),
+ identity::Config {
+ key_path: None,
+ author: Author {
+ user_name: login.unwrap_or(default.config.author.user_name),
+ display_name: display_name.unwrap_or(default.config.author.display_name),
+ email: email.unwrap_or(default.config.author.email),
+ origin: origin.unwrap_or(default.config.author.origin),
+ key_path: None,
+ },
replacement in pijul/src/commands/identity.rs at line 231
[3.48446]→[3.48446:48480](∅→∅) + secret_key.public_key(),
+ Some(identity::Credentials::new(
+ secret_key.save(pw.as_deref()),
+ pw,
+ )),
+ ))
replacement in pijul/src/commands/identity.rs at line 293
[3.50560]→[3.50560:50773](∅→∅) − "Name: {}",
− identity
− .identity
− .name
− .unwrap_or_else(|| "<NO NAME>".to_string())
+ "Display name: {}",
+ if identity.config.author.display_name.is_empty() {
+ "<NO NAME>"
+ } else {
+ &identity.config.author.display_name
+ }
edit in pijul/src/commands/identity.rs at line 300
replacement in pijul/src/commands/identity.rs at line 303
[3.50884]→[3.50884:51063](∅→∅) − identity
− .identity
− .email
− .unwrap_or_else(|| "<NO EMAIL>".to_string())
+ if identity.config.author.email.is_empty() {
+ "<NO EMAIL>"
+ } else {
+ &identity.config.author.email
+ }
replacement in pijul/src/commands/identity.rs at line 310
[3.51088]→[3.51088:51214](∅→∅) − let login = if identity.identity.login.is_empty() {
− String::from("<NO USERNAME>")
+ let username = if identity.config.author.user_name.is_empty() {
+ "<NO USERNAME>"
replacement in pijul/src/commands/identity.rs at line 313
[3.51243]→[3.51243:51291](∅→∅) − identity.identity.login
+ &identity.config.author.user_name
replacement in pijul/src/commands/identity.rs at line 316
[3.51315]→[3.51315:51441](∅→∅) − let origin = if identity.identity.origin.is_empty() {
− String::from("<NO ORIGIN>")
+ let origin = if identity.config.author.origin.is_empty() {
+ "<NO ORIGIN>"
replacement in pijul/src/commands/identity.rs at line 319
[3.51470]→[3.51470:51519](∅→∅) − identity.identity.origin
+ &identity.config.author.origin
replacement in pijul/src/commands/identity.rs at line 321
[3.51542]→[3.51542:51620](∅→∅) − tree.add_empty_child(format!("Login: {login}@{origin}"));
+ tree.add_empty_child(format!("Login: {username}@{origin}"));
replacement in pijul/src/commands/identity.rs at line 324
[3.51685]→[3.51685:51955](∅→∅) − tree.add_empty_child(format!("Key: {}", identity.identity.public_key.key));
− tree.add_empty_child(format!(
− "Version: {}",
− identity.identity.public_key.version
− ));
+ tree.add_empty_child(format!("Key: {}", identity.public_key.key));
+ tree.add_empty_child(format!("Version: {}", identity.public_key.version));
replacement in pijul/src/commands/identity.rs at line 328
[3.52049]→[3.52049:52112](∅→∅) − identity.identity.public_key.algorithm
+ identity.public_key.algorithm
edit in pijul/src/commands/identity.rs at line 333
[3.52257]→[3.52257:52295](∅→∅) replacement in pijul/src/commands/identity.rs at line 341
[3.52659]→[3.52659:52754](∅→∅) − tree.add_empty_child(format!("Version: {}", identity.secret_key.version));
+ tree.add_empty_child(format!(
+ "Version: {}",
+ identity.secret_key().unwrap().version
+ ));
replacement in pijul/src/commands/identity.rs at line 347
[3.52848]→[3.52848:52902](∅→∅) − identity.secret_key.algorithm
+ identity.secret_key().unwrap().algorithm
replacement in pijul/src/commands/identity.rs at line 351
[3.52972]→[3.52972:53055](∅→∅) − if let Some(encryption) = identity.secret_key.encryption {
+ if let Some(encryption) = identity.secret_key().unwrap().encryption {
edit in pijul/src/commands/identity.rs at line 368
[3.53771]→[3.53771:54041](∅→∅) − let naive_time = NaiveDateTime::from_timestamp(
− identity.identity.last_modified.try_into()?,
− 0,
− );
− let date: DateTime<Utc> = DateTime::from_utc(naive_time, Utc);
replacement in pijul/src/commands/identity.rs at line 370
[3.54135]→[3.54135:54198](∅→∅) − date.format("%Y-%m-%d %H:%M:%S (UTC)")
+ identity.last_modified.format("%Y-%m-%d %H:%M:%S (UTC)")
edit in pijul/src/commands/identity.rs at line 383
+ writeln!(std::io::stderr(), "Editing identity: {old_id_name}")?;
replacement in pijul/src/commands/identity.rs at line 399
[3.55139]→[3.55139:55202](∅→∅) − old_identity.clone().prompt_changes(true)?
+ old_identity
+ .clone()
+ .prompt_changes(true, !options.no_link)
+ .await?
edit in pijul/src/commands/identity.rs at line 404
[3.55221]→[3.55221:55296](∅→∅) −
− old_identity.clone().replace_with(new_identity.clone())?;
replacement in pijul/src/commands/identity.rs at line 405
[3.55297]→[3.55297:55730](∅→∅) − // User has changed credentials, so attempt to re-prove
− if old_identity.identity.login != new_identity.identity.login
− || old_identity.identity.origin != new_identity.identity.origin
− {
− if !options.no_link && new_identity.link_remote {
− new_identity.prove(self.no_cert_check).await?;
− }
− }
+ old_identity.replace_with(new_identity)?;
replacement in pijul/src/commands/identity.rs at line 435
[3.56822]→[3.56822:56884](∅→∅) − if identity.secret_key.encryption.is_some() {
+ if identity.secret_key().unwrap().encryption.is_some() {