Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sign: Define signing backend API and integrate it #2634

Merged
merged 2 commits into from
Nov 30, 2023
Merged
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.

5 changes: 4 additions & 1 deletion cli/examples/custom-backend/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use jj_lib::git_backend::GitBackend;
use jj_lib::repo::StoreFactories;
use jj_lib::repo_path::RepoPath;
use jj_lib::settings::UserSettings;
use jj_lib::workspace::Workspace;
use jj_lib::signing::Signer;
use jj_lib::workspace::{Workspace, WorkspaceInitError};

#[derive(clap::Parser, Clone, Debug)]
enum CustomCommands {
Expand Down Expand Up @@ -59,6 +60,8 @@ fn run_custom_command(
command_helper.settings(),
wc_path,
&|settings, store_path| Ok(Box::new(JitBackend::init(settings, store_path)?)),
Signer::from_settings(command_helper.settings())
.map_err(WorkspaceInitError::SignInit)?,
)?;
Ok(())
}
Expand Down
7 changes: 6 additions & 1 deletion cli/examples/custom-working-copy/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ use jj_lib::op_store::{OperationId, WorkspaceId};
use jj_lib::repo::ReadonlyRepo;
use jj_lib::repo_path::RepoPathBuf;
use jj_lib::settings::UserSettings;
use jj_lib::signing::Signer;
use jj_lib::store::Store;
use jj_lib::working_copy::{
CheckoutError, CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, SnapshotOptions,
WorkingCopy, WorkingCopyStateError,
};
use jj_lib::workspace::{default_working_copy_factories, WorkingCopyInitializer, Workspace};
use jj_lib::workspace::{
default_working_copy_factories, WorkingCopyInitializer, Workspace, WorkspaceInitError,
};

#[derive(clap::Parser, Clone, Debug)]
enum CustomCommands {
Expand All @@ -58,6 +61,8 @@ fn run_custom_command(
command_helper.settings(),
wc_path,
&backend_initializer,
Signer::from_settings(command_helper.settings())
.map_err(WorkspaceInitError::SignInit)?,
&ReadonlyRepo::default_op_store_initializer(),
&ReadonlyRepo::default_op_heads_store_initializer(),
&ReadonlyRepo::default_index_store_initializer(),
Expand Down
9 changes: 9 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ use jj_lib::revset::{
};
use jj_lib::rewrite::restore_tree;
use jj_lib::settings::{ConfigResultExt as _, UserSettings};
use jj_lib::signing::SignInitError;
use jj_lib::str_util::{StringPattern, StringPatternParseError};
use jj_lib::transaction::Transaction;
use jj_lib::tree::TreeMergeError;
Expand Down Expand Up @@ -194,6 +195,10 @@ impl From<WorkspaceInitError> for CommandError {
WorkspaceInitError::WorkingCopyState(err) => {
CommandError::InternalError(format!("Failed to access the repository: {err}"))
}
WorkspaceInitError::SignInit(err @ SignInitError::UnknownBackend(_)) => {
user_error(format!("{err}"))
}
WorkspaceInitError::SignInit(err) => CommandError::InternalError(format!("{err}")),
}
}
}
Expand Down Expand Up @@ -1655,6 +1660,10 @@ jj init --git-repo=.",
) => CommandError::InternalError(format!(
"The repository appears broken or inaccessible: {err}"
)),
WorkspaceLoadError::StoreLoadError(StoreLoadError::Signing(
err @ SignInitError::UnknownBackend(_),
)) => user_error(format!("{err}")),
WorkspaceLoadError::StoreLoadError(err) => CommandError::InternalError(format!("{err}")),
}
}

Expand Down
25 changes: 25 additions & 0 deletions cli/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,31 @@
"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"
},
"key": {
"type": "string",
"description": "The key parameter to pass to the signing backend. Overridden by `jj sign` parameter or by the global `--sign-with` option"
},
"sign-all": {
"type": "boolean",
"description": "Whether to sign all commits by default. Overridden by global `--no-sign` option",
"default": false
},
"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, RepoPathComponentBuf};
use crate::signing::SignResult;

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]) -> SignResult<Vec<u8>>>;

/// 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
15 changes: 15 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::{SignResult, Verification};
use crate::store::Store;

#[derive(Clone)]
Expand Down Expand Up @@ -146,6 +147,20 @@ 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) -> SignResult<Option<Verification>> {
self.data
.secure_sig
.as_ref()
.map(|sig| self.store.signer().verify(&self.id, &sig.data, &sig.sig))
.transpose()
}
}

/// Wrapper to sort `Commit` by committer timestamp.
Expand Down
42 changes: 38 additions & 4 deletions lib/src/commit_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@

use std::sync::Arc;

use crate::backend::{self, BackendResult, ChangeId, CommitId, MergedTreeId, Signature};
use crate::backend::{self, BackendResult, ChangeId, CommitId, MergedTreeId, Signature, SigningFn};
use crate::commit::Commit;
use crate::repo::{MutableRepo, Repo};
use crate::settings::{JJRng, UserSettings};
use crate::settings::{JJRng, SignSettings, UserSettings};
use crate::signing::SignBehavior;

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

impl CommitBuilder<'_> {
Expand Down Expand Up @@ -55,6 +57,7 @@ impl CommitBuilder<'_> {
rng,
commit,
rewrite_source: None,
sign_settings: settings.sign_settings(),
}
}

Expand Down Expand Up @@ -83,6 +86,7 @@ impl CommitBuilder<'_> {
commit,
rng: settings.get_rng(),
rewrite_source: Some(predecessor.clone()),
sign_settings: settings.sign_settings(),
}
}

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

pub fn write(self) -> BackendResult<Commit> {
pub fn sign_settings(&self) -> &SignSettings {
&self.sign_settings
}

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

pub fn set_sign_key(mut self, sign_key: Option<String>) -> Self {
self.sign_settings.key = sign_key;
self
}

pub fn write(mut 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_settings = self.sign_settings;
let store = self.mut_repo.store();

let signing_fn = (store.signer().can_sign() && sign_settings.should_sign(&self.commit))
.then(|| {
let store = store.clone();
Box::new(move |data: &_| store.signer().sign(data, sign_settings.key.as_deref()))
as SigningFn
});

// Commit backend doesn't use secure_sig for writing and enforces it with an
// assert, but sign_settings.should_sign check above will want to know
// if we're rewriting a signed commit
self.commit.secure_sig = None;

let commit = self.mut_repo.write_commit(self.commit, signing_fn)?;
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
18 changes: 14 additions & 4 deletions lib/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use tracing::instrument;
use self::dirty_cell::DirtyCell;
use crate::backend::{
Backend, BackendError, BackendInitError, BackendLoadError, BackendResult, ChangeId, CommitId,
MergedTreeId, ObjectId,
MergedTreeId, ObjectId, SigningFn,
};
use crate::commit::{Commit, CommitByCommitterTimestamp};
use crate::commit_builder::CommitBuilder;
Expand All @@ -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::{SignInitError, Signer};
use crate::simple_op_heads_store::SimpleOpHeadsStore;
use crate::simple_op_store::SimpleOpStore;
use crate::store::Store;
Expand Down Expand Up @@ -140,10 +141,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: Signer,
op_store_initializer: &OpStoreInitializer,
op_heads_store_initializer: &OpHeadsStoreInitializer,
index_store_initializer: &IndexStoreInitializer,
Expand All @@ -156,7 +159,7 @@ 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 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 @@ -418,6 +421,8 @@ pub enum StoreLoadError {
},
#[error(transparent)]
Backend(#[from] BackendLoadError),
#[error(transparent)]
Signing(#[from] SignInitError),
}

impl StoreFactories {
Expand Down Expand Up @@ -608,6 +613,7 @@ impl RepoLoader {
) -> Result<Self, StoreLoadError> {
let store = Store::new(
store_factories.load_backend(user_settings, &repo_path.join("store"))?,
Signer::from_settings(user_settings)?,
user_settings.use_tree_conflict_format(),
);
let repo_settings = user_settings.with_repo(repo_path).unwrap();
Expand Down Expand Up @@ -792,8 +798,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_with: Option<SigningFn>,
) -> BackendResult<Commit> {
let commit = self.store().write_commit(commit, sign_with)?;
self.add_head(&commit);
Ok(commit)
}
Expand Down
Loading