Skip to content
Draft
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
77 changes: 73 additions & 4 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ subtle = "2.6"
zeroize = { version = "1.8.1", features = ["serde", "derive"] }
# Exact pin: canonicalization changes silently break all existing attestation signatures.
json-canon = "=0.1.3"
# Exact pin: pre-1.0 capability API — pin to avoid silent breaking changes.
capsec = { version = "=0.1.6", features = ["tokio"] }

auths-core = { path = "crates/auths-core", version = "0.0.1-rc.4" }
auths-id = { path = "crates/auths-id", version = "0.0.1-rc.4" }
Expand Down
1 change: 1 addition & 0 deletions crates/auths-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ url = "2.5"
which = "8.0.0"
open = "5"
indicatif = "0.18.4"
capsec.workspace = true

# LAN pairing (optional, default-on)
auths-pairing-daemon = { workspace = true, optional = true }
Expand Down
44 changes: 35 additions & 9 deletions crates/auths-cli/src/adapters/config_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,58 @@
use std::path::Path;

use auths_core::ports::config_store::{ConfigStore, ConfigStoreError};
use capsec::SendCap;

/// Reads and writes config files from the local filesystem.
pub struct FileConfigStore;
pub struct FileConfigStore {
_fs_read: SendCap<capsec::FsRead>,
fs_write: SendCap<capsec::FsWrite>,
}

impl FileConfigStore {
pub fn new(fs_read: SendCap<capsec::FsRead>, fs_write: SendCap<capsec::FsWrite>) -> Self {
Self {
_fs_read: fs_read,
fs_write,
}
}
}

impl ConfigStore for FileConfigStore {
fn read(&self, path: &Path) -> Result<Option<String>, ConfigStoreError> {
match std::fs::read_to_string(path) {
match capsec::fs::read_to_string(path, &self._fs_read) {
Ok(content) => Ok(Some(content)),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(capsec::CapSecError::Io(ref io_err))
if io_err.kind() == std::io::ErrorKind::NotFound =>
{
Ok(None)
}
Err(e) => Err(ConfigStoreError::Read {
path: path.to_path_buf(),
source: e,
source: capsec_to_io(e),
}),
}
}

fn write(&self, path: &Path, content: &str) -> Result<(), ConfigStoreError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| ConfigStoreError::Write {
path: path.to_path_buf(),
source: e,
capsec::fs::create_dir_all(parent, &self.fs_write).map_err(|e| {
ConfigStoreError::Write {
path: path.to_path_buf(),
source: capsec_to_io(e),
}
})?;
}
std::fs::write(path, content).map_err(|e| ConfigStoreError::Write {
capsec::fs::write(path, content, &self.fs_write).map_err(|e| ConfigStoreError::Write {
path: path.to_path_buf(),
source: e,
source: capsec_to_io(e),
})
}
}

fn capsec_to_io(e: capsec::CapSecError) -> std::io::Error {
match e {
capsec::CapSecError::Io(io) => io,
other => std::io::Error::other(other.to_string()),
}
}
18 changes: 13 additions & 5 deletions crates/auths-cli/src/adapters/git_config.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
use capsec::SendCap;
use std::path::PathBuf;

use auths_sdk::ports::git_config::{GitConfigError, GitConfigProvider};

/// System adapter for git signing configuration.
///
/// Runs `git config <scope> <key> <value>` via `std::process::Command`.
/// Construct with `SystemGitConfigProvider::global()` or
/// `SystemGitConfigProvider::local(repo_path)`.
/// Holds a `SendCap<Spawn>` to document subprocess execution.
///
/// Usage:
/// ```ignore
/// let provider = SystemGitConfigProvider::global();
/// let cap_root = capsec::test_root();
/// let provider = SystemGitConfigProvider::global(cap_root.spawn().make_send());
/// provider.set("gpg.format", "ssh")?;
/// ```
pub struct SystemGitConfigProvider {
scope_flag: &'static str,
working_dir: Option<PathBuf>,
_spawn_cap: SendCap<capsec::Spawn>,
}

impl SystemGitConfigProvider {
/// Creates a provider that sets git config in global scope.
pub fn global() -> Self {
///
/// Args:
/// * `spawn_cap`: Capability token proving the caller has subprocess execution permission.
pub fn global(spawn_cap: SendCap<capsec::Spawn>) -> Self {
Self {
scope_flag: "--global",
working_dir: None,
_spawn_cap: spawn_cap,
}
}

/// Creates a provider that sets git config in local scope for the given repo.
///
/// Args:
/// * `repo_path`: Path to the git repository to configure.
pub fn local(repo_path: PathBuf) -> Self {
/// * `spawn_cap`: Capability token proving the caller has subprocess execution permission.
pub fn local(repo_path: PathBuf, spawn_cap: SendCap<capsec::Spawn>) -> Self {
Self {
scope_flag: "--local",
working_dir: Some(repo_path),
_spawn_cap: spawn_cap,
}
}
}
Expand Down
16 changes: 11 additions & 5 deletions crates/auths-cli/src/adapters/local_file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Local filesystem artifact adapter.

use auths_sdk::ports::artifact::{ArtifactDigest, ArtifactError, ArtifactMetadata, ArtifactSource};
use capsec::SendCap;
use sha2::{Digest, Sha256};
use std::io::Read;
use std::path::{Path, PathBuf};
Expand All @@ -9,16 +10,21 @@ use std::path::{Path, PathBuf};
///
/// Usage:
/// ```ignore
/// let artifact = LocalFileArtifact::new("path/to/file.tar.gz");
/// let cap_root = capsec::test_root();
/// let artifact = LocalFileArtifact::new("path/to/file.tar.gz", cap_root.fs_read().make_send());
/// let digest = artifact.digest()?;
/// ```
pub struct LocalFileArtifact {
path: PathBuf,
fs_read: SendCap<capsec::FsRead>,
}

impl LocalFileArtifact {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
pub fn new(path: impl Into<PathBuf>, fs_read: SendCap<capsec::FsRead>) -> Self {
Self {
path: path.into(),
fs_read,
}
}

pub fn path(&self) -> &Path {
Expand All @@ -28,7 +34,7 @@ impl LocalFileArtifact {

impl ArtifactSource for LocalFileArtifact {
fn digest(&self) -> Result<ArtifactDigest, ArtifactError> {
let mut file = std::fs::File::open(&self.path)
let mut file = capsec::fs::open(&self.path, &self.fs_read)
.map_err(|e| ArtifactError::Io(format!("{}: {}", self.path.display(), e)))?;

let mut hasher = Sha256::new();
Expand All @@ -52,7 +58,7 @@ impl ArtifactSource for LocalFileArtifact {

fn metadata(&self) -> Result<ArtifactMetadata, ArtifactError> {
let digest = self.digest()?;
let file_meta = std::fs::metadata(&self.path)
let file_meta = capsec::fs::metadata(&self.path, &self.fs_read)
.map_err(|e| ArtifactError::Metadata(format!("{}: {}", self.path.display(), e)))?;

Ok(ArtifactMetadata {
Expand Down
18 changes: 15 additions & 3 deletions crates/auths-cli/src/bin/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use auths_core::storage::passphrase_cache::{get_passphrase_cache, parse_duration
use auths_sdk::workflows::signing::{
CommitSigningContext, CommitSigningParams, CommitSigningWorkflow,
};
use capsec::SendCap;

/// Auths SSH signing program for Git integration.
///
Expand Down Expand Up @@ -125,7 +126,11 @@ fn parse_key_identifier(key_file: &str) -> Result<String> {
}
}

fn build_signing_context(alias: &str) -> Result<CommitSigningContext> {
fn build_signing_context(
alias: &str,
fs_read: SendCap<capsec::FsRead>,
fs_write: SendCap<capsec::FsWrite>,
) -> Result<CommitSigningContext> {
let env_config = EnvironmentConfig::from_env();

let keychain =
Expand All @@ -135,7 +140,8 @@ fn build_signing_context(alias: &str) -> Result<CommitSigningContext> {
if let Some(passphrase) = env_config.keychain.passphrase.clone() {
Arc::new(auths_core::PrefilledPassphraseProvider::new(&passphrase))
} else {
let config = load_config(&FileConfigStore);
let store = FileConfigStore::new(fs_read, fs_write);
let config = load_config(&store);
let cache = get_passphrase_cache(config.passphrase.biometric);
let ttl_secs = config
.passphrase
Expand Down Expand Up @@ -260,6 +266,8 @@ fn run_delegate_to_ssh_keygen(args: &Args) -> Result<()> {
}

fn run_sign(args: &Args) -> Result<()> {
let cap_root = capsec::root();

let file_arg = args
.file_arg
.as_deref()
Expand All @@ -279,7 +287,11 @@ fn run_sign(args: &Args) -> Result<()> {

let repo_path = auths_id::storage::layout::resolve_repo_path(None).ok();

let ctx = build_signing_context(&alias)?;
let ctx = build_signing_context(
&alias,
cap_root.fs_read().make_send(),
cap_root.fs_write().make_send(),
)?;
let mut params = CommitSigningParams::new(&alias, namespace, data).with_pubkey(pubkey);
if let Some(path) = repo_path {
params = params.with_repo_path(path);
Expand Down
Loading
Loading