Initialize `radicle-tools` crate

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-09-23 17:33:31 +02:00
parent fcb1007f9d
commit a3bf5ab7c3
No known key found for this signature in database
11 changed files with 223 additions and 1 deletions

8
Cargo.lock generated
View File

@ -826,6 +826,14 @@ name = "radicle-std-ext"
version = "0.1.0"
source = "git+https://github.com/radicle-dev/radicle-link?tag=cycle/2022-07-12#541a8161cb24c3b7b10d44f958cc5c5ed05cf443"
[[package]]
name = "radicle-tools"
version = "0.2.0"
dependencies = [
"anyhow",
"radicle",
]
[[package]]
name = "rand"
version = "0.8.5"

View File

@ -1,5 +1,5 @@
[workspace]
members = ["radicle", "radicle-node"]
members = ["radicle", "radicle-node", "radicle-tools"]
[patch.crates-io.radicle-git-ext]
git = "https://github.com/radicle-dev/radicle-link"

25
radicle-tools/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "radicle-tools"
license = "MIT OR Apache-2.0"
version = "0.2.0"
authors = ["Alexis Sellier <alexis@radicle.xyz>"]
edition = "2021"
[dependencies]
anyhow = { version = "1" }
[dependencies.radicle]
version = "0"
path = "../radicle"
[[bin]]
name = "rad-init"
path = "src/rad-init.rs"
[[bin]]
name = "rad-auth"
path = "src/rad-auth.rs"
[[bin]]
name = "rad-self"
path = "src/rad-self.rs"

View File

@ -0,0 +1,9 @@
fn main() -> anyhow::Result<()> {
let keypair = radicle::crypto::KeyPair::generate();
let profile = radicle::Profile::init(keypair)?;
println!("id: {}", profile.id());
println!("home: {}", profile.home.display());
Ok(())
}

View File

@ -0,0 +1,20 @@
use std::path::Path;
fn main() -> anyhow::Result<()> {
let cwd = Path::new(".").canonicalize()?;
let name = cwd.file_name().unwrap().to_string_lossy().to_string();
let repo = radicle::git::raw::Repository::open(cwd)?;
let profile = radicle::Profile::load()?;
let (id, _) = radicle::rad::init(
&repo,
&name,
"",
radicle::git::BranchName::from("master"),
&profile.signer,
&profile.storage,
)?;
println!("ok: {}", id);
Ok(())
}

View File

@ -0,0 +1,8 @@
fn main() -> anyhow::Result<()> {
let profile = radicle::Profile::load()?;
println!("id: {}", profile.id());
println!("home: {}", profile.home.display());
Ok(())
}

View File

@ -116,6 +116,12 @@ impl TryFrom<&[u8]> for Signature {
#[serde(into = "String", try_from = "String")]
pub struct PublicKey(pub ed25519::PublicKey);
impl PublicKey {
pub fn from_pem(pem: &str) -> Result<Self, ed25519::Error> {
ed25519::PublicKey::from_pem(pem).map(Self)
}
}
/// The private/signing key.
pub type SecretKey = ed25519::SecretKey;

View File

@ -6,6 +6,7 @@ use once_cell::sync::Lazy;
use crate::collections::HashMap;
use crate::crypto::PublicKey;
use crate::storage;
use crate::storage::refs::Refs;
use crate::storage::RemoteId;
@ -17,6 +18,7 @@ pub use git_ref_format::{refname, RefStr, RefString};
pub use git_url as url;
pub use git_url::Url;
pub use radicle_git_ext as ext;
pub use storage::BranchName;
/// Default port of the `git` transport protocol.
pub const PROTOCOL_PORT: u16 = 9418;

59
radicle/src/keystore.rs Normal file
View File

@ -0,0 +1,59 @@
use std::fs;
use std::io;
use std::io::Write;
use std::os::unix::fs::DirBuilderExt;
use std::os::unix::prelude::OpenOptionsExt;
use std::path::{Path, PathBuf};
use crate::crypto::{PublicKey, SecretKey};
pub struct UnsafeKeystore {
path: PathBuf,
}
impl UnsafeKeystore {
pub fn new<P: AsRef<Path>>(path: &P) -> Self {
Self {
path: path.as_ref().to_path_buf(),
}
}
pub fn put(&mut self, public: &PublicKey, secret: &SecretKey) -> Result<(), io::Error> {
// TODO: Zeroize secret key.
let public = public.to_pem();
let secret = secret.to_pem();
fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(&self.path)?;
fs::OpenOptions::new()
.mode(0o644)
.create_new(true)
.write(true)
.open(self.path.join("radicle.pub"))?
.write_all(public.as_bytes())?;
fs::OpenOptions::new()
.mode(0o600)
.create_new(true)
.write(true)
.open(self.path.join("radicle"))?
.write_all(secret.as_bytes())?;
Ok(())
}
pub fn get(&self) -> Result<(PublicKey, SecretKey), io::Error> {
let public = fs::read(self.path.join("radicle.pub"))?;
let public = String::from_utf8(public).unwrap();
let public = PublicKey::from_pem(&public).unwrap();
let secret = fs::read(self.path.join("radicle"))?;
let secret = String::from_utf8(secret).unwrap();
let secret = SecretKey::from_pem(&secret).unwrap();
Ok((public, secret))
}
}

View File

@ -3,9 +3,15 @@ pub mod crypto;
pub mod git;
pub mod hash;
pub mod identity;
pub mod keystore;
pub mod node;
pub mod profile;
pub mod rad;
pub mod serde_ext;
pub mod storage;
#[cfg(feature = "test")]
pub mod test;
pub use keystore::UnsafeKeystore;
pub use profile::Profile;
pub use storage::git::Storage;

79
radicle/src/profile.rs Normal file
View File

@ -0,0 +1,79 @@
use std::path::PathBuf;
use std::{env, io};
use crate::crypto::{KeyPair, PublicKey, SecretKey, Signature, Signer};
use crate::keystore::UnsafeKeystore;
use crate::storage::git::Storage;
pub struct UnsafeSigner {
public: PublicKey,
secret: SecretKey,
}
impl Signer for UnsafeSigner {
fn public_key(&self) -> &PublicKey {
&self.public
}
fn sign(&self, msg: &[u8]) -> Signature {
Signature(self.secret.sign(msg, None))
}
}
pub struct Profile {
pub home: PathBuf,
pub signer: UnsafeSigner,
pub storage: Storage,
}
impl Profile {
pub fn init(keypair: KeyPair) -> Result<Self, io::Error> {
let home = self::home()?;
let mut keystore = UnsafeKeystore::new(&home.join("keys"));
let public = keypair.pk.into();
let signer = UnsafeSigner {
public,
secret: keypair.sk,
};
let storage = Storage::open(&home.join("storage"))?;
keystore.put(&signer.public, &signer.secret)?;
Ok(Profile {
home,
signer,
storage,
})
}
pub fn load() -> Result<Self, io::Error> {
let home = self::home()?;
let (public, secret) = UnsafeKeystore::new(&home.join("keys")).get()?;
let signer = UnsafeSigner { public, secret };
let storage = Storage::open(&home.join("storage"))?;
Ok(Profile {
home,
signer,
storage,
})
}
pub fn id(&self) -> &PublicKey {
self.signer.public_key()
}
}
/// Get the path to the radicle home folder.
pub fn home() -> Result<PathBuf, io::Error> {
if let Some(home) = env::var_os("RAD_HOME") {
Ok(PathBuf::from(home))
} else if let Some(home) = env::var_os("HOME") {
Ok(PathBuf::from(home).join(".radicle"))
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"Neither `RAD_HOME` nor `HOME` are set",
))
}
}