Skip to content

Commit

Permalink
sign: Define signing backend API and interate it
Browse files Browse the repository at this point in the history
No actual signer implementations yet, but abstract integration is
mostly done here
  • Loading branch information
necauqua committed Nov 25, 2023
1 parent 4f2503c commit b5201b0
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 15 deletions.
1 change: 1 addition & 0 deletions cli/examples/custom-working-copy/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn run_custom_command(
command_helper.settings(),
wc_path,
&backend_initializer,
&ReadonlyRepo::default_signer_initializer(),
&ReadonlyRepo::default_op_store_initializer(),
&ReadonlyRepo::default_op_heads_store_initializer(),
&ReadonlyRepo::default_index_store_initializer(),
Expand Down
16 changes: 16 additions & 0 deletions cli/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,22 @@
"default": "1MiB"
}
}
},
"signing": {
"type": "object",
"description": "Settings for verifying and creating cryptographic commit signatures",
"properties": {
"backend": {
"type": "string",
"description": "Which backend to use to create commit signatures"
},
"backends": {
"type": "object",
"description": "Tables of options to pass to specific signing backends",
"properties": {},
"additionalProperties": true
}
}
}
}
}
3 changes: 2 additions & 1 deletion lib/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use thiserror::Error;
use crate::content_hash::ContentHash;
use crate::merge::Merge;
use crate::repo_path::{RepoPath, RepoPathComponent};
use crate::signing::SignError;

pub trait ObjectId {
fn new(value: Vec<u8>) -> Self;
Expand Down Expand Up @@ -147,7 +148,7 @@ content_hash! {
}
}

pub type SigningFn = Box<dyn FnMut(&[u8]) -> BackendResult<Vec<u8>>>;
pub type SigningFn = Box<dyn FnMut(&[u8]) -> Result<Vec<u8>, SignError>>;

/// Identifies a single legacy tree, which may have path-level conflicts, or a
/// merge of multiple trees, where the individual trees do not have conflicts.
Expand Down
14 changes: 14 additions & 0 deletions lib/src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use std::sync::Arc;
use crate::backend;
use crate::backend::{BackendError, ChangeId, CommitId, MergedTreeId, Signature};
use crate::merged_tree::MergedTree;
use crate::signing::Verification;
use crate::store::Store;

#[derive(Clone)]
Expand Down Expand Up @@ -146,6 +147,19 @@ impl Commit {
}
false
}

/// A quick way to just check if a signature is present.
pub fn is_signed(&self) -> bool {
self.data.secure_sig.is_some()
}

/// A slow (but cached) way to get the full verification.
pub fn verification(&self) -> Option<Verification> {
self.data
.secure_sig
.as_ref()
.map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig))
}
}

/// Wrapper to sort `Commit` by committer timestamp.
Expand Down
22 changes: 21 additions & 1 deletion lib/src/commit_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ use crate::backend::{self, BackendResult, ChangeId, CommitId, MergedTreeId, Sign
use crate::commit::Commit;
use crate::repo::{MutableRepo, Repo};
use crate::settings::{JJRng, UserSettings};
use crate::signing::{SignBehavior, SignConfig};

#[must_use]
pub struct CommitBuilder<'repo> {
mut_repo: &'repo mut MutableRepo,
rng: Arc<JJRng>,
commit: backend::Commit,
rewrite_source: Option<Commit>,
user_email: String,
sign_behavior: SignBehavior,
}

impl CommitBuilder<'_> {
Expand Down Expand Up @@ -55,6 +58,8 @@ impl CommitBuilder<'_> {
rng,
commit,
rewrite_source: None,
user_email: settings.user_email(),
sign_behavior: SignBehavior::Drop,
}
}

Expand Down Expand Up @@ -83,6 +88,8 @@ impl CommitBuilder<'_> {
commit,
rng: settings.get_rng(),
rewrite_source: Some(predecessor.clone()),
user_email: settings.user_email(),
sign_behavior: SignBehavior::Drop,
}
}

Expand Down Expand Up @@ -157,14 +164,27 @@ impl CommitBuilder<'_> {
self
}

pub fn sign_behavior(&self) -> SignBehavior {
self.sign_behavior
}

pub fn set_sign_behavior(mut self, sign_behavior: SignBehavior) -> Self {
self.sign_behavior = sign_behavior;
self
}

pub fn write(self) -> BackendResult<Commit> {
let mut rewrite_source_id = None;
if let Some(rewrite_source) = self.rewrite_source {
if *rewrite_source.change_id() == self.commit.change_id {
rewrite_source_id.replace(rewrite_source.id().clone());
}
}
let commit = self.mut_repo.write_commit(self.commit)?;
let sign_config = SignConfig {
behavior: self.sign_behavior,
user_email: &self.user_email,
};
let commit = self.mut_repo.write_commit(self.commit, sign_config)?;
if let Some(rewrite_source_id) = rewrite_source_id {
self.mut_repo
.record_rewritten_commit(rewrite_source_id, commit.id().clone())
Expand Down
5 changes: 4 additions & 1 deletion lib/src/git_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,10 @@ impl Backend for GitBackend {
let mut data = Vec::with_capacity(512);
commit.write_to(&mut data).unwrap();

let sig = sign(&data)?;
let sig = sign(&data).map_err(|err| BackendError::WriteObject {
object_type: "commit",
source: Box::new(err),
})?;
commit
.extra_headers
.push(("gpgsig".into(), sig.clone().into()));
Expand Down
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub mod revset;
pub mod revset_graph;
pub mod rewrite;
pub mod settings;
pub mod signing;
pub mod simple_op_heads_store;
pub mod simple_op_store;
pub mod stacked_table;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/local_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl Backend for LocalBackend {
let mut proto = commit_to_proto(&commit);
if let Some(mut sign) = sign_with {
let data = proto.encode_to_vec();
let sig = sign(&data)?;
let sig = sign(&data).map_err(to_other_err)?;
proto.secure_sig = Some(sig.clone());
commit.secure_sig = Some(SecureSig { data, sig });
}
Expand Down
53 changes: 50 additions & 3 deletions lib/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use crate::refs::{
use crate::revset::{self, ChangeIdIndex, Revset, RevsetExpression};
use crate::rewrite::{DescendantRebaser, RebaseOptions};
use crate::settings::{RepoSettings, UserSettings};
use crate::signing::{SignConfig, Signer, SigningBackend};
use crate::simple_op_heads_store::SimpleOpHeadsStore;
use crate::simple_op_store::SimpleOpStore;
use crate::store::Store;
Expand Down Expand Up @@ -121,6 +122,18 @@ pub enum RepoInitError {
}

impl ReadonlyRepo {
pub fn default_signer_initializer() -> &'static SignerInitializer {
&|_settings| {
// let config = settings.signing_backend();
let /* mut */ main_backend = None;
let /* mut */ other_backends = Vec::new();

// todo add signing backend impls here

Signer::new(main_backend, other_backends)
}
}

pub fn default_op_store_initializer() -> &'static OpStoreInitializer {
&|_settings, store_path| Box::new(SimpleOpStore::init(store_path))
}
Expand All @@ -140,10 +153,12 @@ impl ReadonlyRepo {
&|_settings, store_path| Box::new(DefaultSubmoduleStore::init(store_path))
}

#[allow(clippy::too_many_arguments)]
pub fn init(
user_settings: &UserSettings,
repo_path: &Path,
backend_initializer: &BackendInitializer,
signer_initializer: &SignerInitializer,
op_store_initializer: &OpStoreInitializer,
op_heads_store_initializer: &OpHeadsStoreInitializer,
index_store_initializer: &IndexStoreInitializer,
Expand All @@ -156,7 +171,8 @@ impl ReadonlyRepo {
let backend = backend_initializer(user_settings, &store_path)?;
let backend_path = store_path.join("type");
fs::write(&backend_path, backend.name()).context(&backend_path)?;
let store = Store::new(backend, user_settings.use_tree_conflict_format());
let signer = signer_initializer(user_settings);
let store = Store::new(backend, signer, user_settings.use_tree_conflict_format());
let repo_settings = user_settings.with_repo(&repo_path).unwrap();

let op_store_path = repo_path.join("op_store");
Expand Down Expand Up @@ -346,16 +362,19 @@ pub type OpStoreInitializer = dyn Fn(&UserSettings, &Path) -> Box<dyn OpStore>;
pub type OpHeadsStoreInitializer = dyn Fn(&UserSettings, &Path) -> Box<dyn OpHeadsStore>;
pub type IndexStoreInitializer = dyn Fn(&UserSettings, &Path) -> Box<dyn IndexStore>;
pub type SubmoduleStoreInitializer = dyn Fn(&UserSettings, &Path) -> Box<dyn SubmoduleStore>;
pub type SignerInitializer = dyn Fn(&UserSettings) -> Signer;

type BackendFactory =
Box<dyn Fn(&UserSettings, &Path) -> Result<Box<dyn Backend>, BackendLoadError>>;
type OpStoreFactory = Box<dyn Fn(&UserSettings, &Path) -> Box<dyn OpStore>>;
type OpHeadsStoreFactory = Box<dyn Fn(&UserSettings, &Path) -> Box<dyn OpHeadsStore>>;
type IndexStoreFactory = Box<dyn Fn(&UserSettings, &Path) -> Box<dyn IndexStore>>;
type SubmoduleStoreFactory = Box<dyn Fn(&UserSettings, &Path) -> Box<dyn SubmoduleStore>>;
type SigningBackendFactory = Box<dyn Fn(&UserSettings) -> Box<dyn SigningBackend>>;

pub struct StoreFactories {
backend_factories: HashMap<String, BackendFactory>,
signing_backend_factories: HashMap<String, SigningBackendFactory>,
op_store_factories: HashMap<String, OpStoreFactory>,
op_heads_store_factories: HashMap<String, OpHeadsStoreFactory>,
index_store_factories: HashMap<String, IndexStoreFactory>,
Expand Down Expand Up @@ -424,6 +443,7 @@ impl StoreFactories {
pub fn empty() -> Self {
StoreFactories {
backend_factories: HashMap::new(),
signing_backend_factories: HashMap::new(),
op_store_factories: HashMap::new(),
op_heads_store_factories: HashMap::new(),
index_store_factories: HashMap::new(),
Expand All @@ -435,6 +455,11 @@ impl StoreFactories {
self.backend_factories.insert(name.to_string(), factory);
}

pub fn add_signing_backend(&mut self, name: &str, factory: SigningBackendFactory) {
self.signing_backend_factories
.insert(name.to_string(), factory);
}

pub fn load_backend(
&self,
settings: &UserSettings,
Expand Down Expand Up @@ -462,6 +487,23 @@ impl StoreFactories {
Ok(backend_factory(settings, store_path)?)
}

pub fn load_signer(&self, settings: &UserSettings) -> Result<Signer, StoreLoadError> {
let config = settings.signing_backend();

let mut main_backend = None;
let mut other_backends = Vec::new();

for (name, factory) in &self.signing_backend_factories {
if config.as_deref() == Some(name) {
main_backend = Some(factory(settings));
continue;
}
other_backends.push(factory(settings));
}

Ok(Signer::new(main_backend, other_backends))
}

pub fn add_op_store(&mut self, name: &str, factory: OpStoreFactory) {
self.op_store_factories.insert(name.to_string(), factory);
}
Expand Down Expand Up @@ -608,6 +650,7 @@ impl RepoLoader {
) -> Result<Self, StoreLoadError> {
let store = Store::new(
store_factories.load_backend(user_settings, &repo_path.join("store"))?,
store_factories.load_signer(user_settings)?,
user_settings.use_tree_conflict_format(),
);
let repo_settings = user_settings.with_repo(repo_path).unwrap();
Expand Down Expand Up @@ -792,8 +835,12 @@ impl MutableRepo {
CommitBuilder::for_rewrite_from(self, settings, predecessor)
}

pub fn write_commit(&mut self, commit: backend::Commit) -> BackendResult<Commit> {
let commit = self.store().write_commit(commit)?;
pub fn write_commit(
&mut self,
commit: backend::Commit,
sign_config: SignConfig,
) -> BackendResult<Commit> {
let commit = self.store().write_commit(commit, sign_config)?;
self.add_head(&commit);
Ok(commit)
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ impl UserSettings {
e @ Err(_) => e,
}
}

pub fn signing_backend(&self) -> Option<String> {
self.config.get_string("signing.backend").ok()
}
}

/// This Rng uses interior mutability to allow generating random values using an
Expand Down
Loading

0 comments on commit b5201b0

Please sign in to comment.