Fully replace crate::Identity

finchie
Sep 22, 2022, 8:35 AM
4OJWMSOWWNT5N4W4FDMKBZB5UARCLGV3SRZVKGR4EFAYFUMUHM7AC

Dependencies

  • [2] FVQYZQFL Create dialoguer themes based on global config
  • [3] 4KJ45IJL Implement new identity management
  • [4] DWSAYGVE Update codebase to use new identity management
  • [5] OU243LAB Support for staging
  • [6] EEBKW7VT Keys and identities
  • [7] KI2AFWOS Fixing a panic in pull
  • [8] MU5GSJAW Partial push and pull (WARNING: breaks the existing protocol)
  • [9] A3RM526Y Integrating identity malleability
  • [10] LJFJEX43 Fixing newline issues in the protocol over OpenSSH
  • [11] OU6JOR3C Add path filtering for log, add json output for log
  • [12] DO2Y5TY5 Tag synchronisation
  • [13] SMMBFECL Converting to the new patch format "online"
  • [14] UDHP4ZVB Fixing SSH asynchronicity issues
  • [15] SXEYMYF7 Fixing the bad changes in history (unfortunately, by rebooting).
  • [16] 5BB266P6 Optional colours in the global config file
  • [17] NAUECZW3 Fixing the map between keys and identities
  • [18] I24UEJQL Various post-fire fixes
  • [19] SLJ3OHD4 unrecord: show list of changes if none were given as arguments
  • [*] LGEJSLTY Fixing output (including its uses in reset and pull)
  • [*] FBXYP7QM Forgot to add remote::http

Change contents

  • edit in pijul/src/remote/ssh.rs at line 2
    [21.31]
    [5.25647]
    use std::convert::TryInto;
  • replacement in pijul/src/remote/ssh.rs at line 309
    [5.839][5.839:907]()
    sender: Option<tokio::sync::mpsc::Sender<crate::Identity>>,
    [5.839]
    [5.907]
    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);
    [5.5863]
    [5.5918]
    revision = revision.max(id.last_modified.timestamp());
  • replacement in pijul/src/remote/ssh.rs at line 1044
    [5.5962][5.5962:5983]()
    Ok(revision)
    [5.5962]
    [5.52456]
    Ok(revision.try_into().unwrap())
  • replacement in pijul/src/remote/http.rs at line 468
    [5.12184][5.12184:12222]()
    id: Vec<crate::Identity>,
    [5.12184]
    [5.12222]
    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)?;
    [5.12543]
    [5.12605]
    serde_json::to_writer_pretty(&mut id_file, &id.as_portable())?;
  • replacement in pijul/src/identity/repair.rs at line 2
    [3.97][3.97:171]()
    use super::{get_valid_password, Complete};
    use crate::commands::Identity;
    [3.97]
    [3.171]
    use super::Complete;
  • edit in pijul/src/identity/repair.rs at line 7
    [3.235][3.235:262]()
    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);
    [3.3880]
    [3.3945]
    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);
    [3.4969]
    [3.5070]
    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();
    [3.6793]
    [3.7160]
    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)?;
    }
    [3.7161]
    [3.8040]
    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]()
    return Ok(false);
    }
    [3.8041]
    [3.8089]
    return Ok(false);
  • 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> {
    [3.9995]
    [3.10086]
    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() {
    [3.12053]
    [3.12124]
    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![];
    [3.12303]
    [3.12363]
    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, _> =
    [3.13095]
    [3.13169]
    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() {
    [3.13841]
    [3.13904]
    let config: super::Config = if config_path.exists() {
  • replacement in pijul/src/identity/repair.rs at line 285
    [3.14336][3.14336:14367]()
    config_data.author
    [3.14336]
    [3.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 login = identity
    [3.14384]
    [3.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,
    }
    };
    [3.14443]
    [3.14646]
    .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()
    [3.14647]
    [3.14762]
    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,
    [3.14980]
    [3.15434]
    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};
    [3.16999]
    [3.17069]
    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::Identity;
    [3.17121]
    [3.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};
    [3.17143]
    [3.17181]
    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
    [3.17273]
    [3.17273]
    use std::path::PathBuf;
  • edit in pijul/src/identity/mod.rs at line 56
    [3.17299]
    [3.17299]
    use once_cell::sync::OnceCell;
    use serde::{Deserialize, Serialize};
  • edit in pijul/src/identity/mod.rs at line 59
    [3.17300]
    [3.17300]
    #[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
    [3.17324]
    [3.17324]
    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
    [3.17431]
    [3.17431]
    #[serde(skip)]
  • 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,
    [3.17453]
    [3.17573]
    #[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(
    [3.17743]
    [3.18053]
    /// * `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,
    [3.18075]
    [3.18195]
    config: Config,
    public_key: PublicKey,
    credentials: Option<Credentials>,
  • edit in pijul/src/identity/mod.rs at line 185
    [3.18211]
    [3.18211]
    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,
    [3.18244]
    [3.18337]
    config,
    public_key,
    credentials,
    last_modified: chrono::offset::Utc::now(),
  • edit in pijul/src/identity/mod.rs at line 197
    [3.18353]
    [3.18353]
  • replacement in pijul/src/identity/mod.rs at line 201
    [3.18571][3.18571:18634]()
    let author: config::Author = if config_path.exists() {
    [3.18571]
    [3.18634]
    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()),
    }
    [3.18937]
    [3.19100]
    Author::default()
  • 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,
    [3.19269]
    [3.19697]
    Config::from(author),
    public_key,
    Some(Credentials::from(secret_key.save(None))),
  • edit in pijul/src/identity/mod.rs at line 221
    [3.19708]
    [3.19708]
    }
    /// 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();
    [3.19976]
    [3.20095]
    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
    [3.20227]
    [3.20285]
    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))
    [3.20333]
    [3.20390]
    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))
    [3.20421]
    [3.20479]
    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]()
    use crate::Identity;
  • replacement in pijul/src/identity/load.rs at line 49
    [3.23838][3.23838:23891]()
    let identity: Identity = toml::from_str(&text)?;
    [3.23838]
    [3.23891]
    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)?;
    [3.27158]
    [3.27215]
    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,
    [3.27331]
    [3.27413]
    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};
    [3.28199]
    [3.28242]
    use super::Complete;
  • edit in pijul/src/identity/create.rs at line 6
    [3.28326][3.28326:28347]()
    use crate::Identity;
  • edit in pijul/src/identity/create.rs at line 7
    [3.28348]
    [3.28348]
    use std::fs;
  • 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};
    [3.28431]
    [3.28474]
    use dialoguer::{Confirm, FuzzySelect, Input};
  • edit in pijul/src/identity/create.rs at line 16
    [3.28571]
    [3.28571]
    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.
    [3.28970]
    [3.29026]
    /// 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> {
    [3.29133]
    [3.29225]
    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())
    [3.29270]
    [3.29341]
    new_identity.name = Input::with_theme(config.as_ref())
  • replacement in pijul/src/identity/create.rs at line 46
    [3.29520][3.29520:29546]()
    self.name
    [3.29520]
    [3.29546]
    self.name.clone()
  • 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())
    [3.30212]
    [3.30279]
    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()
    })
    [3.30351]
    [3.30565]
    .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())
    [3.30597]
    [3.30665]
    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())
    [3.30753]
    [3.30825]
    .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)
    };
    [3.31135]
    [3.31272]
    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
    [3.31273]
    [3.31273]
    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]()
    .identity
  • replacement in pijul/src/identity/create.rs at line 206
    [3.31446][3.31446:31507]()
    let expiry = if Confirm::with_theme(config.as_ref())
    [3.31446]
    [3.31507]
    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,
    ))
    [3.34536]
    [3.35714]
    Ok(())
  • replacement in pijul/src/identity/create.rs at line 261
    [3.36298][3.36298:36365]()
    let config_data = toml::to_string_pretty(&self.identity)?;
    [3.36298]
    [3.36365]
    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)?;
    [3.36512]
    [3.36584]
    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
    [3.37098]
    [3.37154]
    &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();
    [3.37167]
    [3.37218]
    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)?;
    [3.38119]
    [3.38194]
    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]()
    self,
    [3.38554]
    [3.38568]
    &self,
  • replacement in pijul/src/identity/create.rs at line 325
    [3.38620][3.38620:38665]()
    ) -> Result<Self, IdentityCreateError> {
    [3.38620]
    [3.38665]
    ) -> Result<(), IdentityCreateError> {
    // Prompt the user to edit changes interactively
  • replacement in pijul/src/identity/create.rs at line 328
    [3.38713][3.38713:38730]()
    self
    [3.38713]
    [3.38730]
    self.clone()
  • replacement in pijul/src/identity/create.rs at line 330
    [3.38747][3.38747:38787]()
    self.prompt_changes(false)?
    [3.38747]
    [3.38787]
    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)
    [3.38842]
    [3.39610]
    Ok(())
  • replacement in pijul/src/identity/create.rs at line 359
    [3.40456][3.40456:40520]()
    if let Some(password) = new_identity.password.clone() {
    [3.40456]
    [3.40520]
    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>,
    [5.182]
    [5.302]
    // 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,
    )?;
    [5.1069]
    [5.1910]
    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);
    [5.2347]
    [5.2431]
    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();
    [5.2578]
    [5.2639]
    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) {
    [5.994]
    [5.13174]
    if let Ok(id_) =
    serde_json::from_reader::<_, crate::identity::Complete>(f)
    {
  • edit in pijul/src/commands/log.rs at line 385
    [4.1553]
    [4.1553]
  • 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);
    [4.1706]
    [5.1745]
    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))
    [5.13350]
    [5.2142]
    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))
    [5.2187]
    [5.2272]
    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,
    [3.40839]
    [3.40929]
    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};
    [3.40933]
    [3.40973]
    use std::io::Write;
  • replacement in pijul/src/commands/identity.rs at line 9
    [3.40992][3.40992:41036]()
    use chrono::{DateTime, NaiveDateTime, Utc};
    [3.40992]
    [3.41036]
    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(),
    [3.47911]
    [3.48435]
    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]()
    link_remote: true,
    })
    [3.48446]
    [3.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())
    [3.50560]
    [3.50773]
    "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
    [3.50797]
    [3.50797]
  • 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())
    [3.50884]
    [3.51063]
    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>")
    [3.51088]
    [3.51214]
    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
    [3.51243]
    [3.51291]
    &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>")
    [3.51315]
    [3.51441]
    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
    [3.51470]
    [3.51519]
    &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}"));
    [3.51542]
    [3.51620]
    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
    ));
    [3.51685]
    [3.51955]
    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
    [3.52049]
    [3.52112]
    identity.public_key.algorithm
  • edit in pijul/src/commands/identity.rs at line 333
    [3.52257][3.52257:52295]()
    .identity
  • 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));
    [3.52659]
    [3.52754]
    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
    [3.52848]
    [3.52902]
    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 {
    [3.52972]
    [3.53055]
    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)")
    [3.54135]
    [3.54198]
    identity.last_modified.format("%Y-%m-%d %H:%M:%S (UTC)")
  • edit in pijul/src/commands/identity.rs at line 383
    [3.54595]
    [3.54595]
    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)?
    [3.55139]
    [3.55202]
    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?;
    }
    }
    [3.55297]
    [3.55730]
    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() {
    [3.56822]
    [3.56884]
    if identity.secret_key().unwrap().encryption.is_some() {