Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 53 additions & 8 deletions crates/gitlawb-node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,23 @@ fn load_or_create_keypair(config: &Config) -> Result<Keypair> {
let key_path = config.resolved_key_path();

if key_path.exists() {
#[cfg(unix)]
let pem = {
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
// O_NOFOLLOW so a planted symlink can't redirect the read to an
// attacker-chosen file presented as the node identity.
let mut f = std::fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(&key_path)
.with_context(|| format!("failed to read key from {}", key_path.display()))?;
let mut pem = String::new();
f.read_to_string(&mut pem)
.with_context(|| format!("failed to read key from {}", key_path.display()))?;
pem
};
#[cfg(not(unix))]
let pem = std::fs::read_to_string(&key_path)
.with_context(|| format!("failed to read key from {}", key_path.display()))?;
let kp = Keypair::from_pem(&pem).map_err(|e| anyhow::anyhow!("invalid PEM key: {e}"))?;
Expand All @@ -1375,18 +1392,46 @@ fn load_or_create_keypair(config: &Config) -> Result<Keypair> {
.to_pem()
.map_err(|e| anyhow::anyhow!("failed to serialize key: {e}"))?;

if let Some(parent) = key_path.parent() {
std::fs::create_dir_all(parent)?;
}

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::write(&key_path, pem.as_bytes())?;
std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))?;
use std::io::Write;
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt};

if let Some(parent) = key_path.parent() {
std::fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(parent)?;
Comment thread
beardthelion marked this conversation as resolved.
// Re-pin a pre-existing permissive dir, but never through a
// symlink: chmod would land on the link's target.
if !std::fs::symlink_metadata(parent)?.file_type().is_symlink() {
std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))?;
}
}

// Pin the mode at creation: a plain write then chmod leaves the
// key world-readable between the two syscalls, and O_NOFOLLOW
// refuses to write through a pre-planted symlink.
let mut f = std::fs::OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.custom_flags(libc::O_NOFOLLOW)
.open(&key_path)?;
// mode() only applies when the file is created; pin on the
// descriptor before truncating so a pre-existing loose file is
// tightened before the new contents land in it.
f.set_permissions(std::fs::Permissions::from_mode(0o600))?;
f.set_len(0)?;
f.write_all(pem.as_bytes())?;
}
#[cfg(not(unix))]
Comment thread
beardthelion marked this conversation as resolved.
std::fs::write(&key_path, pem.as_bytes())?;
{
if let Some(parent) = key_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&key_path, pem.as_bytes())?;
}

info!(path = %key_path.display(), did = %kp.did(), "generated new node identity");
Ok(kp)
Expand Down
1 change: 1 addition & 0 deletions crates/gl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dirs = "5"
reqwest = { workspace = true }
uuid = { workspace = true }
urlencoding = "2"
libc = "0.2"
alloy = { version = "1", default-features = false, features = [
"contract",
"provider-http",
Expand Down
38 changes: 5 additions & 33 deletions crates/gl/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,13 @@ async fn cmd_new_with_reader(
}
}

fs::create_dir_all(&dir)
crate::secret_file::create_dir(&dir)
.with_context(|| format!("failed to create directory {}", dir.display()))?;

let keypair = Keypair::generate();
let pem = keypair.to_pem()?;

// Write with restricted permissions
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::write(&path, pem.as_bytes())?;
fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
}
#[cfg(not(unix))]
{
fs::write(&path, pem.as_bytes())?;
}
crate::secret_file::write(&path, pem.as_bytes())?;

let did = keypair.did();
println!("✓ Generated new identity");
Expand Down Expand Up @@ -202,16 +192,7 @@ async fn cmd_backup(out: Option<PathBuf>, dir: Option<PathBuf>) -> Result<()> {
.join("identity.pem.bak")
});

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::write(&dest, pem.as_bytes())?;
fs::set_permissions(&dest, fs::Permissions::from_mode(0o600))?;
}
#[cfg(not(unix))]
{
fs::write(&dest, pem.as_bytes())?;
}
crate::secret_file::write(&dest, pem.as_bytes())?;

println!("✓ Identity backed up");
println!(" DID: {}", keypair.did());
Expand Down Expand Up @@ -262,19 +243,10 @@ async fn cmd_restore_with_reader(
}
}

fs::create_dir_all(&base)
crate::secret_file::create_dir(&base)
.with_context(|| format!("failed to create directory {}", base.display()))?;

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::write(&dest, pem.as_bytes())?;
fs::set_permissions(&dest, fs::Permissions::from_mode(0o600))?;
}
#[cfg(not(unix))]
{
fs::write(&dest, pem.as_bytes())?;
}
crate::secret_file::write(&dest, pem.as_bytes())?;

println!("✓ Identity restored");
println!(" DID: {}", keypair.did());
Expand Down
21 changes: 6 additions & 15 deletions crates/gl/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ pub async fn run(args: InitArgs) -> Result<()> {
.dir
.clone()
.unwrap_or_else(|| dirs::home_dir().unwrap_or_default().join(".gitlawb"));
std::fs::create_dir_all(&ucan_dir)?;
crate::secret_file::create_dir(&ucan_dir)?;
let record = json!({
"ucan": ucan,
"node": args.node,
"did": did.to_string(),
"saved_at": chrono::Utc::now().to_rfc3339(),
});
std::fs::write(
ucan_dir.join("ucan.json"),
serde_json::to_string_pretty(&record)?,
crate::secret_file::write(
&ucan_dir.join("ucan.json"),
serde_json::to_string_pretty(&record)?.as_bytes(),
)?;
}
}
Expand Down Expand Up @@ -228,22 +228,13 @@ fn generate_identity(dir: Option<&std::path::Path>) -> Result<gitlawb_core::iden
.context("could not determine home directory")?
.join(".gitlawb")
};
std::fs::create_dir_all(&base)?;
crate::secret_file::create_dir(&base)?;

let keypair = gitlawb_core::identity::Keypair::generate();
let pem = keypair.to_pem()?;
let path = base.join("identity.pem");

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::write(&path, pem.as_bytes())?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
}
#[cfg(not(unix))]
{
std::fs::write(&path, pem.as_bytes())?;
}
crate::secret_file::write(&path, pem.as_bytes())?;

Ok(keypair)
}
Expand Down
1 change: 1 addition & 0 deletions crates/gl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod protect;
mod quickstart;
mod register;
mod repo;
mod secret_file;
mod star;
mod status;
mod sync;
Expand Down
23 changes: 9 additions & 14 deletions crates/gl/src/quickstart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,17 @@ pub async fn run(args: QuickstartArgs) -> Result<()> {
let payload: Value = resp.json().await.unwrap_or_default();
let ucan = payload["ucan"].as_str().unwrap_or("");
if !ucan.is_empty() {
std::fs::create_dir_all(&dir)?;
crate::secret_file::create_dir(&dir)?;
let record = json!({
"ucan": ucan,
"node": args.node,
"did": did,
"saved_at": chrono::Utc::now().to_rfc3339(),
});
std::fs::write(&ucan_path, serde_json::to_string_pretty(&record)?)?;
crate::secret_file::write(
&ucan_path,
serde_json::to_string_pretty(&record)?.as_bytes(),
)?;
}
let trust = payload["trust_score"].as_f64().unwrap_or(0.0);
println!(" ✓ Registered successfully");
Expand Down Expand Up @@ -226,23 +229,15 @@ pub async fn run(args: QuickstartArgs) -> Result<()> {

// ── Helpers ───────────────────────────────────────────────────────────────

fn generate_identity(dir: &PathBuf) -> Result<gitlawb_core::identity::Keypair> {
std::fs::create_dir_all(dir).with_context(|| format!("failed to create {}", dir.display()))?;
fn generate_identity(dir: &std::path::Path) -> Result<gitlawb_core::identity::Keypair> {
crate::secret_file::create_dir(dir)
.with_context(|| format!("failed to create {}", dir.display()))?;

let keypair = gitlawb_core::identity::Keypair::generate();
let pem = keypair.to_pem()?;
let path = dir.join("identity.pem");

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::write(&path, pem.as_bytes())?;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
}
#[cfg(not(unix))]
{
std::fs::write(&path, pem.as_bytes())?;
}
crate::secret_file::write(&path, pem.as_bytes())?;

let did = keypair.did();
println!(" ✓ Generated new identity");
Expand Down
7 changes: 5 additions & 2 deletions crates/gl/src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ pub async fn run(args: RegisterArgs) -> Result<()> {
"did": did.to_string(),
"saved_at": chrono::Utc::now().to_rfc3339(),
});
std::fs::write(&ucan_path, serde_json::to_string_pretty(&record)?)?;
crate::secret_file::write(
&ucan_path,
serde_json::to_string_pretty(&record)?.as_bytes(),
)?;
tracing::debug!("saved UCAN to {}", ucan_path.display());
}

Expand Down Expand Up @@ -113,7 +116,7 @@ fn ucan_path(dir: Option<&std::path::Path>) -> Result<PathBuf> {
.context("could not determine home directory")?
.join(".gitlawb")
};
std::fs::create_dir_all(&base)?;
crate::secret_file::create_dir(&base)?;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Ok(base.join("ucan.json"))
}

Expand Down
Loading
Loading