refactor(pubky): move recovery_file code to pubky_common

This commit is contained in:
nazeh
2024-08-31 17:29:32 +03:00
parent 8c56e6033b
commit de38987d96
13 changed files with 76 additions and 893 deletions

772
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,13 +5,8 @@ edition = "2021"
[dependencies]
anyhow = "1.0.86"
clap = "4.5.16"
dialoguer = "0.11.0"
dirs-next = "2.0.0"
keyring = { version = "3.2.0", features = ["linux-native", "apple-native", "windows-native"] }
keyring-search = "1.2.1"
clap = { version = "4.5.16", features = ["derive"] }
pubky = { version = "0.1.0", path = "../../../pubky" }
pubky-common = { version = "0.1.0", path = "../../../pubky-common" }
rpassword = "7.3.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.5.2"

View File

@@ -1,82 +1,40 @@
// use std::io;
use anyhow::Result;
// use clap::{App, Arg, SubCommand};
// use dialoguer::{Input, Select};
use clap::Parser;
use std::path::PathBuf;
use url::Url;
use keyring_search::{Limit, List, Search};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
/// Pubky Auth url
url: Url,
// use keyring::Entry;
// use pubky_common::crypto::Keypair;
const SERVICE_NAME: &str = "pubky";
/// Path to a recovery_file of the Pubky you want to sign in with
// #[arg(short, long, value_name = "FILE")]
recovery_file: PathBuf,
// /// Mutable data public key.
// public_key: String,
}
fn main() -> Result<()> {
let result = Search::new().unwrap().by_service(SERVICE_NAME).unwrap();
let cli = Cli::parse();
// let list: Vec<_> = result
// .values()
// .map(|v| {
// dbg!(&v);
// v.get("acct")
// })
// .filter(|acc| acc.is_some())
// .collect();
let url = cli.url;
dbg!(url);
let list = List::list_credentials(&Search::new().unwrap().by_service(SERVICE_NAME), Limit::All);
let recovery_file = std::fs::read(&cli.recovery_file)?;
println!("Successfully opened recovery file");
dbg!(list);
// // println!("Enter Pubky Auth URL to start the consent form:");
// // let pubky_auth_url = rl.readline("> ")?;
// // dbg!(pubky_auth_url);
// println!("Enter the alias for your keypair in your operating system secure storage:");
// let mut name = String::new();
// io::stdin().read_line(&mut name)?;
// name = name.trim_end().to_lowercase();
//
// let entry = Entry::new(SERVICE_NAME, &name)?;
//
// let keypair = match entry.get_secret() {
// Ok(secret_key) => {
// let secret_key: &[u8; 32] = secret_key
// .as_slice()
// .try_into()
// .expect("Invalid secret_key");
// let keypair = Keypair::from_secret_key(&secret_key);
//
// println!("\nFound secret_key for Pubky {}", keypair.public_key());
//
// keypair
// }
// Err(error) => {
// let keypair = Keypair::random();
//
// println!(
// "\n{}\nGenerated new Pubky {}",
// error.to_string(),
// keypair.public_key()
// );
//
// loop {
// println!("\nStore the new Pubky keypair in operating system secure storage?[y/n]");
// let mut choice = String::new();
// io::stdin().read_line(&mut choice)?;
//
// match choice.as_str() {
// "y\n" => {
// entry.set_secret(&keypair.secret_key())?;
//
// break;
// }
// "n\n" => {
// return Ok(());
// }
// _ => {}
// };
// }
//
// keypair
// }
// };
// dbg!(keypair);
println!("Enter your recovery_file's passphrase to confirm:");
let passphrase = rpassword::read_password()?;
let keypair = pubky_common::recovery_file::decrypt_recovery_file(&recovery_file, &passphrase)?;
println!("Successfully decrypted recovery file...");
Ok(())
}

View File

@@ -15,6 +15,7 @@ rand = "0.8.5"
thiserror = "1.0.60"
postcard = { version = "1.0.8", features = ["alloc"] }
crypto_secretbox = { version = "0.1.1", features = ["std"] }
argon2 = { version = "0.5.3", features = ["std"] }
serde = { workspace = true, optional = true }

View File

@@ -2,5 +2,6 @@ pub mod auth;
pub mod capabilities;
pub mod crypto;
pub mod namespaces;
pub mod recovery_file;
pub mod session;
pub mod timestamp;

View File

@@ -1,13 +1,12 @@
use argon2::Argon2;
use pkarr::Keypair;
use pubky_common::crypto::{decrypt, encrypt};
use crate::error::{Error, Result};
use crate::crypto::{decrypt, encrypt};
static SPEC_NAME: &str = "recovery";
static SPEC_LINE: &str = "pubky.org/recovery";
pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result<Keypair> {
pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result<Keypair, Error> {
let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?;
let newline_index = recovery_file
@@ -39,7 +38,7 @@ pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result<K
Ok(Keypair::from_secret_key(&secret_key))
}
pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result<Vec<u8>> {
pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result<Vec<u8>, Error> {
let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?;
let secret_key = keypair.secret_key();
@@ -54,7 +53,7 @@ pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result<Vec<u
Ok(out)
}
fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8; 32]> {
fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8; 32], Error> {
let argon2id = Argon2::default();
let mut out = [0; 32];
@@ -64,19 +63,39 @@ fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8;
Ok(out)
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
// === Recovery file ==
#[error("Recovery file should start with a spec line, followed by a new line character")]
RecoveryFileMissingSpecLine,
#[error("Recovery file should start with a spec line, followed by a new line character")]
RecoveryFileVersionNotSupported,
#[error("Recovery file should contain an encrypted secret key after the new line character")]
RecoverFileMissingEncryptedSecretKey,
#[error("Recovery file encrypted secret key should be 32 bytes, got {0}")]
RecoverFileInvalidSecretKeyLength(usize),
#[error(transparent)]
Argon(#[from] argon2::Error),
#[error(transparent)]
Crypto(#[from] crate::crypto::Error),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PubkyClient;
#[test]
fn encrypt_decrypt_recovery_file() {
let passphrase = "very secure password";
let keypair = Keypair::random();
let recovery_file = PubkyClient::create_recovery_file(&keypair, passphrase).unwrap();
let recovered = PubkyClient::decrypt_recovery_file(&recovery_file, passphrase).unwrap();
let recovery_file = create_recovery_file(&keypair, passphrase).unwrap();
let recovered = decrypt_recovery_file(&recovery_file, passphrase).unwrap();
assert_eq!(recovered.public_key(), keypair.public_key());
}

View File

@@ -17,7 +17,6 @@ url = "2.5.2"
bytes = "^1.7.1"
pubky-common = { version = "0.1.0", path = "../pubky-common" }
argon2 = { version = "0.5.3", features = ["std"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pkarr = { workspace = true, features = ["async"] }

View File

@@ -15,19 +15,6 @@ pub enum Error {
#[error("Could not resolve endpoint for {0}")]
ResolveEndpoint(String),
// === Recovery file ==
#[error("Recovery file should start with a spec line, followed by a new line character")]
RecoveryFileMissingSpecLine,
#[error("Recovery file should start with a spec line, followed by a new line character")]
RecoveryFileVersionNotSupported,
#[error("Recovery file should contain an encrypted secret key after the new line character")]
RecoverFileMissingEncryptedSecretKey,
#[error("Recovery file encrypted secret key should be 32 bytes, got {0}")]
RecoverFileInvalidSecretKeyLength(usize),
#[error("Could not convert the passed type into a Url")]
InvalidUrl,
@@ -51,7 +38,7 @@ pub enum Error {
Crypto(#[from] pubky_common::crypto::Error),
#[error(transparent)]
Argon(#[from] argon2::Error),
RecoveryFile(#[from] pubky_common::recovery_file::Error),
}
#[cfg(target_arch = "wasm32")]

View File

@@ -6,18 +6,14 @@ use ::pkarr::{
};
use bytes::Bytes;
use pkarr::Keypair;
use pubky_common::session::Session;
use pubky_common::{
recovery_file::{create_recovery_file, decrypt_recovery_file},
session::Session,
};
use reqwest::{RequestBuilder, Response};
use url::Url;
use crate::{
error::Result,
shared::{
list_builder::ListBuilder,
recovery_file::{create_recovery_file, decrypt_recovery_file},
},
PubkyClient,
};
use crate::{error::Result, shared::list_builder::ListBuilder, PubkyClient};
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
@@ -151,12 +147,12 @@ impl PubkyClient {
/// Create a recovery file of the `keypair`, containing the secret key encrypted
/// using the `passphrase`.
pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result<Vec<u8>> {
create_recovery_file(keypair, passphrase)
Ok(create_recovery_file(keypair, passphrase)?)
}
/// Recover a keypair from a recovery file by decrypting the secret key using `passphrase`.
pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result<Keypair> {
decrypt_recovery_file(recovery_file, passphrase)
Ok(decrypt_recovery_file(recovery_file, passphrase)?)
}
}

View File

@@ -2,4 +2,3 @@ pub mod auth;
pub mod list_builder;
pub mod pkarr;
pub mod public;
pub mod recovery_file;

View File

@@ -6,13 +6,9 @@ use std::{
use js_sys::{Array, Uint8Array};
use wasm_bindgen::prelude::{wasm_bindgen, JsValue};
use reqwest::{IntoUrl, Method, RequestBuilder, Response};
use url::Url;
use pubky_common::recovery_file::{create_recovery_file, decrypt_recovery_file};
use crate::{
shared::recovery_file::{create_recovery_file, decrypt_recovery_file},
PubkyClient,
};
use crate::{error::Error, PubkyClient};
mod http;
mod keys;
@@ -62,7 +58,7 @@ impl PubkyClient {
) -> Result<Uint8Array, JsValue> {
create_recovery_file(keypair.as_inner(), passphrase)
.map(|b| b.as_slice().into())
.map_err(|e| e.into())
.map_err(|e| Error::from(e).into())
}
/// Create a recovery file of the `keypair`, containing the secret key encrypted
@@ -74,7 +70,7 @@ impl PubkyClient {
) -> Result<Keypair, JsValue> {
decrypt_recovery_file(recovery_file, passphrase)
.map(Keypair::from)
.map_err(|e| e.into())
.map_err(|e| Error::from(e).into())
}
/// Set Pkarr relays used for publishing and resolving Pkarr packets.

View File

@@ -3,8 +3,6 @@ use crate::PubkyClient;
use reqwest::{Method, RequestBuilder, Response};
use url::Url;
use ::pkarr::PublicKey;
impl PubkyClient {
pub(crate) fn request(&self, method: Method, url: Url) -> RequestBuilder {
let mut request = self.http.request(method, url).fetch_credentials_include();

View File

@@ -21,7 +21,7 @@ impl Keypair {
}
let len = secret_key.byte_length();
if (len != 32) {
if len != 32 {
return Err(format!("Expected secret_key to be 32 bytes, got {len}"))?;
}