Skip to content

Commit

Permalink
Add ssh signing backend tests
Browse files Browse the repository at this point in the history
  • Loading branch information
julienvincent committed Feb 11, 2024
1 parent 762b19e commit d72a9ee
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 2 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ jobs:
toolchain: 1.71
- name: Build
run: cargo build --workspace --all-targets --verbose ${{ matrix.cargo_flags }}
- name: Start Signing Agents
run: |
source <(gpg-agent --daemon)
eval `ssh-agent -s`
- name: Test
run: cargo test --workspace --all-targets --verbose ${{ matrix.cargo_flags }}
env:
Expand Down
6 changes: 5 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@
installShellFiles
makeWrapper
pkg-config
gnupg # for signing tests

# for signing tests
gnupg
openssh
] ++ linuxNativeDeps;
buildInputs = with pkgs; [
openssl zstd libgit2 libssh2
Expand Down Expand Up @@ -147,6 +150,7 @@

# To run the signing tests
gnupg
openssh

# For building the documentation website
poetry
Expand Down
3 changes: 2 additions & 1 deletion lib/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod test_default_revset_graph_iterator;
mod test_diff_summary;
mod test_git;
mod test_git_backend;
mod test_gpg;
mod test_id_prefix;
mod test_index;
mod test_init;
Expand All @@ -29,6 +30,6 @@ mod test_refs;
mod test_revset;
mod test_rewrite;
mod test_signing;
mod test_ssh_signing;
mod test_view;
mod test_workspace;
mod test_gpg;
164 changes: 164 additions & 0 deletions lib/tests/test_ssh_signing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use std::io::Write;
use std::process::Stdio;

use jj_lib::signing::{SigStatus, SigningBackend};
use jj_lib::ssh_signing::SshBackend;

static PRIVATE_KEY: &str = r#"-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBo/iejekjvuD/HTman0daImstssYYR52oB+dmr1KsOYQAAAIiuGFMFrhhT
BQAAAAtzc2gtZWQyNTUxOQAAACBo/iejekjvuD/HTman0daImstssYYR52oB+dmr1KsOYQ
AAAECcUtn/J/jk/+D5+/+WbQRNN4eInj5L60pt6FioP0nQfGj+J6N6SO+4P8dOZqfR1oia
y2yxhhHnagH52avUqw5hAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----
"#;

static PUBLIC_KEY: &str =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5h";

struct SshEnvironment {
private_key: Option<tempfile::TempPath>,
allowed_signers: Option<tempfile::TempPath>,
}

impl SshEnvironment {
fn new() -> Result<Self, std::process::Output> {
let mut private_key = tempfile::Builder::new()
.prefix("jj-test-pk-")
.tempfile()
.unwrap();

private_key.write_all(PRIVATE_KEY.as_bytes()).unwrap();
private_key.flush().unwrap();

let private_key_path = private_key.into_temp_path();

let ssh_add = std::process::Command::new("ssh-add")
.arg(private_key_path.as_os_str())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();

let res = ssh_add.wait_with_output().unwrap();
if !res.status.success() {
println!("Failed to add private key to ssh-agent. Make sure it is running!");
println!("{}", String::from_utf8_lossy(&res.stderr));
return Err(res);
}

let mut env = SshEnvironment {
private_key: Some(private_key_path),
allowed_signers: None,
};

env.with_good_public_key();

Ok(env)
}

fn with_good_public_key(&mut self) {
let mut allowed_signers = tempfile::Builder::new()
.prefix("jj-test-allowed-signers-")
.tempfile()
.unwrap();

allowed_signers
.write_all("[email protected] ".as_bytes())
.unwrap();
allowed_signers.write_all(PUBLIC_KEY.as_bytes()).unwrap();
allowed_signers.flush().unwrap();

let allowed_signers_path = allowed_signers.into_temp_path();

self.allowed_signers = Some(allowed_signers_path);
}

fn with_bad_public_key(&mut self) {
let mut allowed_signers = tempfile::Builder::new()
.prefix("jj-test-allowed-signers-")
.tempfile()
.unwrap();

allowed_signers
.write_all("[email protected] ".as_bytes())
.unwrap();
allowed_signers
.write_all("INVALID PUBLIC KEY".as_bytes())
.unwrap();
allowed_signers.flush().unwrap();

let allowed_signers_path = allowed_signers.into_temp_path();

self.allowed_signers = Some(allowed_signers_path);
}
}

impl Drop for SshEnvironment {
fn drop(&mut self) {
let mut ssh_add = std::process::Command::new("ssh-add")
.arg("-d")
.arg("-")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();

ssh_add
.stdin
.as_mut()
.unwrap()
.write_all(PUBLIC_KEY.as_bytes())
.unwrap();

ssh_add.stdin.as_mut().unwrap().flush().unwrap();

ssh_add.wait().unwrap();
drop(self.private_key.take());
drop(self.allowed_signers.take());
}
}

fn backend(env: &SshEnvironment) -> SshBackend {
SshBackend::new(
"ssh-keygen".into(),
Some(env.allowed_signers.as_ref().unwrap().as_os_str().into()),
)
}

#[test]
fn roundtrip() {
let env = SshEnvironment::new().unwrap();
let backend = backend(&env);
let data = b"hello world";

let signature = backend.sign(data, Some(PUBLIC_KEY)).unwrap();

let check = backend.verify(data, &signature).unwrap();
assert_eq!(check.status, SigStatus::Good);
assert_eq!(check.backend(), None); // backend is set by the signer

assert_eq!(check.display.unwrap(), "[email protected]");

let check = backend.verify(b"invalid-commit-data", &signature).unwrap();
assert_eq!(check.status, SigStatus::Bad);
assert_eq!(check.backend(), None);
assert_eq!(check.display.unwrap(), "[email protected]");
}

#[test]
fn bad_allowed_signers() {
let mut env = SshEnvironment::new().unwrap();
env.with_bad_public_key();

let backend = backend(&env);
let data = b"hello world";

let signature = backend.sign(data, Some(PUBLIC_KEY)).unwrap();

let check = backend.verify(data, &signature).unwrap();
assert_eq!(check.status, SigStatus::Unknown);
assert_eq!(check.display.unwrap(), "Signature OK. Unknown principal");
}

0 comments on commit d72a9ee

Please sign in to comment.