Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Initialize `radicle-tools` crate
Alexis Sellier committed 3 years ago
commit a3bf5ab7c3c6df038bb49e72073a9e43f89ad92d
parent fcb1007f9d2f0873ab6f89c0445836c500f412b0
11 files changed +223 -1
modified Cargo.lock
@@ -827,6 +827,14 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -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"
added radicle-tools/Cargo.toml
@@ -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"
added radicle-tools/src/rad-auth.rs
@@ -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(())
+
}
added radicle-tools/src/rad-init.rs
@@ -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(())
+
}
added radicle-tools/src/rad-self.rs
@@ -0,0 +1,8 @@
+
fn main() -> anyhow::Result<()> {
+
    let profile = radicle::Profile::load()?;
+

+
    println!("id: {}", profile.id());
+
    println!("home: {}", profile.home.display());
+

+
    Ok(())
+
}
modified radicle/src/crypto.rs
@@ -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;

modified radicle/src/git.rs
@@ -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;
added radicle/src/keystore.rs
@@ -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))
+
    }
+
}
modified radicle/src/lib.rs
@@ -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;
added radicle/src/profile.rs
@@ -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",
+
        ))
+
    }
+
}