diff --git a/bpb-pkgx-cli/src/config.rs b/bpb-pkgx-cli/src/config.rs index 735e233..cbced90 100644 --- a/bpb-pkgx-cli/src/config.rs +++ b/bpb-pkgx-cli/src/config.rs @@ -1,53 +1,37 @@ -use std::io::Read; +use std::io::{Read, Write}; use failure::Error; -use crate::key_data::KeyData; - -use crate::keychain::{add_keychain_item, get_keychain_item}; - -const KEYCHAIN_SERVICE: &str = "xyz.tea.BASE.bpb"; -const KEYCHAIN_ACCOUNT: &str = "example_account"; - #[derive(Serialize, Deserialize)] pub struct Config { public: PublicKey, - secret: SecretKey, } impl Config { - pub fn create(key_data: &KeyData) -> Result { - let keypair = key_data.keypair(); - let userid = key_data.user_id().to_owned(); - let timestamp = key_data.timestamp(); + pub fn create(public_key: String, user_id: String, timestamp: u64) -> Result { + let userid = user_id.to_owned(); + let key = public_key; Ok(Config { public: PublicKey { - key: hex::encode(keypair.verifying_key().as_bytes()), + key, userid, timestamp, }, - secret: SecretKey { - key: Some(hex::encode(keypair.as_bytes())), - program: None, - }, }) } - pub fn legacy_load(file: &mut impl Read) -> Result { + pub fn load() -> Result { + let mut file = std::fs::File::open(keys_file())?; let mut buf = vec![]; file.read_to_end(&mut buf)?; Ok(toml::from_slice(&buf)?) } - pub fn load() -> Result { - let str = get_keychain_item(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT)?; - Ok(toml::from_str::(&str)?) - } - pub fn write(&self) -> Result<(), Error> { - let secret = toml::to_string(self)?; - // let account = self.user_id(); - add_keychain_item(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, &secret) + let path = keys_file(); + std::fs::create_dir_all(path.parent().unwrap())?; + let mut file = std::fs::File::create(path)?; + Ok(file.write_all(&toml::to_vec(self)?)?) } pub fn timestamp(&self) -> u64 { @@ -57,10 +41,6 @@ impl Config { pub fn user_id(&self) -> &str { &self.public.userid } - - pub fn secret(&self) -> Result<[u8; 32], Error> { - self.secret.secret() - } } #[derive(Serialize, Deserialize)] @@ -70,26 +50,10 @@ struct PublicKey { timestamp: u64, } -#[derive(Serialize, Deserialize)] -struct SecretKey { - key: Option, - program: Option, -} - -impl SecretKey { - fn secret(&self) -> Result<[u8; 32], Error> { - if let Some(key) = &self.key { - to_32_bytes(key) - } else { - bail!("No secret key or program specified") - } +fn keys_file() -> std::path::PathBuf { + if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { + std::path::PathBuf::from(config_home).join("pkgx/bpb.toml") + } else { + std::path::PathBuf::from(std::env::var("HOME").unwrap()).join(".config/pkgx/bpb.toml") } } - -fn to_32_bytes(slice: &String) -> Result<[u8; 32], Error> { - let vector = hex::decode(slice)?; - let mut array = [0u8; 32]; - let len = std::cmp::min(vector.len(), 32); - array[..len].copy_from_slice(&vector[..len]); - Ok(array) -} diff --git a/bpb-pkgx-cli/src/key_data.rs b/bpb-pkgx-cli/src/key_data.rs index 05b6fd2..5a027eb 100644 --- a/bpb-pkgx-cli/src/key_data.rs +++ b/bpb-pkgx-cli/src/key_data.rs @@ -1,6 +1,6 @@ use std::time::SystemTime; -use ed25519_dalek as ed25519; +use ed25519_dalek::{self as ed25519}; use failure::Error; use crate::config::Config; @@ -20,8 +20,8 @@ impl KeyData { } } - pub fn load(config: &Config) -> Result { - let keypair = ed25519::SigningKey::from_bytes(&config.secret()?); + pub fn load(config: &Config, secret: [u8; 32]) -> Result { + let keypair = ed25519::SigningKey::from_bytes(&secret); Ok(KeyData::create( keypair, config.user_id().to_owned(), @@ -42,18 +42,6 @@ impl KeyData { )) } - pub fn keypair(&self) -> &ed25519::SigningKey { - &self.keypair - } - - pub fn timestamp(&self) -> u64 { - self.timestamp - } - - pub fn user_id(&self) -> &str { - &self.user_id - } - pub fn fingerprint(&self) -> pbp_pkgx::Fingerprint { self.public().fingerprint() } diff --git a/bpb-pkgx-cli/src/legacy_config.rs b/bpb-pkgx-cli/src/legacy_config.rs new file mode 100644 index 0000000..b744735 --- /dev/null +++ b/bpb-pkgx-cli/src/legacy_config.rs @@ -0,0 +1,59 @@ +use failure::Error; +use std::io::Read; + +use crate::config::Config; + +#[derive(Serialize, Deserialize)] +pub struct LegacyConfig { + public: PublicKey, + secret: SecretKey, +} + +impl LegacyConfig { + pub fn convert(file: &mut impl Read) -> Result<(Config, [u8; 32]), Error> { + let mut buf = vec![]; + file.read_to_end(&mut buf)?; + let config: LegacyConfig = toml::from_slice(&buf)?; + Ok(( + Config::create( + config.public.key, + config.public.userid, + config.public.timestamp, + )?, + config.secret.secret()?, + )) + } +} + +#[derive(Serialize, Deserialize)] +struct PublicKey { + pub key: String, + pub userid: String, + pub timestamp: u64, +} + +#[derive(Serialize, Deserialize)] +struct SecretKey { + key: Option, + program: Option, +} + +impl SecretKey { + pub fn secret(&self) -> Result<[u8; 32], Error> { + if let Some(key) = &self.key { + to_32_bytes(key) + } else if let Some(_program) = &self.program { + bail!("unsupported program configuration") + } else { + bail!("no secret key found") + } + } +} + +fn to_32_bytes(slice: &String) -> Result<[u8; 32], Error> { + let vector = hex::decode(slice)?; + let mut array = [0u8; 32]; + let len = std::cmp::min(vector.len(), 32); + array[..len].copy_from_slice(&vector[..len]); + Ok(array) +} diff --git a/bpb-pkgx-cli/src/main.rs b/bpb-pkgx-cli/src/main.rs index 59379ce..7b655ff 100644 --- a/bpb-pkgx-cli/src/main.rs +++ b/bpb-pkgx-cli/src/main.rs @@ -6,17 +6,19 @@ extern crate serde_derive; mod config; mod key_data; mod keychain; +mod legacy_config; mod tests; -use std::fs; use std::time::SystemTime; use ed25519_dalek as ed25519; use failure::Error; +use keychain::{add_keychain_item, get_keychain_item}; use rand::RngCore; use crate::config::Config; use crate::key_data::KeyData; +use crate::legacy_config::LegacyConfig; fn main() -> Result<(), Error> { let mut args = std::env::args().skip(1); @@ -43,46 +45,55 @@ fn main() -> Result<(), Error> { } fn gpg_sign_arg(arg: &str) -> bool { - arg == "--sign" || (arg.starts_with("-") && !arg.starts_with("--") && arg.contains("s")) + arg == "--sign" || (arg.starts_with('-') && !arg.starts_with("--") && arg.contains('s')) } fn print_help_message() -> Result<(), Error> { println!("bpb: boats's personal barricade\n"); - println!("This is a program for signing your git commits.\n"); + println!("A program for signing git commits.\n"); println!("Arguments:"); - println!(" init : (Re)initialize bpb, generate a new keypair."); - println!(" print: Print the current bpb public key, in OpenPGP format.\n"); - println!("See https://github.com/withoutboats/bpb for more information."); + println!(" init : Generate a keypair and store in the keychain."); + println!(" print: Print public key in OpenPGP format.\n"); + println!("See https://github.com/pkgxdev/bpb for more information."); Ok(()) } fn generate_keypair(userid: String) -> Result<(), Error> { - let keys_file = keys_file(); - if std::fs::metadata(&keys_file).is_ok() { + if let Ok(_config) = Config::load() { eprintln!( - "A bpb_keys.toml already exists. If you want to reinitialize your state\n\ - delete the file at `{}` first", - keys_file + "A keypair already exists. If you (really) want to reinitialize your state\n\ + run `security delete-generic-password -s xyz.tea.BASE.bpb` first." ); return Ok(()); } + let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? .as_secs(); + let mut rng = [0u8; 32]; rand::rngs::OsRng.fill_bytes(&mut rng[0..32]); let keypair = ed25519::SigningKey::from_bytes(&rng); - let key_data = KeyData::create(keypair, userid, timestamp); - let config = Config::create(&key_data)?; + let public_key = hex::encode(keypair.verifying_key().as_bytes()); + let config = Config::create(public_key, userid, timestamp)?; config.write()?; - println!("{}", key_data.public()); + + let service = "xyz.tea.BASE.bpb"; + let account = config.user_id(); + let hex = hex::encode(keypair.to_bytes()); + add_keychain_item(service, account, &hex)?; + + let keydata = KeyData::load(&config, keypair.to_bytes())?; + println!("{}", keydata.public()); + Ok(()) } fn print_public_key() -> Result<(), Error> { let config = Config::load()?; - let keypair = KeyData::load(&config)?; + let secret = [0u8; 32]; + let keypair = KeyData::load(&config, secret)?; println!("{}", keypair.public()); Ok(()) } @@ -95,7 +106,13 @@ fn verify_commit() -> Result<(), Error> { stdin.read_to_string(&mut commit)?; let config = Config::load()?; - let keypair = KeyData::load(&config)?; + let service = "xyz.tea.BASE.bpb"; + let account = config.user_id(); + let secret_str = get_keychain_item(service, account)?; + let secret = to_32_bytes(&secret_str)?; + + let config = Config::load()?; + let keypair = KeyData::load(&config, secret)?; let sig = keypair.sign(commit.as_bytes())?; @@ -114,13 +131,24 @@ fn delegate() -> ! { } fn upgrade() -> Result<(), Error> { - let mut file = fs::File::open(keys_file())?; - let config = Config::legacy_load(&mut file)?; - config.write()?; - fs::remove_file(keys_file()).map_err(|e| failure::err_msg(e.to_string())) + let mut file = std::fs::File::open(legacy_keys_file())?; + let (config, secret) = LegacyConfig::convert(&mut file)?; + let service = "xyz.tea.BASE.bpb"; + let account = config.user_id(); + let hex = hex::encode(secret); + add_keychain_item(service, account, &hex)?; + config.write() } -fn keys_file() -> String { +fn legacy_keys_file() -> String { std::env::var("BPB_KEYS") .unwrap_or_else(|_| format!("{}/.bpb_keys.toml", std::env::var("HOME").unwrap())) } + +fn to_32_bytes(slice: &String) -> Result<[u8; 32], Error> { + let vector = hex::decode(slice)?; + let mut array = [0u8; 32]; + let len = std::cmp::min(vector.len(), 32); + array[..len].copy_from_slice(&vector[..len]); + Ok(array) +}