This also adds some support for caching user secrets where appropriate, so it should make interaction with remotes have less friction.
DWSAYGVEOR4D2EKIICEZUWCRGJTUXQQLOUWMYIFV7XN62K44F4FAC MU6P2JXG6RQEUOUV7HIZNA2734BGUCYMJYXAOLOO2CSGMNKVIH5QC C267PHOH3QJBSBEWQB3J7PPOOXIUKM3DIIZIPLHPU4D5OXRCGLZAC NAUECZW353R5RHT4GGQJIEZPA5EYRGQYTSP7IJNBJS3CXBSTNJDQC QL6K2ZM35B3NIXEMMCJWUSFXOBQHAGXRDMO7ID5DCKTJH4QJVY7QC RUBBHYZ7MCLKJIHZ3EWEC3JR3FSKOU4T2NH7KRBG7ECAU4JF3LUAC SMMBFECLGSUKRZW5YPOQPOQCOY2CH2OTZXBSZ3KG2N3J3HQZ5PSAC EEBKW7VTILH6AGGV57ZIJ3DJGYHDSYBWGU3C7Q4WWAKSVNUGIYMQC A3RM526Y7LUXNYW4TL56YKQ5GVOK2R5D7JJVTSQ6TT5MEXIR6YAAC SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC TFPETWTVADLG2DL7WERHJPGMJVOY4WOKCRWB3NZ3YOOQ4CVAUHBAC I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC CCLLB7OIFNFYJZTG3UCI7536TOCWSCSXR67VELSB466R24WLJSDAC LJFJEX43HDS33O5HCRXH7AR3GTQZDHNWHEQBOERDNPNXR3B3XZ3QC IIV3EL2XYI2X7HZWKXEXQFAE3R3KC2Q7SGOT3Q332HSENMYVF32QC E7UUQQCCX2WSVOSMO4OWCJFFU7RGQKQ4TRBBICVM52K7ATTHYNSAC 6F6AAHK4M2IVS23TVISR5OJSTZXUSEKLOP5BMM7SUHYG2FQNTSGQC OU6JOR3CDZTH2H3NTGMV3WDIAWPD3VEJI7JRY3VJ7LPDR3QOA52QC 6U42MTEZTINWUU2KLJLQW33ZBZXKTHPTF6TMEG56TO642CQDZQMQC UDHP4ZVBQZT2VBURB2MDCU2IZDNMCAFSIUKWRBDQ5BWMFKSN2LYQC TPEH2XNBS5RO4IEVKENVF6P65AH7IX64KK2JAYMSJT3J5GXO67EAC I6DVZEFUMGH6BFOLGBPM6J4PL5I4PAAODJYG7REXYPDHPKPBLDTAC SZWBLWZ4LUJZHTSYOIGJMXM7KPCGWJFPKLHYA5MHY7UTPNLZV5KQC RRCSHAYZ6RLWVPNYHF2F5FSRL2KKJNPQUQRIJYLJGB23BZQQ7JLQC HKA66XOQ5LOF3E5DIQGVKPEZGZKCXLLYRVTKDK7SAD7Y5JZB5OUQC } else if let Some(mut dir) = crate::config::global_config_dir() {dir.push("publickey.json");if let Ok(key) = std::fs::File::open(&dir) {let k: libpijul::key::PublicKey = serde_json::from_reader(key).unwrap();b.insert("key".to_string(), k.key);} else {bail!("No identity configured yet. Please use `pijul key` to create one")}
} else if let Some(_dir) = crate::config::global_config_dir() {let k = crate::identity::public_key(&crate::identity::choose_identity_name(false).await?)?;b.insert("key".to_string(), k.key);
} else if let Some(mut dir) = crate::config::global_config_dir() {dir.push("publickey.json");if let Ok(key) = std::fs::File::open(&dir) {let k: libpijul::key::PublicKey = serde_json::from_reader(key).unwrap();b.insert("key".to_string(), k.key);} else {bail!("No identity configured yet. Please use `pijul key` to create one")}
} else {let public_key = crate::identity::public_key(&self.identity.clone().unwrap_or(crate::identity::choose_identity_name(false).await?),);b.insert("key".to_string(), public_key?.key);
fn get_public_key() -> Result<libpijul::key::PublicKey, anyhow::Error> {if let Some(mut dir) = crate::config::global_config_dir() {dir.push("publickey.json");if let Ok(mut pkf) = std::fs::File::open(&dir) {if let Ok(pkf) = serde_json::from_reader(&mut pkf) {return Ok(pkf);}}}bail!("No public key found")}
let public_key: libpijul::key::PublicKey = if let Ok(pk) = get_public_key() {pk} else {return Ok(());};if !done.insert(public_key.key.clone()) {return Ok(());}if let Ok((config, last_modified)) = crate::config::Global::load() {serde_json::to_writer(&mut o,&crate::Identity {public_key,email: config.author.email,name: config.author.full_name,login: config.author.name,origin: String::new(),last_modified,},).unwrap();writeln!(o)?;} else {debug!("no global config");}
warn!("Skipping serializing old public key format.");return Ok(());
fn load_key() -> Result<(libpijul::key::SecretKey, libpijul::key::SKey), anyhow::Error> {if let Some(mut dir) = crate::config::global_config_dir() {dir.push("secretkey.json");if let Ok(key) = std::fs::File::open(&dir) {let k: libpijul::key::SecretKey = serde_json::from_reader(key)?;let pass = if k.encryption.is_some() {Some(rpassword::read_password_from_tty(Some(&format!("Password for {:?}: ",dir)))?)} else {None};let sk = k.load(pass.as_deref())?;Ok((k, sk))} else {bail!("Secret key not found, please use `pijul key generate` and try again")}} else {bail!("Secret key not found, please use `pijul key generate` and try again")}}
debug!("{:?} {:?}", global_id_path, id);if let Some(ref mut global_id_path) = global_id_path {if id.is_none() {global_id_path.push(e.key());debug!("{:?}", global_id_path);if let Ok(f) = std::fs::File::open(&global_id_path) {if let Ok(id_) = serde_json::from_reader(f) {id = Some(id_)} else {debug!("wrong identity for {:?}", e.key());}
debug!("{:?}", id);if let Ok(identities) = crate::identity::Complete::load_all() {for identity in identities {if &identity.identity.public_key.key == e.key() {id = Some(identity.identity);
self.auth_pk(&mut h, &mut key_path).await || self.auth_password(&mut h).await?
let mut stderr = std::io::stderr();writeln!(stderr, "Warning: Unable to automatically authenticate with server. Please make sure your SSH keys have been uploaded to the Nest.")?;writeln!(stderr, "For more information, please visit https://pijul.org/manual/the_nest/public_keys.html#ssh-public-keys")?;self.auth_password(&mut h).await?
}async fn auth_pk(&self,h: &mut thrussh::client::Handle<SshClient>,key_path: &mut PathBuf,) -> bool {if h.is_closed() {return false;}let mut authenticated = false;let mut keys = Vec::new();if let Some(ref file) = self.config.identity_file {keys.push(file.as_str())} else {keys.push("id_ed25519");keys.push("id_rsa");}for k in keys.iter() {key_path.push(k);let k = if let Some(k) = load_secret_key(&key_path, k) {k} else {key_path.pop();continue;};if let Ok(auth) = h.authenticate_publickey(&self.config.user, Arc::new(k)).await{authenticated = auth}key_path.pop();if authenticated {return true;}}false
let pass = rpassword::read_password_from_tty(Some(&format!("Password for {}@{}: ",self.config.user, self.config.host_name)))?;h.authenticate_password(self.config.user.to_string(), &pass).await}}
pub fn load_secret_key<P: AsRef<Path>>(key_path: P, k: &str) -> Option<thrussh_keys::key::KeyPair> {match thrussh_keys::load_secret_key(key_path.as_ref(), None) {Ok(k) => Some(k),Err(e) => {if let thrussh_keys::Error::KeyIsEncrypted = e {let pass = if let Ok(pass) =rpassword::read_password_from_tty(Some(&format!("Password for key {:?}: ", k))){pass} else {return None;};if pass.is_empty() {return None;}if let Ok(k) = thrussh_keys::load_secret_key(&key_path, Some(&pass)) {return Some(k);
// Authentication can be attempted multiple timeslet mut authenticated = false;let username = format!("{}@{}", self.config.user, self.config.host_name);// Try authenticate using the user's keyringif let Ok(password) = keyring::Entry::new("pijul", &username).get_password() {authenticated = h.authenticate_password(self.config.user.to_string(), &password).await?;}// Try authenticate using user's passwordif !authenticated {let password = dialoguer::Password::with_theme(crate::config::load_theme().expect("Could not load config").as_ref(),).with_prompt(format!("Password for {username}")).allow_empty_password(true).interact().unwrap();authenticated = h.authenticate_password(self.config.user.to_string(), &password).await?;// If the new password is valid, update the keyring to matchif authenticated {match keyring::Entry::new("pijul", &username).set_password(&password) {Err(e) => writeln!(std::io::stderr(),"Warning: could not write new password to keychain: {e}").unwrap(),_ => (),