Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split public/private key storage #3

Merged
merged 1 commit into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 16 additions & 52 deletions bpb-pkgx-cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -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<Config, Error> {
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<Config, Error> {
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<Config, Error> {
pub fn load() -> Result<Config, Error> {
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<Config, Error> {
let str = get_keychain_item(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT)?;
Ok(toml::from_str::<Config>(&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 {
Expand All @@ -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)]
Expand All @@ -70,26 +50,10 @@ struct PublicKey {
timestamp: u64,
}

#[derive(Serialize, Deserialize)]
struct SecretKey {
key: Option<String>,
program: Option<String>,
}

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)
}
18 changes: 3 additions & 15 deletions bpb-pkgx-cli/src/key_data.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,8 +20,8 @@ impl KeyData {
}
}

pub fn load(config: &Config) -> Result<KeyData, Error> {
let keypair = ed25519::SigningKey::from_bytes(&config.secret()?);
pub fn load(config: &Config, secret: [u8; 32]) -> Result<KeyData, Error> {
let keypair = ed25519::SigningKey::from_bytes(&secret);
Ok(KeyData::create(
keypair,
config.user_id().to_owned(),
Expand All @@ -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()
}
Expand Down
59 changes: 59 additions & 0 deletions bpb-pkgx-cli/src/legacy_config.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
program: Option<String>,
}

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)
}
71 changes: 52 additions & 19 deletions bpb-pkgx-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ 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);
Expand All @@ -43,46 +46,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 <userid>: (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 <userid>: 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(())
}
Expand All @@ -95,7 +107,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())?;

Expand All @@ -114,13 +132,28 @@ fn delegate() -> ! {
}

fn upgrade() -> Result<(), Error> {
let mut file = fs::File::open(keys_file())?;
let config = Config::legacy_load(&mut file)?;
let mut file = std::fs::File::open(legacy_keys_file())?;
let (config, secret) = LegacyConfig::convert(&mut file)?;

config.write()?;
fs::remove_file(keys_file()).map_err(|e| failure::err_msg(e.to_string()))

let service = "xyz.tea.BASE.bpb";
let account = config.user_id();
let hex = hex::encode(secret);
add_keychain_item(service, account, &hex)?;

fs::remove_file(legacy_keys_file()).map_err(|e| failure::err_msg(e.to_string()))
}

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)
}
Loading