This patch moves the functionality from pijul key
into the crate's public interface. The change is a precursor to the identity management wizard which should allow for significantly greater ergonomics in regards to using keys.
F6C6CUAARYNS7W3HUN362HYEMIJJWOFFQRRVNCMHYFPC7UFIWIHQC
ZSFJT4SFIAS7WBODRZOFKKG4SVYBC5PC6XY75WYN7CCQ3SMV7IUQC
CXSCA5HNK3BSSUMZ2SZ5XNKSSHC2IFHPC35SMO5WMWVJBYEFSJTQC
7ZROQSSN2M3LW6ASYMM6DPR5AERWV4K4TKWKEBKTCEJPMIJAHHXQC
NAUECZW353R5RHT4GGQJIEZPA5EYRGQYTSP7IJNBJS3CXBSTNJDQC
OFQY3GUUXYY5GTLHRH4NSZMLVXJRO67QK3TVDLNNOHZ7T66ZSRJAC
6ZHY3XTG6JIVKAJTEYS6IRZR3PTRRMISCQGIIPBXLUOCIL72TEWQC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC
OKE6SXPP34GKAXKZTWLNHRJRQQN32T3SQCSOWWC3GTV425ZF5Q6QC
EEBKW7VTILH6AGGV57ZIJ3DJGYHDSYBWGU3C7Q4WWAKSVNUGIYMQC
PIQCNEEBNHZDYOU2O7667XBB6D3V2MUALRRVJX6VO5BGYR7LTYRQC
SMMBFECLGSUKRZW5YPOQPOQCOY2CH2OTZXBSZ3KG2N3J3HQZ5PSAC
A3RM526Y7LUXNYW4TL56YKQ5GVOK2R5D7JJVTSQ6TT5MEXIR6YAAC
E7UUQQCCX2WSVOSMO4OWCJFFU7RGQKQ4TRBBICVM52K7ATTHYNSAC
#[derive(Parser, Debug)]
pub struct Key {
#[clap(subcommand)]
subcmd: Option<SubCommand>,
pub 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")
}
#[derive(Parser, Debug)]
pub enum SubCommand {
/// Generate a new key. The name used for a key is not required to
/// match a user's remote or SSH credentials. By default, new keys
/// are stored in your global configuration directory.
Generate {
#[clap(long = "email")]
email: Option<String>,
login: String,
},
/// Associate a generated key with a remote identity. Patches authored
/// by unproven keys will only display the key as the author. Example
/// of proving a key (after generating one): `pijul key prove <nestlogin>@ssh.pijul.com`
Prove {
#[clap(short = 'k')]
no_cert_check: bool,
remote: String,
},
}
/// Generate a new key. The name used for a key is not required to
/// match a user's remote or SSH credentials. By default, new keys
/// are stored in your global configuration directory.
pub fn generate(email: Option<String>, login: String) -> Result<(), anyhow::Error> {
if let Some(mut dir) = global_config_dir() {
std::fs::create_dir_all(&dir)?;
dir.push("secretkey.json");
if std::fs::metadata(&dir).is_ok() {
bail!("Cannot overwrite key file {:?}", dir)
}
debug!("creating file {:?}", dir);
let mut f = open_secret_file(&dir)?;
let pass = rpassword::read_password_from_tty(Some(
"Password for the new key (press enter to leave it unencrypted): ",
))?;
let pass = if pass.is_empty() {
None
} else {
Some(pass.as_ref())
};
impl Key {
pub async fn run(self) -> Result<(), anyhow::Error> {
match self.subcmd {
Some(SubCommand::Generate { email, login }) => {
if let Some(mut dir) = global_config_dir() {
std::fs::create_dir_all(&dir)?;
dir.push("secretkey.json");
if std::fs::metadata(&dir).is_ok() {
bail!("Cannot overwrite key file {:?}", dir)
}
debug!("creating file {:?}", dir);
let mut f = open_secret_file(&dir)?;
let pass = rpassword::read_password_from_tty(Some(
"Password for the new key (press enter to leave it unencrypted): ",
))?;
let pass = if pass.is_empty() {
None
} else {
Some(pass.as_ref())
};
let k = libpijul::key::SKey::generate(None);
serde_json::to_writer_pretty(&mut f, &k.save(pass))?;
f.write_all(b"\n")?;
let mut stderr = std::io::stderr();
writeln!(stderr, "Wrote secret key in {:?}", dir)?;
dir.pop();
let k = libpijul::key::SKey::generate(None);
serde_json::to_writer_pretty(&mut f, &k.save(pass))?;
f.write_all(b"\n")?;
let mut stderr = std::io::stderr();
writeln!(stderr, "Wrote secret key in {:?}", dir)?;
dir.pop();
dir.push("publickey.json");
debug!("creating file {:?}", dir);
let mut f = std::fs::File::create(&dir)?;
let pk = k.public_key();
serde_json::to_writer_pretty(&mut f, &pk)?;
f.write_all(b"\n")?;
dir.push("publickey.json");
debug!("creating file {:?}", dir);
let mut f = std::fs::File::create(&dir)?;
let pk = k.public_key();
serde_json::to_writer_pretty(&mut f, &pk)?;
f.write_all(b"\n")?;
dir.pop();
dir.push("identities");
std::fs::create_dir_all(&dir)?;
dir.push(&pk.key);
debug!("creating file {:?}", dir);
let mut f = std::fs::File::create(&dir)?;
serde_json::to_writer_pretty(
&mut f,
&super::Identity {
public_key: pk,
origin: String::new(),
login,
email,
name: None,
last_modified: std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
},
)?;
f.write_all(b"\n")?;
}
dir.pop();
dir.push("identities");
std::fs::create_dir_all(&dir)?;
dir.push(&pk.key);
debug!("creating file {:?}", dir);
let mut f = std::fs::File::create(&dir)?;
serde_json::to_writer_pretty(
&mut f,
&super::Identity {
public_key: pk,
origin: String::new(),
login,
email,
name: None,
last_modified: std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
},
)?;
f.write_all(b"\n")?;
}
}
Some(SubCommand::Prove {
remote,
no_cert_check,
}) => {
let mut remote = if let Ok(repo) = Repository::find_root(None) {
use crate::remote::*;
if let RemoteRepo::Ssh(ssh) = repo
.remote(
None,
&remote,
crate::DEFAULT_CHANNEL,
Direction::Pull,
no_cert_check,
false,
)
.await?
{
ssh
} else {
bail!("No such remote: {}", remote)
}
} else if let Some(mut ssh) = crate::remote::ssh::ssh_remote(&remote, false) {
if let Some(c) = ssh.connect(&remote, crate::DEFAULT_CHANNEL).await? {
c
} else {
bail!("No such remote: {}", remote)
}
} else {
bail!("No such remote: {}", remote)
};
let (_, key) = super::load_key()?;
remote.prove(key).await?;
}
Ok(())
}
None => {
Self::command().write_long_help(&mut std::io::stdout())?;
}
/// Associate a generated key with a remote identity. Patches authored
/// by unproven keys will only display the key as the author. Example
/// of proving a key (after generating one): `pijul key prove <nestlogin>@ssh.pijul.com`
pub async fn prove(no_cert_check: bool, remote: String) -> Result<(), anyhow::Error> {
let mut remote = if let Ok(repo) = Repository::find_root(None) {
use crate::remote::*;
if let RemoteRepo::Ssh(ssh) = repo
.remote(
None,
&remote,
crate::DEFAULT_CHANNEL,
Direction::Pull,
no_cert_check,
false,
)
.await?
{
ssh
} else {
bail!("No such remote: {}", remote)
Ok(())
}
} else if let Some(mut ssh) = crate::remote::ssh::ssh_remote(&remote, false) {
if let Some(c) = ssh.connect(&remote, crate::DEFAULT_CHANNEL).await? {
c
} else {
bail!("No such remote: {}", remote)
}
} else {
bail!("No such remote: {}", remote)
};
let (_, key) = load_key()?;
remote.prove(key).await?;
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")
}
}