EEBKW7VTILH6AGGV57ZIJ3DJGYHDSYBWGU3C7Q4WWAKSVNUGIYMQC VICI7FB367TT4FELZNDAL2CCXMHLVAEOE6RS53BA46K75KV3OLMQC TFPETWTVADLG2DL7WERHJPGMJVOY4WOKCRWB3NZ3YOOQ4CVAUHBAC IXC43DSHDSXCE2X6H6N47GGVKTYM2D2BUUWF34CFWMFT6Z45NOOAC TVVW53HZGYPODAXEQ4BFZNSPBOFG6JEDVOKIYIDZMWFAMOBKOR2QC GUL4M5FIE7JYXJHR7MCTQS3543HIQXGPLBWZKASBAWWEA4SZ75CAC QL6K2ZM35B3NIXEMMCJWUSFXOBQHAGXRDMO7ID5DCKTJH4QJVY7QC SNZ3OAMCPUGFYON5SZHQQQK46ZZMVMJECJYEUCMG657UVLY2PNBQC SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC SLJ3OHD4F6GJGZ3SV2D7DMR3PXYHPSI64X77KZ3RJ24EGEX6ZNQAC 5BB266P6HPUGYEVR7QNNOA62EFPYPUYJ3UMLE5J3LLYMSUWXANIQC BZSC7VMYSFRXDHDDAMCDR6X67FN5VWIBOSE76BQLX7OCVOJFUA3AC IUGP6ZGBFLDRAKJOHFQNG67LZBDXUJ4QM25GOY3QT6GER3NVTHXQC KWAGWB73AMLJFK2Z7SBKHHKKHFRX7AQKXCWDN2MBX72RYCNMB36QC L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC PGERZ3KJIQIREXAYLKLZR4526X5RPKIA6LSVZL2DGYQ5UVPIIBUQC I7VL7VPZV2NKOZRKBWWEHFOGNGGTYLPONHABVJ767D6HPJJNY5RAC Y6EVFMTA6FOH3OQH6QCSWMI3F6SYZT2FSHO6GF4M3ICENDCWFM4QC F6V27C3M7GZHBHXMGAZMYO5XGWDYYNNIF2HLDE2VPNHTEVDSYGVQC 7MNTFTDF62XHPRL56GJLTGRVB6QOLYDRYGY2XOKDO745RXOCBB4QC 4H2XTVJ2BNXDNHQ3RQTMOG3I4NRGZT7JDLC2GRINS56TIYTYTO4QC AEPEFS7O3YT7CRRFYQVJWUXUUSRGJ6K6XZQVK62B6N74UXOIFWYAC IIV3EL2XYI2X7HZWKXEXQFAE3R3KC2Q7SGOT3Q332HSENMYVF32QC VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC V435QOJRFHNKW3NKJHMVMFOGO3KGAZVSURLSGFUHVKOMHMF4Q2ZQC 6HNRL5RT76NH5YNSUN7B4FHNRZXKNLX4DROFGMO4R5P2U7JWOL2QC Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC 3WIQYEISUMGOL5FY4LCWOJS55CTTQA7WMJZXAE3Q4GQUTBFLAE7QC XWETQ4DE4KL2GQWSEBE5NENLTSLIDF7RIWOCCVWJFQOVQJE5P33AC JRENVH5DF2F4SOV7UNJENFA7VDI3H63XK76R3LFZK6QCW7JIBLSQC LYTVEPH3W5UHF7MAYFWBT6NVNC42HEVKJGGMFDKUDZDNDOI33YJQC XSEODPNEN2Y2THBRO7L5QFPAEQVSQTLAFZFWCRMBGZ3YSRZB2UJAC T3S4P4ETZABWNK6UET3IDKAWRUOUKQNGCSDMTBJZC7U5R47LQ5DQC WEHUTJUKHOJIBMEK2M7ILPK532FMO7YGWTEAHOIXZP5WOOOSF3ZAC UFCZKKLXVYQYQBYENCAFHY3ZPPSDAJJAIREZSYNQM4QNXV6G6RXAC OUWD436ATBTZJR53B6XDSI5SXRRNZV7YGRUEA5ACHZC2RUDP7G5QC 3M7WBE24JTPTHWQOU5PO2ZJYKPKEH2F6R4M6RWIRFG334PQEA55QC Q3GU26WDEYE2HXMM4WWNEGVMSXLQAKRJBSB2EJZA6JTHPKQHXCXQC 23LVKATNTT74YKHG7KJM6SBO2IVZEV24TQ46ZJIHQ2IXONWNVXJAC ENKQ3QZGH2QW246C7GSZRKYLODJOQHKZZSYV7QHB7VPOFP5PASVQC B5Z4IMEUYAEJPOU5EIAXI7VYZVUM6CWKV7CTSOXK3F4GXTNNMMAAC TPEH2XNBS5RO4IEVKENVF6P65AH7IX64KK2JAYMSJT3J5GXO67EAC H62VFFJEBL2I3O4D3BAJZ57ROPWUISC7JCDIWFBC5DAYJRHMMDXAC let authors = if let Some(a) = author {vec![libpijul::change::Author {name: a.to_string(),full_name: None,email: None,}]} else if let Ok(global) = crate::config::Global::load() {vec![global.author]} else {Vec::new()};
let mut authors = Vec::new();if let Some(ref a) = author {authors.push(a.to_string());} 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();authors.push(k.key)}}
use log::{debug, error};use serde_derive::{Deserialize, Serialize};use thrussh_keys::PublicKeyBase64;
use log::debug;
let key = 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};k.load(pass.as_deref())?} 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")};
if sign {let mut key_path = dirs_next::home_dir().unwrap().join(".ssh");match sign_hash(&mut key_path, hash).await? {Some((pk, signature)) if !signature.is_empty() => {let sig = toml::Value::try_from(vec![Signature {public_key: pk,timestamp: change.header.timestamp,signature,}])?;let mut toml = toml::map::Map::new();toml.insert("signatures".to_string(), sig);change.unhashed = Some(toml.into());let hash2 = repo.changes.save_change(&change).unwrap();assert_eq!(hash2, hash);}_ => {bail!("Could not sign the change");}}}
change.unhashed = Some(serde_json::json!({"signature": key.sign_raw(&hash.to_bytes())?,}));
let authors = if let Some(ref a) = self.author {vec![libpijul::change::Author {name: a.clone(),full_name: None,email: None,}]} else if let Ok(global) = config.as_ref() {vec![global.author.clone()]} else {Vec::new()};
let mut authors = Vec::new();if let Some(ref a) = self.author {authors.push(a.clone());} 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();authors.push(k.key)}}
async fn sign_hash(key_path: &mut PathBuf,hash: libpijul::Hash,) -> Result<Option<(String, String)>, anyhow::Error> {let to_sign = hash.to_bytes();match thrussh_keys::agent::client::AgentClient::connect_env().await {Ok(agent) => {let mut agent = Some(agent);for k in &["id_ed25519.pub", "id_rsa.pub"] {key_path.push(k);if let Ok(key) = thrussh_keys::load_public_key(&key_path) {debug!("key");if let Some(a) = agent.take() {debug!("authenticate future");if let (_, Ok(sig)) = a.sign_request_base64(&key, &to_sign).await {key_path.pop();let key = key.public_key_base64();return Ok(Some((key, sig)));}}}key_path.pop();}}Err(e) => {error!("{:?}", e);}}for k in &["id_ed25519", "id_rsa"] {key_path.push(k);if let Some(k) = crate::remote::ssh::load_secret_key(&key_path, k) {key_path.pop();let pk = k.public_key_base64();return Ok(Some((pk, k.sign_detached(&to_sign)?.to_base64())));} else {key_path.pop();}}Ok(None)}
use crate::config::*;use anyhow::bail;use chrono::Datelike;use clap::Clap;use log::debug;use std::io::Write;use std::path::Path;#[derive(Clap, Debug)]pub struct Key {#[clap(subcommand)]subcmd: Option<SubCommand>,}#[derive(Clap, Debug)]pub enum SubCommand {Generate {},}impl Key {pub async fn run(self) -> Result<(), anyhow::Error> {match self.subcmd {Some(SubCommand::Generate {}) => {if let Some(mut dir) = global_config_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 expires = chrono::Utc::now();let expires = expires.with_year(expires.year() + 1).unwrap();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(expires);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)?;serde_json::to_writer_pretty(&mut f, &k.public_key())?;f.write_all(b"\n")?;}}None => {}}Ok(())}}#[cfg(unix)]fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {use std::fs::OpenOptions;use std::os::unix::fs::OpenOptionsExt;OpenOptions::new().write(true).create(true).mode(0o600).open(path)}#[cfg(not(unix))]fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {std::fs::File::create(path)}
use hmac::Hmac;use sha2::{Digest, Sha256};pub const CONTEXT: &'static [u8] = b"pijul";pub const VERSION: u64 = 0;#[derive(Debug, Error)]pub enum KeyError {#[error("Base 32 decoding error")]Encoding,#[error(transparent)]Dalek(#[from] ed25519_dalek::ed25519::Error),#[error("No password supplied")]NoPassword,#[error("The key expired")]Expired,}impl From<data_encoding::DecodePartial> for KeyError {fn from(_: data_encoding::DecodePartial) -> Self {KeyError::Encoding}}impl From<data_encoding::DecodeError> for KeyError {fn from(_: data_encoding::DecodeError) -> Self {KeyError::Encoding}}#[derive(Serialize, Deserialize)]pub struct SecretKey {pub version: u64,pub algorithm: Algorithm,pub expires: chrono::DateTime<chrono::Utc>,pub encryption: Option<Encryption>,pub key: String,}pub enum SKey {Ed25519 {key: ed25519_dalek::Keypair,expires: chrono::DateTime<chrono::Utc>,},}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct PublicKey {pub version: u64,pub algorithm: Algorithm,pub expires: chrono::DateTime<chrono::Utc>,pub signature: String,pub key: String,}#[derive(Debug)]pub enum PKey {Ed25519 {expires: chrono::DateTime<chrono::Utc>,signature: String,key: ed25519_dalek::PublicKey,},}#[test]fn sign_public_key() {use chrono::Datelike;let expires = chrono::Utc::now();let expires = expires.with_year(expires.year() + 1).unwrap();let sk = SKey::generate(expires);let pk = sk.public_key();println!("{:?}", pk);let pk = pk.load().unwrap();println!("{:?}", pk);}#[derive(Debug, Serialize, Deserialize)]pub struct Signature {pub version: u64,pub key: PublicKey,pub signature: String,pub date: chrono::DateTime<chrono::Utc>,}impl SKey {pub fn sign(&self, h: &[u8]) -> Result<Signature, KeyError> {Ok(Signature {version: VERSION,signature: self.sign_raw(h)?,key: self.public_key(),date: chrono::Utc::now(),})}pub fn sign_raw(&self, h: &[u8]) -> Result<String, KeyError> {match self {SKey::Ed25519 { key, expires } => {if expires <= &chrono::Utc::now() {return Err(KeyError::Expired);}let mut prehashed = ed25519_dalek::Sha512::new();prehashed.update(h);let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();Ok(data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()))}}}pub fn generate(expires: chrono::DateTime<chrono::Utc>) -> Self {use rand::RngCore;let mut key = [0; 32];rand::thread_rng().fill_bytes(&mut key);SKey::Ed25519 {key: ed25519_dalek::Keypair::from_bytes(&key).unwrap(),expires,}}pub fn save(&self, password: Option<&str>) -> SecretKey {match self {SKey::Ed25519 { key, expires } => {let mut key = key.to_bytes();let encryption = if let Some(password) = password {use rand::Rng;let salt = rand::thread_rng().sample_iter(&rand::distributions::Alphanumeric).take(32).collect();let enc = Encryption::Aes128(Kdf::Pbkdf2 { salt });enc.encrypt(password.as_bytes(), &mut key);Some(enc)} else {None};SecretKey {version: VERSION,algorithm: Algorithm::Ed25519,expires: expires.clone(),encryption,key: data_encoding::BASE32_NOPAD.encode(&key),}}}}pub fn public_key(&self) -> PublicKey {match self {SKey::Ed25519 { key, expires } => {let to_sign =bincode::serialize(&(Algorithm::Ed25519, expires.clone(), key.public)).unwrap();debug!("to_sign {:?}", to_sign);let mut prehashed = ed25519_dalek::Sha512::new();prehashed.update(&to_sign);let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();PublicKey {version: VERSION,algorithm: Algorithm::Ed25519,expires: expires.clone(),key: data_encoding::BASE32_NOPAD.encode(&key.public.clone().to_bytes()),signature: data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()),}}}}pub fn pkey(&self) -> PKey {match self {SKey::Ed25519 { key, expires } => {let to_sign =bincode::serialize(&(Algorithm::Ed25519, expires.clone(), key.public)).unwrap();debug!("to_sign {:?}", to_sign);let mut prehashed = ed25519_dalek::Sha512::new();prehashed.update(&to_sign);let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();PKey::Ed25519 {expires: expires.clone(),key: key.public.clone(),signature: data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()),}}}}}impl SecretKey {pub fn load(&self, pw: Option<&str>) -> Result<SKey, KeyError> {if self.expires <= chrono::Utc::now() {return Err(KeyError::Expired);}match self.algorithm {Algorithm::Ed25519 => {let mut key_enc = data_encoding::BASE32_NOPAD.decode(self.key.as_bytes())?;if let Some(ref enc) = self.encryption {let password = if let Some(ref pw) = pw {pw} else {return Err(KeyError::NoPassword);};enc.decrypt(password.as_bytes(), &mut key_enc);}Ok(SKey::Ed25519 {key: ed25519_dalek::Keypair::from_bytes(&key_enc)?,expires: self.expires,})}}}}impl PublicKey {pub fn fingerprint(&self) -> String {match self.algorithm {Algorithm::Ed25519 => {let signed =bincode::serialize(&(Algorithm::Ed25519, self.expires.clone(), &self.key)).unwrap();let mut prehashed = ed25519_dalek::Sha512::new();prehashed.update(&signed);data_encoding::BASE32_NOPAD.encode(&prehashed.finalize())}}}pub fn load(&self) -> Result<PKey, KeyError> {match self.algorithm {Algorithm::Ed25519 => {let key = ed25519_dalek::PublicKey::from_bytes(&data_encoding::BASE32_NOPAD.decode(self.key.as_bytes())?,)?;let mut signature = [0; 64];data_encoding::BASE32_NOPAD.decode_mut(self.signature.as_bytes(), &mut signature)?;let signature = ed25519_dalek::Signature::new(signature);let signed =bincode::serialize(&(Algorithm::Ed25519, self.expires.clone(), &key)).unwrap();debug!("signed {:?}", signed);key.verify_strict(&signed, &signature)?;Ok(PKey::Ed25519 {signature: self.signature.clone(),expires: self.expires.clone(),key,})}}}}impl PKey {pub fn save(&self) -> PublicKey {match self {PKey::Ed25519 {key,expires,signature,} => PublicKey {version: VERSION,algorithm: Algorithm::Ed25519,expires: expires.clone(),signature: signature.clone(),key: data_encoding::BASE32_NOPAD.encode(key.as_bytes()),},}}pub fn verify(&self,h: &[u8],signature: &str,date: &chrono::DateTime<chrono::Utc>,) -> Result<(), KeyError> {match self {PKey::Ed25519 { key, expires, .. } => {if expires <= date {return Err(KeyError::Expired);}let mut sig = [0; 64];data_encoding::BASE32_NOPAD.decode_mut(signature.as_bytes(), &mut sig)?;let sig = ed25519_dalek::Signature::new(sig);let mut prehashed = ed25519_dalek::Sha512::new();prehashed.update(h);key.verify_prehashed(prehashed, Some(CONTEXT), &sig)?;Ok(())}}}}#[test]fn verify_test() {use chrono::Datelike;let expires = chrono::Utc::now();let expires = expires.with_year(expires.year() + 1).unwrap();let sk = SKey::generate(expires);let m = b"blabla";let signature = sk.sign(m).unwrap();signature.verify(m).unwrap();}impl Signature {pub fn verify(&self, h: &[u8]) -> Result<(), KeyError> {self.key.load()?.verify(h, &self.signature, &self.date)}}#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]pub enum Algorithm {Ed25519,}#[derive(Serialize, Deserialize)]pub enum Encryption {Aes128(Kdf),}#[derive(Serialize, Deserialize)]pub enum Kdf {Pbkdf2 { salt: String },}impl Encryption {pub fn encrypt<'a>(&self, password: &[u8], bytes: &'a mut [u8]) {match self {Encryption::Aes128(Kdf::Pbkdf2 { ref salt }) => {let mut kdf = [0; 32];pbkdf2::pbkdf2::<Hmac<Sha256>>(password, salt.as_ref(), 10_000, &mut kdf);use aes::{cipher::FromBlockCipher, cipher::StreamCipher, Aes128, Aes128Ctr,NewBlockCipher,};let (a, b) = kdf.split_at(16);let cipher = Aes128::new(generic_array::GenericArray::from_slice(&a));let mut cipher = Aes128Ctr::from_block_cipher(cipher,generic_array::GenericArray::from_slice(b),);cipher.apply_keystream(bytes);}}}pub fn decrypt<'a>(&self, password: &[u8], bytes: &'a mut [u8]) {self.encrypt(password, bytes)}}#[test]fn encrypt_decrypt() {let enc = Encryption::Aes128(Kdf::Pbkdf2 {salt: "blabla".to_string(),});let b0 = b"very confidential secret".to_vec();let mut b = b0.clone();enc.encrypt(b"password", &mut b[..]);println!("{:?}", b);enc.decrypt(b"password", &mut b[..]);println!("{:?}", b);assert_eq!(b, b0);}
pub authors: Vec<Author>,}#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]pub struct Author {pub name: String,#[serde(default)]pub full_name: Option<String>,#[serde(default)]pub email: Option<String>,}impl std::fmt::Display for Author {fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {if self.full_name.is_none() && self.email.is_none() {write!(fmt, "{:?}", self.name)} else {write!(fmt, "{{ name = {:?}", self.name)?;if let Some(ref f) = self.full_name {write!(fmt, ", full_name = {:?}", f)?;}if let Some(ref f) = self.email {write!(fmt, ", email = {:?}", f)?;}write!(fmt, " }}")}}
pub authors: Vec<String>,
name = "ed25519"version = "1.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8d0860415b12243916284c67a9be413e044ee6668247b99ba26d94b2bc06c8f6"dependencies = ["serde","signature",][[package]]name = "ed25519-dalek"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"dependencies = ["curve25519-dalek","ed25519","rand 0.7.3","serde","serde_bytes","sha2","zeroize",][[package]]
name = "lexical-core"version = "0.7.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"dependencies = ["arrayvec","bitflags","cfg-if 1.0.0","ryu","static_assertions",][[package]]
"lock_api","parking_lot_core",
"lock_api 0.4.4","parking_lot_core 0.8.3",][[package]]name = "parking_lot_core"version = "0.7.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"dependencies = ["cfg-if 0.1.10","cloudabi","libc","redox_syscall 0.1.57","smallvec","winapi",
version = "0.3.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"dependencies = ["rand_core 0.4.2",][[package]]name = "rand_core"version = "0.4.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"[[package]]name = "rand_core"
dependencies = ["zeroize_derive",][[package]]name = "zeroize_derive"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"dependencies = ["proc-macro2","quote","syn","synstructure",]
]name = "bitvec"version = "0.19.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"dependencies = ["funty","radium","tap","wyz",